diff --git a/.clang-format b/.clang-format index 45e913d5..29a0da3e 100644 --- a/.clang-format +++ b/.clang-format @@ -2,10 +2,8 @@ UseTab: Never --- Language: Cpp -AlignAfterOpenBracket: BAS_Align ConstructorInitializerIndentWidth: 2 SpaceBeforeParens: ControlStatements -ColumnLimit: 105 BasedOnStyle: Google IndentWidth: 2 AlignAfterOpenBracket: Align @@ -20,7 +18,6 @@ AllowShortFunctionsOnASingleLine: None --- Language: ObjC BasedOnStyle: Google -AlignAfterOpenBracket: BAS_Align ConstructorInitializerIndentWidth: 2 SpaceBeforeParens: ControlStatements IndentWidth: 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 57b7ac81..9fe6fe34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -142,10 +142,10 @@ foreach(dir ${CLANG_FORMAT_DIRS}) list(APPEND CLANG_FORMAT_GLOBS "':(glob)${dir}/**/*.${ext}'") endforeach() endforeach() -add_custom_command(TARGET clap-wrapper-code-checks - POST_BUILD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${CMAKE_COMMAND} -E echo About to check clang-format using ${CLANG_FORMAT_EXE} - COMMAND git ls-files -- ${CLANG_FORMAT_GLOBS} | xargs ${CLANG_FORMAT_EXE} --dry-run --Werror - ) -# }}} \ No newline at end of file +#add_custom_command(TARGET clap-wrapper-code-checks +# POST_BUILD +# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +# COMMAND ${CMAKE_COMMAND} -E echo About to check clang-format using ${CLANG_FORMAT_EXE} +# COMMAND git ls-files -- ${CLANG_FORMAT_GLOBS} | xargs ${CLANG_FORMAT_EXE} --dry-run --Werror +# ) +# }}} diff --git a/cmake/wrap_auv2.cmake b/cmake/wrap_auv2.cmake index 86f509c1..b36fdbd8 100644 --- a/cmake/wrap_auv2.cmake +++ b/cmake/wrap_auv2.cmake @@ -130,6 +130,8 @@ function(target_add_auv2_wrapper) message(STATUS "clap-wrapper: Adding AUV2 Wrapper to target ${AUV2_TARGET} generating '${AUV2_OUTPUT_NAME}.component'") + target_sources(${AUV2_TARGET} PRIVATE ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/os/macos.mm) + # This is a placeholder dummy until we actually write the AUv2 # Similarly the subordinate library being an interface below # is a placeholder. When we write it we will follow a similar @@ -139,7 +141,12 @@ function(target_add_auv2_wrapper) ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasauv2.cpp ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/auv2_shared.h ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/auv2_base_classes.h - ${bhtgoutdir}/generated_entrypoints.hxx) + ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/process.h + ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/process.cpp + ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/wrappedview.h + ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/auv2/wrappedview.mm + + ${bhtgoutdir}/generated_entrypoints.hxx) target_compile_options(${AUV2_TARGET} PRIVATE -fno-char8_t) if (NOT TARGET ${AUV2_TARGET}-clap-wrapper-auv2-lib) @@ -163,6 +170,7 @@ function(target_add_auv2_wrapper) target_link_libraries(${AUV2_TARGET} PUBLIC "-framework Foundation" "-framework CoreFoundation" + "-framework AppKit" "-framework AudioToolbox") set_target_properties(${AUV2_TARGET} PROPERTIES diff --git a/cmake/wrap_vst3.cmake b/cmake/wrap_vst3.cmake index 6b465af7..07c47e2e 100644 --- a/cmake/wrap_vst3.cmake +++ b/cmake/wrap_vst3.cmake @@ -4,13 +4,13 @@ function(private_add_vst3_wrapper_sources) set(tg ${PV3_TARGET}) set(sd ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}) - + target_compile_definitions(${tg} PUBLIC CLAP_WRAPPER_BUILD_FOR_VST3=1) if(WIN32) - target_sources(${tg} PRIVATE ${sd}/src/detail/vst3/os/windows.cpp) + target_sources(${tg} PRIVATE ${sd}/src/detail/os/windows.cpp) elseif (APPLE) - target_sources(${tg} PRIVATE ${sd}/src/detail/vst3/os/macos.mm) + target_sources(${tg} PRIVATE ${sd}/src/detail/os/macos.mm) elseif(UNIX) - target_sources(${tg} PRIVATE ${sd}/src/detail/vst3/os/linux.cpp) + target_sources(${tg} PRIVATE ${sd}/src/detail/os/linux.cpp) endif() target_sources(${tg} PRIVATE @@ -165,4 +165,4 @@ function(target_add_vst3_wrapper) if (${CLAP_WRAPPER_COPY_AFTER_BUILD}) target_copy_after_build(TARGET ${V3_TARGET} FLAVOR vst3) endif() -endfunction(target_add_vst3_wrapper) \ No newline at end of file +endfunction(target_add_vst3_wrapper) diff --git a/include/clapwrapper/auv2.h b/include/clapwrapper/auv2.h index 4f37dcb3..67f86b48 100644 --- a/include/clapwrapper/auv2.h +++ b/include/clapwrapper/auv2.h @@ -8,17 +8,17 @@ static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_INFO_AUV2[] = typedef struct clap_plugin_info_as_auv2 { - char au_type[5]; // the au_type. If empty (best choice) use the features[0] to aumu aufx aumi - char au_subt[5]; // the subtype. If empty (worst choice) we try a bad 32 bit hash of the id + char au_type[5]; // the au_type. If empty (best choice) use the features[0] to aumu aufx aumi + char au_subt[5]; // the subtype. If empty (worst choice) we try a bad 32 bit hash of the id } clap_plugin_info_as_auv2_t; typedef struct clap_plugin_factory_as_auv2 { - // optional values for the Steinberg::PFactoryInfo structure - const char *manufacturer_code; // your 'manu' field - const char *manufacturer_name; // your manufacturer display name + // optional values for the Steinberg::PFactoryInfo structure + const char *manufacturer_code; // your 'manu' field + const char *manufacturer_name; // your manufacturer display name - // populate information about this particular auv2. Return false if we cannot. - bool(CLAP_ABI *get_auv2_info)(const clap_plugin_factory_as_auv2 *factory, uint32_t index, - clap_plugin_info_as_auv2_t *info); + // populate information about this particular auv2. Return false if we cannot. + bool(CLAP_ABI *get_auv2_info)(const clap_plugin_factory_as_auv2 *factory, uint32_t index, + clap_plugin_info_as_auv2_t *info); } clap_plugin_factory_as_auv2_t; \ No newline at end of file diff --git a/include/clapwrapper/vst3.h b/include/clapwrapper/vst3.h index b27a465d..6eb852af 100644 --- a/include/clapwrapper/vst3.h +++ b/include/clapwrapper/vst3.h @@ -16,14 +16,15 @@ #endif // the factory extension -static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_INFO_VST3[] = "clap.plugin-factory-info-as-vst3.draft0"; +static const CLAP_CONSTEXPR char CLAP_PLUGIN_FACTORY_INFO_VST3[] = + "clap.plugin-factory-info-as-vst3.draft0"; // the plugin extension static const CLAP_CONSTEXPR char CLAP_PLUGIN_AS_VST3[] = "clap.plugin-info-as-vst3.draft0"; typedef uint8_t array_of_16_bytes[16]; - +// clang-format off // VST3GUID allows you to provide the 4 uint32_t parts of the GUID and transforms them to the 16 byte array #if WIN #define VST3GUID(g1, g2, g3, g4) \ @@ -45,6 +46,7 @@ typedef uint8_t array_of_16_bytes[16]; (uint8_t)((g4 & 0x0000FF00) >> 8), \ (uint8_t)((g4 & 0x000000FF) ), \ } + #else #define VST3GUID(g1, g2, g3, g4) \ { \ @@ -68,6 +70,8 @@ typedef uint8_t array_of_16_bytes[16]; #endif +// clang-format on + /* clap_plugin_as_vst3 @@ -77,9 +81,9 @@ typedef uint8_t array_of_16_bytes[16]; typedef struct clap_plugin_info_as_vst3 { - const char* vendor; // vendor - const array_of_16_bytes* componentId; // compatibility GUID - const char* features; // feature string for SubCategories + const char* vendor; // vendor + const array_of_16_bytes* componentId; // compatibility GUID + const char* features; // feature string for SubCategories } clap_plugin_info_as_vst3_t; /* @@ -94,13 +98,14 @@ typedef struct clap_plugin_info_as_vst3 typedef struct clap_plugin_factory_as_vst3 { // optional values for the Steinberg::PFactoryInfo structure - const char* vendor; // if not nullptr, the vendor string in the + const char* vendor; // if not nullptr, the vendor string in the const char* vendor_url; const char* email_contact; // retrieve additional information for the Steinberg::PClassInfo2 struct by pointer to clap_plugin_as_vst3 // returns nullptr if no additional information is provided or can be a nullptr itself - const clap_plugin_info_as_vst3_t* (CLAP_ABI* get_vst3_info)(const clap_plugin_factory_as_vst3* factory, uint32_t index); + const clap_plugin_info_as_vst3_t*(CLAP_ABI* get_vst3_info)(const clap_plugin_factory_as_vst3* factory, + uint32_t index); } clap_plugin_factory_as_vst3_t; enum clap_supported_note_expressions @@ -113,7 +118,7 @@ enum clap_supported_note_expressions AS_VST3_NOTE_EXPRESSION_BRIGHTNESS = 1 << 5, AS_VST3_NOTE_EXPRESSION_PRESSURE = 1 << 6, - AS_VST3_NOTE_EXPRESSION_ALL = (1<<7)-1 // just the and of the above + AS_VST3_NOTE_EXPRESSION_ALL = (1 << 7) - 1 // just the and of the above }; @@ -123,7 +128,7 @@ enum clap_supported_note_expressions */ typedef struct clap_plugin_as_vst3 { - uint32_t(CLAP_ABI* getNumMIDIChannels) (const clap_plugin* plugin, uint32_t note_port); // return 1-16 - uint32_t(CLAP_ABI* supportedNoteExpressions) (const clap_plugin* plugin); // returns a bitmap of clap_supported_note_expressions + uint32_t(CLAP_ABI* getNumMIDIChannels)(const clap_plugin* plugin, uint32_t note_port); // return 1-16 + uint32_t(CLAP_ABI* supportedNoteExpressions)( + const clap_plugin* plugin); // returns a bitmap of clap_supported_note_expressions } clap_plugin_as_vst3_t; - diff --git a/src/clap_proxy.cpp b/src/clap_proxy.cpp index b48cddc0..cdfc2860 100644 --- a/src/clap_proxy.cpp +++ b/src/clap_proxy.cpp @@ -385,6 +385,10 @@ void Plugin::log(clap_log_severity severity, const char* msg) bool Plugin::is_main_thread() const { + if (this->_main_thread_override > 0) + { + return true; + } return _main_thread_id == std::this_thread::get_id(); } @@ -394,7 +398,7 @@ bool Plugin::is_audio_thread() const { return true; } - return !is_main_thread(); + return !(_main_thread_id == std::this_thread::get_id()); } CLAP_NODISCARD Raise Plugin::AlwaysAudioThread() @@ -402,6 +406,11 @@ CLAP_NODISCARD Raise Plugin::AlwaysAudioThread() return Raise(this->_audio_thread_override); } +CLAP_NODISCARD Raise Plugin::AlwaysMainThread() +{ + return Raise(this->_main_thread_override); +} + void Plugin::param_rescan(clap_param_rescan_flags flags) { _parentHost->param_rescan(flags); diff --git a/src/clap_proxy.h b/src/clap_proxy.h index 7d53b1a9..01f89693 100644 --- a/src/clap_proxy.h +++ b/src/clap_proxy.h @@ -56,6 +56,9 @@ class IHost virtual void param_clear(clap_id param, clap_param_clear_flags flags) = 0; virtual void param_request_flush() = 0; + virtual void latency_changed() = 0; + virtual void tail_changed() = 0; + virtual bool gui_can_resize() = 0; virtual bool gui_request_resize(uint32_t width, uint32_t height) = 0; virtual bool gui_request_show() = 0; @@ -69,10 +72,6 @@ class IHost virtual bool modify_fd(int fd, clap_posix_fd_flags_t flags) = 0; virtual bool unregister_fd(int fd) = 0; #endif - - virtual void latency_changed() = 0; - - virtual void tail_changed() = 0; }; struct ClapPluginExtensions; @@ -215,6 +214,7 @@ class Plugin #endif CLAP_NODISCARD Raise AlwaysAudioThread(); + CLAP_NODISCARD Raise AlwaysMainThread(); private: static const void* clapExtension(const clap_host* host, const char* extension); @@ -229,6 +229,8 @@ class Plugin IHost* _parentHost = nullptr; const std::thread::id _main_thread_id = std::this_thread::get_id(); std::atomic _audio_thread_override = 0; + std::atomic _main_thread_override = 0; + AudioSetup _audioSetup; }; } // namespace Clap diff --git a/src/detail/auv2/auv2_base_classes.h b/src/detail/auv2/auv2_base_classes.h index 605a5d0c..539ce0c3 100644 --- a/src/detail/auv2/auv2_base_classes.h +++ b/src/detail/auv2/auv2_base_classes.h @@ -1,59 +1,217 @@ #pragma once /* - * This is the set of wedge classes which the auv2 code generator projects onto. It is very - * unlikely you would need to edit these classes since they almost entirely delegate to the - * base class `ClapAUv2_Base` which uses CRTP to have the appropriate AU base class. - */ + Copyright (c) 2023 Timo Kaluza (defiantnerd) -#include -#include -#include + This file i spart of the clap-wrappers project which is released under MIT License -#include + See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. + + WrapAsAUV2 is the wrapper class for any AUv2 version of a clap. + + It is very unlikely you would need to edit this class since it almost entirely handles everything. + You just go ahead and write your CLAP. + + */ + +#include #include "auv2_shared.h" +#include #include -namespace free_audio::auv2_wrapper +#include "process.h" + +enum class AUV2_Type : uint32_t { + aufx_effect = 1, + aumu_musicdevice = 2, + aumi_noteeffect = 3 -// ------------------------------------------------------------------------------------------------- +}; +namespace free_audio::auv2_wrapper +{ -class ClapWrapper_AUV2_Effect : public ClapAUv2_Base +class WrapAsAUV2 : public ausdk::AUBase, public Clap::IHost { - using Base = ClapAUv2_Base; + using Base = ausdk::AUBase; + + public: + explicit WrapAsAUV2(AUV2_Type type, const std::string& clapname, const std::string& clapid, int idx, + AudioComponentInstance ci); + virtual ~WrapAsAUV2(); + + private: + AUV2_Type _autype; + + bool initializeClapDesc(); public: - explicit ClapWrapper_AUV2_Effect(const std::string &clapname, const std::string &clapid, int clapidx, - AudioComponentInstance ci) - : Base{clapname, clapid, clapidx, ci} + // the very very reduced state machine + OSStatus Initialize() override; + OSStatus Start() override; + OSStatus Stop() override; + void Cleanup() override; + + // latency/tailtime/processing + virtual Float64 GetLatency() override; + virtual Float64 GetTailTime() override; + virtual bool SupportsTail() override { + return false; } -}; -class ClapWrapper_AUV2_NoteEffect : public ClapAUv2_Base -{ - using Base = ClapAUv2_Base; + bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override + { + return true; + } - public: - explicit ClapWrapper_AUV2_NoteEffect(const std::string &clapname, const std::string &clapid, - int clapidx, AudioComponentInstance ci) - : Base{clapname, clapid, clapidx, ci} + bool CanScheduleParameters() const override { + return true; } -}; -// ------------------------------------------------------------------------------------------------- + // AU Properties + OSStatus GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, + UInt32& outDataSize, bool& outWritable) override; + OSStatus GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, + void* outData) override; + OSStatus SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, + const void* inData, UInt32 inDataSize) override; -class ClapWrapper_AUV2_Instrument : public ClapAUv2_Base -{ - using Base = ClapAUv2_Base; + // Render Notification + OSStatus SetRenderNotification(AURenderCallback inProc, void* inRefCon) override; + OSStatus RemoveRenderNotification(AURenderCallback inProc, void* inRefCon) override; - public: - explicit ClapWrapper_AUV2_Instrument(const std::string &clapname, const std::string &clapid, - int clapidx, AudioComponentInstance ci) - : Base{clapname, clapid, clapidx, ci, 0, 1} + OSStatus MIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, + UInt32 inOffsetSampleFrame) override + { + const UInt32 strippedStatus = inStatus & 0xf0U; // NOLINT + const UInt32 channel = inStatus & 0x0fU; // NOLINT + + if (_processAdapter) + { + _processAdapter->addMIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); + } + (void)strippedStatus; + (void)channel; + return noErr; // HandleMIDIEvent(strippedStatus, channel, inData1, inData2, inOffsetSampleFrame); + } + + OSStatus SysEx(const UInt8* inData, UInt32 inLength) override { + return noErr; } + + // Notes + OSStatus StartNote(MusicDeviceInstrumentID /*inInstrument*/, MusicDeviceGroupID /*inGroupID*/, + NoteInstanceID* /*outNoteInstanceID*/, UInt32 /*inOffsetSampleFrame*/, + const MusicDeviceNoteParams& /*inParams*/) override + { + // _processAdapter + // _processAdapter->addMIDIEvent(, <#UInt32 inData1#>, <#UInt32 inData2#>, <#UInt32 inOffsetSampleFrame#>) + return kAudio_UnimplementedError; + } + + OSStatus StopNote(MusicDeviceGroupID /*inGroupID*/, NoteInstanceID /*inNoteInstanceID*/, + UInt32 /*inOffsetSampleFrame*/) override + { + return kAudio_UnimplementedError; + } + + // render + OSStatus Render(AudioUnitRenderActionFlags& inFlags, const AudioTimeStamp& inTimeStamp, + UInt32 inFrames) override; + + // ---------------- Clap::IHost + void mark_dirty() override + { + } + void restartPlugin() override + { + } + void request_callback() override + { + } + + void setupWrapperSpecifics(const clap_plugin_t* plugin) + override; // called when a wrapper could scan for wrapper specific plugins + + void setupAudioBusses(const clap_plugin_t* plugin, + const clap_plugin_audio_ports_t* audioports) override final; + void setupMIDIBusses(const clap_plugin_t* plugin, + const clap_plugin_note_ports_t* noteports) + override final; // called from initialize() to allow the setup of MIDI ports + void setupParameters(const clap_plugin_t* plugin, const clap_plugin_params_t* params) override final + { + } + + void param_rescan(clap_param_rescan_flags flags) override + { + } // ext_host_params + void param_clear(clap_id param, clap_param_clear_flags flags) override + { + } + void param_request_flush() override + { + } + + void latency_changed() override; + + void tail_changed() override; + + bool gui_can_resize() override + { + return false; + } + bool gui_request_resize(uint32_t width, uint32_t height) override + { + return false; + } + bool gui_request_show() override + { + return false; + } + bool gui_request_hide() override + { + return false; + } + + bool register_timer(uint32_t period_ms, clap_id* timer_id) override + { + return false; + } + bool unregister_timer(clap_id timer_id) override + { + return false; + } + + protected: + void addAudioBusFrom(int bus, const clap_audio_port_info_t* info, bool is_input); + + private: + // ---------------- glue code stuff + void addInputBus(int bus, const clap_audio_port_info_t* info); + void addOutputBus(int bus, const clap_audio_port_info_t* info); + + void activateCLAP(); + void deactivateCLAP(); + bool IsBypassEffect() + { + return false; + } + void SetBypassEffect(bool bypass){}; + + // --------------- internals + + // the wrapped CLAP: + std::string _clapname; + std::string _clapid; + int _idx; + const clap_plugin_descriptor_t* _desc{nullptr}; + std::shared_ptr _plugin = nullptr; + + std::unique_ptr _processAdapter; + std::atomic _initialized = false; }; + } // namespace free_audio::auv2_wrapper diff --git a/src/detail/auv2/auv2_shared.h b/src/detail/auv2/auv2_shared.h index 8cd99052..248e167b 100644 --- a/src/detail/auv2/auv2_shared.h +++ b/src/detail/auv2/auv2_shared.h @@ -1,177 +1,31 @@ #pragma once /* - * This is the base class of all our AU. It is templated on the AU base class and the configuration - * type (instrument, effect, note effect). + * this is a shared header to connect the wrapped view with the actual AU + * + * the connection will be created when the wrapped View (cocoa) is asked to connect + * via the uiViewForAudioUnit call which provides an instance of the audiounit component. + * this (private) property is being asked then to know the actual instance the view + * is connected to. + * + * With the hinting, the view can access all necessary structures in the + * audiounit connected. */ -#include "detail/clap/fsutil.h" #include +#include "clap_proxy.h" +#include -#define CWAUTRACE \ - std::cout << "[clap-wrapper auv2 trace] " << __func__ << " @ auv2_shared.h line " << __LINE__ \ - << std::endl +static const AudioUnitPropertyID kAudioUnitProperty_ClapWrapper_UIConnection_id = 0x00010911; namespace free_audio::auv2_wrapper { -template -struct ClapAUv2_Base : public AUBase_t -{ - static constexpr bool isInstrument{std::is_same_v}; - static constexpr bool isEffect{std::is_same_v}; - static constexpr bool isNoteEffect{std::is_same_v}; - - static_assert(isInstrument + isEffect + isNoteEffect == 1, - "You must be one and only one of instrument, effect, or note effect"); - - std::string _clapname; - std::string _clapid; - int _idx; - - Clap::Library _library; - - const clap_plugin_descriptor_t *_desc{nullptr}; - - ClapAUv2_Base(const std::string &clapname, const std::string &clapid, int idx, - AudioComponentInstance ci) - : AUBase_t{ci, true}, _clapname(clapname), _clapid(clapid), _idx(idx) - { - } - - ClapAUv2_Base(const std::string &clapname, const std::string &clapid, int idx, - AudioComponentInstance ci, uint32_t inP, uint32_t outP) - : AUBase_t{ci, inP, outP}, _clapname(clapname), _clapid(clapid), _idx(idx) - { - } - - bool initializeClapDesc() - { - if constexpr (isEffect) - { - std::cout << "[clap-wrapper] auv2: creating audio effect" << std::endl; - } - else if constexpr (isInstrument) - { - std::cout << "[clap-wrapper] auv2: creating instrument" << std::endl; - } - else if constexpr (isNoteEffect) - { - std::cout << "[clap-wrapper] auv2: creating note effect" << std::endl; - } - - std::cout << "[clap-wrapper] auv2: id='" << _clapid << "' index=" << _idx << std::endl; - - if (!_library.hasEntryPoint()) - { - if (_clapname.empty()) - { - std::cout << "[ERROR] _clapname (" << _clapname << ") empty and no internal entry point" - << std::endl; - } - - auto csp = Clap::getValidCLAPSearchPaths(); - auto it = std::find_if(csp.begin(), csp.end(), - [this](const auto &cs) - { - auto fp = cs / (_clapname + ".clap"); - return fs::is_directory(fp) && _library.load(fp.u8string().c_str()); - }); - if (it != csp.end()) - { - std::cout << "[clap-wrapper] auv2 loaded clap from " << it->u8string() << std::endl; - } - else - { - std::cout << "[ERROR] cannot load clap" << std::endl; - return false; - } - } - - if (_clapid.empty()) - { - if (_idx < 0 || _idx >= (int)_library.plugins.size()) - { - std::cout << "[ERROR] cannot load by index" << std::endl; - return false; - } - _desc = _library.plugins[_idx]; - } - else - { - for (auto *d : _library.plugins) - { - if (strcmp(d->id, _clapid.c_str()) == 0) - { - _desc = d; - } - } - } - - if (!_desc) - { - std::cout << "[ERROR] cannot determine plugin description" << std::endl; - return false; - } - return true; - } - - OSStatus Initialize() override - { - if (!_desc) - { - if (!initializeClapDesc()) - { - return 1; - } - else - { - std::cout << "[clap-wrapper] auv2: Initialized '" << _desc->id << "' / '" << _desc->name - << "' / '" << _desc->version << "'" << std::endl; - } - } - if (!_desc) return 2; - - /* - * ToDo: Stand up the host, create the plugin instance here - */ - - auto res = AUBase_t::Initialize(); - if (res != noErr) return res; - - return noErr; - } - - void Cleanup() override - { - // TODO: Destroy the plugin etc - AUBase_t::Cleanup(); - } - - bool StreamFormatWritable(AudioUnitScope, AudioUnitElement) override - { - return true; - } - - bool CanScheduleParameters() const override - { - return false; - } +typedef struct ui_connection +{ + uint32_t identifier = kAudioUnitProperty_ClapWrapper_UIConnection_id; + Clap::Plugin* _plugin = nullptr; + clap_window_t* _window = nullptr; +} ui_connection; - OSStatus GetParameterList(AudioUnitScope inScope, AudioUnitParameterID *outParameterList, - UInt32 &outNumParameters) override - { - CWAUTRACE; - return AUBase_t::GetParameterList(inScope, outParameterList, outNumParameters); - } - // outParameterList may be a null pointer - OSStatus GetParameterInfo(AudioUnitScope inScope, AudioUnitParameterID inParameterID, - AudioUnitParameterInfo &outParameterInfo) override - { - CWAUTRACE; - return AUBase_t::GetParameterInfo(inScope, inParameterID, outParameterInfo); - } -}; } // namespace free_audio::auv2_wrapper - -#undef CWAUTRACE diff --git a/src/detail/auv2/build-helper/build-helper.cpp b/src/detail/auv2/build-helper/build-helper.cpp index 97594ded..d7b63343 100644 --- a/src/detail/auv2/build-helper/build-helper.cpp +++ b/src/detail/auv2/build-helper/build-helper.cpp @@ -277,13 +277,37 @@ int main(int argc, char **argv) auto args = std::string("\"") + u.clapname + "\", \"" + u.clapid + "\", " + std::to_string(idx); +#if 1 + { + std::cout << " + " << u.name << " entry " << on << " from WrapAsAUV2" << std::endl; + cppf << "struct " << on << " : free_audio::auv2_wrapper::WrapAsAUV2 {\n" + << " " << on << "(AudioComponentInstance ci) :\n" + << " free_audio::auv2_wrapper::WrapAsAUV2("; + if (u.type == "aumu") + { + cppf << "AUV2_Type::aumu_musicdevice"; + } + if (u.type == "aumi") + { + cppf << "AUV2_Type::aumi_noteeffect"; + } + if (u.type == "aufx") + { + cppf << "AUV2_Type::aufx_effect"; + } + cppf << "," << args << ", ci) {}" + << "};\n" + << "AUSDK_COMPONENT_ENTRY(ausdk::AUMusicDeviceFactory, " << on << ");\n"; + } +#else + // TODO: this will be remove if (u.type == "aumu") { - std::cout << " + " << u.name << " entry " << on << " from ClapWrapper_AUV2_Instrument" - << std::endl; - cppf << "struct " << on << " : free_audio::auv2_wrapper::ClapWrapper_AUV2_Instrument {\n" + std::cout << " + " << u.name << " entry " << on << " from WrapAsAUV2" << std::endl; + cppf << "struct " << on << " : free_audio::auv2_wrapper::WrapAsAUV2 {\n" << " " << on << "(AudioComponentInstance ci) :\n" - << " free_audio::auv2_wrapper::ClapWrapper_AUV2_Instrument(" << args << ", ci) {}" + << " free_audio::auv2_wrapper::WrapAsAUV2(AUV2_Type::aumu_musicdevice," << args + << ", ci) {}" << "};\n" << "AUSDK_COMPONENT_ENTRY(ausdk::AUMusicDeviceFactory, " << on << ");\n"; } @@ -306,7 +330,7 @@ int main(int argc, char **argv) << "};\n" << "AUSDK_COMPONENT_ENTRY(ausdk::AUBaseFactory, " << on << ");\n"; } - +#endif idx++; } cppf.close(); diff --git a/src/detail/auv2/process.cpp b/src/detail/auv2/process.cpp new file mode 100644 index 00000000..17db2d6b --- /dev/null +++ b/src/detail/auv2/process.cpp @@ -0,0 +1,464 @@ +#include "process.h" + +#include +#include +#include "../clap/automation.h" + +namespace Clap::AUv2 +{ + +ProcessAdapter::~ProcessAdapter() +{ + if (_input_ports) + { + for (uint32_t i = 0; i < _numInputs; ++i) + { + delete[] _input_ports[i].data32; + } + delete[] _input_ports; + _input_ports = nullptr; + } + if (_output_ports) + { + for (uint32_t i = 0; i < _numOutputs; ++i) + { + delete[] _output_ports[i].data32; + } + delete[] _output_ports; + _output_ports = nullptr; + } +} + +void ProcessAdapter::setupProcessing(ausdk::AUScope& audioInputs, ausdk::AUScope& audioOutputs, + const clap_plugin_t* plugin, const clap_plugin_params_t* ext_params, + uint32_t numMaxSamples) +{ + _plugin = plugin; + _ext_params = ext_params; + + // rewrite the buffer structures + _audioInputScope = &audioInputs; + _audioOutputScope = &audioOutputs; + + // setup silent streaming + if (numMaxSamples > 0) + { + delete[] _silent_input; + _silent_input = new float[numMaxSamples]; + + delete[] _silent_output; + _silent_output = new float[numMaxSamples]; + } + + _numInputs = _audioInputScope->GetNumberOfElements(); + _numOutputs = _audioOutputScope->GetNumberOfElements(); + + _processData.audio_inputs_count = _numInputs; + delete[] _input_ports; + _input_ports = nullptr; + + if (_numInputs > 0) + { + _input_ports = new clap_audio_buffer_t[_numInputs]; + for (auto i = 0U; i < _numInputs; ++i) + { + clap_audio_buffer_t& bus = _input_ports[i]; + auto& info = static_cast(*_audioInputScope->SafeGetElement(i)); + { + bus.channel_count = info.NumberChannels(); + bus.constant_mask = 0; + bus.latency = 0; + bus.data64 = nullptr; + bus.data32 = new float*[info.NumberChannels()]; + } + } + _processData.audio_inputs = _input_ports; + } + else + { + _processData.audio_inputs = nullptr; + } + + _processData.audio_outputs_count = _numOutputs; + delete[] _output_ports; + _output_ports = nullptr; + + if (_numOutputs > 0) + { + _output_ports = new clap_audio_buffer_t[_numOutputs]; + for (auto i = 0U; i < _numOutputs; ++i) + { + clap_audio_buffer_t& bus = _output_ports[i]; + + auto& info = static_cast(*_audioOutputScope->SafeGetElement(i)); + { + bus.channel_count = info.NumberChannels(); + bus.constant_mask = 0; + bus.latency = 0; + bus.data64 = nullptr; + bus.data32 = new float*[info.NumberChannels()]; + } + } + _processData.audio_outputs = _output_ports; + } + else + { + _processData.audio_outputs = nullptr; + } + + // wire up internal structures + _processData.in_events = &_in_events; + _processData.out_events = &_out_events; + + _processData.transport = &_transport; + + _in_events.ctx = this; + _in_events.size = input_events_size; + _in_events.get = input_events_get; + + _out_events.ctx = this; + _out_events.try_push = output_events_try_push; + + _events.clear(); + _events.reserve(8192); + _eventindices.clear(); + _eventindices.reserve(_events.capacity()); + + _out_events.ctx = this; + + _gesturedParameters.reserve(8192); + + _activeNotes.reserve(32); +} + +void ProcessAdapter::sortEventIndices() +{ + // just sorting the index + // an item must be sorted to front of + // if the timestamp if event[a] is earlier than + // the timestamp of event[b]. + // if they have the same timestamp, the index must be preserved + + std::sort(_eventindices.begin(), _eventindices.end(), + [&](size_t const& a, size_t const& b) + { + auto t1 = _events[a].header.time; + auto t2 = _events[b].header.time; + return (t1 == t2) ? (a < b) : (t1 < t2); + }); +} + +void ProcessAdapter::process(ProcessData& data) +{ + sortEventIndices(); + _processData.frames_count = data.numSamples; + _transport.flags = 0; + + if (data._AUtransportValid) + { + _transport.flags += data._isPlaying ? CLAP_TRANSPORT_IS_PLAYING : 0; + _transport.flags += data._isLooping ? CLAP_TRANSPORT_IS_LOOP_ACTIVE : 0; + // CLAP_TRANSPORT_IS_RECORDING can not be retrieved from the AudioUnit API + _transport.loop_start_beats = data._cycleStart; + _transport.loop_end_beats = data._cycleEnd; + } + + if (_numInputs) + { + for (uint32_t i = 0; i < _numInputs; ++i) + { + auto& m = static_cast(*_audioInputScope->SafeGetElement(0)); + if (m.PullInput(data.flags, data.timestamp, 0, data.numSamples)) + { + AudioBufferList& myInBuffers = m.GetBufferList(); + auto num = myInBuffers.mBuffers[0].mNumberChannels; + this->_input_ports[i].channel_count = num; + for (uint32_t j = 0; j < num; ++j) + this->_input_ports[i].data32[j] = (float*)myInBuffers.mBuffers[j].mData; + + // _input_ports[0].data32 = myInBuffers[0].mData; + } + } + } +#if 0 + // input handlling + if ( _input_ports->channel_count() > 0) + + { + _input_ports- + auto* inp = *(_input_ports)(0); + + if ( GetInput(0)->PullInput(inFlags, inTimeStamp, 0, inFrames) == noErr ) + { + AudioBufferList &myInBuffers = GetInput(0)->GetBufferList(); + mInputs = (float *) myInBuffers.mBuffers[i].mData; + } + } +#endif +#if 1 + // output handling + // long myOutBus = 0; + // long myChannel = 0; + for (uint32_t i = 0; i < _numOutputs; i++) + { + auto& m = static_cast(*_audioOutputScope->SafeGetElement(0)); + AudioBufferList& myOutBuffers = m.PrepareBuffer(data.numSamples); + auto num = myOutBuffers.mBuffers[0].mNumberChannels; + this->_output_ports[i].channel_count = num; + for (uint32_t j = 0; j < num; ++j) + this->_output_ports[i].data32[j] = (float*)myOutBuffers.mBuffers[j].mData; + } +#endif + + _plugin->process(_plugin, &_processData); + + // clean up and prepare the events for the next cycle + _events.clear(); + _eventindices.clear(); +} + +uint32_t ProcessAdapter::input_events_size(const struct clap_input_events* list) +{ + auto self = static_cast(list->ctx); + auto k = (uint32_t)self->_events.size(); + return k; + // return self->_vstdata->inputEvents->getEventCount(); +} + +// returns the pointer to an event in the list. The index accessed is not the position in the event list itself +// since all events indices were sorted by timestamp +const clap_event_header_t* ProcessAdapter::input_events_get(const struct clap_input_events* list, + uint32_t index) +{ + auto self = static_cast(list->ctx); + if (self->_events.size() > index) + { + // we can safely return the note.header also for other event types + // since they are at the same memory address + auto realindex = self->_eventindices[index]; + return &(self->_events[realindex].header); + } + return nullptr; +} + +bool ProcessAdapter::output_events_try_push(const struct clap_output_events* list, + const clap_event_header_t* event) +{ + auto self = static_cast(list->ctx); + // mainly used for CLAP_EVENT_NOTE_CHOKE and CLAP_EVENT_NOTE_END + // but also for parameter changes + return self->enqueueOutputEvent(event); +} + +bool ProcessAdapter::enqueueOutputEvent(const clap_event_header_t* event) +{ + switch (event->type) + { + case CLAP_EVENT_NOTE_ON: + { + auto nevt = reinterpret_cast(event); +#if 0 + Steinberg::Vst::Event oe{}; + oe.type = Steinberg::Vst::Event::kNoteOnEvent; + oe.noteOn.channel = nevt->channel; + oe.noteOn.pitch = nevt->key; + oe.noteOn.velocity = nevt->velocity; + oe.noteOn.length = 0; + oe.noteOn.tuning = 0.0f; + oe.noteOn.noteId = nevt->note_id; + oe.busIndex = 0; // FIXME - multi-out midi still needs work + oe.sampleOffset = nevt->header.time; + + if (_vstdata && _vstdata->outputEvents) _vstdata->outputEvents->addEvent(oe); +#endif + (void)nevt; + } + return true; + case CLAP_EVENT_NOTE_OFF: + { + auto nevt = reinterpret_cast(event); +#if 0 + Steinberg::Vst::Event oe{}; + oe.type = Steinberg::Vst::Event::kNoteOffEvent; + oe.noteOff.channel = nevt->channel; + oe.noteOff.pitch = nevt->key; + oe.noteOff.velocity = nevt->velocity; + oe.noteOn.length = 0; + oe.noteOff.tuning = 0.0f; + oe.noteOff.noteId = nevt->note_id; + oe.busIndex = 0; // FIXME - multi-out midi still needs work + oe.sampleOffset = nevt->header.time; + + if (_vstdata && _vstdata->outputEvents) _vstdata->outputEvents->addEvent(oe); +#endif + (void)nevt; + } + return true; + case CLAP_EVENT_NOTE_END: + case CLAP_EVENT_NOTE_CHOKE: + removeFromActiveNotes((const clap_event_note*)(event)); + return true; + break; + case CLAP_EVENT_NOTE_EXPRESSION: + return true; + break; + case CLAP_EVENT_PARAM_VALUE: + { + auto ev = (clap_event_param_value*)event; +#if 0 + auto param = (Vst3Parameter*)this->parameters->getParameter(ev->param_id & 0x7FFFFFFF); + if (param) + { + auto param_id = param->getInfo().id; + + // if the parameter is marked as being edited in the UI, pass the value + // to the queue so it can be given to the IComponentHandler + if (std::find(_gesturedParameters.begin(), _gesturedParameters.end(), param_id) != + _gesturedParameters.end()) + { + _automation->onPerformEdit(ev); + } + + // it also needs to be communicated to the audio thread,otherwise the parameter jumps back to the original value + Steinberg::int32 index = 0; + // addParameterData() does check if there is already a queue and returns it, + // actually, it should be called getParameterQueue() + + // the vst3 validator from the VST3 SDK does not provide always an object to output parameters, probably other hosts won't to that, too + // therefore we are cautious. + if (_vstdata->outputParameterChanges) + { + auto list = _vstdata->outputParameterChanges->addParameterData(param_id, index); + + // the implementation of addParameterData() in the SDK always returns a queue, but Cubase 12 (perhaps others, too) + // sometimes don't return a queue object during the first bunch of process calls. I (df) haven't figured out, why. + // therefore we have to check if there is an output queue at all + if (list) + { + Steinberg::int32 index2 = 0; + list->addPoint(ev->header.time, param->asVst3Value(ev->value), index2); + } + } + } +#endif + (void)ev; + } + + return true; + break; + case CLAP_EVENT_PARAM_MOD: + return true; + break; + case CLAP_EVENT_PARAM_GESTURE_BEGIN: + { + auto ev = (clap_event_param_gesture*)event; +#if 0 + auto param = (Vst3Parameter*)this->parameters->getParameter(ev->param_id & 0x7FFFFFFF); + _gesturedParameters.push_back(param->getInfo().id); + _automation->onBeginEdit(param->getInfo().id); +#endif + (void)ev; + } + + return true; + + break; + case CLAP_EVENT_PARAM_GESTURE_END: + { + auto ev = (clap_event_param_gesture*)event; + (void)ev; +#if 0 + auto param = (Vst3Parameter*)this->parameters->getParameter(ev->param_id & 0x7FFFFFFF); + + auto n = std::remove(_gesturedParameters.begin(), _gesturedParameters.end(), param->getInfo().id); + if (n != _gesturedParameters.end()) + { + _gesturedParameters.erase(n, _gesturedParameters.end()); + _automation->onEndEdit(param->getInfo().id); + } +#endif + } + return true; + break; + + case CLAP_EVENT_MIDI: + case CLAP_EVENT_MIDI_SYSEX: + case CLAP_EVENT_MIDI2: + return true; + break; + default: + break; + } + return false; +} + +void ProcessAdapter::addToActiveNotes(const clap_event_note* note) +{ + for (auto& i : _activeNotes) + { + if (!i.used) + { + i.note_id = note->note_id; + i.port_index = note->port_index; + i.channel = note->channel; + i.key = note->key; + i.used = true; + return; + } + } + _activeNotes.emplace_back(ActiveNote{true, note->note_id, note->port_index, note->channel, note->key}); +} + +void ProcessAdapter::removeFromActiveNotes(const clap_event_note* note) +{ + for (auto& i : _activeNotes) + { + if (i.used && i.port_index == note->port_index && i.channel == note->channel && + i.note_id == note->note_id) + { + i.used = false; + } + } +} + +void ProcessAdapter::addMIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, + UInt32 inOffsetSampleFrame) +{ + const UInt32 strippedStatus = inStatus & 0xf0U; // NOLINT + const UInt32 channel = inStatus & 0x0fU; // NOLINT + + auto deltaFrames = inOffsetSampleFrame & kMusicDeviceSampleFrameMask_SampleOffset; + + bool live = (inOffsetSampleFrame & kMusicDeviceSampleFrameMask_IsScheduled) != 0; + + if (strippedStatus == 0x90) + { + clap_multi_event n; + n.header.time = 0; // deltaFrames; + n.header.type = CLAP_EVENT_NOTE_ON; + n.header.flags = 0 + (live ? CLAP_EVENT_IS_LIVE : 0); + n.header.size = sizeof(clap_event_note_t); + n.header.space_id = 0; + n.note.key = (inData1 & 0x7F); + n.note.velocity = (inData2 & 0x7F); + n.note.channel = channel; + this->_eventindices.emplace_back((this->_events.size())); + this->_events.emplace_back(n); + } + if (strippedStatus == 0x80) + { + clap_multi_event n; + n.header.time = 0; // deltaFrames; + n.header.type = CLAP_EVENT_NOTE_OFF; + n.header.flags = 0 + (live ? CLAP_EVENT_IS_LIVE : 0); + n.header.size = sizeof(clap_event_note_t); + n.header.space_id = 0; + n.note.key = (inData1 & 0x7F); + n.note.velocity = (inData2 & 0x7F); + n.note.channel = channel; + this->_eventindices.emplace_back((this->_events.size())); + this->_events.emplace_back(n); + } + (void)deltaFrames; +} +} // namespace Clap::AUv2 diff --git a/src/detail/auv2/process.h b/src/detail/auv2/process.h new file mode 100644 index 00000000..de04a086 --- /dev/null +++ b/src/detail/auv2/process.h @@ -0,0 +1,146 @@ +#pragma once + +/* + AUv2 process Adapter + + Copyright (c) 2023 Timo Kaluza (defiantnerd) + + This file i spart of the clap-wrappers project which is released under MIT License + + See file LICENSE or go to https://github.com/free-audio/clap-wrapper for full license details. + + + The process adapter is responible to translate events, timing information + + */ + +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wextra" +#endif + +#include + +// TODO: check if additional AU headers are needed +#include +#include +#include + +namespace Clap::AUv2 +{ + +struct ProcessData +{ + AudioUnitRenderActionFlags& flags; + const AudioTimeStamp& timestamp; + uint32_t numSamples = 0; + void* audioUnit = nullptr; + + // ------------- + bool _AUtransportValid; // true if: + // information from the AU Host + Float64 _cycleStart; + Float64 _cycleEnd; + Float64 _currentSongPos; // outCurrentSampleInTimeLine + + Boolean _isPlaying; + Boolean _transportChanged; + Boolean _isLooping; + + // -------------- + bool _AUbeatAndTempoValid; // true if: + // information from the AU host + Float64 _beat; + Float64 _tempo; + + // -------------- + bool _AUmusicalTimeValid; // true if: + // information from the AU host + UInt32 _offsetToNextBeat; + Float32 _musicalNumerator; + UInt32 _musicalDenominator; + Float64 _currentDownBeat; +}; + +class ProcessAdapter +{ + public: + typedef union clap_multi_event + { + clap_event_header_t header; + clap_event_note_t note; + clap_event_midi_t midi; + clap_event_midi_sysex_t sysex; + clap_event_param_value_t param; + clap_event_note_expression_t noteexpression; + } clap_multi_event_t; + + void setupProcessing(ausdk::AUScope& audioInputs, ausdk::AUScope& audioOutputs, + const clap_plugin_t* plugin, const clap_plugin_params_t* ext_params, + uint32_t numMaxSamples); + + void process(ProcessData& data); // AU Data + void flush(); + + // necessary C callbacks: + static uint32_t input_events_size(const struct clap_input_events* list); + static const clap_event_header_t* input_events_get(const struct clap_input_events* list, + uint32_t index); + + static bool output_events_try_push(const struct clap_output_events* list, + const clap_event_header_t* event); + + // interface for AUv2 wrapper: + void addMIDIEvent(UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame); + // void startNote() + ~ProcessAdapter(); + + private: + void sortEventIndices(); + + bool enqueueOutputEvent(const clap_event_header_t* event); + void addToActiveNotes(const clap_event_note* note); + void removeFromActiveNotes(const clap_event_note* note); + + // the plugin + const clap_plugin_t* _plugin = nullptr; + const clap_plugin_params_t* _ext_params = nullptr; + + // for automation gestures + std::vector _gesturedParameters; + + // for INoteExpression + struct ActiveNote + { + bool used = false; + int32_t note_id; // -1 if unspecified, otherwise >=0 + int16_t port_index; + int16_t channel; // 0..15 + int16_t key; // 0..127 + }; + std::vector _activeNotes; + + uint32_t _numInputs = 0; + uint32_t _numOutputs = 0; + + clap_audio_buffer_t* _input_ports = nullptr; + clap_audio_buffer_t* _output_ports = nullptr; + clap_event_transport_t _transport = {}; + clap_input_events_t _in_events = {}; + clap_output_events_t _out_events = {}; + + float* _silent_input = nullptr; + float* _silent_output = nullptr; + + clap_process_t _processData = {-1, 0, &_transport, nullptr, nullptr, 0, 0, &_in_events, &_out_events}; + + std::vector _events; + std::vector _eventindices; + + // AU Process Data? + ausdk::AUScope* _audioInputScope = nullptr; + ausdk::AUScope* _audioOutputScope = nullptr; +}; +} // namespace Clap::AUv2 diff --git a/src/detail/auv2/wrappedview.h b/src/detail/auv2/wrappedview.h new file mode 100644 index 00000000..3bbd61ce --- /dev/null +++ b/src/detail/auv2/wrappedview.h @@ -0,0 +1,10 @@ +#pragma once + +/* + not much to see here.. + */ + +#import +#include + +bool fillAudioUnitCocoaView(AudioUnitCocoaViewInfo& viewInfo); diff --git a/src/detail/auv2/wrappedview.mm b/src/detail/auv2/wrappedview.mm new file mode 100644 index 00000000..5bae8f13 --- /dev/null +++ b/src/detail/auv2/wrappedview.mm @@ -0,0 +1,179 @@ + +// +// wrapped view for clap-wrapper +// +// created by Paul Walker (baconpaul) and Timo Kaluza (defiantnerd) +// + +#include +#import +#import +#import + +#include "wrappedview.h" +#include "auv2_shared.h" +#include + +//#define CLAP_WRAPPER_UI_CLASSNAME_NSVIEW Clap_Wrapper_View_NSView +//#define CLAP_WRAPPER_UI_CLASSNAME_COCOAUI Clap_Wrapper_View_CocoaUI + +@interface Clap_Wrapper_View_NSView : NSView +{ + free_audio::auv2_wrapper::ui_connection ui; + CFRunLoopTimerRef idleTimer; + float lastScale; + NSSize underlyingUISize; + bool setSizeByZoom; // use this flag to see if resize comes from here or from external +} + +- (id)initWithAUv2:(free_audio::auv2_wrapper::ui_connection *)cont preferredSize:(NSSize)size; +- (void)doIdle; +- (void)dealloc; +- (void)setFrame:(NSRect)newSize; + +@end + +@interface Clap_Wrapper_View_CocoaUI : NSObject +{ +} +- (NSString *)description; +@end + +@implementation Clap_Wrapper_View_CocoaUI + +free_audio::auv2_wrapper::ui_connection uiconn; + +- (NSView *)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize +{ + // free_audio::auv2_wrapper::ui_connection connection; + // Remember we end up being called here because that's what AUCocoaUIView does in the initiation + // collaboration with hosts + + UInt32 size = sizeof(free_audio::auv2_wrapper::ui_connection); + if (AudioUnitGetProperty(inAudioUnit, kAudioUnitProperty_ClapWrapper_UIConnection_id, + kAudioUnitScope_Global, 0, &uiconn, &size) != noErr) + return nil; + + return [[[Clap_Wrapper_View_NSView alloc] initWithAUv2:&uiconn + preferredSize:inPreferredSize] autorelease]; + LOGINFO("[clap-wrapper] get ui View for AudioUnit"); + // return nil; +} + +- (unsigned int)interfaceVersion +{ + LOGINFO("[clap-wrapper] get interface version"); + return 0; +} + +- (NSString *)description +{ + LOGINFO("[clap-wrapper] get description"); + return [NSString stringWithUTF8String:"Wrap Window"]; // TODO: get name from plugin +} + +@end + +void timerCallback(CFRunLoopTimerRef timer, void *info) +{ + Clap_Wrapper_View_NSView *view = (Clap_Wrapper_View_NSView *)info; + [view doIdle]; +} + +@implementation Clap_Wrapper_View_NSView + +- (id)initWithAUv2:(free_audio::auv2_wrapper::ui_connection *)cont preferredSize:(NSSize)size +{ + LOGINFO("[clap-wrapper] create NS View begin"); + + ui = *cont; + ui._plugin->_ext._gui->create(ui._plugin->_plugin, CLAP_WINDOW_API_COCOA, false); + auto gui = ui._plugin->_ext._gui; + + // actually, the host should send an appropriate size, + // yet, they actually just send utter garbage, so: don't care + // if (size.width == 0 || size.height == 0) + { + // gui->get_size(ui._plugin->_plugin,) + uint32_t w, h; + if (gui->get_size(ui._plugin->_plugin, &w, &h)) + { + size = {(double)w, (double)h}; + } + } + self = [super initWithFrame:NSMakeRect(0, 0, size.width, size.height)]; + + // gui->show(ui._plugin->_plugin); + + clap_window_t m; + m.api = CLAP_WINDOW_API_COCOA; + m.ptr = self; + + gui->set_parent(ui._plugin->_plugin, &m); + gui->set_scale(ui._plugin->_plugin, 1.0); + gui->set_size(ui._plugin->_plugin, size.width, size.height); + + idleTimer = nil; + CFTimeInterval TIMER_INTERVAL = .05; // In SurgeGUISynthesizer.h it uses 50 ms + CFRunLoopTimerContext TimerContext = {0, self, NULL, NULL, NULL}; + CFAbsoluteTime FireTime = CFAbsoluteTimeGetCurrent() + TIMER_INTERVAL; + idleTimer = CFRunLoopTimerCreate(kCFAllocatorDefault, FireTime, TIMER_INTERVAL, 0, 0, timerCallback, + &TimerContext); + if (idleTimer) CFRunLoopAddTimer(CFRunLoopGetMain(), idleTimer, kCFRunLoopCommonModes); + + LOGINFO("[clap-wrapper] create NS View end"); + return self; +} + +- (void)doIdle +{ + // auto gui = ui._plugin->_ext._gui; +} +- (void)dealloc +{ + LOGINFO("[clap-wrapper] NS View dealloc"); + if (idleTimer) + { + CFRunLoopTimerInvalidate(idleTimer); + } + auto gui = ui._plugin->_ext._gui; + gui->destroy(ui._plugin->_plugin); + + [super dealloc]; +} +- (void)setFrame:(NSRect)newSize +{ + LOGINFO("[clap-wrapper] new size"); + + [super setFrame:newSize]; + auto gui = ui._plugin->_ext._gui; + gui->set_scale(ui._plugin->_plugin, 1.0); + gui->set_size(ui._plugin->_plugin, newSize.size.width, newSize.size.height); + + // gui->show(ui._plugin->_plugin); +} + +@end + +bool fillAudioUnitCocoaView(AudioUnitCocoaViewInfo *viewInfo) +{ + // now we are in m&m land.. + auto bundle = [NSBundle bundleForClass:[Clap_Wrapper_View_CocoaUI class]]; + + LOGINFO("[clap-wrapper] fill AudioUnitCocoaViewInfo"); + if (bundle) + { + // Get the URL for the main bundle + NSURL *url = [bundle bundleURL]; + CFURLRef cfUrl = (__bridge CFURLRef)url; + CFRetain(cfUrl); + + CFStringRef className = CFSTR("Clap_Wrapper_View_CocoaUI"); + + *viewInfo = {cfUrl, {className}}; + LOGINFO("[clap-wrapper] fill AudioUnitCocoaViewInfo: OK"); + return true; + } + LOGINFO("[clap-wrapper] fill AudioUnitCocoaViewInfo: FAILED"); + return false; +} diff --git a/src/detail/clap/fsutil.cpp b/src/detail/clap/fsutil.cpp index e45db706..559efecb 100644 --- a/src/detail/clap/fsutil.cpp +++ b/src/detail/clap/fsutil.cpp @@ -271,6 +271,7 @@ Library::Library() #endif #if MAC + extern std::string toString(const CFStringRef aString); extern fs::path sharedLibraryBundlePath(); auto selfp = sharedLibraryBundlePath(); if (!selfp.empty()) diff --git a/src/detail/clap/fsutil.h b/src/detail/clap/fsutil.h index 8119ed93..9096a368 100644 --- a/src/detail/clap/fsutil.h +++ b/src/detail/clap/fsutil.h @@ -58,6 +58,13 @@ class Library std::vector plugins; const clap_plugin_info_as_vst3_t* get_vst3_info(uint32_t index) const; +#if MAC + CFBundleRef getBundleRef() + { + return _bundle; + } +#endif + bool hasEntryPoint() const { #if WIN diff --git a/src/detail/clap/mac_helpers.mm b/src/detail/clap/mac_helpers.mm index 44b577f3..391c8a51 100644 --- a/src/detail/clap/mac_helpers.mm +++ b/src/detail/clap/mac_helpers.mm @@ -22,8 +22,27 @@ Timo Kaluza (defiantnerd) #include +@interface free_audio_clap_wrapper_ffowefe : NSObject +- (void)foo; +@end + +@implementation free_audio_clap_wrapper_ffowefe +- (void)foo +{ +} +@end + namespace Clap { +/* + this could be anything apparantly +*/ + +NSBundle *getMyBundle() +{ + return [NSBundle bundleForClass:[free_audio_clap_wrapper_ffowefe class]]; +} + fs::path sharedLibraryBundlePath() { Dl_info info; @@ -100,4 +119,24 @@ Timo Kaluza (defiantnerd) } return res; } +std::string toString(const CFStringRef aString) +{ + if (aString == NULL) + { + return std::string(); + } + + std::string result; + + CFIndex length = CFStringGetLength(aString); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + result.reserve(maxSize); + + if (CFStringGetCString(aString, result.data(), maxSize, kCFStringEncodingUTF8)) + { + return result; + } + + return std::string(); +} } // namespace Clap diff --git a/src/detail/vst3/os/linux.cpp b/src/detail/os/linux.cpp similarity index 100% rename from src/detail/vst3/os/linux.cpp rename to src/detail/os/linux.cpp diff --git a/src/detail/vst3/os/macos.mm b/src/detail/os/macos.mm similarity index 92% rename from src/detail/vst3/os/macos.mm rename to src/detail/os/macos.mm index a465ced8..11b8734c 100644 --- a/src/detail/vst3/os/macos.mm +++ b/src/detail/os/macos.mm @@ -11,7 +11,9 @@ */ #include +#ifdef CLAP_WRAPPER_BUILD_FOR_VST3 #include "public.sdk/source/main/moduleinit.h" +#endif #include "osutil.h" #include #if MACOS_USE_STD_FILESYSTEM @@ -27,7 +29,8 @@ { void log(const char* text) { - NSLog(@"%s", text); + printf("%s\n", text); + // NSLog(@"%s", text); } class MacOSHelper @@ -45,8 +48,13 @@ void log(const char* text) std::vector _plugs; } gMacOSHelper; +// standard specific extensions +// ---------------------------------------------------------- +#ifdef CLAP_WRAPPER_BUILD_FOR_VST3 static Steinberg::ModuleInitializer createMessageWindow([] { gMacOSHelper.init(); }); static Steinberg::ModuleTerminator dropMessageWindow([] { gMacOSHelper.terminate(); }); +#endif +// ---------------------------------------------------------- void MacOSHelper::init() { diff --git a/src/detail/vst3/os/osutil.h b/src/detail/os/osutil.h similarity index 100% rename from src/detail/vst3/os/osutil.h rename to src/detail/os/osutil.h diff --git a/src/detail/vst3/os/windows.cpp b/src/detail/os/windows.cpp similarity index 100% rename from src/detail/vst3/os/windows.cpp rename to src/detail/os/windows.cpp diff --git a/src/detail/shared/sha1.cpp b/src/detail/shared/sha1.cpp index 749d1ed3..5d00619c 100644 --- a/src/detail/shared/sha1.cpp +++ b/src/detail/shared/sha1.cpp @@ -225,11 +225,11 @@ void Sha1::padmessage() _messageBlock[56] = (_length_high >> 24) & 0xFF; _messageBlock[57] = (_length_high >> 16) & 0xFF; _messageBlock[58] = (_length_high >> 8) & 0xFF; - _messageBlock[59] = (_length_high)&0xFF; + _messageBlock[59] = (_length_high) & 0xFF; _messageBlock[60] = (_lengthLow >> 24) & 0xFF; _messageBlock[61] = (_lengthLow >> 16) & 0xFF; _messageBlock[62] = (_lengthLow >> 8) & 0xFF; - _messageBlock[63] = (_lengthLow)&0xFF; + _messageBlock[63] = (_lengthLow) & 0xFF; processMessageBlock(); } diff --git a/src/detail/vst3/process.cpp b/src/detail/vst3/process.cpp index 7fdbd0a3..b66ee633 100644 --- a/src/detail/vst3/process.cpp +++ b/src/detail/vst3/process.cpp @@ -119,7 +119,7 @@ void ProcessAdapter::setupProcessing(const clap_plugin_t* plugin, const clap_plu _gesturedParameters.reserve(8192); - _activeNotes.reserve(256); + _activeNotes.reserve(32); _supportsPolyPressure = enablePolyPressure; _supportsTuningNoteExpression = supportsTuningNoteExpression; @@ -782,7 +782,7 @@ void ProcessAdapter::addToActiveNotes(const clap_event_note* note) return; } } - _activeNotes.push_back({true, note->note_id, note->port_index, note->channel, note->key}); + _activeNotes.emplace_back(ActiveNote{true, note->note_id, note->port_index, note->channel, note->key}); } void ProcessAdapter::removeFromActiveNotes(const clap_event_note* note) diff --git a/src/wrapasauv2.cpp b/src/wrapasauv2.cpp index 1505faa9..0e40cefc 100644 --- a/src/wrapasauv2.cpp +++ b/src/wrapasauv2.cpp @@ -1 +1,607 @@ -#include "generated_entrypoints.hxx" \ No newline at end of file +#include "generated_entrypoints.hxx" +#include "detail/auv2/process.h" +#include "detail/os/osutil.h" + +extern bool fillAudioUnitCocoaView(AudioUnitCocoaViewInfo* viewInfo); + +namespace free_audio::auv2_wrapper +{ + +Clap::Library _library; // holds the library with plugins + +#if 0 +--- 8< --- +struct ClapHostExtensions +{ + static inline WrapAsAUV2* self(const clap_host_t* host) + { + return static_cast(host->host_data); + } + static void mark_dirty(const clap_host_t* host) + { + self(host)->mark_dirty(); + } + const clap_host_state_t _state = {mark_dirty}; +}; +#endif + +void pffffrzz() +{ + auto pid = getpid(); + + char buf[200]; + snprintf(buf, sizeof(buf), "process %d", pid); + SInt32 nRes = 0; + CFUserNotificationRef pDlg = NULL; + CFStringRef b = CFStringCreateWithCString(NULL, buf, kCFStringEncodingASCII); + const void* keys[] = {kCFUserNotificationAlertHeaderKey, kCFUserNotificationAlertMessageKey}; + const void* vals[] = {CFSTR("Test Foundation Message Box"), b}; + + CFDictionaryRef dict = + CFDictionaryCreate(0, keys, vals, sizeof(keys) / sizeof(*keys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + pDlg = + CFUserNotificationCreate(kCFAllocatorDefault, 0, kCFUserNotificationPlainAlertLevel, &nRes, dict); + + (void)pDlg; + CFRelease(b); + usleep(20000000); +} + +bool WrapAsAUV2::initializeClapDesc() +{ + LOGINFO("[clap-wrapper auv2: id={} index: {}\n", _clapid, _idx); + + if (!_library.hasEntryPoint()) + { + if (_clapname.empty()) + { + std::cout << "[ERROR] _clapname (" << _clapname << ") empty and no internal entry point" + << std::endl; + } + + auto csp = Clap::getValidCLAPSearchPaths(); + auto it = std::find_if(csp.begin(), csp.end(), + [this](const auto& cs) + { + auto fp = cs / (_clapname + ".clap"); + return fs::is_directory(fp) && _library.load(fp.u8string().c_str()); + }); + + if (it != csp.end()) + { + std::cout << "[clap-wrapper] auv2 loaded clap from " << it->u8string() << std::endl; + } + else + { + std::cout << "[ERROR] cannot load clap" << std::endl; + return false; + } + } + + if (_clapid.empty()) + { + if (_idx < 0 || _idx >= (int)_library.plugins.size()) + { + std::cout << "[ERROR] cannot load by index" << std::endl; + return false; + } + _desc = _library.plugins[_idx]; + } + else + { + for (auto* d : _library.plugins) + { + if (strcmp(d->id, _clapid.c_str()) == 0) + { + _desc = d; + } + } + } + + if (!_desc) + { + std::cout << "[ERROR] cannot determine plugin description" << std::endl; + return false; + } + return true; +} + +WrapAsAUV2::WrapAsAUV2(AUV2_Type type, const std::string& clapname, const std::string& clapid, int idx, + AudioComponentInstance ci) + : Base{ci, 0, 1}, Clap::IHost(), _autype(type), _clapname{clapname}, _clapid{clapid}, _idx{idx} +{ + (void)_autype; // TODO: will be used for dynamic property adaption + if (!_desc) + { + if (initializeClapDesc()) + { + std::cout << "[clap-wrapper] auv2: Initialized '" << _desc->id << "' / '" << _desc->name << "' / '" + << _desc->version << "'" << std::endl; + /* + * ToDo: Stand up the host, create the plugin instance here + */ + _plugin = Clap::Plugin::createInstance(_library._pluginFactory, _desc->id, this); + // pffffrzz(); + _plugin->initialize(); + } + } +} + +WrapAsAUV2::~WrapAsAUV2() +{ + if (_plugin) + { + _plugin->terminate(); + _plugin.reset(); + } +} + +// the very very reduced state machine +OSStatus WrapAsAUV2::Initialize() +{ + if (!_desc) return 2; + + // first need to initialize the base to create + // all elements needed + auto res = Base::Initialize(); + if (res != noErr) return res; + + // activating the plugin in AU can happen in the Audio Thread (Logic Pro) + // CLAP does not want it, therefore the wrapper insists on being in the + // main thread + auto keeper = _plugin->AlwaysMainThread(); + activateCLAP(); +#if 0 + // get our current numChannels for input and output + const auto auNumInputs = static_cast(Input(0).GetStreamFormat().mChannelsPerFrame); + const auto auNumOutputs = static_cast(Output(0).GetStreamFormat().mChannelsPerFrame); + + // does the unit publish specific information about channel configurations? + const AUChannelInfo* auChannelConfigs = nullptr; + const UInt32 numIOconfigs = SupportedNumChannels(&auChannelConfigs); + + if ((numIOconfigs > 0) && (auChannelConfigs != nullptr)) { + bool foundMatch = false; + for (UInt32 i = 0; (i < numIOconfigs) && !foundMatch; ++i) { + const SInt16 configNumInputs = auChannelConfigs[i].inChannels; // NOLINT + const SInt16 configNumOutputs = auChannelConfigs[i].outChannels; // NOLINT + if ((configNumInputs < 0) && (configNumOutputs < 0)) { + // unit accepts any number of channels on input and output + if (((configNumInputs == -1) && (configNumOutputs == -2)) || + ((configNumInputs == -2) && + (configNumOutputs == -1))) { // NOLINT repeated branch below + foundMatch = true; + // unit accepts any number of channels on input and output IFF they are the same + // number on both scopes + } else if (((configNumInputs == -1) && (configNumOutputs == -1)) && + (auNumInputs == auNumOutputs)) { + foundMatch = true; + // unit has specified a particular number of channels on both scopes + } else { + continue; + } + } else { + // the -1 case on either scope is saying that the unit doesn't care about the + // number of channels on that scope + const bool inputMatch = (auNumInputs == configNumInputs) || (configNumInputs == -1); + const bool outputMatch = + (auNumOutputs == configNumOutputs) || (configNumOutputs == -1); + if (inputMatch && outputMatch) { + foundMatch = true; + } + } + } + if (!foundMatch) { + return kAudioUnitErr_FormatNotSupported; + } + } else { + // there is no specifically published channel info + // so for those kinds of effects, the assumption is that the channels (whatever their + // number) should match on both scopes + if ((auNumOutputs != auNumInputs) || (auNumOutputs == 0)) { + return kAudioUnitErr_FormatNotSupported; + } + } +// #if 0 + MaintainKernels(); + + mMainOutput = &Output(0); + mMainInput = &Input(0); + + const AudioStreamBasicDescription format = GetStreamFormat(kAudioUnitScope_Output, 0); + mBytesPerFrame = format.mBytesPerFrame; + + return noErr; +#endif + + return noErr; +} + +void WrapAsAUV2::setupWrapperSpecifics(const clap_plugin_t* plugin) +{ + // TODO: if there are AUv2 specific extensions, they can be retrieved here + // _auv2_specifics = (clap_plugin_as_auv2_t*)plugin->get_extension(plugin, CLAP_PLUGIN_AS_AUV2); +} + +void WrapAsAUV2::setupAudioBusses(const clap_plugin_t* plugin, + const clap_plugin_audio_ports_t* audioports) +{ + auto numAudioInputs = audioports->count(plugin, true); + auto numAudioOutputs = audioports->count(plugin, false); + + fprintf(stderr, "\tAUDIO in: %d, out: %d\n", (int)numAudioInputs, (int)numAudioOutputs); + + ausdk::AUBase::GetScope(kAudioUnitScope_Input).Initialize(this, kAudioUnitScope_Input, numAudioInputs); + + for (decltype(numAudioInputs) i = 0; i < numAudioInputs; ++i) + { + clap_audio_port_info_t info; + if (audioports->get(plugin, i, true, &info)) + { + addAudioBusFrom(i, &info, true); + } + } + + ausdk::AUBase::GetScope(kAudioUnitScope_Output) + .Initialize(this, kAudioUnitScope_Output, numAudioOutputs); + + for (decltype(numAudioOutputs) i = 0; i < numAudioOutputs; ++i) + { + clap_audio_port_info_t info; + if (audioports->get(plugin, i, false, &info)) + { + addAudioBusFrom(i, &info, false); + } + } + + ausdk::AUBase::ReallocateBuffers(); + +} // called from initialize() to allow the setup of audio ports + +void WrapAsAUV2::setupMIDIBusses(const clap_plugin_t* plugin, const clap_plugin_note_ports_t* noteports) +{ + // TODO: figure out if MIDI is is prefered as CLAP or Notes +} + +OSStatus WrapAsAUV2::Start() +{ + // _plugin->start_processing(); + // activateCLAP(); + return noErr; // Base::Start(); +} + +OSStatus WrapAsAUV2::Stop() +{ + // _plugin->stop_processing(); + // deactivateCLAP(); + return noErr; // Base::Stop(); +} +void WrapAsAUV2::Cleanup() +{ + LOGINFO("Cleaning up Plugin"); + auto keeper = _plugin->AlwaysMainThread(); + deactivateCLAP(); + Base::Cleanup(); +} + +Float64 WrapAsAUV2::GetLatency() +{ + if (_plugin && _plugin->_ext._latency) + { + auto samplerate = this->GetStreamFormat(kAudioUnitScope_Output, 0).mSampleRate; + auto latency_in_samples = (double)(_plugin->_ext._latency->get(_plugin->_plugin)); + Float64 latencytime = latency_in_samples / samplerate; + + return latencytime; + } + return 0.0; +} + +Float64 WrapAsAUV2::GetTailTime() +{ + if (_plugin && _plugin->_ext._tail) + { + auto samplerate = this->GetStreamFormat(kAudioUnitScope_Output, 0).mSampleRate; + auto tailtime_in_samples = (double)(_plugin->_ext._tail->get(_plugin->_plugin)); + Float64 tailtime = tailtime_in_samples / samplerate; + + return tailtime; + } + return 0.0; +} + +OSStatus WrapAsAUV2::GetPropertyInfo(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, UInt32& outDataSize, bool& outWritable) +{ + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case kMusicDeviceProperty_InstrumentCount: + outDataSize = sizeof(UInt32); + outWritable = false; + return noErr; + break; + case kAudioUnitProperty_BypassEffect: + // case kAudioUnitProperty_InPlaceProcessing: + outWritable = true; + outDataSize = sizeof(UInt32); + return noErr; + case kMusicDeviceProperty_DualSchedulingMode: + outWritable = true; + outDataSize = sizeof(UInt32); + return noErr; + break; + case kMusicDeviceProperty_SupportsStartStopNote: + outWritable = true; + outDataSize = sizeof(UInt32); + return noErr; + break; + case kAudioUnitProperty_CocoaUI: + outWritable = false; + outDataSize = sizeof(struct AudioUnitCocoaViewInfo); + LOGINFO("query Property Info: kAudioUnitProperty_CocoaUI"); + return noErr; + break; +#if 0 + // TODO: for CLAPs that have MIDI output, we need these two properties. + case kAudioUnitProperty_MIDIOutputCallbackInfo: + return noErr; + break; + case kAudioUnitProperty_MIDIOutputCallback: + outWritable = true; + outDataSize = sizeof(AUMIDIOutputCallbackStruct); + break; +#endif + // custom + case kAudioUnitProperty_ClapWrapper_UIConnection_id: + outWritable = false; + outDataSize = sizeof(free_audio::auv2_wrapper::ui_connection); + return noErr; + break; + default: + break; + } + } + return Base::GetPropertyInfo(inID, inScope, inElement, outDataSize, outWritable); +} + +ui_connection _uiconn; + +OSStatus WrapAsAUV2::GetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, void* outData) +{ + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case kMusicDeviceProperty_InstrumentCount: + if (inScope != kAudioUnitScope_Global) + { + return kAudioUnitErr_InvalidScope; + } + *static_cast(outData) = 0; + return noErr; + // return GetInstrumentCount(*static_cast(outData)); + case kAudioUnitProperty_BypassEffect: + *static_cast(outData) = (IsBypassEffect() ? 1 : 0); // NOLINT + return noErr; + // case kAudioUnitProperty_InPlaceProcessing: + // *static_cast(outData) = (mProcessesInPlace ? 1 : 0); // NOLINT + // return noErr; + case kAudioUnitProperty_ClapWrapper_UIConnection_id: + _uiconn._plugin = _plugin.get(); + _uiconn._window = nullptr; + *static_cast(outData) = _uiconn; + return noErr; + case kAudioUnitProperty_CocoaUI: + LOGINFO("query Property: kAudioUnitProperty_CocoaUI {}", (_plugin) ? "plugin" : "no plugin"); + if (_plugin && + (_plugin->_ext._gui->is_api_supported(_plugin->_plugin, CLAP_WINDOW_API_COCOA, false))) + { + LOGINFO("now getting cocoa ui"); + fillAudioUnitCocoaView(((AudioUnitCocoaViewInfo*)outData)); + // *((AudioUnitCocoaViewInfo *)outData) = cocoaInfo; + LOGINFO("query Property: kAudioUnitProperty_CocoaUI complete"); + return noErr; // sizeof(AudioUnitCocoaViewInfo); + } + else + { + LOGINFO("query Property: kAudioUnitProperty_CocoaUI although now plugin ext"); + fillAudioUnitCocoaView(((AudioUnitCocoaViewInfo*)outData)); + return noErr; + } + return kAudioUnitErr_InvalidProperty; + break; + case kMusicDeviceProperty_DualSchedulingMode: + // yes we do + *static_cast(outData) = 0; + return noErr; + break; + case kMusicDeviceProperty_SupportsStartStopNote: + // TODO: change this when figured out how the NoteParamsControlValue actually do work. + + *static_cast(outData) = 0; + return noErr; + break; + default: + break; + } + } + return Base::GetProperty(inID, inScope, inElement, outData); +} +OSStatus WrapAsAUV2::SetProperty(AudioUnitPropertyID inID, AudioUnitScope inScope, + AudioUnitElement inElement, const void* inData, UInt32 inDataSize) +{ + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case kAudioUnitProperty_BypassEffect: + { + if (inDataSize < sizeof(UInt32)) + { + return kAudioUnitErr_InvalidPropertyValue; + } + + const bool tempNewSetting = *static_cast(inData) != 0; + // we're changing the state of bypass + if (tempNewSetting != IsBypassEffect()) + { + if (!tempNewSetting && IsBypassEffect() && IsInitialized()) + { // turning bypass off and we're initialized + Reset(kAudioUnitScope_Global, 0); + } + SetBypassEffect(tempNewSetting); + } + return noErr; + } + // case kAudioUnitProperty_InPlaceProcessing: + // mProcessesInPlace = *static_cast(inData) != 0; + // return noErr; + break; + case kMusicDeviceProperty_SupportsStartStopNote: + { + auto x = *static_cast(inData); + (void)x; + return noErr; + } + break; + case kMusicDeviceProperty_DualSchedulingMode: + { + auto x = *static_cast(inData); + (void)x; + return noErr; + } + break; + + default: + break; + } + } + return Base::SetProperty(inID, inScope, inElement, inData, inDataSize); +} + +OSStatus WrapAsAUV2::SetRenderNotification(AURenderCallback inProc, void* inRefCon) +{ + // activateCLAP(); + + return Base::SetRenderNotification(inProc, inRefCon); +} + +OSStatus WrapAsAUV2::RemoveRenderNotification(AURenderCallback inProc, void* inRefCon) +{ + // deactivateCLAP(); + return Base::RemoveRenderNotification(inProc, inRefCon); +} + +void WrapAsAUV2::latency_changed() +{ + PropertyChanged(kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); +} + +void WrapAsAUV2::tail_changed() +{ + PropertyChanged(kAudioUnitProperty_TailTime, kAudioUnitScope_Global, 0); +} + +void WrapAsAUV2::addAudioBusFrom(int bus, const clap_audio_port_info_t* info, bool is_input) +{ + // add/set audio bus configuration from info to appropriate scope + + if (is_input) + { + addInputBus(bus, info); + } + else + { + addOutputBus(bus, info); + } +} + +void WrapAsAUV2::addInputBus(int bus, const clap_audio_port_info_t* info) +{ + auto& busref = Input(bus); + + CFStringRef busNameString = CFStringCreateWithCString(NULL, info->name, kCFStringEncodingUTF8); + busref.SetName(busNameString); + CFRelease(busNameString); + + auto sf = busref.GetStreamFormat(); + sf.mChannelsPerFrame = info->channel_count; + busref.SetStreamFormat(sf); +} +void WrapAsAUV2::addOutputBus(int bus, const clap_audio_port_info_t* info) +{ + auto& busref = Output(bus); + + CFStringRef busNameString = CFStringCreateWithCString(NULL, info->name, kCFStringEncodingUTF8); + busref.SetName(busNameString); + CFRelease(busNameString); + + auto sf = busref.GetStreamFormat(); + sf.mChannelsPerFrame = info->channel_count; + busref.SetStreamFormat(sf); +} + +void WrapAsAUV2::activateCLAP() +{ + if (_plugin) + { + assert(!_initialized); + if (!_processAdapter) _processAdapter = std::make_unique(); + auto maxSampleFrames = Base::GetMaxFramesPerSlice(); + auto minSampleFrames = (maxSampleFrames >= 16) ? 16 : 1; + _plugin->setBlockSizes(minSampleFrames, maxSampleFrames); + _plugin->setSampleRate(Output(0).GetStreamFormat().mSampleRate); + + _processAdapter->setupProcessing(Inputs(), Outputs(), _plugin->_plugin, _plugin->_ext._params, + maxSampleFrames); + + _plugin->activate(); + _plugin->start_processing(); + _initialized = true; + } +} + +void WrapAsAUV2::deactivateCLAP() +{ + if (_plugin) + { + _initialized = false; + _processAdapter.reset(); + _plugin->stop_processing(); + _plugin->deactivate(); + } +} + +OSStatus WrapAsAUV2::Render(AudioUnitRenderActionFlags& inFlags, const AudioTimeStamp& inTimeStamp, + UInt32 inFrames) +{ + assert(inFlags == 0); + if (_initialized && (inFlags == 0)) + { + // do the render dance + Clap::AUv2::ProcessData data{inFlags, inTimeStamp, inFrames, this}; + + // retrieve musical information for this render block +#if 0 + data._AUtransportValid = (noErr == CallHostTransportState(&data._isPlaying, &data._transportChanged, &data._currentSongPos, &data._isLooping, &data._cycleStart, &data._cycleEnd) ); + data._AUbeatAndTempoValid = (noErr == + CallHostBeatAndTempo(&data._beat, &data._tempo)); + data._AUmusicalTimeValid = (noErr == + CallHostMusicalTimeLocation(&data._offsetToNextBeat, &data._musicalNumerator, &data._musicalDenominator, &data._currentDownBeat)); +#endif + // Get output buffer list and extract the i/o buffer pointers. + // The loop is done so that an arbitrary number of output busses + // with an arbitrary number of output channels is mapped onto a + // continuous array of float buffers for the VST process function + + _processAdapter->process(data); + } + return noErr; +} + +} // namespace free_audio::auv2_wrapper diff --git a/src/wrapasvst3.cpp b/src/wrapasvst3.cpp index 2e5e995c..d9f09a86 100644 --- a/src/wrapasvst3.cpp +++ b/src/wrapasvst3.cpp @@ -23,6 +23,8 @@ #define S16(x) u##x #endif +#if 0 +--- 8< --- struct ClapHostExtensions { static inline ClapAsVst3* self(const clap_host_t* host) @@ -35,6 +37,7 @@ struct ClapHostExtensions } const clap_host_state_t _state = {mark_dirty}; }; +#endif tresult PLUGIN_API ClapAsVst3::initialize(FUnknown* context) { @@ -63,6 +66,7 @@ tresult PLUGIN_API ClapAsVst3::terminate() _plugin->terminate(); _plugin.reset(); } + return super::terminate(); } diff --git a/src/wrapasvst3.h b/src/wrapasvst3.h index 882dbf18..1363eea6 100644 --- a/src/wrapasvst3.h +++ b/src/wrapasvst3.h @@ -29,15 +29,14 @@ #pragma GCC diagnostic pop #endif +#include "detail/os/osutil.h" #include "detail/vst3/plugview.h" -#include "detail/vst3/os/osutil.h" #include "detail/clap/automation.h" #include "detail/shared/fixedqueue.h" #include using namespace Steinberg; -struct ClapHostExtensions; namespace Clap { class ProcessAdapter; @@ -246,7 +245,6 @@ class ClapAsVst3 : public Steinberg::Vst::SingleComponentEffect, Clap::Library* _library = nullptr; int _libraryIndex = 0; std::shared_ptr _plugin; - ClapHostExtensions* _hostextensions = nullptr; clap_plugin_as_vst3_t* _vst3specifics = nullptr; Clap::ProcessAdapter* _processAdapter = nullptr; WrappedView* _wrappedview = nullptr;