I'll answer very shortly and broadly without even looking at the code since I'm a bit tired today and will later on make it more precise if needed.
As is known, I measure duration it takes for the hammer to pass through the last two sensor points. From that duration one can calculate velocity. The school formula is:
distance = velocity x time
The distance is the escapement length (because you would like to place the second sensor as close as possible to escapement point, in order to have more time for the hammer to reach the last sensor, i.e. the rail), so the formula becomes:
velocity = escapement distance / duration
I remember I found some paper that measured the hammer velocity of a Yamaha Disclavier and the corresponding MIDI velocity and they deduced that:
MIDI velocity = 57.96 + 71.3 * log10(hammer_velocity_ms)
So, it appeared that translation between hammer velocity in m/s
and MIDI velocity 1-127
is a logarithmic one.
This meant that for ever hammer strike I have to do a mathematical division, logarithm, multiplication and addition. That's a lot when you seek performance in a tiny controller which is why I decided to create a precalculated table in memory that would store the corresponding MIDI velocity for every duration. This way I'm not calculating anything in runtime, instead I have a duration in microseconds and lookup the value in the c corresponding array index. This is a bit risky because it can potentially take a lot of memory space but I made some measurements which luckily I keep and I'm copy-pasting them:
Measured duration in microseconds:
12187us -> produces MIDI value of 2 when escapement is 2mm
9140us -> produces MIDI value of 2 when escapement is 1.5mm
6094us -> produces MIDI value of 2 when escapement is 1mm
It meant that even if the escapement is long at 2mm, I'd still have a table with a maximum of 12187 elements which would fit in the Teensy RAM 🙂 However things were not that simple, see below...
By changing the factor in the formula above (71.3) you would map the usable velocity region within the proper range of 127 values, and the addition coefficient (57.96) determines the absolute position of the produced values within the range.
Basically I experimented with Garritan CFX until I found what felt good. The values are in GitHub.
Now, as I said things were not that simple 🙂 I noticed that with just using that static formula the higher notes would produce unnaturally high velocities. I gave it some thought and finally concluded that that the higher you go, the less heavy the hammer is (at least in my action) and thus it's easier to throw it with a high velocity. However it's also lighter, so the higher velocity is negated by the lower hammer mass (and shorter strings) to produce balanced tone which is why it turned out I had to compensate on a per key basis. Meaning that I had to create a separate array for every hammer. But there's not enough RAM to store 88 tables as the one above, so instead I created groups. Luckily most of the hammers in the left half of the keyboard seemed to work with the same coefficient and could reuse the same table. If I'm not mistaken I ended up with 12 groups.