/** * World 4 — "Modulación" (Modulation) * * Teaches: LFO routing, vibrato, PWM, FM synthesis, ring modulation, complex patches * 8 levels, progressive difficulty */ export const WORLD_4 = { id: 'w4', name: 'Modulación', subtitle: 'Haz que el sonido viva y respire', icon: '∿', color: '#ffcc00', unlockStars: 36, levels: [ // ─────────────── LEVEL 4.1 ─────────────── { id: 'w4-1', title: 'Vibrato', subtitle: 'LFO → Frecuencia', description: 'El vibrato es una oscilación sutil de la frecuencia. Cantantes, violinistas y sintetizadores lo usan para dar expresividad. Se consigue conectando un LFO lento a la frecuencia del oscilador.', concept: 'Conecta un LFO a la entrada "Freq" del oscilador. Un LFO a ~5-7 Hz con amplitud pequeña crea un vibrato natural. Demasiado rápido o amplio suena a sirena.', availableModules: ['lfo'], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'sine', frequency: 440, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 440 } }, ], lfo: { frequency: 6, type: 'sine', min: 420, max: 460, target: 'frequency' }, duration: 3, }, checks: [ { star: 1, name: 'Señal básica', desc: 'Oscilador conectado a la salida', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const out = mods.find(m => m.type === 'output'); return osc && out && conns.some(c => c.from.moduleId === osc.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'LFO a frecuencia', desc: 'Conecta LFO → Osc (Freq)', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const osc = mods.find(m => m.type === 'oscillator'); if (!lfo || !osc) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === osc.id && c.to.port === 'freq'); }, }, { star: 3, name: 'Vibrato musical', desc: 'LFO entre 4-8 Hz (vibrato natural)', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; const rate = lfo.params.frequency ?? 2; return rate >= 4 && rate <= 8; }, }, ], }, // ─────────────── LEVEL 4.2 ─────────────── { id: 'w4-2', title: 'Sirena', subtitle: 'LFO lento = pitch sweep', description: 'Cuando el LFO es muy lento y con mucha amplitud, el vibrato se convierte en un barrido de frecuencia — como una sirena. Los DJs y productores usan este efecto para crear tensión y transiciones.', concept: 'Usa un LFO muy lento (~0.2-0.5 Hz) con forma de onda sine o triangle conectado a la frecuencia del oscilador. La velocidad lenta crea un sweep dramático arriba y abajo.', availableModules: ['lfo'], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'square', frequency: 440, detune: 0 }, locked: false }, { id: 2, type: 'output', x: 500, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'square', frequency: 440 } }, ], lfo: { frequency: 0.3, type: 'sine', min: 200, max: 800, target: 'frequency' }, duration: 4, }, checks: [ { star: 1, name: 'LFO conectado', desc: 'LFO → Osc (Freq) → Output', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const osc = mods.find(m => m.type === 'oscillator'); if (!lfo || !osc) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === osc.id && c.to.port === 'freq'); }, }, { star: 2, name: 'Sweep lento', desc: 'LFO por debajo de 1 Hz', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); return lfo && (lfo.params.frequency ?? 2) < 1; }, }, { star: 3, name: 'Sirena perfecta', desc: 'LFO 0.1-0.5 Hz, onda sine o triangle', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; const rate = lfo.params.frequency ?? 2; const wave = lfo.params.waveform ?? 'sine'; return rate >= 0.1 && rate <= 0.5 && (wave === 'sine' || wave === 'triangle'); }, }, ], }, // ─────────────── LEVEL 4.3 ─────────────── { id: 'w4-3', title: 'Wah-Wah Rítmico', subtitle: 'LFO square → Cutoff', description: 'Un LFO con onda cuadrada crea cambios bruscos en el cutoff del filtro — el filtro salta entre abierto y cerrado. Es un efecto rítmico perfecto para música electrónica y funk.', concept: 'LFO square a ~2-4 Hz conectado al cutoff del filtro. La onda cuadrada crea un on/off rítmico. Ajusta el cutoff base del filtro y la resonancia para darle más carácter.', availableModules: ['lfo'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 60, params: { waveform: 'sawtooth', frequency: 110, detune: 0 }, locked: true }, { id: 2, type: 'filter', x: 300, y: 60, params: { type: 'lowpass', frequency: 600, Q: 4 }, locked: false }, { id: 3, type: 'output', x: 560, y: 80, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 110 } }, ], filter: { type: 'lowpass', frequency: 800, Q: 5 }, lfo: { frequency: 3, type: 'square', min: 400, max: 4000, target: 'frequency' }, duration: 3, }, checks: [ { star: 1, name: 'Cadena con LFO', desc: 'Osc → Filter → Out, LFO al Cutoff', 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 out = mods.find(m => m.type === 'output'); if (!osc || !flt || !lfo || !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 === out.id) && conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); }, }, { star: 2, name: 'LFO cuadrado', desc: 'LFO con onda square', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); return lfo && (lfo.params.waveform ?? 'sine') === 'square'; }, }, { star: 3, name: 'Wah rítmico', desc: 'LFO square a 2-4 Hz, filtro con Q > 3', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); const flt = mods.find(m => m.type === 'filter'); if (!lfo || !flt) return false; const rate = lfo.params.frequency ?? 2; return (lfo.params.waveform ?? 'sine') === 'square' && rate >= 2 && rate <= 4 && (flt.params.Q ?? 1) > 3; }, }, ], }, // ─────────────── LEVEL 4.4 ─────────────── { id: 'w4-4', title: 'Auto-Pan', subtitle: 'Sonido en movimiento', description: 'Conectar LFOs a los niveles de un mixer permite mover el sonido entre canales. Si envías el mismo oscilador al Left y Right con LFOs invertidos, el sonido viaja de un altavoz al otro.', concept: 'Conecta el oscilador al output con dos cables (Left y Right). Añade un LFO que module algo para crear movimiento estéreo. El efecto auto-pan crea una sensación de espacio.', availableModules: ['lfo', 'vca', 'mixer'], preplacedModules: [ { id: 1, type: 'oscillator', x: 80, y: 80, params: { waveform: 'sine', frequency: 440, detune: 0 }, locked: true }, { id: 2, type: 'output', x: 600, y: 100, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 440 } }, ], lfo: { frequency: 2, type: 'sine', min: 0.3, max: 1.0, target: 'amplitude' }, duration: 3, }, checks: [ { star: 1, name: 'Estéreo', desc: 'Oscilador conectado a ambos canales (Left + Right)', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const out = mods.find(m => m.type === 'output'); if (!osc || !out) return false; // Direct or through other modules to both channels const toLeft = conns.some(c => c.to.moduleId === out.id && c.to.port === 'left'); const toRight = conns.some(c => c.to.moduleId === out.id && c.to.port === 'right'); return toLeft && toRight; }, }, { star: 2, name: 'LFO presente', desc: 'Hay al menos un LFO conectado', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; return conns.some(c => c.from.moduleId === lfo.id); }, }, { star: 3, name: 'Modulación estéreo', desc: 'LFO modula VCA(s) en la cadena estéreo', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const vcas = mods.filter(m => m.type === 'vca'); if (!lfo || vcas.length < 1) return false; return vcas.some(v => conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === v.id)); }, }, ], }, // ─────────────── LEVEL 4.5 ─────────────── { id: 'w4-5', title: 'Doble Modulación', subtitle: 'LFO al filter + LFO al VCA', description: 'Un solo LFO puede modular múltiples destinos a la vez. Conectar el mismo LFO al cutoff del filtro y al gain del VCA crea un sonido que se abre y se hace más fuerte simultáneamente — un efecto muy dinámico.', concept: 'Usa un LFO y conéctalo tanto al Cutoff del filtro como al CV del VCA. El mismo movimiento cíclico afecta brillo y volumen a la vez. Ajusta ~2-3 Hz.', availableModules: ['lfo'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 40, params: { waveform: 'sawtooth', frequency: 110, detune: 0 }, locked: true }, { id: 2, type: 'filter', x: 280, y: 40, params: { type: 'lowpass', frequency: 800, Q: 5 }, locked: false }, { id: 3, type: 'vca', x: 480, y: 40, params: { gain: 0.6 }, locked: false }, { id: 4, type: 'output', x: 680, y: 60, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 110 } }, ], filter: { type: 'lowpass', frequency: 1200, Q: 6 }, lfo: { frequency: 2.5, type: 'sine', min: 400, max: 3500, target: 'frequency' }, duration: 3, }, checks: [ { star: 1, name: 'Cadena completa', desc: 'Osc → Filter → VCA → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); const out = mods.find(m => m.type === 'output'); if (!osc || !flt || !vca || !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 === vca.id) && conns.some(c => c.from.moduleId === vca.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'LFO a dos destinos', desc: 'Un LFO conectado al Cutoff Y al CV', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); if (!lfo || !flt || !vca) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff') && conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === vca.id && c.to.port === 'cv'); }, }, { star: 3, name: 'Pulso rítmico', desc: 'LFO a 1-4 Hz, filtro resonante (Q > 4)', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); const flt = mods.find(m => m.type === 'filter'); if (!lfo || !flt) return false; const rate = lfo.params.frequency ?? 2; return rate >= 1 && rate <= 4 && (flt.params.Q ?? 1) > 4; }, }, ], }, // ─────────────── LEVEL 4.6 ─────────────── { id: 'w4-6', title: 'Cross-Modulation', subtitle: 'Oscilador modula oscilador', description: 'Cuando un oscilador modula la frecuencia de otro oscilador a velocidades audibles (>20 Hz), se crea FM synthesis — timbres metálicos, campanas, y texturas inarmónicas que no puedes conseguir de otra forma.', concept: 'Conecta la salida de un oscilador a la entrada "Freq" de otro. Si el modulador está a frecuencia audible (>50 Hz), se crea FM. Frequencies bajas = vibrato, altas = nuevos timbres.', availableModules: ['oscillator', 'mixer'], preplacedModules: [ { id: 1, type: 'output', x: 600, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sine', frequency: 220 } }, { type: 'oscillator', params: { waveform: 'sine', frequency: 880 } }, ], duration: 3, }, checks: [ { star: 1, name: 'Dos osciladores', desc: 'Al menos 2 osciladores con uno modulando al otro', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); if (oscs.length < 2) return false; // One osc connected to another osc's freq return oscs.some(o1 => oscs.some(o2 => o1.id !== o2.id && conns.some(c => c.from.moduleId === o1.id && c.to.moduleId === o2.id && c.to.port === 'freq' ) )); }, }, { star: 2, name: 'Sonido audible', desc: 'El oscilador portador está conectado a la salida', test: (mods, conns) => { const out = mods.find(m => m.type === 'output'); if (!out) return false; // Something reaches the output return conns.some(c => c.to.moduleId === out.id); }, }, { star: 3, name: 'FM metálico', desc: 'Modulador > 50 Hz (crea timbres FM reales)', test: (mods, conns) => { const oscs = mods.filter(m => m.type === 'oscillator'); if (oscs.length < 2) return false; // Find modulator: osc that connects to another osc's freq for (const o1 of oscs) { for (const o2 of oscs) { if (o1.id !== o2.id) { const isModulating = conns.some(c => c.from.moduleId === o1.id && c.to.moduleId === o2.id && c.to.port === 'freq' ); if (isModulating && (o1.params.frequency ?? 440) > 50) return true; } } } return false; }, }, ], }, // ─────────────── LEVEL 4.7 ─────────────── { id: 'w4-7', title: 'Modulación Compleja', subtitle: 'Multi-destino', description: 'Los sintetizadores modulares brillan cuando conectas múltiples fuentes de modulación a múltiples destinos. Un LFO al filtro, un envelope al VCA, el keyboard a la frecuencia — cada conexión añade expresividad.', concept: 'Construye un patch con: Keyboard → Osc freq + Env gate. LFO → Filter cutoff. Envelope → VCA cv. Cada fuente de modulación controla un aspecto diferente del sonido.', availableModules: ['lfo', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'oscillator', x: 60, y: 40, params: { waveform: 'sawtooth', frequency: 220, detune: 0 }, locked: false }, { id: 2, type: 'filter', x: 280, y: 40, params: { type: 'lowpass', frequency: 1000, Q: 4 }, locked: false }, { id: 3, type: 'vca', x: 480, y: 40, params: { gain: 0 }, locked: false }, { id: 4, type: 'output', x: 680, y: 60, params: { volume: -6 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 220 } }, ], filter: { type: 'lowpass', frequency: 1500, Q: 5 }, envelope: { attack: 0.1, decay: 0.3, sustain: 0.5, release: 0.4 }, lfo: { frequency: 3, type: 'sine', min: 600, max: 3000, target: 'frequency' }, duration: 3, }, checks: [ { star: 1, name: 'Cadena sustractiva', desc: 'Osc → Filter → VCA → Output', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); const out = mods.find(m => m.type === 'output'); if (!osc || !flt || !vca || !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 === vca.id) && conns.some(c => c.from.moduleId === vca.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Tres moduladores', desc: 'LFO, Envelope y Keyboard todos conectados', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const env = mods.find(m => m.type === 'envelope'); const kb = mods.find(m => m.type === 'keyboard'); if (!lfo || !env || !kb) return false; const lfoConn = conns.some(c => c.from.moduleId === lfo.id); const envConn = conns.some(c => c.from.moduleId === env.id); const kbConn = conns.some(c => c.from.moduleId === kb.id); return lfoConn && envConn && kbConn; }, }, { star: 3, name: 'Routing correcto', desc: 'KB→freq, LFO→cutoff, Env→VCA cv, KB→gate', test: (mods, conns) => { const lfo = mods.find(m => m.type === 'lfo'); const env = mods.find(m => m.type === 'envelope'); const kb = mods.find(m => m.type === 'keyboard'); const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); if (!lfo || !env || !kb || !osc || !flt || !vca) return false; return conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === osc.id && c.to.port === 'freq') && conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff') && conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv') && conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === env.id && c.to.port === 'gate'); }, }, ], }, // ─────────────── LEVEL 4.8: BOSS ─────────────── { id: 'w4-8', title: 'Dubstep Wobble', subtitle: 'BOSS: El bajo que wobbles', description: 'El wobble bass de dubstep es modulación pura: un oscilador grave con un filtro lowpass resonante modulado por un LFO. Añade un envelope para el ataque y tienes el sonido que definió un género.', concept: 'Osc saw grave (~55 Hz) → Filter LP resonante → VCA → Output. LFO (~1-3 Hz) → Filter cutoff. Envelope → VCA cv. Keyboard → gate + freq. Q alta (~10+) para ese sonido agresivo.', availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [ { type: 'oscillator', params: { waveform: 'sawtooth', frequency: 55 } }, ], filter: { type: 'lowpass', frequency: 400, Q: 10 }, envelope: { attack: 0.02, decay: 0.2, sustain: 0.7, release: 0.3 }, lfo: { frequency: 1.5, type: 'sine', min: 200, max: 2000, target: 'frequency' }, duration: 4, }, checks: [ { star: 1, name: 'Cadena con modulación', desc: 'Osc → Filter → VCA → Output con LFO al cutoff', test: (mods, conns) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); const lfo = mods.find(m => m.type === 'lfo'); const out = mods.find(m => m.type === 'output'); if (!osc || !flt || !vca || !lfo || !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 === vca.id) && conns.some(c => c.from.moduleId === vca.id && c.to.moduleId === out.id) && conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); }, }, { star: 2, name: 'Wobble bass', desc: 'Osc grave (<130 Hz), LFO lento (1-3 Hz), Q > 8', test: (mods) => { const osc = mods.find(m => m.type === 'oscillator'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); if (!osc || !flt || !lfo) return false; return (osc.params.frequency ?? 440) < 130 && (lfo.params.frequency ?? 2) >= 1 && (lfo.params.frequency ?? 2) <= 3 && (flt.params.Q ?? 1) > 8; }, }, { star: 3, name: 'Wobble completo', desc: 'Todo lo anterior + Envelope al VCA + Keyboard al gate', 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 env = mods.find(m => m.type === 'envelope'); const vca = mods.find(m => m.type === 'vca'); const kb = mods.find(m => m.type === 'keyboard'); if (!osc || !flt || !lfo || !env || !vca || !kb) return false; return (osc.params.frequency ?? 440) < 130 && (flt.params.Q ?? 1) > 8 && conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv') && conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === env.id && c.to.port === 'gate'); }, }, ], }, ], };