diff --git a/examples/Helpers/PeakBandFFTPort.hpp b/examples/Helpers/PeakBandFFTPort.hpp new file mode 100644 index 00000000..01f65e5e --- /dev/null +++ b/examples/Helpers/PeakBandFFTPort.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace examples::helpers +{ + +/** + * For the usual case where we just want the spectrum of an input buffer, + * no need to template: we can ask it to be precomputed beforehand by the host. + */ +struct PeakBandFFTPort +{ + halp_meta(name, "Peak band") + halp_meta(c_name, "avnd_peak_band") + halp_meta(uuid, "5610b62e-ef1f-4a34-abe0-e57816bc44c2") + + // TODO implement user-controllable buffering to allow various fft sizes... + int buffer_size = 1024; + + struct + { + // Here the host will fill audio.spectrum with a non-windowed FFT. + // Option A (an helper type is provided) + halp::audio_spectrum_channel<"In", double> audio; + + // Option B with the raw spectrum: + struct { + halp_meta(name, "In 2"); + + double* channel{}; + // complex numbers... using value_type = double[2] is also ok + std::complex* spectrum{}; + } audio_2; + } inputs; + + struct + { + halp::val_port<"Peak", double> peak; + halp::val_port<"Band", int> band; + + halp::val_port<"Peak 2", double> peak_2; + halp::val_port<"Band 2", int> band_2; + } outputs; + + void operator()(int frames) + { + // Process with option A + { + outputs.peak = 0.; + + // Compute the band with the highest amplitude + for (int k = 0; k < frames / 2; k++) + { + const double ampl = inputs.audio.spectrum.amplitude[k]; + const double phas = inputs.audio.spectrum.phase[k]; + const double mag_squared = ampl * ampl + phas * phas; + + if (mag_squared > outputs.peak) + { + outputs.peak = mag_squared; + outputs.band = k; + } + } + + outputs.peak = std::sqrt(outputs.peak); + } + + // Process with option B + { + outputs.peak_2 = 0.; + + // Compute the band with the highest amplitude + for (int k = 0; k < frames / 2; k++) + { + const double mag_squared = std::norm(inputs.audio_2.spectrum[k]); + + if (mag_squared > outputs.peak_2) + { + outputs.peak_2 = mag_squared; + outputs.band_2 = k; + } + } + + outputs.peak_2 = std::sqrt(outputs.peak_2); + } + } +}; + +} diff --git a/include/avnd/binding/ossia/ffts.hpp b/include/avnd/binding/ossia/ffts.hpp new file mode 100644 index 00000000..148e7dab --- /dev/null +++ b/include/avnd/binding/ossia/ffts.hpp @@ -0,0 +1,117 @@ +#pragma once + +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace oscr +{ +template +using spectrum_fft_type + = ossia::fft; + +template +struct spectrum_split_channel_input_storage +{ + void init(avnd::effect_container& t, int buffer_size) { } +}; + +template +struct spectrum_complex_channel_input_storage +{ + void init(avnd::effect_container& t, int buffer_size) { } +}; + +// Field: +// struct { T* amplitude; T* phase; } spectrum; +template +requires(avnd::spectrum_split_channel_input_introspection::size > 0) +struct spectrum_split_channel_input_storage +{ + using sc_in = avnd::spectrum_split_channel_input_introspection; + + using fft_tuple = avnd::filter_and_apply< + spectrum_fft_type, + avnd::spectrum_split_channel_input_introspection, + T>; + + // std::tuple< ossia::fft, ossia::fft > + [[no_unique_address]] fft_tuple ffts; + + void init(avnd::effect_container& t, int buffer_size) + { + if constexpr (sc_in::size > 0) + { + auto init_raw_in = [&](M& port, avnd::predicate_index) + { + // Get the matching fft in our storage + ossia::fft& fft = get(this->ffts); + + // Reserve space for the current buffer size + fft.reset(buffer_size); + + port.spectrum.amplitude = nullptr; + port.spectrum.phase = nullptr; + }; + sc_in::for_all_n(avnd::get_inputs(t), init_raw_in); + } + } +}; + +template +requires(avnd::spectrum_complex_channel_input_introspection::size > 0) +struct spectrum_complex_channel_input_storage +{ + using sc_in = avnd::spectrum_complex_channel_input_introspection; + + using fft_tuple = avnd::filter_and_apply< + spectrum_fft_type, + avnd::spectrum_complex_channel_input_introspection, + T>; + + // std::tuple< ossia::fft, ossia::fft > + [[no_unique_address]] fft_tuple ffts; + + void init(avnd::effect_container& t, int buffer_size) + { + if constexpr (sc_in::size > 0) + { + auto init_raw_in = [&](M& port, avnd::predicate_index) + { + // Get the matching fft in our storage + ossia::fft& fft = get(this->ffts); + + // Reserve space for the current buffer size + fft.reset(buffer_size); + + port.spectrum = nullptr; + }; + sc_in::for_all_n(avnd::get_inputs(t), init_raw_in); + } + } +}; + + +template +struct spectrum_storage +{ + [[no_unique_address]] + spectrum_split_channel_input_storage split_channel; + [[no_unique_address]] + spectrum_complex_channel_input_storage complex_channel; + + void init(avnd::effect_container& t, int buffer_size) + { + split_channel.init(t, buffer_size); + complex_channel.init(t, buffer_size); + } +}; + +} diff --git a/include/avnd/binding/ossia/node.hpp b/include/avnd/binding/ossia/node.hpp index 5b95de3a..039e2aa9 100644 --- a/include/avnd/binding/ossia/node.hpp +++ b/include/avnd/binding/ossia/node.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -95,13 +96,6 @@ static void safety_checks(const ossia::token_request& tk, ossia::exec_state_faca // void process(double** in, double** out, int N) -struct ui_communication -{ - // Sends and receives info from the processor, e.g. audio signal, controls - - // Sets the controls from the UI -}; - template struct builtin_arg_audio_ports { @@ -205,13 +199,13 @@ class safe_node_base_base : public ossia::nonowning_graph_node [[no_unique_address]] avnd::effect_container impl; - [[no_unique_address]] builtin_arg_audio_ports audio_ports; + [[no_unique_address]] oscr::builtin_arg_audio_ports audio_ports; - [[no_unique_address]] builtin_message_value_ports message_ports; + [[no_unique_address]] oscr::builtin_message_value_ports message_ports; - [[no_unique_address]] inlet_storage ossia_inlets; + [[no_unique_address]] oscr::inlet_storage ossia_inlets; - [[no_unique_address]] outlet_storage ossia_outlets; + [[no_unique_address]] oscr::outlet_storage ossia_outlets; [[no_unique_address]] avnd::process_adapter processor; @@ -225,8 +219,7 @@ class safe_node_base_base : public ossia::nonowning_graph_node [[no_unique_address]] oscr::soundfile_storage soundfiles; - // [[no_unique_address]] - // controls_mirror feedback; + [[no_unique_address]] oscr::spectrum_storage spectrums; [[no_unique_address]] controls_queue control; @@ -262,6 +255,7 @@ class safe_node_base : public safe_node_base_base this->audio_ports.init(this->m_inlets, this->m_outlets); this->message_ports.init(this->m_inlets); this->soundfiles.init(this->impl); + this->spectrums.init(this->impl, buffer_size); // constexpr const int total_input_channels = avnd::input_channels(-1); // constexpr const int total_output_channels = avnd::output_channels(-1); @@ -285,7 +279,7 @@ class safe_node_base : public safe_node_base_base using namespace tpl; (f(avnd::pfr::get(in), tuplet::get(this->ossia_inlets.ports), - avnd::num{}), + avnd::field_index{}), ...); } (typename info::indices_n{}); @@ -304,7 +298,7 @@ class safe_node_base : public safe_node_base_base { (f(avnd::pfr::get(in), tuplet::get(this->ossia_outlets.ports), - avnd::num{}), + avnd::field_index{}), ...); } (typename info::indices_n{}); diff --git a/include/avnd/binding/ossia/port_run_postprocess.hpp b/include/avnd/binding/ossia/port_run_postprocess.hpp index 8591e77c..75bad9e8 100644 --- a/include/avnd/binding/ossia/port_run_postprocess.hpp +++ b/include/avnd/binding/ossia/port_run_postprocess.hpp @@ -123,22 +123,22 @@ struct process_after_run { Exec_T& self; template - void operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::texture_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::texture_inlet& port, avnd::field_index) const noexcept { } @@ -148,7 +148,7 @@ struct process_after_run ossia::value_outlet& port, auto& val, int64_t ts, - avnd::num) const noexcept + avnd::field_index) const noexcept { port->write_value(val, ts); } @@ -159,15 +159,14 @@ struct process_after_run ossia::value_outlet& port, auto& val, int64_t ts, - avnd::num) const noexcept + avnd::field_index idx) const noexcept { port->write_value(val, ts); // Get the index of the control in [0; N[ using type = typename Exec_T::processor_type; using controls = avnd::control_output_introspection; - constexpr int control_index - = avnd::index_of_element(typename controls::indices_n{}); + constexpr int control_index = controls::field_index_to_index(idx); // Mark the control as changed self.control.outputs_set.set(control_index); @@ -175,21 +174,20 @@ struct process_after_run template requires(!avnd::sample_accurate_parameter) void - operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { - write_value(ctrl, port, ctrl.value, 0, avnd::num{}); + write_value(ctrl, port, ctrl.value, 0, avnd::field_index{}); } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index idx) const noexcept { auto& buffers = self.control_buffers.linear_inputs; // Idx is the index of the port in the complete input array. // We need to map it to the linear input index. using processor_type = typename Exec_T::processor_type; using lin_out = avnd::linear_timed_parameter_output_introspection; - using indices = typename lin_out::indices_n; - constexpr int storage_index = avnd::index_of_element(indices{}); + constexpr int storage_index = lin_out::field_index_to_index(idx); auto& buffer = get(buffers); @@ -197,34 +195,34 @@ struct process_after_run { if (buffer[i]) { - write_value(ctrl, port, *buffer[i], i, avnd::num{}); + write_value(ctrl, port, *buffer[i], i, avnd::field_index{}); buffer[i] = {}; } } } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { for (auto& [timestamp, val] : ctrl.values) { - write_value(ctrl, port, val, timestamp, avnd::num{}); + write_value(ctrl, port, val, timestamp, avnd::field_index{}); } ctrl.values.clear(); } // does not make sense as output, only as input template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept = delete; template - void operator()(Field& ctrl, ossia::audio_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::audio_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::field_index) const noexcept { const int N = ctrl.midi_messages.size; port.data.messages.clear(); @@ -240,7 +238,7 @@ struct process_after_run } template - void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::field_index) const noexcept { port.data.messages.clear(); port.data.messages.reserve(ctrl.midi_messages.size()); @@ -255,12 +253,12 @@ struct process_after_run template void - operator()(Field& ctrl, ossia::texture_outlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::texture_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } }; diff --git a/include/avnd/binding/ossia/port_run_preprocess.hpp b/include/avnd/binding/ossia/port_run_preprocess.hpp index 08e09214..024602d0 100644 --- a/include/avnd/binding/ossia/port_run_preprocess.hpp +++ b/include/avnd/binding/ossia/port_run_preprocess.hpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include namespace avnd { @@ -19,6 +21,36 @@ concept enum_ish_parameter = avnd::enum_parameter || namespace oscr { +// For interleaving / deinterleaving fft: +// https://stackoverflow.com/a/55112294/1495627 +static constexpr int source_index(int i, int length) +{ + const int mid = length - length / 2; + if (i +constexpr void deinterleave(T* arr, int length) +{ + if(length <= 1) + return; + + for(int i = 1; i < length; i++) + { + int j = source_index(i, length); + + while (j < i) { + j = source_index(j, length); + } + + std::swap(arr[i], arr[j]); + } +} + + template void from_ossia_value(const ossia::value& src, T& dst) { @@ -90,7 +122,7 @@ struct process_before_run requires(!avnd::control) void init_value( Field& ctrl, ossia::value_inlet& port, - avnd::num) const noexcept + avnd::field_index) const noexcept { if (!port.data.get_data().empty()) { @@ -103,7 +135,7 @@ struct process_before_run requires(avnd::control) void init_value( Field& ctrl, ossia::value_inlet& port, - avnd::num) const noexcept + avnd::field_index idx) const noexcept { if (!port.data.get_data().empty()) { @@ -113,8 +145,7 @@ struct process_before_run // Get the index of the control in [0; N[ using type = typename Exec_T::processor_type; using controls = avnd::control_input_introspection; - constexpr int control_index - = avnd::index_of_element(typename controls::indices_n{}); + constexpr int control_index = controls::field_index_to_index(idx); // Mark the control as changed self.control.inputs_set.set(control_index); @@ -123,16 +154,16 @@ struct process_before_run template requires(!avnd::sample_accurate_parameter) void - operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { - init_value(ctrl, port, avnd::num{}); + init_value(ctrl, port, avnd::field_index{}); } template - void operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { // FIXME we need to know about the buffer size ! - init_value(ctrl, port, avnd::num{}); + init_value(ctrl, port, avnd::field_index{}); for (auto& [val, ts] : port->get_data()) { @@ -142,9 +173,9 @@ struct process_before_run } template - void operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { - init_value(ctrl, port, avnd::num{}); + init_value(ctrl, port, avnd::field_index{}); for (auto& [val, ts] : port->get_data()) { // FIXME @@ -152,33 +183,81 @@ struct process_before_run } template - void operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { - init_value(ctrl, port, avnd::num{}); + init_value(ctrl, port, avnd::field_index{}); for (auto& [val, ts] : port->get_data()) { from_ossia_value(ctrl, val, ctrl.values[ts]); } } + template + void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::field_index idx) const noexcept + { + if(port.data.channels() >= 1) + { + using sc_in = avnd::spectrum_split_channel_input_introspection; + constexpr auto fft_idx = sc_in::field_index_to_index(idx); + ossia::fft& fft = get(self.spectrums.split_channel.ffts); + + auto& samples = port.data.channel(0); + const int N = samples.size(); + if(N > 0) + { + auto fftIn = fft.input(); + for (int i = 0; i < N; i++) + fftIn[i] = samples[i]; + + auto fftOut = reinterpret_cast(fft.execute()); + deinterleave(fftOut, N); + + ctrl.spectrum.amplitude = fftOut; + ctrl.spectrum.phase = fftOut + N / 2; + } + } + } + + template + void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::field_index idx) const noexcept + { + if(port.data.channels() >= 1) + { + using sc_in = avnd::spectrum_complex_channel_input_introspection; + constexpr auto fft_idx = sc_in::field_index_to_index(idx); + ossia::fft& fft = get(self.spectrums.complex_channel.ffts); + + auto& samples = port.data.channel(0); + const int N = samples.size(); + if(N > 0) + { + auto fftIn = fft.input(); + for (int i = 0; i < N; i++) + fftIn[i] = samples[i]; + + ctrl.spectrum = reinterpret_cast(fft.execute()); + } + } + } + template - void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::audio_inlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::field_index) const noexcept { // FIXME } template - void operator()(Field& ctrl, ossia::texture_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::texture_inlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::value_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_inlet& port, avnd::field_index) const noexcept { auto& dat = port.data.get_data(); if(dat.empty()) @@ -193,7 +272,7 @@ struct process_before_run } template - void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_inlet& port, avnd::field_index) const noexcept { ctrl.midi_messages.reserve(port.data.messages.size()); for (const libremidi::message& msg_in : port.data.messages) @@ -205,40 +284,40 @@ struct process_before_run template requires(!avnd::sample_accurate_control) void - operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } template requires(!avnd::sample_accurate_value_port) void - operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::audio_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::audio_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::midi_outlet& port, avnd::field_index) const noexcept { } template void - operator()(Field& ctrl, ossia::texture_outlet& port, avnd::num) const noexcept + operator()(Field& ctrl, ossia::texture_outlet& port, avnd::field_index) const noexcept { } template - void operator()(Field& ctrl, ossia::value_outlet& port, avnd::num) const noexcept + void operator()(Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept { } }; diff --git a/include/avnd/common/struct_reflection.hpp b/include/avnd/common/struct_reflection.hpp index 6f75dcfb..c93056e8 100644 --- a/include/avnd/common/struct_reflection.hpp +++ b/include/avnd/common/struct_reflection.hpp @@ -12,10 +12,14 @@ namespace avnd { template -struct field_index { }; +struct field_index { + consteval operator std::size_t() const noexcept { return N; } +}; template -struct predicate_index { }; +struct predicate_index { + consteval operator std::size_t() const noexcept { return N; } +}; template consteval int index_of_element(std::integer_sequence) noexcept @@ -190,6 +194,15 @@ struct predicate_introspection return avnd::index_of_element(indices_n{}); } + template + static constexpr auto index_to_field_index(avnd::predicate_index) noexcept { + return avnd::field_index{}; + } + template + static constexpr auto field_index_to_index(avnd::field_index) noexcept { + return avnd::predicate_index(indices_n{})>{}; + } + static constexpr void for_all(auto&& func) noexcept { if constexpr (size > 0) diff --git a/include/avnd/concepts/fft.hpp b/include/avnd/concepts/fft.hpp index b59fd1f2..a976d560 100644 --- a/include/avnd/concepts/fft.hpp +++ b/include/avnd/concepts/fft.hpp @@ -47,5 +47,23 @@ concept rfft_1d = requires(T t) { t.normalization(128) } -> std::floating_point; }; -// TODO spectra input port + +// FIXME support float +template +concept spectrum_split_channel_port = + std::is_same_v().spectrum.amplitude)>, double*> && + std::is_same_v().spectrum.phase)>, double*> +; + +template +concept spectrum_complex_channel_port = complex_number().spectrum[0])>; + +template +concept spectrum_split_bus_port = + std::is_same_v().spectrum.amplitude[0])>, double*> && + std::is_same_v().spectrum.phase[0])>, double*> +; + +template +concept spectrum_complex_bus_port = complex_number().spectrum[0][0])>; } diff --git a/include/avnd/introspection/input.hpp b/include/avnd/introspection/input.hpp index c007fa78..da36c7aa 100644 --- a/include/avnd/introspection/input.hpp +++ b/include/avnd/introspection/input.hpp @@ -105,6 +105,30 @@ struct soundfile_input_introspection { }; +template +struct spectrum_split_channel_input_introspection + : spectrum_split_channel_port_introspection::type> +{ +}; + +template +struct spectrum_complex_channel_input_introspection + : spectrum_complex_channel_port_introspection::type> +{ +}; + +template +struct spectrum_split_bus_input_introspection + : spectrum_split_bus_port_introspection::type> +{ +}; + +template +struct spectrum_complex_bus_input_introspection + : spectrum_complex_bus_port_introspection::type> +{ +}; + template struct input_introspection : fields_introspection::type> { diff --git a/include/avnd/introspection/port.hpp b/include/avnd/introspection/port.hpp index 766189ec..c44b72ee 100644 --- a/include/avnd/introspection/port.hpp +++ b/include/avnd/introspection/port.hpp @@ -128,6 +128,7 @@ using audio_channel_introspection = predicate_introspection // using message_introspection = predicate_introspection; +// Callbacks template using is_callback_t = boost::mp11::mp_bool>; template @@ -143,9 +144,31 @@ using is_view_callback_t = boost::mp11::mp_bool>; template using view_callback_introspection = predicate_introspection; +// Soundfile template using is_soundfile_t = boost::mp11::mp_bool>; template using soundfile_introspection = predicate_introspection; +// FFT +template +using is_spectrum_split_channel_port_t = boost::mp11::mp_bool>; +template +using spectrum_split_channel_port_introspection = predicate_introspection; + +template +using is_spectrum_complex_channel_port_t = boost::mp11::mp_bool>; +template +using spectrum_complex_channel_port_introspection = predicate_introspection; + +template +using is_spectrum_split_bus_port_t = boost::mp11::mp_bool>; +template +using spectrum_split_bus_port_introspection = predicate_introspection; + +template +using is_spectrum_complex_bus_port_t = boost::mp11::mp_bool>; +template +using spectrum_complex_bus_port_introspection = predicate_introspection; + } diff --git a/include/halp/audio.hpp b/include/halp/audio.hpp index 65f23894..903f257f 100644 --- a/include/halp/audio.hpp +++ b/include/halp/audio.hpp @@ -40,6 +40,8 @@ struct audio_channel FP operator[](std::size_t i) const noexcept { return channel[i]; } }; + + template struct fixed_audio_bus { @@ -73,6 +75,68 @@ struct dynamic_audio_bus } }; + +template +struct audio_spectrum_channel +{ + static consteval auto name() { return std::string_view{Name.value}; } + + FP* channel{}; + + struct { + FP* amplitude{}; + FP* phase{}; + } spectrum; + + operator FP*() const noexcept { return channel; } + FP& operator[](std::size_t i) noexcept { return channel[i]; } + FP operator[](std::size_t i) const noexcept { return channel[i]; } +}; + +template +struct fixed_audio_spectrum_bus +{ + static consteval auto name() { return std::string_view{lit.value}; } + static consteval auto description() { return std::string_view{Desc.value}; } + static constexpr int channels() { return WantedChannels; } + + FP** samples{}; + + struct { + FP** amplitude{}; + FP** phase{}; + } spectrum; + + operator FP**() const noexcept { return samples; } + FP* operator[](std::size_t i) const noexcept { return samples[i]; } + avnd::span channel(std::size_t i, std::size_t frames) const noexcept + { + return {samples[i], frames}; + } +}; + +template +struct dynamic_audio_spectrum_bus +{ + static consteval auto name() { return std::string_view{Name.value}; } + static consteval auto description() { return std::string_view{Desc.value}; } + + FP** samples{}; + struct { + FP** amplitude{}; + FP** phase{}; + } spectrum; + + int channels{}; + + operator FP**() const noexcept { return samples; } + FP* operator[](std::size_t i) const noexcept { return samples[i]; } + avnd::span channel(std::size_t i, std::size_t frames) const noexcept + { + return {samples[i], frames}; + } +}; + struct tick { int frames{};