Desarrollo de una librería de sonido en C

Igual que hemos hecho con los sprites, se podrían desarrollar librerías en C para trabajar en modo bitmap, modo bitmap multicolor, definir y activar juegos de caracteres personalizados, hacer scroll, etc. Algunas de estas librerías ya estarían parcialmente cubiertas por otras propias de cc65, como TGI; otras no.

Sea como fuere, la otra gran librería que podemos hacer es una librería de sonido en C, y a esto es a lo que vamos a dedicar esta entrada.

Nuevamente, si nos inspiramos en la librería en ensamblador del Volumen II:

https://programacionretroc64.files.wordpress.com/2019/11/lib-v2.zip

y, más concretamente, en el fichero “LibSonido.asm”, vemos que tiene rutinas para:

  • Inicializar una imagen del SID.
  • Transferir la imagen del SID (al SID, claro).
  • Fijar el volumen.
  • Fijar la frecuencia de una voz.
  • Fijar la forma de onda de una voz.
  • Fijar el ADSR de una voz (attack – decay – sustain – release).
  • Fijar el ancho de pulso de una voz, en caso de que la forma de onda sea cuadrada.
  • Configurar un filtro sobre una voz.
  • Activar una voz.
  • Desactivar una voz.
  • Pasar de octava y nota a frecuencia.
  • Introducir un retardo.

Pues bien, todo esto perfectamente puede hacerse en C. Es más, es mucho más cómodo usarlo mediante una librería (pareja de ficheros sonido.h y sonido.c) que conociendo y manejando las estructuras de datos que cc65 define en _sid.h:

Vamos a ello:

Fichero de cabecera sonido.h:

El fichero de cabecera tiene una primera parte de definición de constantes que es así:

Es decir, aparte de controlar si el fichero ya está incluido con __SONIDO_H, define constantes para el tamaño del SID (25 registros de sólo escritura; 29 en total), para las tres voces (que ahora vamos a numerar 0, 1 y 2), para las formas de onda y para los tipos de filtro. También define el tipo byte.

A partir de ahí, incluye prototipos para las funciones de interés:

No vamos a repetir aquí las funciones, porque en el fondo son las mismas que las rutinas que ya se han enumerado para la librería en ensamblador.

Sí interesa recordar que, al ser el SID en su mayoría registros de sólo escritura, salvo los cuatro últimos ($d419 – $d41c), que son de sólo lectura, aplicaremos la técnica de trabajar sobre una imagen del SID (un array de 25 posiciones, uno por cada registro de sólo escritura) y copiar o transferir esa imagen al SID cuando se quiera configurar éste. De este modo será posible conocer y modificar el estado del SID a partir de su imagen.

Fichero de implementación sonido.c:

El fichero que implementa la librería empieza con esta apariencia:

Es decir, primero incluye el header file sonido.h (con “…” para que no se busque en cc65\include, sino en el mismo directorio que sonido.c) y algunas librerías estándar. Después recoge tres tablas de interés:

  • La tabla sonido_imagen_sid[] es la imagen del SID. Esta es la tabla que vamos a configurar con las diferentes funciones y, llegado el momento, vamos a transferir o copiar al SID.
  • La tabla sonido_offset_voces[] nos da el offset de los registros de cada voz ($00, $07 y $0e respectivamente) dentro del mapa de memoria del SID.
  • La tabla sonido_frecuencias_oct7[] nos da las frecuencias correspondientes a las 12 notas de la octava 7, y que nos permite calcular las frecuencias de esas mismas notas en otras octavas (0…6).

A partir de ahí, vienen las implementaciones de las diferentes funciones. E, igual que en el caso de la librería de sprites, no vamos a revisarlas todas, sino alguna seleccionada:

Por ejemplo, para la función sonido_fija_volumen():

  • Recibe el volumen en un byte, aunque sólo ocupa un nibble.
  • Se queda con el nibble bajo mediante un AND (volumen & 0x0F).
  • Toma la posición 0x18 = 24 de la imagen del SID, le hace un OR con el volumen (sonido_imagen_sid[0x18] | volumen), y lo vuelve a guardar en la posición 0x18.

Esto último, es decir, el tomar la posición y hacerle un OR, lo podemos hacer porque estamos trabajando con una imagen del SID. Directamente contra el SID no sería posible hacerlo, porque esos registros son de sólo escritura.

Ya sólo nos quedaría transferir la imagen al SID para que el cambio de volumen fuera efectivo.

Por otro lado, la función sonido_fija_frecuencia() hace cosas parecidas, por ejemplo un AND (frecuencia & 0x00FF) para quedarse con el byte low o menos significativo de la frecuencia, y un desplazamiento de 8 bits a la derecha (frecuencia >> 8) para quedarse con el byte high o más significativo.

Todo esto demuestra que C es un lenguaje de “alto nivel”, pero no mucho, ya que tiene operadores como &, |, >> y muchos otros que permiten operar con bits de forma similar al ensamblador.

Programa de ejemplo:

Para el programa de ejemplo nuevamente nos vamos a basar en uno ya conocido, concretamente el programa 53 del Volumen I:

Este programa, tanto la versión en ensamblador como la versión en C, tiene una tabla de cuatro entradas, siendo cada entrada:

  • La octava y la nota de la voz 0.
  • La octava y la nota de la voz 1.
  • La octava y la nota de la voz 2.
  • La duración y el volumen, que son comunes a las tres voces.

Y para que la tabla con la melodía ocupe lo menos posible, octavas y notas se codifican en el mismo byte, a razón de un nibble cada una, al igual que la duración y el volumen.

Por tanto, lo que tiene que hacer el programa es inicializar las voces con su forma de onda y ADSR y, luego, reproducir la melodía. Para esto último:

  • Lee un byte de la tabla, separa octava y nota, calcula la frecuencia, y configura la voz 0 con esa frecuencia.
  • Idem para la voz 1.
  • Idem para la voz 2.
  • Lee un byte de la tabla, separa duración y volumen, y configura el volumen para las tres voces.
  • Activa las tres voces y transfiere la imagen al SID. Hasta este punto el SID no ha cambiado.
  • Espera la duración de la nota.
  • Y repite todo lo anterior hasta que termine la melodía, lo cual se señaliza con el byte $ff, que no es un byte válido pues el C64 no soporta ni $0f = 15 octavas ni 15 notas por octava.
  • Para terminar, desactiva las tres voces y vuelve a transferir la imagen al SID. Esto último –desactivar las voces– se puede hacer en cada iteración del bucle, es decir, de una nota a la siguiente, pero si se hace sólo al final del mismo la reproducción queda como más fluida.

Y como el programa es largo, aunque esencialmente es un bucle, sólo vamos a ver en detalle algunas de sus funciones más representativas. Por ejemplo, la función configura_frecuencia() es así:

Es decir, recibe como parámetros la pareja (octava, nota) y la voz, que salen de la tabla con la melodía, y separa el nibble alto (octava) del nibble bajo (nota), imprime ambos con printf(), obtiene la frecuencia asociada con sonido_obten_frecuencia(), también la imprime, y fija esa frecuencia para la voz en cuestión.

Por su parte, configura_volumen() es parecida:

Es decir, recibe la pareja (duración, volumen), separa el nibble alto (duración) del nibble bajo (volumen), imprime ambos con printf(), y fija el volumen con sonido_fija_volumen(). Aquí no interviene la voz porque el volumen es común para todas.

Si compilamos y ejecutamos el programa el resultado es así, aparte de escuchar una bonita canción:

Los datos que se muestran en cada fila son la voz, la octava, la nota y la frecuencia. Esto para las tres voces (numeradas ahora como 0, 1 y 2), y luego la duración y el volumen.


Código de ejemplo: sonido

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s