Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
forge_analog_vcf_catalog.hpp
Go to the documentation of this file.
1#pragma once
2
3// Forge-facing adapter for Pulp's analog-modelled VCF.
4//
5// The filter engine, voicing laws, and measured calibration tables belong to
6// pulp::signal. This header owns only the bakeable CustomNodeType identities
7// and parameter bridge needed by Forge (and any other graph host). Keeping that
8// boundary in a dedicated header prevents the general lo-fi catalog from
9// becoming the owner of analog DSP policy.
10
14
15#include <cstddef>
16#include <cstdint>
17
19
20inline constexpr const char* kAnalogVcfJunoTypeId = "vcf.juno";
21inline constexpr const char* kAnalogVcfJupiterTypeId = "vcf.jupiter";
22inline constexpr const char* kAnalogVcfProphet5TypeId = "vcf.prophet5";
23inline constexpr const char* kAnalogVcfMinimoogTypeId = "vcf.minimoog";
24
25inline constexpr state::ParamID kAnalogVcfCutoff = 1; // panel knob, not Hz
26inline constexpr state::ParamID kAnalogVcfCutoffMod = 2; // octave modulation
28inline constexpr state::ParamID kAnalogVcfDriveDb = 4;
29
30// Oversampling changes the realized topology and fixed latency, so it is an
31// adapter construction policy rather than an injectable musical parameter.
32// Keep prepare and the published latency contract tied to this one value.
33inline constexpr int kAnalogVcfOversampling = 2;
34
36 explicit AnalogVcfInstance(signal::AnalogVcf::Voicing selected) noexcept {
37 filter.set_voicing(selected);
38 }
39
41};
42
44 const char* type_id;
45 const char* name;
46};
47
49 signal::AnalogVcf::Voicing voicing) noexcept {
50 switch (voicing) {
51 case signal::AnalogVcf::Voicing::juno:
52 return {kAnalogVcfJunoTypeId, "Juno VCF"};
53 case signal::AnalogVcf::Voicing::jupiter:
54 return {kAnalogVcfJupiterTypeId, "Jupiter-8 VCF"};
55 case signal::AnalogVcf::Voicing::prophet5:
56 return {kAnalogVcfProphet5TypeId, "Prophet-5 VCF"};
57 case signal::AnalogVcf::Voicing::minimoog:
58 return {kAnalogVcfMinimoogTypeId, "Minimoog VCF"};
59 }
60 return {kAnalogVcfJunoTypeId, "Analog VCF"};
61}
62
64 const AnalogVcfIdentity identity = analog_vcf_identity(voicing);
65 CustomNodeType type;
66 type.type_id = identity.type_id;
67 type.version = 1;
68 type.num_input_ports = 1;
69 type.num_output_ports = 1;
70 type.default_name = identity.name;
71 // Rate-independent: the OTA cascade's half-band group delay is a function of
72 // the oversampling factor alone, so the callback ignores the sample rate.
73 type.latency_samples = [](double) {
76 };
77 type.lowerable = true;
78 type.create = [voicing]() -> void* { return new AnalogVcfInstance(voicing); };
79 type.destroy = [](void* p) { delete static_cast<AnalogVcfInstance*>(p); };
80 type.prepare = [](void* p, double sample_rate, int /*max_block*/) {
81 auto* instance = static_cast<AnalogVcfInstance*>(p);
82 instance->filter.set_sample_rate(sample_rate);
83 instance->filter.set_oversampling(kAnalogVcfOversampling);
84 instance->filter.set_smoothing_time_ms(3.0);
85 instance->filter.reset();
86 };
87 type.reset = [](void* p) { static_cast<AnalogVcfInstance*>(p)->filter.reset(); };
88 type.process_instance =
89 [](void* p, audio::BufferView<float>& output,
90 const audio::BufferView<const float>& input, int num_samples) {
91 auto* instance = static_cast<AnalogVcfInstance*>(p);
92 const float* input_samples = input.channel_ptr(0);
93 float* output_samples = output.channel_ptr(0);
94 for (int i = 0; i < num_samples; ++i)
95 output_samples[i] = instance->filter.process(input_samples[i]);
96 };
97 type.baked_params.push_back({kAnalogVcfCutoff, 0.0f, 1.0f, 0.5f});
98 type.baked_params.push_back({kAnalogVcfCutoffMod, -5.0f, 5.0f, 0.0f});
99 type.baked_params.push_back({kAnalogVcfResonance, 0.0f, 1.0f, 0.0f});
100 type.baked_params.push_back({kAnalogVcfDriveDb, -24.0f, 48.0f, 0.0f});
101 type.process_instance_baked_param =
102 [](void* p, audio::BufferView<float>& output,
103 const audio::BufferView<const float>& input, int num_samples,
104 const BakedParamView& params) {
105 auto* instance = static_cast<AnalogVcfInstance*>(p);
106 const float* input_samples = input.channel_ptr(0);
107 float* output_samples = output.channel_ptr(0);
108 for (int sample = 0; sample < num_samples; ++sample) {
109 const auto offset = static_cast<std::int32_t>(sample);
110 instance->filter.set_parameters(
111 params.value_at(kAnalogVcfCutoff, offset),
112 params.value_at(kAnalogVcfCutoffMod, offset),
113 params.value_at(kAnalogVcfResonance, offset),
114 params.value_at(kAnalogVcfDriveDb, offset));
115 output_samples[static_cast<std::size_t>(sample)] =
116 instance->filter.process(
117 input_samples[static_cast<std::size_t>(sample)]);
118 }
119 };
120 return type;
121}
122
125 d.key = "analog_vcf";
126 d.label = "Analog VCF";
127 d.description = "Four analog-inspired voltage-controlled filter voicings with panel-style "
128 "cutoff, resonance, modulation, and input drive.";
129 d.axes = {{"voicing",
130 "Voicing",
131 "Circuit family whose calibration, resonance, and saturation behavior "
132 "define the filter.",
133 {{"juno", "Juno", 0.0f},
134 {"jupiter", "Jupiter-8", 1.0f},
135 {"prophet5", "Prophet-5", 2.0f},
136 {"minimoog", "Minimoog", 3.0f}}}};
137 d.realizations = {
138 {"juno", kAnalogVcfJunoTypeId, {{"voicing", "juno"}}},
139 {"jupiter", kAnalogVcfJupiterTypeId, {{"voicing", "jupiter"}}},
140 {"prophet5", kAnalogVcfProphet5TypeId, {{"voicing", "prophet5"}}},
141 {"minimoog", kAnalogVcfMinimoogTypeId, {{"voicing", "minimoog"}}},
142 };
143 d.params = {
144 {"cutoff", kAnalogVcfCutoff, "Cutoff", "",
145 "Panel cutoff position after the selected voicing's calibrated frequency law.",
147 {"cutoff_mod", kAnalogVcfCutoffMod, "Cutoff Modulation", "oct",
148 "Musical pitch offset applied to the cutoff in octaves.", ForgeParamKind::continuous,
150 {"resonance", kAnalogVcfResonance, "Resonance", "%",
151 "Emphasis at the cutoff, extending into self-oscillating behavior for the "
152 "selected circuit.",
154 {"drive_db", kAnalogVcfDriveDb, "Drive", "dB",
155 "Input drive into the filter's nonlinear stages.", ForgeParamKind::continuous,
157 };
158 return d;
159}
160
161} // namespace pulp::host::forge_lofi
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Definition custom_node_type.hpp:51
void set_sample_rate(double sample_rate) noexcept
Sets the base sample rate in Hz, with values below 64 Hz clamped to 64.
Definition analog_vcf.hpp:91
Voicing
Definition analog_vcf.hpp:71
static constexpr int latency_samples_for_oversampling(int factor) noexcept
Definition analog_vcf.hpp:221
SampleType process(SampleType input) noexcept
Processes one base-rate sample and advances filter, oversampling, smoothing, and deterministic drift ...
Definition analog_vcf.hpp:195
Definition forge_analog_vcf_catalog.hpp:18
constexpr const char * kAnalogVcfMinimoogTypeId
Definition forge_analog_vcf_catalog.hpp:23
constexpr const char * kAnalogVcfJupiterTypeId
Definition forge_analog_vcf_catalog.hpp:21
constexpr const char * kAnalogVcfJunoTypeId
Definition forge_analog_vcf_catalog.hpp:20
constexpr state::ParamID kAnalogVcfResonance
Definition forge_analog_vcf_catalog.hpp:27
constexpr state::ParamID kAnalogVcfCutoffMod
Definition forge_analog_vcf_catalog.hpp:26
constexpr int kAnalogVcfOversampling
Definition forge_analog_vcf_catalog.hpp:33
constexpr state::ParamID kAnalogVcfDriveDb
Definition forge_analog_vcf_catalog.hpp:28
AnalogVcfIdentity analog_vcf_identity(signal::AnalogVcf::Voicing voicing) noexcept
Definition forge_analog_vcf_catalog.hpp:48
CustomNodeType make_analog_vcf_node(signal::AnalogVcf::Voicing voicing)
Definition forge_analog_vcf_catalog.hpp:63
constexpr state::ParamID kAnalogVcfCutoff
Definition forge_analog_vcf_catalog.hpp:25
constexpr const char * kAnalogVcfProphet5TypeId
Definition forge_analog_vcf_catalog.hpp:22
ForgeNodeDescriptor analog_vcf_descriptor()
Definition forge_analog_vcf_catalog.hpp:123
@ continuous
Definition forge_param_descriptor.hpp:25
@ linear
Definition forge_param_descriptor.hpp:29
AnalogVcfT< float > AnalogVcf
Single-precision measured analog VCF.
Definition analog_vcf.hpp:488
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_analog_vcf_catalog.hpp:43
const char * type_id
Definition forge_analog_vcf_catalog.hpp:44
const char * name
Definition forge_analog_vcf_catalog.hpp:45
Definition forge_analog_vcf_catalog.hpp:35
AnalogVcfInstance(signal::AnalogVcf::Voicing selected) noexcept
Definition forge_analog_vcf_catalog.hpp:36
signal::AnalogVcf filter
Definition forge_analog_vcf_catalog.hpp:40