Volume curve
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).
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:
- 1 second sawtooth at 127 velocity followed 1 second at 64, then a control message VOLUME_MSB to 50% and the same notes again
- Then export the generated 4 second PCM to a WAV from TSF and bass midi or fluidsynth.
- Compare the generated audio waves
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.