“Sampling” utilizando osciladores de tabla de onda (Tutorial 4)

1. Introducción. En música, el término "sampling" hace referencia al acto de tomar una porción de sonido grabado y re-utilizarlo en el desarrollo de discurso musical, utilizando "samplers" (hardware como la MPC de Akai) o estaciones de trabajo digital (software como Ableton o Logic) (más info aquí). En Max existe una gran variedad de objetos que permiten trabajar con sonido grabado, de los cuales mencionaremos unicamente los objetos wave~ y play~. Para construir un "sampler" con alguno de estos objetos, es necesario entender primero cómo funciona un oscilador de tabla de onda ("wavetable oscillator"), como se muestra en el video a continuación:

Descargue este patch aquí.

En el ejemplo anterior wave~ funciona como cycle~, el cual, en tutoriales previos, ha sido utilizado para implementar una sinusoidal. Esto se debe a que ambos objetos permiten leer repetidamente a través de un conjunto de valores que describen un ciclo de una onda sinusoidal. Al utilizar wave~, sin embargo, dicho conjunto de valores reside dentro del objeto buffer~, y no dentro del oscilador en sí, como se había presentado anteriormente (observe que el mensaje "fill sin 1" indica a buffer~ generar el conjunto de valores que representa la sinusoidal).

Dado que wave~ y buffer~ comparten el mismo símbolo como argumento ("myBuffer"), wave~ oscila de acuerdo al conjunto de valores contenido por buffer~ a una frecuencia especificada por el usuario. Observe que para especificar la frecuencia, se utiliza la salida del objeto phasor~ conectada a la entrada izquierda del objeto wave~; phasor~ entrega a su salida una rampa de valores decimales entre 0 y 1, que se repite de manera constante (pues phasor~ es en sí un generador de señal MSP); dicha rampa es utilizada por el objeto wave~ para indexar los contenidos del buffer~ (en donde 0 equivale al inicio y 1 al final de la tabla de onda). El diagrama de bloques a continuación, tomado de Puckette (2007, p.34), ilustra dicha funcionalidad:



Utilizando la misma implementación es posible también reproducir archivos de audio. Dado que buffer~ tiene acceso a la memoria RAM del ordenador, el conjunto de valores dentro de la tabla de onda puede tomarse a partir de una ruta de acceso a un archivo de audio en el disco duro ("path"). Para ello es posible utilizar el mensaje "replace", o arrastrar un archivo de audio desde "Finder" hacia el objeto buffer~, como lo muestra el video a continuación:

Descargue este patch aquí.

En este caso, el objeto wave~ también funciona como un oscilador de tabla de onda, más sin embargo, el contenido de la tabla corresponde a un archivo de audio que hemos cargado en la memoria RAM por medio de buffer~, convirtiendo nuestro oscilador en un "sampler". Para reproducir el archivo de audio de acuerdo a su altura y longitud original, necesitamos que la frecuencia de oscilación sea igual al inverso de la duración del archivo de audio. Si en tutoriales anteriores hemos utilizado el oscilador a una frecuencia lo suficientemente alta como para percibir su salida a una altura específica (p. ej. 440 Hz), en este caso, la frecuencia debe ser mucho menor, para que los ciclos sean percibidos como eventos sonoros individuales. Si, por ejemplo, el archivo de audio tiene dos segundos de longitud, la frecuencia para el objeto phasor~ debe ser equivalente a ½, es decir, 0.5 (Hz). Si el archivo es de 4 segundos, la frecuencia debe ser ¼, es decir, 0.25, etc. Esto se debe a la relación matemática entre frecuencia y período (Cipriani 2014, p.76). Observe a continuación como utilizar el objeto info~ (asociado al argumento de buffer~) para reproducir el archivo de audio de acuerdo a la altura y longitud original:

Descargue este patch aquí.

En el video anterior, utilizamos la salida número siete del objeto info~ para obtener la duración total en milisegundos de la tabla contenida por buffer~; dicho valor es expresado en Hz utilizando el objeto [translate], y posteriormente entregado a phasor~ como frecuencia de oscilación (determinando "qué tan a menudo ocurre la rampa"). Al modificar dicha frecuencia obtenemos un loop del archivo de audio a distintas velocidades (cambiando por ende, la altura original del sonido grabado). Si utilizamos valores de frecuencia negativos, la salida de phasor~ generará una lectura en reversa (pues se produce una rampa de valores decimales de 1 a 0 en lugar de 0 a 1) (Cipriani 2014, p.86). Observe cómo multiplicando la frecuencia original del oscilador por una segunda señal es posible reproducir el archivo a distintas velocidades (1x, 2x, 3x, etc), modificando la altura de la salida del "sampler" de manera proporcional:

Descargue este patch aquí.

2. Variables. En el diseño de un "sampler" es usual manipular la posición inicial de lectura del archivo de audio ("playback position"), así como el tamaño de la tabla ("segment size"). Existen múltiples ejemplos para implementar dicha funcionalidad (Carruthers 2014; Cipriani 2014, p. 86; Puckette 2007, p. 48-50; Raja The Resident 2012); una forma de hacerlo es utilizando el objeto waveform~, cuya interfaz gráfica permite seleccionar segmentos arbitrarios sobre la tabla de onda. Para asociar a waveform~ y buffer~ es necesario configurar el mensaje "name myBuffer"; posteriormente, es posible utilizar las salidas tres y cuatro del objeto waveform~ para controlar las entradas dos y tres del objeto wave~, habilitadas para modificar el inicio y final de la lectura de la tabla de onda:

Descargue este patch aquí.

Intercambiando entre los modos "select" y "loop" es posible seleccionar porciones del archivo de audio deliberadamente e interpolar entre ellas; dichos modos son habilitados por medio del objeto [attrui]. Sin embargo, como resulta evidente en el video anterior, el patch presenta una gran cantidad de clics y pops, es decir, cortes en la salida de audio generados por discontinuidades en las señales que están siendo manipuladas (siempre que exista una discontinuidad en los valores de amplitud entre dos "samples" consecutivos de una señal de audio digital, se generará un corte en la salida de audio). Para solventar este problema es necesario implementar dos técnicas en el diseño del patch: "sample and hold" y "windowing".

I. "Sample And Hold". Como se describió en el tutorial anterior, la unidad de muestreo y retención ("sample and hold") congela ciertos valores de una señal de audio de acuerdo a una secuencia de eventos en el tiempo ("Trigger"). A su salida, obtenemos una señal "escalonada" (semejante a una señal discreta), en donde cada nuevo escalón coincide con uno de los eventos de la secuencia:



Implementar esta técnia para el manejo de las variables en el "sampler" (p. ej. los extremos de la tabla) permite reducir significativamente la presencia de clics en la salida de audio. Para ello el objeto sah~ recibe una secuencia de eventos correspondientes al cambio de fase del oscilador ("phase wraparound"), momento en el que la señal es menor a 0.001, es decir, muy cercana a 0 (pues en realidad nunca es igual a 0). En el video a continuación, utilizamos el objeto sig~ para convertir las salidas tres y cuatro del objeto waveform~ a señales de audio; posteriormente, utilizamos dos instancias del objeto sah~ para controlar dichas señales:

II. "Windowing". Por qué actualizar estas variables durante el cambio de fase del oscilador? En un "sampler" de tamaño variable existe una discontinuidad regular durante el cambio de fase del oscilador, pues al modificar los extremos de la tabla de onda, necesariamente, se genera una discontinuidad de amplitudes entre el último y el primer "sample" (precisamente, en el momento denominado "phase wraparound"). Dado que la señal salta en este punto, es necesario aplicar una envolvente a la salida del "sampler" que permita enmascarar la discontinuidad, y de paso, enmascarar los artefactos y clics que puedan generarse debido a la manipulación de las variables en la lectura de la tabla. En Max, es usual implementar esta técnica utilizando una señal trapezoidal, entregada por el objeto trapezoid~, como lo muestra el video a continuación:

Descargue este patch aquí.

Observe que la señal a la salida de trapezoid~ presenta un fundido de entrada y uno de salida (como en un trapecio); dicha señal se multiplica con la salida de wave~ antes de pasar a la salida de audio, aplicando un fundido de amplitud durante el cambio de fase del oscilador. Dado que trapezoid~ monitorea la salida de phasor~, el primer argumento (p. ej. 0.1) indica el valor de la señal de phasor~, por debajo del cual la salida de trapezoid~ entregará un fundido de entrada ("ramp up"); el segundo argumento (p. ej. 0.99), en su lugar, indica el valor de dicha señal por encima del cual trapezoid~ entregará un fundido de salida ("ramp down"). Observe en el video a continuación la manipulación libre de clics y pops del tamaño y los extremos de la tabla:

3. "Enveloping Sampler". El término "sampler envolvente" ("enveloping sampler") es utilizado por Puckette (2007, p. 36) para describir el control sincronizado de la amplitud, con el loop de la lectura de la tabla, como se discutió en el ejemplo anterior. Sin embargo, en el diseño de Puckette existe una diferencia fundamental: la salida del oscilador diente de sierra (phasor~) se modifica mediante operaciones de aritmética simple, para ajustarse a un rango y una posición de lectura deseados. En lugar de utilizar las entradas dos y tres del objeto wave~ para definir los extremos de la tabla, es posible utilizar objetos nativos para multiplicar y sumar la salida de phasor~ por variables correspondientes al tamaño y la posición de lectura. El siguiente grupo de videos muestran como realizar esta modificación en el patch anterior, acorde a un ejemplo original de Sakonda (2011). Esta implementación es relevante, puesto que permite explorar la síntesis granular (es decir, la práctica de reproducir en una sucesión rápida múltiples segmentos cortos de una tabla de onda, utilizando uno o más osciladores) (Roads 2001), así como la expansión y compresión del tiempo en la reproducción de sonido grabado ("timbre-stretching") (Puckette 2007, p. 37).

Iniciamos por hacer a un lado el objeto waveform~ y convertir la duración total de la tabla (entregada por info~) en una señal de audio; observe que la operación !/~ 1000 reemplaza al objeto [translate]:

Descargue este patch aquí.

Posteriormente, multiplicamos la salida de phasor~ por una nueva señal (sig~) que representa el rango de lectura deseado para la tabla de onda ("segment size"). Para obtener el valor de dicha señal a partir de la variable dada en milisegundos, dividimos la variable por la longitud total de la tabla de onda, para obtener un valor entre 0 y 1, correspondiente al funcionamiento de wave~. Observe que, de acuerdo a lo discutido anteriormente, el cambio de las variables en la lectura del "sampler" debe ocurrir exclusivamente durante el cambio de fase del oscilador, para lo cual se utiliza el objeto sah~ como se presentó en la sección anterior.

Después de modificar el tamaño de la tabla, sumamos al resultado una segunda señal que representa la posición de lectura ("playback position"); para ello, repetimos el procedimiento anterior (por medio de float, sig~, sah~, etc.), incluyendo la división de la variable en milisegundos por la longitud total de la tabla, para obtener una señal entre 0 y 1. El resultado de se conecta a la entrada izquierda de wave~, quien en este caso, no recibe señales adicionales:

Observe que al cargar un archivo de audio en el buffer~, el objeto info~ actualiza el valor de la señal y permite la reproducción de dicho archivo de acuerdo a la altura y longitud original. Al reducir el tamaño de la tabla a menos de 100 milisegundos, es posible interpolar la posición de lectura y conseguir un efecto tipo "time-stretching":

Descargue este patch aquí.

Descargue este patch y pruebe a variar el tamaño de la tabla para cualquier archivo de audio. Observe que, en comparación con la implementación inicial de este tutorial, la altura de la salida del "sampler" no se modifica al cambiar el segmento de la tabla, o, al menos, no de manera permanente, sino momentánea (Puckette 2007, p. 33). Esto permite, por ejemplo, pedir copias de la lectura de la tabla incluyendo segmentos de silencio entre ellas, o, en sentido contrario, logrando que se superpongan (Puckette 2007, p. 40). En otras palabras, el oscilador lee la tabla acorde a un grado de transposición deseado, cuyo control es independiente del tamaño, como lo muestra el video a continuación:

Utilizando de nuevo el objeto waveform~, es posible visualizar la manipulación del tamaño de la tabla y la posición de lectura, utilizando en esta ocasión las entradas tres y cuatro de dicho objeto. Para ello, utilizamos conexiones remotas (es decir, los objetos "send~" y "receive~") junto con el objeto snapshot~, que convierte las señales de audio a flujo de control numérico:

Descargue este patch aquí.

Por último, utilizando el objeto [expr] es posible configurar un un grado de transposición deseado en semitonos, utilizando la ecuación "pow(2, $f1/120)" y dividiendo el resultado por el tamaño de la tabla en segundos; como en el ejemplo anterior, la altura se mantiene independiente del tamaño de la tabla. Observe que para que el tamaño de la tabla sea menor a 50 milisegundos ("granos"), es necesario modificar los argumentos del objeto trapezoid~ para aumentar el valor de la pendiente en el fundido de la amplitud implementado durante el cambio de fase del oscilador:

Descargue este patch aquí.

La transposición momentánea de la altura puede ser enmascarada, adicionando al patch una técnica "sample and hold" aplicada a la manipulación del tamaño de la tabla. Dicha modificación, sin embargo, no se incluye en este tutorial.

4. Acerca del objeto play~. Vale la pena anotar que también es posible utilizar el objeto play~ en lugar del objeto wave~ para implementar el "sampler" del patch anterior. El objeto play~ funciona de manera similar a wave~, puesto que reproduce también el archivo de audio contenido en un pedazo de memoria RAM por el objeto buffer~; sin embargo, mientras que wave~ está diseñado para reproducir la totalidad del archivo de acuerdo a una rampa de valores decimales entre 0 y 1, play~ requiere de una rampa que especifique la posición en milisegundos dentro del archivo de audio a reproducir (Cipriani 2014, p. 118). Debido a que play~ emplea interpolación cúbica (mientras que wave~ emplea interpolación lineal), puede ser más adecuado y arrojar un mejor resultado sonoro en situaciones particulares (más info aquí), como por ejemplo, en síntesis granular, en donde es usual utilizar múltiples instancias de la implementación ilustrada en el video a continuación, variando la posición de lectura y la relación de fase entre dos o más osciladores:

Descargue este patch aquí.

Referencias en Zotero