schellingb / TinySoundFont

SoundFont2 synthesizer library in a single C/C++ file
MIT License
608 stars 71 forks source link

Volume curve #53

Open chriscoomber opened 3 years ago

chriscoomber commented 3 years ago

I've been looking into volume curves recently, because my implementation of a MIDI player using TSF didn't sound quite right. In the end I was using a linear curve to map MIDI velocity (0-127) to the velocity (0-1) in tsf_note_on. It turns out that a quadratic curve is a better fit.

This led me to do some maths that a) I'd like to share in case anyone else is worrying about this, and b) I think it would be useful to use a quadratic curve in your examples (e.g. here), and also here.

A MIDI note has a velocity v between 0 and 127 (int).
TSF asks for a velocity x between 0 and 1 (float).
In the final output, we get a volume L measured in decibels between -∞ and 0 (this is the difference in decibels from full volume, i.e. 0dB is full volume).

According to the GM recommendations (bottom of page 9) the recommendation is to map between v and L by L = 40 log (v/127).

From looking at TSF's code, it maps between x and L by L = 20 log (x) (see tsf_gainToDecibels).

It's the user of TSF's job to implement the map between v and x. If we want to conform to the GM recommendations, we need 40 log (v/127) = 20 log (x), i.e. x = (v/127)^2.

Therefore, I recommend that people using TSF use the mapping x = (v/127)^2 when calling tsf_note_on or similar functions. It makes sense to me to default to conforming to the GM recommendations.

I think it would be good to update the examples to use this map, rather than x = v/127 (which I see in example3.c). Also, I wonder if you should use a quadratic map rather than the cubic map x = (v/16383)^3 in TCMC_SET_VOLUME (here v is between 0 and 16383).

schellingb commented 3 years ago

Whoa, thank you so much for this post.

The comment in TMC_SET_VOLUME

//Raising to the power of 3 seems to result in a decent sounding volume curve for MIDI

explains about how much my understanding of this is.

Your description makes a lot of sense though and I'll happily incorporate your recommendations.

To verify things a bit, what do you think would be a good test MIDI to generate, and against what player should we compare? For example:

chriscoomber commented 3 years ago

Honestly my knowledge yesterday is about what yours is. All I did was by ear compare some simple piano notes of different velocities against the stock Android MIDI player, which is far from thorough. I found that the quadratic curve seems to match the stock Android MIDI player the best, and I think the maths explains why. I did find I needed to boost the global volume a little in tsf_set_output as well, to match that particular MIDI player (I settled on about 15dB). I haven't tested against other MIDI players though.

Your suggestion sounds sensible, I think that's about all I can contribute though.