Another option for SNES audio is SNESMOD. It doesn’t have its own tracker, but you would use OpenMPT in the IT format, and save the file as an IT file. SNESMOD comes with a converter program called smconv.exe which can turn the IT file into a soundbank that can be used in a game. (It can, optionally, convert the IT into an SPC file to be used as a standalone music file).
https://github.com/mukunda-/snesmod
This is the original SNESMOD, and Mukunda has been nice enough to change the license to a more permissive open source (MIT). And, because of that I have gone and modified both the SPC and the SNES code. The SPC code (tree/master/driver/spc/sm_spc.asm) is supposed to be assembled with TASM using the TASM07.TAB file as the table… but I found it easier to assemble it with ca65 using blargg’s macro pack.
You can find both TASM and smconv.exe in the PVSNESLIB repository. Also the SPC code here has a bug fix that is missing from the original version.
https://github.com/alekmaul/pvsneslib/tree/master/pvsneslib/snesmod
https://github.com/alekmaul/pvsneslib/tree/master/devkitsnes/tools
(by the way, absent is the TASM documentation, which I found online. As far as I know, it is not freeware, and it asks that you pay for a license and register each computer that uses it… which is another reason that I suggest you use ca65 if you need to reassemble the SPC code). It didn’t assemble perfectly at first, but I only needed to make a few minor syntax changes to get it to work.
https://github.com/nesdoug/SNES_14_SNESMOD
OpenMPT
This is what you will use to make the IT files. I really thought this would be easier than SNESGSS, but surprisingly I had several difficulties. Let’s go over this really quick.
File/New IT file.
You will see 5 tabs: General, Patterns, Samples, Instruments, Comments
First thing you should do (in the General tab) is to click the button that says IT…32 channels, and set it to IT…8 channels.
Then click on the Instrument tab, and click the folder icon (import instrument) to load… and you select your .wav files. It will load it both as a sample and as an instrument. You can go back to the Sample tab if you want to turn on looping and edit the start/end point.
Back to the Instrument tab, you will want to give it a volume envelope. So at the bottom click “Vol” and move and add dots to create a volume envelope for the instrument. If you like, you can loop the envelope, or part of the envelope. You could use this to make a tremolo effect (like a vibraphone).
Then you write the song in the Pattern. Select an instrument and a column and press keyboard keys to enter notes. Personally, I changed the keyboard layout to be more like Famitracker… View/Setup/Keyboard/Keyboard Mapping-Import Keys then find the More Keymaps folder and select one. I can’t remember which one I chose… maybe it was UK_IT2 (?).
This is part of where I had confusion. To play just the current pattern, you click the arrow buttons that point down below the Pattern Tab… but it will keep looping back to the same pattern. If you want to play not looped, as in to hear the whole song, you press the arrow above that (next to the word octave).
Each column is NOTE, VOL, INSTRUMENT, then EFFECT.
To add a new pattern, you right click on the next blank pattern box and select Create New Pattern.
Here are the effects that are supported by SNESMOD.
A = song speed (ticks per row)
B = set position (jump backwards to a specific pattern #)
C = pattern break (jump the the next pattern) [must be C00]
D = volume slide (fade in/out)
E = pitch bend down (for 1 line)
F = pitch bend up (for 1 line)
G = portamento speed (turns off once it hits the target note)
H = vibrato speed,depth
J = arpeggio +x semitones +y semitones
K = volume slide + vibrato
M = channel volume
N = channel volume slide
O = (I don’t think this is supported) **
P = pan slide
Q = retrigger
S = special (see below) *
T = set tempo (BPM)
V = global volume
X = set pan
*S01 – Turn off echo for channel.
S02 – Turn on echo for channel.
S03 – Turn off echo for all channels.
S04 – Turn on echo for all channels.
** The spc code sets t_sampoff, but never uses it. Makes me think it doesn’t do anything.
Oh, and to do echo settings, you have to put it in comments, like this…
[[SNESMOD]]
edl 4
efb 90
evol 70 70
efir 127 0 0 0 0 0 0 0
eon 1 2 3 4 5
edl = echo buffer size x $800 bytes, affects delay time.
efb = feedback, don’t set it too high
evol = echo volume L and R
efir = FIR filter settings
eon = which channels to have echo on.
You don’t need to use Sxx effects to turn echo on at the start of the song, it does it automatically.
I wrote a whole page about why you should use mono WAV files at sample rate 16000 and tune the samples to B+21 cents. 16000 to reduce file size without loosing too much high end. B+21 so that you can perfectly loop short samples with the BRR compression limitation of 16 samples per block. That is still my recommendation here. Use short samples if possible, there isn’t very much ARAM space and all the used samples need to fit into 64k along with the SPC code and the echo buffer.
Once the song is done, save the IT file. smconv.exe can convert it into soundbank. (It’s a command line program). -s puts it in soundbank mode (otherwise it will generate an SPC file). One interesting point here… if you specify multiple .it files, it will put them all into the same soundbank. However, it may be a waste of space since (I think) it will duplicate the samples, even if the same sample is used in multiple songs.
If you used that method, when you go to LOAD the song, you use #0 for the first song, #1 for the second. Again, I don’t think this is the best use of space. Here’s what I recommend for putting multiple songs in…
You make one song that has multiple songs back to back in it. At the end of each song you insert a Bxx effect to loop back onto itself. So each song would be a closed loop to itself. When you go to PLAY the song, you select the pattern # of the start of the song as the parameter you send when you call it. It’s a bit more of a pain to set it up this way, but (I think) you save ROM space, as samples used in multiple songs would only be stored once.
You might need many many songs, and this method won’t be enough, because you will run out of ARAM space to fit all the song data. In that case, you will need to make multiple soundbanks.
Another advantage of this (multiple songs squished together into one song) approach is that you can switch back and forth between songs without having to wait for a it to be transferred from the SNES ROM to the ARAM. You only have to wait at the beginning when you initially load everything.
When you include the soundbank to the SNES source file, it needs to be located at the very beginning of a bank. If you have multiple soundbanks, each needs to be at the beginning of a bank (ie, address $8000 in LoROM mapping).
For sound effects, you can make a separate soundbank… just an IT file with no song, just samples, converted by smconv into a soundbank. Or, you can use the existing song soundbank to store your samples, but there is a caveat. The song samples use a different table than the sfx, so you will have to load the sample, which might be a duplicate of a sample in the song… ie. you would have to copy the same sample in twice. But, I made a function (spcClone) so you don’t have to have 2 copies of the same sample to use them both in the song and as a sfx.
If you opt to use the streaming functions, you don’t need a soundbank, you need a BRR file (without the 2 byte loop address at the beginning, like you might find on BRR files at SMWcentral). The BRR file should exactly be divisible by 9. If it isn’t, then you will have to strip the 2 bytes off the beginning of the file. You can use brr_encoder.exe from brrtool to convert a WAV to BRR. There is an app called snesbrr.exe in the pvnsneslib github that probably does the same thing.
You need to allocate room for the streamed sample with spcAllocateSoundRegion. Send the size of the largest BRR you might use / 256 and round up) so that it can reserve space for it. This needs to be done before you Play the song. Don’t use spcAllocateSoundRegion while a song is playing. Interesting side note, there is no error check in the SPC code on size… if you load a lot of big files with spcLoad or spcLoadEffect, it could overflow into the echo buffer or the sound region.
But (to use the streaming functions) you also need to make an 8 byte array…
1 = pitch (1-6)
2 = pan (0-15)
3 = volume (0-15)
4-5 = length of the BRR / 9
6,7,8 = long address of the BRR sample
and pass the address of this array to spcSetSoundTable()
Then you call a play the sample with spcPlaySound, spcPlaySoundV, or spcPlaySoundEx and the sample will stream in as it plays.
So.. confession time. I removed the streaming functions from my version. I had modified the SPC code and I made it too big and the streaming code overlapped where the module loads, and it would crash if you tried to play a streamed sample. You can modify where the module loads, but that breaks the song playing code, because the song pointer table will be in the wrong location, and so I would need to modify the smconv.exe source code… etc, etc, etc. and I didn’t do that. The easiest thing was to disable streaming.
But, I believe I made the regular sfx playing functions robust enough that we don’t need it. Also, the streaming function had very limited ability to repitch the sample. Furthermore, it sometimes didn’t work right, such as for PAL systems. I didn’t really want to use the streaming much.
Let’s look at the files we need. In my project (SNES 14) in the SNESMOD folder there is a file called sm_spc.s which is the SPC program code. spc-ca65.asm is the macro pack from blargg that allows us to assemble this file with ca65. The output file is called snesmod_driver.bin and that is what we are sending with the spc boot code. I just .incbin it between these labels SNESMOD_SPC and SNESMOD_SPC_END.
Over in the MUSIC folder is smconv.exe which I used to convert the .it files into soundbanks (the .bank files). The other files generated (.asm and .inc) aren’t interesting to me.
Also in the MUSIC folder, snesmod_ca65.asm has our SNESMOD 65816 functions. I made some changes. Also, the SFX_LIB.asm has some functions for generating a sfx sequence. SFX_DATA.txt is the data for those sequences.
SNESMOD Functions
spcBoot – call this at the very beginning to send the SPC program over to the ARAM.
spcSetBank – tells the Loading functions where our soundbank is located. Use it before spcLoad and again before spcLoadEffect.
spcLoad – loads a song (and its samples) to the ARAM. (Also, it resets the sfx loader back to zero). The parameter is if you have multiple songs loaded to the same soundbank.
(Note, spcLoad takes a long time, maybe even several seconds to complete)
spcPlay – plays a song. The parameter is the starting pattern #.
spcStop – stops the song
spcSetModuleVolume – sets the volume for the song (0-255)
spcFadeModuleVolume – fade in or out the song volume
spcLoadEffect – loads a sfx sample (up to 16). The parameter is the position of the sample in the soundbank, which may or may not be the same as the “id”, which depends on the order that the samples are loaded as effects.
spcEffect – plays a sfx sample. The parameter is the “id”
spcProcess – call this once per frame. Several of the functions above do nothing at all until spcProcess is called. They are stored in a queue and sent to the SPC one at a time.
Functions I removed —
spcTest – didn’t seem to do anything useful
spcAllocateSoundRegion – tells the SPC how big the streamed BRR might be
spcSetSoundTable – sets pointer to an 8 byte array (see above) describing the BRR sample. You need 8 bytes per BRR sample you would use.
spcPlaySound – play the streamed sample (using that 8 byte table)
spcPlaySoundV – same, but with custom volume setting
spcPlaySoundEx – same, but you manually send it the settings (volume, pitch, pan)
spcProcess calls spcProcessStream, which needs to run every frame
Note, streamed sound always plays on channel 7.
Functions I added —
spcGlobalVolume – set the volume level for everything (except echo) (0-127)
spcEffect – has been modified so pitch can now be 0-60 which corresponds to C2 to C7.
Before, it was volume, pan, pitch, and id. Now it’s just volume, pitch, and id.
Note, I made it so that sending a volume of zero will cancel the effect.
spcFxParams – additional parameters. Call this before you use spcEffect. It is used to set the pan and which channel the effect will go to. Originally, you wouldn’t get the choice, it would alternate between channels 6 and 7.
spcClone – copy the pointer for a BRR from the song table to the sfx table. Do this after loading the song to the ARAM. This is so you can use a BRR sample from the song as a sound effect without having to have 2 copies of it. (I’m using the word clone in the sense that it is used in programming, which is that the data itself is not copied, only creating a 2nd reference to the same data.)
Functions in SFX_LIB —
Sfx_Stop_All – stops the sound effects
Sfx_Queue – use this to call a sound effect from the data tables, however only the last sfx called per frame will actually play. It’s not really a cue or stack or whatever, I just didn’t want to call the sfx functions multiple times a frame and waste so much CPU time trying to communicate with the SPC. I believe that allowing only one sfx per frame should be sufficient… however, you might decide that one sfx is more important, and you might have to modify this code so that the more important sfx has higher priority.
Sfx_Process – call this once per frame. It handles new sfx to play (from the queue) and it processes the data of
Notes
If you look at the example, in main.s, you notice that the “id” of each sound effect depends on the order that they are loaded, not the order that they are in the soundbank.
ldx #0 ;saw
jsr spcLoadEffect
ldx #1 ;square
jsr spcLoadEffect
ldx #2 ;piano
jsr spcLoadEffect
So, the 0,1,2 here refers to the sample’s position in the soundbank, and coincidentally since they are loaded in the same order their ID would be 0,1,2 as well. If you changed the order and put ldx #2 ;piano first, then the piano sample would have the “id” of zero (for when you play it with spcEffect). I threw a curveball and put…
lda #1 ;strings
jsr spcClone
and actually, that the string sample would have an “id” of #3 (because of the order of how things were loaded to the SPC). The lda #1 here refers to the position of the sample in the song’s soundbank. If you open the song IT file, this would be the 2nd sample (the first sample would be #0).
Max Sound Effects are 16.
The Data Format
My main goal in modifying SNESMOD was to get a sound effect note sequence. This is what I came up with. Once all the sound effects are loaded, you just call Sfx_Queue to trigger a note sequence. The data for that is store in SFX_DATA.
The data system is fairly easy. 1-127 means wait that # of frames. 0 = end of data. $80-BF = pitches of notes. $C0-CF = id of sample. $D0-DF = pan (L to R). $E0-EF = volume. $F0 = retrigger off. $FF = retrigger on. There is table of constants you can use. Unfortunately, there is no app to generate this data, you just have to type it in.
So… I should explain an important point, the volume and pan don’t go into effect until a pitch is entered (that is just the way I decided, a new pitch is the thing that makes the code send the info to the SPC) which means you would have to send the note signal again, which would restart the note. Because this might sound weird retriggering the note over and over, I made it so you can skip the retriggering part.
Don’t put “retrigger off” before the first note in the sequence. That note needs to be triggered. Also, if you volume down to zero and then have more sequence after that, you will have to retrigger the next non-zero volume part (internally, my SPC code sees a volume of zero as “off”). Retrigger means that it sends a key off and then key on signal to restart the BRR sample.
Maybe I will refactor this in the future. It’s a bit complicated, and some of the data is repetative (putting the same note value over and over). But, it seems to work the way I planned it, and I’m not having any difficulties making data.
One of the key things that I changed is that you can have LOOPED samples for sound effects. That way you could use very small BRR samples… like 128 bytes, for example. How do you loop the sample? In OpenMPT, under the sample tab, change the sample to looped, and change the start and end points of the loop. Keep in mind that the length of the sample and the staring loop point need to be exactly divisible by 16. That is a limitation of the BRR compression format.
Is SNESMOD better than SNESGSS?
SNESMOD has more effects column possibilities, such as arpeggio and volume slide. You can make a more elaborate volume envelope (SNESGSS uses only the built in ADSR), and can make a looped volume envelope for a tremolo effect. It is a little weird that the effects column requires you to put the same effect on every row, where other tools let you use an effect once and it persists until it is shut off.
SNESGSS gives you better information about the size of each thing and how much room you have left. Originally, SNESGSS didn’t have the ability to use echo, but I manage to fix that issue.
I still feel like both of these are acceptable, but need further development.
Note: I’ve been told the version of smconv.exe in my github may have a minor bug that incorrectly calculates the size of some things… although the data seems to be correctly playing back… I’ll assume that the bug makes it more difficult to determine how much space is available. Maybe I can fix this soon.