From 06b5c32e67c6c37822e039714c98b2376667ab28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= <jeanmichael.celerier@gmail.com> Date: Fri, 15 Mar 2024 11:51:09 -0400 Subject: [PATCH] [ossia] Improve standalone & oscquery bindings --- cmake/avendish.cmake | 2 +- cmake/avendish.dependencies.cmake | 17 ++ cmake/avendish.ossia.cmake | 15 +- cmake/avendish.sources.cmake | 4 +- cmake/avendish.standalone.cmake | 17 +- cmake/avendish.vst3.cmake | 44 ++++- examples/Helpers/Controls.hpp | 2 +- .../binding/standalone/oscquery_mapper.hpp | 164 +++++------------- .../avnd/binding/standalone/prototype.cpp.in | 18 +- .../avnd/binding/standalone/standalone.hpp | 6 +- 10 files changed, 152 insertions(+), 137 deletions(-) diff --git a/cmake/avendish.cmake b/cmake/avendish.cmake index 3d4266f4..c02c7847 100644 --- a/cmake/avendish.cmake +++ b/cmake/avendish.cmake @@ -9,7 +9,7 @@ set(AVND_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "") find_package(Boost QUIET REQUIRED) find_package(Threads QUIET) find_package(fmt QUIET) - +find_package(ossia QUIET) set(AVENDISH_SOURCES "${AVND_SOURCE_DIR}/include/avnd/concepts/all.hpp" diff --git a/cmake/avendish.dependencies.cmake b/cmake/avendish.dependencies.cmake index 61b4e8d3..56d4aaf9 100644 --- a/cmake/avendish.dependencies.cmake +++ b/cmake/avendish.dependencies.cmake @@ -41,3 +41,20 @@ if(NOT TARGET pantor::inja) ) FetchContent_MakeAvailable(pantor_inja) endif() + +if(APPLE) +if(NOT TARGET jthread) +FetchContent_Declare( + jthread + GIT_REPOSITORY "https://github.com/StirlingLabs/jthread" + GIT_TAG main + GIT_PROGRESS true +) +FetchContent_MakeAvailable(jthread) +endif() +endif() + + +if(NOT TARGET jthread) + add_library(jthread INTERFACE) +endif() diff --git a/cmake/avendish.ossia.cmake b/cmake/avendish.ossia.cmake index f88962b7..8a2c9663 100644 --- a/cmake/avendish.ossia.cmake +++ b/cmake/avendish.ossia.cmake @@ -67,9 +67,22 @@ function(avnd_make_ossia) PUBLIC Avendish::Avendish ossia::ossia - SDL2 ) + if(TARGET SDL2::SDL2) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + SDL2::SDL2 + ) + elseif(TARGET SDL2::SDL2-static) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + SDL2::SDL2-static + ) + endif() + avnd_common_setup("${AVND_TARGET}" "${AVND_FX_TARGET}") target_sources(Avendish PRIVATE diff --git a/cmake/avendish.sources.cmake b/cmake/avendish.sources.cmake index 889987dc..5b3126bd 100644 --- a/cmake/avendish.sources.cmake +++ b/cmake/avendish.sources.cmake @@ -72,7 +72,7 @@ function(avnd_target_setup AVND_FX_TARGET) target_compile_options( ${AVND_FX_TARGET} PUBLIC - -stdlib=libc++ + # -stdlib=libc++ # -flto -fno-stack-protector -fno-ident @@ -133,7 +133,7 @@ function(avnd_target_setup AVND_FX_TARGET) ) elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") target_link_libraries(${AVND_FX_TARGET} PRIVATE - -lc++ + # -lc++ -Bsymbolic # -flto ) diff --git a/cmake/avendish.standalone.cmake b/cmake/avendish.standalone.cmake index dcaf1bb3..8ca6bb26 100644 --- a/cmake/avendish.standalone.cmake +++ b/cmake/avendish.standalone.cmake @@ -3,7 +3,6 @@ if(CMAKE_SYSTEM_NAME MATCHES "WAS.*") endfunction() return() endif() -find_package(ossia) find_package(GLEW QUIET) find_package(glfw3 QUIET) find_package(OpenGL QUIET) @@ -86,7 +85,20 @@ function(avnd_make_standalone) ${AVND_FX_TARGET} PUBLIC ossia::ossia - SDL2 + ) + endif() + + if(TARGET SDL2::SDL2) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + SDL2::SDL2 + ) + elseif(TARGET SDL2::SDL2-static) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + SDL2::SDL2-static ) endif() @@ -148,6 +160,7 @@ function(avnd_make_standalone) ${AVND_FX_TARGET} PUBLIC Avendish::Avendish + jthread ) if(TARGET ossia::ossia) diff --git a/cmake/avendish.vst3.cmake b/cmake/avendish.vst3.cmake index 673620d7..2bfd6f6f 100644 --- a/cmake/avendish.vst3.cmake +++ b/cmake/avendish.vst3.cmake @@ -29,6 +29,15 @@ if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor") endif() +if(APPLE) + set(VST3_DYNAMIC_LIST "_BundleEntry\n_BundleExit\n_bundleEntry\n_bundleExit\n_GetPluginFactory") +elseif(WIN32) + set(VST3_DYNAMIC_LIST "AVND {\nlocal:*;\nglobal:InitDll;\nExitDll;\nGetPluginFactory;\n};\n") +else() + set(VST3_DYNAMIC_LIST "AVND {\n global:\n ModuleEntry;\n ModuleExit;\n GetPluginFactory;\n local:\n *;\n};\n") +endif() + +file(WRITE "${CMAKE_BINARY_DIR}/vst3_symbols" "${VST3_DYNAMIC_LIST}") add_subdirectory("${VST3_SDK_ROOT}" "${CMAKE_BINARY_DIR}/vst3_sdk") function(avnd_make_vst3) @@ -62,9 +71,42 @@ function(avnd_make_vst3) ${AVND_FX_TARGET} PUBLIC Avendish::Avendish_vst3 - sdk_common pluginterfaces + $<IF:$<BOOL:${APPLE}>,base sdk_common pluginterfaces,$<LINK_GROUP:RESCAN,base,sdk_common,pluginterfaces>> DisableExceptions ) + + if(UNIX AND NOT APPLE) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + -Wl,-z,defs + ) + endif() + + if(APPLE) + target_link_libraries(${AVND_FX_TARGET} PRIVATE jthread "-Wl,-exported_symbols_list,${CMAKE_BINARY_DIR}/vst3_symbols") + elseif(WIN32) + if(NOT MSVC) + target_link_libraries(${AVND_FX_TARGET} PRIVATE "-Wl,--version-script=${CMAKE_BINARY_DIR}/vst3_symbols") + endif() + else() + target_link_libraries(${AVND_FX_TARGET} PRIVATE "-Wl,--version-script=${CMAKE_BINARY_DIR}/vst3_symbols") + endif() + + + if(TARGET ossia::ossia) + target_compile_definitions( + ${AVND_FX_TARGET} + PUBLIC + AVND_ADD_OSCQUERY_BINDINGS=1 + ) + target_link_libraries( + ${AVND_FX_TARGET} + PUBLIC + ossia::ossia + ) + endif() + if(APPLE) find_library(COREFOUNDATION_FK CoreFoundation) target_link_libraries( diff --git a/examples/Helpers/Controls.hpp b/examples/Helpers/Controls.hpp index 71ba432b..4ddfa921 100644 --- a/examples/Helpers/Controls.hpp +++ b/examples/Helpers/Controls.hpp @@ -58,7 +58,7 @@ struct Controls struct range { - halp::combo_pair<float> values[3]{{"Foo", 0.1f}, {"Bar", 0.5f}, {"Baz", 0.8f}}; + halp::combo_pair<float> values[3]{{"A", 0.1f}, {"B", 0.5f}, {"C", 0.8f}}; int init{1}; // Bar }; diff --git a/include/avnd/binding/standalone/oscquery_mapper.hpp b/include/avnd/binding/standalone/oscquery_mapper.hpp index 1acae3bc..0e98743b 100644 --- a/include/avnd/binding/standalone/oscquery_mapper.hpp +++ b/include/avnd/binding/standalone/oscquery_mapper.hpp @@ -2,6 +2,8 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ +#include <avnd/binding/ossia/from_value.hpp> +#include <avnd/binding/ossia/to_value.hpp> #include <avnd/concepts/all.hpp> #include <avnd/introspection/input.hpp> #include <avnd/introspection/messages.hpp> @@ -16,9 +18,7 @@ #include <ossia/network/context_functions.hpp> #include <ossia/network/generic/generic_device.hpp> #include <ossia/network/generic/generic_parameter.hpp> -#include <ossia/protocols/midi/midi.hpp> #include <ossia/protocols/oscquery/oscquery_server_asio.hpp> - namespace standalone { template <typename T> @@ -35,13 +35,14 @@ struct oscquery_mapper std::shared_ptr<ossia::net::network_context> m_context; ossia::net::generic_device m_dev; - explicit oscquery_mapper(avnd::effect_container<T>& object, int osc_port, int ws_port) + explicit oscquery_mapper( + avnd::effect_container<T>& object, std::string name, int osc_port, int ws_port) : object{object} , m_context{std::make_shared<ossia::net::network_context>()} , m_dev{ std::make_unique<ossia::oscquery_asio::oscquery_server_protocol>( m_context, osc_port, ws_port), - "my_device"} + name} { create_ports(); } @@ -50,7 +51,7 @@ struct oscquery_mapper requires(!avnd::enum_parameter<Field>) void setup_control(Field& field, ossia::net::parameter_base& param) { - param.set_value_type(type_for_arg<decltype(Field::value)>()); + param.set_value_type(oscr::type_for_arg<decltype(Field::value)>()); // Set-up the metadata if constexpr(avnd::parameter_with_minmax_range<Field>) @@ -63,8 +64,10 @@ struct oscquery_mapper param.set_access(ossia::access_mode::BI); // Set-up the external callback - param.add_callback([&field](const ossia::value& val) { - field.value = convert(val, tag<decltype(field.value)>{}); + param.add_callback([object = &object, &field](const ossia::value& val) { + oscr::from_ossia_value(field, val, field.value); + + if_possible(field.update(object.effect)); }); } @@ -82,32 +85,9 @@ struct oscquery_mapper param.set_access(ossia::access_mode::BI); // Set-up the external callback - - param.add_callback([&field](const ossia::value& val) { - if(const int* iindex = val.target<int>()) - { - if(*iindex >= 0 && *iindex < choices_count) - { - field.value = static_cast<decltype(field.value)>(*iindex); - } - } - else if(const float* findex = val.target<float>()) - { - int index = *findex; - if(index >= 0 && index < choices_count) - { - field.value = static_cast<decltype(field.value)>(index); - } - } - else if(const std::string* txt = val.target<std::string>()) - { - auto it = std::find(choices.begin(), choices.end(), *txt); - if(it != choices.end()) - { - int index = std::distance(choices.begin(), it); - field.value = static_cast<decltype(field.value)>(index); - } - } + param.add_callback([object = &object, &field](const ossia::value& val) { + oscr::from_ossia_value(field, val, field.value); + if_possible(field.update(object.effect)); }); } @@ -115,9 +95,7 @@ struct oscquery_mapper void create_control(Field& field) { ossia::net::node_base& node = m_dev.get_root_node(); - std::string name = "input"; - if constexpr(requires { Field::name(); }) - name = Field::name(); + std::string name{avnd::get_path(field)}; if(auto param = ossia::net::create_parameter<ossia::net::generic_parameter>(node, name)) @@ -158,7 +136,9 @@ struct oscquery_mapper template <typename Arg> static Arg convert(const ossia::value& atom, tag<Arg>) { - return ossia::convert<Arg>(atom); + Arg a; + oscr::from_ossia_value(atom, a); + return a; } static std::string convert(const ossia::value& atom, tag<const char*>) @@ -253,6 +233,10 @@ struct oscquery_mapper { f(object.effect, ossia::convert<std::string>(in)); } + else if constexpr(std::is_same_v<arg_t, std::string_view>) + { + f(object.effect, ossia::convert<std::string>(in)); + } else if constexpr(std::is_same_v<arg_t, std::array<float, 2>>) { f(object.effect, ossia::convert<std::array<float, 2>>(in)); @@ -350,44 +334,6 @@ struct oscquery_mapper } } - template <typename arg_t> - static constexpr ossia::val_type type_for_arg() - { - if constexpr(std::floating_point<arg_t>) - { - return ossia::val_type::FLOAT; - } - else if constexpr(std::integral<arg_t>) - { - return ossia::val_type::INT; - } - else if constexpr(std::is_same_v<arg_t, bool>) - { - return ossia::val_type::BOOL; - } - else if constexpr(std::is_same_v<arg_t, const char*>) - { - return ossia::val_type::STRING; - } - else if constexpr(std::is_same_v<arg_t, std::string>) - { - return ossia::val_type::STRING; - } - else if constexpr(std::is_same_v<arg_t, std::array<float, 2>>) - { - return ossia::val_type::VEC2F; - } - else if constexpr(std::is_same_v<arg_t, std::array<float, 3>>) - { - return ossia::val_type::VEC3F; - } - else if constexpr(std::is_same_v<arg_t, std::array<float, 4>>) - { - return ossia::val_type::VEC4F; - } - return ossia::val_type::IMPULSE; - } - template <typename... Args> void init_message_arguments( ossia::net::parameter_base& param, boost::mp11::mp_list<Args...>) @@ -398,7 +344,7 @@ struct oscquery_mapper } else if constexpr(sizeof...(Args) == 1) { - param.set_value_type(type_for_arg<Args...>()); + param.set_value_type(oscr::type_for_arg<Args...>()); } else { @@ -406,10 +352,11 @@ struct oscquery_mapper init.reserve(sizeof...(Args)); param.set_value_type(ossia::val_type::LIST); - (init.push_back(ossia::init_value(type_for_arg<Args>())), ...); + (init.push_back(ossia::init_value(oscr::type_for_arg<Args>())), ...); param.set_value(std::move(init)); } } + template <typename... Args> void init_message_arguments( ossia::net::parameter_base& param, boost::mp11::mp_list<T&, Args...>) @@ -439,7 +386,7 @@ struct oscquery_mapper if constexpr(requires { avnd::function_reflection<Field::func()>::count; }) { ossia::net::node_base& node = m_dev.get_root_node(); - std::string name{Field::name()}; + std::string name{avnd::get_path(field)}; if(auto param = ossia::net::create_parameter<ossia::net::generic_parameter>( node, name)) // TODO { @@ -467,14 +414,12 @@ struct oscquery_mapper void create_output(Field& field) { ossia::net::node_base& node = m_dev.get_root_node(); - std::string name = "output"; - if constexpr(requires { Field::name(); }) - name = Field::name(); + std::string name{avnd::get_path(field)}; if(auto param = ossia::net::create_parameter<ossia::net::generic_parameter>(node, name)) { - param->set_value_type(type_for_arg<decltype(Field::value)>()); + param->set_value_type(oscr::type_for_arg<decltype(Field::value)>()); if constexpr(avnd::has_range<Field>) { constexpr auto ctl = avnd::get_range<Field>(); @@ -485,51 +430,32 @@ struct oscquery_mapper } } - template <typename Field> - void create_control(Field& field) - { - } - - template <typename Field> - void create_output(Field& field) - { - } - void create_ports() { - /* - if constexpr (avnd::float_parameter_input_introspection<T>::size > 0) - { - avnd::for_each_field_ref( - object.inputs(), + if constexpr(avnd::has_inputs<T>) + avnd::parameter_input_introspection<T>::for_all( + avnd::get_inputs<T>(object), [this]<typename Field>(Field& f) { create_control(f); }); - } - if constexpr (avnd::float_parameter_output_introspection<T>::size > 0) - { - avnd::for_each_field_ref( - object.outputs(), + if constexpr(avnd::has_outputs<T>) + avnd::parameter_output_introspection<T>::for_all( + avnd::get_outputs<T>(object), [this]<typename Field>(Field& f) { create_output(f); }); - } -*/ + if constexpr(avnd::has_messages<T>) - { - avnd::for_each_field_ref( - avnd::get_messages(object), + avnd::messages_introspection<T>::for_all( + avnd::get_messages<T>(object), [&]<typename Field>(Field& f) { create_message(f); }); - } - - /* - std::vector<ossia::net::parameter_base*> my_params; - for(int i = 0; i < 10; i++) - { - auto& node = find_or_create_node(m_dev, "/tes t/ fo o." + std::to_string(i)); - auto param = node.create_parameter(ossia::val_type::FLOAT); - param->push_value(0.1 + 0.01 * i); - my_params.push_back(param); - } - */ + // FIXME: for callbacks we must not overwrite the callback already set + // by the binding. + // Same thing for the UI. + // -> in the end, we need to have some multiplexing layer for when we have more than 1 binding + // + // if constexpr(avnd::has_outputs<T>) + // avnd::callback_output_introspection<T>::for_all( + // avnd::get_outputs<T>(object), + // [&]<typename Field>(Field& f) { create_callback(f); }); } void run() { m_context->run(); } diff --git a/include/avnd/binding/standalone/prototype.cpp.in b/include/avnd/binding/standalone/prototype.cpp.in index c21a5fa5..ff18d55f 100644 --- a/include/avnd/binding/standalone/prototype.cpp.in +++ b/include/avnd/binding/standalone/prototype.cpp.in @@ -22,18 +22,18 @@ void run_ui(auto& object) #if AVND_STANDALONE_NKL if constexpr(avnd::has_ui<type>) { - nkl::layout_ui< type > ui{object}; + nkl::layout_ui<type> ui{object}; ui.render(); } #elif AVND_STANDALONE_QML if constexpr(avnd::has_ui<type>) { - qml::qml_layout_ui< type > ui{object}; + qml::qml_layout_ui<type> ui{object}; qApp->exec(); } else { - qml::qml_ui< type > ui{object}; + qml::qml_ui<type> ui{object}; qApp->exec(); } #endif @@ -48,6 +48,7 @@ int main(int argc, char** argv) int osc_port = 1234; int ws_port = 5678; + const auto app_name = std::string(avnd::get_name<type>()); #if AVND_STANDALONE_QML qputenv("QML_DISABLE_DISTANCEFIELD", "1"); qputenv("QT_SCALE_FACTOR", "1"); @@ -55,8 +56,7 @@ int main(int argc, char** argv) qputenv("QT_QUICK_CONTROLS_STYLE", "Material"); qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "Dark"); - QCoreApplication::setApplicationName( - QString::fromStdString(std::string(avnd::get_name<type>()))); + QCoreApplication::setApplicationName(QString::fromStdString(app_name)); QCoreApplication::setApplicationVersion( QString::fromStdString(std::string(avnd::get_version<type>()))); QCoreApplication::setOrganizationName( @@ -119,18 +119,20 @@ int main(int argc, char** argv) avnd::init_controls(object); // Create an audio processor -#if __has_include(<ossia/detail/config.hpp>) +#if AVND_STANDALONE_AUDIO standalone::audio_mapper<type> audio{ object, in_channels, out_channels, buffer_size, sample_rate}; +#endif +#if AVND_STANDALONE_OSCQUERY // Create an oscquery interface to it. - standalone::oscquery_mapper<type> oscq{object, osc_port, ws_port}; + standalone::oscquery_mapper<type> oscq{object, app_name, osc_port, ws_port}; std::thread t{[&] { oscq.run(); }}; #endif run_ui(object); -#if __has_include(<ossia/detail/config.hpp>) +#if AVND_STANDALONE_OSCQUERY oscq.stop(); t.join(); #endif diff --git a/include/avnd/binding/standalone/standalone.hpp b/include/avnd/binding/standalone/standalone.hpp index c90517d5..a9749442 100644 --- a/include/avnd/binding/standalone/standalone.hpp +++ b/include/avnd/binding/standalone/standalone.hpp @@ -2,13 +2,15 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#if __has_include(<ossia/detail/config.hpp>) +#if __has_include(<ossia/audio/audio_device.hpp>) #include <avnd/binding/standalone/audio.hpp> +#define AVND_STANDALONE_AUDIO 1 +#endif + #if __has_include(<ossia/protocols/oscquery/oscquery_server_asio.hpp>) #define AVND_STANDALONE_OSCQUERY 1 #include <avnd/binding/standalone/oscquery_mapper.hpp> #endif -#endif #if __has_include(<QQuickView>) && __has_include(<verdigris>) #define AVND_STANDALONE_QML 1