46inline constexpr std::array<double, 5>
kSpeedsIps = {1.875, 3.75, 7.5, 15.0, 30.0};
82 if (speed_ips >= 22.5)
return {0.0, 17.5e-6};
83 if (speed_ips >= 11.25)
return {0.0, 35
e-6};
89 if (speed_ips >= 22.5)
return {0.0, 17.5e-6};
90 return {3180
e-6, 50
e-6};
107 auto magnitude = [&tc](
double f) {
109 const double numerator = tc.has_bass_shelf() ? std::hypot(1.0, w * tc.bass_s) : 1.0;
110 return numerator / std::hypot(1.0, w * tc.treble_s);
112 return 20.0 * std::log10(magnitude(hz) / magnitude(
kEqReferenceHz));
141 if (speed_ips >= 11.25)
return {3.5e-6, 10
e-6};
142 if (speed_ips >= 2.8125)
return {5
e-6, 12
e-6};
143 return {0.8e-6, 3
e-6};
159 if (x < 1
e-9)
return 1.0;
160 return std::abs(std::sin(x) / x);
163inline double gap_loss_db(
double hz,
double gap_m,
double speed_ips)
noexcept {
168inline double gap_null_hz(
double gap_m,
double speed_ips)
noexcept {
179inline constexpr std::array<double, 3>
kAgeAxis = {0.0, 0.5, 1.0};
192 std::clamp(age01, 0.0, 1.0));
197 std::clamp(age01, 0.0, 1.0));
213 double sample_rate)
noexcept {
214 const double fc = std::clamp(corner_hz, 0.1, 0.49 * sample_rate);
216 const double g = t / (1.0 + t);
218 const std::complex<double> z_inv(std::cos(theta), -std::sin(theta));
219 const std::complex<double> lp = g * (1.0 + z_inv) / (1.0 - (1.0 - 2.0 * g) * z_inv);
220 const std::complex<double> shelf = gain_linear + (1.0 - gain_linear) * lp;
221 return 20.0 * std::log10(std::max(std::abs(shelf), 1
e-12));
260 p.legal_speeds_ips = {7.5, 15.0, 30.0};
261 p.legal_speed_count = 3;
262 p.default_speed_ips = 15.0;
264 p.crosstalk_db = -40.0;
265 p.companding =
false;
269 p.legal_speeds_ips = {1.875, 0.0, 0.0};
270 p.legal_speed_count = 1;
271 p.default_speed_ips = 1.875;
273 p.crosstalk_db = -30.0;
282 p.legal_speeds_ips = {7.5, 15.0, 0.0};
283 p.legal_speed_count = 2;
284 p.default_speed_ips = 15.0;
286 p.crosstalk_db = -38.0;
287 p.companding =
false;
298 double best_distance = std::abs(std::log(std::max(requested_ips, 1
e-6) / best));
304 const double distance = std::abs(std::log(std::max(requested_ips, 1
e-6) / candidate));
305 if (distance < best_distance) {
307 best_distance = distance;
371 const double fs = sample_rate > 0.0 ? sample_rate : 48000.0;
372 const double treble_hz =
374 const double u_treble = std::tan(
chardelay::kPi * treble_hz / fs);
376 double b0 = 0.0, b1 = 0.0;
377 const double a1 = (u_treble - 1.0) / (u_treble + 1.0);
378 if (tc.has_bass_shelf()) {
379 const double bass_hz =
382 const double scale = (u_treble / u_bass) / (u_treble + 1.0);
383 b0 = scale * (u_bass + 1.0);
384 b1 = scale * (u_bass - 1.0);
386 b0 = u_treble / (u_treble + 1.0);
390 normalize(b0, b1, a1, fs);
393 const double inv = 1.0 / b0;
395 const double new_b0 = inv;
396 const double new_b1 = a1 * inv;
397 const double new_a1 = damping * b1 * inv;
400 normalize(b0, b1, new_a1, fs);
417 const double y = b0_ * x + b1_ * x1_ - a1_ * y1_;
425 double response_db(
double hz,
double sample_rate)
const noexcept {
426 return 20.0 * std::log10(std::max(magnitude(b0_, b1_, a1_, hz, sample_rate), 1
e-12));
430 static double magnitude(
double b0,
double b1,
double a1,
double hz,
431 double sample_rate)
noexcept {
433 const double c = std::cos(theta), s = std::sin(theta);
435 const double num = std::hypot(b0 + b1 *
c, -b1 * s);
436 const double den = std::hypot(1.0 + a1 *
c, -a1 * s);
437 return den > 1
e-30 ? num / den : 0.0;
440 static void normalize(
double& b0,
double& b1,
double a1,
double fs)
noexcept {
448 double b0_ = 1.0, b1_ = 0.0, a1_ = 0.0;
449 double x1_ = 0.0, y1_ = 0.0;
468template <
typename SampleType =
float>
485 for (
auto* follower : {&encode_env_, &decode_env_}) {
486 follower->prepare(
static_cast<SampleType
>(sample_rate));
487 follower->set_attack_ms(
static_cast<SampleType
>(
kAttackMs));
488 follower->set_release_ms(
static_cast<SampleType
>(
kReleaseMs));
500 double encode(
double x)
noexcept {
return x * gain(encode_env_, x, -0.5); }
503 double decode(
double x)
noexcept {
return x * gain(decode_env_, x, +1.0); }
507 const auto envelope =
508 static_cast<double>(follower.process(
static_cast<SampleType
>(x)));
513 BallisticsFilterT<SampleType> encode_env_{};
514 BallisticsFilterT<SampleType> decode_env_{};
515 double reference_ = 0.1;
Peak/RMS envelope follower with independent attack and release curves.
Envelope follower with configurable attack and release times.
Definition ballistics_filter.hpp:31
The 2:1 / 1:2 linear-dB compander pair.
Definition tape_machine_components.hpp:469
static constexpr double kReleaseMs
Definition tape_machine_components.hpp:477
static constexpr double kRefLevelDbfs
Definition tape_machine_components.hpp:482
static constexpr double kAttackMs
Definition tape_machine_components.hpp:474
double decode(double x) noexcept
Playback side: 1:2 expansion, exact inverse slope.
Definition tape_machine_components.hpp:503
double encode(double x) noexcept
Record side: 2:1 compression toward the reference.
Definition tape_machine_components.hpp:500
void reset() noexcept
Definition tape_machine_components.hpp:494
void prepare(double sample_rate)
Definition tape_machine_components.hpp:484
One first-order EQ section — a (curve, speed) record network, or its exact inverse.
Definition tape_machine_components.hpp:341
void set(const tape::EqTimeConstants &tc, double sample_rate, bool inverse) noexcept
Definition tape_machine_components.hpp:370
static constexpr double kInversePoleDamping
How far inside the unit circle the inverse's pole is pulled when the forward section's numerator is (...
Definition tape_machine_components.hpp:364
void reset() noexcept
Definition tape_machine_components.hpp:411
double response_db(double hz, double sample_rate) const noexcept
Magnitude response in dB.
Definition tape_machine_components.hpp:425
static constexpr double kPrewarpCeiling
Highest corner the prewarp will place, as a fraction of the sample rate.
Definition tape_machine_components.hpp:368
double process(double x) noexcept
Definition tape_machine_components.hpp:416
constexpr double kPi
Definition primitives.hpp:38
double interpolate_knots(const std::array< double, N > &axis, const std::array< double, N > &values, double c) noexcept
Definition primitives.hpp:125
Published tables and closed forms, free of any instance so a test can assert the SHIPPED constant rat...
Definition tape_machine_components.hpp:41
EqTimeConstants eq_time_constants(TapeCurve curve, double speed_ips) noexcept
The published time-constant table, selected by (curve, speed).
Definition tape_machine_components.hpp:74
HeadGapGeometry head_gap_geometry(double speed_ips) noexcept
Gap widths by speed class.
Definition tape_machine_components.hpp:140
constexpr double kMetresPerInch
Inches to metres. Exact by definition of the international inch.
Definition tape_machine_components.hpp:49
constexpr double kEqReferenceHz
The frequency every EQ curve in this module is normalized to be unity at.
Definition tape_machine_components.hpp:55
double age_bias_drift(double age01) noexcept
Definition tape_machine_components.hpp:195
constexpr std::array< double, 3 > kAgeAxis
The reused kAgeAxis table drives spacing, hiss, chew and degrade, and kTapeAxis drives wow/flutter de...
Definition tape_machine_components.hpp:179
constexpr std::array< double, 3 > kAgePrintThroughDb
Storage print grows with age.
Definition tape_machine_components.hpp:183
constexpr std::array< double, 5 > kSpeedsIps
The halving speed ladder, in inches per second.
Definition tape_machine_components.hpp:46
double eq_record_response_db(const EqTimeConstants &tc, double hz) noexcept
The analog record (pre-emphasis) prototype H(s) = (1 + s·t₁)/(1 + s·t₂), in dB, normalized to 0 dB at...
Definition tape_machine_components.hpp:106
double first_order_shelf_db(double corner_hz, double gain_linear, double hz, double sample_rate) noexcept
Magnitude, in dB, of the REALIZED chardelay::FirstOrderShelf — the TPT one-pole shelf this module use...
Definition tape_machine_components.hpp:212
double eq_playback_response_db(const EqTimeConstants &tc, double hz) noexcept
The playback (de-emphasis) network is the exact reciprocal of the record network, so its response is ...
Definition tape_machine_components.hpp:117
double snap_speed_ips(TapeArchetype archetype, double requested_ips) noexcept
Nearest legal speed for an archetype.
Definition tape_machine_components.hpp:295
double gap_loss_magnitude(double hz, double gap_m, double speed_ips) noexcept
Westmijze's reproduce gap loss: |sin(π·g/λ) / (π·g/λ)|, λ = v/f.
Definition tape_machine_components.hpp:157
double gap_null_hz(double gap_m, double speed_ips) noexcept
First extinction null, where λ = g: f = v / g.
Definition tape_machine_components.hpp:168
double speed_m_per_s(double speed_ips) noexcept
Tape speed in metres per second.
Definition tape_machine_components.hpp:147
double gap_loss_db(double hz, double gap_m, double speed_ips) noexcept
Definition tape_machine_components.hpp:163
ArchetypePreset archetype_preset(TapeArchetype archetype) noexcept
Baked defaults per §5 of the source spec.
Definition tape_machine_components.hpp:256
double eq_corner_hz(double tau_s) noexcept
Corner frequency of a time constant, in Hz. f = 1 / (2π·t).
Definition tape_machine_components.hpp:95
double age_print_through_db(double age01) noexcept
Definition tape_machine_components.hpp:190
constexpr std::array< double, 3 > kAgeBiasDriftUnits
An aging bias supply drifts toward OVER-bias — the documented failure direction, since oscillator com...
Definition tape_machine_components.hpp:188
T linear_to_db(T linear)
Linear gain → amplitude dB, floored at kDbFloorAmplitude so silence maps to a finite −200 dB rather t...
Definition units.hpp:79
T db_to_linear(T db)
Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
Definition units.hpp:71
Definition additive_bank.hpp:218
TapeCurve
Which recording standard's record/playback network pair is in force.
Definition tape_machine_components.hpp:25
@ iec_ccir
IEC/CCIR reel. Treble turnover only — no bass shelf.
Definition tape_machine_components.hpp:27
@ cassette_type1
Compact cassette, ferric ("Normal"). 3180 / 120 µs.
Definition tape_machine_components.hpp:28
@ cassette_type2
Compact cassette, chrome. 3180 / 70 µs.
Definition tape_machine_components.hpp:29
@ nab
NAB reel. Bass turnover AND treble turnover.
Definition tape_machine_components.hpp:26
@ c
Definition spectrum_trace.hpp:35
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ i
Definition formant_filter_bank.hpp:20
@ e
Definition formant_filter_bank.hpp:20
TapeArchetype
Which machine's full preset a fresh instance starts from.
Definition tape_machine_components.hpp:33
@ cassette_deck
Fixed 1.875 ips, Type I / Type II EQ switch.
Definition tape_machine_components.hpp:36
@ ampex_350_440
Documented NAB-native two-speed studio recorder lineage.
Definition tape_machine_components.hpp:34
@ studer_a800
Documented NAB and CCIR-switchable multitrack.
Definition tape_machine_components.hpp:35
The complete baked preset for one machine.
Definition tape_machine_components.hpp:228
std::array< double, 3 > legal_speeds_ips
Definition tape_machine_components.hpp:229
TapeCurve default_curve
Definition tape_machine_components.hpp:232
double crosstalk_db
Definition tape_machine_components.hpp:233
double age01
Definition tape_machine_components.hpp:235
double default_speed_ips
Definition tape_machine_components.hpp:231
bool companding
Definition tape_machine_components.hpp:234
int legal_speed_count
Definition tape_machine_components.hpp:230
One standard's pair of time constants, in SECONDS.
Definition tape_machine_components.hpp:60
double bass_s
Definition tape_machine_components.hpp:61
double treble_s
Definition tape_machine_components.hpp:62
bool has_bass_shelf() const noexcept
Definition tape_machine_components.hpp:64
Reproduce and record gap widths for one speed class, in METRES.
Definition tape_machine_components.hpp:124
double record_m
Definition tape_machine_components.hpp:126
double reproduce_m
Definition tape_machine_components.hpp:125
The real-unit vocabulary shared by every DSP block in the catalog.