/** * World 12 — "Gran Final" (Grand Finale) * * Teaches: building a complete track from start to finish * 8 levels creating a full production: intro, drop, lead, breakdown, build-up, mix, outro * boss challenge: create a complete musical piece with scope visualization */ export const WORLD_12 = { id: 'w12', name: 'Gran Final', subtitle: 'Tu obra maestra', icon: '♛', color: '#ffd700', unlockStars: 132, levels: [ // ─────────────── LEVEL 12.1 ─────────────── { id: 'w12-1', title: 'Intro Ambiental', subtitle: 'Comenzando suavemente', description: 'Toda gran pista comienza con una introducción ambiental. Crea una atmósfera con pads, sonidos largos y efectos de reverb/delay. Sin ritmo fuerte, solo texturas flotantes.', concept: 'Dos oscs sine graves detuned + Mixer → Filter LP → VCA con envelope muy largo → Reverb → Output. LFO lento al cutoff. Sin percusión, puro ambiente. Cero attack, máximo sustain.', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 5 }, checks: [ { star: 1, name: 'Pad ambiental', desc: '2 oscs sine grave + reverb largo', test: (mods) => { const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sine'); const rev = mods.find(m => m.type === 'reverb'); if (oscs.length < 2 || !rev) return false; return oscs.filter(o => (o.params.frequency ?? 440) < 150).length >= 2 && (rev.params.decay ?? 2) > 3; }, }, { star: 2, name: 'Evolución lenta', desc: 'LFO < 1 Hz modulando cutoff, envelope muy largo (decay > 1s)', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); const env = mods.find(m => m.type === 'envelope'); if (!lfo || !env) return false; return (lfo.params.frequency ?? 2) < 1 && (env.params.decay ?? 0.2) > 1 && (env.params.sustain ?? 0.5) > 0.4; }, }, { star: 3, name: 'Intro hipnótica', desc: '2+ oscs detuned, filter LP, LFO lento al cutoff, reverb > 4s, envelope attack 0', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sine'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); if (oscs.length < 2 || !flt || !lfo || !rev || !env) return false; const graveLong = oscs.filter(o => (o.params.frequency ?? 440) < 150).length >= 2; const hasDetune = oscs.some(o => Math.abs(o.params.detune ?? 0) > 2); const slowLfo = (lfo.params.frequency ?? 2) < 1; const longRev = (rev.params.decay ?? 2) > 4; const niceEnv = (env.params.attack ?? 0.01) < 0.05 && (env.params.decay ?? 0.2) > 1; const lfoToFilter = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); return graveLong && hasDetune && slowLfo && longRev && niceEnv && lfoToFilter; }, }, ], }, // ─────────────── LEVEL 12.2 ─────────────── { id: 'w12-2', title: 'El Drop', subtitle: 'Entra el beat con fuerza', description: 'Después de la intro, llega el drop: un cambio dramático donde entra el kick, snare y bass graves. Es el momento de tensión y energía. Combina un bass grave con un beat de síntesis.', concept: 'Dos elementos: 1) Drum: Osc sine grave (~55 Hz) con envelope rápido (attack 0, decay 0.2). 2) Bass: Oscs sawtooth detuned, filtro LP abierto, sonido gordo y agresivo. Sequencer para el ritmo.', availableModules: ['oscillator', 'vca', 'envelope', 'mixer', 'filter', 'sequencer'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 140, params: { volume: -6 }, locked: true }, ], target: { build: [], duration: 4 }, checks: [ { star: 1, name: 'Kick + Bass', desc: 'Osc grave con envelope corto (kick) + osc grave para bass', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const vca = mods.find(m => m.type === 'vca'); const env = mods.find(m => m.type === 'envelope'); if (oscs.length < 2 || !vca || !env) return false; const graveOscs = oscs.filter(o => (o.params.frequency ?? 440) < 100); return graveOscs.length >= 2 && (env.params.decay ?? 0.2) < 0.3; }, }, { star: 2, name: 'Ritmo percibible', desc: 'Sequencer conectado, beat claro con kick percusivo', test: (mods, conns) => { const seq = mods.find(m => m.type === 'sequencer'); const osc = mods.find(m => m.type === 'oscillator'); const env = mods.find(m => m.type === 'envelope'); if (!seq || !osc || !env) return false; return conns.some(c => c.from.moduleId === seq.id && c.to.moduleId === osc.id) && (env.params.decay ?? 0.2) < 0.25; }, }, { star: 3, name: 'Drop potente', desc: 'Kick < 80 Hz decay < 0.2s, bass sawtooth detuned, sequencer, sonido gordo y fuerte', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const seq = mods.find(m => m.type === 'sequencer'); const env = mods.find(m => m.type === 'envelope'); const flt = mods.find(m => m.type === 'filter'); if (oscs.length < 2 || !seq || !env) return false; const kickOsc = oscs.find(o => (o.params.frequency ?? 440) < 80); const sawOscs = oscs.filter(o => o.params.waveform === 'sawtooth'); const hasDetune = sawOscs.some(o => Math.abs(o.params.detune ?? 0) > 3); const fastKick = (env.params.decay ?? 0.2) < 0.2; const seqConnected = conns.some(c => c.from.moduleId === seq.id); return kickOsc && sawOscs.length > 0 && hasDetune && fastKick && seqConnected; }, }, ], }, // ─────────────── LEVEL 12.3 ─────────────── { id: 'w12-3', title: 'Lead Melódico', subtitle: 'Melodía protagonista', description: 'Usa el piano roll para crear una melodía líder que brille sobre el bass. El lead es típicamente un solo sintetizado con oscilador brillante, filtro modulado y reverb para espaciosidad.', concept: 'Piano roll → Osc square/bright → Filter LP con resonancia → VCA → Reverb → Mixer. Envelope para notas definidas (attack corto, decay/sustain para "peso"). LFO lento al cutoff para movimiento.', availableModules: ['oscillator', 'filter', 'vca', 'envelope', 'lfo', 'pianoroll', 'reverb', 'mixer'], preplacedModules: [ { id: 1, type: 'output', x: 850, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 5 }, checks: [ { star: 1, name: 'Melodía activa', desc: 'Piano roll conectado a osc, notas reproducidas', test: (mods, conns) => { const pr = mods.find(m => m.type === 'pianoroll'); const osc = mods.find(m => m.type === 'oscillator'); if (!pr || !osc) return false; return conns.some(c => c.from.moduleId === pr.id && c.to.moduleId === osc.id && c.to.port === 'freq'); }, }, { star: 2, name: 'Lead con carácter', desc: 'Osc square/bright, filter resonante, envelope con ataque corto', test: (mods) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const env = mods.find(m => m.type === 'envelope'); if (!osc || !flt || !env) return false; const isBright = osc.params.waveform === 'square' || osc.params.waveform === 'sawtooth'; const hasResonance = (flt.params.Q ?? 1) > 2; const quickAttack = (env.params.attack ?? 0.01) < 0.05; return isBright && hasResonance && quickAttack; }, }, { star: 3, name: 'Lead melódico', desc: 'Piano roll + osc square con filter resonante + LFO al cutoff + reverb, notas claramente escuchables', test: (mods, conns) => { const pr = mods.find(m => m.type === 'pianoroll'); const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); if (!pr || !osc || !flt || !lfo || !rev || !env) return false; const prConnected = conns.some(c => c.from.moduleId === pr.id && c.to.moduleId === osc.id && c.to.port === 'freq'); const gateConnected = conns.some(c => c.from.moduleId === pr.id && c.to.moduleId === env.id && c.to.port === 'gate'); const lfoToFilter = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); return prConnected && gateConnected && lfoToFilter && (rev.params.decay ?? 2) > 2; }, }, ], }, // ─────────────── LEVEL 12.4 ─────────────── { id: 'w12-4', title: 'Breakdown', subtitle: 'Menos es más', description: 'El breakdown es una sección donde quitas elementos clave para crear contraste. Quitas el kick, quitas el bass pesado, dejas solo los pads suaves o un synth secundario. Construye anticipación para el regreso.', concept: 'Calla el kick y bass de secciones previas. Deja solo pads suaves, lead melódico suave, y efectos. Opcional: introduce un elemento nuevo y suave (strings sintéticos, pad etéreo). Todo con reverb abundante.', availableModules: ['oscillator', 'filter', 'vca', 'envelope', 'lfo', 'mixer', 'reverb', 'pianoroll'], preplacedModules: [ { id: 1, type: 'output', x: 850, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 5 }, checks: [ { star: 1, name: 'Sonido suave', desc: 'Oscs sine/pads, sin percusión aguda, reverb presente', test: (mods) => { const oscs = mods.filter(m => m.type === 'oscillator'); const rev = mods.find(m => m.type === 'reverb'); if (!rev || oscs.length < 1) return false; const sines = oscs.filter(o => o.params.waveform === 'sine'); return sines.length >= 1 && (rev.params.decay ?? 2) > 2; }, }, { star: 2, name: 'Atmósfera ambiental', desc: 'Múltiples layers suaves, LFO modulando filtro, no hay kicks agudos', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); if (oscs.length < 2 || !flt || !lfo) return false; const softOscs = oscs.filter(o => (o.params.frequency ?? 440) < 200); return softOscs.length >= 1 && conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); }, }, { star: 3, name: 'Breakdown perfecto', desc: '2+ oscs suaves, filtro con LFO, envelope largo, reverb > 3s, sonido flotante y aéreo', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); if (oscs.length < 2 || !flt || !lfo || !rev || !env) return false; const softOscs = oscs.filter(o => (o.params.frequency ?? 440) < 200); const longEnv = (env.params.decay ?? 0.2) > 1 && (env.params.sustain ?? 0.5) > 0.3; const lfoToFilter = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); return softOscs.length >= 2 && longEnv && lfoToFilter && (rev.params.decay ?? 2) > 3; }, }, ], }, // ─────────────── LEVEL 12.5 ─────────────── { id: 'w12-5', title: 'Build-Up', subtitle: 'La tensión sube', description: 'El build-up es donde añades elementos gradualmente para construir tensión. Comienzas minimal, y lentamente añades más capas: pads, bass, efectos, filtros abriendo. La audiencia siente que algo grande viene.', concept: 'Empieza con un LFO lento abriendo un filtro sobre un oscilador suave. Gradualmente: añade un segundo osc, un tercer osc, baja el cutoff, suena más agresivo. El sequencer acelera. La reverb se vuelve más agresiva (menos decay).', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb', 'sequencer'], preplacedModules: [ { id: 1, type: 'output', x: 850, y: 140, params: { volume: -6 }, locked: true }, ], target: { build: [], duration: 5 }, checks: [ { star: 1, name: 'Tensión creciente', desc: 'LFO modulando filter cutoff, sonido evoluciona', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const flt = mods.find(m => m.type === 'filter'); if (!lfo || !flt) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); }, }, { star: 2, name: 'Múltiples layers', desc: '3+ oscs, filtro con LFO, sonido más agresivo que intro', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const seq = mods.find(m => m.type === 'sequencer'); if (oscs.length < 3 || !flt) return false; const hasSeq = seq && conns.some(c => c.from.moduleId === seq.id); return hasSeq; }, }, { star: 3, name: 'Build-Up intenso', desc: '3+ oscs, LFO lento al cutoff, sequencer activo, reverb < 2s (más seco), sonido cresce', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); const seq = mods.find(m => m.type === 'sequencer'); const rev = mods.find(m => m.type === 'reverb'); if (oscs.length < 3 || !flt || !lfo || !seq) return false; const slowLfo = (lfo.params.frequency ?? 2) < 1; const dryReverb = rev && (rev.params.decay ?? 2) < 2.5; const seqConnected = conns.some(c => c.from.moduleId === seq.id); const lfoToFilter = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); return slowLfo && seqConnected && lfoToFilter && conns.length >= 10; }, }, ], }, // ─────────────── LEVEL 12.6 ─────────────── { id: 'w12-6', title: 'Mix Completo', subtitle: 'Todos los elementos unidos', description: 'Ahora mezcla todo: intro, drop, lead, breakdown, build-up. Todos los elementos están presentes. El desafío es balancear los volúmenes para que nada se ahogue. Usa un mixer y output con gain correcto.', concept: 'Enruta todos los elementos de secciones anteriores a un único mixer. Todos los canales del mixer contribuyen al sonido final. Ajusta los gains del mixer y output para balance: nada clipeado, nada muy suave. Sonido cohesivo.', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb', 'sequencer', 'pianoroll'], preplacedModules: [ { id: 1, type: 'output', x: 950, y: 160, params: { volume: -10 }, locked: true }, ], target: { build: [], duration: 6 }, checks: [ { star: 1, name: 'Mixer activo', desc: 'Mixer con múltiples entradas, output rellenado', test: (mods, conns) => { const mixer = mods.find(m => m.type === 'mixer'); const out = mods.find(m => m.type === 'output'); if (!mixer || !out) return false; const inputsToMixer = conns.filter(c => c.to.moduleId === mixer.id).length; const mixerToOut = conns.some(c => c.from.moduleId === mixer.id && c.to.moduleId === out.id); return inputsToMixer >= 2 && mixerToOut; }, }, { star: 2, name: 'Balance de sonido', desc: 'Múltiples elementos (oscs, reverb, seq, pianoroll) todos en mixer', test: (mods, conns) => { const mixer = mods.find(m => m.type === 'mixer'); const oscs = mods.filter(m => m.type === 'oscillator'); const seq = mods.find(m => m.type === 'sequencer'); const pr = mods.find(m => m.type === 'pianoroll'); if (!mixer) return false; const inputCount = conns.filter(c => c.to.moduleId === mixer.id).length; return oscs.length >= 3 && inputCount >= 4; }, }, { star: 3, name: 'Mix profesional', desc: '8+ elementos en mixer, sonido balanceado, output -10 a -6dB, 15+ conexiones totales', test: (mods, conns) => { const nonOut = mods.filter(m => m.type !== 'output'); const mixer = mods.find(m => m.type === 'mixer'); const out = mods.find(m => m.type === 'output'); if (nonOut.length < 8 || !mixer || !out) return false; const inputsToMixer = conns.filter(c => c.to.moduleId === mixer.id).length; const outVolume = out.params.volume ?? -6; return inputsToMixer >= 5 && outVolume >= -12 && outVolume <= -4 && conns.length >= 15; }, }, ], }, // ─────────────── LEVEL 12.7 ─────────────── { id: 'w12-7', title: 'Outro Etéreo', subtitle: 'Despedida musical', description: 'El outro es donde se desvanece todo. Quitas elementos poco a poco, quizás repites la intro ambiental, y añades mucha reverb para crear una sensación de distancia y cierre. El sonido debe desvanecer suavemente.', concept: 'Repite elementos de la intro: oscs sine graves detuned, filtro suave, LFO muy lento al cutoff, reverb LARGO (5+ segundos). Envelope con sustain muy bajo para fade suave. Opcional: distorsión suave o delay con feedback para movimiento final.', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb', 'delay'], preplacedModules: [ { id: 1, type: 'output', x: 850, y: 140, params: { volume: -10 }, locked: true }, ], target: { build: [], duration: 5 }, checks: [ { star: 1, name: 'Reverb largo', desc: 'Reverb con decay > 4s para fade etéreo', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); return rev && (rev.params.decay ?? 2) > 4; }, }, { star: 2, name: 'Sonido desvanecido', desc: 'Oscs graves, LFO lento, reverb largo, envelope largo sin gates', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const lfo = mods.find(m => m.type === 'lfo'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); if (oscs.length < 1 || !lfo || !rev || !env) return false; const softOscs = oscs.filter(o => (o.params.frequency ?? 440) < 150); const slowLfo = (lfo.params.frequency ?? 2) < 0.5; const veryLongRev = (rev.params.decay ?? 2) > 4; return softOscs.length >= 1 && slowLfo && veryLongRev; }, }, { star: 3, name: 'Outro perfecto', subtitle: '2+ oscs graves detuned, LFO < 0.5 Hz, reverb > 5s, delay con feedback, sonido flota al silencio', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); const rev = mods.find(m => m.type === 'reverb'); const del = mods.find(m => m.type === 'delay'); const env = mods.find(m => m.type === 'envelope'); if (oscs.length < 2 || !flt || !lfo || !rev || !env) return false; const graveDetuned = oscs.filter(o => (o.params.frequency ?? 440) < 150).length >= 2 && oscs.some(o => Math.abs(o.params.detune ?? 0) > 2); const verySlowLfo = (lfo.params.frequency ?? 2) < 0.5; const veryLongRev = (rev.params.decay ?? 2) > 5; const lfoToFilter = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); return graveDetuned && verySlowLfo && veryLongRev && lfoToFilter; }, }, ], }, // ─────────────── LEVEL 12.8: BOSS FINAL ─────────────── { id: 'w12-8', title: 'Tu Obra Maestra', subtitle: 'BOSS FINAL: Tu track completa', description: 'Eres un sintetista maestro. Construye una obra musical completa: una pista de principio a fin. Intro, drop, lead, breakdown, build-up, mezcla y outro. Usa el módulo scope para visualizar tu sonido. Sin límites. Solo tu visión.', concept: 'Crea un track de 10+ módulos y 12+ conexiones. Debe tener: keyboard O sequencer, pianoroll para lead, múltiples osciladores, filtros modulados, reverb/delay, y OBLIGATORIO: scope module para visualización. Mixer para balance. Sonido profesional, único y musical.', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb', 'delay', 'sequencer', 'pianoroll', 'keyboard', 'scope'], preplacedModules: [ { id: 1, type: 'output', x: 1000, y: 160, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 8 }, checks: [ { star: 1, name: 'Track básica', desc: '10+ módulos, 12+ conexiones, scope presente, sonido a través de output', test: (mods, conns) => { const nonOut = mods.filter(m => m.type !== 'output'); const scope = mods.find(m => m.type === 'scope'); const out = mods.find(m => m.type === 'output'); if (nonOut.length < 10 || !scope || !out) return false; const hasOutput = conns.some(c => c.to.moduleId === out.id); return conns.length >= 12 && hasOutput; }, }, { star: 2, name: 'Estructura musical', desc: '4+ secciones reconocibles: lead, bass, pads, efectos. Scope visualiza.', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const seq = mods.find(m => m.type === 'sequencer'); const pr = mods.find(m => m.type === 'pianoroll'); const flt = mods.filter(m => m.type === 'filter'); const rev = mods.find(m => m.type === 'reverb'); const scope = mods.find(m => m.type === 'scope'); if (oscs.length < 4 || !scope) return false; const hasSequencing = seq || pr; const hasMelody = (pr && conns.some(c => c.from.moduleId === pr.id)) || (seq && conns.some(c => c.from.moduleId === seq.id)); return hasSequencing && flt.length >= 2 && rev && hasMelody; }, }, { star: 3, name: 'Masterpiece', desc: '10+ módulos, keyboard/sequencer/pianoroll, 4+ oscs, mixer, 3+ efectos, scope, 15+ conexiones, música profesional', test: (mods, conns) => { const nonOut = mods.filter(m => m.type !== 'output'); const oscs = mods.filter(m => m.type === 'oscillator'); const seq = mods.find(m => m.type === 'sequencer'); const pr = mods.find(m => m.type === 'pianoroll'); const kb = mods.find(m => m.type === 'keyboard'); const mixer = mods.find(m => m.type === 'mixer'); const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type)); const scope = mods.find(m => m.type === 'scope'); if (nonOut.length < 10 || oscs.length < 4 || !mixer || !scope || conns.length < 15) return false; const hasControl = (seq && conns.some(c => c.from.moduleId === seq.id)) || (pr && conns.some(c => c.from.moduleId === pr.id)) || (kb && conns.some(c => c.from.moduleId === kb.id)); return hasControl && effects.length >= 3 && conns.length >= 15; }, }, ], }, ], };