Blargg SPC upload

This is an article about uploading SPC music rips for playback on real SNES hardware. It was written by Blargg and contributed to the NESDev Wiki, migrated here now that a SNES Wiki exists.

Introduction
This covers the important points of uploading an SPC music file into the SNES SPC-700 sound module. See spc_loader.c for tested code in C. Thanks to Anti Resonance for much of the original algorithm. I've mostly streamlined and tweaked it.

The main tasks to play an SPC file are
 * Restore DSP registers
 * Restore 64K RAM
 * Restore CPU registers and other final values

DSP registers
We can't just restore all the registers directly, because some have effects that depend on the others, or cause RAM to be overwritten. Those must be restored at the end. We make a copy of the DSP registers and modify as follows:

With these modifications, we upload them to the DSP via a short SPC program:

.org $0002 mov x,#dsp_regs    ; pointer to table mov y,#0 next: mov a,(x)+          ; copy to DSP mov $F2,y mov $F3,a inc y       bpl next            ; stop when y > 127 jmp !$FFC0         ; rerun bootrom dsp_regs: .res 128           ; modified values to load

64K RAM
Again, we can't restore every byte of RAM directly, because some of the I/O locations have effects that we can't have just yet. We make a copy of the 64K RAM and modify it as follows:

Some final patching is necessary before sending the RAM.

Final restoration
With those values patched, we still need to insert some code to restore the final registers. We need to find some free space in RAM. Many have long stretches of $FF bytes, a good candidate. If the echo buffer is enabled, it could be used, though this will introduce a slight click. Some other SPC files have runs of the repeating pattern of 32 $FF bytes followed by 32 $00 bytes, aligned to a 32-byte boundary.

Once we've found space, we can patch in the following code. The notation spc.ram [n] refers to the RAM in the unpatched SPC file, spc.dsp [reg] to the unpatched DSP value, and spc. to the processor register value in the SPC file.

; Restore first two bytes of RAM mov $00,#spc.ram [0] mov $01,#spc.ram [1] ; Restore CPU registers mov x,#spc.sp - 1  ; See below for why the -1 mov sp,X mov y,#spc.y   mov x,#spc.x    mov a,#spc.a    ; Restore SMP/DSP registers mov $F1,#spc.ram [$F1] AND $CF   ; Control

Note that we clear bits 4 and 5, because we don't want the input ports being cleared.

mov $F3,#spc.dsp [FLG]

Now we restore the original FLG value, which might enable echo writing. Note that we don't need to set the DSP address, as it's already been set during RAM restore. Since we know the echo pointer will now be at the beginning of the echo buffer, writing is safe. If we put this final loader in the echo buffer, we should NOT place it at the very beginning, otherwise we'll be chased by the echo overwriting as we execute here, and might lose the race. So we should be at a small offset like 1024 (small enough to fit in case EDL is only $01).

mov $F2,#$7D   ; EDL mov $F3,#spd.dsp [EDL] mov $F2,#$4C   ; KON mov $F3,#spd.dsp [KON]

No need to restore the original KOFF.

mov $F2,#spc.ram [$F2]  ; DSP Address

Don't forget this, as the code might be expecting the address to already be set to something in particular.

; Restore PSW and PC   pop psw mov spc.sp,#saved byte from stack

We pop the saved PSW we push on the stack before executing this final restore code. That saved PSW has the P flag set. Then we restore that byte on the stack to whatever it originally was. The SPC file could have been depending on that byte, even though it's outside the current stack. The P flag is set, so we can use direct-page addressing here.

setp or clrp   ; depending on what's set in PSW in SPC file

Now we restore the P flag to what it should be.

jmp !spc.pc

And finally jump to wherever the SPC was executing when the SPC file was captured.

The above code relies on the PSW being on the stack, so we must push that ourselves, saving the old byte so we can restore that too.

With the 64K RAM patched, we upload that to the SPC and execute our patch inside it, and away it goes. Immediately after that, we must do one final restoration: write the final values to the input ports that were there when the SPC was captured.