Search guides and the APICtrl K
  1. API reference
  2. DSP

tape.h

Tape, modelled rather than named.

#include <sonore/tape.h>
On this page

Design notes

Every "tape saturation" this pipeline shipped was a tanh with the word tape on the faceplate. What makes tape sound like tape is not a curve, it is HYSTERESIS: the magnetisation the tape keeps depends on where it has BEEN, so the output on the way up through a value differs from the output on the way down, and the loop between them is where the compression, the softening of transients and the harmonic signature all come from. Then the playback head loses highs in a way that depends on speed, and the transport wobbles.

The hysteresis is the Jiles-Atherton model of ferromagnetism, integrated per sample the way Jatin Chowdhury does it in "Real-time physical modelling for analog tape machines" (DAFx 2019): fourth-order Runge-Kutta over each step at twice the rate. The model is dimensionless here -- saturation magnetisation Ms = 1 and the anhysteretic scale a = 1, so the paper's physical constants become alpha = 1.6e-3 * Ms/a = 0.0255 and k = 27/22 = 1.23 -- because the physical units (A/m, Tesla) buy nothing a plugin can use and cost every parameter a unit conversion. Three things the paper's equations do not say and this file had to learn:

  • The Langevin derivative 1/x^2 - 1/sinh^2(x) cancels catastrophically in float below |x| ~ 0.05: both terms are ~10^3 and the answer is 1/3. The series is used below 0.1, where its truncation error is 1e-9.
  • The irreversible term's denominator (1-c)*delta*k - alpha*(Man - M) has a ZERO at Man - M = (1-c)k/alpha, and a fast swing reaches it whenever (1-c)k is not comfortably larger than alpha. The model is well-posed only inside that region, so setHysteresis() is clamped to keep it there: a narrow loop cannot also be a nearly-reversible one, and the floor on the hysteresis rises as the width falls.
  • RK4 over one step assumes the field moves a little per step. A hot 10 kHz signal at +36 dB moves it by a hundred knee-widths per sample, so a large step is SUBDIVIDED (up to 8 sub-steps) rather than trusting the clamp on M to catch the overshoot.

The loop is normalised to UNITY small-signal gain -- the initial susceptibility c/3 / (1 - c*alpha/3) is known in closed form -- so quiet material passes at the level it came in and the compression starts at the knee. That holds while most of the magnetisation is reversible. As the hysteresis control takes the reversible share c towards 0, the initial susceptibility goes with it, but a full-scale swing does not: the irreversible part carries it, and it magnetises the loop about as far at every setting (0.12 to 0.26 per unit field). Normalised by the small-signal slope alone, hysteresis 1 came out +34 dB at full scale. So the slope the gain is taken from is never below 0.2 (kMinSusceptibility): past hysteresis 0.4 at the default width, a full-scale swing stays near unity and quiet material comes out quieter -- a mostly irreversible material barely answers a small field, which is the physics and why tape is biased. The saturation ceiling is 1/(slope * 2.5): about +4.7 dB above unity at the default reversibility, +6 dB at most. TapeSaturator divides the drive back out after the loop, so its drive knob changes how far into the loop the signal goes and not how loud it comes out; makeupDb is the volume.

Playback loss is the head model from the same paper (Kadis's spacing, thickness and gap losses: e^{-kd}, (1 - e^{-k delta})/(k delta) and sin(kg/2)/(kg/2) with k = 2 pi f / v). The whole curve slides along the frequency axis with the tape speed -- which is why 7.5 ips is dull by 10 kHz and 30 ips is not -- and that scaling law, H(f, v) = H(f/2, v/2), is what the unit test asserts. It is realised as a 65-tap linear-phase FIR designed from the magnitude by inverse Fourier sum at prepare (and on a speed change): 32 samples of latency up to 48 kHz, and above it the same span in time (about 0.67 ms, 257 taps at most). The default head geometry is CALIBRATED, not measured: real machines run record and playback EQ that this model does not carry, so the defaults are the ones that give a pro 15 ips machine's net response (-3 dB at 10 kHz, -6 dB at 20 kHz). The physics gives the shape and the speed law; the numbers give the level.

Wow is a slow sine on the transport, flutter a faster shimmer -- both are the tape's read point moving, so they are a modulated delay with the cubic tap around a 4 ms nominal position, which is reported as latency.

Included by dsp.h. Uses Oversampled<> from lofi.h and PinkNoise.

Types 2

The hysteresis loop on its own: a per-sample shaper with memory, meant to run inside Oversampled<> (its state advances at the oversampled rate, which is the rate RK4 wants). TapeSaturator does that for you.

The output is the magnetisation, and the magnetisation is what the loop REMEMBERS: when a loud signal stops, it stays where the loop left it, a standing DC offset that grows with the hysteresis (about 0.1 at 0.3 after a full-scale tone, 0.5 at 1). TapeSaturator removes it after the loop; used on its own, follow it with a DC blocker.

The whole machine, one channel: record equalisation into the hysteresis at 2x, the head's playback loss for the chosen speed, playback equalisation, wow and flutter, makeup. Two of these for stereo -- they hold state and the transports should not be shared. Rates up to 192 kHz: the transport ring is sized for 4 ms + the deepest wobble at that rate.

Enums 1

  • enumenum class TapeEq
    The record and playback equalisation standards. A tape machine does not record the signal flat: the record amplifier pre-emphasises the top (and, under NAB, cuts the bottom) and the playback amplifier undoes it, so the chain is flat and the TAPE sees the emphasised signal. That is why a slow machine softens the top when driven -- at 7.5 ips the top is 10 dB hotter on the tape than in the signal -- and why IEC at 15 ips has more high- frequency headroom than NAB: less pre-emphasis. The time constants are the standards' own (NAB: 3180 + 50 us at 7.5 and 15 ips; IEC/CCIR: 70 us at 7.5, 35 us at 15, 17.5 us at 30, no low-frequency term above 7.5; AES is the 30 ips 17.5 us), realised as exact inverse pairs around the loop.
    tape.h:369
↑↓ to move↵ to openEvery public name in the headers, and every guide