Open chriscoomber opened 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:
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.
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
andL
byL = 40 log (v/127)
.From looking at TSF's code, it maps between
x
andL
byL = 20 log (x)
(seetsf_gainToDecibels
).It's the user of TSF's job to implement the map between
v
andx
. If we want to conform to the GM recommendations, we need40 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 callingtsf_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 mapx = (v/16383)^3
inTCMC_SET_VOLUME
(herev
is between 0 and 16383).