diff --git a/cmake/avendish.examples.cmake b/cmake/avendish.examples.cmake index 0ff9f7ee..cb57be6d 100644 --- a/cmake/avendish.examples.cmake +++ b/cmake/avendish.examples.cmake @@ -107,6 +107,14 @@ avnd_make_object( ) +avnd_make_object( + TARGET CompleteMessageExample + MAIN_FILE examples/Complete/CompleteMessageExample.hpp + MAIN_CLASS examples::helpers::CompleteMessageExample + C_NAME avnd_complete_message_example +) + + # This one does not really make sense as a Pd or Max object # (Pd has no notion of MIDI port) diff --git a/cmake/avendish.max.cmake b/cmake/avendish.max.cmake index ccdf7a86..2426eaf5 100644 --- a/cmake/avendish.max.cmake +++ b/cmake/avendish.max.cmake @@ -14,6 +14,10 @@ elseif(EXISTS "${AVND_MAXSDK_PATH}/c74support/max-includes") set(MAXSDK_MAX_INCLUDE_DIR "${AVND_MAXSDK_PATH}/c74support/max-includes") set(MAXSDK_MSP_INCLUDE_DIR "${AVND_MAXSDK_PATH}/c74support/msp-includes") set(MAXSDK_JIT_INCLUDE_DIR "${AVND_MAXSDK_PATH}/c74support/jit-includes") +elseif(EXISTS "${AVND_MAXSDK_PATH}/source/max-sdk-base/c74support/max-includes") + set(MAXSDK_MAX_INCLUDE_DIR "${AVND_MAXSDK_PATH}/source/max-sdk-base/c74support/max-includes") + set(MAXSDK_MSP_INCLUDE_DIR "${AVND_MAXSDK_PATH}/source/max-sdk-base/c74support/msp-includes") + set(MAXSDK_JIT_INCLUDE_DIR "${AVND_MAXSDK_PATH}/source/max-sdk-base/c74support/jit-includes") endif() if(APPLE) diff --git a/examples/Complete/CompleteMessageExample.hpp b/examples/Complete/CompleteMessageExample.hpp new file mode 100644 index 00000000..bdceecf5 --- /dev/null +++ b/examples/Complete/CompleteMessageExample.hpp @@ -0,0 +1,111 @@ +#pragma once +#include +#include +#include +#include + +#include +#include + +namespace examples::helpers +{ +struct CompleteMessageExample +{ + halp_meta(name, "CompleteMessageExample") + halp_meta(c_name, "avnd_complete_message_example") + halp_meta(author, "Jean-Michaƫl Celerier") + halp_meta(category, "Examples") + halp_meta(description, "Test all the Max & Pd features at once") + halp_meta(uuid, "ecbf8e41-5596-4d13-8407-6b962a02fa54") + + struct + { + // Pd: + // - first inlet will receive either: + // * Numbers directly + // * The message [ Test1 123 > + // * The message [ test1 45 > + struct : halp::val_port<"Test1", int> + { + // Class attribute: always goes to the first inlet + // and does not create a new inlet + halp_flag(class_attribute); + + void update(CompleteMessageExample& a) + { + std::cerr << "Test1: " << value << std::endl; + a.outputs.out_msg1(1.f, 10, "message1"); + a.outputs.out_msg2("heya", "message2"); + } + } test1; + + struct : halp::val_port<"Test2", float> + { + // Class attribute: always goes to the first inlet + // and does not create a new inlet + halp_flag(class_attribute); + + void update(CompleteMessageExample& a) + { + std::cerr << "Test2: " << value << std::endl; + } + } test2; + struct : halp::val_port<"Test3", float> + { + // Not a class attribute: will create a new inlet + + void update(CompleteMessageExample& a) + { + std::cerr << "Test3: " << value << std::endl; + } + } test3; + struct : halp::val_port<"Test4", std::string> + { + // Not a class attribute: will create a new inlet + + void update(CompleteMessageExample& a) + { + std::cerr << "Test4: " << value << std::endl; + } + } test4; + struct : halp::val_port<"Test5", std::vector> + { + // Not a class attribute: will create a new inlet + + void update(CompleteMessageExample& a) + { + std::cerr << "Test5: [ "; + for(auto v : value) + std::cerr << v << ", "; + std::cerr << "]\n"; + } + } test5; + } inputs; + + struct messages + { + struct + { + halp_meta(name, "m1"); + void operator()(int a, float b, std::string c) + { + std::cerr << "m1: " << a << " " << b << " " << c << "\n"; + } + } m1; + }; + + struct + { + halp::callback<"some_symbol", float, int, std::string> out_msg1; + halp::callback<"other_symbol", std::string, std::string> out_msg2; + } outputs; + + void operator()() + { + std::cerr << "Test1: " << inputs.test1.value << "\n"; + std::cerr << "Test2: " << inputs.test2.value << "\n"; + std::cerr << "Test3: " << inputs.test3.value << "\n"; + } +}; + +} diff --git a/examples/Complete/CompleteMessageExample.pd b/examples/Complete/CompleteMessageExample.pd new file mode 100644 index 00000000..4f293789 --- /dev/null +++ b/examples/Complete/CompleteMessageExample.pd @@ -0,0 +1,37 @@ +#N canvas 1344 0 956 1035 12; +#X obj 135 472 avnd_complete_message_example; +#X obj 138 523 print A; +#X obj 337 524 print B; +#X floatatom 143 306 5 0 0 0 - - - 0; +#X msg 135 76 Test1 1; +#X msg 137 228 m1 1 2.3 foobar; +#X msg 137 134 Test2 15; +#X msg 137 183 Test3 123; +#X floatatom 202 401 4 0 0 0 - - - 0; +#X obj 140 272 bng 18 250 50 0 empty empty empty 0 -9 0 12 #fcfcfc #000000 #000000; +#X text 182 306 Set Test1 \, update(); +#X text 193 78 Set Test1 \, update(); +#X text 162 273 operator()(); +#X text 201 135 Set Test2 \, update(); +#X text 208 184 Set Test3 \, update(); +#X text 251 230 call m1(); +#X symbolatom 268 438 10 0 0 0 - - - 0; +#X msg 361 445 list 1 32 17 14 6 145; +#X text 197 382 Set Test3 \, update(); +#X msg 145 347 Test5 1 3 5 17 12; +#X msg 266 408 symbol hello; +#X text 367 407 Set Test4 \, update(); +#X text 519 446 Set Test5 \, update(); +#X connect 0 0 1 0; +#X connect 0 1 2 0; +#X connect 3 0 0 0; +#X connect 4 0 0 0; +#X connect 5 0 0 0; +#X connect 6 0 0 0; +#X connect 7 0 0 0; +#X connect 8 0 0 1; +#X connect 9 0 0 0; +#X connect 16 0 0 2; +#X connect 17 0 0 3; +#X connect 19 0 0 0; +#X connect 20 0 16 0; diff --git a/include/avnd/binding/max/outputs.hpp b/include/avnd/binding/max/outputs.hpp index 386408b1..9c0f04c8 100644 --- a/include/avnd/binding/max/outputs.hpp +++ b/include/avnd/binding/max/outputs.hpp @@ -69,6 +69,12 @@ struct do_process_outlet outlet_list(p, nullptr, l.atoms.size(), l.atoms.data()); } + void operator()(const avnd::variant_ish auto& v) const noexcept + { + using namespace std; + visit([](const auto& val) { (*this)(val); }, v); + } + void operator()(const avnd::map_ish auto& v) const noexcept { /* diff --git a/include/avnd/binding/pd/helpers.hpp b/include/avnd/binding/pd/helpers.hpp index 3bc00116..c865cbbc 100644 --- a/include/avnd/binding/pd/helpers.hpp +++ b/include/avnd/binding/pd/helpers.hpp @@ -91,10 +91,10 @@ static void process_generic_message(T& implementation, t_symbol* s) template static constexpr bool compatible(t_atomtype type) { - if constexpr(requires(Arg arg) { arg = 0.f; }) - return type == t_atomtype::A_FLOAT; - else if constexpr(requires(Arg arg) { arg = "str"; }) + if constexpr(requires(Arg arg) { arg = "str"; }) return type == t_atomtype::A_SYMBOL; + else if constexpr(requires(Arg arg) { arg = 0.f; }) + return type == t_atomtype::A_FLOAT; return false; } @@ -102,24 +102,14 @@ static constexpr bool compatible(t_atomtype type) template static Arg convert(t_atom& atom) { - if constexpr(requires(Arg arg) { arg = 0.f; }) - return atom.a_w.w_float; - else if constexpr(requires(Arg arg) { arg = "str"; }) + if constexpr(requires(Arg arg) { arg = "str"; }) return atom.a_w.w_symbol->s_name; + else if constexpr(requires(Arg arg) { arg = 0.f; }) + return atom.a_w.w_float; else static_assert(std::is_same_v, "Argument type not handled yet"); } -t_symbol* symbol_for_port(avnd::float_parameter auto& port) -{ - return &s_float; -} - -t_symbol* symbol_for_port(avnd::string_parameter auto& port) -{ - return &s_symbol; -} - t_symbol* symbol_for_port(avnd::mono_audio_port auto& port) { return &s_signal; @@ -130,9 +120,32 @@ t_symbol* symbol_for_port(avnd::poly_audio_port auto& port) return &s_signal; } -t_symbol* symbol_for_port(auto& port) +t_symbol* symbol_for_port(avnd::parameter auto& port) { + using type = std::remove_cvref_t; + if constexpr(avnd::vector_ish) + return &s_list; + else if constexpr(avnd::set_ish) + return &s_list; + else if constexpr(avnd::map_ish) + return &s_list; + else if constexpr(avnd::pair_ish) + return &s_list; + else if constexpr(avnd::tuple_ish) + return &s_list; + else if constexpr(avnd::span_ish) + return &s_list; + else if constexpr(std::floating_point) + return &s_float; + else if constexpr(std::integral) + return &s_float; + else if constexpr(avnd::string_ish) + return &s_symbol; return &s_anything; // TODO is that correct ? } +t_symbol* symbol_for_port(auto& port) +{ + return &s_anything; // TODO is that correct ? +} } diff --git a/include/avnd/binding/pd/inputs.hpp b/include/avnd/binding/pd/inputs.hpp index 5125b177..ef52b804 100644 --- a/include/avnd/binding/pd/inputs.hpp +++ b/include/avnd/binding/pd/inputs.hpp @@ -1,9 +1,170 @@ #pragma once #include +#if !defined(__cpp_lib_to_chars) +#include +#else +#include +#endif + +#include namespace pd { +// Note: this assumes that ac >= +struct from_atom +{ + t_atom& av; + template + requires std::integral || std::floating_point + bool operator()(T& v) const noexcept + { + switch(av.a_type) + { + case A_FLOAT: { + v = atom_getfloat(&av); + return true; + } + case A_SYMBOL: { + auto sym = av.a_w.w_symbol; + if(sym && sym->s_name) + { + double vv{}; +#if defined(__cpp_lib_to_chars) + auto [_, ec] + = std::from_chars(sym->s_name, sym->s_name + strlen(sym->s_name), vv); + if(ec == std::errc{}) + { + v = vv; + return true; + } +#else + std::string_view str{sym->s_name, strlen(sym->s_name)}; + if(boost::conversion::try_lexical_convert(str, vv)) + { + v = vv; + return true; + } +#endif + } + return false; + } + } + + return false; + } + + bool operator()(std::string& v) const noexcept + { + if(av.a_type == A_SYMBOL) + { + if(auto sym = av.a_w.w_symbol; sym && sym->s_name) + { + v = sym->s_name; + } + } + return true; + } +}; + +struct from_atoms +{ + long ac{}; + t_atom* av{}; + + bool operator()(std::integral auto& v) const noexcept { return from_atom{av[0]}(v); } + + bool operator()(std::floating_point auto& v) const noexcept + { + return from_atom{av[0]}(v); + } + + bool operator()(avnd::vector_ish auto& v) const noexcept + { + v.clear(); + v.resize(ac); + + for(int i = 0; i < ac; i++) + from_atom{av[i]}(v[i]); + return true; + } + + template + bool operator()(avnd::array_ish auto& v) const noexcept + { + for(int i = 0; i < ac; i++) + from_atom{av[i]}(v[i]); + return true; + } + + bool operator()(avnd::pair_ish auto& v) const noexcept + { + if(ac < 2) + return false; + + from_atom{av[0]}(v.first); + from_atom{av[1]}(v.second); + return true; + } + + bool operator()(avnd::set_ish auto& v) const noexcept + { + v.clear(); + + using value_type = std::remove_cvref_t; + for(int i = 0; i < ac; i++) + { + value_type val; + from_atom{av[i]}(val); + v.insert(std::move(val)); + } + return true; + } + + bool operator()(avnd::map_ish auto& v) const noexcept + { + v.clear(); + if(ac <= 1) + return false; + + using value_type = std::remove_cvref_t; + using key_type = std::remove_cvref_t; + using mapped_type = std::remove_cvref_t; + for(int i = 0; i < ac / 2; i += 2) + { + value_type val; + from_atom{av[i]}(val.first); + from_atom{av[i + 1]}(val.second); + v.insert(std::move(val)); + } + return true; + } + + template + requires std::is_enum_v + bool operator()(T& v) const noexcept + { + auto r = static_cast>(v); + auto res = from_atom{av[0]}(r); + if(res) + v = static_cast(r); + return res; + } + + template + requires( + std::is_class_v && avnd::pfr::tuple_size_v == 0 + && std::is_trivial_v && std::is_standard_layout_v) + bool operator()(T& v) const noexcept + { + // Impulse case, nothing to do + // FIXME unless we have an optional parameter ! + return true; + } + + bool operator()(std::string& v) const noexcept { return from_atom{av[0]}(v); } +}; + template struct inputs { @@ -11,16 +172,56 @@ struct inputs { int k = 0; avnd::input_introspection::for_all( - avnd::get_inputs(implementation), [&x_obj, &k](auto& ctl) { - if(k++) - { // Skip the first port - if_possible(floatinlet_new(&x_obj, &ctl.value)) - - // TODO - //else if_possible(symbolinlet_new(&x_obj, &ctl.value)); - // => we must allocate a t_symbol* and copy the s_name before execution - } - }); + avnd::get_inputs(implementation), [&x_obj, &k](M& ctl) { + // Skip the first port + if(k++) + { + // Do not create a port for attributes + if constexpr(!avnd::attribute_port) + { + static constexpr auto name = avnd::get_name(); + + inlet_new(&x_obj, &x_obj.ob_pd, pd::symbol_for_port(ctl), gensym(name.data())); + } + } + }); + } + + template + bool + process_inlet_control(T& obj, Field& field, std::string_view s, int argc, t_atom* argv) + { + if(from_atoms{argc, argv}(field.value)) + { + if constexpr(requires { field.update(obj); }) + { + field.update(obj); + } + return true; + } + return false; + } + + bool process_inputs( + avnd::effect_container& implementation, t_symbol* s, int argc, t_atom* argv) + { + // FIXME create static pointer tables instead + if constexpr(avnd::parameter_input_introspection::size > 0) + { + bool ok = false; + std::string_view symname = s->s_name; + avnd::parameter_input_introspection::for_all( + avnd::get_inputs(implementation), [&](M& field) { + if(ok) + return; + if(symname == avnd::get_name()) + { + ok = process_inlet_control(implementation.effect, field, symname, argc, argv); + } + }); + return ok; + } + return false; } }; diff --git a/include/avnd/binding/pd/message_processor.hpp b/include/avnd/binding/pd/message_processor.hpp index 0da4ed36..e24af0e0 100644 --- a/include/avnd/binding/pd/message_processor.hpp +++ b/include/avnd/binding/pd/message_processor.hpp @@ -128,13 +128,19 @@ 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; }) + { avnd::apply_control(port, arg.a_w.w_float); + if_possible(port.update(implementation.effect)); + } break; } case A_SYMBOL: { if constexpr(requires { port.value = "string"; }) + { avnd::apply_control(port, arg.a_w.w_symbol->s_name); + if_possible(port.update(implementation.effect)); + } break; } @@ -190,6 +196,8 @@ struct message_processor // First try to process messages handled explicitely in the object if(messages_setup.process_messages(implementation, s, argc, argv)) return; + if(input_setup.process_inputs(implementation, s, argc, argv)) + return; // Then some default behaviour switch(argc) diff --git a/include/avnd/binding/pd/messages.hpp b/include/avnd/binding/pd/messages.hpp index f1a48a05..203b4bac 100644 --- a/include/avnd/binding/pd/messages.hpp +++ b/include/avnd/binding/pd/messages.hpp @@ -175,19 +175,20 @@ struct messages static bool process_messages( avnd::effect_container& implementation, t_symbol* s, int argc, t_atom* argv) { + // FIXME create static pointer tables instead if constexpr(avnd::has_messages) { bool ok = false; std::string_view symname = s->s_name; avnd::messages_introspection::for_all( avnd::get_messages(implementation), [&](M& field) { - if(ok) - return; - if(symname == M::name()) - { - ok = process_message(implementation.effect, field, symname, argc, argv); - } - }); + if(ok) + return; + if(symname == avnd::get_name()) + { + ok = process_message(implementation.effect, field, symname, argc, argv); + } + }); return ok; } return false; diff --git a/include/avnd/binding/pd/outputs.hpp b/include/avnd/binding/pd/outputs.hpp index c4ca7fba..c0be9605 100644 --- a/include/avnd/binding/pd/outputs.hpp +++ b/include/avnd/binding/pd/outputs.hpp @@ -192,6 +192,22 @@ inline void value_to_pd(t_outlet* outlet, const std::string& v) noexcept outlet_symbol(outlet, gensym(v.c_str())); } +template + requires(sizeof...(Args) > 1) +inline void value_to_pd(t_outlet* outlet, Args&&... v) noexcept +{ + std::array atoms; + static constexpr int N = sizeof...(Args); + + int i = 0; + + [&](std::index_sequence) { + (value_to_pd(atoms[I], v), ...); + }(std::make_index_sequence{}); + + outlet_list(outlet, &s_list, N, atoms.data()); +} + template struct value_writer { @@ -213,7 +229,7 @@ struct value_writer using processor_type = typename 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{}); + static constexpr int storage_index = avnd::index_of_element(indices{}); auto& buffer = get(buffers); diff --git a/include/avnd/wrappers/widgets.hpp b/include/avnd/wrappers/widgets.hpp index d788fd90..c4dd977e 100644 --- a/include/avnd/wrappers/widgets.hpp +++ b/include/avnd/wrappers/widgets.hpp @@ -94,6 +94,8 @@ struct widget_reflection return "range_spinbox"; case widget_type::multi_slider: return "multi_slider"; + default: + return "unknown"; } } using value_type = T;