Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
forge_multiband_catalog.hpp
Go to the documentation of this file.
1#pragma once
2
3// Multiband — Forge-facing bake-layer catalog nodes.
4//
5// The shipped multiband contract is composition: a Linkwitz-Riley crossover
6// followed by independent shipped Compressor instances. This two-band node is
7// that composition made directly lowerable, not a parallel DSP implementation.
8
9#include <pulp/host/detail/forge_compressor_ballistics.hpp>
14
15#include <algorithm>
16#include <cmath>
17#include <cstddef>
18
20
21inline constexpr const char* kTypeId = "multiband.compressor";
22
23inline constexpr state::ParamID kCrossoverHz = 1;
24inline constexpr state::ParamID kLowThresholdDb = 2;
25inline constexpr state::ParamID kLowRatio = 3;
26inline constexpr state::ParamID kHighThresholdDb = 4;
27inline constexpr state::ParamID kHighRatio = 5;
28inline constexpr state::ParamID kAttackMs = 6;
29inline constexpr state::ParamID kReleaseMs = 7;
30inline constexpr state::ParamID kLowMakeupDb = 8;
31inline constexpr state::ParamID kHighMakeupDb = 9;
32inline constexpr state::ParamID kBallistics = 10;
33
34// Samples per band-split pass of the process callback.
35inline constexpr int kBandChunk = 256;
36
43
44namespace detail {
45
46inline signal::Compressor::Params compressor_params(float threshold_db, float ratio,
47 float attack_ms, float release_ms,
48 float makeup_db) {
49 signal::Compressor::Params p;
50 p.threshold_db = threshold_db;
51 p.ratio = std::max(1.0f, ratio);
52 p.attack_ms = attack_ms;
53 p.release_ms = release_ms;
54 p.knee_db = 6.0f;
55 p.makeup_db = makeup_db;
56 return p;
57}
58
59} // namespace detail
60
63 t.type_id = kTypeId;
64 t.version = 1;
65 t.num_input_ports = 1;
66 t.num_output_ports = 1;
67 t.default_name = "Multiband Compressor";
68 t.lowerable = true;
69
70 t.create = []() -> void* { return new Instance{}; };
71 t.destroy = [](void* p) { delete static_cast<Instance*>(p); };
72 t.prepare = [](void* p, double sample_rate, int /*max_block*/) {
73 auto* s = static_cast<Instance*>(p);
74 s->sample_rate = static_cast<float>(sample_rate);
75 s->crossover.set_frequency(800.0f, s->sample_rate);
76 s->low.set_sample_rate(s->sample_rate);
77 s->high.set_sample_rate(s->sample_rate);
78 s->crossover.reset();
79 s->low.reset();
80 s->high.reset();
81 };
82 t.reset = [](void* p) {
83 auto* s = static_cast<Instance*>(p);
84 s->crossover.reset();
85 s->low.reset();
86 s->high.reset();
87 };
88
89 t.baked_params.push_back({kCrossoverHz, 80.0f, 8000.0f, 800.0f});
90 t.baked_params.push_back({kLowThresholdDb, -60.0f, 0.0f, -18.0f});
91 t.baked_params.push_back({kLowRatio, 1.0f, 20.0f, 4.0f});
92 t.baked_params.push_back({kHighThresholdDb, -60.0f, 0.0f, -18.0f});
93 t.baked_params.push_back({kHighRatio, 1.0f, 20.0f, 4.0f});
94 t.baked_params.push_back({kAttackMs, 0.1f, 100.0f, 10.0f});
95 t.baked_params.push_back({kReleaseMs, 10.0f, 1000.0f, 150.0f});
96 t.baked_params.push_back({kLowMakeupDb, -24.0f, 12.0f, 0.0f});
97 t.baked_params.push_back({kHighMakeupDb, -24.0f, 12.0f, 0.0f});
98 // ballistics: 0 branching (default, as before the control existed), 1 decoupled.
99 t.baked_params.push_back({kBallistics, 0.0f, 1.0f, 0.0f});
100
101 t.process_instance_baked_param = [](void* p, audio::BufferView<float>& out,
102 const audio::BufferView<const float>& in, int n,
103 const BakedParamView& params) {
104 auto* s = static_cast<Instance*>(p);
105 const float* input = in.channel_ptr(0);
106 float* output = out.channel_ptr(0);
107
108 s->crossover.set_frequency(params.value_at(kCrossoverHz, 0), s->sample_rate);
109 s->low.set_params(detail::compressor_params(
110 params.value_at(kLowThresholdDb, 0), params.value_at(kLowRatio, 0),
111 params.value_at(kAttackMs, 0), params.value_at(kReleaseMs, 0),
112 params.value_at(kLowMakeupDb, 0)));
113 s->high.set_params(detail::compressor_params(
114 params.value_at(kHighThresholdDb, 0), params.value_at(kHighRatio, 0),
115 params.value_at(kAttackMs, 0), params.value_at(kReleaseMs, 0),
116 params.value_at(kHighMakeupDb, 0)));
117 const auto ballistics = forge_compressor::ballistics_for(params.value_at(kBallistics, 0));
118 s->low.set_ballistics(ballistics);
119 s->high.set_ballistics(ballistics);
120
121 // Split a chunk into its two bands, compress each band through the
122 // compressor's buffer path, then sum. Stack scratch keeps the node free
123 // of allocation and independent of the prepared block size.
124 float low[kBandChunk];
125 float high[kBandChunk];
126 for (int start = 0; start < n; start += kBandChunk) {
127 const int count = std::min(kBandChunk, n - start);
128 for (int k = 0; k < count; ++k) {
129 const auto split = s->crossover.process(input[static_cast<std::size_t>(start + k)]);
130 low[k] = split.low;
131 high[k] = split.high;
132 }
133 s->low.process(low, count);
134 s->high.process(high, count);
135 for (int k = 0; k < count; ++k)
136 output[static_cast<std::size_t>(start + k)] = low[k] + high[k];
137 }
138 };
139 return t;
140}
141
143 return {
144 "multiband_compressor",
145 "Multiband Compressor",
146 "A two-band compressor with independent low- and high-band dynamics.",
147 {},
148 {{"default", kTypeId}},
149 {
150 {"crossover_hz", kCrossoverHz, "Crossover", "Hz",
151 "Frequency that divides the low and high compression bands.",
153 {"low_threshold_db", kLowThresholdDb, "Low Threshold", "dB",
154 "Level above which the low band is compressed.", ForgeParamKind::continuous,
156 {"low_ratio", kLowRatio, "Low Ratio", ":1", "Gain-reduction ratio for the low band.",
158 {"high_threshold_db", kHighThresholdDb, "High Threshold", "dB",
159 "Level above which the high band is compressed.", ForgeParamKind::continuous,
161 {"high_ratio", kHighRatio, "High Ratio", ":1",
162 "Gain-reduction ratio for the high band.", ForgeParamKind::continuous,
164 {"attack_ms", kAttackMs, "Attack", "ms",
165 "Time for compression to engage in both bands.", ForgeParamKind::continuous,
167 {"release_ms", kReleaseMs, "Release", "ms",
168 "Time for compression to recover in both bands.", ForgeParamKind::continuous,
170 {"low_makeup_db", kLowMakeupDb, "Low Makeup", "dB",
171 "Output gain applied after low-band compression.", ForgeParamKind::continuous,
173 {"high_makeup_db", kHighMakeupDb, "High Makeup", "dB",
174 "Output gain applied after high-band compression.", ForgeParamKind::continuous,
176 forge_compressor::ballistics_descriptor(kBallistics),
177 },
178 };
179}
180
181inline float worst_case_gain() {
182 return 2.0f * std::pow(10.0f, 12.0f / 20.0f);
183}
184
185} // namespace pulp::host::multiband
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Definition custom_node_type.hpp:51
Definition forge_multiband_catalog.hpp:44
signal::Compressor::Params compressor_params(float threshold_db, float ratio, float attack_ms, float release_ms, float makeup_db)
Definition forge_multiband_catalog.hpp:46
Definition forge_multiband_catalog.hpp:19
constexpr state::ParamID kLowThresholdDb
Definition forge_multiband_catalog.hpp:24
constexpr int kBandChunk
Definition forge_multiband_catalog.hpp:35
constexpr state::ParamID kHighRatio
Definition forge_multiband_catalog.hpp:27
CustomNodeType make_multiband_compressor_node()
Definition forge_multiband_catalog.hpp:61
ForgeNodeDescriptor descriptor()
Definition forge_multiband_catalog.hpp:142
constexpr state::ParamID kHighMakeupDb
Definition forge_multiband_catalog.hpp:31
constexpr const char * kTypeId
Definition forge_multiband_catalog.hpp:21
constexpr state::ParamID kCrossoverHz
Definition forge_multiband_catalog.hpp:23
constexpr state::ParamID kBallistics
Definition forge_multiband_catalog.hpp:32
constexpr state::ParamID kReleaseMs
Definition forge_multiband_catalog.hpp:29
constexpr state::ParamID kAttackMs
Definition forge_multiband_catalog.hpp:28
constexpr state::ParamID kHighThresholdDb
Definition forge_multiband_catalog.hpp:26
constexpr state::ParamID kLowMakeupDb
Definition forge_multiband_catalog.hpp:30
float worst_case_gain()
Definition forge_multiband_catalog.hpp:181
constexpr state::ParamID kLowRatio
Definition forge_multiband_catalog.hpp:25
@ continuous
Definition forge_param_descriptor.hpp:25
@ logarithmic
Definition forge_param_descriptor.hpp:29
@ linear
Definition forge_param_descriptor.hpp:29
LinkwitzRileyT< float > LinkwitzRiley
Definition linkwitz_riley.hpp:88
CompressorT< float > Compressor
Definition compressor.hpp:425
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
Definition custom_node_type.hpp:90
Definition forge_param_descriptor.hpp:127
Definition forge_multiband_catalog.hpp:37
signal::Compressor low
Definition forge_multiband_catalog.hpp:39
signal::LinkwitzRiley crossover
Definition forge_multiband_catalog.hpp:38
float sample_rate
Definition forge_multiband_catalog.hpp:41
signal::Compressor high
Definition forge_multiband_catalog.hpp:40