User prompt
Le nombre de coeurs gagnes doit correspondre à la performance réelle dans le microphone, par rapport a la chanson originale ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
Le jeu doit capter la ressemblance entre la performance du joueur ans le microphone et les vrais bonnes notes de la chanson, ça ne doit pas être aléatoire ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
User prompt
Le joueur ne doit gagne seulement les coeurs gagnes en chantant selon sa performance, et ces coeurs la cumule servent a monter en niveau, mais le nombre de coeurs gagnes doit correspondre à la performance !
User prompt
Fait en sorte que le jeu ne donne seulement les coeurs gagnes correspondant a la performance dans les coeurs requis pour monter en niveau, le bon nombre de coeurs (par exemple si le joueur na gagnes 0 coeurs, il ne gagne aucun coeur dans son niveau et ne monte pas, et le nombre de coeurs gagnes dans le niveau doit correspondre au nombre de coeurs gagnes en chantant)
User prompt
Fait en sorte que le joueur ne monte pas en niveau tout le temps, mais uniquement quand il a collé te assez de coeurs pour son niveau actuel
User prompt
Fait en sorte que le jeu ne donne seulement les coeurs gagnes correspondant a la performance dans les coeurs requis pour monter en niveau, le bon nombre de coeurs (par exemple si le joueur na gagnes 0 coeurs, il ne gagne aucun coeur dans son niveau et ne monte pas, et le nombre de coeurs gagnes dans le niveau doit correspondre au nombre de coeurs gagnes en chantant)
User prompt
Fait en sorte que le jeu ne donne seulement les coeurs gagnes correspondant a la performance dans les coeurs requis pour monter en niveau
User prompt
Mets les textes de niveau et de coeurs de L'ui en noir pour une meilleure visibilité
User prompt
Le jeu ne doit pas demander accès a la caméra, uniquement au microphone, le fond du jeu doit être mon asset personnalisé ↪💡 Consider importing and using the following plugins: @upit/facekit.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Sing Heart
Initial prompt
Crée mon jeu "Sing heart", Sing heart est littéralement un jeu dans lequel faut chanter le mieux possible pour collecter des points et monter en niveau (je suis un peu folle mdr), voici la description du jeu, du gameplay et des mécaniques : Le jeu se présente sous forme d'écran mignon aux designs de karaoker et de coeurs (je veux personnaliser le fond avec mon propre asset), Sur lequel un énorme récipient (une énorme boîte transparente) est affiché, et présent dans de grosses dimensions, Le jeu contient 220 niveaux, et le joueur monte en niveau en accumulant des coeurs (littéralement), il faut 5 coeurs au joueur pour monter au niveau 2, puis à chaque nouveau niveau il lui faut le double pour monter au niveau supérieur ! Le jeu se joue en chantant des chansons le mieux possible (no fake), Le but étant de gagner des cœurs pour monter en niveau et remplir l'énorme récipient (de touts petit cœur minuscules apparaissent a l'intérieur fur a mesure que le joueur monte en niveau et gagne des cœurs), Pour jouer et gagner des coeurs, Le joueur doit littéralement chanter des chansons, Pour chanter, le joueur appuie sur le bouton magenta a texte blanc dans L'ui "chanter", le jeu tire alors aléatoirement une chanson de sa base de données, que le joueur doit chanter dans son microphone le plus juste possible et le plus en rythme possible (Le jeu informe le joueur de la chanson tirée aléatoirement avant que le joueur appuie sur commencer et commence à chanter avec le microphone de son appareil), Mieux le joueur chante, plus il gagne des cœurs (si le joueur est 0 a 15 % juste et en rythme, il gagne 0 coeurs , si le joueur est 15 a 30% juste et en rythme il gagne 2 coeurs , si le joueur est 30 a 50% juste et en rythme il gagne 4 coeurs, si le joueur est de 50% a 70% juste et en rythme il gagne 6 coeurs ,et si il est plus de 70% juste et en rythme il gagne 10 coeurs!), Le joueur peut s'arrêter de chanter quand il le souhaite en appuyant sur un bouton "arrêter" La liste des chansons que le jeu demande de chanter est une liste de chansons connues, voici une liste non exhaustive : 1) Au clair de la lune 2) frère Jacque 3) une souris verte 4) chandelier 5) Blue de Eiffel 65 6) la maya danse de maya l'abeille 7) la musique est bonne de Jean jacques Goldman 8) Diamond de Rihanna 9) sur ma route de black M (avec les kids united), 10) tout le bonheur du monde (de sinsemilia repris par les kids united) 11) on écrit sur les murs (des kids united) 12) laissez-moi danser de Dalida
/**** * Plugins ****/ var facekit = LK.import("@upit/facekit.v1"); var storage = LK.import("@upit/storage.v1", { currentLevel: 1, totalHearts: 0, heartsNeeded: 5 }); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var SongButton = Container.expand(function (buttonType) { var self = Container.call(this); var buttonAsset = buttonType === 'chanter' ? 'chanterButton' : 'arreterButton'; var buttonGraphics = self.attachAsset(buttonAsset, { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2(buttonType === 'chanter' ? 'CHANTER' : 'ARRÊTER', { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.buttonType = buttonType; self.isPressed = false; self.down = function (x, y, obj) { self.isPressed = true; buttonGraphics.alpha = 0.8; }; self.up = function (x, y, obj) { if (self.isPressed) { if (self.buttonType === 'chanter') { startSinging(); } else { stopSinging(); } } self.isPressed = false; buttonGraphics.alpha = 1.0; }; return self; }); var TinyHeart = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('tinyHeart', { anchorX: 0.5, anchorY: 0.5 }); self.floatSpeed = Math.random() * 2 + 1; self.bobOffset = Math.random() * Math.PI * 2; self.update = function () { self.y -= self.floatSpeed; self.x += Math.sin(LK.ticks * 0.05 + self.bobOffset) * 0.5; if (self.y < -50) { self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xffeef8 }); /**** * Game Code ****/ // Add custom background var customBackground = game.addChild(LK.getAsset('customBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); // Song list var songList = ["Au clair de la lune", "Frère Jacques", "Chandelier", "Blue (Da Ba Dee)", "Diamonds", "On écrit sur les murs", "Tout le bonheur du monde", "L'Oiseau et l'Enfant", "La Marseillaise", "Happy Birthday"]; var currentSong = ""; var isSinging = false; var singingStartTime = 0; var singingDuration = 15000; // 15 seconds per song var volumeHistory = []; var pitchHistory = []; var tinyHearts = []; // UI Elements var heartContainerBorder = game.addChild(LK.getAsset('heartContainerBorder', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 800 })); var heartContainer = game.addChild(LK.getAsset('heartContainer', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 800, alpha: 0.3 })); var chanterButton = game.addChild(new SongButton('chanter')); chanterButton.x = 1024; chanterButton.y = 1500; var arreterButton = game.addChild(new SongButton('arreter')); arreterButton.x = 1024; arreterButton.y = 1600; arreterButton.alpha = 0.5; // Text displays var levelText = new Text2('Niveau: ' + storage.currentLevel, { size: 60, fill: 0x000000 }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); levelText.y = 100; var heartsText = new Text2('Cœurs: ' + storage.totalHearts + '/' + storage.heartsNeeded, { size: 50, fill: 0x000000 }); heartsText.anchor.set(0.5, 0); LK.gui.top.addChild(heartsText); heartsText.y = 180; var songText = new Text2(currentSong, { size: 45, fill: 0x333333 }); songText.anchor.set(0.5, 0.5); game.addChild(songText); songText.x = 1024; songText.y = 1200; var progressText = new Text2('', { size: 40, fill: 0x666666 }); progressText.anchor.set(0.5, 0.5); game.addChild(progressText); progressText.x = 1024; progressText.y = 1300; var debugText = new Text2('', { size: 30, fill: 0x444444 }); debugText.anchor.set(0.5, 0.5); game.addChild(debugText); debugText.x = 1024; debugText.y = 1370; function getRandomSong() { return songList[Math.floor(Math.random() * songList.length)]; } function startSinging() { if (isSinging) return; currentSong = getRandomSong(); songText.setText(currentSong); isSinging = true; singingStartTime = LK.ticks; volumeHistory = []; pitchHistory = []; chanterButton.alpha = 0.5; arreterButton.alpha = 1.0; progressText.setText('Chantez maintenant! 🎤'); } function stopSinging() { if (!isSinging) return; isSinging = false; var accuracy = calculateAccuracy(); var heartsEarned = getHeartsFromAccuracy(accuracy); // Always add earned hearts (even if 0) to maintain accurate tracking storage.totalHearts += heartsEarned; // Only create visual effects and play sound if hearts were actually earned if (heartsEarned > 0) { // Create visual hearts only for earned hearts for (var i = 0; i < heartsEarned; i++) { createTinyHeart(); } LK.getSound('heartCollect').play(); } // Check for level up only if player has enough total hearts while (storage.totalHearts >= storage.heartsNeeded) { // Calculate remaining hearts after leveling up var remainingHearts = storage.totalHearts - storage.heartsNeeded; levelUp(); // Set remaining hearts for the new level storage.totalHearts = remainingHearts; } updateUI(); chanterButton.alpha = 1.0; arreterButton.alpha = 0.5; progressText.setText('Score: ' + Math.round(accuracy) + '% - ' + heartsEarned + ' cœurs!'); // Clear song after 3 seconds LK.setTimeout(function () { if (!isSinging) { songText.setText(''); progressText.setText(''); } }, 3000); } function calculateAccuracy() { if (volumeHistory.length === 0) return 0; // Enhanced song patterns with timing and pitch sequences var songPatterns = { "Au clair de la lune": { pitches: [261.63, 261.63, 261.63, 293.66, 329.63, 293.66, 261.63, 329.63, 293.66, 293.66, 261.63], timing: [1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 3], // Relative note durations rhythm: [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1] // 1=sing, 0=pause }, "Frère Jacques": { pitches: [261.63, 293.66, 329.63, 261.63, 261.63, 293.66, 329.63, 261.63], timing: [1, 1, 1, 1, 1, 1, 1, 1], rhythm: [1, 1, 1, 1, 0, 1, 1, 1] }, "Chandelier": { pitches: [220, 246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94], timing: [1, 1, 2, 1, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1, 1] }, "Blue (Da Ba Dee)": { pitches: [246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94], timing: [1, 1, 1, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 0, 1, 1] }, "Diamonds": { pitches: [261.63, 329.63, 392, 440, 392, 329.63, 261.63], timing: [1, 1, 2, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1] }, "On écrit sur les murs": { pitches: [261.63, 293.66, 329.63, 349.23, 329.63, 293.66, 261.63], timing: [1, 1, 1, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1] }, "Tout le bonheur du monde": { pitches: [246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94], timing: [1, 1, 1, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1] }, "L'Oiseau et l'Enfant": { pitches: [261.63, 293.66, 329.63, 349.23, 329.63, 293.66, 261.63], timing: [1, 1, 2, 1, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1] }, "La Marseillaise": { pitches: [261.63, 293.66, 329.63, 392, 440, 392, 329.63, 293.66], timing: [1, 1, 1, 2, 2, 1, 1, 2], rhythm: [1, 1, 1, 1, 1, 1, 1, 1] }, "Happy Birthday": { pitches: [261.63, 261.63, 293.66, 261.63, 349.23, 329.63], timing: [1, 1, 1, 1, 2, 3], rhythm: [1, 0, 1, 1, 1, 1] } }; var pattern = songPatterns[currentSong] || songPatterns["Au clair de la lune"]; var targetPitches = pattern.pitches; var targetTiming = pattern.timing; var targetRhythm = pattern.rhythm; var volumeScore = 0; var pitchScore = 0; var rhythmScore = 0; var timingScore = 0; var validSamples = 0; // Calculate average volume and consistency var avgVolume = 0; var volumeVariance = 0; for (var i = 0; i < volumeHistory.length; i++) { avgVolume += volumeHistory[i]; } avgVolume /= volumeHistory.length; // Volume score: reward appropriate singing volume (0.2-0.8 range) if (avgVolume > 0.15) { volumeScore = Math.min(avgVolume / 0.5 * 25, 25); // Calculate volume consistency for (var i = 0; i < volumeHistory.length; i++) { volumeVariance += Math.abs(volumeHistory[i] - avgVolume); } volumeVariance /= volumeHistory.length; // Penalize excessive volume variation volumeScore = Math.max(0, volumeScore - volumeVariance * 30); } // Enhanced pitch analysis with sequential matching var segmentLength = Math.floor(pitchHistory.length / targetPitches.length); var pitchMatches = 0; var totalPitchChecks = 0; for (var segment = 0; segment < targetPitches.length; segment++) { var startIdx = segment * segmentLength; var endIdx = Math.min(startIdx + segmentLength, pitchHistory.length); var targetPitch = targetPitches[segment]; var segmentMatches = 0; var segmentChecks = 0; var segmentAvgPitch = 0; var segmentAvgVolume = 0; // Analyze this segment for (var i = startIdx; i < endIdx; i++) { if (volumeHistory[i] > 0.15) { // Only when actually singing segmentChecks++; segmentAvgPitch += pitchHistory[i]; segmentAvgVolume += volumeHistory[i]; if (pitchHistory[i] > 15) { var pitchDistance = Math.abs(pitchHistory[i] - targetPitch); if (pitchDistance <= 15) { // Very close segmentMatches += 3; } else if (pitchDistance <= 30) { // Close segmentMatches += 2; } else if (pitchDistance <= 50) { // Acceptable segmentMatches += 1; } } } } if (segmentChecks > 0) { segmentAvgPitch /= segmentChecks; segmentAvgVolume /= segmentChecks; totalPitchChecks += segmentChecks; pitchMatches += segmentMatches; // Bonus for maintaining consistent pitch in segment var pitchConsistency = 0; for (var i = startIdx; i < endIdx; i++) { if (volumeHistory[i] > 0.15 && pitchHistory[i] > 15) { var deviation = Math.abs(pitchHistory[i] - segmentAvgPitch); if (deviation <= 20) pitchConsistency++; } } if (segmentChecks > 0) { pitchMatches += pitchConsistency / segmentChecks * 2; } } } if (totalPitchChecks > 0) { pitchScore = Math.min(pitchMatches / totalPitchChecks * 40, 40); } // Enhanced rhythm analysis based on expected singing/silence patterns var rhythmMatches = 0; var rhythmSegmentLength = Math.floor(volumeHistory.length / targetRhythm.length); for (var r = 0; r < targetRhythm.length; r++) { var rStartIdx = r * rhythmSegmentLength; var rEndIdx = Math.min(rStartIdx + rhythmSegmentLength, volumeHistory.length); var shouldSing = targetRhythm[r] === 1; var actualSinging = 0; var segmentSamples = rEndIdx - rStartIdx; for (var i = rStartIdx; i < rEndIdx; i++) { if (volumeHistory[i] > 0.15) { actualSinging++; } } var singingRatio = actualSinging / segmentSamples; if (shouldSing && singingRatio > 0.6) { rhythmMatches += 2; // Good singing when expected } else if (!shouldSing && singingRatio < 0.3) { rhythmMatches += 1; // Good silence when expected } else if (shouldSing && singingRatio > 0.3) { rhythmMatches += 1; // Partial singing when expected } } rhythmScore = Math.min(rhythmMatches / targetRhythm.length * 20, 20); // Timing score: reward sustained notes and proper pacing var sustainedNotes = 0; var properPacing = 0; var lastVolume = 0; var noteTransitions = 0; for (var i = 1; i < volumeHistory.length; i++) { var currentVol = volumeHistory[i]; var previousVol = volumeHistory[i - 1]; // Detect note transitions if (previousVol <= 0.15 && currentVol > 0.15) { noteTransitions++; // Note start } else if (previousVol > 0.15 && currentVol <= 0.15) { noteTransitions++; // Note end } // Reward sustained singing if (currentVol > 0.2 && previousVol > 0.2) { sustainedNotes++; } } var expectedTransitions = targetPitches.length * 2; // Each note has start and end var transitionAccuracy = Math.min(noteTransitions / expectedTransitions, 1.0); var sustainRatio = sustainedNotes / volumeHistory.length; timingScore = transitionAccuracy * 7.5 + sustainRatio * 7.5; // Total accuracy with improved weighting var totalAccuracy = volumeScore + pitchScore + rhythmScore + timingScore; return Math.min(Math.max(totalAccuracy, 0), 100); } function getHeartsFromAccuracy(accuracy) { // More granular heart distribution based on real performance if (accuracy < 10) return 0; // Very poor performance if (accuracy < 25) return 1; // Poor performance if (accuracy < 40) return 2; // Below average if (accuracy < 55) return 3; // Average performance if (accuracy < 70) return 5; // Good performance if (accuracy < 85) return 7; // Very good performance if (accuracy < 95) return 9; // Excellent performance return 12; // Perfect performance } function createTinyHeart() { var heart = new TinyHeart(); heart.x = heartContainer.x + (Math.random() - 0.5) * 400; heart.y = heartContainer.y + (Math.random() - 0.5) * 400; tinyHearts.push(heart); game.addChild(heart); // Animate heart appearing heart.alpha = 0; heart.scaleX = 0; heart.scaleY = 0; tween(heart, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500 }); } function levelUp() { storage.currentLevel++; storage.heartsNeeded *= 2; // Don't reset hearts - they are handled in stopSinging function LK.getSound('levelUp').play(); // Flash effect LK.effects.flashScreen(0xffb6c1, 1000); // Check win condition if (storage.currentLevel > 220) { LK.showYouWin(); return; } } function updateUI() { levelText.setText('Niveau: ' + storage.currentLevel); heartsText.setText('Cœurs: ' + storage.totalHearts + '/' + storage.heartsNeeded); // Update heart container fill based on progress var fillProgress = Math.min(storage.totalHearts / storage.heartsNeeded, 1.0); heartContainer.alpha = 0.3 + fillProgress * 0.4; } // Initialize UI updateUI(); game.update = function () { // Handle singing session if (isSinging) { var currentTime = LK.ticks - singingStartTime; var timeLeft = Math.max(0, singingDuration - currentTime * 16.67); // Convert ticks to ms if (timeLeft <= 0) { stopSinging(); } else { progressText.setText('Temps restant: ' + Math.ceil(timeLeft / 1000) + 's'); // Show real-time feedback with enhanced analysis var currentVolume = facekit.volume; var currentPitch = facekit.pitch; var volumeStatus = currentVolume > 0.15 ? 'CHANT' : currentVolume > 0.05 ? 'FAIBLE' : 'SILENCE'; var pitchDisplay = currentPitch > 15 ? Math.round(currentPitch) + 'Hz' : 'Aucun son'; // Calculate current pitch accuracy if we have target var currentAccuracy = ''; if (currentPitch > 15 && currentVolume > 0.15) { var songPatterns = { "Au clair de la lune": [261.63, 293.66, 329.63, 349.23], "Frère Jacques": [261.63, 293.66, 329.63, 261.63], "Chandelier": [220, 246.94, 261.63, 293.66], "Blue (Da Ba Dee)": [246.94, 261.63, 293.66, 329.63], "Diamonds": [261.63, 329.63, 392, 440], "On écrit sur les murs": [261.63, 293.66, 329.63, 349.23], "Tout le bonheur du monde": [246.94, 261.63, 293.66, 329.63], "L'Oiseau et l'Enfant": [261.63, 293.66, 329.63, 349.23], "La Marseillaise": [261.63, 293.66, 329.63, 392], "Happy Birthday": [261.63, 261.63, 293.66, 261.63] }; var targets = songPatterns[currentSong] || [261.63, 293.66, 329.63, 349.23]; var minDist = Infinity; for (var t = 0; t < targets.length; t++) { var dist = Math.abs(currentPitch - targets[t]); if (dist < minDist) minDist = dist; } if (minDist <= 15) currentAccuracy = '✓ Parfait';else if (minDist <= 30) currentAccuracy = '~ Bien';else if (minDist <= 50) currentAccuracy = '- Moyen';else currentAccuracy = 'X Faux'; } debugText.setText('Vol: ' + Math.round(currentVolume * 100) + '% (' + volumeStatus + ') | Pitch: ' + pitchDisplay + ' ' + currentAccuracy); } // Record audio data every few ticks if (LK.ticks % 3 === 0) { volumeHistory.push(facekit.volume); pitchHistory.push(facekit.pitch); } } else { debugText.setText(''); } // Update tiny hearts for (var i = tinyHearts.length - 1; i >= 0; i--) { var heart = tinyHearts[i]; if (heart.destroyed) { tinyHearts.splice(i, 1); } } // Add floating hearts periodically if we have collected hearts if (storage.totalHearts > 0 && LK.ticks % 120 === 0) { if (Math.random() < 0.3) { createTinyHeart(); } } };
===================================================================
--- original.js
+++ change.js
@@ -199,102 +199,205 @@
}, 3000);
}
function calculateAccuracy() {
if (volumeHistory.length === 0) return 0;
- // Define target pitch ranges for different songs (in Hz)
- var songPitchTargets = {
- "Au clair de la lune": [261.63, 293.66, 329.63, 349.23],
- // C4, D4, E4, F4
- "Frère Jacques": [261.63, 293.66, 329.63, 261.63],
- // C4, D4, E4, C4
- "Chandelier": [220, 246.94, 261.63, 293.66],
- // A3, B3, C4, D4
- "Blue (Da Ba Dee)": [246.94, 261.63, 293.66, 329.63],
- // B3, C4, D4, E4
- "Diamonds": [261.63, 329.63, 392, 440],
- // C4, E4, G4, A4
- "On écrit sur les murs": [261.63, 293.66, 329.63, 349.23],
- // C4, D4, E4, F4
- "Tout le bonheur du monde": [246.94, 261.63, 293.66, 329.63],
- // B3, C4, D4, E4
- "L'Oiseau et l'Enfant": [261.63, 293.66, 329.63, 349.23],
- // C4, D4, E4, F4
- "La Marseillaise": [261.63, 293.66, 329.63, 392],
- // C4, D4, E4, G4
- "Happy Birthday": [261.63, 261.63, 293.66, 261.63] // C4, C4, D4, C4
+ // Enhanced song patterns with timing and pitch sequences
+ var songPatterns = {
+ "Au clair de la lune": {
+ pitches: [261.63, 261.63, 261.63, 293.66, 329.63, 293.66, 261.63, 329.63, 293.66, 293.66, 261.63],
+ timing: [1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 3],
+ // Relative note durations
+ rhythm: [1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1] // 1=sing, 0=pause
+ },
+ "Frère Jacques": {
+ pitches: [261.63, 293.66, 329.63, 261.63, 261.63, 293.66, 329.63, 261.63],
+ timing: [1, 1, 1, 1, 1, 1, 1, 1],
+ rhythm: [1, 1, 1, 1, 0, 1, 1, 1]
+ },
+ "Chandelier": {
+ pitches: [220, 246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94],
+ timing: [1, 1, 2, 1, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1, 1]
+ },
+ "Blue (Da Ba Dee)": {
+ pitches: [246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94],
+ timing: [1, 1, 1, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 0, 1, 1]
+ },
+ "Diamonds": {
+ pitches: [261.63, 329.63, 392, 440, 392, 329.63, 261.63],
+ timing: [1, 1, 2, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1]
+ },
+ "On écrit sur les murs": {
+ pitches: [261.63, 293.66, 329.63, 349.23, 329.63, 293.66, 261.63],
+ timing: [1, 1, 1, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1]
+ },
+ "Tout le bonheur du monde": {
+ pitches: [246.94, 261.63, 293.66, 329.63, 293.66, 261.63, 246.94],
+ timing: [1, 1, 1, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1]
+ },
+ "L'Oiseau et l'Enfant": {
+ pitches: [261.63, 293.66, 329.63, 349.23, 329.63, 293.66, 261.63],
+ timing: [1, 1, 2, 1, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1]
+ },
+ "La Marseillaise": {
+ pitches: [261.63, 293.66, 329.63, 392, 440, 392, 329.63, 293.66],
+ timing: [1, 1, 1, 2, 2, 1, 1, 2],
+ rhythm: [1, 1, 1, 1, 1, 1, 1, 1]
+ },
+ "Happy Birthday": {
+ pitches: [261.63, 261.63, 293.66, 261.63, 349.23, 329.63],
+ timing: [1, 1, 1, 1, 2, 3],
+ rhythm: [1, 0, 1, 1, 1, 1]
+ }
};
- var targetPitches = songPitchTargets[currentSong] || [261.63, 293.66, 329.63, 349.23];
- var targetVolume = 0.4; // Target singing volume
+ var pattern = songPatterns[currentSong] || songPatterns["Au clair de la lune"];
+ var targetPitches = pattern.pitches;
+ var targetTiming = pattern.timing;
+ var targetRhythm = pattern.rhythm;
var volumeScore = 0;
var pitchScore = 0;
var rhythmScore = 0;
+ var timingScore = 0;
var validSamples = 0;
- // Analyze volume consistency (should maintain steady singing volume)
- var volumeVariance = 0;
+ // Calculate average volume and consistency
var avgVolume = 0;
+ var volumeVariance = 0;
for (var i = 0; i < volumeHistory.length; i++) {
avgVolume += volumeHistory[i];
}
avgVolume /= volumeHistory.length;
- // Volume score: reward consistent singing above minimum threshold
- if (avgVolume > 0.1) {
- volumeScore = Math.min(avgVolume / targetVolume * 30, 30);
- // Penalize too much volume variation
+ // Volume score: reward appropriate singing volume (0.2-0.8 range)
+ if (avgVolume > 0.15) {
+ volumeScore = Math.min(avgVolume / 0.5 * 25, 25);
+ // Calculate volume consistency
for (var i = 0; i < volumeHistory.length; i++) {
volumeVariance += Math.abs(volumeHistory[i] - avgVolume);
}
volumeVariance /= volumeHistory.length;
- volumeScore = Math.max(0, volumeScore - volumeVariance * 50);
+ // Penalize excessive volume variation
+ volumeScore = Math.max(0, volumeScore - volumeVariance * 30);
}
- // Analyze pitch accuracy
- for (var i = 0; i < pitchHistory.length; i++) {
- if (pitchHistory[i] > 15 && volumeHistory[i] > 0.1) {
- // Only analyze when actually singing
- validSamples++;
- var currentPitch = pitchHistory[i];
- // Find closest target pitch
- var minDistance = Infinity;
- for (var j = 0; j < targetPitches.length; j++) {
- var distance = Math.abs(currentPitch - targetPitches[j]);
- if (distance < minDistance) {
- minDistance = distance;
+ // Enhanced pitch analysis with sequential matching
+ var segmentLength = Math.floor(pitchHistory.length / targetPitches.length);
+ var pitchMatches = 0;
+ var totalPitchChecks = 0;
+ for (var segment = 0; segment < targetPitches.length; segment++) {
+ var startIdx = segment * segmentLength;
+ var endIdx = Math.min(startIdx + segmentLength, pitchHistory.length);
+ var targetPitch = targetPitches[segment];
+ var segmentMatches = 0;
+ var segmentChecks = 0;
+ var segmentAvgPitch = 0;
+ var segmentAvgVolume = 0;
+ // Analyze this segment
+ for (var i = startIdx; i < endIdx; i++) {
+ if (volumeHistory[i] > 0.15) {
+ // Only when actually singing
+ segmentChecks++;
+ segmentAvgPitch += pitchHistory[i];
+ segmentAvgVolume += volumeHistory[i];
+ if (pitchHistory[i] > 15) {
+ var pitchDistance = Math.abs(pitchHistory[i] - targetPitch);
+ if (pitchDistance <= 15) {
+ // Very close
+ segmentMatches += 3;
+ } else if (pitchDistance <= 30) {
+ // Close
+ segmentMatches += 2;
+ } else if (pitchDistance <= 50) {
+ // Acceptable
+ segmentMatches += 1;
+ }
}
}
- // Score based on how close to target pitch (within 20Hz is good)
- if (minDistance <= 20) {
- pitchScore += 40;
- } else if (minDistance <= 40) {
- pitchScore += 20;
- } else if (minDistance <= 60) {
- pitchScore += 10;
+ }
+ if (segmentChecks > 0) {
+ segmentAvgPitch /= segmentChecks;
+ segmentAvgVolume /= segmentChecks;
+ totalPitchChecks += segmentChecks;
+ pitchMatches += segmentMatches;
+ // Bonus for maintaining consistent pitch in segment
+ var pitchConsistency = 0;
+ for (var i = startIdx; i < endIdx; i++) {
+ if (volumeHistory[i] > 0.15 && pitchHistory[i] > 15) {
+ var deviation = Math.abs(pitchHistory[i] - segmentAvgPitch);
+ if (deviation <= 20) pitchConsistency++;
+ }
}
+ if (segmentChecks > 0) {
+ pitchMatches += pitchConsistency / segmentChecks * 2;
+ }
}
}
- if (validSamples > 0) {
- pitchScore /= validSamples;
+ if (totalPitchChecks > 0) {
+ pitchScore = Math.min(pitchMatches / totalPitchChecks * 40, 40);
}
- // Rhythm score: reward consistent singing throughout the duration
- var singingPeriods = 0;
- var silencePeriods = 0;
- for (var i = 0; i < volumeHistory.length; i++) {
- if (volumeHistory[i] > 0.15) {
- singingPeriods++;
- } else {
- silencePeriods++;
+ // Enhanced rhythm analysis based on expected singing/silence patterns
+ var rhythmMatches = 0;
+ var rhythmSegmentLength = Math.floor(volumeHistory.length / targetRhythm.length);
+ for (var r = 0; r < targetRhythm.length; r++) {
+ var rStartIdx = r * rhythmSegmentLength;
+ var rEndIdx = Math.min(rStartIdx + rhythmSegmentLength, volumeHistory.length);
+ var shouldSing = targetRhythm[r] === 1;
+ var actualSinging = 0;
+ var segmentSamples = rEndIdx - rStartIdx;
+ for (var i = rStartIdx; i < rEndIdx; i++) {
+ if (volumeHistory[i] > 0.15) {
+ actualSinging++;
+ }
}
+ var singingRatio = actualSinging / segmentSamples;
+ if (shouldSing && singingRatio > 0.6) {
+ rhythmMatches += 2; // Good singing when expected
+ } else if (!shouldSing && singingRatio < 0.3) {
+ rhythmMatches += 1; // Good silence when expected
+ } else if (shouldSing && singingRatio > 0.3) {
+ rhythmMatches += 1; // Partial singing when expected
+ }
}
- var singingRatio = singingPeriods / volumeHistory.length;
- rhythmScore = Math.min(singingRatio * 30, 30);
- // Total accuracy: volume (30%) + pitch (40%) + rhythm (30%)
- var totalAccuracy = volumeScore + pitchScore + rhythmScore;
+ rhythmScore = Math.min(rhythmMatches / targetRhythm.length * 20, 20);
+ // Timing score: reward sustained notes and proper pacing
+ var sustainedNotes = 0;
+ var properPacing = 0;
+ var lastVolume = 0;
+ var noteTransitions = 0;
+ for (var i = 1; i < volumeHistory.length; i++) {
+ var currentVol = volumeHistory[i];
+ var previousVol = volumeHistory[i - 1];
+ // Detect note transitions
+ if (previousVol <= 0.15 && currentVol > 0.15) {
+ noteTransitions++; // Note start
+ } else if (previousVol > 0.15 && currentVol <= 0.15) {
+ noteTransitions++; // Note end
+ }
+ // Reward sustained singing
+ if (currentVol > 0.2 && previousVol > 0.2) {
+ sustainedNotes++;
+ }
+ }
+ var expectedTransitions = targetPitches.length * 2; // Each note has start and end
+ var transitionAccuracy = Math.min(noteTransitions / expectedTransitions, 1.0);
+ var sustainRatio = sustainedNotes / volumeHistory.length;
+ timingScore = transitionAccuracy * 7.5 + sustainRatio * 7.5;
+ // Total accuracy with improved weighting
+ var totalAccuracy = volumeScore + pitchScore + rhythmScore + timingScore;
return Math.min(Math.max(totalAccuracy, 0), 100);
}
function getHeartsFromAccuracy(accuracy) {
- if (accuracy < 15) return 0;
- if (accuracy < 30) return 2;
- if (accuracy < 50) return 4;
- if (accuracy < 70) return 6;
- return 10;
+ // More granular heart distribution based on real performance
+ if (accuracy < 10) return 0; // Very poor performance
+ if (accuracy < 25) return 1; // Poor performance
+ if (accuracy < 40) return 2; // Below average
+ if (accuracy < 55) return 3; // Average performance
+ if (accuracy < 70) return 5; // Good performance
+ if (accuracy < 85) return 7; // Very good performance
+ if (accuracy < 95) return 9; // Excellent performance
+ return 12; // Perfect performance
}
function createTinyHeart() {
var heart = new TinyHeart();
heart.x = heartContainer.x + (Math.random() - 0.5) * 400;
@@ -343,14 +446,37 @@
if (timeLeft <= 0) {
stopSinging();
} else {
progressText.setText('Temps restant: ' + Math.ceil(timeLeft / 1000) + 's');
- // Show real-time feedback
+ // Show real-time feedback with enhanced analysis
var currentVolume = facekit.volume;
var currentPitch = facekit.pitch;
- var volumeStatus = currentVolume > 0.1 ? 'ON' : 'OFF';
+ var volumeStatus = currentVolume > 0.15 ? 'CHANT' : currentVolume > 0.05 ? 'FAIBLE' : 'SILENCE';
var pitchDisplay = currentPitch > 15 ? Math.round(currentPitch) + 'Hz' : 'Aucun son';
- debugText.setText('Vol: ' + Math.round(currentVolume * 100) + '% (' + volumeStatus + ') | Pitch: ' + pitchDisplay);
+ // Calculate current pitch accuracy if we have target
+ var currentAccuracy = '';
+ if (currentPitch > 15 && currentVolume > 0.15) {
+ var songPatterns = {
+ "Au clair de la lune": [261.63, 293.66, 329.63, 349.23],
+ "Frère Jacques": [261.63, 293.66, 329.63, 261.63],
+ "Chandelier": [220, 246.94, 261.63, 293.66],
+ "Blue (Da Ba Dee)": [246.94, 261.63, 293.66, 329.63],
+ "Diamonds": [261.63, 329.63, 392, 440],
+ "On écrit sur les murs": [261.63, 293.66, 329.63, 349.23],
+ "Tout le bonheur du monde": [246.94, 261.63, 293.66, 329.63],
+ "L'Oiseau et l'Enfant": [261.63, 293.66, 329.63, 349.23],
+ "La Marseillaise": [261.63, 293.66, 329.63, 392],
+ "Happy Birthday": [261.63, 261.63, 293.66, 261.63]
+ };
+ var targets = songPatterns[currentSong] || [261.63, 293.66, 329.63, 349.23];
+ var minDist = Infinity;
+ for (var t = 0; t < targets.length; t++) {
+ var dist = Math.abs(currentPitch - targets[t]);
+ if (dist < minDist) minDist = dist;
+ }
+ if (minDist <= 15) currentAccuracy = '✓ Parfait';else if (minDist <= 30) currentAccuracy = '~ Bien';else if (minDist <= 50) currentAccuracy = '- Moyen';else currentAccuracy = 'X Faux';
+ }
+ debugText.setText('Vol: ' + Math.round(currentVolume * 100) + '% (' + volumeStatus + ') | Pitch: ' + pitchDisplay + ' ' + currentAccuracy);
}
// Record audio data every few ticks
if (LK.ticks % 3 === 0) {
volumeHistory.push(facekit.volume);