MayaFlux 0.1.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
7namespace MayaFlux::Nodes {
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
113 mode.decay_coefficient = std::exp(-1.0 / (base_decay * Config::get_sample_rate()));
114
115 m_modes.push_back(std::move(mode));
116 }
117}
118
120{
121 for (auto& mode : m_modes) {
122 mode.amplitude = 0.0;
123 mode.phase = 0.0;
124 mode.current_frequency = mode.base_frequency;
125 }
126 m_last_output = 0.0;
127}
128
129//-----------------------------------------------------------------------------
130// Processing
131//-----------------------------------------------------------------------------
132
133void ModalNetwork::process_batch(unsigned int num_samples)
134{
136
137 m_last_audio_buffer.clear();
138
139 if (!is_enabled()) {
140 m_last_audio_buffer.assign(num_samples, 0.0);
141 m_last_output = 0.0;
142 return;
143 }
144
146 m_last_audio_buffer.reserve(num_samples);
147
148 for (unsigned int i = 0; i < num_samples; ++i) {
149 double sum = 0.0;
150
151 for (auto& mode : m_modes) {
152
153 if (mode.amplitude > 0.0001) {
154 mode.amplitude *= mode.decay_coefficient;
155 } else {
156 mode.amplitude = 0.0;
157 }
158
159 double sample = mode.oscillator->process_sample(0.0) * mode.amplitude;
160
161 sum += sample;
162 }
163 m_last_audio_buffer.push_back(sum);
164 }
166}
167
168//-----------------------------------------------------------------------------
169// Parameter Mapping
170//-----------------------------------------------------------------------------
171
173{
174 for (const auto& mapping : m_parameter_mappings) {
175 if (mapping.mode == MappingMode::BROADCAST && mapping.broadcast_source) {
176 double value = mapping.broadcast_source->get_last_output();
177 apply_broadcast_parameter(mapping.param_name, value);
178 } else if (mapping.mode == MappingMode::ONE_TO_ONE && mapping.network_source) {
179 apply_one_to_one_parameter(mapping.param_name, mapping.network_source);
180 }
181 }
182}
183
184void ModalNetwork::apply_broadcast_parameter(const std::string& param,
185 double value)
186{
187 if (param == "frequency") {
188 set_fundamental(value);
189 } else if (param == "decay") {
190 m_decay_multiplier = std::max(0.01, value);
191 } else if (param == "amplitude") {
192 for (auto& mode : m_modes) {
193 mode.amplitude *= value;
194 }
195 }
196}
197
199 const std::string& param, const std::shared_ptr<NodeNetwork>& source)
200{
201 if (source->get_node_count() != m_modes.size()) {
202 return;
203 }
204
205 if (param == "amplitude") {
206 for (size_t i = 0; i < m_modes.size(); ++i) {
207 auto val = source->get_node_output(i);
208 if (val) {
209 m_modes[i].amplitude *= *val;
210 }
211 }
212 } else if (param == "detune") {
213 for (size_t i = 0; i < m_modes.size(); ++i) {
214 auto val = source->get_node_output(i);
215 if (val) {
216 double detune_cents = *val * 100.0; // ±100 cents
217 double ratio = std::pow(2.0, detune_cents / 1200.0);
218 m_modes[i].current_frequency = m_modes[i].base_frequency * ratio;
219 m_modes[i].oscillator->set_frequency(m_modes[i].current_frequency);
220 }
221 }
222 }
223}
224
225void ModalNetwork::map_parameter(const std::string& param_name,
226 const std::shared_ptr<Node>& source,
227 MappingMode mode)
228{
229 unmap_parameter(param_name);
230
231 ParameterMapping mapping;
232 mapping.param_name = param_name;
233 mapping.mode = mode;
234 mapping.broadcast_source = source;
235 mapping.network_source = nullptr;
236
237 m_parameter_mappings.push_back(std::move(mapping));
238}
239
241 const std::string& param_name,
242 const std::shared_ptr<NodeNetwork>& source_network)
243{
244 unmap_parameter(param_name);
245
246 ParameterMapping mapping;
247 mapping.param_name = param_name;
249 mapping.broadcast_source = nullptr;
250 mapping.network_source = source_network;
251
252 m_parameter_mappings.push_back(std::move(mapping));
253}
254
255void ModalNetwork::unmap_parameter(const std::string& param_name)
256{
258 std::remove_if(m_parameter_mappings.begin(), m_parameter_mappings.end(),
259 [&](const auto& m) { return m.param_name == param_name; }),
261}
262
263//-----------------------------------------------------------------------------
264// Modal Control
265//-----------------------------------------------------------------------------
266
267void ModalNetwork::excite(double strength)
268{
269 for (auto& mode : m_modes) {
270 mode.amplitude = mode.initial_amplitude * strength;
271 }
272}
273
274void ModalNetwork::excite_mode(size_t mode_index, double strength)
275{
276 if (mode_index < m_modes.size()) {
277 m_modes[mode_index].amplitude = m_modes[mode_index].initial_amplitude * strength;
278 }
279}
280
281void ModalNetwork::damp(double damping_factor)
282{
283 for (auto& mode : m_modes) {
284 mode.amplitude *= damping_factor;
285 }
286}
287
288void ModalNetwork::set_fundamental(double frequency)
289{
290 m_fundamental = frequency;
291
292 for (auto& mode : m_modes) {
293 mode.base_frequency = m_fundamental * mode.frequency_ratio;
294 mode.current_frequency = mode.base_frequency;
295
296 mode.oscillator->set_frequency(mode.current_frequency);
297 }
298}
299
300//-----------------------------------------------------------------------------
301// Metadata
302//-----------------------------------------------------------------------------
303
304std::unordered_map<std::string, std::string>
306{
307 auto metadata = NodeNetwork::get_metadata();
308
309 metadata["fundamental"] = std::to_string(m_fundamental) + " Hz";
310 metadata["spectrum"] = [this]() {
311 switch (m_spectrum) {
313 return "HARMONIC";
315 return "INHARMONIC";
317 return "STRETCHED";
318 case Spectrum::CUSTOM:
319 return "CUSTOM";
320 default:
321 return "UNKNOWN";
322 }
323 }();
324 metadata["decay_multiplier"] = std::to_string(m_decay_multiplier);
325
326 double avg_amplitude = 0.0;
327 for (const auto& mode : m_modes) {
328 avg_amplitude += mode.amplitude;
329 }
330 avg_amplitude /= m_modes.size();
331 metadata["avg_amplitude"] = std::to_string(avg_amplitude);
332
333 return metadata;
334}
335
336[[nodiscard]] std::optional<double> ModalNetwork::get_node_output(size_t index) const
337{
338 if (m_modes.size() > index) {
339 return m_modes[index].oscillator->get_last_output();
340 }
341 return std::nullopt; // Default: not supported
342}
343
344} // namespace MayaFlux::Nodes
#define B(method_name, full_type_name)
Definition Creator.hpp:203
static std::vector< double > generate_spectrum_ratios(Spectrum spectrum, size_t count)
Generate frequency ratios for predefined spectra.
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 damp(double damping_factor=0.1)
Damp all modes (rapidly reduce amplitude)
std::vector< ModalNode > m_modes
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 unmap_parameter(const std::string &param_name) override
Remove parameter mapping.
ModalNetwork(size_t num_modes, double fundamental=220.0, Spectrum spectrum=Spectrum::HARMONIC, double base_decay=1.0)
Create modal network with predefined spectrum.
void apply_broadcast_parameter(const std::string &param, double value)
Apply broadcast parameter to all modes.
void excite(double strength=1.0)
Excite all modes (strike/pluck)
std::unordered_map< std::string, std::string > get_metadata() const override
Get network metadata for debugging/visualization.
void set_fundamental(double frequency)
Set base frequency (fundamental)
std::optional< double > get_node_output(size_t index) const override
Get output of specific internal node (for ONE_TO_ONE mapping)
void reset() override
Reset network to initial state.
void update_mapped_parameters()
Update mapped parameters before processing.
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.
void excite_mode(size_t mode_index, double strength=1.0)
Excite specific mode.
void initialize_modes(const std::vector< double > &ratios, double base_decay)
Initialize modes with given frequency ratios.
std::vector< ParameterMapping > m_parameter_mappings
void set_topology(Topology topology)
Set the network's topology.
void ensure_initialized()
Ensure initialize() is called exactly once.
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.
@ INDEPENDENT
No connections, nodes process independently.
std::vector< double > m_last_audio_buffer
virtual std::unordered_map< std::string, std::string > get_metadata() const
Get network metadata for debugging/visualization.
@ AUDIO_SINK
Aggregated audio samples sent to output.
bool is_enabled() const
Check if network is enabled.
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
Contains the node-based computational processing system components.
Definition Chronie.hpp:5
std::shared_ptr< Generator::Generator > oscillator
Sine wave generator.
double decay_time
Time constant for amplitude decay (seconds)
double decay_coefficient
Precomputed exp factor.
double frequency_ratio
Ratio relative to fundamental.
double base_frequency
Frequency without modulation.
double current_frequency
After mapping/modulation.
double initial_amplitude
Amplitude at excitation.
double amplitude
Current amplitude (0.0 to 1.0)
Represents a single resonant mode.
std::shared_ptr< NodeNetwork > network_source