So I've made a decent music engine (though it needs some work), but I realized I still need to make a sound effects engine. When thinking about how to go about doing this, I've run into a few problems.
The first is obvious. There are only five sound channels (though I'm thinking I'm only going to use the common 4), and music is usually taking up all of them. Sound effects will cancel out the music most of the time, but do sound effects ever "compete" with the music? Like if the music is louder, the music will play instead of the sound effects, and the other way around? I suppose though that sound effects wouldn't need to take up all the sound channels, actually I'd probably only work with Noise and Square 2, putting the least important music stuff in Square 2 so when it's canceled out it's not as noticeable.
Secondly, how to store it. Sound effects seem to be like "clouds" of sound. There's really no rhyme or reason to them, like you'd have to store them as complete uncompressed data to store directly into the sound registers. Anyone have thoughts about this?
Lastly, and probably more importantly, competing sound effects. What happens when more than one sound effect tries to happen at the same time (generally)? I suppose there are games like Castlevania where it seems like only the player can make sound effects, but I don't quite like that idea. Do any games enforce some sort of priority system for this problem? Or do any games even make this problem something they have to deal with?
Any ideas are welcome. Thanks in advance.
From what I've heard, audio data ($40xx registers) is usually buffered in RAM, and changes in audio are written to the buffer from least priority to greatest priority, and then the buffer is written to the actual registers. It makes it easy to do sound effects with background music this way. In the majority of NES games sound effects have priority over the music becuase a short sound effect stopping a channel from playing for less than a second usually isn't noticeable. Usually, when more than one sound effect tries to occur, and the sound effects share the same channel, the sound effect that occurs latest is played.
Celius wrote:
Secondly, how to store it. Sound effects seem to be like "clouds" of sound. There's really no rhyme or reason to them, like you'd have to store them as complete uncompressed data to store directly into the sound registers. Anyone have thoughts about this?
Sound effects in
Tetramino 0.33 through 0.35 consist of two bytes for each frame: the $4000/$400C value followed by a pitch value. Currently, there is a limitation that pitch must be a whole semitone.
Quote:
Lastly, and probably more importantly, competing sound effects. What happens when more than one sound effect tries to happen at the same time (generally)?
Tetramino's sound effect engine uses remaining length as priority. When it plays a sound effect, it compares the length of the sound effect to the remaining lengths of the currently playing sound effects on the compatible channels ($4000 and $4004 for a sound effect whose header specifies square or $400C for noise). If the sound effect isn't longer than the remaining time on some channel, it doesn't get played.
It's not a good idea to want to store data that is directly written to the regs, it takes up way too much space and you'd end up not knowing what to write there.
You'd rather want to store the sound effects exactly like you store the music, but you'd want more fast paced note and pitch slides typically. Just double the number of sound channels available, and when a sound effect channel is active, the corresponding music channel should not write to it's registers. Ram buffering the registers is pointless, just have a routine that check if "corresponding SFX channel is active", and output the result in the carry flag. Also that routine will have to always clear the carry if the channel is itself a SFX channel so that it's always enabled.
When a new SFX should happen you must somel to play that SFX, and it should possible to play many effect at a time if they don't use the same channels (altough the music will stop). I came up with a system that takes up whether square channel is free (Square 2 normally, or if another effect is already present on Square2, Square 1). I do not have a priority system, so if an enormous earthquake effect is plaing then a single "beep" is ocuring, the beep will overwrite the earthquare, and after the beep that channel will continue to play the music while the gigantic earthquake continues to play on the others channels. For that reason if you do something like that you'd want to store the earthquake as music and not as SFX.
I think that in my game, most enemies won't make much sound (if any). But there will be some enemies that may cast a lightning spell on you or something that will make sound effects. Bosses will definitely make sound, and the player hitting an enemy or getting hit by an enemy will definitely make sound. So I guess priority isn't as much of an issue as I thought it was. I'll probably just enforce a really loose priority system of some sort.
Another issue I just thought of. I know that the sound effects will cancel out the music. I've heard sound engine where a sound effect cancels out the music, and the music doesn't come back until a new note is played. Thinking about it, doing this might make it less noticeable as apposed to when the sound effect stops the music immediately starts playing again. Or is that just stupid?
And I'll have to think about the sound format. I don't think it's necessarily a waste of ROM to define 3 bytes per frame, because using music format to store sound effects, you might end up defining more than just 3 bytes per frame (if the effect is really complex). I think I could make a really long arpeggio envelope (1 byte per frame to bend pitch) and a really long volume envelope for a sound effect, and maybe reuse them.
Celius wrote:
Another issue I just thought of. I know that the sound effects will cancel out the music. I've heard sound engine where a sound effect cancels out the music, and the music doesn't come back until a new note is played. Thinking about it, doing this might make it less noticeable as apposed to when the sound effect stops the music immediately starts playing again. Or is that just stupid?
Not a good idea, I think unless the sound effect are extremely long and fairly quiet. Most sound effects are loud and short, so the music interruption shouldn't be too noticable.
For example in a lot of songs I made I use the triangle channel as the bass line and a drum. The drum hits are so short that you really don't notice the bassline cutting out.
I think I just came up with a good idea. For coding parts of a game engine in general but also for sound effects.
I got the idea from how I handle AI in my game. Every enemy/object is given a mind of its own, and that mind is basically a bunch of 6502 code with a designated chunk of RAM. That basically gives the enemy power to do anything it wants. Even though the AI is made of code, it's still data (it's not part of the game engine, necessarily).
I really like the idea of code as data. I think this could also be applied to NES music/SFX. Some SFX will have a sort of formula for them. Like pitch = pitch * 1.1 every frame. If you jump to a unique subroutine for sound effects in special cases like this, you could end up saving yourself a ton of space. Like what if a sweeping sound effect was defined with a table? It might look like:
SoundEffect:
.db $02,$04,$06,$08,$0A,$0C,$0E,$10...$3A,$3C,$3E
while as code it would look like:
SoundEffectPlay:
clc
lda Pitch
adc #2
sta Pitch
;somehow turn the value "pitch" into a sound the NES can play
That would save you a lot. Do many games use code as data like this?
Celius wrote:
That would save you a lot. Do many games use code as data like this?
I've used the approach for some graphics effects. Compiled 'graphics' is the term, I believe. I don't see why you couldn't do the same with music or logic. Having a flag/control code that tells the routine to 'jmp' on the following byte. I had planned on doing this for level scripts. For certain effects that would be too tedious to write out extra decoding logic for. Something that the script compiler would generate at compile time based on logic or just inlined/attached function.