|
DSPark 1.6.1
Header-only audio DSP framework in pure C++20 — zero dependencies
|
Physical tape machine: JA hysteresis, EQ standards, losses, transport. More...
#include "../Core/AudioBuffer.h"#include "../Core/AudioSpec.h"#include "../Core/Biquad.h"#include "../Core/DenormalGuard.h"#include "../Core/DspMath.h"#include "../Core/Hysteresis.h"#include "../Core/Oversampling.h"#include "../Core/SimdOps.h"#include "../Core/StateBlob.h"#include <algorithm>#include <atomic>#include <cmath>#include <cstddef>#include <cstdint>#include <memory>#include <numbers>#include <vector>

Go to the source code of this file.
Classes | |
| class | dspark::TapeMachine< T > |
| Reel-to-reel tape emulation with physical hysteresis and transport. More... | |
Namespaces | |
| namespace | dspark |
| Main namespace for the DSPark framework. | |
Physical tape machine: JA hysteresis, EQ standards, losses, transport.
A complete record/playback chain built around the Jiles-Atherton hysteresis core (Core/Hysteresis.h), modelling the five things that make tape sound like tape:
bias maps to carrier amplitude: under-bias drops below the erase threshold (real grit and level instability), over-bias adds the self-erasure roll-off of short wavelengths. Gain is calibrated per setting through the same biased chain, so drive changes saturation, not level.The dry path of the mix control is delay-compensated to getLatency(). The mix is applied per block without smoothing: the wet stream is correlated with and aligned to the dry, so a mix step moves the output by the (small) timbral difference only - measured below the steady-state sample delta. Channels beyond the prepared count pass through untouched.
Threading model: parameter setters/getters are std::atomic based and safe from any thread (non-finite values are ignored; parameter changes are published with a release store and consumed at the next block). prepare() is setup-thread only (allocates; invalid specs are ignored and an unprepared instance passes audio through). reset() belongs to the stream owner. getState()/setState() are setup/UI threads. The recompute on a parameter change runs a short scratch calibration on the audio thread (allocation-free, a few thousand samples of model time).
Cost: the push-pull biased core runs two JA solvers at the active oversampling factor (4x default), ~11% of one core at 48 kHz stereo on a desktop CPU at 4x - the price of physical AC bias (history-independent LF response, exact even-harmonic cancellation, ultrasonic residue below -75 dB). A SIMD lane-parallel JA (2 channels x 2 polarities) is the noted future optimisation if a lighter budget is ever needed.
Oversampling transparency (RF-009/ADR-011). setOversampling(int) (setup thread only; it reallocates and re-calibrates like prepare()) selects the internal factor in {1,2,4,8,16}. DEFAULT 4. The AC-bias carrier is always 0.375 * the internal rate, so the factor sets how far the carrier sits above the audio band: at 4x/48k it is ultrasonic (72 kHz) and its even folds die in the downsampler; CPU scales ~linearly with the factor. factor = 1 means OFF - no internal resampling and zero added oversampler latency, but then the carrier lands at 0.375 * the base rate (18 kHz at 48k), i.e. IN-BAND, so 1x is only sensible when the host runs a high sample rate or the surrounding chain is already oversampled (the AC-bias model needs ultrasonic headroom). getLatency() always reflects the active factor (oversampler group delay + loss FIR + transport delay), so hosts get correct PDC.
Dependencies: Core/Hysteresis.h, Core/Oversampling.h, Core/Biquad.h, Core/SimdOps.h, Core/AudioSpec.h, Core/AudioBuffer.h, Core/DspMath.h, Core/DenormalGuard.h, Core/StateBlob.h.
Definition in file TapeMachine.h.