/** * World 5 — "Efectos" (Effects) * * Teaches: delay, reverb, distortion, effect chains, wet/dry mixing * 8 levels, progressive difficulty */ export const WORLD_5 = { id: 'w5', name: 'Efectos', subtitle: 'Transforma el sonido con efectos', icon: '◈', color: '#44ff88', unlockStars: 48, levels: [ // ─────────────── LEVEL 5.1 ─────────────── { id: 'w5-1', title: 'El Eco', subtitle: 'Delay básico', description: 'El delay repite el sonido después de un tiempo. Es como gritar en un cañón y escuchar tu voz rebotando. El delay más simple tiene un tiempo de repetición y un feedback que controla cuántas veces se repite.', concept: 'Conecta: Oscilador → Delay → Output. El knob "Time" controla el tiempo entre repeticiones. El "Feedback" controla cuántas repeticiones. Empieza con un feedback bajo (~0.3).', availableModules: [], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'sine', frequency: 440, detune: 0 }, locked: true }, { id: 2, type: 'delay', x: 340, y: 80, params: { time: 0.3, feedback: 0.3, mix: 0.5 }, locked: false }, { id: 3, type: 'output', x: 600, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 440 } }, ], effects: [ { type: 'delay', time: 0.35, feedback: 0.4, wet: 0.6 }, ], duration: 3, }, checks: [ { star: 1, name: 'Delay conectado', desc: 'Oscilador → Delay → Salida', 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: 'Repeticiones', desc: 'Feedback por encima de 0.2', test: (mods) => { const del = mods.find(m => m.type === 'delay'); return del && (del.params.feedback ?? 0) > 0.2; }, }, { star: 3, name: 'Eco rítmico', desc: 'Delay time 0.2-0.5s, feedback 0.3-0.6', test: (mods) => { const del = mods.find(m => m.type === 'delay'); if (!del) return false; const t = del.params.time ?? 0.3; const fb = del.params.feedback ?? 0; return t >= 0.2 && t <= 0.5 && fb >= 0.3 && fb <= 0.6; }, }, ], }, // ─────────────── LEVEL 5.2 ─────────────── { id: 'w5-2', title: 'Slapback', subtitle: 'El delay rockabilly', description: 'El slapback es un delay muy corto (50-120ms) con una sola repetición. Es el efecto clásico de las voces de Elvis y el rockabilly — da presencia sin crear un eco largo.', concept: 'Delay con tiempo corto (~0.05-0.12s) y feedback muy bajo (~0.1 o menos). Una sola repetición rápida. El mix controla cuánto delay se mezcla con la señal original.', availableModules: ['delay'], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'square', frequency: 330, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 600, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'square', frequency: 330 } }, ], effects: [ { type: 'delay', time: 0.08, feedback: 0.05, wet: 0.5 }, ], duration: 2, }, checks: [ { star: 1, name: 'Delay en la cadena', desc: 'Osc → Delay → Salida', 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: 'Tiempo corto', desc: 'Delay time menor de 0.15s', test: (mods) => { const del = mods.find(m => m.type === 'delay'); return del && (del.params.time ?? 0.3) < 0.15; }, }, { star: 3, name: 'Slapback perfecto', desc: 'Time 0.05-0.12s, feedback < 0.15', test: (mods) => { const del = mods.find(m => m.type === 'delay'); if (!del) return false; const t = del.params.time ?? 0.3; const fb = del.params.feedback ?? 0.3; return t >= 0.05 && t <= 0.12 && fb < 0.15; }, }, ], }, // ─────────────── LEVEL 5.3 ─────────────── { id: 'w5-3', title: 'Reverb Espacial', subtitle: 'El sonido del espacio', description: 'La reverb simula el sonido de un espacio acústico — desde una habitación pequeña hasta una catedral enorme. Es quizás el efecto más usado en toda la producción musical.', concept: 'Conecta: Oscilador → Reverb → Output. El knob de "decay" (o room size) controla el tamaño del espacio. Más largo = catedral. Más corto = habitación pequeña.', availableModules: [], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'triangle', frequency: 440, detune: 0 }, locked: true }, { id: 2, type: 'reverb', x: 340, y: 80, params: { decay: 2, mix: 0.4 }, locked: false }, { id: 3, type: 'output', x: 600, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'triangle', frequency: 440 } }, ], effects: [ { type: 'reverb', decay: 5.5, wet: 0.55 }, ], duration: 3, }, checks: [ { star: 1, name: 'Reverb conectada', desc: 'Oscilador → Reverb → Salida', 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: 'Espacio grande', desc: 'Decay mayor de 3 segundos', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); return rev && (rev.params.decay ?? 2) > 3; }, }, { star: 3, name: 'Catedral', desc: 'Decay > 5s, mix 0.3-0.6 (no demasiado)', test: (mods) => { const rev = mods.find(m => m.type === 'reverb'); if (!rev) return false; return (rev.params.decay ?? 2) > 5 && (rev.params.mix ?? 0.5) >= 0.3 && (rev.params.mix ?? 0.5) <= 0.6; }, }, ], }, // ─────────────── LEVEL 5.4 ─────────────── { id: 'w5-4', title: 'Distorsión', subtitle: 'Rompe la señal', description: 'La distorsión amplifica la señal hasta que se "rompe", creando armónicos nuevos. Desde el overdrive suave de un amplificador de guitarra hasta el fuzz salvaje — la distorsión añade agresividad y presencia.', concept: 'Conecta: Oscilador → Distortion → Output. Sube el "Drive" para más distorsión. Con una onda sine pura, escucharás cómo aparecen armónicos que no estaban antes.', availableModules: ['distortion'], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'sine', frequency: 220, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 600, y: 100, params: { volume: -10 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 220 } }, ], effects: [ { type: 'distortion', amount: 6 }, ], duration: 2.5, }, checks: [ { star: 1, name: 'Distorsión conectada', desc: 'Osc → Distortion → Salida', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const dist = mods.find(m => m.type === 'distortion'); const out = mods.find(m => m.type === 'output'); if (!osc || !dist || !out) return false; return conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === dist.id) && conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Drive alto', desc: 'Distorsión con drive moderado-alto', test: (mods) => { const dist = mods.find(m => m.type === 'distortion'); return dist && (dist.params.drive ?? 1) > 3; }, }, { star: 3, name: 'Fuzz sine', desc: 'Onda sine con drive > 5 (máxima transformación)', test: (mods) => { const osc = mods.find(m => m.type === 'oscillator'); const dist = mods.find(m => m.type === 'distortion'); if (!osc || !dist) return false; return osc.params.waveform === 'sine' && (dist.params.drive ?? 1) > 5; }, }, ], }, // ─────────────── LEVEL 5.5 ─────────────── { id: 'w5-5', title: 'Cadena de Efectos', subtitle: 'Orden importa', description: 'El orden de los efectos cambia radicalmente el resultado. Distorsión antes de delay suena diferente a delay antes de distorsión. Experimenta encadenando efectos en diferente orden.', concept: 'Prueba: Osc → Distortion → Delay → Output (la distorsión se repite limpia). El orden crea caracteres distintos. Encadena al menos 2 efectos diferentes.', availableModules: ['delay', 'distortion'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 80, params: { waveform: 'sawtooth', frequency: 220, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 740, y: 100, params: { volume: -10 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 220 } }, ], effects: [ { type: 'distortion', amount: 3 }, { type: 'delay', time: 0.35, feedback: 0.35, wet: 0.5 }, ], duration: 3, }, checks: [ { star: 1, name: 'Dos efectos', desc: 'Al menos 2 módulos de efecto en la cadena', test: (mods, conns) => { const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type)); const out = mods.find(m => m.type === 'output'); return effects.length >= 2 && out && conns.some(c => c.to.moduleId === out.id); }, }, { star: 2, name: 'Efectos encadenados', desc: 'Los efectos están conectados en serie (uno al otro)', test: (mods, conns) => { const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type)); if (effects.length < 2) return false; // Check if any effect connects to another effect return effects.some(e1 => effects.some(e2 => e1.id !== e2.id && conns.some(c => c.from.moduleId === e1.id && c.to.moduleId === e2.id) )); }, }, { star: 3, name: 'Cadena completa', desc: 'Osc → efecto1 → efecto2 → Output (cadena lineal)', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type)); const out = mods.find(m => m.type === 'output'); if (!osc || effects.length < 2 || !out) return false; // Osc → some effect const oscToFx = effects.find(e => conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === e.id)); if (!oscToFx) return false; // That effect → another effect const fxToFx = effects.find(e => e.id !== oscToFx.id && conns.some(c => c.from.moduleId === oscToFx.id && c.to.moduleId === e.id)); if (!fxToFx) return false; // Second effect → output return conns.some(c => c.from.moduleId === fxToFx.id && c.to.moduleId === out.id); }, }, ], }, // ─────────────── LEVEL 5.6 ─────────────── { id: 'w5-6', title: 'Delay + Filtro', subtitle: 'Dub echo', description: 'El sonido dub es delay con feedback alto pasado por un filtro que va quitando agudos. Cada repetición suena más oscura y lejana — es el efecto que definió el reggae dub en los 70.', concept: 'Osc → Delay (feedback alto ~0.5-0.7) → Filter (lowpass, cutoff bajo ~800 Hz) → Output. El filtro después del delay oscurece las repeticiones, creando profundidad.', availableModules: ['delay', 'filter'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 80, params: { waveform: 'square', frequency: 330, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 720, y: 100, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'square', frequency: 330 } }, ], filter: { type: 'lowpass', frequency: 850, Q: 2 }, effects: [ { type: 'delay', time: 0.4, feedback: 0.6, wet: 0.6 }, ], duration: 3, }, checks: [ { star: 1, name: 'Delay + Filter', desc: 'Osc → Delay → Filter → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const del = mods.find(m => m.type === 'delay'); const flt = mods.find(m => m.type === 'filter'); const out = mods.find(m => m.type === 'output'); if (!osc || !del || !flt || !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 === flt.id) && conns.some(c => c.from.moduleId === flt.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Echo largo', desc: 'Delay feedback > 0.4, time > 0.2s', test: (mods) => { const del = mods.find(m => m.type === 'delay'); return del && (del.params.feedback ?? 0) > 0.4 && (del.params.time ?? 0.3) > 0.2; }, }, { star: 3, name: 'Dub echo', desc: 'Feedback 0.5-0.7, filtro lowpass cutoff < 1000 Hz', test: (mods) => { const del = mods.find(m => m.type === 'delay'); const flt = mods.find(m => m.type === 'filter'); if (!del || !flt) return false; const fb = del.params.feedback ?? 0; return fb >= 0.5 && fb <= 0.7 && flt.params.type === 'lowpass' && (flt.params.frequency ?? 2000) < 1000; }, }, ], }, // ─────────────── LEVEL 5.7 ─────────────── { id: 'w5-7', title: 'Shoegaze Wall', subtitle: 'Reverb + Distorsión', description: 'El sonido shoegaze (My Bloody Valentine, Slowdive) es una pared de sonido creada con distorsión y reverb masiva. La distorsión aplasta la señal y la reverb la convierte en una nube etérea.', concept: 'Osc → Distortion (drive medio) → Reverb (decay largo, mix alto) → Output. La combinación de distorsión y reverb crea una textura densa y atmosférica.', availableModules: ['distortion', 'reverb'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 80, params: { waveform: 'sawtooth', frequency: 220, detune: 0 }, locked: false }, { id: 2, type: 'output', x: 720, y: 100, params: { volume: -10 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 220 } }, ], effects: [ { type: 'distortion', amount: 5 }, { type: 'reverb', decay: 6.5, wet: 0.65 }, ], duration: 4, }, checks: [ { star: 1, name: 'Dist + Reverb', desc: 'Osc → Distortion → Reverb → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const dist = mods.find(m => m.type === 'distortion'); const rev = mods.find(m => m.type === 'reverb'); const out = mods.find(m => m.type === 'output'); if (!osc || !dist || !rev || !out) return false; return conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === dist.id) && conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === rev.id) && conns.some(c => c.from.moduleId === rev.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Pared de sonido', desc: 'Drive > 3, reverb decay > 4s', test: (mods) => { const dist = mods.find(m => m.type === 'distortion'); const rev = mods.find(m => m.type === 'reverb'); if (!dist || !rev) return false; return (dist.params.drive ?? 1) > 3 && (rev.params.decay ?? 2) > 4; }, }, { star: 3, name: 'Shoegaze perfecto', desc: 'Drive 4-8, decay > 6s, reverb mix > 0.5', test: (mods) => { const dist = mods.find(m => m.type === 'distortion'); const rev = mods.find(m => m.type === 'reverb'); if (!dist || !rev) return false; const drive = dist.params.drive ?? 1; return drive >= 4 && drive <= 8 && (rev.params.decay ?? 2) > 6 && (rev.params.mix ?? 0.5) > 0.5; }, }, ], }, // ─────────────── LEVEL 5.8: BOSS ─────────────── { id: 'w5-8', title: 'Ambient Scape', subtitle: 'BOSS: Paisaje sonoro', description: 'Crea un paisaje sonoro ambient completo: un sonido que evoluciona lentamente, envuelto en efectos. Combina osciladores, filtros, modulación y efectos para crear una textura atmosférica.', concept: 'Osc → Filter (LFO al cutoff) → Delay → Reverb → Output. Envelope al VCA para control. Experimenta con tiempos largos, feedback alto, y modulación lenta para un sonido que "flota".', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'delay', 'reverb', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 900, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 110 } }, ], filter: { type: 'lowpass', frequency: 1200, Q: 3 }, envelope: { attack: 0.5, decay: 0.3, sustain: 0.6, release: 1.5 }, lfo: { frequency: 0.5, type: 'sine', min: 400, max: 4000, target: 'frequency' }, effects: [ { type: 'delay', time: 0.5, feedback: 0.5, wet: 0.5 }, { type: 'reverb', decay: 5, wet: 0.6 }, ], duration: 5, }, checks: [ { star: 1, name: 'Cadena con efectos', desc: 'Al menos un efecto (delay/reverb) conectado a la salida', test: (mods, conns) => { const effects = mods.filter(m => ['delay', 'reverb'].includes(m.type)); const out = mods.find(m => m.type === 'output'); if (effects.length === 0 || !out) return false; return effects.some(e => conns.some(c => c.from.moduleId === e.id && c.to.moduleId === out.id)) || conns.some(c => c.to.moduleId === out.id && effects.some(e => c.from.moduleId === e.id)); }, }, { star: 2, name: 'Modulación + Efectos', desc: 'Tiene oscilador, filtro, y al menos 2 efectos conectados', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type)); if (!osc || !flt || effects.length < 2) return false; // Check osc is connected to something return conns.some(c => c.from.moduleId === osc.id) && effects.length >= 2; }, }, { star: 3, name: 'Paisaje completo', desc: 'Osc+Filter+LFO(cutoff)+Delay+Reverb, todo conectado', test: (mods, conns) => { 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 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 || !lfo || !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 === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff') && conns.some(c => c.to.moduleId === out.id); }, }, ], }, ], };