
Introducció
Al post Programming OPL4 (FM Part) vam explicar el que era el Moonsound i com es programava la part FM del xip. En aquest article parlarem de l'altra part, la part wave del xip. Primer parlarem dels registres que té i veurem com podem carregar un so creat per nosaltres. Després veurem com estan organitzats els instruments al Moonsound del xip YRW801. Finalment veurem com implementem aquests coneixements en un programa en C.
Part Wave de l'OPL4
Comparat amb tots els paràmetres que hi ha a la part FM, la part wave és molt més senzilla.
El Moonsound permet tenir fins a 24 canals sonant a la vegada amb sons wave, barrejats també amb els sons FM.
Què és un senyal wave?
Un senyal wave o d'ona és una representació gràfica d'un senyal sonor o elèctric que varia en funció del temps. En el context de l'àudio digital, un senyal d'ona (wave) s'utilitza per representar mostres digitals de so. Aquestes mostres són valors numèrics que es capturen a intervals regulars a partir d'una ona sonora analògica i es converteixen en dades digitals mitjançant un procés de mostreig (sampling).
Avui en dia hi ha molts programes que mostregen/digitalitzen el so. El programa que jo he utilitzant és l'audacity que és de codi obert. Amb ell he generat un senyal wav que he carregat al Moonsound i l'he reproduït.
Com gravem un audio amb l'audacity?
Els passos per gravar un audio senzill per poder carregar al Moonsound són els següents:
Carregar el programa i donar-li al botó de gravar.
Això farà que l'aplicació comenci a mostrejar per al micròfon que hi hagi a l'ordinador. Per parar només cal donar-li al botó amb un quadrat.
Acabat de gravar podrem veure el senyal temporal del nostre àudio.
Aquí podem editar l'àudio per retallar, afegir efectes, etc.
El senyal que s'ha gravat és en estèreo, i el Moonsound és mono, només necessitem la informació d'un sol canal. És per això que eliminem un dels dos de la següent manera, primer separem les pistes clicant "Split Stereo Track":
I després cliquem la x d'un dels canals per eliminar-lo:
Finalitzades les edicions hem d'exportar el nostre senyal. El Moonsound permet sons samplejats de 8, 12 i 16 bits amb signe. El format de 12 bits està obsolet, he provat diferent software per passar les dades del YRW801 que són de 12 bits i no en trobava cap. Finalment després de moltes proves intentant extreure les dades, quan ja havia trobat com funcionava, descobreixo que en el furnace donant al botó dret a la carpeta d'obrir instruments a la pestanya dels samples, permet importar amb tota una sèrie diferent de formats, entre ells el 12 bits. Tornant a l'audacity permet exportar en 8 bits sense signe o 16 amb signe. A l'exemple que he usat en aquest article és de 8 bits sense signe, ja que ocupa menys memòria que els de 16, tot i tenir menys qualitat.
L'audacity grava d'entre molts altres formats, en wav, que afegeix una capçalera de 44 bits i la resta són les mostres sense transformar, crus, en anglès raw. Per eliminar aquesta capçalera podem utilitzar algun editor en hexadecimal com l'Okteta o pots utilitzar la següent comanda en linux a on if és el nom del fitxer d'entrada i of el de sortida:
dd if=audio.wav of=audio_no_header.raw bs=44 skip=1
Si els deixeu no passa res, sonarà una mica de distorsió al principi de la reproducció, però com que són molts pocs bytes no es notaran.
Programant l'OPL4 la part wave
A diferència de la part FM que té dos bancs gairebé idèntic de registres per configurar tots els canals, aquí amb un sol banc podem configurar els 24 canals. En el següent esquema de la documentació de Yamaha hi ha representats tots els registres de la part wave:

El primer registre important és el 0x02 que és el de la configuració. En el cas del Moonsound, els bits 7-1 són sempre els mateixos i el bit 0 s'activa per escriure els samples de la memòria del MSX cap al Moonsound. Els bits D4-D2 especifiquen l'arquitectura de la memòria a on es guarden els samples. En el cas del Moonsound ha de prendre el valor 0b100 que és la configuració a on els 384 primers samples es troben a l'adreça 0x0 i estan llegits del YRW801, que és un xip de Yamaha que conté samples de 12 bits que segueixen l'estàndard MIDI1. La resta, els 128 samples que queden, es poden carregar des del MSX a partir de l'adreça 0x200000. En el dispositiu DalSoRi R2 hi ha un interruptor que permet configurar-lo sense que utilitzi el YRW801 i així tot l'espai de memòria, els 512 samples, es poden ser configurats per l'usuari.
Els registres 0x03-0x05 indiquen l'adreça de memòria del Moonsound a on s'escriurà la dada guardada al registre 0x06.
A partir d'aquí tots els registres van en blocs de 24, a on cada registre de cada bloc ens permetrà configurar cadascun dels 24 canals wave que pot fer sonar el Moonsound a la vegada. S'ha de mantenir l'offset que indica el número de canal per ser coherents amb tota la configuració. És a dir, si vull configurar el canal 4, hauré de modificar els registres 0x08+4, 0x20+4, 0x30+4, ..., 0xE0+4.
El primer bloc 0x08-0x1F serveix per seleccionar quin sample es vol tocar. El màxim número de samples és 2^9=512. Cadascun d'aquests 1024 samples pot tenir un tamany de 2^16=65536 bytes. Segons la configuració que hàgim posat al registre 0x02 haurem de definir els samples a una posició de memòria o una altra. En el cas del Moonsound, els primers 384 instruments són llegits del YRW801 i els altres comencenc a l'adreça 0x200000.
També m'agradaria resaltar que a diferència de la part FM, la part Wave té 4 bits per determinar l'octava (registres 0x38-0x4F), aquest quart bit és el de signe del complement a 2 del bloc. El complement a 2 és una forma de representar els números negatius en binari i que té l'aventatge que si sumo dos binaris expressats en component a dos, el resultat de la suma és correcte, per exemple -4+3 = -1. En binari complement a 2 de -4 es representa com 0b1100, els números positius són com el binari normal, així 3 és 0b0011 i el -1 en complement a 2 és 0b1111, així si fem la suma en binari obtenim: 0b1100 + 0b0011 = 0b1111 que és el número esperat. Una informació més completa la podeu trobar en aquest vídeo.
L'octava pot prendre valors negatius, ja que l'octava i el F-number determinen a quina freqüència es reprodueix el so mostrejat. Aquesta freqüència serveix per acabar d'afinar aquest so. En el següent apartat fem una breu explicació tècnica d'aquest funcionament.
Per acabar el tema dels registres només comentar que els dos últims registres 0xF8 i 0xF9 són per indicar el volum de la sortia dels sons FM i wave respectivament.
Com canvio el so mostrejat a una altra freqüència?
A l'apartat "Què és un senyal wave?" hem explicat que és una gravació d'un so, és com una foto del so, però feta amb moltes, moltes peces petites anomenades "mostres" (samples). Cada mostra és com un punt en un gràfic que descriu com de fort o suau és el so en un moment concret.
Un cop tenim aquestes mostres, i les volem tornar a convertir en un so, utilitzem l'OPL4 per reproduir-les, les mostres han estat agafades a una velocitat de mostreig, per exemple 44.1Khz que vol dir que per cada segon tenim 44100 punts de l'amplitud del so. Però, què passa si vols que el so soni més greu o més agut, com si toquessis una nota diferent en un piano?
Aquí entra en joc la "freqüència de reproducció". Això és com dir-li al xip com de ràpid ha de passar per les mostres:
Per fer el so més agut (una nota més alta): El xip llegeix les mostres més ràpidament. És com passar un vídeo a càmera ràpida. Això s’anomena sobremostrejar el senyal, perquè estàs reproduint les mostres més sovint per segon.
Per fer el so més greu (una nota més baixa): El xip llegeix les mostres més lentament, com passar un vídeo a càmera lenta. Això s’anomena submostrejar el senyal, perquè estàs reproduint les mostres menys sovint per segon.
Aquest canvi de velocitat no afecta la qualitat del so si es fa dins de certs límits, però permet que un mateix conjunt de mostres es pugui fer servir per generar moltes notes diferents, igual que pots tocar moltes notes en un piano amb una sola corda si canvies la tensió de la corda.
Així, el xip ajusta la velocitat a la que llegeix les mostres per crear la il·lusió d’un so més greu o més agut, i això fa que puguis escoltar melodies amb so realista utilitzant una sola gravació de referència. Ara bé, arriba un punt que el so escalat ja no s'assembla a l'original, després és qüestió de tornar a gravar el so amb el nou to per així poder-lo tornar a transportar a altres freqüències.
Veiem ara un cas pràctic, Imaginem que tens una mostra d'una nota específica, per exemple, un La de la tercera octava (A3) d'un piano, i que aquesta mostra s'ha enregistrat a una freqüència de 44100 Hz (això vol dir que s'han pres 44100 punts del so cada segon). Aquest La té una freqüència fonamental de 440 Hz, és a dir, la vibració principal del so fa 440 ones per segon.
Ara, volem generar altres notes a partir d’aquesta mostra. Per aconseguir-ho, només cal reproduir les mostres a una velocitat diferent:
Per baixar una octava (anar al La de la segona octava, A2), reproduïm les mostres a 22050 Hz, és a dir, a la meitat de la velocitat original. Això fa que el so sigui més greu, perquè les ones sonores es desplacen més lentament, i la freqüència fonamental baixa a 220 Hz.
Per pujar una octava (anar al La de la quarta octava, A4), reproduïm les mostres al doble de velocitat, és a dir, a 88200 Hz. Això fa que el so sigui més agut, perquè les ones sonores es desplacen més ràpidament, i la freqüència fonamental puja a 880 Hz.
Per generar una nota intermèdia (com el Si just per sobre del A3), cal reproduir les mostres a una velocitat ajustada proporcionalment. Per exemple, el Si té una freqüència fonamental de 493,88 Hz, així que hauríem de reproduir les mostres a una velocitat un 12% més ràpida (aproximadament 49388 Hz).
Però com puc saber la quantitat que he d'augmentar o disminuir per aconseguir un to diferent? Per això existeixen el cent que és una unitat de mesura musical que representa 1/100 de to a l'escala temperada igual (l'escala que s'utilitza en la majoria d'instruments musicals moderns com pianos i sintetitzadors). Cada semitó està format per 100 cents, per tant una octava que són 12 semitons són 1200 cents.
I tota aquesta teoria com afecta a l'OPL4? Tal i com hem dit, el to reproduït del sample depèn de l'octava i del F-Number segons aquesta fórmula:
F(c) = 1200 * (octava-1) + 1200 * log_2((1024+F-Number)/1024)
a on F(c) són els cents que volem aconseguir. Així si el meu sample està a to C-3 i vull augmentar-lo a D-3 que són dos semitons (F(c)=200), hauria de configurar els registres d'octava a 0 (ja que si no ens passem) i el F-Number com:
F-Number = 1024 * 2^(F(c)/1200) - 1024 = 125.401
aproximant a l'enter més proper utilitzaríem un F-Number = 125.
Com carreguem el to del MSX a l'OPL4?
Les passes per pujar el nostre so samplejat a l'OPL4 consisteixen en:
Posar el Moonsound en estat de rebre les dades. Hem de posar el registre 0x02 el bit 0 en Memory Acces Mode, que en el cas del Moonsound és posar el valor 0x11
Escriure a quina adreça començarà a deixar les dades. En el Moonsound l'adreça inicial és el 0x200000 però aquí hi ha d'haver com a màxim 127 instruments wave. Per això als registres 0x03-0x05 comencem amb un offset de 127*12 bytes.
Anar guardant els bytes i iterar el procés. Hem de guardar les dades al registre 0x06 i esperar que l'OPL4 hagi llegit el byte. No hi ha cap senyal de ready, només s'indica en el manual que s'ha d'esperar 28 master clock cicles per tornar a escriure. Un cop escrit el byute, la memòria autoincrementa un posició i només hem de tornar a escriure el valor.
Guardar la capçalera del wave Un cop hem pujat totes les dades, hem d'escriure la capçalera del Wave que indica l'adreça inicial de les mostres, el format, la longitud, el punt de repetició i paràmetres per formatar el so. Cada capçalera té una longitud de 12 bytes. Els primers 3 bytes indiquen el format i l'adreça inicial, els següents dos bytes és el punt de la cançó a on començarà a sonar el loop i finalment els últims dos és la longitud del sample en complement a 2 (CA2). Per què en CA2? M'ho he estat preguntant i he arribat a la conlcusió que en CA2, només cal que vagis sumant d'un en un i quan arribi a zero ja has recorregut tots els bytes, mentre que si només posessis el tamany del sample, hauria de fer-ho restant 1, cosa que requereix més recursos. Els altres 5 bytes (0x07-0x0B) són els paràmetres de configuració del wave: LFO, VIB, AR, D1R, DL, D2R, Rate correction, RR i AM. Són els mateixos paràmetres que un cop carregat el wave pots configurar amb els registres 0x80-0xF7
El format midi 1 del YRW801
Fins ara hem estudiat com carregar samples a l'OPL4, però el Moonsound només permet carregar 128 instruments de l'usuari, què passa amb els 384 primers? El Moonsound els llegeix del xip YRW801, que tal i com s'explica a call MSX 3 (pàg 15), és un xip que compleix el format General MIDI 1 (GM1), que es pot trobar com està distribuït gràcies al treball fet en el projecte Alsa audio, a on hi ha aquest codi yrw801.c.
L'estàndar GM1 defineix 128 instruments, aquests instruments, tal i com es veu en el yrw801.c estan definits amb l'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;
Aixi per exemple, el piano acústic (instrument GM1 número 1) està representat pel següent 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}},
...
};
Algorisme per reproduir una nota MIDI amb el Moonsound
El procediment per reproduir un instrument MIDI amb el Moonsound és:
Seleccionar l’array regions_XX segons l’instrument MIDI (del 0 al 127, on XX és el valor hexadecimal de l’instrument).
Triar la regió adequada (key_min ≤ nota ≤ key_max). Segons la nota que volguem tocar en notació midi, que va del do de l'octava 1, que correspon al 0, al sol de l'octava novena, que correspon al 127.
Calcular el pitch, l’octava i el F-number:
Pitch: El desplaçament tonal en funció de la nota MIDI es calcula així: pitch = ((nota - 60) << 7) * key_scaling / 100 + (60<<7) + opl4_sound_t.pitch_offset;
F-number: Es calcula a partir de les centèsimes de semitò (valors de 0 a 599) amb una taula de correspondència (ms_wave_pitch_map). Aquesta taula converteix el valor de centèsimes al registre corresponent al F-number per al xip.
Octava: Es determina dividint el pitch entre 0x600 (1536 en decimal), restant 8 per ajustar l’escala. Per exemple: octave = pitch / 0x600 - 8;
Assignar els valors als registres corresponents: to (tone), panoràmic (pan), volum, etc.
Activar la nota amb el bit 7 del registre 0x68+ch.
Programa que carrega samples al Moonsound
Un cop ja hem vist tota la teoria, anem a veure un exemple a on apliquem tot el que hem vist fins ara. En aquest programa carregarem el nostre sample i el reproduirem amb el Moonsound. Apretant diferents tecles podem variar els paràmetres que conformen la forma d'ona del wave i així poder escollir quins ens agradaria que tinguessin per defecte quan escrivim la capçalera a l'OPL4. Apretant F i G reproduïm un to FM. Aquest programa el podeu trobar al gitlab de MoltSXalats.
Com sempre, comencem amb els includes de les funcions de les llibreries que utilitzarem. Definim la constant d'espera, per saber que l'OPL4 està apunt, l'adreça de mempòria a on podem començar a escriure els samples i els ports de comunicació del MSX amb els registres de l'OPL4. Finalment a la línia 25 hi ha l'estructura que permet la conversió dels pitch a octava i F-Number.

I ja tenim les mateixes funcions que vam utilitzar a la part FM per escriure als registres del Moonsound: ms_wave_write, ms_fm1_write i ms_fm2_write. A partir de la línia 244 tenim les funcions d'accés al disquet que hem utilitzat diverses vegades en aquests articles: FT_SetName i FT_errorHandler.

Després a la línia 290, declarem la funció confgure_FM_channel que s'encarregarà de configurar el canal FM tal i com està explicat a l'article del Moonsound FM.

Després tenim la funció ms_wave_wait_after_memory_write que és una sèrie de nops que s'utilitzen com a funció d'espera per poder escriure un altre dada en el registre de memòria de l'OPL4, el 0x06.

La següent funció que tenim és la load_sample que utilitzant les anteriors funcions, s'encarrega de llegir el fitxer amb els samples del disquet a l'OPL4.
I passem ja a descriure la funció main a la línia 378. Aquesta funció main és diferent a totes les altres ja que fins ara no havíem usat paràmetres a la línia d'execució del DOS. Ara quan cridem el nostre programa des de DOS, podrem passar-li paràmetres que utilitzem per configurar el so wave de l'OPL4. Així si volem fer proves amb diferents valors, no caldrà que recopilem el programa, només caldrà canviar el valor del paràmetre. Aquests permeten modificar l'ar, d1r, dl, d2r, RR, rate correction i am. Si no els omplim tots, té uns valors per defecte que estan posats de la línia 379 a la 386. Les línies posteriors fins al 410 són d'assignació d'aquests paràmetres de la línia de comandes al programa.

Un cop recollits tots els paràmetres de la línia de comandes, passem a configurar el Moonsound des de la línia 410 a la 423. Després indiquem a on començarem a guardar les mostres del sample, tenint en compte que el Moonsound permet personalitzar 128 waves i que cada capçalera de configuració d'aquests waves és de 12 bytes ja tenim la informació per calcular la primera adreça disponible tal i com es fa a la línia 424.
Després ja cridem la funció de llegir el fitxer del disquet i guardar els bytes al Moonsound, load_sample, que retorna el tamany del fitxer en bytes. I comencem a escriure la capçalera:
Adreça inicial (línies 439-442).
L'offset del loop, la part que es vagir repetint (línies 444-448).
I el número de mostres que conté el sample. A l'exemple he fet un audio de 16 bits, amb el que el número de mostres és el número de bytes del fitxer dividit entre dos. Aquest número hem de fer el CA2 per guardar-lo a la capçalera. El complement a 2 és negar tots els bits i sumar-li 1 (línies 450-453).
Els paràmetres que configuren la forma del so del wave (línies 456-464).

Un cop hem deixat d'escriure dades a l'OPL4, s'ha de treure del mode escriure dades en que es troba el Moonsound al mode reproduir so escrivint al registre 0x02 el valor 0x30 (línia 467).
Després a la línia 470 configurem un canal FM tal com hem fet a l'article FM. Si només volem reproduir so PCM / Wave, això no cal. L'únic que jo ho he fet ja que vull provar de fer sonar les dues parts a la vegada.
Un cop ja tenim el Moonsound en mode so, hem de configurar la part wave perquè en un dels seus 24 canals reprodueix el sample que li hem pujat. Les instruccions de l'OPL4 diuen que els registres s'han d'escriure en un cert ordre perquè soni correctament. L'ordre ha de ser primer l'octava amb la part alta del F-number (0x38-0x4F), el F-number amb el MSB del Wave number (0x20-0x37) i finalment el que queda del wave number (0x08-0x1F).
Nosaltres utilitzarem el primer canal i per això usarem els registres 0x38, 0x20 i 0x08. A la línia 476 definim el wave_number, 384 (0x180) però que no utilizem, ja que ho hem deixat fixat. L'octava que utilitzem és 0xF i l'F-Number superior és el 0b100, per tant al registre 0x38 guardem el valor 0xF4. El wave number és 0x180, per tant té el MSB a 1 que afegirem al que queda del F-Number 0b0010001 obtenint el valor 0x23 al registre 0x20. Finalment la part baixa del wave number, el 0x80 al registre 0x08.
A la línia 482 escollim el Total Level del wave i a la 485 activem el canal, com quan a la part FM apretàvem la tecla, com donar-li al play del CD perquè comenci a sonar.
A la línia 488 definim la variable sortir que l'utilitzarem per sortir del bucle infinit i la variable llegirTeclat que la farem servir per evitar rebots de lectura del teclat (llegir més d'una vegada el valor de la tecla, quan voldríem que només comptés com una sola vegada).
Les línies següents van mirant al teclat si hi ha la tecla apretada i si és així fan una acció pertinent. Les tecles són:
ESC: Per acabar el bucle
1: Apaguem el so del wave perquè deixi d'escoltar-se, ja que l'hem iniciat abans i com que té loop sempre estarà sonant.
2: Tornem a activar la reproducció dels samples.
Q: Puja el valor del paràmetre AR.

W: Puja el valor del paràmetre D1R.
E: Puja el valor del paràmetre DL.
R: Puja el valor del paràmetre D2R.
T: Puja el valor del paràmetre Rate Correction.
Y: Puja el valor del paràmetre RR.
F: Toquem una nota FM.

G: Parem de tocar la nota FM.
K: Toquem una nota d'un so samplejat del YRW801.
L: Apaquem la nota del so samplejat tocat amb la tecla K.

Finalment el codi per controlar els rebots: Anem mirant que totes les línies de la matriu del teclat estan desactivades, si és així, podem tornar a llegir el teclat.
A la línia 635 buidem el buffer per si ha quedat algun caràcter apretat i sortim de l'aplicació amb Exit(0).

Com que aquest programa utilitza paràmetres rebuts del MSX-DOS, s'ha de compilar de forma diferent, s'ha de canviar el punt d'inicialització, el crt0, escollint en aquest cas crt0_msxdos_advanced.rel. La comanda queda doncs com:
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
Conclusions
En aquest article hem fet un repàs de la teoria de mostreig per tal de poder entendre què estem fent quan carreguem un sample al Moonsound. Hem presentat els registres i l'operativa bàsica de l'OPL4 per aconseguir reproduir un so mostrejat. També hem vist de forma bàsica el que és l'estàndard Midi1 per tal de poder reproduir els sons que es troben al YRW801.
Malauradament el webMSX no té la part PCM / Wave implementada en l'emulació de l'OPL4. Per això només us deixem aquí el dsk preparat per ser utiltizat amb l'openMSX, no us deixeu de posar l'extensió Moonsound al vostre ordinador emulat preferit. En el meu cas seria:
~/MSX/openMSX/derived/openmsx -machine Panasonic_FS-A1ST -ext moonsound -diska ~/MSX/Fusion-C\ 2.0\ SDK\ pre-beta\ 2b/WorkingFolder/out/dska/
Puja el volum, asseu-te i escolta el sample d'exemple que t'hem preparat i que sonarà en executar loadwav.com en el dsk que trobareu aquí.
Comments