I added echo functions to SNESGSS.
Again, I only patched the SPC code. You will not notice anything different in the actual app, and you won’t be able to hear the echo effect in the app.
So, I wrote over the streaming functions so they now write to the echo registers. Don’t worry too much about the SPC code itself. When you export from snesgssQ it will output a spc700.bin file which will be copied to the Audio RAM on spc_init.
You need to include the newest music.asm file which has these new functions.
All these functions prefer 8 bit arguments, loaded into A and X, so it’s best to put AXY in 8 bit mode.
Sets the Echo Volume, and turns specific channels on or off. Probably, you should set the Echo Volume lower than the Main Volume.
A = volume 0-$7f (0 will turn off echo completely)
X = a bitfield (bit 0 = channel 0, bit 1 = channel 1, etc) 1 = on, 0 = off.
LDX #$ff ;all channels on
Sets the Echo Buffer Address, and how big it is ( controls the delay timing)
A = Echo Buffer location (times $100, and the low byte of the address will be 00)
X = Echo Delay 0-$0f (times $800 = buffer size)
A delay value of 0 does actually function and it writes a minimal number of values at the echo buffer address (I think it’s 4 bytes per channel, so 32 bytes total) but you probably don’t want to use such a short delay time. You will probably want to use values 1-5 for the delay ($800-$2800 byte buffer size).
LDA #$ef ;buffer at $ef00
LDX #$02 ;buffer size = $1000
This setup will use $ef00-feff as the buffer.
!!! It is absolutely critical that your buffer address is set higher than everything else loaded to the SPC RAM, and that the buffer size isn’t larger than the remaining space. Also, you probably want to avoid the BOOTROM at $ffc0.
!!! You must have echo volume set to zero when making changes to the echo address. If echo volume is not zero, It can and will destroy all the SPC code/data and probably crash the game. Changing the echo address also takes a very long time, because the code does a mandatory wait loop to avoid this sort of failure. So, probably, you should set the address only once at the very beginning of starting the game.
The SNESGSS app has no way of warning you about your SPC files being in the same location as your echo buffer. You will have to manage this yourself, and make sure you don’t overlap them.
This sets the Echo Feedback Volume and the FIR filter settings. I recommend you don’t set the feedback above $60. It would be like putting a microphone directly in front of a speaker. A FIR filter controls how the echo is processed.
A = 0-3 to select a set of values for the FIR filter. I chose some popular ones.
a = 0 = simple echo = 7f 00 00 00 00 00 00 00
a = 1 = multi echo = 48 20 12 0c 00 00 00 00
a = 2 = low pass echo = 0c 21 2b 2b 13 fe f3 f9
a = 3 = high pass echo = 01 02 04 08 10 20 40 80
X = 0-$7f, the echo feedback volume
After some consideration, I thought that someone might like to use a custom FIR filter. This could be accomplished by manually patching over the first FIR filter set, at SPC address $3aa (copy 8 bytes here). In the main.asm file, you will see some commented out code at line 180. It’s a bit complex, because patching like this is beyond what SNESGSS what intended to do. After overwriting the table, you have to call echo_fb_fir with A=0 to copy the table to the DSP FIR registers.
Here’s a link for more FIR examples
Unfortunately, there is no way to attach echo settings to a song file. This will have to be managed at the programming level.
You might want to make a table of values for each song, and a function to read the values (when a song is loaded) and call all the echo functions automatically. I haven’t written any code to do this yet, but maybe I will in the future.
spc_init now can handle an spc700.bin file larger than $8000 (32768) bytes. Even though the spc700 is split accross 2 banks, it will wrap from ffff to 8000 in the next bank (with LOROM = 1 defined in music.asm).
It helps that I remembered that you can split a binary file using extra arguments on the .incbin line. If you look at the bottom of main.asm you see…
.incbin “MUSIC/spc700.bin”, 0, 32768
.incbin “MUSIC/spc700.bin”, 32768
The top one says copy 32768 bytes starting at 0. The second one (with 1 number) says to include from 32768 to the end of the file. The file is split across bank 6 and 7, but spc_init can copy the entire thing to the SPC RAM.
THE EXAMPLE SONG
I retuned my samples to B+21, and that seems to help on looping samples. But, sometimes I had to crossfade the end point and the loop point. One important thing I learned, for looped samples. You need the end point a multiple of 16 samples and the loop point needs to be a multiple of 16 samples. This is just how BRR compression works. It’s always blocks of 16. Tuning to B+21 makes 16 samples line up much better, but it’s not necessary. I’ve seen samples tuned to F, or other keys. But the cross fading to the loop point is very helpful to create a clean loop (no click).
Here’s a link of me processing audio to work on the SNES.
DEEP IN THE WEEDS
I wanted to give a little more information about SNES Music. Open this link, and let’s review…
The SPC program needs to set DSP variables. Scroll down to “Registers”… these are addresses in the SPC RAM. F2 and F3 are for communicating with the DSP. First you set a DSP address at F2 and then you set the value at F3. Scroll down this link to “DSP Register Map”.
So there are 8 channels, each one has a set of variables. Channel #0 will use 00-09, channel #1 will use 10-19, channel #2 will use 20-29, etc. Volume, Pitch, ASDR. An important one here is called Source Number. That says “which BRR sample?” But how does it know where the samples are?
DSP register 5d is the Source Directory. Whatever value there (x $100) is our list of addresses to the BRR samples. So if the value of the Source Directory Register is 2, the DSP will look for the pointer table at $200. The table consists of 4 bytes per BRR sample, that’s 2 addresses, first the start of the BRR sample, then the loop point of the BRR sample.
So, let’s go back to the Source Number for each channel. That value is multiplied by 4 and added to the source directory base, to locate the BRR sample. Source Number #3, with Source Directory at 2, the pointers to that BRR sample will be at $200 + (3 x 4) = $20c.
If the value at $20c is $1234, then when it will start playing the BRR sample at SPC address $1234. When (if) the sample sees a “loop” in the header, it will look to $20e for the loop point to jump to.
A note is triggered with a KON and stopped with a KOFF signal.
BRR samples is a compression system that always uses blocks of 16 samples. There is a header byte and then 8 bytes of 2 samples per byte. That means a BRR file size will always be multiple of 9. If you find a sample somewhere that isn’t a multiple of 9, it probably has 2 bytes at the beginning of the file that indicates the loop point. You might have to delete those 2 bytes to get a BRR file to work with some of the BRR tools available.
There is no way to import a BRR into SNESGSS, but you can convert the BRR to WAV (with BRR Tools) and load that into SNESGSS.
This could be my last blog post for a while. Good luck. Hope you can make a game.