diff --git a/cmake/platforms/mac/AUInfo.plist b/cmake/platforms/mac/AUInfo.plist
new file mode 100644
index 000000000..aa796a91f
--- /dev/null
+++ b/cmake/platforms/mac/AUInfo.plist
@@ -0,0 +1,47 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ English
+ CFBundleExecutable
+ ${MACOSX_BUNDLE_EXECUTABLE_NAME}
+ CFBundleIdentifier
+ ${MACOSX_BUNDLE_GUI_IDENTIFIER}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ @PLUGIN_AU_NAME@
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ @PLUGIN_AU_VERSION@
+ CFBundleVersion
+ @PLUGIN_AU_VERSION@
+ CFBundleSignature
+ @PLUGIN_AU_MANUFACTURER@
+ NSHumanReadableCopyright
+ Copyright (c) 2026 - kunitoki@gmail.com
+ NSHighResolutionCapable
+
+ AudioComponents
+
+
+ type
+ @PLUGIN_AU_TYPE@
+ subtype
+ @PLUGIN_AU_SUBTYPE@
+ manufacturer
+ @PLUGIN_AU_MANUFACTURER@
+ name
+ @PLUGIN_AU_NAME@
+ version
+ 1
+ factoryFunction
+ AudioPluginProcessorAUFactory
+ sandboxSafe
+
+
+
+
+
diff --git a/cmake/yup_audio_plugin.cmake b/cmake/yup_audio_plugin.cmake
index d5f7ce766..6cfcc5cc9 100644
--- a/cmake/yup_audio_plugin.cmake
+++ b/cmake/yup_audio_plugin.cmake
@@ -27,7 +27,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_CLAP PLUGIN_CREATE_VST3 PLUGIN_CREATE_STANDALONE PLUGIN_CREATE_AU)
set (multi_value_args
DEFINITIONS
@@ -44,6 +44,11 @@ function (yup_audio_plugin)
set (target_app_id "${YUP_ARG_TARGET_APP_ID}")
set (target_app_namespace "${YUP_ARG_TARGET_APP_NAMESPACE}")
set (target_cxx_standard "${YUP_ARG_TARGET_CXX_STANDARD}")
+ set (target_bundle_id "${target_app_id}")
+ if (NOT target_bundle_id)
+ set (target_bundle_id "org.kunitoki.yup.${target_name}")
+ endif()
+ string (REGEX REPLACE "[^A-Za-z0-9.-]" "-" target_bundle_id "${target_bundle_id}")
set (additional_definitions "")
set (additional_options "")
set (additional_libraries "")
@@ -55,8 +60,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)
- _yup_message (FATAL_ERROR "At least one plugin type must be enabled (CLAP, VST3, 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)
+ _yup_message (FATAL_ERROR "At least one plugin type must be enabled (CLAP, VST3, AU, or Standalone).")
return()
endif()
@@ -140,11 +145,16 @@ function (yup_audio_plugin)
${YUP_ARG_MODULES})
set_target_properties (${target_name}_clap_plugin PROPERTIES
+ C_VISIBILITY_PRESET hidden
+ CXX_VISIBILITY_PRESET hidden
+ OBJC_VISIBILITY_PRESET hidden
+ OBJCXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN ON
SUFFIX ".clap"
FOLDER "${YUP_ARG_TARGET_IDE_GROUP}"
XCODE_GENERATE_SCHEME ON)
- #yup_audio_plugin_copy_bundle (${target_name} clap)
+ yup_audio_plugin_copy_bundle (${target_name} clap)
endif()
# ==== Fetch vst3 SDK and build vst3 target
@@ -152,7 +162,9 @@ function (yup_audio_plugin)
_yup_fetch_vst3sdk()
_yup_message (STATUS "Setting up VST3 plugin client")
+ get_directory_property (_yup_vst3_saved_compile_options COMPILE_OPTIONS)
smtg_enable_vst3_sdk()
+ set_directory_properties (PROPERTIES COMPILE_OPTIONS "${_yup_vst3_saved_compile_options}")
_yup_module_setup_plugin_client (
${target_name}
@@ -191,7 +203,7 @@ function (yup_audio_plugin)
if (YUP_PLATFORM_MAC)
smtg_target_set_bundle (${target_name}_vst3_plugin
- BUNDLE_IDENTIFIER org.kunitoki.yup.${target_name}
+ BUNDLE_IDENTIFIER "${target_bundle_id}"
COMPANY_NAME "kunitoki")
#smtg_target_set_debug_executable(MyPlugin
@@ -211,6 +223,11 @@ function (yup_audio_plugin)
endif()
set_target_properties (${target_name}_vst3_plugin PROPERTIES
+ C_VISIBILITY_PRESET hidden
+ CXX_VISIBILITY_PRESET hidden
+ OBJC_VISIBILITY_PRESET hidden
+ OBJCXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN ON
SUFFIX ".vst3"
FOLDER "${YUP_ARG_TARGET_IDE_GROUP}"
XCODE_GENERATE_SCHEME ON)
@@ -233,7 +250,7 @@ function (yup_audio_plugin)
TARGET_NAME ${target_name}_standalone_plugin
TARGET_VERSION ${target_version}
TARGET_IDE_GROUP ${target_ide_group}
- TARGET_APP_ID ${target_app_id}
+ TARGET_APP_ID ${target_bundle_id}
TARGET_APP_NAMESPACE ${target_app_namespace}
TARGET_CXX_STANDARD ${target_cxx_standard}
DEFINITIONS
@@ -247,6 +264,141 @@ function (yup_audio_plugin)
${YUP_ARG_MODULES})
endif()
+ # ==== Build AUv2 plugin target (macOS only)
+ if (YUP_ARG_PLUGIN_CREATE_AU)
+ if (NOT YUP_PLATFORM_MAC)
+ _yup_message (WARNING "AUv2 plugins are only supported on macOS. Skipping AU target.")
+ else()
+ _yup_fetch_apple_ausdk()
+
+ _yup_message (STATUS "Setting up AUv2 plugin client")
+ _yup_module_setup_plugin_client (
+ ${target_name}
+ yup_audio_plugin_client
+ ${YUP_ARG_TARGET_IDE_GROUP}
+ au
+ ${YUP_ARG_UNPARSED_ARGUMENTS})
+
+ # Determine AU type (aumu for instruments, aufx for effects)
+ cmake_parse_arguments (AU_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})
+ if (AU_ARGS_PLUGIN_IS_SYNTH)
+ set (au_bundle_type "aumu")
+ else()
+ set (au_bundle_type "aufx")
+ endif()
+
+ if (NOT AU_ARGS_PLUGIN_AU_SUBTYPE)
+ set (AU_ARGS_PLUGIN_AU_SUBTYPE "Dflt")
+ endif()
+ if (NOT AU_ARGS_PLUGIN_AU_MANUFACTURER)
+ set (AU_ARGS_PLUGIN_AU_MANUFACTURER "Yup!")
+ endif()
+ if (NOT AU_ARGS_PLUGIN_NAME)
+ set (AU_ARGS_PLUGIN_NAME "${target_name}")
+ endif()
+ if (NOT AU_ARGS_PLUGIN_VERSION)
+ set (AU_ARGS_PLUGIN_VERSION "1")
+ endif()
+
+ _yup_message (STATUS "Creating AUv2 plugin target")
+ add_library (${target_name}_au_plugin MODULE)
+
+ target_compile_features (${target_name}_au_plugin PRIVATE cxx_std_${target_cxx_standard})
+
+ target_compile_definitions (${target_name}_au_plugin PRIVATE
+ YUP_AUDIO_PLUGIN_ENABLE_AU=1
+ YUP_STANDALONE_APPLICATION=0)
+
+ target_link_libraries (${target_name}_au_plugin PRIVATE
+ ${target_name}_shared
+ yup_audio_plugin_client
+ base-sdk-auv2
+ ${target_name}_au
+ ${additional_libraries}
+ ${YUP_ARG_MODULES}
+ "-framework AudioUnit"
+ "-framework AudioToolbox"
+ "-framework CoreAudio"
+ "-framework CoreFoundation"
+ "-framework AppKit")
+
+ _yup_module_apply_arc_to_target_sources (${target_name}_au_plugin
+ ${target_name}_shared
+ yup_audio_plugin_client
+ base-sdk-auv2
+ ${target_name}_au
+ ${additional_libraries}
+ ${YUP_ARG_MODULES})
+
+ # Generate the AU Info.plist from our template
+ set (au_plist_template "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/platforms/mac/AUInfo.plist")
+ set (au_plist_output "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_au_plugin.plist")
+
+ set (PLUGIN_AU_TYPE "${au_bundle_type}")
+ set (PLUGIN_AU_SUBTYPE "${AU_ARGS_PLUGIN_AU_SUBTYPE}")
+ set (PLUGIN_AU_MANUFACTURER "${AU_ARGS_PLUGIN_AU_MANUFACTURER}")
+ set (PLUGIN_AU_NAME "${AU_ARGS_PLUGIN_NAME}")
+ set (PLUGIN_AU_VERSION "${AU_ARGS_PLUGIN_VERSION}")
+
+ set (au_bundle_identifier "${target_bundle_id}.au")
+ string (REGEX REPLACE "[^A-Za-z0-9.-]" "-" au_bundle_identifier "${au_bundle_identifier}")
+
+ set (au_pkginfo_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}_au_plugin.PkgInfo")
+ file (WRITE "${au_pkginfo_file}" "BNDL${AU_ARGS_PLUGIN_AU_MANUFACTURER}")
+
+ configure_file ("${au_plist_template}" "${au_plist_output}" @ONLY)
+
+ set_target_properties (${target_name}_au_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 "component"
+ MACOSX_BUNDLE TRUE
+ MACOSX_BUNDLE_INFO_PLIST "${au_plist_output}"
+ MACOSX_BUNDLE_BUNDLE_NAME "${AU_ARGS_PLUGIN_NAME}"
+ MACOSX_BUNDLE_BUNDLE_VERSION "${AU_ARGS_PLUGIN_VERSION}"
+ MACOSX_BUNDLE_SHORT_VERSION_STRING "${AU_ARGS_PLUGIN_VERSION}"
+ MACOSX_BUNDLE_GUI_IDENTIFIER "${au_bundle_identifier}"
+ FOLDER "${YUP_ARG_TARGET_IDE_GROUP}"
+ XCODE_ATTRIBUTE_GENERATE_PKGINFO_FILE YES
+ XCODE_ATTRIBUTE_PRODUCT_BUNDLE_PACKAGE_TYPE BNDL
+ XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${au_bundle_identifier}"
+ XCODE_GENERATE_SCHEME ON)
+
+ add_custom_command (TARGET ${target_name}_au_plugin POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different "${au_pkginfo_file}" "$/PkgInfo"
+ COMMENT "Generating AU PkgInfo"
+ VERBATIM)
+
+ yup_audio_plugin_copy_bundle (${target_name} au)
+ endif()
+ endif()
+
+ # ==== Create composite target for all enabled plugin formats
+ set (_all_plugin_targets "")
+ if (YUP_ARG_PLUGIN_CREATE_CLAP)
+ list (APPEND _all_plugin_targets ${target_name}_clap_plugin)
+ endif()
+ if (YUP_ARG_PLUGIN_CREATE_VST3)
+ list (APPEND _all_plugin_targets ${target_name}_vst3_plugin)
+ endif()
+ if (YUP_ARG_PLUGIN_CREATE_STANDALONE)
+ list (APPEND _all_plugin_targets ${target_name}_standalone_plugin)
+ endif()
+ if (YUP_ARG_PLUGIN_CREATE_AU AND YUP_PLATFORM_MAC)
+ list (APPEND _all_plugin_targets ${target_name}_au_plugin)
+ endif()
+
+ add_custom_target (${target_name} DEPENDS ${_all_plugin_targets})
+ set_target_properties (${target_name} PROPERTIES
+ FOLDER "${YUP_ARG_TARGET_IDE_GROUP}"
+ XCODE_GENERATE_SCHEME ON)
+
endfunction()
#==============================================================================
@@ -258,11 +410,18 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type)
string (TOUPPER "${plugin_type}" plugin_type_upper)
set (dependency_target ${target_name}_${plugin_type}_plugin)
- set (target_file_name "${target_name}_${plugin_type}_plugin.${plugin_type}")
- set (plugin_target_path "$ENV{HOME}/Library/Audio/Plug-Ins/${plugin_type_upper}")
+
+ 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")
+ 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()
+
set (plugin_path "${plugin_target_path}/${target_file_name}")
- if (NOT EXISTS ${plugin_target_path})
+ if (NOT EXISTS ${plugin_target_path} AND NOT "${plugin_type}" STREQUAL "clap")
_yup_message (STATUS "Plugin path ${plugin_target_path} does not exist, skipping copy")
return()
endif()
@@ -271,15 +430,28 @@ function (yup_audio_plugin_copy_bundle target_name plugin_type)
if ("${plugin_type}" STREQUAL "clap")
add_custom_command(TARGET ${dependency_target} POST_BUILD
- COMMAND ${CMAKE_COMMAND} -E rm -f ${plugin_path}
- COMMAND ${CMAKE_COMMAND} -E create_symlink "$" ${plugin_path}
- COMMENT "Copying ${plugin_type_upper} plugin to ${plugin_path}"
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${plugin_target_path}"
+ COMMAND ${CMAKE_COMMAND} -E rm -f "${plugin_path}"
+ COMMAND ${CMAKE_COMMAND} -E create_symlink "$" "${plugin_path}"
+ COMMENT "Symlinking CLAP plugin ${plugin_type_upper} plugin to ${plugin_path}"
VERBATIM)
elseif ("${plugin_type}" STREQUAL "vst3")
+ get_target_property (source_plugin_path ${dependency_target} SMTG_PLUGIN_PACKAGE_PATH)
+ if (NOT source_plugin_path)
+ set (source_plugin_path "$")
+ endif()
+
+ add_custom_command(TARGET ${dependency_target} POST_BUILD
+ 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 rm -f ${plugin_path}
- COMMAND ${CMAKE_COMMAND} -E create_symlink "$/../../../${target_file_name}" ${plugin_path}
- COMMENT "Copying ${plugin_type_upper} plugin to ${plugin_path}"
+ COMMAND ${CMAKE_COMMAND} -E rm -rf "${plugin_path}"
+ COMMAND ${CMAKE_COMMAND} -E copy_directory "$" "${plugin_path}"
+ COMMAND codesign --force --sign - "${plugin_path}"
+ COMMENT "Copying AU plugin ${plugin_type_upper} to ${plugin_path}"
VERBATIM)
else()
_yup_message (FATAL_ERROR "Unsupported plugin type ${plugin_type} for copying bundle")
diff --git a/cmake/yup_dependencies.cmake b/cmake/yup_dependencies.cmake
index c2e74f493..14e4a2c80 100644
--- a/cmake/yup_dependencies.cmake
+++ b/cmake/yup_dependencies.cmake
@@ -77,6 +77,45 @@ endfunction()
#==============================================================================
+function (_yup_fetch_apple_ausdk)
+ if (NOT TARGET base-sdk-auv2)
+ if (NOT AUDIOUNIT_SDK_ROOT)
+ _yup_message (STATUS "Fetching Apple AudioUnitSDK")
+ _yup_fetchcontent_declare (AudioUnitSDK
+ GIT_REPOSITORY https://github.com/apple/AudioUnitSDK.git
+ GIT_TAG AudioUnitSDK-1.1.0)
+ FetchContent_MakeAvailable (AudioUnitSDK)
+ set (AUDIOUNIT_SDK_ROOT "${audiounitsdk_SOURCE_DIR}")
+ endif()
+
+ set (AUSDK_SRC "${AUDIOUNIT_SDK_ROOT}/src/AudioUnitSDK")
+
+ add_library (base-sdk-auv2 STATIC
+ "${AUSDK_SRC}/AUBase.cpp"
+ "${AUSDK_SRC}/AUBuffer.cpp"
+ "${AUSDK_SRC}/AUBufferAllocator.cpp"
+ "${AUSDK_SRC}/AUEffectBase.cpp"
+ "${AUSDK_SRC}/AUInputElement.cpp"
+ "${AUSDK_SRC}/AUMIDIBase.cpp"
+ "${AUSDK_SRC}/AUMIDIEffectBase.cpp"
+ "${AUSDK_SRC}/AUOutputElement.cpp"
+ "${AUSDK_SRC}/AUPlugInDispatch.cpp"
+ "${AUSDK_SRC}/AUScopeElement.cpp"
+ "${AUSDK_SRC}/ComponentBase.cpp"
+ "${AUSDK_SRC}/MusicDeviceBase.cpp")
+
+ target_include_directories (base-sdk-auv2 PUBLIC "${AUDIOUNIT_SDK_ROOT}/include")
+ target_compile_features (base-sdk-auv2 PUBLIC cxx_std_17)
+ target_compile_options (base-sdk-auv2 PRIVATE -Wno-deprecated-declarations)
+
+ set_target_properties (base-sdk-auv2 PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ FOLDER "Thirdparty")
+ endif()
+endfunction()
+
+#==============================================================================
+
function (_yup_fetch_clap)
if (NOT TARGET clap)
_yup_message (STATUS "Fetching CLAP SDK")
diff --git a/cmake/yup_modules.cmake b/cmake/yup_modules.cmake
index c9be8ac3f..737d74629 100644
--- a/cmake/yup_modules.cmake
+++ b/cmake/yup_modules.cmake
@@ -509,7 +509,7 @@ function (_yup_module_setup_plugin_client target_name plugin_client_target folde
endif()
set (options "")
- set (one_value_args PLUGIN_ID PLUGIN_NAME PLUGIN_VENDOR PLUGIN_VERSION PLUGIN_DESCRIPTION PLUGIN_URL PLUGIN_EMAIL PLUGIN_IS_SYNTH PLUGIN_IS_MONO)
+ set (one_value_args PLUGIN_ID PLUGIN_NAME PLUGIN_VENDOR PLUGIN_VERSION PLUGIN_DESCRIPTION PLUGIN_URL PLUGIN_EMAIL PLUGIN_IS_SYNTH PLUGIN_IS_MONO PLUGIN_AU_SUBTYPE PLUGIN_AU_MANUFACTURER)
set (multi_value_args "")
cmake_parse_arguments (YUP_ARG "${options}" "${one_value_args}" "${multi_value_args}" ${ARGN})
@@ -523,8 +523,11 @@ function (_yup_module_setup_plugin_client target_name plugin_client_target folde
elseif (plugin_type STREQUAL "standalone")
set (custom_target_name "${target_name}_standalone")
set (plugin_define "YUP_AUDIO_PLUGIN_ENABLE_STANDALONE=1")
+ elseif (plugin_type STREQUAL "au")
+ set (custom_target_name "${target_name}_au")
+ set (plugin_define "YUP_AUDIO_PLUGIN_ENABLE_AU=1")
else()
- _yup_message (FATAL_ERROR "Invalid plugin type: ${plugin_type}. Must be either 'vst3', 'clap' or 'standalone'")
+ _yup_message (FATAL_ERROR "Invalid plugin type: ${plugin_type}. Must be either 'vst3', 'clap', 'au' or 'standalone'")
endif()
add_library (${custom_target_name} INTERFACE)
@@ -562,6 +565,14 @@ function (_yup_module_setup_plugin_client target_name plugin_client_target folde
list (APPEND module_defines YupPlugin_IsMono=0)
endif()
+ if (YUP_ARG_PLUGIN_AU_SUBTYPE)
+ list (APPEND module_defines "YupPlugin_AUSubType=\"${YUP_ARG_PLUGIN_AU_SUBTYPE}\"")
+ endif()
+
+ if (YUP_ARG_PLUGIN_AU_MANUFACTURER)
+ list (APPEND module_defines "YupPlugin_AUManufacturer=\"${YUP_ARG_PLUGIN_AU_MANUFACTURER}\"")
+ endif()
+
if (YUP_PLATFORM_APPLE)
_yup_glob_recurse ("${module_path}/${plugin_type}/*.mm" module_sources)
else()
diff --git a/cmake/yup_standalone.cmake b/cmake/yup_standalone.cmake
index 053928a7b..f0e18d278 100644
--- a/cmake/yup_standalone.cmake
+++ b/cmake/yup_standalone.cmake
@@ -128,6 +128,13 @@ function (yup_standalone_app)
add_executable (${target_name} ${executable_options})
endif()
+ set_target_properties (${target_name} PROPERTIES
+ C_VISIBILITY_PRESET hidden
+ CXX_VISIBILITY_PRESET hidden
+ OBJC_VISIBILITY_PRESET hidden
+ OBJCXX_VISIBILITY_PRESET hidden
+ VISIBILITY_INLINES_HIDDEN ON)
+
target_compile_features (${target_name} PRIVATE cxx_std_${target_cxx_standard})
target_include_directories (${target_name} PRIVATE ${module_include_dirs})
diff --git a/examples/audiograph/source/AudioGraphApp.cpp b/examples/audiograph/source/AudioGraphApp.cpp
index 14f0efdae..35f31717f 100644
--- a/examples/audiograph/source/AudioGraphApp.cpp
+++ b/examples/audiograph/source/AudioGraphApp.cpp
@@ -50,6 +50,13 @@ AudioGraphApp::AudioGraphApp()
{
deviceManager.initialiseWithDefaultDevices (2, 2);
+ if (auto defaultMidiIn = yup::MidiInput::getDefaultDevice();
+ defaultMidiIn != yup::MidiDeviceInfo())
+ {
+ deviceManager.setMidiInputDeviceEnabled (defaultMidiIn.identifier, true);
+ deviceManager.addMidiInputDeviceCallback (defaultMidiIn.identifier, &midiCollector);
+ }
+
model = std::make_shared();
graph = std::make_shared (model);
nodeRegistry.registerInternalNodes();
@@ -89,6 +96,13 @@ AudioGraphApp::~AudioGraphApp()
scanLifetime->store (false);
#endif
+ if (auto defaultMidiIn = yup::MidiInput::getDefaultDevice();
+ defaultMidiIn != yup::MidiDeviceInfo())
+ {
+ deviceManager.removeMidiInputDeviceCallback (defaultMidiIn.identifier, &midiCollector);
+ deviceManager.setMidiInputDeviceEnabled (defaultMidiIn.identifier, false);
+ }
+
closePluginEditor();
closeAllSubgraphEditors();
@@ -164,8 +178,11 @@ void AudioGraphApp::audioDeviceIOCallbackWithContext (const float* const* inputC
yup::FloatVectorOperations::copy (outputChannelData[ch], inputChannelData[ch], numSamples);
}
- yup::MidiBuffer midi;
- graph->processBlock (outputBuffer, midi);
+ midiCollector.removeNextBlockOfMessages (midiBuffer, numSamples);
+
+ yup::ParameterChangeBuffer emptyParams;
+ yup::AudioProcessContext ctx { outputBuffer, midiBuffer, emptyParams };
+ graph->processBlock (ctx);
}
void AudioGraphApp::audioDeviceAboutToStart (yup::AudioIODevice* device)
@@ -173,6 +190,11 @@ void AudioGraphApp::audioDeviceAboutToStart (yup::AudioIODevice* device)
if (graph == nullptr || device == nullptr)
return;
+ const auto sampleRate = device->getCurrentSampleRate();
+ midiCollector.reset (sampleRate);
+
+ midiBuffer.ensureSize (4096);
+
#if YUP_DESKTOP
yup::AudioPluginHostContext ctx;
ctx.sampleRate = static_cast (device->getCurrentSampleRate());
@@ -424,13 +446,13 @@ struct SubgraphEditorRecord
std::unique_ptr AudioGraphApp::createMainPanel()
{
AudioGraphEditorPanel::EndpointViews endpointViews;
- endpointViews.createInputView = []
+ endpointViews.createInputView = [this]
{
- return std::make_unique();
+ return std::make_unique (graph, "sound card");
};
- endpointViews.createOutputView = []
+ endpointViews.createOutputView = [this]
{
- return std::make_unique();
+ return std::make_unique (graph, "sound card");
};
auto panel = std::make_unique (graph, nodeRegistry, std::move (endpointViews));
diff --git a/examples/audiograph/source/AudioGraphApp.h b/examples/audiograph/source/AudioGraphApp.h
index 8878c4a60..e96f194b0 100644
--- a/examples/audiograph/source/AudioGraphApp.h
+++ b/examples/audiograph/source/AudioGraphApp.h
@@ -132,6 +132,9 @@ class AudioGraphApp final
NodeRegistry nodeRegistry;
std::unique_ptr editorPanel;
+ yup::MidiMessageCollector midiCollector;
+ yup::MidiBuffer midiBuffer;
+
yup::File currentFilePath;
yup::FileChooser::Ptr fileChooser;
diff --git a/examples/audiograph/source/nodes/GainNode.h b/examples/audiograph/source/nodes/GainNode.h
index f292456f0..e62982277 100644
--- a/examples/audiograph/source/nodes/GainNode.h
+++ b/examples/audiograph/source/nodes/GainNode.h
@@ -40,9 +40,9 @@ class GainProcessor final : public yup::AudioProcessor
void releaseResources() override {}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer&) override
+ void processBlock (yup::AudioProcessContext& context) override
{
- audioBuffer.applyGain (gain.load (std::memory_order_relaxed));
+ context.audio.applyGain (gain.load (std::memory_order_relaxed));
}
int getCurrentPreset() const noexcept override { return 0; }
diff --git a/examples/audiograph/source/nodes/LatencyNode.h b/examples/audiograph/source/nodes/LatencyNode.h
index 1b25d20b8..4b9b58b86 100644
--- a/examples/audiograph/source/nodes/LatencyNode.h
+++ b/examples/audiograph/source/nodes/LatencyNode.h
@@ -57,8 +57,9 @@ class LatencyProcessor final : public yup::AudioProcessor
writePosition = 0;
}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer&) override
+ void processBlock (yup::AudioProcessContext& context) override
{
+ auto& audioBuffer = context.audio;
const int currentDelaySamples = getLatencySamples();
if (currentDelaySamples <= 0)
return;
diff --git a/examples/audiograph/source/nodes/LowPassFilterNode.h b/examples/audiograph/source/nodes/LowPassFilterNode.h
index 6018acbed..14e0d9331 100644
--- a/examples/audiograph/source/nodes/LowPassFilterNode.h
+++ b/examples/audiograph/source/nodes/LowPassFilterNode.h
@@ -46,8 +46,9 @@ class LowPassFilterProcessor final : public yup::AudioProcessor
void releaseResources() override {}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer&) override
+ void processBlock (yup::AudioProcessContext& context) override
{
+ auto& audioBuffer = context.audio;
const auto currentCutoff = static_cast (cutoff.load (std::memory_order_relaxed));
const auto alpha = static_cast (1.0 - std::exp (-yup::MathConstants::twoPi * currentCutoff / static_cast (sampleRate)));
diff --git a/examples/audiograph/source/nodes/OscillatorNode.h b/examples/audiograph/source/nodes/OscillatorNode.h
index e494c34ad..d48c2b2fe 100644
--- a/examples/audiograph/source/nodes/OscillatorNode.h
+++ b/examples/audiograph/source/nodes/OscillatorNode.h
@@ -45,8 +45,9 @@ class OscillatorProcessor final : public yup::AudioProcessor
void releaseResources() override {}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer&) override
+ void processBlock (yup::AudioProcessContext& context) override
{
+ auto& audioBuffer = context.audio;
const auto currentFrequency = static_cast (frequency.load (std::memory_order_relaxed));
const auto increment = yup::MathConstants::twoPi * currentFrequency / static_cast (sampleRate);
diff --git a/examples/audiograph/source/nodes/PluginNodeView.h b/examples/audiograph/source/nodes/PluginNodeView.h
index 3ce9ea68d..2250d7f11 100644
--- a/examples/audiograph/source/nodes/PluginNodeView.h
+++ b/examples/audiograph/source/nodes/PluginNodeView.h
@@ -43,12 +43,22 @@ class PluginNodeView final : public yup::AudioGraphNodeView
int getNumInputPorts() const override
{
- return desc.numInputChannels > 0 ? 1 : 0;
+ int count = 0;
+ if (desc.numInputChannels > 0)
+ ++count;
+ if (desc.numMidiInputPorts > 0)
+ ++count;
+ return count;
}
int getNumOutputPorts() const override
{
- return desc.numOutputChannels > 0 ? 1 : 0;
+ int count = 0;
+ if (desc.numOutputChannels > 0)
+ ++count;
+ if (desc.numMidiOutputPorts > 0)
+ ++count;
+ return count;
}
int getPreferredWidth() const override
@@ -61,14 +71,20 @@ class PluginNodeView final : public yup::AudioGraphNodeView
return desc.isInstrument ? yup::Color (0xffe11d48) : yup::Color (0xff0891b2);
}
- PortInfo getInputPortInfo (int) const override
+ PortInfo getInputPortInfo (int portIndex) const override
{
- return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+ if (desc.numInputChannels > 0 && portIndex == 0)
+ return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ return { "MIDI", getPortKindColor (PortKind::midi), PortKind::midi };
}
- PortInfo getOutputPortInfo (int) const override
+ PortInfo getOutputPortInfo (int portIndex) const override
{
- return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+ if (desc.numOutputChannels > 0 && portIndex == 0)
+ return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ return { "MIDI", getPortKindColor (PortKind::midi), PortKind::midi };
}
int getNumParameterRows() const override { return 0; }
diff --git a/examples/audiograph/source/nodes/SamplePlayerNode.h b/examples/audiograph/source/nodes/SamplePlayerNode.h
index aa104993f..0ee28fa44 100644
--- a/examples/audiograph/source/nodes/SamplePlayerNode.h
+++ b/examples/audiograph/source/nodes/SamplePlayerNode.h
@@ -53,8 +53,9 @@ class SamplePlayerProcessor final : public yup::AudioProcessor
void releaseResources() override {}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer&) override
+ void processBlock (yup::AudioProcessContext& context) override
{
+ auto& audioBuffer = context.audio;
audioBuffer.clear();
const auto* sample = currentSample.load (std::memory_order_acquire);
diff --git a/examples/audiograph/source/nodes/SoundCardInputNodeView.h b/examples/audiograph/source/nodes/SoundCardInputNodeView.h
index 28780af97..959a6a97b 100644
--- a/examples/audiograph/source/nodes/SoundCardInputNodeView.h
+++ b/examples/audiograph/source/nodes/SoundCardInputNodeView.h
@@ -25,8 +25,10 @@
class SoundCardInputNodeView final : public yup::AudioGraphNodeView
{
public:
- explicit SoundCardInputNodeView (yup::StringRef subtitleIn = "sound card")
+ explicit SoundCardInputNodeView (std::shared_ptr graphIn,
+ yup::StringRef subtitleIn = "sound card")
: AudioGraphNodeView (yup::AudioGraphModel::getGraphInputNodeID())
+ , graph (std::move (graphIn))
, subtitle (subtitleIn)
{
}
@@ -37,17 +39,34 @@ class SoundCardInputNodeView final : public yup::AudioGraphNodeView
int getNumInputPorts() const override { return 0; }
- int getNumOutputPorts() const override { return 1; }
+ int getNumOutputPorts() const override
+ {
+ return graph != nullptr ? static_cast (graph->getBusLayout().getInputBuses().size()) : 1;
+ }
int getPreferredWidth() const override { return 150; }
yup::Color getNodeColor() const override { return yup::Color (0xfff97316); }
- PortInfo getOutputPortInfo (int) const override
+ PortInfo getOutputPortInfo (int busIndex) const override
{
- return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+ if (graph == nullptr)
+ return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ return getPortInfo (graph->getBusLayout().getInputBuses(), busIndex);
}
private:
+ static PortInfo getPortInfo (yup::Span buses, int busIndex)
+ {
+ if (busIndex < 0 || busIndex >= static_cast (buses.size()))
+ return { "?", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ const auto& bus = buses[static_cast (busIndex)];
+ const auto kind = bus.getType() == yup::AudioBus::Type::Audio ? PortKind::audio : PortKind::midi;
+ return { bus.getName(), getPortKindColor (kind), kind };
+ }
+
+ std::shared_ptr graph;
yup::String subtitle;
};
diff --git a/examples/audiograph/source/nodes/SoundCardOutputNodeView.h b/examples/audiograph/source/nodes/SoundCardOutputNodeView.h
index 48ec7c153..b288d3bf7 100644
--- a/examples/audiograph/source/nodes/SoundCardOutputNodeView.h
+++ b/examples/audiograph/source/nodes/SoundCardOutputNodeView.h
@@ -25,8 +25,10 @@
class SoundCardOutputNodeView final : public yup::AudioGraphNodeView
{
public:
- explicit SoundCardOutputNodeView (yup::StringRef subtitleIn = "sound card")
+ explicit SoundCardOutputNodeView (std::shared_ptr graphIn,
+ yup::StringRef subtitleIn = "sound card")
: AudioGraphNodeView (yup::AudioGraphModel::getGraphOutputNodeID())
+ , graph (std::move (graphIn))
, subtitle (subtitleIn)
{
}
@@ -35,7 +37,10 @@ class SoundCardOutputNodeView final : public yup::AudioGraphNodeView
yup::String getNodeSubtitle() const override { return subtitle; }
- int getNumInputPorts() const override { return 1; }
+ int getNumInputPorts() const override
+ {
+ return graph != nullptr ? static_cast (graph->getBusLayout().getOutputBuses().size()) : 1;
+ }
int getNumOutputPorts() const override { return 0; }
@@ -43,11 +48,25 @@ class SoundCardOutputNodeView final : public yup::AudioGraphNodeView
yup::Color getNodeColor() const override { return yup::Color (0xff06b6d4); }
- PortInfo getInputPortInfo (int) const override
+ PortInfo getInputPortInfo (int busIndex) const override
{
- return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+ if (graph == nullptr)
+ return { "audio", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ return getPortInfo (graph->getBusLayout().getOutputBuses(), busIndex);
}
private:
+ static PortInfo getPortInfo (yup::Span buses, int busIndex)
+ {
+ if (busIndex < 0 || busIndex >= static_cast (buses.size()))
+ return { "?", getPortKindColor (PortKind::audio), PortKind::audio };
+
+ const auto& bus = buses[static_cast (busIndex)];
+ const auto kind = bus.getType() == yup::AudioBus::Type::Audio ? PortKind::audio : PortKind::midi;
+ return { bus.getName(), getPortKindColor (kind), kind };
+ }
+
+ std::shared_ptr graph;
yup::String subtitle;
};
diff --git a/examples/audiograph/source/nodes/SubgraphNode.h b/examples/audiograph/source/nodes/SubgraphNode.h
index 1b60a7c0b..43976d539 100644
--- a/examples/audiograph/source/nodes/SubgraphNode.h
+++ b/examples/audiograph/source/nodes/SubgraphNode.h
@@ -145,9 +145,9 @@ class SubgraphProcessor final : public yup::AudioProcessor
graph->releaseResources();
}
- void processBlock (yup::AudioBuffer& audioBuffer, yup::MidiBuffer& midiBuffer) override
+ void processBlock (yup::AudioProcessContext& context) override
{
- graph->processBlock (audioBuffer, midiBuffer);
+ graph->processBlock (context);
}
void flush() override
diff --git a/examples/plugin/CMakeLists.txt b/examples/plugin/CMakeLists.txt
index ba1eee55c..aafba5afd 100644
--- a/examples/plugin/CMakeLists.txt
+++ b/examples/plugin/CMakeLists.txt
@@ -32,8 +32,8 @@ yup_audio_plugin (
TARGET_APP_ID "org.yup.${target_name}"
TARGET_APP_NAMESPACE "org.yup"
TARGET_CXX_STANDARD 20
- PLUGIN_ID "org.yup.YupCLAP"
- PLUGIN_NAME "YupCLAPPZ"
+ PLUGIN_ID "org.yup.YupSynth"
+ PLUGIN_NAME "YupSynth"
PLUGIN_VENDOR "org.yup"
PLUGIN_EMAIL "kunitoki@gmail.com"
PLUGIN_VERSION "${target_version}"
@@ -43,6 +43,7 @@ yup_audio_plugin (
PLUGIN_IS_MONO OFF
PLUGIN_CREATE_CLAP ON
PLUGIN_CREATE_VST3 ON
+ PLUGIN_CREATE_AU ON
PLUGIN_CREATE_STANDALONE ON
MODULES
yup::yup_gui
diff --git a/examples/plugin/source/ExamplePlugin.cpp b/examples/plugin/source/ExamplePlugin.cpp
index 41b06aa25..acb8f44e2 100644
--- a/examples/plugin/source/ExamplePlugin.cpp
+++ b/examples/plugin/source/ExamplePlugin.cpp
@@ -22,12 +22,72 @@
#include "ExamplePlugin.h"
#include "ExampleEditor.h"
+#include
+
+//==============================================================================
+
+namespace
+{
+
+const char* getPluginFormatName()
+{
+#if YUP_AUDIO_PLUGIN_ENABLE_AU
+ return "au";
+#elif YUP_AUDIO_PLUGIN_ENABLE_CLAP
+ return "clap";
+#elif YUP_AUDIO_PLUGIN_ENABLE_VST3
+ return "vst3";
+#elif YUP_AUDIO_PLUGIN_ENABLE_STANDALONE
+ return "standalone";
+#else
+ return "unknown";
+#endif
+}
+
+class ExamplePluginLogger final
+{
+public:
+ ExamplePluginLogger()
+ {
+ const auto logFileName = yup::String (YupPlugin_Name) + "_" + getPluginFormatName() + ".log";
+ logger.reset (new yup::FileLogger (yup::FileLogger::getSystemLogFileFolder().getChildFile (logFileName),
+ yup::String (YupPlugin_Name) + " " + getPluginFormatName() + " log"));
+
+ yup::Logger::setCurrentLogger (logger.get());
+ yup::Logger::writeToLog ("Logger initialised: " + logger->getLogFile().getFullPathName());
+ }
+
+ void setAsCurrentLogger()
+ {
+ yup::Logger::setCurrentLogger (logger.get());
+ }
+
+ ~ExamplePluginLogger()
+ {
+ if (yup::Logger::getCurrentLogger() == logger.get())
+ yup::Logger::setCurrentLogger (nullptr);
+ }
+
+private:
+ std::unique_ptr logger;
+};
+
+void initialiseExamplePluginLogger()
+{
+ static ExamplePluginLogger logger;
+ logger.setAsCurrentLogger();
+}
+
+} // namespace
+
//==============================================================================
ExamplePlugin::ExamplePlugin()
: yup::AudioProcessor ("MyPlugin",
yup::AudioBusLayout ({}, { yup::AudioBus ("main", yup::AudioBus::Audio, yup::AudioBus::Output, 2) }))
{
+ initialiseExamplePluginLogger();
+
addParameter (gainParameter = yup::AudioParameterBuilder()
.withID ("volume")
.withName ("Volume")
@@ -56,8 +116,11 @@ void ExamplePlugin::releaseResources()
voices.free();
}
-void ExamplePlugin::processBlock (yup::AudioSampleBuffer& audioBuffer, yup::MidiBuffer& midiBuffer)
+void ExamplePlugin::processBlock (yup::AudioProcessContext& context)
{
+ auto& audioBuffer = context.audio;
+ auto& midiBuffer = context.midi;
+
int numSamples = audioBuffer.getNumSamples();
float* outputL = audioBuffer.getWritePointer (0);
float* outputR = audioBuffer.getWritePointer (1);
@@ -65,10 +128,12 @@ void ExamplePlugin::processBlock (yup::AudioSampleBuffer& audioBuffer, yup::Midi
int nextEventSample = midiBuffer.getNumEvents() ? 0 : numSamples;
auto midiIterator = midiBuffer.begin();
- gainHandle.updateNextAudioBlock();
+ gainHandle.prepareBlock (context.params, gainParameter->getIndexInContainer());
for (int currentSample = 0; currentSample < numSamples;)
{
+ gainHandle.advanceToSample (currentSample);
+
while (midiIterator != midiBuffer.end() && nextEventSample == currentSample)
{
const auto& event = *midiIterator;
diff --git a/examples/plugin/source/ExamplePlugin.h b/examples/plugin/source/ExamplePlugin.h
index 5463edd03..4c1887bcf 100644
--- a/examples/plugin/source/ExamplePlugin.h
+++ b/examples/plugin/source/ExamplePlugin.h
@@ -114,7 +114,7 @@ class ExamplePlugin : public yup::AudioProcessor
void prepareToPlay (float sampleRate, int maxBlockSize) override;
void releaseResources() override;
- void processBlock (yup::AudioSampleBuffer& audioBuffer, yup::MidiBuffer& midiBuffer) override;
+ void processBlock (yup::AudioProcessContext& context) override;
void flush() override;
int getCurrentPreset() const noexcept override;
diff --git a/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.cpp b/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.cpp
index 9b79604aa..664c52b6a 100644
--- a/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.cpp
+++ b/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.cpp
@@ -410,11 +410,19 @@ class AudioGraphProcessor::Pimpl final : private AudioProcessor::Listener
if (details.latencyChanged)
{
latencyChangeCounter.fetch_add (1);
+
+ if (! commitInProgress.load())
+ {
+ ignoreUnused (commitChanges());
+ }
}
}
- void processBlock (AudioBuffer& audioBuffer, MidiBuffer& midiBuffer)
+ void processBlock (AudioProcessContext& context)
{
+ auto& audioBuffer = context.audio;
+ auto& midiBuffer = context.midi;
+
ScopedNoDenormals noDenormals;
const ScopedProcessBlock scopedProcessBlock (activeProcessBlocks);
swapPendingPlan();
@@ -886,7 +894,9 @@ class AudioGraphProcessor::Pimpl final : private AudioProcessor::Listener
for (const auto connectionIndex : node.incomingConnections)
routeConnection (graph, graph.connections[static_cast (connectionIndex)], node.audioBuffer, node.midiBuffer, numSamples);
- node.processor->processBlock (node.audioBuffer, node.midiBuffer);
+ ParameterChangeBuffer emptyParams;
+ AudioProcessContext nodeCtx { node.audioBuffer, node.midiBuffer, emptyParams };
+ node.processor->processBlock (nodeCtx);
}
void processLevels (CompiledGraph& graph, int numSamples)
@@ -1267,8 +1277,10 @@ void AudioGraphProcessor::Pimpl::WorkerThread::run()
//==============================================================================
AudioBusLayout AudioGraphProcessor::createDefaultBusLayout()
{
- return AudioBusLayout ({ AudioBus ("Input", AudioBus::Type::Audio, AudioBus::Direction::Input, 2) },
- { AudioBus ("Output", AudioBus::Type::Audio, AudioBus::Direction::Output, 2) });
+ return AudioBusLayout ({ AudioBus ("Input", AudioBus::Type::Audio, AudioBus::Direction::Input, 2),
+ AudioBus ("MIDI Input", AudioBus::Type::MIDI, AudioBus::Direction::Input, 1) },
+ { AudioBus ("Output", AudioBus::Type::Audio, AudioBus::Direction::Output, 2),
+ AudioBus ("MIDI Output", AudioBus::Type::MIDI, AudioBus::Direction::Output, 1) });
}
AudioGraphProcessor::AudioGraphProcessor (std::shared_ptr model,
@@ -1346,9 +1358,9 @@ void AudioGraphProcessor::releaseResources()
pimpl->releaseResources();
}
-void AudioGraphProcessor::processBlock (AudioBuffer& audioBuffer, MidiBuffer& midiBuffer)
+void AudioGraphProcessor::processBlock (AudioProcessContext& context)
{
- pimpl->processBlock (audioBuffer, midiBuffer);
+ pimpl->processBlock (context);
}
void AudioGraphProcessor::flush()
diff --git a/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.h b/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.h
index 5417b8ef1..ec812865d 100644
--- a/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.h
+++ b/modules/yup_audio_graph/graph/yup_AudioGraphProcessor.h
@@ -29,11 +29,11 @@ namespace yup
Topology edits are made to a control-thread graph model. commitChanges()
validates the model, prepares newly compiled nodes for the current playback
configuration, and publishes an immutable processing plan. Child processor
- latency notifications mark the graph dirty; call commitChanges() from the
- control thread to rebuild delay compensation. Metadata edits such as node
- positions and properties are saved by the model without invalidating the
- compiled plan. processBlock() only swaps pending plans at block boundaries
- and keeps retired plans alive until a later control-thread commit or destruction.
+ latency notifications are handled by the graph as host notifications and
+ rebuild delay compensation. Metadata edits such as node positions and
+ properties are saved by the model without invalidating the compiled plan.
+ processBlock() only swaps pending plans at block boundaries and keeps retired
+ plans alive until a later control-thread commit or destruction.
*/
class YUP_API AudioGraphProcessor final : public AudioProcessor
{
@@ -90,7 +90,7 @@ class YUP_API AudioGraphProcessor final : public AudioProcessor
// AudioProcessor
void prepareToPlay (float sampleRate, int maxBlockSize) override;
void releaseResources() override;
- void processBlock (AudioBuffer& audioBuffer, MidiBuffer& midiBuffer) override;
+ void processBlock (AudioProcessContext& context) override;
void flush() override;
int getLatencySamples() override;
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
new file mode 100644
index 000000000..848980814
--- /dev/null
+++ b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.cpp
@@ -0,0 +1,1100 @@
+/*
+ ==============================================================================
+
+ 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();
+
+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
+{
+
+//==============================================================================
+
+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
+{
+public:
+ //==============================================================================
+
+ 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");
+
+ 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()));
+
+ 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();
+
+ 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_IsWritable | kAudioUnitParameterFlag_HasCFNameString;
+
+ 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]->isPerformingChangeGesture())
+ return noErr;
+
+ parameters[parameterIndex]->setValue (static_cast (inValue));
+ 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;
+ }
+
+ //==============================================================================
+
+#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();
+
+ std::vector channels;
+ for (UInt32 ch = 0; ch < outBufList.mNumberBuffers; ++ch)
+ channels.push_back (static_cast (outBufList.mBuffers[ch].mData));
+
+ AudioSampleBuffer audioBuffer (channels.data(),
+ static_cast (channels.size()),
+ 0,
+ static_cast (inNumberFrames));
+
+ {
+ std::lock_guard lock (midiMutex);
+ AudioProcessContext context { audioBuffer, midiBuffer, emptyParamChanges };
+ processor->processBlock (context);
+ midiBuffer.clear();
+ }
+
+ return noErr;
+ }
+
+ //==============================================================================
+
+ OSStatus HandleMIDIEvent (UInt8 status, UInt8 channel, UInt8 data1, UInt8 data2, UInt32 offsetSampleFrame) override
+ {
+ std::lock_guard lock (midiMutex);
+
+ 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::lock_guard lock (midiMutex);
+
+ 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);
+
+ std::vector channels;
+ 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));
+
+ channels.push_back (out);
+ }
+
+ AudioSampleBuffer audioBuffer (channels.data(),
+ static_cast (channels.size()),
+ 0,
+ static_cast (inFramesToProcess));
+
+ AudioProcessContext context { audioBuffer, midiBuffer, emptyParamChanges };
+ processor->processBlock (context);
+ midiBuffer.clear();
+
+ return noErr;
+ }
+#endif
+
+ //==============================================================================
+
+ OSStatus SaveState (CFPropertyListRef* outData) override
+ {
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState requested");
+
+ if (processor == nullptr || outData == nullptr)
+ {
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: processor=" << describePointer (processor.get()) << ", outData=" << describePointer (outData));
+ return kAudioUnitErr_InvalidPropertyValue;
+ }
+
+ MemoryBlock data;
+ const auto result = processor->saveStateIntoMemory (data);
+ if (result.failed())
+ {
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState failed: " << result.getErrorMessage());
+ return kAudioUnitErr_InvalidPropertyValue;
+ }
+
+ NSData* nsData = [NSData dataWithBytes:data.getData()
+ length:data.getSize()];
+ *outData = (__bridge_retained CFPropertyListRef) nsData;
+
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "SaveState completed: bytes=" << String (static_cast (data.getSize())));
+
+ return noErr;
+ }
+
+ OSStatus RestoreState (CFPropertyListRef inData) override
+ {
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState requested");
+
+ if (processor == nullptr || inData == nullptr)
+ {
+ YUP_MODULE_DBG (PLUGIN_CLIENT_AU, "RestoreState failed: processor=" << describePointer (processor.get()) << ", inData=" << describePointer (inData));
+ return kAudioUnitErr_InvalidPropertyValue;
+ }
+
+ NSData* nsData = (__bridge NSData*) inData;
+
+ MemoryBlock data ([nsData bytes], [nsData length]);
+
+ 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_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;
+ }
+
+ 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(); }
+
+ 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())));
+ }
+
+ Float64 getCurrentSampleRate()
+ {
+ return Output (0).GetStreamFormat().mSampleRate;
+ }
+
+ AUScopedYupInitialiser scopeInitialiser;
+ std::unique_ptr processor;
+
+ MidiBuffer midiBuffer;
+ ParameterChangeBuffer emptyParamChanges; // AU delivers param changes via SetParameter, not in the audio stream
+ std::mutex midiMutex;
+ std::vector channelInfoCache;
+ AudioUnit componentInstance = nullptr;
+ bool renderingOffline = false;
+};
+
+} // namespace yup
+
+//==============================================================================
+// Objective-C editor view
+
+@interface AudioPluginEditorViewAU : NSView
+{
+ yup::AUScopedYupWindowingInitialiser _scopeInitialiser;
+ yup::AudioProcessor* _processor;
+ std::unique_ptr _processorEditor;
+}
+- (instancetype)initWithProcessor:(yup::AudioProcessor*)processor
+ preferredSize:(NSSize)size;
+- (void)attachEditorIfNeeded;
+- (void)detachEditorIfNeeded;
+- (void)resizeEditorToBounds;
+@end
+
+@implementation AudioPluginEditorViewAU
+
+- (instancetype)initWithProcessor:(yup::AudioProcessor*)processor
+ 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)) << ", processor=" << yup::describePointer (processor) << ", 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));
+ _processor = processor;
+
+ 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()));
+
+ [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)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()));
+
+ _processorEditor->setBounds ({ 0.0f,
+ 0.0f,
+ yup::jmax (1.0f, static_cast (NSWidth (bounds))),
+ yup::jmax (1.0f, static_cast (NSHeight (bounds))) });
+}
+
+- (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 detachEditorIfNeeded];
+
+ yup::endActiveParameterGestures (_processor);
+
+ _processorEditor.reset();
+ _processor = nullptr;
+}
+
+@end
+
+//==============================================================================
+// 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] initWithProcessor:proc->getProcessor()
+ 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_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/au/yup_audio_plugin_client_AU.mm b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm
new file mode 100644
index 000000000..e9bc75ea8
--- /dev/null
+++ b/modules/yup_audio_plugin_client/au/yup_audio_plugin_client_AU.mm
@@ -0,0 +1,22 @@
+/*
+ ==============================================================================
+
+ 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_AU.cpp"
diff --git a/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp b/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp
index beb01079d..292d91f6f 100644
--- a/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp
+++ b/modules/yup_audio_plugin_client/clap/yup_audio_plugin_client_CLAP.cpp
@@ -1,1144 +1,1337 @@
-/*
- ==============================================================================
-
- This file is part of the YUP library.
- Copyright (c) 2024 - 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"
-
-#if ! defined(YUP_AUDIO_PLUGIN_ENABLE_CLAP)
-#error "YUP_AUDIO_PLUGIN_ENABLE_CLAP must be defined"
-#endif
-
-#include
-#include
-
-#include
-
-extern "C" yup::AudioProcessor* createPluginProcessor();
-
-namespace yup
-{
-
-//==============================================================================
-
-std::optional clapEventToMidiNoteMessage (const clap_event_header_t* event)
-{
- switch (event->type)
- {
- case CLAP_EVENT_NOTE_ON:
- {
- const clap_event_note_t* noteEvent = reinterpret_cast (event);
- const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
-
- return MidiMessage::noteOn (channel, noteEvent->key, static_cast (noteEvent->velocity * 127.0f));
- }
-
- case CLAP_EVENT_NOTE_OFF:
- {
- const clap_event_note_t* noteEvent = reinterpret_cast (event);
- const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
-
- return MidiMessage::noteOff (channel, noteEvent->key, static_cast (noteEvent->velocity));
- }
-
- case CLAP_EVENT_NOTE_CHOKE:
- {
- const clap_event_note_t* noteEvent = reinterpret_cast (event);
- const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
-
- return MidiMessage::noteOff (channel, noteEvent->key);
- }
-
- case CLAP_EVENT_NOTE_END:
- case CLAP_EVENT_NOTE_EXPRESSION:
- case CLAP_EVENT_PARAM_VALUE:
- case CLAP_EVENT_PARAM_MOD:
- case CLAP_EVENT_MIDI:
- case CLAP_EVENT_MIDI_SYSEX:
- default:
- break;
- }
-
- return std::nullopt;
-}
-
-//==============================================================================
-
-void clapEventToParameterChange (const clap_event_header_t* event, AudioProcessor& audioProcessor)
-{
- if (event->type != CLAP_EVENT_PARAM_VALUE)
- return;
-
- const clap_event_param_value_t* paramEvent = reinterpret_cast (event);
-
- auto parameters = audioProcessor.getParameters();
-
- auto parameterIndex = static_cast (paramEvent->param_id);
- if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size())))
- return;
-
- parameters[parameterIndex]->setValue (static_cast (paramEvent->value));
-}
-
-//==============================================================================
-
-/*
-void pluginSyncMainToAudio (AudioProcessor& audioProcessor, const clap_output_events_t* out)
-{
- auto sl = CriticalSection::ScopedLockType (plugin->syncParameters);
-
- for (uint32_t i = 0; i < P_COUNT; i++)
- {
- if (plugin->mainChanged[i])
- {
- plugin->parameters[i] = plugin->mainParameters[i];
- plugin->mainChanged[i] = false;
-
- clap_event_param_value_t event = {};
- event.header.size = sizeof(event);
- event.header.time = 0;
- event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
- event.header.type = CLAP_EVENT_PARAM_VALUE;
- event.header.flags = 0;
- event.param_id = i;
- event.cookie = NULL;
- event.note_id = -1;
- event.port_index = -1;
- event.channel = -1;
- event.key = -1;
- event.value = plugin->parameters[i];
- out->try_push(out, &event.header);
- }
- }
-}
-
-bool pluginSyncAudioToMain (AudioProcessor& audioProcessor)
-{
- bool anyChanged = false;
- auto sl = CriticalSection::ScopedLockType (plugin->syncParameters);
-
- for (uint32_t i = 0; i < P_COUNT; i++)
- {
- if (plugin->changed[i])
- {
- plugin->mainParameters[i] = plugin->parameters[i];
- plugin->changed[i] = false;
- anyChanged = true;
- }
- }
-
- return anyChanged;
-
- return false;
-}
-*/
-
-//==============================================================================
-
-static const char* pluginFeatures[] = {
-#if YupPlugin_IsSynth
- CLAP_PLUGIN_FEATURE_INSTRUMENT,
- CLAP_PLUGIN_FEATURE_SYNTHESIZER,
-#else
- CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
-#endif
-
-#if YupPlugin_IsMono
- CLAP_PLUGIN_FEATURE_MONO,
-#else
- CLAP_PLUGIN_FEATURE_STEREO,
-#endif
-
- nullptr
-};
-
-static const clap_plugin_descriptor_t pluginDescriptor = {
- .clap_version = CLAP_VERSION_INIT,
- .id = YupPlugin_Id,
- .name = YupPlugin_Name,
- .vendor = YupPlugin_Vendor,
- .url = YupPlugin_URL,
- .manual_url = YupPlugin_URL,
- .support_url = YupPlugin_URL,
- .version = YupPlugin_Version,
- .description = YupPlugin_Description,
- .features = pluginFeatures,
-};
-
-#if YUP_MAC
-static const char* const preferredApi = CLAP_WINDOW_API_COCOA;
-#elif YUP_WINDOWS
-static const char* const preferredApi = CLAP_WINDOW_API_WIN32;
-#elif YUP_LINUX
-static const char* const preferredApi = CLAP_WINDOW_API_X11;
-#endif
-
-//==============================================================================
-
-class AudioPluginProcessorCLAP;
-
-//==============================================================================
-
-class AudioPluginPlayHeadCLAP final : public AudioPlayHead
-{
-public:
- explicit AudioPluginPlayHeadCLAP (float sampleRate, const clap_process_t* process)
- : process (*process)
- , sampleRate (sampleRate)
- {
- }
-
- bool canControlTransport() override
- {
- return false;
- }
-
- void transportPlay (bool shouldSartPlaying) override
- {
- if (! canControlTransport())
- return;
- }
-
- void transportRecord (bool shouldStartRecording) override
- {
- if (! canControlTransport())
- return;
- }
-
- void transportRewind() override
- {
- if (! canControlTransport())
- return;
- }
-
- std::optional getPosition() const override
- {
- if (process.transport == nullptr)
- return {};
-
- PositionInfo result;
-
- result.setTimeInSeconds (process.transport->song_pos_seconds / (double) CLAP_SECTIME_FACTOR);
- result.setTimeInSamples ((int64) (sampleRate * (process.transport->song_pos_seconds / (double) CLAP_SECTIME_FACTOR)));
- result.setTimeSignature (TimeSignature { process.transport->tsig_num, process.transport->tsig_denom });
- result.setBpm (process.transport->tempo);
- result.setBarCount (process.transport->bar_number);
- result.setPpqPositionOfLastBarStart (process.transport->bar_start / (double) CLAP_BEATTIME_FACTOR);
- result.setIsPlaying (process.transport->flags & CLAP_TRANSPORT_IS_PLAYING);
- result.setIsRecording (process.transport->flags & CLAP_TRANSPORT_IS_RECORDING);
- result.setIsLooping (process.transport->flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE);
- result.setLoopPoints (LoopPoints {
- process.transport->loop_start_beats / (double) CLAP_BEATTIME_FACTOR,
- process.transport->loop_end_beats / (double) CLAP_BEATTIME_FACTOR });
- result.setFrameRate (AudioPlayHead::fpsUnknown);
-
- return result;
- }
-
-private:
- clap_process_t process;
- float sampleRate = 44100.0f;
-};
-
-//==============================================================================
-
-class AudioPluginEditorCLAP final : public Component
-{
-public:
- AudioPluginEditorCLAP (AudioPluginProcessorCLAP* wrapper, AudioProcessorEditor* editor)
- : wrapper (wrapper)
- , processorEditor (editor)
- {
- addAndMakeVisible (*processorEditor);
- }
-
- AudioProcessorEditor* getAudioProcessorEditor() { return processorEditor.get(); }
-
- void resized() override;
-
-private:
- AudioPluginProcessorCLAP* wrapper = nullptr;
- std::unique_ptr processorEditor;
-};
-
-//==============================================================================
-
-class AudioPluginProcessorCLAP final
-{
-public:
- AudioPluginProcessorCLAP (const clap_host_t* host);
- ~AudioPluginProcessorCLAP();
-
- bool initialise();
- void destroy();
-
- bool activate (float sampleRate, int samplesPerBlock);
- void deactivate();
-
- bool startProcessing();
- void stopProcessing();
-
- void reset();
-
- void registerTimer (uint32_t periodMs, clap_id* timerId);
- void unregisterTimer (clap_id timerId);
-
- const void* getExtension (std::string_view id);
- const clap_plugin_t* getPlugin() const;
-
- void editorResized();
- ScopedValueSetter scopedHostEditorResizing();
-
-private:
- std::unique_ptr audioProcessor;
- std::unique_ptr audioPluginEditor;
-
- const clap_host_t* host = nullptr;
-
- clap_plugin_t plugin;
-
- clap_plugin_note_ports_t extensionNotePorts;
- clap_plugin_audio_ports_t extensionAudioPorts;
- clap_plugin_params_t extensionParams;
- clap_plugin_state_t extensionState;
- clap_plugin_tail_t extensionTail;
- clap_plugin_latency_t extensionLatency;
- clap_plugin_timer_support_t extensionTimerSupport;
- clap_plugin_gui_t extensionGUI;
-
- const clap_host_params_t* hostParams = nullptr;
- const clap_host_state_t* hostState = nullptr;
- const clap_host_tail_t* hostTail = nullptr;
- const clap_host_latency_t* hostLatency = nullptr;
- const clap_host_timer_support_t* hostTimerSupport = nullptr;
- const clap_host_gui_t* hostGUI = nullptr;
-
- clap_id guiTimerId;
- bool hostTriggeredResizing = false;
-
- MidiBuffer midiEvents;
-
- static std::atomic_int instancesCount;
-};
-
-//==============================================================================
-
-std::atomic_int AudioPluginProcessorCLAP::instancesCount = 0;
-
-//==============================================================================
-
-AudioPluginProcessorCLAP* getWrapper (const clap_plugin_t* plugin)
-{
- return reinterpret_cast (plugin->plugin_data);
-}
-
-//==============================================================================
-
-AudioPluginProcessorCLAP::AudioPluginProcessorCLAP (const clap_host_t* host)
- : host (host)
-{
- jassert (host != nullptr);
-
- plugin.desc = &pluginDescriptor;
- plugin.plugin_data = this;
-
- plugin.init = [] (const clap_plugin* plugin) -> bool
- {
- return getWrapper (plugin)->initialise();
- };
-
- plugin.destroy = [] (const clap_plugin* plugin)
- {
- getWrapper (plugin)->destroy();
- };
-
- plugin.activate = [] (const clap_plugin* plugin, double sampleRate, uint32_t minimumFramesCount, uint32_t maximumFramesCount) -> bool
- {
- return getWrapper (plugin)->activate (static_cast (sampleRate), static_cast (maximumFramesCount));
- };
-
- plugin.deactivate = [] (const clap_plugin* plugin)
- {
- getWrapper (plugin)->deactivate();
- };
-
- plugin.start_processing = [] (const clap_plugin* plugin) -> bool
- {
- return getWrapper (plugin)->startProcessing();
- };
-
- plugin.stop_processing = [] (const clap_plugin* plugin)
- {
- getWrapper (plugin)->stopProcessing();
- };
-
- plugin.reset = [] (const clap_plugin* plugin)
- {
- getWrapper (plugin)->reset();
- };
-
- plugin.process = [] (const clap_plugin* plugin, const clap_process_t* process) -> clap_process_status
- {
- auto wrapper = getWrapper (plugin);
-
- auto& audioProcessor = *wrapper->audioProcessor;
- auto& midiBuffer = wrapper->midiEvents;
-
- auto lock = CriticalSection::ScopedTryLockType (audioProcessor.getProcessLock());
- if (! lock.isLocked() || audioProcessor.isSuspended())
- return CLAP_PROCESS_CONTINUE;
-
- jassert (process->audio_outputs_count == audioProcessor.getNumAudioOutputs());
- jassert (process->audio_inputs_count == audioProcessor.getNumAudioInputs());
-
- // PluginSyncMainToAudio(plugin, process->out_events);
-
- // Prepare midi events
- midiBuffer.clear();
-
- const uint32_t inputEventCount = process->in_events->size (process->in_events);
- for (uint32_t eventIndex = 0; eventIndex < inputEventCount; ++eventIndex)
- {
- const clap_event_header_t* event = process->in_events->get (process->in_events, eventIndex);
-
- if (event->space_id != CLAP_CORE_EVENT_SPACE_ID)
- continue;
-
- if (auto convertedEvent = clapEventToMidiNoteMessage (event))
- midiBuffer.addEvent (*convertedEvent, static_cast (event->time));
- else
- clapEventToParameterChange (event, audioProcessor);
- }
-
- // Prepare audio buffers, play head and process block
- float* buffers[2] = {
- process->audio_outputs[0].data32[0],
- process->audio_outputs[0].data32[1]
- };
-
- AudioSampleBuffer audioBuffer (&buffers[0], 2, 0, static_cast (process->frames_count));
-
- AudioPluginPlayHeadCLAP playHead (audioProcessor.getSampleRate(), process);
- audioProcessor.setPlayHead (&playHead);
-
- audioProcessor.processBlock (audioBuffer, midiBuffer);
-
- audioProcessor.setPlayHead (nullptr);
-
- // Send back note end to host
- for (const MidiMessageMetadata metadata : midiBuffer)
- {
- if (const auto& message = metadata.getMessage(); message.isNoteOff())
- {
- clap_event_note_t event = {};
- event.header.size = sizeof (event);
- event.header.time = 0;
- event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
- event.header.type = CLAP_EVENT_NOTE_END;
- event.header.flags = 0;
- event.note_id = -1;
- event.key = message.getNoteNumber();
- event.channel = message.getChannel() - 1;
- event.port_index = 0;
-
- process->out_events->try_push (process->out_events, &event.header);
- }
- }
-
- return CLAP_PROCESS_CONTINUE;
- };
-
- plugin.get_extension = [] (const clap_plugin* plugin, const char* id) -> const void*
- {
- return getWrapper (plugin)->getExtension (id);
- };
-
- plugin.on_main_thread = [] (const clap_plugin* plugin) {};
-}
-
-//==============================================================================
-
-AudioPluginProcessorCLAP::~AudioPluginProcessorCLAP()
-{
-}
-
-//==============================================================================
-
-bool AudioPluginProcessorCLAP::initialise()
-{
- jassert (audioProcessor == nullptr);
-
- audioProcessor.reset (::createPluginProcessor());
- if (audioProcessor == nullptr)
- return false;
-
- // ==== Setup extensions: parameters
- extensionParams.count = [] (const clap_plugin_t* plugin) -> uint32_t
- {
- return static_cast (getWrapper (plugin)->audioProcessor->getParameters().size());
- };
-
- extensionParams.get_info = [] (const clap_plugin_t* plugin, uint32_t index, clap_param_info_t* information) -> bool
- {
- std::memset (information, 0, sizeof (clap_param_info_t));
-
- auto wrapper = getWrapper (plugin);
- auto parameters = wrapper->audioProcessor->getParameters();
-
- if (index >= static_cast (parameters.size()))
- return false;
-
- auto& parameter = parameters[index];
-
- information->id = index;
- information->cookie = parameter.get();
- information->flags = CLAP_PARAM_IS_AUTOMATABLE | CLAP_PARAM_IS_MODULATABLE | CLAP_PARAM_IS_MODULATABLE_PER_NOTE_ID;
- information->min_value = parameter->getMinimumValue();
- information->max_value = parameter->getMaximumValue();
- information->default_value = parameter->getDefaultValue();
- parameter->getName().copyToUTF8 (information->name, CLAP_NAME_SIZE);
-
- return true;
- };
-
- extensionParams.get_value = [] (const clap_plugin_t* plugin, clap_id parameterId, double* value) -> bool
- {
- auto wrapper = getWrapper (plugin);
- auto parameters = wrapper->audioProcessor->getParameters();
-
- if (parameterId >= static_cast (parameters.size()))
- return false;
-
- *value = parameters[parameterId]->getValue();
-
- return true;
- };
-
- extensionParams.value_to_text = [] (const clap_plugin_t* plugin, clap_id parameterId, double value, char* display, uint32_t size) -> bool
- {
- auto wrapper = getWrapper (plugin);
- auto parameters = wrapper->audioProcessor->getParameters();
-
- if (parameterId >= static_cast (parameters.size()))
- return false;
-
- const auto text = parameters[parameterId]->convertToString (static_cast (value));
- text.copyToUTF8 (display, size);
-
- return true;
- };
-
- extensionParams.text_to_value = [] (const clap_plugin_t* plugin, clap_id parameterId, const char* display, double* value) -> bool
- {
- auto wrapper = getWrapper (plugin);
- auto parameters = wrapper->audioProcessor->getParameters();
-
- if (parameterId >= static_cast (parameters.size()))
- return false;
-
- *value = static_cast (parameters[parameterId]->convertFromString (display));
-
- return true;
- };
-
- extensionParams.flush = [] (const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out)
- {
- /* // TODO
- auto wrapper = getWrapper (plugin);
-
- MyPlugin *plugin = (MyPlugin *) _plugin->plugin_data;
- const uint32_t eventCount = in->size(in);
-
- // For parameters that have been modified by the main thread, send CLAP_EVENT_PARAM_VALUE events to the host.
- PluginSyncMainToAudio(plugin, out);
-
- // Process events sent to our plugin from the host.
- for (uint32_t eventIndex = 0; eventIndex < eventCount; eventIndex++)
- {
- PluginProcessEvent(plugin, in->get(in, eventIndex));
- }
- */
- };
-
- // ==== Setup extensions: note ports
- extensionNotePorts.count = [] (const clap_plugin_t* plugin, bool isInput) -> uint32_t
- {
- // TODO - this depends on the YupPlugin_IsSynth, but we might want to probe for midi input buses
- return isInput ? 1 : 0;
- };
-
- extensionNotePorts.get = [] (const clap_plugin_t* plugin, uint32_t index, bool isInput, clap_note_port_info_t* info) -> bool
- {
- if (! isInput || index)
- return false;
-
- info->id = 0;
- info->supported_dialects = CLAP_NOTE_DIALECT_CLAP; // TODO Also support the MIDI dialect.
- info->preferred_dialect = CLAP_NOTE_DIALECT_CLAP;
-
- std::snprintf (info->name, sizeof (info->name), "%s", "Note Port");
-
- return true;
- };
-
- // ==== Setup extensions: audio ports
- extensionAudioPorts.count = [] (const clap_plugin_t* plugin, bool isInput) -> uint32_t
- {
- auto wrapper = getWrapper (plugin);
- auto* audioProcessor = wrapper->audioProcessor.get();
-
- Span busses = isInput
- ? audioProcessor->getBusLayout().getInputBuses()
- : audioProcessor->getBusLayout().getOutputBuses();
-
- uint32_t count = 0;
- for (const auto& bus : busses)
- if (bus.getType() == AudioBus::Type::Audio)
- ++count;
-
- return count;
- };
-
- extensionAudioPorts.get = [] (const clap_plugin_t* plugin, uint32_t index, bool isInput, clap_audio_port_info_t* info) -> bool
- {
- auto wrapper = getWrapper (plugin);
- auto* audioProcessor = wrapper->audioProcessor.get();
-
- Span busses = isInput
- ? audioProcessor->getBusLayout().getInputBuses()
- : audioProcessor->getBusLayout().getOutputBuses();
-
- const AudioBus* audioBus = nullptr;
- uint32_t audioBusIndex = 0;
-
- for (const auto& bus : busses)
- {
- if (bus.getType() != AudioBus::Type::Audio)
- continue;
-
- if (audioBusIndex == index)
- {
- audioBus = &bus;
- break;
- }
-
- ++audioBusIndex;
- }
-
- if (audioBus == nullptr)
- return false;
-
- info->id = index;
- info->channel_count = audioBus->getNumChannels();
- info->flags = (index == 0) ? CLAP_AUDIO_PORT_IS_MAIN : 0;
- info->port_type = audioBus->isStereo() ? CLAP_PORT_STEREO : CLAP_PORT_MONO;
- info->in_place_pair = CLAP_INVALID_ID;
- audioBus->getName().copyToUTF8 (info->name, sizeof (info->name));
-
- return true;
- };
-
- // ==== Setup extensions: state
- extensionState.save = [] (const clap_plugin_t* plugin, const clap_ostream_t* stream) -> bool
- {
- auto wrapper = getWrapper (plugin);
-
- MemoryBlock data;
-
- // TODO - should we suspend ?
-
- if (auto result = wrapper->audioProcessor->saveStateIntoMemory (data); result.failed())
- return false;
-
- // TODO - should we resume ?
-
- return stream->write (stream, data.getData(), data.getSize()) == data.getSize();
- };
-
- extensionState.load = [] (const clap_plugin_t* plugin, const clap_istream_t* stream) -> bool
- {
- auto wrapper = getWrapper (plugin);
- auto parameters = wrapper->audioProcessor->getParameters();
-
- MemoryBlock data;
- if (auto result = stream->read (stream, data.getData(), data.getSize()); result <= 0)
- return false;
-
- // TODO - should we suspend ?
-
- auto result = wrapper->audioProcessor->loadStateFromMemory (data);
-
- // TODO - should we resume ?
-
- return result.wasOk();
- };
-
- // ==== Setup extensions: tail
- extensionTail.get = [] (const clap_plugin_t* plugin) -> uint32_t
- {
- auto wrapper = getWrapper (plugin);
- return static_cast (wrapper->audioProcessor->getTailSamples());
- };
-
- // ==== Setup extensions: latency
- extensionLatency.get = [] (const clap_plugin_t* plugin) -> uint32_t
- {
- auto wrapper = getWrapper (plugin);
- return static_cast (wrapper->audioProcessor->getLatencySamples());
- };
-
- // ==== Setup extensions: timer support
- extensionTimerSupport.on_timer = [] (const clap_plugin_t* plugin, clap_id timerId)
- {
-#if YUP_LINUX
- if (auto wrapper = getWrapper (plugin); wrapper->guiTimerId == timerId)
- MessageManager::getInstance()->runDispatchLoopUntil (10);
-#endif
- };
-
- // ==== Setup extensions: gui
- extensionGUI.is_api_supported = [] (const clap_plugin_t* plugin, const char* api, bool isFloating) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioProcessor == nullptr || ! wrapper->audioProcessor->hasEditor())
- return false;
-
- return std::string_view (api) == preferredApi && ! isFloating;
- };
-
- extensionGUI.get_preferred_api = [] (const clap_plugin_t* plugin, const char** api, bool* isFloating) -> bool
- {
- *api = preferredApi;
- *isFloating = false;
- return true;
- };
-
- extensionGUI.create = [] (const clap_plugin_t* plugin, const char* api, bool isFloating) -> bool
- {
- if (api == nullptr || std::string_view (api) != preferredApi || isFloating)
- return false;
-
- auto wrapper = getWrapper (plugin);
-
- auto processorEditor = wrapper->audioProcessor->createEditor();
- if (processorEditor == nullptr)
- return false;
-
- wrapper->audioPluginEditor = std::make_unique (wrapper, processorEditor);
-
- if (isFloating)
- {
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
- if (audioProcessorEditor == nullptr)
- return false;
-
- ComponentNative::Flags flags = ComponentNative::defaultFlags;
-
- if (audioProcessorEditor->shouldRenderContinuous())
- flags.set (ComponentNative::renderContinuous);
-
- auto options = ComponentNative::Options()
- .withFlags (flags)
- .withResizableWindow (audioProcessorEditor->isResizable());
-
- wrapper->audioPluginEditor->addToDesktop (options);
- wrapper->audioPluginEditor->setVisible (true);
-
- audioProcessorEditor->attachedToNative();
- }
-
- return true;
- };
-
- extensionGUI.destroy = [] (const clap_plugin_t* plugin)
- {
- auto wrapper = getWrapper (plugin);
- wrapper->audioPluginEditor.reset();
- };
-
- extensionGUI.set_scale = [] (const clap_plugin_t* plugin, double scale) -> bool
- {
- return false;
- };
-
- extensionGUI.get_size = [] (const clap_plugin_t* plugin, uint32_t* width, uint32_t* height) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
-
- if (audioProcessorEditor->isResizable() && audioProcessorEditor->getWidth() != 0)
- {
- *width = static_cast (audioProcessorEditor->getWidth());
- *height = static_cast (audioProcessorEditor->getHeight());
- }
- else
- {
- *width = static_cast (audioProcessorEditor->getPreferredSize().getWidth());
- *height = static_cast (audioProcessorEditor->getPreferredSize().getHeight());
- }
-
- return true;
- };
-
- extensionGUI.can_resize = [] (const clap_plugin_t* plugin) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- return wrapper->audioPluginEditor->getAudioProcessorEditor()->isResizable();
- };
-
- extensionGUI.get_resize_hints = [] (const clap_plugin_t* plugin, clap_gui_resize_hints_t* hints) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
-
- hints->can_resize_horizontally = audioProcessorEditor->isResizable();
- hints->can_resize_vertically = audioProcessorEditor->isResizable();
- hints->preserve_aspect_ratio = audioProcessorEditor->shouldPreserveAspectRatio();
- hints->aspect_ratio_width = audioProcessorEditor->getPreferredSize().getWidth();
- hints->aspect_ratio_height = audioProcessorEditor->getPreferredSize().getHeight();
-
- return true;
- };
-
- extensionGUI.adjust_size = [] (const clap_plugin_t* plugin, uint32_t* width, uint32_t* height) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
-
- const auto preferredSize = audioProcessorEditor->getPreferredSize();
-
- if (! audioProcessorEditor->isResizable())
- {
- *width = static_cast (preferredSize.getWidth());
- *height = static_cast (preferredSize.getHeight());
- }
- else if (audioProcessorEditor->shouldPreserveAspectRatio())
- {
- if (preferredSize.getWidth() > preferredSize.getHeight())
- *height = static_cast (*width * (preferredSize.getWidth() / static_cast (preferredSize.getHeight())));
- else
- *width = static_cast (*height * (preferredSize.getHeight() / static_cast (preferredSize.getWidth())));
- }
-
- return true;
- };
-
- extensionGUI.set_size = [] (const clap_plugin_t* plugin, uint32_t width, uint32_t height) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
-
- if (! audioProcessorEditor->isResizable())
- {
- const auto preferredSize = audioProcessorEditor->getPreferredSize();
-
- width = static_cast (preferredSize.getWidth());
- height = static_cast (preferredSize.getHeight());
- }
-
- const auto scoped = wrapper->scopedHostEditorResizing();
-
- wrapper->audioPluginEditor->setSize ({ static_cast (width), static_cast (height) });
-
- return true;
- };
-
- extensionGUI.set_parent = [] (const clap_plugin_t* plugin, const clap_window_t* window) -> bool
- {
- jassert (std::string_view (window->api) == preferredApi);
-
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- auto audioProcessorEditor = wrapper->audioPluginEditor->getAudioProcessorEditor();
- if (audioProcessorEditor == nullptr)
- return false;
-
- ComponentNative::Flags flags = ComponentNative::defaultFlags & ~ComponentNative::decoratedWindow;
-
- if (audioProcessorEditor->shouldRenderContinuous())
- flags.set (ComponentNative::renderContinuous);
-
- auto options = ComponentNative::Options()
- .withFlags (flags)
- .withResizableWindow (audioProcessorEditor->isResizable());
-
- wrapper->audioPluginEditor->addToDesktop (
- options,
-#if YUP_MAC
- window->cocoa);
-#elif YUP_WINDOWS
- window->win32);
-#elif YUP_LINUX
- reinterpret_cast (window->x11));
-#else
- nullptr);
-#endif
-
- wrapper->audioPluginEditor->setVisible (true);
-
- audioProcessorEditor->attachedToNative();
-
- return true;
- };
-
- extensionGUI.set_transient = [] (const clap_plugin_t* plugin, const clap_window_t* window) -> bool
- {
- return false;
- };
-
- extensionGUI.suggest_title = [] (const clap_plugin_t* plugin, const char* title) {};
-
- extensionGUI.show = [] (const clap_plugin_t* plugin) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- wrapper->audioPluginEditor->setVisible (true);
- return true;
- };
-
- extensionGUI.hide = [] (const clap_plugin_t* plugin) -> bool
- {
- auto wrapper = getWrapper (plugin);
- if (wrapper->audioPluginEditor == nullptr)
- return false;
-
- wrapper->audioPluginEditor->setVisible (false);
- return true;
- };
-
- // ==== Setup extensions: host
- hostParams = reinterpret_cast (host->get_extension (host, CLAP_EXT_PARAMS));
- hostState = reinterpret_cast (host->get_extension (host, CLAP_EXT_STATE));
- hostTail = reinterpret_cast (host->get_extension (host, CLAP_EXT_TAIL));
- hostLatency = reinterpret_cast (host->get_extension (host, CLAP_EXT_LATENCY));
- hostTimerSupport = reinterpret_cast (host->get_extension (host, CLAP_EXT_TIMER_SUPPORT));
- hostGUI = reinterpret_cast (host->get_extension (host, CLAP_EXT_GUI));
-
- return true;
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::destroy()
-{
- plugin.plugin_data = nullptr;
- delete this;
-}
-
-//==============================================================================
-
-bool AudioPluginProcessorCLAP::activate (float sampleRate, int samplesPerBlock)
-{
-#if YUP_LINUX
- if (instancesCount.fetch_add (1) == 0)
- registerTimer (16, &guiTimerId);
-#endif
-
- audioProcessor->setPlaybackConfiguration (sampleRate, samplesPerBlock);
- return true;
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::deactivate()
-{
- audioProcessor->releaseResources();
-
-#if YUP_LINUX
- if (instancesCount.fetch_sub (1) == 1)
- unregisterTimer (guiTimerId);
-#endif
-}
-
-//==============================================================================
-
-bool AudioPluginProcessorCLAP::startProcessing()
-{
- audioProcessor->suspendProcessing (false);
- return true;
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::stopProcessing()
-{
- audioProcessor->suspendProcessing (true);
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::reset()
-{
- audioProcessor->flush(); // TODO - should we just call releaseResources()?
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::registerTimer (uint32_t periodMs, clap_id* timerId)
-{
- if (hostTimerSupport != nullptr && hostTimerSupport->register_timer)
- hostTimerSupport->register_timer (host, periodMs, timerId);
-}
-
-void AudioPluginProcessorCLAP::unregisterTimer (clap_id timerId)
-{
- if (hostTimerSupport != nullptr && hostTimerSupport->register_timer)
- hostTimerSupport->unregister_timer (host, timerId);
-}
-
-//==============================================================================
-
-const void* AudioPluginProcessorCLAP::getExtension (std::string_view id)
-{
- if (id == CLAP_EXT_NOTE_PORTS)
- return std::addressof (extensionNotePorts);
- if (id == CLAP_EXT_AUDIO_PORTS)
- return std::addressof (extensionAudioPorts);
- if (id == CLAP_EXT_PARAMS)
- return std::addressof (extensionParams);
- if (id == CLAP_EXT_STATE)
- return std::addressof (extensionState);
- if (id == CLAP_EXT_TAIL)
- return std::addressof (extensionTail);
- if (id == CLAP_EXT_LATENCY)
- return std::addressof (extensionLatency);
- if (id == CLAP_EXT_TIMER_SUPPORT)
- return std::addressof (extensionTimerSupport);
- if (id == CLAP_EXT_GUI)
- return std::addressof (extensionGUI);
-
- return nullptr;
-}
-
-//==============================================================================
-
-const clap_plugin_t* AudioPluginProcessorCLAP::getPlugin() const
-{
- return std::addressof (plugin);
-}
-
-//==============================================================================
-
-void AudioPluginProcessorCLAP::editorResized()
-{
- if (audioPluginEditor == nullptr || hostTriggeredResizing)
- return;
-
- if (hostGUI != nullptr && hostGUI->request_resize != nullptr)
- hostGUI->request_resize (host, audioPluginEditor->getWidth(), audioPluginEditor->getHeight());
-}
-
-ScopedValueSetter AudioPluginProcessorCLAP::scopedHostEditorResizing()
-{
- return { hostTriggeredResizing, true };
-}
-
-//==============================================================================
-
-void AudioPluginEditorCLAP::resized()
-{
- if (processorEditor == nullptr)
- return;
-
- processorEditor->setBounds (getLocalBounds());
-
- wrapper->editorResized();
-}
-
-} // namespace yup
-
-//==============================================================================
-
-static const clap_plugin_factory_t plugin_factory = []
-{
- clap_plugin_factory_t factory;
-
- factory.get_plugin_count = [] (const clap_plugin_factory* factory) -> uint32_t
- {
- return 1;
- };
-
- factory.get_plugin_descriptor = [] (const clap_plugin_factory* factory, uint32_t index) -> const clap_plugin_descriptor_t*
- {
- return index == 0 ? &yup::pluginDescriptor : nullptr;
- };
-
- factory.create_plugin = [] (const clap_plugin_factory* factory, const clap_host_t* host, const char* pluginId) -> const clap_plugin_t*
- {
- if (! clap_version_is_compatible (host->clap_version) || std::string_view (pluginId) != yup::pluginDescriptor.id)
- return nullptr;
-
- auto wrapper = new yup::AudioPluginProcessorCLAP (host);
- return wrapper->getPlugin();
- };
-
- return factory;
-}();
-
-//==============================================================================
-
-extern "C" const CLAP_EXPORT clap_plugin_entry_t clap_entry = []
-{
- clap_plugin_entry_t plugin;
-
- plugin.clap_version = CLAP_VERSION_INIT;
-
- plugin.init = [] (const char* path) -> bool
- {
- yup::initialiseYup_GUI();
- yup::initialiseYup_Windowing();
-
- return true;
- };
-
- plugin.deinit = []
- {
- yup::shutdownYup_Windowing();
- yup::shutdownYup_GUI();
- };
-
- plugin.get_factory = [] (const char* factoryId) -> const void*
- {
- if (std::string_view (factoryId) == CLAP_PLUGIN_FACTORY_ID)
- return std::addressof (plugin_factory);
-
- return nullptr;
- };
-
- return plugin;
-}();
+/*
+ ==============================================================================
+
+ This file is part of the YUP library.
+ Copyright (c) 2024 - 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_CLAP)
+#error "YUP_AUDIO_PLUGIN_ENABLE_CLAP must be defined"
+#endif
+
+#include
+#include
+
+#include
+
+extern "C" yup::AudioProcessor* createPluginProcessor();
+
+namespace yup
+{
+
+//==============================================================================
+
+std::optional clapEventToMidiMessage (const clap_event_header_t* event)
+{
+ switch (event->type)
+ {
+ case CLAP_EVENT_NOTE_ON:
+ {
+ const auto* noteEvent = reinterpret_cast (event);
+ const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
+ return MidiMessage::noteOn (channel, noteEvent->key, static_cast (noteEvent->velocity * 127.0f));
+ }
+
+ case CLAP_EVENT_NOTE_OFF:
+ {
+ const auto* noteEvent = reinterpret_cast (event);
+ const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
+ return MidiMessage::noteOff (channel, noteEvent->key, static_cast (noteEvent->velocity * 127.0f));
+ }
+
+ case CLAP_EVENT_NOTE_CHOKE:
+ {
+ const auto* noteEvent = reinterpret_cast (event);
+ const int channel = noteEvent->channel < 0 ? 1 : noteEvent->channel + 1;
+ return MidiMessage::noteOff (channel, noteEvent->key);
+ }
+
+ case CLAP_EVENT_MIDI:
+ {
+ const auto* midiEvent = reinterpret_cast (event);
+ return MidiMessage (midiEvent->data, 3);
+ }
+
+ case CLAP_EVENT_NOTE_EXPRESSION:
+ {
+ const auto* ev = reinterpret_cast (event);
+ const int channel = ev->channel < 0 ? 1 : ev->channel + 1;
+
+ if (ev->expression_id == CLAP_NOTE_EXPRESSION_TUNING)
+ {
+ const int pitchBendValue = jlimit (0, 16383, static_cast (ev->value * 8192.0 + 8192.0));
+ return MidiMessage::pitchWheel (channel, pitchBendValue);
+ }
+
+ if (ev->expression_id == CLAP_NOTE_EXPRESSION_PRESSURE)
+ return MidiMessage::channelPressureChange (channel, static_cast (ev->value * 127.0));
+
+ if (ev->expression_id == CLAP_NOTE_EXPRESSION_BRIGHTNESS)
+ return MidiMessage::controllerEvent (channel, 74, static_cast (ev->value * 127.0));
+
+ break;
+ }
+
+ case CLAP_EVENT_MIDI_SYSEX:
+ {
+ const auto* sysexEvent = reinterpret_cast (event);
+ return MidiMessage (sysexEvent->buffer, static_cast (sysexEvent->size));
+ }
+
+ default:
+ break;
+ }
+
+ return std::nullopt;
+}
+
+//==============================================================================
+
+void clapEventToParameterChange (const clap_event_header_t* event, AudioProcessor& audioProcessor)
+{
+ if (event->type != CLAP_EVENT_PARAM_VALUE)
+ return;
+
+ const clap_event_param_value_t* paramEvent = reinterpret_cast (event);
+
+ auto parameterIndex = audioProcessor.getParameterIndexByHostID (paramEvent->param_id);
+ auto parameters = audioProcessor.getParameters();
+ if (! isPositiveAndBelow (parameterIndex, static_cast (parameters.size())))
+ return;
+
+ parameters[parameterIndex]->setValue (static_cast (paramEvent->value));
+}
+
+//==============================================================================
+
+/*
+void pluginSyncMainToAudio (AudioProcessor& audioProcessor, const clap_output_events_t* out)
+{
+ auto sl = CriticalSection::ScopedLockType (plugin->syncParameters);
+
+ for (uint32_t i = 0; i < P_COUNT; i++)
+ {
+ if (plugin->mainChanged[i])
+ {
+ plugin->parameters[i] = plugin->mainParameters[i];
+ plugin->mainChanged[i] = false;
+
+ clap_event_param_value_t event = {};
+ event.header.size = sizeof(event);
+ event.header.time = 0;
+ event.header.space_id = CLAP_CORE_EVENT_SPACE_ID;
+ event.header.type = CLAP_EVENT_PARAM_VALUE;
+ event.header.flags = 0;
+ event.param_id = i;
+ event.cookie = NULL;
+ event.note_id = -1;
+ event.port_index = -1;
+ event.channel = -1;
+ event.key = -1;
+ event.value = plugin->parameters[i];
+ out->try_push(out, &event.header);
+ }
+ }
+}
+
+bool pluginSyncAudioToMain (AudioProcessor& audioProcessor)
+{
+ bool anyChanged = false;
+ auto sl = CriticalSection::ScopedLockType (plugin->syncParameters);
+
+ for (uint32_t i = 0; i < P_COUNT; i++)
+ {
+ if (plugin->changed[i])
+ {
+ plugin->mainParameters[i] = plugin->parameters[i];
+ plugin->changed[i] = false;
+ anyChanged = true;
+ }
+ }
+
+ return anyChanged;
+
+ return false;
+}
+*/
+
+//==============================================================================
+
+static const char* pluginFeatures[] = {
+#if YupPlugin_IsSynth
+ CLAP_PLUGIN_FEATURE_INSTRUMENT,
+ CLAP_PLUGIN_FEATURE_SYNTHESIZER,
+#else
+ CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
+#endif
+#if YupPlugin_IsMono
+ CLAP_PLUGIN_FEATURE_MONO,
+#else
+ CLAP_PLUGIN_FEATURE_STEREO,
+#endif
+ nullptr
+};
+
+static const clap_plugin_descriptor_t pluginDescriptor = {
+ .clap_version = CLAP_VERSION_INIT,
+ .id = YupPlugin_Id,
+ .name = YupPlugin_Name,
+ .vendor = YupPlugin_Vendor,
+ .url = YupPlugin_URL,
+ .manual_url = YupPlugin_URL,
+ .support_url = YupPlugin_URL,
+ .version = YupPlugin_Version,
+ .description = YupPlugin_Description,
+ .features = pluginFeatures,
+};
+
+#if YUP_MAC
+static const char* const preferredApi = CLAP_WINDOW_API_COCOA;
+#elif YUP_WINDOWS
+static const char* const preferredApi = CLAP_WINDOW_API_WIN32;
+#elif YUP_LINUX
+static const char* const preferredApi = CLAP_WINDOW_API_X11;
+#endif
+
+struct CLAPScopedGuiInitialiser
+{
+ CLAPScopedGuiInitialiser()
+ {
+ if (numCLAPScopedGuiInitInstances.fetch_add (1) == 0)
+ {
+ initialiseYup_GUI();
+ }
+ }
+
+ ~CLAPScopedGuiInitialiser()
+ {
+ if (numCLAPScopedGuiInitInstances.fetch_sub (1) == 1)
+ {
+ shutdownYup_GUI();
+ }
+ }
+
+private:
+ static std::atomic_int numCLAPScopedGuiInitInstances;
+};
+
+std::atomic_int CLAPScopedGuiInitialiser::numCLAPScopedGuiInitInstances = 0;
+
+struct CLAPScopedWindowingInitialiser
+{
+ CLAPScopedWindowingInitialiser()
+ {
+ if (numCLAPScopedGuiInitInstances.fetch_add (1) == 0)
+ {
+ initialiseYup_Windowing();
+ }
+ }
+
+ ~CLAPScopedWindowingInitialiser()
+ {
+ if (numCLAPScopedGuiInitInstances.fetch_sub (1) == 1)
+ {
+ shutdownYup_Windowing();
+ }
+ }
+
+private:
+ static std::atomic_int numCLAPScopedGuiInitInstances;
+};
+
+std::atomic_int CLAPScopedWindowingInitialiser::numCLAPScopedGuiInitInstances = 0;
+
+//==============================================================================
+
+class AudioPluginProcessorCLAP;
+
+//==============================================================================
+
+class AudioPluginPlayHeadCLAP final : public AudioPlayHead
+{
+public:
+ explicit AudioPluginPlayHeadCLAP (float sampleRate, const clap_process_t* process)
+ : process (*process)
+ , sampleRate (sampleRate)
+ {
+ }
+
+ bool canControlTransport() override
+ {
+ return false;
+ }
+
+ void transportPlay (bool shouldSartPlaying) override
+ {
+ if (! canControlTransport())
+ return;
+ }
+
+ void transportRecord (bool shouldStartRecording) override
+ {
+ if (! canControlTransport())
+ return;
+ }
+
+ void transportRewind() override
+ {
+ if (! canControlTransport())
+ return;
+ }
+
+ std::optional getPosition() const override
+ {
+ if (process.transport == nullptr)
+ return {};
+
+ PositionInfo result;
+
+ result.setTimeInSeconds (process.transport->song_pos_seconds / (double) CLAP_SECTIME_FACTOR);
+ result.setTimeInSamples ((int64) (sampleRate * (process.transport->song_pos_seconds / (double) CLAP_SECTIME_FACTOR)));
+ result.setTimeSignature (TimeSignature { process.transport->tsig_num, process.transport->tsig_denom });
+ result.setBpm (process.transport->tempo);
+ result.setBarCount (process.transport->bar_number);
+ result.setPpqPositionOfLastBarStart (process.transport->bar_start / (double) CLAP_BEATTIME_FACTOR);
+ result.setIsPlaying (process.transport->flags & CLAP_TRANSPORT_IS_PLAYING);
+ result.setIsRecording (process.transport->flags & CLAP_TRANSPORT_IS_RECORDING);
+ result.setIsLooping (process.transport->flags & CLAP_TRANSPORT_IS_LOOP_ACTIVE);
+ result.setLoopPoints (LoopPoints {
+ process.transport->loop_start_beats / (double) CLAP_BEATTIME_FACTOR,
+ process.transport->loop_end_beats / (double) CLAP_BEATTIME_FACTOR });
+ result.setFrameRate (AudioPlayHead::fpsUnknown);
+
+ return result;
+ }
+
+private:
+ clap_process_t process;
+ float sampleRate = 44100.0f;
+};
+
+//==============================================================================
+
+class AudioPluginEditorCLAP final : public Component
+{
+public:
+ AudioPluginEditorCLAP (AudioPluginProcessorCLAP* wrapper, AudioProcessorEditor* editor)
+ : wrapper (wrapper)
+ , processorEditor (editor)
+ {
+ addAndMakeVisible (*processorEditor);
+ }
+
+ AudioProcessorEditor* getAudioProcessorEditor() { return processorEditor.get(); }
+
+ void resized() override;
+
+private:
+ CLAPScopedWindowingInitialiser scopedWindowingInitialiser;
+
+ AudioPluginProcessorCLAP* wrapper = nullptr;
+ std::unique_ptr processorEditor;
+};
+
+//==============================================================================
+
+class AudioPluginProcessorCLAP final
+{
+public:
+ AudioPluginProcessorCLAP (const clap_host_t* host);
+ ~AudioPluginProcessorCLAP();
+
+ bool initialise();
+ void destroy();
+
+ bool activate (float sampleRate, int samplesPerBlock);
+ void deactivate();
+
+ bool startProcessing();
+ void stopProcessing();
+
+ void reset();
+
+ void registerTimer (uint32_t periodMs, clap_id* timerId);
+ void unregisterTimer (clap_id timerId);
+
+ const void* getExtension (std::string_view id);
+ const clap_plugin_t* getPlugin() const;
+
+ void editorResized();
+ ScopedValueSetter scopedHostEditorResizing();
+
+private:
+ CLAPScopedGuiInitialiser scopedGuiInitialiser;
+
+ std::unique_ptr audioProcessor;
+ std::unique_ptr audioPluginEditor;
+
+ const clap_host_t* host = nullptr;
+
+ clap_plugin_t plugin;
+
+ clap_plugin_note_ports_t extensionNotePorts;
+ clap_plugin_audio_ports_t extensionAudioPorts;
+ clap_plugin_params_t extensionParams;
+ clap_plugin_state_t extensionState;
+ clap_plugin_tail_t extensionTail;
+ clap_plugin_latency_t extensionLatency;
+ clap_plugin_timer_support_t extensionTimerSupport;
+ clap_plugin_gui_t extensionGUI;
+ clap_plugin_render_t extensionRender;
+ clap_plugin_voice_info_t extensionVoiceInfo;
+
+ const clap_host_params_t* hostParams = nullptr;
+ const clap_host_state_t* hostState = nullptr;
+ const clap_host_tail_t* hostTail = nullptr;
+ const clap_host_latency_t* hostLatency = nullptr;
+ const clap_host_timer_support_t* hostTimerSupport = nullptr;
+ const clap_host_gui_t* hostGUI = nullptr;
+
+ clap_id guiTimerId;
+ bool hostTriggeredResizing = false;
+
+ MidiBuffer midiEvents;
+ ParameterChangeBuffer paramChangeBuffer;
+
+ static std::atomic_int instancesCount;
+};
+
+//==============================================================================
+
+std::atomic_int AudioPluginProcessorCLAP::instancesCount = 0;
+
+//==============================================================================
+
+AudioPluginProcessorCLAP* getWrapper (const clap_plugin_t* plugin)
+{
+ return reinterpret_cast (plugin->plugin_data);
+}
+
+//==============================================================================
+
+AudioPluginProcessorCLAP::AudioPluginProcessorCLAP (const clap_host_t* host)
+ : host (host)
+{
+ jassert (host != nullptr);
+
+ plugin.desc = &pluginDescriptor;
+ plugin.plugin_data = this;
+
+ plugin.init = [] (const clap_plugin* plugin) -> bool
+ {
+ return getWrapper (plugin)->initialise();
+ };
+
+ plugin.destroy = [] (const clap_plugin* plugin)
+ {
+ getWrapper (plugin)->destroy();
+ };
+
+ plugin.activate = [] (const clap_plugin* plugin, double sampleRate, uint32_t minimumFramesCount, uint32_t maximumFramesCount) -> bool
+ {
+ return getWrapper (plugin)->activate (static_cast (sampleRate), static_cast (maximumFramesCount));
+ };
+
+ plugin.deactivate = [] (const clap_plugin* plugin)
+ {
+ getWrapper (plugin)->deactivate();
+ };
+
+ plugin.start_processing = [] (const clap_plugin* plugin) -> bool
+ {
+ return getWrapper (plugin)->startProcessing();
+ };
+
+ plugin.stop_processing = [] (const clap_plugin* plugin)
+ {
+ getWrapper (plugin)->stopProcessing();
+ };
+
+ plugin.reset = [] (const clap_plugin* plugin)
+ {
+ getWrapper (plugin)->reset();
+ };
+
+ plugin.process = [] (const clap_plugin* plugin, const clap_process_t* process) -> clap_process_status
+ {
+ auto wrapper = getWrapper (plugin);
+
+ auto& audioProcessor = *wrapper->audioProcessor;
+ auto& midiBuffer = wrapper->midiEvents;
+
+ auto lock = CriticalSection::ScopedTryLockType (audioProcessor.getProcessLock());
+ if (! lock.isLocked() || audioProcessor.isSuspended())
+ return CLAP_PROCESS_CONTINUE;
+
+ jassert (process->audio_outputs_count == audioProcessor.getNumAudioOutputs());
+ jassert (process->audio_inputs_count == audioProcessor.getNumAudioInputs());
+
+ // Process incoming parameter and MIDI events (CLAP guarantees time-sorted order)
+ midiBuffer.clear();
+ wrapper->paramChangeBuffer.clear();
+
+ const uint32_t inputEventCount = process->in_events->size (process->in_events);
+ for (uint32_t eventIndex = 0; eventIndex < inputEventCount; ++eventIndex)
+ {
+ const clap_event_header_t* event = process->in_events->get (process->in_events, eventIndex);
+
+ if (event->space_id != CLAP_CORE_EVENT_SPACE_ID)
+ continue;
+
+ if (event->type == CLAP_EVENT_PARAM_VALUE)
+ {
+ const auto* paramEvent = reinterpret_cast (event);
+ const auto paramIndex = audioProcessor.getParameterIndexByHostID (paramEvent->param_id);
+
+ if (isPositiveAndBelow (paramIndex, static_cast (audioProcessor.getParameters().size())))
+ {
+ wrapper->paramChangeBuffer.addChange (paramIndex,
+ static_cast (paramEvent->value),
+ static_cast (event->time));
+ }
+ }
+ else if (auto convertedEvent = clapEventToMidiMessage (event))
+ {
+ midiBuffer.addEvent (*convertedEvent, static_cast (event->time));
+ }
+ }
+
+ // CLAP events arrive sorted — no sort needed; apply final values for backward compat
+ for (const auto& change : wrapper->paramChangeBuffer)
+ audioProcessor.getParameters()[change.parameterIndex]->setNormalizedValue (change.normalizedValue);
+
+ // Copy input audio into output buffers for effect processors
+ for (uint32_t busIdx = 0; busIdx < std::min (process->audio_inputs_count, process->audio_outputs_count); ++busIdx)
+ {
+ const auto& inBus = process->audio_inputs[busIdx];
+ const auto& outBus = process->audio_outputs[busIdx];
+ const uint32_t chCount = std::min (inBus.channel_count, outBus.channel_count);
+
+ for (uint32_t ch = 0; ch < chCount; ++ch)
+ {
+ const auto* in = inBus.data32[ch];
+ auto* out = outBus.data32[ch];
+ if (in != out)
+ std::memcpy (out, in, process->frames_count * sizeof (float));
+ }
+ }
+
+ // Build flat channel pointer array across all output buses
+ std::vector