User prompt
Remove the win scenario.
User prompt
Reduce note density in dense patterns and lean more towards syncing notes and holds between sides overall
User prompt
Make the length between the start and end of hold notes shorter overall and make sure that the placement of the starting note does not place the end off screen.
User prompt
Make the space between hold notes shorter overall and make sure the end does not go off the screen. Smarter placement of hold note starts.
User prompt
Replace with: var SongGenerator = { generateSong: function(config) { var notes = []; var totalLength = config.totalLength || 202136; var startTime = config.startDelay || 2000; return this.generateZoneAwareSong(startTime, totalLength); }, generateZoneAwareSong: function(startTime, endTime) { var notes = []; var currentTime = startTime; // Track when each zone will be free var zoneFreeTime = { left: startTime, right: startTime }; // Minimum spacing between notes (human reaction time) var MIN_SAME_ZONE_SPACING = 400; // 400ms minimum in same zone var MIN_DIFFERENT_ZONE_SPACING = 200; // 200ms minimum between different zones var MIN_SYNC_SPACING = 800; // 800ms minimum between sync notes var lastSyncTime = startTime - 1000; // Define sections with different complexities var sections = [ {start: 0, end: 16000, complexity: 'simple'}, {start: 16000, end: 48000, complexity: 'medium'}, {start: 48000, end: 72000, complexity: 'medium'}, {start: 72000, end: 104000, complexity: 'medium'}, {start: 104000, end: 128000, complexity: 'complex'}, {start: 128000, end: 160000, complexity: 'medium'}, {start: 160000, end: 184000, complexity: 'complex'}, {start: 184000, end: 202136, complexity: 'simple'} ]; for (var s = 0; s < sections.length; s++) { var section = sections[s]; var sectionStartTime = startTime + section.start; var sectionEndTime = startTime + section.end; currentTime = Math.max(currentTime, sectionStartTime); while (currentTime < sectionEndTime - 2000) { var pattern = this.selectPattern(section.complexity, zoneFreeTime, currentTime, lastSyncTime); var patternResult = this.placePattern(pattern, currentTime, zoneFreeTime); if (patternResult.notes.length > 0) { notes = notes.concat(patternResult.notes); currentTime = patternResult.nextTime; if (pattern.type === 'sync') { lastSyncTime = currentTime; } } else { // If pattern couldn't be placed, advance time currentTime += 300; } } } return notes; }, selectPattern: function(complexity, zoneFreeTime, currentTime, lastSyncTime) { var patterns = []; var MIN_SYNC_SPACING = 800; if (complexity === 'simple') { patterns = [ {type: 'single', zone: 'left'}, {type: 'single', zone: 'right'}, {type: 'rest'} ]; // Add sync occasionally if enough time has passed if (currentTime - lastSyncTime > MIN_SYNC_SPACING) { patterns.push({type: 'sync'}); } } else if (complexity === 'medium') { patterns = [ {type: 'single', zone: 'left'}, {type: 'single', zone: 'right'}, {type: 'alternating'}, {type: 'hold', zone: 'left'}, {type: 'hold', zone: 'right'} ]; if (currentTime - lastSyncTime > MIN_SYNC_SPACING) { patterns.push({type: 'sync'}); } } else { // complex patterns = [ {type: 'single', zone: 'left'}, {type: 'single', zone: 'right'}, {type: 'alternating'}, {type: 'hold', zone: 'left'}, {type: 'hold', zone: 'right'}, {type: 'holdWithTaps', zone: 'left'}, {type: 'holdWithTaps', zone: 'right'} ]; if (currentTime - lastSyncTime > MIN_SYNC_SPACING) { patterns.push({type: 'sync'}); } } return patterns[Math.floor(Math.random() * patterns.length)]; }, placePattern: function(pattern, requestedTime, zoneFreeTime) { var notes = []; var MIN_SAME_ZONE_SPACING = 400; var MIN_DIFFERENT_ZONE_SPACING = 200; // Calculate earliest possible start time based on zone availability var earliestTime = requestedTime; if (pattern.type === 'single') { earliestTime = Math.max(earliestTime, zoneFreeTime[pattern.zone]); if (earliestTime < requestedTime + 2000) { // Don't delay too much notes.push({ time: earliestTime, type: 'tap', zone: pattern.zone }); zoneFreeTime[pattern.zone] = earliestTime + MIN_SAME_ZONE_SPACING; return {notes: notes, nextTime: earliestTime + MIN_DIFFERENT_ZONE_SPACING}; } } else if (pattern.type === 'sync') { earliestTime = Math.max(earliestTime, zoneFreeTime.left, zoneFreeTime.right); if (earliestTime < requestedTime + 2000) { notes.push({time: earliestTime, type: 'tap', zone: 'left'}); notes.push({time: earliestTime, type: 'tap', zone: 'right'}); zoneFreeTime.left = earliestTime + MIN_SAME_ZONE_SPACING; zoneFreeTime.right = earliestTime + MIN_SAME_ZONE_SPACING; return {notes: notes, nextTime: earliestTime + 600}; // Extra space after sync } } else if (pattern.type === 'alternating') { var leftTime = Math.max(earliestTime, zoneFreeTime.left); var rightTime = Math.max(leftTime + MIN_DIFFERENT_ZONE_SPACING, zoneFreeTime.right); if (rightTime < requestedTime + 2000) { notes.push({time: leftTime, type: 'tap', zone: 'left'}); notes.push({time: rightTime, type: 'tap', zone: 'right'}); zoneFreeTime.left = leftTime + MIN_SAME_ZONE_SPACING; zoneFreeTime.right = rightTime + MIN_SAME_ZONE_SPACING; return {notes: notes, nextTime: rightTime + MIN_DIFFERENT_ZONE_SPACING}; } } else if (pattern.type === 'hold') { earliestTime = Math.max(earliestTime, zoneFreeTime[pattern.zone]); var holdDuration = 1500; if (earliestTime < requestedTime + 2000) { notes.push({ time: earliestTime, type: 'hold', zone: pattern.zone, duration: holdDuration }); zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200; // Buffer after hold return {notes: notes, nextTime: earliestTime + 800}; // Can place other zone notes during hold } } else if (pattern.type === 'holdWithTaps') { var holdZone = pattern.zone; var tapZone = holdZone === 'left' ? 'right' : 'left'; var holdStart = Math.max(earliestTime, zoneFreeTime[holdZone]); var holdDuration = 2000; if (holdStart < requestedTime + 2000) { notes.push({ time: holdStart, type: 'hold', zone: holdZone, duration: holdDuration }); // Add taps in opposite zone during hold var tapTime = Math.max(holdStart + 500, zoneFreeTime[tapZone]); if (tapTime < holdStart + holdDuration - 500) { notes.push({time: tapTime, type: 'tap', zone: tapZone}); zoneFreeTime[tapZone] = tapTime + MIN_SAME_ZONE_SPACING; // Second tap tapTime = Math.max(tapTime + MIN_SAME_ZONE_SPACING, zoneFreeTime[tapZone]); if (tapTime < holdStart + holdDuration - 200) { notes.push({time: tapTime, type: 'tap', zone: tapZone}); zoneFreeTime[tapZone] = tapTime + MIN_SAME_ZONE_SPACING; } } zoneFreeTime[holdZone] = holdStart + holdDuration + 200; return {notes: notes, nextTime: holdStart + 1000}; } } else if (pattern.type === 'rest') { return {notes: [], nextTime: requestedTime + 500}; } // Pattern couldn't be placed return {notes: [], nextTime: requestedTime}; } };
User prompt
Update with: var SongGenerator = { generateSong: function(config) { var notes = []; var totalLength = config.totalLength || 202136; var startTime = config.startDelay || 2000; // Generate notes with consistent density throughout the song return this.generateDensityBasedSong(startTime, totalLength); }, generateDensityBasedSong: function(startTime, endTime) { var notes = []; var currentTime = startTime; var beatInterval = 500; // Base beat (120 BPM = 500ms per beat) // Define sections with different intensities var sections = [ {start: 0, end: 16000, intensity: 0.3, name: 'intro'}, {start: 16000, end: 48000, intensity: 0.6, name: 'verse1'}, {start: 48000, end: 72000, intensity: 0.8, name: 'chorus1'}, {start: 72000, end: 104000, intensity: 0.6, name: 'verse2'}, {start: 104000, end: 128000, intensity: 0.9, name: 'chorus2'}, {start: 128000, end: 160000, intensity: 0.7, name: 'verse3'}, {start: 160000, end: 184000, intensity: 1.0, name: 'finalchorus'}, {start: 184000, end: 202136, intensity: 0.4, name: 'outro'} ]; for (var s = 0; s < sections.length; s++) { var section = sections[s]; var sectionNotes = this.generateSection( startTime + section.start, startTime + section.end, section.intensity, beatInterval ); notes = notes.concat(sectionNotes); } return notes; }, generateSection: function(startTime, endTime, intensity, beatInterval) { var notes = []; var currentTime = startTime; // Calculate note frequency based on intensity var baseNoteInterval = beatInterval; // 500ms at intensity 1.0 var noteInterval = baseNoteInterval / intensity; // Ensure minimum spacing if (noteInterval < 250) noteInterval = 250; var patternIndex = 0; var patterns = this.getPatterns(intensity); while (currentTime < endTime - 1000) { // Leave 1s buffer at end var pattern = patterns[patternIndex % patterns.length]; var patternNotes = this.createPattern(pattern, currentTime); // Add notes if they fit in the section for (var i = 0; i < patternNotes.length; i++) { if (patternNotes[i].time < endTime - 500) { notes.push(patternNotes[i]); } } currentTime += pattern.duration; patternIndex++; // Add small random variation to prevent mechanical feeling currentTime += Math.random() * 100 - 50; // ±50ms variation } return notes; }, getPatterns: function(intensity) { if (intensity <= 0.4) { // Low intensity - simple patterns return [ {type: 'single', zone: 'left', duration: 1000}, {type: 'single', zone: 'right', duration: 1000}, {type: 'sync', duration: 1000}, {type: 'rest', duration: 1000} ]; } else if (intensity <= 0.7) { // Medium intensity return [ {type: 'alternating', duration: 1000}, {type: 'sync', duration: 500}, {type: 'single', zone: 'left', duration: 500}, {type: 'single', zone: 'right', duration: 500}, {type: 'hold', zone: 'left', duration: 2000, holdLength: 1500}, {type: 'hold', zone: 'right', duration: 2000, holdLength: 1500} ]; } else { // High intensity return [ {type: 'sync', duration: 500}, {type: 'alternating', duration: 500}, {type: 'triplet', zone: 'left', duration: 750}, {type: 'triplet', zone: 'right', duration: 750}, {type: 'sync', duration: 250}, {type: 'sync', duration: 250}, {type: 'holdWithTaps', zone: 'left', duration: 2500, holdLength: 2000} ]; } }, createPattern: function(pattern, startTime) { var notes = []; switch(pattern.type) { case 'single': notes.push({ time: startTime, type: 'tap', zone: pattern.zone }); break; case 'sync': notes.push({time: startTime, type: 'tap', zone: 'left'}); notes.push({time: startTime, type: 'tap', zone: 'right'}); break; case 'alternating': notes.push({time: startTime, type: 'tap', zone: 'left'}); notes.push({time: startTime + 250, type: 'tap', zone: 'right'}); break; case 'triplet': for (var i = 0; i < 3; i++) { notes.push({ time: startTime + i * 167, type: 'tap', zone: pattern.zone }); } break; case 'hold': notes.push({ time: startTime, type: 'hold', zone: pattern.zone, duration: pattern.holdLength || 1500 }); break; case 'holdWithTaps': // Hold note notes.push({ time: startTime, type: 'hold', zone: pattern.zone, duration: pattern.holdLength || 2000 }); // Taps in opposite zone var tapZone = pattern.zone === 'left' ? 'right' : 'left'; notes.push({time: startTime + 500, type: 'tap', zone: tapZone}); notes.push({time: startTime + 1000, type: 'tap', zone: tapZone}); break; case 'rest': // No notes - just time passage break; } return notes; } }; // Simpler song database var SongDatabase = { 'gameMusic': { bpm: 120, scannerCycleDuration: 4000, totalLength: 202136, notes: null } }; // Generate with the new system SongDatabase['gameMusic'].notes = SongGenerator.generateSong({ startDelay: 2000, totalLength: 202136 }); var defaultSongData = SongDatabase['gameMusic'];
User prompt
Replace with: // Song Generator Class - FIXED VERSION var SongGenerator = { generateSong: function(config) { var notes = []; var currentTime = config.startDelay || 2000; // Add intro section notes = notes.concat(this.generateIntro(currentTime)); currentTime += config.introLength || 16000; // Add verse sections with smooth transitions for (var v = 0; v < (config.verseCount || 3); v++) { notes = notes.concat(this.generateVerse(currentTime, v)); currentTime += config.verseLength || 32000; // Add chorus after each verse notes = notes.concat(this.generateChorus(currentTime, v)); currentTime += config.chorusLength || 24000; } // Add bridge section notes = notes.concat(this.generateBridge(currentTime)); currentTime += config.bridgeLength || 16000; // Final chorus notes = notes.concat(this.generateFinalChorus(currentTime)); currentTime += config.finalChorusLength || 32000; // Add outro notes = notes.concat(this.generateOutro(currentTime, config.totalLength)); // Sort notes by time and remove conflicts return this.cleanupNotes(notes); }, generateIntro: function(startTime) { var notes = []; var time = startTime; // Simple alternating pattern - FIXED for (var i = 0; i < 8; i++) { if (i % 2 === 0) { notes = notes.concat(PatternTemplates.tapLeft(time)); } else { notes = notes.concat(PatternTemplates.tapRight(time)); } time += 1000; } // Sync taps with spacing notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; // Build up notes = notes.concat(PatternTemplates.buildUp(time)); return notes; }, generateVerse: function(startTime, verseNumber) { var notes = []; var time = startTime; // More controlled verse pattern for (var section = 0; section < 8; section++) { if (section % 4 === 0) { // Sync tap every 4 beats notes = notes.concat(PatternTemplates.syncTap(time)); time += 1000; } else if (section % 4 === 1) { // Alternating taps notes = notes.concat(PatternTemplates.leftRightTaps(time, 500)); time += 1000; } else if (section % 4 === 2) { // Single zone hold var holdZone = verseNumber % 2 === 0 ? 'left' : 'right'; if (holdZone === 'left') { notes = notes.concat(PatternTemplates.holdLeft(time, 1500)); } else { notes = notes.concat(PatternTemplates.holdRight(time, 1500)); } time += 2000; } else { // Rest beat time += 1000; } } return notes; }, generateChorus: function(startTime, chorusNumber) { var notes = []; var time = startTime; // More structured chorus for (var section = 0; section < 6; section++) { if (section % 3 === 0) { // Double sync taps notes = notes.concat(PatternTemplates.syncTap(time)); time += 500; notes = notes.concat(PatternTemplates.syncTap(time)); time += 1500; } else if (section % 3 === 1) { // Triplets in alternating zones var zone = section % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.tripletTaps(time, zone, 167)); time += 1000; } else { // Single hold with opposite taps var holdZone = chorusNumber % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.holdWithTaps(time, holdZone, 1500)); time += 2000; } } return notes; }, generateBridge: function(startTime) { var notes = []; var time = startTime; // Controlled bridge pattern // Long sync hold notes = notes.concat(PatternTemplates.syncHold(time, 2000)); time += 3000; // Alternating pattern for (var i = 0; i < 4; i++) { notes = notes.concat(PatternTemplates.alternatingTriplets(time, 167)); time += 1000; } // Final holds notes = notes.concat(PatternTemplates.holdLeft(time, 2000)); time += 3000; notes = notes.concat(PatternTemplates.holdRight(time, 2000)); return notes; }, generateFinalChorus: function(startTime) { var notes = []; var time = startTime; // Intense but controlled final section for (var section = 0; section < 8; section++) { if (section % 2 === 0) { // Sync pattern notes = notes.concat(PatternTemplates.syncTap(time)); time += 750; notes = notes.concat(PatternTemplates.syncTap(time)); time += 1250; } else { // Alternating fast taps notes = notes.concat(PatternTemplates.leftRightTaps(time, 250)); time += 500; notes = notes.concat(PatternTemplates.rightLeftTaps(time, 250)); time += 1500; } } return notes; }, generateOutro: function(startTime, totalLength) { var notes = []; var time = startTime; var endTime = totalLength || 202136; // Gradual wind-down while (time < endTime - 6000) { notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; } // Final hold if (time < endTime - 3000) { notes = notes.concat(PatternTemplates.syncHold(time, 2000)); } return notes; }, // NEW: Cleanup function to prevent conflicts cleanupNotes: function(notes) { // Sort by time notes.sort(function(a, b) { return a.time - b.time; }); var cleanNotes = []; var lastLeftTime = -1000; var lastRightTime = -1000; var activeHolds = { left: null, right: null }; for (var i = 0; i < notes.length; i++) { var note = notes[i]; var zone = note.zone; var minSpacing = note.type === 'tap' ? 200 : 500; // Minimum time between notes // Check for timing conflicts var lastTime = zone === 'left' ? lastLeftTime : lastRightTime; if (note.time - lastTime < minSpacing) { continue; // Skip this note } // Check for hold conflicts var activeHold = activeHolds[zone]; if (activeHold && note.time < activeHold.endTime) { continue; // Skip notes during active holds } // Add the note cleanNotes.push(note); // Update tracking if (zone === 'left') { lastLeftTime = note.time; } else { lastRightTime = note.time; } // Track hold notes if (note.type === 'hold') { activeHolds[zone] = { endTime: note.time + (note.duration || 1000) }; } } return cleanNotes; } };
User prompt
Add this system: /**** * Song Pattern System ****/ // Pattern Templates - reusable note patterns var PatternTemplates = { // Basic patterns tapLeft: function(startTime) { return [{time: startTime, type: 'tap', zone: 'left'}]; }, tapRight: function(startTime) { return [{time: startTime, type: 'tap', zone: 'right'}]; }, syncTap: function(startTime) { return [ {time: startTime, type: 'tap', zone: 'left'}, {time: startTime, type: 'tap', zone: 'right'} ]; }, // Alternating patterns leftRightTaps: function(startTime, spacing) { spacing = spacing || 500; // Default to beat spacing return [ {time: startTime, type: 'tap', zone: 'left'}, {time: startTime + spacing, type: 'tap', zone: 'right'} ]; }, rightLeftTaps: function(startTime, spacing) { spacing = spacing || 500; return [ {time: startTime, type: 'tap', zone: 'right'}, {time: startTime + spacing, type: 'tap', zone: 'left'} ]; }, // Hold patterns holdLeft: function(startTime, duration) { duration = duration || 1000; return [{time: startTime, type: 'hold', zone: 'left', duration: duration}]; }, holdRight: function(startTime, duration) { duration = duration || 1000; return [{time: startTime, type: 'hold', zone: 'right', duration: duration}]; }, syncHold: function(startTime, duration) { duration = duration || 1000; return [ {time: startTime, type: 'hold', zone: 'left', duration: duration}, {time: startTime, type: 'hold', zone: 'right', duration: duration} ]; }, // Complex patterns tripletTaps: function(startTime, zone, spacing) { spacing = spacing || 167; // Triplet timing return [ {time: startTime, type: 'tap', zone: zone}, {time: startTime + spacing, type: 'tap', zone: zone}, {time: startTime + spacing * 2, type: 'tap', zone: zone} ]; }, alternatingTriplets: function(startTime, spacing) { spacing = spacing || 167; return [ {time: startTime, type: 'tap', zone: 'left'}, {time: startTime + spacing, type: 'tap', zone: 'right'}, {time: startTime + spacing * 2, type: 'tap', zone: 'left'} ]; }, buildUp: function(startTime) { return [ {time: startTime, type: 'tap', zone: 'left'}, {time: startTime + 250, type: 'tap', zone: 'right'}, {time: startTime + 500, type: 'tap', zone: 'left'}, {time: startTime + 625, type: 'tap', zone: 'right'}, {time: startTime + 750, type: 'tap', zone: 'left'}, {time: startTime + 875, type: 'tap', zone: 'right'} ]; }, holdWithTaps: function(startTime, holdZone, holdDuration) { var tapZone = holdZone === 'left' ? 'right' : 'left'; holdDuration = holdDuration || 2000; return [ {time: startTime, type: 'hold', zone: holdZone, duration: holdDuration}, {time: startTime + 500, type: 'tap', zone: tapZone}, {time: startTime + 1000, type: 'tap', zone: tapZone}, {time: startTime + 1500, type: 'tap', zone: tapZone} ]; } }; // Song Generator Class var SongGenerator = { generateSong: function(config) { var notes = []; var currentTime = config.startDelay || 2000; // Start after 2 seconds // Add intro section notes = notes.concat(this.generateIntro(currentTime)); currentTime += config.introLength || 16000; // Add verse sections for (var v = 0; v < (config.verseCount || 3); v++) { notes = notes.concat(this.generateVerse(currentTime, v)); currentTime += config.verseLength || 32000; // Add chorus after each verse notes = notes.concat(this.generateChorus(currentTime, v)); currentTime += config.chorusLength || 24000; } // Add bridge section notes = notes.concat(this.generateBridge(currentTime)); currentTime += config.bridgeLength || 16000; // Final chorus with variations notes = notes.concat(this.generateFinalChorus(currentTime)); currentTime += config.finalChorusLength || 32000; // Add outro notes = notes.concat(this.generateOutro(currentTime, config.totalLength)); return notes; }, generateIntro: function(startTime) { var notes = []; var time = startTime; // Simple alternating taps to start for (var i = 0; i < 8; i++) { var zone = i % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.tapLeft(time)); time += 1000; // Every beat } // Add some sync taps notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; // Build up pattern notes = notes.concat(PatternTemplates.buildUp(time)); return notes; }, generateVerse: function(startTime, verseNumber) { var notes = []; var time = startTime; // Verse pattern: alternating with some holds for (var section = 0; section < 4; section++) { // 4-beat alternating pattern notes = notes.concat(PatternTemplates.leftRightTaps(time, 500)); time += 1000; notes = notes.concat(PatternTemplates.rightLeftTaps(time, 500)); time += 1000; // Add a hold every other section if (section % 2 === 1) { var holdZone = verseNumber % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.holdLeft(time, 1500)); time += 2000; } else { // Sync tap notes = notes.concat(PatternTemplates.syncTap(time)); time += 1000; // Rest time += 1000; } } return notes; }, generateChorus: function(startTime, chorusNumber) { var notes = []; var time = startTime; // Chorus is more intense with sync patterns for (var section = 0; section < 3; section++) { // Sync taps notes = notes.concat(PatternTemplates.syncTap(time)); time += 1000; // Triplet pattern var zone = section % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.tripletTaps(time, zone, 167)); time += 1000; // Hold with taps var holdZone = chorusNumber % 2 === 0 ? 'left' : 'right'; notes = notes.concat(PatternTemplates.holdWithTaps(time, holdZone, 2000)); time += 3000; // Build up notes = notes.concat(PatternTemplates.buildUp(time)); time += 1000; } return notes; }, generateBridge: function(startTime) { var notes = []; var time = startTime; // Bridge: Different rhythm, more complex // Sync holds notes = notes.concat(PatternTemplates.syncHold(time, 2000)); time += 3000; // Alternating triplets for (var i = 0; i < 4; i++) { notes = notes.concat(PatternTemplates.alternatingTriplets(time)); time += 1000; } // Long holds with opposite taps notes = notes.concat(PatternTemplates.holdWithTaps(time, 'left', 3000)); time += 4000; notes = notes.concat(PatternTemplates.holdWithTaps(time, 'right', 3000)); return notes; }, generateFinalChorus: function(startTime) { var notes = []; var time = startTime; // Most intense section for (var section = 0; section < 4; section++) { // Double sync taps notes = notes.concat(PatternTemplates.syncTap(time)); time += 500; notes = notes.concat(PatternTemplates.syncTap(time)); time += 500; // Fast alternating for (var i = 0; i < 4; i++) { var zone = i % 2 === 0 ? 'left' : 'right'; notes.push({time: time, type: 'tap', zone: zone}); time += 250; } // Sync hold notes = notes.concat(PatternTemplates.syncHold(time, 1500)); time += 2000; // Build up notes = notes.concat(PatternTemplates.buildUp(time)); time += 1500; } return notes; }, generateOutro: function(startTime, totalLength) { var notes = []; var time = startTime; var endTime = totalLength || 202136; // Simple pattern to end while (time < endTime - 4000) { notes = notes.concat(PatternTemplates.syncTap(time)); time += 2000; } // Final sync hold if (time < endTime - 2000) { notes = notes.concat(PatternTemplates.syncHold(time, Math.min(2000, endTime - time))); } return notes; } }; // Song Database var SongDatabase = { 'gameMusic': { bpm: 120, scannerCycleDuration: 4000, totalLength: 202136, notes: null // Will be generated } }; // Generate the song pattern SongDatabase['gameMusic'].notes = SongGenerator.generateSong({ startDelay: 2000, introLength: 16000, verseCount: 3, verseLength: 32000, chorusLength: 24000, bridgeLength: 16000, finalChorusLength: 32000, totalLength: 202136 }); // Update the defaultSongData to use the generated pattern var defaultSongData = SongDatabase['gameMusic']; // Helper function to add new songs easily function addSong(songId, config) { SongDatabase[songId] = { bpm: config.bpm || 120, scannerCycleDuration: config.scannerCycleDuration || 4000, totalLength: config.totalLength, notes: config.customNotes || SongGenerator.generateSong(config) }; } // Example of adding a new song: /* addSong('newSong', { bpm: 140, totalLength: 180000, // 3 minutes startDelay: 1500, verseCount: 2, verseLength: 30000 }); */
User prompt
Remove tap feedback altogether.
User prompt
Remove tap feed back indicators
User prompt
Remove flick notes. Make all notes tap or hold.
User prompt
Remove player health and all associated functions from the game.
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'if (heldNote && heldNote.isHolding) {' Line Number: 895
User prompt
I’d like to make flick notes show an indication of which flick direction is required to “hit” the note and check the travel in game.up. Direction can be up down left or right. Also let’s remove the secondary note and the particle trail on the finger.
User prompt
Hold notes should not be removed until player stops holding at the end note. If the player doesn’t hold all the way then the note is a miss. Add particle effects around connection while holding. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix held notes so that they show a connecting line between the start and end note. Currently I only see one note and not the end and no connecting.
User prompt
double note and note glow size
User prompt
update with: self.showHitEffect = function () { LK.getSound('hitSound').play(); self.explodeIntoParticles(); // Clean up end note and glow for hold notes if (self.endNote && self.endNote.destroy) { self.endNote.destroy(); } if (self.endGlow && self.endGlow.destroy) { self.endGlow.destroy(); } tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.active = false; } }); }; ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
update with: self.createHoldTrail = function () { // Determine scanner direction at hit time var cycleTime = self.hitTime % SCANNER_CYCLE_DURATION; var scannerMovingUp = cycleTime < SCANNER_HALF_CYCLE; // Make trail shorter - reduced from duration/4*100 to duration/6*60 var trailLength = self.duration / 6 * 60; // Much shorter trail var direction = scannerMovingUp ? -1 : 1; // -1 for up, 1 for down var segments = Math.max(1, Math.floor(trailLength / 15)); // Smaller segments for (var i = 0; i < segments; i++) { var trail = self.attachAsset('holdTrail', { anchorX: 0.5, anchorY: 0 }); trail.tint = self.color; trail.alpha = 0.6 - (i * 0.1); // Fade out as it goes further trail.y = i * 15 * direction; // Follow scanner direction trail.height = 15; self.holdTrails.push(trail); } // Add end indicator note self.endNote = self.attachAsset('noteCore', { anchorX: 0.5, anchorY: 0.5 }); self.endNote.tint = self.color; self.endNote.alpha = 0.8; self.endNote.scaleX = 0.7; self.endNote.scaleY = 0.7; self.endNote.y = (segments * 15 * direction); // Position at end of trail // Add a subtle glow to the end note self.endGlow = self.attachAsset('glowRing', { anchorX: 0.5, anchorY: 0.5 }); self.endGlow.tint = self.color; self.endGlow.alpha = 0.2; self.endGlow.scaleX = 0.5; self.endGlow.scaleY = 0.5; self.endGlow.y = (segments * 15 * direction); };
User prompt
Please fix the bug: 'TypeError: setTimeout is not a function' in or related to this line: 'setTimeout(function (p, fx, fy) {' Line Number: 154
User prompt
Please fix the bug: 'TypeError: self.createHoldTrail is not a function' in or related to this line: 'self.createHoldTrail();' Line Number: 72
User prompt
Please fix the bug: 'setInterval is not a function' in or related to this line: 'var countdownInterval = setInterval(function () {' Line Number: 462
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: self.createHoldTrail is not a function' in or related to this line: 'self.createHoldTrail();' Line Number: 72
User prompt
Please fix the bug: 'setInterval is not a function' in or related to this line: 'var countdownInterval = setInterval(function () {' Line Number: 476
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Note = Container.expand(function (noteData, spawnTime) { var self = Container.call(this); // Note properties self.noteData = noteData; self.type = noteData.type; // 'tap', 'hold', 'flick' self.targetX = noteData.x; self.targetY = noteData.y; self.color = noteData.color || 0x00ffff; self.hitTime = noteData.time; self.duration = noteData.duration || 0; // For hold notes // self.flickTarget = noteData.flickTarget || null; // Removed self.spawnTime = spawnTime; self.zone = noteData.x < GAME_WIDTH / 2 ? 'left' : 'right'; // Determine zone // self.requiredFlickDirection = noteData.flickDirection || null; // Removed // State self.active = true; self.isHit = false; self.isMissed = false; self.isSpawning = true; self.isHolding = false; // True if player is currently holding this note self.holdSuccessfullyCompleted = false; // True if hold was completed successfully self.holdParticles = []; // Stores active connection particles for this hold note self.lastParticleEmissionTime = 0; // For throttling particle emission self.PARTICLE_EMISSION_INTERVAL = 50; // Emit particles every 50ms during hold // self.isHolding = false; // For hold notes // Duplicate line, already declared above self.holdStarted = false; // Visual components var assetName = self.type === 'hold' ? 'holdNoteCore' : 'noteCore'; // Removed flickNoteCore self.noteGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.noteGraphics.tint = self.color; // self.flickDirectionIndicator logic removed self.glowRing = self.attachAsset('glowRing', { anchorX: 0.5, anchorY: 0.5 }); self.glowRing.tint = self.color; self.glowRing.alpha = 0.3; // Position self.x = self.targetX; self.y = self.targetY; // Start invisible and scale up self.alpha = 0; self.scaleX = 0.1; self.scaleY = 0.1; // Define createHoldTrail method first self.createHoldTrail = function () { // Determine scanner direction at hit time var cycleTime = self.hitTime % SCANNER_CYCLE_DURATION; var scannerMovingUp = cycleTime < SCANNER_HALF_CYCLE; var direction = scannerMovingUp ? -1 : 1; // -1 for up, 1 for down // Calculate actual trail length in pixels based on duration and scanner speed var actualTrailPixelLength = self.duration / SCANNER_HALF_CYCLE * PLAY_AREA_HEIGHT; if (actualTrailPixelLength < 0) actualTrailPixelLength = 0; // Ensure length is not negative // Create the main connecting trail line var trailLine = self.attachAsset('holdTrail', { anchorX: 0.5, anchorY: 0 // Anchor at the top of the trail asset }); trailLine.tint = self.color; trailLine.alpha = 0.6; trailLine.width = 20; // Set a visible width for the trail line trailLine.height = actualTrailPixelLength; if (direction === -1) { // Trail goes upwards from the note trailLine.y = -actualTrailPixelLength; } else { // Trail goes downwards from the note trailLine.y = 0; // AnchorY is 0, so top of trailLine is at note's y } self.holdTrails.push(trailLine); // Add end indicator note, positioned relative to the start note self.endNote = self.attachAsset('noteCore', { anchorX: 0.5, anchorY: 0.5 }); self.endNote.tint = self.color; self.endNote.alpha = 0.8; self.endNote.scaleX = 0.7; self.endNote.scaleY = 0.7; self.endNote.y = actualTrailPixelLength * direction; // Add a subtle glow to the end note, positioned with the end note self.endGlow = self.attachAsset('glowRing', { anchorX: 0.5, anchorY: 0.5 }); self.endGlow.tint = self.color; self.endGlow.alpha = 0.2; self.endGlow.scaleX = 0.5; self.endGlow.scaleY = 0.5; self.endGlow.y = actualTrailPixelLength * direction; }; // Hold trail for hold notes self.holdTrails = []; if (self.type === 'hold' && self.duration > 0) { self.createHoldTrail(); } self.spawnIn = function () { self.isSpawning = true; tween(self, { alpha: 1, scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { self.isSpawning = false; } }); }; self.showHitEffect = function () { // Primarily for TAP notes now LK.getSound('hitSound').play(); self.explodeIntoParticles(); // For tap notes, there's no endNote or endGlow to clean typically. // This method makes the note inactive. tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 200, onFinish: function onFinish() { self.active = false; } }); }; self.startHoldEffect = function () { // Called for HOLD note initial interaction LK.getSound('hitSound').play(); // Visual cue for hold start, e.g., a slight pulse. Note remains active. tween(self.noteGraphics, { scaleX: 1.2, scaleY: 1.2 }, { duration: 100, onFinish: function onFinish() { tween(self.noteGraphics, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); // EndNote and EndGlow MUST remain visible. Note itself is not deactivated here. }; // self.showFlickEffect method removed. // self.createFlickTrail method is now obsolete and removed. self.showMissEffect = function () { LK.getSound('missSound').play(); // Flick direction indicator cleanup removed tween(self.noteGraphics, { tint: 0xff0000 }, { duration: 150, onFinish: function onFinish() { tween(self, { alpha: 0.3 }, { duration: 300, onFinish: function onFinish() { self.active = false; } }); } }); }; self.explodeIntoParticles = function () { // Flick direction indicator cleanup removed var particleCount = 12; for (var i = 0; i < particleCount; i++) { var particle = game.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); particle.tint = self.color; particle.x = self.x; particle.y = self.y; var angle = i / particleCount * Math.PI * 2; var speed = 150 + Math.random() * 100; var targetX = self.x + Math.cos(angle) * speed; var targetY = self.y + Math.sin(angle) * speed; tween(particle, { x: targetX, y: targetY, alpha: 0, scaleX: 0.3, scaleY: 0.3 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { if (particle && particle.destroy) { particle.destroy(); } } }); } }; self.updateHoldEffects = function (scannerCurrentY) { if (self.type === 'hold' && self.isHolding && self.active && !self.holdSuccessfullyCompleted && !self.isMissed) { var currentTime = Date.now(); if (currentTime - self.lastParticleEmissionTime > self.PARTICLE_EMISSION_INTERVAL) { self.lastParticleEmissionTime = currentTime; var trailStartY = self.y; var trailEndY = self.y + (self.endNote ? self.endNote.y : 0); // endNote.y is relative var scannerIsOverTrail = self.endNote && scannerCurrentY >= Math.min(trailStartY, trailEndY) && scannerCurrentY <= Math.max(trailStartY, trailEndY); if (scannerIsOverTrail) { var particle = game.attachAsset('holdConnectionParticle', { anchorX: 0.5, anchorY: 0.5 }); particle.x = self.x; particle.y = scannerCurrentY; particle.tint = self.color; particle.alpha = 0.8; self.holdParticles.push(particle); tween(particle, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { if (particle && particle.destroy) particle.destroy(); var index = self.holdParticles.indexOf(particle); if (index > -1) self.holdParticles.splice(index, 1); } }); } } } }; self.cleanupHoldVisuals = function () { self.holdParticles.forEach(function (p) { if (p && p.destroy) p.destroy(); }); self.holdParticles = []; if (self.endNote && self.endNote.destroy) self.endNote.destroy(); if (self.endGlow && self.endGlow.destroy) self.endGlow.destroy(); self.endNote = null; self.endGlow = null; self.holdTrails.forEach(function (trail) { if (trail && trail.destroy) trail.destroy(); }); self.holdTrails = []; }; self.completeHold = function () { if (self.holdSuccessfullyCompleted || self.isMissed) return; self.holdSuccessfullyCompleted = true; self.isHolding = false; self.explodeIntoParticles(); // Main note explosion self.cleanupHoldVisuals(); // Clean up trail, end note, particles // The main note (self) will become inactive due to explodeIntoParticles tween or explicitly here tween(self, { alpha: 0 }, { duration: 100, delay: 100, onFinish: function onFinish() { self.active = false; } }); }; self.failHold = function () { if (self.isMissed || self.holdSuccessfullyCompleted) return; self.isMissed = true; self.isHolding = false; self.showMissEffect(); // Main note shows miss effect self.cleanupHoldVisuals(); // Clean up trail, end note, particles // self.active will be set to false by showMissEffect's tween's onFinish }; return self; }); var Scanner = Container.expand(function () { var self = Container.call(this); self.glow = self.attachAsset('scannerGlow', { anchorX: 0, anchorY: 0.5 }); self.glow.alpha = 0.3; self.line = self.attachAsset('scannerLine', { anchorX: 0, anchorY: 0.5 }); self.x = 0; self.isMovingUp = true; // Start moving up return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game Constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BPM = 120; var BEAT_DURATION_MS = 60 / BPM * 1000; var SCANNER_Y_MIN = 200; var SCANNER_Y_MAX = GAME_HEIGHT - 300; var PLAY_AREA_HEIGHT = SCANNER_Y_MAX - SCANNER_Y_MIN; var HIT_TOLERANCE_PX = 80; var TARGET_SCORE_TO_WIN = 1500; var NOTE_SPAWN_AHEAD_MS = 800; // Reduced to 800ms to prevent mass spawning var SCANNER_CYCLE_DURATION = 4000; var SCANNER_HALF_CYCLE = SCANNER_CYCLE_DURATION / 2; var HIT_TOLERANCE_PX_FOR_HOLD_END = 100; // Tolerance for releasing hold note at the end var HOLD_END_GRACE_PERIOD_MS = 250; // Grace period for hold note release time vs scanner position // var MIN_FLICK_DISTANCE = 75; // Removed // Game State Variables var activeHoldNotes = {}; // Stores the currently active hold note, keyed by 'left' or 'right' zone // var activeFlickPrimer = null; // Removed var scoreTxt; var countdownTxt; var scanner; var scannerIsMovingUp = true; var gameStartTime; var gameStarted = false; var notes = []; var currentSongData; var spawnedNotes = []; // Using array instead of Set var leftZone, rightZone; var isDragging = false; var dragStartX, dragStartY; var currentTrail = []; // Color palette for notes var NOTE_COLORS = [0x00ffff, // Cyan 0xff00ff, // Magenta 0xffff00, // Yellow 0x00ff00, // Green 0xff8800, // Orange 0x8800ff // Purple ]; // Song Pattern System // Pattern Templates - reusable note patterns var PatternTemplates = { // Basic patterns tapLeft: function tapLeft(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }]; }, tapRight: function tapRight(startTime) { return [{ time: startTime, type: 'tap', zone: 'right' }]; }, syncTap: function syncTap(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime, type: 'tap', zone: 'right' }]; }, // Alternating patterns leftRightTaps: function leftRightTaps(startTime, spacing) { spacing = spacing || 500; // Default to beat spacing return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + spacing, type: 'tap', zone: 'right' }]; }, rightLeftTaps: function rightLeftTaps(startTime, spacing) { spacing = spacing || 500; return [{ time: startTime, type: 'tap', zone: 'right' }, { time: startTime + spacing, type: 'tap', zone: 'left' }]; }, // Hold patterns holdLeft: function holdLeft(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'left', duration: duration }]; }, holdRight: function holdRight(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'right', duration: duration }]; }, syncHold: function syncHold(startTime, duration) { duration = duration || 1000; return [{ time: startTime, type: 'hold', zone: 'left', duration: duration }, { time: startTime, type: 'hold', zone: 'right', duration: duration }]; }, // Complex patterns tripletTaps: function tripletTaps(startTime, zone, spacing) { spacing = spacing || 167; // Triplet timing return [{ time: startTime, type: 'tap', zone: zone }, { time: startTime + spacing, type: 'tap', zone: zone }, { time: startTime + spacing * 2, type: 'tap', zone: zone }]; }, alternatingTriplets: function alternatingTriplets(startTime, spacing) { spacing = spacing || 167; return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + spacing, type: 'tap', zone: 'right' }, { time: startTime + spacing * 2, type: 'tap', zone: 'left' }]; }, buildUp: function buildUp(startTime) { return [{ time: startTime, type: 'tap', zone: 'left' }, { time: startTime + 250, type: 'tap', zone: 'right' }, { time: startTime + 500, type: 'tap', zone: 'left' }, { time: startTime + 625, type: 'tap', zone: 'right' }, { time: startTime + 750, type: 'tap', zone: 'left' }, { time: startTime + 875, type: 'tap', zone: 'right' }]; }, holdWithTaps: function holdWithTaps(startTime, holdZone, holdDuration) { var tapZone = holdZone === 'left' ? 'right' : 'left'; holdDuration = holdDuration || 2000; return [{ time: startTime, type: 'hold', zone: holdZone, duration: holdDuration }, { time: startTime + 500, type: 'tap', zone: tapZone }, { time: startTime + 1000, type: 'tap', zone: tapZone }, { time: startTime + 1500, type: 'tap', zone: tapZone }]; } }; var SongGenerator = { generateSong: function generateSong(config) { var notes = []; var totalLength = config.totalLength || 202136; var startTime = config.startDelay || 2000; notes = this.generateZoneAwareSong(startTime, totalLength); notes.sort(function (a, b) { return a.time - b.time; }); // Ensure notes are sorted return notes; }, generateZoneAwareSong: function generateZoneAwareSong(startTime, songTotalLength) { var notes = []; var currentTime = startTime; // Track when each zone will be free var zoneFreeTime = { left: startTime, right: startTime }; // Minimum spacing between notes (human reaction time) var MIN_SAME_ZONE_SPACING = 400; // 400ms minimum in same zone var MIN_DIFFERENT_ZONE_SPACING = 200; // 200ms minimum between different zones var MIN_SYNC_SPACING = 800; // 800ms minimum between sync notes var lastSyncTime = startTime - MIN_SYNC_SPACING; // Initialize to allow early sync // Define sections with different complexities var sections = [{ start: 0, end: 16000, complexity: 'simple' }, { start: 16000, end: 48000, complexity: 'medium' }, { start: 48000, end: 72000, complexity: 'medium' }, // Original: chorus1, intensity 0.8 { start: 72000, end: 104000, complexity: 'medium' }, { start: 104000, end: 128000, complexity: 'complex' }, // Original: chorus2, intensity 0.9 { start: 128000, end: 160000, complexity: 'medium' }, { start: 160000, end: 184000, complexity: 'complex' }, // Original: finalchorus, intensity 1.0 { start: 184000, end: songTotalLength, complexity: 'simple' } // Ensure it goes to the end ]; for (var s = 0; s < sections.length; s++) { var section = sections[s]; var sectionStartTime = startTime + section.start; var sectionEndTime = startTime + section.end; if (sectionEndTime > startTime + songTotalLength) { sectionEndTime = startTime + songTotalLength; } if (sectionStartTime >= sectionEndTime) continue; currentTime = Math.max(currentTime, sectionStartTime); while (currentTime < sectionEndTime - 1000) { // Leave 1s buffer at end of section var pattern = this.selectPattern(section.complexity, zoneFreeTime, currentTime, lastSyncTime); var patternResult = this.placePattern(pattern, currentTime, zoneFreeTime, MIN_SAME_ZONE_SPACING, MIN_DIFFERENT_ZONE_SPACING, sectionEndTime); if (patternResult.notes.length > 0) { var allNotesFit = true; for (var ni = 0; ni < patternResult.notes.length; ni++) { if (patternResult.notes[ni].time + (patternResult.notes[ni].duration || 0) > sectionEndTime - 200) { // Check end time allNotesFit = false; break; } } if (allNotesFit) { notes = notes.concat(patternResult.notes); currentTime = patternResult.nextTime; if (pattern.type === 'sync') { lastSyncTime = patternResult.notes[0].time; // Time of the sync notes themselves } } else { currentTime += 300; // Advance time if pattern didn't fit } } else { currentTime += 300; // If pattern couldn't be placed, advance time } if (currentTime >= sectionEndTime - 1000) break; } } return notes; }, selectPattern: function selectPattern(complexity, zoneFreeTime, currentTime, lastSyncTime) { var patterns = []; var MIN_SYNC_SPACING = 800; // Local constant for clarity if (complexity === 'simple') { patterns = [{ type: 'single', zone: 'left', duration: 500 }, { type: 'single', zone: 'right', duration: 500 }, { type: 'rest', duration: 1000 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) { patterns.push({ type: 'sync', duration: 600 }); } } else if (complexity === 'medium') { patterns = [{ type: 'single', zone: 'left', duration: 400 }, { type: 'single', zone: 'right', duration: 400 }, { type: 'alternating', duration: 600 }, // two taps, 250ms apart { type: 'hold', zone: 'left', holdDuration: 1500, duration: 800 }, { type: 'hold', zone: 'right', holdDuration: 1500, duration: 800 }, { type: 'rest', duration: 800 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) { patterns.push({ type: 'sync', duration: 600 }); } } else { // complex patterns = [{ type: 'single', zone: 'left', duration: 300 }, { type: 'single', zone: 'right', duration: 300 }, { type: 'alternating', duration: 500 }, // two taps, 250ms apart { type: 'hold', zone: 'left', holdDuration: 1500, duration: 800 }, { type: 'hold', zone: 'right', holdDuration: 1500, duration: 800 }, { type: 'holdWithTaps', zone: 'left', holdDuration: 2000, duration: 1000 }, { type: 'holdWithTaps', zone: 'right', holdDuration: 2000, duration: 1000 }]; if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) { patterns.push({ type: 'sync', duration: 500 }); // Shorter duration for sync in complex sections } } return patterns[Math.floor(Math.random() * patterns.length)]; }, placePattern: function placePattern(pattern, requestedTime, zoneFreeTime, minSameZoneSpacing, minDiffZoneSpacing, sectionEndTime) { var notes = []; var earliestTime = requestedTime; var nextTimeAdvance = pattern.duration || 500; // Default advancement if (pattern.type === 'single') { earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]); if (earliestTime + (pattern.duration || 0) < sectionEndTime) { notes.push({ time: earliestTime, type: 'tap', zone: pattern.zone }); zoneFreeTime[pattern.zone] = earliestTime + minSameZoneSpacing; return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; } } else if (pattern.type === 'sync') { earliestTime = Math.max(requestedTime, zoneFreeTime.left, zoneFreeTime.right); if (earliestTime + (pattern.duration || 0) < sectionEndTime) { notes.push({ time: earliestTime, type: 'tap', zone: 'left' }); notes.push({ time: earliestTime, type: 'tap', zone: 'right' }); zoneFreeTime.left = earliestTime + minSameZoneSpacing; zoneFreeTime.right = earliestTime + minSameZoneSpacing; return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; } } else if (pattern.type === 'alternating') { // Attempt Left then Right var firstZone = Math.random() < 0.5 ? 'left' : 'right'; var secondZone = firstZone === 'left' ? 'right' : 'left'; var t1 = Math.max(requestedTime, zoneFreeTime[firstZone]); var t2 = Math.max(t1 + minDiffZoneSpacing, zoneFreeTime[secondZone]); if (t2 + (pattern.duration || 0) - (t2 - t1) < sectionEndTime) { // check if second note fits notes.push({ time: t1, type: 'tap', zone: firstZone }); notes.push({ time: t2, type: 'tap', zone: secondZone }); zoneFreeTime[firstZone] = t1 + minSameZoneSpacing; zoneFreeTime[secondZone] = t2 + minSameZoneSpacing; return { notes: notes, nextTime: t2 + minDiffZoneSpacing }; // Next logical placement time } } else if (pattern.type === 'hold') { earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]); var holdDuration = pattern.holdDuration || 1500; if (earliestTime + holdDuration < sectionEndTime) { notes.push({ time: earliestTime, type: 'hold', zone: pattern.zone, duration: holdDuration }); zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200; // Buffer after hold return { notes: notes, nextTime: earliestTime + nextTimeAdvance }; // Pattern's own duration logic } } else if (pattern.type === 'holdWithTaps') { var holdZone = pattern.zone; var tapZone = holdZone === 'left' ? 'right' : 'left'; var holdStart = Math.max(requestedTime, zoneFreeTime[holdZone]); var mainHoldDuration = pattern.holdDuration || 2000; if (holdStart + mainHoldDuration < sectionEndTime) { notes.push({ time: holdStart, type: 'hold', zone: holdZone, duration: mainHoldDuration }); zoneFreeTime[holdZone] = holdStart + mainHoldDuration + 200; var tap1Time = Math.max(holdStart + 500, zoneFreeTime[tapZone]); if (tap1Time < holdStart + mainHoldDuration - 500 && tap1Time + minSameZoneSpacing < sectionEndTime) { notes.push({ time: tap1Time, type: 'tap', zone: tapZone }); zoneFreeTime[tapZone] = tap1Time + minSameZoneSpacing; var tap2Time = Math.max(tap1Time + minSameZoneSpacing, zoneFreeTime[tapZone]); // Ensure tap2 is after tap1 is free if (tap2Time < holdStart + mainHoldDuration - 200 && tap2Time + minSameZoneSpacing < sectionEndTime) { notes.push({ time: tap2Time, type: 'tap', zone: tapZone }); zoneFreeTime[tapZone] = tap2Time + minSameZoneSpacing; } } return { notes: notes, nextTime: holdStart + nextTimeAdvance }; } } else if (pattern.type === 'rest') { return { notes: [], nextTime: requestedTime + (pattern.duration || 500) }; } return { notes: [], nextTime: requestedTime + 100 }; // Failed to place, small advance } }; // Simpler song database var SongDatabase = { 'gameMusic': { bpm: 120, scannerCycleDuration: 4000, totalLength: 202136, notes: null } }; // Generate with the new system SongDatabase['gameMusic'].notes = SongGenerator.generateSong({ startDelay: 2000, totalLength: SongDatabase['gameMusic'].totalLength }); var defaultSongData = SongDatabase['gameMusic']; // Helper function to add new songs easily function addSong(songId, config) { SongDatabase[songId] = { bpm: config.bpm || 120, scannerCycleDuration: config.scannerCycleDuration || 4000, totalLength: config.totalLength, notes: config.customNotes || SongGenerator.generateSong(config) }; } // Example of adding a new song: /* addSong('newSong', { bpm: 140, totalLength: 180000, // 3 minutes startDelay: 1500, verseCount: 2, verseLength: 30000 }); */ function calculateNotePosition(noteTime) { // Calculate where scanner will be when note should be hit var cycleTime = noteTime % SCANNER_CYCLE_DURATION; var y; if (cycleTime < SCANNER_HALF_CYCLE) { // First half: moving up from bottom to top var progress = cycleTime / SCANNER_HALF_CYCLE; y = SCANNER_Y_MAX - progress * PLAY_AREA_HEIGHT; } else { // Second half: moving down from top to bottom var progress = (cycleTime - SCANNER_HALF_CYCLE) / SCANNER_HALF_CYCLE; y = SCANNER_Y_MIN + progress * PLAY_AREA_HEIGHT; } return y; } function calculateZoneX(zone) { // Calculate random X position within the specified zone if (zone === 'left') { return 200 + Math.random() * (GAME_WIDTH / 2 - 400); // Leave some margin } else { return GAME_WIDTH / 2 + 200 + Math.random() * (GAME_WIDTH / 2 - 400); } } function createZoneIndicators() { // Left zone indicator leftZone = game.attachAsset('zoneIndicator', { anchorX: 0, anchorY: 0 }); leftZone.x = 0; leftZone.y = 0; leftZone.width = GAME_WIDTH / 2; leftZone.alpha = 0.1; leftZone.tint = 0x00ffff; // Right zone indicator rightZone = game.attachAsset('zoneIndicator', { anchorX: 0, anchorY: 0 }); rightZone.x = GAME_WIDTH / 2; rightZone.y = 0; rightZone.width = GAME_WIDTH / 2; rightZone.alpha = 0.1; rightZone.tint = 0xff00ff; } // function createFlickResultNote is removed as it's no longer used. function startCountdown() { countdownTxt = new Text2('3', { size: 200, fill: 0xFFFFFF }); countdownTxt.anchor.set(0.5, 0.5); countdownTxt.x = GAME_WIDTH / 2; countdownTxt.y = GAME_HEIGHT / 2; game.addChild(countdownTxt); var count = 3; var countdownInterval = LK.setInterval(function () { count--; if (count > 0) { countdownTxt.setText(count.toString()); } else if (count === 0) { countdownTxt.setText('GO!'); } else { LK.clearInterval(countdownInterval); if (countdownTxt && countdownTxt.destroy) { countdownTxt.destroy(); } startGame(); } }, 1000); } function startGame() { gameStarted = true; gameStartTime = Date.now(); moveScanner(); LK.playMusic('gameMusic', { loop: true }); // updateHealthDisplay function is removed. } function moveScanner() { if (!scanner || !gameStarted) return; tween.stop(scanner); var targetY = scannerIsMovingUp ? SCANNER_Y_MIN : SCANNER_Y_MAX; tween(scanner, { y: targetY }, { duration: SCANNER_CYCLE_DURATION / 2, easing: tween.linear, onFinish: function onFinish() { scannerIsMovingUp = !scannerIsMovingUp; moveScanner(); } }); } function spawnNotesForCurrentTime(currentTime) { if (!currentSongData) return; currentSongData.notes.forEach(function (noteData, index) { var noteKey = index + '_' + noteData.time; // Check if already spawned var alreadySpawned = false; for (var i = 0; i < spawnedNotes.length; i++) { if (spawnedNotes[i] === noteKey) { alreadySpawned = true; break; } } // Only spawn if note time is approaching and not yet spawned if (!alreadySpawned && currentTime >= noteData.time - NOTE_SPAWN_AHEAD_MS && currentTime <= noteData.time - NOTE_SPAWN_AHEAD_MS + 100) { // Narrow spawn window // Calculate proper position var calculatedY = calculateNotePosition(noteData.time); var calculatedX = calculateZoneX(noteData.zone); // Create note data with calculated positions var processedNoteData = { time: noteData.time, type: noteData.type, x: calculatedX, y: calculatedY, color: NOTE_COLORS[Math.floor(Math.random() * NOTE_COLORS.length)], duration: noteData.duration // flickDirection: noteData.flickDirection // Removed }; // Flick target logic is removed var note = new Note(processedNoteData, currentTime); notes.push(note); game.addChild(note); note.spawnIn(); spawnedNotes.push(noteKey); } }); } function setupGame() { LK.setScore(0); // playerHealth = INITIAL_PLAYER_HEALTH; // playerHealth assignment removed gameStarted = false; // Score Text if (scoreTxt && scoreTxt.destroy) { scoreTxt.destroy(); } scoreTxt = new Text2('0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.setText(LK.getScore()); // Health Display removed // Zone indicators createZoneIndicators(); // Scanner - start at bottom if (scanner && scanner.destroy) { scanner.destroy(); } scanner = new Scanner(); scanner.y = SCANNER_Y_MAX; // Start at bottom game.addChild(scanner); // Clear previous notes notes.forEach(function (note) { if (note && note.destroy) { note.destroy(); } }); notes = []; spawnedNotes = []; // Load song data currentSongData = defaultSongData; BPM = currentSongData.bpm; BEAT_DURATION_MS = 60 / BPM * 1000; scannerIsMovingUp = true; // Will move up first // Start countdown instead of immediate game start startCountdown(); } function checkZoneHit(x, y) { var hitZone = x < GAME_WIDTH / 2 ? 'left' : 'right'; var currentTime = Date.now() - gameStartTime; for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note || !note.active || note.isHit || note.isSpawning) continue; // Check if note is in the same zone if (note.zone === hitZone) { var scannerDiff = Math.abs(scanner.y - note.y); var timeDiff = Math.abs(currentTime - note.hitTime); if (scannerDiff < HIT_TOLERANCE_PX) { note.isHit = true; // Mark as initially judged for timing. Actual success for hold comes later. var points = 0; // Removed flick note type check if (note.type === 'tap') { note.showHitEffect(); // Makes note inactive points = timeDiff < 50 ? 20 : timeDiff < 100 ? 15 : 10; LK.setScore(LK.getScore() + points); scoreTxt.setText(LK.getScore()); if (LK.getScore() >= TARGET_SCORE_TO_WIN) { LK.showYouWin(); gameStartTime = null; gameStarted = false; tween.stop(scanner); return; } } else if (note.type === 'hold') { note.startHoldEffect(); // Prepares note for holding, does NOT make inactive // Points for hold notes are awarded on successful completion (in game.up) } // Removed general points block as it's now specific to tap notes here. break; //{4u} // Found and processed a note for this hit } } } } // Initial setup call setupGame(); game.down = function (x, y, obj) { if (!gameStarted) return; //{4L} // playerHealth check removed checkZoneHit(x, y); // This will set note.isHit for taps and call startHoldEffect for holds. var hitZoneForDown = x < GAME_WIDTH / 2 ? 'left' : 'right'; // Logic for starting a hold // activeFlickPrimer = null; // Removed for (var i = 0; i < notes.length; i++) { var note = notes[i]; if (!note.active || note.isSpawning || note.holdSuccessfullyCompleted || note.isMissed) continue; if (note.zone === hitZoneForDown && note.isHit) { // note.isHit was set by checkZoneHit if timing was good if (note.type === 'hold' && !note.isHolding) { // checkZoneHit already called startHoldEffect if proximity was good. // Here we confirm and set isHolding. if (Math.abs(scanner.y - note.y) < HIT_TOLERANCE_PX) { note.isHolding = true; activeHoldNotes[note.zone] = note; // A flick note should not also be a hold note. // Comment remains for context, but flick logic is gone. } // Removed 'else if (note.type === 'flick' ...)' block } //{4I} // This closing brace now corresponds to 'if (note.type === 'hold' ...)' } } isDragging = true; // Still useful for game.up to know a drag sequence occurred dragStartX = x; // General drag start, flick primer uses its own copy dragStartY = y; // currentTrail related lines removed as trails are gone. }; game.move = function (x, y, obj) { if (!isDragging || !gameStarted) return; // Particle trail logic removed. This function can be empty or used for other drag-move effects if needed later. }; game.up = function (x, y, obj) { if (!gameStarted) return; // Allow processing even if not dragging for safety, but check gameStarted var releasedZone = x < GAME_WIDTH / 2 ? 'left' : 'right'; // Flick logic (activeFlickPrimer related) removed entirely. // Now, directly handle hold note release. var heldNote = activeHoldNotes[releasedZone]; if (heldNote && heldNote.isHolding) { heldNote.isHolding = false; // Stop particle effects etc. delete activeHoldNotes[releasedZone]; var currentTimeMs = Date.now() - gameStartTime; var noteExpectedEndTimeMs = heldNote.hitTime + heldNote.duration; var absoluteEndNoteY = heldNote.y + (heldNote.endNote ? heldNote.endNote.y : 0); // endNote.y is relative // Determine scanner direction at hit time for this note (needed for correct end check if trail goes up) var hitTimeCycle = heldNote.hitTime % SCANNER_CYCLE_DURATION; var noteScannerMovingUp = hitTimeCycle < SCANNER_HALF_CYCLE; var trailDirection = noteScannerMovingUp ? -1 : 1; var scannerAtEnd; if (trailDirection === -1) { // Trail goes upwards scannerAtEnd = scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END / 2; // Scanner near or slightly past the end upwardly } else { // Trail goes downwards scannerAtEnd = scanner.y >= absoluteEndNoteY - HIT_TOLERANCE_PX_FOR_HOLD_END && scanner.y <= absoluteEndNoteY + HIT_TOLERANCE_PX_FOR_HOLD_END / 2; // Scanner near or slightly past the end downwardly } var timeReachedOrPassedEnd = currentTimeMs >= noteExpectedEndTimeMs - HOLD_END_GRACE_PERIOD_MS; var timeNotTooLate = currentTimeMs <= noteExpectedEndTimeMs + HOLD_END_GRACE_PERIOD_MS + 200; // Not held excessively long if (scannerAtEnd && timeReachedOrPassedEnd && timeNotTooLate) { heldNote.completeHold(); LK.setScore(LK.getScore() + 50); // More points for a successful hold scoreTxt.setText(LK.getScore()); if (LK.getScore() >= TARGET_SCORE_TO_WIN) { LK.showYouWin(); gameStartTime = null; gameStarted = false; tween.stop(scanner); } } else { heldNote.failHold(); // playerHealth--;//{5x} // Health decrement removed // updateHealthDisplay();//{5y} // Health display update removed } } // This bracket no longer closes an "else" block, but the "if (heldNote && heldNote.isHolding)" block. isDragging = false; // Reset general dragging flag. // currentTrail cleanup is removed as it's no longer created. }; game.update = function () { if (!gameStarted) return; //{5C} // playerHealth check removed var currentTime = Date.now() - gameStartTime; spawnNotesForCurrentTime(currentTime); for (var i = notes.length - 1; i >= 0; i--) { var note = notes[i]; if (!note) { notes.splice(i, 1); continue; } // Update hold effects if note is being held if (note.type === 'hold' && note.isHolding && note.active && !note.holdSuccessfullyCompleted && !note.isMissed) { note.updateHoldEffects(scanner.y); // Fail-safe for holds that are held too long past their end time var currentTimeMsForUpdate = Date.now() - gameStartTime; var noteExpectedEndTimeMsForUpdate = note.hitTime + note.duration; if (currentTimeMsForUpdate > noteExpectedEndTimeMsForUpdate + HOLD_END_GRACE_PERIOD_MS + 500) { // 500ms buffer if (activeHoldNotes[note.zone] === note) { // Still registered as actively held note.failHold(); // playerHealth--;//{5I} // Health decrement removed // updateHealthDisplay();//{5J} // Health display update removed delete activeHoldNotes[note.zone]; } } } // Check for missed notes if (note.active && !note.isHit && !note.isMissed && !note.isSpawning) { var timeDiff = currentTime - note.hitTime; var scannerPassed = false; if (scannerIsMovingUp && scanner.y < note.y - 50) { scannerPassed = true; } else if (!scannerIsMovingUp && scanner.y > note.y + 50) { scannerPassed = true; } if (scannerPassed && timeDiff > 300) { note.isMissed = true; note.showMissEffect(); // playerHealth--;//{5Q} // Health decrement removed // updateHealthDisplay();//{5R} // Health display update removed // if (playerHealth <= 0) {//{5S} // Game over check removed // return; // } } } // Cleanup inactive notes if (!note.active) { notes.splice(i, 1); if (game.children.indexOf(note) > -1) { game.removeChild(note); } if (note.destroy) { note.destroy(); } } } };
===================================================================
--- original.js
+++ change.js
@@ -530,310 +530,348 @@
generateSong: function generateSong(config) {
var notes = [];
var totalLength = config.totalLength || 202136;
var startTime = config.startDelay || 2000;
- // Generate notes with consistent density throughout the song
- notes = this.generateDensityBasedSong(startTime, totalLength);
- // Original cleanupNotes is removed as density based approach should ideally manage conflicts by design.
- // If conflicts are still an issue, a similar cleanup function might be needed or adjustments to generation logic.
- // For now, let's assume the new generator produces clean notes or that cleanup will be added later if necessary.
+ notes = this.generateZoneAwareSong(startTime, totalLength);
notes.sort(function (a, b) {
return a.time - b.time;
}); // Ensure notes are sorted
return notes;
},
- generateDensityBasedSong: function generateDensityBasedSong(startTime, songTotalLength) {
- // songTotalLength is the total duration of the song audio
+ generateZoneAwareSong: function generateZoneAwareSong(startTime, songTotalLength) {
var notes = [];
- // Define sections relative to the song's actual start time (0 for song's beginning)
- // The 'startTime' parameter for generateSong is an offset *before* the song's actual 0-time for notes
+ var currentTime = startTime;
+ // Track when each zone will be free
+ var zoneFreeTime = {
+ left: startTime,
+ right: startTime
+ };
+ // Minimum spacing between notes (human reaction time)
+ var MIN_SAME_ZONE_SPACING = 400; // 400ms minimum in same zone
+ var MIN_DIFFERENT_ZONE_SPACING = 200; // 200ms minimum between different zones
+ var MIN_SYNC_SPACING = 800; // 800ms minimum between sync notes
+ var lastSyncTime = startTime - MIN_SYNC_SPACING; // Initialize to allow early sync
+ // Define sections with different complexities
var sections = [{
start: 0,
end: 16000,
- intensity: 0.3,
- name: 'intro'
+ complexity: 'simple'
}, {
start: 16000,
end: 48000,
- intensity: 0.6,
- name: 'verse1'
+ complexity: 'medium'
}, {
start: 48000,
end: 72000,
- intensity: 0.8,
- name: 'chorus1'
- }, {
+ complexity: 'medium'
+ },
+ // Original: chorus1, intensity 0.8
+ {
start: 72000,
end: 104000,
- intensity: 0.6,
- name: 'verse2'
+ complexity: 'medium'
}, {
start: 104000,
end: 128000,
- intensity: 0.9,
- name: 'chorus2'
- }, {
+ complexity: 'complex'
+ },
+ // Original: chorus2, intensity 0.9
+ {
start: 128000,
end: 160000,
- intensity: 0.7,
- name: 'verse3'
+ complexity: 'medium'
}, {
start: 160000,
end: 184000,
- intensity: 1.0,
- name: 'finalchorus'
- }, {
+ complexity: 'complex'
+ },
+ // Original: finalchorus, intensity 1.0
+ {
start: 184000,
- end: 202136,
- intensity: 0.4,
- name: 'outro'
- } // Ensure last section ends at songTotalLength
+ end: songTotalLength,
+ complexity: 'simple'
+ } // Ensure it goes to the end
];
- var beatInterval = 500; // Base beat (120 BPM = 500ms per beat)
for (var s = 0; s < sections.length; s++) {
var section = sections[s];
- // Adjust section start and end times by the initial `startTime` delay for note spawning
var sectionStartTime = startTime + section.start;
var sectionEndTime = startTime + section.end;
- // Ensure sectionEndTime does not exceed the overall song length plus start delay
if (sectionEndTime > startTime + songTotalLength) {
sectionEndTime = startTime + songTotalLength;
}
- if (sectionStartTime >= sectionEndTime) continue; // Skip if section has no duration
- var sectionNotes = this.generateSection(sectionStartTime, sectionEndTime, section.intensity, beatInterval);
- notes = notes.concat(sectionNotes);
- }
- return notes;
- },
- generateSection: function generateSection(startTime, endTime, intensity, beatInterval) {
- var notes = [];
- var currentTime = startTime;
- var patterns = this.getPatterns(intensity);
- var patternIndex = 0;
- var lastNoteTimes = {
- left: -Infinity,
- right: -Infinity
- }; // Track last note time for each zone
- var MIN_SPACING = 200; // Minimum ms between notes in the same zone
- while (currentTime < endTime - 1000) {
- // Leave 1s buffer at end of section
- if (patterns.length === 0) break; // No patterns for this intensity
- var patternDef = patterns[patternIndex % patterns.length];
- var patternNotesRaw = this.createPattern(patternDef, currentTime);
- var validPatternNotes = [];
- var patternDuration = patternDef.duration; // Use defined duration
- var canPlaceAll = true;
- for (var k = 0; k < patternNotesRaw.length; k++) {
- var rawNote = patternNotesRaw[k];
- if (rawNote.time < endTime - 500) {
- // Check if note fits in section
- // Check spacing for the specific zone
- var lastTimeInZone = lastNoteTimes[rawNote.zone];
- if (rawNote.time - lastTimeInZone < MIN_SPACING) {
- // Check for holds, if it's a hold, the spacing might need to consider its duration
- if (rawNote.type === 'hold' && rawNote.time - lastTimeInZone < (rawNote.duration || 1000)) {
- canPlaceAll = false;
+ if (sectionStartTime >= sectionEndTime) continue;
+ currentTime = Math.max(currentTime, sectionStartTime);
+ while (currentTime < sectionEndTime - 1000) {
+ // Leave 1s buffer at end of section
+ var pattern = this.selectPattern(section.complexity, zoneFreeTime, currentTime, lastSyncTime);
+ var patternResult = this.placePattern(pattern, currentTime, zoneFreeTime, MIN_SAME_ZONE_SPACING, MIN_DIFFERENT_ZONE_SPACING, sectionEndTime);
+ if (patternResult.notes.length > 0) {
+ var allNotesFit = true;
+ for (var ni = 0; ni < patternResult.notes.length; ni++) {
+ if (patternResult.notes[ni].time + (patternResult.notes[ni].duration || 0) > sectionEndTime - 200) {
+ // Check end time
+ allNotesFit = false;
break;
- } else if (rawNote.type !== 'hold') {
- canPlaceAll = false;
- break;
}
}
+ if (allNotesFit) {
+ notes = notes.concat(patternResult.notes);
+ currentTime = patternResult.nextTime;
+ if (pattern.type === 'sync') {
+ lastSyncTime = patternResult.notes[0].time; // Time of the sync notes themselves
+ }
+ } else {
+ currentTime += 300; // Advance time if pattern didn't fit
+ }
} else {
- canPlaceAll = false; // Note extends beyond section
- break;
+ currentTime += 300; // If pattern couldn't be placed, advance time
}
+ if (currentTime >= sectionEndTime - 1000) break;
}
- if (canPlaceAll) {
- for (var l = 0; l < patternNotesRaw.length; l++) {
- var noteToAdd = patternNotesRaw[l];
- validPatternNotes.push(noteToAdd);
- lastNoteTimes[noteToAdd.zone] = noteToAdd.time + (noteToAdd.type === 'hold' ? noteToAdd.duration || 0 : 0);
- }
- notes = notes.concat(validPatternNotes);
- }
- currentTime += patternDuration;
- patternIndex++;
- currentTime += Math.random() * 100 - 50; // ±50ms variation
- if (currentTime < startTime) currentTime = startTime; // Ensure currentTime does not go backward
}
return notes;
},
- getPatterns: function getPatterns(intensity) {
- if (intensity <= 0.4) {
- // Low intensity - simple patterns
- return [{
+ selectPattern: function selectPattern(complexity, zoneFreeTime, currentTime, lastSyncTime) {
+ var patterns = [];
+ var MIN_SYNC_SPACING = 800; // Local constant for clarity
+ if (complexity === 'simple') {
+ patterns = [{
type: 'single',
zone: 'left',
- duration: 1000
+ duration: 500
}, {
type: 'single',
zone: 'right',
- duration: 1000
+ duration: 500
}, {
- type: 'sync',
- duration: 1000
- }, {
type: 'rest',
duration: 1000
}];
- } else if (intensity <= 0.7) {
- // Medium intensity
- return [{
- type: 'alternating',
- duration: 1000
- },
- // Two taps, 250ms apart
- {
- type: 'sync',
- duration: 500
- }, {
+ if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) {
+ patterns.push({
+ type: 'sync',
+ duration: 600
+ });
+ }
+ } else if (complexity === 'medium') {
+ patterns = [{
type: 'single',
zone: 'left',
- duration: 500
+ duration: 400
}, {
type: 'single',
zone: 'right',
- duration: 500
+ duration: 400
}, {
+ type: 'alternating',
+ duration: 600
+ },
+ // two taps, 250ms apart
+ {
type: 'hold',
zone: 'left',
- duration: 2000,
- holdLength: 1500
+ holdDuration: 1500,
+ duration: 800
}, {
type: 'hold',
zone: 'right',
- duration: 2000,
- holdLength: 1500
+ holdDuration: 1500,
+ duration: 800
+ }, {
+ type: 'rest',
+ duration: 800
}];
+ if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) {
+ patterns.push({
+ type: 'sync',
+ duration: 600
+ });
+ }
} else {
- // High intensity
- return [{
- type: 'sync',
- duration: 500
+ // complex
+ patterns = [{
+ type: 'single',
+ zone: 'left',
+ duration: 300
}, {
+ type: 'single',
+ zone: 'right',
+ duration: 300
+ }, {
type: 'alternating',
duration: 500
},
- // Two taps, 250ms apart
+ // two taps, 250ms apart
{
- type: 'triplet',
+ type: 'hold',
zone: 'left',
- duration: 750
- },
- // 3 taps, 167ms apart
- {
- type: 'triplet',
+ holdDuration: 1500,
+ duration: 800
+ }, {
+ type: 'hold',
zone: 'right',
- duration: 750
+ holdDuration: 1500,
+ duration: 800
}, {
- type: 'sync',
- duration: 250
- },
- // Quick sync
- {
type: 'holdWithTaps',
zone: 'left',
- duration: 2500,
- holdLength: 2000
+ holdDuration: 2000,
+ duration: 1000
+ }, {
+ type: 'holdWithTaps',
+ zone: 'right',
+ holdDuration: 2000,
+ duration: 1000
}];
+ if (currentTime - lastSyncTime >= MIN_SYNC_SPACING) {
+ patterns.push({
+ type: 'sync',
+ duration: 500
+ }); // Shorter duration for sync in complex sections
+ }
}
+ return patterns[Math.floor(Math.random() * patterns.length)];
},
- createPattern: function createPattern(pattern, startTime) {
+ placePattern: function placePattern(pattern, requestedTime, zoneFreeTime, minSameZoneSpacing, minDiffZoneSpacing, sectionEndTime) {
var notes = [];
- switch (pattern.type) {
- case 'single':
+ var earliestTime = requestedTime;
+ var nextTimeAdvance = pattern.duration || 500; // Default advancement
+ if (pattern.type === 'single') {
+ earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]);
+ if (earliestTime + (pattern.duration || 0) < sectionEndTime) {
notes.push({
- time: startTime,
+ time: earliestTime,
type: 'tap',
zone: pattern.zone
});
- break;
- case 'sync':
+ zoneFreeTime[pattern.zone] = earliestTime + minSameZoneSpacing;
+ return {
+ notes: notes,
+ nextTime: earliestTime + nextTimeAdvance
+ };
+ }
+ } else if (pattern.type === 'sync') {
+ earliestTime = Math.max(requestedTime, zoneFreeTime.left, zoneFreeTime.right);
+ if (earliestTime + (pattern.duration || 0) < sectionEndTime) {
notes.push({
- time: startTime,
+ time: earliestTime,
type: 'tap',
zone: 'left'
});
notes.push({
- time: startTime,
+ time: earliestTime,
type: 'tap',
zone: 'right'
});
- break;
- case 'alternating':
- // Assumes left then right, 250ms spacing within a 500ms or 1000ms pattern duration
+ zoneFreeTime.left = earliestTime + minSameZoneSpacing;
+ zoneFreeTime.right = earliestTime + minSameZoneSpacing;
+ return {
+ notes: notes,
+ nextTime: earliestTime + nextTimeAdvance
+ };
+ }
+ } else if (pattern.type === 'alternating') {
+ // Attempt Left then Right
+ var firstZone = Math.random() < 0.5 ? 'left' : 'right';
+ var secondZone = firstZone === 'left' ? 'right' : 'left';
+ var t1 = Math.max(requestedTime, zoneFreeTime[firstZone]);
+ var t2 = Math.max(t1 + minDiffZoneSpacing, zoneFreeTime[secondZone]);
+ if (t2 + (pattern.duration || 0) - (t2 - t1) < sectionEndTime) {
+ // check if second note fits
notes.push({
- time: startTime,
+ time: t1,
type: 'tap',
- zone: 'left'
+ zone: firstZone
});
notes.push({
- time: startTime + 250,
+ time: t2,
type: 'tap',
- zone: 'right'
+ zone: secondZone
});
- break;
- case 'triplet':
- // 3 taps, 167ms spacing
- for (var i = 0; i < 3; i++) {
- notes.push({
- time: startTime + i * 167,
- type: 'tap',
- zone: pattern.zone
- });
- }
- break;
- case 'hold':
+ zoneFreeTime[firstZone] = t1 + minSameZoneSpacing;
+ zoneFreeTime[secondZone] = t2 + minSameZoneSpacing;
+ return {
+ notes: notes,
+ nextTime: t2 + minDiffZoneSpacing
+ }; // Next logical placement time
+ }
+ } else if (pattern.type === 'hold') {
+ earliestTime = Math.max(requestedTime, zoneFreeTime[pattern.zone]);
+ var holdDuration = pattern.holdDuration || 1500;
+ if (earliestTime + holdDuration < sectionEndTime) {
notes.push({
- time: startTime,
+ time: earliestTime,
type: 'hold',
zone: pattern.zone,
- duration: pattern.holdLength || 1500
+ duration: holdDuration
});
- break;
- case 'holdWithTaps':
+ zoneFreeTime[pattern.zone] = earliestTime + holdDuration + 200; // Buffer after hold
+ return {
+ notes: notes,
+ nextTime: earliestTime + nextTimeAdvance
+ }; // Pattern's own duration logic
+ }
+ } else if (pattern.type === 'holdWithTaps') {
+ var holdZone = pattern.zone;
+ var tapZone = holdZone === 'left' ? 'right' : 'left';
+ var holdStart = Math.max(requestedTime, zoneFreeTime[holdZone]);
+ var mainHoldDuration = pattern.holdDuration || 2000;
+ if (holdStart + mainHoldDuration < sectionEndTime) {
notes.push({
- time: startTime,
+ time: holdStart,
type: 'hold',
- zone: pattern.zone,
- duration: pattern.holdLength || 2000
+ zone: holdZone,
+ duration: mainHoldDuration
});
- var tapZone = pattern.zone === 'left' ? 'right' : 'left';
- notes.push({
- time: startTime + 500,
- type: 'tap',
- zone: tapZone
- });
- notes.push({
- time: startTime + 1000,
- type: 'tap',
- zone: tapZone
- });
- break;
- case 'rest':
- // No notes
- break;
+ zoneFreeTime[holdZone] = holdStart + mainHoldDuration + 200;
+ var tap1Time = Math.max(holdStart + 500, zoneFreeTime[tapZone]);
+ if (tap1Time < holdStart + mainHoldDuration - 500 && tap1Time + minSameZoneSpacing < sectionEndTime) {
+ notes.push({
+ time: tap1Time,
+ type: 'tap',
+ zone: tapZone
+ });
+ zoneFreeTime[tapZone] = tap1Time + minSameZoneSpacing;
+ var tap2Time = Math.max(tap1Time + minSameZoneSpacing, zoneFreeTime[tapZone]); // Ensure tap2 is after tap1 is free
+ if (tap2Time < holdStart + mainHoldDuration - 200 && tap2Time + minSameZoneSpacing < sectionEndTime) {
+ notes.push({
+ time: tap2Time,
+ type: 'tap',
+ zone: tapZone
+ });
+ zoneFreeTime[tapZone] = tap2Time + minSameZoneSpacing;
+ }
+ }
+ return {
+ notes: notes,
+ nextTime: holdStart + nextTimeAdvance
+ };
+ }
+ } else if (pattern.type === 'rest') {
+ return {
+ notes: [],
+ nextTime: requestedTime + (pattern.duration || 500)
+ };
}
- return notes;
+ return {
+ notes: [],
+ nextTime: requestedTime + 100
+ }; // Failed to place, small advance
}
};
// Simpler song database
var SongDatabase = {
'gameMusic': {
bpm: 120,
- // Remains for reference, but new generator doesn't directly use it
scannerCycleDuration: 4000,
totalLength: 202136,
- // Actual duration of the music track
notes: null
}
};
// Generate with the new system
SongDatabase['gameMusic'].notes = SongGenerator.generateSong({
startDelay: 2000,
- // Time before the first note appears, relative to game start
- totalLength: SongDatabase['gameMusic'].totalLength // Pass the song's actual total length
+ totalLength: SongDatabase['gameMusic'].totalLength
});
var defaultSongData = SongDatabase['gameMusic'];
// Helper function to add new songs easily
function addSong(songId, config) {
The word 'Pulsar' in a glowing neon SVG in futuristic font. The word is half blue on the left and half red on the right. In-Game asset. 2d. High contrast. No shadows
Remove the background.
A thin expanding ring with energy distortion ``` - Outer ring: 4-6 pixels thick, bright cyan (#00FFFF) - Inner ring: 2-3 pixels thick, white (#FFFFFF) - Ring thickness: Tapers from thick to thin as it expands - Transparency: Ring itself at 80% opacity - Background: Completely transparent - Edge treatment: Soft anti-aliased edges, slight glow effect - Optional: Subtle "energy crackle" texture within the ring. In-Game asset. 2d. High contrast. No shadows
Soft, lingering light effect ``` - Center: Warm orange (#FF6600) at 40% opacity - Middle: Yellow (#FFAA00) at 25% opacity - Edge: Transparent - Shape: Perfect circle with very soft, wide falloff. In-Game asset. 2d. High contrast. No shadows