initial: code-sinth — DSL-driven modular synth (Python engine + web app)

Patch language with osc/noise/trig/seq/adsr/filter/delay/poly + voice templates
and inline live values. Two runtimes:

- code_sinth/ — Python engine (numpy + sounddevice). Hot-reload via mtime
  watcher. Offline render to WAV. Static-HTTP+WS visualizer (viz/) that
  injects waveforms next to each `node X = ...` line.
- web/ — port of the engine to JS running in AudioWorklet. Single static
  page with CodeMirror 6 editor (line widgets for live waveforms) and a
  control surface on the right with knobs/faders/step_seq/piano_roll
  declared from the patch. State preserved across hot-reload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jose Luis Montañes
2026-05-01 17:37:06 +02:00
commit 7debc7436e
19 changed files with 3260 additions and 0 deletions

10
examples/arp.patch Normal file
View File

@@ -0,0 +1,10 @@
# Arpegio: Am7 ascendente y descendente sobre 8 pasos.
# clk y mel comparten rate (8 pasos/seg) por lo que arrancan en fase desde t=0.
node clk = trig(period=0.125, duration=0.06)
node mel = seq(rate=8, steps=[220.0, 261.63, 329.63, 392.0, 523.25, 392.0, 329.63, 261.63])
node o1 = osc(saw, freq=mel)
node env = adsr(a=0.005, d=0.08, s=0.3, r=0.08, gate=clk)
node lp = filter(lp, in=o1, cutoff=300 + env*3000, q=3.0)
out <- lp * env * 0.5

12
examples/arp_delay.patch Normal file
View File

@@ -0,0 +1,12 @@
# Mismo arpegio pero con delay con feedback (ecos a tiempo de corchea).
# rate=8 -> step=125ms; delay 250ms = 2 steps; feedback 0.45 da ~3-4 ecos.
node clk = trig(period=0.125, duration=0.06)
node mel = seq(rate=8, steps=[220.0, 261.63, 329.63, 392.0, 523.25, 392.0, 329.63, 261.63])
node o1 = osc(saw, freq=mel)
node env = adsr(a=0.005, d=0.08, s=0.3, r=0.08, gate=clk)
node lp = filter(lp, in=o1, cutoff=300 + env*3000, q=3.0)
node dry = lp * env * 0.5
node wet = delay(in=dry, time=0.25, feedback=0.45, mix=0.5)
out <- wet

8
examples/hello.patch Normal file
View File

@@ -0,0 +1,8 @@
# Primer patch: oscilador saw modulado por una envolvente ADSR.
# trig dispara una compuerta cada 2s (1s alto, 1s bajo) que retriguerea el ADSR.
node g1 = trig(period=2.0, duration=1.0)
node o1 = osc(saw, freq=220)
node e1 = adsr(a=0.01, d=0.2, s=0.6, r=0.4, gate=g1)
out <- o1 * e1

25
examples/pad.patch Normal file
View File

@@ -0,0 +1,25 @@
# Pad polifonico. Cada voz tiene dos saws desafinados, env con release largo, LP suave.
# rate=4 (250ms/nota) con gate_duration=0.6 -> notas se solapan. voices=8 cubre el stack.
voice pad {
node o1 = osc(saw, freq=freq)
node o2 = osc(saw, freq=freq * 1.005)
node sum = (o1 + o2) * 0.5
node env = adsr(a=0.06, d=0.3, s=0.5, r=1.2, gate=gate)
node lp = filter(lp, in=sum, cutoff=400 + env*1500, q=1.2)
out <- lp * env
}
# Progresion en C: Cmaj - Am - Fmaj - Gmaj, 4 notas por acorde.
node p = poly(
voice=pad, voices=8,
rate=4, gate_duration=0.6,
notes=[
261.63, 329.63, 392.00, 523.25,
220.00, 261.63, 329.63, 440.00,
174.61, 220.00, 261.63, 349.23,
196.00, 246.94, 293.66, 392.00
]
)
out <- p * 0.18

25
examples/pad_delay.patch Normal file
View File

@@ -0,0 +1,25 @@
# Pad polifonico + delay sincronizado al rate (250ms = una nota).
voice pad {
node o1 = osc(saw, freq=freq)
node o2 = osc(saw, freq=freq * 1.005)
node sum = (o1 + o2) * 0.5
node env = adsr(a=0.06, d=0.3, s=0.5, r=1.2, gate=gate)
node lp = filter(lp, in=sum, cutoff=400 + env*1500, q=1.2)
out <- lp * env
}
node p = poly(
voice=pad, voices=8,
rate=4, gate_duration=0.6,
notes=[
261.63, 329.63, 392.00, 523.25,
220.00, 261.63, 329.63, 440.00,
174.61, 220.00, 261.63, 349.23,
196.00, 246.94, 293.66, 392.00
]
)
node dry = p * 0.18
node wet = delay(in=dry, time=0.375, feedback=0.35, mix=0.35)
out <- wet

7
examples/perc.patch Normal file
View File

@@ -0,0 +1,7 @@
# Hi-hat: ruido blanco filtrado en pasa-altos con envolvente percusiva.
node g1 = trig(period=0.5, duration=0.01)
node ne = adsr(a=0.001, d=0.06, s=0.0, r=0.04, gate=g1)
node n1 = noise()
node hp = filter(hp, in=n1, cutoff=4000, q=1.0)
out <- hp * ne

8
examples/sweep.patch Normal file
View File

@@ -0,0 +1,8 @@
# Bass con barrido: ADSR modula el cutoff de un pasa-bajos resonante.
# La salida se atenua tambien por la envolvente para tener attack y release de amplitud.
node g1 = trig(period=2.0, duration=0.6)
node o1 = osc(saw, freq=110)
node e1 = adsr(a=0.005, d=0.4, s=0.2, r=0.5, gate=g1)
node lp = filter(lp, in=o1, cutoff=200 + e1*3500, q=4.0)
out <- lp * e1 * 0.7