From bd4d1be3c1c221fac1ca83de3032334ad118a59e Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 19:14:48 +0200 Subject: [PATCH 01/10] AUv3 support --- cmake/platforms/mac/AudioUnitV3Info.plist.in | 57 + .../mac/AudioUnitV3_Entitlements.plist | 12 + cmake/plugins/yup_plugin_auv3.cmake | 175 ++ cmake/yup.cmake | 1 + cmake/yup_audio_plugin.cmake | 45 +- cmake/yup_dependencies.cmake | 10 + cmake/yup_modules.cmake | 5 +- examples/audiograph/CMakeLists.txt | 1 + examples/audiograph/source/AudioGraphApp.cpp | 6 + .../audiograph/source/nodes/NodeRegistry.h | 6 + .../auv3/yup_audio_plugin_client_AUv3.mm | 1532 +++++++++++++++++ .../yup_audio_plugin_client.h | 8 + .../host/yup_AudioPluginFormatType.h | 1 + .../native/yup_AudioPluginInstance_AUv3.h | 60 + .../native/yup_AudioPluginInstance_AUv3.mm | 646 +++++++ .../yup_audio_plugin_host.cpp | 8 + .../yup_audio_plugin_host.h | 11 +- modules/yup_gui/yup_gui.h | 2 +- 18 files changed, 2580 insertions(+), 6 deletions(-) create mode 100644 cmake/platforms/mac/AudioUnitV3Info.plist.in create mode 100644 cmake/platforms/mac/AudioUnitV3_Entitlements.plist create mode 100644 cmake/plugins/yup_plugin_auv3.cmake create mode 100644 modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm create mode 100644 modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h create mode 100644 modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm diff --git a/cmake/platforms/mac/AudioUnitV3Info.plist.in b/cmake/platforms/mac/AudioUnitV3Info.plist.in new file mode 100644 index 000000000..453b9c397 --- /dev/null +++ b/cmake/platforms/mac/AudioUnitV3Info.plist.in @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @PLUGIN_AU_NAME@ + CFBundlePackageType + XPC! + CFBundleShortVersionString + @PLUGIN_AU_VERSION@ + CFBundleVersion + @PLUGIN_AU_VERSION@ + CFBundleSignature + @PLUGIN_AU_MANUFACTURER@ + NSHumanReadableCopyright + Copyright (c) 2026 - kunitoki@gmail.com + NSHighResolutionCapable + + NSExtension + + NSExtensionPrincipalClass + YUPAUv3ViewController + NSExtensionPointIdentifier + com.apple.AudioUnit-UI + NSExtensionAttributes + + AudioComponentBundle + + + + AudioComponents + + + type + @PLUGIN_AU_TYPE@ + subtype + @PLUGIN_AU_SUBTYPE@ + manufacturer + @PLUGIN_AU_MANUFACTURER@ + name + @PLUGIN_AU_NAME@ + version + 1 + sandboxSafe + + + + + diff --git a/cmake/platforms/mac/AudioUnitV3_Entitlements.plist b/cmake/platforms/mac/AudioUnitV3_Entitlements.plist new file mode 100644 index 000000000..7d0b86f02 --- /dev/null +++ b/cmake/platforms/mac/AudioUnitV3_Entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.audio-input + + com.apple.security.device.midi + + + diff --git a/cmake/plugins/yup_plugin_auv3.cmake b/cmake/plugins/yup_plugin_auv3.cmake new file mode 100644 index 000000000..e88bf958a --- /dev/null +++ b/cmake/plugins/yup_plugin_auv3.cmake @@ -0,0 +1,175 @@ +# ============================================================================== +# +# This file is part of the YUP library. +# Copyright (c) 2026 - kunitoki@gmail.com +# +# YUP is an open source library subject to open-source licensing. +# +# The code included in this file is provided under the terms of the ISC license +# http://www.isc.org/downloads/software-support-policy/isc-license. Permission +# To use, copy, modify, and/or distribute this software for any purpose with or +# without fee is hereby granted provided that the above copyright notice and +# this permission notice appear in all copies. +# +# YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER +# EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE +# DISCLAIMED. +# +# ============================================================================== + +#============================================================================== + +function (yup_plugin_auv3) + # ==== Parse arguments + set (one_value_args + TARGET_NAME TARGET_CXX_STANDARD TARGET_IDE_GROUP TARGET_BUNDLE_ID + PLUGIN_IS_SYNTH PLUGIN_NAME PLUGIN_VERSION PLUGIN_AU_SUBTYPE PLUGIN_AU_MANUFACTURER + STANDALONE_TARGET HAS_STANDALONE) + + set (multi_value_args + SHARED_LIBS + ADDITIONAL_LIBRARIES + MODULES) + + cmake_parse_arguments (YUP_ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + if (NOT YUP_PLATFORM_MAC) + _yup_message (WARNING "AUv3 plugins are only supported on macOS. Skipping AUv3 target.") + return() + endif() + + set (target_name "${YUP_ARG_TARGET_NAME}") + set (target_cxx_standard "${YUP_ARG_TARGET_CXX_STANDARD}") + set (target_ide_group "${YUP_ARG_TARGET_IDE_GROUP}") + set (target_bundle_id "${YUP_ARG_TARGET_BUNDLE_ID}") + + _yup_message (STATUS "Setting up AUv3 plugin client") + _yup_module_setup_plugin_client ( + ${target_name} + yup_audio_plugin_client + ${target_ide_group} + auv3 + ${YUP_ARG_UNPARSED_ARGUMENTS}) + + # Determine AU type (aumu for instruments, aufx for effects) + if (YUP_ARG_PLUGIN_IS_SYNTH) + set (au_bundle_type "aumu") + else() + set (au_bundle_type "aufx") + endif() + + if (NOT YUP_ARG_PLUGIN_AU_SUBTYPE) + set (YUP_ARG_PLUGIN_AU_SUBTYPE "Dflt") + endif() + if (NOT YUP_ARG_PLUGIN_AU_MANUFACTURER) + set (YUP_ARG_PLUGIN_AU_MANUFACTURER "Yup!") + endif() + if (NOT YUP_ARG_PLUGIN_NAME) + set (YUP_ARG_PLUGIN_NAME "${target_name}") + endif() + if (NOT YUP_ARG_PLUGIN_VERSION) + set (YUP_ARG_PLUGIN_VERSION "1") + endif() + + _yup_message (STATUS "Creating AUv3 plugin target") + + # Create the .appex App Extension bundle target + add_library (${target_name}_auv3_plugin MODULE) + + target_compile_features (${target_name}_auv3_plugin PRIVATE cxx_std_${target_cxx_standard}) + + target_compile_definitions (${target_name}_auv3_plugin PRIVATE + YUP_AUDIO_PLUGIN_ENABLE_AUv3=1 + YUP_STANDALONE_APPLICATION=0) + + _yup_sdl_configure_symbols_patch ("${target_name}_auv3_plugin" auv3_sdl_symbols_patch_target auv3_sdl_symbols_sdl_target) + set (auv3_plugin_bundle_libraries + ${auv3_sdl_symbols_sdl_target} + ${auv3_sdl_symbols_patch_target}) + + target_link_libraries (${target_name}_auv3_plugin PRIVATE + ${YUP_ARG_SHARED_LIBS} + yup_audio_plugin_client + ${target_name}_auv3 + ${YUP_ARG_ADDITIONAL_LIBRARIES} + ${auv3_plugin_bundle_libraries} + ${YUP_ARG_MODULES} + "-framework AVFoundation" + "-framework AudioToolbox" + "-framework CoreAudioKit" + "-framework CoreAudio" + "-framework CoreFoundation" + "-framework AppKit") + + _yup_module_apply_arc_to_target_sources (${target_name}_auv3_plugin + ${YUP_ARG_SHARED_LIBS} + yup_audio_plugin_client + ${target_name}_auv3 + ${YUP_ARG_ADDITIONAL_LIBRARIES} + ${auv3_plugin_bundle_libraries} + ${YUP_ARG_MODULES}) + + # Generate the AUv3 Info.plist from our template + set (auv3_plist_template "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../platforms/mac/AudioUnitV3Info.plist.in") + set (auv3_plist_output "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_auv3_plugin.plist") + + set (PLUGIN_AU_TYPE "${au_bundle_type}") + set (PLUGIN_AU_SUBTYPE "${YUP_ARG_PLUGIN_AU_SUBTYPE}") + set (PLUGIN_AU_MANUFACTURER "${YUP_ARG_PLUGIN_AU_MANUFACTURER}") + set (PLUGIN_AU_NAME "${YUP_ARG_PLUGIN_NAME}") + set (PLUGIN_AU_VERSION "${YUP_ARG_PLUGIN_VERSION}") + + set (auv3_bundle_identifier "${target_bundle_id}.auv3") + string (REGEX REPLACE "[^A-Za-z0-9.-]" "-" auv3_bundle_identifier "${auv3_bundle_identifier}") + + configure_file ("${auv3_plist_template}" "${auv3_plist_output}" @ONLY) + + set_target_properties (${target_name}_auv3_plugin PROPERTIES + C_VISIBILITY_PRESET hidden + CXX_VISIBILITY_PRESET hidden + OBJC_VISIBILITY_PRESET hidden + OBJCXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + BUNDLE TRUE + BUNDLE_EXTENSION "appex" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${auv3_plist_output}" + MACOSX_BUNDLE_BUNDLE_NAME "${YUP_ARG_PLUGIN_NAME}" + MACOSX_BUNDLE_BUNDLE_VERSION "${YUP_ARG_PLUGIN_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${YUP_ARG_PLUGIN_VERSION}" + MACOSX_BUNDLE_GUI_IDENTIFIER "${auv3_bundle_identifier}" + FOLDER "${target_ide_group}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_PACKAGE_TYPE XPC! + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${auv3_bundle_identifier}" + XCODE_GENERATE_SCHEME ON) + + # If standalone is also enabled, embed the .appex in the standalone app + if (YUP_ARG_HAS_STANDALONE) + add_dependencies (${YUP_ARG_STANDALONE_TARGET} ${target_name}_auv3_plugin) + + if (XCODE) + set_target_properties (${target_name}_auv3_plugin PROPERTIES + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_PACKAGE_TYPE XPC! + XCODE_EMBED_APP_EXTENSIONS "${target_name}_auv3_plugin.appex") + endif() + endif() + + yup_codesign_target (${target_name}_auv3_plugin "$") + + yup_audio_plugin_copy_bundle (${target_name} auv3) + + # Validation + set (auv3_pluginval_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions/${target_name}_auv3_plugin.appex") + + yup_validate_au_plugin ( + ${target_name}_auv3_plugin + "${YUP_ARG_PLUGIN_NAME}" + "${au_bundle_type}" + "${YUP_ARG_PLUGIN_AU_SUBTYPE}" + "${YUP_ARG_PLUGIN_AU_MANUFACTURER}") + + yup_validate_pluginval ( + ${target_name}_auv3_plugin + "${auv3_pluginval_path}") + +endfunction() diff --git a/cmake/yup.cmake b/cmake/yup.cmake index c53e74b34..820e95bd3 100644 --- a/cmake/yup.cmake +++ b/cmake/yup.cmake @@ -97,6 +97,7 @@ include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_clap.cmake) include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_vst3.cmake) include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_standalone.cmake) include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_au.cmake) +include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_auv3.cmake) include (${CMAKE_CURRENT_LIST_DIR}/plugins/yup_plugin_aax.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_modules.cmake) include (${CMAKE_CURRENT_LIST_DIR}/yup_codesign.cmake) diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index 8c563f1fb..94c795e1e 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -34,7 +34,7 @@ function (yup_audio_plugin) # Globals TARGET_NAME TARGET_VERSION TARGET_IDE_GROUP TARGET_APP_ID TARGET_APP_NAMESPACE TARGET_CXX_STANDARD # Plugin types - PLUGIN_CREATE_CLAP PLUGIN_CREATE_VST3 PLUGIN_CREATE_STANDALONE PLUGIN_CREATE_AU PLUGIN_CREATE_AAX) + PLUGIN_CREATE_CLAP PLUGIN_CREATE_VST3 PLUGIN_CREATE_STANDALONE PLUGIN_CREATE_AU PLUGIN_CREATE_AUv3 PLUGIN_CREATE_AAX) set (multi_value_args DEFINITIONS @@ -67,8 +67,8 @@ function (yup_audio_plugin) return() endif() - if (NOT YUP_ARG_PLUGIN_CREATE_CLAP AND NOT YUP_ARG_PLUGIN_CREATE_VST3 AND NOT YUP_ARG_PLUGIN_CREATE_STANDALONE AND NOT YUP_ARG_PLUGIN_CREATE_AU AND NOT YUP_ARG_PLUGIN_CREATE_AAX) - _yup_message (FATAL_ERROR "At least one plugin type must be enabled (CLAP, VST3, AU, AAX, or Standalone).") + if (NOT YUP_ARG_PLUGIN_CREATE_CLAP AND NOT YUP_ARG_PLUGIN_CREATE_VST3 AND NOT YUP_ARG_PLUGIN_CREATE_STANDALONE AND NOT YUP_ARG_PLUGIN_CREATE_AU AND NOT YUP_ARG_PLUGIN_CREATE_AUv3 AND NOT YUP_ARG_PLUGIN_CREATE_AAX) + _yup_message (FATAL_ERROR "At least one plugin type must be enabled (CLAP, VST3, AU, AUv3, AAX, or Standalone).") return() endif() @@ -189,6 +189,37 @@ function (yup_audio_plugin) "${YUP_ARG_UNPARSED_ARGUMENTS}") endif() + # ==== Build AUv3 plugin target (macOS only) + if (YUP_ARG_PLUGIN_CREATE_AUv3) + cmake_parse_arguments (AUv3_ARGS "" + "PLUGIN_IS_SYNTH;PLUGIN_AU_SUBTYPE;PLUGIN_AU_MANUFACTURER;PLUGIN_NAME;PLUGIN_VERSION;PLUGIN_ID;PLUGIN_VENDOR;PLUGIN_DESCRIPTION;PLUGIN_URL;PLUGIN_EMAIL;PLUGIN_IS_MONO" + "" ${YUP_ARG_UNPARSED_ARGUMENTS}) + + set (auv3_has_standalone OFF) + set (auv3_standalone_target "") + if (YUP_ARG_PLUGIN_CREATE_STANDALONE) + set (auv3_has_standalone ON) + set (auv3_standalone_target "${target_name}_standalone_plugin") + endif() + + yup_plugin_auv3 ( + TARGET_NAME ${target_name} + TARGET_CXX_STANDARD ${target_cxx_standard} + TARGET_IDE_GROUP ${target_ide_group} + TARGET_BUNDLE_ID ${target_bundle_id} + PLUGIN_IS_SYNTH ${AUv3_ARGS_PLUGIN_IS_SYNTH} + PLUGIN_NAME ${AUv3_ARGS_PLUGIN_NAME} + PLUGIN_VERSION ${AUv3_ARGS_PLUGIN_VERSION} + PLUGIN_AU_SUBTYPE ${AUv3_ARGS_PLUGIN_AU_SUBTYPE} + PLUGIN_AU_MANUFACTURER ${AUv3_ARGS_PLUGIN_AU_MANUFACTURER} + HAS_STANDALONE ${auv3_has_standalone} + STANDALONE_TARGET ${auv3_standalone_target} + SHARED_LIBS ${target_name}_shared + ADDITIONAL_LIBRARIES ${additional_libraries} + MODULES ${YUP_ARG_MODULES} + ${YUP_ARG_UNPARSED_ARGUMENTS}) + endif() + # ==== Create composite target for all enabled plugin formats set (_all_plugin_targets "") if (YUP_ARG_PLUGIN_CREATE_CLAP) @@ -206,6 +237,9 @@ function (yup_audio_plugin) if (YUP_ARG_PLUGIN_CREATE_AAX) list (APPEND _all_plugin_targets ${target_name}_aax_plugin) endif() + if (YUP_ARG_PLUGIN_CREATE_AUv3 AND YUP_PLATFORM_MAC) + list (APPEND _all_plugin_targets ${target_name}_auv3_plugin) + endif() add_custom_target (${target_name} DEPENDS ${_all_plugin_targets}) set_target_properties (${target_name} PROPERTIES @@ -227,6 +261,11 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) if ("${plugin_type}" STREQUAL "au") set (target_file_name "${target_name}_${plugin_type}_plugin.component") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/Components") + elseif ("${plugin_type}" STREQUAL "auv3") + set (target_file_name "${target_name}_${plugin_type}_plugin.appex") + set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions") + # AppExtension destination may not exist, create it + file (MAKE_DIRECTORY "${plugin_target_path}") else() set (target_file_name "${target_name}_${plugin_type}_plugin.${plugin_type}") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/${plugin_type_upper}") diff --git a/cmake/yup_dependencies.cmake b/cmake/yup_dependencies.cmake index ba8a1b13d..a32a5e148 100644 --- a/cmake/yup_dependencies.cmake +++ b/cmake/yup_dependencies.cmake @@ -155,6 +155,16 @@ function (_yup_collect_audio_plugin_host_dependencies definitions output_variabl "-framework CoreFoundation") endif() + _yup_definitions_enable ("${definitions}" YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 enable_auv3) + if (enable_auv3 AND YUP_PLATFORM_MAC) + list (APPEND dependencies + "-framework AVFoundation" + "-framework AudioToolbox" + "-framework CoreAudioKit" + "-framework CoreAudio" + "-framework CoreFoundation") + endif() + set (${output_variable} "${dependencies}" PARENT_SCOPE) endfunction() diff --git a/cmake/yup_modules.cmake b/cmake/yup_modules.cmake index 1431f3ab8..9f4438b1c 100644 --- a/cmake/yup_modules.cmake +++ b/cmake/yup_modules.cmake @@ -584,11 +584,14 @@ function (_yup_module_setup_plugin_client target_name plugin_client_target folde elseif (plugin_type STREQUAL "au") set (custom_target_name "${target_name}_au") set (plugin_define "YUP_AUDIO_PLUGIN_ENABLE_AU=1") + elseif (plugin_type STREQUAL "auv3") + set (custom_target_name "${target_name}_auv3") + set (plugin_define "YUP_AUDIO_PLUGIN_ENABLE_AUv3=1") elseif (plugin_type STREQUAL "aax") set (custom_target_name "${target_name}_aax") set (plugin_define "YUP_AUDIO_PLUGIN_ENABLE_AAX=1") else() - _yup_message (FATAL_ERROR "Invalid plugin type: ${plugin_type}. Must be either 'vst3', 'clap', 'au', 'aax' or 'standalone'") + _yup_message (FATAL_ERROR "Invalid plugin type: ${plugin_type}. Must be either 'vst3', 'clap', 'au', 'auv3', 'aax' or 'standalone'") endif() add_library (${custom_target_name} INTERFACE) diff --git a/examples/audiograph/CMakeLists.txt b/examples/audiograph/CMakeLists.txt index 170097ea5..486b0f279 100644 --- a/examples/audiograph/CMakeLists.txt +++ b/examples/audiograph/CMakeLists.txt @@ -33,6 +33,7 @@ yup_standalone_app ( DEFINITIONS YUP_AUDIO_PLUGIN_HOST_ENABLE_CLAP=1 YUP_AUDIO_PLUGIN_HOST_ENABLE_AU=1 + YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3=1 YUP_AUDIO_PLUGIN_HOST_ENABLE_VST3=1 MODULES yup::yup_core diff --git a/examples/audiograph/source/AudioGraphApp.cpp b/examples/audiograph/source/AudioGraphApp.cpp index b5bfe1219..14fc1ac14 100644 --- a/examples/audiograph/source/AudioGraphApp.cpp +++ b/examples/audiograph/source/AudioGraphApp.cpp @@ -37,6 +37,9 @@ yup::String formatTypeToString (yup::AudioPluginFormatType type) case yup::AudioPluginFormatType::audioUnit: return "AU"; + case yup::AudioPluginFormatType::audioUnitV3: + return "AUv3"; + default: return "?"; } @@ -73,6 +76,9 @@ AudioGraphApp::AudioGraphApp() #if YUP_AUDIO_PLUGIN_HOST_ENABLE_AU && YUP_MAC scanner->addFormat (std::make_unique()); #endif +#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC + scanner->addFormat (std::make_unique()); +#endif nodeRegistry.registerPluginFormats (scanner.get(), makeHostContext()); #endif diff --git a/examples/audiograph/source/nodes/NodeRegistry.h b/examples/audiograph/source/nodes/NodeRegistry.h index 30b1cc3d1..05b8b383f 100644 --- a/examples/audiograph/source/nodes/NodeRegistry.h +++ b/examples/audiograph/source/nodes/NodeRegistry.h @@ -420,6 +420,9 @@ class NodeRegistry case yup::AudioPluginFormatType::audioUnit: return pluginAuIdentifier; + case yup::AudioPluginFormatType::audioUnitV3: + return pluginAuIdentifier; // reuse AU identifier for now + default: return pluginUnknownIdentifier; } @@ -460,6 +463,9 @@ class NodeRegistry case yup::AudioPluginFormatType::audioUnit: formatTypeStr = "au"; break; + case yup::AudioPluginFormatType::audioUnitV3: + formatTypeStr = "auv3"; + break; default: formatTypeStr = "unknown"; break; diff --git a/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm new file mode 100644 index 000000000..a5dc5f723 --- /dev/null +++ b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm @@ -0,0 +1,1532 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "../yup_audio_plugin_client.h" + +#include "../common/yup_AudioPluginUtilities.h" + +#if ! defined(YUP_AUDIO_PLUGIN_ENABLE_AUv3) +#error "YUP_AUDIO_PLUGIN_ENABLE_AUv3 must be defined" +#endif + +#if YUP_MAC + +#import +#import +#import + +#import + +#include +#include +#include +#include +#include +#include +#include + +//============================================================================== + +extern "C" yup::AudioProcessor* createPluginProcessor(); + +namespace yup +{ + +//============================================================================== + +static String describePointer (const void* value) +{ + return "0x" + String::toHexString (static_cast (reinterpret_cast (value))); +} + +//============================================================================== + +struct AUScopedYupInitialiser +{ + AUScopedYupInitialiser() + { + if (numAUScopedInitInstances.fetch_add (1) == 0) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AUV3, "initialising YUP GUI"); + initialiseYup_GUI(); + } + } + + ~AUScopedYupInitialiser() + { + if (numAUScopedInitInstances.fetch_sub (1) == 1) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AUV3, "shutting down YUP GUI"); + shutdownYup_GUI(); + } + } + +private: + static std::atomic_int numAUScopedInitInstances; +}; + +std::atomic_int AUScopedYupInitialiser::numAUScopedInitInstances = 0; + +//============================================================================== + +static float getMaximumParameterValue (const AudioParameter& p) +{ + return p.getMaximumValue(); +} + +//============================================================================== + +} // namespace yup + +//============================================================================== +// Forward declarations + +@class YUPAUv3ViewController; + +//============================================================================== +// The main C++ AUv3 wrapper class + +namespace yup +{ + +class AudioPluginProcessorAUv3 final + : public AudioProcessorBase::Listener + , public AudioPlayHead + , private AudioParameter::Listener +{ +public: + //============================================================================== + + AudioPluginProcessorAUv3 (AUAudioUnit* audioUnit, + AudioComponentDescription, + AudioComponentInstantiationOptions, + NSError**) + : au (audioUnit) + { + processor.reset (::createPluginProcessor()); + init(); + } + + ~AudioPluginProcessorAUv3() override + { + if (editorObserverToken != nullptr) + { + if (paramTree.get() != nil) + [paramTree.get() removeParameterObserver:*editorObserverToken]; + + delete editorObserverToken; + editorObserverToken = nullptr; + } + + if (processor != nullptr) + { + processor->removeListener (this); + yup::endActiveParameterGestures (processor.get()); + processor->releaseResources(); + } + + unregisterAllParameterListeners(); + } + + //============================================================================== + + void init() + { + if (processor == nullptr) + return; + + inParameterChangedCallback = false; + + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + processor->setPlayHead (this); + + const auto& busLayout = processor->getBusLayout(); + + totalInChannels = 0; + for (const auto& bus : busLayout.getInputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + totalInChannels += bus.getNumChannels(); + + totalOutChannels = 0; + for (const auto& bus : busLayout.getOutputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + totalOutChannels += bus.getNumChannels(); + + // Build channel capabilities + { + channelCapabilities.reset ([[NSMutableArray alloc] init]); + + int maxInputCh = 0; + int maxOutputCh = 0; + + for (const auto& bus : busLayout.getInputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + maxInputCh = std::max (maxInputCh, bus.getNumChannels()); + + for (const auto& bus : busLayout.getOutputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + maxOutputCh = std::max (maxOutputCh, bus.getNumChannels()); + + [channelCapabilities.get() addObject:[NSNumber numberWithInteger:maxInputCh]]; + [channelCapabilities.get() addObject:[NSNumber numberWithInteger:maxOutputCh]]; + } + + internalRenderBlock = CreateObjCBlock (this, &AudioPluginProcessorAUv3::renderCallback); + + renderContextObserver = ^(const AudioUnitRenderContext*) {}; + + processor->setPlaybackConfiguration (static_cast (44100.0), + static_cast (maxFrames)); + processor->addListener (this); + + addParameters(); + addPresets(); + addAudioUnitBusses (true); + addAudioUnitBusses (false); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AUV3, "initialised: processor=" << describePointer (processor.get()) << ", inCh=" << String (totalInChannels) << ", outCh=" << String (totalOutChannels)); + } + + AudioProcessor* getProcessor() const noexcept { return processor.get(); } + + //============================================================================== + // Parameter management + + void addParameters() + { + if (processor == nullptr) + return; + + const auto parameters = processor->getParameters(); + addressForIndex.resize (parameters.size()); + + for (size_t i = 0; i < parameters.size(); ++i) + { + auto* param = parameters[i].get(); + param->addListener (this); + + const auto address = param->getHostParameterID(); + addressForIndex[i] = address; + } + + installParameterTree (createTopLevelNodes()); + } + + void installParameterTree (NSMutableArray* topLevelNodes) + { + if (editorObserverToken != nullptr) + { + [paramTree.get() removeParameterObserver:*editorObserverToken]; + delete editorObserverToken; + editorObserverToken = nullptr; + } + + @try + { + paramTree.reset ([AUParameterTree createTreeWithChildren:topLevelNodes]); + } + @catch (NSException* exception) + { + ignoreUnused (exception); + return; + } + + auto* self = this; + + [paramTree.get() setImplementorValueObserver:^(AUParameter* p, AUValue value) { + self->valueChangedFromHost (p, value); + }]; + + [paramTree.get() setImplementorValueProvider:^(AUParameter* p) { + return self->getValueForHost (p); + }]; + + [paramTree.get() setImplementorStringFromValueCallback:^(AUParameter* p, const AUValue* v) { + return self->stringFromValue (p, v); + }]; + + [paramTree.get() setImplementorValueFromStringCallback:^(AUParameter* p, NSString* str) { + return self->valueFromString (p, str); + }]; + + if (processor->hasEditor()) + { + editorObserverToken = new AUParameterObserverToken ([paramTree.get() tokenByAddingParameterObserver:^(AUParameterAddress, AUValue) { + }]); + } + } + + NSMutableArray* createTopLevelNodes() + { + auto* nodes = [[NSMutableArray alloc] init]; + + const auto parameters = processor->getParameters(); + + for (size_t i = 0; i < parameters.size(); ++i) + { + auto* param = parameters[i].get(); + const auto address = param->getHostParameterID(); + const auto name = param->getName().toNSString(); + const auto identifier = param->getID().toNSString(); + + AUValue minVal = static_cast (param->getMinimumValue()); + AUValue maxVal = static_cast (param->getMaximumValue()); + AUValue defaultVal = static_cast (param->getDefaultValue()); + + auto* auParam = [AUParameterTree createParameterWithIdentifier:identifier + name:name + address:address + min:minVal + max:maxVal + unit:kAudioUnitParameterUnit_Generic + unitName:nil + flags:0 + valueStrings:nil + dependentParameters:nil]; + + if (auParam != nullptr) + { + auParam.value = defaultVal; + [nodes addObject:auParam]; + } + } + + return [nodes autorelease]; + } + + void valueChangedFromHost (AUParameter* param, AUValue value) + { + if (param == nullptr) + return; + + AudioParameter* yupParam = getParamForAUAddress ([param address]); + if (yupParam == nullptr) + return; + + const auto normalisedValue = static_cast (value) / getMaximumParameterValue (*yupParam); + + if (! approximatelyEqual (normalisedValue, yupParam->getNormalizedValue())) + { + yupParam->setNormalizedValue (normalisedValue); + + inParameterChangedCallback = true; + yupParam->beginChangeGesture(); + yupParam->endChangeGesture(); + } + } + + AUValue getValueForHost (AUParameter* param) const + { + if (param == nullptr) + return 0; + + AudioParameter* yupParam = getParamForAUAddress ([param address]); + if (yupParam == nullptr) + return 0; + + return static_cast (yupParam->getNormalizedValue() * getMaximumParameterValue (*yupParam)); + } + + NSString* stringFromValue (AUParameter* param, const AUValue* value) const + { + if (param == nullptr || value == nullptr) + return @""; + + AudioParameter* yupParam = getParamForAUAddress ([param address]); + if (yupParam == nullptr) + return @""; + + const auto normalised = static_cast (*value) / getMaximumParameterValue (*yupParam); + return yupParam->convertToString (normalised).toNSString(); + } + + AUValue valueFromString (AUParameter* param, NSString* str) const + { + if (param == nullptr || str == nullptr) + return 0; + + AudioParameter* yupParam = getParamForAUAddress ([param address]); + if (yupParam == nullptr) + return 0; + + const auto normalised = yupParam->convertFromString (String::fromCFString ((__bridge CFStringRef) str)); + return static_cast (normalised * getMaximumParameterValue (*yupParam)); + } + + AudioParameter* getParamForAUAddress (AUParameterAddress address) const + { + for (size_t i = 0; i < addressForIndex.size(); ++i) + { + if (addressForIndex[i] == address) + { + const auto parameters = processor->getParameters(); + if (i < parameters.size()) + return parameters[i].get(); + } + } + + return nullptr; + } + + AudioParameter* getParamForIndex (int index) const + { + const auto parameters = processor->getParameters(); + if (isPositiveAndBelow (index, static_cast (parameters.size()))) + return parameters[index].get(); + + return nullptr; + } + + //============================================================================== + // Presets + + void addPresets() + { + if (processor == nullptr) + return; + + auto* newPresets = [[NSMutableArray alloc] init]; + + const int n = static_cast (processor->getNumPrograms()); + + for (int i = 0; i < n; ++i) + { + auto* preset = [[AUAudioUnitPreset alloc] init]; + [preset setName:processor->getProgramName (i).toNSString()]; + [preset setNumber:static_cast (i)]; + [newPresets addObject:preset]; + } + + std::lock_guard lock (factoryPresetsMutex); + factoryPresets.reset (newPresets); + } + + NSArray* getFactoryPresets() const + { + std::lock_guard lock (factoryPresetsMutex); + return factoryPresets.get(); + } + + AUAudioUnitPreset* getCurrentPreset() const + { + if (processor == nullptr) + return nil; + + std::lock_guard lock (factoryPresetsMutex); + const auto current = processor->getCurrentProgram(); + + if (isPositiveAndBelow (current, static_cast ([factoryPresets.get() count]))) + return [factoryPresets.get() objectAtIndex:static_cast (current)]; + + return nil; + } + + void setCurrentPreset (AUAudioUnitPreset* preset) + { + if (processor != nullptr && preset != nullptr) + processor->setCurrentProgram (static_cast ([preset number])); + } + + //============================================================================== + // State + + NSDictionary* getFullState() const + { + auto* retval = [[NSMutableDictionary alloc] init]; + + { + auto superRetval = ObjCMsgSendSuper*> (au, @selector (fullState)); + if (superRetval != nil) + [retval addEntriesFromDictionary:superRetval]; + } + + MemoryBlock state; + if (processor != nullptr) + processor->getStateInformation (state); + + if (state.getSize() > 0) + { + [retval setObject:[[NSData alloc] initWithBytes:state.getData() length:state.getSize()] + forKey:@"YUPProcessorState"]; + } + + return [retval autorelease]; + } + + void setFullState (NSDictionary* state) + { + if (state == nil || processor == nullptr) + return; + + auto* obj = [state objectForKey:@"YUPProcessorState"]; + if (obj == nil || ! [obj isKindOfClass:[NSData class]]) + return; + + auto* data = static_cast (obj); + const auto numBytes = static_cast ([data length]); + if (numBytes <= 0) + return; + + { + ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"allParameterValues"); + } + + processor->setStateInformation (static_cast ([data bytes]), numBytes); + + { + ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"allParameterValues"); + } + } + + //============================================================================== + // Bus management + + void addAudioUnitBusses (bool isInput) + { + auto* array = [[NSMutableArray alloc] init]; + + const auto& busLayout = processor->getBusLayout(); + const auto& buses = isInput ? busLayout.getInputBuses() : busLayout.getOutputBuses(); + + const int numBuses = static_cast (buses.size()); + + for (int i = 0; i < numBuses; ++i) + { + const auto& bus = buses[i]; + if (bus.getType() != AudioBus::Type::Audio) + continue; + + const int numChannels = bus.getNumChannels(); + + AVAudioChannelLayout* layout = nil; + if (numChannels <= 2) + layout = [[AVAudioChannelLayout alloc] initWithLayoutTag:(numChannels == 1 ? kAudioChannelLayoutTag_Mono : kAudioChannelLayoutTag_Stereo)]; + + AVAudioFormat* format = nil; + if (layout != nil) + { + format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0 channelLayout:layout]; + } + else + { + format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100.0 channels:static_cast (numChannels)]; + } + + NSError* error = nil; + auto* auBus = [[AUAudioUnitBus alloc] initWithFormat:format error:&error]; + + if (auBus != nil) + [array addObject:auBus]; + } + + if (isInput) + inputBusses.reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit:au + busType:AUAudioUnitBusTypeInput + busses:array]); + else + outputBusses.reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit:au + busType:AUAudioUnitBusTypeOutput + busses:array]); + } + + AUAudioUnitBusArray* getInputBusses() const { return inputBusses.get(); } + AUAudioUnitBusArray* getOutputBusses() const { return outputBusses.get(); } + + NSArray* getChannelCapabilities() const { return channelCapabilities.get(); } + + bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) + { + if (allocated) + return false; + + const auto isInput = ([auBus busType] == AUAudioUnitBusTypeInput); + const auto busIdx = static_cast ([auBus index]); + const auto newNumChannels = static_cast ([format channelCount]); + + const auto& busLayout = processor->getBusLayout(); + const auto& buses = isInput ? busLayout.getInputBuses() : busLayout.getOutputBuses(); + + if (! isPositiveAndBelow (busIdx, static_cast (buses.size()))) + return false; + + const auto& bus = buses[busIdx]; + if (bus.getType() != AudioBus::Type::Audio) + return false; + + return newNumChannels > 0 && newNumChannels <= bus.getMaxSupportedChannels(); + } + + //============================================================================== + // Render resources + + bool allocateRenderResourcesAndReturnError (NSError** outError) + { + allocated = false; + + if (processor == nullptr) + return false; + + if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) + return false; + + if (outError != nullptr) + *outError = nil; + + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + auto sampleRate = 44100.0; + for (auto* busses : { inputBusses.get(), outputBusses.get() }) + { + if ([busses count] > 0) + { + sampleRate = [[[busses objectAtIndexedSubscript:0] format] sampleRate]; + break; + } + } + + processor->setPlaybackConfiguration (sampleRate, static_cast (maxFrames)); + + midiMessages.ensureSize (2048); + midiMessages.clear(); + + hostMusicalContextCallback = [au musicalContextBlock]; + hostTransportStateCallback = [au transportStateBlock]; + + if (@available (macOS 10.13, *)) + midiOutputEventBlock = [au MIDIOutputEventBlock]; + + lastTimeStamp.mSampleTime = std::numeric_limits::max(); + lastTimeStamp.mFlags = 0; + + allocated = true; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AUV3, "render resources allocated: sr=" << String (sampleRate) << ", maxFrames=" << String (static_cast (maxFrames))); + + return true; + } + + void deallocateRenderResources() + { + allocated = false; + midiOutputEventBlock = nullptr; + hostMusicalContextCallback = nullptr; + hostTransportStateCallback = nullptr; + midiMessages.clear(); + + ObjCMsgSendSuper (au, @selector (deallocateRenderResources)); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AUV3, "render resources deallocated"); + } + + void reset() + { + midiMessages.clear(); + lastTimeStamp.mSampleTime = std::numeric_limits::max(); + lastTimeStamp.mFlags = 0; + } + + //============================================================================== + // Render callback + + AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, + const AudioTimeStamp* timestamp, + AUAudioFrameCount frameCount, + NSInteger outputBusNumber, + AudioBufferList* outputData, + const AURenderEvent* realtimeEventListHead, + AURenderPullInputBlock pullInputBlock) + { + if (processor == nullptr) + return kAudioUnitErr_NoConnection; + + const int numFrames = static_cast (frameCount); + + if (! approximatelyEqual (lastTimeStamp.mSampleTime, timestamp->mSampleTime)) + { + midiMessages.clear(); + + // Process events (MIDI and parameters) + processEvents (realtimeEventListHead, static_cast (timestamp->mSampleTime)); + + lastTimeStamp = *timestamp; + + // Prepare audio buffer + scratchBuffer.setSize (std::max (totalInChannels, totalOutChannels), numFrames); + scratchBuffer.clear(); + + const auto& busLayout = processor->getBusLayout(); + + // Pull inputs + { + int chIdx = 0; + const auto& inputBuses = busLayout.getInputBuses(); + + for (int busIdx = 0; busIdx < static_cast (inputBuses.size()); ++busIdx) + { + const auto& bus = inputBuses[busIdx]; + if (bus.getType() != AudioBus::Type::Audio) + continue; + + const int numCh = bus.getNumChannels(); + AudioBufferList* pullData = nullptr; + + if (pullInputBlock != nullptr) + { + AudioBufferList localBuffer = {}; + localBuffer.mNumberBuffers = static_cast (numCh); + + float* channelPtrs[16] = {}; + for (int ch = 0; ch < numCh && ch < 16; ++ch) + { + localBuffer.mBuffers[ch].mNumberChannels = 1; + localBuffer.mBuffers[ch].mData = scratchBuffer.getWritePointer (chIdx + ch); + localBuffer.mBuffers[ch].mDataByteSize = static_cast (numFrames * sizeof (float)); + } + + if (pullInputBlock (actionFlags, timestamp, frameCount, busIdx, &localBuffer) != noErr) + { + for (int ch = 0; ch < numCh; ++ch) + scratchBuffer.clear (chIdx + ch, 0, numFrames); + } + } + else + { + for (int ch = 0; ch < numCh; ++ch) + scratchBuffer.clear (chIdx + ch, 0, numFrames); + } + + chIdx += numCh; + } + } + + // Process audio + { + AudioPluginPlayHeadAU playHead (*this, timestamp); + + AudioProcessContext context { scratchBuffer, + midiMessages, + emptyParamChangeBuffer, + &playHead }; + + if (bypassHostParam != nullptr) + processAudioBlock (*processor, context, bypassHostParam->getNormalizedValue() > 0.5f); + else + processAudioBlock (*processor, context, false); + } + + sendMidi (static_cast (timestamp->mSampleTime + 0.5), frameCount); + } + + // Copy output data + if (outputData != nullptr && outputBusNumber >= 0) + { + const auto& outputBuses = processor->getBusLayout().getOutputBuses(); + int chIdx = 0; + + for (int busIdx = 0; busIdx < static_cast (outputBuses.size()); ++busIdx) + { + const auto& bus = outputBuses[busIdx]; + if (bus.getType() != AudioBus::Type::Audio) + continue; + + const int numCh = bus.getNumChannels(); + + if (busIdx == static_cast (outputBusNumber)) + { + for (int ch = 0; ch < numCh && ch < static_cast (outputData->mNumberBuffers); ++ch) + { + const auto* src = scratchBuffer.getReadPointer (chIdx + ch); + auto* dst = static_cast (outputData->mBuffers[ch].mData); + + std::copy (src, src + numFrames, dst); + } + } + + chIdx += numCh; + } + } + + return noErr; + } + + void processEvents (const AURenderEvent* realtimeEventListHead, AUEventSampleTime startTime) + { + for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) + { + switch (event->head.eventType) + { + case AURenderEventMIDI: + case AURenderEventMIDISysEx: + { + const AUMIDIEvent& midiEvent = event->MIDI; + midiMessages.addEvent (midiEvent.data, + static_cast (midiEvent.length), + static_cast (midiEvent.eventSampleTime - startTime)); + } + break; + + case AURenderEventParameter: + case AURenderEventParameterRamp: + { + const AUParameterEvent& paramEvent = event->parameter; + + if (auto* p = getParamForAUAddress (paramEvent.parameterAddress)) + { + auto normalisedValue = static_cast (paramEvent.value) / getMaximumParameterValue (*p); + p->setNormalizedValue (normalisedValue); + + inParameterChangedCallback = true; + } + } + break; + + default: + break; + } + } + } + + void sendMidi (int64_t baseTimeStamp, AUAudioFrameCount frameCount) + { + ignoreUnused (baseTimeStamp); + ignoreUnused (frameCount); + + if (! processor->producesMidi()) + return; + + if (@available (macOS 10.13, *)) + { + if (auto midiOut = midiOutputEventBlock) + { + for (const auto& metadata : midiMessages) + { + if (! isPositiveAndBelow (metadata.samplePosition, static_cast (frameCount))) + continue; + + midiOut (static_cast (metadata.samplePosition) + baseTimeStamp, + 0, + metadata.numBytes, + metadata.data); + } + } + } + } + + //============================================================================== + // Bypass + + bool getShouldBypassEffect() const + { + if (bypassHostParam != nullptr) + return bypassHostParam->getNormalizedValue() > 0.5f; + + return false; + } + + void setShouldBypassEffect (bool shouldBypass) + { + if (bypassHostParam != nullptr) + bypassHostParam->setNormalizedValue (shouldBypass ? 1.0f : 0.0f); + } + + //============================================================================== + // Properties + + NSTimeInterval getLatency() const + { + if (processor == nullptr) + return 0.0; + + return static_cast (processor->getLatencySamples()) / processor->getSampleRate(); + } + + NSTimeInterval getTailTime() const + { + if (processor == nullptr) + return 0.0; + + return static_cast (processor->getTailSamples()) / processor->getSampleRate(); + } + + bool getRenderingOffline() const + { + return processor != nullptr && processor->isNonRealtime(); + } + + void setRenderingOffline (bool offline) + { + if (processor != nullptr) + processor->setNonRealtime (offline); + } + + //============================================================================== + // MIDI + + int getVirtualMIDICableCount() const + { + return (processor != nullptr && processor->acceptsMidi()) ? 1 : 0; + } + + bool getSupportsMPE() const + { + return processor != nullptr && processor->supportsMPE(); + } + + NSArray* getMIDIOutputNames() const + { + if (processor != nullptr && processor->producesMidi()) + return @[@"MIDI Out"]; + + return @[]; + } + + //============================================================================== + // Transport / PlayHead + + std::optional getPosition() const override + { + PositionInfo info; + + info.setTimeInSamples (static_cast (lastTimeStamp.mSampleTime + 0.5)); + + if (processor != nullptr && processor->getSampleRate() > 0.0) + info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processor->getSampleRate()); + + double num = 0, den = 0, ppqPosition = 0; + NSInteger deltaSampleOffsetToNextBeat = 0; + double currentMeasureDownBeat = 0, bpm = 0; + + if (hostMusicalContextCallback != nullptr) + { + AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; + + if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &deltaSampleOffsetToNextBeat, ¤tMeasureDownBeat)) + { + info.setTimeSignature (TimeSignature { static_cast (num), static_cast (den) }); + info.setPpqPositionOfLastBarStart (currentMeasureDownBeat); + info.setBpm (bpm); + info.setPpqPosition (ppqPosition); + } + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + AUHostTransportStateFlags flags = 0; + + if (hostTransportStateCallback != nullptr) + { + AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; + + if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) + { + info.setTimeInSamples (static_cast (outCurrentSampleInTimeLine + 0.5)); + + if (processor != nullptr && processor->getSampleRate() > 0.0) + info.setTimeInSeconds (static_cast (outCurrentSampleInTimeLine) / processor->getSampleRate()); + + info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); + info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); + info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + } + } + + if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) + info.setHostTimeNs (static_cast (AudioConvertHostTimeToNanos (lastTimeStamp.mHostTime))); + + return info; + } + + //============================================================================== + // View configurations + + NSIndexSet* supportedViewConfigurations (NSArray* configs) const + { + auto* indices = [[NSMutableIndexSet alloc] init]; + + if (! processor->hasEditor()) + return [indices autorelease]; + + auto* editor = processor->createEditorAndMakeActive(); + if (editor == nullptr) + return [indices autorelease]; + + for (NSUInteger i = 0; i < [configs count]; ++i) + { + auto* config = [configs objectAtIndex:i]; + (void) config; + [indices addIndex:i]; + } + + delete editor; + return [indices autorelease]; + } + + void selectViewConfiguration (AUAudioUnitViewConfiguration* config) + { + viewConfigWidth = [config width]; + viewConfigHeight = [config height]; + } + + //============================================================================== + // Listener callbacks + + void audioProcessorChanged (AudioProcessorBase*, const ChangeDetails& details) override + { + if (details.programChanged) + { + { + ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"allParameterValues"); + } + addPresets(); + { + ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"allParameterValues"); + } + + { + ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"currentPreset"); + } + { + ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"currentPreset"); + } + } + + if (details.latencyChanged) + { + { + ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"latency"); + } + { + ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"latency"); + } + } + + if (details.parameterInfoChanged) + { + updateParameterTree(); + } + } + + void updateParameterTree() + { + unregisterAllParameterListeners(); + + { + ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"parameterTree"); + } + + installParameterTree (createTopLevelNodes()); + + { + ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"parameterTree"); + } + + registerAllParameterListeners(); + } + + void sendParameterEvent (int idx, float newValue, AUParameterAutomationEventType type) + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (! isPositiveAndBelow (static_cast (idx), addressForIndex.size())) + return; + + if (auto* p = [paramTree.get() parameterWithAddress:addressForIndex[idx]]) + { + auto* yupParam = getParamForIndex (idx); + if (yupParam == nullptr) + return; + + const auto value = newValue * getMaximumParameterValue (*yupParam); + + if (@available (macOS 10.12, *)) + { + [p setValue:value + originator:editorObserverToken + atHostTime:lastTimeStamp.mHostTime + eventType:type]; + } + else if (type == AUParameterAutomationEventTypeValue) + { + [p setValue:value originator:editorObserverToken]; + } + } + } + + void parameterValueChanged (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + ignoreUnused (parameter); + sendParameterEvent (indexInContainer, parameter->getNormalizedValue(), AUParameterAutomationEventTypeValue); + } + + void parameterGestureBegin (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + ignoreUnused (parameter); + sendParameterEvent (indexInContainer, parameter->getNormalizedValue(), AUParameterAutomationEventTypeTouch); + } + + void parameterGestureEnd (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + ignoreUnused (parameter); + sendParameterEvent (indexInContainer, parameter->getNormalizedValue(), AUParameterAutomationEventTypeRelease); + } + + //============================================================================== + // Instance management + + void registerAllParameterListeners() + { + const auto parameters = processor->getParameters(); + for (auto& p : parameters) + p->addListener (this); + } + + void unregisterAllParameterListeners() + { + const auto parameters = processor->getParameters(); + for (auto& p : parameters) + p->removeListener (this); + } + + //============================================================================== + // Accessors + + AUAudioUnit* getAudioUnit() const { return au; } + AUParameterTree* getParameterTree() const { return paramTree.get(); } + AUInternalRenderBlock getInternalRenderBlock() const { return internalRenderBlock; } + AURenderContextObserver getRenderContextObserver() const { return renderContextObserver; } + bool isRenderResourcesAllocated() const { return allocated; } + +private: + //============================================================================== + // PlayHead helper + + class AudioPluginPlayHeadAU final : public AudioPlayHead + { + public: + AudioPluginPlayHeadAU (AudioPluginProcessorAUv3& o, const AudioTimeStamp* ts) + : owner (o), timeStamp (ts) + { + } + + std::optional getPosition() const override + { + return owner.getPosition(); + } + + private: + AudioPluginProcessorAUv3& owner; + const AudioTimeStamp* timeStamp; + }; + + //============================================================================== + + AUAudioUnit* au = nil; + std::unique_ptr processor; + + int totalInChannels = 0; + int totalOutChannels = 0; + + std::vector addressForIndex; + + NSUniquePtr inputBusses; + NSUniquePtr outputBusses; + NSUniquePtr> channelCapabilities; + + NSUniquePtr paramTree; + AUParameterObserverToken* editorObserverToken = nullptr; + + mutable std::mutex factoryPresetsMutex; + NSUniquePtr> factoryPresets; + + ObjCBlock internalRenderBlock; + ObjCBlock renderContextObserver; + + MidiBuffer midiMessages; + ParameterChangeBuffer emptyParamChangeBuffer; + AudioBuffer scratchBuffer; + + AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; + + ObjCBlock hostMusicalContextCallback; + ObjCBlock hostTransportStateCallback; + + AudioTimeStamp lastTimeStamp; + + AudioParameter* bypassHostParam = nullptr; + + double viewConfigWidth = 0; + double viewConfigHeight = 0; + + ThreadLocalValue inParameterChangedCallback; + bool allocated = false; + + YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginProcessorAUv3) +}; + +//============================================================================== +// Dynamic AUAudioUnit subclass using YUP's ObjCClass + +struct AUAudioUnitSubclass final : public ObjCClass +{ + AUAudioUnitSubclass() + : ObjCClass ("YUPAUAudioUnit_") + { + addIvar ("cppObject"); + + YUP_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (initWithComponentDescription:options:error:yupClass:), [] (id self, + SEL, + AudioComponentDescription descr, + AudioComponentInstantiationOptions options, + NSError** error, + AudioPluginProcessorAUv3* cpp) { + self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + setThis (self, cpp); + return self; + }); + YUP_END_IGNORE_WARNINGS_GCC_LIKE + + addMethod (@selector (initWithComponentDescription:options:error:), [] (id self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) { + self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + + auto* cpp = new AudioPluginProcessorAUv3 (self, descr, options, error); + setThis (self, cpp); + return self; + }); + + addMethod (@selector (dealloc), [] (id self, SEL) { + auto* cpp = _this (self); + delete cpp; + setThis (self, nullptr); + }); + + // Internal render block + addMethod (@selector (internalRenderBlock), [] (id self, SEL) { + return _this (self)->getInternalRenderBlock(); + }); + + // Parameter tree + addMethod (@selector (parameterTree), [] (id self, SEL) { + return _this (self)->getParameterTree(); + }); + + // Busses + addMethod (@selector (inputBusses), [] (id self, SEL) { + return _this (self)->getInputBusses(); + }); + + addMethod (@selector (outputBusses), [] (id self, SEL) { + return _this (self)->getOutputBusses(); + }); + + addMethod (@selector (channelCapabilities), [] (id self, SEL) { + return _this (self)->getChannelCapabilities(); + }); + + addMethod (@selector (shouldChangeToFormat:forBus:), [] (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { + return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; + }); + + // Render resources + addMethod (@selector (allocateRenderResourcesAndReturnError:), [] (id self, SEL, NSError** error) { + return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; + }); + + addMethod (@selector (deallocateRenderResources), [] (id self, SEL) { + _this (self)->deallocateRenderResources(); + }); + + addMethod (@selector (renderResourcesAllocated), [] (id self, SEL) { + return _this (self)->isRenderResourcesAllocated() ? YES : NO; + }); + + addMethod (@selector (reset), [] (id self, SEL) { + _this (self)->reset(); + }); + + // State + addMethod (@selector (fullState), [] (id self, SEL) { + return _this (self)->getFullState(); + }); + + addMethod (@selector (setFullState:), [] (id self, SEL, NSDictionary* state) { + _this (self)->setFullState (state); + }); + + // Presets + addMethod (@selector (factoryPresets), [] (id self, SEL) { + return _this (self)->getFactoryPresets(); + }); + + addMethod (@selector (currentPreset), [] (id self, SEL) { + return _this (self)->getCurrentPreset(); + }); + + addMethod (@selector (setCurrentPreset:), [] (id self, SEL, AUAudioUnitPreset* preset) { + _this (self)->setCurrentPreset (preset); + }); + + // Latency / tail + addMethod (@selector (latency), [] (id self, SEL) { + return _this (self)->getLatency(); + }); + + addMethod (@selector (tailTime), [] (id self, SEL) { + return _this (self)->getTailTime(); + }); + + // Rendering offline + addMethod (@selector (isRenderingOffline), [] (id self, SEL) { + return _this (self)->getRenderingOffline() ? YES : NO; + }); + + addMethod (@selector (setRenderingOffline:), [] (id self, SEL, BOOL offline) { + _this (self)->setRenderingOffline (offline); + }); + + // Bypass + addMethod (@selector (shouldBypassEffect), [] (id self, SEL) { + return _this (self)->getShouldBypassEffect() ? YES : NO; + }); + + addMethod (@selector (setShouldBypassEffect:), [] (id self, SEL, BOOL bypass) { + _this (self)->setShouldBypassEffect (bypass); + }); + + // Can process in place + addMethod (@selector (canProcessInPlace), [] (id, SEL) { + return NO; + }); + + // MIDI + addMethod (@selector (virtualMIDICableCount), [] (id self, SEL) { + return _this (self)->getVirtualMIDICableCount(); + }); + + YUP_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (supportsMPE), [] (id self, SEL) { + return _this (self)->getSupportsMPE() ? YES : NO; + }); + YUP_END_IGNORE_WARNINGS_GCC_LIKE + + if (@available (macOS 10.13, *)) + { + addMethod (@selector (MIDIOutputNames), [] (id self, SEL) { + return _this (self)->getMIDIOutputNames(); + }); + } + + // View configurations + if (@available (macOS 10.13, *)) + { + addMethod (@selector (supportedViewConfigurations:), [] (id self, SEL, NSArray* configs) { + return _this (self)->supportedViewConfigurations (configs); + }); + + addMethod (@selector (selectViewConfiguration:), [] (id self, SEL, AUAudioUnitViewConfiguration* config) { + _this (self)->selectViewConfiguration (config); + }); + } + + // Render context observer + addMethod (@selector (renderContextObserver), [] (id self, SEL) { + return _this (self)->getRenderContextObserver(); + }); + + registerClass(); + } + + static AudioPluginProcessorAUv3* _this (id self) + { + return getIvar (self, "cppObject"); + } + + static void setThis (id self, AudioPluginProcessorAUv3* cpp) + { + object_setInstanceVariable (self, "cppObject", cpp); + } +}; + +//============================================================================== +// View controller C++ companion + +class AudioPluginViewControllerv3 +{ +public: + explicit AudioPluginViewControllerv3 (AUViewController* vc) + : myself (vc) + { + initialiseYup_GUI(); + } + + ~AudioPluginViewControllerv3() + { + if (processor != nullptr) + { + yup::endActiveParameterGestures (processor.get()); + processor->removeListener (this); + + if (auto* editor = processor->getActiveEditor()) + { + processor->editorBeingDeleted (editor); + delete editor; + } + } + } + + void loadView() + { + processor.reset (::createPluginProcessor()); + + if (processor == nullptr) + return; + + if (processor->hasEditor()) + { + if (auto* editor = processor->createEditorAndMakeActive()) + { + preferredSize = { editor->getWidth(), editor->getHeight() }; + + NSView* view = [[NSView alloc] initWithFrame:NSMakeRect (0, 0, preferredSize.width, preferredSize.height)]; + [myself setView:view]; + + editor->setVisible (true); + + auto options = ComponentNative::Options() + .withFlags (ComponentNative::defaultFlags & ~ComponentNative::decoratedWindow); + + editor->addToDesktop (options, (__bridge void*) view); + } + } + } + + void viewDidLayoutSubviews() + { + if (processor == nullptr) + return; + + if ([myself view] != nil) + { + if (auto* editor = processor->getActiveEditor()) + { + const auto bounds = [[myself view] bounds]; + editor->setBounds ({ 0.0f, + 0.0f, + static_cast (NSWidth (bounds)), + static_cast (NSHeight (bounds)) }); + } + } + } + + CGSize getPreferredContentSize() const + { + return CGSizeMake (static_cast (preferredSize.width), + static_cast (preferredSize.height)); + } + + AUAudioUnit* createAudioUnit (const AudioComponentDescription& desc, NSError** error) + { + if (processor == nullptr) + return nil; + + auto* cpp = new AudioPluginProcessorAUv3 (nil, desc, 0, error); + + static AUAudioUnitSubclass auClass; + auto* au = auClass.createInstance(); + au = ObjCMsgSendSuper (au, @selector (initWithComponentDescription:options:error:), desc, 0, error); + + if (au == nil) + { + delete cpp; + return nil; + } + + AUAudioUnitSubclass::setThis (au, cpp); + cpp->init(); + + return au; + } + + AudioProcessor* getProcessor() const { return processor.get(); } + +private: + AUViewController* myself = nil; + std::unique_ptr processor; + Rectangle preferredSize { 1.0f, 1.0f }; +}; + +} // namespace yup + +//============================================================================== +// Static AUViewController implementation + +@interface YUPAUv3ViewController : AUViewController +{ + std::unique_ptr cpp; +} +@end + +@implementation YUPAUv3ViewController + +- (instancetype) initWithNibName:(nullable NSString*)nib bundle:(nullable NSBundle*)bndl +{ + self = [super initWithNibName:nib bundle:bndl]; + cpp.reset (new yup::AudioPluginViewControllerv3 (self)); + return self; +} + +- (void) loadView +{ + cpp->loadView(); +} + +- (AUAudioUnit*) createAudioUnitWithComponentDescription:(AudioComponentDescription)desc + error:(NSError**)error +{ + return cpp->createAudioUnit (desc, error); +} + +- (CGSize) preferredContentSize +{ + return cpp->getPreferredContentSize(); +} + +- (void) viewDidLayoutSubviews +{ + cpp->viewDidLayoutSubviews(); +} + +- (void) viewDidLayout +{ + cpp->viewDidLayoutSubviews(); +} + +@end + +#endif // YUP_MAC diff --git a/modules/yup_audio_plugin_client/yup_audio_plugin_client.h b/modules/yup_audio_plugin_client/yup_audio_plugin_client.h index 0bd2afaff..6882c16a1 100644 --- a/modules/yup_audio_plugin_client/yup_audio_plugin_client.h +++ b/modules/yup_audio_plugin_client/yup_audio_plugin_client.h @@ -51,6 +51,14 @@ #define YUP_ENABLE_PLUGIN_CLIENT_AU_LOGGING 0 #endif +/** Config: YUP_ENABLE_PLUGIN_CLIENT_AUV3_LOGGING + + Enable debug logging for AUv3 plugin client. +*/ +#ifndef YUP_ENABLE_PLUGIN_CLIENT_AUV3_LOGGING +#define YUP_ENABLE_PLUGIN_CLIENT_AUV3_LOGGING 0 +#endif + /** Config: YUP_ENABLE_PLUGIN_CLIENT_CLAP_LOGGING Enable debug logging for CLAP plugin client. diff --git a/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h b/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h index f0da8990f..3ea1c88f4 100644 --- a/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h +++ b/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h @@ -28,6 +28,7 @@ enum class AudioPluginFormatType vst3, clap, audioUnit, + audioUnitV3, unknown }; diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h new file mode 100644 index 000000000..ba434ba89 --- /dev/null +++ b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h @@ -0,0 +1,60 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC + +namespace yup +{ + +/** + AudioPluginFormat implementation for AUv3 (macOS only). + + AUv3 plugins are App Extensions (`.appex`) discovered through + `AVAudioUnitComponentManager` which enumerates the system + App Extension registry. +*/ +class AUv3Format : public AudioPluginFormat +{ +public: + AUv3Format(); + ~AUv3Format() override; + + AudioPluginFormatType getFormatType() const override; + String getFormatName() const override; + StringArray getFileExtensions() const override; + + /** Returns an empty FileSearchPath — AUv3 uses the App Extension registry. */ + FileSearchPath getDefaultSearchPaths() const override; + + /** + Passing an invalid File triggers full AUv3 registry scan. + Otherwise scans only the component matching the file's bundle identifier. + */ + ResultValue> scanFile (const File& file) override; + + ResultValue> loadPlugin ( + const AudioPluginDescription& description, + const AudioPluginHostContext& context) override; +}; + +} // namespace yup + +#endif // YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm new file mode 100644 index 000000000..35d455159 --- /dev/null +++ b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm @@ -0,0 +1,646 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC + +namespace yup +{ + +//============================================================================== +// AUv3Instance - wraps an AUAudioUnit for hosting + +class AUv3Instance final : public AudioPluginInstance +{ +public: + //============================================================================== + + static std::unique_ptr create (const AudioPluginDescription& description, + const AudioPluginHostContext& context) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "creating AUv3 instance from: " << description.identifier); + + // Parse the identifier into AudioComponentDescription + auto components = parseIdentifierToComponentDescription (description.identifier); + if (! components.has_value()) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "failed to parse identifier"); + return nullptr; + } + + auto instance = std::make_unique (description, context); + instance->description = description; + + return instance; + } + + //============================================================================== + + void prepareToPlay (float sampleRate, int samplesPerBlock) override + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "prepareToPlay: sr=" << String (sampleRate) << ", block=" << String (samplesPerBlock)); + + if (audioUnit == nil) + { + // Instantiate the AUAudioUnit + auto components = parseIdentifierToComponentDescription (description.identifier); + + if (components.has_value()) + { + dispatch_semaphore_t sem = dispatch_semaphore_create (0); + + [AUAudioUnit instantiateWithComponentDescription:*components + options:0 + completionHandler:^(AUAudioUnit* au, NSError* error) { + if (au != nil) + audioUnit = au; + + if (error != nil) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "instantiate failed: " << String::fromCFString ((__bridge CFStringRef)[error localizedDescription])); + } + + dispatch_semaphore_signal (sem); + }]; + + dispatch_semaphore_wait (sem, DISPATCH_TIME_FOREVER); + } + } + + if (audioUnit == nil) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "audioUnit is nil after instantiate"); + return; + } + + // Set maximum frames to render + [audioUnit setMaximumFramesToRender:static_cast (samplesPerBlock)]; + + // Allocate render resources + NSError* error = nil; + if (! [audioUnit allocateRenderResourcesAndReturnError:&error]) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "allocateRenderResources failed: " << (error != nil ? String::fromCFString ((__bridge CFStringRef)[error localizedDescription]) : String ())); + return; + } + + numInputChannels = static_cast ([audioUnit.inputBusses count]); + numOutputChannels = static_cast ([audioUnit.outputBusses count]); + + // Collect parameter info + collectParameters(); + + // Setup render observer + setupRenderObserver(); + + // Store block size + blockSize = samplesPerBlock; + scratchBuffer.setSize (std::max (numInputChannels, numOutputChannels), blockSize); + + prepared = true; + + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "prepareToPlay complete: inCh=" << String (numInputChannels) << ", outCh=" << String (numOutputChannels)); + } + + void releaseResources() override + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "releaseResources"); + + removeRenderObserver(); + + if (audioUnit != nil) + [audioUnit deallocateRenderResources]; + + prepared = false; + } + + //============================================================================== + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + if (audioUnit == nil || ! prepared) + return; + + const int numFrames = buffer.getNumSamples(); + + AudioTimeStamp timeStamp = {}; + timeStamp.mFlags = kAudioTimeStampSampleTimeValid; + timeStamp.mSampleTime = static_cast (processedFrames); + + // Pull input if this is an effect + AudioUnitRenderActionFlags actionFlags = 0; + + // For instruments, output renders directly + // For effects, we need to use AudioUnitRender with input callback + if (numInputChannels > 0) + { + // Prepare input data for the effect + scratchBuffer.clear(); + + for (int ch = 0; ch < numInputChannels && ch < buffer.getNumChannels(); ++ch) + { + const auto* src = buffer.getReadPointer (ch); + auto* dst = scratchBuffer.getWritePointer (ch); + std::copy (src, src + numFrames, dst); + } + + // Render through the audio unit + AudioBufferList* inputBufferList = createBufferListForChannels (numInputChannels, scratchBuffer, numFrames); + AudioBufferList* outputBufferList = createBufferListForChannels (numOutputChannels, buffer, numFrames); + + AURenderPullInputBlock pullInput = ^AUAudioUnitStatus (AudioUnitRenderActionFlags* flags, + const AudioTimeStamp* ts, + AUAudioFrameCount frames, + NSInteger busNumber, + AudioBufferList* data) { + ignoreUnused (flags, ts); + + if (busNumber >= 0 && busNumber < numInputChannels) + { + // Copy from our input buffer + for (UInt32 i = 0; i < data->mNumberBuffers; ++i) + { + const auto* src = scratchBuffer.getReadPointer (static_cast (busNumber)); + auto* dst = static_cast (data->mBuffers[i].mData); + memcpy (dst, src, frames * sizeof (float)); + } + } + + return noErr; + }; + + // Process via internal render block or AudioUnitRender + AUInternalRenderBlock renderBlock = [audioUnit internalRenderBlock]; + if (renderBlock) + { + renderBlock (&actionFlags, &timeStamp, static_cast (numFrames), + 0, outputBufferList, nullptr, pullInput); + } + + freeBufferList (inputBufferList); + freeBufferList (outputBufferList); + } + else + { + // Instrument - render directly to output + AudioTimeStamp ts = {}; + ts.mFlags = kAudioTimeStampSampleTimeValid; + ts.mSampleTime = static_cast (processedFrames); + + AudioBufferList* outBufList = createBufferListForChannels (numOutputChannels, buffer, numFrames); + + AUInternalRenderBlock renderBlock = [audioUnit internalRenderBlock]; + if (renderBlock) + { + AudioUnitRenderActionFlags flags = 0; + renderBlock (&flags, &ts, static_cast (numFrames), + 0, outBufList, nullptr, nullptr); + } + + freeBufferList (outBufList); + } + + // Handle MIDI + if (! midiMessages.isEmpty() && [audioUnit respondsToSelector:@selector (scheduleMIDIEventBlock)]) + { + AUScheduleMIDIEventBlock midiBlock = [audioUnit scheduleMIDIEventBlock]; + if (midiBlock) + { + for (const auto& metadata : midiMessages) + { + midiBlock (static_cast (metadata.samplePosition + processedFrames), + 0, + metadata.numBytes, + metadata.data); + } + } + } + + processedFrames += numFrames; + } + + //============================================================================== + + int getNumPrograms() override + { + return static_cast ([[audioUnit factoryPresets] count]); + } + + String getProgramName (int index) override + { + if (audioUnit == nil) + return {}; + + auto* presets = [audioUnit factoryPresets]; + if (! isPositiveAndBelow (index, static_cast ([presets count]))) + return {}; + + auto* preset = [presets objectAtIndex:static_cast (index)]; + return String::fromCFString ((__bridge CFStringRef)[preset name]); + } + + int getCurrentProgram() override + { + if (audioUnit == nil) + return 0; + + auto* preset = [audioUnit currentPreset]; + if (preset != nil) + return static_cast ([preset number]); + + return 0; + } + + void setCurrentProgram (int index) override + { + if (audioUnit == nil) + return; + + auto* presets = [audioUnit factoryPresets]; + if (isPositiveAndBelow (index, static_cast ([presets count]))) + { + auto* preset = [presets objectAtIndex:static_cast (index)]; + [audioUnit setCurrentPreset:preset]; + } + } + + //============================================================================== + + bool hasEditor() const override + { + return audioUnit != nil && [audioUnit respondsToSelector:@selector (requestViewControllerWithCompletionHandler:)]; + } + + AudioProcessorEditor* createEditor() override + { + if (audioUnit == nil) + return nullptr; + + return new AUv3Editor (*this); + } + + //============================================================================== + + void getStateInformation (MemoryBlock& destData) override + { + if (audioUnit == nil) + return; + + auto* state = [audioUnit fullState]; + if (state == nil) + return; + + auto* data = [NSJSONSerialization dataWithJSONObject:state options:0 error:nil]; + if (data != nil) + { + destData.setSize ([data length]); + memcpy (destData.getData(), [data bytes], [data length]); + } + } + + void setStateInformation (const char* data, int sizeInBytes) override + { + if (audioUnit == nil || sizeInBytes <= 0) + return; + + auto* nsData = [NSData dataWithBytes:data length:static_cast (sizeInBytes)]; + auto* state = [NSJSONSerialization JSONObjectWithData:nsData options:0 error:nil]; + + if (state != nil) + // [audioUnit setFullState:state]; + [audioUnit setFullStateForDocument:state]; + } + + //============================================================================== + + const AudioPluginDescription& getPluginDescription() const + { + return description; + } + + AUAudioUnit* getAudioUnit() const { return audioUnit; } + + float getSampleRate() const override { return sampleRate; } + + int getBlockSize() const override { return blockSize; } + +private: + //============================================================================== + + AUv3Instance (const AudioPluginDescription& desc, + const AudioPluginHostContext&) + : description (desc) + { + } + + ~AUv3Instance() override + { + releaseResources(); + audioUnit = nil; + } + + //============================================================================== + + static std::optional parseIdentifierToComponentDescription (const String& identifier) + { + // Format: "type/subtype/manufacturer" (four-char codes) + auto parts = StringArray::fromTokens (identifier, "/", ""); + if (parts.size() < 3) + return std::nullopt; + + auto parseOSType = [] (const String& s) -> OSType { + if (s.length() < 4) + return 0; + + return static_cast ( + (static_cast (static_cast (s[0])) << 24) | + (static_cast (static_cast (s[1])) << 16) | + (static_cast (static_cast (s[2])) << 8) | + static_cast (static_cast (s[3]))) + ); + }; + + AudioComponentDescription result = {}; + result.componentType = parseOSType (parts[0]); + result.componentSubType = parseOSType (parts[1]); + result.componentManufacturer = parseOSType (parts[2]); + + return result; + } + + //============================================================================== + + void collectParameters() + { + hostedParameters.clear(); + + if (audioUnit == nil) + return; + + auto* paramTree = [audioUnit parameterTree]; + if (paramTree == nil) + return; + + // Collect all parameters from the tree + collectParametersFromNode (paramTree); + } + + void collectParametersFromNode (AUParameterNode* node) + { + if (node == nil) + return; + + if ([node isKindOfClass:[AUParameter class]]) + { + auto* param = static_cast (node); + + hostedParameters.push_back (param); + + // Observe parameter changes + [param setValueObserver:^(AUValue value) { + // Parameter changed from host side + }]; + } + else if ([node isKindOfClass:[AUParameterGroup class]]) + { + // Recurse into groups - AUParameterGroup is a subclass with children + // The actual API to enumerate children varies; for now we collect top-level + } + } + + //============================================================================== + + void setupRenderObserver() + { + if (audioUnit == nil) + return; + + // Observe latency changes + renderObserver = (__bridge void*) [audioUnit addObserver:^(AudioUnitProperty property, const AudioUnitParameterValue value) { + ignoreUnused (property, value); + }]; + } + + void removeRenderObserver() + { + if (audioUnit != nil && renderObserver != nullptr) + { + // Remove observer logic + renderObserver = nullptr; + } + } + + //============================================================================== + + static AudioBufferList* createBufferListForChannels (int numChannels, AudioBuffer& buffer, int numFrames) + { + const size_t bufferListSize = offsetof (AudioBufferList, mBuffers) + static_cast (numChannels) * sizeof (AudioBuffer); + auto* bufList = static_cast (malloc (bufferListSize)); + bufList->mNumberBuffers = static_cast (numChannels); + + for (int i = 0; i < numChannels; ++i) + { + bufList->mBuffers[i].mNumberChannels = 1; + bufList->mBuffers[i].mDataByteSize = static_cast (numFrames * sizeof (float)); + bufList->mBuffers[i].mData = buffer.getWritePointer (i); + } + + return bufList; + } + + static void freeBufferList (AudioBufferList* bufList) + { + if (bufList != nullptr) + free (bufList); + } + + //============================================================================== + + AudioPluginDescription description; + AUAudioUnit* audioUnit = nil; + int numInputChannels = 0; + int numOutputChannels = 0; + float sampleRate = 44100.0f; + int blockSize = 512; + bool prepared = false; + int64_t processedFrames = 0; + + std::vector hostedParameters; + void* renderObserver = nullptr; + + AudioBuffer scratchBuffer; + + //============================================================================== + + class AUv3Editor final : public AudioProcessorEditor + { + public: + AUv3Editor (AUv3Instance& instance) + : AudioProcessorEditor (&instance) + { + if (@available (macOS 10.13, *)) + { + auto* au = instance.getAudioUnit(); + + [au requestViewControllerWithCompletionHandler:^(AUViewControllerBase* vc) { + if (vc != nil) + { + viewController = vc; + + // Get the view from the view controller + NSView* view = [vc view]; + if (view != nil) + { + const auto size = view.bounds.size; + setSize (static_cast (size.width), static_cast (size.height)); + } + } + }]; + } + } + + void resized() override + { + if (viewController != nil) + { + auto* view = [viewController view]; + if (view != nil) + { + view.frame = NSMakeRect (0, 0, getWidth(), getHeight()); + } + } + } + + AUViewControllerBase* getViewController() const { return viewController; } + + private: + AUViewControllerBase* viewController = nil; + }; + + YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AUv3Instance) +}; + +//============================================================================== +// AUv3Format implementation + +AUv3Format::AUv3Format() = default; +AUv3Format::~AUv3Format() = default; + +AudioPluginFormatType AUv3Format::getFormatType() const +{ + return AudioPluginFormatType::audioUnitV3; +} + +String AUv3Format::getFormatName() const +{ + return "AUv3"; +} + +StringArray AUv3Format::getFileExtensions() const +{ + return {}; +} + +FileSearchPath AUv3Format::getDefaultSearchPaths() const +{ + return {}; +} + +ResultValue> AUv3Format::scanFile (const File&) +{ + std::vector results; + + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "scanning AUv3 components via AVAudioUnitComponentManager"); + + // Use AVAudioUnitComponentManager to discover AUv3 plugins + AVAudioUnitComponentManager* manager = [AVAudioUnitComponentManager sharedAudioUnitComponentManager]; + + // Get all available components + NSArray* components = [manager componentsMatchingPredicate:nil]; + + for (AVAudioUnitComponent* component in components) + { + AudioPluginDescription desc; + + desc.formatType = AudioPluginFormatType::audioUnitV3; + desc.name = String::fromCFString ((__bridge CFStringRef)[component name]); + + auto* acd = [component audioComponentDescription]; + desc.identifier = String::createStringFromData (reinterpret_cast (&acd.componentType), 4) + + "/" + + String::createStringFromData (reinterpret_cast (&acd.componentSubType), 4) + + "/" + + String::createStringFromData (reinterpret_cast (&acd.componentManufacturer), 4); + + desc.vendor = String::fromCFString ((__bridge CFStringRef)[component manufacturerName]); + desc.version = (component.versionString != nil) ? String::fromCFString ((__bridge CFStringRef)[component versionString]) : String(); + + desc.isInstrument = (acd.componentType == kAudioUnitType_MusicDevice + || acd.componentType == kAudioUnitType_MusicEffect); + desc.isEffect = (acd.componentType == kAudioUnitType_Effect + || acd.componentType == kAudioUnitType_MusicEffect); + + if (desc.isInstrument) + desc.numOutputChannels = 2; + + if (desc.isEffect) + { + desc.numInputChannels = 2; + desc.numOutputChannels = 2; + } + + desc.numMidiInputPorts = [component hasMIDIInput] ? 1 : 0; + desc.numMidiOutputPorts = [component hasMIDIOutput] ? 1 : 0; + + desc.fileOrBundlePath = {}; // App Extensions don't expose bundle path directly + + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "found: " << desc.name << " [" << desc.identifier << "]"); + results.push_back (std::move (desc)); + } + + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "scan complete: " << results.size() << " AUv3 components found"); + + if (results.empty()) + return makeResultValueFail ("No AUv3 components found in registry"); + + return makeResultValueOk (std::move (results)); +} + +ResultValue> AUv3Format::loadPlugin ( + const AudioPluginDescription& description, + const AudioPluginHostContext& context) +{ + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "loading: " << description.name << " [" << description.identifier << "]"); + + auto instance = AUv3Instance::create (description, context); + + if (instance == nullptr) + { + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "load failed: " << description.name); + return makeResultValueFail ("Failed to instantiate AUv3 plugin: " + description.name); + } + + YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "loaded: " << description.name); + return makeResultValueOk (std::move (instance)); +} + +} // namespace yup + +#endif // YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp index 018fb2629..d55a2eaf5 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp @@ -99,3 +99,11 @@ using CLAPModuleHandle = void*; #include "native/yup_AudioPluginInstance_AUv2.mm" #endif + +#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC +#import +#import +#import + +#include "native/yup_AudioPluginInstance_AUv3.mm" +#endif diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h index d4f68f1cc..3f740ef3d 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h @@ -28,7 +28,7 @@ vendor: yup version: 1.0.0 name: YUP Audio Plugin Host - description: In-process hosting of VST3, CLAP, and AUv2 audio plugins. + description: In-process hosting of VST3, CLAP, AUv2, and AUv3 audio plugins. website: https://github.com/kunitoki/yup license: ISC @@ -52,6 +52,14 @@ #define YUP_ENABLE_PLUGIN_HOST_AU_LOGGING 0 #endif +/** Config: YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING + + Enable debug logging for AUv3 plugin scanning and loading. +*/ +#ifndef YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING +#define YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING 0 +#endif + /** Config: YUP_ENABLE_PLUGIN_HOST_CLAP_LOGGING Enable debug logging for CLAP plugin scanning and loading. @@ -83,3 +91,4 @@ #include "native/yup_AudioPluginInstance_VST3.h" #include "native/yup_AudioPluginInstance_CLAP.h" #include "native/yup_AudioPluginInstance_AUv2.h" +#include "native/yup_AudioPluginInstance_AUv3.h" diff --git a/modules/yup_gui/yup_gui.h b/modules/yup_gui/yup_gui.h index 4377947a8..c19a87d3d 100644 --- a/modules/yup_gui/yup_gui.h +++ b/modules/yup_gui/yup_gui.h @@ -66,7 +66,7 @@ Enable logging of windowing events like movement, resizes, mouse interactions. */ #ifndef YUP_ENABLE_GUI_WINDOWING_LOGGING -#define YUP_ENABLE_GUI_WINDOWING_LOGGING 0 +#define YUP_ENABLE_GUI_WINDOWING_LOGGING 1 #endif //============================================================================== From a075d73168c826167726897ae1a36317e459ee60 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 21:03:17 +0200 Subject: [PATCH 02/10] More tweaks --- README.md | 10 +++++----- cmake/plugins/yup_plugin_auv3.cmake | 10 ++-------- cmake/yup_audio_plugin.cmake | 12 +++++++++--- examples/plugin/CMakeLists.txt | 1 + 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index eeebbec52..b052f8d96 100644 --- a/README.md +++ b/README.md @@ -135,16 +135,16 @@ YUP is usable for experimentation, examples, prototypes, and contributors who ar ## Supported Plugin Formats | | **CLAP** | **VST3** | **VST2** | **AUv3** | **AUv2** | **AAX** | **LV2** | |--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:-------------------------:|:---------------------:|:---------------------:| -| **Windows** | :white_check_mark: | :white_check_mark: | | | | :construction: | | -| **macOS** | :white_check_mark: | :white_check_mark: | | :construction: | :white_check_mark: | :construction: | | -| **Linux** | :construction: | :construction: | | | | | | +| **Windows** | :white_check_mark: | :white_check_mark: | | | | :construction: | :construction: | +| **macOS** | :white_check_mark: | :white_check_mark: | | :construction: | :white_check_mark: | :construction: | :construction: | +| **Linux** | :construction: | :construction: | | | | | :construction: | ## Supported Plugin Hosting Formats | | **CLAP** | **VST3** | **VST2** | **AUv3** | **AUv2** | **AAX** | **LV2** | |--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:-------------------------:|:---------------------:|:---------------------:| -| **Windows** | :construction: | :construction: | | | | | | -| **macOS** | :white_check_mark: | :white_check_mark: | | | :white_check_mark: | | | +| **Windows** | :white_check_mark: | :white_check_mark: | | | | | | +| **macOS** | :white_check_mark: | :white_check_mark: | | :construction: | :white_check_mark: | | | | **Linux** | :construction: | :construction: | | | | | | diff --git a/cmake/plugins/yup_plugin_auv3.cmake b/cmake/plugins/yup_plugin_auv3.cmake index e88bf958a..2ea488b61 100644 --- a/cmake/plugins/yup_plugin_auv3.cmake +++ b/cmake/plugins/yup_plugin_auv3.cmake @@ -33,11 +33,6 @@ function (yup_plugin_auv3) cmake_parse_arguments (YUP_ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN}) - if (NOT YUP_PLATFORM_MAC) - _yup_message (WARNING "AUv3 plugins are only supported on macOS. Skipping AUv3 target.") - return() - endif() - set (target_name "${YUP_ARG_TARGET_NAME}") set (target_cxx_standard "${YUP_ARG_TARGET_CXX_STANDARD}") set (target_ide_group "${YUP_ARG_TARGET_IDE_GROUP}") @@ -148,9 +143,8 @@ function (yup_plugin_auv3) add_dependencies (${YUP_ARG_STANDALONE_TARGET} ${target_name}_auv3_plugin) if (XCODE) - set_target_properties (${target_name}_auv3_plugin PROPERTIES - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_PACKAGE_TYPE XPC! - XCODE_EMBED_APP_EXTENSIONS "${target_name}_auv3_plugin.appex") + set_target_properties (${YUP_ARG_STANDALONE_TARGET} PROPERTIES + XCODE_EMBED_APP_EXTENSIONS ${target_name}_auv3_plugin) endif() endif() diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index 94c795e1e..b4481bd5c 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -63,7 +63,7 @@ function (yup_audio_plugin) # ==== Validation stage if (NOT YUP_PLATFORM_DESKTOP) - _yup_message (FATAL_ERROR "Audio plugins are not supported on emscripten or android.") + _yup_message (FATAL_ERROR "Audio plugins are not supported on emscripten or mobile (yet).") return() endif() @@ -264,13 +264,13 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) elseif ("${plugin_type}" STREQUAL "auv3") set (target_file_name "${target_name}_${plugin_type}_plugin.appex") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions") - # AppExtension destination may not exist, create it - file (MAKE_DIRECTORY "${plugin_target_path}") else() set (target_file_name "${target_name}_${plugin_type}_plugin.${plugin_type}") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/${plugin_type_upper}") endif() + file (MAKE_DIRECTORY "${plugin_target_path}") + set (plugin_path "${plugin_target_path}/${target_file_name}") if (NOT EXISTS ${plugin_target_path} AND NOT "${plugin_type}" STREQUAL "clap") @@ -308,6 +308,12 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" COMMENT "Copying AU plugin ${plugin_type_upper} to ${plugin_path}" VERBATIM) + elseif ("${plugin_type}" STREQUAL "auv3") + add_custom_command(TARGET ${dependency_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" + COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" + COMMENT "Copying AUv3 plugin ${plugin_type_upper} to ${plugin_path}" + VERBATIM) elseif ("${plugin_type}" STREQUAL "aax") _yup_message (STATUS "AAX plugin bundle copy not yet implemented for development") else() diff --git a/examples/plugin/CMakeLists.txt b/examples/plugin/CMakeLists.txt index 51a2be44d..ceb80f9ea 100644 --- a/examples/plugin/CMakeLists.txt +++ b/examples/plugin/CMakeLists.txt @@ -64,6 +64,7 @@ yup_audio_plugin ( PLUGIN_CREATE_CLAP ON PLUGIN_CREATE_VST3 ON PLUGIN_CREATE_AU ON + PLUGIN_CREATE_AUv3 ON PLUGIN_CREATE_STANDALONE ON PLUGIN_CREATE_AAX ${plugin_create_aax} DEFINITIONS From 1833535ab16c30ec586c00be920bfe03d3a37cff Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 23:05:51 +0200 Subject: [PATCH 03/10] Remove AUv3 hosting --- .../mac/AudioUnitV3ContainerInfo.plist.in | 28 + cmake/platforms/mac/AudioUnitV3Info.plist.in | 36 +- cmake/plugins/yup_plugin_auv3.cmake | 1 + cmake/yup_audio_plugin.cmake | 8 +- cmake/yup_dependencies.cmake | 10 - examples/audiograph/source/AudioGraphApp.cpp | 7 - .../audiograph/source/nodes/NodeRegistry.h | 6 - .../auv3/yup_audio_plugin_client_AUv3.mm | 101 +-- .../host/yup_AudioPluginFormatType.h | 1 - .../native/yup_AudioPluginInstance_AUv3.h | 60 -- .../native/yup_AudioPluginInstance_AUv3.mm | 646 ------------------ .../yup_audio_plugin_host.cpp | 8 - .../yup_audio_plugin_host.h | 11 +- .../processors/yup_AudioProcessorBase.h | 11 + 14 files changed, 122 insertions(+), 812 deletions(-) create mode 100644 cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in delete mode 100644 modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h delete mode 100644 modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm diff --git a/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in b/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in new file mode 100644 index 000000000..a6d2801a3 --- /dev/null +++ b/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleIdentifier + @auv3_container_bundle_identifier@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @PLUGIN_AU_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + @PLUGIN_AU_VERSION@ + CFBundleVersion + @PLUGIN_AU_VERSION@ + NSHumanReadableCopyright + Copyright (c) 2026 - kunitoki@gmail.com + NSHighResolutionCapable + + LSUIElement + + + diff --git a/cmake/platforms/mac/AudioUnitV3Info.plist.in b/cmake/platforms/mac/AudioUnitV3Info.plist.in index 453b9c397..cd2853525 100644 --- a/cmake/platforms/mac/AudioUnitV3Info.plist.in +++ b/cmake/platforms/mac/AudioUnitV3Info.plist.in @@ -32,26 +32,24 @@ com.apple.AudioUnit-UI NSExtensionAttributes - AudioComponentBundle - + AudioComponents + + + type + @PLUGIN_AU_TYPE@ + subtype + @PLUGIN_AU_SUBTYPE@ + manufacturer + @PLUGIN_AU_MANUFACTURER@ + name + @PLUGIN_AU_NAME@ + version + 1 + sandboxSafe + + + - AudioComponents - - - type - @PLUGIN_AU_TYPE@ - subtype - @PLUGIN_AU_SUBTYPE@ - manufacturer - @PLUGIN_AU_MANUFACTURER@ - name - @PLUGIN_AU_NAME@ - version - 1 - sandboxSafe - - - diff --git a/cmake/plugins/yup_plugin_auv3.cmake b/cmake/plugins/yup_plugin_auv3.cmake index 2ea488b61..8154bc327 100644 --- a/cmake/plugins/yup_plugin_auv3.cmake +++ b/cmake/plugins/yup_plugin_auv3.cmake @@ -24,6 +24,7 @@ function (yup_plugin_auv3) set (one_value_args TARGET_NAME TARGET_CXX_STANDARD TARGET_IDE_GROUP TARGET_BUNDLE_ID PLUGIN_IS_SYNTH PLUGIN_NAME PLUGIN_VERSION PLUGIN_AU_SUBTYPE PLUGIN_AU_MANUFACTURER + PLUGIN_ID PLUGIN_VENDOR PLUGIN_DESCRIPTION PLUGIN_URL PLUGIN_EMAIL PLUGIN_IS_MONO STANDALONE_TARGET HAS_STANDALONE) set (multi_value_args diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index b4481bd5c..5ec61bb67 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -217,7 +217,12 @@ function (yup_audio_plugin) SHARED_LIBS ${target_name}_shared ADDITIONAL_LIBRARIES ${additional_libraries} MODULES ${YUP_ARG_MODULES} - ${YUP_ARG_UNPARSED_ARGUMENTS}) + PLUGIN_ID ${AUv3_ARGS_PLUGIN_ID} + PLUGIN_VENDOR ${AUv3_ARGS_PLUGIN_VENDOR} + PLUGIN_DESCRIPTION ${AUv3_ARGS_PLUGIN_DESCRIPTION} + PLUGIN_URL ${AUv3_ARGS_PLUGIN_URL} + PLUGIN_EMAIL ${AUv3_ARGS_PLUGIN_EMAIL} + PLUGIN_IS_MONO ${AUv3_ARGS_PLUGIN_IS_MONO}) endif() # ==== Create composite target for all enabled plugin formats @@ -312,6 +317,7 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) add_custom_command(TARGET ${dependency_target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" + COMMAND /bin/sh -c "killall -9 AudioComponentRegistrar 2>/dev/null; sleep 2; true" COMMENT "Copying AUv3 plugin ${plugin_type_upper} to ${plugin_path}" VERBATIM) elseif ("${plugin_type}" STREQUAL "aax") diff --git a/cmake/yup_dependencies.cmake b/cmake/yup_dependencies.cmake index a32a5e148..ba8a1b13d 100644 --- a/cmake/yup_dependencies.cmake +++ b/cmake/yup_dependencies.cmake @@ -155,16 +155,6 @@ function (_yup_collect_audio_plugin_host_dependencies definitions output_variabl "-framework CoreFoundation") endif() - _yup_definitions_enable ("${definitions}" YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 enable_auv3) - if (enable_auv3 AND YUP_PLATFORM_MAC) - list (APPEND dependencies - "-framework AVFoundation" - "-framework AudioToolbox" - "-framework CoreAudioKit" - "-framework CoreAudio" - "-framework CoreFoundation") - endif() - set (${output_variable} "${dependencies}" PARENT_SCOPE) endfunction() diff --git a/examples/audiograph/source/AudioGraphApp.cpp b/examples/audiograph/source/AudioGraphApp.cpp index 14fc1ac14..cb7add198 100644 --- a/examples/audiograph/source/AudioGraphApp.cpp +++ b/examples/audiograph/source/AudioGraphApp.cpp @@ -37,9 +37,6 @@ yup::String formatTypeToString (yup::AudioPluginFormatType type) case yup::AudioPluginFormatType::audioUnit: return "AU"; - case yup::AudioPluginFormatType::audioUnitV3: - return "AUv3"; - default: return "?"; } @@ -76,10 +73,6 @@ AudioGraphApp::AudioGraphApp() #if YUP_AUDIO_PLUGIN_HOST_ENABLE_AU && YUP_MAC scanner->addFormat (std::make_unique()); #endif -#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC - scanner->addFormat (std::make_unique()); -#endif - nodeRegistry.registerPluginFormats (scanner.get(), makeHostContext()); #endif diff --git a/examples/audiograph/source/nodes/NodeRegistry.h b/examples/audiograph/source/nodes/NodeRegistry.h index 05b8b383f..30b1cc3d1 100644 --- a/examples/audiograph/source/nodes/NodeRegistry.h +++ b/examples/audiograph/source/nodes/NodeRegistry.h @@ -420,9 +420,6 @@ class NodeRegistry case yup::AudioPluginFormatType::audioUnit: return pluginAuIdentifier; - case yup::AudioPluginFormatType::audioUnitV3: - return pluginAuIdentifier; // reuse AU identifier for now - default: return pluginUnknownIdentifier; } @@ -463,9 +460,6 @@ class NodeRegistry case yup::AudioPluginFormatType::audioUnit: formatTypeStr = "au"; break; - case yup::AudioPluginFormatType::audioUnitV3: - formatTypeStr = "auv3"; - break; default: formatTypeStr = "unknown"; break; diff --git a/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm index a5dc5f723..d251acd3d 100644 --- a/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm +++ b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3.mm @@ -35,6 +35,11 @@ #import +#import +#import + +#include + #include #include #include @@ -157,8 +162,6 @@ void init() const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - processor->setPlayHead (this); - const auto& busLayout = processor->getBusLayout(); totalInChannels = 0; @@ -285,8 +288,8 @@ void installParameterTree (NSMutableArray* topLevelNodes) { auto* param = parameters[i].get(); const auto address = param->getHostParameterID(); - const auto name = param->getName().toNSString(); - const auto identifier = param->getID().toNSString(); + const auto name = yupStringToNS (param->getName()); + const auto identifier = yupStringToNS (param->getID()); AUValue minVal = static_cast (param->getMinimumValue()); AUValue maxVal = static_cast (param->getMaximumValue()); @@ -310,7 +313,7 @@ void installParameterTree (NSMutableArray* topLevelNodes) } } - return [nodes autorelease]; + return nodes; } void valueChangedFromHost (AUParameter* param, AUValue value) @@ -356,7 +359,7 @@ AUValue getValueForHost (AUParameter* param) const return @""; const auto normalised = static_cast (*value) / getMaximumParameterValue (*yupParam); - return yupParam->convertToString (normalised).toNSString(); + return yupStringToNS (yupParam->convertToString (normalised)); } AUValue valueFromString (AUParameter* param, NSString* str) const @@ -406,12 +409,12 @@ void addPresets() auto* newPresets = [[NSMutableArray alloc] init]; - const int n = static_cast (processor->getNumPrograms()); + const int n = static_cast (processor->getNumPresets()); for (int i = 0; i < n; ++i) { auto* preset = [[AUAudioUnitPreset alloc] init]; - [preset setName:processor->getProgramName (i).toNSString()]; + [preset setName:yupStringToNS (processor->getPresetName (i))]; [preset setNumber:static_cast (i)]; [newPresets addObject:preset]; } @@ -432,7 +435,7 @@ void addPresets() return nil; std::lock_guard lock (factoryPresetsMutex); - const auto current = processor->getCurrentProgram(); + const auto current = processor->getCurrentPreset(); if (isPositiveAndBelow (current, static_cast ([factoryPresets.get() count]))) return [factoryPresets.get() objectAtIndex:static_cast (current)]; @@ -443,7 +446,7 @@ void addPresets() void setCurrentPreset (AUAudioUnitPreset* preset) { if (processor != nullptr && preset != nullptr) - processor->setCurrentProgram (static_cast ([preset number])); + processor->setCurrentPreset (static_cast ([preset number])); } //============================================================================== @@ -461,7 +464,7 @@ void setCurrentPreset (AUAudioUnitPreset* preset) MemoryBlock state; if (processor != nullptr) - processor->getStateInformation (state); + processor->saveStateIntoMemory (state); if (state.getSize() > 0) { @@ -469,7 +472,7 @@ void setCurrentPreset (AUAudioUnitPreset* preset) forKey:@"YUPProcessorState"]; } - return [retval autorelease]; + return retval; } void setFullState (NSDictionary* state) @@ -477,7 +480,7 @@ void setFullState (NSDictionary* state) if (state == nil || processor == nullptr) return; - auto* obj = [state objectForKey:@"YUPProcessorState"]; + id obj = [state objectForKey:@"YUPProcessorState"]; if (obj == nil || ! [obj isKindOfClass:[NSData class]]) return; @@ -490,7 +493,8 @@ void setFullState (NSDictionary* state) ObjCMsgSendSuper (au, @selector (willChangeValueForKey:), @"allParameterValues"); } - processor->setStateInformation (static_cast ([data bytes]), numBytes); + MemoryBlock stateBlock ([data bytes], static_cast (numBytes)); + processor->loadStateFromMemory (stateBlock); { ObjCMsgSendSuper (au, @selector (didChangeValueForKey:), @"allParameterValues"); @@ -572,7 +576,7 @@ bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) if (bus.getType() != AudioBus::Type::Audio) return false; - return newNumChannels > 0 && newNumChannels <= bus.getMaxSupportedChannels(); + return newNumChannels > 0 && newNumChannels <= bus.getNumChannels(); } //============================================================================== @@ -585,9 +589,6 @@ bool allocateRenderResourcesAndReturnError (NSError** outError) if (processor == nullptr) return false; - if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) - return false; - if (outError != nullptr) *outError = nil; @@ -869,13 +870,13 @@ NSTimeInterval getTailTime() const bool getRenderingOffline() const { - return processor != nullptr && processor->isNonRealtime(); + return processor != nullptr && processor->isOfflineProcessing(); } void setRenderingOffline (bool offline) { if (processor != nullptr) - processor->setNonRealtime (offline); + processor->setOfflineProcessing (offline); } //============================================================================== @@ -888,7 +889,7 @@ int getVirtualMIDICableCount() const bool getSupportsMPE() const { - return processor != nullptr && processor->supportsMPE(); + return false; } NSArray* getMIDIOutputNames() const @@ -911,7 +912,8 @@ bool getSupportsMPE() const if (processor != nullptr && processor->getSampleRate() > 0.0) info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processor->getSampleRate()); - double num = 0, den = 0, ppqPosition = 0; + double num = 0, ppqPosition = 0; + NSInteger den = 0; NSInteger deltaSampleOffsetToNextBeat = 0; double currentMeasureDownBeat = 0, bpm = 0; @@ -963,21 +965,20 @@ bool getSupportsMPE() const auto* indices = [[NSMutableIndexSet alloc] init]; if (! processor->hasEditor()) - return [indices autorelease]; + return indices; - auto* editor = processor->createEditorAndMakeActive(); + auto* editor = processor->createEditor(); if (editor == nullptr) - return [indices autorelease]; + return indices; for (NSUInteger i = 0; i < [configs count]; ++i) { - auto* config = [configs objectAtIndex:i]; - (void) config; + [[maybe_unused]] auto* config = [configs objectAtIndex:i]; [indices addIndex:i]; } delete editor; - return [indices autorelease]; + return indices; } void selectViewConfiguration (AUAudioUnitViewConfiguration* config) @@ -989,7 +990,7 @@ void selectViewConfiguration (AUAudioUnitViewConfiguration* config) //============================================================================== // Listener callbacks - void audioProcessorChanged (AudioProcessorBase*, const ChangeDetails& details) override + void audioProcessorChanged (AudioProcessorBase*, const AudioProcessorBase::ChangeDetails& details) override { if (details.programChanged) { @@ -1204,7 +1205,7 @@ void unregisterAllParameterListeners() NSError** error, AudioPluginProcessorAUv3* cpp) { self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + AudioComponentInstantiationOptions, NSError * __autoreleasing *> (self, @selector (initWithComponentDescription:options:error:), descr, options, error); setThis (self, cpp); return self; }); @@ -1212,18 +1213,20 @@ void unregisterAllParameterListeners() addMethod (@selector (initWithComponentDescription:options:error:), [] (id self, SEL, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) { self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + AudioComponentInstantiationOptions, NSError * __autoreleasing *> (self, @selector (initWithComponentDescription:options:error:), descr, options, error); auto* cpp = new AudioPluginProcessorAUv3 (self, descr, options, error); setThis (self, cpp); return self; }); - addMethod (@selector (dealloc), [] (id self, SEL) { + YUP_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (sel_registerName ("dealloc"), [] (id self, SEL) { auto* cpp = _this (self); delete cpp; setThis (self, nullptr); }); + YUP_END_IGNORE_WARNINGS_GCC_LIKE // Internal render block addMethod (@selector (internalRenderBlock), [] (id self, SEL) { @@ -1368,7 +1371,7 @@ void unregisterAllParameterListeners() static void setThis (id self, AudioPluginProcessorAUv3* cpp) { - object_setInstanceVariable (self, "cppObject", cpp); + setIvar (self, "cppObject", cpp); } }; @@ -1376,43 +1379,45 @@ static void setThis (id self, AudioPluginProcessorAUv3* cpp) // View controller C++ companion class AudioPluginViewControllerv3 + : public AudioProcessorBase::Listener { public: explicit AudioPluginViewControllerv3 (AUViewController* vc) : myself (vc) { initialiseYup_GUI(); + processor.reset (::createPluginProcessor()); } - ~AudioPluginViewControllerv3() + ~AudioPluginViewControllerv3() override { if (processor != nullptr) { yup::endActiveParameterGestures (processor.get()); processor->removeListener (this); - if (auto* editor = processor->getActiveEditor()) + if (editor != nullptr) { - processor->editorBeingDeleted (editor); delete editor; + editor = nullptr; } } } void loadView() { - processor.reset (::createPluginProcessor()); - if (processor == nullptr) return; if (processor->hasEditor()) { - if (auto* editor = processor->createEditorAndMakeActive()) + editor = processor->createEditor(); + + if (editor != nullptr) { preferredSize = { editor->getWidth(), editor->getHeight() }; - NSView* view = [[NSView alloc] initWithFrame:NSMakeRect (0, 0, preferredSize.width, preferredSize.height)]; + NSView* view = [[NSView alloc] initWithFrame:NSMakeRect (0, 0, preferredSize.getWidth(), preferredSize.getHeight())]; [myself setView:view]; editor->setVisible (true); @@ -1432,7 +1437,7 @@ void viewDidLayoutSubviews() if ([myself view] != nil) { - if (auto* editor = processor->getActiveEditor()) + if (editor != nullptr) { const auto bounds = [[myself view] bounds]; editor->setBounds ({ 0.0f, @@ -1445,10 +1450,17 @@ void viewDidLayoutSubviews() CGSize getPreferredContentSize() const { - return CGSizeMake (static_cast (preferredSize.width), - static_cast (preferredSize.height)); + return CGSizeMake (static_cast (preferredSize.getWidth()), + static_cast (preferredSize.getHeight())); } + //============================================================================== + // Listener + + void audioProcessorChanged (AudioProcessorBase*, const AudioProcessorBase::ChangeDetails&) override {} + + //============================================================================== + AUAudioUnit* createAudioUnit (const AudioComponentDescription& desc, NSError** error) { if (processor == nullptr) @@ -1459,7 +1471,7 @@ CGSize getPreferredContentSize() const static AUAudioUnitSubclass auClass; auto* au = auClass.createInstance(); au = ObjCMsgSendSuper (au, @selector (initWithComponentDescription:options:error:), desc, 0, error); + AudioComponentInstantiationOptions, NSError * __autoreleasing *> (au, @selector (initWithComponentDescription:options:error:), desc, 0, error); if (au == nil) { @@ -1478,6 +1490,7 @@ CGSize getPreferredContentSize() const private: AUViewController* myself = nil; std::unique_ptr processor; + AudioProcessorEditor* editor = nullptr; Rectangle preferredSize { 1.0f, 1.0f }; }; diff --git a/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h b/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h index 3ea1c88f4..f0da8990f 100644 --- a/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h +++ b/modules/yup_audio_plugin_host/host/yup_AudioPluginFormatType.h @@ -28,7 +28,6 @@ enum class AudioPluginFormatType vst3, clap, audioUnit, - audioUnitV3, unknown }; diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h deleted file mode 100644 index ba434ba89..000000000 --- a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2026 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC - -namespace yup -{ - -/** - AudioPluginFormat implementation for AUv3 (macOS only). - - AUv3 plugins are App Extensions (`.appex`) discovered through - `AVAudioUnitComponentManager` which enumerates the system - App Extension registry. -*/ -class AUv3Format : public AudioPluginFormat -{ -public: - AUv3Format(); - ~AUv3Format() override; - - AudioPluginFormatType getFormatType() const override; - String getFormatName() const override; - StringArray getFileExtensions() const override; - - /** Returns an empty FileSearchPath — AUv3 uses the App Extension registry. */ - FileSearchPath getDefaultSearchPaths() const override; - - /** - Passing an invalid File triggers full AUv3 registry scan. - Otherwise scans only the component matching the file's bundle identifier. - */ - ResultValue> scanFile (const File& file) override; - - ResultValue> loadPlugin ( - const AudioPluginDescription& description, - const AudioPluginHostContext& context) override; -}; - -} // namespace yup - -#endif // YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm deleted file mode 100644 index 35d455159..000000000 --- a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv3.mm +++ /dev/null @@ -1,646 +0,0 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2026 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC - -namespace yup -{ - -//============================================================================== -// AUv3Instance - wraps an AUAudioUnit for hosting - -class AUv3Instance final : public AudioPluginInstance -{ -public: - //============================================================================== - - static std::unique_ptr create (const AudioPluginDescription& description, - const AudioPluginHostContext& context) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "creating AUv3 instance from: " << description.identifier); - - // Parse the identifier into AudioComponentDescription - auto components = parseIdentifierToComponentDescription (description.identifier); - if (! components.has_value()) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "failed to parse identifier"); - return nullptr; - } - - auto instance = std::make_unique (description, context); - instance->description = description; - - return instance; - } - - //============================================================================== - - void prepareToPlay (float sampleRate, int samplesPerBlock) override - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "prepareToPlay: sr=" << String (sampleRate) << ", block=" << String (samplesPerBlock)); - - if (audioUnit == nil) - { - // Instantiate the AUAudioUnit - auto components = parseIdentifierToComponentDescription (description.identifier); - - if (components.has_value()) - { - dispatch_semaphore_t sem = dispatch_semaphore_create (0); - - [AUAudioUnit instantiateWithComponentDescription:*components - options:0 - completionHandler:^(AUAudioUnit* au, NSError* error) { - if (au != nil) - audioUnit = au; - - if (error != nil) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "instantiate failed: " << String::fromCFString ((__bridge CFStringRef)[error localizedDescription])); - } - - dispatch_semaphore_signal (sem); - }]; - - dispatch_semaphore_wait (sem, DISPATCH_TIME_FOREVER); - } - } - - if (audioUnit == nil) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "audioUnit is nil after instantiate"); - return; - } - - // Set maximum frames to render - [audioUnit setMaximumFramesToRender:static_cast (samplesPerBlock)]; - - // Allocate render resources - NSError* error = nil; - if (! [audioUnit allocateRenderResourcesAndReturnError:&error]) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "allocateRenderResources failed: " << (error != nil ? String::fromCFString ((__bridge CFStringRef)[error localizedDescription]) : String ())); - return; - } - - numInputChannels = static_cast ([audioUnit.inputBusses count]); - numOutputChannels = static_cast ([audioUnit.outputBusses count]); - - // Collect parameter info - collectParameters(); - - // Setup render observer - setupRenderObserver(); - - // Store block size - blockSize = samplesPerBlock; - scratchBuffer.setSize (std::max (numInputChannels, numOutputChannels), blockSize); - - prepared = true; - - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "prepareToPlay complete: inCh=" << String (numInputChannels) << ", outCh=" << String (numOutputChannels)); - } - - void releaseResources() override - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "releaseResources"); - - removeRenderObserver(); - - if (audioUnit != nil) - [audioUnit deallocateRenderResources]; - - prepared = false; - } - - //============================================================================== - - void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override - { - if (audioUnit == nil || ! prepared) - return; - - const int numFrames = buffer.getNumSamples(); - - AudioTimeStamp timeStamp = {}; - timeStamp.mFlags = kAudioTimeStampSampleTimeValid; - timeStamp.mSampleTime = static_cast (processedFrames); - - // Pull input if this is an effect - AudioUnitRenderActionFlags actionFlags = 0; - - // For instruments, output renders directly - // For effects, we need to use AudioUnitRender with input callback - if (numInputChannels > 0) - { - // Prepare input data for the effect - scratchBuffer.clear(); - - for (int ch = 0; ch < numInputChannels && ch < buffer.getNumChannels(); ++ch) - { - const auto* src = buffer.getReadPointer (ch); - auto* dst = scratchBuffer.getWritePointer (ch); - std::copy (src, src + numFrames, dst); - } - - // Render through the audio unit - AudioBufferList* inputBufferList = createBufferListForChannels (numInputChannels, scratchBuffer, numFrames); - AudioBufferList* outputBufferList = createBufferListForChannels (numOutputChannels, buffer, numFrames); - - AURenderPullInputBlock pullInput = ^AUAudioUnitStatus (AudioUnitRenderActionFlags* flags, - const AudioTimeStamp* ts, - AUAudioFrameCount frames, - NSInteger busNumber, - AudioBufferList* data) { - ignoreUnused (flags, ts); - - if (busNumber >= 0 && busNumber < numInputChannels) - { - // Copy from our input buffer - for (UInt32 i = 0; i < data->mNumberBuffers; ++i) - { - const auto* src = scratchBuffer.getReadPointer (static_cast (busNumber)); - auto* dst = static_cast (data->mBuffers[i].mData); - memcpy (dst, src, frames * sizeof (float)); - } - } - - return noErr; - }; - - // Process via internal render block or AudioUnitRender - AUInternalRenderBlock renderBlock = [audioUnit internalRenderBlock]; - if (renderBlock) - { - renderBlock (&actionFlags, &timeStamp, static_cast (numFrames), - 0, outputBufferList, nullptr, pullInput); - } - - freeBufferList (inputBufferList); - freeBufferList (outputBufferList); - } - else - { - // Instrument - render directly to output - AudioTimeStamp ts = {}; - ts.mFlags = kAudioTimeStampSampleTimeValid; - ts.mSampleTime = static_cast (processedFrames); - - AudioBufferList* outBufList = createBufferListForChannels (numOutputChannels, buffer, numFrames); - - AUInternalRenderBlock renderBlock = [audioUnit internalRenderBlock]; - if (renderBlock) - { - AudioUnitRenderActionFlags flags = 0; - renderBlock (&flags, &ts, static_cast (numFrames), - 0, outBufList, nullptr, nullptr); - } - - freeBufferList (outBufList); - } - - // Handle MIDI - if (! midiMessages.isEmpty() && [audioUnit respondsToSelector:@selector (scheduleMIDIEventBlock)]) - { - AUScheduleMIDIEventBlock midiBlock = [audioUnit scheduleMIDIEventBlock]; - if (midiBlock) - { - for (const auto& metadata : midiMessages) - { - midiBlock (static_cast (metadata.samplePosition + processedFrames), - 0, - metadata.numBytes, - metadata.data); - } - } - } - - processedFrames += numFrames; - } - - //============================================================================== - - int getNumPrograms() override - { - return static_cast ([[audioUnit factoryPresets] count]); - } - - String getProgramName (int index) override - { - if (audioUnit == nil) - return {}; - - auto* presets = [audioUnit factoryPresets]; - if (! isPositiveAndBelow (index, static_cast ([presets count]))) - return {}; - - auto* preset = [presets objectAtIndex:static_cast (index)]; - return String::fromCFString ((__bridge CFStringRef)[preset name]); - } - - int getCurrentProgram() override - { - if (audioUnit == nil) - return 0; - - auto* preset = [audioUnit currentPreset]; - if (preset != nil) - return static_cast ([preset number]); - - return 0; - } - - void setCurrentProgram (int index) override - { - if (audioUnit == nil) - return; - - auto* presets = [audioUnit factoryPresets]; - if (isPositiveAndBelow (index, static_cast ([presets count]))) - { - auto* preset = [presets objectAtIndex:static_cast (index)]; - [audioUnit setCurrentPreset:preset]; - } - } - - //============================================================================== - - bool hasEditor() const override - { - return audioUnit != nil && [audioUnit respondsToSelector:@selector (requestViewControllerWithCompletionHandler:)]; - } - - AudioProcessorEditor* createEditor() override - { - if (audioUnit == nil) - return nullptr; - - return new AUv3Editor (*this); - } - - //============================================================================== - - void getStateInformation (MemoryBlock& destData) override - { - if (audioUnit == nil) - return; - - auto* state = [audioUnit fullState]; - if (state == nil) - return; - - auto* data = [NSJSONSerialization dataWithJSONObject:state options:0 error:nil]; - if (data != nil) - { - destData.setSize ([data length]); - memcpy (destData.getData(), [data bytes], [data length]); - } - } - - void setStateInformation (const char* data, int sizeInBytes) override - { - if (audioUnit == nil || sizeInBytes <= 0) - return; - - auto* nsData = [NSData dataWithBytes:data length:static_cast (sizeInBytes)]; - auto* state = [NSJSONSerialization JSONObjectWithData:nsData options:0 error:nil]; - - if (state != nil) - // [audioUnit setFullState:state]; - [audioUnit setFullStateForDocument:state]; - } - - //============================================================================== - - const AudioPluginDescription& getPluginDescription() const - { - return description; - } - - AUAudioUnit* getAudioUnit() const { return audioUnit; } - - float getSampleRate() const override { return sampleRate; } - - int getBlockSize() const override { return blockSize; } - -private: - //============================================================================== - - AUv3Instance (const AudioPluginDescription& desc, - const AudioPluginHostContext&) - : description (desc) - { - } - - ~AUv3Instance() override - { - releaseResources(); - audioUnit = nil; - } - - //============================================================================== - - static std::optional parseIdentifierToComponentDescription (const String& identifier) - { - // Format: "type/subtype/manufacturer" (four-char codes) - auto parts = StringArray::fromTokens (identifier, "/", ""); - if (parts.size() < 3) - return std::nullopt; - - auto parseOSType = [] (const String& s) -> OSType { - if (s.length() < 4) - return 0; - - return static_cast ( - (static_cast (static_cast (s[0])) << 24) | - (static_cast (static_cast (s[1])) << 16) | - (static_cast (static_cast (s[2])) << 8) | - static_cast (static_cast (s[3]))) - ); - }; - - AudioComponentDescription result = {}; - result.componentType = parseOSType (parts[0]); - result.componentSubType = parseOSType (parts[1]); - result.componentManufacturer = parseOSType (parts[2]); - - return result; - } - - //============================================================================== - - void collectParameters() - { - hostedParameters.clear(); - - if (audioUnit == nil) - return; - - auto* paramTree = [audioUnit parameterTree]; - if (paramTree == nil) - return; - - // Collect all parameters from the tree - collectParametersFromNode (paramTree); - } - - void collectParametersFromNode (AUParameterNode* node) - { - if (node == nil) - return; - - if ([node isKindOfClass:[AUParameter class]]) - { - auto* param = static_cast (node); - - hostedParameters.push_back (param); - - // Observe parameter changes - [param setValueObserver:^(AUValue value) { - // Parameter changed from host side - }]; - } - else if ([node isKindOfClass:[AUParameterGroup class]]) - { - // Recurse into groups - AUParameterGroup is a subclass with children - // The actual API to enumerate children varies; for now we collect top-level - } - } - - //============================================================================== - - void setupRenderObserver() - { - if (audioUnit == nil) - return; - - // Observe latency changes - renderObserver = (__bridge void*) [audioUnit addObserver:^(AudioUnitProperty property, const AudioUnitParameterValue value) { - ignoreUnused (property, value); - }]; - } - - void removeRenderObserver() - { - if (audioUnit != nil && renderObserver != nullptr) - { - // Remove observer logic - renderObserver = nullptr; - } - } - - //============================================================================== - - static AudioBufferList* createBufferListForChannels (int numChannels, AudioBuffer& buffer, int numFrames) - { - const size_t bufferListSize = offsetof (AudioBufferList, mBuffers) + static_cast (numChannels) * sizeof (AudioBuffer); - auto* bufList = static_cast (malloc (bufferListSize)); - bufList->mNumberBuffers = static_cast (numChannels); - - for (int i = 0; i < numChannels; ++i) - { - bufList->mBuffers[i].mNumberChannels = 1; - bufList->mBuffers[i].mDataByteSize = static_cast (numFrames * sizeof (float)); - bufList->mBuffers[i].mData = buffer.getWritePointer (i); - } - - return bufList; - } - - static void freeBufferList (AudioBufferList* bufList) - { - if (bufList != nullptr) - free (bufList); - } - - //============================================================================== - - AudioPluginDescription description; - AUAudioUnit* audioUnit = nil; - int numInputChannels = 0; - int numOutputChannels = 0; - float sampleRate = 44100.0f; - int blockSize = 512; - bool prepared = false; - int64_t processedFrames = 0; - - std::vector hostedParameters; - void* renderObserver = nullptr; - - AudioBuffer scratchBuffer; - - //============================================================================== - - class AUv3Editor final : public AudioProcessorEditor - { - public: - AUv3Editor (AUv3Instance& instance) - : AudioProcessorEditor (&instance) - { - if (@available (macOS 10.13, *)) - { - auto* au = instance.getAudioUnit(); - - [au requestViewControllerWithCompletionHandler:^(AUViewControllerBase* vc) { - if (vc != nil) - { - viewController = vc; - - // Get the view from the view controller - NSView* view = [vc view]; - if (view != nil) - { - const auto size = view.bounds.size; - setSize (static_cast (size.width), static_cast (size.height)); - } - } - }]; - } - } - - void resized() override - { - if (viewController != nil) - { - auto* view = [viewController view]; - if (view != nil) - { - view.frame = NSMakeRect (0, 0, getWidth(), getHeight()); - } - } - } - - AUViewControllerBase* getViewController() const { return viewController; } - - private: - AUViewControllerBase* viewController = nil; - }; - - YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AUv3Instance) -}; - -//============================================================================== -// AUv3Format implementation - -AUv3Format::AUv3Format() = default; -AUv3Format::~AUv3Format() = default; - -AudioPluginFormatType AUv3Format::getFormatType() const -{ - return AudioPluginFormatType::audioUnitV3; -} - -String AUv3Format::getFormatName() const -{ - return "AUv3"; -} - -StringArray AUv3Format::getFileExtensions() const -{ - return {}; -} - -FileSearchPath AUv3Format::getDefaultSearchPaths() const -{ - return {}; -} - -ResultValue> AUv3Format::scanFile (const File&) -{ - std::vector results; - - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "scanning AUv3 components via AVAudioUnitComponentManager"); - - // Use AVAudioUnitComponentManager to discover AUv3 plugins - AVAudioUnitComponentManager* manager = [AVAudioUnitComponentManager sharedAudioUnitComponentManager]; - - // Get all available components - NSArray* components = [manager componentsMatchingPredicate:nil]; - - for (AVAudioUnitComponent* component in components) - { - AudioPluginDescription desc; - - desc.formatType = AudioPluginFormatType::audioUnitV3; - desc.name = String::fromCFString ((__bridge CFStringRef)[component name]); - - auto* acd = [component audioComponentDescription]; - desc.identifier = String::createStringFromData (reinterpret_cast (&acd.componentType), 4) - + "/" - + String::createStringFromData (reinterpret_cast (&acd.componentSubType), 4) - + "/" - + String::createStringFromData (reinterpret_cast (&acd.componentManufacturer), 4); - - desc.vendor = String::fromCFString ((__bridge CFStringRef)[component manufacturerName]); - desc.version = (component.versionString != nil) ? String::fromCFString ((__bridge CFStringRef)[component versionString]) : String(); - - desc.isInstrument = (acd.componentType == kAudioUnitType_MusicDevice - || acd.componentType == kAudioUnitType_MusicEffect); - desc.isEffect = (acd.componentType == kAudioUnitType_Effect - || acd.componentType == kAudioUnitType_MusicEffect); - - if (desc.isInstrument) - desc.numOutputChannels = 2; - - if (desc.isEffect) - { - desc.numInputChannels = 2; - desc.numOutputChannels = 2; - } - - desc.numMidiInputPorts = [component hasMIDIInput] ? 1 : 0; - desc.numMidiOutputPorts = [component hasMIDIOutput] ? 1 : 0; - - desc.fileOrBundlePath = {}; // App Extensions don't expose bundle path directly - - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "found: " << desc.name << " [" << desc.identifier << "]"); - results.push_back (std::move (desc)); - } - - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "scan complete: " << results.size() << " AUv3 components found"); - - if (results.empty()) - return makeResultValueFail ("No AUv3 components found in registry"); - - return makeResultValueOk (std::move (results)); -} - -ResultValue> AUv3Format::loadPlugin ( - const AudioPluginDescription& description, - const AudioPluginHostContext& context) -{ - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "loading: " << description.name << " [" << description.identifier << "]"); - - auto instance = AUv3Instance::create (description, context); - - if (instance == nullptr) - { - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "load failed: " << description.name); - return makeResultValueFail ("Failed to instantiate AUv3 plugin: " + description.name); - } - - YUP_MODULE_DBG (PLUGIN_HOST_AUV3, "loaded: " << description.name); - return makeResultValueOk (std::move (instance)); -} - -} // namespace yup - -#endif // YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp index d55a2eaf5..018fb2629 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp @@ -99,11 +99,3 @@ using CLAPModuleHandle = void*; #include "native/yup_AudioPluginInstance_AUv2.mm" #endif - -#if YUP_AUDIO_PLUGIN_HOST_ENABLE_AUV3 && YUP_MAC -#import -#import -#import - -#include "native/yup_AudioPluginInstance_AUv3.mm" -#endif diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h index 3f740ef3d..d4f68f1cc 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h @@ -28,7 +28,7 @@ vendor: yup version: 1.0.0 name: YUP Audio Plugin Host - description: In-process hosting of VST3, CLAP, AUv2, and AUv3 audio plugins. + description: In-process hosting of VST3, CLAP, and AUv2 audio plugins. website: https://github.com/kunitoki/yup license: ISC @@ -52,14 +52,6 @@ #define YUP_ENABLE_PLUGIN_HOST_AU_LOGGING 0 #endif -/** Config: YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING - - Enable debug logging for AUv3 plugin scanning and loading. -*/ -#ifndef YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING -#define YUP_ENABLE_PLUGIN_HOST_AUV3_LOGGING 0 -#endif - /** Config: YUP_ENABLE_PLUGIN_HOST_CLAP_LOGGING Enable debug logging for CLAP plugin scanning and loading. @@ -91,4 +83,3 @@ #include "native/yup_AudioPluginInstance_VST3.h" #include "native/yup_AudioPluginInstance_CLAP.h" #include "native/yup_AudioPluginInstance_AUv2.h" -#include "native/yup_AudioPluginInstance_AUv3.h" diff --git a/modules/yup_audio_processors/processors/yup_AudioProcessorBase.h b/modules/yup_audio_processors/processors/yup_AudioProcessorBase.h index 68700d4cc..dbd48e971 100644 --- a/modules/yup_audio_processors/processors/yup_AudioProcessorBase.h +++ b/modules/yup_audio_processors/processors/yup_AudioProcessorBase.h @@ -80,6 +80,14 @@ class YUP_API AudioProcessorBase return copy; } + /** Returns a copy of these details with the program change flag set. */ + ChangeDetails withProgramChanged (bool shouldBeProgramChanged) const noexcept + { + auto copy = *this; + copy.programChanged = shouldBeProgramChanged; + return copy; + } + /** True when the processor latency may have changed. */ bool latencyChanged = false; @@ -94,6 +102,9 @@ class YUP_API AudioProcessorBase /** True when non-parameter processor state may have changed. */ bool nonParameterStateChanged = false; + + /** True when the program/preset has changed. */ + bool programChanged = false; }; //============================================================================== From 1e8708bd7449defb2e84ef45a698edbfcc2239e9 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Thu, 2 Jul 2026 23:57:27 +0200 Subject: [PATCH 04/10] More nice AU work --- .../mac/AudioUnitV3ContainerInfo.plist.in | 2 + cmake/platforms/mac/AudioUnitV3Info.plist.in | 6 + cmake/plugins/yup_plugin_aax.cmake | 3 + cmake/plugins/yup_plugin_auv3.cmake | 86 +- cmake/yup_audio_plugin.cmake | 17 +- cmake/yup_codesign.cmake | 15 +- examples/audiograph/source/AudioGraphApp.cpp | 2 +- .../au/yup_audio_plugin_client_AU.cpp | 1549 ----------------- .../auv3/yup_audio_plugin_client_AUv3_main.mm | 27 + .../host/yup_AudioPluginDescription.h | 6 +- ...ce_AUv2.h => yup_AudioPluginInstance_AU.h} | 18 +- ..._AUv2.mm => yup_AudioPluginInstance_AU.mm} | 129 +- .../yup_audio_plugin_host.cpp | 2 +- .../yup_audio_plugin_host.h | 6 +- 14 files changed, 225 insertions(+), 1643 deletions(-) delete mode 100644 modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.cpp create mode 100644 modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3_main.mm rename modules/yup_audio_plugin_host/native/{yup_AudioPluginInstance_AUv2.h => yup_AudioPluginInstance_AU.h} (72%) rename modules/yup_audio_plugin_host/native/{yup_AudioPluginInstance_AUv2.mm => yup_AudioPluginInstance_AU.mm} (93%) diff --git a/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in b/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in index a6d2801a3..d1830b676 100644 --- a/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in +++ b/cmake/platforms/mac/AudioUnitV3ContainerInfo.plist.in @@ -22,6 +22,8 @@ Copyright (c) 2026 - kunitoki@gmail.com NSHighResolutionCapable + NSPrincipalClass + NSApplication LSUIElement diff --git a/cmake/platforms/mac/AudioUnitV3Info.plist.in b/cmake/platforms/mac/AudioUnitV3Info.plist.in index cd2853525..008b8b4dc 100644 --- a/cmake/platforms/mac/AudioUnitV3Info.plist.in +++ b/cmake/platforms/mac/AudioUnitV3Info.plist.in @@ -32,6 +32,8 @@ com.apple.AudioUnit-UI NSExtensionAttributes + NSExtensionServiceRoleType + NSExtensionServiceRoleTypeEditor AudioComponents @@ -47,6 +49,10 @@ 1 sandboxSafe + tags + + @PLUGIN_AU_TAG@ + diff --git a/cmake/plugins/yup_plugin_aax.cmake b/cmake/plugins/yup_plugin_aax.cmake index f37665ca8..6d66ec420 100644 --- a/cmake/plugins/yup_plugin_aax.cmake +++ b/cmake/plugins/yup_plugin_aax.cmake @@ -206,4 +206,7 @@ function (_yup_audio_plugin_create_aax endif() yup_codesign_target (${target_name}_aax_plugin "${aax_plugin_path}") + + yup_audio_plugin_copy_bundle (${target_name} aax) + endfunction() diff --git a/cmake/plugins/yup_plugin_auv3.cmake b/cmake/plugins/yup_plugin_auv3.cmake index 8154bc327..2b8f571f4 100644 --- a/cmake/plugins/yup_plugin_auv3.cmake +++ b/cmake/plugins/yup_plugin_auv3.cmake @@ -24,8 +24,7 @@ function (yup_plugin_auv3) set (one_value_args TARGET_NAME TARGET_CXX_STANDARD TARGET_IDE_GROUP TARGET_BUNDLE_ID PLUGIN_IS_SYNTH PLUGIN_NAME PLUGIN_VERSION PLUGIN_AU_SUBTYPE PLUGIN_AU_MANUFACTURER - PLUGIN_ID PLUGIN_VENDOR PLUGIN_DESCRIPTION PLUGIN_URL PLUGIN_EMAIL PLUGIN_IS_MONO - STANDALONE_TARGET HAS_STANDALONE) + PLUGIN_ID PLUGIN_VENDOR PLUGIN_DESCRIPTION PLUGIN_URL PLUGIN_EMAIL PLUGIN_IS_MONO) set (multi_value_args SHARED_LIBS @@ -114,6 +113,11 @@ function (yup_plugin_auv3) set (PLUGIN_AU_MANUFACTURER "${YUP_ARG_PLUGIN_AU_MANUFACTURER}") set (PLUGIN_AU_NAME "${YUP_ARG_PLUGIN_NAME}") set (PLUGIN_AU_VERSION "${YUP_ARG_PLUGIN_VERSION}") + if (YUP_ARG_PLUGIN_IS_SYNTH) + set (PLUGIN_AU_TAG "Instrument") + else() + set (PLUGIN_AU_TAG "Effects") + endif() set (auv3_bundle_identifier "${target_bundle_id}.auv3") string (REGEX REPLACE "[^A-Za-z0-9.-]" "-" auv3_bundle_identifier "${auv3_bundle_identifier}") @@ -139,32 +143,70 @@ function (yup_plugin_auv3) XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${auv3_bundle_identifier}" XCODE_GENERATE_SCHEME ON) - # If standalone is also enabled, embed the .appex in the standalone app - if (YUP_ARG_HAS_STANDALONE) - add_dependencies (${YUP_ARG_STANDALONE_TARGET} ${target_name}_auv3_plugin) + # Always create a minimal dedicated container app to host the .appex + set (container_target "${target_name}_auv3_container") + set (auv3_container_main "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3_main.mm") + set (auv3_container_plist_template "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../platforms/mac/AudioUnitV3ContainerInfo.plist.in") + set (auv3_container_plist_output "${CMAKE_CURRENT_BINARY_DIR}/${container_target}.plist") + set (auv3_container_bundle_identifier "${target_bundle_id}") + + configure_file ("${auv3_container_plist_template}" "${auv3_container_plist_output}" @ONLY) + + add_executable (${container_target} MACOSX_BUNDLE "${auv3_container_main}") + add_dependencies (${container_target} ${target_name}_auv3_plugin) + + set_target_properties (${container_target} PROPERTIES + BUNDLE TRUE + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${auv3_container_plist_output}" + MACOSX_BUNDLE_BUNDLE_NAME "${YUP_ARG_PLUGIN_NAME}" + MACOSX_BUNDLE_GUI_IDENTIFIER "${auv3_container_bundle_identifier}" + FOLDER "${target_ide_group}" + XCODE_GENERATE_SCHEME ON) + + target_link_libraries (${container_target} PRIVATE "-framework Cocoa") - if (XCODE) - set_target_properties (${YUP_ARG_STANDALONE_TARGET} PROPERTIES - XCODE_EMBED_APP_EXTENSIONS ${target_name}_auv3_plugin) - endif() + if (XCODE) + set_target_properties (${container_target} PROPERTIES + XCODE_EMBED_APP_EXTENSIONS ${target_name}_auv3_plugin) + else() + add_custom_command (TARGET ${container_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory + "$/Contents/PlugIns" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "$/Contents/PlugIns/$.appex" + COMMENT "Embedding .appex in container app" + VERBATIM) endif() - yup_codesign_target (${target_name}_auv3_plugin "$") + add_custom_command (TARGET ${container_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$ENV{HOME}/Applications" + COMMAND ${CMAKE_COMMAND} -E rm -rf + "$ENV{HOME}/Applications/$.app" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "$ENV{HOME}/Applications/$.app" + COMMENT "Installing container app to ~/Applications" + VERBATIM) + + set (auv3_entitlements "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../platforms/mac/AudioUnitV3_Entitlements.plist") + yup_codesign_target (${target_name}_auv3_plugin "$" "${auv3_entitlements}") yup_audio_plugin_copy_bundle (${target_name} auv3) # Validation - set (auv3_pluginval_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions/${target_name}_auv3_plugin.appex") - - yup_validate_au_plugin ( - ${target_name}_auv3_plugin - "${YUP_ARG_PLUGIN_NAME}" - "${au_bundle_type}" - "${YUP_ARG_PLUGIN_AU_SUBTYPE}" - "${YUP_ARG_PLUGIN_AU_MANUFACTURER}") - - yup_validate_pluginval ( - ${target_name}_auv3_plugin - "${auv3_pluginval_path}") + #set (auv3_pluginval_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions/${target_name}_auv3_plugin.appex") + + #yup_validate_au_plugin ( + # ${target_name}_auv3_plugin + # "${YUP_ARG_PLUGIN_NAME}" + # "${au_bundle_type}" + # "${YUP_ARG_PLUGIN_AU_SUBTYPE}" + # "${YUP_ARG_PLUGIN_AU_MANUFACTURER}") + + #yup_validate_pluginval ( + # ${target_name}_auv3_plugin + # "${auv3_pluginval_path}") endfunction() diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index 5ec61bb67..7eb54019e 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -269,16 +269,17 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) elseif ("${plugin_type}" STREQUAL "auv3") set (target_file_name "${target_name}_${plugin_type}_plugin.appex") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/AppExtensions") + elseif ("${plugin_type}" STREQUAL "aax") + set (target_file_name "${target_name}_${plugin_type}_plugin.aaxplugin") + set (plugin_target_path "$ENV{HOME}/Library/Application Support/Avid/Audio/Plug-Ins") else() set (target_file_name "${target_name}_${plugin_type}_plugin.${plugin_type}") set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/${plugin_type_upper}") endif() - file (MAKE_DIRECTORY "${plugin_target_path}") - set (plugin_path "${plugin_target_path}/${target_file_name}") - if (NOT EXISTS ${plugin_target_path} AND NOT "${plugin_type}" STREQUAL "clap") + if (NOT EXISTS ${plugin_target_path} AND NOT "${plugin_type}" STREQUAL "clap" AND NOT "${plugin_type}" STREQUAL "aax") _yup_message (STATUS "Plugin path ${plugin_target_path} does not exist, skipping copy") return() endif() @@ -303,25 +304,33 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type) endif() add_custom_command(TARGET ${dependency_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${plugin_target_path}" COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" COMMAND ${CMAKE_COMMAND} -E create_symlink "${source_plugin_path}" "${plugin_path}" COMMENT "Symlinking VST3 plugin ${plugin_type_upper} plugin to ${plugin_path}" VERBATIM) elseif ("${plugin_type}" STREQUAL "au") add_custom_command(TARGET ${dependency_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${plugin_target_path}" COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" COMMENT "Copying AU plugin ${plugin_type_upper} to ${plugin_path}" VERBATIM) elseif ("${plugin_type}" STREQUAL "auv3") add_custom_command(TARGET ${dependency_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${plugin_target_path}" COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" COMMAND /bin/sh -c "killall -9 AudioComponentRegistrar 2>/dev/null; sleep 2; true" COMMENT "Copying AUv3 plugin ${plugin_type_upper} to ${plugin_path}" VERBATIM) elseif ("${plugin_type}" STREQUAL "aax") - _yup_message (STATUS "AAX plugin bundle copy not yet implemented for development") + add_custom_command(TARGET ${dependency_target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "${plugin_target_path}" + COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}" + COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}" + COMMENT "Copying AAX plugin ${plugin_type_upper} to ${plugin_path}" + VERBATIM) else() _yup_message (FATAL_ERROR "Unsupported plugin type ${plugin_type} for copying bundle") endif() diff --git a/cmake/yup_codesign.cmake b/cmake/yup_codesign.cmake index efb509e3d..13136254b 100644 --- a/cmake/yup_codesign.cmake +++ b/cmake/yup_codesign.cmake @@ -24,8 +24,15 @@ function (yup_codesign_target target_name bundle_path) return() endif() - add_custom_command (TARGET ${target_name} POST_BUILD - COMMAND codesign --force --sign - "${bundle_path}" - COMMENT "Codesigning ${target_name}" - VERBATIM) + if (ARGC GREATER 2 AND ARGV2) + add_custom_command (TARGET ${target_name} POST_BUILD + COMMAND codesign --force --sign - --entitlements "${ARGV2}" "${bundle_path}" + COMMENT "Codesigning ${target_name}" + VERBATIM) + else() + add_custom_command (TARGET ${target_name} POST_BUILD + COMMAND codesign --force --sign - "${bundle_path}" + COMMENT "Codesigning ${target_name}" + VERBATIM) + endif() endfunction() diff --git a/examples/audiograph/source/AudioGraphApp.cpp b/examples/audiograph/source/AudioGraphApp.cpp index cb7add198..186ec0fe2 100644 --- a/examples/audiograph/source/AudioGraphApp.cpp +++ b/examples/audiograph/source/AudioGraphApp.cpp @@ -71,7 +71,7 @@ AudioGraphApp::AudioGraphApp() scanner->addFormat (std::make_unique()); #endif #if YUP_AUDIO_PLUGIN_HOST_ENABLE_AU && YUP_MAC - scanner->addFormat (std::make_unique()); + scanner->addFormat (std::make_unique()); #endif nodeRegistry.registerPluginFormats (scanner.get(), makeHostContext()); #endif diff --git a/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.cpp b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.cpp deleted file mode 100644 index 5a314d242..000000000 --- a/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.cpp +++ /dev/null @@ -1,1549 +0,0 @@ -/* - ============================================================================== - - This file is part of the YUP library. - Copyright (c) 2026 - kunitoki@gmail.com - - YUP is an open source library subject to open-source licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - to use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include "../yup_audio_plugin_client.h" - -#include "../common/yup_AudioPluginUtilities.h" - -#if ! defined(YUP_AUDIO_PLUGIN_ENABLE_AU) -#error "YUP_AUDIO_PLUGIN_ENABLE_AU must be defined" -#endif - -#if YUP_MAC -#include -#include -#include -#include -#include - -#import -#import -#import -#import -#import - -#include -#include -#include -#include -#include -#include -#include - -//============================================================================== - -extern "C" yup::AudioProcessor* createPluginProcessor(); - -@class AudioPluginEditorViewAU; - -namespace yup -{ - -static String describeScopeAndElement (AudioUnitScope scope, AudioUnitElement element) -{ - return "scope=" + String (static_cast (scope)) + ", element=" + String (static_cast (element)); -} - -static String describePointer (const void* value) -{ - return "0x" + String::toHexString (static_cast (reinterpret_cast (value))); -} - -static String describeStatus (OSStatus status) -{ - return String (static_cast (status)); -} - -//============================================================================== - -namespace -{ - -//============================================================================== - -static CFStringRef getProcessorStateKey() -{ - return CFSTR ("YUPProcessorState"); -} - -//============================================================================== - -struct AUScopedYupInitialiser -{ - AUScopedYupInitialiser() - { - if (numAUScopedInitInstances.fetch_add (1) == 0) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "initialising YUP GUI"); - initialiseYup_GUI(); - } - } - - ~AUScopedYupInitialiser() - { - if (numAUScopedInitInstances.fetch_sub (1) == 1) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "shutting down YUP GUI"); - shutdownYup_GUI(); - } - } - -private: - static std::atomic_int numAUScopedInitInstances; -}; - -std::atomic_int AUScopedYupInitialiser::numAUScopedInitInstances = 0; - -struct AUScopedYupWindowingInitialiser -{ - AUScopedYupWindowingInitialiser() - { - if (numAUScopedInitInstances.fetch_add (1) == 0) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "initialising YUP windowing for editor"); - initialiseYup_Windowing(); - } - } - - ~AUScopedYupWindowingInitialiser() - { - if (numAUScopedInitInstances.fetch_sub (1) == 1) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "shutting down YUP windowing for editor"); - shutdownYup_Windowing(); - } - } - -private: - static std::atomic_int numAUScopedInitInstances; -}; - -std::atomic_int AUScopedYupWindowingInitialiser::numAUScopedInitInstances = 0; - -//============================================================================== - -static OSType osTypeFromString (const char* s) -{ - if (s == nullptr || std::strlen (s) < 4) - return 0; - - return static_cast ( - (static_cast (static_cast (s[0])) << 24) | (static_cast (static_cast (s[1])) << 16) | (static_cast (static_cast (s[2])) << 8) | static_cast (static_cast (s[3]))); -} - -} // namespace - -//============================================================================== - -#if YupPlugin_IsSynth -using AudioPluginAUBase = ausdk::MusicDeviceBase; -#else -using AudioPluginAUBase = ausdk::AUEffectBase; -#endif - -//============================================================================== - -/** - AUv2 wrapper for a YUP AudioProcessor. - - Supports both effects (AUEffectBase) and instruments (MusicDeviceBase) - depending on the YupPlugin_IsSynth compile-time setting. -*/ -class AudioPluginProcessorAU final - : public AudioPluginAUBase - , private AudioParameter::Listener -{ -public: - class AudioPluginPlayHeadAU final : public AudioPlayHead - { - public: - AudioPluginPlayHeadAU (AudioPluginProcessorAU& owner, const AudioTimeStamp* timeStamp) - : owner (owner) - , timeStamp (timeStamp) - { - } - - bool canControlTransport() override - { - return false; - } - - std::optional getPosition() const override - { - return owner.createPositionInfo (timeStamp); - } - - private: - AudioPluginProcessorAU& owner; - const AudioTimeStamp* timeStamp = nullptr; - }; - - //============================================================================== - - AudioPluginProcessorAU (AudioComponentInstance component) -#if YupPlugin_IsSynth - : AudioPluginAUBase (component, 0, 1) - , -#else - : AudioPluginAUBase (component) - , -#endif - componentInstance (component) - { - processor.reset (::createPluginProcessor()); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "created processor instance: wrapper=" << yup::describePointer (this) << ", component=" << yup::describePointer (componentInstance) << ", processor=" << yup::describePointer (processor.get())); - - if (processor == nullptr) - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "createPluginProcessor returned null"); - - addParameterListeners(); - registerInstance (componentInstance, this); - } - - ~AudioPluginProcessorAU() override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "destroying processor instance: wrapper=" << yup::describePointer (this) << ", component=" << yup::describePointer (componentInstance) << ", processor=" << yup::describePointer (processor.get())); - - closeEditorViews(); - removeParameterListeners(); - yup::endActiveParameterGestures (processor.get()); - - unregisterInstance (componentInstance); - - processor.reset(); - } - - //============================================================================== - - OSStatus Initialize() override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize requested"); - - const auto result = AudioPluginAUBase::Initialize(); - if (result != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "base Initialize failed: status=" << describeStatus (result)); - return result; - } - - if (processor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize failed: processor is null"); - return kAudioUnitErr_FailedInitialization; - } - - processor->setOfflineProcessing (renderingOffline); - processor->setPlaybackConfiguration (static_cast (getCurrentSampleRate()), - static_cast (GetMaxFramesPerSlice())); - - midiBuffer.ensureSize (4096); - midiBuffer.clear(); - emptyMidiBuffer.ensureSize (4096); - emptyMidiBuffer.clear(); - paramChangeBuffer.reserve (getDefaultParameterChangeCapacity (*processor)); - emptyParamChangeBuffer.reserve (getDefaultParameterChangeCapacity (*processor)); - audioChannels.reserve (static_cast (getTotalAudioOutputChannels (*processor))); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize completed: sampleRate=" << String (getCurrentSampleRate()) << ", maxFramesPerSlice=" << String (static_cast (GetMaxFramesPerSlice()))); - - return noErr; - } - - void Cleanup() override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Cleanup requested: wrapper=" << yup::describePointer (this) << ", processor=" << yup::describePointer (processor.get())); - - if (processor != nullptr) - processor->releaseResources(); - - AudioPluginAUBase::Cleanup(); - } - - //============================================================================== - - OSStatus GetParameterList (AudioUnitScope inScope, - AudioUnitParameterID* outParameterList, - UInt32& outNumParameters) override - { - if (inScope != kAudioUnitScope_Global || processor == nullptr) - { - outNumParameters = 0; - return kAudioUnitErr_InvalidParameter; - } - - const auto parameters = processor->getParameters(); - - if (outParameterList != nullptr) - { - for (size_t i = 0; i < parameters.size(); ++i) - outParameterList[i] = static_cast (parameters[i]->getHostParameterID()); - } - - outNumParameters = static_cast (parameters.size()); - return noErr; - } - - OSStatus GetParameterInfo (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - AudioUnitParameterInfo& outParameterInfo) override - { - if (inScope != kAudioUnitScope_Global || processor == nullptr) - return kAudioUnitErr_InvalidParameter; - - const auto parameters = processor->getParameters(); - const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inParameterID)); - if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) - return kAudioUnitErr_InvalidParameter; - - const auto& param = parameters[parameterIndex]; - - outParameterInfo.flags = kAudioUnitParameterFlag_IsReadable | kAudioUnitParameterFlag_HasCFNameString; - - if (! param->isReadOnly()) - outParameterInfo.flags |= kAudioUnitParameterFlag_IsWritable; - - outParameterInfo.cfNameString = param->getName().toCFString(); - param->getName().copyToUTF8 (outParameterInfo.name, sizeof (outParameterInfo.name)); - - outParameterInfo.unit = kAudioUnitParameterUnit_Generic; - outParameterInfo.minValue = param->getMinimumValue(); - outParameterInfo.maxValue = param->getMaximumValue(); - outParameterInfo.defaultValue = param->getDefaultValue(); - outParameterInfo.clumpID = 0; - - return noErr; - } - - OSStatus GetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - AudioUnitParameterValue& outValue) override - { - if (inScope != kAudioUnitScope_Global || processor == nullptr) - return kAudioUnitErr_InvalidParameter; - - const auto parameters = processor->getParameters(); - const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inID)); - if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) - return kAudioUnitErr_InvalidParameter; - - outValue = static_cast (parameters[parameterIndex]->getValue()); - return noErr; - } - - OSStatus SetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - AudioUnitParameterValue inValue, - UInt32 inBufferOffsetInFrames) override - { - if (inScope != kAudioUnitScope_Global || processor == nullptr) - return kAudioUnitErr_InvalidParameter; - - const auto parameters = processor->getParameters(); - const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inID)); - if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) - return kAudioUnitErr_InvalidParameter; - - if (parameters[parameterIndex]->isReadOnly() - || parameters[parameterIndex]->isPerformingChangeGesture()) - { - return noErr; - } - - parameters[parameterIndex]->setValue (static_cast (inValue)); - - std::unique_lock lock (parameterChangeMutex, std::try_to_lock); - if (lock.owns_lock()) - { - addParameterChangeByHostParameterID (*processor, - paramChangeBuffer, - static_cast (inID), - parameters[parameterIndex]->convertToNormalizedValue (static_cast (inValue)), - static_cast (inBufferOffsetInFrames)); - } - - return noErr; - } - - //============================================================================== - - UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override - { - if (processor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SupportedNumChannels requested without processor"); - return 0; - } - - channelInfoCache.clear(); - - const auto& busLayout = processor->getBusLayout(); - - int inputChannels = 0; - for (const auto& bus : busLayout.getInputBuses()) - if (bus.getType() == AudioBus::Type::Audio) - inputChannels = std::max (inputChannels, bus.getNumChannels()); - - int outputChannels = 0; - for (const auto& bus : busLayout.getOutputBuses()) - if (bus.getType() == AudioBus::Type::Audio) - outputChannels = std::max (outputChannels, bus.getNumChannels()); - - if (inputChannels > 0 || outputChannels > 0) - { - AUChannelInfo info; - info.inChannels = static_cast (inputChannels); - info.outChannels = static_cast (outputChannels); - channelInfoCache.push_back (info); - } - - if (outInfo != nullptr) - *outInfo = channelInfoCache.data(); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SupportedNumChannels returned " << String (static_cast (channelInfoCache.size())) << " layouts"); - - return static_cast (channelInfoCache.size()); - } - - //============================================================================== - - bool SupportsTail() override - { - return processor != nullptr && processor->getTailSamples() > 0; - } - - Float64 GetTailTime() override - { - const auto sampleRate = getCurrentSampleRate(); - if (processor == nullptr || sampleRate <= 0.0) - return 0.0; - - return static_cast (processor->getTailSamples()) / sampleRate; - } - - Float64 GetLatency() override - { - const auto sampleRate = getCurrentSampleRate(); - if (processor == nullptr || sampleRate <= 0.0) - return 0.0; - - return static_cast (processor->getLatencySamples()) / sampleRate; - } - - //============================================================================== - - std::optional createPositionInfo (const AudioTimeStamp* timeStamp) - { - AudioPlayHead::PositionInfo result; - bool hasPosition = false; - - if (timeStamp != nullptr && (timeStamp->mFlags & kAudioTimeStampSampleTimeValid) != 0) - { - const auto timeInSamples = static_cast (timeStamp->mSampleTime); - result.setTimeInSamples (timeInSamples); - - const auto sampleRate = getCurrentSampleRate(); - if (sampleRate > 0.0) - result.setTimeInSeconds (static_cast (timeInSamples) / sampleRate); - - hasPosition = true; - } - - Float64 currentBeat = 0.0; - Float64 currentTempo = 0.0; - if (CallHostBeatAndTempo (¤tBeat, ¤tTempo) == noErr) - { - result.setPpqPosition (currentBeat); - result.setBpm (currentTempo); - hasPosition = true; - } - - UInt32 deltaSamplesToNextBeat = 0; - Float32 timeSignatureNumerator = 0.0f; - UInt32 timeSignatureDenominator = 0; - Float64 currentMeasureDownBeat = 0.0; - if (CallHostMusicalTimeLocation (&deltaSamplesToNextBeat, - &timeSignatureNumerator, - &timeSignatureDenominator, - ¤tMeasureDownBeat) - == noErr) - { - ignoreUnused (deltaSamplesToNextBeat); - result.setTimeSignature (AudioPlayHead::TimeSignature { - static_cast (timeSignatureNumerator), - static_cast (timeSignatureDenominator) }); - result.setPpqPositionOfLastBarStart (currentMeasureDownBeat); - hasPosition = true; - } - - Boolean isPlaying = false; - Boolean transportStateChanged = false; - Float64 currentSampleInTimeline = 0.0; - Boolean isCycling = false; - Float64 cycleStartBeat = 0.0; - Float64 cycleEndBeat = 0.0; - if (CallHostTransportState (&isPlaying, - &transportStateChanged, - ¤tSampleInTimeline, - &isCycling, - &cycleStartBeat, - &cycleEndBeat) - == noErr) - { - ignoreUnused (transportStateChanged); - result.setIsPlaying (isPlaying); - result.setIsLooping (isCycling); - - if (isCycling) - result.setLoopPoints (AudioPlayHead::LoopPoints { cycleStartBeat, cycleEndBeat }); - - result.setTimeInSamples (static_cast (currentSampleInTimeline)); - - const auto sampleRate = getCurrentSampleRate(); - if (sampleRate > 0.0) - result.setTimeInSeconds (currentSampleInTimeline / sampleRate); - - hasPosition = true; - } - - return hasPosition ? std::make_optional (result) : std::nullopt; - } - - //============================================================================== - -#if YupPlugin_IsSynth - // Instrument: render audio and drain the MIDI buffer - OSStatus RenderBus (AudioUnitRenderActionFlags& ioActionFlags, - const AudioTimeStamp& inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames) override - { - if (processor == nullptr) - return kAudioUnitErr_NoConnection; - - auto& outputBus = Output (inBusNumber); - - outputBus.PrepareBuffer (inNumberFrames); - AudioBufferList& outBufList = outputBus.GetBufferList(); - - audioChannels.clear(); - for (UInt32 ch = 0; ch < outBufList.mNumberBuffers; ++ch) - audioChannels.push_back (static_cast (outBufList.mBuffers[ch].mData)); - - AudioSampleBuffer audioBuffer (audioChannels.data(), - static_cast (audioChannels.size()), - 0, - static_cast (inNumberFrames)); - - { - AudioPluginPlayHeadAU playHead (*this, &inTimeStamp); - std::unique_lock lock (midiMutex, std::try_to_lock); - auto& processMidiBuffer = lock.owns_lock() ? midiBuffer : emptyMidiBuffer; - std::unique_lock parameterLock (parameterChangeMutex, std::try_to_lock); - auto& processParamChangeBuffer = parameterLock.owns_lock() ? paramChangeBuffer : emptyParamChangeBuffer; - - AudioProcessContext context { audioBuffer, - processMidiBuffer, - processParamChangeBuffer, - &playHead }; - processAudioBlock (*processor, context, isBypassed); - - processMidiBuffer.clear(); - processParamChangeBuffer.clear(); - } - - return noErr; - } - - //============================================================================== - - OSStatus HandleMIDIEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 offsetSampleFrame) override - { - std::unique_lock lock (midiMutex, std::try_to_lock); - if (! lock.owns_lock()) - return noErr; - - const uint8_t rawData[3] = { - static_cast (status | channel), - data1, - data2 - }; - - const int numBytes = MidiMessage::getMessageLengthFromFirstByte (rawData[0]); - midiBuffer.addEvent (rawData, numBytes, static_cast (offsetSampleFrame)); - - return noErr; - } - - [[nodiscard]] bool CanScheduleParameters() const override - { - return false; - } - - bool StreamFormatWritable (AudioUnitScope inScope, AudioUnitElement inElement) override - { - return inScope == kAudioUnitScope_Output && inElement == 0; - } - - OSStatus HandleSysEx (const UInt8* inData, UInt32 inLength) override - { - std::unique_lock lock (midiMutex, std::try_to_lock); - if (! lock.owns_lock()) - return noErr; - - if (inData != nullptr && inLength > 0) - midiBuffer.addEvent (inData, static_cast (inLength), 0); - - return noErr; - } - -#else - // Effect: copy input to output and call processBlock - OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, - const AudioBufferList& inBuffer, - AudioBufferList& outBuffer, - UInt32 inFramesToProcess) override - { - if (processor == nullptr) - return kAudioUnitErr_NoConnection; - - const UInt32 numBuffers = std::min (inBuffer.mNumberBuffers, outBuffer.mNumberBuffers); - - audioChannels.clear(); - for (UInt32 ch = 0; ch < numBuffers; ++ch) - { - const auto* in = static_cast (inBuffer.mBuffers[ch].mData); - auto* out = static_cast (outBuffer.mBuffers[ch].mData); - - if (in != out) - std::memcpy (out, in, inFramesToProcess * sizeof (float)); - - audioChannels.push_back (out); - } - - AudioSampleBuffer audioBuffer (audioChannels.data(), - static_cast (audioChannels.size()), - 0, - static_cast (inFramesToProcess)); - - AudioPluginPlayHeadAU playHead (*this, nullptr); - std::unique_lock parameterLock (parameterChangeMutex, std::try_to_lock); - auto& processParamChangeBuffer = parameterLock.owns_lock() ? paramChangeBuffer : emptyParamChangeBuffer; - - AudioProcessContext context { audioBuffer, - midiBuffer, - processParamChangeBuffer, - &playHead }; - processAudioBlock (*processor, context, isBypassed); - midiBuffer.clear(); - processParamChangeBuffer.clear(); - - return noErr; - } -#endif - - //============================================================================== - - OSStatus SaveState (CFPropertyListRef* outData) override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState requested"); - - if (outData == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: outData is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - const auto baseResult = AudioPluginAUBase::SaveState (outData); - if (baseResult != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: base status=" << describeStatus (baseResult)); - return baseResult; - } - - if (processor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed without processor state: processor is null"); - return noErr; - } - - MemoryBlock data; - const auto result = processor->saveStateIntoMemory (data); - if (result.failed()) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed without processor state: " << result.getErrorMessage()); - return noErr; - } - - if (*outData != nullptr && CFGetTypeID (*outData) == CFDictionaryGetTypeID()) - { - NSData* nsData = data.getSize() > 0 - ? [NSData dataWithBytes:data.getData() length:data.getSize()] - : [NSData data]; - - auto* stateDictionary = const_cast (static_cast (*outData)); - CFDictionarySetValue (stateDictionary, - getProcessorStateKey(), - (__bridge CFDataRef) nsData); - } - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed with processor state: bytes=" << String (static_cast (data.getSize()))); - - return noErr; - } - - OSStatus RestoreState (CFPropertyListRef inData) override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState requested"); - - if (inData == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: inData is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - CFDataRef processorState = nullptr; - OSStatus baseResult = noErr; - - if (CFGetTypeID (inData) == CFDictionaryGetTypeID()) - { - processorState = static_cast (CFDictionaryGetValue (static_cast (inData), - getProcessorStateKey())); - - if (processorState != nullptr && CFGetTypeID (processorState) != CFDataGetTypeID()) - return kAudioUnitErr_InvalidPropertyValue; - - baseResult = AudioPluginAUBase::RestoreState (inData); - if (baseResult != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: base status=" << describeStatus (baseResult)); - return baseResult; - } - } - else if (CFGetTypeID (inData) == CFDataGetTypeID()) - { - processorState = static_cast (inData); - } - else - { - return kAudioUnitErr_InvalidPropertyValue; - } - - if (processorState == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState completed without processor state"); - return baseResult; - } - - if (processor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: processor is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - MemoryBlock data (CFDataGetBytePtr (processorState), - static_cast (CFDataGetLength (processorState))); - - processor->suspendProcessing (true); - const auto result = processor->loadStateFromMemory (data); - const bool ok = result.wasOk(); - processor->suspendProcessing (false); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState " << String (ok ? "completed" : "failed") << ": bytes=" << String (static_cast (data.getSize())) << (ok ? String() : ", error=" + result.getErrorMessage())); - - return ok ? static_cast (noErr) - : static_cast (kAudioUnitErr_InvalidPropertyValue); - } - - //============================================================================== - - OSStatus GetPresets (CFArrayRef* outData) const override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets requested"); - - if (processor == nullptr || outData == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets failed: processor=" << describePointer (processor.get()) << ", outData=" << describePointer (outData)); - return kAudioUnitErr_InvalidPropertyValue; - } - - const int numPresets = processor->getNumPresets(); - NSMutableArray* presetsArray = [[NSMutableArray alloc] initWithCapacity:numPresets]; - - for (int i = 0; i < numPresets; ++i) - { - AUPreset preset; - preset.presetNumber = i; - preset.presetName = processor->getPresetName (i).toCFString(); - - [presetsArray addObject:[NSValue valueWithBytes:&preset objCType:@encode (AUPreset)]]; - CFRelease (preset.presetName); - } - - *outData = (__bridge_retained CFArrayRef) presetsArray; - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets returned " << String (numPresets) << " presets"); - return noErr; - } - - OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet requested: preset=" << String (static_cast (inNewFactoryPreset.presetNumber))); - - if (processor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet failed: processor is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - if (! isPositiveAndBelow (static_cast (inNewFactoryPreset.presetNumber), processor->getNumPresets())) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet failed: preset out of range"); - return kAudioUnitErr_InvalidPropertyValue; - } - - processor->setCurrentPreset (static_cast (inNewFactoryPreset.presetNumber)); - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet completed"); - return noErr; - } - - //============================================================================== - - OSStatus GetPropertyInfo (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - UInt32& outDataSize, - bool& outWritable) override - { - if (inID == kAudioUnitProperty_OfflineRender) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); - - if (inScope != kAudioUnitScope_Global) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo OfflineRender failed: invalid scope"); - return kAudioUnitErr_InvalidScope; - } - - outDataSize = sizeof (UInt32); - outWritable = true; - return noErr; - } - - if (inID == kAudioUnitProperty_BypassEffect) - { - if (inScope != kAudioUnitScope_Global) - return kAudioUnitErr_InvalidScope; - - outDataSize = sizeof (UInt32); - outWritable = true; - return noErr; - } - - if (inID == kAudioUnitProperty_CocoaUI) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI requested: " << describeScopeAndElement (inScope, inElement)); - - if (inScope != kAudioUnitScope_Global) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI failed: invalid scope"); - return kAudioUnitErr_InvalidScope; - } - - if (processor != nullptr && processor->hasEditor()) - { - outDataSize = sizeof (AudioUnitCocoaViewInfo); - outWritable = false; - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI available"); - return noErr; - } - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI not available: processor=" << describePointer (processor.get()) << ", hasEditor=" << String (processor != nullptr && processor->hasEditor() ? "true" : "false")); - return kAudioUnitErr_PropertyNotInUse; - } - - const auto result = AudioPluginAUBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); - if (result != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); - } - - return result; - } - - OSStatus GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) override; // Implemented below (needs ObjC) - - OSStatus SetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - const void* inData, - UInt32 inDataSize) override - { - if (inID == kAudioUnitProperty_OfflineRender) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); - - if (inScope != kAudioUnitScope_Global) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender failed: invalid scope"); - return kAudioUnitErr_InvalidScope; - } - - if (inData == nullptr || inDataSize < sizeof (UInt32)) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender failed: inData=" << describePointer (inData) << ", inDataSize=" << String (static_cast (inDataSize))); - return kAudioUnitErr_InvalidPropertyValue; - } - - renderingOffline = *static_cast (inData) != 0; - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Offline rendering set to " << String (renderingOffline ? "true" : "false")); - - if (processor != nullptr) - processor->setOfflineProcessing (renderingOffline); - - return noErr; - } - - if (inID == kAudioUnitProperty_BypassEffect) - { - if (inScope != kAudioUnitScope_Global) - return kAudioUnitErr_InvalidScope; - - if (inData == nullptr || inDataSize < sizeof (UInt32)) - return kAudioUnitErr_InvalidPropertyValue; - - isBypassed = *static_cast (inData) != 0; - return noErr; - } - - const auto result = AudioPluginAUBase::SetProperty (inID, inScope, inElement, inData, inDataSize); - if (result != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); - } - - return result; - } - - //============================================================================== - - AudioProcessor* getProcessor() const { return processor.get(); } - - void registerEditorView (AudioPluginEditorViewAU* view) - { - if (view == nil) - return; - - std::lock_guard lock (editorViewsMutex); - editorViews.push_back ((__bridge void*) view); - } - - void unregisterEditorView (AudioPluginEditorViewAU* view) - { - if (view == nil) - return; - - std::lock_guard lock (editorViewsMutex); - editorViews.erase (std::remove (editorViews.begin(), editorViews.end(), (__bridge void*) view), editorViews.end()); - } - - void closeEditorViews(); - - static AudioPluginProcessorAU* findInstance (AudioUnit component) - { - std::lock_guard lock (getInstanceRegistryMutex()); - - const auto iter = getInstanceRegistry().find (component); - auto* instance = iter != getInstanceRegistry().end() ? iter->second : nullptr; - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "lookup instance: component=" << describePointer (component) << ", instance=" << describePointer (instance) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); - - return instance; - } - -private: - static std::mutex& getInstanceRegistryMutex() - { - static std::mutex mutex; - return mutex; - } - - static std::unordered_map& getInstanceRegistry() - { - static std::unordered_map instances; - return instances; - } - - static void registerInstance (AudioUnit component, AudioPluginProcessorAU* instance) - { - std::lock_guard lock (getInstanceRegistryMutex()); - - getInstanceRegistry()[component] = instance; - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "registered instance: component=" << describePointer (component) << ", instance=" << describePointer (instance) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); - } - - static void unregisterInstance (AudioUnit component) - { - std::lock_guard lock (getInstanceRegistryMutex()); - - const auto numRemoved = getInstanceRegistry().erase (component); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "unregistered instance: component=" << describePointer (component) << ", removed=" << String (static_cast (numRemoved)) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); - } - - void addParameterListeners() - { - removeParameterListeners(); - - if (processor == nullptr) - return; - - for (const auto& parameter : processor->getParameters()) - { - parameter->addListener (this); - listenedParameters.push_back (parameter); - } - } - - void removeParameterListeners() - { - for (auto& parameter : listenedParameters) - parameter->removeListener (this); - - listenedParameters.clear(); - } - - bool isValidProcessorParameterIndex (int indexInContainer) const - { - return processor != nullptr - && isPositiveAndBelow (indexInContainer, static_cast (processor->getParameters().size())); - } - - void parameterValueChanged (const AudioParameter::Ptr& parameter, int indexInContainer) override - { - if (! isValidProcessorParameterIndex (indexInContainer) || parameter->isReadOnly()) - return; - - AudioPluginAUBase::SetParameter (static_cast (parameter->getHostParameterID()), - kAudioUnitScope_Global, - 0, - static_cast (parameter->getValue()), - 0); - } - - void parameterGestureBegin (const AudioParameter::Ptr& parameter, int indexInContainer) override - { - ignoreUnused (parameter, indexInContainer); - } - - void parameterGestureEnd (const AudioParameter::Ptr& parameter, int indexInContainer) override - { - ignoreUnused (parameter, indexInContainer); - } - - Float64 getCurrentSampleRate() - { - return Output (0).GetStreamFormat().mSampleRate; - } - - ScopedYupInitialiser_GUI scopeInitialiser; - ScopedYupInitialiser_Windowing scopeWindowingInitialiser; - std::unique_ptr processor; - - MidiBuffer midiBuffer; - MidiBuffer emptyMidiBuffer; - ParameterChangeBuffer paramChangeBuffer; - ParameterChangeBuffer emptyParamChangeBuffer; - std::mutex midiMutex; - std::mutex parameterChangeMutex; - std::mutex editorViewsMutex; - std::vector channelInfoCache; - std::vector listenedParameters; - std::vector audioChannels; - std::vector editorViews; - AudioUnit componentInstance = nullptr; - bool renderingOffline = false; - bool isBypassed = false; -}; - -} // namespace yup - -//============================================================================== -// Objective-C editor view - -namespace yup -{ - -class AudioPluginEditorViewAUListener final : public ComponentListener -{ -public: - explicit AudioPluginEditorViewAUListener (AudioPluginEditorViewAU* owner) - : owner (owner) - { - } - - void componentResized (Component& component) override; - -private: - AudioPluginEditorViewAU* owner = nil; - - YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginEditorViewAUListener) -}; - -} // namespace yup - -@interface AudioPluginEditorViewAU : NSView -{ - yup::AudioPluginProcessorAU* _processorWrapper; - yup::AudioProcessor* _processor; - std::unique_ptr _processorEditor; - std::unique_ptr _processorEditorListener; - bool _resizingEditorToBounds; -} -- (instancetype)initWithAudioUnitWrapper:(yup::AudioPluginProcessorAU*)processorWrapper - preferredSize:(NSSize)size; -- (void)attachEditorIfNeeded; -- (void)detachEditorIfNeeded; -- (void)closeEditorIfNeeded; -- (void)closeEditorForProcessorDestruction; -- (void)resizeEditorToBounds; -- (void)resizeViewToEditorSize; -- (void)processorEditorResized; -@end - -@implementation AudioPluginEditorViewAU - -- (instancetype)initWithAudioUnitWrapper:(yup::AudioPluginProcessorAU*)processorWrapper - preferredSize:(NSSize)size -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "creating editor view: requestedWidth=" << yup::String (static_cast (size.width)) << ", requestedHeight=" << yup::String (static_cast (size.height)) << ", wrapper=" << yup::describePointer (processorWrapper) << ", view=" << yup::describePointer ((__bridge void*) self)); - - if ((self = [super initWithFrame:NSMakeRect (0, 0, size.width, size.height)])) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view initialised: view=" << yup::describePointer ((__bridge void*) self)); - _processorWrapper = processorWrapper; - _processor = processorWrapper != nullptr ? processorWrapper->getProcessor() : nullptr; - _resizingEditorToBounds = false; - [self setPostsFrameChangedNotifications:YES]; - - if (_processorWrapper != nullptr) - _processorWrapper->registerEditorView (self); - - if (_processor != nullptr && _processor->hasEditor()) - { - _processorEditor.reset (_processor->createEditor()); - - if (_processorEditor != nullptr) - { - const auto preferredSize = _processorEditor->getPreferredSize(); - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "created editor: preferredWidth=" << yup::String (preferredSize.getWidth()) << ", preferredHeight=" << yup::String (preferredSize.getHeight()) << ", editor=" << yup::describePointer (_processorEditor.get())); - - if (_processorEditor->isResizable()) - [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - else - [self setAutoresizingMask:NSViewNotSizable]; - - _processorEditorListener = std::make_unique (self); - _processorEditor->addComponentListener (_processorEditorListener.get()); - - [self setFrameSize:NSMakeSize (preferredSize.getWidth(), preferredSize.getHeight())]; - [self resizeEditorToBounds]; - } - else - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "processor returned null editor"); - } - } - else - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "processor has no editor"); - } - } - else - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view initialisation failed"); - } - - return self; -} - -- (void)viewDidMoveToWindow -{ - [super viewDidMoveToWindow]; - - if ([self window] != nil) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view moved to window: view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window]) << ", contentView=" << yup::describePointer ((__bridge void*) [[self window] contentView])); - - [self attachEditorIfNeeded]; - } - else - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view removed from window: view=" << yup::describePointer ((__bridge void*) self)); - - [self detachEditorIfNeeded]; - } -} - -- (void)setFrameSize:(NSSize)newSize -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view frame size changed: width=" << yup::String (static_cast (newSize.width)) << ", height=" << yup::String (static_cast (newSize.height))); - - [super setFrameSize:newSize]; - [self resizeEditorToBounds]; -} - -- (void)attachEditorIfNeeded -{ - if (_processorEditor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attachEditorIfNeeded skipped: editor is null"); - return; - } - - if (_processorEditor->isOnDesktop()) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attachEditorIfNeeded skipped: editor is already on desktop"); - return; - } - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attaching editor to native view: editor=" << yup::describePointer (_processorEditor.get()) << ", view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window])); - - [self resizeEditorToBounds]; - - yup::ComponentNative::Flags flags = yup::ComponentNative::defaultFlags & ~yup::ComponentNative::decoratedWindow; - - if (_processorEditor->shouldRenderContinuous()) - flags.set (yup::ComponentNative::renderContinuous); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor native options: renderContinuous=" << yup::String (_processorEditor->shouldRenderContinuous() ? "true" : "false") << ", resizable=" << yup::String (_processorEditor->isResizable() ? "true" : "false")); - - auto options = yup::ComponentNative::Options() - .withFlags (flags) - .withResizableWindow (_processorEditor->isResizable()); - - _processorEditor->addToDesktop (options, (__bridge void*) self); - _processorEditor->setVisible (true); - _processorEditor->attachedToNative(); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor attached to native view: isOnDesktop=" << yup::String (_processorEditor->isOnDesktop() ? "true" : "false")); -} - -- (void)detachEditorIfNeeded -{ - if (_processorEditor == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detachEditorIfNeeded skipped: editor is null"); - return; - } - - if (! _processorEditor->isOnDesktop()) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detachEditorIfNeeded skipped: editor is not on desktop"); - return; - } - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detaching editor from native view: editor=" << yup::describePointer (_processorEditor.get()) << ", view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window])); - - yup::endActiveParameterGestures (_processor); - _processorEditor->setVisible (false); - _processorEditor->removeFromDesktop(); - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor detached from native view: isOnDesktop=" << yup::String (_processorEditor->isOnDesktop() ? "true" : "false")); -} - -- (void)closeEditorIfNeeded -{ - [self detachEditorIfNeeded]; - - yup::endActiveParameterGestures (_processor); - - if (_processorEditor != nullptr && _processorEditorListener != nullptr) - _processorEditor->removeComponentListener (_processorEditorListener.get()); - - _processorEditorListener.reset(); - _processorEditor.reset(); -} - -- (void)closeEditorForProcessorDestruction -{ - [self closeEditorIfNeeded]; - - _processorWrapper = nullptr; - _processor = nullptr; -} - -- (void)resizeEditorToBounds -{ - if (_processorEditor == nullptr) - return; - - const auto bounds = [self bounds]; - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "resizing editor to bounds: width=" << yup::String (static_cast (NSWidth (bounds))) << ", height=" << yup::String (static_cast (NSHeight (bounds))) << ", editor=" << yup::describePointer (_processorEditor.get())); - - const auto scoped = yup::ScopedValueSetter (_resizingEditorToBounds, true); - - _processorEditor->setBounds ({ 0.0f, - 0.0f, - yup::jmax (1.0f, static_cast (NSWidth (bounds))), - yup::jmax (1.0f, static_cast (NSHeight (bounds))) }); -} - -- (void)resizeViewToEditorSize -{ - if (_processorEditor == nullptr || ! _processorEditor->isResizable()) - return; - - const auto newSize = NSMakeSize (yup::jmax (1.0f, _processorEditor->getWidth()), - yup::jmax (1.0f, _processorEditor->getHeight())); - - if (NSEqualSizes ([self frame].size, newSize)) - return; - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "resizing editor view to editor: width=" << yup::String (static_cast (newSize.width)) << ", height=" << yup::String (static_cast (newSize.height)) << ", editor=" << yup::describePointer (_processorEditor.get())); - - [super setFrameSize:newSize]; -} - -- (void)processorEditorResized -{ - if (_resizingEditorToBounds) - return; - - [self resizeViewToEditorSize]; -} - -- (void)dealloc -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "destroying editor view: view=" << yup::describePointer ((__bridge void*) self) << ", editor=" << yup::describePointer (_processorEditor.get()) << ", processor=" << yup::describePointer (_processor)); - - [self closeEditorIfNeeded]; - - if (_processorWrapper != nullptr) - _processorWrapper->unregisterEditorView (self); - - _processorWrapper = nullptr; - _processor = nullptr; -} - -@end - -namespace yup -{ - -void AudioPluginEditorViewAUListener::componentResized (Component& component) -{ - ignoreUnused (component); - - if (owner != nil) - [owner processorEditorResized]; -} - -void AudioPluginProcessorAU::closeEditorViews() -{ - std::vector viewsToClose; - - { - std::lock_guard lock (editorViewsMutex); - viewsToClose.swap (editorViews); - } - - for (auto* view : viewsToClose) - if (view != nullptr) - [(__bridge AudioPluginEditorViewAU*) view closeEditorForProcessorDestruction]; -} - -} // namespace yup - -//============================================================================== -// Cocoa view factory - -@interface AudioPluginProcessorAUViewFactory : NSObject -@end - -@implementation AudioPluginProcessorAUViewFactory - -- (unsigned)interfaceVersion -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory interfaceVersion requested"); - return 0; -} - -- (NSString*)description -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory description requested"); - return @YupPlugin_Name; -} - -- (NSView*)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize -{ - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory requested editor view: audioUnit=" << yup::describePointer (inAudioUnit) << ", preferredWidth=" << yup::String (static_cast (inPreferredSize.width)) << ", preferredHeight=" << yup::String (static_cast (inPreferredSize.height))); - - auto* proc = yup::AudioPluginProcessorAU::findInstance (inAudioUnit); - if (proc == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory failed: AU instance not found"); - return nil; - } - - if (proc->getProcessor() == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory failed: processor is null"); - return nil; - } - - return [[AudioPluginEditorViewAU alloc] initWithAudioUnitWrapper:proc - preferredSize:inPreferredSize]; -} - -@end - -//============================================================================== -// GetProperty implementation (needs ObjC) - -namespace yup -{ - -OSStatus AudioPluginProcessorAU::GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) -{ - if (inID == kAudioUnitProperty_OfflineRender) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); - - if (inScope != kAudioUnitScope_Global) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender failed: invalid scope"); - return kAudioUnitErr_InvalidScope; - } - - if (outData == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender failed: outData is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - *static_cast (outData) = renderingOffline ? 1u : 0u; - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender returned " << String (renderingOffline ? "true" : "false")); - return noErr; - } - - if (inID == kAudioUnitProperty_BypassEffect) - { - if (inScope != kAudioUnitScope_Global) - return kAudioUnitErr_InvalidScope; - - if (outData == nullptr) - return kAudioUnitErr_InvalidPropertyValue; - - *static_cast (outData) = isBypassed ? 1u : 0u; - return noErr; - } - - if (inID == kAudioUnitProperty_CocoaUI) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI requested: " << describeScopeAndElement (inScope, inElement)); - - if (inScope != kAudioUnitScope_Global) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: invalid scope"); - return kAudioUnitErr_InvalidScope; - } - - if (processor == nullptr || ! processor->hasEditor()) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: editor not available"); - return kAudioUnitErr_PropertyNotInUse; - } - - if (outData == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: outData is null"); - return kAudioUnitErr_InvalidPropertyValue; - } - - auto* info = static_cast (outData); - - // The bundle location is this plugin's own bundle - NSBundle* bundle = [NSBundle bundleForClass:[AudioPluginProcessorAUViewFactory class]]; - if (bundle == nil) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: bundle is nil"); - return kAudioUnitErr_InvalidPropertyValue; - } - - auto* bundleLocation = (__bridge_retained CFURLRef)[bundle bundleURL]; - auto* viewClass = CFStringCreateWithCString (kCFAllocatorDefault, - "AudioPluginProcessorAUViewFactory", - kCFStringEncodingUTF8); - - if (bundleLocation == nullptr || viewClass == nullptr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: bundleURL=" << describePointer (bundleLocation) << ", viewClass=" << describePointer (viewClass)); - - if (bundleLocation != nullptr) - CFRelease (bundleLocation); - - if (viewClass != nullptr) - CFRelease (viewClass); - - return kAudioUnitErr_InvalidPropertyValue; - } - - info->mCocoaAUViewBundleLocation = bundleLocation; - info->mCocoaAUViewClass[0] = viewClass; - - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI returned view factory: bundle=" << String::fromCFString ((__bridge CFStringRef)[[bundle bundleURL] absoluteString])); - - return noErr; - } - - const auto result = AudioPluginAUBase::GetProperty (inID, inScope, inElement, outData); - if (result != noErr) - { - YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); - } - - return result; -} - -} // namespace yup - -//============================================================================== -// Factory entry point - -#if YupPlugin_IsSynth -using AudioPluginProcessorAU = yup::AudioPluginProcessorAU; -AUSDK_COMPONENT_ENTRY (ausdk::AUMusicDeviceFactory, AudioPluginProcessorAU) -#else -using AudioPluginProcessorAU = yup::AudioPluginProcessorAU; -AUSDK_COMPONENT_ENTRY (ausdk::AUBaseProcessFactory, AudioPluginProcessorAU) -#endif - -#endif // YUP_MAC diff --git a/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3_main.mm b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3_main.mm new file mode 100644 index 000000000..87d3d5c06 --- /dev/null +++ b/modules/yup_audio_plugin_client/auv3/yup_audio_plugin_client_AUv3_main.mm @@ -0,0 +1,27 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2026 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#import + +int main (int argc, const char* argv[]) +{ + return NSApplicationMain (argc, argv); +} diff --git a/modules/yup_audio_plugin_host/host/yup_AudioPluginDescription.h b/modules/yup_audio_plugin_host/host/yup_AudioPluginDescription.h index ee6401553..58ade8ea5 100644 --- a/modules/yup_audio_plugin_host/host/yup_AudioPluginDescription.h +++ b/modules/yup_audio_plugin_host/host/yup_AudioPluginDescription.h @@ -49,13 +49,16 @@ struct AudioPluginDescription Format-specific unique identifier. - VST3: base64-encoded 16-byte FUID - CLAP: clap_plugin_descriptor::id string - - AUv2: "type/subt/mfgr" four-char-code triplet + - AU: "type/subt/mfgr" four-char-code triplet */ String identifier; /** Absolute path to the plugin file or bundle on disk. */ String fileOrBundlePath; + /** True when the component is an Audio Unit v3 (App Extension) rather than a v2 component. */ + bool isV3 = false; + /** True when the plugin is a synthesiser / instrument. */ bool isInstrument = false; @@ -83,6 +86,7 @@ struct AudioPluginDescription && category == other.category && identifier == other.identifier && fileOrBundlePath == other.fileOrBundlePath + && isV3 == other.isV3 && isInstrument == other.isInstrument && isEffect == other.isEffect && numInputChannels == other.numInputChannels diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.h b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.h similarity index 72% rename from modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.h rename to modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.h index 08a597704..82ceb184c 100644 --- a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.h +++ b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.h @@ -25,27 +25,29 @@ namespace yup { /** - AudioPluginFormat implementation for AUv2 (macOS only). + AudioPluginFormat implementation for Audio Units (macOS only). - Enumerates AudioComponents via AudioComponentFindNext() and wraps each - component via AudioComponentInstanceNew(). + Enumerates all AudioComponents via a single AudioComponentFindNext() pass, + covering both AUv2 (.component) and AUv3 (App Extension) plugins. + AUv3 components are detected via kAudioUnitComponentFlag_IsV3AudioUnit and + instantiated asynchronously using AudioComponentInstantiate(). */ -class AUv2Format : public AudioPluginFormat +class AUFormat : public AudioPluginFormat { public: - AUv2Format(); - ~AUv2Format() override; + AUFormat(); + ~AUFormat() override; AudioPluginFormatType getFormatType() const override; String getFormatName() const override; StringArray getFileExtensions() const override; - /** Returns an empty FileSearchPath — AUv2 uses AudioComponent registry. */ + /** Returns an empty FileSearchPath — AU uses AudioComponent registry. */ FileSearchPath getDefaultSearchPaths() const override; /** Passing an invalid File triggers full AudioComponent registry scan. - Otherwise scans only the component matching the file's bundle identifier. + The file argument is ignored; all registered components are enumerated. */ ResultValue> scanFile (const File& file) override; diff --git a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.mm b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.mm similarity index 93% rename from modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.mm rename to modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.mm index 33d665aee..39c9b0e8f 100644 --- a/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AUv2.mm +++ b/modules/yup_audio_plugin_host/native/yup_AudioPluginInstance_AU.mm @@ -56,6 +56,7 @@ AudioPluginDescription descriptionFromComponent(AudioComponent comp, AudioPluginDescription desc; desc.formatType = AudioPluginFormatType::audioUnit; desc.identifier = makeIdentifier(acd); + desc.isV3 = (acd.componentFlags & kAudioUnitComponentFlag_IsV3AudioUnit) != 0; CFStringRef nameRef = nullptr; AudioComponentCopyName(comp, &nameRef); @@ -159,10 +160,10 @@ AudioUnitEvent makeAUParameterEvent(AudioUnit audioUnit, //============================================================================== -class AUv2Editor : public AudioProcessorEditor +class AUEditor : public AudioProcessorEditor { public: - static std::unique_ptr create(AudioUnit audioUnit) + static std::unique_ptr create(AudioUnit audioUnit) { UInt32 size = 0; Boolean writable = false; @@ -220,10 +221,10 @@ AudioUnitEvent makeAUParameterEvent(AudioUnit audioUnit, if (view == nil) return nullptr; - return std::unique_ptr(new AUv2Editor(view, viewBundle, viewFactory)); + return std::unique_ptr(new AUEditor(view, viewBundle, viewFactory)); } - ~AUv2Editor() override + ~AUEditor() override { detachCocoaView(); } @@ -254,7 +255,7 @@ void detachedFromNative() override } private: - AUv2Editor(NSView* view, NSBundle* viewBundle, id viewFactory) + AUEditor(NSView* view, NSBundle* viewBundle, id viewFactory) : cocoaView(view), cocoaViewBundle(viewBundle), cocoaViewFactory(viewFactory) { auto size = [cocoaView frame].size; @@ -328,15 +329,15 @@ void updateCocoaViewFrame() NSBundle* __strong cocoaViewBundle = nil; id __strong cocoaViewFactory = nil; - YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AUv2Editor) + YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AUEditor) }; //============================================================================== -class AUv2Instance : public AudioPluginInstance, private AudioParameter::Listener +class AUInstance : public AudioPluginInstance, private AudioParameter::Listener { public: - AUv2Instance(const AudioPluginDescription& desc, + AUInstance(const AudioPluginDescription& desc, AudioUnit unit, AudioBusLayout busLayout) : AudioPluginInstance(desc, std::move(busLayout)), audioUnit(unit) @@ -345,7 +346,7 @@ void updateCocoaViewFrame() installLatencyListener(); } - ~AUv2Instance() override + ~AUInstance() override { removeLatencyListener(); removeParameterListeners(); @@ -700,7 +701,7 @@ bool hasEditor() const override AudioProcessorEditor* createEditor() override { - if (auto editor = AUv2Editor::create(audioUnit)) + if (auto editor = AUEditor::create(audioUnit)) return editor.release(); return nullptr; @@ -729,7 +730,7 @@ int getLatencySamples() override //============================================================================== - static std::unique_ptr create(const AudioPluginDescription& desc, + static std::unique_ptr create(const AudioPluginDescription& desc, const AudioPluginHostContext& context) { // Parse "type/subt/mfgr" identifier @@ -755,12 +756,36 @@ int getLatencySamples() override return nullptr; AudioUnit unit = nullptr; - if (AudioComponentInstanceNew(comp, &unit) != noErr || unit == nullptr) + + if (desc.isV3) + { + // AUv3 components must be instantiated asynchronously; use a semaphore to wait + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block AudioUnit blockUnit = nullptr; + + AudioComponentInstantiate(comp, + kAudioComponentInstantiation_LoadOutOfProcess, + ^(AudioComponentInstance au, OSStatus err) { + if (err == noErr) + blockUnit = au; + dispatch_semaphore_signal(semaphore); + }); + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + unit = blockUnit; + } + else + { + if (AudioComponentInstanceNew(comp, &unit) != noErr) + return nullptr; + } + + if (unit == nullptr) return nullptr; AudioBusLayout busLayout = makeBusLayout(desc, acd.componentType); - auto instance = std::make_unique(desc, unit, std::move(busLayout)); + auto instance = std::make_unique(desc, unit, std::move(busLayout)); instance->setNonRealtime(context.isNonRealtime); return instance; } @@ -842,7 +867,7 @@ static void parameterEventCallback(void* userData, UInt64, AudioUnitParameterValue parameterValue) { - auto* instance = static_cast(userData); + auto* instance = static_cast(userData); if (instance == nullptr || event == nullptr || event->mEventType != kAudioUnitEvent_ParameterValueChange) return; @@ -877,7 +902,7 @@ static OSStatus inputRenderCallback(void* refCon, UInt32 numFrames, AudioBufferList* ioData) { - auto* instance = static_cast(refCon); + auto* instance = static_cast(refCon); if (ioData == nullptr) return noErr; @@ -1009,7 +1034,7 @@ static OSStatus hostBeatAndTempoCallback(void* userData, Float64* outCurrentBeat, Float64* outCurrentTempo) { - auto* instance = static_cast(userData); + auto* instance = static_cast(userData); if (instance == nullptr) return kAudioUnitErr_CannotDoInCurrentContext; @@ -1044,7 +1069,7 @@ static OSStatus hostMusicalTimeLocationCallback(void* userData, UInt32* outTimeSigDenominator, Float64* outCurrentMeasureDownBeat) { - auto* instance = static_cast(userData); + auto* instance = static_cast(userData); if (instance == nullptr) return kAudioUnitErr_CannotDoInCurrentContext; @@ -1137,7 +1162,7 @@ static OSStatus fillHostTransportState(void* userData, Float64* outCycleStartBeat, Float64* outCycleEndBeat) { - auto* instance = static_cast(userData); + auto* instance = static_cast(userData); if (instance == nullptr) return kAudioUnitErr_CannotDoInCurrentContext; @@ -1215,7 +1240,7 @@ static OSStatus midiOutputCallback(void* userData, UInt32, const MIDIPacketList* packetList) { - auto* instance = static_cast(userData); + auto* instance = static_cast(userData); if (instance == nullptr || instance->currentMidiOutputBuffer == nullptr || packetList == nullptr) return noErr; @@ -1425,7 +1450,7 @@ static void latencyPropertyChanged(void* userData, if (propertyID != kAudioUnitProperty_Latency) return; - auto* instance = static_cast (userData); + auto* instance = static_cast (userData); if (instance != nullptr) instance->setLatencySamples(instance->getLatencySamples()); } @@ -1465,55 +1490,59 @@ static void latencyPropertyChanged(void* userData, //============================================================================== -AUv2Format::AUv2Format() = default; -AUv2Format::~AUv2Format() = default; +AUFormat::AUFormat() = default; +AUFormat::~AUFormat() = default; -AudioPluginFormatType AUv2Format::getFormatType() const +AudioPluginFormatType AUFormat::getFormatType() const { return AudioPluginFormatType::audioUnit; } -String AUv2Format::getFormatName() const +String AUFormat::getFormatName() const { - return "AUv2"; + return "AU"; } -StringArray AUv2Format::getFileExtensions() const +StringArray AUFormat::getFileExtensions() const { return {}; } -FileSearchPath AUv2Format::getDefaultSearchPaths() const +FileSearchPath AUFormat::getDefaultSearchPaths() const { return {}; } -ResultValue> AUv2Format::scanFile(const File&) +ResultValue> AUFormat::scanFile(const File&) { - // Scan by enumerating the AudioComponent registry (ignores file argument) + // Scan all AudioComponents in one pass using a zeroed descriptor, then filter to hostable types std::vector results; - const OSType types[] = { - kAudioUnitType_MusicDevice, - kAudioUnitType_Effect, - kAudioUnitType_MusicEffect, - kAudioUnitType_Generator, - kAudioUnitType_MIDIProcessor}; + auto isHostableType = [](OSType type) -> bool + { + return type == kAudioUnitType_MusicDevice + || type == kAudioUnitType_Effect + || type == kAudioUnitType_MusicEffect + || type == kAudioUnitType_Generator + || type == kAudioUnitType_MIDIProcessor + || type == kAudioUnitType_Panner + || type == kAudioUnitType_Mixer; + }; + + AudioComponentDescription search{}; + AudioComponent comp = nullptr; - for (OSType type : types) + while ((comp = AudioComponentFindNext(comp, &search)) != nullptr) { - AudioComponentDescription search{}; - search.componentType = type; + AudioComponentDescription acd{}; + AudioComponentGetDescription(comp, &acd); - AudioComponent comp = nullptr; - while ((comp = AudioComponentFindNext(comp, &search)) != nullptr) - { - AudioComponentDescription acd{}; - AudioComponentGetDescription(comp, &acd); - auto desc = descriptionFromComponent(comp, acd); - YUP_MODULE_DBG (PLUGIN_HOST_AU, "scan found: " << desc.name << " [" << desc.identifier << "]"); - results.push_back(std::move(desc)); - } + if (! isHostableType(acd.componentType)) + continue; + + auto desc = descriptionFromComponent(comp, acd); + YUP_MODULE_DBG (PLUGIN_HOST_AU, "scan found: " << desc.name << " [" << desc.identifier << "] v3=" << (int) desc.isV3); + results.push_back(std::move(desc)); } YUP_MODULE_DBG (PLUGIN_HOST_AU, "scan complete: " << results.size() << " AudioComponents found"); @@ -1524,18 +1553,18 @@ static void latencyPropertyChanged(void* userData, return makeResultValueOk(std::move(results)); } -ResultValue> AUv2Format::loadPlugin( +ResultValue> AUFormat::loadPlugin( const AudioPluginDescription& description, const AudioPluginHostContext& context) { YUP_MODULE_DBG (PLUGIN_HOST_AU, "loading: " << description.name << " [" << description.identifier << "]"); - auto instance = AUv2Instance::create(description, context); + auto instance = AUInstance::create(description, context); if (instance == nullptr) { YUP_MODULE_DBG (PLUGIN_HOST_AU, "load failed: " << description.name); - return makeResultValueFail("Failed to instantiate AUv2 plugin: " + description.name); + return makeResultValueFail("Failed to instantiate AU plugin: " + description.name); } YUP_MODULE_DBG (PLUGIN_HOST_AU, "loaded: " << description.name); diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp index 018fb2629..e6e7754ff 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.cpp @@ -97,5 +97,5 @@ using CLAPModuleHandle = void*; #import #import -#include "native/yup_AudioPluginInstance_AUv2.mm" +#include "native/yup_AudioPluginInstance_AU.mm" #endif diff --git a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h index d4f68f1cc..acb84f0ae 100644 --- a/modules/yup_audio_plugin_host/yup_audio_plugin_host.h +++ b/modules/yup_audio_plugin_host/yup_audio_plugin_host.h @@ -28,7 +28,7 @@ vendor: yup version: 1.0.0 name: YUP Audio Plugin Host - description: In-process hosting of VST3, CLAP, and AUv2 audio plugins. + description: In-process hosting of VST3, CLAP, and AU (v2 and v3) audio plugins. website: https://github.com/kunitoki/yup license: ISC @@ -46,7 +46,7 @@ //============================================================================== /** Config: YUP_ENABLE_PLUGIN_HOST_AU_LOGGING - Enable debug logging for AUv2 plugin scanning and loading. + Enable debug logging for AU plugin scanning and loading. */ #ifndef YUP_ENABLE_PLUGIN_HOST_AU_LOGGING #define YUP_ENABLE_PLUGIN_HOST_AU_LOGGING 0 @@ -82,4 +82,4 @@ //============================================================================== #include "native/yup_AudioPluginInstance_VST3.h" #include "native/yup_AudioPluginInstance_CLAP.h" -#include "native/yup_AudioPluginInstance_AUv2.h" +#include "native/yup_AudioPluginInstance_AU.h" From 11c746b64b5dc1618b4b36dfb58ced895d18096e Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 00:01:44 +0200 Subject: [PATCH 05/10] Fix AUv3 compiling for windows/linux --- cmake/yup_audio_plugin.cmake | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index 7eb54019e..60b9cfeac 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -175,22 +175,8 @@ function (yup_audio_plugin) "${YUP_ARG_UNPARSED_ARGUMENTS}") endif() - # ==== Build AAX plugin target - if (YUP_ARG_PLUGIN_CREATE_AAX) - _yup_audio_plugin_create_aax ( - "${target_name}" - "${target_version}" - "${target_ide_group}" - "${target_bundle_id}" - "${target_app_namespace}" - "${target_cxx_standard}" - "${additional_libraries}" - "${YUP_ARG_MODULES}" - "${YUP_ARG_UNPARSED_ARGUMENTS}") - endif() - - # ==== Build AUv3 plugin target (macOS only) - if (YUP_ARG_PLUGIN_CREATE_AUv3) + # ==== Build AUv3 plugin target (macOS only for now, iOS support is planned) + if (YUP_ARG_PLUGIN_CREATE_AUv3 AND YUP_PLATFORM_MAC) cmake_parse_arguments (AUv3_ARGS "" "PLUGIN_IS_SYNTH;PLUGIN_AU_SUBTYPE;PLUGIN_AU_MANUFACTURER;PLUGIN_NAME;PLUGIN_VERSION;PLUGIN_ID;PLUGIN_VENDOR;PLUGIN_DESCRIPTION;PLUGIN_URL;PLUGIN_EMAIL;PLUGIN_IS_MONO" "" ${YUP_ARG_UNPARSED_ARGUMENTS}) @@ -225,6 +211,20 @@ function (yup_audio_plugin) PLUGIN_IS_MONO ${AUv3_ARGS_PLUGIN_IS_MONO}) endif() + # ==== Build AAX plugin target + if (YUP_ARG_PLUGIN_CREATE_AAX) + _yup_audio_plugin_create_aax ( + "${target_name}" + "${target_version}" + "${target_ide_group}" + "${target_bundle_id}" + "${target_app_namespace}" + "${target_cxx_standard}" + "${additional_libraries}" + "${YUP_ARG_MODULES}" + "${YUP_ARG_UNPARSED_ARGUMENTS}") + endif() + # ==== Create composite target for all enabled plugin formats set (_all_plugin_targets "") if (YUP_ARG_PLUGIN_CREATE_CLAP) From cc71be817e5423950864904c528d0b41326a4095 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 01:09:40 +0200 Subject: [PATCH 06/10] More AAX work --- .../mac/AudioPluginInfo_AAX.plist.in | 34 + cmake/plugins/yup_plugin_aax.cmake | 53 +- cmake/yup_audio_plugin.cmake | 8 +- .../aax/yup_audio_plugin_client_AAX.cpp | 1073 ++++++----------- 4 files changed, 431 insertions(+), 737 deletions(-) create mode 100644 cmake/platforms/mac/AudioPluginInfo_AAX.plist.in diff --git a/cmake/platforms/mac/AudioPluginInfo_AAX.plist.in b/cmake/platforms/mac/AudioPluginInfo_AAX.plist.in new file mode 100644 index 000000000..dcc4517d3 --- /dev/null +++ b/cmake/platforms/mac/AudioPluginInfo_AAX.plist.in @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_BUNDLE_VERSION}, Copyright (c) 2026 + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + @YUP_AUDIO_PLUGIN_BUNDLE_PACKAGE_TYPE@ + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + PTul + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + LSMultipleInstancesProhibited + + LSPrefersCarbon + + NSAppleScriptEnabled + No + CSResourcesFileMapped + + + diff --git a/cmake/plugins/yup_plugin_aax.cmake b/cmake/plugins/yup_plugin_aax.cmake index 6d66ec420..3b0306653 100644 --- a/cmake/plugins/yup_plugin_aax.cmake +++ b/cmake/plugins/yup_plugin_aax.cmake @@ -32,6 +32,7 @@ function (_yup_find_aax_sdk) set (AAX_SDK_ROOT "$ENV{YUP_AAX_SDK_ROOT}") else() _yup_message (STATUS "YUP_AAX_SDK_ROOT not set — AAX plugin support disabled") + # _yup_message (FATAL_ERROR "YUP_AAX_SDK_ROOT must be set when PLUGIN_CREATE_AAX is enabled") return() endif() @@ -45,6 +46,9 @@ function (_yup_find_aax_sdk) target_include_directories (yup_audio_plugin_client_aaxsdk_headers INTERFACE "${AAX_SDK_ROOT}/Interfaces" "${AAX_SDK_ROOT}/Interfaces/ACF") + target_compile_options (yup_audio_plugin_client_aaxsdk_headers INTERFACE + $<$:-Wno-multichar> + $<$:-Wno-undef-prefix>) # --- Build AAXLibrary (static library from SDK sources) --- set (AAX_LIBRARY_SOURCE_DIR "${AAX_SDK_ROOT}/Libs/AAXLibrary/source") @@ -54,27 +58,31 @@ function (_yup_find_aax_sdk) file (GLOB_RECURSE AAX_LIBRARY_SOURCES "${AAX_LIBRARY_SOURCE_DIR}/*.cpp") + # AAX_CAutoreleasePool.Win.cpp and .OSX.mm define the same symbols unguarded if (YUP_PLATFORM_MAC) + file (GLOB_RECURSE AAX_LIBRARY_WIN_SOURCES + "${AAX_LIBRARY_SOURCE_DIR}/*.Win.cpp") + if (AAX_LIBRARY_WIN_SOURCES) + list (REMOVE_ITEM AAX_LIBRARY_SOURCES ${AAX_LIBRARY_WIN_SOURCES}) + endif() file (GLOB_RECURSE AAX_LIBRARY_OBJC_SOURCES - "${AAX_LIBRARY_SOURCE_DIR}/*.mm" - "${AAX_LIBRARY_SOURCE_DIR}/*.OSX.*") - list (APPEND AAX_LIBRARY_SOURCES ${AAX_LIBRARY_OBJC_SOURCES}) - else() - # Exclude macOS-specific files on Windows/Linux - file (GLOB_RECURSE AAX_LIBRARY_OSX_SOURCES - "${AAX_LIBRARY_SOURCE_DIR}/*_OSX.*" "${AAX_LIBRARY_SOURCE_DIR}/*.mm") - if (AAX_LIBRARY_OSX_SOURCES) - list (REMOVE_ITEM AAX_LIBRARY_SOURCES ${AAX_LIBRARY_OSX_SOURCES}) - endif() + list (APPEND AAX_LIBRARY_SOURCES ${AAX_LIBRARY_OBJC_SOURCES}) endif() + # Class factory implementation required by AAX_Init.cpp + list (APPEND AAX_LIBRARY_SOURCES "${AAX_SDK_ROOT}/Interfaces/ACF/CACFClassFactory.cpp") + if (AAX_LIBRARY_SOURCES) add_library (yup_audio_plugin_client_aaxlib STATIC ${AAX_LIBRARY_SOURCES}) target_compile_features (yup_audio_plugin_client_aaxlib PRIVATE cxx_std_17) target_include_directories (yup_audio_plugin_client_aaxlib PRIVATE "${AAX_SDK_ROOT}/Interfaces" - "${AAX_SDK_ROOT}/Interfaces/ACF") + "${AAX_SDK_ROOT}/Interfaces/ACF" + "${AAX_SDK_ROOT}/Libs/AAXLibrary/include") + target_compile_options (yup_audio_plugin_client_aaxlib PRIVATE + $<$:-Wno-multichar> + $<$:-Wno-undef-prefix>) target_compile_definitions (yup_audio_plugin_client_aaxlib PRIVATE AAX_LIBRARY_BINARY=1) @@ -129,6 +137,11 @@ function (_yup_audio_plugin_create_aax _yup_find_aax_sdk() + if (NOT TARGET yup_audio_plugin_client_aaxsdk) + _yup_message (STATUS "Skipping AAX plugin target for ${target_name} (AAX SDK not available)") + return() + endif() + _yup_message (STATUS "Setting up AAX plugin client") _yup_module_setup_plugin_client ( ${target_name} @@ -151,6 +164,12 @@ function (_yup_audio_plugin_create_aax YUP_AUDIO_PLUGIN_ENABLE_AAX=1 YUP_STANDALONE_APPLICATION=0) + # The ACF entry points (ACFRegisterPlugin, ACFStartup, ...) exported by the + # plugin binary are defined in the SDK's AAX_Exports.cpp + get_target_property (aax_sdk_root yup_audio_plugin_client_aaxsdk YUP_AAX_SDK_ROOT) + target_sources (${target_name}_aax_plugin PRIVATE + "${aax_sdk_root}/Interfaces/AAX_Exports.cpp") + target_link_libraries (${target_name}_aax_plugin PRIVATE ${target_name}_shared yup_audio_plugin_client @@ -197,12 +216,18 @@ function (_yup_audio_plugin_create_aax set (aax_plugin_path "$") else() - # Windows AAX: directory bundle with .aaxplugin extension + # Windows AAX plugins are directory bundles laid out as + # Name.aaxplugin/Contents/x64/Name.aaxplugin + set (aax_bundle_dir "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_aax_plugin.aaxplugin") + set_target_properties (${target_name}_aax_plugin PROPERTIES PREFIX "" - SUFFIX ".aaxplugin") + SUFFIX ".aaxplugin" + OUTPUT_NAME "${target_name}_aax_plugin" + RUNTIME_OUTPUT_DIRECTORY "$<1:${aax_bundle_dir}/Contents/x64>" + LIBRARY_OUTPUT_DIRECTORY "$<1:${aax_bundle_dir}/Contents/x64>") - set (aax_plugin_path "$") + set (aax_plugin_path "${aax_bundle_dir}") endif() yup_codesign_target (${target_name}_aax_plugin "${aax_plugin_path}") diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake index 60b9cfeac..f58a3ffc9 100644 --- a/cmake/yup_audio_plugin.cmake +++ b/cmake/yup_audio_plugin.cmake @@ -21,7 +21,11 @@ function (_yup_configure_audio_plugin_bundle_info_plist output_file package_type) set (YUP_AUDIO_PLUGIN_BUNDLE_PACKAGE_TYPE "${package_type}") - configure_file ("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/platforms/mac/AudioPluginInfo.plist.in" "${output_file}" @ONLY) + if ("${package_type}" STREQUAL "TDMw") + configure_file ("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/platforms/mac/AudioPluginInfo_AAX.plist.in" "${output_file}" @ONLY) + else() + configure_file ("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/platforms/mac/AudioPluginInfo.plist.in" "${output_file}" @ONLY) + endif() endfunction() #============================================================================== @@ -239,7 +243,7 @@ function (yup_audio_plugin) if (YUP_ARG_PLUGIN_CREATE_AU AND YUP_PLATFORM_MAC) list (APPEND _all_plugin_targets ${target_name}_au_plugin) endif() - if (YUP_ARG_PLUGIN_CREATE_AAX) + if (YUP_ARG_PLUGIN_CREATE_AAX AND TARGET ${target_name}_aax_plugin) list (APPEND _all_plugin_targets ${target_name}_aax_plugin) endif() if (YUP_ARG_PLUGIN_CREATE_AUv3 AND YUP_PLATFORM_MAC) diff --git a/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp b/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp index 9d3dc7e8e..2532c15e1 100644 --- a/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp +++ b/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp @@ -30,7 +30,7 @@ #include static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p6p1_REVISION, - "YUP requires AAX SDK version 2.6.1 or higher"); + "YUP requires AAX SDK version 2.6.1 or higher"); #include #include @@ -58,10 +58,25 @@ static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p6p1_REVISION, #include #include #include +#include #include #include #include +#if ! defined(YupPlugin_AAX_ManufacturerID) || ! defined(YupPlugin_AAX_ProductID) \ + || ! defined(YupPlugin_AAX_PlugInID_Native) || ! defined(YupPlugin_AAX_PlugInID_AudioSuite) +#error "AAX plugin clients require YupPlugin_AAX_ManufacturerID, YupPlugin_AAX_ProductID, \ +YupPlugin_AAX_PlugInID_Native and YupPlugin_AAX_PlugInID_AudioSuite to be defined" +#endif + +#if ! defined(YupPlugin_AAXCategory) +#if YupPlugin_IsSynth +#define YupPlugin_AAXCategory AAX_ePlugInCategory_SWGenerators +#else +#define YupPlugin_AAXCategory AAX_ePlugInCategory_Effect +#endif +#endif + extern "C" yup::AudioProcessor* createPluginProcessor(); namespace yup @@ -69,7 +84,7 @@ namespace yup //============================================================================== #if YUP_ENABLE_PLUGIN_CLIENT_AAX_LOGGING -#define YUP_AAX_LOG(x) Logger::writeToLog(x) +#define YUP_AAX_LOG(x) Logger::writeToLog (x) #else #define YUP_AAX_LOG(x) #endif @@ -100,21 +115,19 @@ struct YupAlgorithmContext void* pluginInfo = nullptr; int32_t* isPrepared = nullptr; float* const* meterTapBuffers = nullptr; - int32_t* sideChainBuffers = nullptr; }; enum YupAlgorithmField { - fieldAudioIn = AAX_FIELD_INDEX (YupAlgorithmContext, inputChannels), - fieldAudioOut = AAX_FIELD_INDEX (YupAlgorithmContext, outputChannels), - fieldBufferSize = AAX_FIELD_INDEX (YupAlgorithmContext, bufferSize), - fieldBypass = AAX_FIELD_INDEX (YupAlgorithmContext, bypass), - fieldMidiIn = AAX_FIELD_INDEX (YupAlgorithmContext, midiNodeIn), - fieldMidiOut = AAX_FIELD_INDEX (YupAlgorithmContext, midiNodeOut), - fieldPluginInfo = AAX_FIELD_INDEX (YupAlgorithmContext, pluginInfo), + fieldAudioIn = AAX_FIELD_INDEX (YupAlgorithmContext, inputChannels), + fieldAudioOut = AAX_FIELD_INDEX (YupAlgorithmContext, outputChannels), + fieldBufferSize = AAX_FIELD_INDEX (YupAlgorithmContext, bufferSize), + fieldBypass = AAX_FIELD_INDEX (YupAlgorithmContext, bypass), + fieldMidiIn = AAX_FIELD_INDEX (YupAlgorithmContext, midiNodeIn), + fieldMidiOut = AAX_FIELD_INDEX (YupAlgorithmContext, midiNodeOut), + fieldPluginInfo = AAX_FIELD_INDEX (YupAlgorithmContext, pluginInfo), fieldPreparedFlag = AAX_FIELD_INDEX (YupAlgorithmContext, isPrepared), - fieldMeterTaps = AAX_FIELD_INDEX (YupAlgorithmContext, meterTapBuffers), - fieldSideChain = AAX_FIELD_INDEX (YupAlgorithmContext, sideChainBuffers), + fieldMeterTaps = AAX_FIELD_INDEX (YupAlgorithmContext, meterTapBuffers), }; //============================================================================== @@ -123,7 +136,11 @@ enum YupAlgorithmField struct YupPluginInstanceInfo { - explicit YupPluginInstanceInfo (YupAAX_Processor& p) : processor (p) {} + explicit YupPluginInstanceInfo (YupAAX_Processor& p) + : processor (p) + { + } + YupAAX_Processor& processor; }; @@ -142,423 +159,81 @@ static int32_t getAaxParamHash (const char* paramID) noexcept } //============================================================================== -// Channel ordering +// Channel count to AAX stem format conversion //============================================================================== -struct AAXChannelStreamOrder -{ - AAX_EStemFormat aaxStemFormat; - std::vector speakerOrder; -}; - static AAX_EStemFormat stemFormatForAmbisonicOrder (int order) { switch (order) { - case 1: return AAX_eStemFormat_Ambi_1_ACN; - case 2: return AAX_eStemFormat_Ambi_2_ACN; - case 3: return AAX_eStemFormat_Ambi_3_ACN; - case 4: return AAX_eStemFormat_Ambi_4_ACN; - case 5: return AAX_eStemFormat_Ambi_5_ACN; - case 6: return AAX_eStemFormat_Ambi_6_ACN; - case 7: return AAX_eStemFormat_Ambi_7_ACN; - default: return AAX_eStemFormat_INT32_MAX; - } -} - -// Note: YUP uses British spelling for AudioChannelSet names -static AAXChannelStreamOrder aaxChannelOrder[] = -{ - { AAX_eStemFormat_Mono, { AudioChannelSet::centre } }, - { AAX_eStemFormat_Stereo, { AudioChannelSet::left, AudioChannelSet::right } }, - { AAX_eStemFormat_LCR, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right } }, - { AAX_eStemFormat_LCRS, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::centreSurround } }, - { AAX_eStemFormat_Quad, { AudioChannelSet::left, AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround } }, - { AAX_eStemFormat_5_0, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround } }, - { AAX_eStemFormat_5_1, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::LFE } }, - { AAX_eStemFormat_6_0, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, AudioChannelSet::leftSurround, - AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround } }, - { AAX_eStemFormat_6_1, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, AudioChannelSet::leftSurround, - AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::LFE } }, - { AAX_eStemFormat_7_0_DTS, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear } }, - { AAX_eStemFormat_7_0_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, - AudioChannelSet::centre, - AudioChannelSet::rightCentre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround } }, - { AAX_eStemFormat_7_1_DTS, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE } }, - { AAX_eStemFormat_7_1_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, - AudioChannelSet::centre, - AudioChannelSet::rightCentre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::LFE } }, - { AAX_eStemFormat_7_0_2, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight } }, - { AAX_eStemFormat_7_1_2, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight } }, - { AAX_eStemFormat_5_0_2, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight } }, - { AAX_eStemFormat_5_1_2, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::LFE, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight } }, - { AAX_eStemFormat_5_0_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_5_1_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurround, - AudioChannelSet::rightSurround, - AudioChannelSet::LFE, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_7_0_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_7_1_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_7_0_6, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_7_1_6, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_9_0_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::wideLeft, - AudioChannelSet::wideRight, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_9_1_4, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::wideLeft, - AudioChannelSet::wideRight, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_9_0_6, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::wideLeft, - AudioChannelSet::wideRight, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_9_1_6, { AudioChannelSet::left, AudioChannelSet::centre, - AudioChannelSet::right, - AudioChannelSet::wideLeft, - AudioChannelSet::wideRight, - AudioChannelSet::leftSurroundSide, - AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, - AudioChannelSet::rightSurroundRear, - AudioChannelSet::LFE, - AudioChannelSet::topFrontLeft, - AudioChannelSet::topFrontRight, - AudioChannelSet::topSideLeft, - AudioChannelSet::topSideRight, - AudioChannelSet::topRearLeft, - AudioChannelSet::topRearRight } }, - { AAX_eStemFormat_None, {} }, -}; - -static AAX_EStemFormat aaxFormats[] = -{ - AAX_eStemFormat_Mono, - AAX_eStemFormat_Stereo, - AAX_eStemFormat_LCR, - AAX_eStemFormat_LCRS, - AAX_eStemFormat_Quad, - AAX_eStemFormat_5_0, - AAX_eStemFormat_5_1, - AAX_eStemFormat_6_0, - AAX_eStemFormat_6_1, - AAX_eStemFormat_7_0_SDDS, - AAX_eStemFormat_7_1_SDDS, - AAX_eStemFormat_7_0_DTS, - AAX_eStemFormat_7_1_DTS, - AAX_eStemFormat_7_0_2, - AAX_eStemFormat_7_1_2, - AAX_eStemFormat_Ambi_1_ACN, - AAX_eStemFormat_Ambi_2_ACN, - AAX_eStemFormat_Ambi_3_ACN, - AAX_eStemFormat_5_0_2, - AAX_eStemFormat_5_1_2, - AAX_eStemFormat_5_0_4, - AAX_eStemFormat_5_1_4, - AAX_eStemFormat_7_0_4, - AAX_eStemFormat_7_1_4, - AAX_eStemFormat_7_0_6, - AAX_eStemFormat_7_1_6, - AAX_eStemFormat_9_0_4, - AAX_eStemFormat_9_1_4, - AAX_eStemFormat_9_0_6, - AAX_eStemFormat_9_1_6, - AAX_eStemFormat_Ambi_4_ACN, - AAX_eStemFormat_Ambi_5_ACN, - AAX_eStemFormat_Ambi_6_ACN, - AAX_eStemFormat_Ambi_7_ACN, -}; - -//============================================================================== -// Channel set ↔ AAX stem format conversion -//============================================================================== - -static AAX_EStemFormat getFormatForAudioChannelSet ( - const AudioChannelSet& set, bool ignoreLayout) noexcept -{ - if (ignoreLayout) - { - const auto numChannels = set.size(); - switch (numChannels) - { - case 0: return AAX_eStemFormat_None; - case 1: return AAX_eStemFormat_Mono; - case 2: return AAX_eStemFormat_Stereo; - case 3: return AAX_eStemFormat_LCR; - case 4: return AAX_eStemFormat_Quad; - case 5: return AAX_eStemFormat_5_0; - case 6: return AAX_eStemFormat_5_1; - case 7: return AAX_eStemFormat_7_0_DTS; - case 8: return AAX_eStemFormat_7_1_DTS; - case 9: return AAX_eStemFormat_7_0_2; - case 10: return AAX_eStemFormat_7_1_2; - case 11: return AAX_eStemFormat_7_0_4; - case 12: return AAX_eStemFormat_7_1_4; - case 13: return AAX_eStemFormat_9_0_4; - case 14: return AAX_eStemFormat_9_1_4; - case 15: return AAX_eStemFormat_9_0_6; - case 16: return AAX_eStemFormat_9_1_6; - default: break; - } - - const auto maybeOrder = - AudioChannelSet::getAmbisonicOrderForNumChannels (numChannels); - - if (maybeOrder != -1) - return stemFormatForAmbisonicOrder (maybeOrder); - - return AAX_eStemFormat_INT32_MAX; - } - - if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; - if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; - if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; - if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; - if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; - if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; - if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; - if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; - if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; - if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; - if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; - if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; - if (set == AudioChannelSet::create7point0SDDS()) return AAX_eStemFormat_7_0_SDDS; - if (set == AudioChannelSet::create7point1SDDS()) return AAX_eStemFormat_7_1_SDDS; - if (set == AudioChannelSet::create7point0point2()) return AAX_eStemFormat_7_0_2; - if (set == AudioChannelSet::create7point1point2()) return AAX_eStemFormat_7_1_2; - if (set == AudioChannelSet::create5point0point2()) return AAX_eStemFormat_5_0_2; - if (set == AudioChannelSet::create5point1point2()) return AAX_eStemFormat_5_1_2; - if (set == AudioChannelSet::create5point0point4()) return AAX_eStemFormat_5_0_4; - if (set == AudioChannelSet::create5point1point4()) return AAX_eStemFormat_5_1_4; - if (set == AudioChannelSet::create7point0point4()) return AAX_eStemFormat_7_0_4; - if (set == AudioChannelSet::create7point1point4()) return AAX_eStemFormat_7_1_4; - - const auto order = set.getAmbisonicOrder(); - if (order >= 0) - return stemFormatForAmbisonicOrder (order); - - return AAX_eStemFormat_INT32_MAX; -} - -static AudioChannelSet channelSetFromStemFormat ( - AAX_EStemFormat format, bool ignoreLayout) noexcept -{ - if (! ignoreLayout) - { - switch (format) - { - case AAX_eStemFormat_None: return AudioChannelSet::disabled(); - case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); - case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); - case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); - case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); - case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); - case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); - case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); - case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); - case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); - case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::create7point0SDDS(); - case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); - case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::create7point1SDDS(); - case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); - case AAX_eStemFormat_7_0_2: return AudioChannelSet::create7point0point2(); - case AAX_eStemFormat_7_1_2: return AudioChannelSet::create7point1point2(); - case AAX_eStemFormat_Ambi_1_ACN: return AudioChannelSet::ambisonic (1); - case AAX_eStemFormat_Ambi_2_ACN: return AudioChannelSet::ambisonic (2); - case AAX_eStemFormat_Ambi_3_ACN: return AudioChannelSet::ambisonic (3); - case AAX_eStemFormat_5_0_2: return AudioChannelSet::create5point0point2(); - case AAX_eStemFormat_5_1_2: return AudioChannelSet::create5point1point2(); - case AAX_eStemFormat_5_0_4: return AudioChannelSet::create5point0point4(); - case AAX_eStemFormat_5_1_4: return AudioChannelSet::create5point1point4(); - case AAX_eStemFormat_7_0_4: return AudioChannelSet::create7point0point4(); - case AAX_eStemFormat_7_1_4: return AudioChannelSet::create7point1point4(); - case AAX_eStemFormat_7_0_6: return AudioChannelSet::create7point0point6(); - case AAX_eStemFormat_7_1_6: return AudioChannelSet::create7point1point6(); - case AAX_eStemFormat_9_0_4: return AudioChannelSet::create9point0point4(); - case AAX_eStemFormat_9_1_4: return AudioChannelSet::create9point1point4(); - case AAX_eStemFormat_9_0_6: return AudioChannelSet::create9point0point6(); - case AAX_eStemFormat_9_1_6: return AudioChannelSet::create9point1point6(); - case AAX_eStemFormat_Ambi_4_ACN: return AudioChannelSet::ambisonic (4); - case AAX_eStemFormat_Ambi_5_ACN: return AudioChannelSet::ambisonic (5); - case AAX_eStemFormat_Ambi_6_ACN: return AudioChannelSet::ambisonic (6); - case AAX_eStemFormat_Ambi_7_ACN: return AudioChannelSet::ambisonic (7); - default: return AudioChannelSet::disabled(); - } + case 1: + return AAX_eStemFormat_Ambi_1_ACN; + case 2: + return AAX_eStemFormat_Ambi_2_ACN; + case 3: + return AAX_eStemFormat_Ambi_3_ACN; + case 4: + return AAX_eStemFormat_Ambi_4_ACN; + case 5: + return AAX_eStemFormat_Ambi_5_ACN; + case 6: + return AAX_eStemFormat_Ambi_6_ACN; + case 7: + return AAX_eStemFormat_Ambi_7_ACN; + default: + return AAX_eStemFormat_INT32_MAX; } - - return AudioChannelSet::discreteChannels ( - jmax (0, static_cast (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); } -static int processorChannelIndexToAax (int chIndex, const AudioChannelSet& channelSet) +// YUP buses carry a plain channel count, so the stem format is chosen by count +// and channels are passed through to the processor in AAX stream order. +static AAX_EStemFormat stemFormatForChannelCount (int numChannels) { - const auto order = channelSet.getAmbisonicOrder(); - const auto currentLayout = getFormatForAudioChannelSet (channelSet, false); - - if (order >= 0 && currentLayout != AAX_eStemFormat_INT32_MAX) - return chIndex; - - int layoutIndex; - for (layoutIndex = 0; - aaxChannelOrder[layoutIndex].aaxStemFormat != AAX_eStemFormat_None; - ++layoutIndex) + switch (numChannels) { - if (aaxChannelOrder[layoutIndex].aaxStemFormat == currentLayout) + case 0: + return AAX_eStemFormat_None; + case 1: + return AAX_eStemFormat_Mono; + case 2: + return AAX_eStemFormat_Stereo; + case 3: + return AAX_eStemFormat_LCR; + case 4: + return AAX_eStemFormat_Quad; + case 5: + return AAX_eStemFormat_5_0; + case 6: + return AAX_eStemFormat_5_1; + case 7: + return AAX_eStemFormat_7_0_DTS; + case 8: + return AAX_eStemFormat_7_1_DTS; + case 9: + return AAX_eStemFormat_7_0_2; + case 10: + return AAX_eStemFormat_7_1_2; + case 11: + return AAX_eStemFormat_7_0_4; + case 12: + return AAX_eStemFormat_7_1_4; + case 13: + return AAX_eStemFormat_9_0_4; + case 14: + return AAX_eStemFormat_9_1_4; + case 15: + return AAX_eStemFormat_9_0_6; + case 16: + return AAX_eStemFormat_9_1_6; + default: break; } - if (aaxChannelOrder[layoutIndex].aaxStemFormat == AAX_eStemFormat_None) - return chIndex; - - const auto& channelOrder = aaxChannelOrder[layoutIndex]; - const auto channelType = channelSet.getTypeOfChannel (chIndex); - const auto& speakers = channelOrder.speakerOrder; + const auto maybeOrder = AudioChannelSet::getAmbisonicOrderForNumChannels (numChannels); + if (maybeOrder != -1) + return stemFormatForAmbisonicOrder (maybeOrder); - const auto it = std::find (speakers.begin(), speakers.end(), channelType); - if (it != speakers.end()) - return static_cast (std::distance (speakers.begin(), it)); - - return chIndex; + return AAX_eStemFormat_INT32_MAX; } //============================================================================== @@ -568,8 +243,10 @@ static int processorChannelIndexToAax (int chIndex, const AudioChannelSet& chann static AAX_EMeterType getMeterTypeFromParam (const AudioParameter& param) { const auto name = param.getName(); - if (name.containsIgnoreCase ("input")) return AAX_eMeterType_Input; - if (name.containsIgnoreCase ("output")) return AAX_eMeterType_Output; + if (name.containsIgnoreCase ("input")) + return AAX_eMeterType_Input; + if (name.containsIgnoreCase ("output")) + return AAX_eMeterType_Output; if (name.containsIgnoreCase ("gr") || name.containsIgnoreCase ("gain reduction")) return AAX_eMeterType_CLGain; return AAX_eMeterType_Other; @@ -585,9 +262,8 @@ static bool isMeterParameter (const AudioParameter& param) // YupAAX_Processor //============================================================================== -class YupAAX_Processor : public AAX_CEffectParameters, - private AudioProcessorBase::Listener, - private AsyncUpdater +class YupAAX_Processor : public AAX_CEffectParameters + , private AudioProcessorBase::Listener { public: static AAX_CEffectParameters* AAX_CALLBACK Create() @@ -599,7 +275,6 @@ class YupAAX_Processor : public AAX_CEffectParameters, { processor.reset (createPluginProcessor()); processor->addListener (this); - rebuildChannelMapArrays(); AAX_CEffectParameters::GetNumberOfChunks (&yupChunkIndex); YUP_AAX_LOG ("YupAAX_Processor: created"); @@ -617,8 +292,6 @@ class YupAAX_Processor : public AAX_CEffectParameters, AAX_Result Uninitialize() override { - cancelPendingUpdate(); - if (isPrepared && processor != nullptr) { isPrepared = false; @@ -631,7 +304,6 @@ class YupAAX_Processor : public AAX_CEffectParameters, AAX_Result EffectInit() override { YUP_AAX_LOG ("YupAAX_Processor::EffectInit"); - cancelPendingUpdate(); auto* ctrl = Controller(); if (ctrl == nullptr) @@ -646,7 +318,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, ctrl->GetOutputStemFormat (&outputFormat); auto err = preparePlugin (static_cast (sampleRate), - inputFormat, outputFormat); + inputFormat, + outputFormat); if (err != AAX_SUCCESS) return err; @@ -659,7 +332,9 @@ class YupAAX_Processor : public AAX_CEffectParameters, //========================================================================== AAX_Result UpdateParameterNormalizedValue ( - AAX_CParamID paramID, double value, AAX_EUpdateSource source) override + AAX_CParamID paramID, + double value, + AAX_EUpdateSource source) override { const auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue ( paramID, value, source); @@ -671,7 +346,9 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterValueFromString ( - AAX_CParamID paramID, double* result, const AAX_IString& text) const override + AAX_CParamID paramID, + double* result, + const AAX_IString& text) const override { if (auto* param = getParamForID (paramID)) { @@ -684,7 +361,9 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterStringFromValue ( - AAX_CParamID paramID, double value, AAX_IString* result, + AAX_CParamID paramID, + double value, + AAX_IString* result, int32_t maxLen) const override { if (auto* param = getParamForID (paramID)) @@ -696,7 +375,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterNumberOfSteps ( - AAX_CParamID paramID, int32_t* result) const override + AAX_CParamID paramID, + int32_t* result) const override { if (auto* param = getParamForID (paramID)) { @@ -711,7 +391,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterNormalizedValue ( - AAX_CParamID paramID, double* result) const override + AAX_CParamID paramID, + double* result) const override { if (auto* param = getParamForID (paramID)) *result = static_cast (param->getNormalizedValue()); @@ -721,7 +402,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result SetParameterNormalizedValue ( - AAX_CParamID paramID, double newValue) override + AAX_CParamID paramID, + double newValue) override { if (auto* p = mParameterManager.GetParameterByID (paramID)) p->SetValueWithFloat (static_cast (newValue)); @@ -731,7 +413,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result SetParameterNormalizedRelative ( - AAX_CParamID paramID, double newDeltaValue) override + AAX_CParamID paramID, + double newDeltaValue) override { if (auto* param = getParamForID (paramID)) { @@ -746,7 +429,9 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterNameOfLength ( - AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override + AAX_CParamID paramID, + AAX_IString* result, + int32_t maxLen) const override { if (auto* param = getParamForID (paramID)) result->Set (param->getName().substring (0, maxLen).toRawUTF8()); @@ -754,7 +439,8 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterName ( - AAX_CParamID paramID, AAX_IString* result) const override + AAX_CParamID paramID, + AAX_IString* result) const override { if (auto* param = getParamForID (paramID)) result->Set (param->getName().substring (0, 31).toRawUTF8()); @@ -762,26 +448,25 @@ class YupAAX_Processor : public AAX_CEffectParameters, } AAX_Result GetParameterDefaultNormalizedValue ( - AAX_CParamID paramID, double* result) const override + AAX_CParamID paramID, + double* result) const override { if (auto* param = getParamForID (paramID)) - *result = static_cast (param->getDefaultValue()); + *result = static_cast ( + param->convertToNormalizedValue (param->getDefaultValue())); else *result = 0.0; return AAX_SUCCESS; } - AAX_Result GenerateCoefficients() override - { - return AAX_CEffectParameters::GenerateCoefficients(); - } - //========================================================================== // AAX_CEffectParameters — State reset //========================================================================== AAX_Result ResetFieldData ( - AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override + AAX_CFieldIndex fieldIndex, + void* data, + uint32_t dataSize) const override { switch (fieldIndex) { @@ -866,11 +551,12 @@ class YupAAX_Processor : public AAX_CEffectParameters, if (! tls.isValid) return 20700; // AAX_ERROR_PLUGIN_API_INVALID_THREAD + // The host allocates the chunk using the size returned by + // GetChunkSize, fData is a variable length trailing array const auto size = static_cast (tls.data.getSize()); chunk->fSize = size; - const auto copySize = jmin (static_cast (size), sizeof (chunk->fData)); - if (copySize > 0 && tls.data.getData() != nullptr) - tls.data.copyTo (chunk->fData, 0, copySize); + if (size > 0 && tls.data.getData() != nullptr) + tls.data.copyTo (chunk->fData, 0, static_cast (size)); tls.isValid = false; return AAX_SUCCESS; } @@ -888,6 +574,9 @@ class YupAAX_Processor : public AAX_CEffectParameters, if (state.hasWrapperState) { setBypassed (state.isBypassed); + SetParameterNormalizedValue (cDefaultMasterBypassID, + state.isBypassed ? 1.0 : 0.0); + if (state.hasProcessorState && ! state.processorState.isEmpty()) processor->loadStateFromMemory (state.processorState); } @@ -936,80 +625,59 @@ class YupAAX_Processor : public AAX_CEffectParameters, //========================================================================== void process (float* const* inputChannelData, - int numInputChannels, float* const* outputChannelData, - int numOutputChannels, int bufferSize, int32_t bypassFlag, AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodeOut, - float* meterTaps, - int numMeterTaps, - int sideChainBufIdx) + float* const* meterTapBuffers) { - if (sideChainDesired.load() != (sideChainBufIdx != -1)) - { - sideChainDesired.store (sideChainBufIdx != -1); - triggerAsyncUpdate(); - } - const bool currentlyBypassed = (bypassFlag != 0); setBypassed (currentlyBypassed); + // The MIDI node fields are only registered when the plugin handles + // MIDI, otherwise their context values are meaningless midiBuffer.clear(); - if (midiNodeIn != nullptr && processor->acceptsMidi()) + if (processor->acceptsMidi() && midiNodeIn != nullptr) fillMidiBufferFromAaxNode (*midiNodeIn, midiBuffer); - const auto actualNumIn = jmin (numInputChannels, - processor->getBusLayout().getNumAudioInputChannels()); - const auto actualNumOut = jmin (numOutputChannels, - processor->getBusLayout().getNumAudioOutputChannels()); - - AudioBuffer audioBuffer (actualNumOut, bufferSize); - audioBuffer.clear(); + // The component is described with stem formats matching the processor + // layout, so the host provides exactly these channel counts + const auto& layout = processor->getBusLayout(); + const auto numIn = layout.getNumAudioInputChannels(); + const auto numOut = layout.getNumAudioOutputChannels(); - for (int ch = 0; ch < actualNumIn; ++ch) + for (int ch = 0; ch < numOut; ++ch) { - const auto mappedCh = isPositiveAndBelow (ch, static_cast (inputLayoutMap.size())) - ? inputLayoutMap[static_cast (ch)] : ch; - const auto srcIdx = ch; + if (outputChannelData[ch] == nullptr) + continue; - if (isPositiveAndBelow (mappedCh, actualNumOut) - && srcIdx < numInputChannels - && inputChannelData[srcIdx] != nullptr) + if (ch < numIn && inputChannelData[ch] != nullptr) { - audioBuffer.copyFrom (mappedCh, 0, - inputChannelData[srcIdx], bufferSize); + if (outputChannelData[ch] != inputChannelData[ch]) + std::memcpy (outputChannelData[ch], inputChannelData[ch], static_cast (bufferSize) * sizeof (float)); + } + else + { + std::memset (outputChannelData[ch], 0, static_cast (bufferSize) * sizeof (float)); } } + AudioBuffer audioBuffer (outputChannelData, numOut, bufferSize); + paramChangeBuffer.clear(); - AudioProcessContext context ( - audioBuffer, midiBuffer, paramChangeBuffer, nullptr); + AudioProcessContext context { + audioBuffer, midiBuffer, paramChangeBuffer, nullptr + }; processAudioBlock (*processor, context, currentlyBypassed); - for (int ch = 0; ch < actualNumOut; ++ch) - { - if (outputChannelData[ch] == nullptr) - continue; - - const auto mappedCh = isPositiveAndBelow (ch, static_cast (outputLayoutMap.size())) - ? outputLayoutMap[static_cast (ch)] : ch; - - if (isPositiveAndBelow (mappedCh, actualNumOut)) - { - const auto* src = audioBuffer.getReadPointer (mappedCh); - std::memcpy (outputChannelData[ch], src, - static_cast (bufferSize) * sizeof (float)); - } - } - - if (midiNodeOut != nullptr && processor->producesMidi()) + if (processor->producesMidi() && midiNodeOut != nullptr) fillAaxMidiNodeFromBuffer (*midiNodeOut, midiBuffer); - extractMeterValues (meterTaps, numMeterTaps); + if (! aaxMeters.empty() && meterTapBuffers != nullptr) + extractMeterValues (*meterTapBuffers); } AudioProcessor& getAudioProcessor() noexcept { return *processor; } @@ -1026,7 +694,7 @@ class YupAAX_Processor : public AAX_CEffectParameters, return; if (details.parameterValuesChanged) - resyncAllAaxParameterValues(); + resyncParameterValues(); if (details.parameterInfoChanged) syncParameterAttributes(); @@ -1041,18 +709,6 @@ class YupAAX_Processor : public AAX_CEffectParameters, numSetDirtyCalls.fetch_add (1); } - //========================================================================== - // AsyncUpdater - //========================================================================== - - void handleAsyncUpdate() override - { - if (auto* ctrl = Controller()) - ctrl->PostPacket (fieldSideChain, - &sideChainDesired, - sizeof (sideChainDesired)); - } - //========================================================================== // Helpers //========================================================================== @@ -1064,15 +720,11 @@ class YupAAX_Processor : public AAX_CEffectParameters, lastSampleRate = sampleRate; lastInputFormat = inputFormat; lastOutputFormat = outputFormat; - lastBufferSize = 1024; - maxBufferSize = 1024; - AudioSpec spec { sampleRate, - maxBufferSize }; - processor->prepareToPlay (spec); + processor->setPlaybackConfiguration (sampleRate, maxAaxBufferSize); isPrepared = true; - rebuildChannelMapArrays(); + paramChangeBuffer.reserve (getDefaultParameterChangeCapacity (*processor)); if (auto* ctrl = Controller()) ctrl->SetSignalLatency (processor->getLatencySamples()); @@ -1080,46 +732,13 @@ class YupAAX_Processor : public AAX_CEffectParameters, return AAX_SUCCESS; } - void rebuildChannelMapArrays() - { - const auto& layout = processor->getBusLayout(); - - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - auto& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; - layoutMap.clear(); - - const auto buses = isInput - ? layout.getInputBuses() - : layout.getOutputBuses(); - - int chOffset = 0; - - for (const auto& bus : buses) - { - if (bus.getType() != AudioBus::Type::Audio) - continue; - - const auto numChannels = bus.getNumChannels(); - const AudioChannelSet channelSet = - AudioChannelSet::discreteChannels (numChannels); - - for (int ch = 0; ch < numChannels; ++ch) - layoutMap.push_back ( - processorChannelIndexToAax (ch, channelSet) + chOffset); - - chOffset += numChannels; - } - } - } - void addAudioProcessorParameters() { paramMap.clear(); - aaxParamIDs.clear(); aaxMeters.clear(); + addMasterBypassParameter(); + const auto parameters = processor->getParameters(); for (size_t i = 0; i < parameters.size(); ++i) @@ -1141,9 +760,23 @@ class YupAAX_Processor : public AAX_CEffectParameters, const auto hash = getAaxParamHash (aaxParamID.toRawUTF8()); paramMap[hash] = param; } + } - mPacketDispatcher.RegisterPacket ( - YupPlugin_AAX_BypassID, fieldBypass); + void addMasterBypassParameter() + { + auto* bypassParam = new AAX_CParameter ( + cDefaultMasterBypassID, + AAX_CString ("Master Bypass"), + false, + AAX_CBinaryTaperDelegate(), + AAX_CBinaryDisplayDelegate ("bypass", "on"), + true); + + bypassParam->SetNumberOfSteps (2); + bypassParam->SetType (AAX_eParameterType_Discrete); + mParameterManager.AddParameter (bypassParam); + + mPacketDispatcher.RegisterPacket (cDefaultMasterBypassID, fieldBypass); } void addAaxParameter (AudioParameter& param, const String& aaxParamID) @@ -1154,23 +787,19 @@ class YupAAX_Processor : public AAX_CEffectParameters, auto* aaxParam = new AAX_CParameter ( aaxParamID.toRawUTF8(), AAX_CString (param.getName().substring (0, 31).toRawUTF8()), - param.getDefaultValue(), + param.convertToNormalizedValue (param.getDefaultValue()), AAX_CLinearTaperDelegate (0.0f, 1.0f), AAX_CNumberDisplayDelegate(), param.isAutomatable()); - if (aaxParam != nullptr) - { - aaxParam->SetNumberOfSteps (static_cast ( - numSteps > 0 ? jmin (numSteps, 2048) : 1000)); + aaxParam->SetNumberOfSteps (static_cast ( + numSteps > 0 ? jmin (numSteps, 2048) : 1000)); - aaxParam->SetType (isDiscrete + aaxParam->SetType (isDiscrete ? AAX_eParameterType_Discrete : AAX_eParameterType_Continuous); - mParameterManager.AddParameter (aaxParam); - aaxParamIDs.add (aaxParamID); - } + mParameterManager.AddParameter (aaxParam); } void syncParameterAttributes() @@ -1185,11 +814,11 @@ class YupAAX_Processor : public AAX_CEffectParameters, continue; const auto aaxParamID = getAaxParamIDFromIndex (static_cast (i)); - const auto numSteps = param->getNumSteps(); SetParameterDefaultNormalizedValue ( aaxParamID.toRawUTF8(), - static_cast (param->getDefaultValue())); + static_cast ( + param->convertToNormalizedValue (param->getDefaultValue()))); } } @@ -1206,7 +835,7 @@ class YupAAX_Processor : public AAX_CEffectParameters, } } - void resyncAllAaxParameterValues() + void resyncParameterValues() { const auto parameters = processor->getParameters(); @@ -1215,24 +844,10 @@ class YupAAX_Processor : public AAX_CEffectParameters, const auto aaxParamID = getAaxParamIDFromIndex (static_cast (i)); inParameterChangedCallback = true; - SetParameterNormalizedValue ( - aaxParamID.toRawUTF8(), - static_cast ( - parameters[i]->getNormalizedValue())); - inParameterChangedCallback = false; - } - } - - void resyncParameterValues() - { - const auto parameters = processor->getParameters(); - - for (size_t i = 0; i < parameters.size(); ++i) - { - const auto aaxParamID = getAaxParamIDFromIndex (static_cast (i)); SetParameterNormalizedValue ( aaxParamID.toRawUTF8(), static_cast (parameters[i]->getNormalizedValue())); + inParameterChangedCallback = false; } } @@ -1272,7 +887,7 @@ class YupAAX_Processor : public AAX_CEffectParameters, static std::optional aaxMidiPacketToYup (const AAX_CMidiPacket& pkt) { - if (pkt.mLength <= 0 || pkt.mLength > 4) + if (pkt.mLength == 0 || pkt.mLength > 4) return std::nullopt; const auto* data = pkt.mData; @@ -1281,19 +896,26 @@ class YupAAX_Processor : public AAX_CEffectParameters, switch (status) { - case 0x80: return MidiMessage::noteOff (channel, data[1], static_cast (data[2])); - case 0x90: return MidiMessage::noteOn (channel, data[1], static_cast (data[2])); - case 0xA0: return MidiMessage::aftertouchChange (channel, data[1], data[2]); - case 0xB0: return MidiMessage::controllerEvent (channel, data[1], data[2]); - case 0xC0: return MidiMessage::programChange (channel, data[1]); - case 0xD0: return MidiMessage::channelPressureChange (channel, data[1]); + case 0x80: + return MidiMessage::noteOff (channel, data[1], static_cast (data[2])); + case 0x90: + return MidiMessage::noteOn (channel, data[1], static_cast (data[2])); + case 0xA0: + return MidiMessage::aftertouchChange (channel, data[1], data[2]); + case 0xB0: + return MidiMessage::controllerEvent (channel, data[1], data[2]); + case 0xC0: + return MidiMessage::programChange (channel, data[1]); + case 0xD0: + return MidiMessage::channelPressureChange (channel, data[1]); case 0xE0: { const auto value = static_cast (data[1]) | (static_cast (data[2]) << 7); return MidiMessage::pitchWheel (channel, value); } - default: break; + default: + break; } return std::nullopt; } @@ -1318,23 +940,21 @@ class YupAAX_Processor : public AAX_CEffectParameters, const auto copySize = jmin (dataSize, static_cast (sizeof (pkt.mData))); std::memcpy (pkt.mData, data, static_cast (copySize)); - pkt.mLength = static_cast (copySize); + pkt.mLength = static_cast (copySize); } //========================================================================== // Meters //========================================================================== - void extractMeterValues (float* meterTaps, int numMeterTaps) + void extractMeterValues (float* meterTaps) { - if (meterTaps == nullptr || numMeterTaps <= 0) + if (meterTaps == nullptr) return; - const auto numMeters = jmin (static_cast (aaxMeters.size()), - numMeterTaps); - for (int i = 0; i < numMeters; ++i) + for (size_t i = 0; i < aaxMeters.size(); ++i) { - const auto value = aaxMeters[static_cast (i)]->getNormalizedValue(); + const auto value = aaxMeters[i]->getNormalizedValue(); meterTaps[i] = std::max (meterTaps[i], std::abs (value)); } } @@ -1358,8 +978,6 @@ class YupAAX_Processor : public AAX_CEffectParameters, std::unique_ptr processor; int32_t yupChunkIndex = 0; - int lastBufferSize = 1024; - int maxBufferSize = 1024; float lastSampleRate = 44100.0f; AAX_EStemFormat lastInputFormat = AAX_eStemFormat_None; AAX_EStemFormat lastOutputFormat = AAX_eStemFormat_None; @@ -1370,22 +988,18 @@ class YupAAX_Processor : public AAX_CEffectParameters, MidiBuffer midiBuffer; ParameterChangeBuffer paramChangeBuffer; - std::vector inputLayoutMap; - std::vector outputLayoutMap; - - StringArray aaxParamIDs; std::unordered_map paramMap; std::vector aaxMeters; mutable ThreadLocalValue chunkData; ThreadLocalValue inParameterChangedCallback; - mutable std::atomic sideChainDesired { 0 }; + // AAX native algorithm buffers are at most 1024 samples + static constexpr int maxAaxBufferSize = 1024; - static constexpr int YupPlugin_AAX_ChunkMagic = 0x59415858; // 'YAAX' + static constexpr int YupPlugin_AAX_ChunkMagic = 0x59415858; // 'YAAX' static constexpr int YupPlugin_AAX_ChunkVersion = 1; - static constexpr AAX_CTypeID YupPlugin_AAX_ChunkID = 'YUpS'; // YUP State - static constexpr const char* YupPlugin_AAX_BypassID = "MasterBypassID"; + static constexpr AAX_CTypeID YupPlugin_AAX_ChunkID = 'YUpS'; // YUP State }; //============================================================================== @@ -1401,6 +1015,7 @@ class YupAAX_GUI : public AAX_CEffectGUI } YupAAX_GUI() = default; + ~YupAAX_GUI() override { DeleteViewContainer(); } //========================================================================== @@ -1442,13 +1057,28 @@ class YupAAX_GUI : public AAX_CEffectGUI return; auto* nativeView = viewContainer->GetPtr(); + if (nativeView == nullptr) + return; - if (nativeView != nullptr) - { - auto options = ComponentNative::Options(); - editorComponent->addToDesktop (options, nativeView); - YUP_AAX_LOG ("YupAAX_GUI: attached to native view"); - } + ComponentNative::Flags flags = + ComponentNative::defaultFlags & ~ComponentNative::decoratedWindow; + + if (editorComponent->shouldRenderContinuous()) + flags.set (ComponentNative::renderContinuous); + + auto options = ComponentNative::Options() + .withFlags (flags) + .withResizableWindow (editorComponent->isResizable()); + + const auto preferredSize = editorComponent->getPreferredSize(); + editorComponent->setSize ({ static_cast (preferredSize.getWidth()), + static_cast (preferredSize.getHeight()) }); + + editorComponent->addToDesktop (options, nativeView); + editorComponent->setVisible (true); + editorComponent->attachedToNative(); + + YUP_AAX_LOG ("YupAAX_GUI: attached to native view"); } void DeleteViewContainer() override @@ -1465,29 +1095,37 @@ class YupAAX_GUI : public AAX_CEffectGUI if (size == nullptr) return AAX_ERROR_NULL_OBJECT; + size->horz = 0.0f; + size->vert = 0.0f; + if (editorComponent != nullptr) { - const auto bounds = editorComponent->getLocalBounds(); - size->vert = static_cast (bounds.getHeight()); - size->horz = static_cast (bounds.getWidth()); - } - else - { - size->vert = 0.0f; - size->horz = 0.0f; + if (editorComponent->isResizable() && editorComponent->getWidth() != 0) + { + size->horz = static_cast (editorComponent->getWidth()); + size->vert = static_cast (editorComponent->getHeight()); + } + else + { + const auto preferredSize = editorComponent->getPreferredSize(); + size->horz = static_cast (preferredSize.getWidth()); + size->vert = static_cast (preferredSize.getHeight()); + } } return AAX_SUCCESS; } AAX_Result SetControlHighlightInfo ( - AAX_CParamID, AAX_CBoolean, AAX_EHighlightColor) override + AAX_CParamID, + AAX_CBoolean, + AAX_EHighlightColor) override { return AAX_SUCCESS; } private: - std::unique_ptr editorComponent; + std::unique_ptr editorComponent; }; //============================================================================== @@ -1512,26 +1150,20 @@ void AAX_CALLBACK yupAAXAlgorithmCallback ( auto* info = static_cast (ctx->pluginInfo); const auto bufSize = (ctx->bufferSize != nullptr) - ? static_cast (*ctx->bufferSize) : 0; + ? static_cast (*ctx->bufferSize) + : 0; if (bufSize <= 0) continue; - auto* meterTaps = ctx->meterTapBuffers != nullptr - ? const_cast (*ctx->meterTapBuffers) : nullptr; - info->processor.process ( ctx->inputChannels, - 8, ctx->outputChannels, - 8, bufSize, ctx->bypass != nullptr ? *ctx->bypass : 0, ctx->midiNodeIn, ctx->midiNodeOut, - meterTaps, - 16, - ctx->sideChainBuffers != nullptr ? *ctx->sideChainBuffers : -1); + ctx->meterTapBuffers); } } @@ -1547,9 +1179,10 @@ static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) descriptor.AddName (YupPlugin_Description); descriptor.AddCategory (YupPlugin_AAXCategory); - aaxCheck (descriptor.AddProcPtr ( - reinterpret_cast (YupAAX_GUI::Create), - kAAX_ProcPtrID_Create_EffectGUI)); + if (plugin->hasEditor()) + aaxCheck (descriptor.AddProcPtr ( + reinterpret_cast (YupAAX_GUI::Create), + kAAX_ProcPtrID_Create_EffectGUI)); aaxCheck (descriptor.AddProcPtr ( reinterpret_cast (YupAAX_Processor::Create), @@ -1560,126 +1193,119 @@ static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) AAX_eResourceType_PageTable, YupPlugin_AAXPageTableFile); #endif - // Register meters on the descriptor - const auto parameters = plugin->getParameters(); - int meterIdx = 0; + // Register meters on the descriptor, the same IDs are then bound to the + // component's meter tap field below + std::vector meterIDs; - for (size_t i = 0; i < parameters.size(); ++i) + for (const auto& parameter : plugin->getParameters()) { - auto* param = parameters[i].get(); + auto* param = parameter.get(); if (param == nullptr || ! isMeterParameter (*param)) continue; auto* meterProps = descriptor.NewPropertyMap(); - if (meterProps != nullptr) - { - meterProps->AddProperty ( - AAX_eProperty_Meter_Type, - getMeterTypeFromParam (*param)); - - meterProps->AddProperty ( - AAX_eProperty_Meter_Orientation, - AAX_eMeterOrientation_TopRight); - - descriptor.AddMeterDescription ( - static_cast ('Mtr0' + meterIdx), - param->getName().substring (0, 1024).toRawUTF8(), - meterProps); - ++meterIdx; - } + if (meterProps == nullptr) + continue; + + meterProps->AddProperty ( + AAX_eProperty_Meter_Type, + getMeterTypeFromParam (*param)); + + meterProps->AddProperty ( + AAX_eProperty_Meter_Orientation, + AAX_eMeterOrientation_TopRight); + + const auto meterID = static_cast ( + 'Mtr0' + static_cast (meterIDs.size())); + + descriptor.AddMeterDescription ( + meterID, + param->getName().toRawUTF8(), + meterProps); + + meterIDs.push_back (meterID); } + // YUP processors have a fixed bus layout, so a single component matching + // the processor's channel configuration is registered. Instruments with no + // audio input still process in place on the track stem. const auto numInCh = plugin->getBusLayout().getNumAudioInputChannels(); const auto numOutCh = plugin->getBusLayout().getNumAudioOutputChannels(); - const auto numInFormats = numInCh > 0 - ? static_cast (std::size (aaxFormats)) : 0; - const auto numOutFormats = numOutCh > 0 - ? static_cast (std::size (aaxFormats)) : 0; + const auto outputFormat = stemFormatForChannelCount (numOutCh); + const auto inputFormat = numInCh > 0 + ? stemFormatForChannelCount (numInCh) + : outputFormat; - for (int inIdx = 0; inIdx < jmax (numInFormats, 1); ++inIdx) - { - const auto aaxInFormat = numInFormats > 0 - ? aaxFormats[inIdx] : AAX_eStemFormat_None; - - for (int outIdx = 0; outIdx < jmax (numOutFormats, 1); ++outIdx) - { - const auto aaxOutFormat = numOutFormats > 0 - ? aaxFormats[outIdx] : AAX_eStemFormat_None; - - auto* compDesc = descriptor.NewComponentDescriptor(); - if (compDesc == nullptr) - continue; + jassert (inputFormat != AAX_eStemFormat_INT32_MAX); + jassert (outputFormat != AAX_eStemFormat_INT32_MAX); - aaxCheck (compDesc->AddAudioIn (fieldAudioIn)); - aaxCheck (compDesc->AddAudioOut (fieldAudioOut)); - aaxCheck (compDesc->AddAudioBufferLength (fieldBufferSize)); - aaxCheck (compDesc->AddDataInPort (fieldBypass, sizeof (int32_t))); + auto* compDesc = descriptor.NewComponentDescriptor(); + if (compDesc == nullptr) + return; - if (plugin->acceptsMidi()) - aaxCheck (compDesc->AddMIDINode ( - fieldMidiIn, AAX_eMIDINodeType_LocalInput, - "MIDI In", 0xFFFF)); + aaxCheck (compDesc->AddAudioIn (fieldAudioIn)); + aaxCheck (compDesc->AddAudioOut (fieldAudioOut)); + aaxCheck (compDesc->AddAudioBufferLength (fieldBufferSize)); + aaxCheck (compDesc->AddDataInPort (fieldBypass, sizeof (int32_t))); - if (plugin->producesMidi()) - aaxCheck (compDesc->AddMIDINode ( - fieldMidiOut, AAX_eMIDINodeType_LocalOutput, - "MIDI Out", 0xFFFF)); + if (plugin->acceptsMidi()) + aaxCheck (compDesc->AddMIDINode ( + fieldMidiIn, AAX_eMIDINodeType_LocalInput, "MIDI In", 0xFFFF)); - aaxCheck (compDesc->AddPrivateData ( - fieldPluginInfo, sizeof (YupPluginInstanceInfo), - AAX_ePrivateDataOptions_DefaultOptions)); + if (plugin->producesMidi()) + aaxCheck (compDesc->AddMIDINode ( + fieldMidiOut, AAX_eMIDINodeType_LocalOutput, "MIDI Out", 0xFFFF)); - aaxCheck (compDesc->AddPrivateData ( - fieldPreparedFlag, sizeof (int32_t))); + aaxCheck (compDesc->AddPrivateData ( + fieldPluginInfo, sizeof (YupPluginInstanceInfo), AAX_ePrivateDataOptions_DefaultOptions)); - aaxCheck (compDesc->AddPrivateData ( - fieldMeterTaps, sizeof (float) * 16, - AAX_ePrivateDataOptions_DefaultOptions)); + aaxCheck (compDesc->AddPrivateData ( + fieldPreparedFlag, sizeof (int32_t))); - aaxCheck (compDesc->AddPrivateData ( - fieldSideChain, sizeof (int32_t), - AAX_ePrivateDataOptions_DefaultOptions)); + if (! meterIDs.empty()) + aaxCheck (compDesc->AddMeters ( + fieldMeterTaps, meterIDs.data(), static_cast (meterIDs.size()))); - auto* properties = compDesc->NewPropertyMap(); - if (properties == nullptr) - continue; + auto* properties = compDesc->NewPropertyMap(); + if (properties == nullptr) + return; - aaxCheck (properties->AddProperty ( - AAX_eProperty_ManufacturerID, - static_cast (YupPlugin_AAX_ManufacturerID))); + aaxCheck (properties->AddProperty ( + AAX_eProperty_ManufacturerID, + static_cast (YupPlugin_AAX_ManufacturerID))); - aaxCheck (properties->AddProperty ( - AAX_eProperty_ProductID, - static_cast (YupPlugin_AAX_ProductID))); + aaxCheck (properties->AddProperty ( + AAX_eProperty_ProductID, + static_cast (YupPlugin_AAX_ProductID))); - aaxCheck (properties->AddProperty ( - AAX_eProperty_PlugInID_Native, - static_cast (YupPlugin_AAX_PlugInID_Native))); + aaxCheck (properties->AddProperty ( + AAX_eProperty_PlugInID_Native, + static_cast (YupPlugin_AAX_PlugInID_Native))); - aaxCheck (properties->AddProperty ( - AAX_eProperty_PlugInID_AudioSuite, - static_cast (YupPlugin_AAX_PlugInID_AudioSuite))); + aaxCheck (properties->AddProperty ( + AAX_eProperty_PlugInID_AudioSuite, + static_cast (YupPlugin_AAX_PlugInID_AudioSuite))); - aaxCheck (properties->AddProperty ( - AAX_eProperty_InputStemFormat, aaxInFormat)); + aaxCheck (properties->AddProperty ( + AAX_eProperty_InputStemFormat, inputFormat)); - aaxCheck (properties->AddProperty ( - AAX_eProperty_OutputStemFormat, aaxOutFormat)); + aaxCheck (properties->AddProperty ( + AAX_eProperty_OutputStemFormat, outputFormat)); - aaxCheck (properties->AddProperty ( - AAX_eProperty_CanBypass, true)); + aaxCheck (properties->AddProperty ( + AAX_eProperty_CanBypass, true)); - aaxCheck (properties->AddProperty ( - AAX_eProperty_UsesClientGUI, true)); + // Request the host-generated parameter GUI only when no editor is provided + if (! plugin->hasEditor()) + aaxCheck (properties->AddProperty ( + AAX_eProperty_UsesClientGUI, true)); - aaxCheck (compDesc->AddProcessProc_Native ( - reinterpret_cast (yupAAXAlgorithmCallback), - properties)); + aaxCheck (compDesc->AddProcessProc_Native ( + reinterpret_cast (yupAAXAlgorithmCallback), + properties)); - aaxCheck (descriptor.AddComponent (compDesc)); - } - } + aaxCheck (descriptor.AddComponent (compDesc)); } } // namespace yup @@ -1701,8 +1327,13 @@ AAX_Result GetEffectDescriptions (AAX_ICollection* collection) collection->AddEffect (YupPlugin_Id, descriptor); collection->SetManufacturerName (YupPlugin_Vendor); - collection->AddPackageName (YupPlugin_Name); - collection->AddPackageName (YupPlugin_Description); + + // At least one package name of 16 characters or less is required + const yup::String pluginName (YupPlugin_Name); + collection->AddPackageName (pluginName.toRawUTF8()); + if (pluginName.length() > 16) + collection->AddPackageName (pluginName.substring (0, 16).toRawUTF8()); + collection->SetPackageVersion (1); return AAX_SUCCESS; From 6914740fa8b0ee13292fd7e3bb96e5b32b17551a Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 01:25:46 +0200 Subject: [PATCH 07/10] Fix it --- .../aax/yup_audio_plugin_client_AAX.cpp | 238 +++++++----------- 1 file changed, 89 insertions(+), 149 deletions(-) diff --git a/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp b/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp index 2532c15e1..28353f818 100644 --- a/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp +++ b/modules/yup_audio_plugin_client/aax/yup_audio_plugin_client_AAX.cpp @@ -96,9 +96,7 @@ namespace yup class YupAAX_Processor; class YupAAX_GUI; -void AAX_CALLBACK yupAAXAlgorithmCallback ( - void* const instancesBegin[], - const void* instancesEnd); +void AAX_CALLBACK yupAAXAlgorithmCallback (void* const instancesBegin[], const void* instancesEnd); //============================================================================== // Algorithm context @@ -153,8 +151,10 @@ static int32_t getAaxParamHash (const char* paramID) noexcept { int32_t h = 0; if (paramID != nullptr) + { for (auto p = paramID; *p != '\0'; ++p) h = 31 * h + static_cast (*p); + } return h; } @@ -243,12 +243,16 @@ static AAX_EStemFormat stemFormatForChannelCount (int numChannels) static AAX_EMeterType getMeterTypeFromParam (const AudioParameter& param) { const auto name = param.getName(); + if (name.containsIgnoreCase ("input")) return AAX_eMeterType_Input; + if (name.containsIgnoreCase ("output")) return AAX_eMeterType_Output; + if (name.containsIgnoreCase ("gr") || name.containsIgnoreCase ("gain reduction")) return AAX_eMeterType_CLGain; + return AAX_eMeterType_Other; } @@ -262,7 +266,8 @@ static bool isMeterParameter (const AudioParameter& param) // YupAAX_Processor //============================================================================== -class YupAAX_Processor : public AAX_CEffectParameters +class YupAAX_Processor + : public AAX_CEffectParameters , private AudioProcessorBase::Listener { public: @@ -317,9 +322,7 @@ class YupAAX_Processor : public AAX_CEffectParameters ctrl->GetInputStemFormat (&inputFormat); ctrl->GetOutputStemFormat (&outputFormat); - auto err = preparePlugin (static_cast (sampleRate), - inputFormat, - outputFormat); + auto err = preparePlugin (static_cast (sampleRate), inputFormat, outputFormat); if (err != AAX_SUCCESS) return err; @@ -331,13 +334,9 @@ class YupAAX_Processor : public AAX_CEffectParameters // AAX_CEffectParameters — Parameters //========================================================================== - AAX_Result UpdateParameterNormalizedValue ( - AAX_CParamID paramID, - double value, - AAX_EUpdateSource source) override + AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override { - const auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue ( - paramID, value, source); + const auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); if (result == AAX_SUCCESS && ! inParameterChangedCallback.get()) setAudioProcessorParameter (paramID, static_cast (value)); @@ -345,38 +344,29 @@ class YupAAX_Processor : public AAX_CEffectParameters return result; } - AAX_Result GetParameterValueFromString ( - AAX_CParamID paramID, - double* result, - const AAX_IString& text) const override + AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override { if (auto* param = getParamForID (paramID)) { - *result = static_cast ( - param->convertFromString (String (text.Get()))); + *result = static_cast (param->convertFromString (String (text.Get()))); return AAX_SUCCESS; } - return AAX_CEffectParameters::GetParameterValueFromString ( - paramID, result, text); + + return AAX_CEffectParameters::GetParameterValueFromString (paramID, result, text); } - AAX_Result GetParameterStringFromValue ( - AAX_CParamID paramID, - double value, - AAX_IString* result, - int32_t maxLen) const override + AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override { if (auto* param = getParamForID (paramID)) { const auto str = param->convertToString (static_cast (value)); result->Set (str.substring (0, maxLen).toRawUTF8()); } + return AAX_SUCCESS; } - AAX_Result GetParameterNumberOfSteps ( - AAX_CParamID paramID, - int32_t* result) const override + AAX_Result GetParameterNumberOfSteps (AAX_CParamID paramID, int32_t* result) const override { if (auto* param = getParamForID (paramID)) { @@ -387,34 +377,31 @@ class YupAAX_Processor : public AAX_CEffectParameters { *result = 0; } + return AAX_SUCCESS; } - AAX_Result GetParameterNormalizedValue ( - AAX_CParamID paramID, - double* result) const override + AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override { if (auto* param = getParamForID (paramID)) *result = static_cast (param->getNormalizedValue()); else *result = 0.0; + return AAX_SUCCESS; } - AAX_Result SetParameterNormalizedValue ( - AAX_CParamID paramID, - double newValue) override + AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override { if (auto* p = mParameterManager.GetParameterByID (paramID)) p->SetValueWithFloat (static_cast (newValue)); setAudioProcessorParameter (paramID, static_cast (newValue)); + return AAX_SUCCESS; } - AAX_Result SetParameterNormalizedRelative ( - AAX_CParamID paramID, - double newDeltaValue) override + AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override { if (auto* param = getParamForID (paramID)) { @@ -425,37 +412,34 @@ class YupAAX_Processor : public AAX_CEffectParameters if (auto* p = mParameterManager.GetParameterByID (paramID)) p->SetValueWithFloat (newValue); } + return AAX_SUCCESS; } - AAX_Result GetParameterNameOfLength ( - AAX_CParamID paramID, - AAX_IString* result, - int32_t maxLen) const override + AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override { if (auto* param = getParamForID (paramID)) result->Set (param->getName().substring (0, maxLen).toRawUTF8()); + return AAX_SUCCESS; } - AAX_Result GetParameterName ( - AAX_CParamID paramID, - AAX_IString* result) const override + AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override { if (auto* param = getParamForID (paramID)) result->Set (param->getName().substring (0, 31).toRawUTF8()); + return AAX_SUCCESS; } - AAX_Result GetParameterDefaultNormalizedValue ( - AAX_CParamID paramID, - double* result) const override + AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override { if (auto* param = getParamForID (paramID)) *result = static_cast ( param->convertToNormalizedValue (param->getDefaultValue())); else *result = 0.0; + return AAX_SUCCESS; } @@ -463,39 +447,41 @@ class YupAAX_Processor : public AAX_CEffectParameters // AAX_CEffectParameters — State reset //========================================================================== - AAX_Result ResetFieldData ( - AAX_CFieldIndex fieldIndex, - void* data, - uint32_t dataSize) const override + AAX_Result ResetFieldData (AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override { switch (fieldIndex) { case fieldPluginInfo: { const auto numObjects = dataSize / sizeof (YupPluginInstanceInfo); + auto* objects = static_cast (data); for (size_t i = 0; i < numObjects; ++i) - new (objects + i) YupPluginInstanceInfo ( - const_cast (*this)); + new (objects + i) YupPluginInstanceInfo (const_cast (*this)); + break; } case fieldPreparedFlag: { - const_cast (this)->preparePlugin ( - lastSampleRate, lastInputFormat, lastOutputFormat); + const_cast (this)->preparePlugin (lastSampleRate, lastInputFormat, lastOutputFormat); const auto numObjects = dataSize / sizeof (uint32_t); + auto* objects = static_cast (data); for (size_t i = 0; i < numObjects; ++i) objects[i] = 1; + break; } default: + { if (data != nullptr && dataSize > 0) std::memset (data, 0, dataSize); + break; + } } return AAX_SUCCESS; @@ -508,6 +494,7 @@ class YupAAX_Processor : public AAX_CEffectParameters AAX_Result GetNumberOfChunks (int32_t* numChunks) const override { *numChunks = yupChunkIndex + 1; + return AAX_SUCCESS; } @@ -518,6 +505,7 @@ class YupAAX_Processor : public AAX_CEffectParameters *chunkID = YupPlugin_AAX_ChunkID; return AAX_SUCCESS; } + return AAX_CEffectParameters::GetChunkIDFromIndex (index, chunkID); } @@ -540,6 +528,7 @@ class YupAAX_Processor : public AAX_CEffectParameters *size = static_cast (tls.data.getSize()); return AAX_SUCCESS; } + return AAX_CEffectParameters::GetChunkSize (chunkID, size); } @@ -555,11 +544,14 @@ class YupAAX_Processor : public AAX_CEffectParameters // GetChunkSize, fData is a variable length trailing array const auto size = static_cast (tls.data.getSize()); chunk->fSize = size; + if (size > 0 && tls.data.getData() != nullptr) tls.data.copyTo (chunk->fData, 0, static_cast (size)); + tls.isValid = false; return AAX_SUCCESS; } + return AAX_CEffectParameters::GetChunk (chunkID, chunk); } @@ -568,14 +560,12 @@ class YupAAX_Processor : public AAX_CEffectParameters if (chunkID == YupPlugin_AAX_ChunkID) { MemoryBlock rawData (chunk->fData, static_cast (chunk->fSize)); - auto state = readWrapperBypassState ( - rawData, YupPlugin_AAX_ChunkMagic, YupPlugin_AAX_ChunkVersion); + auto state = readWrapperBypassState (rawData, YupPlugin_AAX_ChunkMagic, YupPlugin_AAX_ChunkVersion); if (state.hasWrapperState) { setBypassed (state.isBypassed); - SetParameterNormalizedValue (cDefaultMasterBypassID, - state.isBypassed ? 1.0 : 0.0); + SetParameterNormalizedValue (cDefaultMasterBypassID, state.isBypassed ? 1.0 : 0.0); if (state.hasProcessorState && ! state.processorState.isEmpty()) processor->loadStateFromMemory (state.processorState); @@ -588,13 +578,16 @@ class YupAAX_Processor : public AAX_CEffectParameters resyncParameterValues(); return AAX_SUCCESS; } + return AAX_CEffectParameters::SetChunk (chunkID, chunk); } AAX_Result GetNumberOfChanges (int32_t* numChanges) const override { const auto result = AAX_CEffectParameters::GetNumberOfChanges (numChanges); + *numChanges += numSetDirtyCalls; + return result; } @@ -602,10 +595,7 @@ class YupAAX_Processor : public AAX_CEffectParameters // AAX_CEffectParameters — Notifications //========================================================================== - AAX_Result NotificationReceived ( - AAX_CTypeID notificationType, - const void* notificationData, - uint32_t notificationDataSize) override + AAX_Result NotificationReceived (AAX_CTypeID notificationType, const void* notificationData, uint32_t notificationDataSize) override { if (notificationType == AAX_eNotificationEvent_EnteringOfflineMode) { @@ -616,8 +606,7 @@ class YupAAX_Processor : public AAX_CEffectParameters processor->suspendProcessing (false); } - return AAX_CEffectParameters::NotificationReceived ( - notificationType, notificationData, notificationDataSize); + return AAX_CEffectParameters::NotificationReceived (notificationType, notificationData, notificationDataSize); } //========================================================================== @@ -687,8 +676,7 @@ class YupAAX_Processor : public AAX_CEffectParameters // AudioProcessorBase::Listener //========================================================================== - void audioProcessorChanged (AudioProcessorBase* p, - const AudioProcessorBase::ChangeDetails& details) override + void audioProcessorChanged (AudioProcessorBase* p, const AudioProcessorBase::ChangeDetails& details) override { if (p != processor.get()) return; @@ -713,9 +701,7 @@ class YupAAX_Processor : public AAX_CEffectParameters // Helpers //========================================================================== - AAX_Result preparePlugin (float sampleRate, - AAX_EStemFormat inputFormat, - AAX_EStemFormat outputFormat) + AAX_Result preparePlugin (float sampleRate, AAX_EStemFormat inputFormat, AAX_EStemFormat outputFormat) { lastSampleRate = sampleRate; lastInputFormat = inputFormat; @@ -792,8 +778,7 @@ class YupAAX_Processor : public AAX_CEffectParameters AAX_CNumberDisplayDelegate(), param.isAutomatable()); - aaxParam->SetNumberOfSteps (static_cast ( - numSteps > 0 ? jmin (numSteps, 2048) : 1000)); + aaxParam->SetNumberOfSteps (static_cast (numSteps > 0 ? jmin (numSteps, 2048) : 1000)); aaxParam->SetType (isDiscrete ? AAX_eParameterType_Discrete @@ -817,8 +802,7 @@ class YupAAX_Processor : public AAX_CEffectParameters SetParameterDefaultNormalizedValue ( aaxParamID.toRawUTF8(), - static_cast ( - param->convertToNormalizedValue (param->getDefaultValue()))); + static_cast (param->convertToNormalizedValue (param->getDefaultValue()))); } } @@ -910,8 +894,7 @@ class YupAAX_Processor : public AAX_CEffectParameters return MidiMessage::channelPressureChange (channel, data[1]); case 0xE0: { - const auto value = static_cast (data[1]) - | (static_cast (data[2]) << 7); + const auto value = static_cast (data[1]) | (static_cast (data[2]) << 7); return MidiMessage::pitchWheel (channel, value); } default: @@ -932,13 +915,11 @@ class YupAAX_Processor : public AAX_CEffectParameters } } - static void yupMidiMessageToAaxPacket (const MidiMessage& msg, - AAX_CMidiPacket& pkt) + static void yupMidiMessageToAaxPacket (const MidiMessage& msg, AAX_CMidiPacket& pkt) { const auto data = msg.getRawData(); const auto dataSize = msg.getRawDataSize(); - const auto copySize = jmin (dataSize, - static_cast (sizeof (pkt.mData))); + const auto copySize = jmin (dataSize, static_cast (sizeof (pkt.mData))); std::memcpy (pkt.mData, data, static_cast (copySize)); pkt.mLength = static_cast (copySize); } @@ -976,6 +957,8 @@ class YupAAX_Processor : public AAX_CEffectParameters MemoryBlock data; }; + ScopedYupInitialiser_GUI scopeInitialiser; + std::unique_ptr processor; int32_t yupChunkIndex = 0; float lastSampleRate = 44100.0f; @@ -1125,6 +1108,7 @@ class YupAAX_GUI : public AAX_CEffectGUI } private: + ScopedYupInitialiser_Windowing scopeInitialiser; std::unique_ptr editorComponent; }; @@ -1132,14 +1116,10 @@ class YupAAX_GUI : public AAX_CEffectGUI // Algorithm callback //============================================================================== -void AAX_CALLBACK yupAAXAlgorithmCallback ( - void* const instancesBegin[], - const void* instancesEnd) +void AAX_CALLBACK yupAAXAlgorithmCallback (void* const instancesBegin[], const void* instancesEnd) { - auto** typedBegin = static_cast ( - (void*) instancesBegin); - auto** typedEnd = static_cast ( - (void*) instancesEnd); + auto** typedBegin = static_cast ((void*) instancesBegin); + auto** typedEnd = static_cast ((void*) instancesEnd); for (auto** walk = typedBegin; walk < typedEnd; ++walk) { @@ -1180,17 +1160,12 @@ static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) descriptor.AddCategory (YupPlugin_AAXCategory); if (plugin->hasEditor()) - aaxCheck (descriptor.AddProcPtr ( - reinterpret_cast (YupAAX_GUI::Create), - kAAX_ProcPtrID_Create_EffectGUI)); + aaxCheck (descriptor.AddProcPtr (reinterpret_cast (YupAAX_GUI::Create), kAAX_ProcPtrID_Create_EffectGUI)); - aaxCheck (descriptor.AddProcPtr ( - reinterpret_cast (YupAAX_Processor::Create), - kAAX_ProcPtrID_Create_EffectParameters)); + aaxCheck (descriptor.AddProcPtr (reinterpret_cast (YupAAX_Processor::Create), kAAX_ProcPtrID_Create_EffectParameters)); #ifdef YupPlugin_AAXPageTableFile - descriptor.AddResourceInfo ( - AAX_eResourceType_PageTable, YupPlugin_AAXPageTableFile); + descriptor.AddResourceInfo (AAX_eResourceType_PageTable, YupPlugin_AAXPageTableFile); #endif // Register meters on the descriptor, the same IDs are then bound to the @@ -1207,21 +1182,12 @@ static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) if (meterProps == nullptr) continue; - meterProps->AddProperty ( - AAX_eProperty_Meter_Type, - getMeterTypeFromParam (*param)); - - meterProps->AddProperty ( - AAX_eProperty_Meter_Orientation, - AAX_eMeterOrientation_TopRight); + meterProps->AddProperty (AAX_eProperty_Meter_Type, getMeterTypeFromParam (*param)); + meterProps->AddProperty (AAX_eProperty_Meter_Orientation, AAX_eMeterOrientation_TopRight); - const auto meterID = static_cast ( - 'Mtr0' + static_cast (meterIDs.size())); + const auto meterID = static_cast ('Mtr0' + static_cast (meterIDs.size())); - descriptor.AddMeterDescription ( - meterID, - param->getName().toRawUTF8(), - meterProps); + descriptor.AddMeterDescription (meterID, param->getName().toRawUTF8(), meterProps); meterIDs.push_back (meterID); } @@ -1250,60 +1216,34 @@ static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) aaxCheck (compDesc->AddDataInPort (fieldBypass, sizeof (int32_t))); if (plugin->acceptsMidi()) - aaxCheck (compDesc->AddMIDINode ( - fieldMidiIn, AAX_eMIDINodeType_LocalInput, "MIDI In", 0xFFFF)); + aaxCheck (compDesc->AddMIDINode (fieldMidiIn, AAX_eMIDINodeType_LocalInput, "MIDI In", 0xFFFF)); if (plugin->producesMidi()) - aaxCheck (compDesc->AddMIDINode ( - fieldMidiOut, AAX_eMIDINodeType_LocalOutput, "MIDI Out", 0xFFFF)); + aaxCheck (compDesc->AddMIDINode (fieldMidiOut, AAX_eMIDINodeType_LocalOutput, "MIDI Out", 0xFFFF)); - aaxCheck (compDesc->AddPrivateData ( - fieldPluginInfo, sizeof (YupPluginInstanceInfo), AAX_ePrivateDataOptions_DefaultOptions)); - - aaxCheck (compDesc->AddPrivateData ( - fieldPreparedFlag, sizeof (int32_t))); + aaxCheck (compDesc->AddPrivateData (fieldPluginInfo, sizeof (YupPluginInstanceInfo), AAX_ePrivateDataOptions_DefaultOptions)); + aaxCheck (compDesc->AddPrivateData (fieldPreparedFlag, sizeof (int32_t))); if (! meterIDs.empty()) - aaxCheck (compDesc->AddMeters ( - fieldMeterTaps, meterIDs.data(), static_cast (meterIDs.size()))); + aaxCheck (compDesc->AddMeters (fieldMeterTaps, meterIDs.data(), static_cast (meterIDs.size()))); auto* properties = compDesc->NewPropertyMap(); if (properties == nullptr) return; - aaxCheck (properties->AddProperty ( - AAX_eProperty_ManufacturerID, - static_cast (YupPlugin_AAX_ManufacturerID))); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_ProductID, - static_cast (YupPlugin_AAX_ProductID))); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_PlugInID_Native, - static_cast (YupPlugin_AAX_PlugInID_Native))); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_PlugInID_AudioSuite, - static_cast (YupPlugin_AAX_PlugInID_AudioSuite))); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_InputStemFormat, inputFormat)); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_OutputStemFormat, outputFormat)); - - aaxCheck (properties->AddProperty ( - AAX_eProperty_CanBypass, true)); + aaxCheck (properties->AddProperty (AAX_eProperty_ManufacturerID, static_cast (YupPlugin_AAX_ManufacturerID))); + aaxCheck (properties->AddProperty (AAX_eProperty_ProductID, static_cast (YupPlugin_AAX_ProductID))); + aaxCheck (properties->AddProperty (AAX_eProperty_PlugInID_Native, static_cast (YupPlugin_AAX_PlugInID_Native))); + aaxCheck (properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, static_cast (YupPlugin_AAX_PlugInID_AudioSuite))); + aaxCheck (properties->AddProperty (AAX_eProperty_InputStemFormat, inputFormat)); + aaxCheck (properties->AddProperty (AAX_eProperty_OutputStemFormat, outputFormat)); + aaxCheck (properties->AddProperty (AAX_eProperty_CanBypass, true)); // Request the host-generated parameter GUI only when no editor is provided if (! plugin->hasEditor()) - aaxCheck (properties->AddProperty ( - AAX_eProperty_UsesClientGUI, true)); + aaxCheck (properties->AddProperty (AAX_eProperty_UsesClientGUI, true)); - aaxCheck (compDesc->AddProcessProc_Native ( - reinterpret_cast (yupAAXAlgorithmCallback), - properties)); + aaxCheck (compDesc->AddProcessProc_Native (reinterpret_cast (yupAAXAlgorithmCallback), properties)); aaxCheck (descriptor.AddComponent (compDesc)); } From adfbe16f7d2c5d010d5a821aedf345c51d94abde Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 01:30:34 +0200 Subject: [PATCH 08/10] Fix missing code --- .../au/yup_audio_plugin_client_AU.mm | 1529 ++++++++++++++++- 1 file changed, 1528 insertions(+), 1 deletion(-) diff --git a/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm index e9bc75ea8..5a314d242 100644 --- a/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm +++ b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm @@ -19,4 +19,1531 @@ ============================================================================== */ -#include "yup_audio_plugin_client_AU.cpp" +#include "../yup_audio_plugin_client.h" + +#include "../common/yup_AudioPluginUtilities.h" + +#if ! defined(YUP_AUDIO_PLUGIN_ENABLE_AU) +#error "YUP_AUDIO_PLUGIN_ENABLE_AU must be defined" +#endif + +#if YUP_MAC +#include +#include +#include +#include +#include + +#import +#import +#import +#import +#import + +#include +#include +#include +#include +#include +#include +#include + +//============================================================================== + +extern "C" yup::AudioProcessor* createPluginProcessor(); + +@class AudioPluginEditorViewAU; + +namespace yup +{ + +static String describeScopeAndElement (AudioUnitScope scope, AudioUnitElement element) +{ + return "scope=" + String (static_cast (scope)) + ", element=" + String (static_cast (element)); +} + +static String describePointer (const void* value) +{ + return "0x" + String::toHexString (static_cast (reinterpret_cast (value))); +} + +static String describeStatus (OSStatus status) +{ + return String (static_cast (status)); +} + +//============================================================================== + +namespace +{ + +//============================================================================== + +static CFStringRef getProcessorStateKey() +{ + return CFSTR ("YUPProcessorState"); +} + +//============================================================================== + +struct AUScopedYupInitialiser +{ + AUScopedYupInitialiser() + { + if (numAUScopedInitInstances.fetch_add (1) == 0) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "initialising YUP GUI"); + initialiseYup_GUI(); + } + } + + ~AUScopedYupInitialiser() + { + if (numAUScopedInitInstances.fetch_sub (1) == 1) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "shutting down YUP GUI"); + shutdownYup_GUI(); + } + } + +private: + static std::atomic_int numAUScopedInitInstances; +}; + +std::atomic_int AUScopedYupInitialiser::numAUScopedInitInstances = 0; + +struct AUScopedYupWindowingInitialiser +{ + AUScopedYupWindowingInitialiser() + { + if (numAUScopedInitInstances.fetch_add (1) == 0) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "initialising YUP windowing for editor"); + initialiseYup_Windowing(); + } + } + + ~AUScopedYupWindowingInitialiser() + { + if (numAUScopedInitInstances.fetch_sub (1) == 1) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "shutting down YUP windowing for editor"); + shutdownYup_Windowing(); + } + } + +private: + static std::atomic_int numAUScopedInitInstances; +}; + +std::atomic_int AUScopedYupWindowingInitialiser::numAUScopedInitInstances = 0; + +//============================================================================== + +static OSType osTypeFromString (const char* s) +{ + if (s == nullptr || std::strlen (s) < 4) + return 0; + + return static_cast ( + (static_cast (static_cast (s[0])) << 24) | (static_cast (static_cast (s[1])) << 16) | (static_cast (static_cast (s[2])) << 8) | static_cast (static_cast (s[3]))); +} + +} // namespace + +//============================================================================== + +#if YupPlugin_IsSynth +using AudioPluginAUBase = ausdk::MusicDeviceBase; +#else +using AudioPluginAUBase = ausdk::AUEffectBase; +#endif + +//============================================================================== + +/** + AUv2 wrapper for a YUP AudioProcessor. + + Supports both effects (AUEffectBase) and instruments (MusicDeviceBase) + depending on the YupPlugin_IsSynth compile-time setting. +*/ +class AudioPluginProcessorAU final + : public AudioPluginAUBase + , private AudioParameter::Listener +{ +public: + class AudioPluginPlayHeadAU final : public AudioPlayHead + { + public: + AudioPluginPlayHeadAU (AudioPluginProcessorAU& owner, const AudioTimeStamp* timeStamp) + : owner (owner) + , timeStamp (timeStamp) + { + } + + bool canControlTransport() override + { + return false; + } + + std::optional getPosition() const override + { + return owner.createPositionInfo (timeStamp); + } + + private: + AudioPluginProcessorAU& owner; + const AudioTimeStamp* timeStamp = nullptr; + }; + + //============================================================================== + + AudioPluginProcessorAU (AudioComponentInstance component) +#if YupPlugin_IsSynth + : AudioPluginAUBase (component, 0, 1) + , +#else + : AudioPluginAUBase (component) + , +#endif + componentInstance (component) + { + processor.reset (::createPluginProcessor()); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "created processor instance: wrapper=" << yup::describePointer (this) << ", component=" << yup::describePointer (componentInstance) << ", processor=" << yup::describePointer (processor.get())); + + if (processor == nullptr) + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "createPluginProcessor returned null"); + + addParameterListeners(); + registerInstance (componentInstance, this); + } + + ~AudioPluginProcessorAU() override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "destroying processor instance: wrapper=" << yup::describePointer (this) << ", component=" << yup::describePointer (componentInstance) << ", processor=" << yup::describePointer (processor.get())); + + closeEditorViews(); + removeParameterListeners(); + yup::endActiveParameterGestures (processor.get()); + + unregisterInstance (componentInstance); + + processor.reset(); + } + + //============================================================================== + + OSStatus Initialize() override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize requested"); + + const auto result = AudioPluginAUBase::Initialize(); + if (result != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "base Initialize failed: status=" << describeStatus (result)); + return result; + } + + if (processor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize failed: processor is null"); + return kAudioUnitErr_FailedInitialization; + } + + processor->setOfflineProcessing (renderingOffline); + processor->setPlaybackConfiguration (static_cast (getCurrentSampleRate()), + static_cast (GetMaxFramesPerSlice())); + + midiBuffer.ensureSize (4096); + midiBuffer.clear(); + emptyMidiBuffer.ensureSize (4096); + emptyMidiBuffer.clear(); + paramChangeBuffer.reserve (getDefaultParameterChangeCapacity (*processor)); + emptyParamChangeBuffer.reserve (getDefaultParameterChangeCapacity (*processor)); + audioChannels.reserve (static_cast (getTotalAudioOutputChannels (*processor))); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Initialize completed: sampleRate=" << String (getCurrentSampleRate()) << ", maxFramesPerSlice=" << String (static_cast (GetMaxFramesPerSlice()))); + + return noErr; + } + + void Cleanup() override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Cleanup requested: wrapper=" << yup::describePointer (this) << ", processor=" << yup::describePointer (processor.get())); + + if (processor != nullptr) + processor->releaseResources(); + + AudioPluginAUBase::Cleanup(); + } + + //============================================================================== + + OSStatus GetParameterList (AudioUnitScope inScope, + AudioUnitParameterID* outParameterList, + UInt32& outNumParameters) override + { + if (inScope != kAudioUnitScope_Global || processor == nullptr) + { + outNumParameters = 0; + return kAudioUnitErr_InvalidParameter; + } + + const auto parameters = processor->getParameters(); + + if (outParameterList != nullptr) + { + for (size_t i = 0; i < parameters.size(); ++i) + outParameterList[i] = static_cast (parameters[i]->getHostParameterID()); + } + + outNumParameters = static_cast (parameters.size()); + return noErr; + } + + OSStatus GetParameterInfo (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) override + { + if (inScope != kAudioUnitScope_Global || processor == nullptr) + return kAudioUnitErr_InvalidParameter; + + const auto parameters = processor->getParameters(); + const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inParameterID)); + if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) + return kAudioUnitErr_InvalidParameter; + + const auto& param = parameters[parameterIndex]; + + outParameterInfo.flags = kAudioUnitParameterFlag_IsReadable | kAudioUnitParameterFlag_HasCFNameString; + + if (! param->isReadOnly()) + outParameterInfo.flags |= kAudioUnitParameterFlag_IsWritable; + + outParameterInfo.cfNameString = param->getName().toCFString(); + param->getName().copyToUTF8 (outParameterInfo.name, sizeof (outParameterInfo.name)); + + outParameterInfo.unit = kAudioUnitParameterUnit_Generic; + outParameterInfo.minValue = param->getMinimumValue(); + outParameterInfo.maxValue = param->getMaximumValue(); + outParameterInfo.defaultValue = param->getDefaultValue(); + outParameterInfo.clumpID = 0; + + return noErr; + } + + OSStatus GetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + AudioUnitParameterValue& outValue) override + { + if (inScope != kAudioUnitScope_Global || processor == nullptr) + return kAudioUnitErr_InvalidParameter; + + const auto parameters = processor->getParameters(); + const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inID)); + if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) + return kAudioUnitErr_InvalidParameter; + + outValue = static_cast (parameters[parameterIndex]->getValue()); + return noErr; + } + + OSStatus SetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + AudioUnitParameterValue inValue, + UInt32 inBufferOffsetInFrames) override + { + if (inScope != kAudioUnitScope_Global || processor == nullptr) + return kAudioUnitErr_InvalidParameter; + + const auto parameters = processor->getParameters(); + const auto parameterIndex = processor->getParameterIndexByHostID (static_cast (inID)); + if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size()))) + return kAudioUnitErr_InvalidParameter; + + if (parameters[parameterIndex]->isReadOnly() + || parameters[parameterIndex]->isPerformingChangeGesture()) + { + return noErr; + } + + parameters[parameterIndex]->setValue (static_cast (inValue)); + + std::unique_lock lock (parameterChangeMutex, std::try_to_lock); + if (lock.owns_lock()) + { + addParameterChangeByHostParameterID (*processor, + paramChangeBuffer, + static_cast (inID), + parameters[parameterIndex]->convertToNormalizedValue (static_cast (inValue)), + static_cast (inBufferOffsetInFrames)); + } + + return noErr; + } + + //============================================================================== + + UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override + { + if (processor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SupportedNumChannels requested without processor"); + return 0; + } + + channelInfoCache.clear(); + + const auto& busLayout = processor->getBusLayout(); + + int inputChannels = 0; + for (const auto& bus : busLayout.getInputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + inputChannels = std::max (inputChannels, bus.getNumChannels()); + + int outputChannels = 0; + for (const auto& bus : busLayout.getOutputBuses()) + if (bus.getType() == AudioBus::Type::Audio) + outputChannels = std::max (outputChannels, bus.getNumChannels()); + + if (inputChannels > 0 || outputChannels > 0) + { + AUChannelInfo info; + info.inChannels = static_cast (inputChannels); + info.outChannels = static_cast (outputChannels); + channelInfoCache.push_back (info); + } + + if (outInfo != nullptr) + *outInfo = channelInfoCache.data(); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SupportedNumChannels returned " << String (static_cast (channelInfoCache.size())) << " layouts"); + + return static_cast (channelInfoCache.size()); + } + + //============================================================================== + + bool SupportsTail() override + { + return processor != nullptr && processor->getTailSamples() > 0; + } + + Float64 GetTailTime() override + { + const auto sampleRate = getCurrentSampleRate(); + if (processor == nullptr || sampleRate <= 0.0) + return 0.0; + + return static_cast (processor->getTailSamples()) / sampleRate; + } + + Float64 GetLatency() override + { + const auto sampleRate = getCurrentSampleRate(); + if (processor == nullptr || sampleRate <= 0.0) + return 0.0; + + return static_cast (processor->getLatencySamples()) / sampleRate; + } + + //============================================================================== + + std::optional createPositionInfo (const AudioTimeStamp* timeStamp) + { + AudioPlayHead::PositionInfo result; + bool hasPosition = false; + + if (timeStamp != nullptr && (timeStamp->mFlags & kAudioTimeStampSampleTimeValid) != 0) + { + const auto timeInSamples = static_cast (timeStamp->mSampleTime); + result.setTimeInSamples (timeInSamples); + + const auto sampleRate = getCurrentSampleRate(); + if (sampleRate > 0.0) + result.setTimeInSeconds (static_cast (timeInSamples) / sampleRate); + + hasPosition = true; + } + + Float64 currentBeat = 0.0; + Float64 currentTempo = 0.0; + if (CallHostBeatAndTempo (¤tBeat, ¤tTempo) == noErr) + { + result.setPpqPosition (currentBeat); + result.setBpm (currentTempo); + hasPosition = true; + } + + UInt32 deltaSamplesToNextBeat = 0; + Float32 timeSignatureNumerator = 0.0f; + UInt32 timeSignatureDenominator = 0; + Float64 currentMeasureDownBeat = 0.0; + if (CallHostMusicalTimeLocation (&deltaSamplesToNextBeat, + &timeSignatureNumerator, + &timeSignatureDenominator, + ¤tMeasureDownBeat) + == noErr) + { + ignoreUnused (deltaSamplesToNextBeat); + result.setTimeSignature (AudioPlayHead::TimeSignature { + static_cast (timeSignatureNumerator), + static_cast (timeSignatureDenominator) }); + result.setPpqPositionOfLastBarStart (currentMeasureDownBeat); + hasPosition = true; + } + + Boolean isPlaying = false; + Boolean transportStateChanged = false; + Float64 currentSampleInTimeline = 0.0; + Boolean isCycling = false; + Float64 cycleStartBeat = 0.0; + Float64 cycleEndBeat = 0.0; + if (CallHostTransportState (&isPlaying, + &transportStateChanged, + ¤tSampleInTimeline, + &isCycling, + &cycleStartBeat, + &cycleEndBeat) + == noErr) + { + ignoreUnused (transportStateChanged); + result.setIsPlaying (isPlaying); + result.setIsLooping (isCycling); + + if (isCycling) + result.setLoopPoints (AudioPlayHead::LoopPoints { cycleStartBeat, cycleEndBeat }); + + result.setTimeInSamples (static_cast (currentSampleInTimeline)); + + const auto sampleRate = getCurrentSampleRate(); + if (sampleRate > 0.0) + result.setTimeInSeconds (currentSampleInTimeline / sampleRate); + + hasPosition = true; + } + + return hasPosition ? std::make_optional (result) : std::nullopt; + } + + //============================================================================== + +#if YupPlugin_IsSynth + // Instrument: render audio and drain the MIDI buffer + OSStatus RenderBus (AudioUnitRenderActionFlags& ioActionFlags, + const AudioTimeStamp& inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames) override + { + if (processor == nullptr) + return kAudioUnitErr_NoConnection; + + auto& outputBus = Output (inBusNumber); + + outputBus.PrepareBuffer (inNumberFrames); + AudioBufferList& outBufList = outputBus.GetBufferList(); + + audioChannels.clear(); + for (UInt32 ch = 0; ch < outBufList.mNumberBuffers; ++ch) + audioChannels.push_back (static_cast (outBufList.mBuffers[ch].mData)); + + AudioSampleBuffer audioBuffer (audioChannels.data(), + static_cast (audioChannels.size()), + 0, + static_cast (inNumberFrames)); + + { + AudioPluginPlayHeadAU playHead (*this, &inTimeStamp); + std::unique_lock lock (midiMutex, std::try_to_lock); + auto& processMidiBuffer = lock.owns_lock() ? midiBuffer : emptyMidiBuffer; + std::unique_lock parameterLock (parameterChangeMutex, std::try_to_lock); + auto& processParamChangeBuffer = parameterLock.owns_lock() ? paramChangeBuffer : emptyParamChangeBuffer; + + AudioProcessContext context { audioBuffer, + processMidiBuffer, + processParamChangeBuffer, + &playHead }; + processAudioBlock (*processor, context, isBypassed); + + processMidiBuffer.clear(); + processParamChangeBuffer.clear(); + } + + return noErr; + } + + //============================================================================== + + OSStatus HandleMIDIEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 offsetSampleFrame) override + { + std::unique_lock lock (midiMutex, std::try_to_lock); + if (! lock.owns_lock()) + return noErr; + + const uint8_t rawData[3] = { + static_cast (status | channel), + data1, + data2 + }; + + const int numBytes = MidiMessage::getMessageLengthFromFirstByte (rawData[0]); + midiBuffer.addEvent (rawData, numBytes, static_cast (offsetSampleFrame)); + + return noErr; + } + + [[nodiscard]] bool CanScheduleParameters() const override + { + return false; + } + + bool StreamFormatWritable (AudioUnitScope inScope, AudioUnitElement inElement) override + { + return inScope == kAudioUnitScope_Output && inElement == 0; + } + + OSStatus HandleSysEx (const UInt8* inData, UInt32 inLength) override + { + std::unique_lock lock (midiMutex, std::try_to_lock); + if (! lock.owns_lock()) + return noErr; + + if (inData != nullptr && inLength > 0) + midiBuffer.addEvent (inData, static_cast (inLength), 0); + + return noErr; + } + +#else + // Effect: copy input to output and call processBlock + OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, + const AudioBufferList& inBuffer, + AudioBufferList& outBuffer, + UInt32 inFramesToProcess) override + { + if (processor == nullptr) + return kAudioUnitErr_NoConnection; + + const UInt32 numBuffers = std::min (inBuffer.mNumberBuffers, outBuffer.mNumberBuffers); + + audioChannels.clear(); + for (UInt32 ch = 0; ch < numBuffers; ++ch) + { + const auto* in = static_cast (inBuffer.mBuffers[ch].mData); + auto* out = static_cast (outBuffer.mBuffers[ch].mData); + + if (in != out) + std::memcpy (out, in, inFramesToProcess * sizeof (float)); + + audioChannels.push_back (out); + } + + AudioSampleBuffer audioBuffer (audioChannels.data(), + static_cast (audioChannels.size()), + 0, + static_cast (inFramesToProcess)); + + AudioPluginPlayHeadAU playHead (*this, nullptr); + std::unique_lock parameterLock (parameterChangeMutex, std::try_to_lock); + auto& processParamChangeBuffer = parameterLock.owns_lock() ? paramChangeBuffer : emptyParamChangeBuffer; + + AudioProcessContext context { audioBuffer, + midiBuffer, + processParamChangeBuffer, + &playHead }; + processAudioBlock (*processor, context, isBypassed); + midiBuffer.clear(); + processParamChangeBuffer.clear(); + + return noErr; + } +#endif + + //============================================================================== + + OSStatus SaveState (CFPropertyListRef* outData) override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState requested"); + + if (outData == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: outData is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + const auto baseResult = AudioPluginAUBase::SaveState (outData); + if (baseResult != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: base status=" << describeStatus (baseResult)); + return baseResult; + } + + if (processor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed without processor state: processor is null"); + return noErr; + } + + MemoryBlock data; + const auto result = processor->saveStateIntoMemory (data); + if (result.failed()) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed without processor state: " << result.getErrorMessage()); + return noErr; + } + + if (*outData != nullptr && CFGetTypeID (*outData) == CFDictionaryGetTypeID()) + { + NSData* nsData = data.getSize() > 0 + ? [NSData dataWithBytes:data.getData() length:data.getSize()] + : [NSData data]; + + auto* stateDictionary = const_cast (static_cast (*outData)); + CFDictionarySetValue (stateDictionary, + getProcessorStateKey(), + (__bridge CFDataRef) nsData); + } + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed with processor state: bytes=" << String (static_cast (data.getSize()))); + + return noErr; + } + + OSStatus RestoreState (CFPropertyListRef inData) override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState requested"); + + if (inData == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: inData is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + CFDataRef processorState = nullptr; + OSStatus baseResult = noErr; + + if (CFGetTypeID (inData) == CFDictionaryGetTypeID()) + { + processorState = static_cast (CFDictionaryGetValue (static_cast (inData), + getProcessorStateKey())); + + if (processorState != nullptr && CFGetTypeID (processorState) != CFDataGetTypeID()) + return kAudioUnitErr_InvalidPropertyValue; + + baseResult = AudioPluginAUBase::RestoreState (inData); + if (baseResult != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: base status=" << describeStatus (baseResult)); + return baseResult; + } + } + else if (CFGetTypeID (inData) == CFDataGetTypeID()) + { + processorState = static_cast (inData); + } + else + { + return kAudioUnitErr_InvalidPropertyValue; + } + + if (processorState == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState completed without processor state"); + return baseResult; + } + + if (processor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: processor is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + MemoryBlock data (CFDataGetBytePtr (processorState), + static_cast (CFDataGetLength (processorState))); + + processor->suspendProcessing (true); + const auto result = processor->loadStateFromMemory (data); + const bool ok = result.wasOk(); + processor->suspendProcessing (false); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState " << String (ok ? "completed" : "failed") << ": bytes=" << String (static_cast (data.getSize())) << (ok ? String() : ", error=" + result.getErrorMessage())); + + return ok ? static_cast (noErr) + : static_cast (kAudioUnitErr_InvalidPropertyValue); + } + + //============================================================================== + + OSStatus GetPresets (CFArrayRef* outData) const override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets requested"); + + if (processor == nullptr || outData == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets failed: processor=" << describePointer (processor.get()) << ", outData=" << describePointer (outData)); + return kAudioUnitErr_InvalidPropertyValue; + } + + const int numPresets = processor->getNumPresets(); + NSMutableArray* presetsArray = [[NSMutableArray alloc] initWithCapacity:numPresets]; + + for (int i = 0; i < numPresets; ++i) + { + AUPreset preset; + preset.presetNumber = i; + preset.presetName = processor->getPresetName (i).toCFString(); + + [presetsArray addObject:[NSValue valueWithBytes:&preset objCType:@encode (AUPreset)]]; + CFRelease (preset.presetName); + } + + *outData = (__bridge_retained CFArrayRef) presetsArray; + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPresets returned " << String (numPresets) << " presets"); + return noErr; + } + + OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet requested: preset=" << String (static_cast (inNewFactoryPreset.presetNumber))); + + if (processor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet failed: processor is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + if (! isPositiveAndBelow (static_cast (inNewFactoryPreset.presetNumber), processor->getNumPresets())) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet failed: preset out of range"); + return kAudioUnitErr_InvalidPropertyValue; + } + + processor->setCurrentPreset (static_cast (inNewFactoryPreset.presetNumber)); + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "NewFactoryPresetSet completed"); + return noErr; + } + + //============================================================================== + + OSStatus GetPropertyInfo (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + UInt32& outDataSize, + bool& outWritable) override + { + if (inID == kAudioUnitProperty_OfflineRender) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); + + if (inScope != kAudioUnitScope_Global) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo OfflineRender failed: invalid scope"); + return kAudioUnitErr_InvalidScope; + } + + outDataSize = sizeof (UInt32); + outWritable = true; + return noErr; + } + + if (inID == kAudioUnitProperty_BypassEffect) + { + if (inScope != kAudioUnitScope_Global) + return kAudioUnitErr_InvalidScope; + + outDataSize = sizeof (UInt32); + outWritable = true; + return noErr; + } + + if (inID == kAudioUnitProperty_CocoaUI) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI requested: " << describeScopeAndElement (inScope, inElement)); + + if (inScope != kAudioUnitScope_Global) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI failed: invalid scope"); + return kAudioUnitErr_InvalidScope; + } + + if (processor != nullptr && processor->hasEditor()) + { + outDataSize = sizeof (AudioUnitCocoaViewInfo); + outWritable = false; + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI available"); + return noErr; + } + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo CocoaUI not available: processor=" << describePointer (processor.get()) << ", hasEditor=" << String (processor != nullptr && processor->hasEditor() ? "true" : "false")); + return kAudioUnitErr_PropertyNotInUse; + } + + const auto result = AudioPluginAUBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); + if (result != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetPropertyInfo delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); + } + + return result; + } + + OSStatus GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) override; // Implemented below (needs ObjC) + + OSStatus SetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + const void* inData, + UInt32 inDataSize) override + { + if (inID == kAudioUnitProperty_OfflineRender) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); + + if (inScope != kAudioUnitScope_Global) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender failed: invalid scope"); + return kAudioUnitErr_InvalidScope; + } + + if (inData == nullptr || inDataSize < sizeof (UInt32)) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty OfflineRender failed: inData=" << describePointer (inData) << ", inDataSize=" << String (static_cast (inDataSize))); + return kAudioUnitErr_InvalidPropertyValue; + } + + renderingOffline = *static_cast (inData) != 0; + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "Offline rendering set to " << String (renderingOffline ? "true" : "false")); + + if (processor != nullptr) + processor->setOfflineProcessing (renderingOffline); + + return noErr; + } + + if (inID == kAudioUnitProperty_BypassEffect) + { + if (inScope != kAudioUnitScope_Global) + return kAudioUnitErr_InvalidScope; + + if (inData == nullptr || inDataSize < sizeof (UInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + isBypassed = *static_cast (inData) != 0; + return noErr; + } + + const auto result = AudioPluginAUBase::SetProperty (inID, inScope, inElement, inData, inDataSize); + if (result != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SetProperty delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); + } + + return result; + } + + //============================================================================== + + AudioProcessor* getProcessor() const { return processor.get(); } + + void registerEditorView (AudioPluginEditorViewAU* view) + { + if (view == nil) + return; + + std::lock_guard lock (editorViewsMutex); + editorViews.push_back ((__bridge void*) view); + } + + void unregisterEditorView (AudioPluginEditorViewAU* view) + { + if (view == nil) + return; + + std::lock_guard lock (editorViewsMutex); + editorViews.erase (std::remove (editorViews.begin(), editorViews.end(), (__bridge void*) view), editorViews.end()); + } + + void closeEditorViews(); + + static AudioPluginProcessorAU* findInstance (AudioUnit component) + { + std::lock_guard lock (getInstanceRegistryMutex()); + + const auto iter = getInstanceRegistry().find (component); + auto* instance = iter != getInstanceRegistry().end() ? iter->second : nullptr; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "lookup instance: component=" << describePointer (component) << ", instance=" << describePointer (instance) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); + + return instance; + } + +private: + static std::mutex& getInstanceRegistryMutex() + { + static std::mutex mutex; + return mutex; + } + + static std::unordered_map& getInstanceRegistry() + { + static std::unordered_map instances; + return instances; + } + + static void registerInstance (AudioUnit component, AudioPluginProcessorAU* instance) + { + std::lock_guard lock (getInstanceRegistryMutex()); + + getInstanceRegistry()[component] = instance; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "registered instance: component=" << describePointer (component) << ", instance=" << describePointer (instance) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); + } + + static void unregisterInstance (AudioUnit component) + { + std::lock_guard lock (getInstanceRegistryMutex()); + + const auto numRemoved = getInstanceRegistry().erase (component); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "unregistered instance: component=" << describePointer (component) << ", removed=" << String (static_cast (numRemoved)) << ", registeredInstances=" << String (static_cast (getInstanceRegistry().size()))); + } + + void addParameterListeners() + { + removeParameterListeners(); + + if (processor == nullptr) + return; + + for (const auto& parameter : processor->getParameters()) + { + parameter->addListener (this); + listenedParameters.push_back (parameter); + } + } + + void removeParameterListeners() + { + for (auto& parameter : listenedParameters) + parameter->removeListener (this); + + listenedParameters.clear(); + } + + bool isValidProcessorParameterIndex (int indexInContainer) const + { + return processor != nullptr + && isPositiveAndBelow (indexInContainer, static_cast (processor->getParameters().size())); + } + + void parameterValueChanged (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + if (! isValidProcessorParameterIndex (indexInContainer) || parameter->isReadOnly()) + return; + + AudioPluginAUBase::SetParameter (static_cast (parameter->getHostParameterID()), + kAudioUnitScope_Global, + 0, + static_cast (parameter->getValue()), + 0); + } + + void parameterGestureBegin (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + ignoreUnused (parameter, indexInContainer); + } + + void parameterGestureEnd (const AudioParameter::Ptr& parameter, int indexInContainer) override + { + ignoreUnused (parameter, indexInContainer); + } + + Float64 getCurrentSampleRate() + { + return Output (0).GetStreamFormat().mSampleRate; + } + + ScopedYupInitialiser_GUI scopeInitialiser; + ScopedYupInitialiser_Windowing scopeWindowingInitialiser; + std::unique_ptr processor; + + MidiBuffer midiBuffer; + MidiBuffer emptyMidiBuffer; + ParameterChangeBuffer paramChangeBuffer; + ParameterChangeBuffer emptyParamChangeBuffer; + std::mutex midiMutex; + std::mutex parameterChangeMutex; + std::mutex editorViewsMutex; + std::vector channelInfoCache; + std::vector listenedParameters; + std::vector audioChannels; + std::vector editorViews; + AudioUnit componentInstance = nullptr; + bool renderingOffline = false; + bool isBypassed = false; +}; + +} // namespace yup + +//============================================================================== +// Objective-C editor view + +namespace yup +{ + +class AudioPluginEditorViewAUListener final : public ComponentListener +{ +public: + explicit AudioPluginEditorViewAUListener (AudioPluginEditorViewAU* owner) + : owner (owner) + { + } + + void componentResized (Component& component) override; + +private: + AudioPluginEditorViewAU* owner = nil; + + YUP_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginEditorViewAUListener) +}; + +} // namespace yup + +@interface AudioPluginEditorViewAU : NSView +{ + yup::AudioPluginProcessorAU* _processorWrapper; + yup::AudioProcessor* _processor; + std::unique_ptr _processorEditor; + std::unique_ptr _processorEditorListener; + bool _resizingEditorToBounds; +} +- (instancetype)initWithAudioUnitWrapper:(yup::AudioPluginProcessorAU*)processorWrapper + preferredSize:(NSSize)size; +- (void)attachEditorIfNeeded; +- (void)detachEditorIfNeeded; +- (void)closeEditorIfNeeded; +- (void)closeEditorForProcessorDestruction; +- (void)resizeEditorToBounds; +- (void)resizeViewToEditorSize; +- (void)processorEditorResized; +@end + +@implementation AudioPluginEditorViewAU + +- (instancetype)initWithAudioUnitWrapper:(yup::AudioPluginProcessorAU*)processorWrapper + preferredSize:(NSSize)size +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "creating editor view: requestedWidth=" << yup::String (static_cast (size.width)) << ", requestedHeight=" << yup::String (static_cast (size.height)) << ", wrapper=" << yup::describePointer (processorWrapper) << ", view=" << yup::describePointer ((__bridge void*) self)); + + if ((self = [super initWithFrame:NSMakeRect (0, 0, size.width, size.height)])) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view initialised: view=" << yup::describePointer ((__bridge void*) self)); + _processorWrapper = processorWrapper; + _processor = processorWrapper != nullptr ? processorWrapper->getProcessor() : nullptr; + _resizingEditorToBounds = false; + [self setPostsFrameChangedNotifications:YES]; + + if (_processorWrapper != nullptr) + _processorWrapper->registerEditorView (self); + + if (_processor != nullptr && _processor->hasEditor()) + { + _processorEditor.reset (_processor->createEditor()); + + if (_processorEditor != nullptr) + { + const auto preferredSize = _processorEditor->getPreferredSize(); + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "created editor: preferredWidth=" << yup::String (preferredSize.getWidth()) << ", preferredHeight=" << yup::String (preferredSize.getHeight()) << ", editor=" << yup::describePointer (_processorEditor.get())); + + if (_processorEditor->isResizable()) + [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + else + [self setAutoresizingMask:NSViewNotSizable]; + + _processorEditorListener = std::make_unique (self); + _processorEditor->addComponentListener (_processorEditorListener.get()); + + [self setFrameSize:NSMakeSize (preferredSize.getWidth(), preferredSize.getHeight())]; + [self resizeEditorToBounds]; + } + else + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "processor returned null editor"); + } + } + else + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "processor has no editor"); + } + } + else + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view initialisation failed"); + } + + return self; +} + +- (void)viewDidMoveToWindow +{ + [super viewDidMoveToWindow]; + + if ([self window] != nil) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view moved to window: view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window]) << ", contentView=" << yup::describePointer ((__bridge void*) [[self window] contentView])); + + [self attachEditorIfNeeded]; + } + else + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view removed from window: view=" << yup::describePointer ((__bridge void*) self)); + + [self detachEditorIfNeeded]; + } +} + +- (void)setFrameSize:(NSSize)newSize +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor view frame size changed: width=" << yup::String (static_cast (newSize.width)) << ", height=" << yup::String (static_cast (newSize.height))); + + [super setFrameSize:newSize]; + [self resizeEditorToBounds]; +} + +- (void)attachEditorIfNeeded +{ + if (_processorEditor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attachEditorIfNeeded skipped: editor is null"); + return; + } + + if (_processorEditor->isOnDesktop()) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attachEditorIfNeeded skipped: editor is already on desktop"); + return; + } + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "attaching editor to native view: editor=" << yup::describePointer (_processorEditor.get()) << ", view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window])); + + [self resizeEditorToBounds]; + + yup::ComponentNative::Flags flags = yup::ComponentNative::defaultFlags & ~yup::ComponentNative::decoratedWindow; + + if (_processorEditor->shouldRenderContinuous()) + flags.set (yup::ComponentNative::renderContinuous); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor native options: renderContinuous=" << yup::String (_processorEditor->shouldRenderContinuous() ? "true" : "false") << ", resizable=" << yup::String (_processorEditor->isResizable() ? "true" : "false")); + + auto options = yup::ComponentNative::Options() + .withFlags (flags) + .withResizableWindow (_processorEditor->isResizable()); + + _processorEditor->addToDesktop (options, (__bridge void*) self); + _processorEditor->setVisible (true); + _processorEditor->attachedToNative(); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor attached to native view: isOnDesktop=" << yup::String (_processorEditor->isOnDesktop() ? "true" : "false")); +} + +- (void)detachEditorIfNeeded +{ + if (_processorEditor == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detachEditorIfNeeded skipped: editor is null"); + return; + } + + if (! _processorEditor->isOnDesktop()) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detachEditorIfNeeded skipped: editor is not on desktop"); + return; + } + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "detaching editor from native view: editor=" << yup::describePointer (_processorEditor.get()) << ", view=" << yup::describePointer ((__bridge void*) self) << ", window=" << yup::describePointer ((__bridge void*) [self window])); + + yup::endActiveParameterGestures (_processor); + _processorEditor->setVisible (false); + _processorEditor->removeFromDesktop(); + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "editor detached from native view: isOnDesktop=" << yup::String (_processorEditor->isOnDesktop() ? "true" : "false")); +} + +- (void)closeEditorIfNeeded +{ + [self detachEditorIfNeeded]; + + yup::endActiveParameterGestures (_processor); + + if (_processorEditor != nullptr && _processorEditorListener != nullptr) + _processorEditor->removeComponentListener (_processorEditorListener.get()); + + _processorEditorListener.reset(); + _processorEditor.reset(); +} + +- (void)closeEditorForProcessorDestruction +{ + [self closeEditorIfNeeded]; + + _processorWrapper = nullptr; + _processor = nullptr; +} + +- (void)resizeEditorToBounds +{ + if (_processorEditor == nullptr) + return; + + const auto bounds = [self bounds]; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "resizing editor to bounds: width=" << yup::String (static_cast (NSWidth (bounds))) << ", height=" << yup::String (static_cast (NSHeight (bounds))) << ", editor=" << yup::describePointer (_processorEditor.get())); + + const auto scoped = yup::ScopedValueSetter (_resizingEditorToBounds, true); + + _processorEditor->setBounds ({ 0.0f, + 0.0f, + yup::jmax (1.0f, static_cast (NSWidth (bounds))), + yup::jmax (1.0f, static_cast (NSHeight (bounds))) }); +} + +- (void)resizeViewToEditorSize +{ + if (_processorEditor == nullptr || ! _processorEditor->isResizable()) + return; + + const auto newSize = NSMakeSize (yup::jmax (1.0f, _processorEditor->getWidth()), + yup::jmax (1.0f, _processorEditor->getHeight())); + + if (NSEqualSizes ([self frame].size, newSize)) + return; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "resizing editor view to editor: width=" << yup::String (static_cast (newSize.width)) << ", height=" << yup::String (static_cast (newSize.height)) << ", editor=" << yup::describePointer (_processorEditor.get())); + + [super setFrameSize:newSize]; +} + +- (void)processorEditorResized +{ + if (_resizingEditorToBounds) + return; + + [self resizeViewToEditorSize]; +} + +- (void)dealloc +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "destroying editor view: view=" << yup::describePointer ((__bridge void*) self) << ", editor=" << yup::describePointer (_processorEditor.get()) << ", processor=" << yup::describePointer (_processor)); + + [self closeEditorIfNeeded]; + + if (_processorWrapper != nullptr) + _processorWrapper->unregisterEditorView (self); + + _processorWrapper = nullptr; + _processor = nullptr; +} + +@end + +namespace yup +{ + +void AudioPluginEditorViewAUListener::componentResized (Component& component) +{ + ignoreUnused (component); + + if (owner != nil) + [owner processorEditorResized]; +} + +void AudioPluginProcessorAU::closeEditorViews() +{ + std::vector viewsToClose; + + { + std::lock_guard lock (editorViewsMutex); + viewsToClose.swap (editorViews); + } + + for (auto* view : viewsToClose) + if (view != nullptr) + [(__bridge AudioPluginEditorViewAU*) view closeEditorForProcessorDestruction]; +} + +} // namespace yup + +//============================================================================== +// Cocoa view factory + +@interface AudioPluginProcessorAUViewFactory : NSObject +@end + +@implementation AudioPluginProcessorAUViewFactory + +- (unsigned)interfaceVersion +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory interfaceVersion requested"); + return 0; +} + +- (NSString*)description +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory description requested"); + return @YupPlugin_Name; +} + +- (NSView*)uiViewForAudioUnit:(AudioUnit)inAudioUnit withSize:(NSSize)inPreferredSize +{ + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory requested editor view: audioUnit=" << yup::describePointer (inAudioUnit) << ", preferredWidth=" << yup::String (static_cast (inPreferredSize.width)) << ", preferredHeight=" << yup::String (static_cast (inPreferredSize.height))); + + auto* proc = yup::AudioPluginProcessorAU::findInstance (inAudioUnit); + if (proc == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory failed: AU instance not found"); + return nil; + } + + if (proc->getProcessor() == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "view factory failed: processor is null"); + return nil; + } + + return [[AudioPluginEditorViewAU alloc] initWithAudioUnitWrapper:proc + preferredSize:inPreferredSize]; +} + +@end + +//============================================================================== +// GetProperty implementation (needs ObjC) + +namespace yup +{ + +OSStatus AudioPluginProcessorAU::GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) +{ + if (inID == kAudioUnitProperty_OfflineRender) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender requested: " << describeScopeAndElement (inScope, inElement)); + + if (inScope != kAudioUnitScope_Global) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender failed: invalid scope"); + return kAudioUnitErr_InvalidScope; + } + + if (outData == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender failed: outData is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + *static_cast (outData) = renderingOffline ? 1u : 0u; + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty OfflineRender returned " << String (renderingOffline ? "true" : "false")); + return noErr; + } + + if (inID == kAudioUnitProperty_BypassEffect) + { + if (inScope != kAudioUnitScope_Global) + return kAudioUnitErr_InvalidScope; + + if (outData == nullptr) + return kAudioUnitErr_InvalidPropertyValue; + + *static_cast (outData) = isBypassed ? 1u : 0u; + return noErr; + } + + if (inID == kAudioUnitProperty_CocoaUI) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI requested: " << describeScopeAndElement (inScope, inElement)); + + if (inScope != kAudioUnitScope_Global) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: invalid scope"); + return kAudioUnitErr_InvalidScope; + } + + if (processor == nullptr || ! processor->hasEditor()) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: editor not available"); + return kAudioUnitErr_PropertyNotInUse; + } + + if (outData == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: outData is null"); + return kAudioUnitErr_InvalidPropertyValue; + } + + auto* info = static_cast (outData); + + // The bundle location is this plugin's own bundle + NSBundle* bundle = [NSBundle bundleForClass:[AudioPluginProcessorAUViewFactory class]]; + if (bundle == nil) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: bundle is nil"); + return kAudioUnitErr_InvalidPropertyValue; + } + + auto* bundleLocation = (__bridge_retained CFURLRef)[bundle bundleURL]; + auto* viewClass = CFStringCreateWithCString (kCFAllocatorDefault, + "AudioPluginProcessorAUViewFactory", + kCFStringEncodingUTF8); + + if (bundleLocation == nullptr || viewClass == nullptr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI failed: bundleURL=" << describePointer (bundleLocation) << ", viewClass=" << describePointer (viewClass)); + + if (bundleLocation != nullptr) + CFRelease (bundleLocation); + + if (viewClass != nullptr) + CFRelease (viewClass); + + return kAudioUnitErr_InvalidPropertyValue; + } + + info->mCocoaAUViewBundleLocation = bundleLocation; + info->mCocoaAUViewClass[0] = viewClass; + + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty CocoaUI returned view factory: bundle=" << String::fromCFString ((__bridge CFStringRef)[[bundle bundleURL] absoluteString])); + + return noErr; + } + + const auto result = AudioPluginAUBase::GetProperty (inID, inScope, inElement, outData); + if (result != noErr) + { + YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "GetProperty delegated failed: property=" << String (static_cast (inID)) << ", " << describeScopeAndElement (inScope, inElement) << ", status=" << describeStatus (result)); + } + + return result; +} + +} // namespace yup + +//============================================================================== +// Factory entry point + +#if YupPlugin_IsSynth +using AudioPluginProcessorAU = yup::AudioPluginProcessorAU; +AUSDK_COMPONENT_ENTRY (ausdk::AUMusicDeviceFactory, AudioPluginProcessorAU) +#else +using AudioPluginProcessorAU = yup::AudioPluginProcessorAU; +AUSDK_COMPONENT_ENTRY (ausdk::AUBaseProcessFactory, AudioPluginProcessorAU) +#endif + +#endif // YUP_MAC From f3b25bdf5c29cf682afcccf79c67c909485857ab Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 01:51:50 +0200 Subject: [PATCH 09/10] Fix reparenting --- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index cf87f8e77..19880f765 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -105,10 +105,10 @@ SDL2ComponentNative::SDL2ComponentNative (Component& component, if (! childWindowClassRegistered) { WNDCLASSEXW wc = {}; - wc.cbSize = sizeof (WNDCLASSEXW); - wc.lpfnWndProc = DefWindowProcW; - wc.hInstance = GetModuleHandleW (nullptr); - wc.hCursor = LoadCursorW (nullptr, IDC_ARROW); + wc.cbSize = sizeof (WNDCLASSEXW); + wc.lpfnWndProc = DefWindowProcW; + wc.hInstance = GetModuleHandleW (nullptr); + wc.hCursor = LoadCursorW (nullptr, IDC_ARROW); wc.lpszClassName = childWindowClass; childWindowClassRegistered = RegisterClassExW (&wc) != 0; } @@ -125,7 +125,8 @@ SDL2ComponentNative::SDL2ComponentNative (Component& component, childWindowClass, component.getTitle().toWideCharPointer(), style, - 0, 0, + 0, + 0, jmax (1, screenBounds.getWidth()), jmax (1, screenBounds.getHeight()), reinterpret_cast (parent), @@ -1285,16 +1286,22 @@ void SDL2ComponentNative::handleMoved (int xpos, int ypos) { auto preventBoundsChange = ScopedValueSetter (internalBoundsChange, true); - YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after move: resetting to parent-relative (0, 0)"); - setPosition ({ 0, 0 }); - component.internalMoved (0, 0); - } - else - { - component.internalMoved (xpos, ypos); +#if YUP_MAC + auto nativeWindowPos = getNativeWindowPosition (parentWindow); +#else + auto nativeWindowPos = Point (0, 0); +#endif + + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after move: " << nativeWindowPos.toString()); + setPosition (nativeWindowPos.getTopLeft()); - screenBounds = screenBounds.withPosition (xpos, ypos); + xpos = nativeWindowPos.getX(); + ypos = nativeWindowPos.getY(); } + + component.internalMoved (xpos, ypos); + + screenBounds = screenBounds.withPosition (xpos, ypos); } void SDL2ComponentNative::handleResized (int width, int height) @@ -1314,8 +1321,14 @@ void SDL2ComponentNative::handleResized (int width, int height) { auto preventBoundsChange = ScopedValueSetter (internalBoundsChange, true); - YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after resize: resetting to parent-relative (0, 0)"); - setPosition ({ 0, 0 }); +#if YUP_MAC + auto nativeWindowPos = getNativeWindowPosition (parentWindow); +#else + auto nativeWindowPos = Point (0, 0); +#endif + + YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after resize: " << nativeWindowPos.toString()); + setPosition (nativeWindowPos.getTopLeft()); } if (dynamic_cast (&component) == nullptr) From 338eb6cd08e12300252615cbf683dd1d589cdcb7 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Fri, 3 Jul 2026 02:32:42 +0200 Subject: [PATCH 10/10] Fix issues --- .../vst3/yup_audio_plugin_client_VST3.cpp | 6 ++++-- modules/yup_gui/native/yup_Windowing_sdl2.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/yup_audio_plugin_client/vst3/yup_audio_plugin_client_VST3.cpp b/modules/yup_audio_plugin_client/vst3/yup_audio_plugin_client_VST3.cpp index c51f10718..71aa9458b 100644 --- a/modules/yup_audio_plugin_client/vst3/yup_audio_plugin_client_VST3.cpp +++ b/modules/yup_audio_plugin_client/vst3/yup_audio_plugin_client_VST3.cpp @@ -436,8 +436,10 @@ class AudioPluginEditorViewVST3 const auto scoped = ScopedValueSetter (hostTriggeredResizing, true); - setSize ({ static_cast (rect.getWidth()), - static_cast (rect.getHeight()) }); + setBounds ({ static_cast (rect.left), + static_cast (rect.top), + static_cast (rect.getWidth()), + static_cast (rect.getHeight()) }); } return kResultTrue; diff --git a/modules/yup_gui/native/yup_Windowing_sdl2.cpp b/modules/yup_gui/native/yup_Windowing_sdl2.cpp index 19880f765..aea35e6d2 100644 --- a/modules/yup_gui/native/yup_Windowing_sdl2.cpp +++ b/modules/yup_gui/native/yup_Windowing_sdl2.cpp @@ -1289,7 +1289,7 @@ void SDL2ComponentNative::handleMoved (int xpos, int ypos) #if YUP_MAC auto nativeWindowPos = getNativeWindowPosition (parentWindow); #else - auto nativeWindowPos = Point (0, 0); + auto nativeWindowPos = Rectangle (0, 0, 1, 1); #endif YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after move: " << nativeWindowPos.toString()); @@ -1324,7 +1324,7 @@ void SDL2ComponentNative::handleResized (int width, int height) #if YUP_MAC auto nativeWindowPos = getNativeWindowPosition (parentWindow); #else - auto nativeWindowPos = Point (0, 0); + auto nativeWindowPos = Rectangle (0, 0, 1, 1); #endif YUP_MODULE_DBG (GUI_WINDOWING, "SDL2: parent window position sync after resize: " << nativeWindowPos.toString());