/** * World 10 — "Espacio y Stereo" (Space and Stereo) * * Teaches: Stereo imaging, spatial effects, delay for width, reverb placement * 8 levels, boss challenges with complete stereo mix */ export const WORLD_10 = { id: 'w10', name: 'Espacio y Stereo', subtitle: 'Profundidad y dimensión', icon: '◉◉', color: '#44ddaa', unlockStars: 108, levels: [ // ─────────────── LEVEL 10.1 ─────────────── { id: 'w10-1', title: 'Pan Left-Right', subtitle: 'Los canales estéreo básicos', description: 'La estéreo más simple: coloca una fuente en el canal izquierdo y otra en el derecho. El output tiene dos entradas: "left" y "right". Conecta diferentes osciladores a cada uno.', concept: 'Osc 1 → Output (left). Osc 2 → Output (right). El output tiene dos canales separados. Juntos crean la ilusión de width — como si el sonido viniera de dos lugares diferentes.', availableModules: ['oscillator', 'vca', 'envelope', 'keyboard', 'mixer'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 220, detune: 0 } }, { type: 'oscillator', params: { waveform: 'sine', frequency: 330, detune: 0 } }, ], envelope: { attack: 0.05, decay: 0.3, sustain: 0.4, release: 0.2 }, duration: 3, }, checks: [ { star: 1, name: 'Estéreo básica', desc: 'Dos osciladores, uno al left, uno al right', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || !out) return false; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); return leftConn && rightConn; }, }, { star: 2, name: 'Estéreo con VCA', desc: 'Cada oscilador con su VCA antes de output', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const vcas = mods.filter(m => m.type === 'vca'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || vcas.length < 2 || !out) return false; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); return leftConn && rightConn && oscs.some(o => conns.some(c => c.from.moduleId === o.id && c.to.moduleId === vcas[0].id)); }, }, { star: 3, name: 'Estéreo Controlada', desc: 'Oscs left/right con envelopes separados gateados por keyboard', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const vcas = mods.filter(m => m.type === 'vca'); const envs = mods.filter(m => m.type === 'envelope'); const kb = mods.find(m => m.type === 'keyboard'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || vcas.length < 2 || envs.length < 2 || !kb || !out) return false; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); const gated = envs.filter(e => conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === e.id && c.to.port === 'gate') ); return leftConn && rightConn && gated.length >= 2; }, }, ], }, // ─────────────── LEVEL 10.2 ─────────────── { id: 'w10-2', title: 'Stereo Detune', subtitle: 'Ancho con osciladores diferentes', description: 'Coloca el mismo oscilador en ambos canales pero detuned: izquierda a la frecuencia exacta, derecha con un pequeño detune (+5 a +15 cents). Crea un "chorus" natural que te envuelve.', concept: 'Osc 1 (detune 0) → Left. Osc 2 (detune +7) a misma nota → Right. Cuando están cerca pero no iguales, el beating crea width. Es como tener dos cantantes cantando casi al unísono.', availableModules: ['oscillator', 'vca', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 220, detune: 0 } }, { type: 'oscillator', params: { waveform: 'sine', frequency: 220, detune: 8 } }, ], envelope: { attack: 0.06, decay: 0.35, sustain: 0.35, release: 0.25 }, duration: 3, }, checks: [ { star: 1, name: 'Dos osciladores detuned', desc: 'Oscs a misma frecuencia pero con detune diferente', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || !out) return false; const detunes = oscs.map(o => o.params.detune ?? 0); const freqs = oscs.map(o => o.params.frequency ?? 440); const sameFreq = Math.abs(freqs[0] - freqs[1]) < 10; const differentDetune = Math.abs(detunes[0] - detunes[1]) > 3; return sameFreq && differentDetune; }, }, { star: 2, name: 'Stereo width audible', desc: 'Detune entre oscs > 5 cents para efecto chorus', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); if (oscs.length < 2) return false; const detunes = oscs.map(o => o.params.detune ?? 0); return Math.abs(detunes[0] - detunes[1]) > 5; }, }, { star: 3, name: 'Chorus Estéreo', desc: 'Detuned oscs left/right con VCAs y envelopes', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const vcas = mods.filter(m => m.type === 'vca'); const envs = mods.filter(m => m.type === 'envelope'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || vcas.length < 2 || envs.length < 1 || !out) return false; const detunes = oscs.map(o => o.params.detune ?? 0); const freqs = oscs.map(o => o.params.frequency ?? 440); const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); return Math.abs(freqs[0] - freqs[1]) < 10 && Math.abs(detunes[0] - detunes[1]) > 5 && leftConn && rightConn; }, }, ], }, // ─────────────── LEVEL 10.3 ─────────────── { id: 'w10-3', title: 'Delay para Ancho', subtitle: 'La profundidad del eco', description: 'El delay es uno de los mejores trucos para width: copia la señal, la envía al otro canal con un pequeño delay (20-80ms). El cerebro interpreta esto como "la misma fuente reflejada en espacio".', concept: 'Osc → Left (seco). Osc → Delay (15-50ms) → Right. El delay crea la ilusión de distancia. Cuanto más delay, más separación. Mantén el feedback bajo para evitar caos.', availableModules: ['oscillator', 'vca', 'delay', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 165, detune: 0 } }, ], effects: [ { type: 'delay', delayTime: 0.035, feedback: 0.15, wet: 0.8 }, ], envelope: { attack: 0.08, decay: 0.4, sustain: 0.3, release: 0.3 }, duration: 3, }, checks: [ { star: 1, name: 'Delay en señal', desc: 'Oscilador → Delay → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const del = mods.find(m => m.type === 'delay'); const out = mods.find(m => m.type === 'output'); if (!osc || !del || !out) return false; return conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === del.id) && conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Delay corto', desc: 'Delay con tiempo entre 20-80ms', test: (mods) => { const del = mods.find(m => m.type === 'delay'); if (!del) return false; const time = del.params.delayTime ?? 0.3; return time >= 0.02 && time <= 0.08; }, }, { star: 3, name: 'Delay Estéreo', desc: 'Osc left + Osc/Delay right con envelopes', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const del = mods.find(m => m.type === 'delay'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 1 || !del || !out) return false; const time = del.params.delayTime ?? 0.3; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out.id && c.to.port === 'right'); return time >= 0.015 && time <= 0.1 && (del.params.feedback ?? 0.4) < 0.5 && leftConn && rightConn; }, }, ], }, // ─────────────── LEVEL 10.4 ─────────────── { id: 'w10-4', title: 'Reverb Corta', subtitle: 'La sala pequeña', description: 'Una reverb corta (decay 1-2s) simula una habitación pequeña. No es mucha cola, solo lo suficiente para darle "espacio" al sonido sin que desaparezca en la distancia. Perfecto para síntesis.', concept: 'Osc → VCA → Reverb (decay 1-2s, wet 0.3-0.5) → Output. La reverb enturbia ligeramente el sonido y lo coloca "en una sala". Mantén wet bajo para que no sea un sonido amortiguado.', availableModules: ['oscillator', 'vca', 'reverb', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 262, detune: 0 } }, ], effects: [ { type: 'reverb', decay: 1.5, wet: 0.4 }, ], envelope: { attack: 0.07, decay: 0.4, sustain: 0.35, release: 0.25 }, duration: 3, }, checks: [ { star: 1, name: 'Reverb en la cadena', desc: 'Osc → Reverb → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const rev = mods.find(m => m.type === 'reverb'); const out = mods.find(m => m.type === 'output'); if (!osc || !rev || !out) return false; return conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === rev.id) && conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Decay corta', desc: 'Reverb con decay entre 1-2 segundos', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); if (!rev) return false; const decay = rev.params.decay ?? 3; return decay >= 1 && decay <= 2; }, }, { star: 3, name: 'Sala Perfecta', desc: 'Reverb (decay 1-2s, wet 0.3-0.5) + envelope al VCA', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const vca = mods.find(m => m.type === 'vca'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); if (!osc || !vca || !rev || !env) return false; const decay = rev.params.decay ?? 3; const wet = rev.params.wet ?? 0.4; return decay >= 1 && decay <= 2 && wet >= 0.25 && wet <= 0.6 && conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv'); }, }, ], }, // ─────────────── LEVEL 10.5 ─────────────── { id: 'w10-5', title: 'Catedral Reverb', subtitle: 'Los espacios enormes', description: 'Una catedral reverb es lo opuesto: decay largo (3+ segundos), wet alto. El sonido se desvanece lentamente, como si estuvieras en una basílica gigante. Crea atmósfera épica.', concept: 'Osc → VCA → Reverb (decay > 3s, wet > 0.5) → Output. El sonido se desmorona lentamente en el aire. Usa notas largas para aprovechar la cola reverb. ¡Es mágico!', availableModules: ['oscillator', 'vca', 'reverb', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 196, detune: 0 } }, ], effects: [ { type: 'reverb', decay: 4.2, wet: 0.65 }, ], envelope: { attack: 0.06, decay: 0.8, sustain: 0.4, release: 0.5 }, duration: 4, }, checks: [ { star: 1, name: 'Reverb larga', desc: 'Reverb con decay > 3 segundos', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); if (!rev) return false; return (rev.params.decay ?? 3) > 3; }, }, { star: 2, name: 'Reverb mojada', desc: 'Reverb con wet > 0.5 para efecto dramático', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); if (!rev) return false; return (rev.params.decay ?? 3) > 3 && (rev.params.wet ?? 0.4) > 0.5; }, }, { star: 3, name: 'Catedral Épica', desc: 'Reverb (decay > 4s, wet > 0.6) con envelope lento al VCA', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const vca = mods.find(m => m.type === 'vca'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); const kb = mods.find(m => m.type === 'keyboard'); if (!osc || !vca || !rev || !env || !kb) return false; return (rev.params.decay ?? 3) > 4 && (rev.params.wet ?? 0.4) > 0.6 && (env.params.attack ?? 0.01) < 0.1 && (env.params.decay ?? 0.2) > 0.5 && conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === env.id && c.to.port === 'gate'); }, }, ], }, // ─────────────── LEVEL 10.6 ─────────────── { id: 'w10-6', title: 'Slapback Echo', subtitle: 'Doblado rítmico', description: 'El slapback echo es un delay muy corto (100-200ms) sin feedback, que crea un efecto de "doblado" — como si hubiera una copia del sonido muy cerca. Popular en rockabilly y sintetizadores.', concept: 'Osc → Left (seco). Osc → Delay (100-200ms, feedback bajo) → Right. El delay corto mantiene la segunda "voz" identificable pero cercana. Es como tener un doblante.', availableModules: ['oscillator', 'vca', 'delay', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'square', frequency: 220, detune: 0 } }, ], effects: [ { type: 'delay', delayTime: 0.15, feedback: 0.08, wet: 0.75 }, ], envelope: { attack: 0.05, decay: 0.35, sustain: 0.4, release: 0.2 }, duration: 3, }, checks: [ { star: 1, name: 'Delay rítmico', desc: 'Delay entre 80-250ms', test: (mods) => { const del = mods.find(m => m.type === 'delay'); if (!del) return false; const time = del.params.delayTime ?? 0.3; return time >= 0.08 && time <= 0.25; }, }, { star: 2, name: 'Sin feedback', desc: 'Delay con feedback < 0.2 para no crear repeticiones caóticas', test: (mods) => { const del = mods.find(m => m.type === 'delay'); if (!del) return false; const time = del.params.delayTime ?? 0.3; const fb = del.params.feedback ?? 0.4; return time >= 0.08 && time <= 0.25 && fb < 0.2; }, }, { star: 3, name: 'Doblante Perfecto', desc: 'Delay (100-200ms, feedback < 0.1) en stereo left/right', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const del = mods.find(m => m.type === 'delay'); const out = mods.find(m => m.type === 'output'); if (!osc || !del || !out) return false; const time = del.params.delayTime ?? 0.3; const fb = del.params.feedback ?? 0.4; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out.id && c.to.port === 'right'); return time >= 0.1 && time <= 0.2 && fb < 0.1 && leftConn && rightConn; }, }, ], }, // ─────────────── LEVEL 10.7 ─────────────── { id: 'w10-7', title: 'Orden de Efectos', subtitle: 'La cadena de procesamiento', description: 'El orden de los efectos es crítico: ¿delay antes o después de reverb? ¿Filtro antes que distortion? Aquí aprendes a construir cadenas de efectos que suenen coherentes y profesionales.', concept: 'Construye: Osc → Filter → Distortion → Delay → Reverb → Output. Cada efecto transforma el anterior. El filtro quita brillo, distortion añade armónicos, delay añade movimiento, reverb añade espacio.', availableModules: ['oscillator', 'filter', 'distortion', 'delay', 'reverb', 'vca', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 900, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 280, detune: 0 } }, ], filter: { type: 'lowpass', frequency: 3500, Q: 1.5 }, effects: [ { type: 'distortion', distortion: 0.45, wet: 0.5 }, { type: 'delay', delayTime: 0.3, feedback: 0.35, wet: 0.55 }, { type: 'reverb', decay: 2.2, wet: 0.45 }, ], envelope: { attack: 0.08, decay: 0.45, sustain: 0.25, release: 0.3 }, duration: 4, }, checks: [ { star: 1, name: 'Cadena básica', desc: 'Osc → Filter → Delay → Reverb → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const del = mods.find(m => m.type === 'delay'); const rev = mods.find(m => m.type === 'reverb'); const out = mods.find(m => m.type === 'output'); if (!osc || !flt || !del || !rev || !out) return false; return conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === flt.id) && conns.some(c => c.from.moduleId === flt.id && c.to.moduleId === del.id) && conns.some(c => c.from.moduleId === del.id && c.to.moduleId === rev.id) && conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Con distortion', desc: 'Cadena con filtro + distortion + delay + reverb', test: (mods, conns) => { const flt = mods.find(m => m.type === 'filter'); const dist = mods.find(m => m.type === 'distortion'); const del = mods.find(m => m.type === 'delay'); const rev = mods.find(m => m.type === 'reverb'); if (!flt || !dist || !del || !rev) return false; return conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === del.id) || conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === rev.id); }, }, { star: 3, name: 'Cadena Profesional', desc: 'Osc → Filter → Distortion → Delay → Reverb con envelope', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const dist = mods.find(m => m.type === 'distortion'); const del = mods.find(m => m.type === 'delay'); const rev = mods.find(m => m.type === 'reverb'); const env = mods.find(m => m.type === 'envelope'); const out = mods.find(m => m.type === 'output'); if (!osc || !flt || !dist || !del || !rev || !env || !out) return false; const fltOsc = conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === flt.id); const distFlt = conns.some(c => c.from.moduleId === flt.id && c.to.moduleId === dist.id); const delDist = conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === del.id); const revDel = conns.some(c => c.from.moduleId === del.id && c.to.moduleId === rev.id); const outRev = conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); return fltOsc && distFlt && delDist && revDel && outRev; }, }, ], }, // ─────────────── LEVEL 10.8: BOSS ─────────────── { id: 'w10-8', title: 'Mix Espacial', subtitle: 'BOSS FINAL: Orquesta Estéreo', description: 'Construye una mezcla estéreo completa con múltiples fuentes, cada una con su propia posición en el espacio. Usa delay, reverb, y pan para colocar cada instrumento. Crea una orquesta de sintetizadores.', concept: 'Múltiples osciladores/fuentes, algunos en left/right, algunos con delay, algunos con reverb, todos controlados por keyboard/sequencer. La mezcla final debe sonar amplia, profunda, y multidimensional.', availableModules: ['oscillator', 'filter', 'vca', 'mixer', 'lfo', 'envelope', 'keyboard', 'sequencer', 'delay', 'reverb', 'distortion'], preplacedModules: [ { id: 1, type: 'output', x: 950, y: 160, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 110, detune: 0 } }, { type: 'oscillator', params: { waveform: 'sine', frequency: 110, detune: 10 } }, { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 220, detune: 0 } }, ], filter: { type: 'lowpass', frequency: 4000, Q: 1.3 }, lfo: { frequency: 0.6, type: 'sine', min: 2000, max: 5000, target: 'frequency' }, effects: [ { type: 'delay', delayTime: 0.25, feedback: 0.4, wet: 0.6 }, { type: 'reverb', decay: 3, wet: 0.55 }, ], envelope: { attack: 0.1, decay: 0.5, sustain: 0.3, release: 0.4 }, duration: 5, }, checks: [ { star: 1, name: 'Mezcla funcional', desc: 'Múltiples fuentes en left y right del output', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || !out) return false; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); return leftConn && rightConn && conns.length >= 8; }, }, { star: 2, name: 'Con efectos espaciales', desc: 'Delay y Reverb en la mezcla creando profundidad', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const del = mods.find(m => m.type === 'delay'); const rev = mods.find(m => m.type === 'reverb'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 2 || !del || !rev || !out) return false; const delToOut = conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out.id); const revToOut = conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); return delToOut && revToOut; }, }, { star: 3, name: 'Orquesta Completa', desc: '3+ oscs, stereo pan, delay + reverb, filter, envelope, keyboard/sequencer', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); const flts = mods.filter(m => m.type === 'filter'); const envs = mods.filter(m => m.type === 'envelope'); const del = mods.find(m => m.type === 'delay'); const rev = mods.find(m => m.type === 'reverb'); const kb = mods.find(m => m.type === 'keyboard'); const seq = mods.find(m => m.type === 'sequencer'); const out = mods.find(m => m.type === 'output'); if (oscs.length < 3 || flts.length < 1 || envs.length < 1 || !del || !rev || !out) return false; if (!kb && !seq) return false; const leftConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const rightConn = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); const delToOut = conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out.id); const revToOut = conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); return leftConn && rightConn && delToOut && revToOut && conns.length >= 15; }, }, ], }, ], };