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