Next I started to work out how to update AUDV0, for the 3 voice music, once per scanline while the ARM code was running. batari suggested just running short routines in ARM that would take less than a scanline each. I thought that would be overly complicated, so thought I might be able to pre-fetch the AUDV0 values, stash them in ZP (Zero Page) RAM, then have the 6507 run the following routine in RAM while the ARM code ran:
MAC ZP_ROUTINE SUBROUTINE ; X holds how many Cache values-1 .ZP ldy #$FF ; 2 67 sty CALLFUNCTION ; 4 69 .ZPloop lda CachedAMPLITUDE,x ; 4 73 sta WSYNC ; 3 76/0 sta AUDV0 ; 3 3 dex ; 2 5 bpl .ZPloop ; 3 8 - branch taken .checkARMstate ; 2 7 - branch not taken ldx PosObject ; 4 11 cpx #$38 ; 2 13 - check that we see a SEC command beq .BCcountdown ; 3 16 - branch taken ldx #$80 ; 2 18 - error color since ARM still running stx BackgroundColor ; 3 21 ldx #120 ; 2 23 - show error color for 2 seconds stx BCcount ; 3 26 .BCcountdown ldx BCcount ; 3 29 beq .ZPexit ; 2 31 dex ; 2 33 stx BCcount ; 3 36 bne .ZPexit ; 2 38 ldx #0 ; 2 40 - black stx BackgroundColor ; 3 43 .ZPexit rts ; 6 49 - 49 = worse case path ENDM
The routine will run for a predefined number of scanlines, updating AUDV0 for the 3-voice music using cached values. Once the values are used up, the routine then checks a ROM location to see if it has the expected value of $38 (the SEC command located at the start of the PosObject function) which would tell us if the ARM code has finished running or not. This works because while ARM code is executing $EA, a NOP instruction, is put on the bus so the 6507 doesn't do anything. If the SEC command wasn't found, the background color is changed so I could tell if the ARM code ran too long (remember, at this time Stella didn't run ARM code, so the routines only worked on real hardware).
The routine is defined as a Macro because it needs to be compiled in the RAM block (to allocate space):
CACHE_COUNT = 6 SEG.U VARS ORG $80 BackgroundColor ds 1 ; background color to show - normally black ; but will have a color to denote which ARM routine ; exceeded expected run time BCcount ds 1 ; error color countdown CachedAMPLITUDE: ; must be before ZProutineRAM ds CACHE_COUNT ZProutineRAM: ZP_ROUTINE
as well as in ROM
ZProutineROM: ZP_ROUTINE ZPsize = *-ZProutineROM
so it can be copied into RAM during system initialization.
; Init system, copies ROM version of ZP_ROUTINE to RAM InitSystem: CLEAN_START ldy #ZPsize InitZP lda ZProutineROM,y sta ZProutineRAM,y dey bpl InitZP
Whenever ARM code is called, a set number of AMPLITUDE values are read and stored into CachedAmplitude, then ZProutineRAM is called:
CallARM: lda #<AMPLITUDE sta WSYNC sta AUDV0 lda #<AMPLITUDE sta CachedAMPLITUDE+5 lda #<AMPLITUDE sta CachedAMPLITUDE+4 lda #<AMPLITUDE sta CachedAMPLITUDE+3 lda #<AMPLITUDE sta CachedAMPLITUDE+2 lda #<AMPLITUDE sta CachedAMPLITUDE+1 lda #<AMPLITUDE sta CachedAMPLITUDE+0 ldx #[CACHE_COUNT-1] jmp ZProutineRAM
The routine worked, but the sound was awful. Turns out you can't prefetch AMPLITUDE values because the DPC+ driver uses an internal timer when calculating the values. batari thought about changing DPC+ to allow prefetched values, but he ultimately decided:
On 10/11/2010 at 9:40 PM, batari said:
It might actually be easier to interrupt with a timer set for approximately one scanline, and have the ARM feed STA AUDV0, then NOP and return.
Which is how we ended up with DPC+ support for interrupt driven music when running ARM code.
Blog entry covers October 6 - 12, 2010