top of page

Programando el OPL4 (Parte FM)


¿Qué es el Moonsound?

Moonsound fue el primer cartucho creado para el MSX que utilizaba el chip OPL4 (YMF278B). Fue desarrollado por Henrik Gilvad (también creador de Graphics 9000) y distribuido por Sunrise en 1995.


El Moonsound en mi historia

En aquella época, podías conseguir estas expansiones de hardware a través de Hnostar. La Moonsound costaba 35.000 pesetas y la Gfx9000 40.000 pesetas. Como no tengo muy buen oído, decidí comprar la Gfx9000 y experimentar con imágenes, que es lo que siempre me ha fascinado. Trasteé con ella y busqué programas de la escena MSX que la usaran. Desafortunadamente, los programas que salieron usaban Moonsound en lugar de Gfx9000. Después de eso, mi contacto con el MSX se desvaneció: dejé de comprar fanzines (después de que Hnostar y SDMesxes cerraran) y solo seguía las noticias en msx.org. Hasta que decidí recuperar el ordenador en casa de mis padres a finales de 2022. En este período de reenganche, intenté conseguir un OPL4 en cualquiera de las versiones que existían (Moonsound, Wozblaster, Shockwave, DalSolRi, Monster sound Fm Blaster y MSX-Blaster) sin éxito. Hasta que Cristiano (el retrohacker) me contactó y me dijo que estaba haciendo una tirada de Wozblaster. ¡Finalmente! Después de tanto tiempo persiguiéndolo, lo conseguí.


Mis habilidades musicales siguen siendo las mismas que hace veinte años, prácticamente nulas. Sin embargo, gracias a roboplay, uso Moonsound como una jukebox. Tengo un archivo m3u con todas las canciones que he recopilado y hago que cambie de canción cada 3 minutos. He creado 10 copias del archivo con las líneas en un orden diferente (gracias al comando sort -R en Linux) y así parece que la reproducción es aleatoria.


Mientras investigaba para este artículo, descubrí que roboplay también puede reproducir VGM y descargué un montón de archivos VGMrips para los chips OPL1 (YM3526), OPL2 (YM3812), OPL3 (YMF262) y, por supuesto, el OPL4 (YMF278). Si los descomprimes (roboplay no reproduce VGZ, solo VGM, pero solo necesitas descomprimirlos con gunzip y obtendrás el VGM) pueden ser reproducidos directamente por roboplay. Pero también he descubierto vgm-conv, que me permite convertir archivos VGM de OPL a OPL2.


Y ahora, a parte de usarlo como jukebox, vamos a estudiarlo y aprender más sobre sus capacidades.



Especificaciones del Moonsound

El chip OPL4 incorpora el mismo componente de síntesis FM que se encuentra en el OPL3, pero además incluye una sección dedicada a la síntesis por ondas, que funciona de manera similar al ADPCM que se encuentra en MSX Audio (el chip Y8950, una variante del OPL1 con un canal ADPCM añadido).


A diferencia del OPLL, que presentaba instrumentos preconfigurados, la serie OPL1-4 proporciona la flexibilidad de personalizar los parámetros de los instrumentos para cada canal individual. Vamos a esbozar brevemente las capacidades y la progresión de los chips OPL:

  • OPL1: Ofrece 9 canales FM o, alternativamente, una combinación de 6 canales FM y 5 canales de percusión. Utilizando el chip YM3526, el Y8950 (MSX-Audio) extiende su funcionalidad al incorporar un canal ADPCM. El MSX-Music (OPLL) representa una versión simplificada del OPL1.

  • OPL2: Mantiene las características principales del OPL1 mientras introduce el soporte para 4 formas de onda distintas.

  • OPL3: Expande las capacidades del OPL2 al introducir 4 formas de onda adicionales y habilitar operaciones de 4 operadores para los canales FM. Esta mejora permite un máximo de 18 canales. Antes del OPL3, los chips estaban limitados a operaciones de 2 operadores.

  • OPL4: Conserva las capacidades de síntesis FM del OPL3, pero integra una sección basada en muestras que emplea tecnología PCM. Este enfoque refleja el ADPCM encontrado en MSX Audio.


Parte FM

Para comprender completamente los registros de la sección FM, es esencial entender el concepto de "slot" tal como se define en la documentación de Yamaha. Un slot representa un circuito diseñado para generar una onda sinusoidal. Estos slots corresponden a los operandos mencionados anteriormente, que pueden agruparse en pares (2 operadores) o en grupos de cuatro (4 operadores). El OPL4 posee 36 slots. Cuando se configuran en el emparejamiento mínimo de 2 operadores, esto da lugar a los 18 canales FM anunciados. Sin embargo, no todos los slots son compatibles con configuraciones de 4 operadores; solo 24 slots soportan este modo, dejando los 12 restantes para agruparse en pares.


Para configurar estos slots, se emplean dos bancos de registros: banco 0 y banco 1. Aunque estos bancos son casi idénticos, difieren en sus primeros 8 registros, que sirven para fines de configuración. Además, el banco 0 incluye el registro 0xBD, dedicado al control de ritmo. El banco 0 se usa para programar los primeros 18 slots, mientras que el banco 1 maneja los 18 siguientes. Una representación esquemática de los registros es la siguiente:

Tabla de registros FM

Además de la explicación proporcionada en la documentación oficial de Yamaha, también hay una muy buena explicación en esta guía de programación del OPL3.


Hay dos tipos de registros: los que afectan a los slots, que están organizados en bloques de 16 registros desde 0x20 hasta 0x95. Estos registros se usan para definir la forma de onda del sonido. Y los que afectan a los canales, que están organizados en bloques de 9 registros desde 0xA0 hasta 0xB8. Estos registros se usan para definir la nota, la octava y otros efectos del sonido. Esto se aplica a cada banco de registros. Pero los registros 0x16 son 22 slots, y solo hay 18 slots por banco. ¿Cómo es posible? Bueno, porque los registros de slots 0x?6, 0x?7, 0x?E y 0x?F no se usan; no modifican ningún slot. Aquí hay un gráfico que servirá como una referencia rápida para la dirección correspondiente de cada slot. El indicador "set" toma el valor 0 para los registros del primer banco y 1 para los registros del segundo banco.


Table slots/offsets

Pero, ¿cómo se agrupan los slots para formar canales? Como mencioné antes, hay diferentes combinaciones; no es arbitrario. No puedo combinar los slots 3, 4, 7 y 11, por ejemplo. Solo se pueden agrupar de una manera específica. En esta documentación, hay una tabla para cada combinación, pero he resumido todo en una sola:

Table channels/slots/operator

Aquí tenemos los 18 canales que permite el OPL4 con dos operaciones. Cada columna representa las operaciones de 2 slots. Por ejemplo, el canal 3 consta de los slots 6 y 9. Estos slots se pueden combinar con operaciones de 4 operadores, que son aquellos con el mismo color y ordenados por operandos del slot más pequeño al más grande. Por ejemplo, el canal de 4 operadores número 0 está compuesto por los slots 0, 3, 6 y 9, por lo que el canal 3 desaparece al modificar los bloques de 9 registros, como el número F y la octava. Si también quiero configurar el canal 2 como de 4 operadores, el canal 5 desaparece, y los slots que forman el canal 2 son 2, 5, 8 y 11. Si configuramos para percusión FM, perdemos los canales 6, 7 y 8, que se convierten entonces en el Bombo (12 y 15), el Hi-Hat (13), el Tom-Tom (14), la Caja (16) y el Platillo (17).


En este último esquema, utilicé la numeración decimal para indicar el slot. Si queremos modificar el slot 7, necesitamos considerar su desplazamiento para modificar un valor en los registros de configuración del slot. Por ejemplo, para el Nivel de Escala de Clave, deberíamos modificar el registro 0x49 en lugar de 0x47 porque 0x?6 y 0x?7 no existen.


¿Vale la pena perder 3 canales FM para percusión? En el caso de Moonsound, muchos compositores usaron toda la sección FM y luego usaron la sección de ondas para percusión, ya que esto les permitía usar su sonido sampleado, ya que se notaba que el sonido proporcionado por Yamaha era muy pobre.


Para definir un instrumento, hay muchos parámetros y combinaciones que afectan el timbre del sonido generado (más parecido a un xilófono, piano, campana, etc.). ¿Cómo puedo saber cómo afectará la variación de uno de estos parámetros al sonido generado?


Hoy en día, existe Furnace, un tracker de código abierto inspirado en Deflemask (que tiene una versión antigua que es gratuita). A diferencia de Deflemask, Furnace tiene muchos más chips para los que se puede crear música. En la documentación del furnace sobre cómo generar instrumentos, proporcionan este videotutorial explicativo sobre cómo hacerlo en Deflemask (la interfaz es muy similar a la de Furnace). Hay más tutoriales en video para Deflemask que para Furnace, pero debido a su similitud, también pueden ayudarte a manejar Furnace. Si además de esta explicación práctica, estás interesado en la parte teórica de la generación FM, puedes consultar este documento explicativo.


¿Y qué tenemos para el MSX nativo? Cuando se lanzó el Moonsound, Moonblaster se adaptó para tener un tracker que funcionara con el nuevo chip. Se lanzaron dos versiones: Moonblaster FM y Moonblaster Wave, que servían para operar la parte FM y la parte Wave de Moonsound, respectivamente. MBFM no configuraba los 3 canales de percusión, pero tenía 6 canales Wave para usar como percusión. He intentado averiguar la parte de generación de instrumentos de MBFM pero no he podido encontrarla.


Intentemos usar la generación de instrumentos en Furnace para crear nuevos instrumentos (o cargar los ya disponibles en la aplicación) y usarlos con el MSX. Los instrumentos de Furnace se guardan en archivos con la extensión .fui, mientras que los instrumentos de Deflemask tienen la extensión .dmp. Nos centraremos en el primero ya que es de código abierto y también permite cargar instrumentos dmp.


Hay dos tipos de formatos de instrumentos en Furnace: el formato antiguo, cuyas especificaciones están aquí, y el nuevo formato, cuyas especificaciones puedes encontrar aquí. Los instrumentos que he cargado dentro de la instalación del Furnace siguen el formato antiguo, pero solo necesitamos cargarlos en Furnace y guardarlos para que se conviertan al nuevo formato. Una de las ventajas del nuevo formato es que ocupa menos espacio.



Cargar archivos .fui

¿Qué aplicación usaremos para estudiar el Moonsound? Pensé que podríamos crear una aplicación que cargue el instrumento diseñado con Furnace y toque un par de notas. Además, también mostrará los parámetros extraídos del archivo. Así que, primero veamos la estructura de este archivo.


Formato .fui

La estructura del archivo .fui en su versión moderna se puede encontrar en el GitHub de Furnace. La documentación está en formato de texto y no es muy atractiva visualmente. Por lo tanto, he hecho este resumen:

Table FUI file format

Los primeros 6 bytes son el encabezado, que comienza con la palabra FINS para identificar el tipo de archivo. Luego, puede haber el nombre del instrumento, que está formateado con el código NA, seguido de 2 bytes que indican la longitud de la cadena del nombre y están codificados en little endian. Eso significa que si leemos los valores 0x0700, la longitud correcta es 7 bytes en lugar de 1792. El último byte de la cadena es 0x0. A continuación, podemos encontrar el comando FM y la longitud del bloque de codificación. Todos los bytes siguientes contienen diferentes parámetros para configurar los chips FM, pero no todos son utilizados por el OPL4. He marcado en azul los que sí usamos.


El primer byte de configuración, justo después de la longitud del bloque FM, indica si la configuración es para 2 operadores o 4 operadores. Si es para 2 operadores, el primer slot del canal viene primero, seguido del siguiente slot. Si es para 4 operadores, el primero es para el slot 0 del canal inferior, luego para el slot 0 del otro canal que se fusiona para formar las operaciones de 4 operadores, y luego el slot 1 de los canales respectivos.


Junto al esquema, he colocado la dirección donde encontramos esta configuración en el archivo que usamos como ejemplo, que puedes encontrar en el GitLab de MoltSXalats. Verás que, a partir de la posición 0x10, se repiten para cada uno de los operadores, y el archivo no tiene el comando NA que indica el nombre.


Basándonos en esta estructura, escribamos el código para encontrar la parte FM del instrumento guardado en formato .fui y configurar el Moonsound:

Empecemos con las inclusiones y luego tenemos la definición de MS_WAIT como una macro. Es un fragmento de código que lee un registro de Moonsound hasta que cambia de valor. A diferencia del OPLL, que no indicaba cuándo podíamos escribir de nuevo y, por lo tanto, teníamos dos funciones de escritura, una para el Z80 y otra para el R800, aquí no es necesario porque la espera es independiente del procesador, dependiendo solo del OPL4.


Para escribir en los registros, usaremos la función __sfr __at de sdcc, que realiza un out a la dirección que especificamos, en nuestro caso 0xC4, 0xC5, 0xC6 y 0xC7. Los dos primeros son para el primer banco de registros FM, y los últimos dos son para el segundo banco de registros. En el artículo del OPLL, usamos una función que contenía código ensamblador, que realizaba las instrucciones OUT y las instrucciones de espera. Aquí, dado que no necesitamos medir la espera, usar sfr es suficiente.


En las líneas 18-23, comprobamos si un Moonsound está insertado leyendo si hay información en el registro de Moonsound. Si devuelve ceros, significa que no está conectado.


Las líneas 26-31 definen la función ms_fm1_write, que escribe en el registro reg del primer bloque el valor data que pasamos. Primero, indicamos al Moonsound qué registro queremos modificar, esperamos a que indique que está listo para el siguiente comando usando la macro MS_WAIT, y luego indicamos los datos que debe almacenar. La función ms_fm2_write hace lo mismo pero para el segundo bloque de registros.


A partir de aquí, comenzamos con las funciones que hemos usado en diferentes artículos para leer datos del disco: FT_SetName, FT_errorHandler y FT_openFile. Hemos modificado ligeramente la función FT_errorHandler para escribir mensajes de error que indican que no hay un FINS en el archivo que se está leyendo y que no se encontró Moonsound.

Finalmente, tenemos la función WAIT, que espera el número de ciclos que indicamos y se usará después de cada nota para dejarla sonar durante un cierto período de tiempo. También podríamos usar la función WaitForKey() de la biblioteca Fusion-C para esperar a que el usuario presione una tecla cuando lo desee.


Después de la función WAIT, definimos el buffer para almacenar los bytes que leemos. Leeremos un máximo de 5 bytes a la vez, e instruí a sdcc para que los almacene en la dirección 0x8000. Esto facilita la depuración, pero en una versión final no sería necesario y tendríamos el bloque completo de variables juntas.


Y comenzamos la función principal. En la línea 111, detectamos si hay un Moonsound; si no lo hay, lanzamos un error y salimos. En la línea 115, abrimos el archivo y, en la línea 119, inicializamos el Moonsound especificando que utilizaremos los registros OPL3 (FM) y OPL4 (wave).


A continuación, definimos el canal que usaremos; en este caso, hemos elegido el canal 2, que, junto con el canal 5, forma un canal de 4 operadores (columnas rojas en la tabla de canales/slots/operadores). También podríamos haber elegido los canales 0 y 3, 1 y 4, o sus equivalentes en el banco 1. Luego definimos un array que contendrá la distribución de los slots que usaremos.


Luego comenzamos a leer el archivo. Primero, comprobamos si el archivo es del tipo FINS (línea 126). Si no lo es, salimos con un mensaje de error. Luego, leemos los siguientes 4 bytes, que no son útiles para esta configuración. Después, leemos los bytes y detectamos si el comando es NA, que contiene el nombre del instrumento, o si es FM. Si es NA, leemos los bytes del nombre y no hacemos nada con ellos. Si es FM, salimos del bucle de lectura de bytes y comenzamos la sección de código para configurar el FM.


Leemos los 2 bytes que forman el tamaño del bloque FM y los almacenamos en la variable blockLength para verificar que hemos leído todos los bytes que controlaremos con numBytesLegitsBlockFM.


A continuación está el byte de configuración que indica si se trata de un instrumento de 2-op o 4-op (dos operandos o cuatro). Si es 2-op, utilizaremos el canal 1 con los slots 1 y 4. Si es 4-op, utilizaremos los canales 2 y 5. Note que en la línea 176, el desplazamiento para el canal 8 es 0x0A como se indica en la tabla de slots/desplazamientos, y para el canal 11 es 0x0D.


El siguiente byte contiene información sobre los parámetros alg y fb, que almacenamos en diferentes variables para su posterior procesamiento. La variable alg contiene la información sobre el algoritmo, que define el tipo de conexión para los 4 operadores para generar el sonido FM, y fb es un parámetro de configuración para el canal, no para el slot como los otros.


Dependiendo del número de operandos, tendremos 4 opciones posibles o solo la conexión simple. Aunque fb solo afecta al primer canal, observando lo que hacía Furnace, descubrí que configuraba este parámetro en ambos canales.


Otra característica del byte 0xC? es que indica a través de qué altavoz debe reproducirse: izquierdo, derecho o ambos. Esta es una propiedad que había pasado por alto, y la herramienta vgm2txt que usé para depurar tampoco la transcribió a texto. Después de muchas pruebas diferentes y de comprobar los valores, descubrí que no activaba los bits 4 y 5 del registro 0xC?, correspondientes al canal. Por eso, siempre configuro un 3 en los bits superiores de este registro.


Después de configurar el bit en 0xC0 para el algoritmo, leemos dos bytes más que no se usan en el OPL4.

Y finalmente, la configuración de los slots en los registros 0x2?, 0x4?, y 0x6?. Como he determinado el canal que se está utilizando, toda la configuración es para el bloque 0; si quisiera configurar el bloque 1, usaría el comando ms_fm2_write. Estos registros están mezclados en el archivo fui, por lo que leo los 5 bytes donde se encuentran y extraigo cada parámetro (líneas 234-247) para luego configurar los registros correspondientes (líneas 249-251). Los bytes SL y RR están en el mismo formato que el registro y, por lo tanto, se configuran directamente. Leemos otro byte que no usaremos, y finalmente, configuramos la forma de onda. Las últimas líneas del bucle muestran los valores leídos en la pantalla.


En la línea 271, defino que no usaremos tambores, y solo necesitamos definir la nota que queremos tocar. Al igual que el OPLL, tiene el F-number para indicar la nota y el bloque para indicar la octava. Usé un F-number de 582, que corresponde a la nota La, pero mientras depuraba, vi que Furnace también usaba un valor diferente para la nota La. Esto también ocurrió con Moonblaster, que tenía valores de F-number diferentes a los de la documentación.


El valor del F-number es un entero y se divide en dos registros diferentes, como se muestra en las líneas 277 y 278. Además, en la línea 278, también configuramos la octava y activamos el canal. Con la nota ahora sonando, esperamos unos segundos y luego procedemos a tocar la otra nota.


Para tocar la siguiente nota, primero debes apagar la nota que está sonando y configurar la siguiente, tal como se hacía con el OPLL. Esto se realiza en las líneas 285 y 287. Esperamos a que la nota suene en la nueva octava, la apagamos y luego terminamos el script.


Conclusiones

En este artículo, exploramos la parte FM del Moonsound (OPL4), presentamos los diferentes conceptos de slots y canales utilizados en la documentación, y cargamos un instrumento en formato Furnace (fui) para tocar dos notas.


Como ejercicio práctico con el código, puedes terminar de controlar los bytes leídos con la variable numBytesLegitsBlockFM y verificar que coincidan con el blockLength.


Avelino Herrera tiene un par de artículos en las revistas call MSX 3  y call MSX 4  donde habla sobre el Moonsound. El primero trata sobre la parte Wave, que aún no hemos cubierto y veremos más adelante, y el segundo trata sobre la parte FM. Este segundo artículo también explica los registros, y en lugar de cargar un archivo fui para configurarlos, usa archivos sbi. Otro ejercicio que puedes hacer es convertir el código C del artículo en su sitio web al formato de la biblioteca Fusion-C. Ya lo he hecho, ya que investigaba por qué no sonaba. El resultado se puede encontrar en el GitLab de MoltSXalats.


Para aquellos que no pueden esperar al artículo de MoltSXalats sobre la parte Wave, además del artículo de Avelino, también está el Moai-Tech número 5 en papel (no confundir con el nuevo formato digital), que es más similar a lo que cubriremos aquí.


También me gustaría explicar que para la depuración utilicé la herramienta vgm2txt de vgmtools. Furnace permite exportar a VGM, que es un formato que rastrea los registros que se están modificando en una canción, y puedes ver todos los pasos dados para hacerla sonar. Además, openMSX también tiene un comando para grabar en VGM. Solo necesitas abrir la consola con F10 y escribir vgm_rec start Moonsound para comenzar a grabar, y luego vgm_rec stop para finalizar. La consola indicará dónde se guardó el archivo. ¡No olvides lanzar openMSX con la extensión Moonsound (-ext moonsound)! El formato VGM es binario, por lo que utilicé vgm2txt, que desafortunadamente no transcribió los bits estéreo del registro 0xC?.


Si deseas probar otro instrumento de la biblioteca Furnace, solo cambia el nombre del archivo en la línea 115 (y copia este archivo al directorio que usas como dsk en openMSX). Alternativamente, si no quieres recompilar el programa, puedes copiar el archivo al directorio, renombrándolo a op4bell.fui.


¡Así que disfruta de tu MSX!


Clica aquí para descargar la image .dsk de un ejemplo funcionando.




Comments


bottom of page