/** * World 8 — "Texturas de Ruido" (Noise Textures) * * Teaches: noise types, wind sounds (bandpass), ocean waves (LFO on cutoff), * rain (noise + short envelope), radio static (noise + distortion), * industrial rhythm (noise + LFO on VCA), ambient texture (noise + reverb + delay) * 8 levels + boss challenge: "Paisaje Sonoro" (Soundscape) */ export const WORLD_8 = { id: 'w8', name: 'Texturas de Ruido', subtitle: 'Más allá de las notas', icon: '⣿', color: '#88aaff', unlockStars: 84, levels: [ // ─────────────── LEVEL 8.1 ─────────────── { id: 'w8-1', title: 'Ruido Blanco', subtitle: 'El sonido puro', description: 'El ruido blanco es aleatoriedad pura — todas las frecuencias con igual intensidad. Suena como estática de TV o lluvia lejana. Es el punto de partida para texturas ruidosas.', concept: 'Noise (tipo "white") → VCA → Output. Envelope al VCA. Sonido: "sssshhhhh" — simple pero bonito. Es la base de muchas texturas.', availableModules: ['noise', 'vca', 'envelope'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 1.5 }, checks: [ { star: 1, name: 'Ruido básico', desc: 'Noise → VCA → Output', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const vca = mods.find(m => m.type === 'vca'); const out = mods.find(m => m.type === 'output'); if (!noise || !vca || !out) return false; return conns.some(c => c.from.moduleId === noise.id && c.to.moduleId === vca.id) && conns.some(c => c.from.moduleId === vca.id && c.to.moduleId === out.id); }, }, { star: 2, name: 'Con envelope', desc: 'Envelope dispara el VCA', test: (mods, conns) => { const env = mods.find(m => m.type === 'envelope'); const vca = mods.find(m => m.type === 'vca'); if (!env || !vca) return false; return conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv'); }, }, { star: 3, name: 'Ruido controlado', desc: 'Noise white + envelope con attack suave (< 0.1s), decay moderado (0.2-0.5s)', test: (mods) => { const noise = mods.find(m => m.type === 'noise'); const env = mods.find(m => m.type === 'envelope'); if (!noise || !env) return false; const attack = env.params.attack ?? 0.01; const decay = env.params.decay ?? 0.2; return noise.params.type === 'white' && attack < 0.1 && decay >= 0.2 && decay <= 0.5; }, }, ], }, // ─────────────── LEVEL 8.2 ─────────────── { id: 'w8-2', title: 'Sonido de Viento', subtitle: 'Brisa y vendavales', description: 'El viento es ruido filtrado con un filtro bandpass — solo un rango de frecuencias pasa. Varías el cutoff y Q para cambiar el "tipo" de viento (brisa suave vs. huracán).', concept: 'Noise → Filter bandpass (cutoff ~3000 Hz, Q moderado ~3-5) → VCA → Output. Envelope suave al VCA. Resultado: "whoooosh", viento realista.', availableModules: ['noise', 'filter', 'vca', 'envelope'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 1.5 }, checks: [ { star: 1, name: 'Ruido filtrado', desc: 'Noise → Filter bandpass → VCA → Output', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const flt = mods.find(m => m.type === 'filter'); const vca = mods.find(m => m.type === 'vca'); if (!noise || !flt || !vca) return false; return flt.params.type === 'bandpass' && conns.some(c => c.from.moduleId === noise.id && c.to.moduleId === flt.id) && conns.some(c => c.from.moduleId === flt.id && c.to.moduleId === vca.id); }, }, { star: 2, name: 'Con resonancia', desc: 'Filtro bandpass con Q > 2', test: (mods) => { const flt = mods.find(m => m.type === 'filter'); if (!flt) return false; return flt.params.type === 'bandpass' && (flt.params.Q ?? 1) > 2; }, }, { star: 3, name: 'Viento realista', desc: 'Bandpass 2000-4000 Hz, Q 3-5, envelope suave (attack 0.1-0.2s, decay 0.5+)', test: (mods) => { const flt = mods.find(m => m.type === 'filter'); const env = mods.find(m => m.type === 'envelope'); if (!flt || !env) return false; const cutoff = flt.params.frequency ?? 1000; const Q = flt.params.Q ?? 1; const attack = env.params.attack ?? 0.01; const decay = env.params.decay ?? 0.2; return cutoff >= 2000 && cutoff <= 4000 && Q >= 3 && Q <= 5 && attack >= 0.1 && attack <= 0.2 && decay >= 0.5; }, }, ], }, // ─────────────── LEVEL 8.3 ─────────────── { id: 'w8-3', title: 'Olas del Océano', subtitle: 'LFO al cutoff', description: 'El océano "respira" — la amplitud cambia lentamente. Se logra modulando el cutoff del filtro con un LFO muy lento (~0.2-0.5 Hz). El resultado: un sonido que crece y disminuye como olas.', concept: 'Noise → Filter LP → VCA → Output. LFO lento (0.2-0.5 Hz) al cutoff del filtro. Envelope suave al VCA. Resultado: un sonido hipnótico que respira.', availableModules: ['noise', 'filter', 'vca', 'lfo', 'envelope'], preplacedModules: [ { id: 1, type: 'output', x: 750, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 2 }, checks: [ { star: 1, name: 'LFO al filtro', desc: 'Noise → Filter → VCA. LFO al cutoff del filtro', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const flt = mods.find(m => m.type === 'filter'); const lfo = mods.find(m => m.type === 'lfo'); if (!noise || !flt || !lfo) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff'); }, }, { star: 2, name: 'LFO lento', desc: 'LFO con frequency < 1 Hz para movimiento lento', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; return (lfo.params.frequency ?? 2) < 1; }, }, { star: 3, name: 'Olas hipnóticas', desc: 'LFO 0.2-0.5 Hz, filtro LP cutoff 500-3000 Hz, envelope suave (decay 1+)', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); const flt = mods.find(m => m.type === 'filter'); const env = mods.find(m => m.type === 'envelope'); if (!lfo || !flt || !env) return false; const lfoFreq = lfo.params.frequency ?? 2; const cutoff = flt.params.frequency ?? 1000; const decay = env.params.decay ?? 0.2; return lfoFreq >= 0.2 && lfoFreq <= 0.5 && cutoff >= 500 && cutoff <= 3000 && flt.params.type === 'lowpass' && decay >= 1; }, }, ], }, // ─────────────── LEVEL 8.4 ─────────────── { id: 'w8-4', title: 'Sonido de Lluvia', subtitle: 'Gotas percusivas', description: 'La lluvia es ruido + un envelope muy corto que dispara múltiples veces. Cada "gota" es un ataque y decaimiento rápidos. Varias gotas creadas con los mismos parámetros generan una ilusión de lluvia.', concept: 'Noise → VCA → Output. Envelope CORTO (attack 0, decay ~0.05-0.1s, sustain 0) al VCA. Un keyboard para disparar "gotas". Varias pulsaciones = lluvia.', availableModules: ['noise', 'vca', 'envelope', 'keyboard'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -10 }, locked: true }, ], target: { build: [], duration: 1.5 }, checks: [ { star: 1, name: 'Gota de lluvia', desc: 'Noise → VCA con envelope corto (decay < 0.15s)', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const vca = mods.find(m => m.type === 'vca'); const env = mods.find(m => m.type === 'envelope'); if (!noise || !vca || !env) return false; return conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv') && (env.params.decay ?? 0.2) < 0.15; }, }, { star: 2, name: 'Percusivo', desc: 'Envelope con attack 0, decay 0.05-0.1s, sustain 0', test: (mods) => { const env = mods.find(m => m.type === 'envelope'); if (!env) return false; const decay = env.params.decay ?? 0.2; const sustain = env.params.sustain ?? 0.5; return (env.params.attack ?? 0.01) <= 0.01 && decay >= 0.05 && decay <= 0.1 && sustain < 0.05; }, }, { star: 3, name: 'Lluvia realista', desc: 'Noise white, envelope ultra-corto (decay 0.03-0.08s), keyboard conectado', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const env = mods.find(m => m.type === 'envelope'); const kb = mods.find(m => m.type === 'keyboard'); if (!noise || !env || !kb) return false; const decay = env.params.decay ?? 0.2; const connected = conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === env.id && c.to.port === 'gate'); return noise.params.type === 'white' && decay >= 0.03 && decay <= 0.08 && connected; }, }, ], }, // ─────────────── LEVEL 8.5 ─────────────── { id: 'w8-5', title: 'Estática de Radio', subtitle: 'Ruido + Distorsión', description: 'La estática de radio es ruido MÁS distorsión — un efecto que "rompe" el sonido de forma agresiva. Crea ese sonido crispante, lo-fi, de radio rota o síntesis glitch.', concept: 'Noise → Distortion (distortion 0.6+) → VCA → Output. Envelope al VCA. La distorsión enfatiza ciertas partes del ruido, creando un sonido más agresivo y texturado.', availableModules: ['noise', 'vca', 'envelope', 'distortion'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -12 }, locked: true }, ], target: { build: [], duration: 1.5 }, checks: [ { star: 1, name: 'Ruido distorsionado', desc: 'Noise → Distortion → VCA → Output', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const dist = mods.find(m => m.type === 'distortion'); const vca = mods.find(m => m.type === 'vca'); if (!noise || !dist || !vca) return false; return conns.some(c => c.from.moduleId === noise.id && c.to.moduleId === dist.id) && conns.some(c => c.from.moduleId === dist.id && c.to.moduleId === vca.id); }, }, { star: 2, name: 'Agresivo', desc: 'Distorsión > 0.4 para un sonido roto', test: (mods) => { const dist = mods.find(m => m.type === 'distortion'); if (!dist) return false; return (dist.params.distortion ?? 0.4) > 0.4; }, }, { star: 3, name: 'Estática completa', desc: 'Distorsión 0.6-0.9, wet 0.6+, envelope suave (decay 0.5+)', test: (mods) => { const dist = mods.find(m => m.type === 'distortion'); const env = mods.find(m => m.type === 'envelope'); if (!dist || !env) return false; const distortion = dist.params.distortion ?? 0.4; const wet = dist.params.wet ?? 0.5; const decay = env.params.decay ?? 0.2; return distortion >= 0.6 && distortion <= 0.9 && wet >= 0.6 && decay >= 0.5; }, }, ], }, // ─────────────── LEVEL 8.6 ─────────────── { id: 'w8-6', title: 'Ritmo Industrial', subtitle: 'LFO modulando VCA', description: 'Ahora modulamos el VCA con un LFO en lugar del envelope — crea un efecto de "pulsación" o "tremolo". Combined con noise, crea un sonido industrial, maquínico, hipnótico.', concept: 'Noise → VCA. LFO (frequency ~1-2 Hz) al CV del VCA. Resultado: el ruido sube y baja rítmicamente, como una máquina industrial.', availableModules: ['noise', 'vca', 'lfo'], preplacedModules: [ { id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 1.5 }, checks: [ { star: 1, name: 'LFO al VCA', desc: 'Noise → VCA. LFO al CV del VCA', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const vca = mods.find(m => m.type === 'vca'); const lfo = mods.find(m => m.type === 'lfo'); if (!noise || !vca || !lfo) return false; return conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === vca.id && c.to.port === 'cv') && conns.some(c => c.from.moduleId === noise.id && c.to.moduleId === vca.id && c.to.port === 'in'); }, }, { star: 2, name: 'Pulsación', desc: 'LFO frequency 0.5-3 Hz para tremolo audible', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; const freq = lfo.params.frequency ?? 2; return freq >= 0.5 && freq <= 3; }, }, { star: 3, name: 'Industrial puro', desc: 'LFO 1-2 Hz, square waveform (si hay opción), amplitude > 0.5', test: (mods) => { const lfo = mods.find(m => m.type === 'lfo'); if (!lfo) return false; const freq = lfo.params.frequency ?? 2; const amplitude = lfo.params.amplitude ?? 0.5; return freq >= 1 && freq <= 2 && amplitude > 0.5; }, }, ], }, // ─────────────── LEVEL 8.7 ─────────────── { id: 'w8-7', title: 'Textura Ambiental', subtitle: 'Ruido + Reverb + Delay', description: 'Una textura ambiental es ruido filtrado + MUCHO reverb y delay. El reverb añade espacio (como un reverb de catedral), el delay crea repeticiones. El resultado: un sonido envolvente, envolvente, romántico.', concept: 'Noise → Filter LP (cutoff bajo ~1000 Hz) → Reverb (decay 4+) → Delay → Output. No necesitas envelope — deja que el sonido respire solo. Es puro ambiente.', availableModules: ['noise', 'filter', 'reverb', 'delay'], preplacedModules: [ { id: 1, type: 'output', x: 800, y: 120, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 2.5 }, checks: [ { star: 1, name: 'Reverb en cadena', desc: 'Noise → Filter → Reverb → Output', test: (mods, conns) => { const noise = mods.find(m => m.type === 'noise'); const flt = mods.find(m => m.type === 'filter'); const rev = mods.find(m => m.type === 'reverb'); if (!noise || !flt || !rev) return false; return conns.some(c => c.from.moduleId === flt.id && c.to.moduleId === rev.id) && conns.some(c => c.from.moduleId === rev.id); }, }, { star: 2, name: 'Espacioso', desc: 'Reverb decay > 3, delay en cadena también', test: (mods, conns) => { const rev = mods.find(m => m.type === 'reverb'); const del = mods.find(m => m.type === 'delay'); if (!rev || !del) return false; return (rev.params.decay ?? 2) > 3; }, }, { star: 3, name: 'Ambiente etéreo', desc: 'LP < 1500 Hz, reverb decay 4+, delay feedback 0.4+, combinación crea sonido flotante', test: (mods) => { const flt = mods.find(m => m.type === 'filter'); const rev = mods.find(m => m.type === 'reverb'); const del = mods.find(m => m.type === 'delay'); if (!flt || !rev || !del) return false; const cutoff = flt.params.frequency ?? 1000; const revDecay = rev.params.decay ?? 2; const delFeedback = del.params.feedback ?? 0.4; return cutoff <= 1500 && revDecay >= 4 && delFeedback >= 0.4; }, }, ], }, // ─────────────── LEVEL 8.8: BOSS ─────────────── { id: 'w8-8', title: 'Paisaje Sonoro', subtitle: 'BOSS FINAL: Un mundo de sonido', description: 'Combina TODAS las texturas aprendidas en un único paisaje sonoro. Crea una composición con capas: viento, lluvia, olas, estática, ritmo industrial, ambiente. Una sinfonía de ruido y texturas.', concept: 'Mínimo 4 capas de ruido con diferentes características: 1) filtro bandpass (viento), 2) ruido + envelope corto (lluvia), 3) ruido + LFO al filtro (olas), 4) ruido + LFO al VCA (ritmo). Todo mezclado, con reverb y delay, fluyendo en armonía.', availableModules: ['noise', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'delay', 'reverb', 'distortion'], preplacedModules: [ { id: 1, type: 'output', x: 900, y: 140, params: { volume: -8 }, locked: true }, ], target: { build: [], duration: 6 }, checks: [ { star: 1, name: 'Múltiples texturas', desc: 'Al menos 3 canales de ruido con características diferentes, todos a output', test: (mods, conns) => { const noises = mods.filter(m => m.type === 'noise'); const out = mods.find(m => m.type === 'output'); if (noises.length < 3 || !out) return false; // Count different filter types or modulators const filters = mods.filter(m => m.type === 'filter'); const lfos = mods.filter(m => m.type === 'lfo'); const envs = mods.filter(m => m.type === 'envelope'); const total = filters.length + lfos.length + envs.length; return total >= 3 && conns.some(c => c.to.moduleId === out.id); }, }, { star: 2, name: 'Sonido espacioso', desc: 'Reverb y delay en cadena, crean profundidad y eco', test: (mods, conns) => { const rev = mods.find(m => m.type === 'reverb'); const del = mods.find(m => m.type === 'delay'); if (!rev || !del) return false; // At least one should connect to output or to each other const out = mods.find(m => m.type === 'output'); return (conns.some(c => c.from.moduleId === rev.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) && conns.some(c => c.from.moduleId === del.id && c.to.moduleId === out?.id))); }, }, { star: 3, name: 'Maestro de Texturas', desc: '4+ noises, 2+ filters, 2+ LFOs, mixer, reverb decay 3+, delay feedback 0.4+, distorsión opcional', test: (mods, conns) => { const noises = mods.filter(m => m.type === 'noise'); const filters = mods.filter(m => m.type === 'filter'); const lfos = mods.filter(m => m.type === 'lfo'); const mixer = mods.find(m => m.type === 'mixer'); const rev = mods.find(m => m.type === 'reverb'); const del = mods.find(m => m.type === 'delay'); const nonOutput = mods.filter(m => m.type !== 'output'); if (noises.length < 4 || filters.length < 2 || lfos.length < 2 || !mixer || !rev || !del) return false; const revDecay = rev.params.decay ?? 2; const delFeedback = del.params.feedback ?? 0.4; return nonOutput.length >= 12 && revDecay >= 3 && delFeedback >= 0.4 && conns.length >= 15; }, }, ], }, ], };