- 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>
530 lines
24 KiB
JavaScript
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');
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|