- 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>
555 lines
26 KiB
JavaScript
555 lines
26 KiB
JavaScript
/**
|
|
* World 6 — "Diseño Sonoro" (Sound Design Mastery)
|
|
*
|
|
* Teaches: putting it ALL together, real-world sound recreation
|
|
* 8 levels, boss challenges combining everything learned
|
|
*/
|
|
|
|
export const WORLD_6 = {
|
|
id: 'w6',
|
|
name: 'Diseño Sonoro',
|
|
subtitle: 'Combina todo para crear sonidos reales',
|
|
icon: '◉',
|
|
color: '#ff44aa',
|
|
unlockStars: 60,
|
|
levels: [
|
|
// ─────────────── LEVEL 6.1 ───────────────
|
|
{
|
|
id: 'w6-1',
|
|
title: 'Kick Drum',
|
|
subtitle: 'El latido del beat',
|
|
description: 'Un kick sintético se crea con un oscilador sine a frecuencia baja + un envelope muy rápido en el VCA para el golpe. Algunos añaden un pitch envelope para el "click" del ataque.',
|
|
concept: 'Osc sine a ~55 Hz → VCA → Output. Envelope con attack 0, decay ~0.2s, sustain 0. El envelope al VCA crea el golpe. Para el click: un segundo osc más agudo con decay ultra-corto.',
|
|
availableModules: ['oscillator', 'vca', 'envelope', 'keyboard', 'mixer'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 700, y: 120, params: { volume: -6 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'sine', frequency: 55 } },
|
|
],
|
|
envelope: { attack: 0, decay: 0.25, sustain: 0, release: 0.1 },
|
|
duration: 2,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Kick básico',
|
|
desc: 'Osc sine grave + VCA + Envelope → Output',
|
|
test: (mods, conns) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const vca = mods.find(m => m.type === 'vca');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
const out = mods.find(m => m.type === 'output');
|
|
if (!osc || !vca || !env || !out) return false;
|
|
return (osc.params.frequency ?? 440) < 100 &&
|
|
conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Punch',
|
|
desc: 'Sine < 80 Hz, envelope rápido (attack < 0.01, decay < 0.3)',
|
|
test: (mods) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
if (!osc || !env) return false;
|
|
return (osc.params.frequency ?? 440) < 80 &&
|
|
osc.params.waveform === 'sine' &&
|
|
(env.params.attack ?? 0.01) < 0.01 &&
|
|
(env.params.decay ?? 0.2) < 0.3 &&
|
|
(env.params.sustain ?? 0.5) < 0.1;
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: '808 Kick',
|
|
desc: 'Frecuencia 40-60 Hz, decay 0.15-0.4s, keyboard conectado',
|
|
test: (mods, conns) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
const kb = mods.find(m => m.type === 'keyboard');
|
|
if (!osc || !env || !kb) return false;
|
|
const freq = osc.params.frequency ?? 440;
|
|
const decay = env.params.decay ?? 0.2;
|
|
return freq >= 40 && freq <= 60 && decay >= 0.15 && decay <= 0.4 &&
|
|
conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === env.id && c.to.port === 'gate');
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.2 ───────────────
|
|
{
|
|
id: 'w6-2',
|
|
title: 'Hi-Hat',
|
|
subtitle: 'Noise + Filtro + Envelope',
|
|
description: 'Los hi-hats son ruido blanco filtrado con un envelope corto. El ruido proporciona la textura metálica, el filtro highpass quita los graves, y el envelope corto le da el "tss".',
|
|
concept: 'Noise → Filter (highpass, cutoff alto ~6000+ Hz) → VCA → Output. Envelope corto (attack 0, decay ~0.05-0.15s, sustain 0) al VCA. Keyboard al gate del envelope.',
|
|
availableModules: ['noise', 'filter', 'vca', 'envelope', 'keyboard'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 700, y: 120, params: { volume: -10 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'noise', params: { type: 'white' } },
|
|
],
|
|
filter: { type: 'highpass', frequency: 7000, Q: 2 },
|
|
envelope: { attack: 0, decay: 0.08, sustain: 0, release: 0 },
|
|
duration: 2,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Ruido filtrado',
|
|
desc: 'Noise → Filter → VCA → Output con envelope',
|
|
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');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
if (!noise || !flt || !vca || !env) return false;
|
|
return conns.some(c => c.from.moduleId === noise.id && c.to.moduleId === flt.id) &&
|
|
conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Sonido metálico',
|
|
desc: 'Filtro highpass, cutoff > 4000 Hz',
|
|
test: (mods) => {
|
|
const flt = mods.find(m => m.type === 'filter');
|
|
return flt && flt.params.type === 'highpass' && (flt.params.frequency ?? 1000) > 4000;
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Hi-hat cerrado',
|
|
desc: 'HP > 6000 Hz, envelope ultra-corto (decay < 0.1s)',
|
|
test: (mods) => {
|
|
const flt = mods.find(m => m.type === 'filter');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
if (!flt || !env) return false;
|
|
return flt.params.type === 'highpass' && (flt.params.frequency ?? 1000) > 6000 &&
|
|
(env.params.decay ?? 0.2) < 0.1 && (env.params.sustain ?? 0.5) < 0.05;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.3 ───────────────
|
|
{
|
|
id: 'w6-3',
|
|
title: 'Snare',
|
|
subtitle: 'Tono + Ruido',
|
|
description: 'Un snare es la combinación de un cuerpo tonal (oscilador) y una cola de ruido (noise). Se mezclan juntos con envelopes diferentes — el tono muere rápido y el ruido un poco después.',
|
|
concept: 'Dos cadenas: 1) Osc sine (~200 Hz) → VCA1 → Mixer. 2) Noise → Filter HP → VCA2 → Mixer. Mixer → Output. Envelopes diferentes: el tono más corto que el ruido.',
|
|
availableModules: ['oscillator', 'noise', 'filter', 'vca', 'envelope', 'keyboard', 'mixer'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 800, y: 140, params: { volume: -8 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'sine', frequency: 200 } },
|
|
{ type: 'noise', params: { type: 'white' } },
|
|
],
|
|
filter: { type: 'highpass', frequency: 3000, Q: 1.5 },
|
|
envelope: { attack: 0, decay: 0.12, sustain: 0, release: 0.05 },
|
|
duration: 2,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Dos fuentes',
|
|
desc: 'Oscilador Y Noise, ambos al mixer → output',
|
|
test: (mods, conns) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const noise = mods.find(m => m.type === 'noise');
|
|
const mixer = mods.find(m => m.type === 'mixer');
|
|
const out = mods.find(m => m.type === 'output');
|
|
return osc && noise && mixer && out &&
|
|
conns.some(c => c.to.moduleId === out.id);
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Envelopes',
|
|
desc: 'Al menos 2 envelopes controlando VCAs',
|
|
test: (mods, conns) => {
|
|
const envs = mods.filter(m => m.type === 'envelope');
|
|
const vcas = mods.filter(m => m.type === 'vca');
|
|
if (envs.length < 2 || vcas.length < 2) return false;
|
|
const envToVca = envs.filter(e =>
|
|
vcas.some(v => conns.some(c => c.from.moduleId === e.id && c.to.moduleId === v.id && c.to.port === 'cv'))
|
|
);
|
|
return envToVca.length >= 2;
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Snare realista',
|
|
desc: 'Osc ~150-250 Hz, noise filtrado HP, decays diferentes',
|
|
test: (mods) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const flt = mods.find(m => m.type === 'filter');
|
|
const envs = mods.filter(m => m.type === 'envelope');
|
|
if (!osc || !flt || envs.length < 2) return false;
|
|
const freq = osc.params.frequency ?? 440;
|
|
const decays = envs.map(e => e.params.decay ?? 0.2);
|
|
return freq >= 150 && freq <= 250 &&
|
|
flt.params.type === 'highpass' &&
|
|
Math.abs(decays[0] - decays[1]) > 0.03;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.4 ───────────────
|
|
{
|
|
id: 'w6-4',
|
|
title: 'Pad Espacial',
|
|
subtitle: 'Capas + Efectos',
|
|
description: 'Un pad espacial combina múltiples osciladores detuned, un filtro suave, un envelope lento, y efectos de reverb/delay para crear una textura inmersiva que rellena todo el espectro.',
|
|
concept: 'Dos oscs saw detuned → Mixer → Filter LP → VCA → Reverb → Output. Envelope lento al VCA. LFO lento al cutoff. Reverb con decay largo. El resultado: un colchón de sonido etéreo.',
|
|
availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'reverb', 'keyboard'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 900, y: 140, params: { volume: -8 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 110, detune: -8 } },
|
|
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 110, detune: 8 } },
|
|
],
|
|
filter: { type: 'lowpass', frequency: 1500, Q: 3 },
|
|
envelope: { attack: 1, decay: 0.4, sustain: 0.7, release: 2 },
|
|
lfo: { frequency: 0.6, type: 'sine', min: 600, max: 3500, target: 'frequency' },
|
|
effects: [
|
|
{ type: 'reverb', decay: 5.5, wet: 0.6 },
|
|
],
|
|
duration: 5,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Múltiples osciladores',
|
|
desc: 'Al menos 2 osciladores mezclados',
|
|
test: (mods, conns) => {
|
|
const oscs = mods.filter(m => m.type === 'oscillator');
|
|
const mixer = mods.find(m => m.type === 'mixer');
|
|
return oscs.length >= 2 && mixer;
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Con efectos',
|
|
desc: 'Reverb en la cadena con decay > 3s',
|
|
test: (mods) => {
|
|
const rev = mods.find(m => m.type === 'reverb');
|
|
return rev && (rev.params.decay ?? 2) > 3;
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Pad completo',
|
|
desc: '2+ oscs detuned, filtro, LFO al cutoff, envelope al VCA, reverb',
|
|
test: (mods, conns) => {
|
|
const oscs = mods.filter(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 rev = mods.find(m => m.type === 'reverb');
|
|
if (oscs.length < 2 || !flt || !lfo || !env || !vca || !rev) return false;
|
|
// Check detune
|
|
const hasDetune = oscs.some(o => Math.abs(o.params.detune ?? 0) > 2);
|
|
// Check LFO to cutoff
|
|
const lfoToCutoff = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff');
|
|
// Check env to VCA
|
|
const envToVca = conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
return hasDetune && lfoToCutoff && envToVca;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.5 ───────────────
|
|
{
|
|
id: 'w6-5',
|
|
title: 'Bajo Reese',
|
|
subtitle: 'El bajo de Drum & Bass',
|
|
description: 'El Reese bass es un bajo icónico del Drum & Bass: dos osciladores sawtooth detuned a frecuencia grave, pasados por un filtro lowpass que se abre y cierra. Es gordo, agresivo y hipnótico.',
|
|
concept: 'Dos oscs sawtooth a ~55 Hz, uno con detune +7-12. Mixer → Filter LP resonante → VCA → Output. LFO lento (~0.3-1 Hz) al cutoff del filtro. El "movimiento" del filtro es lo que le da vida.',
|
|
availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'mixer', '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, detune: -9 } },
|
|
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 55, detune: 9 } },
|
|
],
|
|
filter: { type: 'lowpass', frequency: 400, Q: 8 },
|
|
lfo: { frequency: 0.7, type: 'sine', min: 200, max: 2000, target: 'frequency' },
|
|
envelope: { attack: 0.05, decay: 0.2, sustain: 0.6, release: 0.3 },
|
|
duration: 4,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Dos sierras graves',
|
|
desc: '2 osciladores saw < 100 Hz mezclados',
|
|
test: (mods) => {
|
|
const oscs = mods.filter(m => m.type === 'oscillator');
|
|
if (oscs.length < 2) return false;
|
|
return oscs.filter(o => o.params.waveform === 'sawtooth' && (o.params.frequency ?? 440) < 100).length >= 2;
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Detune + Filtro',
|
|
desc: 'Osciladores detuned, filtro LP en la cadena',
|
|
test: (mods) => {
|
|
const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sawtooth');
|
|
const flt = mods.find(m => m.type === 'filter');
|
|
if (oscs.length < 2 || !flt) return false;
|
|
const hasDetune = oscs.some(o => Math.abs(o.params.detune ?? 0) > 3);
|
|
return hasDetune && flt.params.type === 'lowpass';
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Reese Bass',
|
|
desc: 'Detuned saws + LP resonante + LFO al cutoff',
|
|
test: (mods, conns) => {
|
|
const oscs = mods.filter(m => m.type === 'oscillator' && m.params.waveform === 'sawtooth');
|
|
const flt = mods.find(m => m.type === 'filter');
|
|
const lfo = mods.find(m => m.type === 'lfo');
|
|
if (oscs.length < 2 || !flt || !lfo) return false;
|
|
const hasDetune = oscs.some(o => Math.abs(o.params.detune ?? 0) > 3);
|
|
const isLPres = flt.params.type === 'lowpass' && (flt.params.Q ?? 1) > 3;
|
|
const lfoToCut = conns.some(c => c.from.moduleId === lfo.id && c.to.moduleId === flt.id && c.to.port === 'cutoff');
|
|
return hasDetune && isLPres && lfoToCut;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.6 ───────────────
|
|
{
|
|
id: 'w6-6',
|
|
title: 'Efecto Laser',
|
|
subtitle: 'Pew pew!',
|
|
description: 'El sonido laser clásico de los juegos retro es un oscilador cuya frecuencia baja rápidamente — un pitch sweep descendente. Se consigue con un envelope que modula la frecuencia del oscilador.',
|
|
concept: 'Osc square/saw → VCA → Output. Envelope al VCA (ataque rápido, decay corto). Un SEGUNDO envelope a la frecuencia del osc (empieza agudo y baja rápido). Keyboard dispara ambos.',
|
|
availableModules: ['oscillator', 'vca', 'envelope', 'keyboard'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 700, y: 120, params: { volume: -8 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'square', frequency: 440 } },
|
|
],
|
|
envelope: { attack: 0.01, decay: 0.15, sustain: 0.05, release: 0.1 },
|
|
duration: 2,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Sonido con envelope',
|
|
desc: 'Osc → VCA → Output con envelope y keyboard',
|
|
test: (mods, conns) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const vca = mods.find(m => m.type === 'vca');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
const kb = mods.find(m => m.type === 'keyboard');
|
|
const out = mods.find(m => m.type === 'output');
|
|
return osc && vca && env && kb && out &&
|
|
conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Pitch envelope',
|
|
desc: 'Un envelope conectado a la frecuencia del oscilador',
|
|
test: (mods, conns) => {
|
|
const envs = mods.filter(m => m.type === 'envelope');
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
if (!osc || envs.length < 2) return false;
|
|
return envs.some(e => conns.some(c =>
|
|
c.from.moduleId === e.id && c.to.moduleId === osc.id && c.to.port === 'freq'
|
|
));
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Pew pew!',
|
|
desc: 'Osc square/saw, pitch env corto (decay < 0.2s), keyboard a ambos gates',
|
|
test: (mods, conns) => {
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
const envs = mods.filter(m => m.type === 'envelope');
|
|
const kb = mods.find(m => m.type === 'keyboard');
|
|
if (!osc || envs.length < 2 || !kb) return false;
|
|
const wave = osc.params.waveform;
|
|
const pitchEnv = envs.find(e => conns.some(c =>
|
|
c.from.moduleId === e.id && c.to.moduleId === osc.id && c.to.port === 'freq'
|
|
));
|
|
if (!pitchEnv) return false;
|
|
const gated = envs.filter(e =>
|
|
conns.some(c => c.from.moduleId === kb.id && c.to.moduleId === e.id && c.to.port === 'gate')
|
|
);
|
|
return (wave === 'square' || wave === 'sawtooth') &&
|
|
(pitchEnv.params.decay ?? 0.2) < 0.2 &&
|
|
gated.length >= 2;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.7 ───────────────
|
|
{
|
|
id: 'w6-7',
|
|
title: 'Arpegio Trance',
|
|
subtitle: 'Secuenciador + Synth',
|
|
description: 'Los arpegios de trance son notas rápidas que crean patrones hipnóticos. Usa el secuenciador para disparar notas en el sintetizador con un envelope corto y un filtro que sube y baja.',
|
|
concept: 'Sequencer → Osc freq + Envelope gate. Osc → Filter → VCA → Delay → Output. Envelope corto al VCA (pluck). LFO lento al cutoff del filtro. El delay repite el patrón.',
|
|
availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'delay', 'sequencer'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 900, y: 140, params: { volume: -8 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 330 } },
|
|
],
|
|
filter: { type: 'lowpass', frequency: 2000, Q: 4 },
|
|
envelope: { attack: 0.005, decay: 0.15, sustain: 0.1, release: 0.08 },
|
|
lfo: { frequency: 1.5, type: 'sine', min: 1000, max: 4000, target: 'frequency' },
|
|
effects: [
|
|
{ type: 'delay', time: 0.25, feedback: 0.35, wet: 0.45 },
|
|
],
|
|
triggerPattern: { interval: 0.25, count: 16 },
|
|
duration: 4,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Secuenciador activo',
|
|
desc: 'Sequencer conectado al oscilador',
|
|
test: (mods, conns) => {
|
|
const seq = mods.find(m => m.type === 'sequencer');
|
|
const osc = mods.find(m => m.type === 'oscillator');
|
|
if (!seq || !osc) return false;
|
|
return conns.some(c => c.from.moduleId === seq.id && c.to.moduleId === osc.id);
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Synth con envolvente',
|
|
desc: 'Osc → Filter → VCA → Output con envelope al VCA',
|
|
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 env = mods.find(m => m.type === 'envelope');
|
|
const out = mods.find(m => m.type === 'output');
|
|
if (!osc || !flt || !vca || !env || !out) return false;
|
|
return conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Trance completo',
|
|
desc: 'Sequencer + synth sustractivo completo + delay',
|
|
test: (mods, conns) => {
|
|
const seq = mods.find(m => m.type === 'sequencer');
|
|
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 del = mods.find(m => m.type === 'delay');
|
|
const env = mods.find(m => m.type === 'envelope');
|
|
if (!seq || !osc || !flt || !vca || !del || !env) return false;
|
|
return conns.some(c => c.from.moduleId === seq.id && c.to.moduleId === osc.id) &&
|
|
conns.some(c => c.from.moduleId === env.id && c.to.moduleId === vca.id && c.to.port === 'cv');
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// ─────────────── LEVEL 6.8: BOSS ───────────────
|
|
{
|
|
id: 'w6-8',
|
|
title: 'Tu Sintetizador',
|
|
subtitle: 'BOSS FINAL: Diseña tu propio sonido',
|
|
description: 'Has aprendido osciladores, filtros, envelopes, modulación y efectos. Ahora construye el sintetizador más completo que puedas. Sin restricciones. Sin guía. Solo tu creatividad y todo lo que has aprendido.',
|
|
concept: 'Construye un patch completo con al menos: 2 osciladores, 1 filtro, 1 VCA, 2 envelopes, 1 LFO, 1 efecto, y un keyboard. ¡Hazlo sonar increíble!',
|
|
availableModules: ['oscillator', 'filter', 'vca', 'lfo', 'envelope', 'mixer', 'noise', 'keyboard', 'delay', 'reverb', 'distortion', 'sequencer'],
|
|
preplacedModules: [
|
|
{ id: 1, type: 'output', x: 950, y: 160, params: { volume: -8 }, locked: true },
|
|
],
|
|
target: {
|
|
build: [
|
|
{ type: 'oscillator', params: { waveform: 'sawtooth', frequency: 165, detune: -6 } },
|
|
{ type: 'oscillator', params: { waveform: 'square', frequency: 165, detune: 6 } },
|
|
{ type: 'noise', params: { type: 'white' } },
|
|
],
|
|
filter: { type: 'lowpass', frequency: 1800, Q: 6 },
|
|
envelope: { attack: 0.2, decay: 0.4, sustain: 0.5, release: 0.8 },
|
|
lfo: { frequency: 2, type: 'sine', min: 0.3, max: 1.2, target: 'amplitude' },
|
|
effects: [
|
|
{ type: 'distortion', amount: 2 },
|
|
{ type: 'delay', time: 0.3, feedback: 0.4, wet: 0.4 },
|
|
{ type: 'reverb', decay: 3.5, wet: 0.45 },
|
|
],
|
|
duration: 5,
|
|
},
|
|
checks: [
|
|
{
|
|
star: 1,
|
|
name: 'Patch funcional',
|
|
desc: 'Al menos 5 módulos conectados con sonido a la salida',
|
|
test: (mods, conns) => {
|
|
const out = mods.find(m => m.type === 'output');
|
|
if (!out) return false;
|
|
// Count non-output modules
|
|
const modCount = mods.filter(m => m.type !== 'output').length;
|
|
// Something reaches output
|
|
const hasOutput = conns.some(c => c.to.moduleId === out.id);
|
|
return modCount >= 5 && hasOutput && conns.length >= 5;
|
|
},
|
|
},
|
|
{
|
|
star: 2,
|
|
name: 'Síntesis completa',
|
|
desc: 'Tiene osc + filtro + VCA + envelope + efecto, todos conectados',
|
|
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 env = mods.find(m => m.type === 'envelope');
|
|
const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type));
|
|
if (!osc || !flt || !vca || !env || effects.length === 0) return false;
|
|
// All main pieces should have connections
|
|
const oscConn = conns.some(c => c.from.moduleId === osc.id);
|
|
const envConn = conns.some(c => c.from.moduleId === env.id);
|
|
return oscConn && envConn && conns.length >= 7;
|
|
},
|
|
},
|
|
{
|
|
star: 3,
|
|
name: 'Maestro del Sonido',
|
|
desc: '8+ módulos, 2+ oscs, 2+ envelopes, LFO, efecto, keyboard — ¡todo!',
|
|
test: (mods, conns) => {
|
|
const oscs = mods.filter(m => m.type === 'oscillator');
|
|
const envs = mods.filter(m => m.type === 'envelope');
|
|
const lfo = mods.find(m => m.type === 'lfo');
|
|
const kb = mods.find(m => m.type === 'keyboard');
|
|
const effects = mods.filter(m => ['delay', 'reverb', 'distortion'].includes(m.type));
|
|
const nonOutput = mods.filter(m => m.type !== 'output');
|
|
return nonOutput.length >= 8 && oscs.length >= 2 && envs.length >= 2 &&
|
|
lfo && kb && effects.length >= 1 && conns.length >= 10;
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|