Files
reaktor/src/game/levels/world8.js
Jose Luis a1be6df355 feat: UI sounds, live LFO visualization, wire fix, worlds 7-12, bug fixes
- Add procedural UI sound effects (connect/disconnect, engine start/stop,
  level complete/fail, star earned, hint, navigation) via Tone.js
- Live LFO modulation visualization: knobs animate in real-time showing
  modulated value, ghost dot shows base value, number glows cyan
- Fix wire recalculation on zoom/pan/level re-entry (post-layout refresh)
- Fix retry button to keep current patch instead of reloading level
- Fix default param detection: newly added modules now populate all
  default params so level checkers work without manual param changes
- Add worlds 7-12: Secuencias y Ritmos, Texturas de Ruido, Síntesis
  Sustractiva, Espacio y Stereo, Técnicas Avanzadas, Gran Final
  (48 new levels, 144 new possible stars, 288 total stars)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 03:03:29 +01:00

467 lines
21 KiB
JavaScript

/**
* 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;
},
},
],
},
],
};