Files
reaktor/src/game/levels/world4.js
Jose Luis 58d567c671 feat: fix target audio for all 96 levels and improve layout density
- Enhance targetAudio.js with envelope (ADSR), LFO modulation, effects
  (delay/reverb/distortion), and retrigger patterns for rhythmic sounds
- Fill in target audio configs for 87 levels (worlds 3-12) that had empty
  build arrays, making the "Objetivo" preview button functional everywhere
- Increase base sizes for modules, sidebar, ports, knobs, and typography
  so the UI feels less empty at 100% zoom

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

530 lines
24 KiB
JavaScript

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