diff --git a/examples/Helpers/DynamicPort.hpp b/examples/Helpers/DynamicPort.hpp new file mode 100644 index 00000000..cfd3bbce --- /dev/null +++ b/examples/Helpers/DynamicPort.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +#include +#include + +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +namespace examples +{ +struct SumPorts +{ + halp_meta(name, "Sum") + halp_meta(c_name, "avnd_sumports") + halp_meta(author, "Jean-Michaƫl Celerier") + halp_meta(category, "Debug") + halp_meta(description, "Example of an object with dynamic number of inputs") + halp_meta(uuid, "48b57b3e-227a-4a55-adce-86c011dcf491") + + struct inputs + { + struct : halp::spinbox_i32<"Control", halp::range{0, 10, 0}> + { + static std::function on_controller_interaction() + { + return [](SumPorts& object, int value) { + object.inputs.in.request_port_resize(value); + }; + } + } controller; + + struct : halp::knob_f32<"Input {}"> + { + std::vector ports{}; + std::function request_port_resize; + } in; + } inputs; + + struct + { + halp::val_port<"Output", float> out; + } outputs; + + void operator()() + { + outputs.out.value = 0; + int k = 0; + + for(auto val : inputs.in.ports) + outputs.out.value += std::pow(10, k++) * std::floor(10 * val); + } +}; +} diff --git a/include/avnd/binding/ossia/dynamic_ports.hpp b/include/avnd/binding/ossia/dynamic_ports.hpp new file mode 100644 index 00000000..829153b8 --- /dev/null +++ b/include/avnd/binding/ossia/dynamic_ports.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include + +namespace oscr +{ +template +concept has_dynamic_ports = avnd::dynamic_ports_input_introspection::size > 0 + || avnd::dynamic_ports_output_introspection::size > 0; + +template +struct dynamic_ports_state_type; + +template +struct dynamic_ports_state_type +{ + int count = 0; +}; + +template +struct dynamic_ports_storage +{ + template + int num_in_ports(avnd::field_index) const noexcept + { + return 1; + } + template + int num_out_ports(avnd::field_index) const noexcept + { + return 1; + } +}; + +template + requires( + avnd::dynamic_ports_input_introspection::size > 0 + || avnd::dynamic_ports_output_introspection::size > 0) +struct dynamic_ports_storage +{ + using in_tuple = avnd::filter_and_apply< + dynamic_ports_state_type, avnd::dynamic_ports_input_introspection, T>; + using out_tuple = avnd::filter_and_apply< + dynamic_ports_state_type, avnd::dynamic_ports_output_introspection, T>; + + [[no_unique_address]] in_tuple in_handles; + [[no_unique_address]] out_tuple out_handles; + + template + int num_in_ports(avnd::field_index f) const noexcept + { + static constexpr std::size_t pred_idx + = avnd::dynamic_ports_input_introspection::field_index_to_index(f); + return std::get(in_handles).count; + } + template + int num_out_ports(avnd::field_index f) const noexcept + { + static constexpr std::size_t pred_idx + = avnd::dynamic_ports_output_introspection::field_index_to_index(f); + return std::get(out_handles).count; + } + template + int& num_in_ports(avnd::field_index f) noexcept + { + static constexpr std::size_t pred_idx + = avnd::dynamic_ports_input_introspection::field_index_to_index(f); + return std::get(in_handles).count; + } + template + int& num_out_ports(avnd::field_index f) noexcept + { + static constexpr std::size_t pred_idx + = avnd::dynamic_ports_output_introspection::field_index_to_index(f); + return std::get(out_handles).count; + } +}; + +} diff --git a/include/avnd/binding/ossia/node.hpp b/include/avnd/binding/ossia/node.hpp index 9c309ee8..bf0d7e50 100644 --- a/include/avnd/binding/ossia/node.hpp +++ b/include/avnd/binding/ossia/node.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -324,6 +325,8 @@ class safe_node_base_base : public ossia::nonowning_graph_node [[no_unique_address]] controls_queue control; + [[no_unique_address]] oscr::dynamic_ports_storage dynamic_ports; + using control_input_values_type = avnd::filter_and_apply; using control_output_values_type @@ -348,9 +351,6 @@ class safe_node_base : public safe_node_base_base using midi_in_info = avnd::midi_input_introspection; using midi_out_info = avnd::midi_output_introspection; - static constexpr int total_input_ports = avnd::total_input_count(); - static constexpr int total_output_ports = avnd::total_output_count(); - uint64_t instance{}; safe_node_base(int buffer_size, double sample_rate, uint64_t uid) @@ -360,9 +360,6 @@ class safe_node_base : public safe_node_base_base this->buffer_size = buffer_size; this->sample_rate = sample_rate; - this->m_inlets.reserve(total_input_ports + 1); - this->m_outlets.reserve(total_output_ports + 1); - this->audio_ports.init(this->m_inlets, this->m_outlets); this->message_ports.init(this->m_inlets); this->soundfiles.init(this->impl); @@ -514,6 +511,37 @@ class safe_node_base : public safe_node_base_base this->control.inputs_set.set(N); } + template + void control_updated_from_ui(Val&& new_value, int dynamic_port) + { + if constexpr(requires { avnd::effect_container::multi_instance; }) + { + for(const auto& state : this->impl.full_state()) + { + // Replace the value in the field + auto& field + = avnd::control_input_introspection::template field(state.inputs); + + // OPTIMIZEME we're loosing a few allocations here that should be gc'd + field.ports[dynamic_port] = new_value; + + if_possible(field.update(state.effect)); + } + } + else + { + // Replace the value in the field + auto& field + = avnd::control_input_introspection::template field(this->impl.inputs()); + + std::swap(field.ports[dynamic_port], new_value); + + if_possible(field.update(this->impl.effect)); + } + + // Mark the control as changed + this->control.inputs_set.set(N); + } void audio_configuration_changed() { // qDebug() << "New Audio configuration: " @@ -765,6 +793,7 @@ class safe_node_base : public safe_node_base_base state.inputs, [](auto& field) { return field.value; }); } } + auto make_controls_out_tuple() { // Note that this does not yet make a lot of sens for polyphonic effects diff --git a/include/avnd/binding/ossia/port_run_postprocess.hpp b/include/avnd/binding/ossia/port_run_postprocess.hpp index 75f25eba..96d7c80b 100644 --- a/include/avnd/binding/ossia/port_run_postprocess.hpp +++ b/include/avnd/binding/ossia/port_run_postprocess.hpp @@ -28,6 +28,17 @@ struct process_after_run if_possible(ctrl.value.reset()); } + template + void operator()( + Field& ctrl, std::vector& port, + avnd::field_index) const noexcept + { + for(auto& port_value : ctrl.ports) + { + if_possible(port_value.reset()); + } + } + template void operator()( Field& ctrl, ossia::audio_inlet& port, avnd::field_index) const noexcept @@ -90,6 +101,16 @@ struct process_after_run write_value(ctrl, port, ctrl.value, 0, avnd::field_index{}); } + template + void operator()( + Field& ctrl, std::vector& port, + avnd::field_index) const noexcept + { + int N = port.size(); + + write_value(ctrl, port, ctrl.value, 0, avnd::field_index{}); + } + template void operator()( Field& ctrl, ossia::value_outlet& port, avnd::field_index idx) const noexcept diff --git a/include/avnd/binding/ossia/port_run_preprocess.hpp b/include/avnd/binding/ossia/port_run_preprocess.hpp index 8ec274c0..25b8fb1a 100644 --- a/include/avnd/binding/ossia/port_run_preprocess.hpp +++ b/include/avnd/binding/ossia/port_run_preprocess.hpp @@ -40,7 +40,6 @@ struct process_before_run int frames{}; template - requires(!avnd::control) void init_value( Field& ctrl, ossia::value_inlet& port, avnd::field_index idx) const noexcept { @@ -48,26 +47,57 @@ struct process_before_run { auto& last = port.data.get_data().back().value; update_value(self, impl, ctrl, last, ctrl.value, idx); + + if constexpr(avnd::control) + { + // 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 = controls::field_index_to_index(idx); + + // Mark the control as changed + self.control.inputs_set.set(control_index); + } } } template - requires(avnd::control) void init_value( - Field& ctrl, ossia::value_inlet& port, avnd::field_index idx) const noexcept + Field& ctrl, std::vector& ports, + avnd::field_index idx) const noexcept { - if(!port.data.get_data().empty()) + bool written = ports.size() != ctrl.ports.size(); + ctrl.ports.resize(ports.size()); + int p = 0; + for(auto pport : ports) { - auto& last = port.data.get_data().back().value; - update_value(self, impl, ctrl, last, ctrl.value, idx); - - // 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 = controls::field_index_to_index(idx); + auto& port = *pport; + if(!port.data.get_data().empty()) + { + auto& last = port.data.get_data().back().value; + + // FIXME check optional ports case + written |= self.from_ossia_value(ctrl, last, ctrl.ports[p], idx); + qDebug() << ossia::value_to_pretty_string(last) << p << ctrl.ports[p]; + + // FIXME + // if constexpr(avnd::control) + // { + // // 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 = controls::field_index_to_index(idx); + + // // Mark the control as changed + // self.control.inputs_set.set(control_index); + // } + } + ++p; + } - // Mark the control as changed - self.control.inputs_set.set(control_index); + if(written) + { + if_possible(ctrl.update(impl)); } } @@ -78,6 +108,13 @@ struct process_before_run { init_value(ctrl, port, avnd::field_index{}); } + template + void operator()( + Field& ctrl, std::vector& port, + avnd::field_index idx) const noexcept + { + init_value(ctrl, port, avnd::field_index{}); + } template void operator()( @@ -369,6 +406,18 @@ struct process_before_run } } + template + void operator()( + Field& ctrl, std::vector& ports, + avnd::field_index) const noexcept + { + ctrl.ports.resize(ports.size()); + for(auto& port_value : ctrl.ports) + { + port_value = {}; + } + } + template requires(!avnd::sample_accurate_control) void operator()( @@ -377,6 +426,7 @@ struct process_before_run if constexpr(avnd::optional_ish) ctrl.value = {}; } + template requires(!avnd::sample_accurate_value_port) void operator()( @@ -385,6 +435,7 @@ struct process_before_run if constexpr(avnd::optional_ish) ctrl.value = {}; } + template void operator()( Field& ctrl, ossia::value_outlet& port, avnd::field_index) const noexcept diff --git a/include/avnd/binding/ossia/port_setup.hpp b/include/avnd/binding/ossia/port_setup.hpp index 04830735..63eff3bb 100644 --- a/include/avnd/binding/ossia/port_setup.hpp +++ b/include/avnd/binding/ossia/port_setup.hpp @@ -14,6 +14,7 @@ namespace oscr { // Compile-time map of avnd concept to ossia port + template struct get_ossia_inlet_type; template @@ -24,7 +25,8 @@ struct get_ossia_inlet_type template struct get_ossia_inlet_type { - using type = ossia::value_inlet; + using type = std::conditional_t< + avnd::dynamic_ports_port, std::vector, ossia::value_inlet>; }; template struct get_ossia_inlet_type @@ -77,7 +79,9 @@ struct get_ossia_outlet_type template struct get_ossia_outlet_type { - using type = ossia::value_outlet; + using type = std::conditional_t< + avnd::dynamic_ports_port, std::vector, + ossia::value_outlet>; }; template struct get_ossia_outlet_type @@ -119,6 +123,7 @@ struct inlet_storage // typelist using inputs_tuple = typename avnd::inputs_type::tuple; + // inputs_getter -> ossia::value_port template using inputs_getter = typename get_ossia_inlet_type::type; @@ -319,6 +324,21 @@ struct setup_inlets } } + template + void operator()( + avnd::field_reflection ctrl, + std::vector& port) const noexcept + { + int expected = self.dynamic_ports.num_in_ports(avnd::field_index{}); + while(port.size() < expected) + port.push_back(new ossia::value_inlet); + while(port.size() > expected) + port.erase(port.rbegin().base()); + + for(auto& p : port) + (*this)(ctrl, *p); + } + template void operator()( avnd::field_reflection ctrl, ossia::audio_inlet& port) const noexcept @@ -367,6 +387,22 @@ struct setup_outlets port->type = ossia::parse_dataspace(unit); } } + + template + void operator()( + avnd::field_reflection ctrl, + std::vector& port) const noexcept + { + int expected = self.dynamic_ports.num_out_ports(avnd::field_index{}); + while(port.size() < expected) + port.push_back(new ossia::value_outlet); + while(port.size() > expected) + port.erase(port.rbegin().base()); + + for(auto& p : port) + (*this)(ctrl, *p); + } + template void operator()( avnd::field_reflection ctrl, ossia::value_outlet& port) const noexcept diff --git a/include/avnd/binding/vintage/vintage.hpp b/include/avnd/binding/vintage/vintage.hpp index a6104d10..52ba9700 100644 --- a/include/avnd/binding/vintage/vintage.hpp +++ b/include/avnd/binding/vintage/vintage.hpp @@ -818,9 +818,9 @@ struct MidiProgramName { int32_t thisProgramIndex{}; char name[Constants::NameLen]{}; - char midiProgram{-1}; - char midiBankMsb{-1}; - char midiBankLsb{-1}; + char midiProgram = -1; + char midiBankMsb = -1; + char midiBankLsb = -1; char reserved{}; int32_t parentCategoryIndex{-1}; MidiProgramNameFlags flags{}; diff --git a/include/avnd/concepts/all.hpp b/include/avnd/concepts/all.hpp index a354bd35..6c5b52d2 100644 --- a/include/avnd/concepts/all.hpp +++ b/include/avnd/concepts/all.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include diff --git a/include/avnd/concepts/dynamic_ports.hpp b/include/avnd/concepts/dynamic_ports.hpp new file mode 100644 index 00000000..e1063943 --- /dev/null +++ b/include/avnd/concepts/dynamic_ports.hpp @@ -0,0 +1,16 @@ +#pragma once + +/* SPDX-License-Identifier: GPL-3.0-or-later OR BSL-1.0 OR CC0-1.0 OR CC-PDCC OR 0BSD */ + +#include +#include + +namespace avnd +{ + +template +concept dynamic_ports_port = requires(T t) { + t.ports; + t.request_port_resize; +}; +} diff --git a/include/avnd/concepts/layout.hpp b/include/avnd/concepts/layout.hpp index 0f7d5dea..9a9b9803 100644 --- a/include/avnd/concepts/layout.hpp +++ b/include/avnd/concepts/layout.hpp @@ -52,5 +52,7 @@ concept custom_control_layout = (T::layout() == decltype(T::layout())::custom_co template concept custom_layout = (T::layout() == decltype(T::layout())::custom) || (T::layout == decltype(T::layout)::custom); - +template +concept dynamic_controls = (T::layout() == decltype(T::layout())::dynamic_controls) + || (T::layout == decltype(T::layout)::dynamic_controls); } diff --git a/include/avnd/introspection/input.hpp b/include/avnd/introspection/input.hpp index 4315ed47..23d08203 100644 --- a/include/avnd/introspection/input.hpp +++ b/include/avnd/introspection/input.hpp @@ -70,6 +70,12 @@ struct smooth_parameter_input_introspection { }; +template +struct dynamic_ports_input_introspection + : dynamic_ports_port_introspection::type> +{ +}; + template struct midi_input_introspection : midi_port_introspection::type> { diff --git a/include/avnd/introspection/output.hpp b/include/avnd/introspection/output.hpp index 80eaebd3..cc56ba9c 100644 --- a/include/avnd/introspection/output.hpp +++ b/include/avnd/introspection/output.hpp @@ -40,6 +40,12 @@ struct dynamic_timed_parameter_output_introspection { }; +template +struct dynamic_ports_output_introspection + : dynamic_ports_port_introspection::type> +{ +}; + template struct midi_output_introspection : midi_port_introspection::type> diff --git a/include/avnd/introspection/port.hpp b/include/avnd/introspection/port.hpp index 555560df..4ea09132 100644 --- a/include/avnd/introspection/port.hpp +++ b/include/avnd/introspection/port.hpp @@ -87,6 +87,14 @@ using is_smooth_parameter_t = boost::mp11::mp_bool>; template using smooth_parameter_introspection = predicate_introspection; +template +using is_dynamic_ports_port_t = boost::mp11::mp_bool>; +template +struct dynamic_ports_port_introspection + : predicate_introspection +{ +}; + template using is_midi_port_t = boost::mp11::mp_bool>; template diff --git a/include/avnd/wrappers/bus_host_process_adapter.hpp b/include/avnd/wrappers/bus_host_process_adapter.hpp index 7b62c6bb..834df071 100644 --- a/include/avnd/wrappers/bus_host_process_adapter.hpp +++ b/include/avnd/wrappers/bus_host_process_adapter.hpp @@ -145,7 +145,8 @@ void port_visit_dispatcher(auto&& func_inlets, auto&& func_outlets) // Handle message inputs if constexpr(avnd::messages_type::size > 0) { - avnd::messages_introspection::for_all([&](auto m) { func_inlets(m); }); + avnd::messages_introspection::for_all( + [&](auto m) { func_inlets(m, avnd::field_index<0>{}); }); } if constexpr(avnd::has_inputs) diff --git a/include/halp/geometry.hpp b/include/halp/geometry.hpp index 2b0c84e6..c50fdca0 100644 --- a/include/halp/geometry.hpp +++ b/include/halp/geometry.hpp @@ -6,6 +6,12 @@ namespace halp { +template +struct rect2d +{ + T x{}, y{}, w{}, h{}; +}; + struct mesh { float transform[16]{}; diff --git a/include/halp/layout.hpp b/include/halp/layout.hpp index 25acefa4..2911cc43 100644 --- a/include/halp/layout.hpp +++ b/include/halp/layout.hpp @@ -25,7 +25,8 @@ enum class layouts control, widget, custom, - custom_control + custom_control, + multi_control }; enum class colors {