15 Spectrum spectrum,
double base_decay)
16 : m_spectrum(spectrum)
17 , m_fundamental(fundamental)
27 double fundamental,
double base_decay)
29 , m_fundamental(fundamental)
44 std::vector<double> ratios;
45 ratios.reserve(
count);
50 for (
size_t i = 0; i <
count; ++i) {
51 ratios.push_back(
static_cast<double>(i + 1));
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);
69 constexpr double B = 0.0001;
70 for (
size_t n = 1; n <=
count; ++n) {
71 auto dn =
static_cast<double>(n);
72 double ratio = dn * std::sqrt(1.0 + B * dn * dn);
73 ratios.push_back(ratio);
79 for (
size_t i = 0; i <
count; ++i) {
80 ratios.push_back(
static_cast<double>(i + 1));
99 for (
size_t i = 0; i < ratios.size(); ++i) {
102 mode.frequency_ratio = ratios[i];
104 mode.current_frequency =
mode.base_frequency;
106 mode.decay_time = base_decay / ratios[i];
108 mode.initial_amplitude = 1.0 /
static_cast<double>(i + 1);
109 mode.amplitude = 0.0;
111 mode.oscillator = std::make_shared<Generator::Sine>(
static_cast<float>(
mode.current_frequency));
112 mode.oscillator->set_in_network(
true);
126 mode.decay_coefficient = coefficient;
132 multiplier = std::max(0.01, multiplier);
144 mode.amplitude = 0.0;
146 mode.current_frequency =
mode.base_frequency;
165 mode.amplitude = 0.0;
264 for (
size_t i = 0; i <
m_modes.size(); ++i) {
265 double spatial_amp = std::sin((i + 1) * M_PI *
position);
266 spatial_amp = std::abs(spatial_amp);
268 m_modes[i].amplitude =
m_modes[i].initial_amplitude * strength * spatial_amp;
274 if (distribution.size() !=
m_modes.size()) {
285 for (
size_t i = 0; i <
m_modes.size(); ++i) {
296 if (mode_a >=
m_modes.size() || mode_b >=
m_modes.size() || mode_a == mode_b) {
300 strength = std::clamp(strength, 0.0, 1.0);
306 .strength = strength });
321 auto& mode_a =
m_modes[coupling.mode_a];
322 auto& mode_b =
m_modes[coupling.mode_b];
324 double energy_diff = (mode_a.amplitude - mode_b.amplitude) * coupling.strength;
326 mode_a.amplitude -= energy_diff * 0.5;
327 mode_b.amplitude += energy_diff * 0.5;
341 std::this_thread::yield();
348 thread_local std::vector<double> scratch;
349 scratch.assign(num_samples, 0.0);
355 nb.reserve(num_samples);
363 if (!peaks.empty()) {
364 const double peak_scale = 1.0 /
static_cast<double>(peaks.size());
365 for (
size_t peak_idx : peaks) {
369 const double target =
mode.initial_amplitude * strength;
370 mode.amplitude = std::max(
mode.amplitude, target);
378 if (!peaks.empty()) {
379 const auto max_peak = *std::ranges::max_element(peaks,
380 [
this](
size_t a,
size_t b) {
385 const double target =
mode.initial_amplitude * strength;
386 mode.amplitude = std::max<double>(
mode.amplitude, target);
393 for (
size_t i = 0; i < num_samples; ++i) {
396 if (exciter_signal != 0.0) {
399 mode.amplitude += exciter_signal *
mode.initial_amplitude
408 for (
size_t m = 0; m <
m_modes.size(); ++m) {
411 if (
mode.amplitude > 0.0001) {
412 mode.amplitude *=
mode.decay_coefficient;
414 mode.amplitude = 0.0;
417 double sample =
mode.oscillator->process_sample(0.0) *
mode.amplitude;
419 scratch[i] += sample;
424 std::this_thread::yield();
439 double value = mapping.broadcast_source->get_last_output();
450 if (param ==
"frequency") {
452 }
else if (param ==
"decay") {
454 }
else if (param ==
"amplitude") {
458 }
else if (param ==
"scale") {
464 const std::string& param,
const std::shared_ptr<NodeNetwork>& source)
466 if (source->get_node_count() !=
m_modes.size()) {
470 if (param ==
"amplitude") {
471 for (
size_t i = 0; i <
m_modes.size(); ++i) {
472 auto val = source->get_node_output(i);
477 }
else if (param ==
"detune") {
478 for (
size_t i = 0; i <
m_modes.size(); ++i) {
479 auto val = source->get_node_output(i);
481 double detune_cents = *val * 100.0;
482 double ratio = std::pow(2.0, detune_cents / 1200.0);
484 m_modes[i].oscillator->set_frequency(
m_modes[i].current_frequency);
491 const std::shared_ptr<Node>& source,
506 const std::string& param_name,
507 const std::shared_ptr<NodeNetwork>& source_network)
523 [&](
const auto& m) {
return m.param_name == param_name; });
535 mode.amplitude =
mode.initial_amplitude * strength;
541 if (mode_index <
m_modes.size()) {
542 m_modes[mode_index].amplitude =
m_modes[mode_index].initial_amplitude * strength;
549 mode.amplitude *= damping_factor;
559 mode.current_frequency =
mode.base_frequency;
561 mode.oscillator->set_frequency(
mode.current_frequency);
569std::unordered_map<std::string, std::string>
591 double avg_amplitude = 0.0;
593 avg_amplitude +=
mode.amplitude;
595 avg_amplitude /=
static_cast<double>(
m_modes.size());
596 metadata[
"avg_amplitude"] = std::to_string(avg_amplitude);
598 metadata[
"exciter_type"] = [
this]() {
603 return "NOISE_BURST";
605 return "FILTERED_NOISE";
Discrete sequence analysis primitives for MayaFlux::Kinesis.
Digital filter coefficient design for MayaFlux::Kinesis.
std::optional< glm::vec3 > position
std::unordered_map< std::string, std::string > metadata
vk::PhysicalDeviceType type
std::shared_ptr< Filters::Filter > m_exciter_filter
void compute_spatial_distribution()
Compute spatial amplitude distribution.
std::optional< double > get_node_output(size_t index) const override
Get output of specific internal node (for ONE_TO_ONE mapping)
std::vector< std::vector< double > > m_node_buffers
Per-mode sample buffers populated each process_batch()
ExciterType
Excitation signal types for modal synthesis.
@ IMPULSE
Single-sample Dirac impulse (default)
@ FILTERED_NOISE
Spectrally-shaped noise burst.
@ CONTINUOUS
External node as continuous exciter.
@ NOISE_BURST
Short white noise burst.
@ SAMPLE
User-provided excitation waveform.
ExciterType m_exciter_type
void process_batch(unsigned int num_samples) override
Process the network for the given number of samples.
void refresh_decay()
Recompute every mode's per-sample decay from the base decay and multiplier.
size_t m_exciter_node_buffer_pos
void apply_one_to_one_parameter(const std::string ¶m, const std::shared_ptr< NodeNetwork > &source)
Apply one-to-one parameter from another network.
void release()
Release continuous excitation and allow modes to decay naturally.
void set_decay_multiplier(double multiplier)
Set global decay multiplier.
Kinesis::Stochastic::Stochastic m_random_generator
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.
double m_exciter_strength
std::optional< std::span< const double > > get_node_audio_buffer(size_t index) const override
Get output of specific internal node as audio buffer (for ONE_TO_ONE mapping)
std::vector< ModeCoupling > m_couplings
void set_exciter_type(ExciterType type)
Set exciter type.
void reset() override
Reset network to initial state.
void apply_broadcast_parameter(const std::string ¶m, double value)
Apply broadcast parameter to all modes.
double m_exciter_duration
void compute_mode_coupling()
Apply modal coupling energy transfer.
size_t m_exciter_sample_position
double generate_exciter_sample()
Generate exciter signal for current sample.
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.
std::vector< ModalNode > m_modes
std::vector< double > m_exciter_node_buffer
std::shared_ptr< Node > m_exciter_node
void unmap_parameter(const std::string ¶m_name) override
Remove parameter mapping.
void set_mode_coupling(size_t mode_a, size_t mode_b, double strength)
Define bidirectional coupling between two modes.
double m_decay_multiplier
void remove_mode_coupling(size_t mode_a, size_t mode_b)
Remove specific coupling.
void set_fundamental(double frequency)
Set base frequency (fundamental)
std::vector< double > m_exciter_sample
void initialize_exciter(double strength)
Initialize exciter for new excitation event.
void excite_at_position(double position, double strength=1.0)
Excite modes based on normalized strike position.
void map_parameter(const std::string ¶m_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.
void set_exciter_sample(const std::vector< double > &sample)
Set custom excitation sample.
std::vector< double > m_spatial_distribution
size_t m_exciter_samples_remaining
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 set_exciter_duration(double seconds)
Set noise burst duration.
void set_spatial_distribution(const std::vector< double > &distribution)
Set custom spatial amplitude distribution.
std::vector< ParameterMapping > m_parameter_mappings
void apply_output_scale()
Apply m_output_scale to m_last_audio_buffer.
virtual void set_topology(Topology topology)
Set the network's topology.
bool is_enabled() const
Check if network is enabled.
double m_output_scale
Post-processing scalar applied to m_last_audio_buffer each batch.
std::atomic_flag m_audio_buffer_lock
Spinlock guarding m_last_audio_buffer.
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.
std::vector< size_t > peak_positions(std::span< const double > data, double threshold, size_t min_distance)
Sample indices of local peak maxima in the full span.
double decay_per_sample(double seconds, double sample_rate) noexcept
Per-sample factor that falls by 1/e in a given time.
@ INDEPENDENT
No connections, nodes process independently.
@ CUSTOM
User-defined arbitrary topology.
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.
@ AUDIO_SINK
Aggregated audio samples sent to output.
void extract_node_samples(const std::shared_ptr< Nodes::Node > &node, std::vector< double > &buffer, size_t &buffer_pos, size_t num_samples)
Extract num_samples from node into buffer using snapshot guard.
size_t index
Index in network.
Represents a single resonant mode.
std::shared_ptr< Node > broadcast_source
std::shared_ptr< NodeNetwork > network_source