I think my MIDI player works pretty well, all be it only for MIDI type 0 files. But that’s ok, because I can convert virtually any MIDI file to type 0. The only problem I have is when I play a MIDI file with a lot of information (delta time 0 and or running status), I get audible slowdowns.
To clarify:
My MIDI player operates in an interrupt routine. Each interrupt equals one MIDI tick. The time between interrupt is in the order of 5000us (derived from microseconds per quarter note and ticks per quarter note). Delta time is the time in MIDI ticks between MIDI events send to the MIDI device. Running status is a way to reduce the amount of bytes of the MIDI data. It reduces the data by omitting repeated MIDI commands by only sending the data.
Quote:
The MIDI spec allows for a MIDI message to be sent without its Status byte (ie, just its data bytes are sent) as long as the previous, transmitted message had the same Status. This is referred to as running status. Running status is simply a clever scheme to maximize the efficiency of MIDI transmission (by removing extraneous Status bytes). The basic philosophy of running status is that a device must always remember the last Status byte that it received (except for RealTime), and if it doesn't receive a Status byte when expected (on subsequent messages), it should assume that it's dealing with a running status situation.
http://midi.teragonaudio.com/tech/midispec/run.htm
I think that the MIDI file player probably stalls (audibly) in the following two situations:
1.
When there are multiple (a dozen) MIDI events with delta time 0. i.e. all those events have to be handled in one interrupt sequence.
2.
When there are multiple equal MIDI events in a row. They may or may not have a delta time of 0. i.e. running status.
In case 2, I’m not sure if those events with delta time > 0 will slow down playing, but probably only all events with delta time 0.
What I suspect is that each MIDI event with delta time 0 will be send in the next interrupt, i.e. one MIDI tick later. However, as far as I programmed the interrupt routine, that shouldn’t happen. If the events take more time than one or two interrupt sequences, it shouldn’t be noticeable. But it might be noticeable when each event would be send one interrupt later.
Here is the pseudo code of my interrupt routine:
Code:
tc = tick counter
binvlq = delta time in binary
GetVarLength = get delta time
DecodeEvent = decode event and send it to the midi device
Initialisation:
tick counter = 0
GetVarLength
BeginIrq:
if delta time == tick counter
- DecodeEvent
GetVarLength
if delta time == 0 goto -
if delta time <> 0 then tick counter = 0, goto EndIrq
if delta time <> tick counter then tick counter = tick counter + 1, goto EndIrq
EndIrq:
I do not think that the CPU is too slow for playing those MIDI files, because the slowdowns are equal whether I run at 1MHz or 8MHz.
I’m at a loss here and I really don’t know what to try next.
Interrupt code:
Code:
interrupt
pha
phx
phy
lda isr
and #%00001000 ;check if interrupt is caused by C/T
beq endirq ;no? exit irq
lda rop12 ;clear counter ready interrupt status bit
lda tc ;is binvlq = tc?
cmp binvlq
bne IncTc ;no, increment tc and end irq
lda tc+1
cmp binvlq+1
bne IncTc
lda tc+2
cmp binvlq+2
bne IncTc
lda tc+3
cmp binvlq+3
bne IncTc
ExEv jsr DecodeEvent ;yes, decode a track event (midi/sysex/meta)
jsr GetVarLength ;get variable length quantity
lda binvlq ;is binvlq zero?
bne ResetTickCounter ;no, reset tc and end irq
lda binvlq+1
bne ResetTickCounter
lda binvlq+2
bne ResetTickCounter
lda binvlq+3
bne ResetTickCounter
bra ExEv
IncTc
inc tc ;increment tick counter tc
bne +
inc tc+1
+ bne +
inc tc+2
+ bne +
inc tc+3
+
bra EndIrq
ResetTickCounter
stz tc ;Reset Tick Counter tc and end irq
stz tc+1
stz tc+2
stz tc+3
EndIrq
ply
plx
pla
rti
Decode Event code:
Code:
DecodeEvent
lda (mfp) ;load byte from midi data (msb first)
cmp #$80 ;if less than 128,
bcc + ;then running status
bra ++
+ lda runstat ;load previous midi event
jsr midiout ;send it out
and #%11110000
cmp #$c0
beq de0
cmp #$d0
beq de0
lda (mfp) ;load data 1
jsr midiout
jsr IncreaseMidiFilePointer
de0 lda (mfp) ;load data 2
jsr midiout
jsr IncreaseMidiFilePointer
rts
++
and #$f0
lsr
lsr
lsr ; yes only three, because each jump address is 2 bytes
tax
lda (mfp)
jmp (decodetable,x) ;jump to separate commands