4k synth

o como sintetizar música con unos pocos kb 

Introducción

Hace un tiempo decidí crear un sintetizador con el objetivo de poder incluir música en una intro (si no sabes de que hablo es el momento de leer qué es la demoscene). En pocas palabras lo que hay que conseguir es mostrar el mayor número de efectos en el menor espacio posible, sin superar los 4kb.

Si se piensa en incluir una canción directamente no hay más que hacer unos cuandos cáculos para ver que es imposible. Si ponemos un límite de 4kb, usando un muestreo de 44100 (el valor típico), mono, con una codificación de 16 bits por muestra tendremos suficiente para 4*1024*8/44100*16 segundos, esto es, más o menos la mitad de una décima de segundo. A pesar de reducir la calidad de el sonido, ya sea bajando la frecuencia de muestreo o los bits que se toman para codificar cada muestra, no conseguiremos aumentar mucho el tiempo de sonido que podemos almacenar. 

Si no te ha quedado nada de esto claro, te recomiendo que hagas una lectura del siguiente artículo de la wikipedia. En él se explican algunos concetos que necesitarás para entender la mayor parte del este artículo y en pocas palabras explica como a partir de una señal analógica se puede obtener una digital la cual se puede tratar gracias a un PC (o cualquier otro dispositivo apto, como un DSP).

En conclusión, no es posible incluir los datos del sonido dentro del ejecutable ya que esto haría aumentar el tamaño del mismo, por ello, una de las soluciones es generar el sonido.

Generando sonidos

La onda

Seguro que en muchas ocasiones has escuchado que el sonido está compuesto de armonicos, bueno, en general toda señal, bueno, casi toda, se puede descomponer en unos cuantos (o quizás alguno más) armonicos.

Un armónico no es más que una señal senoidal, un sin de toda la vida, esto es, si metemos en un buffer una señal senoidal de 440 hz y lo reproducimos adecuadamente escucharemos un pitido que corresponde a un LA. No es mucho, pero por algo hay que empezar:

int16 wave_data[N_SAMPLES]; 

for(int i=0; i<N_SAMPLES,++i) wave_data[i] = 32767*sin(2*PI*440*i/sample_rate);

 

Un pitido no es una canción y tampoco se parece a ningún instrumento conocido, nisiquiera a ningún sonido de la gameboy... :(. No pasa nada, existen diferentes tipos de ondas simples de generar que están compuestas de muchos más armónicos y por tanto tienen una sonoridad algo más interesante. Ejemplos de estos son la onda cuadrada, triangular, o incluso el ruido. Más adelante se verá como generar estas ondas y los problemas y ventajas que traen consigo.

 Además de onda usada hay otro parámetros importante a la hora de conseguir un sonido u otro. Este parámetro es la envolvente, esto es, la forma que tiene la amplitud o volumen de la onda en función del tiempo. Una de las más usadas en la síntesis de sonido es la ADSR y se pueden conseguir aproximaciones a instrumentos aceptables. Sin embargo, en este caso voy a hablar de una envolvente mucho más simple, una envolvente lineal. En ella el volumen del sonido desciende a medida que pasa el tiempo. Con ella se pueden simular algunos sonidos (bombos, por ejemplo) y es simplísima de implementar. Basta indicar el nivel inicial y lo que quieres que aumente o disminuya cada segundo y tendrás una senoidal.

 

Otro de los parámetros que podemos variar es la frecuencia. Además de que cada nota tiene una frecuencia definida (por ejemplo LA = 440Hz), podemos hacer que la frecuencia varíe en función del tiempo. Como antes, lo único que debemos indicar es la frecuencia inicial (que normlamente será la de la nota) y después lo que queremos que varíe cada segundo. Este parámetro permite obtener, junto a los anteriores, forma de onda y envolvente, sonidos que ya parecen algo.

Esta onda genera un sonido parecido a un bombo

 El filtro

 Un filtro no es más que una herramienta que nos permite eliminar o atenuar los armónicos que no  queremos. Esto permite modificar las ondas que hemos generado anteriormente para conseguir diferentes sonidos. Existen diferentes filtros, paso bajo, paso alto, paso banda (y dentro de ellos puede haber lineales, etc, pero esto es otra historia). En el caso de este pequeño sintetizador he usado un paso bajo, que no es muy complejo pero que permite, con dos parámetros, modificar la onda a gusto y que de paso hace que el sonido sea algo más claro ya que evita el aliasing).

Los parámetros del filtro, como he dicho antes, son dos, la frecuencia de paso, esto es, la frecuencia por encima de la cual no dejará pasar (en realidad atenuará) ninguna frecuencia. Por ejemplo, si pasamos una señal senoidal de 100hz por un filtro de 50hz de frecuencia de paso este se verá atenuado por más de la mitad. El otro parámetro es la resonancia, que tiene que ver con un pico que se produce en las proximidades de la frecuenia de paso. 

Variando estos parámetros en función del tiempo, como se hizo anteriomente con la envolvente y la frecuencia se pueden obtener otro tipo de sonidos. 

Arriba señal cuadrada original, abajo filtrada.

Juntando sonidos

Una vez que se han generado sonidos hay que unirlos ya que una melodía consta de diferentes notas. Por simplicidad tendremos una serie de notas, que llamaremos canal, y cada canal sonará de una forma diferente, en función de la onda que se haya generado para él en base los parámetros explicados antes. Definida la duración de una nota basta con ir avanzando por las notas a medida que el tiempo avanza, tomar la frecuencia de la nota y reproducir el sonido en base a esa frecuencia y a los parámetros del instrumento u onda como lo había llamado hasta ahora.

Si se usan varios canales y después se mezclan se puede obtener una melodía más o menos interensante. Es tan simple como se plantea, una vez conseguida la generación de ondas, basta con hacerlo varias veces para conseguir generar una canción.

Un poco de Código

Qué mejor que un poco de código para explicar todo lo anterior. El siguiente código no introduce todos los concetpos anteriores ya que no hace la mezcla final de canales y tampoco está optimizado para reducir tamaño, sin embargo es completamente funcional.

Las partes más interesantes del código son las siguientes:

  • Channel::Osc()  : Es la función encargada de generar los osciladores. Al fional de la misma se puede ver como se añade ruido. Gracias a esto se pueden generar instrumentos que contengan un oscilador más un ruido de fondo.
  • Channel::Filter():La función que filta la muestra in. Simple pero útil.
  • Channel::Render(): Es la que hace todo el trabajo sucio, con la frecuencia de la nota y los parámetros de frecuencia actualiza la fase del canal y la envolvente. Con eso genera la muestra, la filtra y obtiene la muestra final.
  • synth_channel_test: Dado un canal genera un wav con un canal que rellena con una sola nota que se le puede indicar.

Espero que el código sea suficientemente claro.

Producción

La parte técnica de las cosas es importante pero por muy buena tecnología que tengas si no eres capaz de que se pueda usar no habrás conseguido nada. Por ello voy a hablar un poco del proceso que he seguido para que un músico sea capaz de hacer música que suene en el sintetizador. Partiendo de que el músico no tiene porque saber programar no es aconsejable que le digas que debe escribir las notas en un .h y en hexadecimal....:) lo ideal es partiendo de formatos de fichero que el músico sea capaz de crear generar los ficheros correspondientes para que los pueda interpretar el sintetizador.

 En el caso de el sintetizador que he creado he usado dos herramientas. La primera es un generador de samples (86kb necesita framework .NET) que genera los samples que el músico podrá usar en su programa de composición preferido en formato wav y además los samples en formato .h para poder inclurilos después en el código fuente. Una vez compuesta la melodía, con la herramienta IT2h es posible extraer los datos necesarios para generar el .h que finalmente usará el sintetizador como fuente de datos ya que por nu lado tenemos toda la lógica que sintetiza y por otro el .h que incluye esta que contiene todas las variables necesarias, esto es, informacion de canales y de instrumentos.


Conclusión

En este documento se ha pretendido hacer una introducción a la síntesis práctica de sonido, aunque es un campo mucho más amplio en el que se pueden llegar a hacer cosas mucho más interesantes que generar música para llenar 4kb :) 

Links

Agradecimientos

soledad penades por su código de IT y la música para test, Wonder  y fuzzion por  sus ideas.