Laboratorio de Síntesis con SC
Deriva pasa del método al cuaderno de sesión
Esta quinta entrada recoge lo aprendido construyendo pad, bajo, ritmo y espacio dentro de DERIVA, y convierte ese proceso en un cuaderno técnico centrado en el continuo entre tono y ruido.
Este cuaderno no introduce una pieza nueva, sino una capa más concreta de Deriva: cómo se resolvieron en práctica la sintaxis de ProxySpace, el control de parámetros, el diseño del pad, el bajo, la rejilla rítmica, la reverb compartida y la forma macro. El hilo conductor es claro: usar el continuo tono-ruido como principio tímbrico y compositivo.
DERIVA
sesión de síntesis
Lo aprendido construyendo el pad, el bajo, el ritmo y el espacio — con el continuo entre el tono y el ruido como hilo conductor.
ProxySpace · contexto
Dos sintaxis: ~name y p[\name]
~pad es azúcar para currentEnvironment[\pad], y currentEnvironment se resuelve en tiempo de ejecución según el contexto. p[\pad] accede directamente a p, que es una variable global de letra, ajena al environment. En el top-level los dos coinciden porque el ProxySpace es el environment actual.
El problema aparece en contextos diferidos. Un Tdef, Routine o Task se reanuda en su propio hilo, donde currentEnvironment ya no es p. Ahí ~pad devuelve nil en silencio y revienta al primer .set — no al definir el Tdef, lo que lo hace traicionero.
~name
Evaluación interactiva en el editor: definir y redefinir proxies, pruebas en vivo, ~pad.play.
p[\name]
Dentro de Tdef, Routine, Task, callbacks, funciones en .do. El orquestador macro va siempre por aquí.
Una vez empujado el ProxySpace, ~ queda reservado para audio. Los datos (arrays, diccionarios, secuencias, rutinas auxiliares) van en variables de letra — q, r — nunca en ~, o se convierten en NodeProxy y se corrompen.
Suavizado de parámetros
Dónde colocar .lag
.lag persigue su entrada de forma exponencial. El mismo UGen tiene efecto opuesto según dónde se enchufe: sobre un control escalar (la amplitud, una nota) convierte un salto en una rampa suave; sobre una señal de audio es un filtro paso-bajo de un polo con corte ≈ 1/(2π·t) — con .lag(3) eso son ~0,05 Hz, una guillotina que borra todo lo audible.
// MAL: lag sobre el audio -> LPF a 0.05 Hz -> silencio
sig = BrownNoise.ar(1).lag(3);
// BIEN: lag sobre el VALOR del control -> rampa suave
sig = sig * \amp.kr(0.2).lag(3);
Para automatizar un parámetro desde un Tdef, .lag dentro del proxy es superior a .xset: el Tdef manda saltos con .set y el Lag.kr los suaviza en el servidor.
.xset no interpola el valor: hace un crossfade entre dos copias enteras del synth durante el fadeTime. Sobre una fuente aleatoria como BrownNoise, las dos copias generan ruido descorrelacionado y la transición se oye como engrosamiento, no como rampa de volumen. .xset se reserva para recrear la fuente con crossfade (cambios tímbricos grandes), no para mover un número.
Síntesis sustractiva
El pad: drone por filtro resonante
Un banco de BPF estrechos sobre BrownNoise, afinados a las notas de un acorde y esparcidos al estéreo con Splay. Cada nota es un control escalar independiente (\n0…\n3) con .lag para que los cambios de altura sean glissandos.
~pad = {
var sig, n0, n1, n2, n3, rq, gain;
n0 = \n0.kr(60).lag(2);
n1 = \n1.kr(67).lag(2);
n2 = \n2.kr(72).lag(2);
n3 = \n3.kr(76).lag(2);
rq = \rq.kr(0.005).lag(4);
gain = (0.005 / rq).sqrt * 10;
sig = BrownNoise.ar(1);
sig = BPF.ar(sig, [n0, n1, n2, n3].midicps, rq, gain);
sig = Splay.ar(sig);
sig = sig * \amp.kr(0.2).lag(3);
};
~pad.play;
Toda variable local va declarada en var (si no: Variable not defined). Y los NamedControls multicanal se indexan como notes, notes1… — por eso se usan controles escalares independientes y no un único \notes con array.
Curtis Roads · Microsound
El continuo tono ↔ ruido
El sonido no se divide en categorías limpias sino que ocupa un continuo, y lo que fija dónde caes es la concentración de energía en frecuencia: el ancho de banda. Energía en una banda estrechísima → el oído percibe pitch. Energía repartida en una banda ancha → percibe ruido. Entre medias vive la zona de tonos sucios, parcialmente afinados, donde respira buena parte de la música ambient.
En un BPF, el ancho de banda es frecuencia × rq. Con rq 0.005 sobre 261 Hz la banda mide ~1,3 Hz: casi un oscilador puro. El factor Q es el inverso: Q alto = banda estrecha = rq bajo. La resonancia del MoogFF en el bajo es ese mismo Q.
Llevado al extremo, rq 0.0001 da una banda de ~0,026 Hz: un resonador al borde de la autooscilación que canta la nota casi limpia; el ruido apenas lo excita. Es lento de cargar (régimen transitorio largo) y sube mucho de nivel — la compensación (0.005/rq).sqrt daría ×71, así que hay que vigilar el medidor.
Barrer el rq de 0.0001 a 0.4 y volver es recorrer el continuo de Roads en tiempo real: el pad pasa de armonía cristalina a nube de ruido. En el FreqScope se ve como picos finos que se ensanchan en colinas. Cruzar ese umbral puede ser un evento de forma — revelar la armonía en el clímax.
Voz de bajo
El bajo orgánico
Lo orgánico se construye apilando inestabilidades pequeñas: tres saws ligeramente desafinados que laten entre sí, micro-deriva de afinación, un cutoff que respira y una amplitud que nunca queda fija.
~bass = {
var sig, note, freq, cutoff, amp;
note = \bn.kr(48).lag(5);
note = note + LFNoise2.kr(0.08).range(-0.04, 0.04);
freq = note.midicps;
sig = Saw.ar(freq * [0.998, 1, 1.002]);
cutoff = (freq * 4 + (SinOsc.kr(0.06) * freq * 2)).clip(40, 6000);
sig = MoogFF.ar(sig, cutoff, 1.5);
amp = \amp.kr(0.3).lag(3);
sig = sig * amp * LFNoise2.kr(0.07).range(0.7, 1.0);
Splay.ar(sig);
};
El beating es el continuo en su zona más fina: a 0,2–0,3 Hz de diferencia el oído no separa dos tonos ni los funde en uno, percibe fluctuación — la frontera donde una diferencia de frecuencia se oye como ritmo lento, no como altura. El patrón del cutoff es el canónico de modulación con offset: (base + (mod * profundidad)).clip(min, max), con los paréntesis externos obligatorios para que .clip envuelva toda la suma.
El sub a freq * 0.5 cae en ~65 Hz, dentro del rolloff de los monitores. Verificar en FreqScope antes de fiarse del oído; si no aporta o enturbia, quitarlo.
Coherencia de reloj
Una sola fuente de tempo
El tempo vive solo en t. Todo lo que deba seguirlo se lanza con .play(t, quant: 4) — nunca con .play a secas, que va al TempoClock.default y queda sordo a los cambios de t. Ese fue justo el motivo de que t.tempo_(120/60) no cambiara nada: los Tdefs corrían en otro reloj.
// inmediato (salto)
t.tempo_(120/60);
// rampa suave — en SystemClock, NO en t (sus waits son segundos reales)
(
r = Routine({
var from = t.tempo, to = 120/60, dur = 15;
var n = (dur * 20).asInteger;
n.do { |i|
t.tempo_(from + ((to - from) * ((i+1)/n)));
(dur / n).wait;
};
}).play(SystemClock);
)
t como única fuente de verdad. Cambiar t reajusta pad, bajo y ritmo a la vez, gratis. El reverso oscuro — la triple fuente de verdad (t + ~bpm + un \bpm.kr hardcodeado) — es un bug a evitar. La rampa es la única excepción que vive en otro reloj.
SynthDef percusivos
Percusión sintetizada
Bombo (barrido de pitch + click + tanh), caja (cuerpo de ruido BPF + parciales tonales) y charles (ruido HPF + pulsos inarmónicos, con rel que decide cerrado u abierto).
SynthDef(\hh, { |out=0, amp=0.3, pan=0, rel=0.04|
var aenv, noise, metal, sig;
aenv = EnvGen.kr(Env.perc(0.001, rel), doneAction: 2);
noise = HPF.ar(WhiteNoise.ar, 9000);
metal = Mix(Pulse.ar([4100, 5300, 6700, 8200], 0.5)) * 0.15;
metal = HPF.ar(metal, 8000);
sig = (noise + metal) * aenv * amp;
Out.ar(out, Pan2.ar(sig, pan));
}).add;
En synths percusivos efímeros disparados por evento: doneAction: 2 (deben autodestruirse). Dentro de NodeProxies de señal continua: doneAction: 0 (matar el nodo silenciaría el proxy). Contextos opuestos — no es contradicción.
Ritmo generativo
L-System para el ritmo
Un sistema de reescritura produce una secuencia determinista, autosimilar e irregular — pulso reconocible sin caer en bucles cuadrados. Implementado a mano en sclang termina seguro y se puede inspeccionar:
q = {
var rules = ( \k: [\k,\r,\s,\r], \s: [\s,\r,\k,\r], \r: [\r] );
var seq = [\k];
3.do { seq = seq.collect({ |s| rules.atFail(s, { [s] }) }).flat };
// rellena a múltiplo de 16 -> encaja en compases
seq = seq ++ Array.fill((16 - (seq.size % 16)) % 16, \r);
seq
}.value;
Sin el relleno, la secuencia mide 22 pasos × 0.25 = 5,5 beats — no múltiplo de 4, así que el patrón se desliza contra la rejilla y el hi-hat lo delata como "desincronizado". Rellenar a 32 pasos lo encaja en 2 compases exactos.
Espacio compartido
Reverb como bus de retorno
Una sola reverb como retorno crea un espacio coherente para todo. Se usa JPverb — evita el crash de GVerb por encima de roomsize 290 y sus parámetros (t60, size) son modulables sin sustos. La percusión, al ser Synths, escribe a un bus auxiliar con un segundo Out.ar escalado por \rvb:
SynthDef(\sn, { |out=0, amp=0.5, pan=0, rvb=0.25|
/* … síntesis … */
Out.ar(out, sig);
Out.ar(~rvbBus.index, sig * rvb);
}).add;
Reverberar contenido grave enturbia toda la mezcla. Mandar el bajo muy poco al bus o no mandarlo; reservar el espacio para pad y percusión. El charles va bajo (~0.15) porque demasiada cola lo vuelve un siseo difuso.
Indeterminación controlada
Aleatorizar eventos
Tres grados de azar para el orden de los acordes, de menos a más orgánico:
.scramble— baraja: oyes todos antes de repetir, variedad asegurada..choose— azar puro: puede insistir en el mismo (a veces bonito, a veces se atasca)..reject({ |x| x == prev }).choose— azar que evita la repetición inmediata: la deriva natural sin atascos.
loop {
var ac = acordes.reject({ |x| x == prev }).choose;
prev = ac;
p[\pad].set(\n0, ac[0], \n1, ac[1], \n2, ac[2], \n3, ac[3]);
[8, 13, 21].choose.wait;
};
Aleatorizar también el wait con valores Fibonacci hace que el tiempo entre cambios respire de forma irregular pero dentro del sistema de proporciones de DERIVA: el azar vive dentro de la estructura, no contra ella.
prev es local al bloque del Tdef. Evaluar una línea suelta que la use da Variable not defined — en SC se evalúa la función entera entre paréntesis, nunca una línea interior aislada.
Higiene de servidor
Limpiar nodos
s.freeAll · Cmd-.
Pánico: mata todos los synths del servidor pero deja vivos los objetos NodeProxy. Quedan listados pero mudos — se recuperan con .play.
Reset ordenado
Tdef.all.do(_.clear) para los disparadores; currentEnvironment.clear libera todos los proxies. Deja servidor y t intactos.
// barrido ordenado: parar disparadores + liberar proxies
Tdef.all.do(_.clear);
currentEnvironment.clear;
Tras freeAll los proxies vuelven con ~pad.play; ~bass.play; ~verb.play; y los Tdefs con .play(t, quant: 4). El TempoClock sobrevive a Cmd-. por estar marcado .permanent_(true).
Roads · escalas temporales
Forma: navegar las escalas
Del análisis del set de co34pt salió una arquitectura formal legible en términos de Roads — la pieza navega entre la micro-escala (granos, onsets), la meso (frases, compases) y la macro (forma global), transformando la relación entre ellas:
El giro clave: introducir tarde un elemento de rejilla estable (el hi-hat en 1 y 3) que recontextualiza retroactivamente todo el material flotante anterior — el oído reinterpreta como "colocado" lo que oía como libre. No cambió el material; cambió el marco de referencia.
El clímax usa dos vectores de tensión a la vez: temporal (tempo arriba) y tímbrico (un elemento conocido vuelve deformado). La memoria del oyente de la versión limpia es lo que carga de sentido su retorno corrupto.
El arco Fibonacci con clímax en el compás 21 (21/34 ≈ 1/φ) puede adoptar el doble vector: que el cruce del continuo tono→ruido del pad coincida con la deformación del bajo, ambos en su extremo en el bar 21. Y la entrada del ritmo no como "ahora empieza la batería" sino como la lente que revela un pulso oculto en la deriva previa.