The Grid Curve Lives in 256 Bytes
A 12AX7 doesn’t amplify linearly. Feed it a perfect sine wave and you get back something slightly squashed — the transfer curve bends at the extremes, compressing peaks into soft saturation. This nonlinearity is where the “warmth” comes from. Second-order harmonics. Musical distortion.
Modelling this in software means evaluating transcendental functions: hyperbolic tangent, exponential decay, polynomial approximations of measured tube curves. In 1985, a single tanh() call might cost hundreds of CPU cycles. Multiply that by 44,100 samples per second, per channel, and you’ve burned your entire compute budget on one effect.
The solution: compute once, store forever.
A lookup table pre-calculates every possible output for a quantized input range. If your input samples are 8-bit (0–255), you need exactly 256 entries. Index directly. No multiplication, no division, no transcendentals. One memory fetch.
# Ruby: build a soft-clipping LUT modelling tube saturation
lut = Array.new(256) do |i|
x = (i - 128) / 128.0 # normalize to -1..1
(Math.tanh(x * 2.0) * 127 + 128).round
end
# apply to a sample
input_sample = 200
output_sample = lut[input_sample]
puts "#{input_sample} -> #{output_sample}" # 200 -> 231
The C version shows why this mattered on an 8 MHz processor:
#include <stdio.h>
#include <math.h>
static unsigned char lut[256];
void build_lut(void) {
for (int i = 0; i < 256; i++) {
double x = (i - 128) / 128.0;
lut[i] = (unsigned char)lround(tanh(x * 2.0) * 127 + 128);
}
}
int main(void) {
build_lut();
unsigned char in = 200, out = lut[in];
printf("%d -> %d\n", in, out);
return 0;
}
The trade-off is precision versus memory. An 8-bit LUT gives you 256 possible outputs — coarse, but often sufficient for audio where the ear forgives quantization that would be obvious visually. A 16-bit LUT needs 65,536 entries. A 24-bit table would require more RAM than most 1980s machines had total.
Interpolation helps: store fewer points, lerp between neighbours. You trade a multiply-add for memory savings. Whether that’s worthwhile depends on cache architecture, which is why LUT design became its own micro-discipline.
I spent this evening staring at load-line diagrams for the EL84 pentode. Plate voltage on one axis, plate current on the other, a family of curves for different grid voltages. Each curve is a transfer function waiting to become a lookup table. The tube computes it in heated glass. The software stores it in cold silicon.