25 const FormantEntry k_vowel_a[] = {
26 { .frequency = 800.0, .q = 16.0 },
27 { .frequency = 1200.0, .q = 30.0 },
28 { .frequency = 2500.0, .q = 55.0 },
29 { .frequency = 3500.0, .q = 70.0 },
30 { .frequency = 4500.0, .q = 90.0 },
33 const FormantEntry k_vowel_e[] = {
34 { .frequency = 400.0, .q = 10.0 },
35 { .frequency = 2000.0, .q = 45.0 },
36 { .frequency = 2600.0, .q = 55.0 },
37 { .frequency = 3500.0, .q = 70.0 },
38 { .frequency = 4500.0, .q = 90.0 },
41 const FormantEntry k_vowel_i[] = {
42 { .frequency = 270.0, .q = 7.0 },
43 { .frequency = 2300.0, .q = 50.0 },
44 { .frequency = 3000.0, .q = 60.0 },
45 { .frequency = 3500.0, .q = 70.0 },
46 { .frequency = 4500.0, .q = 90.0 },
49 const FormantEntry k_vowel_o[] = {
50 { .frequency = 500.0, .q = 12.0 },
51 { .frequency = 900.0, .q = 22.0 },
52 { .frequency = 2500.0, .q = 55.0 },
53 { .frequency = 3500.0, .q = 70.0 },
54 { .frequency = 4500.0, .q = 90.0 },
57 const FormantEntry k_vowel_u[] = {
58 { .frequency = 300.0, .q = 8.0 },
59 { .frequency = 800.0, .q = 20.0 },
60 { .frequency = 2300.0, .q = 50.0 },
61 { .frequency = 3500.0, .q = 70.0 },
62 { .frequency = 4500.0, .q = 90.0 },
65 constexpr size_t k_preset_formant_count = 5;
75 std::vector<double>& out_freqs,
76 std::vector<double>& out_qs)
78 const FormantEntry* table =
nullptr;
100 out_freqs.resize(n, 440.0);
101 out_qs.resize(n, 10.0);
107 const size_t defined = std::min(n, k_preset_formant_count);
108 for (
size_t i = 0; i < defined; ++i) {
109 out_freqs[i] = table[i].frequency;
110 out_qs[i] = table[i].q;
121 std::vector<double> freqs, qs;
127 const std::vector<double>& q_values)
129 if (frequencies.size() != q_values.size()) {
131 "ResonatorNetwork: frequencies and q_values vectors must have equal length");
141 const std::vector<double>& qs)
146 for (
size_t i = 0; i < frequencies.size(); ++i) {
149 r.
q = std::clamp(qs[i], 0.1, 1000.0);
153 r.
filter = std::make_shared<Filters::IIR>(
154 std::vector<double> { 1.0, 0.0, 0.0 },
155 std::vector<double> { 0.0, 0.0, 0.0 });
180 const double sinw0 = std::sin(w0);
181 const double cosw0 = std::cos(w0);
182 const double alpha = sinw0 / (2.0 * r.
q);
183 const double a0 = 1.0 + alpha;
185 const std::vector<double>
a = {
190 const std::vector<double>
b = {
215 const double norm = 1.0 /
static_cast<double>(
m_resonators.size());
219 nb.reserve(num_samples);
221 std::vector<std::optional<std::span<const double>>> net_exc_bufs;
228 for (
size_t s = 0; s < num_samples; ++s) {
231 double excitation = 0.0;
234 excitation = r.exciter->process_sample(0.0);
235 }
else if (!net_exc_bufs.empty() && net_exc_bufs[ri] && s < net_exc_bufs[ri]->size()) {
236 excitation = (*net_exc_bufs[ri])[s];
238 excitation =
m_exciter->process_sample(0.0);
241 const double out = r.filter->process_sample(excitation) * r.gain;
279 const std::shared_ptr<Node>& source,
292 const std::shared_ptr<NodeNetwork>& source_network)
306 [&](
const auto& m) {
return m.param_name == param_name; });
315 mapping.broadcast_source->get_last_output());
324 if (param ==
"frequency") {
326 }
else if (param ==
"q") {
328 }
else if (param ==
"gain") {
332 }
else if (param ==
"scale") {
338 const std::shared_ptr<NodeNetwork>& source)
342 for (
size_t i = 0; i <
count; ++i) {
343 const auto val = source->get_node_output(i);
344 if (!val.has_value()) {
347 if (param ==
"frequency") {
349 }
else if (param ==
"q") {
351 }
else if (param ==
"gain") {
375 "ResonatorNetwork::set_resonator_exciter: index out of range (index={}, resonator_count={})", index,
m_resonators.size());
384 "ResonatorNetwork::clear_resonator_exciter: index out of range (index={}, resonator_count={})", index,
m_resonators.size());
407 "ResonatorNetwork::set_frequency: index out of range (index={}, resonator_count={})", index,
m_resonators.size());
418 "ResonatorNetwork::set_q: index out of range (index={}, resonator_count={})", index,
m_resonators.size());
421 r.q = std::clamp(
q, 0.1, 1000.0);
429 "ResonatorNetwork::set_resonator_gain: index out of range (index={}, resonator_count={})", index,
m_resonators.size());
454 std::vector<double> freqs, qs;
472 meta[
"num_resonators"] = std::to_string(
m_resonators.size());
476 const std::string prefix =
"resonator_" + std::to_string(r.index) +
"_";
477 meta[prefix +
"freq"] = std::to_string(r.frequency) +
" Hz";
478 meta[prefix +
"q"] = std::to_string(r.q);
479 meta[prefix +
"gain"] = std::to_string(r.gain);
void apply_output_scale()
Apply m_output_scale to m_last_audio_buffer.
double m_output_scale
Post-processing scalar applied to m_last_audio_buffer each batch.
virtual std::unordered_map< std::string, std::string > get_metadata() const
Get network metadata for debugging/visualization.
std::optional< std::span< const double > > get_node_audio_buffer(size_t index) const override
Get output of specific internal node as audio buffer (for ONE_TO_ONE mapping)
ResonatorNetwork(size_t num_resonators, FormantPreset preset=FormantPreset::NONE)
Construct a ResonatorNetwork with a formant preset.
void map_parameter(const std::string ¶m_name, const std::shared_ptr< Node > &source, MappingMode mode=MappingMode::BROADCAST) override
Map a scalar node output to a named networkparameter (BROADCAST)
std::unordered_map< std::string, std::string > get_metadata() const override
Returns network metadata for debugging and visualisation.
void clear_resonator_exciter(size_t index)
Clear per-resonator exciter, reverting to network-level exciter.
std::vector< double > m_last_audio_buffer
Mixed output from last process_batch()
void set_resonator_gain(size_t index, double gain)
Set amplitude gain of a single resonator.
void compute_biquad(ResonatorNode &r)
Compute RBJ biquad bandpass coefficients and push them into a resonator's IIR.
void set_all_q(double q)
Set Q factor of all resonators uniformly.
void set_resonator_exciter(size_t index, const std::shared_ptr< Node > &exciter)
Set a per-resonator exciter.
void unmap_parameter(const std::string ¶m_name) override
Remove a parameter mapping by name.
void set_frequency(size_t index, double frequency)
Set centre frequency of a single resonator and recompute its coefficients.
void apply_preset(FormantPreset preset)
Apply a FormantPreset to the current network.
static void preset_to_vectors(FormantPreset preset, size_t n, std::vector< double > &out_freqs, std::vector< double > &out_qs)
Translate a FormantPreset into parallel frequency/Q vectors.
void build_resonators(const std::vector< double > &frequencies, const std::vector< double > &qs)
Initialise all resonators from a frequency/Q pair list.
std::vector< std::vector< double > > m_node_buffers
Per-resonator sample buffers populated each process_batch()
void apply_broadcast_parameter(const std::string ¶m, double value)
Apply a BROADCAST value to the named parameter across all resonators.
void set_q(size_t index, double q)
Set Q factor of a single resonator and recompute its coefficients.
void set_all_frequencies(double frequency)
Set centre frequency of all resonators uniformly.
std::vector< ResonatorNode > m_resonators
void clear_network_exciter()
Clear the network exciter.
std::vector< ParameterMapping > m_parameter_mappings
std::optional< double > get_node_output(size_t index) const override
Returns the last output sample of the resonator at index.
std::shared_ptr< Node > m_exciter
networ-level shared exciter (may be nullptr)
void process_batch(unsigned int num_samples) override
Processes num_samples through all resonators and accumulates output.
void clear_exciter()
Clear the network-level exciter.
void set_network_exciter(const std::shared_ptr< NodeNetwork > &network)
Set a NodeNetwork as a source of per-resonator excitation (ONE_TO_ONE)
FormantPreset
Common vowel and spectral formant configurations.
@ VOWEL_O
Back vowel /o/ (F1≈500, F2≈900, F3≈2500, F4≈3500, F5≈4500 Hz)
@ VOWEL_I
Close front vowel /i/ (F1≈270, F2≈2300, F3≈3000, F4≈3500, F5≈4500 Hz)
@ VOWEL_A
Open vowel /a/ (F1≈800, F2≈1200, F3≈2500, F4≈3500, F5≈4500 Hz)
@ VOWEL_E
Front vowel /e/ (F1≈400, F2≈2000, F3≈2600, F4≈3500, F5≈4500 Hz)
@ VOWEL_U
Close back vowel /u/ (F1≈300, F2≈800, F3≈2300, F4≈3500, F5≈4500 Hz)
std::optional< std::vector< double > > get_audio_buffer() const override
Returns the mixed audio buffer from the last process_batch() call.
void apply_one_to_one_parameter(const std::string ¶m, const std::shared_ptr< NodeNetwork > &source)
Apply ONE_TO_ONE values from a source network to the named parameter.
void update_mapped_parameters()
Apply all registered parameter mappings for the current cycle.
void set_exciter(const std::shared_ptr< Node > &exciter)
Set a shared exciter node for all resonators.
std::shared_ptr< NodeNetwork > m_network_exciter
Optional NodeNetwork exciter for ONE_TO_ONE mapping (may be nullptr)
@ NodeProcessing
Node graph processing (Nodes::NodeGraphManager)
@ Nodes
DSP Generator and Filter Nodes, graph pipeline, node management.
MappingMode
Defines how nodes map to external entities (e.g., audio channels, graphics objects)
@ ONE_TO_ONE
Node array/network → network nodes (must match count)
@ BROADCAST
One node → all network nodes.
std::shared_ptr< Node > broadcast_source
std::shared_ptr< NodeNetwork > network_source
double frequency
Centre frequency (Hz)
std::shared_ptr< Filters::IIR > filter
Underlying biquad IIR.
double q
Quality factor (dimensionless; higher = narrower bandwidth)
double last_output
Most recent process_sample output.
double gain
Per-resonator output amplitude scale.
size_t index
Position in network.
State of a single biquad bandpass resonator.