Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
faust_processor.hpp
Go to the documentation of this file.
1#pragma once
2
3// FaustProcessor — bridges FAUST-generated C++ DSP classes into Pulp's Processor model.
4//
5// FAUST offline codegen produces a class derived from ::dsp with:
6// - getNumInputs() / getNumOutputs()
7// - buildUserInterface(UI*) — declares parameters via UI callbacks
8// - compute(int count, float** inputs, float** outputs)
9// - metadata(Meta*) — key/value metadata pairs
10//
11// FaustProcessor acts as both the FAUST UI builder (to reflect parameters)
12// and the Pulp Processor (to map into StateStore and process audio).
13
14#include <cmath>
15#include <cstring>
16#include <functional>
17#include <limits>
18#include <map>
19#include <memory>
22#include <string>
23#include <vector>
24
25namespace pulp::dsl {
26
27// Minimal FAUST UI interface for parameter reflection.
28// FaustProcessor implements this to discover parameters from buildUserInterface().
29class PulpFaustUI : public UI {
30public:
31 struct Zone {
32 FAUSTFLOAT* zone_ptr; // Pointer into FAUST DSP's float field
34 state::ParamID param_id; // Assigned during define_parameters()
35 };
36
37 // UI interface (called by FAUST-generated buildUserInterface)
38 void openTabBox(const char* label) override { push_group(label); }
39 void openHorizontalBox(const char* label) override { push_group(label); }
40 void openVerticalBox(const char* label) override { push_group(label); }
41 void closeBox() override { pop_group(); }
42
43 void addHorizontalSlider(const char* label, FAUSTFLOAT* zone,
44 FAUSTFLOAT init, FAUSTFLOAT min,
45 FAUSTFLOAT max, FAUSTFLOAT step) override {
46 add_zone(label, zone, init, min, max, step);
47 }
48 void addVerticalSlider(const char* label, FAUSTFLOAT* zone,
49 FAUSTFLOAT init, FAUSTFLOAT min,
50 FAUSTFLOAT max, FAUSTFLOAT step) override {
51 add_zone(label, zone, init, min, max, step);
52 }
53 void addNumEntry(const char* label, FAUSTFLOAT* zone,
54 FAUSTFLOAT init, FAUSTFLOAT min,
55 FAUSTFLOAT max, FAUSTFLOAT step) override {
56 add_zone(label, zone, init, min, max, step);
57 }
58 void addButton(const char* label, FAUSTFLOAT* zone) override {
59 add_zone(label, zone, 0.0f, 0.0f, 1.0f, 1.0f);
60 }
61 void addCheckButton(const char* label, FAUSTFLOAT* zone) override {
62 add_zone(label, zone, 0.0f, 0.0f, 1.0f, 1.0f);
63 }
64
65 // Passive outputs (bargraphs) — read-only, not mapped to params
66 void addHorizontalBargraph(const char*, FAUSTFLOAT*, FAUSTFLOAT, FAUSTFLOAT) override {}
67 void addVerticalBargraph(const char*, FAUSTFLOAT*, FAUSTFLOAT, FAUSTFLOAT) override {}
68
69 // Metadata for current zone
70 void declare(FAUSTFLOAT* zone, const char* key, const char* value) override {
71 if (zone) {
72 zone_metadata_[zone][key] = value;
73 }
74 }
75
76 const std::vector<Zone>& zones() const { return zones_; }
77
78private:
79 std::vector<Zone> zones_;
80 std::vector<std::string> group_stack_;
81 std::map<FAUSTFLOAT*, std::map<std::string, std::string>> zone_metadata_;
82
83 void push_group(const char* label) {
84 if (label && label[0]) {
85 group_stack_.emplace_back(label);
86 }
87 }
88
89 void pop_group() {
90 if (!group_stack_.empty()) {
91 group_stack_.pop_back();
92 }
93 }
94
95 std::string current_group() const {
96 std::string result;
97 for (const auto& g : group_stack_) {
98 if (!result.empty()) result += "/";
99 result += g;
100 }
101 return result;
102 }
103
104 void add_zone(const char* label, FAUSTFLOAT* zone,
105 float init, float min, float max, float step) {
106 Zone z;
107 z.zone_ptr = zone;
108 z.desc.name = label ? label : "";
109 z.desc.group = current_group();
110 z.desc.min = min;
111 z.desc.max = max;
112 z.desc.default_value = init;
113 z.desc.step = step;
114 z.param_id = 0; // Assigned later
115
116 // Check for unit metadata
117 auto it = zone_metadata_.find(zone);
118 if (it != zone_metadata_.end()) {
119 auto uit = it->second.find("unit");
120 if (uit != it->second.end()) {
121 z.desc.unit = uit->second;
122 }
123 }
124
125 zones_.push_back(std::move(z));
126 }
127};
128
129// Minimal FAUST Meta interface for metadata extraction
130class PulpFaustMeta : public Meta {
131public:
132 void declare(const char* key, const char* value) override {
133 if (key && value) {
134 metadata_[key] = value;
135 }
136 }
137
138 const std::string& get(const std::string& key, const std::string& fallback = "") const {
139 auto it = metadata_.find(key);
140 return it != metadata_.end() ? it->second : fallback;
141 }
142
143 const std::map<std::string, std::string>& all() const { return metadata_; }
144
145private:
146 std::map<std::string, std::string> metadata_;
147};
148
149// Template wrapper: FaustDsp must be default-constructible and implement the
150// FAUST dsp ABI by deriving from ::dsp.
151// This is the concrete FaustProcessor that ships as a Pulp Processor.
152template <typename FaustDsp>
154public:
156 dsl_name_ = "faust";
157 faust_dsp_ = std::make_unique<FaustDsp>();
158
159 // Extract metadata
160 faust_dsp_->metadata(&meta_);
161
162 // Reflect parameters via UI
163 faust_dsp_->buildUserInterface(&ui_);
164
165 // Populate DSL param descriptors
166 for (const auto& zone : ui_.zones()) {
167 dsl_params_.push_back(zone.desc);
168 }
169
170 // Extract bus layout
171 bus_layout_.num_inputs = faust_dsp_->getNumInputs();
172 bus_layout_.num_outputs = faust_dsp_->getNumOutputs();
173 bus_layout_.accepts_midi = false; // Basic FAUST — no MIDI mapping yet
174 }
175
176 // --- Processor interface ---
177
179 std::string name = meta_.get("name", "FAUST Plugin");
180 std::string author = meta_.get("author", "FAUST");
181
183 desc.name = name;
184 desc.manufacturer = author;
185 desc.bundle_id = "com.pulp.faust." + sanitize_id(name);
186 desc.version = meta_.get("version", "1.0.0");
187 desc.category = (bus_layout_.num_inputs == 0)
190
191 if (bus_layout_.num_inputs > 0) {
192 desc.input_buses = {{"Audio In", bus_layout_.num_inputs}};
193 } else {
194 // An instrument (0 inputs) must not inherit PluginDescriptor's
195 // default stereo "Main In" — hosts will try to route audio in
196 // and complain. Explicitly clear.
197 desc.input_buses.clear();
198 }
199 desc.output_buses = {{"Audio Out", bus_layout_.num_outputs}};
200 desc.accepts_midi = bus_layout_.accepts_midi;
201 desc.produces_midi = false;
202 desc.tail_samples = 0;
203
204 return desc;
205 }
206
207 void define_parameters(state::StateStore& store) override {
208 auto& zones = const_cast<std::vector<PulpFaustUI::Zone>&>(ui_.zones());
209
210 // Build groups from unique group paths
211 std::map<std::string, int> group_ids;
212 int next_group_id = 1;
213
214 for (auto& zone : zones) {
215 if (!zone.desc.group.empty() && group_ids.find(zone.desc.group) == group_ids.end()) {
216 group_ids[zone.desc.group] = next_group_id;
217 store.add_group({next_group_id, zone.desc.group, 0});
218 next_group_id++;
219 }
220 }
221
222 // Register parameters with sequential IDs starting at 1
223 state::ParamID next_id = 1;
224 for (auto& zone : zones) {
225 state::ParamInfo info;
226 info.id = next_id;
227 info.name = zone.desc.name;
228 info.unit = zone.desc.unit;
229 info.range = {zone.desc.min, zone.desc.max, zone.desc.default_value, zone.desc.step};
230
231 if (!zone.desc.group.empty()) {
232 auto it = group_ids.find(zone.desc.group);
233 if (it != group_ids.end()) {
234 info.group_id = it->second;
235 }
236 }
237
238 store.add_parameter(info);
239 zone.param_id = next_id;
240 next_id++;
241 }
242 }
243
244 void prepare(const format::PrepareContext& ctx) override {
245 prepared_ = false;
246 sample_rate_ = ctx.sample_rate;
247 max_block_size_ = ctx.max_buffer_size;
248 const int num_in = bus_layout_.num_inputs;
249 const int num_out = bus_layout_.num_outputs;
250 if (num_in < 0 || num_out < 0 || max_block_size_ < 0) {
251 input_ptrs_.clear();
252 output_ptrs_.clear();
253 return;
254 }
255 input_ptrs_.assign(static_cast<std::size_t>(num_in), nullptr);
256 output_ptrs_.assign(static_cast<std::size_t>(num_out), nullptr);
257 faust_dsp_->init(static_cast<int>(sample_rate_));
258 prepared_ = ctx.input_channels == num_in && ctx.output_channels == num_out;
259 }
260
266 const format::ProcessContext& ctx) override
267 {
268 int n = ctx.num_samples;
269 if (n <= 0) {
270 if (output.num_samples() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
271 output.clear();
272 return;
273 }
274 n = static_cast<int>(output.num_samples());
275 }
276
277 const int num_in = bus_layout_.num_inputs;
278 const int num_out = bus_layout_.num_outputs;
279 const auto frames = static_cast<std::size_t>(n);
280 if (!prepared_ || input.num_channels() != input_ptrs_.size() ||
281 output.num_channels() != output_ptrs_.size() || n > max_block_size_ ||
282 frames > output.num_samples() || (num_in > 0 && frames > input.num_samples())) {
283 output.clear();
284 return;
285 }
286
287 // Sync StateStore → FAUST zone pointers
288 for (const auto& zone : ui_.zones()) {
289 *zone.zone_ptr = state().get_value(zone.param_id);
290 }
291
292 if (n == 0) {
293 faust_dsp_->compute(0, input_ptrs_.data(), output_ptrs_.data());
294 return;
295 }
296
297 for (int ch = 0; ch < num_in; ++ch) {
298 input_ptrs_[static_cast<std::size_t>(ch)] =
299 const_cast<float*>(input.channel_ptr(static_cast<std::size_t>(ch)));
300 }
301 for (int ch = 0; ch < num_out; ++ch) {
302 output_ptrs_[static_cast<std::size_t>(ch)] =
303 output.channel_ptr(static_cast<std::size_t>(ch));
304 }
305
306 faust_dsp_->compute(n, input_ptrs_.data(), output_ptrs_.data());
307 }
308
309 void release() override {
310 // FAUST DSP doesn't have a release, but we could instanceClear if needed
311 }
312
313 bool has_editor() const override { return false; } // No custom UI for FAUST plugins
314
315private:
316 std::unique_ptr<FaustDsp> faust_dsp_;
317 PulpFaustUI ui_;
318 PulpFaustMeta meta_;
319 double sample_rate_ = 48000.0;
320 int max_block_size_ = 512;
321 bool prepared_ = false;
322 std::vector<float*> input_ptrs_;
323 std::vector<float*> output_ptrs_;
324
325 static std::string sanitize_id(const std::string& name) {
326 std::string result;
327 for (char c : name) {
328 if (std::isalnum(static_cast<unsigned char>(c))) {
329 result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
330 } else if (c == ' ' || c == '-' || c == '_') {
331 if (!result.empty() && result.back() != '-') {
332 result += '-';
333 }
334 }
335 }
336 return result;
337 }
338};
339
340} // namespace pulp::dsl
Definition faust_base.hpp:17
Definition faust_base.hpp:24
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
std::size_t num_samples() const
Definition buffer.hpp:84
std::size_t num_channels() const
Definition buffer.hpp:83
void clear()
Zero-fill all channels.
Definition buffer.hpp:90
SampleType * channel_ptr(std::size_t index)
Raw pointer to one channel's sample data.
Definition buffer.hpp:68
Definition dsl_processor.hpp:49
DslBusLayout bus_layout_
Definition dsl_processor.hpp:59
std::string dsl_name_
Definition dsl_processor.hpp:60
std::vector< DslParamDescriptor > dsl_params_
Definition dsl_processor.hpp:58
void prepare(const format::PrepareContext &ctx) override
Prepare for processing.
Definition faust_processor.hpp:244
bool has_editor() const override
Editor support.
Definition faust_processor.hpp:313
void process(audio::BufferView< float > &output, const audio::BufferView< const float > &input, midi::MidiBuffer &, midi::MidiBuffer &, const format::ProcessContext &ctx) override
Process one buffer of audio.
Definition faust_processor.hpp:261
format::PluginDescriptor descriptor() const override
Return the plugin's metadata. Called once during initialization.
Definition faust_processor.hpp:178
FaustProcessor()
Definition faust_processor.hpp:155
void define_parameters(state::StateStore &store) override
Register parameters with the state store.
Definition faust_processor.hpp:207
void release() override
Release resources.
Definition faust_processor.hpp:309
Definition faust_processor.hpp:130
void declare(const char *key, const char *value) override
Definition faust_processor.hpp:132
const std::string & get(const std::string &key, const std::string &fallback="") const
Definition faust_processor.hpp:138
const std::map< std::string, std::string > & all() const
Definition faust_processor.hpp:143
Definition faust_processor.hpp:29
void addHorizontalSlider(const char *label, float *zone, float init, float min, float max, float step) override
Definition faust_processor.hpp:43
void closeBox() override
Definition faust_processor.hpp:41
void openHorizontalBox(const char *label) override
Definition faust_processor.hpp:39
void addVerticalSlider(const char *label, float *zone, float init, float min, float max, float step) override
Definition faust_processor.hpp:48
void addButton(const char *label, float *zone) override
Definition faust_processor.hpp:58
void declare(float *zone, const char *key, const char *value) override
Definition faust_processor.hpp:70
void openTabBox(const char *label) override
Definition faust_processor.hpp:38
void addNumEntry(const char *label, float *zone, float init, float min, float max, float step) override
Definition faust_processor.hpp:53
void addHorizontalBargraph(const char *, float *, float, float) override
Definition faust_processor.hpp:66
void openVerticalBox(const char *label) override
Definition faust_processor.hpp:40
void addVerticalBargraph(const char *, float *, float, float) override
Definition faust_processor.hpp:67
const std::vector< Zone > & zones() const
Definition faust_processor.hpp:76
void addCheckButton(const char *label, float *zone) override
Definition faust_processor.hpp:61
state::StateStore & state()
Access the parameter state store.
Definition processor.hpp:955
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
float get_value(ParamID id) const
Read a parameter's base value (lock-free).
Definition store.hpp:186
void add_parameter(const ParamInfo &info)
Register a parameter.
void add_group(const ParamGroup &group)
Register a parameter group. Call during initialization only.
#define FAUSTFLOAT
Definition faust_base.hpp:11
Definition dsl_processor.hpp:18
@ Instrument
Synth/sampler (receives MIDI, produces audio).
Definition plugin_descriptor.hpp:187
@ Effect
Audio effect (takes input, produces output).
Definition plugin_descriptor.hpp:186
@ g
Definition pitch.hpp:27
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
@ z
Definition input_events.hpp:42
Definition dsl_processor.hpp:21
Definition faust_processor.hpp:31
DslParamDescriptor desc
Definition faust_processor.hpp:33
float * zone_ptr
Definition faust_processor.hpp:32
state::ParamID param_id
Definition faust_processor.hpp:34
Plugin metadata — declared once, immutable.
Definition plugin_descriptor.hpp:245
std::string bundle_id
Definition plugin_descriptor.hpp:248
bool produces_midi
true if plugin sends MIDI output
Definition plugin_descriptor.hpp:258
std::vector< BusInfo > input_buses
Bus configuration — defaults to single stereo in/out for compatibility.
Definition plugin_descriptor.hpp:254
std::vector< BusInfo > output_buses
Definition plugin_descriptor.hpp:255
bool accepts_midi
true if plugin receives MIDI input
Definition plugin_descriptor.hpp:257
std::string manufacturer
Definition plugin_descriptor.hpp:247
std::string name
Definition plugin_descriptor.hpp:246
PluginCategory category
Definition plugin_descriptor.hpp:250
int tail_samples
Tail time in samples (0 = no tail, -1 = infinite).
Definition plugin_descriptor.hpp:291
std::string version
Semantic version string, e.g. "1.0.0".
Definition plugin_descriptor.hpp:249
Definition prepare_resources.hpp:102
double sample_rate
Definition prepare_resources.hpp:103
int input_channels
Definition prepare_resources.hpp:105
int max_buffer_size
Definition prepare_resources.hpp:104
int output_channels
Definition prepare_resources.hpp:106
Process context — passed every audio callback with transport state.
Definition process_context.hpp:89
int num_samples
Definition process_context.hpp:91
Immutable parameter metadata, registered once at plugin initialization.
Definition parameter.hpp:316