Skip to content

Commit

Permalink
[concepts] First tentative of implementation of dynamic ports
Browse files Browse the repository at this point in the history
  • Loading branch information
jcelerier committed Jul 28, 2024
1 parent 11bbf33 commit 5d87d29
Show file tree
Hide file tree
Showing 16 changed files with 348 additions and 27 deletions.
55 changes: 55 additions & 0 deletions examples/Helpers/DynamicPort.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once
#include <cmath>
#include <halp/controls.hpp>
#include <halp/meta.hpp>

#include <functional>
#include <vector>

/* 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<void(SumPorts&, int)> on_controller_interaction()
{
return [](SumPorts& object, int value) {
object.inputs.in.request_port_resize(value);
};
}
} controller;

struct : halp::knob_f32<"Input {}">
{
std::vector<double> ports{};
std::function<void(int)> 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);
}
};
}
82 changes: 82 additions & 0 deletions include/avnd/binding/ossia/dynamic_ports.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#pragma once

#include <avnd/common/struct_reflection.hpp>
#include <avnd/concepts/dynamic_ports.hpp>
#include <avnd/introspection/input.hpp>
#include <avnd/introspection/output.hpp>

namespace oscr
{
template <typename T>
concept has_dynamic_ports = avnd::dynamic_ports_input_introspection<T>::size > 0
|| avnd::dynamic_ports_output_introspection<T>::size > 0;

template <typename Field>
struct dynamic_ports_state_type;

template <avnd::dynamic_ports_port Field>
struct dynamic_ports_state_type<Field>
{
int count = 0;
};

template <typename T>
struct dynamic_ports_storage
{
template <std::size_t Idx>
int num_in_ports(avnd::field_index<Idx>) const noexcept
{
return 1;
}
template <std::size_t Idx>
int num_out_ports(avnd::field_index<Idx>) const noexcept
{
return 1;
}
};

template <typename T>
requires(
avnd::dynamic_ports_input_introspection<T>::size > 0
|| avnd::dynamic_ports_output_introspection<T>::size > 0)
struct dynamic_ports_storage<T>
{
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 <std::size_t Idx>
int num_in_ports(avnd::field_index<Idx> f) const noexcept
{
static constexpr std::size_t pred_idx
= avnd::dynamic_ports_input_introspection<T>::field_index_to_index(f);
return std::get<pred_idx>(in_handles).count;
}
template <std::size_t Idx>
int num_out_ports(avnd::field_index<Idx> f) const noexcept
{
static constexpr std::size_t pred_idx
= avnd::dynamic_ports_output_introspection<T>::field_index_to_index(f);
return std::get<pred_idx>(out_handles).count;
}
template <std::size_t Idx>
int& num_in_ports(avnd::field_index<Idx> f) noexcept
{
static constexpr std::size_t pred_idx
= avnd::dynamic_ports_input_introspection<T>::field_index_to_index(f);
return std::get<pred_idx>(in_handles).count;
}
template <std::size_t Idx>
int& num_out_ports(avnd::field_index<Idx> f) noexcept
{
static constexpr std::size_t pred_idx
= avnd::dynamic_ports_output_introspection<T>::field_index_to_index(f);
return std::get<pred_idx>(out_handles).count;
}
};

}
41 changes: 35 additions & 6 deletions include/avnd/binding/ossia/node.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <avnd/binding/ossia/configure.hpp>
#include <avnd/binding/ossia/dynamic_ports.hpp>
#include <avnd/binding/ossia/ffts.hpp>
#include <avnd/binding/ossia/port_run_postprocess.hpp>
#include <avnd/binding/ossia/port_run_preprocess.hpp>
Expand Down Expand Up @@ -324,6 +325,8 @@ class safe_node_base_base : public ossia::nonowning_graph_node

[[no_unique_address]] controls_queue<T> control;

[[no_unique_address]] oscr::dynamic_ports_storage<T> dynamic_ports;

using control_input_values_type
= avnd::filter_and_apply<controls_type, avnd::control_input_introspection, T>;
using control_output_values_type
Expand All @@ -348,9 +351,6 @@ class safe_node_base : public safe_node_base_base<T>
using midi_in_info = avnd::midi_input_introspection<T>;
using midi_out_info = avnd::midi_output_introspection<T>;

static constexpr int total_input_ports = avnd::total_input_count<T>();
static constexpr int total_output_ports = avnd::total_output_count<T>();

uint64_t instance{};

safe_node_base(int buffer_size, double sample_rate, uint64_t uid)
Expand All @@ -360,9 +360,6 @@ class safe_node_base : public safe_node_base_base<T>
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);
Expand Down Expand Up @@ -514,6 +511,37 @@ class safe_node_base : public safe_node_base_base<T>
this->control.inputs_set.set(N);
}

template <typename Val, std::size_t N>
void control_updated_from_ui(Val&& new_value, int dynamic_port)
{
if constexpr(requires { avnd::effect_container<T>::multi_instance; })
{
for(const auto& state : this->impl.full_state())
{
// Replace the value in the field
auto& field
= avnd::control_input_introspection<T>::template field<N>(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<T>::template field<N>(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: "
Expand Down Expand Up @@ -765,6 +793,7 @@ class safe_node_base : public safe_node_base_base<T>
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
Expand Down
21 changes: 21 additions & 0 deletions include/avnd/binding/ossia/port_run_postprocess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ struct process_after_run
if_possible(ctrl.value.reset());
}

template <typename Field, std::size_t Idx>
void operator()(
Field& ctrl, std::vector<ossia::value_inlet*>& port,
avnd::field_index<Idx>) const noexcept
{
for(auto& port_value : ctrl.ports)
{
if_possible(port_value.reset());
}
}

template <typename Field, std::size_t Idx>
void operator()(
Field& ctrl, ossia::audio_inlet& port, avnd::field_index<Idx>) const noexcept
Expand Down Expand Up @@ -90,6 +101,16 @@ struct process_after_run
write_value(ctrl, port, ctrl.value, 0, avnd::field_index<Idx>{});
}

template <avnd::parameter Field, std::size_t Idx>
void operator()(
Field& ctrl, std::vector<ossia::value_outlet*>& port,
avnd::field_index<Idx>) const noexcept
{
int N = port.size();

write_value(ctrl, port, ctrl.value, 0, avnd::field_index<Idx>{});
}

template <avnd::linear_sample_accurate_parameter Field, std::size_t Idx>
void operator()(
Field& ctrl, ossia::value_outlet& port, avnd::field_index<Idx> idx) const noexcept
Expand Down
77 changes: 64 additions & 13 deletions include/avnd/binding/ossia/port_run_preprocess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,64 @@ struct process_before_run
int frames{};

template <avnd::parameter Field, std::size_t Idx>
requires(!avnd::control<Field>)
void init_value(
Field& ctrl, ossia::value_inlet& port, avnd::field_index<Idx> idx) const noexcept
{
if(!port.data.get_data().empty())
{
auto& last = port.data.get_data().back().value;
update_value(self, impl, ctrl, last, ctrl.value, idx);

if constexpr(avnd::control<Field>)
{
// Get the index of the control in [0; N[
using type = typename Exec_T::processor_type;
using controls = avnd::control_input_introspection<type>;
constexpr int control_index = controls::field_index_to_index(idx);

// Mark the control as changed
self.control.inputs_set.set(control_index);
}
}
}

template <avnd::parameter Field, std::size_t Idx>
requires(avnd::control<Field>)
void init_value(
Field& ctrl, ossia::value_inlet& port, avnd::field_index<Idx> idx) const noexcept
Field& ctrl, std::vector<ossia::value_inlet*>& ports,
avnd::field_index<Idx> 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<type>;
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<Field>)
// {
// // Get the index of the control in [0; N[
// using type = typename Exec_T::processor_type;
// using controls = avnd::control_input_introspection<type>;
// 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));
}
}

Expand All @@ -78,6 +108,13 @@ struct process_before_run
{
init_value(ctrl, port, avnd::field_index<Idx>{});
}
template <avnd::parameter Field, std::size_t Idx>
void operator()(
Field& ctrl, std::vector<ossia::value_inlet*>& port,
avnd::field_index<Idx> idx) const noexcept
{
init_value(ctrl, port, avnd::field_index<Idx>{});
}

template <avnd::linear_sample_accurate_parameter Field, std::size_t Idx>
void operator()(
Expand Down Expand Up @@ -369,6 +406,18 @@ struct process_before_run
}
}

template <avnd::parameter Field, std::size_t Idx>
void operator()(
Field& ctrl, std::vector<ossia::value_outlet*>& ports,
avnd::field_index<Idx>) const noexcept
{
ctrl.ports.resize(ports.size());
for(auto& port_value : ctrl.ports)
{
port_value = {};
}
}

template <avnd::control Field, std::size_t Idx>
requires(!avnd::sample_accurate_control<Field>)
void operator()(
Expand All @@ -377,6 +426,7 @@ struct process_before_run
if constexpr(avnd::optional_ish<decltype(Field::value)>)
ctrl.value = {};
}

template <avnd::value_port Field, std::size_t Idx>
requires(!avnd::sample_accurate_value_port<Field>)
void operator()(
Expand All @@ -385,6 +435,7 @@ struct process_before_run
if constexpr(avnd::optional_ish<decltype(Field::value)>)
ctrl.value = {};
}

template <avnd::sample_accurate_control Field, std::size_t Idx>
void operator()(
Field& ctrl, ossia::value_outlet& port, avnd::field_index<Idx>) const noexcept
Expand Down
Loading

0 comments on commit 5d87d29

Please sign in to comment.