My first post in here, so first of all, hello everybody in SNESdev (and NESdev) forums, been loving your work here!
Since early this year I have been reading up on SNES development material and did not find any source that addressed this issue in layman terms.
So, can anyone outline an algorithm for an SPC sound driver? How is the music data updated? How are the timers and counters used (never understood how the counters can be of any use if they are reset each time they are read)? And many other issues you feel necessary to be clear.
My goal for now is to make music with SPC files to be read in standalone APU emulators like the plugin for winamp, so I am not concerned in matters of uploading data to the APU RAM, reading screen information, etc. I also don't care for conversion tools of MOD->SPC files as I understand they are somewhat limited and the only limitations I want to work with are the SNES APU's ones.
Thanks in advance for all the attention guys
There is IPL that offers standart data transfer algorithm. You use it to load any code first, then you can either reuse it load more code/data, or run your own communication code that could work better/faster. IPL is neither good or fast, but it is the only thing for initial upload.
Timers could be needed to update sound state at given rate, say, 240 Hz. You do all update code when wait for timer being non-zero in a loop:
Code:
loop:
lda {T0OT}
beq loop
When it is zero, it'll reset to zero, which won't make any difference, when it is non-zero, it'll reset to zero, but your code will move on, and you also will have a value that indicates how many periods the timer is counted (in case your code took too long and ran longer than one period).
SNES APU limitations are actually going far beyond XM/IT/conversion tools limitations.
Since the SPC700 CPU doesn't have any interrupts, timers are required for a stable timebase.
Other than this you'd code it like any sound engine, except you only have the sound engine running, and nothing else. Having such a CPU entierely dedicated to sound is probably rather unique to the SNES (although I might be wrong).
A typical sound engine will "tick" multiple channel on a fixed (real time) basis, and then upload the channel's data to the S-DSP regs, and wait for the next tick.
Bregalad wrote:
Having such a CPU entierely dedicated to sound is probably rather unique to the SNES (although I might be wrong).
Genesis, Neo Geo, Jaguar, Dreamcast, Nintendo DS, Donkey Kong 3 machine...
Like a thousand of arcade machines also had a dedicated sound CPU/MCU that controlled various sound chips or DSPs, so actually this is very common solution. That is not common is the SPC700 use. Although it seems it wasn't designed specifically for the SNES (but the DSP probably was), I can't recall any other gaming system that had it. Usually it would be something popular, like Z80.
SPC700 is a pretty nice to work with, especially if you ignore the weird official mnemonics and use byuu's replacement, which makes it clear that the MCU is just a well improved 6502 descendant, so a lot of 6502/65816 knowledge easily applied to it.
Personally I'd use the "weird" mnemonics (whch are not weird at all, just different).
I think it makes it clearer you're programming the SPC and not a 6502 or anything like this. There is also many extra instructions like INCW or DJNZ which are not in the 6502.
Bregalad wrote:
I think it makes it clearer you're programming the SPC and not a 6502 or anything like this.
Do you likewise use
nocash's 8086 style mnemonics when programming 65816 to make it clearer that it's not a 6502?
Shiru wrote:
There is IPL that offers standart data transfer algorithm.
But so, if I'm compiling an insolated SPC just for pleasure of playing it in an APU emulator then I won't need to be concerned with the IPL, or?
Shiru wrote:
You do all update code when wait for timer being non-zero in a loop
Right! So you would have you're own counter in the code to reach the desired number of periods of T2 counter for example. It seems pointless to have a 4 bit counter if you can't wait until it reaches your desired count, right?
Shiru wrote:
SNES APU limitations are actually going far beyond XM/IT/conversion tools limitations.
Can you give me an example of this?
Thanks guys for your replys, keep them coming! I'm still expecting a nicely laid out algorithm to work with these so retro but wonderful music making machines. I will eventually come up with my own obviously, but you guys are geniuses and I still have a whole lot to read up on SNES and general assembly programming.
Quote:
But so, if I'm compiling an insolated SPC just for pleasure of playing it in an APU emulator then I won't need to be concerned with the IPL, or?
Correct. You can just disable it and use $200-$ffff as general purpose RAM (normally the IPL ROM sits at the very end of RAM).
One thing you should be particularely careful when using the SPC is that you should always enable a key off one "tick" before a key on, on a particular channel.
If you don't do so, a sudden key on while a note was already playing will "cut" it immediately, causing a noticeable pop in the sound.
This precaution is not required if the key on follows a period of silence where a note was already keyed off, but I just do it this way to be 100% sure all key ons are clean.
For an SPC file, you don't need to concern yourself with the IPL loading at all.
Point of counters is in case your code doesn't check often enough, this way it can know how many counts occurred. The hardware is well-designed in this regard. Though true, as you say, mostly it's just read continuously until it becomes 1.
Included is some code I used to make a simple beep, though it's not complete, might still help with getting basic sound.
Pires wrote:
Right! So you would have you're own counter in the code to reach the desired number of periods of T2 counter for example. It seems pointless to have a 4 bit counter if you can't wait until it reaches your desired count, right?
You don't have to use the counter. You can set up a required timer period and just go for one period. The counter is only needed if your code potentially could run longer than a period and you would want to compensate it somehow.
Pires wrote:
Shiru wrote:
SNES APU limitations are actually going far beyond XM/IT/conversion tools limitations.
Can you give me an example of this?
BRR encoding, samples has to be looped at 16 samples granularity, and only forward looping is possible. It is almost worse than even MOD, and sure far away from XM/IT.
Shiru wrote:
You don't have to use the counter. You can set up a required timer period and just go for one period. The counter is only needed if your code potentially could run longer than a period and you would want to compensate it somehow.
Exactly, thanks, I think I understand now fully how the timers work. So, the development manual must be wrong in saying the maximum value you can write to the timer registers is 01H!?
Shiru wrote:
BRR encoding, samples has to be looped at 16 samples granularity, and only forward looping is possible. It is almost worse than even MOD, and sure far away from XM/IT.
Sure, but if you then convert a MOD to an SPC file you are bound to respect the limitations of the SNES APU, right?
Thanks bregalad for the tip and blargg for the test code, will give it a go as soon as I have a proper testing environment set up.
Quote:
BRR encoding, samples has to be looped at 16 samples granularity, and only forward looping is possible. It is almost worse than even MOD, and sure far away from XM/IT.
Resampling can fix the 16 samples granularity problem, and you can simulate pingpong looping by doubling the size of the sample.
That being said a lot of things are possible on SPC which are not possible on MOD (or even XM/IT) like pitch modulation, noise and echo. As far I know I don't think MOD supports automatic volume enveloppe.
Double the size of your samples and they won't all fit.
The whole point of the pingpong looping is to save room.
As for the granularity and resampling, my own experience with this was pretty tiresome, all I can tell is don't expect it'll solve all the problems easily. It is damn difficult to make a seamless loop of anything more complex than a simple waveform on SPC. In general, all the clicky issues on the SPC, such as sample looping and key off, although may seem to have a simple solution, aren't that easily solvable in practice.
Quote:
The whole point of the pingpong looping is to save room.
Pingpong looping would have been impossible considering BRR compression, which is in itself even more efficient when it comes to save room.
Quote:
Double the size of your samples and they won't all fit.
This is indeed the major weak point of the SPC.
Well, technically it is possible to make ping-pong looping for compressed audio. I even wrote a backwards player for AAC/OGG/MP3 years ago, just decode a frame and play it in needed direction. 16 sample buffers per channel wouldn't take too much resources on a DSP probably. But we have that we have, so no ping-pong on the SNES audio hardware, and it is kind of limitation, especially when converting from existing XM/IT, as it widely used there. Sure not the most serious limitation in this area, though.
You are correct that backwards playing a BRR sample could be possible, but it would need much more hardware resources. I think the SDSP decodes 4 samples at a time, if backwards playing would have to be done, then an entiere block (16 samples) would have to be decoded at a time.
Not only that, but the start adress of the sample would have to be constantly compared to the entry in the sample table (since there is no bit for loop start block in the BRR encoding).
Finally the limitation of the SPC is not the absence of ping-pong looping (which can be easily emulated with a longer sample), but effectively the lack of memory for long, high quality samples.
You can thank god Nintendo finally decided to use 64kb of RAM. Supposedly, it was intended to have only 32 kb ! However I think 128kb would have been better. 64 kb for samples and 64kb for program + variables + echo buffer would have been a nice idea.
Anyways we're not redoing the SNES hardware
Actually it is circulated in old docs around that SPC only has 32K. Don't know where it came from, probably because there are 32K chips on the board. The memory map in the official docs is also introduces some confusion, it shows 512kbit total, but only shows up $0000-$7fff on the graph and does not explain what is in the $8000-$ffbf. Still, that's a minor confusion compared to what I've heard recently in a SNES hardware review, that the IPL takes 64K. That was fun.
Perhaps it was thought that IPL was mirrored into $8000-$FFFF when enabled, and disabling IPL was risky given homebrew programmers' lack of confidence at the time.
I take my conversions very carefully: I do sample chopping and quality reduction right off the bat. Without any actual note data (and practically the entire RAM for sample data), the SPC700 can take around 100,000 samples.
Another one that I think of is sample offset. That should be possible as long as you allocate a sample in the table for your offset data per channel.
I also can't help but think of a possible psuedo-synth mode that would take a set of small samples and would have a dynamically altered loop point (this would use the timer quite well, although remember that your code must manually check for it). This only works properly for the smaller samples (I'd say 1-8 blocks), and the lower the pitch, the more possibility that you might accidentally skip a step (unless you use more blocks to offset this problem).