Smooth fadeout

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Smooth fadeout
by on (#206807)
In another thread, I was talking about fading out the music in an NES game.

I do a pretty simple way: Right before FamiTone writes its buffer values into the APU, I take the buffer values that contain the volume, extract the volume, decrement it accordingly and put it back in.

This thread shall not be about the technical way of accessing the APU or of actual Assembly programming etc. or the inability of the triangle channel to change the volume.
This one is only about the general way of fading out the music smoothly, in a general algorithmic way.


In the moment, I'm doing it like this:

I use a counter that goes from 0 to 15 and that increments every few frames.

In every frame, for each volume in every channel, I take the volume and check if it's still bigger than my counter.
If it's bigger, then: newVolume = oldVolume - counter.
If the volume is less than the counter, the volume gets set to 0.

So, let's say a melody has the values 2, 6, 4.

In this case, the fadout would look like this:
2 6 4
1 5 3
0 4 2
0 3 1
0 2 0
0 1 0
0 0 0

The problem with this is that more quiet sounds get muted earlier than loud sounds.

So, if a melody sounds like this:
loud, quiet, quiet, loud, quiet, quiet

then at one point during the fadout, you just hear:
quiet, nothing, nothing, quiet, nothing, nothing

Since the NES has only 16 volume levels and not 256 or more, this doesn't sound very smooth. It feels more like a glitch when you have isolated sounds suddenly appearing and disappearing.


Another approach is: I have a counter that goes from 15 to 0.
If the current volume is still less than the counter, it remains the same. If it's bigger, it gets the counter value:

2 6 4
2 5 4
2 4 4
2 3 3
2 2 2
1 1 1
0 0 0

But in this case, loud sounds gradually get to the same volume as quiet sounds. The volume gap between two different sounds gets gradually removed. I'm not sure in how far this might sound odd.


Then there's the possibility to divide by 2, then by 4, then by 8:

2 6 4
1 3 2
0 1 1
0 0 0

But this makes the fadeout much shorter and might also have those isolated sounds appearing and disappearing.


A last idea: Using the first method, but keeping the minimum value at 1 instead of 0 until the whole counting is done:

2 6 4
1 5 3
1 4 2
1 3 1
1 2 1
1 1 1
0 0 0


So, which version is the best when you cannot access the song itself or manipulate the sound driver, but you're only able to manipulate the final three volume values shortly before they are written into the APU?

Do you maybe know of a better method to decrease the volume values for a fadeout?
Re: Smooth fadeout
by on (#206808)
I'm all too familiar with this problem. I know of no great solution, but here's a suggestion

after you've hit the first 1 and want to decrease but not really go to 0, you could instead switch to a more mellow timbre. In the case of squares, going from tinny/narrow to hollow/wide, and in the case of noise, pitch goes one down to dampen the high frequency content. You need to make sure it doesn't wrap around, that they eventually drop to 0, and that they only behave this way up until all channels are on 1, in which case every remaining 1 should drop to 0. The goal is approximating a frame (at least one) that sounds a bit 0.5-ish by reducing the harmonic content.
Re: Smooth fadeout
by on (#206811)
FrankenGraphics wrote:
you could instead switch to a more mellow timbre.

Sorry, but since I'm not a musician, I don't really know what these things mean. I only understand them if you talk to me in algorithmic/programming terms. :mrgreen:

For your approach, which bits in which of the APU values have to be changed in which way?
Re: Smooth fadeout
by on (#206813)
Aha! Ok, so a narrow duty pulse (say, 12,5%) will produce a "brighter" sound than a 50% duty cycle. The idea is to dampen these bright frequencies, which the human ear is more keen/sensitive to (we percieve bright sounds as more "present"), by switching the duty bits "one up". Of course, this doesn't work if the duty is already at its softest/widest (50%), but it's the best we can do with this technique. edit: for clarity, i'm speaking about DDxxxxxx (duty bits) in $4000 / $4004. Be wary that the square duties shouldn't increase from 50 to 75% or wrap from 75% to 12,5% as that would brighten the tone instead.

As for the noise, you simply shave off some high-freq noise content like a low-pass filter if you step a semitone (or several) down.
Re: Smooth fadeout
by on (#206814)
Famitracker has a volume table that basically is "multiply and round down, except never round to 0", which is uses to apply a channel volume on top of instrument envelope volumes:
http://www.famitracker.com/wiki/index.php?title=Volume

Personally I like this better than the linear subtraction method (which Famitracker used in some very old versions), and by avoiding 0 it avoids some unpleasant drop-out-- going from 1 to 0 is a strong audible signal, so in most cases I'd prefer "imprecise"/flattened envelopes at volume 1 instead of instruments that start dropping to 0 early/erratically. That's a subjective call thought.

You can use the same kind of operation a second time to apply a global volume fade down.


As for the suggestion to use duty to approximate a filtering of the timbre, I doubt you'll find that satisfying. A change of timbre like that really stands out, and their harmonics are so different from one another. If anything I think the narrow pulse sounds much "quieter" than the square, not the other way around... but either way I think the change of tone will sound abrupt and do the opposite of what you want (i.e. you want the tone to "fade" from notice, not become more noticeable).

It's very easy to try these things out in famitracker though, you can put in some music and just change the duty or volume row by row to see how it would sound. You don't have to actually build the system to test the concept.
Re: Smooth fadeout
by on (#206815)
Admittedly, i've only gotten it to work circumstantially and manually as a method to improve echoes and a few tail-offs. It might be that a too general application is way too noticeable, as the duty steps are very coarse on the APU squares. The noise should be easier, if you find you still need a way to mask it post-volume calculations.
Re: Smooth fadeout
by on (#206824)
An echo with a change of duty works great, but I think that's precisely because it sounds discontinuous with the rest of the sound, a fresh new voice entering.
Re: Smooth fadeout
by on (#206825)
That's a good point. Also, some instruments will drop their brighter harmonics as you blow softer, which one briefly would at a transition towards note-off. I suppose tampering with the harmonics is (best case) more akin to having an orchestra play quieter until they're silent, rather than doing a fadeout on a mixer.
Re: Smooth fadeout
by on (#206952)
I did a quite simple approach now:

Every few frames, I increment a counter. And that counter is also the value that I subtract from each volume value. (Unless the volume value is already smaller than the counter, then it gets set to 0 of course.)

But for pulse 1 and 2, if the original volume value is not 0, but the subtracted value is 0, then I set it back to 1.
This way, you always hear something until the end and don't have to deal with separate sounds between two gaps of silence.

(Important:
Really only set the new volume from 0 to 1 if the original value was non-zero. Because at least in FamiTone, setting it to 1 if nothing is supposed to be on the channel and the volume is originally 0 means that you might hear some artifact sounds in certain situations.
Never set an original volume of 0 to non-zero since you don't know what garbage data the sound library has in its variables that get muted out due to the volume of 0.)

The if value = 0 then value = 1 method is not done for the noise channel, though because this would mean hearing some unpleasant buzzing.

And triangle channel is immediately disabled right at the beginning.