Le support de la lecture de fichiers midi est maintenant intégrée au moteur de jeu.
Il est constitué des éléments suivants :
- Un convertisseur de fichier midi
- Un outil de "split" des données intégré au builder
- Un driver asm pour la lecture "in game"
L'objectif étant d'avoir un lecteur performant, voici les choix :
- lecture par IRQ 50Hz
- adaptation du format de fichier midi pour des données plus compactes sans pénaliser la vitesse d'exécution
Convertisseur
-------------
Pour ne pas pénaliser les perfs à la lecture et pour que les jeux qui utilisent les drivers FM ou Midi aient des perfs similaires, il fallait rester sur une IRQ à 50Hz.
Il a donc fallu écrire un convertisseur qui redéfini les indicateurs temporels de chaque commande pour les arrondirs au 1/50s.
Le convertisseur gère les changements de tempo en cours de morceau.
Un fichier midi contient un indicateur de temps devant chaque bloc de commande, y compris si des blocs doivent être lus de manière consécutives (sans temps d'attente).
Pour gagner en place, j'ai retiré l'indicateur quand celui ci vaut 0 (lecture consécutive), la valeur 0 devient l'indicateur de fin de page/données.
La distinction se fait alors sur la plage de valeur :
00-7F : nombre de frame a attendre
80-FF : commande midi
Pour mon fichier de test on passe de 54 052 octets à 46 687 octets (morceau de 4m 20s) juste avec cette modification sur le "timestamp" des messages.
Je n'ai pas touché aux données midi pour ne pas avoir a décompresser les données au runtime (on cherche la performance)
Outil de split
--------------
Divise les données par page de 16Ko, mais ne coupe pas les données en plein milieu, on s'arrête avant le début d'une nouvelle commande.
On ajoute alors une balise de fin (0).
ça permet de simplifier le controle de fin de page/fin des données lors de la lecture, car on ne controle la fin qu'a chaque début de commande et non à chaque lecture d'un octet.
Driver asm
----------
On gère tous les messages midi, y compris les sysex (pour changer à la volée les params des instruments)
Voici le code :
Code:
* ---------------------------------------------------------------------------
* SMIDI 6809 - Small Midi playback system for 6809
* ---------------------------------------------------------------------------
* by Bentoc October 2021
* with inputs from Fool-DupleX
* ---------------------------------------------------------------------------
opt c,ct
MIDI.CTRL EQU $E7F2
MIDI.STAT EQU MIDI.CTRL
MIDI.RX EQU $E7F3
MIDI.TX EQU MIDI.RX
MIDI.RXFULL EQU $01
MIDI.TXEMPTY EQU $02
MIDI.RXIRQ EQU $80
MusicLoop fcb 0 ; 0 : do not loop music
MusicIndex fdb 0 ; first index of all data chuncks (page, address)
MusicIndexPos fdb 0 ; position in index
MusicPage fcb 0 ; current memory page of music data
MusicDataPos fdb 0 ; current playing position in Music Data
MusicStatus fcb 0 ; 0 : stop playing, 1-255 : play music
WaitFrame fcb 0 ; number of frames to wait before next play
******************************************************************************
* ResetMidi - Reset Midi Controller
*
******************************************************************************
ResetMidi
pshs a
lda #$03
sta MIDI.CTRL ; reset midi controller
lda #$15
sta MIDI.CTRL ; no r/w interrupt
puls a,pc
******************************************************************************
* PlayMusic - Load a new music and init midi interface
*
* receives in X the address of the song
* destroys X
******************************************************************************
PlayMusicLoop
pshs a
lda #$ff
sta MusicLoop ; set the loop flag on
bra @a
PlayMusic
pshs a
@a stx MusicIndex ; store index for loop restart
stx MusicIndexPos ; init data chunck index position
lda ,x ; get memory page that contains track data
sta MusicPage
sta MusicStatus ; no data on page 0, page is used to init status to a non zero value
lda #1
sta WaitFrame ; wait frame is only zero where reading commands, should be init to 1
ldx 1,x ; get ptr to track data
stx MusicDataPos ; init data location
puls a,pc
******************************************************************************
* MusicFrame - processes a music frame (VInt - DP $E7)
*
* format:
* -------
* xnn
* |____ x00 : (1 byte) next page/adr or end of data
* |____ x01-x7f : (1 byte) wait xnn frames
* |____ x80-xff xnn (xnn) : (2 or 3 bytes) command, data1, (data2)
* |____ xf0 xnn ... xf7 : (x bytes) SysEx
*
* destroys A,B,X
******************************************************************************
MusicFrame
lda MusicStatus ; check if a music track is to play
bne @a
rts
@a lda WaitFrame
deca
sta WaitFrame
beq ReadCommand ; no more frame to wait, play the next commands
rts
IsMusicLoop
tst MusicLoop
beq StopMusic
ldx MusicIndex
bra LoopRestart
StopMusic
lda #0
sta MusicStatus
sta MusicLoop
rts
BankSwitch
ldx MusicIndexPos ; read byte was $00, this is end of data chunk or music track
leax 5,x ; move to next index
lda ,x ; get memory page that contains track data
beq IsMusicLoop ; this is an end of track
LoopRestart
sta MusicPage ; store the new page
stx MusicIndexPos ; this is an end of data chunck, save new index
ldx 1,x ; get ptr to track data
bra @a
;
ReadCommand
ldx MusicDataPos ; load current position in music track
lda MusicPage
@a _SetCartPageA
CommandLoop
lda ,x+ ; read data byte in new page
bmi DoCommand
beq BankSwitch
DoWait
sta WaitFrame
stx MusicDataPos
rts
DoCommand
cmpa #$C0
blo TXRdyLoop0 ; skip 2 bytes (command 8n to Bn, 2 data byte)
cmpa #$E0
blo TXRdyLoop1 ; skip 1 byte (command Cn and Dn, 1 data byte)
cmpa #$f0
beq DoSysEx
; bne: skip 2 bytes (command En, 2 data byte)
TXRdyLoop0
ldb <MIDI.STAT ; wait interface to be ready
andb #MIDI.TXEMPTY
beq TXRdyLoop0
sta <MIDI.TX ; send byte to the midi interface
lda ,x+
TXRdyLoop1
ldb <MIDI.STAT ; wait interface to be ready
andb #MIDI.TXEMPTY
beq TXRdyLoop1
sta <MIDI.TX ; send byte to the midi interface
lda ,x+
TXRdyLoop2
ldb <MIDI.STAT ; wait interface to be ready
andb #MIDI.TXEMPTY
beq TXRdyLoop2
sta <MIDI.TX ; send byte to the midi interface
bra CommandLoop
DoSysEx
ldb <MIDI.STAT ; wait interface to be ready
andb #MIDI.TXEMPTY
beq DoSysEx
sta <MIDI.TX ; send byte to the midi interface
lda ,x+
cmpa #$f7
bne DoSysEx
sta <MIDI.TX ; send byte to the midi interface
bra CommandLoop
Question pour Fool-DupleX : on est obligé de faire du pooling avant chaque envoi d'un octet sur l'interface, ou on peut se baser sur un nb de cycles a attendre ?
Une démo est prête ...
Reste plus qu'a tester une fois l'interface disponible ;-)