Finding Notes Between the Samples
Slide position 47mm gives you G4. Position 62mm gives you F4. But what lives at position 54mm?
The cheap plastic whistle doesn’t care — it produces whatever frequency the physics demands. But early digital synthesizers couldn’t afford that luxury. Computing a frequency from first principles meant logarithms, and logarithms meant cycles, and cycles meant latency. The solution that emerged in the late 1970s was table lookup with interpolation: store a handful of known values, then estimate everything between them.
The maths are pleasantly simple. If you know two points and where you stand between them, you blend:
result = low_value + (high_value - low_value) × fraction
Forth programmers building MIDI gear in the 1980s kept these tables in ROM, because RAM was expensive and frequencies don’t change. The stack-based approach makes the interpolation almost visual — you can watch the values climb and combine:
: LERP ( frac lo hi -- result )
OVER - \ frac (hi-lo)
ROT \ (hi-lo) frac
255 */ \ scale frac from 0-255 to actual delta
+ ; \ add to lo
CREATE FREQ-TABLE 262 , 294 , 330 , 349 , 392 , 440 , 494 , 523 ,
\ C4 through C5 in Hz, stored as cells
: NOTE-FREQ ( note -- freq )
DUP 7 AND CELLS FREQ-TABLE + @ \ low sample
OVER 1+ 7 AND CELLS FREQ-TABLE + @ \ high sample
ROT 8 RSHIFT 255 AND \ fractional part
-ROT LERP ;
Kotlin inherits none of this austerity, but the algorithm remains identical. Modern JVM code just wraps it in types:
fun lerp(fraction: Double, low: Double, high: Double) =
low + (high - low) * fraction
val freqTable = doubleArrayOf(262.0, 294.0, 330.0, 349.0, 392.0, 440.0, 494.0, 523.0)
fun noteFreq(position: Double): Double {
val index = position.toInt().coerceIn(0, freqTable.lastIndex - 1)
val frac = position - index
return lerp(frac, freqTable[index], freqTable[index + 1])
}
println(noteFreq(2.5)) // 339.5 Hz — halfway between E4 and F4
The Yamaha DX7 used this. So did the Ensoniq Mirage. Eight kilobytes of ROM could hold enough samples that linear interpolation sounded acceptable — not perfect, but close enough that the ear forgave the shortcuts.
My slide whistles, naturally, need no forgiveness. They just are the continuous function. But when I mark positions on the slide with a fine-point pen — G4 here, F4 there — I’m building a lookup table in physical space. The whistle interpolates for free; the pen marks are for my fingers, which don’t.