MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
ModalNetwork.hpp
Go to the documentation of this file.
1#pragma once
2
4#include "NodeNetwork.hpp"
5
7class Generator;
8}
9
11class Filter;
12}
13
15
16/**
17 * @class ModalNetwork
18 * @brief Network of resonant modes for modal synthesis
19 *
20 * CONCEPT:
21 * ========
22 * Modal synthesis models physical objects as collections of resonant modes,
23 * each with its own frequency, decay rate, and amplitude. The sum of all
24 * modes produces rich, organic timbres characteristic of struck/plucked
25 * instruments (bells, marimbas, strings, membranes).
26 *
27 * STRUCTURE:
28 * ==========
29 * Each mode is an independent oscillator (typically Sine) with:
30 * - Frequency: Resonant frequency of the mode
31 * - Decay: Exponential amplitude envelope
32 * - Amplitude: Initial strike/excitation strength
33 *
34 * Modes can follow various frequency relationships:
35 * - HARMONIC: f, 2f, 3f, 4f... (ideal strings)
36 * - INHARMONIC: f, 2.76f, 5.40f, 8.93f... (bells, bars)
37 * - STRETCHED: f, 2.01f, 3.02f, 4.04f... (stiff strings, piano)
38 * - CUSTOM: User-defined frequency ratios
39 *
40 * USAGE:
41 * ======
42 * ```cpp
43 * // Bell-like inharmonic spectrum
44 * auto bell = std::make_shared<ModalNetwork>(
45 * 16, // 16 modes
46 * 220.0, // Base frequency
47 * ModalNetwork::Spectrum::INHARMONIC
48 * );
49 * bell->set_output_mode(OutputMode::AUDIO_SINK);
50 * bell->excite(1.0); // Strike the bell
51 *
52 * node_graph_manager->add_network(bell, ProcessingToken::AUDIO_RATE);
53 * ```
54 *
55 * PARAMETER MAPPING:
56 * ==================
57 * External nodes can control:
58 * - "frequency": Base frequency (BROADCAST)
59 * - "decay": Global decay multiplier (BROADCAST)
60 * - "amplitude": Per-mode amplitude (ONE_TO_ONE)
61 * - "detune": Per-mode frequency offset (ONE_TO_ONE)
62 */
63class MAYAFLUX_API ModalNetwork : public NodeNetwork {
64public:
65 /**
66 * @enum Spectrum
67 * @brief Predefined frequency relationship patterns
68 */
69 enum class Spectrum : uint8_t {
70 HARMONIC, ///< Integer harmonics: f, 2f, 3f, 4f...
71 INHARMONIC, ///< Bell-like: f, 2.76f, 5.40f, 8.93f, 13.34f...
72 STRETCHED, ///< Piano-like stiffness: f, 2.01f, 3.02f, 4.04f...
73 CUSTOM ///< User-provided frequency ratios
74 };
75
76 /**
77 * @enum ExciterType
78 * @brief Excitation signal types for modal synthesis
79 */
80 enum class ExciterType : uint8_t {
81 IMPULSE, ///< Single-sample Dirac impulse (default)
82 NOISE_BURST, ///< Short white noise burst
83 FILTERED_NOISE, ///< Spectrally-shaped noise burst
84 SAMPLE, ///< User-provided excitation waveform
85 CONTINUOUS ///< External node as continuous exciter
86 };
87
88 /**
89 * @struct ModalNode
90 * @brief Represents a single resonant mode
91 */
92 struct ModalNode {
93 std::shared_ptr<Generator::Generator> oscillator; ///< Sine wave generator
94
95 double base_frequency; ///< Frequency without modulation
96 double current_frequency; ///< After mapping/modulation
97 double frequency_ratio; ///< Ratio relative to fundamental
98
99 double decay_time; ///< Time constant for amplitude decay (seconds)
100 double amplitude; ///< Current amplitude (0.0 to 1.0)
101 double initial_amplitude; ///< Amplitude at excitation
102 double decay_coefficient; ///< Precomputed exp factor
103
104 double phase = 0.0; ///< Current phase (for manual oscillator impl)
105 size_t index; ///< Index in network
106 };
107
108 //-------------------------------------------------------------------------
109 // Construction
110 //-------------------------------------------------------------------------
111
112 /**
113 * @brief Create modal network with predefined spectrum
114 * @param num_modes Number of resonant modes
115 * @param fundamental Base frequency in Hz
116 * @param spectrum Frequency relationship pattern
117 * @param base_decay Base decay time in seconds (modes get proportional decay)
118 */
119 ModalNetwork(size_t num_modes, double fundamental = 220.0,
120 Spectrum spectrum = Spectrum::HARMONIC, double base_decay = 1.0);
121
122 /**
123 * @brief Create modal network with custom frequency ratios
124 * @param frequency_ratios Vector of frequency multipliers relative to
125 * fundamental
126 * @param fundamental Base frequency in Hz
127 * @param base_decay Base decay time in seconds
128 */
129 ModalNetwork(const std::vector<double>& frequency_ratios,
130 double fundamental = 220.0, double base_decay = 1.0);
131
132 //-------------------------------------------------------------------------
133 // NodeNetwork Interface Implementation
134 //-------------------------------------------------------------------------
135
136 void process_batch(unsigned int num_samples) override;
137
138 [[nodiscard]] size_t get_node_count() const override
139 {
140 return m_modes.size();
141 }
142
143 void initialize() override { };
144
145 void reset() override;
146
147 [[nodiscard]] std::unordered_map<std::string, std::string>
148 get_metadata() const override;
149
150 [[nodiscard]] std::optional<std::span<const double>>
151 get_node_audio_buffer(size_t index) const override;
152
153 //-------------------------------------------------------------------------
154 // Parameter Mapping
155 //-------------------------------------------------------------------------
156
157 void map_parameter(const std::string& param_name,
158 const std::shared_ptr<Node>& source,
159 MappingMode mode = MappingMode::BROADCAST) override;
160
161 void
162 map_parameter(const std::string& param_name,
163 const std::shared_ptr<NodeNetwork>& source_network) override;
164
165 void unmap_parameter(const std::string& param_name) override;
166
167 //-------------------------------------------------------------------------
168 // Modal Control
169 //-------------------------------------------------------------------------
170
171 /**
172 * @brief Excite all modes (strike/pluck)
173 * @param strength Excitation strength (0.0 to 1.0+)
174 *
175 * Resets all mode amplitudes to their initial values scaled by strength.
176 * Simulates striking or plucking the resonant structure.
177 */
178 void excite(double strength = 1.0);
179
180 /**
181 * @brief Excite specific mode
182 * @param mode_index Index of mode to excite
183 * @param strength Excitation strength
184 */
185 void excite_mode(size_t mode_index, double strength = 1.0);
186
187 /**
188 * @brief Damp all modes (rapidly reduce amplitude)
189 * @param damping_factor Multiplier applied to current amplitudes (0.0 to 1.0)
190 */
191 void damp(double damping_factor = 0.1);
192
193 /**
194 * @brief Release continuous excitation and allow modes to decay naturally
195 */
196 void release();
197
198 /**
199 * @brief Set base frequency (fundamental)
200 * @param frequency Frequency in Hz
201 *
202 * Updates all mode frequencies proportionally to maintain spectrum shape.
203 */
204 void set_fundamental(double frequency);
205
206 /**
207 * @brief Get current fundamental frequency
208 */
209 [[nodiscard]] double get_fundamental() const { return m_fundamental; }
210
211 /**
212 * @brief Set global decay multiplier
213 * @param multiplier Scales all decay times (>1.0 = longer decay, <1.0 =
214 * shorter)
215 */
216 void set_decay_multiplier(double multiplier)
217 {
218 m_decay_multiplier = multiplier;
219 }
220
221 /**
222 * @brief Get mode data (read-only access for visualization)
223 */
224 [[nodiscard]] const std::vector<ModalNode>& get_modes() const
225 {
226 return m_modes;
227 }
228
229 /**
230 * @brief Get specific mode
231 */
232 [[nodiscard]] const ModalNode& get_mode(size_t index) const
233 {
234 return m_modes.at(index);
235 }
236
237 /**
238 * @brief Get output of specific internal node (for ONE_TO_ONE mapping)
239 * @param index Index of node in network
240 * @return Output value, or nullopt if not applicable
241 */
242 [[nodiscard]] std::optional<double> get_node_output(size_t index) const override;
243
244 //-------------------------------------------------------------------------
245 // Exciter System
246 //-------------------------------------------------------------------------
247
248 /**
249 * @brief Set exciter type
250 * @param type Excitation signal type
251 */
252 void set_exciter_type(ExciterType type);
253
254 /**
255 * @brief Set noise burst duration
256 * @param seconds Duration in seconds (for NOISE_BURST and FILTERED_NOISE)
257 */
258 void set_exciter_duration(double seconds);
259
260 /**
261 * @brief Set filter for shaped noise excitation
262 * @param filter Filter node for spectral shaping (FILTERED_NOISE only)
263 */
264 void set_exciter_filter(const std::shared_ptr<Filters::Filter>& filter) { m_exciter_filter = filter; }
265
266 /**
267 * @brief Set custom excitation sample
268 * @param sample Excitation waveform (SAMPLE only)
269 */
270 void set_exciter_sample(const std::vector<double>& sample);
271
272 /**
273 * @brief Set continuous exciter node
274 * @param node Source node for continuous excitation
275 */
276 void set_exciter_node(const std::shared_ptr<Node>& node)
277 {
278 m_exciter_node = node;
279 }
280
281 [[nodiscard]] ExciterType get_exciter_type() const { return m_exciter_type; }
282
283 //-------------------------------------------------------------------------
284 // Spatial Excitation
285 //-------------------------------------------------------------------------
286
287 /**
288 * @brief Excite modes based on normalized strike position
289 * @param position Normalized position (0.0 = node, 1.0 = antinode)
290 * @param strength Overall excitation strength
291 *
292 * Amplitude distribution follows spatial mode shapes:
293 * - Position 0.5 excites all modes equally (center strike)
294 * - Position 0.0 or 1.0 excites odd modes only (edge strike)
295 * - Intermediate positions create physical strike distributions
296 */
297 void excite_at_position(double position, double strength = 1.0);
298
299 /**
300 * @brief Set custom spatial amplitude distribution
301 * @param distribution Per-mode amplitude multipliers (size must match mode count)
302 *
303 * Defines how strike position maps to mode amplitudes.
304 * Default uses sinusoidal mode shapes: sin(n * pi * position)
305 */
306 void set_spatial_distribution(const std::vector<double>& distribution);
307
308 /**
309 * @brief Get current spatial distribution
310 */
311 [[nodiscard]] const std::vector<double>& get_spatial_distribution() const
312 {
313 return m_spatial_distribution;
314 }
315
316 //-------------------------------------------------------------------------
317 // Modal Coupling
318 //-------------------------------------------------------------------------
319
320 /**
321 * @brief Enable/disable modal coupling
322 * @param enable Coupling state
323 */
324 void set_coupling_enabled(bool enable) { m_coupling_enabled = enable; }
325
326 /**
327 * @brief Define bidirectional coupling between two modes
328 * @param mode_a First mode index
329 * @param mode_b Second mode index
330 * @param strength Coupling coefficient (0.0 = no coupling, 1.0 = strong)
331 *
332 * Energy transfer is proportional to amplitude difference:
333 * delta_E = (A_a - A_b) * strength
334 * Conservative transfer: A_a -= delta_E/2, A_b += delta_E/2
335 */
336 void set_mode_coupling(size_t mode_a, size_t mode_b, double strength);
337
338 /**
339 * @brief Remove specific coupling
340 */
341 void remove_mode_coupling(size_t mode_a, size_t mode_b);
342
343 /**
344 * @brief Clear all mode couplings
345 */
346 void clear_couplings() { m_couplings.clear(); }
347
348 /**
349 * @brief Get all active couplings
350 */
351 [[nodiscard]] const auto& get_couplings() const { return m_couplings; }
352
353 /**
354 * @brief Check if coupling is enabled
355 */
356 [[nodiscard]] bool is_coupling_enabled() const { return m_coupling_enabled; }
357
358private:
359 //-------------------------------------------------------------------------
360 // Internal State
361 //-------------------------------------------------------------------------
362
363 std::vector<ModalNode> m_modes;
367 double m_decay_multiplier = 1.0;
368
369 std::vector<std::vector<double>> m_node_buffers; ///< Per-mode sample buffers populated each process_batch()
370
371 //-------------------------------------------------------------------------
372 // Exciter State
373 //-------------------------------------------------------------------------
374
375 ExciterType m_exciter_type { ExciterType::IMPULSE };
376 double m_exciter_duration { 0.01 };
377 double m_exciter_strength { 1.0 };
378 std::vector<double> m_exciter_sample;
379 std::vector<double> m_exciter_node_buffer;
380 std::shared_ptr<Filters::Filter> m_exciter_filter;
381 std::shared_ptr<Node> m_exciter_node;
382
383 size_t m_exciter_sample_position {};
384 size_t m_exciter_node_buffer_pos {};
385 bool m_exciter_active {};
386 size_t m_exciter_samples_remaining {};
387
388 //-------------------------------------------------------------------------
389 // Spatial Excitation State
390 //-------------------------------------------------------------------------
391
392 std::vector<double> m_spatial_distribution;
393
394 //-------------------------------------------------------------------------
395 // Modal Coupling State
396 //-------------------------------------------------------------------------
397
399 size_t mode_a;
400 size_t mode_b;
401 double strength;
402 };
403
404 std::vector<ModeCoupling> m_couplings;
405 bool m_coupling_enabled {};
406
407 //-------------------------------------------------------------------------
408 // Exciter Implementation Helpers
409 //-------------------------------------------------------------------------
410
411 /**
412 * @brief Generate exciter signal for current sample
413 */
414 double generate_exciter_sample();
415
416 /**
417 * @brief Initialize exciter for new excitation event
418 */
419 void initialize_exciter(double strength);
420
421 /**
422 * @brief Compute spatial amplitude distribution
423 */
424 void compute_spatial_distribution();
425
426 /**
427 * @brief Apply modal coupling energy transfer
428 */
429 void compute_mode_coupling();
430
431 //-------------------------------------------------------------------------
432 // Initialization Helpers
433 //-------------------------------------------------------------------------
434
435 /**
436 * @brief Generate frequency ratios for predefined spectra
437 */
438 static std::vector<double> generate_spectrum_ratios(Spectrum spectrum,
439 size_t count);
440
441 /**
442 * @brief Initialize modes with given frequency ratios
443 */
444 void initialize_modes(const std::vector<double>& ratios, double base_decay);
445
446 /**
447 * @brief Update mapped parameters before processing
448 */
449 void update_mapped_parameters();
450
451 /**
452 * @brief Apply broadcast parameter to all modes
453 */
454 void apply_broadcast_parameter(const std::string& param, double value);
455
456 /**
457 * @brief Apply one-to-one parameter from another network
458 */
459 void apply_one_to_one_parameter(const std::string& param,
460 const std::shared_ptr<NodeNetwork>& source);
461};
462
463} // namespace MayaFlux::Nodes::Network
double frequency
size_t count
Unified generative infrastructure for stochastic and procedural algorithms.
double get_fundamental() const
Get current fundamental frequency.
std::shared_ptr< Filters::Filter > m_exciter_filter
std::vector< std::vector< double > > m_node_buffers
Per-mode sample buffers populated each process_batch()
ExciterType
Excitation signal types for modal synthesis.
void set_exciter_filter(const std::shared_ptr< Filters::Filter > &filter)
Set filter for shaped noise excitation.
void set_coupling_enabled(bool enable)
Enable/disable modal coupling.
void set_exciter_node(const std::shared_ptr< Node > &node)
Set continuous exciter node.
void set_decay_multiplier(double multiplier)
Set global decay multiplier.
Kinesis::Stochastic::Stochastic m_random_generator
const std::vector< ModalNode > & get_modes() const
Get mode data (read-only access for visualization)
const ModalNode & get_mode(size_t index) const
Get specific mode.
std::vector< ModeCoupling > m_couplings
bool is_coupling_enabled() const
Check if coupling is enabled.
size_t get_node_count() const override
Get the number of nodes in the network.
void initialize() override
Called once before first process_batch()
const auto & get_couplings() const
Get all active couplings.
std::vector< double > m_exciter_node_buffer
void clear_couplings()
Clear all mode couplings.
Spectrum
Predefined frequency relationship patterns.
std::vector< double > m_spatial_distribution
const std::vector< double > & get_spatial_distribution() const
Get current spatial distribution.
Network of resonant modes for modal synthesis.
Abstract base class for structured collections of nodes with defined relationships.
MappingMode
Defines how nodes map to external entities (e.g., audio channels, graphics objects)
double base_frequency
Frequency without modulation.
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)