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