MayaFlux 0.3.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 Set base frequency (fundamental)
195 * @param frequency Frequency in Hz
196 *
197 * Updates all mode frequencies proportionally to maintain spectrum shape.
198 */
199 void set_fundamental(double frequency);
200
201 /**
202 * @brief Get current fundamental frequency
203 */
204 [[nodiscard]] double get_fundamental() const { return m_fundamental; }
205
206 /**
207 * @brief Set global decay multiplier
208 * @param multiplier Scales all decay times (>1.0 = longer decay, <1.0 =
209 * shorter)
210 */
211 void set_decay_multiplier(double multiplier)
212 {
213 m_decay_multiplier = multiplier;
214 }
215
216 /**
217 * @brief Get mode data (read-only access for visualization)
218 */
219 [[nodiscard]] const std::vector<ModalNode>& get_modes() const
220 {
221 return m_modes;
222 }
223
224 /**
225 * @brief Get specific mode
226 */
227 [[nodiscard]] const ModalNode& get_mode(size_t index) const
228 {
229 return m_modes.at(index);
230 }
231
232 /**
233 * @brief Get output of specific internal node (for ONE_TO_ONE mapping)
234 * @param index Index of node in network
235 * @return Output value, or nullopt if not applicable
236 */
237 [[nodiscard]] std::optional<double> get_node_output(size_t index) const override;
238
239 //-------------------------------------------------------------------------
240 // Exciter System
241 //-------------------------------------------------------------------------
242
243 /**
244 * @brief Set exciter type
245 * @param type Excitation signal type
246 */
247 void set_exciter_type(ExciterType type) { m_exciter_type = type; }
248
249 /**
250 * @brief Set noise burst duration
251 * @param seconds Duration in seconds (for NOISE_BURST and FILTERED_NOISE)
252 */
253 void set_exciter_duration(double seconds);
254
255 /**
256 * @brief Set filter for shaped noise excitation
257 * @param filter Filter node for spectral shaping (FILTERED_NOISE only)
258 */
259 void set_exciter_filter(const std::shared_ptr<Filters::Filter>& filter) { m_exciter_filter = filter; }
260
261 /**
262 * @brief Set custom excitation sample
263 * @param sample Excitation waveform (SAMPLE only)
264 */
265 void set_exciter_sample(const std::vector<double>& sample);
266
267 /**
268 * @brief Set continuous exciter node
269 * @param node Source node for continuous excitation
270 */
271 void set_exciter_node(const std::shared_ptr<Node>& node)
272 {
273 m_exciter_node = node;
274 }
275
276 [[nodiscard]] ExciterType get_exciter_type() const { return m_exciter_type; }
277
278 //-------------------------------------------------------------------------
279 // Spatial Excitation
280 //-------------------------------------------------------------------------
281
282 /**
283 * @brief Excite modes based on normalized strike position
284 * @param position Normalized position (0.0 = node, 1.0 = antinode)
285 * @param strength Overall excitation strength
286 *
287 * Amplitude distribution follows spatial mode shapes:
288 * - Position 0.5 excites all modes equally (center strike)
289 * - Position 0.0 or 1.0 excites odd modes only (edge strike)
290 * - Intermediate positions create physical strike distributions
291 */
292 void excite_at_position(double position, double strength = 1.0);
293
294 /**
295 * @brief Set custom spatial amplitude distribution
296 * @param distribution Per-mode amplitude multipliers (size must match mode count)
297 *
298 * Defines how strike position maps to mode amplitudes.
299 * Default uses sinusoidal mode shapes: sin(n * pi * position)
300 */
301 void set_spatial_distribution(const std::vector<double>& distribution);
302
303 /**
304 * @brief Get current spatial distribution
305 */
306 [[nodiscard]] const std::vector<double>& get_spatial_distribution() const
307 {
308 return m_spatial_distribution;
309 }
310
311 //-------------------------------------------------------------------------
312 // Modal Coupling
313 //-------------------------------------------------------------------------
314
315 /**
316 * @brief Enable/disable modal coupling
317 * @param enable Coupling state
318 */
319 void set_coupling_enabled(bool enable) { m_coupling_enabled = enable; }
320
321 /**
322 * @brief Define bidirectional coupling between two modes
323 * @param mode_a First mode index
324 * @param mode_b Second mode index
325 * @param strength Coupling coefficient (0.0 = no coupling, 1.0 = strong)
326 *
327 * Energy transfer is proportional to amplitude difference:
328 * delta_E = (A_a - A_b) * strength
329 * Conservative transfer: A_a -= delta_E/2, A_b += delta_E/2
330 */
331 void set_mode_coupling(size_t mode_a, size_t mode_b, double strength);
332
333 /**
334 * @brief Remove specific coupling
335 */
336 void remove_mode_coupling(size_t mode_a, size_t mode_b);
337
338 /**
339 * @brief Clear all mode couplings
340 */
341 void clear_couplings() { m_couplings.clear(); }
342
343 /**
344 * @brief Get all active couplings
345 */
346 [[nodiscard]] const auto& get_couplings() const { return m_couplings; }
347
348 /**
349 * @brief Check if coupling is enabled
350 */
351 [[nodiscard]] bool is_coupling_enabled() const { return m_coupling_enabled; }
352
353private:
354 //-------------------------------------------------------------------------
355 // Internal State
356 //-------------------------------------------------------------------------
357
358 std::vector<ModalNode> m_modes;
362 double m_decay_multiplier = 1.0;
363
364 std::vector<std::vector<double>> m_node_buffers; ///< Per-mode sample buffers populated each process_batch()
365
366 //-------------------------------------------------------------------------
367 // Exciter State
368 //-------------------------------------------------------------------------
369
370 ExciterType m_exciter_type { ExciterType::IMPULSE };
371 double m_exciter_duration { 0.01 };
372 std::vector<double> m_exciter_sample;
373 std::shared_ptr<Filters::Filter> m_exciter_filter;
374 std::shared_ptr<Node> m_exciter_node;
375
376 size_t m_exciter_sample_position { 0 };
377 bool m_exciter_active { false };
378 size_t m_exciter_samples_remaining { 0 };
379
380 //-------------------------------------------------------------------------
381 // Spatial Excitation State
382 //-------------------------------------------------------------------------
383
384 std::vector<double> m_spatial_distribution;
385
386 //-------------------------------------------------------------------------
387 // Modal Coupling State
388 //-------------------------------------------------------------------------
389
391 size_t mode_a;
392 size_t mode_b;
393 double strength;
394 };
395
396 std::vector<ModeCoupling> m_couplings;
397 bool m_coupling_enabled { false };
398
399 //-------------------------------------------------------------------------
400 // Exciter Implementation Helpers
401 //-------------------------------------------------------------------------
402
403 /**
404 * @brief Generate exciter signal for current sample
405 */
406 double generate_exciter_sample();
407
408 /**
409 * @brief Initialize exciter for new excitation event
410 */
411 void initialize_exciter(double strength);
412
413 /**
414 * @brief Compute spatial amplitude distribution
415 */
416 void compute_spatial_distribution();
417
418 /**
419 * @brief Apply modal coupling energy transfer
420 */
421 void compute_mode_coupling();
422
423 //-------------------------------------------------------------------------
424 // Initialization Helpers
425 //-------------------------------------------------------------------------
426
427 /**
428 * @brief Generate frequency ratios for predefined spectra
429 */
430 static std::vector<double> generate_spectrum_ratios(Spectrum spectrum,
431 size_t count);
432
433 /**
434 * @brief Initialize modes with given frequency ratios
435 */
436 void initialize_modes(const std::vector<double>& ratios, double base_decay);
437
438 /**
439 * @brief Update mapped parameters before processing
440 */
441 void update_mapped_parameters();
442
443 /**
444 * @brief Apply broadcast parameter to all modes
445 */
446 void apply_broadcast_parameter(const std::string& param, double value);
447
448 /**
449 * @brief Apply one-to-one parameter from another network
450 */
451 void apply_one_to_one_parameter(const std::string& param,
452 const std::shared_ptr<NodeNetwork>& source);
453};
454
455} // namespace MayaFlux::Nodes::Network
Eigen::Index count
double frequency
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.
void set_exciter_type(ExciterType type)
Set exciter type.
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.
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)