Skip to content

Latest commit

 

History

History
410 lines (266 loc) · 16.6 KB

tutorial.es.md

File metadata and controls

410 lines (266 loc) · 16.6 KB

Tutorial (es)

Índice

  1. Introducción
  2. Variables y formas de ondas básicas
    • Ondas: sine, saw, square, pulse, triangle, noise
    • Variables: t, o
  3. Mezcla: cómo sumar señales
  4. Frecuencia: f, m
  5. Secuencias: seq, seq1, aseq, rseq
  6. Envolventes: env , invEnv
  7. Velocidad: r
  8. Aleatoriedad: rand, randInt
  9. Modulación: RM, AM y FM
  10. Extensibilidad: Armonía básica con escalas y grados

Introducción

Tilt es un entorno de live coding para hacer síntesis de sonido en tiempo real. Está programado en JavaScript y corre en navegadores, tanto de escritorio como en teléfonos celulares (testeado en Chrome, Firefox y Safari).

Como sucede a menudo en la escena de live coding, es un software experimental y a veces puede tener errores y fallar, aunque generalmente recargando la página se puede recuperar. Tilt siempre guarda lo último escrito, por lo que basta con recargar la página para recuperarlo.

Empecemos escribiendo la siguiente línea de código:

o = sine(t)

Para crear sonido en Tilt, tenemos que definir la variable o. Esta es la variable de salida, que determina que es lo que sonará por la placa de sonido y por los parlantes. Debe ser un número entre 0 y 1. En este caso estamos definiéndola con sine(), que es una función que genera una onda sinusoidal, y su argumento en este caso es t. t es una variable de lectura que se incrementa continuamente, y junto con ciertas funciones matemáticas permite generar ondas periódicas, que son las que "suenan".

t por defecto va incrementándose a una velocidad tal que sine(t) genere una onda a 440 Hz (A4). Si dividimos t por 2 en el argumento de sine, iría a la mitad de velocidad (220Hz). En términos musicales, estaríamos sonando una octava abajo. Lo contrario sucede si multiplicamos por 2 (880Hz, una octava arriba). Se puede multiplicar o dividir por números decimales también, y eso generaría microtonos, pero no entraremos en detalle.

Formas de onda

Además del seno, Tilt tiene definidas otras formas de onda que se pueden usar como osciladores:

saw(t) es la forma de onda diente de sierra (sawtooth en inglés)

o = saw(t)

pulse(t, width) es la forma de onda de pulso. Esta función acepta dos parámetros, el primero es la frecuencia, y el segundo, width es el ancho del pulso activo, puede ser un número entre 0 y 1.

Estos osciladores no sólo sirven para generar sonido, también son útiles para modular parámetros de estas ondas. Por ejemplo, usando sine para el parámetro width en pulse, se puede fácilmente modular el ancho del pulso (pulse width modulation, PWM). Generalmente se utilizan osciladores con valores de frecuencia bajos (en el mundo de síntesis se llaman low frequency oscillators o simplemente LFOs).

// PWM, en el sine dividimos t por 1000 
// para hacer un seno de baja frecuencia
o = pulse(t, sine(t/1000))

Con // se pueden escribir comentarios en el código. Los comentarios son líneas de texto que Tilt va a ignorar al ejecutar. Todo lo que se escriba después de // hasta que termine la línea se ignora. Sirven para dejar notas y aclaraciones para otros coders (o nosotros mismos).

Otra forma de onda conocida y relacionada a pulse es la onda cuadrada:

o = square(t)

Es un caso particular de pulse pero con ancho 0.5.

Tambíén tenemos la onda triangular, que suena similar a sine:

o = tri(t)

Por último, una onda de ruido blanco o noise, que puede ser útil para sonidos percusivos:

o = noise()

Notar que esta última no tiene argumentos y no depende de t, dado que para cada muestra genera siempre un valor aleatorio (técnicamente, se genera un valor pseudoaleatorio con distribución uniforme).

Mezcla

Podemos sumar varios osciladores en o, pero hay que asegurarse que el total de la suma no supere 1, porque podría producirse distorsión.

o = sine(t) + saw(t)

Todas las ondas generan valores entre 0 y 1, si las sumamos directamente, pueden producirse valores más grandes que 1 (en el peor caso, en una muestra puede darse que ambas funciones generen 1, con lo cual la suma daría 2). Se puede solucionar dividiendo ambas funciones por 2:

o = sine(t)/2 + saw(t)/2

De esta manera, estaríamos sumando dos ondas que van de 0 a 0.5, y en el peor caso al sumarse lleguen a 1.

// también podemos multiplicar por 0.5
o = sine(t)*0.5 + saw(t)*0.5

Si fueran 3 ondas, habría que hacerlo por 3, y así sucesivamente.

Si una onda suena demasiado fuerte o abrasiva, siempre se puede multiplicar o dividir para achicar su amplitud y hacer que suene más bajito.

// en este caso saw suena más bajito que sine.
o = saw(t) * 0.25 + sine(t) * 0.5

Frecuencia

Venimos utilizando la variable t para controlar la frecuencia de nuestros osciladores, pero hay dos funciones más que generan valores que los osciladores entienden y que son más intuitivos desde un punto de vista musical:

La función f toma un valor de frecuencia en Hz. Por ejemplo, en vez de escribir sine(t):

o = sine(f(440))

Si uno quiere reproducir una frecuencia específica, esta función es más facil.

Por otro lado, la función m toma un valor de nota MIDI. El equivalente sería:

// 69 es A4 (440Hz)
o = sine(m(69))

Con esta función se podría hablar en notas musicales (semitonos) con facilidad, simplemente usando números enteros:

o = sine(m(60))  // C4, do, 4ta octava
o = sine(m(62))  // D4, re, 4ta octava
o = sine(m(63))  // D#4, re sostenido, 4ta octava

Ahora que tenemos los elementos básicos para generar ondas, vamos a generar ritmos con las funciones de secuencia y envolventes.

Secuencias

Tilt define un conjunto de funciones para cambiar los valores de los parámetros de las funciones secuencialmente y de esta manera generar ritmos. La velocidad con la que Tilt secuencia está dada por una variable especial K (que también se puede modificar!) y el uso de las funciones % y / (ver Apéndice A para más información técnica).

La primera función que podemos ver es seq. Esta función genera valores enteros del 0 al número que querramos, a cierta velocidad. Por ejemplo:

o = tri(t * seq(8, 4))

seq(8, 4) genera los valores 0, 1, 2 y 3, y vuelve a empezar, porque length es 4 (notar que va de 0 a 3, siempre es un numero menos), y cambia de valor cada medio segundo porque subdiv es 8. Si cambiamos subdiv a 4, cambiaría de valor cada segundo, es decir al doble de lento; si subdiv es 16 lo haría al doble de rápido.

Un ejemplo más musical, usando m en vez de t:

o = tri(m(60 + seq(16, 8)))

En este ejemplo estamos recorriendo las primeras 8 notas desde C4, pasando por cada semitono.

Otra función para secuenciar es aseq, y esta sirve para secuenciar listas de valores. Por ejemplo, podemos recorrer una lista de frecuencias:

o = tri(f(aseq(8, [440, 330, 660])))

o usando la función m, las notas del acorde Cmaj7:

let cmaj = [60, 64, 67, 71]
o = tri(m(aseq(32, cmaj)))

Aprovechamos este ejemplo para mostrar cómo definir nuestras propias variables. let define variables o funciones, dándoles un nombre al cual podremos referirnos más abajo. En este caso definimos cmaj como una lista de números (que sabemos que representan las notas de este acorde), y la usamos como parámetro de aseq. Esto es muy util cuando queremos reutilizar la misma lista en varios lugares en el código, dado que luego podríamos modificar esa lista facilmente cambiandola en un sólo lugar. En la sección Extensibilidad veremos más ejemplos de definición de variables y funciones.

La función aseq(subdiv, list) recibe dos parámetros, subdiv es la velocidad (idem seq), y list es una lista de valores. Dependiendo de dónde usemos seq, usaremos valores que tengan sentido. En el ejemplo con f es una lista de frecuencias en Hz, pero en el ejemplo de m es una lista de notas MIDI.

Por último, existe la función seq1, que es igual a seq, pero genera números a partir de 1, en vez de empezar por el 0. Basicamente es seq(subdiv, length) + 1. Hay situaciones donde generar ceros causa problemas, y seq1 está para evitar tener que escribir + 1 y poner paréntesis extras.

Volvamos al siguiente ejemplo:

o = tri(t * seq(8, 4))

Hay un momento donde seq genera un 0 y es por eso que el sonido se silencia, tri(t * 0) es tri(0), que es 0. Podemos arreglar este ejemplo usando seq1:

o = tri(t * seq1(8, 4))

Envolventes

Tilt define un par de funciones para generar envolventes exponenciales:

env(subdiv, curve, smooth) genera un envolvente que dura subdiv, y curve es un valor no negativo (entre 0 e infinito), que define que tan ajustada es la curva. Por defecto curve es 1, y cuanto más grande, mas corta es la curva y menos dura el sonido. Valores entre 0 y 1 generan curvas más largas.

Para usar las envolventes, dado que queremos modificar la amplitud del sonido, lo más usual es multiplicarlo al oscilador:

o = saw(t) * env(8)

El tercer parámetro smooth también es opcional, es un número entre 0 y 1, y divide el envolvente en una rampa lineal y la curva exponencial, permite generar un ataque más suave.

Otra función de envolventes es invEnv. Es igual a env, pero genera una curva invertida.

o = saw(t) * invEnv(8,2)

Veamos un ejemplo más complejo, usando algunas de las funciones vistes hasta ahora:

o = pulse(m(aseq(4,[69,60,64,69])) * seq1(aseq(seq1(16,16), [256,128,64,32,16]), 4),
          tri(t/1000)) * env(32,1)

Hay una función más llamada rseq que es similar a aseq y permite secuenciar listas de valores pero de manera aleatoria. La veremos más adelante en la sección Aleatoriedad.

Velocidad

Hay una variable especial que controla la velocidad de todo lo que suena en Tilt.

Supongamos que tenemos:

o = sine(t)

Si agregagamos lo siguiente:

o = sine(t)
r = 1.25

Notamos que el sine parece generarse a una frecuencia más alta. Si estamos usando funciones de secuencia, la velocidad de las secuencias también cambia.

o = sine(t) * env(16)

r = 1
// r = 2
// r = 0.5

r controla la velocidad de muestreo, que por defecto es 1. Hace que t se incremente más rápido en cada paso.

Aleatoriedad

Tilt posee algunas funciones básicas para generar números aleatorios. Estas funciones también son secuenciales, es decir, toman como primer parámetro subdiv, al igual que seq y derivados.

La primera que veremos es rand, que genera números entre 0 y 1.

o = sine(t * rand(32))

Este ejemplo genera un seno de frecuencias entre 0 y 440Hz de manera aleatoria.

rand(subdiv, seed) toma dos parámetros, subdiv es la velocidad (idem seq), y seed es la semilla, que es un número entero no negativo. El generador de números aleatorios de Tilt siempre genera la misma secuencia de números, pero seed permite elegir una secuencia distinta. Esto nos da la posibilidad de usar la misma secuencia aleatoria en varios lugares (empleando el mismo seed), y variarlo cuando querramos.

La segunda función es randInt, que genera números aleatorios enteros. Esto es util para parametrizar enteros, como notas MIDI o índices en listas de notas.

o = sine(t * randInt(32,8)) 

En este caso randInt está generando valores entre 0 y 7 de manera aleatoria con velocidad 32.

La función rseq es similar a aseq, pero utiliza randInt para acceder a los elementos de la lista. Permite secuenciar una lista de valores de manera aleatoria.

Por ejemplo, el siguiente ejemplo genera notas de la escala mayor con rseq

o = sine(m(rseq(32, [60, 62, 64, 65, 67, 69, 71, 72])))

Modulación

Esta sección está basada y adaptada del tutorial de Síntesis en SuperCollider de Nick Collins.

En síntesis de modulación, una onda, la portadora (carrier), es influenciada o modulada por una segunda onda, la moduladora.

Dependiendo de cómo la portadora y la moduladora se conectan, hay varios métodos en uso.

RM: Modulación de Anillo (ring modulation)

Modulación de anillo (RM) es la multiplicación de ambas señales: carrier * modulator. Por ejemplo:

o = sine(t) * sine(t/2)

AM: Modulación de Amplitud

Modulación de amplitud (AM) es prácticamente igual a RM, pero la amplitud de la moduladora esta entre 0.5 y 1, porque al convertirse en señal de salida, termina siendo sólo positiva (unipolar: entre 0 y 1, en vez de bipolar: entre -1 y 1).

// multiplica por 0.25 y suma por 0.75, para la amplitud de la señal de salida oscile entre 0.5 y 1.
o = sine(t) * (sin(t/100)*0.25+0.75)

En música este efecto también se llama tremolo.

FM: Modulación de Frecuencia

En vez de conectar la moduladora a la amplitud de la portadora, vamos a conectarla a la frecuencia de la portadora. Podemos pensar en 3 parámetros, la frecuencia de la portadora cfreq, la frecuencia de modulación mfreq, y la profundidad de modulación depth.

let cfreq = f(440);
let mfreq = f(200);
let depth = tri(t/5000) * 100;

o = sine(cfreq + (depth * sine(mfreq)));

Depth permite ajustar rapidamente la frecuencia de modulación.

Cuando usamos valores de mfreq más bajos, se produce un efecto conocido como vibrato:

let cfreq = f(440);
let mfreq = f(4);
let depth = 4;

o = sine(cfreq + (depth * sine(mfreq)));

A diferencia del tremolo, en este caso se modula lentamente el tono (la frecuencia) de la señal, en vez de su amplitud.

Extensibilidad

En Tilt se pueden escribir variables y funciones personalizadas, para reutilizar fragmentos de código y agregar nuevas funcionalidades.

Para ver un ejemplo concreto del uso de variables, vamos a explorar un ejemplo de harmonía básica y de secuenciación de patrones de notas.

o = tri(m(60)) * env(16)

En este ejemplo está sonando siempre la misma nota, C4 (Do central). Ahora, supongamos que queremos hacer una melodía dentro de la escala mayor de Do.

o = tri(m(aseq(16, [60, 62, 64, 58])) * env(16)

Podemos reescribir la lista de notas MIDI tomando una nota raíz, que sea 60, y sumar una lista de notas relativas:

o = tri(m(60 + aseq(16, [0, 2, 4, -2])) * env(16)

La ventaja es que ahora podemos modificar solo el valor 60, y con solo cambiar ese número podemos trasponer toda la melodía.

o = tri(m(57 + aseq(16, [0, 2, 4, -2]))) * env(16)

Y la raíz la podemos definir en una variable, para reutilizarla en otras ondas:

let root = 60

o = tri(m(root + aseq(16, [0, 2, 4, -2]))) * env(16) * 0.5
o += sine(m(root - 12 + aseq(4, [0, -2]))) * env(4,0.5) * 0.5

A root no sólo podemos asignarle una nota, podemos usar las funciones de secuencia o aleatorias que generen números enteros:

// Ahora la raiz pasa entre C4 y C5 (sube una octava, es decir 12 semitonos, pues 72 = 60 + 12)
let root = aseq(2, [60, 72])

o = tri(m(root + aseq(16, [0, 2, 4, -2]))) * env(16) * 0.5
o += sine(m(root - 12 + aseq(4, [0, -2]))) * env(4,0.5) * 0.5

Pero también podemos definir el concepto de octavas, sabiendo que la raíz la podemos calcular:

// root se calcular a traves de octave, pues cada octava son 12 semitonos
let octave = aseq(2, [5, 6])
let root = octave * 12

o = tri(m(root + aseq(16, [0, 2, 4, -2]))) * env(16) * 0.5
o += sine(m(root - 12 + aseq(4, [0, -2]))) * env(4,0.5) * 0.5

Podemos definir escalas y trabajar con el concepto de grados, para definir una melodía en grados e ir cambiando la escala secuencialmente:

// Escalas
let major = [0, 2, 4, 5, 7, 9, 11];
let minor = [0, 2, 3, 5, 7, 8, 10];

// Octava base
let octave = 5;

// Funcion que convierte grados a notas MIDI desde la octava base
let deg2note = (sc, degree) => ((octave + Math.floor(degree / sc.length)) * 12) + sc[degree % sc.length];

// Una melodía en grados, que son los índices dentro de la escala
let degrees = [0, 2, 3, 6];

let melo = aseq(2, [ 
  deg2note(major, aseq(32, degrees)),
  deg2note(minor, aseq(32, degrees))
]);

o = tri(m(melo + aseq(16, [0,7,12,7]))) * env(32,1)