MayaFlux 0.2.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ModalNetwork.cpp
Go to the documentation of this file.
1#include "ModalNetwork.hpp"
2
4
6
8
9//-----------------------------------------------------------------------------
10// Construction
11//-----------------------------------------------------------------------------
12
13ModalNetwork::ModalNetwork(size_t num_modes, double fundamental,
14 Spectrum spectrum, double base_decay)
15 : m_spectrum(spectrum)
16 , m_fundamental(fundamental)
17{
20
21 auto ratios = generate_spectrum_ratios(spectrum, num_modes);
22 initialize_modes(ratios, base_decay);
23}
24
25ModalNetwork::ModalNetwork(const std::vector<double>& frequency_ratios,
26 double fundamental, double base_decay)
27 : m_spectrum(Spectrum::CUSTOM)
28 , m_fundamental(fundamental)
29{
32
33 initialize_modes(frequency_ratios, base_decay);
34}
35
36//-----------------------------------------------------------------------------
37// Spectrum Generation
38//-----------------------------------------------------------------------------
39
41 size_t count)
42{
43 std::vector<double> ratios;
44 ratios.reserve(count);
45
46 switch (spectrum) {
48 // Perfect integer harmonics: 1, 2, 3, 4...
49 for (size_t i = 0; i < count; ++i) {
50 ratios.push_back(static_cast<double>(i + 1));
51 }
52 break;
53
55 // Bell-like spectrum (approximate mode ratios for circular plates)
56 // Based on Bessel function zeros
57 ratios = { 1.0, 2.756, 5.404, 8.933, 13.344, 18.64, 24.81, 31.86 };
58 while (ratios.size() < count) {
59 double last = ratios.back();
60 ratios.push_back(last + 6.8);
61 }
62 ratios.resize(count);
63 break;
64
66 // f_n = n * f_0 * sqrt(1 + B * n^2)
67 // Using small B = 0.0001 for moderate stretching
68 {
69 constexpr double B = 0.0001;
70 for (size_t n = 1; n <= count; ++n) {
71 double ratio = n * std::sqrt(1.0 + B * n * n);
72 ratios.push_back(ratio);
73 }
74 }
75 break;
76
78 // Should not reach here - custom ratios provided directly
79 for (size_t i = 0; i < count; ++i) {
80 ratios.push_back(static_cast<double>(i + 1));
81 }
82 break;
83 }
84
85 return ratios;
86}
87
88//-----------------------------------------------------------------------------
89// Mode Initialization
90//-----------------------------------------------------------------------------
91
92void ModalNetwork::initialize_modes(const std::vector<double>& ratios,
93 double base_decay)
94{
95 m_modes.clear();
96 m_modes.reserve(ratios.size());
97
98 for (size_t i = 0; i < ratios.size(); ++i) {
99 ModalNode mode;
100 mode.index = i;
101 mode.frequency_ratio = ratios[i];
102 mode.base_frequency = m_fundamental * ratios[i];
104
105 mode.decay_time = base_decay / ratios[i];
106
107 // Initial amplitude decreases with mode number (1/n falloff)
108 mode.initial_amplitude = 1.0 / (i + 1);
109 mode.amplitude = 0.0;
110
111 mode.oscillator = std::make_shared<Generator::Sine>(mode.current_frequency);
112 mode.oscillator->set_in_network(true);
113
114 mode.decay_coefficient = std::exp(-1.0 / (base_decay * Config::get_sample_rate()));
115
116 m_modes.push_back(std::move(mode));
117 }
118}
119
121{
122 for (auto& mode : m_modes) {
123 mode.amplitude = 0.0;
124 mode.phase = 0.0;
125 mode.current_frequency = mode.base_frequency;
126 }
127 m_last_output = 0.0;
128}
129
130//-----------------------------------------------------------------------------
131// Processing
132//-----------------------------------------------------------------------------
133
134void ModalNetwork::process_batch(unsigned int num_samples)
135{
137
138 m_last_audio_buffer.clear();
139
140 if (!is_enabled()) {
141 m_last_audio_buffer.assign(num_samples, 0.0);
142 m_last_output = 0.0;
143 return;
144 }
145
147 m_last_audio_buffer.reserve(num_samples);
148
149 for (unsigned int i = 0; i < num_samples; ++i) {
150 double sum = 0.0;
151
152 for (auto& mode : m_modes) {
153
154 if (mode.amplitude > 0.0001) {
155 mode.amplitude *= mode.decay_coefficient;
156 } else {
157 mode.amplitude = 0.0;
158 }
159
160 double sample = mode.oscillator->process_sample(0.0) * mode.amplitude;
161
162 sum += sample;
163 }
164 m_last_audio_buffer.push_back(sum);
165 }
167}
168
169//-----------------------------------------------------------------------------
170// Parameter Mapping
171//-----------------------------------------------------------------------------
172
174{
175 for (const auto& mapping : m_parameter_mappings) {
176 if (mapping.mode == MappingMode::BROADCAST && mapping.broadcast_source) {
177 double value = mapping.broadcast_source->get_last_output();
178 apply_broadcast_parameter(mapping.param_name, value);
179 } else if (mapping.mode == MappingMode::ONE_TO_ONE && mapping.network_source) {
180 apply_one_to_one_parameter(mapping.param_name, mapping.network_source);
181 }
182 }
183}
184
185void ModalNetwork::apply_broadcast_parameter(const std::string& param,
186 double value)
187{
188 if (param == "frequency") {
189 set_fundamental(value);
190 } else if (param == "decay") {
191 m_decay_multiplier = std::max(0.01, value);
192 } else if (param == "amplitude") {
193 for (auto& mode : m_modes) {
194 mode.amplitude *= value;
195 }
196 }
197}
198
200 const std::string& param, const std::shared_ptr<NodeNetwork>& source)
201{
202 if (source->get_node_count() != m_modes.size()) {
203 return;
204 }
205
206 if (param == "amplitude") {
207 for (size_t i = 0; i < m_modes.size(); ++i) {
208 auto val = source->get_node_output(i);
209 if (val) {
210 m_modes[i].amplitude *= *val;
211 }
212 }
213 } else if (param == "detune") {
214 for (size_t i = 0; i < m_modes.size(); ++i) {
215 auto val = source->get_node_output(i);
216 if (val) {
217 double detune_cents = *val * 100.0; // ±100 cents
218 double ratio = std::pow(2.0, detune_cents / 1200.0);
219 m_modes[i].current_frequency = m_modes[i].base_frequency * ratio;
220 m_modes[i].oscillator->set_frequency(m_modes[i].current_frequency);
221 }
222 }
223 }
224}
225
226void ModalNetwork::map_parameter(const std::string& param_name,
227 const std::shared_ptr<Node>& source,
228 MappingMode mode)
229{
230 unmap_parameter(param_name);
231
232 ParameterMapping mapping;
233 mapping.param_name = param_name;
234 mapping.mode = mode;
235 mapping.broadcast_source = source;
236 mapping.network_source = nullptr;
237
238 m_parameter_mappings.push_back(std::move(mapping));
239}
240
242 const std::string& param_name,
243 const std::shared_ptr<NodeNetwork>& source_network)
244{
245 unmap_parameter(param_name);
246
247 ParameterMapping mapping;
248 mapping.param_name = param_name;
250 mapping.broadcast_source = nullptr;
251 mapping.network_source = source_network;
252
253 m_parameter_mappings.push_back(std::move(mapping));
254}
255
256void ModalNetwork::unmap_parameter(const std::string& param_name)
257{
259 std::remove_if(m_parameter_mappings.begin(), m_parameter_mappings.end(),
260 [&](const auto& m) { return m.param_name == param_name; }),
262}
263
264//-----------------------------------------------------------------------------
265// Modal Control
266//-----------------------------------------------------------------------------
267
268void ModalNetwork::excite(double strength)
269{
270 for (auto& mode : m_modes) {
271 mode.amplitude = mode.initial_amplitude * strength;
272 }
273}
274
275void ModalNetwork::excite_mode(size_t mode_index, double strength)
276{
277 if (mode_index < m_modes.size()) {
278 m_modes[mode_index].amplitude = m_modes[mode_index].initial_amplitude * strength;
279 }
280}
281
282void ModalNetwork::damp(double damping_factor)
283{
284 for (auto& mode : m_modes) {
285 mode.amplitude *= damping_factor;
286 }
287}
288
289void ModalNetwork::set_fundamental(double frequency)
290{
291 m_fundamental = frequency;
292
293 for (auto& mode : m_modes) {
294 mode.base_frequency = m_fundamental * mode.frequency_ratio;
295 mode.current_frequency = mode.base_frequency;
296
297 mode.oscillator->set_frequency(mode.current_frequency);
298 }
299}
300
301//-----------------------------------------------------------------------------
302// Metadata
303//-----------------------------------------------------------------------------
304
305std::unordered_map<std::string, std::string>
307{
308 auto metadata = NodeNetwork::get_metadata();
309
310 metadata["fundamental"] = std::to_string(m_fundamental) + " Hz";
311 metadata["spectrum"] = [this]() {
312 switch (m_spectrum) {
314 return "HARMONIC";
316 return "INHARMONIC";
318 return "STRETCHED";
319 case Spectrum::CUSTOM:
320 return "CUSTOM";
321 default:
322 return "UNKNOWN";
323 }
324 }();
325 metadata["decay_multiplier"] = std::to_string(m_decay_multiplier);
326
327 double avg_amplitude = 0.0;
328 for (const auto& mode : m_modes) {
329 avg_amplitude += mode.amplitude;
330 }
331 avg_amplitude /= m_modes.size();
332 metadata["avg_amplitude"] = std::to_string(avg_amplitude);
333
334 return metadata;
335}
336
337[[nodiscard]] std::optional<double> ModalNetwork::get_node_output(size_t index) const
338{
339 if (m_modes.size() > index) {
340 return m_modes[index].oscillator->get_last_output();
341 }
342 return std::nullopt; // Default: not supported
343}
344
345} // namespace MayaFlux::Nodes::Network
#define B(method_name, full_type_name)
Definition Creator.hpp:203
std::optional< double > get_node_output(size_t index) const override
Get output of specific internal node (for ONE_TO_ONE mapping)
void process_batch(unsigned int num_samples) override
Process the network for the given number of samples.
void apply_one_to_one_parameter(const std::string &param, const std::shared_ptr< NodeNetwork > &source)
Apply one-to-one parameter from another network.
void excite_mode(size_t mode_index, double strength=1.0)
Excite specific mode.
void excite(double strength=1.0)
Excite all modes (strike/pluck)
void initialize_modes(const std::vector< double > &ratios, double base_decay)
Initialize modes with given frequency ratios.
void reset() override
Reset network to initial state.
void apply_broadcast_parameter(const std::string &param, double value)
Apply broadcast parameter to all modes.
void damp(double damping_factor=0.1)
Damp all modes (rapidly reduce amplitude)
std::unordered_map< std::string, std::string > get_metadata() const override
Get network metadata for debugging/visualization.
void unmap_parameter(const std::string &param_name) override
Remove parameter mapping.
void set_fundamental(double frequency)
Set base frequency (fundamental)
void map_parameter(const std::string &param_name, const std::shared_ptr< Node > &source, MappingMode mode=MappingMode::BROADCAST) override
Map external node output to network parameter.
Spectrum
Predefined frequency relationship patterns.
@ STRETCHED
Piano-like stiffness: f, 2.01f, 3.02f, 4.04f...
@ INHARMONIC
Bell-like: f, 2.76f, 5.40f, 8.93f, 13.34f...
@ CUSTOM
User-provided frequency ratios.
@ HARMONIC
Integer harmonics: f, 2f, 3f, 4f...
void update_mapped_parameters()
Update mapped parameters before processing.
static std::vector< double > generate_spectrum_ratios(Spectrum spectrum, size_t count)
Generate frequency ratios for predefined spectra.
ModalNetwork(size_t num_modes, double fundamental=220.0, Spectrum spectrum=Spectrum::HARMONIC, double base_decay=1.0)
Create modal network with predefined spectrum.
std::vector< ParameterMapping > m_parameter_mappings
void set_topology(Topology topology)
Set the network's topology.
@ AUDIO_SINK
Aggregated audio samples sent to output.
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.
bool is_enabled() const
Check if network is enabled.
@ INDEPENDENT
No connections, nodes process independently.
void ensure_initialized()
Ensure initialize() is called exactly once.
virtual std::unordered_map< std::string, std::string > get_metadata() const
Get network metadata for debugging/visualization.
std::vector< double > m_last_audio_buffer
void set_output_mode(OutputMode mode)
Set the network's output routing mode.
uint32_t get_sample_rate()
Gets the sample rate from the default engine.
Definition Config.cpp:46
double base_frequency
Frequency without modulation.
double decay_coefficient
Precomputed exp factor.
double initial_amplitude
Amplitude at excitation.
std::shared_ptr< Generator::Generator > oscillator
Sine wave generator.
double frequency_ratio
Ratio relative to fundamental.
double current_frequency
After mapping/modulation.
double decay_time
Time constant for amplitude decay (seconds)
double amplitude
Current amplitude (0.0 to 1.0)