/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ var ExpressionDetector = Container.expand(function () { var self = Container.call(this); // Expression state tracking self.lastSmiling = false; self.lastEyebrowsFrowning = false; self.lastBlinking = false; self.blinkCounter = 0; self.lastBlinkTime = 0; // Head movement tracking self.lastHeadX = 0; self.lastHeadY = 0; self.lastEyesOpen = true; self.lastMouthPosition = 0; self.lastNosePosition = 0; // Expression thresholds self.smileThreshold = 0.3; self.frownThreshold = 0.2; self.blinkThreshold = 50; // milliseconds self.headMovementThreshold = 30; self.eyeCloseThreshold = 20; self.mouthMovementThreshold = 15; self.noseMovementThreshold = 10; self.detectSmile = function () { if (!facekit.upperLip || !facekit.lowerLip) return false; // Simple smile detection based on mouth corner positions var mouthCurve = (facekit.upperLip.y - facekit.lowerLip.y) / 20; return mouthCurve > self.smileThreshold; }; self.detectFrown = function () { if (!facekit.leftEye || !facekit.rightEye) return false; // Detect frown based on eyebrow position relative to eyes var eyebrowPosition = (facekit.leftEye.y + facekit.rightEye.y) / 2 - 30; return eyebrowPosition < self.frownThreshold; }; self.detectBlink = function () { // Simplified blink detection (in real implementation would track eye closure) var currentTime = Date.now(); if (currentTime - self.lastBlinkTime > 2000) { // Auto-trigger every 2 seconds for demo self.lastBlinkTime = currentTime; return true; } return false; }; self.detectHeadMovement = function () { if (!facekit.mouthCenter) return { left: false, right: false, up: false, down: false }; var currentHeadX = facekit.mouthCenter.x; var currentHeadY = facekit.mouthCenter.y; var deltaX = currentHeadX - self.lastHeadX; var deltaY = currentHeadY - self.lastHeadY; return { left: deltaX < -self.headMovementThreshold, right: deltaX > self.headMovementThreshold, up: deltaY < -self.headMovementThreshold, down: deltaY > self.headMovementThreshold }; }; self.detectEyeMovement = function () { if (!facekit.leftEye || !facekit.rightEye) return false; // Detect eye closing based on eye position change var eyeHeight = Math.abs(facekit.leftEye.y - facekit.rightEye.y); var eyesCurrentlyOpen = eyeHeight > self.eyeCloseThreshold; return eyesCurrentlyOpen !== self.lastEyesOpen; }; self.detectMouthMovement = function () { if (!facekit.mouthCenter) return false; var currentMouthY = facekit.mouthCenter.y; var deltaY = Math.abs(currentMouthY - self.lastMouthPosition); return deltaY > self.mouthMovementThreshold; }; self.detectNoseMovement = function () { if (!facekit.noseTip) return false; var currentNoseY = facekit.noseTip.y; var deltaY = Math.abs(currentNoseY - self.lastNosePosition); return deltaY > self.noseMovementThreshold; }; self.onSmile = function (sequencer) { // Add harmonious melody notes when smiling (reduced randomness) var lastNoteIndex = sequencer.melodySequence.length > 0 ? sequencer.melodySequence[sequencer.melodySequence.length - 1].noteIndex || 0 : 0; var harmonicNoteIndex = (lastNoteIndex + 2) % melodyNotes.length; // Always use harmonic intervals var newMelodyNote = melodyNotes[harmonicNoteIndex]; LK.getSound(newMelodyNote).play(); LK.getSound('smile-trigger').play(); // Visual feedback LK.effects.flashScreen(0x00ff88, 300); // Add to score LK.setScore(LK.getScore() + 25); }; self.onFrown = function (sequencer) { // Increase BPM when frowning sequencer.bpm = Math.min(180, sequencer.bpm + 5); sequencer.beatInterval = 60000 / sequencer.bpm; LK.getSound('frown-trigger').play(); // Visual feedback - red flash LK.effects.flashScreen(0xff4444, 200); }; self.onBlink = function (sequencer) { // Trigger random effects on blink var randomEffect = effectSounds[Math.floor(Math.random() * effectSounds.length)]; LK.getSound(randomEffect).play(); LK.getSound('blink-effect').play(); // Create sparkle effect for (var i = 0; i < 5; i++) { var sparkle = game.addChild(LK.getAsset('effectDot', { anchorX: 0.5, anchorY: 0.5, alpha: 1.0, tint: Math.random() * 0xffffff })); sparkle.x = facekit.mouthCenter.x + (Math.random() - 0.5) * 200; sparkle.y = facekit.mouthCenter.y + (Math.random() - 0.5) * 200; tween(sparkle, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 800, onFinish: function onFinish() { sparkle.destroy(); } }); } }; self.onHeadMovement = function (direction, sequencer) { // Different effects for different head movements if (direction.left) { // Pan music left and play bass note sequencer.panValue = -0.8; LK.getSound('bass-e2').play(); LK.effects.flashScreen(0x3498db, 300); } else if (direction.right) { // Pan music right and play different bass note sequencer.panValue = 0.8; LK.getSound('bass-a2').play(); LK.effects.flashScreen(0xe74c3c, 300); } else if (direction.up) { // Increase BPM and play high melody sequencer.bpm = Math.min(160, sequencer.bpm + 10); sequencer.beatInterval = 60000 / sequencer.bpm; LK.getSound('melody-a4').play(); LK.effects.flashScreen(0xf1c40f, 200); } else if (direction.down) { // Decrease BPM and play low melody sequencer.bpm = Math.max(80, sequencer.bpm - 10); sequencer.beatInterval = 60000 / sequencer.bpm; LK.getSound('melody-g3').play(); LK.effects.flashScreen(0x9b59b6, 200); } LK.setScore(LK.getScore() + 15); }; self.onEyeMovement = function (sequencer) { // Create eye-sparkle effect and play chord var chordNotes = ['melody-c4', 'melody-e4', 'melody-g3']; for (var i = 0; i < chordNotes.length; i++) { LK.setTimeout(function (noteIndex) { return function () { LK.getSound(chordNotes[noteIndex]).play(); }; }(i), i * 100); } // Create eye glow effect for (var i = 0; i < 8; i++) { var glow = game.addChild(LK.getAsset('effectDot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8, tint: 0x00ff88 })); var eyeX = (facekit.leftEye.x + facekit.rightEye.x) / 2; var eyeY = (facekit.leftEye.y + facekit.rightEye.y) / 2; var angle = i / 8 * Math.PI * 2; glow.x = eyeX + Math.cos(angle) * 80; glow.y = eyeY + Math.sin(angle) * 50; tween(glow, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 600, onFinish: function onFinish() { glow.destroy(); } }); } LK.setScore(LK.getScore() + 20); }; self.onMouthMovement = function (sequencer) { // Play vocal-like sounds and create mouth wave effect var vocalNotes = ['do-c', 're-d', 'fa-f', 'sol-g', 'la-a', 'si-b']; var randomVocal = vocalNotes[Math.floor(Math.random() * vocalNotes.length)]; LK.getSound(randomVocal).play(); // Create wave effect from mouth for (var i = 0; i < 6; i++) { var wave = game.addChild(LK.getAsset('melodyArc', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6, tint: 0xff6b6b })); wave.x = facekit.mouthCenter.x; wave.y = facekit.mouthCenter.y; wave.scaleX = 0.5; wave.scaleY = 0.5; tween(wave, { scaleX: 2 + i * 0.3, scaleY: 2 + i * 0.3, alpha: 0 }, { duration: 800 + i * 100, onFinish: function onFinish() { wave.destroy(); } }); } LK.setScore(LK.getScore() + 12); }; self.onNoseMovement = function (sequencer) { // Play filter effect and create nose particle burst LK.getSound('filter-sweep').play(); // Create particle burst from nose for (var i = 0; i < 10; i++) { var particle = game.addChild(LK.getAsset('effectDot', { anchorX: 0.5, anchorY: 0.5, alpha: 1.0, tint: 0xf39c12 })); particle.x = facekit.noseTip.x; particle.y = facekit.noseTip.y; var angle = Math.random() * Math.PI * 2; var speed = 3 + Math.random() * 4; var targetX = particle.x + Math.cos(angle) * speed * 30; var targetY = particle.y + Math.sin(angle) * speed * 30; tween(particle, { x: targetX, y: targetY, scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { particle.destroy(); } }); } LK.setScore(LK.getScore() + 8); }; self.update = function (sequencer) { // Check for expression changes var currentSmiling = self.detectSmile(); var currentFrowning = self.detectFrown(); var currentBlinking = self.detectBlink(); var headMovement = self.detectHeadMovement(); var eyeMovement = self.detectEyeMovement(); var mouthMovement = self.detectMouthMovement(); var noseMovement = self.detectNoseMovement(); // Trigger events on state transitions if (!self.lastSmiling && currentSmiling) { self.onSmile(sequencer); } if (!self.lastEyebrowsFrowning && currentFrowning) { self.onFrown(sequencer); } if (currentBlinking) { self.onBlink(sequencer); } // New movement detection if (headMovement.left || headMovement.right || headMovement.up || headMovement.down) { self.onHeadMovement(headMovement, sequencer); } if (eyeMovement) { self.onEyeMovement(sequencer); } if (mouthMovement) { self.onMouthMovement(sequencer); } if (noseMovement) { self.onNoseMovement(sequencer); } // Update last states self.lastSmiling = currentSmiling; self.lastEyebrowsFrowning = currentFrowning; // Update position tracking if (facekit.mouthCenter) { self.lastHeadX = facekit.mouthCenter.x; self.lastHeadY = facekit.mouthCenter.y; } if (facekit.leftEye && facekit.rightEye) { var eyeHeight = Math.abs(facekit.leftEye.y - facekit.rightEye.y); self.lastEyesOpen = eyeHeight > self.eyeCloseThreshold; } if (facekit.mouthCenter) { self.lastMouthPosition = facekit.mouthCenter.y; } if (facekit.noseTip) { self.lastNosePosition = facekit.noseTip.y; } }; return self; }); var FaceShape = Container.expand(function () { var self = Container.call(this); self.faceOutline = self.attachAsset('faceOutline', { anchorX: 0.5, anchorY: 0.5 }); self.leftEye = self.attachAsset('leftEye', { anchorX: 0.5, anchorY: 0.5 }); self.rightEye = self.attachAsset('rightEye', { anchorX: 0.5, anchorY: 0.5 }); self.nose = self.attachAsset('nose', { anchorX: 0.5, anchorY: 0.5 }); self.mouth = self.attachAsset('mouth', { anchorX: 0.5, anchorY: 0.5 }); self.currentColorIndex = 0; self.colorPalettes = [[0x3498db, 0xe74c3c, 0xf39c12, 0x9b59b6], [0x2ecc71, 0xe67e22, 0x34495e, 0x1abc9c], [0xf1c40f, 0x8e44ad, 0x16a085, 0xc0392b], [0x95a5a6, 0x2980b9, 0x27ae60, 0xd35400]]; self.lastMouthOpen = false; self.updateFacePosition = function (faceX, faceY, faceWidth, faceHeight) { self.x = faceX; self.y = faceY; // Scale based on face size var scale = Math.max(faceWidth / 300, faceHeight / 400) * 0.8; self.faceOutline.scaleX = scale; self.faceOutline.scaleY = scale; // Position facial features relative to face center self.leftEye.x = -faceWidth * 0.15; self.leftEye.y = -faceHeight * 0.1; self.rightEye.x = faceWidth * 0.15; self.rightEye.y = -faceHeight * 0.1; self.nose.x = 0; self.nose.y = 0; self.mouth.x = 0; self.mouth.y = faceHeight * 0.15; }; self.changeColors = function () { self.currentColorIndex = (self.currentColorIndex + 1) % self.colorPalettes.length; var palette = self.colorPalettes[self.currentColorIndex]; tween(self.faceOutline, { tint: palette[0] }, { duration: 500 }); tween(self.leftEye, { tint: palette[1] }, { duration: 500 }); tween(self.rightEye, { tint: palette[1] }, { duration: 500 }); tween(self.nose, { tint: palette[2] }, { duration: 500 }); tween(self.mouth, { tint: palette[3] }, { duration: 500 }); LK.getSound('shapeChange').play(); }; self.update = function () { // Check for mouth open expression change var currentMouthOpen = facekit.mouthOpen; if (!self.lastMouthOpen && currentMouthOpen) { // Mouth just opened self.triggerExpression(); } self.lastMouthOpen = currentMouthOpen; // Update mouth shape based on expression if (currentMouthOpen) { self.mouth.scaleY = 1.5; } else { self.mouth.scaleY = 1.0; } }; self.triggerExpression = function () { // Create particle effect createParticleEffect(self.x, self.y); // Pulse effect on face outline tween(self.faceOutline, { scaleX: self.faceOutline.scaleX * 1.2, scaleY: self.faceOutline.scaleY * 1.2 }, { duration: 200, onFinish: function onFinish() { tween(self.faceOutline, { scaleX: self.faceOutline.scaleX / 1.2, scaleY: self.faceOutline.scaleY / 1.2 }, { duration: 200 }); } }); // Play random note based on current face position and expression var randomNoteIndex = Math.floor(Math.random() * noteIds.length); LK.getSound(noteIds[randomNoteIndex]).play(); LK.getSound('expressionTrigger').play(); LK.setScore(LK.getScore() + 10); scoreText.setText('Score: ' + LK.getScore()); }; return self; }); var FacialAnalyzer = Container.expand(function () { var self = Container.call(this); // Musical shape arrays self.melodyArcs = []; self.bassEllipses = []; self.rhythmSquares = []; self.effectDots = []; self.scanLines = []; self.wireframes = []; // Face analysis data self.eyebrowAngle = 0; self.eyeDistance = 0; self.mouthWidth = 0; self.chinLength = 0; self.headTilt = 0; // Musical timing self.bpm = 120; self.beatCount = 0; self.lastBeatTime = 0; self.analyzeFace = function () { if (!facekit.leftEye || !facekit.rightEye || !facekit.mouthCenter) return; // Calculate facial measurements self.eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x); self.mouthWidth = Math.abs(facekit.upperLip.x - facekit.lowerLip.x) || 50; self.chinLength = Math.abs(facekit.chin.y - facekit.mouthCenter.y); self.headTilt = (facekit.rightEye.y - facekit.leftEye.y) / self.eyeDistance; // Generate shapes based on facial features self.generateMelodyArcs(); self.generateBassEllipses(); self.generateRhythmSquares(); self.generateEffectDots(); }; self.generateMelodyArcs = function () { // Clear existing arcs for (var i = 0; i < self.melodyArcs.length; i++) { self.melodyArcs[i].destroy(); } self.melodyArcs = []; // Create arcs based on eyebrow angle and eye distance var arcCount = Math.floor(self.eyeDistance / 50) + 2; for (var i = 0; i < arcCount; i++) { var arc = self.addChild(LK.getAsset('melodyArc', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 })); arc.x = facekit.leftEye.x + i * (self.eyeDistance / arcCount); arc.y = facekit.leftEye.y - 50; arc.rotation = self.headTilt * 0.5; arc.noteIndex = i % melodyNotes.length; self.melodyArcs.push(arc); } }; self.generateBassEllipses = function () { // Clear existing ellipses for (var i = 0; i < self.bassEllipses.length; i++) { self.bassEllipses[i].destroy(); } self.bassEllipses = []; // Create bass ellipses based on mouth width var ellipseCount = Math.floor(self.mouthWidth / 30) + 1; for (var i = 0; i < ellipseCount; i++) { var ellipse = self.addChild(LK.getAsset('bassEllipse', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 })); ellipse.x = facekit.mouthCenter.x + (i - ellipseCount / 2) * 60; ellipse.y = facekit.mouthCenter.y + 80; ellipse.noteIndex = i % bassNotes.length; self.bassEllipses.push(ellipse); } }; self.generateRhythmSquares = function () { // Clear existing squares for (var i = 0; i < self.rhythmSquares.length; i++) { self.rhythmSquares[i].destroy(); } self.rhythmSquares = []; // Create rhythm squares based on chin length var squareCount = Math.floor(self.chinLength / 40) + 2; for (var i = 0; i < squareCount; i++) { var square = self.addChild(LK.getAsset('rhythmSquare', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 })); square.x = facekit.chin.x + (i - squareCount / 2) * 70; square.y = facekit.chin.y + 50; square.rhythmIndex = i % rhythmSounds.length; self.rhythmSquares.push(square); } }; self.generateEffectDots = function () { // Clear existing dots for (var i = 0; i < self.effectDots.length; i++) { self.effectDots[i].destroy(); } self.effectDots = []; // Create effect dots around nose area var dotCount = 4; for (var i = 0; i < dotCount; i++) { var dot = self.addChild(LK.getAsset('effectDot', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 })); var angle = i / dotCount * Math.PI * 2; dot.x = facekit.noseTip.x + Math.cos(angle) * 60; dot.y = facekit.noseTip.y + Math.sin(angle) * 40; dot.effectIndex = i % effectSounds.length; self.effectDots.push(dot); } }; self.createScanEffect = function () { // Create scanning wireframe effect var wireframe = self.addChild(LK.getAsset('faceWireframe', { anchorX: 0.5, anchorY: 0.5, alpha: 0.3 })); wireframe.x = facekit.mouthCenter.x; wireframe.y = facekit.mouthCenter.y; // Animate scanning effect tween(wireframe, { alpha: 0.8, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, onFinish: function onFinish() { tween(wireframe, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { wireframe.destroy(); } }); } }); }; return self; }); var MusicSequencer = Container.expand(function () { var self = Container.call(this); self.bpm = 120; self.beatInterval = 60000 / self.bpm; // milliseconds per beat self.lastBeatTime = 0; self.currentBeat = 0; self.isPlaying = false; // Musical sequences based on face analysis self.melodySequence = []; self.bassSequence = []; self.rhythmSequence = []; self.effectSequence = []; // Stereo panning based on head tilt self.panValue = 0; self.generateSequence = function (analyzer) { // Generate melody sequence from arcs with reduced randomness self.melodySequence = []; // Create a more structured melody pattern var baseNoteIndex = 0; // Start from root note for (var i = 0; i < analyzer.melodyArcs.length; i++) { var arc = analyzer.melodyArcs[i]; // Use sequential notes with small jumps for better harmony var noteIndex = (baseNoteIndex + i) % melodyNotes.length; // Limit note jumps to create smoother melodies if (i > 0 && Math.abs(noteIndex - self.melodySequence[i - 1].noteIndex) > 2) { noteIndex = (self.melodySequence[i - 1].noteIndex + 1) % melodyNotes.length; } self.melodySequence.push({ note: melodyNotes[noteIndex], noteIndex: noteIndex, beat: i % 4, volume: 0.6 + arc.alpha * 0.4 }); } // Generate bass sequence from ellipses self.bassSequence = []; for (var i = 0; i < analyzer.bassEllipses.length; i++) { var ellipse = analyzer.bassEllipses[i]; self.bassSequence.push({ note: bassNotes[ellipse.noteIndex], beat: i % 2, volume: 0.8 }); } // Generate rhythm sequence from squares self.rhythmSequence = []; for (var i = 0; i < analyzer.rhythmSquares.length; i++) { var square = analyzer.rhythmSquares[i]; self.rhythmSequence.push({ sound: rhythmSounds[square.rhythmIndex], beat: i % 4, volume: 0.7 }); } // Set up effect triggers from dots self.effectSequence = []; for (var i = 0; i < analyzer.effectDots.length; i++) { var dot = analyzer.effectDots[i]; self.effectSequence.push({ effect: effectSounds[dot.effectIndex], trigger: Math.random() < 0.3 }); } self.isPlaying = true; }; self.updatePanning = function (headTilt) { self.panValue = Math.max(-1, Math.min(1, headTilt * 2)); // Apply panning to current sounds (simplified representation) }; self.playBeat = function () { if (!self.isPlaying) return; var beatInSequence = self.currentBeat % 4; // Play melody with reduced frequency for cleaner sound var melodyCount = 0; for (var i = 0; i < self.melodySequence.length; i++) { var melodyNote = self.melodySequence[i]; if (melodyNote.beat === beatInSequence) { // Only play every other melody note to reduce clutter if (melodyCount % 2 === 0 || beatInSequence === 0) { LK.getSound(melodyNote.note).play(); } melodyCount++; } } // Play bass for (var i = 0; i < self.bassSequence.length; i++) { var bassNote = self.bassSequence[i]; if (bassNote.beat === beatInSequence) { LK.getSound(bassNote.note).play(); } } // Play rhythm for (var i = 0; i < self.rhythmSequence.length; i++) { var rhythmHit = self.rhythmSequence[i]; if (rhythmHit.beat === beatInSequence) { LK.getSound(rhythmHit.sound).play(); } } // Reduced random effect triggers for cleaner composition if (Math.random() < 0.03 && beatInSequence === 0) { // Only on beat 0, much less frequent var randomEffect = self.effectSequence[Math.floor(Math.random() * self.effectSequence.length)]; if (randomEffect && randomEffect.effect) { LK.getSound(randomEffect.effect).play(); } } self.currentBeat++; }; self.update = function () { var currentTime = Date.now(); if (currentTime - self.lastBeatTime >= self.beatInterval) { self.playBeat(); self.lastBeatTime = currentTime; // Create beat visualization var beatPulse = game.addChild(LK.getAsset('beatPulse', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 })); beatPulse.x = 1024; beatPulse.y = 1366; tween(beatPulse, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 300, onFinish: function onFinish() { beatPulse.destroy(); } }); } }; return self; }); var Particle = Container.expand(function () { var self = Container.call(this); self.graphic = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = (Math.random() - 0.5) * 10; self.velocityY = (Math.random() - 0.5) * 10; self.life = 60; // 1 second at 60fps self.update = function () { self.x += self.velocityX; self.y += self.velocityY; self.life--; // Fade out over time self.graphic.alpha = self.life / 60; if (self.life <= 0) { self.destroy(); // Remove from particles array for (var i = particles.length - 1; i >= 0; i--) { if (particles[i] === self) { particles.splice(i, 1); break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Face-to-Music System Variables // Face analysis shapes mapped to musical roles // Arcs for melody // Ellipses for bass // Squares for rhythm // Dots for effects // Scanning wireframe // Face wireframe // Beat visualization // Melody notes (piano) - mapped to arcs // Bass notes (synth bass) - mapped to ellipses // Rhythm sounds - mapped to squares // Sound effects - mapped to dots/lines // Expression triggers var facialAnalyzer; var musicSequencer; var expressionDetector; var scanEffectTimer = 0; var gameInitialized = false; // Musical arrays mapped to shape types var melodyNotes = ['melody-g3', 'melody-b3', 'melody-c4', 'melody-d4', 'melody-e4', 'melody-f4', 'melody-a4']; var bassNotes = ['bass-e2', 'bass-a2', 'bass-d2']; var rhythmSounds = ['kick', 'snare', 'hihat']; var effectSounds = ['filter-sweep', 'delay-effect', 'reverb-wash']; // UI Elements var scoreText; var instructionText; var bpmText; var statusText; var gameTime = 0; // Create score display scoreText = new Text2('Score: 0', { size: 60, fill: 0x00ff88 }); scoreText.anchor.set(0.5, 0); scoreText.y = 50; LK.gui.top.addChild(scoreText); // Create BPM display bpmText = new Text2('BPM: 120', { size: 40, fill: 0x88aaff }); bpmText.anchor.set(0, 0); bpmText.x = 50; bpmText.y = 120; LK.gui.top.addChild(bpmText); // Create status text statusText = new Text2('Scanning face...', { size: 50, fill: 0xffaa44 }); statusText.anchor.set(0.5, 0.5); LK.gui.center.addChild(statusText); // Create instruction text instructionText = new Text2('Express yourself to create music!', { size: 35, fill: 0xffffff }); instructionText.anchor.set(0.5, 1); instructionText.y = -50; LK.gui.bottom.addChild(instructionText); // Initialize face-to-music system components facialAnalyzer = game.addChild(new FacialAnalyzer()); musicSequencer = new MusicSequencer(); expressionDetector = new ExpressionDetector(); function createScanningEffect() { // Create multiple scanning lines for (var i = 0; i < 3; i++) { var scanLine = game.addChild(LK.getAsset('scanLine', { anchorX: 0.5, anchorY: 0.5, alpha: 0.6 })); scanLine.x = 1024; scanLine.y = 200 + i * 150; scanLine.scaleX = 0; tween(scanLine, { scaleX: 1.5, alpha: 0 }, { duration: 1000 + i * 200, onFinish: function onFinish() { scanLine.destroy(); } }); } } function createBeatVisualization(x, y, intensity) { var beatPulse = game.addChild(LK.getAsset('beatPulse', { anchorX: 0.5, anchorY: 0.5, alpha: intensity, tint: 0x00ff88 })); beatPulse.x = x; beatPulse.y = y; tween(beatPulse, { scaleX: 2 + intensity, scaleY: 2 + intensity, alpha: 0 }, { duration: 400, onFinish: function onFinish() { beatPulse.destroy(); } }); } function updateMusicVisualization() { // Create visual pulses synced to music if (musicSequencer.isPlaying && gameTime % 15 === 0) { var centerX = 1024; var centerY = 1366; createBeatVisualization(centerX, centerY, 0.8); // Create smaller pulses around face shapes if (facialAnalyzer.melodyArcs.length > 0) { for (var i = 0; i < facialAnalyzer.melodyArcs.length; i++) { var arc = facialAnalyzer.melodyArcs[i]; if (Math.random() < 0.3) { createBeatVisualization(arc.x, arc.y, 0.4); } } } } } // Handle tap to regenerate music composition game.down = function (x, y, obj) { if (gameInitialized && facialAnalyzer) { // Regenerate the musical composition facialAnalyzer.analyzeFace(); musicSequencer.generateSequence(facialAnalyzer); // Visual feedback facialAnalyzer.createScanEffect(); // Audio feedback LK.getSound('smile-trigger').play(); // Add score bonus LK.setScore(LK.getScore() + 15); scoreText.setText('Score: ' + LK.getScore()); // Temporary instruction var originalText = instructionText.text; instructionText.setText('Music composition refreshed!'); LK.setTimeout(function () { instructionText.setText(originalText); }, 2000); } }; // Main face-to-music game loop game.update = function () { gameTime++; // Check if face is detected and initialize system if (facekit.mouthCenter && facekit.leftEye && facekit.rightEye && !gameInitialized) { // Face detected - initialize the music system statusText.setText('Face detected! Analyzing...'); createScanningEffect(); // Allow some time for scanning effect if (scanEffectTimer++ > 120) { // 2 seconds at 60fps facialAnalyzer.analyzeFace(); musicSequencer.generateSequence(facialAnalyzer); gameInitialized = true; statusText.setText('Music composition created!'); // Fade out status text tween(statusText, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { statusText.setText(''); statusText.alpha = 1; } }); } } // Main music and visual system updates if (gameInitialized && facekit.mouthCenter) { // Update facial analysis continuously facialAnalyzer.analyzeFace(); // Update music sequencer with head tilt for panning if (facekit.leftEye && facekit.rightEye) { var headTilt = (facekit.rightEye.y - facekit.leftEye.y) / Math.abs(facekit.rightEye.x - facekit.leftEye.x); musicSequencer.updatePanning(headTilt); } // Update music sequencer musicSequencer.update(); // Update expression detection expressionDetector.update(musicSequencer); // Update music visualization updateMusicVisualization(); // Update UI bpmText.setText('BPM: ' + musicSequencer.bpm); // Re-analyze face every 10 seconds to maintain stability if (gameTime % 600 === 0) { // Doubled the interval for more stable music facialAnalyzer.analyzeFace(); musicSequencer.generateSequence(facialAnalyzer); } } // Update score based on active music creation if (gameInitialized && musicSequencer.isPlaying) { if (gameTime % 60 === 0) { LK.setScore(LK.getScore() + 2); scoreText.setText('Score: ' + LK.getScore()); } } // Update instruction text based on game state and score if (!gameInitialized) { if (facekit.mouthCenter) { instructionText.setText('Analyzing your face...'); } else { instructionText.setText('Position your face in view of camera'); } } else { if (LK.getScore() > 150) { instructionText.setText('Musical master! Your face creates beautiful compositions!'); } else if (LK.getScore() > 75) { instructionText.setText('Excellent! Try different expressions for variety!'); } else { instructionText.setText('Smile, frown, blink - each expression adds to your music!'); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
var ExpressionDetector = Container.expand(function () {
var self = Container.call(this);
// Expression state tracking
self.lastSmiling = false;
self.lastEyebrowsFrowning = false;
self.lastBlinking = false;
self.blinkCounter = 0;
self.lastBlinkTime = 0;
// Head movement tracking
self.lastHeadX = 0;
self.lastHeadY = 0;
self.lastEyesOpen = true;
self.lastMouthPosition = 0;
self.lastNosePosition = 0;
// Expression thresholds
self.smileThreshold = 0.3;
self.frownThreshold = 0.2;
self.blinkThreshold = 50; // milliseconds
self.headMovementThreshold = 30;
self.eyeCloseThreshold = 20;
self.mouthMovementThreshold = 15;
self.noseMovementThreshold = 10;
self.detectSmile = function () {
if (!facekit.upperLip || !facekit.lowerLip) return false;
// Simple smile detection based on mouth corner positions
var mouthCurve = (facekit.upperLip.y - facekit.lowerLip.y) / 20;
return mouthCurve > self.smileThreshold;
};
self.detectFrown = function () {
if (!facekit.leftEye || !facekit.rightEye) return false;
// Detect frown based on eyebrow position relative to eyes
var eyebrowPosition = (facekit.leftEye.y + facekit.rightEye.y) / 2 - 30;
return eyebrowPosition < self.frownThreshold;
};
self.detectBlink = function () {
// Simplified blink detection (in real implementation would track eye closure)
var currentTime = Date.now();
if (currentTime - self.lastBlinkTime > 2000) {
// Auto-trigger every 2 seconds for demo
self.lastBlinkTime = currentTime;
return true;
}
return false;
};
self.detectHeadMovement = function () {
if (!facekit.mouthCenter) return {
left: false,
right: false,
up: false,
down: false
};
var currentHeadX = facekit.mouthCenter.x;
var currentHeadY = facekit.mouthCenter.y;
var deltaX = currentHeadX - self.lastHeadX;
var deltaY = currentHeadY - self.lastHeadY;
return {
left: deltaX < -self.headMovementThreshold,
right: deltaX > self.headMovementThreshold,
up: deltaY < -self.headMovementThreshold,
down: deltaY > self.headMovementThreshold
};
};
self.detectEyeMovement = function () {
if (!facekit.leftEye || !facekit.rightEye) return false;
// Detect eye closing based on eye position change
var eyeHeight = Math.abs(facekit.leftEye.y - facekit.rightEye.y);
var eyesCurrentlyOpen = eyeHeight > self.eyeCloseThreshold;
return eyesCurrentlyOpen !== self.lastEyesOpen;
};
self.detectMouthMovement = function () {
if (!facekit.mouthCenter) return false;
var currentMouthY = facekit.mouthCenter.y;
var deltaY = Math.abs(currentMouthY - self.lastMouthPosition);
return deltaY > self.mouthMovementThreshold;
};
self.detectNoseMovement = function () {
if (!facekit.noseTip) return false;
var currentNoseY = facekit.noseTip.y;
var deltaY = Math.abs(currentNoseY - self.lastNosePosition);
return deltaY > self.noseMovementThreshold;
};
self.onSmile = function (sequencer) {
// Add harmonious melody notes when smiling (reduced randomness)
var lastNoteIndex = sequencer.melodySequence.length > 0 ? sequencer.melodySequence[sequencer.melodySequence.length - 1].noteIndex || 0 : 0;
var harmonicNoteIndex = (lastNoteIndex + 2) % melodyNotes.length; // Always use harmonic intervals
var newMelodyNote = melodyNotes[harmonicNoteIndex];
LK.getSound(newMelodyNote).play();
LK.getSound('smile-trigger').play();
// Visual feedback
LK.effects.flashScreen(0x00ff88, 300);
// Add to score
LK.setScore(LK.getScore() + 25);
};
self.onFrown = function (sequencer) {
// Increase BPM when frowning
sequencer.bpm = Math.min(180, sequencer.bpm + 5);
sequencer.beatInterval = 60000 / sequencer.bpm;
LK.getSound('frown-trigger').play();
// Visual feedback - red flash
LK.effects.flashScreen(0xff4444, 200);
};
self.onBlink = function (sequencer) {
// Trigger random effects on blink
var randomEffect = effectSounds[Math.floor(Math.random() * effectSounds.length)];
LK.getSound(randomEffect).play();
LK.getSound('blink-effect').play();
// Create sparkle effect
for (var i = 0; i < 5; i++) {
var sparkle = game.addChild(LK.getAsset('effectDot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
tint: Math.random() * 0xffffff
}));
sparkle.x = facekit.mouthCenter.x + (Math.random() - 0.5) * 200;
sparkle.y = facekit.mouthCenter.y + (Math.random() - 0.5) * 200;
tween(sparkle, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
sparkle.destroy();
}
});
}
};
self.onHeadMovement = function (direction, sequencer) {
// Different effects for different head movements
if (direction.left) {
// Pan music left and play bass note
sequencer.panValue = -0.8;
LK.getSound('bass-e2').play();
LK.effects.flashScreen(0x3498db, 300);
} else if (direction.right) {
// Pan music right and play different bass note
sequencer.panValue = 0.8;
LK.getSound('bass-a2').play();
LK.effects.flashScreen(0xe74c3c, 300);
} else if (direction.up) {
// Increase BPM and play high melody
sequencer.bpm = Math.min(160, sequencer.bpm + 10);
sequencer.beatInterval = 60000 / sequencer.bpm;
LK.getSound('melody-a4').play();
LK.effects.flashScreen(0xf1c40f, 200);
} else if (direction.down) {
// Decrease BPM and play low melody
sequencer.bpm = Math.max(80, sequencer.bpm - 10);
sequencer.beatInterval = 60000 / sequencer.bpm;
LK.getSound('melody-g3').play();
LK.effects.flashScreen(0x9b59b6, 200);
}
LK.setScore(LK.getScore() + 15);
};
self.onEyeMovement = function (sequencer) {
// Create eye-sparkle effect and play chord
var chordNotes = ['melody-c4', 'melody-e4', 'melody-g3'];
for (var i = 0; i < chordNotes.length; i++) {
LK.setTimeout(function (noteIndex) {
return function () {
LK.getSound(chordNotes[noteIndex]).play();
};
}(i), i * 100);
}
// Create eye glow effect
for (var i = 0; i < 8; i++) {
var glow = game.addChild(LK.getAsset('effectDot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
tint: 0x00ff88
}));
var eyeX = (facekit.leftEye.x + facekit.rightEye.x) / 2;
var eyeY = (facekit.leftEye.y + facekit.rightEye.y) / 2;
var angle = i / 8 * Math.PI * 2;
glow.x = eyeX + Math.cos(angle) * 80;
glow.y = eyeY + Math.sin(angle) * 50;
tween(glow, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
glow.destroy();
}
});
}
LK.setScore(LK.getScore() + 20);
};
self.onMouthMovement = function (sequencer) {
// Play vocal-like sounds and create mouth wave effect
var vocalNotes = ['do-c', 're-d', 'fa-f', 'sol-g', 'la-a', 'si-b'];
var randomVocal = vocalNotes[Math.floor(Math.random() * vocalNotes.length)];
LK.getSound(randomVocal).play();
// Create wave effect from mouth
for (var i = 0; i < 6; i++) {
var wave = game.addChild(LK.getAsset('melodyArc', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6,
tint: 0xff6b6b
}));
wave.x = facekit.mouthCenter.x;
wave.y = facekit.mouthCenter.y;
wave.scaleX = 0.5;
wave.scaleY = 0.5;
tween(wave, {
scaleX: 2 + i * 0.3,
scaleY: 2 + i * 0.3,
alpha: 0
}, {
duration: 800 + i * 100,
onFinish: function onFinish() {
wave.destroy();
}
});
}
LK.setScore(LK.getScore() + 12);
};
self.onNoseMovement = function (sequencer) {
// Play filter effect and create nose particle burst
LK.getSound('filter-sweep').play();
// Create particle burst from nose
for (var i = 0; i < 10; i++) {
var particle = game.addChild(LK.getAsset('effectDot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
tint: 0xf39c12
}));
particle.x = facekit.noseTip.x;
particle.y = facekit.noseTip.y;
var angle = Math.random() * Math.PI * 2;
var speed = 3 + Math.random() * 4;
var targetX = particle.x + Math.cos(angle) * speed * 30;
var targetY = particle.y + Math.sin(angle) * speed * 30;
tween(particle, {
x: targetX,
y: targetY,
scaleX: 0.2,
scaleY: 0.2,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
particle.destroy();
}
});
}
LK.setScore(LK.getScore() + 8);
};
self.update = function (sequencer) {
// Check for expression changes
var currentSmiling = self.detectSmile();
var currentFrowning = self.detectFrown();
var currentBlinking = self.detectBlink();
var headMovement = self.detectHeadMovement();
var eyeMovement = self.detectEyeMovement();
var mouthMovement = self.detectMouthMovement();
var noseMovement = self.detectNoseMovement();
// Trigger events on state transitions
if (!self.lastSmiling && currentSmiling) {
self.onSmile(sequencer);
}
if (!self.lastEyebrowsFrowning && currentFrowning) {
self.onFrown(sequencer);
}
if (currentBlinking) {
self.onBlink(sequencer);
}
// New movement detection
if (headMovement.left || headMovement.right || headMovement.up || headMovement.down) {
self.onHeadMovement(headMovement, sequencer);
}
if (eyeMovement) {
self.onEyeMovement(sequencer);
}
if (mouthMovement) {
self.onMouthMovement(sequencer);
}
if (noseMovement) {
self.onNoseMovement(sequencer);
}
// Update last states
self.lastSmiling = currentSmiling;
self.lastEyebrowsFrowning = currentFrowning;
// Update position tracking
if (facekit.mouthCenter) {
self.lastHeadX = facekit.mouthCenter.x;
self.lastHeadY = facekit.mouthCenter.y;
}
if (facekit.leftEye && facekit.rightEye) {
var eyeHeight = Math.abs(facekit.leftEye.y - facekit.rightEye.y);
self.lastEyesOpen = eyeHeight > self.eyeCloseThreshold;
}
if (facekit.mouthCenter) {
self.lastMouthPosition = facekit.mouthCenter.y;
}
if (facekit.noseTip) {
self.lastNosePosition = facekit.noseTip.y;
}
};
return self;
});
var FaceShape = Container.expand(function () {
var self = Container.call(this);
self.faceOutline = self.attachAsset('faceOutline', {
anchorX: 0.5,
anchorY: 0.5
});
self.leftEye = self.attachAsset('leftEye', {
anchorX: 0.5,
anchorY: 0.5
});
self.rightEye = self.attachAsset('rightEye', {
anchorX: 0.5,
anchorY: 0.5
});
self.nose = self.attachAsset('nose', {
anchorX: 0.5,
anchorY: 0.5
});
self.mouth = self.attachAsset('mouth', {
anchorX: 0.5,
anchorY: 0.5
});
self.currentColorIndex = 0;
self.colorPalettes = [[0x3498db, 0xe74c3c, 0xf39c12, 0x9b59b6], [0x2ecc71, 0xe67e22, 0x34495e, 0x1abc9c], [0xf1c40f, 0x8e44ad, 0x16a085, 0xc0392b], [0x95a5a6, 0x2980b9, 0x27ae60, 0xd35400]];
self.lastMouthOpen = false;
self.updateFacePosition = function (faceX, faceY, faceWidth, faceHeight) {
self.x = faceX;
self.y = faceY;
// Scale based on face size
var scale = Math.max(faceWidth / 300, faceHeight / 400) * 0.8;
self.faceOutline.scaleX = scale;
self.faceOutline.scaleY = scale;
// Position facial features relative to face center
self.leftEye.x = -faceWidth * 0.15;
self.leftEye.y = -faceHeight * 0.1;
self.rightEye.x = faceWidth * 0.15;
self.rightEye.y = -faceHeight * 0.1;
self.nose.x = 0;
self.nose.y = 0;
self.mouth.x = 0;
self.mouth.y = faceHeight * 0.15;
};
self.changeColors = function () {
self.currentColorIndex = (self.currentColorIndex + 1) % self.colorPalettes.length;
var palette = self.colorPalettes[self.currentColorIndex];
tween(self.faceOutline, {
tint: palette[0]
}, {
duration: 500
});
tween(self.leftEye, {
tint: palette[1]
}, {
duration: 500
});
tween(self.rightEye, {
tint: palette[1]
}, {
duration: 500
});
tween(self.nose, {
tint: palette[2]
}, {
duration: 500
});
tween(self.mouth, {
tint: palette[3]
}, {
duration: 500
});
LK.getSound('shapeChange').play();
};
self.update = function () {
// Check for mouth open expression change
var currentMouthOpen = facekit.mouthOpen;
if (!self.lastMouthOpen && currentMouthOpen) {
// Mouth just opened
self.triggerExpression();
}
self.lastMouthOpen = currentMouthOpen;
// Update mouth shape based on expression
if (currentMouthOpen) {
self.mouth.scaleY = 1.5;
} else {
self.mouth.scaleY = 1.0;
}
};
self.triggerExpression = function () {
// Create particle effect
createParticleEffect(self.x, self.y);
// Pulse effect on face outline
tween(self.faceOutline, {
scaleX: self.faceOutline.scaleX * 1.2,
scaleY: self.faceOutline.scaleY * 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.faceOutline, {
scaleX: self.faceOutline.scaleX / 1.2,
scaleY: self.faceOutline.scaleY / 1.2
}, {
duration: 200
});
}
});
// Play random note based on current face position and expression
var randomNoteIndex = Math.floor(Math.random() * noteIds.length);
LK.getSound(noteIds[randomNoteIndex]).play();
LK.getSound('expressionTrigger').play();
LK.setScore(LK.getScore() + 10);
scoreText.setText('Score: ' + LK.getScore());
};
return self;
});
var FacialAnalyzer = Container.expand(function () {
var self = Container.call(this);
// Musical shape arrays
self.melodyArcs = [];
self.bassEllipses = [];
self.rhythmSquares = [];
self.effectDots = [];
self.scanLines = [];
self.wireframes = [];
// Face analysis data
self.eyebrowAngle = 0;
self.eyeDistance = 0;
self.mouthWidth = 0;
self.chinLength = 0;
self.headTilt = 0;
// Musical timing
self.bpm = 120;
self.beatCount = 0;
self.lastBeatTime = 0;
self.analyzeFace = function () {
if (!facekit.leftEye || !facekit.rightEye || !facekit.mouthCenter) return;
// Calculate facial measurements
self.eyeDistance = Math.abs(facekit.rightEye.x - facekit.leftEye.x);
self.mouthWidth = Math.abs(facekit.upperLip.x - facekit.lowerLip.x) || 50;
self.chinLength = Math.abs(facekit.chin.y - facekit.mouthCenter.y);
self.headTilt = (facekit.rightEye.y - facekit.leftEye.y) / self.eyeDistance;
// Generate shapes based on facial features
self.generateMelodyArcs();
self.generateBassEllipses();
self.generateRhythmSquares();
self.generateEffectDots();
};
self.generateMelodyArcs = function () {
// Clear existing arcs
for (var i = 0; i < self.melodyArcs.length; i++) {
self.melodyArcs[i].destroy();
}
self.melodyArcs = [];
// Create arcs based on eyebrow angle and eye distance
var arcCount = Math.floor(self.eyeDistance / 50) + 2;
for (var i = 0; i < arcCount; i++) {
var arc = self.addChild(LK.getAsset('melodyArc', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
}));
arc.x = facekit.leftEye.x + i * (self.eyeDistance / arcCount);
arc.y = facekit.leftEye.y - 50;
arc.rotation = self.headTilt * 0.5;
arc.noteIndex = i % melodyNotes.length;
self.melodyArcs.push(arc);
}
};
self.generateBassEllipses = function () {
// Clear existing ellipses
for (var i = 0; i < self.bassEllipses.length; i++) {
self.bassEllipses[i].destroy();
}
self.bassEllipses = [];
// Create bass ellipses based on mouth width
var ellipseCount = Math.floor(self.mouthWidth / 30) + 1;
for (var i = 0; i < ellipseCount; i++) {
var ellipse = self.addChild(LK.getAsset('bassEllipse', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
}));
ellipse.x = facekit.mouthCenter.x + (i - ellipseCount / 2) * 60;
ellipse.y = facekit.mouthCenter.y + 80;
ellipse.noteIndex = i % bassNotes.length;
self.bassEllipses.push(ellipse);
}
};
self.generateRhythmSquares = function () {
// Clear existing squares
for (var i = 0; i < self.rhythmSquares.length; i++) {
self.rhythmSquares[i].destroy();
}
self.rhythmSquares = [];
// Create rhythm squares based on chin length
var squareCount = Math.floor(self.chinLength / 40) + 2;
for (var i = 0; i < squareCount; i++) {
var square = self.addChild(LK.getAsset('rhythmSquare', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
}));
square.x = facekit.chin.x + (i - squareCount / 2) * 70;
square.y = facekit.chin.y + 50;
square.rhythmIndex = i % rhythmSounds.length;
self.rhythmSquares.push(square);
}
};
self.generateEffectDots = function () {
// Clear existing dots
for (var i = 0; i < self.effectDots.length; i++) {
self.effectDots[i].destroy();
}
self.effectDots = [];
// Create effect dots around nose area
var dotCount = 4;
for (var i = 0; i < dotCount; i++) {
var dot = self.addChild(LK.getAsset('effectDot', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
}));
var angle = i / dotCount * Math.PI * 2;
dot.x = facekit.noseTip.x + Math.cos(angle) * 60;
dot.y = facekit.noseTip.y + Math.sin(angle) * 40;
dot.effectIndex = i % effectSounds.length;
self.effectDots.push(dot);
}
};
self.createScanEffect = function () {
// Create scanning wireframe effect
var wireframe = self.addChild(LK.getAsset('faceWireframe', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
}));
wireframe.x = facekit.mouthCenter.x;
wireframe.y = facekit.mouthCenter.y;
// Animate scanning effect
tween(wireframe, {
alpha: 0.8,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(wireframe, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onFinish: function onFinish() {
wireframe.destroy();
}
});
}
});
};
return self;
});
var MusicSequencer = Container.expand(function () {
var self = Container.call(this);
self.bpm = 120;
self.beatInterval = 60000 / self.bpm; // milliseconds per beat
self.lastBeatTime = 0;
self.currentBeat = 0;
self.isPlaying = false;
// Musical sequences based on face analysis
self.melodySequence = [];
self.bassSequence = [];
self.rhythmSequence = [];
self.effectSequence = [];
// Stereo panning based on head tilt
self.panValue = 0;
self.generateSequence = function (analyzer) {
// Generate melody sequence from arcs with reduced randomness
self.melodySequence = [];
// Create a more structured melody pattern
var baseNoteIndex = 0; // Start from root note
for (var i = 0; i < analyzer.melodyArcs.length; i++) {
var arc = analyzer.melodyArcs[i];
// Use sequential notes with small jumps for better harmony
var noteIndex = (baseNoteIndex + i) % melodyNotes.length;
// Limit note jumps to create smoother melodies
if (i > 0 && Math.abs(noteIndex - self.melodySequence[i - 1].noteIndex) > 2) {
noteIndex = (self.melodySequence[i - 1].noteIndex + 1) % melodyNotes.length;
}
self.melodySequence.push({
note: melodyNotes[noteIndex],
noteIndex: noteIndex,
beat: i % 4,
volume: 0.6 + arc.alpha * 0.4
});
}
// Generate bass sequence from ellipses
self.bassSequence = [];
for (var i = 0; i < analyzer.bassEllipses.length; i++) {
var ellipse = analyzer.bassEllipses[i];
self.bassSequence.push({
note: bassNotes[ellipse.noteIndex],
beat: i % 2,
volume: 0.8
});
}
// Generate rhythm sequence from squares
self.rhythmSequence = [];
for (var i = 0; i < analyzer.rhythmSquares.length; i++) {
var square = analyzer.rhythmSquares[i];
self.rhythmSequence.push({
sound: rhythmSounds[square.rhythmIndex],
beat: i % 4,
volume: 0.7
});
}
// Set up effect triggers from dots
self.effectSequence = [];
for (var i = 0; i < analyzer.effectDots.length; i++) {
var dot = analyzer.effectDots[i];
self.effectSequence.push({
effect: effectSounds[dot.effectIndex],
trigger: Math.random() < 0.3
});
}
self.isPlaying = true;
};
self.updatePanning = function (headTilt) {
self.panValue = Math.max(-1, Math.min(1, headTilt * 2));
// Apply panning to current sounds (simplified representation)
};
self.playBeat = function () {
if (!self.isPlaying) return;
var beatInSequence = self.currentBeat % 4;
// Play melody with reduced frequency for cleaner sound
var melodyCount = 0;
for (var i = 0; i < self.melodySequence.length; i++) {
var melodyNote = self.melodySequence[i];
if (melodyNote.beat === beatInSequence) {
// Only play every other melody note to reduce clutter
if (melodyCount % 2 === 0 || beatInSequence === 0) {
LK.getSound(melodyNote.note).play();
}
melodyCount++;
}
}
// Play bass
for (var i = 0; i < self.bassSequence.length; i++) {
var bassNote = self.bassSequence[i];
if (bassNote.beat === beatInSequence) {
LK.getSound(bassNote.note).play();
}
}
// Play rhythm
for (var i = 0; i < self.rhythmSequence.length; i++) {
var rhythmHit = self.rhythmSequence[i];
if (rhythmHit.beat === beatInSequence) {
LK.getSound(rhythmHit.sound).play();
}
}
// Reduced random effect triggers for cleaner composition
if (Math.random() < 0.03 && beatInSequence === 0) {
// Only on beat 0, much less frequent
var randomEffect = self.effectSequence[Math.floor(Math.random() * self.effectSequence.length)];
if (randomEffect && randomEffect.effect) {
LK.getSound(randomEffect.effect).play();
}
}
self.currentBeat++;
};
self.update = function () {
var currentTime = Date.now();
if (currentTime - self.lastBeatTime >= self.beatInterval) {
self.playBeat();
self.lastBeatTime = currentTime;
// Create beat visualization
var beatPulse = game.addChild(LK.getAsset('beatPulse', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
}));
beatPulse.x = 1024;
beatPulse.y = 1366;
tween(beatPulse, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
beatPulse.destroy();
}
});
}
};
return self;
});
var Particle = Container.expand(function () {
var self = Container.call(this);
self.graphic = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = (Math.random() - 0.5) * 10;
self.velocityY = (Math.random() - 0.5) * 10;
self.life = 60; // 1 second at 60fps
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.life--;
// Fade out over time
self.graphic.alpha = self.life / 60;
if (self.life <= 0) {
self.destroy();
// Remove from particles array
for (var i = particles.length - 1; i >= 0; i--) {
if (particles[i] === self) {
particles.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Face-to-Music System Variables
// Face analysis shapes mapped to musical roles
// Arcs for melody
// Ellipses for bass
// Squares for rhythm
// Dots for effects
// Scanning wireframe
// Face wireframe
// Beat visualization
// Melody notes (piano) - mapped to arcs
// Bass notes (synth bass) - mapped to ellipses
// Rhythm sounds - mapped to squares
// Sound effects - mapped to dots/lines
// Expression triggers
var facialAnalyzer;
var musicSequencer;
var expressionDetector;
var scanEffectTimer = 0;
var gameInitialized = false;
// Musical arrays mapped to shape types
var melodyNotes = ['melody-g3', 'melody-b3', 'melody-c4', 'melody-d4', 'melody-e4', 'melody-f4', 'melody-a4'];
var bassNotes = ['bass-e2', 'bass-a2', 'bass-d2'];
var rhythmSounds = ['kick', 'snare', 'hihat'];
var effectSounds = ['filter-sweep', 'delay-effect', 'reverb-wash'];
// UI Elements
var scoreText;
var instructionText;
var bpmText;
var statusText;
var gameTime = 0;
// Create score display
scoreText = new Text2('Score: 0', {
size: 60,
fill: 0x00ff88
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 50;
LK.gui.top.addChild(scoreText);
// Create BPM display
bpmText = new Text2('BPM: 120', {
size: 40,
fill: 0x88aaff
});
bpmText.anchor.set(0, 0);
bpmText.x = 50;
bpmText.y = 120;
LK.gui.top.addChild(bpmText);
// Create status text
statusText = new Text2('Scanning face...', {
size: 50,
fill: 0xffaa44
});
statusText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(statusText);
// Create instruction text
instructionText = new Text2('Express yourself to create music!', {
size: 35,
fill: 0xffffff
});
instructionText.anchor.set(0.5, 1);
instructionText.y = -50;
LK.gui.bottom.addChild(instructionText);
// Initialize face-to-music system components
facialAnalyzer = game.addChild(new FacialAnalyzer());
musicSequencer = new MusicSequencer();
expressionDetector = new ExpressionDetector();
function createScanningEffect() {
// Create multiple scanning lines
for (var i = 0; i < 3; i++) {
var scanLine = game.addChild(LK.getAsset('scanLine', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.6
}));
scanLine.x = 1024;
scanLine.y = 200 + i * 150;
scanLine.scaleX = 0;
tween(scanLine, {
scaleX: 1.5,
alpha: 0
}, {
duration: 1000 + i * 200,
onFinish: function onFinish() {
scanLine.destroy();
}
});
}
}
function createBeatVisualization(x, y, intensity) {
var beatPulse = game.addChild(LK.getAsset('beatPulse', {
anchorX: 0.5,
anchorY: 0.5,
alpha: intensity,
tint: 0x00ff88
}));
beatPulse.x = x;
beatPulse.y = y;
tween(beatPulse, {
scaleX: 2 + intensity,
scaleY: 2 + intensity,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
beatPulse.destroy();
}
});
}
function updateMusicVisualization() {
// Create visual pulses synced to music
if (musicSequencer.isPlaying && gameTime % 15 === 0) {
var centerX = 1024;
var centerY = 1366;
createBeatVisualization(centerX, centerY, 0.8);
// Create smaller pulses around face shapes
if (facialAnalyzer.melodyArcs.length > 0) {
for (var i = 0; i < facialAnalyzer.melodyArcs.length; i++) {
var arc = facialAnalyzer.melodyArcs[i];
if (Math.random() < 0.3) {
createBeatVisualization(arc.x, arc.y, 0.4);
}
}
}
}
}
// Handle tap to regenerate music composition
game.down = function (x, y, obj) {
if (gameInitialized && facialAnalyzer) {
// Regenerate the musical composition
facialAnalyzer.analyzeFace();
musicSequencer.generateSequence(facialAnalyzer);
// Visual feedback
facialAnalyzer.createScanEffect();
// Audio feedback
LK.getSound('smile-trigger').play();
// Add score bonus
LK.setScore(LK.getScore() + 15);
scoreText.setText('Score: ' + LK.getScore());
// Temporary instruction
var originalText = instructionText.text;
instructionText.setText('Music composition refreshed!');
LK.setTimeout(function () {
instructionText.setText(originalText);
}, 2000);
}
};
// Main face-to-music game loop
game.update = function () {
gameTime++;
// Check if face is detected and initialize system
if (facekit.mouthCenter && facekit.leftEye && facekit.rightEye && !gameInitialized) {
// Face detected - initialize the music system
statusText.setText('Face detected! Analyzing...');
createScanningEffect();
// Allow some time for scanning effect
if (scanEffectTimer++ > 120) {
// 2 seconds at 60fps
facialAnalyzer.analyzeFace();
musicSequencer.generateSequence(facialAnalyzer);
gameInitialized = true;
statusText.setText('Music composition created!');
// Fade out status text
tween(statusText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
statusText.setText('');
statusText.alpha = 1;
}
});
}
}
// Main music and visual system updates
if (gameInitialized && facekit.mouthCenter) {
// Update facial analysis continuously
facialAnalyzer.analyzeFace();
// Update music sequencer with head tilt for panning
if (facekit.leftEye && facekit.rightEye) {
var headTilt = (facekit.rightEye.y - facekit.leftEye.y) / Math.abs(facekit.rightEye.x - facekit.leftEye.x);
musicSequencer.updatePanning(headTilt);
}
// Update music sequencer
musicSequencer.update();
// Update expression detection
expressionDetector.update(musicSequencer);
// Update music visualization
updateMusicVisualization();
// Update UI
bpmText.setText('BPM: ' + musicSequencer.bpm);
// Re-analyze face every 10 seconds to maintain stability
if (gameTime % 600 === 0) {
// Doubled the interval for more stable music
facialAnalyzer.analyzeFace();
musicSequencer.generateSequence(facialAnalyzer);
}
}
// Update score based on active music creation
if (gameInitialized && musicSequencer.isPlaying) {
if (gameTime % 60 === 0) {
LK.setScore(LK.getScore() + 2);
scoreText.setText('Score: ' + LK.getScore());
}
}
// Update instruction text based on game state and score
if (!gameInitialized) {
if (facekit.mouthCenter) {
instructionText.setText('Analyzing your face...');
} else {
instructionText.setText('Position your face in view of camera');
}
} else {
if (LK.getScore() > 150) {
instructionText.setText('Musical master! Your face creates beautiful compositions!');
} else if (LK.getScore() > 75) {
instructionText.setText('Excellent! Try different expressions for variety!');
} else {
instructionText.setText('Smile, frown, blink - each expression adds to your music!');
}
}
};
do-c
Sound effect
re-d
Sound effect
fa-f
Sound effect
sol-g
Sound effect
la-a
Sound effect
si-b
Sound effect
melody-g3
Sound effect
melody-b3
Sound effect
melody-c4
Sound effect
melody-d4
Sound effect
melody-e4
Sound effect
melody-f4
Sound effect
melody-a4
Sound effect
smile-trigger
Sound effect
frown-trigger
Sound effect
blink-effect
Sound effect
shapeChange
Sound effect
kick
Sound effect
snare
Sound effect
hihat
Sound effect
filter-sweep
Sound effect
delay-effect
Sound effect
reverb-wash
Sound effect