SPC data upload
Moderator: Moderators
Forum rules
- For making cartridges of your Super NES games, see Reproduction.
Does your code write 0 to EDL at the beginning, then wait at least 1/4 second before writing the new value and executing the final restoration code? I don't see any way around doing all this. For example, if you merely write EDL with the new value at the beginning and FLG with $60, later when you enable echo, the current echo position could be anything within the echo buffer. If that happens to be right at your final restoration code, you've got problems. By keeping EDL at 0 until the very end, you ensure that the echo position is 0 when your restoration code is running (your +$200 ensures that it won't overwrite the beginning once you enable echo).
At the point in my init code where I re-enable echo there are only two instructions that remain to be executed (pop psw / jmp entry_point). If it's true that it takes the DSP 240 ms to write 30 kB of echo buffer data then there's no way it could fill up $200+ bytes in the time it takes the SPC to execute those two instructions.
Now, if the DSP would start writing data at some pseudo-random point in the echo buffer when I enable the function then that could be a problem. But my understanding was that writes to ESA/EDL are "delayed" only when the echo function is active - not when ~ECEN goes from 1 to 0.
I'll do some hardware tests later.
Now, if the DSP would start writing data at some pseudo-random point in the echo buffer when I enable the function then that could be a problem. But my understanding was that writes to ESA/EDL are "delayed" only when the echo function is active - not when ~ECEN goes from 1 to 0.
I'll do some hardware tests later.
What about KON and $F2?At the point in my init code where I re-enable echo there are only two instructions that remain to be executed (pop psw / jmp entry_point).
How do you know that the echo pointer is at the beginning of the buffer when you enable echo writes?If it's true that it takes the DSP 240 ms to write 30 kB of echo buffer data then there's no way it could fill up $200+ bytes in the time it takes the SPC to execute those two instructions.
Yes, the echo pointer etc. is ALWAYS running. The ONLY thing that ECEN affects is whether the final writes occur. Remember, this is hardware, not software, so the usual "why would it waste cycles updating the echo pointer when writes are disabled?" aren't relevant. I've found that with most sound chips, it's the exact opposite: they keep everything running unless it can't be. Some operation that stops somthing will do the minimum possible to achieve the desired effect.Now, if the DSP would start writing data at some pseudo-random point in the echo buffer when I enable the function then that could be a problem. But my understanding was that writes to ESA/EDL are "delayed" only when the echo function is active - not when ~ECEN goes from 1 to 0.
It's not clear exactly what you're doing, so I'll give more detail of one wrong method (as far as I can tell), with made-up values.
At the beginning, you first write $60 to FLG, to disable echo writes. You then write the EDL the SPC file uses, $01. This makes the echo buffer $800 bytes long. You then write ESA with $F0, putting the echo buffer at $F000. The echo position might currently be beyond the new EDL, but that's not a problem because writes are disabled.
Then you upload the rest of RAM, which takes at least 1/4 second, so you know that after that, the echo pointer is guaranteed to be within $F000-$F7FF. You weren't able to find any other space for your final restoration code, so you have it in the echo buffer at $F200. Your final code writes 0 to FLG, enabling echo writing, and then does a few more final things (KON, DSP address, PSW, PC).
The problem is that once you clear FLG, the echo buffer will start writing soon after, and it might happen to be pointing at the last byte of your restoration code and overwrite it before you execute it. I'm not saying your code suffers from the above, but I wanted to be sure I had described the problem fully enough.
It may be a theoretical possibility. It has never happened in practice though (and I've run probably something like 100 tests on my SNES with the different versions of my loader). This wasn't even a concern of mine when I made my latest changes. What I wanted to accomplish was to transfer the entire memory range and to avoid having a static address for my init routine, since those two things were preventing Terranigma songs from playing. While I was at it I decided to speed up the transfer loop a bit.
Of all the songs I've tested now, the only one I've noticed any problems with is the intro from Pinball Fantasies which has some missing voices. I haven't looked into what it does that makes it special, but perhaps I'll en-nerd myself in that tomorrow.
Of all the songs I've tested now, the only one I've noticed any problems with is the intro from Pinball Fantasies which has some missing voices. I haven't looked into what it does that makes it special, but perhaps I'll en-nerd myself in that tomorrow.
Even for an echo buffer of just $2000 bytes, you have less than a 1% possibility of it corrupting your final patch as it's executing, so you won't see it occur often. The fix is simple: set EDL to 0 initially, and then set it to the correct value just before/after restoring FLG. Not sure why you'd avoid this simple fix and have the loader fail occasionally for no obvious reason.
Wow, I was just looking over the code Anti Resonance sent me in 2004, and I came across this comment (not sure if he ever got it working, but it got me thinking outside my box):
He's got code at $F2-$FD! $F2 is DSP addr, a usable RAM location, $F3 is the value in the DSP register, also usually acting like RAM. $F4-$F7 are of course the I/O registers. $F8 and $F9 are RAM. $FA-$FC are the counter targets, and $FD is a counter output. It seems that the above wouldn't work because the counter targets always read back as zero, but still, them reliably reading back as zero, and the DSP registers being usable as RAM are great ideas. He could of course have used $F0 and $F1 as two extra NOPs (since they always read as zero), and perhaps eliminated the MUL as a delay, freeing an extra byte.
Code: Select all
;Code contained at 00F3-00FD:
;00F2 CF Mul YA ;9 41 ;Give the 65816 enough time to write values
;00F3 E8 ?? Mov A,#imm ;2 7 ;Load A with value in port 0
;00F5 C5 ?? ?? Mov abs,A ;5 17 ;Store A to address in ports 2 & 3
;00F8 FE F6 F7 DBNZ 0F6h,00F2 ;7 24 ;Decrease port 2 and write it back [14]
;00FB 5F 00 00 Jmp 0000h ;3 10 ;If port 2 was 01h, jump to 0000
I found out yesterday why Pinball Fantasies wasn't working with my loader. My free-space searcher had a bug in it that caused it to recognize only strings containing 000000.., and never those containing FFFFFF.., so it ended up placing the init routine in the wrong place.
I also managed to cut 2 bytes from the init routine by using the (X)+ addressing mode. So after having saved those 2 bytes I decided to add the write to EDL to the routine, resulting in a net growth of 4 bytes. My init routine now weighs in at 43 bytes in total. I considered storing the 4-byte code sequence MOV $F3,#imm / RETI at $f4..$f7, which would've initialized FLG, popped the PSW and popped the entry point. It would've grown my routine by 12 bytes though, and it didn't work properly so I scrapped that idea.
The main transfer loop that recieves the bulk of the data on the SPC side has also been optimized by 1 cycle/byte.
My latest code: http://jiggawatt.org/badc0de/spcplayer-1.3.zip
I also managed to cut 2 bytes from the init routine by using the (X)+ addressing mode. So after having saved those 2 bytes I decided to add the write to EDL to the routine, resulting in a net growth of 4 bytes. My init routine now weighs in at 43 bytes in total. I considered storing the 4-byte code sequence MOV $F3,#imm / RETI at $f4..$f7, which would've initialized FLG, popped the PSW and popped the entry point. It would've grown my routine by 12 bytes though, and it didn't work properly so I scrapped that idea.
The main transfer loop that recieves the bulk of the data on the SPC side has also been optimized by 1 cycle/byte.
My latest code: http://jiggawatt.org/badc0de/spcplayer-1.3.zip
Argh, wish someone had mentioned that to me sooner. I've fixed the link to my no-patch SPC uploader: upload_spc_nopatch.zip
initial_in_ports: passed.
initial_regs: error code 3. I only write bits 0..2 to $f1. I see no point in writing bits 4..5 since all they do is clear $f4..$f7 and I'm setting those explicitly later anyway (which you check in initial_in_ports.spc). And what do bits 3 and 6 of $f1 even do? I've never seen them being used or documented.
I also don't set $f2..$f3 since I assume those values to be leftovers from writing the DSP register values that are found at $10100.
full_ram: error code 5. Pretty obvious since I put my init code in the echo buffer if the song has one. In this case I put my 43-byte routine at $5800.
initial_regs: error code 3. I only write bits 0..2 to $f1. I see no point in writing bits 4..5 since all they do is clear $f4..$f7 and I'm setting those explicitly later anyway (which you check in initial_in_ports.spc). And what do bits 3 and 6 of $f1 even do? I've never seen them being used or documented.
I also don't set $f2..$f3 since I assume those values to be leftovers from writing the DSP register values that are found at $10100.
full_ram: error code 5. Pretty obvious since I put my init code in the echo buffer if the song has one. In this case I put my 43-byte routine at $5800.
$F0: Always reads back as 0.
$F1: Low three bits should obviously be restored. The top bit should also be restored, since the SPC may have expected to read the IPL ROM, or RAM. The port clear bits should NOT be restored, as they aren't persistent. i.e. an SPC may write with the port clear bits set, have them clear, then thousands of cycles later the SPC file is captured, after the S-CPU has written new values to the ports.
$F2: SPC may have been captured just after the code wrote $F2, and before it's about to write/read $F3. Not restoring this would screw up that access.
$F4-$F7: Obviously these must be restored, as you do.
$F8-$F9: RAM, so obviously should be restored.
$FA-$FC: not tested by this, since they can't be read back directly.
$FD-$FF: I'm not assuming an SPC uploader will restore these to the values from the SPC file. The test requires that these read as zero, since it has the timers stopped via $F1 and thus they should never increment.
full_ram: Yeah, I figured 5. That's why I had it check the other things first, so that echo buffer patching wouldn't mask any other diagnostics.
I for one found these tests to be very useful when modifying my SPC uploader, to be sure I didn't break anything important.
$F1: Low three bits should obviously be restored. The top bit should also be restored, since the SPC may have expected to read the IPL ROM, or RAM. The port clear bits should NOT be restored, as they aren't persistent. i.e. an SPC may write with the port clear bits set, have them clear, then thousands of cycles later the SPC file is captured, after the S-CPU has written new values to the ports.
$F2: SPC may have been captured just after the code wrote $F2, and before it's about to write/read $F3. Not restoring this would screw up that access.
$F4-$F7: Obviously these must be restored, as you do.
$F8-$F9: RAM, so obviously should be restored.
$FA-$FC: not tested by this, since they can't be read back directly.
$FD-$FF: I'm not assuming an SPC uploader will restore these to the values from the SPC file. The test requires that these read as zero, since it has the timers stopped via $F1 and thus they should never increment.
full_ram: Yeah, I figured 5. That's why I had it check the other things first, so that echo buffer patching wouldn't mask any other diagnostics.
I for one found these tests to be very useful when modifying my SPC uploader, to be sure I didn't break anything important.
I feel a bit stupid for asking for a "simple" solution but I'll do it anyway. 
I'm writing my code in C via the SNES SDK.
@blargg
I was happy to see some C code here, too (your spc loader). I read your wiki page about uploading a SPC file to the SNES but it's still very cryptic to me.
@mic_
Didn't know you were here, too
@both
I think I can embed ASM code in C. Would it be possible to use your software in my C code? (either as C or ASM version) And what exactly do they do? From what I understand they load a defined SPC file and upload it to the SPC-700. Maybe that's basic APU stuff (please tell me if so) but are there any play control functions like play, stop etc?
@blargg
If I used your C-spc loader, how would I have to write the remaining functions to actually hear something within my game?
@mic_
As I told you I only know a small amount of ASM. Your spc-player code is embedded into a whole "application", is there a way to know what code I actually have to include in my program to use it?
I hope I'm not asking too many questions again, all I'm looking for is the easiest way to be able to play music in my program (in C code if possible).

I'm writing my code in C via the SNES SDK.
@blargg
I was happy to see some C code here, too (your spc loader). I read your wiki page about uploading a SPC file to the SNES but it's still very cryptic to me.
@mic_
Didn't know you were here, too

@both
I think I can embed ASM code in C. Would it be possible to use your software in my C code? (either as C or ASM version) And what exactly do they do? From what I understand they load a defined SPC file and upload it to the SPC-700. Maybe that's basic APU stuff (please tell me if so) but are there any play control functions like play, stop etc?
@blargg
If I used your C-spc loader, how would I have to write the remaining functions to actually hear something within my game?
@mic_
As I told you I only know a small amount of ASM. Your spc-player code is embedded into a whole "application", is there a way to know what code I actually have to include in my program to use it?
I hope I'm not asking too many questions again, all I'm looking for is the easiest way to be able to play music in my program (in C code if possible).
An .SPC file is an entire program for the APU. Once you have uploaded the .SPC file and begun executing the code in it the song should automatically start playing, because you have no way for further communication with the SPC700 unless you know how that specific game's sound driver works (that, or resetting the SNES).I think I can embed ASM code in C. Would it be possible to use your software in my C code? (either as C or ASM version) And what exactly do they do? From what I understand they load a defined SPC file and upload it to the SPC-700. Maybe that's basic APU stuff (please tell me if so) but are there any play control functions like play, stop etc?
The loading code is pretty much self-contained in loadspc.asm. The only stuff you'd need from spcplay.asm is the call to LoadSPC, and the stuff at the bottom that INCBINs the .SPC file data.As I told you I only know a small amount of ASM. Your spc-player code is embedded into a whole "application", is there a way to know what code I actually have to include in my program to use it?
It would be neat if SPC file rips were wrapped like almost all other ripped formats, where there is a standard way to get control back so you could upload a new song. It might not be that hard to write an automated program to at least determine what values must be written to the ports to cause re-entry to the IPL ROM (though some games might use a custom loader, and never re-enter the IPL ROM). These key values could be stored at some currently-unused offset in an SPC file.