|
| | TapeMachineT () |
| void | prepare (double sample_rate) |
| | Sizes both delay lines for their worst configured case and designs one gap-loss FIR per legal speed, so nothing in this file allocates later.
|
| void | reset () |
| | Never allocates.
|
| void | set_archetype (Archetype archetype) |
| | Applies the full baked preset.
|
| Archetype | archetype () const noexcept |
| void | set_speed_ips (double ips) |
| | Snapped to the nearest legal speed for the active archetype.
|
| double | speed_ips () const noexcept |
| void | set_eq_curve (Curve curve) |
| | Independent of archetype on purpose: a Studer really could be told to run NAB, and the resulting low-end mismatch is the point.
|
| Curve | eq_curve () const noexcept |
| void | set_bias (float bias_c) |
| double | effective_bias () const noexcept |
| | The bias actually in force: what was requested plus the age table's drift, clamped.
|
| void | set_drive (float drive01) |
| double | drive () const noexcept |
| void | set_age (float age01) |
| double | age () const noexcept |
| void | set_crosstalk_db (float db) |
| double | crosstalk_db () const noexcept |
| void | set_companding_enabled (bool enabled) noexcept |
| bool | companding_enabled () const noexcept |
| void | set_print_through (float level_db, float offset_ms, bool pre_echo_enabled) |
| | level_db is the post-echo level before bias erasure; offset_ms is the wrap offset; pre_echo_enabled adds the lookahead and the latency that goes with it.
|
| double | print_through_db () const noexcept |
| double | print_offset_ms () const noexcept |
| bool | pre_echo_enabled () const noexcept |
| void | set_mix (float wet01) noexcept |
| void | process (const SampleType *in_l, const SampleType *in_r, SampleType *out_l, SampleType *out_r, int n_frames) |
| int | latency_samples () const noexcept |
| | Reported exactly, never estimated (series law 5).
|
| double | worst_case_insertion_gain () const noexcept |
| | The largest gain any single stage in this insert can present, as a bound a reviewer can size headroom against.
|
| const TapeEqSection & | record_eq (int channel=0) const noexcept |
| const TapeEqSection & | playback_eq (int channel=0) const noexcept |
| const std::vector< double > & | gap_fir () const noexcept |
| | Coefficients of this instance's reproduce gap-loss FIR at the active speed, so the suite can measure its response without rendering.
|
| double | reproduce_gap_m () const noexcept |
| | The reproduce gap width in force, in metres.
|
| double | reproduce_alignment_db () const noexcept |
| | How much reproduce gain the machine is currently aligned with, in dB.
|
| double | sample_rate () const noexcept |
|
| static constexpr double | kBiasMin = -1.0 |
| | Bias −1 (under) .. 0 (manufacturer optimum) .. +1 (over).
|
| static constexpr double | kBiasMax = 1.0 |
| static constexpr double | kBiasDefault = 0.0 |
| static constexpr double | kOverBiasHfCutDb = 6.0 |
| | Over-bias darkens; under-bias brightens.
|
| static constexpr double | kUnderBiasHfBoostDb = 4.0 |
| | [design parameter] default 4 dB per unit, range 2–8.
|
| static constexpr double | kBiasShelfHz = 8000.0 |
| | [design parameter] default 8000 Hz, range 5000–10000.
|
| static constexpr double | kBiasDistK = 0.6 |
| | A parabola in bias, centred on optimum: minimum distortion at 0, rising either side.
|
| static constexpr double | kBiasErasureDb = 3.0 |
| | Over-bias erases more of whatever has already printed through — the documented direction.
|
| static constexpr double | kDriveDbMax = 24.0 |
| | Drive maps [0, 1] onto this dB span, which is then multiplied by the bias parabola.
|
| static constexpr double | kDriveDefault = 0.3 |
| static constexpr double | kCrosstalkDbMin = -45.0 |
| | Crosstalk bounds are the union of the per-archetype ranges.
|
| static constexpr double | kCrosstalkDbMax = -22.0 |
| static constexpr double | kCrosstalkTiltHz = 5000.0 |
| | Inter-channel coupling worsens with frequency (documented general behaviour of inductive/capacitive coupling), so the leak is tilted up above a corner and shelf-limited so it can never exceed crosstalk_db + kCrosstalkTiltMaxDb at any frequency.
|
| static constexpr double | kCrosstalkTiltMaxDb = 6.0 |
| | [design parameter] default +6 dB total, range 4–9 dB.
|
| static constexpr double | kPrintThroughDbMin = -80.0 |
| | Print-through level bounds.
|
| static constexpr double | kPrintThroughDbMax = -38.0 |
| static constexpr double | kPrintThroughOffsetMsDefault = 700.0 |
| | Wrap offset between the printing layer and the printed one.
|
| static constexpr double | kPrintThroughOffsetMsMin = 200.0 |
| static constexpr double | kPrintThroughOffsetMsMax = 2000.0 |
| static constexpr double | kPreEchoAsymmetryDb = 4.0 |
| | Pre-echo is documented as the weaker of the two in the tails-out storage convention professionals use.
|
| static constexpr double | kAlignmentCeilingDb = 24.0 |
| | Ceiling on the reproduce alignment gain, in dB.
|
| static constexpr double | kInstabilityNominalMs = 2.0 |
| | Nominal read offset of the wow/flutter delay line, in ms.
|
template<typename SampleType = float>
class pulp::signal::TapeMachineT< SampleType >
The tape machine insert.
template<typename SampleType = float>
Reported exactly, never estimated (series law 5).
The sum of the two constant delays this block cannot avoid — the reused oversampling wrap and the wow/flutter line's nominal read offset — plus the print-through offset when pre-echo is enabled. Constant across archetype, curve, speed and age by construction.
template<typename SampleType = float>
How much reproduce gain the machine is currently aligned with, in dB.
A real deck is aligned by playing a calibration tape and setting the reproduce amplifier so the alignment tone reads 0 VU. Without doing the same thing here this insert would be unusable, and the number says why: the reused Wallace model at cassette speed and mid age loses 19.7 dB at 1 kHz (spacing 12 µm at 4.7625 cm/s gives −13.7 dB, coating thickness another −6.0 dB). That model was tuned as delay-loop character, where per-repeat darkening IS the effect and absolute level is set by the feedback control. An insert has no such control, and a twenty-decibel hole at 1 kHz is not tape character — it is a broken insert.
So the loss the playback chain will apply at kEqReferenceHz is computed from the SHIPPED filters actually in the path — the reused tier's fitted IIR tilt, its gap FIR, and this module's own reproduce-gap FIR — and undone. Not measured by rendering, and not a fudge factor: it is the closed-form magnitude of the three stages at one frequency.
The gain sits AFTER the loss stage, so it lifts the reused hiss along with the programme. That is correct rather than unfortunate: a real reproduce amplifier amplifies head noise too, which is exactly why companding exists.
template<typename SampleType = float>
The largest gain any single stage in this insert can present, as a bound a reviewer can size headroom against.
There is no feedback path here, so Forge's worst_case_gain field does not apply and the catalog row says so. This is the useful number instead, and it is measured on the REALIZED sections over the whole (curve × frequency) grid to Nyquist — not on the analog prototype, which is where an earlier version of this got it wrong by 8 dB: the prototype has no equivalent of the damped inverse's behaviour at Nyquist, so a bound derived from it is not a bound.
It is a large number, and the reason is structural rather than a defect to tune away. A reproduce network for a curve with no bass shelf is (1 + s·t₂), a differentiator, so its gain climbs 6 dB per octave to the top of the band and its ceiling therefore rises with sample rate. The programme never sees it: the record network is that section's exact reciprocal, so anything arriving at the reproduce network has already been attenuated by the same amount, and the pair is unity. What DOES see it is whatever was injected in between — the reused hiss generator, which is lowpassed at 4 kHz and so has almost nothing up there. The suite asserts the consequence directly by bounding the module's output on silence and on a full-scale input.
template<typename SampleType = float>
Drive maps [0, 1] onto this dB span, which is then multiplied by the bias parabola.
The floor is 0 dB so drive = 0 leaves the hysteresis stage at unity input — the condition an EQ measurement needs. [design parameter] default span 0 .. +24 dB, range 12–36 dB at the top.
template<typename SampleType = float>
Nominal read offset of the wow/flutter delay line, in ms.
Must exceed the deepest modulation the reused age table can ask for — 1.2 ms of wow plus 0.4 ms × (1 + 0.5 + 0.25) of the flutter harmonic stack = 1.9 ms — and is held CONSTANT so latency_samples() never moves with archetype, curve, speed or age. [design parameter] default 2.0 ms, range 1.9–5.0 ms.
template<typename SampleType = float>
Over-bias darkens; under-bias brightens.
Both are documented DIRECTIONS with no citable closed-form transfer curve — commercial treatments reach for full Preisach/Stoner-Wohlfarth simulation, which is out of scope for an insert. Honest gap: these three constants encode the direction and rough magnitude of a documented trade, not any machine's measured curve. [design parameter] default 6 dB per unit, range 3–10.
template<typename SampleType = float>
Wrap offset between the printing layer and the printed one.
The real offset depends on reel radius, tape thickness and wind position and genuinely varies through a reel; a single representative constant is an honest simplification, not a measured value. [design parameter] default 700 ms, range 200–2000 ms.