Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
forge_dynamics_catalog.hpp
Go to the documentation of this file.
1#pragma once
2
3// Dynamics — bake-layer catalog nodes.
4//
5// The home for the compressor family. It starts with the feedforward
6// (transparent/modern) design and is named for the family rather than for that
7// one member, because the VCA, FET and diode-bridge lineages that follow share
8// this file's conventions and differ only in topology and colour stage —
9// splitting them across four headers named after their circuits would scatter
10// one set of decisions across four places.
11//
12// CHANNEL COUNT IS PER MEMBER, not per family — the one thing here that is easy
13// to get wrong by pattern-matching. The feedforward design is TRUE STEREO, two
14// ports in and two out as ONE logical wire, because it genuinely couples the
15// channels: `stereo_link` feeds both detectors from the louder channel so a
16// hard-panned hit does not pull the stereo image toward centre. Instancing THAT
17// dual-mono would silently discard the link, in the direction that sounds fine
18// until someone pans something hard.
19//
20// The three lineage members below are MONO, one port in and one out, and that is
21// not an oversight: none of them has a stereo link, so a stereo instrument would
22// be exactly two independent copies — which is what instancing two nodes already
23// gives, without pretending there is a stereo image to preserve. Their DSP
24// headers say the same thing (stereo composes as two instances driven from a
25// caller-computed shared detector signal). Giving them two ports would claim a
26// coupling that does not exist in the code.
27//
28// REALIZATION vs INJECTABLE PARAM, applied per member:
29//
30// - Anything that moves `latency_samples()` is a REGISTRATION-TIME argument,
31// because a node whose reported latency changes under the audio thread
32// breaks the host's delay compensation. That is why the VCA member takes
33// its lookahead at construction.
34// - A genuine TOPOLOGY change is a realization too. The diode-bridge member's
35// feedback switch moves the detector from the input to the output, which
36// re-maps the static curve — the module ships a separate
37// `static_curve_feedback_db()` accessor precisely because the measured curve
38// is a different function. Automating that would move the measured ratio
39// under the user, so it is two registered type ids instead.
40// - An ANTIALIASING policy is a realization, following the saturator.
41// - Everything that is a coefficient is an injectable param, including the
42// stepped ones. The FET member's five ratio buttons and the VCA member's
43// negative-ratio mode are front-panel switches over one unchanged signal
44// path with invariant latency, so they inject.
45//
46// The DETECTOR MODE is an injectable param rather than a registration-time
47// realization, unlike the saturator's curve. Peak and RMS share one topology
48// and one parameter layout — the switch selects which pre-stage feeds the same
49// gain computer — so flipping it is a coefficient-level change, not a change of
50// what the node IS. It also has no effect on latency, which is what forces the
51// saturator's alias policy to be frozen at registration.
52//
53// WET ONLY: a compressor's output IS the processed signal. Parallel ("New
54// York") compression composes this with make_drywet_node(), which is the
55// correct shape for it — the dry path in that topology is the mix bus, not
56// something this node should own.
57
58#include <pulp/host/detail/forge_dynamics_catalog_descriptor.hpp>
59#include <pulp/host/detail/forge_realization_identity.hpp>
62
68
69#include <algorithm>
70#include <array>
71#include <cmath>
72#include <cstddef>
73#include <cstdint>
74
76
77// ── Stable type ids ───────────────────────────────────────────────────────
78inline constexpr const char* kFeedforwardCompressorTypeId = "dynamics.feedforward_compressor";
79
80// ── Injectable param ids ──────────────────────────────────────────────────
81// Node-local; the framework namespaces per node so two nodes never collide.
82inline constexpr state::ParamID kThresholdDb = 1; // dB
83inline constexpr state::ParamID kRatio = 2; // :1
84inline constexpr state::ParamID kKneeDb = 3; // dB
85inline constexpr state::ParamID kAttackMs = 4; // ms
86inline constexpr state::ParamID kReleaseMs = 5; // ms
87inline constexpr state::ParamID kDetectorMode = 6; // stepped 0 = peak, 1 = RMS
88inline constexpr state::ParamID kRmsWindowMs = 7; // ms
89// Param id 8 is intentionally reserved. Lookahead changes the node's latency,
90// so it is fixed by the realization factory rather than injectable automation.
91inline constexpr state::ParamID kProgramDependent = 9; // stepped 0/1
92inline constexpr state::ParamID kMakeupDb = 10; // dB
93inline constexpr state::ParamID kAutoMakeup = 11; // stepped 0/1
94inline constexpr state::ParamID kStereoLink = 12; // 0..1
95
102inline constexpr float kNodeMaxLookaheadMs = 10.0f;
103
107
116
123
124namespace true_peak {
125
126inline constexpr const char* kTypeId = "dynamics.true_peak_limiter";
127inline constexpr state::ParamID kCeilingDbtp = 1;
128inline constexpr state::ParamID kReleaseMs = 2;
129
130struct Instance {
131 static constexpr std::size_t kControlTableSize = 4097;
133 bool prepared = false;
134 std::array<double, kControlTableSize> ceiling_table{};
135 std::array<double, kControlTableSize> release_table{};
136 float last_ceiling_dbtp = -1.0f;
137 float last_release_ms = 100.0f;
138
139 static double table_value(const std::array<double, kControlTableSize>& table, double value,
140 double minimum, double maximum) noexcept;
141
142 void prepare(double sample_rate);
143
144 void set_controls(float ceiling_dbtp, float release_ms) noexcept;
145};
146
147CustomNodeType make_node(float lookahead_ms = 5.0f, bool linked = true);
148
149} // namespace true_peak
150
151// ── The VCA lineage (Blackmer/dbx) ────────────────────────────────────────
152//
153// One node type. Nothing here changes topology and nothing changes latency
154// except the lookahead, which is therefore taken at construction.
155namespace vca {
156
157inline constexpr const char* kTypeId = "dynamics.vca_compressor";
158
159// Node-local ids; the framework namespaces per node, so these numbers may
160// restart at 1 without colliding with the feedforward member's.
161inline constexpr state::ParamID kThresholdDb = 1; // dB
162inline constexpr state::ParamID kRatio = 2; // :1
163inline constexpr state::ParamID kKneeDb = 3; // dB, 0 = hard, > 0 = OverEasy
164inline constexpr state::ParamID kTimeMs = 4; // ms — ONE control, both directions
165inline constexpr state::ParamID kMakeupDb = 5; // dB
166inline constexpr state::ParamID kMix = 6; // 0..1
167inline constexpr state::ParamID kNegativeRatio = 7; // stepped 0/1 — "infinity+"
168inline constexpr state::ParamID kNegRatioAmount = 8; // :1, negative
169inline constexpr state::ParamID kCeilingDb = 9; // dB, positive magnitude
170
172
176
184
189CustomNodeType make_vca_compressor_node(float lookahead_ms = 0.0f,
190 double attack_release_k = Comp::kRatioKDefault);
191
192} // namespace vca
193
194// ── The FET lineage (1176) ────────────────────────────────────────────────
195//
196// One node type, and no realization axis at all: `latency_samples()` is a
197// constant 16 for every parameter setting, so there is nothing here that has to
198// be frozen at registration.
199namespace fet {
200
201inline constexpr const char* kTypeId = "dynamics.fet_compressor";
202
203inline constexpr state::ParamID kInputGainDb = 1; // dB — the only lever in
204inline constexpr state::ParamID kOutputGainDb = 2; // dB — makeup
205inline constexpr state::ParamID kRatio = 3; // stepped 0..4, see kRatioSteps
206inline constexpr state::ParamID kAttackUs = 4; // µs
207inline constexpr state::ParamID kReleaseMs = 5; // ms
208inline constexpr state::ParamID kKneeDb = 6; // dB
209inline constexpr state::ParamID kTransformerAmount = 7; // 0..1
210inline constexpr state::ParamID kMix = 8; // 0..1
211
217inline constexpr float kRatioSteps = 4.0f;
218
220
224
235
237
238} // namespace fet
239
240// ── The diode-bridge lineage ──────────────────────────────────────────────
241//
242// TWO node types, split on the detection topology. See the header note: moving
243// the detector from the input to the output re-maps the static curve, which is
244// a different design rather than a mode of one.
245namespace diode {
246
248inline constexpr const char* kTypeId = "dynamics.diode_bridge_compressor";
250inline constexpr const char* kFeedforwardTypeId = "dynamics.diode_bridge_compressor_feedforward";
251
252inline constexpr state::ParamID kThresholdDb = 1; // dB
253inline constexpr state::ParamID kRatio = 2; // :1, kLimitRatio and above = limit
254inline constexpr state::ParamID kKneeDb = 3; // dB
255inline constexpr state::ParamID kAttackMs = 4; // ms
256inline constexpr state::ParamID kReleaseMs = 5; // ms
257inline constexpr state::ParamID kMakeupDb = 6; // dB, positive only
258inline constexpr state::ParamID kCharacter = 7; // 0..1, bridge + transformer drive
259inline constexpr state::ParamID kMixPercent = 8; // %
260inline constexpr state::ParamID kScHpfHz = 9; // Hz — LF de-sensitisation
261inline constexpr state::ParamID kAutoRelease = 10; // stepped 0/1
262
264
268
276
284CustomNodeType make_diode_bridge_compressor_node(bool feedback = true, bool adaa = true);
285
286} // namespace diode
287
288} // namespace pulp::host::dynamics
static constexpr double kRatioKDefault
Definition vca_compressor.hpp:165
The diode-bridge compressor lineage (Neve 2254 / 33609): gain reduction by pushing control current th...
The transparent, modern compressor: a log-domain feedforward design with a soft-knee gain computer an...
The FET compressor: a FEEDBACK-topology design in the documented 1176 lineage, with a FET operated as...
Definition forge_dynamics_catalog.hpp:245
constexpr state::ParamID kThresholdDb
Definition forge_dynamics_catalog.hpp:252
constexpr state::ParamID kMakeupDb
Definition forge_dynamics_catalog.hpp:257
constexpr state::ParamID kReleaseMs
Definition forge_dynamics_catalog.hpp:256
constexpr state::ParamID kMixPercent
Definition forge_dynamics_catalog.hpp:259
signal::DiodeBridgeCompressor Comp
Definition forge_dynamics_catalog.hpp:263
constexpr state::ParamID kKneeDb
Definition forge_dynamics_catalog.hpp:254
constexpr const char * kFeedforwardTypeId
Feedforward detection — the same bridge, sensed from the input.
Definition forge_dynamics_catalog.hpp:250
constexpr const char * kTypeId
Feedback detection — the lineage's own topology, and the DSP's default.
Definition forge_dynamics_catalog.hpp:248
constexpr state::ParamID kCharacter
Definition forge_dynamics_catalog.hpp:258
constexpr state::ParamID kScHpfHz
Definition forge_dynamics_catalog.hpp:260
float diode_bridge_compressor_worst_case_gain()
Worst-case linear gain for the Forge registry (series law 8).
constexpr state::ParamID kRatio
Definition forge_dynamics_catalog.hpp:253
constexpr state::ParamID kAutoRelease
Definition forge_dynamics_catalog.hpp:261
CustomNodeType make_diode_bridge_compressor_node(bool feedback=true, bool adaa=true)
feedback picks the registered type id; adaa is the antialiasing policy, frozen at registration follow...
constexpr state::ParamID kAttackMs
Definition forge_dynamics_catalog.hpp:255
Definition forge_dynamics_catalog.hpp:199
constexpr float kRatioSteps
The ratio switch's positions, in injection order: 4:1, 8:1, 12:1, 20:1, all-buttons-in.
Definition forge_dynamics_catalog.hpp:217
float fet_compressor_worst_case_gain()
Worst-case linear gain for the Forge registry (series law 8).
signal::FetCompressor Comp
Definition forge_dynamics_catalog.hpp:219
constexpr state::ParamID kReleaseMs
Definition forge_dynamics_catalog.hpp:207
constexpr state::ParamID kTransformerAmount
Definition forge_dynamics_catalog.hpp:209
constexpr state::ParamID kMix
Definition forge_dynamics_catalog.hpp:210
constexpr state::ParamID kRatio
Definition forge_dynamics_catalog.hpp:205
constexpr state::ParamID kInputGainDb
Definition forge_dynamics_catalog.hpp:203
constexpr state::ParamID kKneeDb
Definition forge_dynamics_catalog.hpp:208
constexpr const char * kTypeId
Definition forge_dynamics_catalog.hpp:201
constexpr state::ParamID kOutputGainDb
Definition forge_dynamics_catalog.hpp:204
CustomNodeType make_fet_compressor_node()
constexpr state::ParamID kAttackUs
Definition forge_dynamics_catalog.hpp:206
Definition forge_dynamics_catalog.hpp:124
CustomNodeType make_node(float lookahead_ms=5.0f, bool linked=true)
constexpr const char * kTypeId
Definition forge_dynamics_catalog.hpp:126
constexpr state::ParamID kCeilingDbtp
Definition forge_dynamics_catalog.hpp:127
constexpr state::ParamID kReleaseMs
Definition forge_dynamics_catalog.hpp:128
Definition forge_dynamics_catalog.hpp:155
constexpr state::ParamID kTimeMs
Definition forge_dynamics_catalog.hpp:164
constexpr state::ParamID kNegativeRatio
Definition forge_dynamics_catalog.hpp:167
constexpr state::ParamID kMix
Definition forge_dynamics_catalog.hpp:166
CustomNodeType make_vca_compressor_node(float lookahead_ms=0.0f, double attack_release_k=Comp::kRatioKDefault)
lookahead_ms is a construction-time argument, not a param: it IS this member's latency_samples().
constexpr state::ParamID kMakeupDb
Definition forge_dynamics_catalog.hpp:165
constexpr state::ParamID kRatio
Definition forge_dynamics_catalog.hpp:162
constexpr const char * kTypeId
Definition forge_dynamics_catalog.hpp:157
signal::VcaCompressor Comp
Definition forge_dynamics_catalog.hpp:171
float vca_compressor_worst_case_gain()
Worst-case linear gain for the Forge registry (series law 8).
constexpr state::ParamID kNegRatioAmount
Definition forge_dynamics_catalog.hpp:168
constexpr state::ParamID kCeilingDb
Definition forge_dynamics_catalog.hpp:169
constexpr state::ParamID kKneeDb
Definition forge_dynamics_catalog.hpp:163
constexpr state::ParamID kThresholdDb
Definition forge_dynamics_catalog.hpp:161
Definition forge_dynamics_catalog.hpp:75
constexpr state::ParamID kReleaseMs
Definition forge_dynamics_catalog.hpp:86
constexpr state::ParamID kAutoMakeup
Definition forge_dynamics_catalog.hpp:93
constexpr state::ParamID kKneeDb
Definition forge_dynamics_catalog.hpp:84
constexpr state::ParamID kAttackMs
Definition forge_dynamics_catalog.hpp:85
constexpr state::ParamID kThresholdDb
Definition forge_dynamics_catalog.hpp:82
constexpr const char * kFeedforwardCompressorTypeId
Definition forge_dynamics_catalog.hpp:78
constexpr float kNodeMaxLookaheadMs
This node's lookahead ceiling, in ms.
Definition forge_dynamics_catalog.hpp:102
CustomNodeType make_feedforward_compressor_node(float lookahead_ms=0.0f)
The transparent/modern compressor as a lowerable custom node.
constexpr state::ParamID kMakeupDb
Definition forge_dynamics_catalog.hpp:92
constexpr state::ParamID kStereoLink
Definition forge_dynamics_catalog.hpp:94
constexpr state::ParamID kRmsWindowMs
Definition forge_dynamics_catalog.hpp:88
constexpr state::ParamID kDetectorMode
Definition forge_dynamics_catalog.hpp:87
constexpr state::ParamID kProgramDependent
Definition forge_dynamics_catalog.hpp:91
constexpr state::ParamID kRatio
Definition forge_dynamics_catalog.hpp:83
float feedforward_compressor_worst_case_gain()
Worst-case linear gain for the Forge registry (series law 8).
DiodeBridgeCompressorT< float > DiodeBridgeCompressor
Definition diode_bridge_compressor.hpp:961
VcaCompressorT< float > VcaCompressor
Definition vca_compressor.hpp:458
FeedforwardCompressorT< float > FeedforwardCompressor
Definition feedforward_compressor.hpp:494
TruePeakLimiterT< float > TruePeakLimiter
Definition true_peak_limiter.hpp:447
FetCompressorT< float > FetCompressor
Definition fet_compressor.hpp:901
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
Definition custom_node_type.hpp:90
Definition forge_dynamics_catalog.hpp:104
signal::FeedforwardCompressor compressor
Definition forge_dynamics_catalog.hpp:105
Definition forge_dynamics_catalog.hpp:265
signal::DiodeBridgeCompressor compressor
Definition forge_dynamics_catalog.hpp:266
Definition forge_dynamics_catalog.hpp:221
signal::FetCompressor compressor
Definition forge_dynamics_catalog.hpp:222
Definition forge_dynamics_catalog.hpp:130
void set_controls(float ceiling_dbtp, float release_ms) noexcept
static constexpr std::size_t kControlTableSize
Definition forge_dynamics_catalog.hpp:131
std::array< double, kControlTableSize > ceiling_table
Definition forge_dynamics_catalog.hpp:134
std::array< double, kControlTableSize > release_table
Definition forge_dynamics_catalog.hpp:135
float last_release_ms
Definition forge_dynamics_catalog.hpp:137
static double table_value(const std::array< double, kControlTableSize > &table, double value, double minimum, double maximum) noexcept
signal::TruePeakLimiter limiter
Definition forge_dynamics_catalog.hpp:132
float last_ceiling_dbtp
Definition forge_dynamics_catalog.hpp:136
bool prepared
Definition forge_dynamics_catalog.hpp:133
Definition forge_dynamics_catalog.hpp:173
signal::VcaCompressor compressor
Definition forge_dynamics_catalog.hpp:174
Fixed-capacity, look-ahead true-peak limiter for interleaved audio.
The VCA compressor: the dbx/Blackmer lineage, as a true-RMS, log-domain, single-time-constant feedfor...