code-sinth es un sintetizador modular gobernado por un pequeño lenguaje
de patches. El editor de la izquierda es la fuente de verdad: cada
línea node X = ... declara un nodo del grafo, y
out <- ... elige qué señal sale por los altavoces.
Cualquier cambio en el editor se recarga en caliente preservando el
estado interno (fases de osciladores, posición de secuenciadores,
envolventes en vuelo).
El patch es texto plano. Las construcciones son cuatro:
node nombre = expresión — declara un nodo con nombre.
Puedes referirlo por su nombre en cualquier expresión posterior.out <- expresión — la señal que se manda al
output. Debe haber exactamente una.voice nombre { ... } — bloque que define una
plantilla de voz reutilizable. Dentro tiene su propio
node y un out <- propio. Se instancia
desde poly(voice=nombre, voices=N, ...).# comentario — hasta fin de línea.Las expresiones admiten números (0.5, 440),
identificadores, + - * /, paréntesis, listas
[1, 0, 1, 0] y llamadas a funciones-nodo. Los argumentos
pueden ser posicionales o con nombre: osc(saw, freq=220).
Las señales se mezclan sumándolas: out <- a*0.5 + b*0.3.
Multiplicar por una envolvente actúa como amplificador controlado:
o1 * env.
El motor corre en un AudioWorklet y procesa bloques de 128
muestras a la sample-rate del navegador (típicamente 48 kHz). En cada
bloque se evalúa el grafo en orden topológico: cada nodo lee los
buffers de sus entradas, produce su buffer de salida, y al final el
buffer de out sale por los altavoces.
Pulsa RUN para arrancar el audio (los navegadores requieren un click del usuario antes de abrir el AudioContext). El indicador RUNNING_ abajo se enciende cuando el motor está procesando.
oscGenera una forma de onda continua a la frecuencia indicada.
freq puede ser un número fijo o cualquier señal
(otro nodo) — eso da modulación.
node lfo = osc(sine, freq=4) # 4 Hz node pit = 220 + lfo*30 # vibrato +/-30 Hz node main = osc(saw, freq=pit) out <- main*0.4
| Forma | Carácter | Uso típico |
|---|---|---|
sine | limpio, sin armónicos | LFO, sub-bass, pads |
saw | brillante, todos los armónicos | leads, bajos, strings |
square | solo armónicos impares | chiptune, bajos huecos |
tri | cálido, armónicos impares decrecientes | flautas, sub-leads |
adsr y trigtrig(period, duration) emite una compuerta
(gate) cíclica: alta durante duration segundos, luego
baja, repitiéndose cada period segundos.
adsr(a, d, s, r, gate) es la envolvente clásica.
a/d/r en segundos, s nivel 0..1.
Mientras gate esté en 1 sube por la fase de attack,
cae al sustain y se queda; cuando vuelve a 0 entra en release.
node g1 = trig(period=0.5, duration=0.05) node env = adsr(a=0.005, d=0.1, s=0.0, r=0.2, gate=g1) node tone = osc(sine, freq=440) out <- tone * env
Truco: para una percusión, pon s=0 y
d corto (~0.1 s). Para un pad, sube
a a 0.5–2 s y deja s alto.
filterBiquad RBJ, tipos lp (low-pass), hp
(high-pass) o bp (band-pass). cutoff y
q (resonancia) pueden modularse a tasa de audio —
sumarle una envolvente al cutoff es la receta clásica del bajo
sintetizado.
node src = osc(saw, freq=110) node g = trig(period=0.5, duration=0.2) node e = adsr(a=0.005, d=0.2, s=0, r=0.1, gate=g) node lp = filter(lp, in=src, cutoff=300 + e*2500, q=4) out <- lp * e
noiseRuido blanco en [-1, 1]. Filtrado con hp alto y una
envolvente cortita, da un hi-hat. Filtrado con bp con
cutoff bajo, una caja.
node n = noise() node g = trig(period=0.25, duration=0.02) node e = adsr(a=0.001, d=0.04, s=0, r=0.03, gate=g) node hp = filter(hp, in=n, cutoff=5000, q=1.5) out <- hp * e * 0.4
seqRecorre una lista de valores a rate pasos por segundo,
manteniendo cada valor hasta el siguiente. Útil para listas de
frecuencias o de niveles de control.
node freqs = seq(rate=4, steps=[220, 277, 330, 220]) node tone = osc(saw, freq=freqs) node g = trig(period=0.25, duration=0.18) node e = adsr(a=0.005, d=0.1, s=0.6, r=0.2, gate=g) out <- tone * e * 0.4
delayLínea de delay con feedback. time en segundos,
feedback 0..0.99, mix 0..1
(0 = solo seco, 1 = solo retardado).
node src = osc(tri, freq=330) node g = trig(period=1.0, duration=0.05) node e = adsr(a=0.005, d=0.2, s=0, r=0.1, gate=g) node dry = src * e node dl = delay(in=dry, time=0.375, feedback=0.55, mix=0.5) out <- dl * 0.5
voice + polyDefine una plantilla de voz con un bloque
voice nombre { ... }: dentro tendrás dos identificadores
especiales — freq (la frecuencia que el allocator
asigna a la voz) y gate (la compuerta que se abre
durante gate_duration segundos por cada nota).
Luego instancia con poly(voice=nombre, voices=N, rate, notes,
gate_duration). notes es una lista de frecuencias;
0 es silencio. El allocator asigna la voz menos reciente
(LRU) a cada nota nueva.
voice synth { node o = osc(saw, freq=freq) node e = adsr(a=0.005, d=0.2, s=0.4, r=0.3, gate=gate) node f = filter(lp, in=o, cutoff=600 + e*1800, q=2.0) out <- f * e } node mel = poly(voice=synth, voices=4, rate=4, gate_duration=0.18, notes=[220, 277, 330, 0, 220, 330, 277, 0]) out <- mel * 0.4
Hay cuatro tipos de nodo cuyo valor lo decide el panel derecho en vez del código. Al declararlos aparece un widget físico al que puedes interactuar con el ratón.
node cutoff = knob(min=200, max=4000, default=900)
Arrastra arriba/abajo para girarlo, doble-click para volver al centro, Shift + arrastrar para ajuste fino.
node mix = fader(min=0, max=1, default=0.5)
node kick = step_seq(rate=8, steps=16, default=[1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0])
Click en una celda para encender/apagar; arrastra para pintar
muchas a la vez. La salida es 0/1, perfecta como
gate= de un ADSR.
node mel = piano_roll(voice=synth, voices=4, rate=8, length=16, octaves=2, base=220, gate_duration=0.18)
Es un poly() con las notas dispuestas en una rejilla
de pasos × semitonos. Click en cualquier celda para colocar una
nota. La columna 0 es base Hz, cada fila es un
semitono más arriba. Necesita un voice previo, igual
que poly.
filter(...) * env evita clics al inicio/final
mejor que poner la envolvente solo en el cutoff.+ y multiplica el total por un factor pequeño:
out <- bass*0.6 + drum*0.5 + lead*0.4.— fin del manual —