From 53e5de41e2814319637531cc7b6304b0d99e6214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Tue, 26 Nov 2024 11:20:46 -0500 Subject: [PATCH] pd: start implementing support for clock --- cmake/avendish.sources.cmake | 2 +- include/avnd/binding/pd/message_processor.hpp | 327 +++++++++++++----- include/avnd/binding/pd/prototype.cpp.in | 23 +- 3 files changed, 265 insertions(+), 87 deletions(-) diff --git a/cmake/avendish.sources.cmake b/cmake/avendish.sources.cmake index 889987dc..2954bae2 100644 --- a/cmake/avendish.sources.cmake +++ b/cmake/avendish.sources.cmake @@ -148,7 +148,7 @@ function(avnd_target_setup AVND_FX_TARGET) CXX_VISIBILITY_PRESET internal ) - target_link_libraries(${AVND_FX_TARGET} PUBLIC Boost::boost) + target_link_libraries(${AVND_FX_TARGET} PUBLIC Boost::boost concurrentqueue) endfunction() function(avnd_common_setup AVND_TARGET AVND_FX_TARGET) diff --git a/include/avnd/binding/pd/message_processor.hpp b/include/avnd/binding/pd/message_processor.hpp index 0da4ed36..dcc59609 100644 --- a/include/avnd/binding/pd/message_processor.hpp +++ b/include/avnd/binding/pd/message_processor.hpp @@ -14,8 +14,8 @@ #include #include #include -#include #include +#include #include #include @@ -29,23 +29,19 @@ */ namespace pd { -template -struct message_processor_metaclass +struct processor_base { - static inline t_class* g_class{}; - static inline message_processor_metaclass* instance{}; + // Head of the Pd object + t_object x_obj; - message_processor_metaclass(); + int64_t type_index{}; }; template -struct message_processor +struct message_processor : processor_base { using processor_type = T; - // Head of the Pd object - t_object x_obj; - // Our actual code avnd::effect_container implementation; @@ -54,21 +50,17 @@ struct message_processor [[no_unique_address]] outputs output_setup; - [[no_unique_address]] init_arguments init_setup; - - [[no_unique_address]] messages messages_setup; - [[no_unique_address]] avnd::control_storage control_buffers; - t_clock* m_clock{}; using sched_func = void (*)(T&); - struct sched_event - { - sched_func func; - t_clock* clock; - }; + // struct sched_event + // { + // sched_func func; + // }; + t_clock* m_clock{}; - boost::lockfree::queue funcs{1024}; + // moodycamel::ConcurrentQueue funcs{}; + std::unordered_map m_clocks; static constexpr const int buffer_size = 1; // Used for control storage // we don't use ctor / dtor, because @@ -91,22 +83,67 @@ struct message_processor avnd::init_controls(implementation); } + // Impossible to make work reliably in Pd... + // https://github.com/pure-data/pure-data/issues/2104 + /* if constexpr(avnd::has_schedule) { + static constexpr auto tick = +[](message_processor* x) { + sched_event ev; + while(x->funcs.try_dequeue(ev)) + { + ev.func(x->implementation.effect); + } + }; + m_clock = clock_new(&this->x_obj, (t_method)tick); implementation.effect.schedule.schedule_at = [this](int64_t ts, sched_func func) { - auto tick = +[](message_processor* x) { - sched_event ev; - if(x->funcs.pop(ev)) - { - ev.func(x->implementation.effect); - clock_free(ev.clock); - } - }; + { + sys_lock(); + clock_set(m_clock, 0.f); + sys_unlock(); + } + funcs.try_enqueue(sched_event{func}); + }; + } +*/ + + if constexpr(avnd::has_clock) + { + const std::chrono::seconds time = avnd::get_clock(this->implementation.effect); + static constexpr auto tick = +[](message_processor* x) { x->tick_process(); }; + m_clock = clock_new(&this->x_obj, (t_method)tick); + auto ms = std::chrono::duration_cast(time).count(); + clock_set(m_clock, ms); + } + + if constexpr(avnd::has_clocks) + { +#if 0 + static constexpr auto tick = +[](message_processor* x) { + // sched_event ev; + // while(x->funcs.try_dequeue(ev)) + // { + // ev.func(x->implementation.effect); + // } + }; + + auto& clk_spec = avnd::get_clocks(this->implementation.effect); + + implementation.effect.schedule.clock.start = [this](auto time) { + // if(spec has clock) + // use it + // else + + auto m_clock = clock_new(&this->x_obj, (t_method)tick); + auto ms = std::chrono::duration_cast(time).count(); + clock_set(m_clock, ms); + }; - auto clk = clock_new(&this->x_obj, (t_method)tick); - clock_set(clk, 0.f); - funcs.push(sched_event{func, clk}); + implementation.effect.schedule.clock.stop = [this]() { + t_clock* clk{}; + clock_free(clk); }; +#endif } avnd::prepare(implementation, {}); @@ -114,11 +151,19 @@ struct message_processor /// Pass arguments if constexpr(avnd::can_initialize) { - init_setup.process(implementation.effect, argc, argv); + static constexpr auto init = init_arguments{}; + init.process(implementation.effect, argc, argv); } } - void destroy() { } + void destroy() + { + if constexpr(avnd::has_clock) + { + clock_free(m_clock); + m_clock = nullptr; + } + } template void set_inlet(C& port, t_atom& arg) @@ -127,7 +172,9 @@ struct message_processor { case A_FLOAT: { // This is the float that is supposed to go inside the first inlet if any ? - if constexpr(requires { port.value = 0.f; }) + if constexpr(std::is_arithmetic_v && requires { + port.value = 1.f; + }) avnd::apply_control(port, arg.a_w.w_float); break; } @@ -166,14 +213,8 @@ struct message_processor } } - void default_process(t_symbol* s, int argc, t_atom* argv) + void tick_process() { - // Potentially clear the outlets if needed - this->control_buffers.clear_outputs(this->implementation); - - // First apply the data to the first inlet (other inlets are handled by Pd). - process_first_inlet_control(s, argc, argv); - // Do our stuff if it makes sense - some objects may not // even have a "processing" method if_possible(implementation.effect()) else if_possible(implementation.effect(1)); @@ -185,10 +226,22 @@ struct message_processor this->control_buffers.clear_inputs(this->implementation); } + void default_process(t_symbol* s, int argc, t_atom* argv) + { + // Potentially clear the outlets if needed + this->control_buffers.clear_outputs(this->implementation); + + // First apply the data to the first inlet (other inlets are handled by Pd). + process_first_inlet_control(s, argc, argv); + + tick_process(); + } + void process(t_symbol* s, int argc, t_atom* argv) { // First try to process messages handled explicitely in the object - if(messages_setup.process_messages(implementation, s, argc, argv)) + if(static constexpr messages messages_setup; + messages_setup.process_messages(implementation, s, argc, argv)) return; // Then some default behaviour @@ -208,11 +261,16 @@ struct message_processor }; template -message_processor_metaclass::message_processor_metaclass() +struct message_processor_metaclass { - message_processor_metaclass::instance = this; - using instance = message_processor; - /* + static inline t_class* g_class{}; + static inline message_processor_metaclass* instance{}; + + message_processor_metaclass() + { + message_processor_metaclass::instance = this; + using instance = message_processor; + /* #if !defined(_MSC_VER) static_assert(std::is_aggregate_v); static_assert(std::is_aggregate_v); @@ -221,39 +279,146 @@ message_processor_metaclass::message_processor_metaclass() static_assert(std::is_nothrow_move_assignable_v); #endif */ - /// Small wrapper methods which will call into our actual type /// - - // Ctor - constexpr auto obj_new = +[](t_symbol* s, int argc, t_atom* argv) -> void* { - // Initializes the t_object - t_pd* ptr = pd_new(g_class); - - // Initializes the rest - auto obj = reinterpret_cast(ptr); - new(obj) instance; - obj->init(argc, argv); - return obj; - }; - - // Dtor - constexpr auto obj_free = +[](instance* obj) -> void { - obj->destroy(); - obj->~instance(); - }; - - // Message processing - constexpr auto obj_process - = +[](instance* obj, t_symbol* s, int argc, t_atom* argv) -> void { - obj->process(s, argc, argv); - }; - - /// Class creation /// - g_class = class_new( - symbol_from_name(), (t_newmethod)obj_new, (t_method)obj_free, - sizeof(message_processor), CLASS_DEFAULT, A_GIMME, 0); - - // Connect our methods - class_addanything(g_class, (t_method)obj_process); -} + /// Small wrapper methods which will call into our actual type /// + + // Ctor + constexpr auto obj_new = +[](t_symbol* s, int argc, t_atom* argv) -> void* { + // Initializes the t_object + t_pd* ptr = pd_new(g_class); + + // Initializes the rest + auto obj = reinterpret_cast(ptr); + new(obj) instance; + obj->init(argc, argv); + return obj; + }; + + // Dtor + constexpr auto obj_free = +[](instance* obj) -> void { + obj->destroy(); + obj->~instance(); + }; + + // Message processing + constexpr auto obj_process + = +[](instance* obj, t_symbol* s, int argc, t_atom* argv) -> void { + obj->process(s, argc, argv); + }; + + /// Class creation /// + g_class = class_new( + symbol_from_name(), (t_newmethod)obj_new, (t_method)obj_free, + sizeof(message_processor), CLASS_DEFAULT, A_GIMME, 0); + + // Connect our methods + class_addanything(g_class, (t_method)obj_process); + } +}; +template +struct generic_processor_metaclass +{ + static inline t_class* g_class{}; + static inline generic_processor_metaclass* instance{}; + generic_processor_metaclass() + { + generic_processor_metaclass::instance = this; + // using instance = message_processor; + /* +#if !defined(_MSC_VER) + static_assert(std::is_aggregate_v); + static_assert(std::is_aggregate_v); + static_assert(std::is_nothrow_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_assignable_v); +#endif +*/ + /// Small wrapper methods which will call into our actual type /// + // 1. We have to get all the possible types of possible constructors + using type_list = constructor_type_list; + using processor_type_list = boost::mp11::mp_transform; + static_assert(boost::mp11::mp_size{} > 0); + static constexpr auto sz = max_sizeof(processor_type_list{}); + static constexpr auto alternatives = boost::mp11::mp_size{}; + + // Ctor + static constexpr auto obj_new = +[](t_symbol* s, int argc, t_atom* argv) -> void* { + // Initializes the t_object + t_pd* ptr = pd_new(g_class); + reinterpret_cast(ptr)->type_index = -1; + + // Initializes the rest + bool ok = false; + pd::invoke_construct( + s, argc, argv, [&ok, &ptr, argc, argv](X&& new_object) { + static constexpr int64_t index = + typename boost::mp11::mp_find::type{}; + using instance = message_processor; + auto obj = reinterpret_cast(ptr); + auto old_obj = obj->x_obj; + + std::construct_at(obj); + + obj->implementation.effect = std::move(new_object); + obj->x_obj = old_obj; + obj->type_index = index; + obj->init(argc, argv); + ok = true; + }); + if(ok) + { + return ptr; + } + else + { + pd_free(ptr); + return nullptr; + } + }; + + // Dtor + static constexpr auto for_obj = [](processor_base* obj, auto func) { + auto do_obj_free = [=]() { + using type = boost::mp11::mp_at_c; + using processor_type = message_processor; + auto object = static_cast(obj); + func(object); + }; + [&](std::integer_sequence) { + return ( + ((obj->type_index == Args) + && (do_obj_free.template operator()(), true)) + || ...); + }(std::make_integer_sequence{}); + }; + static constexpr auto obj_free = +[](processor_base* obj) -> void { + if(obj->type_index < 0) + { + // If we could not construct the object + std::destroy_at((t_pd*)obj); + } + else + { + for_obj(obj, [](auto* object) { + object->destroy(); + std::destroy_at(object); + }); + } + }; + + // Message processing + constexpr auto obj_process + = +[](processor_base* obj, t_symbol* s, int argc, t_atom* argv) -> void { + for_obj(obj, [=](auto* object) { object->process(s, argc, argv); }); + }; + + /// Class creation /// + g_class = class_new( + symbol_from_name(), (t_newmethod)obj_new, (t_method)obj_free, sz, + CLASS_DEFAULT, A_GIMME, 0); + + // Connect our methods + class_addanything(g_class, (t_method)obj_process); + } +}; } diff --git a/include/avnd/binding/pd/prototype.cpp.in b/include/avnd/binding/pd/prototype.cpp.in index 78a14b42..dcaf2e28 100644 --- a/include/avnd/binding/pd/prototype.cpp.in +++ b/include/avnd/binding/pd/prototype.cpp.in @@ -1,22 +1,35 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ +// clang-format off #include <@AVND_MAIN_FILE@> #include #include #include -extern "C" AVND_EXPORTED_SYMBOL void @AVND_C_NAME@_setup() +template +static void do_setup() { - using type = decltype(avnd::configure())::type; - if constexpr(avnd::monophonic_audio_processor || avnd::polyphonic_audio_processor) + if constexpr( + avnd::monophonic_audio_processor || avnd::polyphonic_audio_processor) { // If we're an audio effect, make a type with the whole DSP stuff - static const pd::audio_processor_metaclass< type > instance{}; + static const pd::audio_processor_metaclass instance{}; } else { // Simpler case which just processes messages - static const pd::message_processor_metaclass< type > instance{}; + if constexpr(requires { &type::construct; }) + static const pd::generic_processor_metaclass instance{}; + else + static const pd::message_processor_metaclass instance{}; } } +extern "C" AVND_EXPORTED_SYMBOL void @AVND_C_NAME@_setup() +{ + using type = decltype(avnd::configure())::type; + + do_setup(); +} + +// clang-format on