Laboratorio de Síntesis con SC
La serie pasa del patch a la composición como sistema
Esta cuarta sesión formaliza el laboratorio como método: una arquitectura temporal donde ritmo, voz, forma y mezcla dejan de ser parches aislados y pasan a operar como agentes coordinados.
Deriva.
Método de composición algorítmica en capas: una arquitectura del tiempo en SuperCollider construida con ProxySpace, NodeProxy, Tdef y un único reloj compartido.
Esta entrada no parte de un patch aislado, sino de una pregunta de método: cómo organizar una pieza algorítmica cuando timbre, patrón, frase y forma global operan simultáneamente en escalas distintas. Deriva propone una respuesta práctica dentro de SuperCollider: un reparto de agentes donde cada nivel temporal tiene su mecanismo propio y todos se sincronizan a un mismo pulso.
Deriva.
Lo que sigue no es un manual de SuperCollider. Es la formalización de un método: una manera de organizar la composición sonora como una jerarquía de agentes, donde cada capa temporal de la música, desde el sample hasta la forma global, tiene su propio mecanismo, su propia herramienta, y todas se sincronizan a un único pulso compartido.
La premisa es de Curtis Roads y la heredamos directamente: la música ocurre en escalas temporales distintas a la vez, y cada escala requiere un modo de pensamiento diferente. Aquí la idea se traduce al entorno ProxySpace de SuperCollider, no como teoría abstracta sino como ingeniería compositiva operativa.
Las capas del tiempo
Toda pieza musical existe simultáneamente en varias escalas temporales. La nota es solo una de ellas. Por encima están las frases, las secciones y la forma global. Por debajo viven las partículas que componen el timbre, los samples individuales y el grano. Roads identifica nueve escalas; para este método, las cinco centrales son las que importan, y cada una se materializa en un mecanismo técnico distinto.
La consecuencia práctica es directa: cuando una pieza no funciona, hay que preguntarse en qué escala está el problema. Si la batería suena tímbricamente plana, la respuesta no está en el patrón rítmico sino en la voz o en la envolvente del grano. Si la pieza es monótona pese a tener voces ricas, el problema está arriba: falta arquitectura macro.
Servidor e intérprete
SuperCollider está partido en dos mundos que se comunican por una grieta estrecha. Saber qué vive a cada lado de esa grieta es la base del resto: el servidor produce sonido continuo; el intérprete decide cuándo y cómo cambian las cosas.
La arquitectura clave es que los mensajes no van a nodos numerados sino a proxies con nombre: estructuras del ProxySpace que sobreviven a redefiniciones, mantienen su bus de salida y permiten esculpir el sonido en vivo sin rehacer la topología completa.
Un solo pulso, muchas lentes
Todo el tiempo de la pieza desciende de un único impulso. No hay tres relojes; hay uno y varias lentes sobre él. Esta es la decisión arquitectónica más importante del método.
// el latido único — semicorcheas a tempo
~clkMaster = { Impulse.kr((~bpm.kr / 60) * 4) };
// el contador que cuenta los latidos
~cnt16 = { PulseCount.kr(~clkMaster.kr.first) - 1 };
// lentes derivadas sobre el mismo contador
~cnt4 = { (~cnt16.kr.first / 4).floor };
~cnt1 = { (~cnt16.kr.first / 16).floor };
~cnt16.kr.first devuelve una referencia a una señal del servidor. Cualquier operación aritmética sobre ella genera UGens en el grafo de audio. Nada de estas líneas ocurre realmente en sclang; lo que haces es describir un circuito.
- Toda gate debe derivar del mismo
~cnt16, no inventar su propioPulseCount. - Al leer un proxy desde otro, conviene ser explícito con
.kr.first. - Cambiar el tempo en vivo requiere reinyectar el valor al servidor; no basta con modificar una variable en
sclang.
Objeto sonoro vivo
Una voz, aquí, no es un sonido que se dispara y muere. Es una escultura que se mantiene activa y se deforma en tiempo real. La encarnación técnica de esa idea es el NodeProxy con NamedControls.
~bd = {
var clk, cnt, pat, gat, envAmp, envFreq, sig;
clk = ~clkMaster.kr.first;
cnt = ~cnt16.kr.first;
pat = \pat.kr([1,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0]);
gat = clk * Select.kr(cnt % 16, pat);
envAmp = EnvGen.kr(Env.perc(0.002, \dec.kr(0.3), curve: -4), gat, doneAction: 0);
envFreq = EnvGen.kr(Env.perc(0.001, \pdec.kr(0.05), curve: -8), gat, doneAction: 0);
sig = SinOsc.ar((envFreq * \sweep.kr(120) + \base.kr(48)).clip(20, 18000), 0.5pi) * envAmp;
sig = (sig * \drive.kr(1)).distort * \amp.kr(0.6);
sig ! 2;
};
\nombre.kr(default). Los números que se quedan duros son decisiones permanentes; los que se exponen son decisiones en curso.
doneAction: 0 dentro de proxies
El default de EnvGen es doneAction: 2, que libera el nodo entero al terminar la envolvente. En un Synth efímero esto es correcto. En un NodeProxy es desastroso: el proxy sigue sosteniendo su bus, pero el sintetizador ya no existe. El resultado no es un error visible, sino un fantasma silencioso.
Patrón canónico de modulación con offset
(\base.kr(500) + (envOrLfo * \amount.kr(800))).clip(min, max)
Los paréntesis externos hacen que .clip envuelva la suma completa. Sin ellos, el recorte solo afectaría al último factor y el barrido podría cruzar rangos absurdos.
La rejilla como partitura
Un patrón rítmico es literalmente un array de unos y ceros. Esa frase no es metáfora: el array que escribes en tu código es la partitura del secuenciador. Select.kr es la cabeza lectora que la recorre.
pat = \pat.kr([1,0,0,0, 0,0,1,0, 1,0,0,0, 1,0,1,0]);
gat = clk * Select.kr(cnt % 16, pat);
Hay dos regímenes de patrón. El primero es periódico: módulo, división, fase. El segundo es irregular: array literal y lectura por índice. El cambio entre uno y otro no es cosmético; define si piensas la rítmica como filtro regular o como partitura explícita.
.set, .xset, map
Una voz parametrizada tiene tres modos de mutación, y elegir el correcto es media composición.
| Modo | Comportamiento | Uso musical |
|---|---|---|
.set(\x, v) |
Instantáneo, salto duro | Cortes, drops, eventos donde el cambio mismo es la decisión musical. |
.xset(\x, v) |
Crossfade durante fadeTime |
Entradas, salidas y transiciones continuas. |
.map(\x, ~lfo) |
Conexión continua a otro proxy | Modulación viva. El parámetro respira por sí mismo. |
~lfoCutoff = { LFNoise1.kr(0.5).range(400, 4000) };
~lead.map(\cutoff, ~lfoCutoff);
El director de orquesta
Hasta aquí todo vive en escalas pequeñas. Falta la capa que decide qué pasa a lo largo de la pieza entera: cuándo entra cada voz, cuándo cambia el groove, cuándo llega el climax. Esa capa se llama Tdef.
Tdef(\macro, {
var bar = 0;
loop {
if (bar == 0) {
p[\pad].xset(\amp, 0.14);
[\bass, \hh, \bd].do { |k| p[k].set(\amp, 0) };
};
if (bar == 8) { p[\bass].xset(\amp, 0.45) };
if (bar == 13) { p[\hh].xset(\amp, 0.40) };
if (bar == 21) {
p[\bd].set(\pat, [1,0,0,0, 0,0,1,0, 1,0,0,0, 1,0,1,0]);
p[\bd].xset(\amp, 0.70);
};
bar = (bar + 1) % 34;
4.wait;
};
}).play(t, quant: 4);
Dentro del Tdef, la referencia correcta a los proxies es p[\nombre], no ~nombre. También hay una regla temporal simple: 4.wait es un compás en 4/4 porque el Tdef mide su espera en beats del TempoClock.
La decisión estructural clave es si la forma es cíclica o de una sola pasada. Un loop con módulo devuelve siempre la pieza al compás cero. Un bucle finito deja la forma morir una vez y conservar el último estado de las voces.
La sección áurea
Si ya puedes decidir cuándo entran las voces, la siguiente pregunta es dónde. La respuesta de este método es concreta: en posiciones guiadas por la razón áurea, φ = 1.618....
Una forma de 34 compases con su climax en el compás 21 cumple que 21 / 34 = 0.618. Esa asimetría crea un peso estructural que la simetría 1:1 raramente produce: más buildup que release, más gravedad dramática.
Un solo bus de salida
Si cada voz se reproduce directamente sobre el bus principal del servidor, no hay un punto donde gestionar la dinámica global. La solución es disciplinada y simple: un único bus máster que lee a todos los demás y aplica el último tramo de la cadena.
.play se llama una sola vez en todo el sistema.~master = {
var dr, bs, pd, sum;
dr = ~drums.ar(2) * \drumsLvl.kr(0.55);
bs = ~bass.ar(2) * \bassLvl.kr(0.45);
pd = ~pad.ar(2) * \padLvl.kr(0.50);
sum = dr + bs + pd;
sum = Compander.ar(sum, sum, 0.4, 1, 0.5, 0.01, 0.1);
sum = (sum * \drive.kr(1.2)).tanh;
Limiter.ar(sum, 0.92) * \master.kr(0.85);
};
- Faders por canal: cada voz entra al cuadro desde un único lugar.
- Pegamento: un
Compandersuave amalgama sin aplastar. - Carácter: una saturación
tanhañade armónicos y cohesión. - Seguridad: el
Limiterfinal impide desbordes.
Cinco escalas, cinco agentes
El método consiste en aceptar que ningún objeto del sistema puede pensar en más de una o dos escalas temporales a la vez y en repartir el trabajo coherentemente entre los agentes que sí saben pensar en su escala.
| Escala | Agente | Mecanismo |
|---|---|---|
| sample | UGens del servidor | DSP continuo a 44.1 kHz |
| micro | Envolventes | Env.perc, Env.asr con doneAction: 0 |
| objeto sonoro | NodeProxy | NamedControls, .set, .xset, .map |
| meso | Tdef + ~clkMaster | Cambios por compás y patrones |
| macro | Tdef + φ | Arco áureo de N compases Fibonacci |
Cada agente hace lo que solo él sabe hacer, y todos comparten un único pulso. El compositor no se limita a tocar botones: compone el sistema y luego escucha qué hace.
Deriva v.2 — el script entero
Este código no es Deriva como pieza terminada. Es la suma didáctica de las técnicas que el cuaderno explica: voces, efectos, Tdef y topología del máster puestos juntos para que se vea el método ejecutándose a la vez.
La pieza estética empieza después: quitando capas, refinando timbres, dejando respirar al material y decidiendo qué sobra.
Desplegar script completo
Twistin · MMXXVI · Vigo
El código es ejemplo pedagógico; la pieza es lo que se hace con él.