Code edit (1 edits merged)
Please save this source code
User prompt
Sing Along Studio
Initial prompt
Toca singing 2 (2017). Hold the mic 🎤 up nealty and now you try. Sing let it go and sing along with Elsa 🩵. Tap on the French button, or the Castilian Spanish button to make Elsa 🩵 sing in 2 different ways.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var LanguageButton = Container.expand(function (language) {
var self = Container.call(this);
var buttonBg = self.attachAsset('languageButton', {
anchorX: 0.5,
anchorY: 0.5
});
var langText = new Text2(language, {
size: 30,
fill: 0xFFFFFF
});
langText.anchor.set(0.5, 0.5);
self.addChild(langText);
self.language = language;
self.down = function (x, y, obj) {
currentLanguage = self.language;
updateLanguageButtons();
tween(buttonBg, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var SongButton = Container.expand(function (songData) {
var self = Container.call(this);
var buttonBg = self.attachAsset('songButton', {
anchorX: 0.5,
anchorY: 0.5
});
var titleText = new Text2(songData.title, {
size: 40,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
self.addChild(titleText);
self.songData = songData;
self.down = function (x, y, obj) {
selectedSong = self.songData;
gameState = 'playing';
initializeSong();
tween(buttonBg, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var TargetNote = Container.expand(function (pitch, time) {
var self = Container.call(this);
var note = self.attachAsset('targetNote', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetPitch = pitch;
self.targetTime = time;
return self;
});
var VoiceParticle = Container.expand(function () {
var self = Container.call(this);
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.life = 1.0;
self.velocityX = (Math.random() - 0.5) * 200;
self.velocityY = (Math.random() - 0.5) * 200;
self.update = function () {
self.x += self.velocityX * 0.016;
self.y += self.velocityY * 0.016;
self.life -= 0.02;
particle.alpha = self.life;
particle.scaleX = self.life;
particle.scaleY = self.life;
if (self.life <= 0) {
self.destroy();
var index = particles.indexOf(self);
if (index > -1) {
particles.splice(index, 1);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A1A2E
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', 'results'
var currentLanguage = 'English';
var selectedSong = null;
var songProgress = 0;
var songStartTime = 0;
var score = 0;
var maxScore = 100;
// Voice tracking
var lastVolume = 0;
var lastPitch = 0;
var pitchHistory = [];
var volumeHistory = [];
// Visual elements
var particles = [];
var targetNotes = [];
var languageButtons = [];
var songButtons = [];
// Song data
var songs = [{
id: 'twinkle',
title: 'Twinkle Twinkle',
duration: 12000,
lyrics: {
'English': [{
text: 'Twinkle, twinkle, little star',
time: 0,
duration: 3000
}, {
text: 'How I wonder what you are',
time: 3000,
duration: 3000
}, {
text: 'Up above the world so high',
time: 6000,
duration: 3000
}, {
text: 'Like a diamond in the sky',
time: 9000,
duration: 3000
}],
'Spanish': [{
text: 'Estrellita, dónde estás',
time: 0,
duration: 3000
}, {
text: 'Me pregunto qué serás',
time: 3000,
duration: 3000
}, {
text: 'En el cielo, tan arriba',
time: 6000,
duration: 3000
}, {
text: 'Como un diamante que brilla',
time: 9000,
duration: 3000
}],
'French': [{
text: 'Brille, brille, petite étoile',
time: 0,
duration: 3000
}, {
text: 'Je me demande ce que tu es',
time: 3000,
duration: 3000
}, {
text: 'Là-haut dans le ciel si haut',
time: 6000,
duration: 3000
}, {
text: 'Comme un diamant là-haut',
time: 9000,
duration: 3000
}]
},
targetPitches: [{
pitch: 200,
time: 0
}, {
pitch: 200,
time: 500
}, {
pitch: 300,
time: 1000
}, {
pitch: 300,
time: 1500
}, {
pitch: 350,
time: 2000
}, {
pitch: 350,
time: 2500
}, {
pitch: 300,
time: 3000
}, {
pitch: 250,
time: 3500
}, {
pitch: 250,
time: 4000
}, {
pitch: 200,
time: 4500
}, {
pitch: 200,
time: 5000
}, {
pitch: 150,
time: 5500
}]
}, {
id: 'happy',
title: 'Happy Birthday',
duration: 15000,
lyrics: {
'English': [{
text: 'Happy birthday to you',
time: 0,
duration: 3000
}, {
text: 'Happy birthday to you',
time: 3000,
duration: 3000
}, {
text: 'Happy birthday dear friend',
time: 6000,
duration: 4500
}, {
text: 'Happy birthday to you',
time: 10500,
duration: 4500
}],
'Spanish': [{
text: 'Cumpleaños feliz',
time: 0,
duration: 3000
}, {
text: 'Cumpleaños feliz',
time: 3000,
duration: 3000
}, {
text: 'Te deseamos amigo',
time: 6000,
duration: 4500
}, {
text: 'Cumpleaños feliz',
time: 10500,
duration: 4500
}],
'French': [{
text: 'Joyeux anniversaire',
time: 0,
duration: 3000
}, {
text: 'Joyeux anniversaire',
time: 3000,
duration: 3000
}, {
text: 'Joyeux anniversaire mon ami',
time: 6000,
duration: 4500
}, {
text: 'Joyeux anniversaire',
time: 10500,
duration: 4500
}]
},
targetPitches: [{
pitch: 180,
time: 0
}, {
pitch: 180,
time: 500
}, {
pitch: 220,
time: 1000
}, {
pitch: 180,
time: 1500
}, {
pitch: 260,
time: 2000
}, {
pitch: 240,
time: 2500
}]
}];
// UI Elements
var titleText = new Text2('Sing Along Studio', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 200;
game.addChild(titleText);
var micIcon = game.attachAsset('microphoneIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 400
});
var volumeBar = game.attachAsset('noteBar', {
anchorX: 0,
anchorY: 0.5,
x: 624,
y: 500
});
var pitchIndicator = game.attachAsset('playerNote', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
var lyricBg = game.attachAsset('lyricBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2400
});
lyricBg.alpha = 0.8;
var currentLyricText = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
currentLyricText.anchor.set(0.5, 0.5);
currentLyricText.x = 1024;
currentLyricText.y = 2400;
game.addChild(currentLyricText);
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Language buttons
var languages = ['English', 'Spanish', 'French'];
for (var i = 0; i < languages.length; i++) {
var langBtn = new LanguageButton(languages[i]);
langBtn.x = 400 + i * 250;
langBtn.y = 1000;
game.addChild(langBtn);
languageButtons.push(langBtn);
}
// Song selection buttons
for (var i = 0; i < songs.length; i++) {
var songBtn = new SongButton(songs[i]);
songBtn.x = 1024;
songBtn.y = 1300 + i * 150;
game.addChild(songBtn);
songButtons.push(songBtn);
}
// Back button
var backButton = game.attachAsset('languageButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 150
});
var backText = new Text2('Back', {
size: 30,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backText.x = 200;
backText.y = 150;
game.addChild(backText);
function updateLanguageButtons() {
for (var i = 0; i < languageButtons.length; i++) {
var btn = languageButtons[i];
if (btn.language === currentLanguage) {
tween(btn, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200
});
} else {
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
}
}
function initializeSong() {
if (!selectedSong) return;
songStartTime = LK.ticks;
songProgress = 0;
score = 0;
// Clear existing target notes
for (var i = 0; i < targetNotes.length; i++) {
targetNotes[i].destroy();
}
targetNotes = [];
// Hide menu elements
titleText.visible = false;
for (var i = 0; i < languageButtons.length; i++) {
languageButtons[i].visible = false;
}
for (var i = 0; i < songButtons.length; i++) {
songButtons[i].visible = false;
}
// Show game elements
lyricBg.visible = true;
currentLyricText.visible = true;
}
function endSong() {
gameState = 'results';
// Calculate final score
var finalScore = Math.floor(score / maxScore * 100);
LK.setScore(finalScore);
// Show results
currentLyricText.setText('Great job! Score: ' + finalScore + '%');
// Auto return to menu after 3 seconds
LK.setTimeout(function () {
returnToMenu();
}, 3000);
}
function returnToMenu() {
gameState = 'menu';
selectedSong = null;
// Show menu elements
titleText.visible = true;
for (var i = 0; i < languageButtons.length; i++) {
languageButtons[i].visible = true;
}
for (var i = 0; i < songButtons.length; i++) {
songButtons[i].visible = true;
}
// Hide game elements
lyricBg.visible = false;
currentLyricText.visible = false;
currentLyricText.setText('');
}
function createVoiceParticles() {
if (facekit.volume > 0.3) {
for (var i = 0; i < 3; i++) {
var particle = new VoiceParticle();
particle.x = micIcon.x + (Math.random() - 0.5) * 100;
particle.y = micIcon.y + (Math.random() - 0.5) * 100;
game.addChild(particle);
particles.push(particle);
}
}
}
function updatePitchVisualization() {
var targetY = 600;
if (facekit.pitch > 0) {
// Map pitch to vertical position (lower pitch = lower on screen)
targetY = 800 - facekit.pitch / 400 * 300;
targetY = Math.max(400, Math.min(800, targetY));
}
tween(pitchIndicator, {
y: targetY
}, {
duration: 100
});
// Update pitch indicator size based on volume
var scale = 0.5 + facekit.volume * 1.5;
tween(pitchIndicator, {
scaleX: scale,
scaleY: scale
}, {
duration: 100
});
}
function updateVolumeBar() {
var barWidth = facekit.volume * 800;
tween(volumeBar, {
width: barWidth
}, {
duration: 100
});
}
function calculatePitchScore() {
if (!selectedSong || gameState !== 'playing') return;
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
// Find closest target pitch
var closestPitch = null;
var minTimeDiff = Infinity;
for (var i = 0; i < selectedSong.targetPitches.length; i++) {
var targetPitch = selectedSong.targetPitches[i];
var timeDiff = Math.abs(targetPitch.time - timeInMs);
if (timeDiff < minTimeDiff && timeDiff < 1000) {
minTimeDiff = timeDiff;
closestPitch = targetPitch;
}
}
if (closestPitch && facekit.pitch > 0) {
var pitchDiff = Math.abs(facekit.pitch - closestPitch.pitch);
var pitchScore = Math.max(0, 1 - pitchDiff / 100);
var volumeBonus = facekit.volume > 0.3 ? 1 : 0.5;
score += pitchScore * volumeBonus * 0.1;
score = Math.min(score, maxScore);
}
}
function updateCurrentLyric() {
if (!selectedSong || gameState !== 'playing') return;
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
var lyrics = selectedSong.lyrics[currentLanguage];
var currentLyric = '';
for (var i = 0; i < lyrics.length; i++) {
var lyric = lyrics[i];
if (timeInMs >= lyric.time && timeInMs < lyric.time + lyric.duration) {
currentLyric = lyric.text;
break;
}
}
if (currentLyricText.text !== currentLyric) {
currentLyricText.setText(currentLyric);
if (currentLyric !== '') {
tween(currentLyricText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(currentLyricText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
}
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'results' || gameState === 'playing') {
// Check if back button clicked
if (x < 300 && y < 250) {
returnToMenu();
}
}
};
// Initialize menu state
updateLanguageButtons();
lyricBg.visible = false;
currentLyricText.visible = false;
game.update = function () {
// Update volume history
volumeHistory.push(facekit.volume);
if (volumeHistory.length > 30) {
volumeHistory.shift();
}
// Update pitch history
pitchHistory.push(facekit.pitch);
if (pitchHistory.length > 30) {
pitchHistory.shift();
}
// Update visual feedback
updateVolumeBar();
updatePitchVisualization();
// Create particles based on voice input
if (LK.ticks % 10 === 0) {
createVoiceParticles();
}
// Update score display
scoreText.setText('Score: ' + Math.floor(score));
if (gameState === 'playing' && selectedSong) {
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
// Update lyrics
updateCurrentLyric();
// Calculate pitch accuracy score
calculatePitchScore();
// Check if song is finished
if (timeInMs >= selectedSong.duration) {
endSong();
}
// Add visual effects for good singing
if (facekit.volume > 0.6 && facekit.pitch > 100) {
LK.effects.flashObject(micIcon, 0x4CAF50, 200);
}
}
// Animate microphone icon based on volume
if (facekit.volume > 0.1) {
var pulse = 1 + facekit.volume * 0.3;
micIcon.scaleX = pulse;
micIcon.scaleY = pulse;
} else {
tween(micIcon, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
// Color microphone based on pitch detection
if (facekit.pitch > 50) {
tween(micIcon, {
tint: 0x4CAF50
}, {
duration: 200
});
} else {
tween(micIcon, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var LanguageButton = Container.expand(function (language) {
var self = Container.call(this);
var buttonBg = self.attachAsset('languageButton', {
anchorX: 0.5,
anchorY: 0.5
});
var langText = new Text2(language, {
size: 30,
fill: 0xFFFFFF
});
langText.anchor.set(0.5, 0.5);
self.addChild(langText);
self.language = language;
self.down = function (x, y, obj) {
currentLanguage = self.language;
updateLanguageButtons();
tween(buttonBg, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var SongButton = Container.expand(function (songData) {
var self = Container.call(this);
var buttonBg = self.attachAsset('songButton', {
anchorX: 0.5,
anchorY: 0.5
});
var titleText = new Text2(songData.title, {
size: 40,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
self.addChild(titleText);
self.songData = songData;
self.down = function (x, y, obj) {
selectedSong = self.songData;
gameState = 'playing';
initializeSong();
tween(buttonBg, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(buttonBg, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
};
return self;
});
var TargetNote = Container.expand(function (pitch, time) {
var self = Container.call(this);
var note = self.attachAsset('targetNote', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetPitch = pitch;
self.targetTime = time;
return self;
});
var VoiceParticle = Container.expand(function () {
var self = Container.call(this);
var particle = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.life = 1.0;
self.velocityX = (Math.random() - 0.5) * 200;
self.velocityY = (Math.random() - 0.5) * 200;
self.update = function () {
self.x += self.velocityX * 0.016;
self.y += self.velocityY * 0.016;
self.life -= 0.02;
particle.alpha = self.life;
particle.scaleX = self.life;
particle.scaleY = self.life;
if (self.life <= 0) {
self.destroy();
var index = particles.indexOf(self);
if (index > -1) {
particles.splice(index, 1);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A1A2E
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu', 'playing', 'results'
var currentLanguage = 'English';
var selectedSong = null;
var songProgress = 0;
var songStartTime = 0;
var score = 0;
var maxScore = 100;
// Voice tracking
var lastVolume = 0;
var lastPitch = 0;
var pitchHistory = [];
var volumeHistory = [];
// Visual elements
var particles = [];
var targetNotes = [];
var languageButtons = [];
var songButtons = [];
// Song data
var songs = [{
id: 'twinkle',
title: 'Twinkle Twinkle',
duration: 12000,
lyrics: {
'English': [{
text: 'Twinkle, twinkle, little star',
time: 0,
duration: 3000
}, {
text: 'How I wonder what you are',
time: 3000,
duration: 3000
}, {
text: 'Up above the world so high',
time: 6000,
duration: 3000
}, {
text: 'Like a diamond in the sky',
time: 9000,
duration: 3000
}],
'Spanish': [{
text: 'Estrellita, dónde estás',
time: 0,
duration: 3000
}, {
text: 'Me pregunto qué serás',
time: 3000,
duration: 3000
}, {
text: 'En el cielo, tan arriba',
time: 6000,
duration: 3000
}, {
text: 'Como un diamante que brilla',
time: 9000,
duration: 3000
}],
'French': [{
text: 'Brille, brille, petite étoile',
time: 0,
duration: 3000
}, {
text: 'Je me demande ce que tu es',
time: 3000,
duration: 3000
}, {
text: 'Là-haut dans le ciel si haut',
time: 6000,
duration: 3000
}, {
text: 'Comme un diamant là-haut',
time: 9000,
duration: 3000
}]
},
targetPitches: [{
pitch: 200,
time: 0
}, {
pitch: 200,
time: 500
}, {
pitch: 300,
time: 1000
}, {
pitch: 300,
time: 1500
}, {
pitch: 350,
time: 2000
}, {
pitch: 350,
time: 2500
}, {
pitch: 300,
time: 3000
}, {
pitch: 250,
time: 3500
}, {
pitch: 250,
time: 4000
}, {
pitch: 200,
time: 4500
}, {
pitch: 200,
time: 5000
}, {
pitch: 150,
time: 5500
}]
}, {
id: 'happy',
title: 'Happy Birthday',
duration: 15000,
lyrics: {
'English': [{
text: 'Happy birthday to you',
time: 0,
duration: 3000
}, {
text: 'Happy birthday to you',
time: 3000,
duration: 3000
}, {
text: 'Happy birthday dear friend',
time: 6000,
duration: 4500
}, {
text: 'Happy birthday to you',
time: 10500,
duration: 4500
}],
'Spanish': [{
text: 'Cumpleaños feliz',
time: 0,
duration: 3000
}, {
text: 'Cumpleaños feliz',
time: 3000,
duration: 3000
}, {
text: 'Te deseamos amigo',
time: 6000,
duration: 4500
}, {
text: 'Cumpleaños feliz',
time: 10500,
duration: 4500
}],
'French': [{
text: 'Joyeux anniversaire',
time: 0,
duration: 3000
}, {
text: 'Joyeux anniversaire',
time: 3000,
duration: 3000
}, {
text: 'Joyeux anniversaire mon ami',
time: 6000,
duration: 4500
}, {
text: 'Joyeux anniversaire',
time: 10500,
duration: 4500
}]
},
targetPitches: [{
pitch: 180,
time: 0
}, {
pitch: 180,
time: 500
}, {
pitch: 220,
time: 1000
}, {
pitch: 180,
time: 1500
}, {
pitch: 260,
time: 2000
}, {
pitch: 240,
time: 2500
}]
}];
// UI Elements
var titleText = new Text2('Sing Along Studio', {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 200;
game.addChild(titleText);
var micIcon = game.attachAsset('microphoneIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 400
});
var volumeBar = game.attachAsset('noteBar', {
anchorX: 0,
anchorY: 0.5,
x: 624,
y: 500
});
var pitchIndicator = game.attachAsset('playerNote', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
var lyricBg = game.attachAsset('lyricBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2400
});
lyricBg.alpha = 0.8;
var currentLyricText = new Text2('', {
size: 50,
fill: 0xFFFFFF
});
currentLyricText.anchor.set(0.5, 0.5);
currentLyricText.x = 1024;
currentLyricText.y = 2400;
game.addChild(currentLyricText);
var scoreText = new Text2('Score: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Language buttons
var languages = ['English', 'Spanish', 'French'];
for (var i = 0; i < languages.length; i++) {
var langBtn = new LanguageButton(languages[i]);
langBtn.x = 400 + i * 250;
langBtn.y = 1000;
game.addChild(langBtn);
languageButtons.push(langBtn);
}
// Song selection buttons
for (var i = 0; i < songs.length; i++) {
var songBtn = new SongButton(songs[i]);
songBtn.x = 1024;
songBtn.y = 1300 + i * 150;
game.addChild(songBtn);
songButtons.push(songBtn);
}
// Back button
var backButton = game.attachAsset('languageButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 150
});
var backText = new Text2('Back', {
size: 30,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backText.x = 200;
backText.y = 150;
game.addChild(backText);
function updateLanguageButtons() {
for (var i = 0; i < languageButtons.length; i++) {
var btn = languageButtons[i];
if (btn.language === currentLanguage) {
tween(btn, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200
});
} else {
tween(btn, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
}
}
function initializeSong() {
if (!selectedSong) return;
songStartTime = LK.ticks;
songProgress = 0;
score = 0;
// Clear existing target notes
for (var i = 0; i < targetNotes.length; i++) {
targetNotes[i].destroy();
}
targetNotes = [];
// Hide menu elements
titleText.visible = false;
for (var i = 0; i < languageButtons.length; i++) {
languageButtons[i].visible = false;
}
for (var i = 0; i < songButtons.length; i++) {
songButtons[i].visible = false;
}
// Show game elements
lyricBg.visible = true;
currentLyricText.visible = true;
}
function endSong() {
gameState = 'results';
// Calculate final score
var finalScore = Math.floor(score / maxScore * 100);
LK.setScore(finalScore);
// Show results
currentLyricText.setText('Great job! Score: ' + finalScore + '%');
// Auto return to menu after 3 seconds
LK.setTimeout(function () {
returnToMenu();
}, 3000);
}
function returnToMenu() {
gameState = 'menu';
selectedSong = null;
// Show menu elements
titleText.visible = true;
for (var i = 0; i < languageButtons.length; i++) {
languageButtons[i].visible = true;
}
for (var i = 0; i < songButtons.length; i++) {
songButtons[i].visible = true;
}
// Hide game elements
lyricBg.visible = false;
currentLyricText.visible = false;
currentLyricText.setText('');
}
function createVoiceParticles() {
if (facekit.volume > 0.3) {
for (var i = 0; i < 3; i++) {
var particle = new VoiceParticle();
particle.x = micIcon.x + (Math.random() - 0.5) * 100;
particle.y = micIcon.y + (Math.random() - 0.5) * 100;
game.addChild(particle);
particles.push(particle);
}
}
}
function updatePitchVisualization() {
var targetY = 600;
if (facekit.pitch > 0) {
// Map pitch to vertical position (lower pitch = lower on screen)
targetY = 800 - facekit.pitch / 400 * 300;
targetY = Math.max(400, Math.min(800, targetY));
}
tween(pitchIndicator, {
y: targetY
}, {
duration: 100
});
// Update pitch indicator size based on volume
var scale = 0.5 + facekit.volume * 1.5;
tween(pitchIndicator, {
scaleX: scale,
scaleY: scale
}, {
duration: 100
});
}
function updateVolumeBar() {
var barWidth = facekit.volume * 800;
tween(volumeBar, {
width: barWidth
}, {
duration: 100
});
}
function calculatePitchScore() {
if (!selectedSong || gameState !== 'playing') return;
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
// Find closest target pitch
var closestPitch = null;
var minTimeDiff = Infinity;
for (var i = 0; i < selectedSong.targetPitches.length; i++) {
var targetPitch = selectedSong.targetPitches[i];
var timeDiff = Math.abs(targetPitch.time - timeInMs);
if (timeDiff < minTimeDiff && timeDiff < 1000) {
minTimeDiff = timeDiff;
closestPitch = targetPitch;
}
}
if (closestPitch && facekit.pitch > 0) {
var pitchDiff = Math.abs(facekit.pitch - closestPitch.pitch);
var pitchScore = Math.max(0, 1 - pitchDiff / 100);
var volumeBonus = facekit.volume > 0.3 ? 1 : 0.5;
score += pitchScore * volumeBonus * 0.1;
score = Math.min(score, maxScore);
}
}
function updateCurrentLyric() {
if (!selectedSong || gameState !== 'playing') return;
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
var lyrics = selectedSong.lyrics[currentLanguage];
var currentLyric = '';
for (var i = 0; i < lyrics.length; i++) {
var lyric = lyrics[i];
if (timeInMs >= lyric.time && timeInMs < lyric.time + lyric.duration) {
currentLyric = lyric.text;
break;
}
}
if (currentLyricText.text !== currentLyric) {
currentLyricText.setText(currentLyric);
if (currentLyric !== '') {
tween(currentLyricText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(currentLyricText, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
}
}
}
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'results' || gameState === 'playing') {
// Check if back button clicked
if (x < 300 && y < 250) {
returnToMenu();
}
}
};
// Initialize menu state
updateLanguageButtons();
lyricBg.visible = false;
currentLyricText.visible = false;
game.update = function () {
// Update volume history
volumeHistory.push(facekit.volume);
if (volumeHistory.length > 30) {
volumeHistory.shift();
}
// Update pitch history
pitchHistory.push(facekit.pitch);
if (pitchHistory.length > 30) {
pitchHistory.shift();
}
// Update visual feedback
updateVolumeBar();
updatePitchVisualization();
// Create particles based on voice input
if (LK.ticks % 10 === 0) {
createVoiceParticles();
}
// Update score display
scoreText.setText('Score: ' + Math.floor(score));
if (gameState === 'playing' && selectedSong) {
var currentTime = LK.ticks - songStartTime;
var timeInMs = currentTime / 60 * 1000;
// Update lyrics
updateCurrentLyric();
// Calculate pitch accuracy score
calculatePitchScore();
// Check if song is finished
if (timeInMs >= selectedSong.duration) {
endSong();
}
// Add visual effects for good singing
if (facekit.volume > 0.6 && facekit.pitch > 100) {
LK.effects.flashObject(micIcon, 0x4CAF50, 200);
}
}
// Animate microphone icon based on volume
if (facekit.volume > 0.1) {
var pulse = 1 + facekit.volume * 0.3;
micIcon.scaleX = pulse;
micIcon.scaleY = pulse;
} else {
tween(micIcon, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
// Color microphone based on pitch detection
if (facekit.pitch > 50) {
tween(micIcon, {
tint: 0x4CAF50
}, {
duration: 200
});
} else {
tween(micIcon, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
};