Blog de Metlivi

Cómo dar a cada nota tocada una cola de reverberación según el tiempo que se mantuvo pulsada

Para dar a cada nota tocada su propia reverberación, dependiente de la duración, mide el tiempo entre el inicio y el fin de la nota, asigna esa duración a un tiempo de decaimiento y aplica el resultado a una reverberación dedicada a esa nota. Mantén activa la reverberación de la nota después de que se libere su fuente de sonido.

Este tutorial crea ese comportamiento en SuperCollider e incluye entrada MIDI y una prueba con notas superpuestas. El resultado esperado es sencillo: una nota mantenida brevemente deja una cola corta, mientras que una nota mantenida durante más tiempo deja una cola más larga, incluso cuando sus reverberaciones se superponen.

29 de octubre de 20268 min de lecturaPor Metlivi Editorial Team
Sección 1

¿Por qué necesita cada nota una reverberación independiente?

Considera dos notas superpuestas. Mantienes la primera durante dos segundos y tocas la segunda durante un cuarto de segundo. Si ambas pasan por una sola reverberación, cambiar su decaimiento para la segunda nota también modifica el procesamiento de la reverberación que aún queda de la primera. Un control de decaimiento compartido no puede conservar dos ajustes independientes en el mismo estado de reverberación.

Por tanto, el enrutamiento debe ser el siguiente: la fuente de sonido de cada nota pasa por su propia reverberación y, después, se mezclan las salidas de esas voces procesadas. Esta es una deducción arquitectónica: las colas independientes requieren estados de procesamiento independientes, salvo que el instrumento o el efecto ya ofrezca una separación interna equivalente.

Un patch polifónico ofrece un marco útil para ello. Cycling ’74 documenta cómo poly~ de Max crea instancias de voz y utiliza thispoly~ para marcar cada instancia como ocupada o disponible. Para esta tarea, una voz debe permanecer ocupada durante toda su cola de reverberación, en lugar de quedar disponible inmediatamente al terminar la nota. Cycling ’74: Polifonía

También hay una limitación temporal: durante una interpretación en directo, no se conoce la duración final de una nota mantenida hasta que termina. La implementación siguiente utiliza un decaimiento provisional mientras la tecla está pulsada y asigna el decaimiento medido al soltarla. Si necesitas que el decaimiento final se aplique desde el principio, trabaja con duraciones de nota ya disponibles en una secuencia o procesa cada nota por separado después.

Sección 2

¿Qué debería controlar la duración de la nota mantenida?

Mantén separados tres controles: la liberación de amplitud del instrumento, el decaimiento de la reverberación y el nivel de salida procesada. Una liberación más larga del instrumento sigue enviando sonido a la reverberación. Un nivel de señal procesada más alto hace que el efecto suene más fuerte. Ninguna de estas operaciones, por sí sola, alarga el decaimiento de la reverberación.

El decaimiento tampoco suele significar un tiempo de corte exacto. La documentación de Reverb de Ableton define su control Decay como el tiempo que tarda la cola en caer hasta una milésima parte de su amplitud inicial, es decir, −60 dB. Esta distinción importa al comparar un ajuste de decaimiento con el momento en que una cola deja de ser audible. Ableton: Manual de referencia de efectos de audio de Live, Reverb

Para una primera implementación, usa este mapeo explícitamente ilustrativo, con todos los valores en segundos:

decay = min(6.0, max(0.25, 0.25 + 1.5 × heldDuration))

Duración mantenida Decaimiento asignado
0.10 segundos 0.40 segundos
0.25 segundos 0.625 segundos
1.00 segundo 1.75 segundos
2.00 segundos 3.25 segundos
4.00 segundos 6.00 segundos, con límite máximo

Estos valores son decisiones de diseño, no resultados acústicos medidos. El mínimo da a los toques breves una cola audible; el multiplicador determina cuánto cambia el efecto al mantener la nota; el máximo limita el decaimiento solicitado. Empieza con este mapeo y luego ajusta esas tres opciones manteniendo constantes la velocidad de la nota y el nivel de señal procesada.

Sección 3

Crea una voz que siga activa después de terminar la nota

El ejemplo usa GVerb, incluido en SuperCollider, que acepta una entrada mono, produce dos canales de salida y expone revtime en segundos. Su documentación también señala un pico de uso de CPU al instanciarlo y artefactos cuando cambia el tamaño de la sala. Por eso, este ejemplo mantiene fijo el tamaño de la sala y conviene empezar probándolo con unas pocas notas simultáneas. SuperCollider: GVerb

Cada voz contiene una fuente de sonido sencilla con dos osciladores, una liberación de amplitud corta y una reverberación. Una envolvente de duración independiente libera la voz solo después de que haya transcurrido el tiempo asignado a su cola. EnvGen de SuperCollider admite envolventes sostenidas que se liberan cuando su puerta llega a cero, así como una acción de finalización que libera el sintetizador que las contiene. SuperCollider: EnvGen

Abre una sesión nueva de SuperCollider con una salida de audio estéreo que funcione. Evalúa el siguiente bloque de configuración completo y espera a que aparezca Held-note reverb ready en la ventana Post. No se necesitan muestras ni bibliotecas adicionales. El código se ha revisado según la documentación, pero no se ha ejecutado aquí porque no está disponible el intérprete de SuperCollider.

(
s.waitForBoot {
    SynthDef(\heldNoteVerb, {
        |out = 0, freq = 440, amp = 0.035,
        gate = 1, decay = 1, alive = 1|
        var sourceEnv, lifetime, source, wet;

        sourceEnv = EnvGen.kr(
            Env.asr(0.01, 1, 0.02), gate
        );
        lifetime = EnvGen.kr(
            Env.asr(0.01, 1, 0.03), alive,
            doneAction: 2
        );
        source = (
            SinOsc.ar(freq) + (0.25 * SinOsc.ar(freq * 2))
        ) * amp * sourceEnv;
        wet = GVerb.ar(
            source,
            roomsize: 20,
            revtime: Lag.kr(decay, 0.03),
            damping: 0.5,
            drylevel: 0,
            earlyreflevel: 0.2,
            taillevel: 0.5
        );
        Out.ar(out, ((source ! 2) + wet) * lifetime);
    }).add;
    s.sync;

    ~heldVoices = Dictionary.new;
    ~tailJobs = List.new;
    ~voiceGroup = Group.head(s);
    ~voiceKey = { |num, chan, src|
        src.asString ++ ":" ++ chan.asString ++ ":" ++ num.asString
    };
    ~decayFor = { |held|
        (0.25 + (1.5 * held)).clip(0.25, 6.0)
    };

    ~releaseHeld = { |num, chan = 0, src = 0|
        var key, queue, entry, held, decay, node;
        key = ~voiceKey.value(num, chan, src);
        queue = ~heldVoices[key];
        if(queue.notNil) {
            if(queue.notEmpty) {
                entry = queue.removeAt(0);
                node = entry[0];
                held = (Main.elapsedTime - entry[1]).max(0);
                decay = ~decayFor.value(held);
                node.set(\decay, decay, \gate, 0);
                [\note, num, \held, held, \decay, decay].postln;
                ~tailJobs.add(Routine {
                    ((2 * decay) + 0.1).wait;
                    node.set(\alive, 0);
                }.play(SystemClock));
            };
            if(queue.isEmpty) { ~heldVoices.removeAt(key) };
        };
    };

    ~startHeld = { |vel, num, chan = 0, src = 0|
        var key, node;
        if(vel <= 0) {
            ~releaseHeld.value(num, chan, src);
        } {
            key = ~voiceKey.value(num, chan, src);
            if(~heldVoices[key].isNil) {
                ~heldVoices[key] = List.new;
            };
            node = Synth(\heldNoteVerb, [
                \freq, num.midicps,
                \amp, (vel / 127) * 0.035
            ], ~voiceGroup);
            ~heldVoices[key].add([node, Main.elapsedTime]);
        };
    };
    "Held-note reverb ready".postln;
};
)

El orden de las operaciones al soltar la nota es importante: la voz recibe a la vez el ajuste de decaimiento y el cambio de puerta de la fuente. La reverberación sigue procesando mientras la fuente se desvanece durante 20 milisegundos. Tras aproximadamente el doble del decaimiento asignado, más un pequeño margen, toda la voz se desvanece y se libera.

Ese margen final es una decisión de gestión de recursos, no una medición del silencio. Auméntalo si la cola restante todavía se oye al liberar la voz. Para un instrumento más grande, sustituye el margen fijo por una supervisión del nivel de salida junto con una duración máxima. El ejemplo también conserva las referencias a las tareas completadas hasta que se ejecuta la limpieza, por lo que es una demostración y no un instrumento diseñado para funcionar indefinidamente.

Sección 4

Conecta las notas MIDI tocadas con la voz correcta

La medición de duración corresponde a una instancia de nota concreta. Si solo registras la hora del inicio más reciente, el método falla en cuanto se superponen notas. En su lugar, la configuración almacena cada voz y su hora de inicio mediante una clave que incluye la fuente, el canal y la altura de la nota.

La guía MIDI de SuperCollider muestra cómo inicializar MIDI, conectar entradas y guardar referencias a sintetizadores para que el fin de nota pueda liberar el sonido correspondiente. La referencia de MIDIdef especifica que los argumentos de la función de retorno siguen este orden: valor, número de nota, canal y fuente. SuperCollider: Uso de MIDI, SuperCollider: MIDIdef

Cuando la configuración indique que está lista, evalúa este bloque para tocar el instrumento desde las entradas MIDI conectadas:

(
MIDIClient.init;
MIDIIn.connectAll;

MIDIdef.noteOn(\heldVerbOn, { |vel, num, chan, src|
    ~startHeld.value(vel, num, chan, src);
});
MIDIdef.noteOff(\heldVerbOff, { |vel, num, chan, src|
    ~releaseHeld.value(num, chan, src);
});
)

En el ejemplo, se define la duración «mantenida» como el intervalo entre los eventos de nota recibidos. No se implementa el comportamiento del pedal de sostenido. Si quieres prolongar la duración con el pedal, pospón las operaciones de liberación correspondientes hasta levantarlo y mide entonces ese intervalo más largo.

Las pulsaciones repetidas de la misma altura se almacenan en una cola y se liberan primero las más antiguas. Esta es una política explícita para emparejar eventos superpuestos de la misma altura en una sola fuente y canal. Los eventos de fin de nota sin pareja no hacen nada; las notas iniciadas sin pareja permanecen activas hasta la limpieza. Comprueba que los eventos estén emparejados antes de ampliar el instrumento.

Sección 5

Prueba las colas superpuestas antes de cambiar el sonido

Primero prueba el comportamiento sin teclado. La siguiente secuencia usa las mismas funciones de evento que la entrada MIDI: la nota 60 dura dos segundos, mientras que la nota 67 empieza a mitad de ese intervalo y dura un cuarto de segundo. Los decaimientos asignados previstos son aproximadamente 3.25 y 0.625 segundos.

(
~heldTest = Routine {
    ~startHeld.value(100, 60);
    1.0.wait;
    ~startHeld.value(100, 67);
    0.25.wait;
    ~releaseHeld.value(67);
    0.75.wait;
    ~releaseHeld.value(60);
}.play(SystemClock);
)

SystemClock permite programar en segundos, por lo que esta prueba no depende de un ajuste de tempo musical. En la práctica puede haber pequeñas diferencias entre las duraciones impresas y las solicitadas. SuperCollider: SystemClock

Escucha la cola corta de la nota más aguda mientras continúa la nota más grave y, después, la cola más larga de la nota grave. Luego invierte las duraciones. Esta segunda prueba ayuda a comprobar que el resultado depende de la duración y no de la altura. Compara los pares impresos de duración mantenida y decaimiento con el audio.

Para detener y eliminar este ejemplo, evalúa el siguiente bloque de limpieza después de instalar los manejadores MIDI. También cancela las tareas de cola pendientes antes de eliminar sus voces:

(
MIDIdef(\heldVerbOn).free;
MIDIdef(\heldVerbOff).free;
if(~heldTest.notNil) { ~heldTest.stop };
~tailJobs.do { |job| job.stop };
~tailJobs.clear;
~voiceGroup.free;
~heldVoices.clear;
)
Sección 6

¿Cómo diagnosticar colas que no se comportan correctamente?

Si todas las colas cambian al soltar una nota, revisa el enrutamiento de audio. Una marca de tiempo por nota sigue produciendo un comportamiento compartido si su resultado controla una sola reverberación después de mezclar las voces. Coloca la reverberación dentro de cada voz o enruta el audio aislado de cada nota a instancias de procesamiento separadas.

Si la cola desaparece justo al terminar la nota, revisa la duración de la voz. La envolvente de amplitud debe detener la fuente de sonido sin liberar la reverberación que la contiene. En una implementación en Max, conserva el estado ocupado de la voz hasta que termine su salida procesada, siguiendo el modelo de asignación documentado de poly~. Cycling ’74: Polifonía

Si los valores de decaimiento son incorrectos, imprime la clave del evento y las marcas de tiempo antes de cambiar la reverberación. En Max, el bloque básico equivalente para medir el tiempo es timer, que mide el tiempo transcurrido entre eventos y usa milisegundos de forma predeterminada. Convierte las unidades explícitamente cuando el destino espere segundos. Cycling ’74: timer

Si el rendimiento se vuelve irregular, reduce el número de voces simultáneas y revisa la reverberación antes de ampliar el patch. Esta demostración no impone un límite de voces y cada nota nueva crea otra reverberación. Un instrumento de producción necesita una política de asignación explícita que tenga en cuenta tanto las notas mantenidas como las colas que siguen sonando.

Cuando funcionen las pruebas de duración y superposición, sustituye el oscilador por la fuente de sonido que elijas para cada voz. Mantén la misma secuencia: asigna una voz, registra la hora de inicio, mide la duración al soltar la nota, asigna su decaimiento y conserva la reverberación hasta que termine la cola. Así, la duración mantenida se convierte en un control de interpretación reproducible.

Sección 7

Fuentes

Lecturas relacionadas

Sigue explorando este tema