Skip to content

Commit

Permalink
[core] Add a way to define pure mapping objects
Browse files Browse the repository at this point in the history
  • Loading branch information
jcelerier committed Nov 1, 2024
1 parent e9b6583 commit 9d15a79
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 66 deletions.
65 changes: 58 additions & 7 deletions examples/Advanced/Utilities/MapTool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,23 @@ struct MapTool
halp_meta(description, "Map a value to a given domain in various ways")
halp_meta(uuid, "ae6e3c9f-40cf-493a-8dc8-d45e75a07213")
halp_flag(cv);
halp_flag(stateless);

struct
struct ins
{
halp::spinbox_f32<"Min"> in_min;
halp::spinbox_f32<"Max"> in_max;
halp::spinbox_f32<"Min", halp::free_range_min<>> in_min;
halp::spinbox_f32<"Max", halp::free_range_max<>> in_max;

halp::toggle<"Learn min"> min_learn;
halp::toggle<"Learn max"> max_learn;

halp::enum_t<MapToolWrapMode, "Range behaviour"> range_behaviour;
halp::knob_f32<"Curve", halp::range{-1., 1., 0.}> curve;
halp::toggle<"Invert"> invert;
halp::toggle<"Smooth"> smooth;
halp::knob_f32<"Smooth"> smooth;

halp::spinbox_f32<"Out min"> out_min;
halp::spinbox_f32<"Out max"> out_max;
halp::spinbox_f32<"Out min", halp::free_range_min<>> out_min;
halp::spinbox_f32<"Out max", halp::free_range_max<>> out_max;
} inputs;

struct
Expand Down Expand Up @@ -83,7 +86,10 @@ struct MapTool
to_01 = wrap(to_01);

// - Curve
to_01 = std::pow(to_01, inputs.curve.value + 1);
if(inputs.curve.value >= 0)
to_01 = std::pow(to_01, std::pow(16., inputs.curve.value));
else
to_01 = 1. - std::pow(1. - to_01, std::pow(16., -inputs.curve.value));

// - Invert
if(inputs.invert)
Expand All @@ -92,5 +98,50 @@ struct MapTool
/// 3. Unscale
return to_01 * (inputs.out_max - inputs.out_min) + inputs.out_min;
}

struct ui
{
halp_meta(layout, halp::layouts::hbox)
struct
{
halp_meta(layout, halp::layouts::vbox)
halp_meta(background, halp::colors::mid)
struct
{
halp_meta(layout, halp::layouts::hbox)
halp::control<&ins::in_min> p;
halp::control<&ins::min_learn> l;
} min;
struct
{
halp_meta(layout, halp::layouts::hbox)
halp::control<&ins::in_max> p;
halp::control<&ins::max_learn> l;
} max;
} in_range;

struct
{
halp_meta(layout, halp::layouts::vbox)
halp_meta(background, halp::colors::mid)

halp::control<&ins::range_behaviour> rb;
halp::control<&ins::invert> i;
struct
{
halp_meta(layout, halp::layouts::hbox)
halp::control<&ins::curve> c;
halp::control<&ins::smooth> s;
} Knobs;
} filter;

struct
{
halp_meta(layout, halp::layouts::vbox)
halp_meta(background, halp::colors::mid)
halp::control<&ins::out_min> min;
halp::control<&ins::out_max> max;
} out_range;
};
};
}
19 changes: 19 additions & 0 deletions include/avnd/binding/ossia/builtin_ports.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,25 @@ struct builtin_arg_audio_ports<T>
}
};

template <typename T>
struct builtin_arg_value_ports
{
void init(ossia::inlets& inlets, ossia::outlets& outlets) { }
};

template <avnd::tag_cv T>
struct builtin_arg_value_ports<T>
{
ossia::value_inlet in;
ossia::value_outlet out;

void init(ossia::inlets& inlets, ossia::outlets& outlets)
{
inlets.push_back(&in);
outlets.push_back(&out);
}
};

template <typename T>
struct builtin_message_value_ports
{
Expand Down
130 changes: 130 additions & 0 deletions include/avnd/binding/ossia/data_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace oscr

// Special case for the easy non-audio case
template <ossia_compatible_nonaudio_processor T>
requires(!(avnd::tag_cv<T> && avnd::tag_stateless<T>))
class safe_node<T> : public safe_node_base<T, safe_node<T>>
{
public:
Expand Down Expand Up @@ -46,4 +47,133 @@ class safe_node<T> : public safe_node_base<T, safe_node<T>>
this->finish_run();
}
};

template <ossia_compatible_nonaudio_processor T>
requires(avnd::tag_cv<T> && avnd::tag_stateless<T>)
class safe_node<T> : public safe_node_base<T, safe_node<T>>
{
public:
using safe_node_base<T, safe_node<T>>::safe_node_base;

bool exec_once{};

constexpr bool scan_audio_input_channels() { return false; }
// This function goes from a host-provided tick to what the plugin expects
template <typename Tick>
auto invoke_effect(auto&& val, const Tick& t)
{
return this->impl.effect(val);
/*
// clang-format off
if constexpr(std::is_integral_v<Tick>)
{
static_assert(!avnd::has_tick<T>);
if_possible_r(this->impl.effect(val, t))
else if_possible_r(this->impl.effect(val))
else static_assert(std::is_void_v<Tick>, "impossible to call");
}
else
{
if constexpr (avnd::has_tick<T>)
{
// Do the process call
if_possible_r(this->impl.effect(val, t))
else if_possible_r(this->impl.effect(val, t.frames))
else if_possible_r(this->impl.effect(val, t.frames, t))
else if_possible_r(this->impl.effect(val))
else static_assert(std::is_void_v<Tick>, "impossible to call");
}
else
{
if_possible_r(this->impl.effect(val, t.frames))
else if_possible_r(this->impl.effect(val))
else static_assert(std::is_void_v<Tick>, "impossible to call");
}
}
// clang-format on
*/
}

template <typename Tick>
struct process_value
{
safe_node& self;
const Tick& tick;
ossia::value_port& out;
int ts{};
void operator()() { }
void operator()(ossia::impulse) { out.write_value(self.invoke_effect(0, tick), 0); }
void operator()(bool v) { out.write_value(self.invoke_effect(v ? 1 : 0, tick), 0); }
void operator()(int v) { out.write_value(self.invoke_effect(v, tick), 0); }
void operator()(float v) { out.write_value(self.invoke_effect(v, tick), 0); }
void operator()(const std::string& v)
{
out.write_value(self.invoke_effect(std::stof(v), tick), 0);
}
template <std::size_t N>
void operator()(std::array<float, N> v)
{
std::array<float, N> res;
for(int i = 0; i < N; i++)
res[i] = self.invoke_effect(v[i], tick);
out.write_value(res, 0);
}

// FIXME handle recursion
void operator()(const std::vector<ossia::value>& v)
{
std::vector<ossia::value> res;
res.reserve(v.size());

for(std::size_t i = 0; i < v.size(); i++)
res.push_back(self.invoke_effect(ossia::convert<float>(v[i]), tick));

out.write_value(std::move(res), 0);
}
void operator()(const ossia::value_map_type& v)
{
ossia::value_map_type res;
for(auto& [k, val] : v)
{
res.emplace_back(k, self.invoke_effect(ossia::convert<float>(val), tick));
}
out.write_value(std::move(res), 0);
}
};

OSSIA_MAXIMUM_INLINE
void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
{
// FIXME handle splitting execution multiple times per-input for e.g. time_independent mapping objects
if constexpr(avnd::tag_single_exec<T>)
{
if(std::exchange(exec_once, true))
{
return;
}
}

auto [start, frames] = st.timings(tk);

if(!this->prepare_run(tk, st, start, frames))
{
this->finish_run();
return;
}

// Smooth
this->process_smooth();

const auto tick
= avnd::get_tick_or_frames(this->impl, tick_info{*this, tk, st, frames});
process_value<decltype(tick)> proc{*this, tick, *this->arg_value_ports.out};

for(const ossia::timed_value& val : this->arg_value_ports.in->get_data())
{
val.value.apply(proc);
}

this->finish_run();
}
};
}
27 changes: 17 additions & 10 deletions include/avnd/binding/ossia/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class safe_node_base_base : public ossia::nonowning_graph_node

[[no_unique_address]] oscr::builtin_arg_audio_ports<T> audio_ports;

[[no_unique_address]] oscr::builtin_arg_value_ports<T> arg_value_ports;

[[no_unique_address]] oscr::builtin_message_value_ports<T> message_ports;

[[no_unique_address]] oscr::inlet_storage<T> ossia_inlets;
Expand Down Expand Up @@ -188,6 +190,7 @@ class safe_node_base : public safe_node_base_base<T>
this->sample_rate = sample_rate;

this->audio_ports.init(this->m_inlets, this->m_outlets);
this->arg_value_ports.init(this->m_inlets, this->m_outlets);
this->message_ports.init(this->m_inlets);
this->soundfiles.init(this->impl);

Expand Down Expand Up @@ -628,27 +631,31 @@ class safe_node_base : public safe_node_base_base<T>
// FIXME these concepts are super messy

template <typename FP, typename T>
concept real_mono_processor = avnd::mono_per_sample_arg_processor<FP, T>
|| avnd::mono_per_sample_port_processor<FP, T>
|| avnd::monophonic_single_port_audio_effect<FP, T>
|| avnd::mono_per_channel_arg_processor<FP, T>
|| avnd::mono_per_channel_port_processor<FP, T>;
concept real_mono_processor = !avnd::tag_cv<T>
&& (avnd::mono_per_sample_arg_processor<FP, T>
|| avnd::mono_per_sample_port_processor<FP, T>
|| avnd::monophonic_single_port_audio_effect<FP, T>
|| avnd::mono_per_channel_arg_processor<FP, T>
|| avnd::mono_per_channel_port_processor<FP, T>);
template <typename T>
concept real_good_mono_processor
= real_mono_processor<float, T> || real_mono_processor<double, T>;

template <typename T>
concept mono_generator = avnd::monophonic_single_port_generator<float, T>
|| avnd::monophonic_single_port_generator<double, T>;
concept mono_generator = !avnd::tag_cv<T>
&& (avnd::monophonic_single_port_generator<float, T>
|| avnd::monophonic_single_port_generator<double, T>);

template <typename T>
concept ossia_compatible_nonaudio_processor
= !(avnd::audio_argument_processor<T> || avnd::audio_port_processor<T>);
= avnd::tag_cv<T>
|| !(avnd::audio_argument_processor<T> || avnd::audio_port_processor<T>);

template <typename T>
concept ossia_compatible_audio_processor
= avnd::poly_sample_array_input_port_count<double, T> > 0
|| avnd::poly_sample_array_output_port_count<double, T> > 0;
= !avnd::tag_cv<T>
&& (avnd::poly_sample_array_input_port_count<double, T> > 0
|| avnd::poly_sample_array_output_port_count<double, T> > 0);

template <typename T>
class safe_node;
Expand Down
1 change: 1 addition & 0 deletions include/avnd/binding/ossia/port_run_postprocess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct process_after_run
assert(N == ctrl.ports.size());
for(int i = 0; i < N; i++)
{
// FIXME double-check all the "0", most likely they should be the tick start timestamp instead
write_value(
ctrl.ports[i], *port[i], ctrl.ports[i].value, 0, avnd::field_index<Idx>{});
}
Expand Down
16 changes: 16 additions & 0 deletions include/avnd/concepts/audio_processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/* SPDX-License-Identifier: GPL-3.0-or-later OR BSL-1.0 OR CC0-1.0 OR CC-PDCC OR 0BSD */

#include <avnd/common/enums.hpp>

#include <iterator>

namespace avnd
Expand All @@ -15,4 +17,18 @@ concept can_bypass = requires(T t) { t.bypass; };
template <typename T>
concept can_prepare = requires(T t) { t.prepare({}); };

/**
* This tag indicates that a processor is to be treated
* as supporting CV-like control for its I/O, even if it looks like an
* audio processor.
*/
AVND_DEFINE_TAG(cv)

/**
* This tag indicates that an object is stateless, e.g.
* the same object instance can be reused across multiple invocations
* for separate input values ; the output only depends
* on the inputs and nothing else.
*/
AVND_DEFINE_TAG(stateless)
}
6 changes: 6 additions & 0 deletions include/avnd/concepts/generic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ namespace avnd
action; \
}

#define if_possible_r(action) \
if constexpr(requires { action; }) \
{ \
return action; \
}

#define value_if_possible(A, X, B) \
[]() consteval \
{ \
Expand Down
9 changes: 5 additions & 4 deletions include/avnd/concepts/processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <avnd/common/function_reflection.hpp>
#include <avnd/concepts/audio_port.hpp>
#include <avnd/concepts/audio_processor.hpp>
#include <avnd/concepts/port.hpp>

// clang-format off
Expand Down Expand Up @@ -286,15 +287,15 @@ concept bus_port_processor

template<typename T>
concept audio_argument_processor =
sample_arg_processor<T>
(sample_arg_processor<T>
|| channel_arg_processor<T>
|| bus_arg_processor<T>
|| bus_arg_processor<T>) && !tag_cv<T>
;
template<typename T>
concept audio_port_processor =
sample_port_processor<T>
(sample_port_processor<T>
|| channel_port_processor<T>
|| bus_port_processor<T>
|| bus_port_processor<T>) && !tag_cv<T>
;
}

Expand Down
Loading

0 comments on commit 9d15a79

Please sign in to comment.