top of page

OPL4 - Wave


Introducción


En el post "Programando el OPL4 (Parte FM)" explicamos qué es el Moonsound y cómo se programaba la parte FM del chip. En este artículo hablaremos de la otra parte, la parte wave del chip. Primero hablaremos de los registros que tiene y veremos cómo podemos cargar un sonido creado por nosotros. Después veremos cómo están organizados los instrumentos en el Moonsound del chip YRW801. Finalmente, veremos cómo implementamos estos conocimientos en un programa en C.


Part Wave del OPL4


Comparado con todos los parámetros que hay en la parte FM, la parte wave es mucho más sencilla.


El Moonsound permite tener hasta 24 canales sonando a la vez con sonidos wave, mezclados también con los sonidos FM.


¿Qué es una señal wave?


Una señal wave o de onda es una representación gráfica de una señal sonora o eléctrica que varía en función del tiempo. En el contexto del audio digital, una señal de onda (wave) se utiliza para representar muestras digitales de sonido. Estas muestras son valores numéricos que se capturan a intervalos regulares a partir de una onda sonora analógica y se convierten en datos digitales mediante un proceso de muestreo (sampling).


Hoy en día hay muchos programas que muestrean/digitalizan el sonido. El programa que yo he utilizado es Audacity, que es de código abierto. Con él he generado una señal wav que he cargado al Moonsound y la he reproducido.


¿Cómo grabamos un audio con Audacity?


Los pasos para grabar un audio sencillo para poder cargar al Moonsound son los siguientes::

  1. Cargar el programa y darle al botón de grabar.

    Esto hará que la aplicación comience a muestrear para el micrófono que haya en el ordenador. Para parar solo hay que darle al botón con un cuadrado.

  2. Al terminar de grabar podremos ver la señal temporal de nuestro audio.

    Aquí podemos editar el audio para recortar, añadir efectos, etc.

  3. La señal que se ha grabado es en estéreo, y el Moonsound es mono, solo necesitamos la información de un solo canal. Es por eso que eliminamos uno de los dos de la siguiente manera: primero separamos las pistas haciendo clic en "Split Stereo Track":

    Y luego clicamos en la "x" de uno de los canales para eliminarlo:

  4. Finalizadas las ediciones, debemos exportar nuestra señal. El Moonsound permite sonidos muestreados de 8, 12 y 16 bits con signo. El formato de 12 bits está obsoleto, he probado diferentes programas para pasar los datos del YRW801, que son de 12 bits, y no encontraba ninguno. Finalmente, después de muchas pruebas intentando extraer los datos, cuando ya había encontrado cómo funcionaba, descubrí que en el Furnace, al hacer clic derecho en la carpeta para abrir instrumentos en la pestaña de los samples, permite importar una serie diferente de formatos, entre ellos el de 12 bits.

    Volviendo a Audacity, permite exportar en 8 bits sin signo o 16 con signo. En el ejemplo que he utilizado en este artículo es de 8 bits sin signo, ya que ocupa menos memoria que los de 16, aunque tiene menos calidad.

  5. Audacity graba, entre otros muchos formatos, en wav, que añade una cabecera de 44 bytes, y el resto son las muestras sin transformar, o "raw" en inglés. Para eliminar esta cabecera podemos utilizar algún editor hexadecimal como el Okteta o puedes usar el siguiente comando en Linux, donde "if" es el nombre del archivo de entrada y "of" el de salida:

dd if=audio.wav of=audio_no_header.raw bs=44 skip=1

Si los dejáis, no pasa nada, sonará un poco de distorsión al principio de la reproducción, pero como son muy pocos bytes, no se notará.


Programando el OPL4 en la part wave


A diferencia de la parte FM, que tiene dos bancos casi idénticos de registros para configurar todos los canales, aquí con un solo banco podemos configurar los 24 canales. En el siguiente esquema de la documentación de Yamaha están representados todos los registros de la parte wave:

El primer registro importante es el 0x02, que es el de la configuración. En el caso del Moonsound, los bits 7-1 siempre son los mismos y el bit 0 se activa para escribir los samples de la memoria del MSX hacia el Moonsound. Los bits D4-D2 especifican la arquitectura de la memoria donde se guardan los samples. En el caso del Moonsound, debe tomar el valor 0b100, que es la configuración donde los 384 primeros samples se encuentran en la dirección 0x0 y son leídos del YRW801, que es un chip de Yamaha que contiene samples de 12 bits que siguen el estándar MIDI1. El resto, los 128 samples que quedan, se pueden cargar desde el MSX a partir de la dirección 0x200000. En el dispositivo DalSoRi R2 hay un interruptor que permite configurarlo sin usar el YRW801, de manera que todo el espacio de memoria, los 512 samples, pueden ser configurados por el usuario.


Los registros 0x03-0x05 indican la dirección de memoria del Moonsound donde se escribirá la data guardada en el registro 0x06.


A partir de aquí, todos los registros van en bloques de 24, donde cada registro de cada bloque nos permitirá configurar cada uno de los 24 canales wave que puede hacer sonar el Moonsound al mismo tiempo. Se debe mantener el offset que indica el número de canal para ser coherentes con toda la configuración. Es decir, si quiero configurar el canal 4, tendré que modificar los registros 0x08+4, 0x20+4, 0x30+4, ..., 0xE0+4.


El primer bloque 0x08-0x1F sirve para seleccionar qué sample se quiere tocar. El número máximo de samples es 2^9=512. Cada uno de estos 1024 samples puede tener un tamaño de 2^16=65536 bytes. Según la configuración que hayamos puesto en el registro 0x02, deberemos definir los samples en una posición de memoria u otra. En el caso del Moonsound, los primeros 384 instrumentos son leídos del YRW801 y los otros comienzan en la dirección 0x200000.


También me gustaría resaltar que, a diferencia de la parte FM, la parte Wave tiene 4 bits para determinar la octava (registros 0x38-0x4F). Este cuarto bit es el de signo del complemento a 2 del bloque. El complemento a 2 es una forma de representar los números negativos en binario y tiene la ventaja de que si sumo dos números binarios expresados en complemento a 2, el resultado de la suma es correcto. Por ejemplo, -4 + 3 = -1. En binario complemento a 2 de -4 se representa como 0b1100, los números positivos son como el binario normal, así 3 es 0b0011 y -1 en complemento a 2 es 0b1111. Así, si hacemos la suma en binario obtenemos: 0b1100 + 0b0011 = 0b1111, que es el número esperado. Una información más completa la podéis encontrar en este vídeo.


La octava puede tomar valores negativos, ya que la octava y el F-number determinan a qué frecuencia se reproduce el sonido muestreado. Esta frecuencia sirve para afinar este sonido. En el siguiente apartado hacemos una breve explicación técnica de este funcionamiento.


Para finalizar el tema de los registros, solo comentar que los dos últimos registros 0xF8 y 0xF9 son para indicar el volumen de la salida de los sonidos FM y wave, respectivamente.


¿Cómo cambio el sonido muestreado a otra frecuencia?


En el apartado "¿Qué es una señal wave?"  hemos explicado que es una grabación de un sonido, es como una foto del sonido, pero hecha con muchas, muchas piezas pequeñas llamadas "muestras" (samples). Cada muestra es como un punto en un gráfico que describe cuán fuerte o suave es el sonido en un momento concreto.


Una vez tenemos estas muestras, y las queremos convertir nuevamente en un sonido, utilizamos el OPL4 para reproducirlas. Las muestras han sido tomadas a una velocidad de muestreo, por ejemplo, 44.1 kHz, lo que significa que por cada segundo tenemos 44100 puntos de la amplitud del sonido. Pero, ¿qué pasa si quieres que el sonido suene más grave o más agudo, como si tocaras una nota diferente en un piano?


Aquí entra en juego la "frecuencia de reproducción". Esto es como decirle al chip cuán rápido debe pasar por las muestras:

  • Para hacer el sonido más agudo (una nota más alta): El chip lee las muestras más rápidamente. Es como pasar un video a cámara rápida. Esto se llama sobremuestrear la señal, porque estás reproduciendo las muestras más a menudo por segundo.

  • Para hacer el sonido más grave (una nota más baja): El chip lee las muestras más lentamente, como pasar un video a cámara lenta. Esto se llama submuestrear la señal, porque estás reproduciendo las muestras menos a menudo por segundo.


Este cambio de velocidad no afecta la calidad del sonido si se hace dentro de ciertos límites, pero permite que un mismo conjunto de muestras se pueda utilizar para generar muchas notas diferentes, al igual que puedes tocar muchas notas en un piano con una sola cuerda si cambias la tensión de la cuerda.


Así, el chip ajusta la velocidad a la que lee las muestras para crear la ilusión de un sonido más grave o más agudo, y esto permite escuchar melodías con sonido realista utilizando una sola grabación de referencia. Ahora bien, llega un punto en el que el sonido escalado ya no se parece al original, por lo que es cuestión de volver a grabar el sonido con el nuevo tono para poder trasladarlo a otras frecuencias.


Veamos ahora un caso práctico. Imaginemos que tienes una muestra de una nota específica, por ejemplo, un La de la tercera octava (A3) de un piano, y que esta muestra se ha grabado a una frecuencia de 44100 Hz (esto significa que se han tomado 44100 puntos del sonido cada segundo). Este La tiene una frecuencia fundamental de 440 Hz, es decir, la vibración principal del sonido hace 440 ondas por segundo.


Ahora, queremos generar otras notas a partir de esta muestra. Para lograrlo, solo es necesario reproducir las muestras a una velocidad diferente:

  • Para bajar una octava (ir al La de la segunda octava, A2), reproducimos las muestras a 22050 Hz, es decir, a la mitad de la velocidad original. Esto hace que el sonido sea más grave, porque las ondas sonoras se desplazan más lentamente, y la frecuencia fundamental baja a 220 Hz.

  • Para subir una octava (ir al La de la cuarta octava, A4), reproducimos las muestras al doble de velocidad, es decir, a 88200 Hz. Esto hace que el sonido sea más agudo, porque las ondas sonoras se desplazan más rápidamente, y la frecuencia fundamental sube a 880 Hz.

  • Para generar una nota intermedia (como el Si justo por encima del A3), se debe reproducir las muestras a una velocidad ajustada proporcionalmente. Por ejemplo, el Si tiene una frecuencia fundamental de 493,88 Hz, por lo que deberíamos reproducir las muestras a una velocidad un 12% más rápida (aproximadamente 49388 Hz).


¿Pero cómo puedo saber cuánto debo aumentar o disminuir para lograr un tono diferente? Para esto existen los "cents", que son una unidad de medida musical que representa 1/100 de tono en la escala temperada igual (la escala que se utiliza en la mayoría de los instrumentos musicales modernos como pianos y sintetizadores). Cada semitono está formado por 100 cents, por lo tanto, una octava, que son 12 semitonos, son 1200 cents.


¿Y toda esta teoría cómo afecta al OPL4? Tal como hemos dicho, el tono reproducido del sample depende de la octava y del F-Number según esta fórmula:

F(c) = 1200 * (octava-1) + 1200 * log_2((1024+F-Number)/1024)

donde F(c) son los cents que queremos alcanzar. Así, si mi sample está en el tono C-3 y quiero aumentarlo a D-3, que son dos semitonos (F(c) = 200), debería configurar los registros de octava a 0 (ya que si no, nos pasaríamos) y el F-Number como:

F-Number = 1024 * 2^(F(c)/1200) - 1024 = 125.401

aproximando al entero más cercano usaríamos un F-Number = 125.


¿Cómo cargamos el tono del MSX al OPL4?

Los pasos para cargar nuestro sonido muestreado al OPL4 consisten en:

  1. Poner el Moonsound en estado de recibir los datos. Debemos poner el registro 0x02, el bit 0 en Memory Access Mode, que en el caso del Moonsound es poner el valor 0x11.

  2. Escribir en qué dirección comenzarán a dejarse los datos. En el Moonsound, la dirección inicial es 0x200000, pero aquí solo pueden haber como máximo 127 instrumentos wave. Por eso, en los registros 0x03-0x05 comenzamos con un offset de 127*12 bytes.

  3. Ir guardando los bytes e iterar el proceso. Debemos guardar los datos en el registro 0x06 y esperar a que el OPL4 haya leído el byte. No hay ninguna señal de "ready", solo se indica en el manual que hay que esperar 28 ciclos de master clock antes de volver a escribir.Una vez escrito el byte, la memoria se autoincrementa una posición y solo tenemos que volver a escribir el valor.

  4. Guardar la cabecera del wave. Una vez hemos subido todos los datos, debemos escribir la cabecera del Wave que indica la dirección inicial de las muestras, el formato, la longitud, el punto de repetición y parámetros para formatear el sonido.Cada cabecera tiene una longitud de 12 bytes. Los primeros 3 bytes indican el formato y la dirección inicial, los siguientes dos bytes son el punto de la canción donde comenzará a sonar el loop, y finalmente los últimos dos son la longitud del sample en complemento a 2 (CA2).¿Por qué en CA2? Me lo he estado preguntando y llegué a la conclusión de que en CA2, solo basta con ir sumando de uno en uno y cuando llegue a cero, ya habrás recorrido todos los bytes. En cambio, si solo pusieras el tamaño del sample, tendría que restar 1, lo que requiere más recursos.Los otros 5 bytes (0x07-0x0B) son los parámetros de configuración del wave: LFO, VIB, AR, D1R, DL, D2R, Rate correction, RR y AM. Son los mismos parámetros que, una vez cargado el wave, puedes configurar con los registros 0x80-0xF7.


El format midi 1 del YRW801


Hasta ahora hemos estudiado cómo cargar samples al OPL4, pero el Moonsound solo permite cargar 128 instrumentos del usuario, ¿qué pasa con los 384 primeros?


El Moonsound los lee del chip YRW801, que como se explica en call MSX 3 (pág. 15), es un chip que cumple con el formato General MIDI 1 (GM1), el cual se puede encontrar distribuido gracias al trabajo realizado en el proyecto Alsa audio, donde se encuentra este código yrw801.c.


El estándar GM1 define 128 instrumentos, estos instrumentos, tal como se ve en el yrw801.c, están definidos con la estructura:

typedef struct opl4_sound {
    unsigned int tone;
    int pitch_offset;
    unsigned char key_scaling;
    char panpot;
    unsigned char vibrato;
    unsigned char tone_attenuate;
    unsigned char volume_factor;
    unsigned char reg_lfo_vibrato;
    unsigned char reg_attack_decay1;
    unsigned char reg_level_decay2;
    unsigned char reg_release_correction;
    unsigned char reg_tremolo;
} opl4_sound_t;

typedef struct opl4_region {
    unsigned char key_min, key_max;
    opl4_sound_t sound;
} opl4_region_t;

Así, por ejemplo, el piano acústico (instrumento GM1 número 1) está representado por el siguiente array:

static const opl4_region_t regions_00[] = { /* Acoustic Grand Piano */
    {0x14, 0x27, {0x12c,7474,100,0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
    ...
};

Algoritmo para reproducir una nota MIDI con el Moonsound


El procedimiento para reproducir un instrumento MIDI con el Moonsound es:


  1. Seleccionar el array regions_XX según el instrumento MIDI (del 0 al 127, donde XX es el valor hexadecimal del instrumento).

  2. Elegir la región adecuada (key_min ≤ nota ≤ key_max). Según la nota que queramos tocar en notación MIDI, que va desde el do de la octava 1, que corresponde al 0, hasta el sol de la octava novena, que corresponde al 127.

  3. Calcular el pitch, la octava y el F-number:

    • Pitch: El desplazamiento tonal en función de la nota MIDI se calcula así:pitch = ((nota - 60) << 7) * key_scaling / 100 + (60 << 7) + opl4_sound_t.pitch_offset;

    • F-number: Se calcula a partir de las centésimas de semitón (valores de 0 a 599) con una tabla de correspondencia (ms_wave_pitch_map). Esta tabla convierte el valor de centésimas al registro correspondiente al F-number para el chip.

    • Octava: Se determina dividiendo el pitch entre 0x600 (1536 en decimal), restando 8 para ajustar la escala. Por ejemplo:octave = pitch / 0x600 - 8;

  4. Asignar los valores a los registros correspondientes: tono (tone), panorámico (pan), volumen, etc.

  5. Activar la nota con el bit 7 del registro 0x68 + ch.


Programa que carga samples al Moonsound


Una vez que ya hemos visto toda la teoría, vamos a ver un ejemplo en el que aplicamos todo lo que hemos visto hasta ahora. En este programa cargaremos nuestro sample y lo reproduciremos con el Moonsound. Al presionar diferentes teclas, podemos variar los parámetros que conforman la forma de onda del wave y así poder elegir cuáles nos gustaría que tuvieran por defecto cuando escribimos la cabecera al OPL4. Al presionar F y G reproducimos un tono FM. Este programa lo podéis encontrar en el gitlab de MoltSXalats.


Como siempre, comenzamos con los includes de las funciones de las bibliotecas que utilizaremos. Definimos la constante de espera, para saber que el OPL4 está listo, la dirección de memoria donde podemos empezar a escribir los samples y los puertos de comunicación del MSX con los registros del OPL4. Finalmente, en la línea 25 está la estructura que permite la conversión del pitch a octava y F-Number.

Y ya tenemos las mismas funciones que utilizamos en la parte FM para escribir en los registros del Moonsound: ms_wave_write, ms_fm1_write y ms_fm2_write. A partir de la línea 244 tenemos las funciones de acceso al disquete que hemos utilizado varias veces en estos artículos: FT_SetName y FT_errorHandler.

Luego, en la línea 290, declaramos la función configure_FM_channel, que se encargará de configurar el canal FM tal como se explicó en el artículo del Moonsound FM.

Luego tenemos la función ms_wave_wait_after_memory_write, que es una serie de nops (no operations) que se utilizan como una función de espera para poder escribir otro dato en el registro de memoria del OPL4, el 0x06.

La siguiente función que tenemos es la load_sample, que, utilizando las funciones anteriores, se encarga de leer el archivo con los samples del disquete al OPL4.


Y pasamos ya a describir la función main en la línea 378. Esta función main es diferente a todas las demás, ya que hasta ahora no habíamos usado parámetros en la línea de ejecución del DOS. Ahora, cuando ejecutemos nuestro programa desde DOS, podremos pasarle parámetros que se utilizarán para configurar el sonido wave del OPL4. De esta manera, si queremos hacer pruebas con diferentes valores, no será necesario recompilar el programa, solo será necesario cambiar el valor del parámetro. Estos parámetros permiten modificar ar, d1r, dl, d2r, RR, rate correction y am. Si no se completan todos, hay valores por defecto que están establecidos en las líneas 379 a 386. Las líneas siguientes hasta la 410 son de asignación de estos parámetros de la línea de comandos al programa.

Una vez recogidos todos los parámetros de la línea de comandos, pasamos a configurar el Moonsound desde la línea 410 a la 423. Después indicamos dónde comenzaremos a guardar las muestras del sample, teniendo en cuenta que el Moonsound permite personalizar 128 waves y que cada cabecera de configuración de estos waves tiene 12 bytes, por lo que ya tenemos la información para calcular la primera dirección disponible tal como se hace en la línea 424.


Luego, llamamos a la función que lee el archivo del disquete y guarda los bytes en el Moonsound, load_sample, que devuelve el tamaño del archivo en bytes. Y comenzamos a escribir la cabecera:

  • Dirección inicial (líneas 439-442).

  • El offset del loop, la parte que se va a repetir (líneas 444-448).

  • El número de muestras que contiene el sample. En el ejemplo, he hecho un audio de 16 bits, por lo que el número de muestras es el número de bytes del archivo dividido entre dos. Este número lo debemos hacer en complemento a 2 (CA2) para guardarlo en la cabecera. El complemento a 2 se obtiene negando todos los bits y sumando 1 (líneas 450-453).

  • Los parámetros que configuran la forma del sonido del wave (líneas 456-464).

Una vez hemos dejado de escribir datos al OPL4, se debe sacar al Moonsound del modo de escritura de datos y ponerlo en modo de reproducción de sonido, escribiendo en el registro 0x02 el valor 0x30 (línea 467).


Después, en la línea 470, configuramos un canal FM tal como lo hicimos en el artículo FM. Si solo queremos reproducir sonido PCM/Wave, esto no es necesario. Lo único que lo hago es para probar que ambas partes suenen al mismo tiempo.


Una vez tenemos el Moonsound en modo sonido, debemos configurar la parte wave para que, en uno de sus 24 canales, reproduzca el sample que hemos cargado. Las instrucciones del OPL4 indican que los registros deben escribirse en un orden específico para que suene correctamente. El orden es primero la octava con la parte alta del F-number (0x38-0x4F), el F-number con el MSB del Wave number (0x20-0x37) y finalmente lo que queda del wave number (0x08-0x1F).


Nosotros utilizaremos el primer canal, por lo que usaremos los registros 0x38, 0x20 y 0x08. En la línea 476 definimos el wave_number, que es 384 (0x180), pero que no utilizamos, ya que lo dejamos fijo. La octava que utilizamos es 0xF y el F-Number superior es 0b100, por lo tanto, en el registro 0x38 guardamos el valor 0xF4. El wave number es 0x180, por lo que tiene el MSB a 1, lo que se agrega al F-number 0b0010001, obteniendo el valor 0x23 en el registro 0x20. Finalmente, la parte baja del wave number es 0x80, que guardamos en el registro 0x08.


En la línea 482, elegimos el Total Level del wave y en la línea 485 activamos el canal, como cuando en la parte FM presionábamos una tecla, es decir, como dar al play de un CD para que empiece a sonar.


En la línea 488 definimos la variable salir, que utilizaremos para salir del bucle infinito, y la variable leerTeclado, que utilizaremos para evitar rebotes de lectura del teclado (leer más de una vez la tecla, cuando debería contar solo una vez).


Las siguientes líneas verifican si una tecla está presionada, y si es así, realizan la acción pertinente. Las teclas son:

  • ESC: Para terminar el bucle.

  • 1: Apaga el sonido del wave para dejar de escucharse, ya que lo hemos iniciado antes y, como tiene loop, siempre estaría sonando.

  • 2: Vuelve a activar la reproducción de los samples.

  • Q: Aumenta el valor del parámetro AR.

  • W: Aumenta el valor del parámetro D1R.

  • E: Aumenta el valor del parámetro DL.

  • R: Aumenta el valor del parámetro D2R.

  • T: Aumenta el valor del parámetro Rate Correction.

  • Y: Aumenta el valor del parámetro RR.

  • F: Tocamos una nota FM.

  • G: Dejamos de tocar la nota FM.

  • K: Tocamos una nota de un sonido muestreado del YRW801.

  • L: Apagamos la nota del sonido muestreado tocada con la tecla K.

Finalmente, el código para controlar los rebotes: vamos revisando que todas las líneas de la matriz del teclado estén desactivadas; si es así, podemos volver a leer el teclado.


En la línea 635 vaciamos el búfer por si ha quedado algún carácter presionado y salimos de la aplicación con Exit(0).


Dado que este programa utiliza parámetros recibidos del MSX-DOS, debe compilarse de manera diferente, cambiando el punto de inicialización, el crt0, eligiendo en este caso crt0_msxdos_advanced.rel. El comando queda como:

sdcc -V --code-loc 0x180 --data-loc 0x0 --disable-warning 196 -mz80 --no-std-crt0 --opt-code-size fusion-DOS.lib -L ./fusion-c/lib/ -I ./fusion-c/include/ ./fusion-c/lib/crt0_msxdos_advanced.rel -o learning-fusion-c-programs/learning-fusion-c-programs/loadwav.ihx learning-fusion-c-programs/loadwav_arg.c


Conclusiones


En este artículo hemos hecho un repaso de la teoría de muestreo para poder entender qué estamos haciendo cuando cargamos una muestra en el Moonsound. Hemos presentado los registros y la operativa básica del OPL4 para conseguir reproducir un sonido muestreado. También hemos visto de manera básica qué es el estándar Midi1 para poder reproducir los sonidos que se encuentran en el YRW801.


Lamentablemente, el webMSX no tiene implementada la parte PCM/Wave en la emulación del OPL4. Por eso solo dejamos aquí el archivo DSK preparado para ser utilizado con el openMSX, no olviden agregar la extensión Moonsound a su ordenador emulado favorito. En mi caso sería:

~/MSX/openMSX/derived/openmsx -machine Panasonic_FS-A1ST -ext moonsound -diska ~/MSX/Fusion-C\ 2.0\ SDK\ pre-beta\ 2b/WorkingFolder/out/dska/ 

Sube el volumen, siéntate y escucha la muestra de ejemplo que te hemos preparado, que sonará al ejecutar loadwav.com en el DSK que encontrarás aquí .



コメント


Envíanos un mensaje y dinos lo que piensas

¡Gracias por tu mensaje!

© 2035 Creado por Tren de ideas con Wix.com

bottom of page