From bb5c679f546525628a800b5fb847329f265e7e61 Mon Sep 17 00:00:00 2001 From: Ywen Date: Wed, 11 Oct 2023 16:34:38 +0200 Subject: [PATCH] Updated juce to 7.0.7 --- Builds/LinuxMakefile/Makefile | 115 +- .../Arpligner.xcodeproj/project.pbxproj | 407 +- Builds/VisualStudio2022/Arpligner.sln | 7 + .../Arpligner_LV2ManifestHelper.vcxproj | 10 +- .../Arpligner_LV2Plugin.vcxproj | 13 +- .../Arpligner_LV2Plugin.vcxproj.filters | 5 +- .../Arpligner_SharedCode.vcxproj | 428 +- .../Arpligner_SharedCode.vcxproj.filters | 661 ++- .../Arpligner_StandalonePlugin.vcxproj | 11 +- ...Arpligner_StandalonePlugin.vcxproj.filters | 3 - .../VisualStudio2022/Arpligner_VST3.vcxproj | 40 +- .../Arpligner_VST3.vcxproj.filters | 8 +- ...de_juce_audio_plugin_client_AAX_utils.cpp} | 16 +- ... include_juce_audio_plugin_client_VST2.mm} | 16 +- .../include_juce_audio_plugin_client_VST3.mm | 8 + .../buffers/juce_AudioChannelSet.cpp | 143 +- .../buffers/juce_AudioChannelSet.h | 80 +- .../buffers/juce_AudioDataConverters.cpp | 4 +- .../buffers/juce_AudioProcessLoadMeasurer.cpp | 2 +- .../buffers/juce_AudioSampleBuffer.h | 55 +- .../buffers/juce_FloatVectorOperations.cpp | 2 +- .../buffers/juce_FloatVectorOperations.h | 2 +- .../juce_audio_basics/juce_audio_basics.cpp | 1 + .../juce_audio_basics/juce_audio_basics.h | 2 +- .../juce_audio_basics/midi/juce_MidiFile.cpp | 6 +- .../midi/juce_MidiMessageSequence.cpp | 4 +- .../juce_audio_basics/midi/juce_MidiRPN.cpp | 364 +- .../juce_audio_basics/midi/juce_MidiRPN.h | 37 +- .../juce_audio_basics/midi/ump/juce_UMP.h | 2 - .../midi/ump/juce_UMPConversion.h | 126 +- .../midi/ump/juce_UMPConverters.h | 53 +- .../midi/ump/juce_UMPDispatcher.h | 2 +- .../midi/ump/juce_UMPFactory.h | 10 +- .../ump/juce_UMPMidi1ToBytestreamTranslator.h | 15 +- .../juce_UMPMidi1ToMidi2DefaultTranslator.cpp | 54 +- .../juce_UMPMidi1ToMidi2DefaultTranslator.h | 22 +- .../midi/ump/juce_UMPSysEx7.cpp | 12 +- .../midi/ump/juce_UMPSysEx7.h | 2 +- .../midi/ump/juce_UMPUtils.h | 7 +- .../midi/ump/juce_UMP_test.cpp | 137 +- .../mpe/juce_MPESynthesiserBase.cpp | 4 +- .../mpe/juce_MPEZoneLayout.cpp | 9 +- .../mpe/juce_MPEZoneLayout.h | 2 + ...oLayouts.h => juce_CoreAudioLayouts_mac.h} | 8 +- ....h => juce_CoreAudioTimeConversions_mac.h} | 0 .../sources/juce_BufferingAudioSource.cpp | 2 +- .../sources/juce_MemoryAudioSource.cpp | 24 +- .../sources/juce_ResamplingAudioSource.cpp | 2 +- .../sources/juce_ToneGeneratorAudioSource.cpp | 2 +- .../synthesisers/juce_Synthesiser.cpp | 4 +- .../utilities/juce_Decibels.h | 14 + .../utilities/juce_IIRFilter.cpp | 74 +- .../utilities/juce_Interpolators.h | 2 +- .../utilities/juce_SmoothedValue.h | 8 +- .../audio_io/juce_AudioDeviceManager.cpp | 85 +- .../audio_io/juce_AudioDeviceManager.h | 6 + .../audio_io/juce_AudioIODevice.h | 6 +- .../audio_io/juce_AudioIODeviceType.h | 2 +- .../juce_audio_devices/juce_audio_devices.cpp | 39 +- .../juce_audio_devices/juce_audio_devices.h | 4 +- .../midi_io/juce_MidiDevices.cpp | 75 + .../midi_io/juce_MidiDevices.h | 86 +- .../midi_io/juce_MidiMessageCollector.cpp | 2 +- .../midi_io/ump/juce_UMPU32InputHandler.h | 4 +- .../app/com/rmsl/juce/JuceMidiSupport.java | 57 +- ...uce_linux_ALSA.cpp => juce_ALSA_linux.cpp} | 0 ...e_win32_ASIO.cpp => juce_ASIO_windows.cpp} | 0 ...droid_Audio.cpp => juce_Audio_android.cpp} | 0 ...{juce_ios_Audio.cpp => juce_Audio_ios.cpp} | 8 +- .../{juce_ios_Audio.h => juce_Audio_ios.h} | 0 ...uce_linux_Bela.cpp => juce_Bela_linux.cpp} | 0 ...c_CoreAudio.cpp => juce_CoreAudio_mac.cpp} | 95 +- ...e_mac_CoreMidi.mm => juce_CoreMidi_mac.mm} | 113 +- ...Sound.cpp => juce_DirectSound_windows.cpp} | 31 +- ...uce_HighPerformanceAudioHelpers_android.h} | 2 +- ...JackAudio.cpp => juce_JackAudio_linux.cpp} | 0 .../native/juce_Midi_android.cpp | 1171 +++++ ...uce_linux_Midi.cpp => juce_Midi_linux.cpp} | 284 +- ...e_win32_Midi.cpp => juce_Midi_windows.cpp} | 12 +- ...android_Oboe.cpp => juce_Oboe_android.cpp} | 41 +- ...oid_OpenSL.cpp => juce_OpenSL_android.cpp} | 0 ...n32_WASAPI.cpp => juce_WASAPI_windows.cpp} | 95 +- .../native/juce_android_Midi.cpp | 701 --- .../sources/juce_AudioTransportSource.h | 2 +- .../codecs/juce_CoreAudioFormat.cpp | 4 +- .../codecs/juce_FlacAudioFormat.cpp | 13 +- .../codecs/juce_OggVorbisAudioFormat.cpp | 23 +- .../codecs/juce_WavAudioFormat.cpp | 2 +- .../format/juce_ARAAudioReaders.cpp | 10 +- .../format/juce_AudioFormatReaderSource.cpp | 10 +- .../juce_BufferingAudioFormatReader.cpp | 97 +- .../juce_audio_formats/juce_audio_formats.h | 2 +- .../AAX/juce_AAX_Wrapper.cpp | 2598 ---------- .../AU/juce_AU_Wrapper.mm | 2432 ---------- .../AU/juce_AUv3_Wrapper.mm | 2027 -------- ...Program.cpp => juce_LV2ManifestHelper.cpp} | 8 +- .../LV2/juce_LV2_Client.cpp | 1814 ------- .../Standalone/juce_StandaloneFilterApp.cpp | 170 - .../Standalone/juce_StandaloneFilterWindow.h | 79 +- .../Unity/juce_UnityPluginInterface.h | 2 +- .../Unity/juce_Unity_Wrapper.cpp | 774 --- .../VST/juce_VST_Wrapper.cpp | 2256 --------- .../VST/juce_VST_Wrapper.mm | 305 -- .../VST3/juce_VST3ManifestHelper.cpp | 73 + .../juce_VST3ManifestHelper.mm} | 2 +- .../VST3/juce_VST3_Wrapper.cpp | 4239 ---------------- .../juce_CheckSettingMacros.h | 0 .../juce_CreatePluginFilter.h | 0 .../juce_IncludeModuleHeaders.h | 26 +- .../juce_IncludeSystemHeaders.h | 0 .../juce_LinuxMessageThread.h | 6 +- .../detail/juce_PluginUtilities.h | 167 + .../detail/juce_VSTWindowUtilities.h | 98 + .../juce_audio_plugin_client.h | 6 +- .../juce_audio_plugin_client_AAX.cpp | 2681 ++++++++++- .../juce_audio_plugin_client_AAX.mm | 2 +- .../juce_audio_plugin_client_AAX_utils.cpp | 107 + .../juce_audio_plugin_client_ARA.cpp | 24 +- .../juce_audio_plugin_client_AU_1.mm | 2597 +++++++++- .../juce_audio_plugin_client_AU_2.mm | 53 +- .../juce_audio_plugin_client_AUv3.mm | 1968 +++++++- .../juce_audio_plugin_client_LV2.cpp | 1791 ++++++- .../juce_audio_plugin_client_Standalone.cpp | 147 +- .../juce_audio_plugin_client_Unity.cpp | 753 ++- .../juce_audio_plugin_client_VST2.cpp | 2184 ++++++++- ...s.cpp => juce_audio_plugin_client_VST2.mm} | 2 +- .../juce_audio_plugin_client_VST3.cpp | 4288 ++++++++++++++++- .../juce_audio_plugin_client_VST3.mm | 26 + .../utility/juce_PluginUtilities.cpp | 156 - .../format_types/VST3_SDK/JUCE_README.md | 5 +- .../format_types/VST3_SDK/LICENSE.txt | 5 +- .../format_types/VST3_SDK/README.md | 267 +- .../VST3_SDK/VST3_License_Agreement.pdf | Bin 160002 -> 135822 bytes .../VST3_SDK/VST3_Usage_Guidelines.pdf | Bin 196713 -> 307448 bytes .../format_types/VST3_SDK/base/LICENSE.txt | 2 +- .../format_types/VST3_SDK/base/README.md | 5 +- .../VST3_SDK/base/source/baseiids.cpp | 2 +- .../base/source/classfactoryhelpers.h | 2 +- .../VST3_SDK/base/source/fbuffer.cpp | 14 +- .../VST3_SDK/base/source/fbuffer.h | 2 +- .../VST3_SDK/base/source/fcommandline.h | 366 ++ .../VST3_SDK/base/source/fdebug.cpp | 25 +- .../VST3_SDK/base/source/fdebug.h | 7 +- .../VST3_SDK/base/source/fobject.cpp | 233 +- .../VST3_SDK/base/source/fobject.h | 25 +- .../VST3_SDK/base/source/fstreamer.cpp | 48 +- .../VST3_SDK/base/source/fstreamer.h | 10 +- .../VST3_SDK/base/source/fstring.cpp | 478 +- .../VST3_SDK/base/source/fstring.h | 25 +- .../VST3_SDK/base/source/updatehandler.cpp | 19 +- .../VST3_SDK/base/source/updatehandler.h | 27 +- .../VST3_SDK/base/thread/include/flock.h | 4 +- .../VST3_SDK/base/thread/source/flock.cpp | 2 +- .../format_types/VST3_SDK/helper.manifest | 10 + .../VST3_SDK/pluginterfaces/LICENSE.txt | 5 +- .../VST3_SDK/pluginterfaces/README.md | 4 +- .../pluginterfaces/base/conststringtable.cpp | 2 +- .../pluginterfaces/base/conststringtable.h | 2 +- .../VST3_SDK/pluginterfaces/base/falignpush.h | 4 + .../VST3_SDK/pluginterfaces/base/fplatform.h | 145 +- .../VST3_SDK/pluginterfaces/base/fstrdefs.h | 104 +- .../VST3_SDK/pluginterfaces/base/ftypes.h | 54 +- .../VST3_SDK/pluginterfaces/base/funknown.cpp | 28 +- .../VST3_SDK/pluginterfaces/base/funknown.h | 40 +- .../VST3_SDK/pluginterfaces/base/futils.h | 11 +- .../pluginterfaces/base/ipluginbase.h | 179 +- .../base/iplugincompatibility.h | 122 + .../gui/iplugviewcontentscalesupport.h | 20 +- .../pluginterfaces/vst/ivstattributes.h | 4 +- .../pluginterfaces/vst/ivstaudioprocessor.h | 84 +- .../pluginterfaces/vst/ivsteditcontroller.h | 9 +- .../pluginterfaces/vst/ivstparameterchanges.h | 2 +- .../vst/ivstparameterfunctionname.h | 76 +- .../pluginterfaces/vst/ivsttestplugprovider.h | 2 +- .../VST3_SDK/pluginterfaces/vst/vstspeaker.h | 601 ++- .../VST3_SDK/pluginterfaces/vst/vsttypes.h | 47 +- .../VST3_SDK/public.sdk/LICENSE.txt | 2 +- .../VST3_SDK/public.sdk/README.md | 14 +- .../moduleinfotool/source/main.cpp | 439 ++ .../public.sdk/source/common/memorystream.cpp | 4 +- .../public.sdk/source/common/memorystream.h | 2 +- .../public.sdk/source/common/pluginview.cpp | 3 +- .../public.sdk/source/common/pluginview.h | 6 +- .../public.sdk/source/common/readfile.cpp | 76 + .../public.sdk/source/common/readfile.h | 55 + .../source/vst/hosting/hostclasses.cpp | 177 +- .../source/vst/hosting/hostclasses.h | 91 +- .../public.sdk/source/vst/hosting/module.cpp | 340 ++ .../public.sdk/source/vst/hosting/module.h | 214 + .../source/vst/hosting/module_linux.cpp | 369 ++ .../source/vst/hosting/module_mac.mm | 390 ++ .../source/vst/hosting/module_win32.cpp | 647 +++ .../vst/hosting/pluginterfacesupport.cpp | 19 +- .../source/vst/hosting/pluginterfacesupport.h | 14 +- .../source/vst/moduleinfo/ReadMe.md | 43 + .../public.sdk/source/vst/moduleinfo/json.h | 3403 +++++++++++++ .../source/vst/moduleinfo/jsoncxx.h | 427 ++ .../source/vst/moduleinfo/moduleinfo.h | 100 + .../vst/moduleinfo/moduleinfocreator.cpp | 309 ++ .../source/vst/moduleinfo/moduleinfocreator.h | 67 + .../vst/moduleinfo/moduleinfoparser.cpp | 537 +++ .../source/vst/moduleinfo/moduleinfoparser.h | 68 + .../public.sdk/source/vst/utility/optional.h | 135 + .../source/vst/utility/stringconvert.cpp | 148 + .../source/vst/utility/stringconvert.h | 147 + .../public.sdk/source/vst/utility/uid.h | 294 ++ .../VST3_SDK/public.sdk/source/vst/vstbus.cpp | 5 +- .../VST3_SDK/public.sdk/source/vst/vstbus.h | 16 +- .../public.sdk/source/vst/vstcomponent.cpp | 4 +- .../public.sdk/source/vst/vstcomponent.h | 3 +- .../source/vst/vstcomponentbase.cpp | 4 +- .../public.sdk/source/vst/vstcomponentbase.h | 2 +- .../source/vst/vsteditcontroller.cpp | 60 +- .../public.sdk/source/vst/vsteditcontroller.h | 20 +- .../public.sdk/source/vst/vstinitiids.cpp | 106 +- .../public.sdk/source/vst/vstparameters.cpp | 22 +- .../public.sdk/source/vst/vstparameters.h | 12 +- .../public.sdk/source/vst/vstpresetfile.cpp | 42 +- .../public.sdk/source/vst/vstpresetfile.h | 6 +- .../format_types/juce_ARAHosting.h | 6 + .../format_types/juce_AU_Shared.h | 19 +- .../juce_AudioUnitPluginFormat.mm | 58 +- .../format_types/juce_LADSPAPluginFormat.cpp | 2 +- .../format_types/juce_LV2PluginFormat.cpp | 23 +- .../format_types/juce_LV2SupportLibs.cpp | 5 + .../juce_LegacyAudioParameter.cpp | 10 +- .../format_types/juce_VST3Common.h | 188 +- .../format_types/juce_VST3Headers.h | 90 +- .../format_types/juce_VST3PluginFormat.cpp | 382 +- .../format_types/juce_VST3PluginFormat.h | 1 + .../juce_VST3PluginFormat_test.cpp | 23 +- .../format_types/juce_VSTPluginFormat.cpp | 15 +- .../juce_audio_processors.cpp | 5 +- .../juce_audio_processors.h | 5 +- .../processors/juce_AudioProcessor.cpp | 124 +- .../processors/juce_AudioProcessor.h | 54 +- .../processors/juce_AudioProcessorEditor.cpp | 12 + .../processors/juce_AudioProcessorEditor.h | 17 + .../processors/juce_AudioProcessorGraph.cpp | 566 ++- .../processors/juce_AudioProcessorGraph.h | 17 +- .../juce_GenericAudioProcessorEditor.cpp | 2 +- .../scanning/juce_PluginListComponent.cpp | 91 +- .../scanning/juce_PluginListComponent.h | 2 + .../ARA/juce_ARADocumentController.cpp | 18 + .../ARA/juce_ARADocumentController.h | 9 + .../utilities/ARA/juce_ARAModelObjects.cpp | 2 +- .../utilities/ARA/juce_ARA_utils.h | 3 +- .../utilities/juce_AAXClientExtensions.cpp | 299 ++ .../utilities/juce_AAXClientExtensions.h | 83 + .../utilities/juce_AudioParameterBool.h | 2 + .../utilities/juce_AudioParameterChoice.h | 2 + .../utilities/juce_AudioParameterFloat.cpp | 4 +- .../utilities/juce_AudioParameterFloat.h | 2 + .../utilities/juce_AudioParameterInt.h | 2 + .../juce_AudioProcessorParameterWithID.h | 4 + .../juce_AudioProcessorValueTreeState.cpp | 18 +- .../juce_AudioProcessorValueTreeState.h | 2 + .../juce_NativeScaleFactorNotifier.h | 2 + .../utilities/juce_ParameterAttachments.cpp | 2 +- .../utilities/juce_PluginHostType.cpp | 3 + .../utilities/juce_PluginHostType.h | 3 + .../utilities/juce_RangedAudioParameter.h | 2 + ...dler.cpp => juce_VST2ClientExtensions.cpp} | 10 +- ...kHandler.h => juce_VST2ClientExtensions.h} | 27 +- .../utilities/juce_VST3ClientExtensions.h | 21 +- .../gui/juce_AudioDeviceSelectorComponent.cpp | 11 +- .../gui/juce_AudioThumbnail.cpp | 4 +- .../gui/juce_KeyboardComponentBase.cpp | 10 +- .../gui/juce_MPEKeyboardComponent.cpp | 4 +- .../gui/juce_MPEKeyboardComponent.h | 2 +- .../gui/juce_MidiKeyboardComponent.cpp | 8 +- .../juce_audio_utils/juce_audio_utils.cpp | 20 +- .../juce_audio_utils/juce_audio_utils.h | 2 +- ...oCDBurner.mm => juce_AudioCDBurner_mac.mm} | 0 ...ner.cpp => juce_AudioCDBurner_windows.cpp} | 0 ...eader.cpp => juce_AudioCDReader_linux.cpp} | 0 ...oCDReader.mm => juce_AudioCDReader_mac.mm} | 0 ...der.cpp => juce_AudioCDReader_windows.cpp} | 0 ...oothMidiDevicePairingDialogue_android.cpp} | 4 +- ...BluetoothMidiDevicePairingDialogue_ios.mm} | 0 ...etoothMidiDevicePairingDialogue_linux.cpp} | 0 ...BluetoothMidiDevicePairingDialogue_mac.mm} | 0 ...oothMidiDevicePairingDialogue_windows.cpp} | 0 .../modules/juce_core/containers/juce_Array.h | 4 +- .../juce_core/containers/juce_ArrayBase.h | 2 +- .../juce_core/containers/juce_ListenerList.h | 3 + .../juce_core/containers/juce_Optional.h | 5 +- .../modules/juce_core/containers/juce_Span.h | 150 + .../juce_core/containers/juce_Variant.cpp | 4 +- .../modules/juce_core/files/juce_File.cpp | 2 +- .../juce_core/files/juce_FileSearchPath.cpp | 86 +- .../juce_core/files/juce_FileSearchPath.h | 13 +- .../juce_core/files/juce_common_MimeTypes.cpp | 1 + .../juce_core/files/juce_common_MimeTypes.h | 9 +- .../juce_core/javascript/juce_Javascript.cpp | 10 +- .../modules/juce_core/juce_core.cpp | 91 +- JuceLibraryCode/modules/juce_core/juce_core.h | 14 +- .../juce_core/maths/juce_MathsFunctions.h | 372 +- .../maths/juce_MathsFunctions_test.cpp | 543 +++ .../juce_core/maths/juce_NormalisableRange.h | 8 +- .../modules/juce_core/maths/juce_Random.cpp | 2 +- .../modules/juce_core/maths/juce_Range.h | 11 +- .../modules/juce_core/memory/juce_Reservoir.h | 2 + .../modules/juce_core/misc/juce_EnumHelpers.h | 103 + .../juce_core/misc/juce_EnumHelpers_test.cpp | 94 + .../modules/juce_core/misc/juce_Functional.h | 4 +- ...t.cpp => juce_AndroidDocument_android.cpp} | 0 .../native/juce_BasicNativeHeaders.h | 8 +- ...e_mac_CFHelpers.h => juce_CFHelpers_mac.h} | 0 ...mSmartPtr.h => juce_ComSmartPtr_windows.h} | 0 ...mmonFile.cpp => juce_CommonFile_linux.cpp} | 0 ...droid_Files.cpp => juce_Files_android.cpp} | 0 ...e_linux_Files.cpp => juce_Files_linux.cpp} | 6 +- .../{juce_mac_Files.mm => juce_Files_mac.mm} | 0 ...win32_Files.cpp => juce_Files_windows.cpp} | 0 ...six_IPAddress.h => juce_IPAddress_posix.h} | 0 ...elpers.cpp => juce_JNIHelpers_android.cpp} | 48 +- ...JNIHelpers.h => juce_JNIHelpers_android.h} | 46 +- ...android_Misc.cpp => juce_Misc_android.cpp} | 0 ...NamedPipe.cpp => juce_NamedPipe_posix.cpp} | 0 ...d_Network.cpp => juce_Network_android.cpp} | 0 ...curl_Network.cpp => juce_Network_curl.cpp} | 15 +- ...nux_Network.cpp => juce_Network_linux.cpp} | 2 +- ...uce_mac_Network.mm => juce_Network_mac.mm} | 5 + ...2_Network.cpp => juce_Network_windows.cpp} | 0 ...c_ObjCHelpers.h => juce_ObjCHelpers_mac.h} | 50 +- .../native/juce_PlatformTimerListener.h | 32 + .../native/juce_PlatformTimer_generic.cpp | 149 + .../native/juce_PlatformTimer_windows.cpp | 68 + ...Registry.cpp => juce_Registry_windows.cpp} | 0 ...pp => juce_RuntimePermissions_android.cpp} | 6 +- ...l_SharedCode.h => juce_SharedCode_intel.h} | 0 ...x_SharedCode.h => juce_SharedCode_posix.h} | 22 +- ...uce_mac_Strings.mm => juce_Strings_mac.mm} | 0 ...Stats.cpp => juce_SystemStats_android.cpp} | 0 ...emStats.cpp => juce_SystemStats_linux.cpp} | 6 +- ...SystemStats.mm => juce_SystemStats_mac.mm} | 93 +- ...temStats.cpp => juce_SystemStats_wasm.cpp} | 0 ...Stats.cpp => juce_SystemStats_windows.cpp} | 221 +- ...ities.h => juce_ThreadPriorities_native.h} | 0 ...d_Threads.cpp => juce_Threads_android.cpp} | 2 - ...nux_Threads.cpp => juce_Threads_linux.cpp} | 0 ...uce_mac_Threads.mm => juce_Threads_mac.mm} | 109 +- ...2_Threads.cpp => juce_Threads_windows.cpp} | 0 .../juce_core/network/juce_WebInputStream.cpp | 5 +- .../streams/juce_MemoryInputStream.cpp | 4 +- .../juce_core/system/juce_CompilerWarnings.h | 23 +- .../juce_core/system/juce_StandardHeader.h | 3 +- .../juce_core/system/juce_SystemStats.cpp | 81 +- .../juce_core/system/juce_SystemStats.h | 41 +- .../juce_core/system/juce_TargetPlatform.h | 2 +- .../text/juce_CharacterFunctions.cpp | 2 +- .../modules/juce_core/text/juce_String.cpp | 14 +- .../modules/juce_core/text/juce_String.h | 2 +- .../threads/juce_HighResolutionTimer.cpp | 419 +- .../threads/juce_HighResolutionTimer.h | 32 +- .../modules/juce_core/threads/juce_Thread.cpp | 20 +- .../modules/juce_core/threads/juce_Thread.h | 121 +- .../juce_core/threads/juce_ThreadPool.cpp | 27 +- .../juce_core/threads/juce_ThreadPool.h | 83 +- .../juce_core/threads/juce_WaitableEvent.cpp | 6 +- .../juce_core/threads/juce_WaitableEvent.h | 2 +- .../juce_core/time/juce_RelativeTime.cpp | 12 +- .../juce_core/unit_tests/juce_UnitTest.h | 4 +- .../juce_data_structures.h | 2 +- .../values/juce_CachedValue.h | 11 +- .../broadcasters/juce_LockingAsyncUpdater.cpp | 133 + .../broadcasters/juce_LockingAsyncUpdater.h | 113 + .../modules/juce_events/juce_events.cpp | 18 +- .../modules/juce_events/juce_events.h | 9 +- .../messages/juce_ApplicationBase.cpp | 4 +- .../messages/juce_MessageManager.cpp | 113 +- .../messages/juce_MessageManager.h | 6 +- ...ernal.h => juce_EventLoopInternal_linux.h} | 0 ...nux_EventLoop.h => juce_EventLoop_linux.h} | 0 ...w.h => juce_HiddenMessageWindow_windows.h} | 10 +- ...eManager.mm => juce_MessageManager_ios.mm} | 0 ...eManager.mm => juce_MessageManager_mac.mm} | 337 +- ...MessageQueue.h => juce_MessageQueue_mac.h} | 0 ...ssaging.cpp => juce_Messaging_android.cpp} | 0 ...Messaging.cpp => juce_Messaging_linux.cpp} | 3 + ...ssaging.cpp => juce_Messaging_windows.cpp} | 26 +- .../juce_events/native/juce_RunningInUnity.h | 35 + ...pper.cpp => juce_WinRTWrapper_windows.cpp} | 0 ...TWrapper.h => juce_WinRTWrapper_windows.h} | 0 .../juce_graphics/colour/juce_Colour.cpp | 9 +- .../juce_graphics/colour/juce_Colour.h | 6 +- .../colour/juce_ColourGradient.cpp | 38 +- .../colour/juce_ColourGradient.h | 11 + .../contexts/juce_GraphicsContext.cpp | 4 +- .../fonts/juce_CustomTypeface.cpp | 2 +- .../modules/juce_graphics/fonts/juce_Font.cpp | 22 +- .../fonts/juce_GlyphArrangement.cpp | 8 +- .../juce_graphics/fonts/juce_TextLayout.cpp | 2 +- .../juce_graphics/fonts/juce_Typeface.cpp | 2 +- .../geometry/juce_AffineTransform.cpp | 29 +- .../juce_graphics/geometry/juce_EdgeTable.cpp | 29 +- .../juce_graphics/geometry/juce_Line.h | 14 +- .../juce_graphics/geometry/juce_Path.cpp | 19 +- .../juce_graphics/geometry/juce_Path.h | 10 +- .../geometry/juce_PathIterator.cpp | 20 +- .../geometry/juce_PathStrokeType.cpp | 191 +- .../juce_graphics/geometry/juce_Point.h | 13 +- .../juce_graphics/geometry/juce_Rectangle.h | 8 +- .../image_formats/juce_GIFLoader.cpp | 4 +- .../image_formats/juce_PNGLoader.cpp | 11 +- .../juce_graphics/images/juce_ImageCache.cpp | 7 +- .../juce_graphics/images/juce_ScaledImage.h | 2 + .../modules/juce_graphics/juce_graphics.cpp | 28 +- .../modules/juce_graphics/juce_graphics.h | 8 +- ...ntext.h => juce_CoreGraphicsContext_mac.h} | 5 +- ...ext.mm => juce_CoreGraphicsContext_mac.mm} | 178 +- ...lpers.h => juce_CoreGraphicsHelpers_mac.h} | 0 ... juce_Direct2DGraphicsContext_windows.cpp} | 0 ...=> juce_Direct2DGraphicsContext_windows.h} | 0 ...=> juce_DirectWriteTypeLayout_windows.cpp} | 2 +- ...p => juce_DirectWriteTypeface_windows.cpp} | 0 ...droid_Fonts.cpp => juce_Fonts_android.cpp} | 0 ...type_Fonts.cpp => juce_Fonts_freetype.cpp} | 0 ...e_linux_Fonts.cpp => juce_Fonts_linux.cpp} | 0 .../{juce_mac_Fonts.mm => juce_Fonts_mac.mm} | 2 +- ...win32_Fonts.cpp => juce_Fonts_windows.cpp} | 0 ...t.cpp => juce_GraphicsContext_android.cpp} | 0 ...lpers.cpp => juce_IconHelpers_android.cpp} | 0 ...Helpers.cpp => juce_IconHelpers_linux.cpp} | 0 ...onHelpers.cpp => juce_IconHelpers_mac.cpp} | 0 ...lpers.cpp => juce_IconHelpers_windows.cpp} | 0 .../native/juce_RenderingHelpers.h | 6 +- .../placement/juce_RectanglePlacement.cpp | 2 +- .../enums/juce_AccessibilityEvent.h | 2 +- .../enums/juce_AccessibilityRole.h | 2 +- .../juce_AccessibilityTableInterface.h | 1 + .../juce_AccessibilityHandler.cpp | 110 +- .../accessibility/juce_AccessibilityHandler.h | 20 + .../application/juce_Application.cpp | 6 +- .../juce_gui_basics/buttons/juce_Button.cpp | 93 +- .../juce_gui_basics/buttons/juce_Button.h | 3 +- .../buttons/juce_HyperlinkButton.cpp | 2 +- .../buttons/juce_HyperlinkButton.h | 5 +- .../buttons/juce_ToggleButton.cpp | 2 +- .../buttons/juce_ToggleButton.h | 5 +- .../juce_ApplicationCommandManager.cpp | 2 +- .../components/juce_Component.cpp | 471 +- .../components/juce_Component.h | 98 +- .../components/juce_FocusTraverser.cpp | 114 +- .../components/juce_ModalComponentManager.cpp | 2 +- .../juce_gui_basics/desktop/juce_Desktop.cpp | 4 +- .../juce_gui_basics/desktop/juce_Desktop.h | 6 +- .../juce_gui_basics/desktop/juce_Displays.cpp | 26 +- .../detail/juce_AccessibilityHelpers.cpp | 33 + .../detail/juce_AccessibilityHelpers.h | 70 + .../detail/juce_AlertWindowHelpers.h | 115 + .../detail/juce_ButtonAccessibilityHandler.h | 125 + .../detail/juce_ComponentHelpers.h | 255 + .../detail/juce_CustomMouseCursorInfo.h | 35 + .../detail/juce_FocusHelpers.h | 117 + .../detail/juce_FocusRestorer.h | 46 + .../detail/juce_LookAndFeelHelpers.h | 62 + .../detail/juce_MouseInputSourceImpl.h | 588 +++ .../detail/juce_MouseInputSourceList.h | 154 + .../{mouse => detail}/juce_PointerState.h | 9 +- .../detail/juce_ScalingHelpers.h | 123 + .../detail/juce_ScopedContentSharerImpl.h | 98 + .../juce_ScopedContentSharerInterface.h | 211 + .../detail/juce_ScopedMessageBoxImpl.h | 132 + .../detail/juce_ScopedMessageBoxInterface.h | 60 + ...e_ToolbarItemDragAndDropOverlayComponent.h | 118 + .../detail/juce_TopLevelWindowManager.h | 136 + .../detail/juce_ViewportHelpers.h | 49 + .../detail/juce_WindowingHelpers.h | 52 + .../drawables/juce_DrawableImage.h | 3 +- .../drawables/juce_DrawableText.cpp | 4 +- .../drawables/juce_DrawableText.h | 3 +- .../drawables/juce_SVGParser.cpp | 4 +- .../filebrowser/juce_ContentSharer.cpp | 252 +- .../filebrowser/juce_ContentSharer.h | 106 +- .../filebrowser/juce_FileBrowserComponent.cpp | 3 +- .../filebrowser/juce_FileBrowserComponent.h | 3 +- .../filebrowser/juce_FileChooser.cpp | 2 +- .../filebrowser/juce_FileChooserDialogBox.cpp | 41 +- .../filebrowser/juce_FileChooserDialogBox.h | 3 +- .../filebrowser/juce_FileListComponent.cpp | 11 +- .../juce_FileSearchPathListComponent.cpp | 6 +- .../filebrowser/juce_FileTreeComponent.cpp | 618 ++- .../filebrowser/juce_FileTreeComponent.h | 3 + .../filebrowser/juce_ImagePreviewComponent.h | 3 +- .../juce_gui_basics/juce_gui_basics.cpp | 402 +- .../modules/juce_gui_basics/juce_gui_basics.h | 33 +- .../keyboard/juce_KeyboardFocusTraverser.cpp | 16 +- .../layout/juce_AnimatedPosition.h | 2 +- .../layout/juce_AnimatedPositionBehaviours.h | 2 +- ...uce_BorderedComponentBoundsConstrainer.cpp | 77 + .../juce_BorderedComponentBoundsConstrainer.h | 69 + .../layout/juce_ComponentAnimator.cpp | 4 +- .../layout/juce_ConcertinaPanel.h | 4 +- .../juce_gui_basics/layout/juce_FlexBox.cpp | 16 +- .../juce_gui_basics/layout/juce_Grid.cpp | 1721 ++++--- .../juce_gui_basics/layout/juce_Grid.h | 8 +- .../layout/juce_GroupComponent.h | 4 +- .../layout/juce_ResizableBorderComponent.cpp | 4 + .../layout/juce_ResizableBorderComponent.h | 2 +- .../layout/juce_ResizableCornerComponent.cpp | 9 +- .../layout/juce_ResizableEdgeComponent.cpp | 21 +- .../juce_gui_basics/layout/juce_ScrollBar.h | 3 +- .../juce_gui_basics/layout/juce_SidePanel.h | 3 +- .../layout/juce_StretchableLayoutManager.cpp | 2 +- .../layout/juce_TabbedButtonBar.h | 3 +- .../layout/juce_TabbedComponent.h | 3 +- .../juce_gui_basics/layout/juce_Viewport.cpp | 21 +- .../juce_gui_basics/layout/juce_Viewport.h | 1 + .../lookandfeel/juce_LookAndFeel_V1.cpp | 3 + .../lookandfeel/juce_LookAndFeel_V2.cpp | 98 +- .../lookandfeel/juce_LookAndFeel_V2.h | 7 + .../lookandfeel/juce_LookAndFeel_V3.cpp | 2 + .../lookandfeel/juce_LookAndFeel_V4.cpp | 68 +- .../lookandfeel/juce_LookAndFeel_V4.h | 8 +- .../menus/juce_BurgerMenuComponent.h | 3 +- .../menus/juce_MenuBarComponent.h | 3 +- .../juce_gui_basics/menus/juce_PopupMenu.cpp | 27 +- .../misc/juce_BubbleComponent.cpp | 9 +- .../misc/juce_BubbleComponent.h | 22 +- .../misc/juce_DropShadower.cpp | 26 +- .../misc/juce_JUCESplashScreen.h | 4 +- .../mouse/juce_DragAndDropContainer.cpp | 24 +- .../mouse/juce_MouseCursor.cpp | 2 +- .../juce_gui_basics/mouse/juce_MouseCursor.h | 2 +- .../mouse/juce_MouseInputSource.cpp | 655 +-- .../mouse/juce_MouseInputSource.h | 9 +- .../accessibility/juce_Accessibility.cpp | 33 + ... => juce_AccessibilityElement_windows.cpp} | 31 +- ....h => juce_AccessibilityElement_windows.h} | 5 +- ...mm => juce_AccessibilitySharedCode_mac.mm} | 10 +- ...ity.cpp => juce_Accessibility_android.cpp} | 92 +- ...ssibility.mm => juce_Accessibility_ios.mm} | 35 +- ...ssibility.mm => juce_Accessibility_mac.mm} | 19 +- ...ity.cpp => juce_Accessibility_windows.cpp} | 22 +- ...erfaces.h => juce_ComInterfaces_windows.h} | 8 + ... juce_UIAExpandCollapseProvider_windows.h} | 0 ...r.h => juce_UIAGridItemProvider_windows.h} | 6 +- ...vider.h => juce_UIAGridProvider_windows.h} | 6 +- ...UIAHelpers.h => juce_UIAHelpers_windows.h} | 0 ...der.h => juce_UIAInvokeProvider_windows.h} | 0 ...rBase.h => juce_UIAProviderBase_windows.h} | 0 ...roviders.h => juce_UIAProviders_windows.h} | 28 +- ...h => juce_UIARangeValueProvider_windows.h} | 0 ....h => juce_UIASelectionProvider_windows.h} | 0 ...vider.h => juce_UIATextProvider_windows.h} | 2 +- ...der.h => juce_UIAToggleProvider_windows.h} | 0 ....h => juce_UIATransformProvider_windows.h} | 0 ...ider.h => juce_UIAValueProvider_windows.h} | 0 ...der.h => juce_UIAWindowProvider_windows.h} | 0 ...per.h => juce_WindowsUIAWrapper_windows.h} | 0 .../app/com/rmsl/juce/ComponentPeerView.java | 18 +- .../javaopt/app/com/rmsl/juce/Receiver.java | 42 + ...erer.h => juce_CGMetalLayerRenderer_mac.h} | 92 +- ...rer.cpp => juce_ContentSharer_android.cpp} | 921 ++-- .../native/juce_ContentSharer_ios.cpp | 125 + ...AndDrop.cpp => juce_DragAndDrop_linux.cpp} | 4 +- ...dDrop.cpp => juce_DragAndDrop_windows.cpp} | 2 +- ...ooser.cpp => juce_FileChooser_android.cpp} | 0 ...FileChooser.mm => juce_FileChooser_ios.mm} | 127 +- ...Chooser.cpp => juce_FileChooser_linux.cpp} | 17 +- ...FileChooser.mm => juce_FileChooser_mac.mm} | 32 +- ...ooser.cpp => juce_FileChooser_windows.cpp} | 87 +- ...e_mac_MainMenu.mm => juce_MainMenu_mac.mm} | 17 +- ...MouseCursor.mm => juce_MouseCursor_mac.mm} | 6 +- ...eer.mm => juce_NSViewComponentPeer_mac.mm} | 367 +- .../native/juce_NativeMessageBox_android.cpp | 115 + .../native/juce_NativeMessageBox_ios.mm | 110 + .../native/juce_NativeMessageBox_linux.cpp | 69 + .../native/juce_NativeMessageBox_mac.mm | 134 + .../native/juce_NativeMessageBox_windows.cpp | 348 ++ .../juce_NativeModalWrapperComponent_ios.h | 132 + ...nks.h => juce_PerScreenDisplayLinks_mac.h} | 0 .../juce_ScopedDPIAwarenessDisabler.cpp | 34 + ..._ScopedThreadDPIAwarenessSetter_windows.h} | 0 ...h => juce_ScopedWindowAssociation_linux.h} | 0 ...eer.mm => juce_UIViewComponentPeer_ios.mm} | 172 +- .../native/juce_WindowUtils_android.cpp | 34 + .../native/juce_WindowUtils_ios.mm | 34 + .../native/juce_WindowUtils_linux.cpp | 39 + .../native/juce_WindowUtils_mac.mm | 38 + .../native/juce_WindowUtils_windows.cpp | 59 + ...ndowing.cpp => juce_Windowing_android.cpp} | 1811 ++++--- ...ios_Windowing.mm => juce_Windowing_ios.mm} | 213 +- ...Windowing.cpp => juce_Windowing_linux.cpp} | 155 +- ...mac_Windowing.mm => juce_Windowing_mac.mm} | 343 +- ...ndowing.cpp => juce_Windowing_windows.cpp} | 527 +- .../native/juce_WindowsHooks_windows.cpp} | 78 +- .../native/juce_WindowsHooks_windows.h | 46 + ...11_Symbols.cpp => juce_XSymbols_linux.cpp} | 2 +- ...ux_X11_Symbols.h => juce_XSymbols_linux.h} | 2 +- ...ystem.cpp => juce_XWindowSystem_linux.cpp} | 133 +- ...dowSystem.h => juce_XWindowSystem_linux.h} | 6 + .../native/juce_ios_ContentSharer.cpp | 204 - .../juce_SliderPropertyComponent.cpp | 2 +- .../juce_gui_basics/widgets/juce_ComboBox.cpp | 22 +- .../juce_gui_basics/widgets/juce_ComboBox.h | 3 +- .../widgets/juce_ImageComponent.h | 4 +- .../juce_gui_basics/widgets/juce_Label.cpp | 2 +- .../juce_gui_basics/widgets/juce_Label.h | 4 +- .../juce_gui_basics/widgets/juce_ListBox.cpp | 214 +- .../juce_gui_basics/widgets/juce_ListBox.h | 18 +- .../widgets/juce_ProgressBar.cpp | 33 +- .../widgets/juce_ProgressBar.h | 88 +- .../juce_gui_basics/widgets/juce_Slider.cpp | 41 +- .../juce_gui_basics/widgets/juce_Slider.h | 11 +- .../widgets/juce_TableHeaderComponent.h | 3 +- .../widgets/juce_TableListBox.cpp | 123 +- .../widgets/juce_TableListBox.h | 14 +- .../widgets/juce_TextEditor.cpp | 7 +- .../juce_gui_basics/widgets/juce_TextEditor.h | 7 +- .../juce_gui_basics/widgets/juce_Toolbar.cpp | 2 +- .../juce_gui_basics/widgets/juce_Toolbar.h | 3 +- .../widgets/juce_ToolbarItemComponent.cpp | 95 +- .../widgets/juce_ToolbarItemComponent.h | 7 +- .../widgets/juce_ToolbarItemPalette.h | 3 +- .../juce_gui_basics/widgets/juce_TreeView.cpp | 2 +- .../juce_gui_basics/widgets/juce_TreeView.h | 3 +- .../windows/juce_AlertWindow.cpp | 215 +- .../windows/juce_AlertWindow.h | 92 +- .../windows/juce_CallOutBox.cpp | 5 +- .../juce_gui_basics/windows/juce_CallOutBox.h | 3 +- .../windows/juce_ComponentPeer.cpp | 6 +- .../windows/juce_ComponentPeer.h | 10 + .../windows/juce_DialogWindow.cpp | 2 +- .../windows/juce_DialogWindow.h | 5 +- .../windows/juce_MessageBoxOptions.cpp | 93 + .../windows/juce_MessageBoxOptions.h | 74 +- .../windows/juce_NativeMessageBox.cpp | 158 + .../windows/juce_NativeMessageBox.h | 37 + .../windows/juce_ScopedMessageBox.cpp} | 41 +- .../windows/juce_ScopedMessageBox.h | 69 + .../windows/juce_TooltipWindow.cpp | 6 +- .../windows/juce_TooltipWindow.h | 3 +- .../windows/juce_TopLevelWindow.cpp | 116 +- .../windows/juce_TopLevelWindow.h | 5 +- ...achement.cpp => juce_VBlankAttachment.cpp} | 0 ...kAttachement.h => juce_VBlankAttachment.h} | 9 +- .../windows/juce_WindowUtils.h | 42 + .../code_editor/juce_CodeEditorComponent.cpp | 10 +- .../code_editor/juce_CodeEditorComponent.h | 9 +- .../documents/juce_FileBasedDocument.cpp | 141 +- .../embedding/juce_AndroidViewComponent.h | 2 + .../embedding/juce_NSViewComponent.h | 2 + .../embedding/juce_UIViewComponent.h | 2 + .../modules/juce_gui_extra/juce_gui_extra.cpp | 36 +- .../modules/juce_gui_extra/juce_gui_extra.h | 2 +- .../misc/juce_ColourSelector.cpp | 6 +- .../misc/juce_KeyMappingEditorComponent.cpp | 55 +- .../misc/juce_KeyMappingEditorComponent.h | 1 + .../misc/juce_WebBrowserComponent.h | 12 +- ....cpp => juce_ActiveXComponent_windows.cpp} | 0 .../native/juce_AndroidViewComponent.cpp | 16 +- ...AppleRemote.mm => juce_AppleRemote_mac.mm} | 0 ...ent.cpp => juce_HWNDComponent_windows.cpp} | 0 ...mponent.mm => juce_NSViewComponent_mac.mm} | 33 +- ...atcher.h => juce_NSViewFrameWatcher_mac.h} | 0 ...cpp => juce_PushNotifications_android.cpp} | 0 ...ons.cpp => juce_PushNotifications_ios.cpp} | 0 ...ons.cpp => juce_PushNotifications_mac.cpp} | 0 ...Icon.cpp => juce_SystemTrayIcon_linux.cpp} | 0 ...ayIcon.cpp => juce_SystemTrayIcon_mac.cpp} | 0 ...on.cpp => juce_SystemTrayIcon_windows.cpp} | 0 ...mponent.mm => juce_UIViewComponent_ios.mm} | 12 +- ...p => juce_WebBrowserComponent_android.cpp} | 10 +- ...cpp => juce_WebBrowserComponent_linux.cpp} | 116 +- ...ent.mm => juce_WebBrowserComponent_mac.mm} | 2 +- ...p => juce_WebBrowserComponent_windows.cpp} | 213 +- ...ent.cpp => juce_XEmbedComponent_linux.cpp} | 0 670 files changed, 47549 insertions(+), 28952 deletions(-) rename JuceLibraryCode/{include_juce_audio_plugin_client_utils.cpp => include_juce_audio_plugin_client_AAX_utils.cpp} (65%) rename JuceLibraryCode/{include_juce_audio_plugin_client_VST_utils.mm => include_juce_audio_plugin_client_VST2.mm} (64%) create mode 100644 JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm rename JuceLibraryCode/modules/juce_audio_basics/native/{juce_mac_CoreAudioLayouts.h => juce_CoreAudioLayouts_mac.h} (98%) rename JuceLibraryCode/modules/juce_audio_basics/native/{juce_mac_CoreAudioTimeConversions.h => juce_CoreAudioTimeConversions_mac.h} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_linux_ALSA.cpp => juce_ALSA_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_win32_ASIO.cpp => juce_ASIO_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_android_Audio.cpp => juce_Audio_android.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_ios_Audio.cpp => juce_Audio_ios.cpp} (99%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_ios_Audio.h => juce_Audio_ios.h} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_linux_Bela.cpp => juce_Bela_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_mac_CoreAudio.cpp => juce_CoreAudio_mac.cpp} (97%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_mac_CoreMidi.mm => juce_CoreMidi_mac.mm} (94%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_win32_DirectSound.cpp => juce_DirectSound_windows.cpp} (98%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_android_HighPerformanceAudioHelpers.h => juce_HighPerformanceAudioHelpers_android.h} (98%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_linux_JackAudio.cpp => juce_JackAudio_linux.cpp} (100%) create mode 100644 JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_android.cpp rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_linux_Midi.cpp => juce_Midi_linux.cpp} (69%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_win32_Midi.cpp => juce_Midi_windows.cpp} (99%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_android_Oboe.cpp => juce_Oboe_android.cpp} (98%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_android_OpenSL.cpp => juce_OpenSL_android.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_devices/native/{juce_win32_WASAPI.cpp => juce_WASAPI_windows.cpp} (96%) delete mode 100644 JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm rename JuceLibraryCode/modules/juce_audio_plugin_client/LV2/{juce_LV2TurtleDumpProgram.cpp => juce_LV2ManifestHelper.cpp} (94%) delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm create mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.cpp rename JuceLibraryCode/modules/juce_audio_plugin_client/{juce_audio_plugin_client_VST_utils.mm => VST3/juce_VST3ManifestHelper.mm} (95%) delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp rename JuceLibraryCode/modules/juce_audio_plugin_client/{utility => detail}/juce_CheckSettingMacros.h (100%) rename JuceLibraryCode/modules/juce_audio_plugin_client/{utility => detail}/juce_CreatePluginFilter.h (100%) rename JuceLibraryCode/modules/juce_audio_plugin_client/{utility => detail}/juce_IncludeModuleHeaders.h (70%) rename JuceLibraryCode/modules/juce_audio_plugin_client/{utility => detail}/juce_IncludeSystemHeaders.h (100%) rename JuceLibraryCode/modules/juce_audio_plugin_client/{utility => detail}/juce_LinuxMessageThread.h (96%) create mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h create mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h create mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp rename JuceLibraryCode/modules/juce_audio_plugin_client/{juce_audio_plugin_client_utils.cpp => juce_audio_plugin_client_VST2.mm} (94%) create mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm delete mode 100644 JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/helper.manifest create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/iplugincompatibility.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h create mode 100644 JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.h rename JuceLibraryCode/modules/juce_audio_processors/utilities/{juce_VSTCallbackHandler.cpp => juce_VST2ClientExtensions.cpp} (64%) rename JuceLibraryCode/modules/juce_audio_processors/utilities/{juce_VSTCallbackHandler.h => juce_VST2ClientExtensions.h} (70%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_mac_AudioCDBurner.mm => juce_AudioCDBurner_mac.mm} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_win32_AudioCDBurner.cpp => juce_AudioCDBurner_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_linux_AudioCDReader.cpp => juce_AudioCDReader_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_mac_AudioCDReader.mm => juce_AudioCDReader_mac.mm} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_win32_AudioCDReader.cpp => juce_AudioCDReader_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_android_BluetoothMidiDevicePairingDialogue.cpp => juce_BluetoothMidiDevicePairingDialogue_android.cpp} (99%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_ios_BluetoothMidiDevicePairingDialogue.mm => juce_BluetoothMidiDevicePairingDialogue_ios.mm} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_linux_BluetoothMidiDevicePairingDialogue.cpp => juce_BluetoothMidiDevicePairingDialogue_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_mac_BluetoothMidiDevicePairingDialogue.mm => juce_BluetoothMidiDevicePairingDialogue_mac.mm} (100%) rename JuceLibraryCode/modules/juce_audio_utils/native/{juce_win_BluetoothMidiDevicePairingDialogue.cpp => juce_BluetoothMidiDevicePairingDialogue_windows.cpp} (100%) create mode 100644 JuceLibraryCode/modules/juce_core/containers/juce_Span.h create mode 100644 JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions_test.cpp create mode 100644 JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers.h create mode 100644 JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers_test.cpp rename JuceLibraryCode/modules/juce_core/native/{juce_android_AndroidDocument.cpp => juce_AndroidDocument_android.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_CFHelpers.h => juce_CFHelpers_mac.h} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_win32_ComSmartPtr.h => juce_ComSmartPtr_windows.h} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_linux_CommonFile.cpp => juce_CommonFile_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_Files.cpp => juce_Files_android.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_linux_Files.cpp => juce_Files_linux.cpp} (98%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_Files.mm => juce_Files_mac.mm} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_win32_Files.cpp => juce_Files_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_posix_IPAddress.h => juce_IPAddress_posix.h} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_JNIHelpers.cpp => juce_JNIHelpers_android.cpp} (96%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_JNIHelpers.h => juce_JNIHelpers_android.h} (96%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_Misc.cpp => juce_Misc_android.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_posix_NamedPipe.cpp => juce_NamedPipe_posix.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_Network.cpp => juce_Network_android.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_curl_Network.cpp => juce_Network_curl.cpp} (97%) rename JuceLibraryCode/modules/juce_core/native/{juce_linux_Network.cpp => juce_Network_linux.cpp} (99%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_Network.mm => juce_Network_mac.mm} (99%) rename JuceLibraryCode/modules/juce_core/native/{juce_win32_Network.cpp => juce_Network_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_ObjCHelpers.h => juce_ObjCHelpers_mac.h} (92%) create mode 100644 JuceLibraryCode/modules/juce_core/native/juce_PlatformTimerListener.h create mode 100644 JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_generic.cpp create mode 100644 JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_windows.cpp rename JuceLibraryCode/modules/juce_core/native/{juce_win32_Registry.cpp => juce_Registry_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_RuntimePermissions.cpp => juce_RuntimePermissions_android.cpp} (97%) rename JuceLibraryCode/modules/juce_core/native/{juce_intel_SharedCode.h => juce_SharedCode_intel.h} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_posix_SharedCode.h => juce_SharedCode_posix.h} (98%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_Strings.mm => juce_Strings_mac.mm} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_SystemStats.cpp => juce_SystemStats_android.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_linux_SystemStats.cpp => juce_SystemStats_linux.cpp} (98%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_SystemStats.mm => juce_SystemStats_mac.mm} (80%) rename JuceLibraryCode/modules/juce_core/native/{juce_wasm_SystemStats.cpp => juce_SystemStats_wasm.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_win32_SystemStats.cpp => juce_SystemStats_windows.cpp} (73%) rename JuceLibraryCode/modules/juce_core/native/{juce_native_ThreadPriorities.h => juce_ThreadPriorities_native.h} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_android_Threads.cpp => juce_Threads_android.cpp} (99%) rename JuceLibraryCode/modules/juce_core/native/{juce_linux_Threads.cpp => juce_Threads_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_core/native/{juce_mac_Threads.mm => juce_Threads_mac.mm} (63%) rename JuceLibraryCode/modules/juce_core/native/{juce_win32_Threads.cpp => juce_Threads_windows.cpp} (100%) create mode 100644 JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.cpp create mode 100644 JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.h rename JuceLibraryCode/modules/juce_events/native/{juce_linux_EventLoopInternal.h => juce_EventLoopInternal_linux.h} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_linux_EventLoop.h => juce_EventLoop_linux.h} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_win32_HiddenMessageWindow.h => juce_HiddenMessageWindow_windows.h} (94%) rename JuceLibraryCode/modules/juce_events/native/{juce_ios_MessageManager.mm => juce_MessageManager_ios.mm} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_mac_MessageManager.mm => juce_MessageManager_mac.mm} (58%) rename JuceLibraryCode/modules/juce_events/native/{juce_osx_MessageQueue.h => juce_MessageQueue_mac.h} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_android_Messaging.cpp => juce_Messaging_android.cpp} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_linux_Messaging.cpp => juce_Messaging_linux.cpp} (99%) rename JuceLibraryCode/modules/juce_events/native/{juce_win32_Messaging.cpp => juce_Messaging_windows.cpp} (94%) create mode 100644 JuceLibraryCode/modules/juce_events/native/juce_RunningInUnity.h rename JuceLibraryCode/modules/juce_events/native/{juce_win32_WinRTWrapper.cpp => juce_WinRTWrapper_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_events/native/{juce_win32_WinRTWrapper.h => juce_WinRTWrapper_windows.h} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_mac_CoreGraphicsContext.h => juce_CoreGraphicsContext_mac.h} (97%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_mac_CoreGraphicsContext.mm => juce_CoreGraphicsContext_mac.mm} (89%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_mac_CoreGraphicsHelpers.h => juce_CoreGraphicsHelpers_mac.h} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_Direct2DGraphicsContext.cpp => juce_Direct2DGraphicsContext_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_Direct2DGraphicsContext.h => juce_Direct2DGraphicsContext_windows.h} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_DirectWriteTypeLayout.cpp => juce_DirectWriteTypeLayout_windows.cpp} (99%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_DirectWriteTypeface.cpp => juce_DirectWriteTypeface_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_android_Fonts.cpp => juce_Fonts_android.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_freetype_Fonts.cpp => juce_Fonts_freetype.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_linux_Fonts.cpp => juce_Fonts_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_mac_Fonts.mm => juce_Fonts_mac.mm} (99%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_Fonts.cpp => juce_Fonts_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_android_GraphicsContext.cpp => juce_GraphicsContext_android.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_android_IconHelpers.cpp => juce_IconHelpers_android.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_linux_IconHelpers.cpp => juce_IconHelpers_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_mac_IconHelpers.cpp => juce_IconHelpers_mac.cpp} (100%) rename JuceLibraryCode/modules/juce_graphics/native/{juce_win32_IconHelpers.cpp => juce_IconHelpers_windows.cpp} (100%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_AlertWindowHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ButtonAccessibilityHandler.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ComponentHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_CustomMouseCursorInfo.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusRestorer.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_LookAndFeelHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceImpl.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceList.h rename JuceLibraryCode/modules/juce_gui_basics/{mouse => detail}/juce_PointerState.h (98%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScalingHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerImpl.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_ViewportHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/detail/juce_WindowingHelpers.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.h create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/accessibility/juce_Accessibility.cpp rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_AccessibilityElement.cpp => juce_AccessibilityElement_windows.cpp} (94%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_AccessibilityElement.h => juce_AccessibilityElement_windows.h} (93%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_mac_AccessibilitySharedCode.mm => juce_AccessibilitySharedCode_mac.mm} (93%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_android_Accessibility.cpp => juce_Accessibility_android.cpp} (94%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_ios_Accessibility.mm => juce_Accessibility_ios.mm} (95%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_mac_Accessibility.mm => juce_Accessibility_mac.mm} (97%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_Accessibility.cpp => juce_Accessibility_windows.cpp} (91%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_ComInterfaces.h => juce_ComInterfaces_windows.h} (97%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAExpandCollapseProvider.h => juce_UIAExpandCollapseProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAGridItemProvider.h => juce_UIAGridItemProvider_windows.h} (94%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAGridProvider.h => juce_UIAGridProvider_windows.h} (94%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAHelpers.h => juce_UIAHelpers_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAInvokeProvider.h => juce_UIAInvokeProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAProviderBase.h => juce_UIAProviderBase_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAProviders.h => juce_UIAProviders_windows.h} (65%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIARangeValueProvider.h => juce_UIARangeValueProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIASelectionProvider.h => juce_UIASelectionProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIATextProvider.h => juce_UIATextProvider_windows.h} (99%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAToggleProvider.h => juce_UIAToggleProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIATransformProvider.h => juce_UIATransformProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAValueProvider.h => juce_UIAValueProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_UIAWindowProvider.h => juce_UIAWindowProvider_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/accessibility/{juce_win32_WindowsUIAWrapper.h => juce_WindowsUIAWrapper_windows.h} (100%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/javaopt/app/com/rmsl/juce/Receiver.java rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_CGMetalLayerRenderer.h => juce_CGMetalLayerRenderer_mac.h} (86%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_android_ContentSharer.cpp => juce_ContentSharer_android.cpp} (53%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_ContentSharer_ios.cpp rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_X11_DragAndDrop.cpp => juce_DragAndDrop_linux.cpp} (98%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_win32_DragAndDrop.cpp => juce_DragAndDrop_windows.cpp} (99%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_android_FileChooser.cpp => juce_FileChooser_android.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_ios_FileChooser.mm => juce_FileChooser_ios.mm} (77%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_linux_FileChooser.cpp => juce_FileChooser_linux.cpp} (91%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_FileChooser.mm => juce_FileChooser_mac.mm} (92%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_win32_FileChooser.cpp => juce_FileChooser_windows.cpp} (90%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_MainMenu.mm => juce_MainMenu_mac.mm} (98%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_MouseCursor.mm => juce_MouseCursor_mac.mm} (96%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_NSViewComponentPeer.mm => juce_NSViewComponentPeer_mac.mm} (90%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeMessageBox_android.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeMessageBox_ios.mm create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeMessageBox_linux.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeMessageBox_mac.mm create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeMessageBox_windows.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_NativeModalWrapperComponent_ios.h rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_PerScreenDisplayLinks.h => juce_PerScreenDisplayLinks_mac.h} (100%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_ScopedDPIAwarenessDisabler.cpp rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_win32_ScopedThreadDPIAwarenessSetter.h => juce_ScopedThreadDPIAwarenessSetter_windows.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_ScopedWindowAssociation.h => juce_ScopedWindowAssociation_linux.h} (100%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_ios_UIViewComponentPeer.mm => juce_UIViewComponentPeer_ios.mm} (93%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowUtils_android.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowUtils_ios.mm create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowUtils_linux.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowUtils_mac.mm create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowUtils_windows.cpp rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_android_Windowing.cpp => juce_Windowing_android.cpp} (55%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_ios_Windowing.mm => juce_Windowing_ios.mm} (77%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_linux_Windowing.cpp => juce_Windowing_linux.cpp} (82%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_mac_Windowing.mm => juce_Windowing_mac.mm} (63%) rename JuceLibraryCode/modules/juce_gui_basics/native/{juce_win32_Windowing.cpp => juce_Windowing_windows.cpp} (92%) rename JuceLibraryCode/modules/{juce_audio_plugin_client/utility/juce_WindowsHooks.h => juce_gui_basics/native/juce_WindowsHooks_windows.cpp} (62%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_WindowsHooks_windows.h rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_X11_Symbols.cpp => juce_XSymbols_linux.cpp} (99%) rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_X11_Symbols.h => juce_XSymbols_linux.h} (99%) rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_XWindowSystem.cpp => juce_XWindowSystem_linux.cpp} (97%) rename JuceLibraryCode/modules/juce_gui_basics/native/{x11/juce_linux_XWindowSystem.h => juce_XWindowSystem_linux.h} (98%) delete mode 100644 JuceLibraryCode/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/windows/juce_MessageBoxOptions.cpp create mode 100644 JuceLibraryCode/modules/juce_gui_basics/windows/juce_NativeMessageBox.cpp rename JuceLibraryCode/modules/{juce_audio_plugin_client/ARA/juce_ARA_Wrapper.cpp => juce_gui_basics/windows/juce_ScopedMessageBox.cpp} (53%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/windows/juce_ScopedMessageBox.h rename JuceLibraryCode/modules/juce_gui_basics/windows/{juce_VBlankAttachement.cpp => juce_VBlankAttachment.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_basics/windows/{juce_VBlankAttachement.h => juce_VBlankAttachment.h} (93%) create mode 100644 JuceLibraryCode/modules/juce_gui_basics/windows/juce_WindowUtils.h rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_win32_ActiveXComponent.cpp => juce_ActiveXComponent_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_AppleRemote.mm => juce_AppleRemote_mac.mm} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_win32_HWNDComponent.cpp => juce_HWNDComponent_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_NSViewComponent.mm => juce_NSViewComponent_mac.mm} (83%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_NSViewFrameWatcher.h => juce_NSViewFrameWatcher_mac.h} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_android_PushNotifications.cpp => juce_PushNotifications_android.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_ios_PushNotifications.cpp => juce_PushNotifications_ios.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_PushNotifications.cpp => juce_PushNotifications_mac.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_linux_X11_SystemTrayIcon.cpp => juce_SystemTrayIcon_linux.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_SystemTrayIcon.cpp => juce_SystemTrayIcon_mac.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_win32_SystemTrayIcon.cpp => juce_SystemTrayIcon_windows.cpp} (100%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_ios_UIViewComponent.mm => juce_UIViewComponent_ios.mm} (90%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_android_WebBrowserComponent.cpp => juce_WebBrowserComponent_android.cpp} (98%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_linux_X11_WebBrowserComponent.cpp => juce_WebBrowserComponent_linux.cpp} (91%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_mac_WebBrowserComponent.mm => juce_WebBrowserComponent_mac.mm} (99%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_win32_WebBrowserComponent.cpp => juce_WebBrowserComponent_windows.cpp} (78%) rename JuceLibraryCode/modules/juce_gui_extra/native/{juce_linux_XEmbedComponent.cpp => juce_XEmbedComponent_linux.cpp} (100%) diff --git a/Builds/LinuxMakefile/Makefile b/Builds/LinuxMakefile/Makefile index a52233f..93f280f 100644 --- a/Builds/LinuxMakefile/Makefile +++ b/Builds/LinuxMakefile/Makefile @@ -39,7 +39,7 @@ ifeq ($(CONFIG),Debug) TARGET_ARCH := endif - JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70005" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_VST3_CAN_REPLACE_VST2=0" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_USE_XRANDR=0" "-DJUCE_USE_XINERAMA=0" "-DJUCE_USE_XSHM=0" "-DJUCE_USE_XCURSOR=0" "-DJUCE_WEB_BROWSER=0" "-DJUCE_USE_WIN_WEBVIEW2=0" "-DJUCE_ENABLE_LIVE_CONSTANT_EDITOR=0" "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=1" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=1" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=1" "-DJucePlugin_Enable_IAA=0" "-DJucePlugin_Enable_ARA=0" "-DJucePlugin_Name=\"Arpligner\"" "-DJucePlugin_Desc=\"Arpligner\"" "-DJucePlugin_Manufacturer=\"Ywen\"" "-DJucePlugin_ManufacturerWebsite=\"https://github.com/YPares\"" "-DJucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\"" "-DJucePlugin_ManufacturerCode=0x5977656e" "-DJucePlugin_PluginCode=0x446b3237" "-DJucePlugin_IsSynth=0" "-DJucePlugin_WantsMidiInput=1" "-DJucePlugin_ProducesMidiOutput=1" "-DJucePlugin_IsMidiEffect=1" "-DJucePlugin_EditorRequiresKeyboardFocus=0" "-DJucePlugin_Version=0.1" "-DJucePlugin_VersionCode=0x100" "-DJucePlugin_VersionString=\"0.1\"" "-DJucePlugin_VSTUniqueID=JucePlugin_PluginCode" "-DJucePlugin_VSTCategory=kPlugCategEffect" "-DJucePlugin_Vst3Category=\"Fx\"" "-DJucePlugin_AUMainType='aumi'" "-DJucePlugin_AUSubType=JucePlugin_PluginCode" "-DJucePlugin_AUExportPrefix=ArplignerAU" "-DJucePlugin_AUExportPrefixQuoted=\"ArplignerAU\"" "-DJucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_CFBundleIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_AAXProductId=JucePlugin_PluginCode" "-DJucePlugin_AAXCategory=0" "-DJucePlugin_AAXDisableBypass=0" "-DJucePlugin_AAXDisableMultiMono=0" "-DJucePlugin_IAAType=0x6175726d" "-DJucePlugin_IAASubType=JucePlugin_PluginCode" "-DJucePlugin_IAAName=\"Ywen: Arpligner\"" "-DJucePlugin_VSTNumMidiInputs=16" "-DJucePlugin_VSTNumMidiOutputs=16" "-DJucePlugin_ARAContentTypes=0" "-DJucePlugin_ARATransformationFlags=0" "-DJucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\"" "-DJucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\"" "-DJucePlugin_ARACompatibleArchiveIDs=\"\"" "-DJUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.1" "-DJUCE_APP_VERSION_HEX=0x100" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules $(CPPFLAGS) + JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DDEBUG=1" "-D_DEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70007" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_VST3_CAN_REPLACE_VST2=0" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_USE_XRANDR=0" "-DJUCE_USE_XINERAMA=0" "-DJUCE_USE_XSHM=0" "-DJUCE_USE_XCURSOR=0" "-DJUCE_WEB_BROWSER=0" "-DJUCE_USE_WIN_WEBVIEW2=0" "-DJUCE_ENABLE_LIVE_CONSTANT_EDITOR=0" "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=1" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=1" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=1" "-DJucePlugin_Enable_IAA=0" "-DJucePlugin_Enable_ARA=0" "-DJucePlugin_Name=\"Arpligner\"" "-DJucePlugin_Desc=\"Arpligner\"" "-DJucePlugin_Manufacturer=\"Ywen\"" "-DJucePlugin_ManufacturerWebsite=\"https://github.com/YPares\"" "-DJucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\"" "-DJucePlugin_ManufacturerCode=0x5977656e" "-DJucePlugin_PluginCode=0x446b3237" "-DJucePlugin_IsSynth=0" "-DJucePlugin_WantsMidiInput=1" "-DJucePlugin_ProducesMidiOutput=1" "-DJucePlugin_IsMidiEffect=1" "-DJucePlugin_EditorRequiresKeyboardFocus=0" "-DJucePlugin_Version=0.1" "-DJucePlugin_VersionCode=0x100" "-DJucePlugin_VersionString=\"0.1\"" "-DJucePlugin_VSTUniqueID=JucePlugin_PluginCode" "-DJucePlugin_VSTCategory=kPlugCategEffect" "-DJucePlugin_Vst3Category=\"Fx\"" "-DJucePlugin_AUMainType='aumi'" "-DJucePlugin_AUSubType=JucePlugin_PluginCode" "-DJucePlugin_AUExportPrefix=ArplignerAU" "-DJucePlugin_AUExportPrefixQuoted=\"ArplignerAU\"" "-DJucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_CFBundleIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_AAXProductId=JucePlugin_PluginCode" "-DJucePlugin_AAXCategory=0" "-DJucePlugin_AAXDisableBypass=0" "-DJucePlugin_AAXDisableMultiMono=0" "-DJucePlugin_IAAType=0x6175726d" "-DJucePlugin_IAASubType=JucePlugin_PluginCode" "-DJucePlugin_IAAName=\"Ywen: Arpligner\"" "-DJucePlugin_VSTNumMidiInputs=16" "-DJucePlugin_VSTNumMidiOutputs=16" "-DJucePlugin_ARAContentTypes=0" "-DJucePlugin_ARATransformationFlags=0" "-DJucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\"" "-DJucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\"" "-DJucePlugin_ARACompatibleArchiveIDs=\"\"" "-DJUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.1" "-DJUCE_APP_VERSION_HEX=0x100" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules $(CPPFLAGS) JUCE_CPPFLAGS_VST3 := JUCE_CFLAGS_VST3 := -fPIC -fvisibility=hidden @@ -64,11 +64,15 @@ ifeq ($(CONFIG),Debug) JUCE_COPYCMD_LV2_PLUGIN := $(JUCE_OUTDIR)/$(JUCE_LV2DIR) $(JUCE_LV2DESTDIR) JUCE_CPPFLAGS_SHARED_CODE := "-DJUCE_SHARED_CODE=1" + JUCE_CFLAGS_SHARED_CODE := -fPIC -fvisibility=hidden JUCE_TARGET_SHARED_CODE := Arpligner.a JUCE_CPPFLAGS_LV2_MANIFEST_HELPER := JUCE_TARGET_LV2_MANIFEST_HELPER := juce_lv2_helper + JUCE_CPPFLAGS_VST3_MANIFEST_HELPER := + JUCE_TARGET_VST3_MANIFEST_HELPER := juce_vst3_helper + JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -fPIC -g -ggdb -O0 $(CFLAGS) JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++20 $(CXXFLAGS) JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) $(shell $(PKG_CONFIG) --libs alsa freetype2 libcurl) -fvisibility=hidden -lrt -ldl -lpthread $(LDFLAGS) @@ -86,7 +90,7 @@ ifeq ($(CONFIG),Release) TARGET_ARCH := endif - JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70005" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_VST3_CAN_REPLACE_VST2=0" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_USE_XRANDR=0" "-DJUCE_USE_XINERAMA=0" "-DJUCE_USE_XSHM=0" "-DJUCE_USE_XCURSOR=0" "-DJUCE_WEB_BROWSER=0" "-DJUCE_USE_WIN_WEBVIEW2=0" "-DJUCE_ENABLE_LIVE_CONSTANT_EDITOR=0" "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=1" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=1" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=1" "-DJucePlugin_Enable_IAA=0" "-DJucePlugin_Enable_ARA=0" "-DJucePlugin_Name=\"Arpligner\"" "-DJucePlugin_Desc=\"Arpligner\"" "-DJucePlugin_Manufacturer=\"Ywen\"" "-DJucePlugin_ManufacturerWebsite=\"https://github.com/YPares\"" "-DJucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\"" "-DJucePlugin_ManufacturerCode=0x5977656e" "-DJucePlugin_PluginCode=0x446b3237" "-DJucePlugin_IsSynth=0" "-DJucePlugin_WantsMidiInput=1" "-DJucePlugin_ProducesMidiOutput=1" "-DJucePlugin_IsMidiEffect=1" "-DJucePlugin_EditorRequiresKeyboardFocus=0" "-DJucePlugin_Version=0.1" "-DJucePlugin_VersionCode=0x100" "-DJucePlugin_VersionString=\"0.1\"" "-DJucePlugin_VSTUniqueID=JucePlugin_PluginCode" "-DJucePlugin_VSTCategory=kPlugCategEffect" "-DJucePlugin_Vst3Category=\"Fx\"" "-DJucePlugin_AUMainType='aumi'" "-DJucePlugin_AUSubType=JucePlugin_PluginCode" "-DJucePlugin_AUExportPrefix=ArplignerAU" "-DJucePlugin_AUExportPrefixQuoted=\"ArplignerAU\"" "-DJucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_CFBundleIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_AAXProductId=JucePlugin_PluginCode" "-DJucePlugin_AAXCategory=0" "-DJucePlugin_AAXDisableBypass=0" "-DJucePlugin_AAXDisableMultiMono=0" "-DJucePlugin_IAAType=0x6175726d" "-DJucePlugin_IAASubType=JucePlugin_PluginCode" "-DJucePlugin_IAAName=\"Ywen: Arpligner\"" "-DJucePlugin_VSTNumMidiInputs=16" "-DJucePlugin_VSTNumMidiOutputs=16" "-DJucePlugin_ARAContentTypes=0" "-DJucePlugin_ARATransformationFlags=0" "-DJucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\"" "-DJucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\"" "-DJucePlugin_ARACompatibleArchiveIDs=\"\"" "-DJUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.1" "-DJUCE_APP_VERSION_HEX=0x100" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules $(CPPFLAGS) + JUCE_CPPFLAGS := $(DEPFLAGS) "-DLINUX=1" "-DNDEBUG=1" "-DJUCE_DISPLAY_SPLASH_SCREEN=1" "-DJUCE_USE_DARK_SPLASH_SCREEN=1" "-DJUCE_PROJUCER_VERSION=0x70007" "-DJUCE_MODULE_AVAILABLE_juce_audio_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_devices=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_formats=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_processors=1" "-DJUCE_MODULE_AVAILABLE_juce_audio_utils=1" "-DJUCE_MODULE_AVAILABLE_juce_core=1" "-DJUCE_MODULE_AVAILABLE_juce_data_structures=1" "-DJUCE_MODULE_AVAILABLE_juce_events=1" "-DJUCE_MODULE_AVAILABLE_juce_graphics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_basics=1" "-DJUCE_MODULE_AVAILABLE_juce_gui_extra=1" "-DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1" "-DJUCE_VST3_CAN_REPLACE_VST2=0" "-DJUCE_STRICT_REFCOUNTEDPOINTER=1" "-DJUCE_USE_XRANDR=0" "-DJUCE_USE_XINERAMA=0" "-DJUCE_USE_XSHM=0" "-DJUCE_USE_XCURSOR=0" "-DJUCE_WEB_BROWSER=0" "-DJUCE_USE_WIN_WEBVIEW2=0" "-DJUCE_ENABLE_LIVE_CONSTANT_EDITOR=0" "-DJucePlugin_Build_VST=0" "-DJucePlugin_Build_VST3=1" "-DJucePlugin_Build_AU=0" "-DJucePlugin_Build_AUv3=0" "-DJucePlugin_Build_AAX=0" "-DJucePlugin_Build_Standalone=1" "-DJucePlugin_Build_Unity=0" "-DJucePlugin_Build_LV2=1" "-DJucePlugin_Enable_IAA=0" "-DJucePlugin_Enable_ARA=0" "-DJucePlugin_Name=\"Arpligner\"" "-DJucePlugin_Desc=\"Arpligner\"" "-DJucePlugin_Manufacturer=\"Ywen\"" "-DJucePlugin_ManufacturerWebsite=\"https://github.com/YPares\"" "-DJucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\"" "-DJucePlugin_ManufacturerCode=0x5977656e" "-DJucePlugin_PluginCode=0x446b3237" "-DJucePlugin_IsSynth=0" "-DJucePlugin_WantsMidiInput=1" "-DJucePlugin_ProducesMidiOutput=1" "-DJucePlugin_IsMidiEffect=1" "-DJucePlugin_EditorRequiresKeyboardFocus=0" "-DJucePlugin_Version=0.1" "-DJucePlugin_VersionCode=0x100" "-DJucePlugin_VersionString=\"0.1\"" "-DJucePlugin_VSTUniqueID=JucePlugin_PluginCode" "-DJucePlugin_VSTCategory=kPlugCategEffect" "-DJucePlugin_Vst3Category=\"Fx\"" "-DJucePlugin_AUMainType='aumi'" "-DJucePlugin_AUSubType=JucePlugin_PluginCode" "-DJucePlugin_AUExportPrefix=ArplignerAU" "-DJucePlugin_AUExportPrefixQuoted=\"ArplignerAU\"" "-DJucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_CFBundleIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXIdentifier=com.Ywen.Arpligner" "-DJucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode" "-DJucePlugin_AAXProductId=JucePlugin_PluginCode" "-DJucePlugin_AAXCategory=0" "-DJucePlugin_AAXDisableBypass=0" "-DJucePlugin_AAXDisableMultiMono=0" "-DJucePlugin_IAAType=0x6175726d" "-DJucePlugin_IAASubType=JucePlugin_PluginCode" "-DJucePlugin_IAAName=\"Ywen: Arpligner\"" "-DJucePlugin_VSTNumMidiInputs=16" "-DJucePlugin_VSTNumMidiOutputs=16" "-DJucePlugin_ARAContentTypes=0" "-DJucePlugin_ARATransformationFlags=0" "-DJucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\"" "-DJucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\"" "-DJucePlugin_ARACompatibleArchiveIDs=\"\"" "-DJUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone" "-DJUCER_LINUX_MAKE_6D53C8B4=1" "-DJUCE_APP_VERSION=0.1" "-DJUCE_APP_VERSION_HEX=0x100" $(shell $(PKG_CONFIG) --cflags alsa freetype2 libcurl) -pthread -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK -I../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK -I../../JuceLibraryCode -I../../JuceLibraryCode/modules $(CPPFLAGS) JUCE_CPPFLAGS_VST3 := JUCE_CFLAGS_VST3 := -fPIC -fvisibility=hidden @@ -111,11 +115,15 @@ ifeq ($(CONFIG),Release) JUCE_COPYCMD_LV2_PLUGIN := $(JUCE_OUTDIR)/$(JUCE_LV2DIR) $(JUCE_LV2DESTDIR) JUCE_CPPFLAGS_SHARED_CODE := "-DJUCE_SHARED_CODE=1" + JUCE_CFLAGS_SHARED_CODE := -fPIC -fvisibility=hidden JUCE_TARGET_SHARED_CODE := Arpligner.a JUCE_CPPFLAGS_LV2_MANIFEST_HELPER := JUCE_TARGET_LV2_MANIFEST_HELPER := juce_lv2_helper + JUCE_CPPFLAGS_VST3_MANIFEST_HELPER := + JUCE_TARGET_VST3_MANIFEST_HELPER := juce_vst3_helper + JUCE_CFLAGS += $(JUCE_CPPFLAGS) $(TARGET_ARCH) -fPIC -O3 $(CFLAGS) JUCE_CXXFLAGS += $(JUCE_CFLAGS) -std=c++20 $(CXXFLAGS) JUCE_LDFLAGS += $(TARGET_ARCH) -L$(JUCE_BINDIR) -L$(JUCE_LIBDIR) $(shell $(PKG_CONFIG) --libs alsa freetype2 libcurl) -fvisibility=hidden -lrt -ldl -lpthread $(LDFLAGS) @@ -142,7 +150,6 @@ OBJECTS_SHARED_CODE := \ $(JUCE_OBJDIR)/include_juce_audio_devices_63111d02.o \ $(JUCE_OBJDIR)/include_juce_audio_formats_15f82001.o \ $(JUCE_OBJDIR)/include_juce_audio_plugin_client_ARA_31a052ed.o \ - $(JUCE_OBJDIR)/include_juce_audio_plugin_client_utils_e32edaee.o \ $(JUCE_OBJDIR)/include_juce_audio_processors_10c03666.o \ $(JUCE_OBJDIR)/include_juce_audio_processors_ara_2a4c6ef7.o \ $(JUCE_OBJDIR)/include_juce_audio_processors_lv2_libs_12bdca08.o \ @@ -155,19 +162,23 @@ OBJECTS_SHARED_CODE := \ $(JUCE_OBJDIR)/include_juce_gui_extra_6dee1c1a.o \ OBJECTS_LV2_MANIFEST_HELPER := \ - $(JUCE_OBJDIR)/juce_LV2TurtleDumpProgram_f9409b5.o \ + $(JUCE_OBJDIR)/juce_LV2ManifestHelper_ddbf2086.o \ -.PHONY: clean all strip VST3 Standalone LV2 LV2_MANIFEST_HELPER +OBJECTS_VST3_MANIFEST_HELPER := \ + $(JUCE_OBJDIR)/juce_VST3ManifestHelper_32018e4.o \ -all : VST3 Standalone LV2 LV2_MANIFEST_HELPER +.PHONY: clean all strip VST3 Standalone LV2 LV2_MANIFEST_HELPER VST3_MANIFEST_HELPER + +all : VST3 Standalone LV2 LV2_MANIFEST_HELPER VST3_MANIFEST_HELPER VST3 : $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) Standalone : $(JUCE_OUTDIR)/$(JUCE_TARGET_STANDALONE_PLUGIN) LV2 : $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) LV2_MANIFEST_HELPER : $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) +VST3_MANIFEST_HELPER : $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3_MANIFEST_HELPER) -$(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) : $(OBJECTS_VST3) $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) +$(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) : $(OBJECTS_VST3) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3_MANIFEST_HELPER) @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl @echo Linking "Arpligner - VST3" @@ -175,19 +186,22 @@ $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) : $(OBJECTS_VST3) $(RESOURCES) $(JUCE_OUTDIR) -$(V_AT)mkdir -p $(JUCE_LIBDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR)/$(JUCE_VST3DIR)/$(JUCE_VST3SUBDIR) - $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) $(OBJECTS_VST3) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(JUCE_LDFLAGS_VST3) $(RESOURCES) $(TARGET_ARCH) + $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3) $(OBJECTS_VST3) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(shell cat $(JUCE_OBJDIR)/execinfo.cmd) $(JUCE_LDFLAGS_VST3) $(RESOURCES) $(TARGET_ARCH) + -$(V_AT)mkdir -p $(JUCE_OUTDIR)/$(JUCE_VST3DIR)/Contents/Resources + -$(V_AT)rm -f $(JUCE_OUTDIR)/$(JUCE_VST3DIR)/Contents/moduleinfo.json + $(V_AT) $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3_MANIFEST_HELPER) -create -version "0.1" -path $(JUCE_OUTDIR)/$(JUCE_VST3DIR) -output $(JUCE_OUTDIR)/$(JUCE_VST3DIR)/Contents/Resources/moduleinfo.json -$(V_AT)[ ! "$(JUCE_VST3DESTDIR)" ] || (mkdir -p $(JUCE_VST3DESTDIR) && cp -R $(JUCE_COPYCMD_VST3)) -$(JUCE_OUTDIR)/$(JUCE_TARGET_STANDALONE_PLUGIN) : $(OBJECTS_STANDALONE_PLUGIN) $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) +$(JUCE_OUTDIR)/$(JUCE_TARGET_STANDALONE_PLUGIN) : $(OBJECTS_STANDALONE_PLUGIN) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl @echo Linking "Arpligner - Standalone Plugin" -$(V_AT)mkdir -p $(JUCE_BINDIR) -$(V_AT)mkdir -p $(JUCE_LIBDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR) - $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_STANDALONE_PLUGIN) $(OBJECTS_STANDALONE_PLUGIN) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(JUCE_LDFLAGS_STANDALONE_PLUGIN) $(RESOURCES) $(TARGET_ARCH) + $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_STANDALONE_PLUGIN) $(OBJECTS_STANDALONE_PLUGIN) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(shell cat $(JUCE_OBJDIR)/execinfo.cmd) $(JUCE_LDFLAGS_STANDALONE_PLUGIN) $(RESOURCES) $(TARGET_ARCH) -$(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) : $(OBJECTS_LV2_PLUGIN) $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) +$(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) : $(OBJECTS_LV2_PLUGIN) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl @echo Linking "Arpligner - LV2 Plugin" @@ -195,11 +209,11 @@ $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) : $(OBJECTS_LV2_PLUGIN) $(RESOURCES) $( -$(V_AT)mkdir -p $(JUCE_LIBDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR)/$(JUCE_LV2DIR) - $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) $(OBJECTS_LV2_PLUGIN) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(JUCE_LDFLAGS_LV2_PLUGIN) $(RESOURCES) $(TARGET_ARCH) + $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_PLUGIN) $(OBJECTS_LV2_PLUGIN) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(shell cat $(JUCE_OBJDIR)/execinfo.cmd) $(JUCE_LDFLAGS_LV2_PLUGIN) $(RESOURCES) $(TARGET_ARCH) $(V_AT) $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) $(JUCE_LV2_FULL_PATH) -$(V_AT)[ ! "$(JUCE_LV2DESTDIR)" ] || (mkdir -p $(JUCE_LV2DESTDIR) && cp -R $(JUCE_COPYCMD_LV2_PLUGIN)) -$(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) : $(OBJECTS_SHARED_CODE) $(RESOURCES) +$(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) : $(OBJECTS_SHARED_CODE) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl @echo Linking "Arpligner - Shared Code" @@ -208,125 +222,139 @@ $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) : $(OBJECTS_SHARED_CODE) $(RESOURCES) -$(V_AT)mkdir -p $(JUCE_OUTDIR) $(V_AT)$(AR) -rcs $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(OBJECTS_SHARED_CODE) -$(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) : $(OBJECTS_LV2_MANIFEST_HELPER) $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) +$(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) : $(OBJECTS_LV2_MANIFEST_HELPER) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl @echo Linking "Arpligner - LV2 Manifest Helper" -$(V_AT)mkdir -p $(JUCE_BINDIR) -$(V_AT)mkdir -p $(JUCE_LIBDIR) -$(V_AT)mkdir -p $(JUCE_OUTDIR) - $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) $(OBJECTS_LV2_MANIFEST_HELPER) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(RESOURCES) $(TARGET_ARCH) + $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_LV2_MANIFEST_HELPER) $(OBJECTS_LV2_MANIFEST_HELPER) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(shell cat $(JUCE_OBJDIR)/execinfo.cmd) $(RESOURCES) $(TARGET_ARCH) + +$(JUCE_OUTDIR)/$(JUCE_TARGET_VST3_MANIFEST_HELPER) : $(OBJECTS_VST3_MANIFEST_HELPER) $(JUCE_OBJDIR)/execinfo.cmd $(RESOURCES) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) + @command -v $(PKG_CONFIG) >/dev/null 2>&1 || { echo >&2 "pkg-config not installed. Please, install it."; exit 1; } + @$(PKG_CONFIG) --print-errors alsa freetype2 libcurl + @echo Linking "Arpligner - VST3 Manifest Helper" + -$(V_AT)mkdir -p $(JUCE_BINDIR) + -$(V_AT)mkdir -p $(JUCE_LIBDIR) + -$(V_AT)mkdir -p $(JUCE_OUTDIR) + $(V_AT)$(CXX) -o $(JUCE_OUTDIR)/$(JUCE_TARGET_VST3_MANIFEST_HELPER) $(OBJECTS_VST3_MANIFEST_HELPER) $(JUCE_OUTDIR)/$(JUCE_TARGET_SHARED_CODE) $(JUCE_LDFLAGS) $(shell cat $(JUCE_OBJDIR)/execinfo.cmd) $(RESOURCES) $(TARGET_ARCH) $(JUCE_OBJDIR)/include_juce_audio_plugin_client_VST3_dd633589.o: ../../JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_plugin_client_VST3.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_VST3) $(JUCE_CFLAGS_VST3) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_plugin_client_Standalone_1a871192.o: ../../JuceLibraryCode/include_juce_audio_plugin_client_Standalone.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_plugin_client_Standalone.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_STANDALONE_PLUGIN) $(JUCE_CFLAGS_STANDALONE_PLUGIN) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_plugin_client_LV2_7d84e0a5.o: ../../JuceLibraryCode/include_juce_audio_plugin_client_LV2.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_plugin_client_LV2.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_LV2_PLUGIN) $(JUCE_CFLAGS_LV2_PLUGIN) -o "$@" -c "$<" $(JUCE_OBJDIR)/ChordStore_e16870ee.o: ../../Source/ChordStore.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling ChordStore.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/Arp_e93b3940.o: ../../Source/Arp.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling Arp.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/PluginProcessor_a059e380.o: ../../Source/PluginProcessor.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling PluginProcessor.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_basics_8a4e984a.o: ../../JuceLibraryCode/include_juce_audio_basics.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_basics.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_devices_63111d02.o: ../../JuceLibraryCode/include_juce_audio_devices.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_devices.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_formats_15f82001.o: ../../JuceLibraryCode/include_juce_audio_formats.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_formats.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_plugin_client_ARA_31a052ed.o: ../../JuceLibraryCode/include_juce_audio_plugin_client_ARA.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_plugin_client_ARA.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" -$(JUCE_OBJDIR)/include_juce_audio_plugin_client_utils_e32edaee.o: ../../JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) - @echo "Compiling include_juce_audio_plugin_client_utils.cpp" - $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" - $(JUCE_OBJDIR)/include_juce_audio_processors_10c03666.o: ../../JuceLibraryCode/include_juce_audio_processors.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_processors.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_processors_ara_2a4c6ef7.o: ../../JuceLibraryCode/include_juce_audio_processors_ara.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_processors_ara.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_processors_lv2_libs_12bdca08.o: ../../JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_processors_lv2_libs.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_audio_utils_9f9fb2d6.o: ../../JuceLibraryCode/include_juce_audio_utils.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_audio_utils.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_core_f26d17db.o: ../../JuceLibraryCode/include_juce_core.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_core.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_data_structures_7471b1e3.o: ../../JuceLibraryCode/include_juce_data_structures.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_data_structures.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_events_fd7d695.o: ../../JuceLibraryCode/include_juce_events.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_events.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_graphics_f817e147.o: ../../JuceLibraryCode/include_juce_graphics.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_graphics.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_gui_basics_e3f79785.o: ../../JuceLibraryCode/include_juce_gui_basics.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_gui_basics.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" $(JUCE_OBJDIR)/include_juce_gui_extra_6dee1c1a.o: ../../JuceLibraryCode/include_juce_gui_extra.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) + -$(V_AT)mkdir -p $(@D) @echo "Compiling include_juce_gui_extra.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_SHARED_CODE) $(JUCE_CFLAGS_SHARED_CODE) -o "$@" -c "$<" -$(JUCE_OBJDIR)/juce_LV2TurtleDumpProgram_f9409b5.o: ../../JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2TurtleDumpProgram.cpp - -$(V_AT)mkdir -p $(JUCE_OBJDIR) - @echo "Compiling juce_LV2TurtleDumpProgram.cpp" +$(JUCE_OBJDIR)/juce_LV2ManifestHelper_ddbf2086.o: ../../JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2ManifestHelper.cpp + -$(V_AT)mkdir -p $(@D) + @echo "Compiling juce_LV2ManifestHelper.cpp" $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_LV2_MANIFEST_HELPER) $(JUCE_CFLAGS_LV2_MANIFEST_HELPER) -o "$@" -c "$<" +$(JUCE_OBJDIR)/juce_VST3ManifestHelper_32018e4.o: ../../JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.cpp + -$(V_AT)mkdir -p $(@D) + @echo "Compiling juce_VST3ManifestHelper.cpp" + $(V_AT)$(CXX) $(JUCE_CXXFLAGS) $(JUCE_CPPFLAGS_VST3_MANIFEST_HELPER) $(JUCE_CFLAGS_VST3_MANIFEST_HELPER) -o "$@" -c "$<" + +$(JUCE_OBJDIR)/execinfo.cmd: + -$(V_AT)mkdir -p $(@D) + -@if [ -z "$(V_AT)" ]; then echo "Checking if we need to link libexecinfo"; fi + $(V_AT)printf "int main() { return 0; }" | $(CXX) -x c++ -o $(@D)/execinfo.x -lexecinfo - >/dev/null 2>&1 && printf -- "-lexecinfo" > "$@" || touch "$@" + clean: @echo Cleaning Arpligner $(V_AT)$(CLEANCMD) @@ -340,3 +368,4 @@ strip: -include $(OBJECTS_LV2_PLUGIN:%.o=%.d) -include $(OBJECTS_SHARED_CODE:%.o=%.d) -include $(OBJECTS_LV2_MANIFEST_HELPER:%.o=%.d) +-include $(OBJECTS_VST3_MANIFEST_HELPER:%.o=%.d) diff --git a/Builds/MacOSX/Arpligner.xcodeproj/project.pbxproj b/Builds/MacOSX/Arpligner.xcodeproj/project.pbxproj index 3e23cd0..0146a7a 100644 --- a/Builds/MacOSX/Arpligner.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/Arpligner.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 1FF01F59FFD06704CB365924, 8E8450DE87AE205D8042FD43, 08CC1F679299D91E35C111D1, + 12EA34C2A3DBF3AD08A31761, ); name = "Arpligner - All"; productName = Arpligner; @@ -31,8 +32,7 @@ 0B6C4BCFE7AA86C7F3C5B724 /* CoreMIDI.framework */ = {isa = PBXBuildFile; fileRef = 98BB7598B8C4B3A8F07E15F1; }; 0C612E69594C74ACCBB924B8 /* AudioToolbox.framework */ = {isa = PBXBuildFile; fileRef = 58776CE1BCD73440CD8E3DC1; }; 1665839F92FFC33F5922DF9C /* include_juce_gui_basics.mm */ = {isa = PBXBuildFile; fileRef = 801FD73C0601DFEF27B9EE96; }; - 1CFA87871B4121436A788060 /* include_juce_audio_plugin_client_VST_utils.mm */ = {isa = PBXBuildFile; fileRef = 63C8827431B3090C6617D5B5; }; - 1D99E694E2435F119D6AE128 /* juce_LV2TurtleDumpProgram.cpp */ = {isa = PBXBuildFile; fileRef = 239BD1BC1A9ECAE71FA498EB; settings = { COMPILER_FLAGS = "-std=c++11 -w -DJUCE_SKIP_PRECOMPILED_HEADER"; }; }; + 22EFA376193C06329957C2FC /* juce_LV2ManifestHelper.cpp */ = {isa = PBXBuildFile; fileRef = 176702336B296859F773D49A; settings = { COMPILER_FLAGS = "-std=c++11 -w -DJUCE_SKIP_PRECOMPILED_HEADER"; }; }; 26D9CF0626CF1E608B6185D7 /* include_juce_audio_processors_lv2_libs.cpp */ = {isa = PBXBuildFile; fileRef = 82F1F27BDC9BF1D807E63411; }; 28C84AA34C9B987203FE05CA /* include_juce_audio_plugin_client_Standalone.cpp */ = {isa = PBXBuildFile; fileRef = BCFF2DC469A5E23DB94F4624; }; 42581295E6F20D72B98E8983 /* include_juce_audio_plugin_client_LV2.mm */ = {isa = PBXBuildFile; fileRef = 64516170C6857F8878FDBA35; }; @@ -43,6 +43,8 @@ 60F22DE05A57E507D1562574 /* ChordStore.cpp */ = {isa = PBXBuildFile; fileRef = BFF752C03C8A75B22C3B7C93; }; 6CC55B16A26F771B79A47C70 /* include_juce_graphics.mm */ = {isa = PBXBuildFile; fileRef = F3BD35D9BE386BD3612B7490; }; 791D85090CB6AE9C19C6573C /* include_juce_audio_utils.mm */ = {isa = PBXBuildFile; fileRef = 3F3F84E40E1CCF841A7F578F; }; + 7AF8B954375E633A141F2EEC /* include_juce_audio_plugin_client_VST3.mm */ = {isa = PBXBuildFile; fileRef = 7503107CE30F5005B8BFC6A9; }; + 87C9784C0524D307817E5C68 /* VST3 Manifest Helper */ = {isa = PBXBuildFile; fileRef = 9B1D520C7E5B6A0215B89EEC; }; 8901EE5F66D6B7CC0C16ED91 /* DiscRecording.framework */ = {isa = PBXBuildFile; fileRef = 2779A996C0A19AF31E35B231; }; 9194007311BB6D70D60F7B47 /* LV2 Plugin */ = {isa = PBXBuildFile; fileRef = 73146A3C65C32A964B1E4E80; }; A46994ED655FFAA0AD2733F8 /* Cocoa.framework */ = {isa = PBXBuildFile; fileRef = 307E86CCEAC765E23AC69DF1; }; @@ -50,7 +52,6 @@ AC3BF9647A1B9D4DD7723801 /* VST3 */ = {isa = PBXBuildFile; fileRef = AD8D1E762DBF18ECE7557158; }; B62EBD5C18D6816B62FC6CA5 /* Arp.cpp */ = {isa = PBXBuildFile; fileRef = 3FC89984DE8AC60800023719; }; B965889A2EEA3C67B9EC4140 /* include_juce_audio_basics.mm */ = {isa = PBXBuildFile; fileRef = DBABCB9F6314534679241DD4; }; - BB8CA42F9699CD9BEFB5E902 /* include_juce_audio_plugin_client_utils.cpp */ = {isa = PBXBuildFile; fileRef = 4E0FEB65CEAFA9E581668377; }; BC87764553F555AF900270ED /* include_juce_audio_devices.mm */ = {isa = PBXBuildFile; fileRef = 1A2ACAE3CACCF1ACE10F440E; }; C5C14347D9830AC97C91D7CA /* LV2 Manifest Helper */ = {isa = PBXBuildFile; fileRef = 648278403DD97498CC3DD742; }; C5FCEF7A75693B7707916AAA /* RecentFilesMenuTemplate.nib */ = {isa = PBXBuildFile; fileRef = 8E6027EBE532F2D378BBE602; }; @@ -58,12 +59,13 @@ CA1548C6B6997A2CD0874B13 /* include_juce_gui_extra.mm */ = {isa = PBXBuildFile; fileRef = 7B83C8FA05CD7B1EE5D1CE04; }; CA4845FA3E1143BE8373C104 /* include_juce_audio_plugin_client_ARA.cpp */ = {isa = PBXBuildFile; fileRef = 29FD8764B8F7E24E5D9B475C; }; CBCEA5211E6D5CBD4A26CA53 /* IOKit.framework */ = {isa = PBXBuildFile; fileRef = B435725B8C1ACD7AD4C3AB9A; }; + CD9B13CF25C776C0B36F2E1F /* Security.framework */ = {isa = PBXBuildFile; fileRef = 7B360B7B0F58DD847D0AB29F; }; D1BE08B9FA627DF2E7782316 /* QuartzCore.framework */ = {isa = PBXBuildFile; fileRef = A2CE797CB570F4BDF0E3C519; }; D7A08B681CB88CEBE90E4513 /* include_juce_data_structures.mm */ = {isa = PBXBuildFile; fileRef = EDDF86E43A5788321064C2DA; }; DE6912E0E29DBF2591CFFCC8 /* include_juce_events.mm */ = {isa = PBXBuildFile; fileRef = 072EAB4468A254911F62544A; }; + F109CF8E0DF0FFD9BFD1CDC9 /* juce_VST3ManifestHelper.mm */ = {isa = PBXBuildFile; fileRef = EF6B6534251ED06591823F40; settings = { COMPILER_FLAGS = "-std=c++17 -fobjc-arc -w -DJUCE_SKIP_PRECOMPILED_HEADER"; }; }; F66D824C1E12F72EBE314183 /* CoreAudioKit.framework */ = {isa = PBXBuildFile; fileRef = BEFCD96CA96E2DC4BD7F4CCB; }; F98D922C1404BE950D980DA1 /* Accelerate.framework */ = {isa = PBXBuildFile; fileRef = 1785F6FB1CC81AFD2554D9AA; }; - FB12A691924FE5B2DCAE24F3 /* include_juce_audio_plugin_client_VST3.cpp */ = {isa = PBXBuildFile; fileRef = FD73DCB2843239812877B3E1; }; FEBA00928B3296F01F02CE71 /* Foundation.framework */ = {isa = PBXBuildFile; fileRef = CD9B62CD7DA9F58DF04FC153; }; /* End PBXBuildFile section */ @@ -71,30 +73,31 @@ 072EAB4468A254911F62544A /* include_juce_events.mm */ /* include_juce_events.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_events.mm; path = ../../JuceLibraryCode/include_juce_events.mm; sourceTree = SOURCE_ROOT; }; 141D247E5E2C55355A44309D /* juce_data_structures */ /* juce_data_structures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_data_structures; path = ../../JuceLibraryCode/modules/juce_data_structures; sourceTree = SOURCE_ROOT; }; 148B49B41720A1BE649B847C /* include_juce_audio_processors_ara.cpp */ /* include_juce_audio_processors_ara.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_processors_ara.cpp; path = ../../JuceLibraryCode/include_juce_audio_processors_ara.cpp; sourceTree = SOURCE_ROOT; }; + 176702336B296859F773D49A /* juce_LV2ManifestHelper.cpp */ /* juce_LV2ManifestHelper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_LV2ManifestHelper.cpp; path = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2ManifestHelper.cpp"; sourceTree = ""; }; 1785F6FB1CC81AFD2554D9AA /* Accelerate.framework */ /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 18A8BF742A51D888640FBF40 /* juce_audio_basics */ /* juce_audio_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_basics; path = ../../JuceLibraryCode/modules/juce_audio_basics; sourceTree = SOURCE_ROOT; }; 19370B4FB18AB641DA9DF90D /* juce_audio_utils */ /* juce_audio_utils */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_utils; path = ../../JuceLibraryCode/modules/juce_audio_utils; sourceTree = SOURCE_ROOT; }; 19BAEE0BE3A5B6DB32907DF1 /* juce_audio_processors */ /* juce_audio_processors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_processors; path = ../../JuceLibraryCode/modules/juce_audio_processors; sourceTree = SOURCE_ROOT; }; 19C850193899AED03418A10E /* JuceHeader.h */ /* JuceHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JuceHeader.h; path = ../../JuceLibraryCode/JuceHeader.h; sourceTree = SOURCE_ROOT; }; 1A2ACAE3CACCF1ACE10F440E /* include_juce_audio_devices.mm */ /* include_juce_audio_devices.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_devices.mm; path = ../../JuceLibraryCode/include_juce_audio_devices.mm; sourceTree = SOURCE_ROOT; }; - 239BD1BC1A9ECAE71FA498EB /* juce_LV2TurtleDumpProgram.cpp */ /* juce_LV2TurtleDumpProgram.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = juce_LV2TurtleDumpProgram.cpp; path = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2TurtleDumpProgram.cpp"; sourceTree = ""; }; + 240F2FD84DF063BD2CEDBDA6 /* Info-VST3_Manifest_Helper.plist */ /* Info-VST3_Manifest_Helper.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-VST3_Manifest_Helper.plist"; path = "Info-VST3_Manifest_Helper.plist"; sourceTree = SOURCE_ROOT; }; 2779A996C0A19AF31E35B231 /* DiscRecording.framework */ /* DiscRecording.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiscRecording.framework; path = System/Library/Frameworks/DiscRecording.framework; sourceTree = SDKROOT; }; 29FD8764B8F7E24E5D9B475C /* include_juce_audio_plugin_client_ARA.cpp */ /* include_juce_audio_plugin_client_ARA.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_ARA.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_ARA.cpp; sourceTree = SOURCE_ROOT; }; 307E86CCEAC765E23AC69DF1 /* Cocoa.framework */ /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 3F3F84E40E1CCF841A7F578F /* include_juce_audio_utils.mm */ /* include_juce_audio_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_utils.mm; sourceTree = SOURCE_ROOT; }; 3FC89984DE8AC60800023719 /* Arp.cpp */ /* Arp.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Arp.cpp; path = ../../Source/Arp.cpp; sourceTree = SOURCE_ROOT; }; - 4E0FEB65CEAFA9E581668377 /* include_juce_audio_plugin_client_utils.cpp */ /* include_juce_audio_plugin_client_utils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_utils.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp; sourceTree = SOURCE_ROOT; }; 52B5D3F229AE1A50670D536F /* PluginProcessor.h */ /* PluginProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginProcessor.h; path = ../../Source/PluginProcessor.h; sourceTree = SOURCE_ROOT; }; 58776CE1BCD73440CD8E3DC1 /* AudioToolbox.framework */ /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 58932B0E2146C8D0C365FCF2 /* Arp.h */ /* Arp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Arp.h; path = ../../Source/Arp.h; sourceTree = SOURCE_ROOT; }; 620F81AFC3169599FFAFC642 /* juce_gui_basics */ /* juce_gui_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_basics; path = ../../JuceLibraryCode/modules/juce_gui_basics; sourceTree = SOURCE_ROOT; }; - 63C8827431B3090C6617D5B5 /* include_juce_audio_plugin_client_VST_utils.mm */ /* include_juce_audio_plugin_client_VST_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_VST_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm; sourceTree = SOURCE_ROOT; }; 64516170C6857F8878FDBA35 /* include_juce_audio_plugin_client_LV2.mm */ /* include_juce_audio_plugin_client_LV2.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_LV2.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_LV2.mm; sourceTree = SOURCE_ROOT; }; 648278403DD97498CC3DD742 /* LV2 Manifest Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = juce_lv2_helper; sourceTree = BUILT_PRODUCTS_DIR; }; 66D9AD8C82A58BAC2EC6234F /* juce_audio_devices */ /* juce_audio_devices */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_devices; path = ../../JuceLibraryCode/modules/juce_audio_devices; sourceTree = SOURCE_ROOT; }; 6E84F18356C4D9C82100A0CB /* JuceLV2Defines.h */ /* JuceLV2Defines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JuceLV2Defines.h; path = ../../JuceLibraryCode/JuceLV2Defines.h; sourceTree = SOURCE_ROOT; }; 6F1FB5E2C006AF7525423605 /* ChordStore.h */ /* ChordStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ChordStore.h; path = ../../Source/ChordStore.h; sourceTree = SOURCE_ROOT; }; 73146A3C65C32A964B1E4E80 /* LV2 Plugin */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Arpligner.so; sourceTree = BUILT_PRODUCTS_DIR; }; + 7503107CE30F5005B8BFC6A9 /* include_juce_audio_plugin_client_VST3.mm */ /* include_juce_audio_plugin_client_VST3.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_VST3.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm; sourceTree = SOURCE_ROOT; }; + 7B360B7B0F58DD847D0AB29F /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 7B83C8FA05CD7B1EE5D1CE04 /* include_juce_gui_extra.mm */ /* include_juce_gui_extra.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_extra.mm; path = ../../JuceLibraryCode/include_juce_gui_extra.mm; sourceTree = SOURCE_ROOT; }; 801FD73C0601DFEF27B9EE96 /* include_juce_gui_basics.mm */ /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; }; 802D4984183E4F53F215D994 /* Standalone Plugin */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Arpligner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -102,6 +105,7 @@ 8DE719B5F11FDEAD420D5272 /* include_juce_audio_formats.mm */ /* include_juce_audio_formats.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_formats.mm; path = ../../JuceLibraryCode/include_juce_audio_formats.mm; sourceTree = SOURCE_ROOT; }; 8E6027EBE532F2D378BBE602 /* RecentFilesMenuTemplate.nib */ /* RecentFilesMenuTemplate.nib */ = {isa = PBXFileReference; lastKnownFileType = file.nib; name = RecentFilesMenuTemplate.nib; path = RecentFilesMenuTemplate.nib; sourceTree = SOURCE_ROOT; }; 98BB7598B8C4B3A8F07E15F1 /* CoreMIDI.framework */ /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; }; + 9B1D520C7E5B6A0215B89EEC /* VST3 Manifest Helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = juce_vst3_helper; sourceTree = BUILT_PRODUCTS_DIR; }; A00D9C6A583FFE5C5C2AF30B /* Info-VST3.plist */ /* Info-VST3.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-VST3.plist"; path = "Info-VST3.plist"; sourceTree = SOURCE_ROOT; }; A2CE797CB570F4BDF0E3C519 /* QuartzCore.framework */ /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; AD8D1E762DBF18ECE7557158 /* VST3 */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Arpligner.vst3; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -125,14 +129,33 @@ ECBB38953FFA7A0AC3B9D9D4 /* juce_graphics */ /* juce_graphics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_graphics; path = ../../JuceLibraryCode/modules/juce_graphics; sourceTree = SOURCE_ROOT; }; ECD35E9F9E00E9DEB08421A4 /* juce_gui_extra */ /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = ../../JuceLibraryCode/modules/juce_gui_extra; sourceTree = SOURCE_ROOT; }; EDDF86E43A5788321064C2DA /* include_juce_data_structures.mm */ /* include_juce_data_structures.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_data_structures.mm; path = ../../JuceLibraryCode/include_juce_data_structures.mm; sourceTree = SOURCE_ROOT; }; + EF6B6534251ED06591823F40 /* juce_VST3ManifestHelper.mm */ /* juce_VST3ManifestHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = juce_VST3ManifestHelper.mm; path = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.mm"; sourceTree = ""; }; F339553FFE81D004EE39D00D /* juce_audio_plugin_client */ /* juce_audio_plugin_client */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_plugin_client; path = ../../JuceLibraryCode/modules/juce_audio_plugin_client; sourceTree = SOURCE_ROOT; }; F3BD35D9BE386BD3612B7490 /* include_juce_graphics.mm */ /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; }; F54BFDDCD27894187482B2FB /* Info-Standalone_Plugin.plist */ /* Info-Standalone_Plugin.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-Standalone_Plugin.plist"; path = "Info-Standalone_Plugin.plist"; sourceTree = SOURCE_ROOT; }; - FD73DCB2843239812877B3E1 /* include_juce_audio_plugin_client_VST3.cpp */ /* include_juce_audio_plugin_client_VST3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_VST3.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp; sourceTree = SOURCE_ROOT; }; FF14056E2662C009B6C9615A /* WebKit.framework */ /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + C5058EAD1CB8F8A32BBBC7DF = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F98D922C1404BE950D980DA1, + 0C612E69594C74ACCBB924B8, + A46994ED655FFAA0AD2733F8, + AB9779F687E7E643B2AA2F54, + F66D824C1E12F72EBE314183, + 0B6C4BCFE7AA86C7F3C5B724, + 8901EE5F66D6B7CC0C16ED91, + FEBA00928B3296F01F02CE71, + CBCEA5211E6D5CBD4A26CA53, + D1BE08B9FA627DF2E7782316, + CD9B13CF25C776C0B36F2E1F, + 0B0970EB5D8C64ADF529B743, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C5459F7C75734F70FF6EAB42 = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -147,6 +170,7 @@ FEBA00928B3296F01F02CE71, CBCEA5211E6D5CBD4A26CA53, D1BE08B9FA627DF2E7782316, + CD9B13CF25C776C0B36F2E1F, 0B0970EB5D8C64ADF529B743, ); runOnlyForDeploymentPostprocessing = 0; @@ -165,6 +189,7 @@ FEBA00928B3296F01F02CE71, CBCEA5211E6D5CBD4A26CA53, D1BE08B9FA627DF2E7782316, + CD9B13CF25C776C0B36F2E1F, 0B0970EB5D8C64ADF529B743, ); runOnlyForDeploymentPostprocessing = 0; @@ -183,6 +208,7 @@ FEBA00928B3296F01F02CE71, CBCEA5211E6D5CBD4A26CA53, D1BE08B9FA627DF2E7782316, + CD9B13CF25C776C0B36F2E1F, 0B0970EB5D8C64ADF529B743, ); runOnlyForDeploymentPostprocessing = 0; @@ -198,6 +224,7 @@ 73146A3C65C32A964B1E4E80, AE1661AAB0EC4ED0AE5BEEE6, 648278403DD97498CC3DD742, + 9B1D520C7E5B6A0215B89EEC, ); name = Products; sourceTree = ""; @@ -241,6 +268,7 @@ F54BFDDCD27894187482B2FB, EB762F2D39AF19B94C4F9D29, D871416B94D2C652359FFC5B, + 240F2FD84DF063BD2CEDBDA6, 8E6027EBE532F2D378BBE602, ); name = Resources; @@ -276,9 +304,7 @@ 29FD8764B8F7E24E5D9B475C, 64516170C6857F8878FDBA35, BCFF2DC469A5E23DB94F4624, - 4E0FEB65CEAFA9E581668377, - 63C8827431B3090C6617D5B5, - FD73DCB2843239812877B3E1, + 7503107CE30F5005B8BFC6A9, BAEF6F05063980815350508D, 148B49B41720A1BE649B847C, 82F1F27BDC9BF1D807E63411, @@ -309,6 +335,7 @@ CD9B62CD7DA9F58DF04FC153, B435725B8C1ACD7AD4C3AB9A, A2CE797CB570F4BDF0E3C519, + 7B360B7B0F58DD847D0AB29F, FF14056E2662C009B6C9615A, ); name = Frameworks; @@ -357,11 +384,13 @@ FE1A85DE620298A53F0BA567, A1B58A09976389DAE92020A6, C85446939CD1FCD71BF4EBF4, + DE14A33F863C6555333E2411, ); buildRules = ( ); dependencies = ( D31F1CEA34C5EF36F0CF3611, + B7E9C36C969B69FF1F1C8598, ); name = "Arpligner - VST3"; productName = Arpligner; @@ -395,14 +424,30 @@ buildRules = ( ); dependencies = ( - 30F06FA4DBA85E0655586C48, F617912211CFAA48B6BEFD01, + 30F06FA4DBA85E0655586C48, ); name = "Arpligner - LV2 Plugin"; productName = Arpligner; productReference = 73146A3C65C32A964B1E4E80; productType = "com.apple.product-type.tool"; }; + DEB6606F03884BEDEE778DAA /* Arpligner - VST3 Manifest Helper */ = { + isa = PBXNativeTarget; + buildConfigurationList = A76535C54000F722178AA671; + buildPhases = ( + 23AB94FBE279ADDA0150DE2B, + C5058EAD1CB8F8A32BBBC7DF, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Arpligner - VST3 Manifest Helper"; + productName = Arpligner; + productReference = 9B1D520C7E5B6A0215B89EEC; + productType = "com.apple.product-type.tool"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -544,6 +589,28 @@ }; }; }; + DEB6606F03884BEDEE778DAA = { + SystemCapabilities = { + com.apple.ApplicationGroups.iOS = { + enabled = 0; + }; + com.apple.HardenedRuntime = { + enabled = 0; + }; + com.apple.InAppPurchase = { + enabled = 0; + }; + com.apple.InterAppAudio = { + enabled = 0; + }; + com.apple.Push = { + enabled = 0; + }; + com.apple.Sandbox = { + enabled = 0; + }; + }; + }; }; }; buildConfigurationList = 4C151062B24DD767F76A70B6; @@ -563,6 +630,7 @@ D62386E97338945F4A6326E3, C4E6413674CFE0CFF64A0DAD, 02528E9AD9542FBC78AEF833, + DEB6606F03884BEDEE778DAA, ); }; /* End PBXProject section */ @@ -595,20 +663,39 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1B760DC8A7D668B1D53938CC /* Generate manifest */ = { + 1B760DC8A7D668B1D53938CC /* Update manifest */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; - name = "Generate manifest"; + name = "Update manifest"; alwaysOutOfDate = 1; shellPath = /bin/sh; - shellScript = "set -e\nxcrun codesign --verify \"$CONFIGURATION_BUILD_DIR/$PRODUCT_NAME\" || xcrun codesign -s - \"$CONFIGURATION_BUILD_DIR/$PRODUCT_NAME\"\n\"$CONFIGURATION_BUILD_DIR/../juce_lv2_helper\" \"$CONFIGURATION_BUILD_DIR/$PRODUCT_NAME\"\nif [ \"$CONFIGURATION\" = \"Debug\" ]; then\nmkdir -p \"$HOME/Library/Audio/Plug-Ins/LV2/\"\n/bin/ln -sfh \"$CONFIGURATION_BUILD_DIR\" \"$HOME/Library/Audio/Plug-Ins/LV2/\"\nfi\nif [ \"$CONFIGURATION\" = \"Release\" ]; then\nmkdir -p \"$HOME/Library/Audio/Plug-Ins/LV2/\"\n/bin/ln -sfh \"$CONFIGURATION_BUILD_DIR\" \"$HOME/Library/Audio/Plug-Ins/LV2/\"\nfi\n"; + shellScript = "set -e\nxcrun codesign --verify \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" || xcrun codesign -f -s - \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\"\n\"$CONFIGURATION_BUILD_DIR/../juce_lv2_helper\" \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\"\nif [ \"$CONFIGURATION\" = \"Debug\" ]; then\nmkdir -p \"$HOME/Library/Audio/Plug-Ins/LV2/\"\n/bin/ln -sfh \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" \"$HOME/Library/Audio/Plug-Ins/LV2/\"\nfi\nif [ \"$CONFIGURATION\" = \"Release\" ]; then\nmkdir -p \"$HOME/Library/Audio/Plug-Ins/LV2/\"\n/bin/ln -sfh \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" \"$HOME/Library/Audio/Plug-Ins/LV2/\"\nfi\n"; + }; + DE14A33F863C6555333E2411 /* Update manifest */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + name = "Update manifest"; + alwaysOutOfDate = 1; + shellPath = /bin/sh; + shellScript = "set -e\nrm -f \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME/Contents/moduleinfo.json\"\nxcrun codesign --verify \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" || xcrun codesign -f -s - \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\"\n\"$CONFIGURATION_BUILD_DIR/juce_vst3_helper\" -create -version \"0.1\" -path \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME\" -output \"$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME/Contents/Resources/moduleinfo.json\"\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 23AB94FBE279ADDA0150DE2B = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F109CF8E0DF0FFD9BFD1CDC9, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 36F6FA7C1D384916E51A2630 = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -621,7 +708,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1D99E694E2435F119D6AE128, + 22EFA376193C06329957C2FC, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -629,7 +716,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FB12A691924FE5B2DCAE24F3, + 7AF8B954375E633A141F2EEC, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -644,8 +731,6 @@ BC87764553F555AF900270ED, C92640C8B20D290B640F897D, CA4845FA3E1143BE8373C104, - BB8CA42F9699CD9BEFB5E902, - 1CFA87871B4121436A788060, 4A9D8F504130822D06D6913E, 02E62F5D38F0ED890958B056, 26D9CF0626CF1E608B6185D7, @@ -674,6 +759,10 @@ isa = PBXTargetDependency; target = 02528E9AD9542FBC78AEF833; }; + 12EA34C2A3DBF3AD08A31761 = { + isa = PBXTargetDependency; + target = DEB6606F03884BEDEE778DAA; + }; 1FF01F59FFD06704CB365924 = { isa = PBXTargetDependency; target = D62386E97338945F4A6326E3; @@ -698,6 +787,10 @@ isa = PBXTargetDependency; target = C4E6413674CFE0CFF64A0DAD; }; + B7E9C36C969B69FF1F1C8598 = { + isa = PBXTargetDependency; + target = DEB6606F03884BEDEE778DAA; + }; D31F1CEA34C5EF36F0CF3611 = { isa = PBXTargetDependency; target = C4E6413674CFE0CFF64A0DAD; @@ -726,7 +819,7 @@ "DEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -832,6 +925,129 @@ }; name = Debug; }; + 1D7A44C00CE0CE90041BF047 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_LINK_OBJC_RUNTIME = NO; + COMBINE_HIDPI_IMAGES = YES; + CONFIGURATION_BUILD_DIR = "$(PROJECT_DIR)/build/$(CONFIGURATION)"; + COPY_PHASE_STRIP = NO; + EXCLUDED_ARCHS = ""; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "_DEBUG=1", + "DEBUG=1", + "JUCE_DISPLAY_SPLASH_SCREEN=1", + "JUCE_USE_DARK_SPLASH_SCREEN=1", + "JUCE_PROJUCER_VERSION=0x70007", + "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", + "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", + "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", + "JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1", + "JUCE_MODULE_AVAILABLE_juce_audio_processors=1", + "JUCE_MODULE_AVAILABLE_juce_audio_utils=1", + "JUCE_MODULE_AVAILABLE_juce_core=1", + "JUCE_MODULE_AVAILABLE_juce_data_structures=1", + "JUCE_MODULE_AVAILABLE_juce_events=1", + "JUCE_MODULE_AVAILABLE_juce_graphics=1", + "JUCE_MODULE_AVAILABLE_juce_gui_basics=1", + "JUCE_MODULE_AVAILABLE_juce_gui_extra=1", + "JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1", + "JUCE_VST3_CAN_REPLACE_VST2=0", + "JUCE_STRICT_REFCOUNTEDPOINTER=1", + "JUCE_USE_XRANDR=0", + "JUCE_USE_XINERAMA=0", + "JUCE_USE_XSHM=0", + "JUCE_USE_XCURSOR=0", + "JUCE_WEB_BROWSER=0", + "JUCE_USE_WIN_WEBVIEW2=0", + "JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0", + "JucePlugin_Build_VST=0", + "JucePlugin_Build_VST3=0", + "JucePlugin_Build_AU=0", + "JucePlugin_Build_AUv3=0", + "JucePlugin_Build_AAX=0", + "JucePlugin_Build_Standalone=0", + "JucePlugin_Build_Unity=0", + "JucePlugin_Build_LV2=0", + "JucePlugin_Enable_IAA=0", + "JucePlugin_Enable_ARA=0", + "JucePlugin_Name=\\\"Arpligner\\\"", + "JucePlugin_Desc=\\\"Arpligner\\\"", + "JucePlugin_Manufacturer=\\\"Ywen\\\"", + "JucePlugin_ManufacturerWebsite=\\\"https://github.com/YPares\\\"", + "JucePlugin_ManufacturerEmail=\\\"yves.pares@gmail.com\\\"", + "JucePlugin_ManufacturerCode=0x5977656e", + "JucePlugin_PluginCode=0x446b3237", + "JucePlugin_IsSynth=0", + "JucePlugin_WantsMidiInput=1", + "JucePlugin_ProducesMidiOutput=1", + "JucePlugin_IsMidiEffect=1", + "JucePlugin_EditorRequiresKeyboardFocus=0", + "JucePlugin_Version=0.1", + "JucePlugin_VersionCode=0x100", + "JucePlugin_VersionString=\\\"0.1\\\"", + "JucePlugin_VSTUniqueID=JucePlugin_PluginCode", + "JucePlugin_VSTCategory=kPlugCategEffect", + "JucePlugin_Vst3Category=\\\"Fx\\\"", + "JucePlugin_AUMainType=\\'aumi\\'", + "JucePlugin_AUSubType=JucePlugin_PluginCode", + "JucePlugin_AUExportPrefix=ArplignerAU", + "JucePlugin_AUExportPrefixQuoted=\\\"ArplignerAU\\\"", + "JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode", + "JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner", + "JucePlugin_AAXIdentifier=com.Ywen.Arpligner", + "JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode", + "JucePlugin_AAXProductId=JucePlugin_PluginCode", + "JucePlugin_AAXCategory=0", + "JucePlugin_AAXDisableBypass=0", + "JucePlugin_AAXDisableMultiMono=0", + "JucePlugin_IAAType=0x6175726d", + "JucePlugin_IAASubType=JucePlugin_PluginCode", + "JucePlugin_IAAName=\\\"Ywen:\\ Arpligner\\\"", + "JucePlugin_VSTNumMidiInputs=16", + "JucePlugin_VSTNumMidiOutputs=16", + "JucePlugin_ARAContentTypes=0", + "JucePlugin_ARATransformationFlags=0", + "JucePlugin_ARAFactoryID=\\\"com.Ywen.Arpligner.factory\\\"", + "JucePlugin_ARADocumentArchiveID=\\\"com.Ywen.Arpligner.aradocumentarchive.0.1\\\"", + "JucePlugin_ARACompatibleArchiveIDs=\\\"\\\"", + "JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone", + "JUCER_XCODE_MAC_F6D2F4CF=1", + "JUCE_APP_VERSION=0.1", + "JUCE_APP_VERSION_HEX=0x100", + ); + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK", + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU", + "$(inherited)", + ); + INFOPLIST_FILE = Info-VST3_Manifest_Helper.plist; + INFOPLIST_PREPROCESS = NO; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU"; + OTHER_LDFLAGS = "-weak_framework Metal -weak_framework MetalKit"; + PRODUCT_BUNDLE_IDENTIFIER = com.Ywen.Arpligner; + PRODUCT_NAME = "juce_vst3_helper"; + USE_HEADERMAP = NO; + VALID_ARCHS = "i386 x86_64 arm64 arm64e"; + }; + name = Debug; + }; 26D9B8EF952CF1AD43921A77 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -851,7 +1067,7 @@ "NDEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -979,7 +1195,7 @@ "NDEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1151,7 +1367,7 @@ "NDEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1277,7 +1493,7 @@ "DEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1451,7 +1667,7 @@ "NDEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1576,7 +1792,7 @@ "DEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1701,7 +1917,7 @@ "DEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1835,7 +2051,7 @@ "NDEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -1959,6 +2175,130 @@ }; name = Debug; }; + CC66DC161EC0C2CD0296130D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_LINK_OBJC_RUNTIME = NO; + COMBINE_HIDPI_IMAGES = YES; + CONFIGURATION_BUILD_DIR = "$(PROJECT_DIR)/build/$(CONFIGURATION)"; + DEAD_CODE_STRIPPING = YES; + EXCLUDED_ARCHS = ""; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PREPROCESSOR_DEFINITIONS = ( + "_NDEBUG=1", + "NDEBUG=1", + "JUCE_DISPLAY_SPLASH_SCREEN=1", + "JUCE_USE_DARK_SPLASH_SCREEN=1", + "JUCE_PROJUCER_VERSION=0x70007", + "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", + "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", + "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", + "JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1", + "JUCE_MODULE_AVAILABLE_juce_audio_processors=1", + "JUCE_MODULE_AVAILABLE_juce_audio_utils=1", + "JUCE_MODULE_AVAILABLE_juce_core=1", + "JUCE_MODULE_AVAILABLE_juce_data_structures=1", + "JUCE_MODULE_AVAILABLE_juce_events=1", + "JUCE_MODULE_AVAILABLE_juce_graphics=1", + "JUCE_MODULE_AVAILABLE_juce_gui_basics=1", + "JUCE_MODULE_AVAILABLE_juce_gui_extra=1", + "JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1", + "JUCE_VST3_CAN_REPLACE_VST2=0", + "JUCE_STRICT_REFCOUNTEDPOINTER=1", + "JUCE_USE_XRANDR=0", + "JUCE_USE_XINERAMA=0", + "JUCE_USE_XSHM=0", + "JUCE_USE_XCURSOR=0", + "JUCE_WEB_BROWSER=0", + "JUCE_USE_WIN_WEBVIEW2=0", + "JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0", + "JucePlugin_Build_VST=0", + "JucePlugin_Build_VST3=0", + "JucePlugin_Build_AU=0", + "JucePlugin_Build_AUv3=0", + "JucePlugin_Build_AAX=0", + "JucePlugin_Build_Standalone=0", + "JucePlugin_Build_Unity=0", + "JucePlugin_Build_LV2=0", + "JucePlugin_Enable_IAA=0", + "JucePlugin_Enable_ARA=0", + "JucePlugin_Name=\\\"Arpligner\\\"", + "JucePlugin_Desc=\\\"Arpligner\\\"", + "JucePlugin_Manufacturer=\\\"Ywen\\\"", + "JucePlugin_ManufacturerWebsite=\\\"https://github.com/YPares\\\"", + "JucePlugin_ManufacturerEmail=\\\"yves.pares@gmail.com\\\"", + "JucePlugin_ManufacturerCode=0x5977656e", + "JucePlugin_PluginCode=0x446b3237", + "JucePlugin_IsSynth=0", + "JucePlugin_WantsMidiInput=1", + "JucePlugin_ProducesMidiOutput=1", + "JucePlugin_IsMidiEffect=1", + "JucePlugin_EditorRequiresKeyboardFocus=0", + "JucePlugin_Version=0.1", + "JucePlugin_VersionCode=0x100", + "JucePlugin_VersionString=\\\"0.1\\\"", + "JucePlugin_VSTUniqueID=JucePlugin_PluginCode", + "JucePlugin_VSTCategory=kPlugCategEffect", + "JucePlugin_Vst3Category=\\\"Fx\\\"", + "JucePlugin_AUMainType=\\'aumi\\'", + "JucePlugin_AUSubType=JucePlugin_PluginCode", + "JucePlugin_AUExportPrefix=ArplignerAU", + "JucePlugin_AUExportPrefixQuoted=\\\"ArplignerAU\\\"", + "JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode", + "JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner", + "JucePlugin_AAXIdentifier=com.Ywen.Arpligner", + "JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode", + "JucePlugin_AAXProductId=JucePlugin_PluginCode", + "JucePlugin_AAXCategory=0", + "JucePlugin_AAXDisableBypass=0", + "JucePlugin_AAXDisableMultiMono=0", + "JucePlugin_IAAType=0x6175726d", + "JucePlugin_IAASubType=JucePlugin_PluginCode", + "JucePlugin_IAAName=\\\"Ywen:\\ Arpligner\\\"", + "JucePlugin_VSTNumMidiInputs=16", + "JucePlugin_VSTNumMidiOutputs=16", + "JucePlugin_ARAContentTypes=0", + "JucePlugin_ARATransformationFlags=0", + "JucePlugin_ARAFactoryID=\\\"com.Ywen.Arpligner.factory\\\"", + "JucePlugin_ARADocumentArchiveID=\\\"com.Ywen.Arpligner.aradocumentarchive.0.1\\\"", + "JucePlugin_ARACompatibleArchiveIDs=\\\"\\\"", + "JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone", + "JUCER_XCODE_MAC_F6D2F4CF=1", + "JUCE_APP_VERSION=0.1", + "JUCE_APP_VERSION_HEX=0x100", + ); + GCC_VERSION = com.apple.compilers.llvm.clang.1_0; + HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK", + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", + "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU", + "$(inherited)", + ); + INFOPLIST_FILE = Info-VST3_Manifest_Helper.plist; + INFOPLIST_PREPROCESS = NO; + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_HEADER_SEARCH_PATHS = "$(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv/src $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lilv $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sratom $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord/src $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/sord $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/serd $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK/lv2 $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/LV2_SDK $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK $(SRCROOT)/../../JuceLibraryCode $(SRCROOT)/../../JuceLibraryCode/modules $(SRCROOT)/../../JuceLibraryCode/modules/juce_audio_plugin_client/AU"; + OTHER_LDFLAGS = "-weak_framework Metal -weak_framework MetalKit"; + PRODUCT_BUNDLE_IDENTIFIER = com.Ywen.Arpligner; + PRODUCT_NAME = "juce_vst3_helper"; + USE_HEADERMAP = NO; + VALID_ARCHS = "i386 x86_64 arm64 arm64e"; + }; + name = Release; + }; EC1941DDE96BF0611C0F87EA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1984,7 +2324,7 @@ "DEBUG=1", "JUCE_DISPLAY_SPLASH_SCREEN=1", "JUCE_USE_DARK_SPLASH_SCREEN=1", - "JUCE_PROJUCER_VERSION=0x70005", + "JUCE_PROJUCER_VERSION=0x70007", "JUCE_MODULE_AVAILABLE_juce_audio_basics=1", "JUCE_MODULE_AVAILABLE_juce_audio_devices=1", "JUCE_MODULE_AVAILABLE_juce_audio_formats=1", @@ -2139,6 +2479,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + A76535C54000F722178AA671 = { + isa = XCConfigurationList; + buildConfigurations = ( + 1D7A44C00CE0CE90041BF047, + CC66DC161EC0C2CD0296130D, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; C879EF1BE4E4DBB9F1AA9A96 = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Builds/VisualStudio2022/Arpligner.sln b/Builds/VisualStudio2022/Arpligner.sln index de5b0f0..a1be0cf 100644 --- a/Builds/VisualStudio2022/Arpligner.sln +++ b/Builds/VisualStudio2022/Arpligner.sln @@ -10,6 +10,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arpligner - VST3", "Arpligner_VST3.vcxproj", "{E824BD2D-A412-C5C3-7E71-8CD7ED772DC6}" ProjectSection(ProjectDependencies) = postProject {A09AD2B7-9E30-77F4-89BE-B3176531F123} = {A09AD2B7-9E30-77F4-89BE-B3176531F123} + {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5} = {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arpligner - LV2 Plugin", "Arpligner_LV2Plugin.vcxproj", "{483E6907-658C-A15C-C473-418D4B1A3C94}" @@ -22,6 +23,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arpligner - Shared Code", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arpligner - LV2 Manifest Helper", "Arpligner_LV2ManifestHelper.vcxproj", "{9EE57CB5-CAF5-16F9-B6BD-745868A53BA0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Arpligner - VST3 Manifest Helper", "Arpligner_VST3ManifestHelper.vcxproj", "{9FB219C9-D81F-A6EA-BE02-081C36BAE4B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -48,6 +51,10 @@ Global {9EE57CB5-CAF5-16F9-B6BD-745868A53BA0}.Debug|x64.Build.0 = Debug|x64 {9EE57CB5-CAF5-16F9-B6BD-745868A53BA0}.Release|x64.ActiveCfg = Release|x64 {9EE57CB5-CAF5-16F9-B6BD-745868A53BA0}.Release|x64.Build.0 = Release|x64 + {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5}.Debug|x64.ActiveCfg = Debug|x64 + {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5}.Debug|x64.Build.0 = Debug|x64 + {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5}.Release|x64.ActiveCfg = Release|x64 + {9FB219C9-D81F-A6EA-BE02-081C36BAE4B5}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Builds/VisualStudio2022/Arpligner_LV2ManifestHelper.vcxproj b/Builds/VisualStudio2022/Arpligner_LV2ManifestHelper.vcxproj index bc36b57..3874e9d 100644 --- a/Builds/VisualStudio2022/Arpligner_LV2ManifestHelper.vcxproj +++ b/Builds/VisualStudio2022/Arpligner_LV2ManifestHelper.vcxproj @@ -64,7 +64,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDebugDLL true NotUsing @@ -78,7 +78,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\juce_lv2_helper.exe @@ -105,7 +105,7 @@ Full ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDLL true NotUsing @@ -119,7 +119,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\juce_lv2_helper.exe @@ -139,7 +139,7 @@ - + diff --git a/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj b/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj index 7c96721..d3302ba 100644 --- a/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj +++ b/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj @@ -66,7 +66,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDebugDLL true NotUsing @@ -80,7 +80,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\Arpligner.dll @@ -115,7 +115,7 @@ Full ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDLL true NotUsing @@ -129,7 +129,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\Arpligner.dll @@ -157,10 +157,7 @@ - - true - - + true diff --git a/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj.filters b/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj.filters index 729837b..81620d4 100644 --- a/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj.filters +++ b/Builds/VisualStudio2022/Arpligner_LV2Plugin.vcxproj.filters @@ -16,10 +16,7 @@ - - JUCE Modules\juce_audio_plugin_client\LV2 - - + JUCE Modules\juce_audio_plugin_client\LV2 diff --git a/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj b/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj index 827ab83..21f14b6 100644 --- a/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj +++ b/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj @@ -64,7 +64,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) MultiThreadedDebugDLL true NotUsing @@ -78,7 +78,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) $(OutDir)\Arpligner.lib @@ -105,7 +105,7 @@ Full ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) MultiThreadedDLL true NotUsing @@ -119,7 +119,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=1;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;JUCE_SHARED_CODE=1;_LIB;%(PreprocessorDefinitions) $(OutDir)\Arpligner.lib @@ -454,46 +454,46 @@ true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true @@ -679,18 +679,9 @@ true - - true - - - true - true - - true - true @@ -850,18 +841,42 @@ true + + true + true true + + true + true + + true + + + true + + + true + true + + true + + + true + + + true + true @@ -961,6 +976,9 @@ true + + true + true @@ -991,7 +1009,7 @@ true - + true @@ -1030,22 +1048,22 @@ true - + true - + true - + true - + true - + true - + true @@ -1141,6 +1159,9 @@ true + + true + true @@ -1153,6 +1174,9 @@ true + + true + true @@ -1162,67 +1186,73 @@ true - + + true + + + true + + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true @@ -1420,6 +1450,9 @@ true + + true + true @@ -1444,19 +1477,19 @@ true - + true - + true - + true - + true - + true @@ -1735,40 +1768,40 @@ true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true @@ -1840,6 +1873,9 @@ true + + true + true @@ -1912,6 +1948,9 @@ true + + true + true @@ -2035,52 +2074,79 @@ true + + true + + + true + + + true + + + true + true - + + true + + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + + true + + + true + + + true + + + true + + true @@ -2182,9 +2248,18 @@ true + + true + + + true + true + + true + true @@ -2194,7 +2269,7 @@ true - + true @@ -2251,43 +2326,43 @@ true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true - + true @@ -2297,7 +2372,6 @@ - /bigobj %(AdditionalOptions) @@ -2354,8 +2428,8 @@ - - + + @@ -2450,8 +2524,8 @@ - - + + @@ -2554,12 +2628,13 @@ - - - - - - + + + + + + + @@ -2635,6 +2710,7 @@ + @@ -2654,6 +2730,7 @@ + @@ -2690,8 +2767,18 @@ + + + + + + + + + + @@ -2732,6 +2819,7 @@ + @@ -2744,8 +2832,8 @@ + - @@ -2779,6 +2867,7 @@ + @@ -2821,20 +2910,22 @@ + - - - - - - - - + + + + + + + + + @@ -2925,6 +3016,7 @@ + @@ -2938,12 +3030,13 @@ - - - + + + + + - - + @@ -3000,10 +3093,10 @@ - - + + + - @@ -3039,6 +3132,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -3069,6 +3182,7 @@ + @@ -3114,36 +3228,37 @@ - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + + + + + @@ -3181,10 +3296,12 @@ + - + + @@ -3212,7 +3329,7 @@ - + @@ -3229,6 +3346,7 @@ + diff --git a/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj.filters b/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj.filters index 2415448..2f64145 100644 --- a/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj.filters +++ b/Builds/VisualStudio2022/Arpligner_SharedCode.vcxproj.filters @@ -137,11 +137,8 @@ {E824435F-FC7B-10BE-5D1A-5DACC51A8836} - - {163782D5-FAD8-1C08-AED0-7400D3946760} - - - {09E4D4E3-1D92-962B-C66E-DD8C8C935FF6} + + {50461EB0-68B5-5582-2DE2-81B0FE171231} {BA0A76FA-458F-0B1C-02E9-ECFBF81140EC} @@ -302,12 +299,30 @@ {DAF30656-5915-0E45-C4E4-54439617D525} + + {600076D4-829D-CE7A-272C-832A4BBC40AB} + + + {C02D05C7-CD20-9901-2F02-95A9BD7FA797} + + + {47771136-6D29-90C7-2C6E-1728E7D1C485} + + + {3E938566-9812-78C0-9E81-75858F44C51F} + {9266EA90-6A0A-5DDB-9CB7-966BEF03BA5C} {9C713CBA-A9E2-5F4E-F83C-2CAB8533913C} + + {D5B5DC1F-B81B-0449-5E26-15D1367B0C8C} + + + {2741675A-628F-4473-FF8D-45CD2C214CDA} + {63571A07-9AA3-5BB0-1103-0B42A2E6BC9E} @@ -503,6 +518,9 @@ {B331BC33-9770-3DB5-73F2-BC2469ECCF7F} + + {3B09E947-B78C-1758-E072-7FD67F8DCB00} + {46A17AC9-0BFF-B5CE-26D6-B9D1992C88AC} @@ -530,9 +548,6 @@ {C0E5DD5D-F8F1-DD25-67D7-291946AB3828} - - {FE7E6CD5-C7A0-DB20-4E7E-D6E7F08C4578} - {895C2D33-E08D-B1BA-BB36-FC4CA65090C8} @@ -901,49 +916,49 @@ JUCE Modules\juce_audio_devices\native\oboe\src\opensles - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native @@ -1135,21 +1150,9 @@ JUCE Modules\juce_audio_formats - - JUCE Modules\juce_audio_plugin_client\ARA - - - JUCE Modules\juce_audio_plugin_client\utility - JUCE Modules\juce_audio_plugin_client - - JUCE Modules\juce_audio_plugin_client - - - JUCE Modules\juce_audio_plugin_client - JUCE Modules\juce_audio_processors\format @@ -1309,18 +1312,45 @@ JUCE Modules\juce_audio_processors\format_types\VST3_SDK\pluginterfaces\base + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\samples\vst-utilities\moduleinfotool\source + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\common JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\common + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\common + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\utility + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst @@ -1423,6 +1453,9 @@ JUCE Modules\juce_audio_processors\utilities\ARA + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors\utilities @@ -1453,7 +1486,7 @@ JUCE Modules\juce_audio_processors\utilities - + JUCE Modules\juce_audio_processors\utilities @@ -1495,34 +1528,34 @@ JUCE Modules\juce_audio_utils\gui - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native - + JUCE Modules\juce_audio_utils\native @@ -1621,6 +1654,9 @@ JUCE Modules\juce_core\maths + + JUCE Modules\juce_core\maths + JUCE Modules\juce_core\maths @@ -1633,6 +1669,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -1642,82 +1681,88 @@ JUCE Modules\juce_core\misc - + + JUCE Modules\juce_core\native + + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + + JUCE Modules\juce_core\native + + JUCE Modules\juce_core\native @@ -1921,6 +1966,9 @@ JUCE Modules\juce_events\broadcasters + + JUCE Modules\juce_events\broadcasters + JUCE Modules\juce_events\interprocess @@ -1945,25 +1993,25 @@ JUCE Modules\juce_events\messages - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native @@ -2245,46 +2293,46 @@ JUCE Modules\juce_graphics\images - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native @@ -2359,6 +2407,9 @@ JUCE Modules\juce_gui_basics\desktop + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\drawables @@ -2431,6 +2482,9 @@ JUCE Modules\juce_gui_basics\keyboard + + JUCE Modules\juce_gui_basics\layout + JUCE Modules\juce_gui_basics\layout @@ -2554,85 +2608,124 @@ JUCE Modules\juce_gui_basics\mouse - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native\accessibility - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + JUCE Modules\juce_gui_basics\native @@ -2734,9 +2827,18 @@ JUCE Modules\juce_gui_basics\windows + + JUCE Modules\juce_gui_basics\windows + + + JUCE Modules\juce_gui_basics\windows + JUCE Modules\juce_gui_basics\windows + + JUCE Modules\juce_gui_basics\windows + JUCE Modules\juce_gui_basics\windows @@ -2746,7 +2848,7 @@ JUCE Modules\juce_gui_basics\windows - + JUCE Modules\juce_gui_basics\windows @@ -2806,55 +2908,55 @@ JUCE Modules\juce_gui_extra\misc - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native - + JUCE Modules\juce_gui_extra\native @@ -2875,9 +2977,6 @@ JUCE Library Code - - JUCE Library Code - JUCE Library Code @@ -3030,10 +3129,10 @@ JUCE Modules\juce_audio_basics\mpe - + JUCE Modules\juce_audio_basics\native - + JUCE Modules\juce_audio_basics\native @@ -3318,10 +3417,10 @@ JUCE Modules\juce_audio_devices\native\oboe\src\opensles - + JUCE Modules\juce_audio_devices\native - + JUCE Modules\juce_audio_devices\native @@ -3630,23 +3729,26 @@ JUCE Modules\juce_audio_formats - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail + + + JUCE Modules\juce_audio_plugin_client\detail - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail - - JUCE Modules\juce_audio_plugin_client\utility + + JUCE Modules\juce_audio_plugin_client\detail JUCE Modules\juce_audio_plugin_client @@ -3873,6 +3975,9 @@ JUCE Modules\juce_audio_processors\format_types\VST3_SDK\base\source + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\base\source + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\base\source @@ -3930,6 +4035,9 @@ JUCE Modules\juce_audio_processors\format_types\VST3_SDK\pluginterfaces\base + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\pluginterfaces\base + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\pluginterfaces\base @@ -4038,12 +4146,42 @@ JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\common + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\common + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\hosting + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\utility + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\utility + + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\utility + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst @@ -4164,6 +4302,9 @@ JUCE Modules\juce_audio_processors\utilities\ARA + + JUCE Modules\juce_audio_processors\utilities + JUCE Modules\juce_audio_processors\utilities @@ -4200,10 +4341,10 @@ JUCE Modules\juce_audio_processors\utilities - + JUCE Modules\juce_audio_processors\utilities - + JUCE Modules\juce_audio_processors\utilities @@ -4305,6 +4446,9 @@ JUCE Modules\juce_core\containers + + JUCE Modules\juce_core\containers + JUCE Modules\juce_core\containers @@ -4431,6 +4575,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -4446,31 +4593,34 @@ JUCE Modules\juce_core\misc - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + JUCE Modules\juce_core\native - + + JUCE Modules\juce_core\native + + JUCE Modules\juce_core\native @@ -4743,6 +4893,9 @@ JUCE Modules\juce_events\broadcasters + + JUCE Modules\juce_events\broadcasters + JUCE Modules\juce_events\interprocess @@ -4782,22 +4935,25 @@ JUCE Modules\juce_events\messages - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + + JUCE Modules\juce_events\native + + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native - + JUCE Modules\juce_events\native @@ -4968,16 +5124,16 @@ JUCE Modules\juce_graphics\images - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native - + JUCE Modules\juce_graphics\native @@ -5085,6 +5241,66 @@ JUCE Modules\juce_gui_basics\desktop + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + + + JUCE Modules\juce_gui_basics\detail + JUCE Modules\juce_gui_basics\drawables @@ -5175,6 +5391,9 @@ JUCE Modules\juce_gui_basics\layout + + JUCE Modules\juce_gui_basics\layout + JUCE Modules\juce_gui_basics\layout @@ -5310,9 +5529,6 @@ JUCE Modules\juce_gui_basics\mouse - - JUCE Modules\juce_gui_basics\mouse - JUCE Modules\juce_gui_basics\mouse @@ -5322,82 +5538,88 @@ JUCE Modules\juce_gui_basics\mouse - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - + JUCE Modules\juce_gui_basics\native\accessibility - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native - - JUCE Modules\juce_gui_basics\native\x11 + + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native - + + JUCE Modules\juce_gui_basics\native + + + JUCE Modules\juce_gui_basics\native + + JUCE Modules\juce_gui_basics\native - + JUCE Modules\juce_gui_basics\native @@ -5511,6 +5733,9 @@ JUCE Modules\juce_gui_basics\windows + + JUCE Modules\juce_gui_basics\windows + JUCE Modules\juce_gui_basics\windows @@ -5520,7 +5745,10 @@ JUCE Modules\juce_gui_basics\windows - + + JUCE Modules\juce_gui_basics\windows + + JUCE Modules\juce_gui_basics\windows @@ -5604,7 +5832,7 @@ JUCE Modules\juce_gui_extra\misc - + JUCE Modules\juce_gui_extra\native @@ -5651,6 +5879,9 @@ JUCE Modules\juce_audio_processors\format_types\VST3_SDK\pluginterfaces + + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk\source\vst\moduleinfo + JUCE Modules\juce_audio_processors\format_types\VST3_SDK\public.sdk diff --git a/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj b/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj index 87b9b2b..70a9e1d 100644 --- a/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj +++ b/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj @@ -66,7 +66,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDebugDLL true NotUsing @@ -80,7 +80,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\Arpligner.exe @@ -111,7 +111,7 @@ Full ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDLL true NotUsing @@ -125,7 +125,7 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=1;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) $(OutDir)\Arpligner.exe @@ -149,9 +149,6 @@ - - true - true diff --git a/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj.filters b/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj.filters index dd981d0..cc5eb83 100644 --- a/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj.filters +++ b/Builds/VisualStudio2022/Arpligner_StandalonePlugin.vcxproj.filters @@ -16,9 +16,6 @@ - - JUCE Modules\juce_audio_plugin_client\Standalone - JUCE Modules\juce_audio_plugin_client diff --git a/Builds/VisualStudio2022/Arpligner_VST3.vcxproj b/Builds/VisualStudio2022/Arpligner_VST3.vcxproj index 95dfd7c..0ecd3f4 100644 --- a/Builds/VisualStudio2022/Arpligner_VST3.vcxproj +++ b/Builds/VisualStudio2022/Arpligner_VST3.vcxproj @@ -42,7 +42,7 @@ <_ProjectFileVersion>10.0.30319.1 - .vst3 + .dll $(SolutionDir)$(Platform)\$(Configuration)\VST3\ $(Platform)\$(Configuration)\VST3\ Arpligner @@ -66,7 +66,7 @@ Disabled ProgramDatabase ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDebugDLL true NotUsing @@ -80,10 +80,10 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) - $(OutDir)\Arpligner.vst3 + $(OutDir)\Arpligner.dll true libcmt.lib; msvcrt.lib;;%(IgnoreSpecificDefaultLibraries) true @@ -99,6 +99,18 @@ Arpligner.lib;%(AdditionalDependencies) + + if not exist "$(OutDir)\\Arpligner.vst3\" del /s /q "$(OutDir)\\Arpligner.vst3" && mkdir "$(OutDir)\\Arpligner.vst3" +if not exist "$(OutDir)\\Arpligner.vst3\Contents\" del /s /q "$(OutDir)\\Arpligner.vst3\Contents" && mkdir "$(OutDir)\\Arpligner.vst3\Contents" +if not exist "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win\" del /s /q "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win" && mkdir "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win" + + + + copy /Y "$(OutDir)\Arpligner.dll" "$(OutDir)\Arpligner.vst3\Contents\x86_64-win\Arpligner.vst3" +del /s /q "$(OutDir)/Arpligner.vst3\Contents\moduleinfo.json" +if not exist "$(OutDir)/Arpligner.vst3\Contents\Resources\" del /s /q "$(OutDir)/Arpligner.vst3\Contents\Resources" && mkdir "$(OutDir)/Arpligner.vst3\Contents\Resources" +"$(SolutionDir)$(Platform)\$(Configuration)\VST3 Manifest Helper\juce_vst3_helper.exe" -create -version "0.1" -path "$(OutDir)/Arpligner.vst3" -output "$(OutDir)/Arpligner.vst3\Contents\Resources\moduleinfo.json" + @@ -111,7 +123,7 @@ Full ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name="Arpligner";JucePlugin_Desc="Arpligner";JucePlugin_Manufacturer="Ywen";JucePlugin_ManufacturerWebsite="https://github.com/YPares";JucePlugin_ManufacturerEmail="yves.pares@gmail.com";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString="0.1";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category="Fx";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted="ArplignerAU";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName="Ywen: Arpligner";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID="com.Ywen.Arpligner.factory";JucePlugin_ARADocumentArchiveID="com.Ywen.Arpligner.aradocumentarchive.0.1";JucePlugin_ARACompatibleArchiveIDs="";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) MultiThreadedDLL true NotUsing @@ -125,10 +137,10 @@ ..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lilv;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sratom;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord\src;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\sord;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\serd;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK\lv2;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\LV2_SDK;..\..\JuceLibraryCode\modules\juce_audio_processors\format_types\VST3_SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories) - _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70005;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;JUCE_DISPLAY_SPLASH_SCREEN=1;JUCE_USE_DARK_SPLASH_SCREEN=1;JUCE_PROJUCER_VERSION=0x70007;JUCE_MODULE_AVAILABLE_juce_audio_basics=1;JUCE_MODULE_AVAILABLE_juce_audio_devices=1;JUCE_MODULE_AVAILABLE_juce_audio_formats=1;JUCE_MODULE_AVAILABLE_juce_audio_plugin_client=1;JUCE_MODULE_AVAILABLE_juce_audio_processors=1;JUCE_MODULE_AVAILABLE_juce_audio_utils=1;JUCE_MODULE_AVAILABLE_juce_core=1;JUCE_MODULE_AVAILABLE_juce_data_structures=1;JUCE_MODULE_AVAILABLE_juce_events=1;JUCE_MODULE_AVAILABLE_juce_graphics=1;JUCE_MODULE_AVAILABLE_juce_gui_basics=1;JUCE_MODULE_AVAILABLE_juce_gui_extra=1;JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1;JUCE_VST3_CAN_REPLACE_VST2=0;JUCE_STRICT_REFCOUNTEDPOINTER=1;JUCE_USE_XRANDR=0;JUCE_USE_XINERAMA=0;JUCE_USE_XSHM=0;JUCE_USE_XCURSOR=0;JUCE_WEB_BROWSER=0;JUCE_USE_WIN_WEBVIEW2=0;JUCE_ENABLE_LIVE_CONSTANT_EDITOR=0;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=1;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;JucePlugin_Build_LV2=0;JucePlugin_Enable_IAA=0;JucePlugin_Enable_ARA=0;JucePlugin_Name=\"Arpligner\";JucePlugin_Desc=\"Arpligner\";JucePlugin_Manufacturer=\"Ywen\";JucePlugin_ManufacturerWebsite=\"https://github.com/YPares\";JucePlugin_ManufacturerEmail=\"yves.pares@gmail.com\";JucePlugin_ManufacturerCode=0x5977656e;JucePlugin_PluginCode=0x446b3237;JucePlugin_IsSynth=0;JucePlugin_WantsMidiInput=1;JucePlugin_ProducesMidiOutput=1;JucePlugin_IsMidiEffect=1;JucePlugin_EditorRequiresKeyboardFocus=0;JucePlugin_Version=0.1;JucePlugin_VersionCode=0x100;JucePlugin_VersionString=\"0.1\";JucePlugin_VSTUniqueID=JucePlugin_PluginCode;JucePlugin_VSTCategory=kPlugCategEffect;JucePlugin_Vst3Category=\"Fx\";JucePlugin_AUMainType='aumi';JucePlugin_AUSubType=JucePlugin_PluginCode;JucePlugin_AUExportPrefix=ArplignerAU;JucePlugin_AUExportPrefixQuoted=\"ArplignerAU\";JucePlugin_AUManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_CFBundleIdentifier=com.Ywen.Arpligner;JucePlugin_AAXIdentifier=com.Ywen.Arpligner;JucePlugin_AAXManufacturerCode=JucePlugin_ManufacturerCode;JucePlugin_AAXProductId=JucePlugin_PluginCode;JucePlugin_AAXCategory=0;JucePlugin_AAXDisableBypass=0;JucePlugin_AAXDisableMultiMono=0;JucePlugin_IAAType=0x6175726d;JucePlugin_IAASubType=JucePlugin_PluginCode;JucePlugin_IAAName=\"Ywen: Arpligner\";JucePlugin_VSTNumMidiInputs=16;JucePlugin_VSTNumMidiOutputs=16;JucePlugin_ARAContentTypes=0;JucePlugin_ARATransformationFlags=0;JucePlugin_ARAFactoryID=\"com.Ywen.Arpligner.factory\";JucePlugin_ARADocumentArchiveID=\"com.Ywen.Arpligner.aradocumentarchive.0.1\";JucePlugin_ARACompatibleArchiveIDs=\"\";JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone;JUCER_VS2022_78A503E=1;JUCE_APP_VERSION=0.1;JUCE_APP_VERSION_HEX=0x100;%(PreprocessorDefinitions) - $(OutDir)\Arpligner.vst3 + $(OutDir)\Arpligner.dll true %(IgnoreSpecificDefaultLibraries) false @@ -147,9 +159,21 @@ Arpligner.lib;%(AdditionalDependencies) + + if not exist "$(OutDir)\\Arpligner.vst3\" del /s /q "$(OutDir)\\Arpligner.vst3" && mkdir "$(OutDir)\\Arpligner.vst3" +if not exist "$(OutDir)\\Arpligner.vst3\Contents\" del /s /q "$(OutDir)\\Arpligner.vst3\Contents" && mkdir "$(OutDir)\\Arpligner.vst3\Contents" +if not exist "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win\" del /s /q "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win" && mkdir "$(OutDir)\\Arpligner.vst3\Contents\x86_64-win" + + + + copy /Y "$(OutDir)\Arpligner.dll" "$(OutDir)\Arpligner.vst3\Contents\x86_64-win\Arpligner.vst3" +del /s /q "$(OutDir)/Arpligner.vst3\Contents\moduleinfo.json" +if not exist "$(OutDir)/Arpligner.vst3\Contents\Resources\" del /s /q "$(OutDir)/Arpligner.vst3\Contents\Resources" && mkdir "$(OutDir)/Arpligner.vst3\Contents\Resources" +"$(SolutionDir)$(Platform)\$(Configuration)\VST3 Manifest Helper\juce_vst3_helper.exe" -create -version "0.1" -path "$(OutDir)/Arpligner.vst3" -output "$(OutDir)/Arpligner.vst3\Contents\Resources\moduleinfo.json" + - + true diff --git a/Builds/VisualStudio2022/Arpligner_VST3.vcxproj.filters b/Builds/VisualStudio2022/Arpligner_VST3.vcxproj.filters index 523d811..4069700 100644 --- a/Builds/VisualStudio2022/Arpligner_VST3.vcxproj.filters +++ b/Builds/VisualStudio2022/Arpligner_VST3.vcxproj.filters @@ -16,12 +16,18 @@ - + + JUCE Modules\juce_audio_plugin_client\VST3 + + JUCE Modules\juce_audio_plugin_client\VST3 JUCE Modules\juce_audio_plugin_client + + JUCE Modules\juce_audio_plugin_client + JUCE Library Code diff --git a/JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp b/JuceLibraryCode/include_juce_audio_plugin_client_AAX_utils.cpp similarity index 65% rename from JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp rename to JuceLibraryCode/include_juce_audio_plugin_client_AAX_utils.cpp index d2ad6de..5463093 100644 --- a/JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp +++ b/JuceLibraryCode/include_juce_audio_plugin_client_AAX_utils.cpp @@ -1,8 +1,8 @@ -/* - - IMPORTANT! This file is auto-generated each time you save your - project - if you alter its contents, your changes may be overwritten! - -*/ - -#include +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm b/JuceLibraryCode/include_juce_audio_plugin_client_VST2.mm similarity index 64% rename from JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm rename to JuceLibraryCode/include_juce_audio_plugin_client_VST2.mm index dcbc376..5923412 100644 --- a/JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm +++ b/JuceLibraryCode/include_juce_audio_plugin_client_VST2.mm @@ -1,8 +1,8 @@ -/* - - IMPORTANT! This file is auto-generated each time you save your - project - if you alter its contents, your changes may be overwritten! - -*/ - -#include +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm b/JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm new file mode 100644 index 0000000..f6f7943 --- /dev/null +++ b/JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm @@ -0,0 +1,8 @@ +/* + + IMPORTANT! This file is auto-generated each time you save your + project - if you alter its contents, your changes may be overwritten! + +*/ + +#include diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp index 86f17a9..f7e14ae 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp @@ -107,6 +107,34 @@ String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) case ambisonicACN33: return NEEDS_TRANS("Ambisonic 33"); case ambisonicACN34: return NEEDS_TRANS("Ambisonic 34"); case ambisonicACN35: return NEEDS_TRANS("Ambisonic 35"); + case ambisonicACN36: return NEEDS_TRANS("Ambisonic 36"); + case ambisonicACN37: return NEEDS_TRANS("Ambisonic 37"); + case ambisonicACN38: return NEEDS_TRANS("Ambisonic 38"); + case ambisonicACN39: return NEEDS_TRANS("Ambisonic 39"); + case ambisonicACN40: return NEEDS_TRANS("Ambisonic 40"); + case ambisonicACN41: return NEEDS_TRANS("Ambisonic 41"); + case ambisonicACN42: return NEEDS_TRANS("Ambisonic 42"); + case ambisonicACN43: return NEEDS_TRANS("Ambisonic 43"); + case ambisonicACN44: return NEEDS_TRANS("Ambisonic 44"); + case ambisonicACN45: return NEEDS_TRANS("Ambisonic 45"); + case ambisonicACN46: return NEEDS_TRANS("Ambisonic 46"); + case ambisonicACN47: return NEEDS_TRANS("Ambisonic 47"); + case ambisonicACN48: return NEEDS_TRANS("Ambisonic 48"); + case ambisonicACN49: return NEEDS_TRANS("Ambisonic 49"); + case ambisonicACN50: return NEEDS_TRANS("Ambisonic 50"); + case ambisonicACN51: return NEEDS_TRANS("Ambisonic 51"); + case ambisonicACN52: return NEEDS_TRANS("Ambisonic 52"); + case ambisonicACN53: return NEEDS_TRANS("Ambisonic 53"); + case ambisonicACN54: return NEEDS_TRANS("Ambisonic 54"); + case ambisonicACN55: return NEEDS_TRANS("Ambisonic 55"); + case ambisonicACN56: return NEEDS_TRANS("Ambisonic 56"); + case ambisonicACN57: return NEEDS_TRANS("Ambisonic 57"); + case ambisonicACN58: return NEEDS_TRANS("Ambisonic 58"); + case ambisonicACN59: return NEEDS_TRANS("Ambisonic 59"); + case ambisonicACN60: return NEEDS_TRANS("Ambisonic 60"); + case ambisonicACN61: return NEEDS_TRANS("Ambisonic 61"); + case ambisonicACN62: return NEEDS_TRANS("Ambisonic 62"); + case ambisonicACN63: return NEEDS_TRANS("Ambisonic 63"); case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); @@ -191,6 +219,34 @@ String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelT case ambisonicACN33: return "ACN33"; case ambisonicACN34: return "ACN34"; case ambisonicACN35: return "ACN35"; + case ambisonicACN36: return "ACN36"; + case ambisonicACN37: return "ACN37"; + case ambisonicACN38: return "ACN38"; + case ambisonicACN39: return "ACN39"; + case ambisonicACN40: return "ACN40"; + case ambisonicACN41: return "ACN41"; + case ambisonicACN42: return "ACN42"; + case ambisonicACN43: return "ACN43"; + case ambisonicACN44: return "ACN44"; + case ambisonicACN45: return "ACN45"; + case ambisonicACN46: return "ACN46"; + case ambisonicACN47: return "ACN47"; + case ambisonicACN48: return "ACN48"; + case ambisonicACN49: return "ACN49"; + case ambisonicACN50: return "ACN50"; + case ambisonicACN51: return "ACN51"; + case ambisonicACN52: return "ACN52"; + case ambisonicACN53: return "ACN53"; + case ambisonicACN54: return "ACN54"; + case ambisonicACN55: return "ACN55"; + case ambisonicACN56: return "ACN56"; + case ambisonicACN57: return "ACN57"; + case ambisonicACN58: return "ACN58"; + case ambisonicACN59: return "ACN59"; + case ambisonicACN60: return "ACN60"; + case ambisonicACN61: return "ACN61"; + case ambisonicACN62: return "ACN62"; + case ambisonicACN63: return "ACN63"; case topSideLeft: return "Tsl"; case topSideRight: return "Tsr"; case bottomFrontLeft: return "Bfl"; @@ -208,9 +264,6 @@ String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelT default: break; } - if (type >= ambisonicACN4 && type <= ambisonicACN35) - return "ACN" + String (type - ambisonicACN4 + 4); - return {}; } @@ -283,6 +336,34 @@ AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (co if (abbr == "ACN33") return ambisonicACN33; if (abbr == "ACN34") return ambisonicACN34; if (abbr == "ACN35") return ambisonicACN35; + if (abbr == "ACN36") return ambisonicACN36; + if (abbr == "ACN37") return ambisonicACN37; + if (abbr == "ACN38") return ambisonicACN38; + if (abbr == "ACN39") return ambisonicACN39; + if (abbr == "ACN40") return ambisonicACN40; + if (abbr == "ACN41") return ambisonicACN41; + if (abbr == "ACN42") return ambisonicACN42; + if (abbr == "ACN43") return ambisonicACN43; + if (abbr == "ACN44") return ambisonicACN44; + if (abbr == "ACN45") return ambisonicACN45; + if (abbr == "ACN46") return ambisonicACN46; + if (abbr == "ACN47") return ambisonicACN47; + if (abbr == "ACN48") return ambisonicACN48; + if (abbr == "ACN49") return ambisonicACN49; + if (abbr == "ACN50") return ambisonicACN50; + if (abbr == "ACN51") return ambisonicACN51; + if (abbr == "ACN52") return ambisonicACN52; + if (abbr == "ACN53") return ambisonicACN53; + if (abbr == "ACN54") return ambisonicACN54; + if (abbr == "ACN55") return ambisonicACN55; + if (abbr == "ACN56") return ambisonicACN56; + if (abbr == "ACN57") return ambisonicACN57; + if (abbr == "ACN58") return ambisonicACN58; + if (abbr == "ACN59") return ambisonicACN59; + if (abbr == "ACN60") return ambisonicACN60; + if (abbr == "ACN61") return ambisonicACN61; + if (abbr == "ACN62") return ambisonicACN62; + if (abbr == "ACN63") return ambisonicACN63; if (abbr == "Tsl") return topSideLeft; if (abbr == "Tsr") return topSideRight; if (abbr == "Bfl") return bottomFrontLeft; @@ -338,6 +419,8 @@ String AudioChannelSet::getDescription() const if (*this == createLCRS()) return "LCRS"; if (*this == create5point0()) return "5.0 Surround"; + if (*this == create5point0point2()) return "5.0.2 Surround"; + if (*this == create5point0point4()) return "5.0.4 Surround"; if (*this == create5point1()) return "5.1 Surround"; if (*this == create5point1point2()) return "5.1.2 Surround"; if (*this == create5point1point4()) return "5.1.4 Surround"; @@ -351,9 +434,13 @@ String AudioChannelSet::getDescription() const if (*this == create7point1SDDS()) return "7.1 Surround SDDS"; if (*this == create7point0point2()) return "7.0.2 Surround"; if (*this == create7point0point4()) return "7.0.4 Surround"; + if (*this == create7point0point6()) return "7.0.6 Surround"; if (*this == create7point1point2()) return "7.1.2 Surround"; if (*this == create7point1point4()) return "7.1.4 Surround"; if (*this == create7point1point6()) return "7.1.6 Surround"; + if (*this == create9point0point4()) return "9.0.4 Surround"; + if (*this == create9point1point4()) return "9.1.4 Surround"; + if (*this == create9point0point6()) return "9.0.6 Surround"; if (*this == create9point1point6()) return "9.1.6 Surround"; if (*this == quadraphonic()) return "Quadraphonic"; @@ -386,11 +473,11 @@ String AudioChannelSet::getDescription() const bool AudioChannelSet::isDiscreteLayout() const noexcept { - for (auto& speaker : getChannelTypes()) - if (speaker <= ambisonicACN35) - return false; + const auto channelTypes = getChannelTypes(); - return true; + return std::none_of (std::begin (channelTypes), + std::end (channelTypes), + [] (const auto& t) { return t < discreteChannel0; }); } int AudioChannelSet::size() const noexcept @@ -467,26 +554,44 @@ AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ({ left, right, centre, leftSurroundRear, rightSurroundRear }); } AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ({ left, right, centre, centreSurround, leftSurroundRear, rightSurroundRear }); } AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, centreSurround, wideLeft, wideRight }); } +AudioChannelSet AudioChannelSet::create5point0point2() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, topSideLeft, topSideRight }); } AudioChannelSet AudioChannelSet::create5point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight }); } +AudioChannelSet AudioChannelSet::create5point0point4() { return AudioChannelSet ({ left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::create5point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight }); } AudioChannelSet AudioChannelSet::create7point0point4() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::create7point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } +AudioChannelSet AudioChannelSet::create7point0point6() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::create7point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } +AudioChannelSet AudioChannelSet::create9point0point4() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } +AudioChannelSet AudioChannelSet::create9point1point4() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight }); } +AudioChannelSet AudioChannelSet::create9point0point6() { return AudioChannelSet ({ left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::create9point1point6() { return AudioChannelSet ({ left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight }); } AudioChannelSet AudioChannelSet::ambisonic (int order) { - jassert (isPositiveAndBelow (order, 6)); + jassert (isPositiveAndBelow (order, 8)); + + static constexpr std::pair continuousRanges[] { { ambisonicACN0, ambisonicACN3 }, + { ambisonicACN4, ambisonicACN35 }, + { ambisonicACN36, ambisonicACN63 } }; - if (order == 0) - return AudioChannelSet ((uint32) (1 << ambisonicACN0)); + AudioChannelSet set; - AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3)); + const auto setBits = [&set] (auto range, auto maxNumToSet) + { + const auto numToSet = std::min (maxNumToSet, range.second - range.first + 1); + set.channels.setRange (range.first, numToSet, true); + return numToSet; + }; - auto numAmbisonicChannels = (order + 1) * (order + 1); - set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true); + const auto numAmbisonicChannels = square (order + 1); + + for (int rangeIdx = 0, bitsSet = 0; bitsSet < numAmbisonicChannels; ++rangeIdx) + { + bitsSet += setBits (continuousRanges[rangeIdx], numAmbisonicChannels - bitsSet); + } return set; } @@ -631,15 +736,13 @@ int32 AudioChannelSet::getWaveChannelMask() const noexcept } //============================================================================== -int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels) +int AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels, int maxOrderToCheck) { - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); + for (auto i = 0; i <= maxOrderToCheck; ++i) + if (numChannels == square (i + 1)) + return i; - if (ambisonicOrder > 5) - return -1; - - return (static_cast (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1); + return -1; } diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h index 5ce4f94..051d9a8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -196,12 +196,24 @@ class JUCE_API AudioChannelSet */ static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); + /** Creates a set for a 5.0.2 surround setup (left, right, centre, leftSurround, rightSurround, topSideLeft, topSideRight). + + Is equivalent to: AAX_eStemFormat_5_0_2 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create5point0point2(); + /** Creates a set for a 5.1.2 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topSideLeft, topSideRight). Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_2 (CoreAudio). */ static AudioChannelSet JUCE_CALLTYPE create5point1point2(); + /** Creates a set for a 5.0.4 surround setup (left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight). + + Is equivalent to: AAX_eStemFormat_5_0_4 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create5point0point4(); + /** Creates a set for a 5.1.4 surround setup (left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight). Is equivalent to: kAudioChannelLayoutTag_Atmos_5_1_4 (CoreAudio). @@ -232,12 +244,36 @@ class JUCE_API AudioChannelSet */ static AudioChannelSet JUCE_CALLTYPE create7point1point4(); + /** Creates a set for 7.0.6 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). + + Is equivalent to: AAX_eStemFormat_7_0_6 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create7point0point6(); + /** Creates a set for Dolby Atmos 7.1.6 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). Is equivalent to: k71_6 (VST), n/a (AAX), n/a (CoreAudio) */ static AudioChannelSet JUCE_CALLTYPE create7point1point6(); + /** Creates a set for a 9.0.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight). + + Is equivalent to: k90_4 (VST3), AAX_eStemFormat_9_0_4 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create9point0point4(); + + /** Creates a set for a 9.1.4 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight). + + Is equivalent to: k91_4 (VST3), AAX_eStemFormat_9_1_4 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create9point1point4(); + + /** Creates a set for a 9.0.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). + + Is equivalent to: k90_6 (VST3), AAX_eStemFormat_9_0_6 (AAX). + */ + static AudioChannelSet JUCE_CALLTYPE create9point0point6(); + /** Creates a set for a 9.1.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight). Note that the VST3 layout arranges the front speakers "L Lc C Rc R", but the JUCE layout @@ -419,6 +455,40 @@ class JUCE_API AudioChannelSet bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ + //============================================================================== + + // sixth-order ambisonic + ambisonicACN36 = 72, /**< Sixth-order ambisonic channel number 36. */ + ambisonicACN37 = 73, /**< Sixth-order ambisonic channel number 37. */ + ambisonicACN38 = 74, /**< Sixth-order ambisonic channel number 38. */ + ambisonicACN39 = 75, /**< Sixth-order ambisonic channel number 39. */ + ambisonicACN40 = 76, /**< Sixth-order ambisonic channel number 40. */ + ambisonicACN41 = 77, /**< Sixth-order ambisonic channel number 41. */ + ambisonicACN42 = 78, /**< Sixth-order ambisonic channel number 42. */ + ambisonicACN43 = 79, /**< Sixth-order ambisonic channel number 43. */ + ambisonicACN44 = 80, /**< Sixth-order ambisonic channel number 44. */ + ambisonicACN45 = 81, /**< Sixth-order ambisonic channel number 45. */ + ambisonicACN46 = 82, /**< Sixth-order ambisonic channel number 46. */ + ambisonicACN47 = 83, /**< Sixth-order ambisonic channel number 47. */ + ambisonicACN48 = 84, /**< Sixth-order ambisonic channel number 48. */ + + // seventh-order ambisonic + ambisonicACN49 = 85, /**< Seventh-order ambisonic channel number 49. */ + ambisonicACN50 = 86, /**< Seventh-order ambisonic channel number 50. */ + ambisonicACN51 = 87, /**< Seventh-order ambisonic channel number 51. */ + ambisonicACN52 = 88, /**< Seventh-order ambisonic channel number 52. */ + ambisonicACN53 = 89, /**< Seventh-order ambisonic channel number 53. */ + ambisonicACN54 = 90, /**< Seventh-order ambisonic channel number 54. */ + ambisonicACN55 = 91, /**< Seventh-order ambisonic channel number 55. */ + ambisonicACN56 = 92, /**< Seventh-order ambisonic channel number 56. */ + ambisonicACN57 = 93, /**< Seventh-order ambisonic channel number 57. */ + ambisonicACN58 = 94, /**< Seventh-order ambisonic channel number 58. */ + ambisonicACN59 = 95, /**< Seventh-order ambisonic channel number 59. */ + ambisonicACN60 = 96, /**< Seventh-order ambisonic channel number 60. */ + ambisonicACN61 = 97, /**< Seventh-order ambisonic channel number 61. */ + ambisonicACN62 = 98, /**< Seventh-order ambisonic channel number 62. */ + ambisonicACN63 = 99, /**< Seventh-order ambisonic channel number 63. */ + //============================================================================== discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ }; @@ -435,7 +505,7 @@ class JUCE_API AudioChannelSet //============================================================================== enum { - maxChannelsOfNamedLayout = 36 + maxChannelsOfNamedLayout = 64 }; /** Adds a channel to the set. */ @@ -505,6 +575,12 @@ class JUCE_API AudioChannelSet */ int32 getWaveChannelMask() const noexcept; + //============================================================================== + /** Returns the ambisonic order that includes exactly numChannels, or -1 if no + supported ambisonic order contains exactly numChannels. + */ + static int getAmbisonicOrderForNumChannels (int numChannels, int maxOrderToCheck = 7); + //============================================================================== bool operator== (const AudioChannelSet&) const noexcept; bool operator!= (const AudioChannelSet&) const noexcept; @@ -518,8 +594,6 @@ class JUCE_API AudioChannelSet explicit AudioChannelSet (uint32); explicit AudioChannelSet (const std::initializer_list&); - //============================================================================== - static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int); }; } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp index 7bd2493..8c49f82 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp @@ -599,7 +599,7 @@ class AudioConversionTests : public UnitTest for (int ch = 0; ch < numChannels; ++ch) for (int i = 0; i < numSamples; ++i) - expect (destBuffer.getSample (0, ch + (i * numChannels)) == sourceBuffer.getSample (ch, i)); + expectEquals (destBuffer.getSample (0, ch + (i * numChannels)), sourceBuffer.getSample (ch, i)); } beginTest ("Deinterleaving"); @@ -620,7 +620,7 @@ class AudioConversionTests : public UnitTest for (int ch = 0; ch < numChannels; ++ch) for (int i = 0; i < numSamples; ++i) - expect (sourceBuffer.getSample (0, ch + (i * numChannels)) == destBuffer.getSample (ch, i)); + expectEquals (sourceBuffer.getSample (0, ch + (i * numChannels)), destBuffer.getSample (ch, i)); } } }; diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp index a13cba6..442f4ce 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp @@ -60,7 +60,7 @@ void AudioProcessLoadMeasurer::registerRenderTime (double milliseconds, int numS void AudioProcessLoadMeasurer::registerRenderTimeLocked (double milliseconds, int numSamples) { - if (msPerSample == 0) + if (approximatelyEqual (msPerSample, 0.0)) return; const auto maxMilliseconds = numSamples * msPerSample; diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 2bcd0bb..ddff0df 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -686,11 +686,11 @@ class AudioBuffer jassert (isPositiveAndBelow (channel, numChannels)); jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - if (gain != Type (1) && ! isClear) + if (! approximatelyEqual (gain, Type (1)) && ! isClear) { auto* d = channels[channel] + startSample; - if (gain == Type()) + if (approximatelyEqual (gain, Type())) FloatVectorOperations::clear (d, numSamples); else FloatVectorOperations::multiply (d, gain, numSamples); @@ -728,7 +728,7 @@ class AudioBuffer { if (! isClear) { - if (startGain == endGain) + if (approximatelyEqual (startGain, endGain)) { applyGain (channel, startSample, numSamples, startGain); } @@ -798,7 +798,7 @@ class AudioBuffer jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - if (gainToApplyToSource != 0 && numSamples > 0 && ! source.isClear) + if (! approximatelyEqual (gainToApplyToSource, (Type) 0) && numSamples > 0 && ! source.isClear) { auto* d = channels[destChannel] + destStartSample; auto* s = source.channels[sourceChannel] + sourceStartSample; @@ -809,14 +809,14 @@ class AudioBuffer { isClear = false; - if (gainToApplyToSource != Type (1)) + if (! approximatelyEqual (gainToApplyToSource, Type (1))) FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); else FloatVectorOperations::copy (d, s, numSamples); } else { - if (gainToApplyToSource != Type (1)) + if (! approximatelyEqual (gainToApplyToSource, Type (1))) FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); else FloatVectorOperations::add (d, s, numSamples); @@ -850,7 +850,7 @@ class AudioBuffer jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); jassert (source != nullptr); - if (gainToApplyToSource != 0 && numSamples > 0) + if (! approximatelyEqual (gainToApplyToSource, Type()) && numSamples > 0) { auto* d = channels[destChannel] + destStartSample; @@ -858,14 +858,14 @@ class AudioBuffer { isClear = false; - if (gainToApplyToSource != Type (1)) + if (! approximatelyEqual (gainToApplyToSource, Type (1))) FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); else FloatVectorOperations::copy (d, source, numSamples); } else { - if (gainToApplyToSource != Type (1)) + if (! approximatelyEqual (gainToApplyToSource, Type (1))) FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); else FloatVectorOperations::add (d, source, numSamples); @@ -899,7 +899,7 @@ class AudioBuffer Type startGain, Type endGain) noexcept { - if (startGain == endGain) + if (approximatelyEqual (startGain, endGain)) { addFrom (destChannel, destStartSample, source, numSamples, startGain); } @@ -912,7 +912,7 @@ class AudioBuffer if (numSamples > 0) { isClear = false; - const auto increment = (endGain - startGain) / numSamples; + const auto increment = (endGain - startGain) / (Type) numSamples; auto* d = channels[destChannel] + destStartSample; while (--numSamples >= 0) @@ -1023,9 +1023,9 @@ class AudioBuffer { auto* d = channels[destChannel] + destStartSample; - if (gain != Type (1)) + if (! approximatelyEqual (gain, Type (1))) { - if (gain == Type()) + if (approximatelyEqual (gain, Type())) { if (! isClear) FloatVectorOperations::clear (d, numSamples); @@ -1071,7 +1071,7 @@ class AudioBuffer Type startGain, Type endGain) noexcept { - if (startGain == endGain) + if (approximatelyEqual (startGain, endGain)) { copyFrom (destChannel, destStartSample, source, numSamples, startGain); } @@ -1084,7 +1084,7 @@ class AudioBuffer if (numSamples > 0) { isClear = false; - const auto increment = (endGain - startGain) / numSamples; + const auto increment = (endGain - startGain) / (Type) numSamples; auto* d = channels[destChannel] + destStartSample; while (--numSamples >= 0) @@ -1280,6 +1280,31 @@ class AudioBuffer JUCE_LEAK_DETECTOR (AudioBuffer) }; +//============================================================================== +template +bool operator== (const AudioBuffer& a, const AudioBuffer& b) +{ + if (a.getNumChannels() != b.getNumChannels()) + return false; + + for (auto c = 0; c < a.getNumChannels(); ++c) + { + const auto begin = [c] (auto& x) { return x.getReadPointer (c); }; + const auto end = [c] (auto& x) { return x.getReadPointer (c) + x.getNumSamples(); }; + + if (! std::equal (begin (a), end (a), begin (b), end (b))) + return false; + } + + return true; +} + +template +bool operator!= (const AudioBuffer& a, const AudioBuffer& b) +{ + return ! (a == b); +} + //============================================================================== /** A multi-channel buffer of 32-bit floating point audio samples. diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index 2fc2386..f9d47e1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -1676,7 +1676,7 @@ class FloatVectorOperationsTests : public UnitTest static bool areAllValuesEqual (const ValueType* d, int num, ValueType target) { while (--num >= 0) - if (*d++ != target) + if (! exactlyEqual (*d++, target)) return false; return true; diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h index 97fcb76..de919ea 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h @@ -98,7 +98,7 @@ struct FloatVectorOperationsBase /** Multiplies the destination values by the source values. */ static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, CountType numValues) noexcept; - /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ + /** Multiplies each source1 value by the corresponding source2 value, then stores it in the destination array. */ static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType numValues) noexcept; /** Multiplies each of the destination values by a fixed multiplier. */ diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp index 1ae64a3..f144945 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp @@ -88,6 +88,7 @@ #include "sources/juce_PositionableAudioSource.cpp" #include "synthesisers/juce_Synthesiser.cpp" #include "audio_play_head/juce_AudioPlayHead.cpp" +#include "midi/juce_MidiDataConcatenator.h" #include "midi/ump/juce_UMP.h" #include "midi/ump/juce_UMPUtils.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h index 1802b61..934b91c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h @@ -32,7 +32,7 @@ ID: juce_audio_basics vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE audio and MIDI data classes description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. website: http://www.juce.com/juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 9b616c5..bdebc8d 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -160,7 +160,7 @@ namespace MidiFileHelpers for (int i = 0; i < numEvents; ++i) { - auto& m = tempoEvents.getEventPointer(i)->message; + auto& m = tempoEvents.getEventPointer (i)->message; auto eventTime = m.getTimeStamp(); if (eventTime >= time) @@ -174,9 +174,9 @@ namespace MidiFileHelpers while (i + 1 < numEvents) { - auto& m2 = tempoEvents.getEventPointer(i + 1)->message; + auto& m2 = tempoEvents.getEventPointer (i + 1)->message; - if (m2.getTimeStamp() != eventTime) + if (! approximatelyEqual (m2.getTimeStamp(), eventTime)) break; if (m2.isTempoMetaEvent()) diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index 5ff513f..924b502 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -266,7 +266,7 @@ void MidiMessageSequence::updateMatchedPairs() noexcept void MidiMessageSequence::addTimeToMessages (double delta) noexcept { - if (delta != 0) + if (! approximatelyEqual (delta, 0.0)) for (auto* m : list) m->message.addToTimeStamp (delta); } @@ -554,7 +554,7 @@ struct MidiMessageSequenceTest : public UnitTest { const auto isEqual = [this] (const ControlValue& cv, const MidiMessage& msg) { - return msg.getTimeStamp() == time + return exactlyEqual (msg.getTimeStamp(), time) && msg.isController() && msg.getChannel() == channel && msg.getControllerNumber() == cv.control diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index e167f12..4faf77e 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -23,42 +23,46 @@ namespace juce { -MidiRPNDetector::MidiRPNDetector() noexcept -{ -} - -MidiRPNDetector::~MidiRPNDetector() noexcept -{ -} - bool MidiRPNDetector::parseControllerMessage (int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage& result) noexcept +{ + auto parsed = tryParse (midiChannel, controllerNumber, controllerValue); + + if (! parsed.has_value()) + return false; + + result = *parsed; + return true; +} + +std::optional MidiRPNDetector::tryParse (int midiChannel, + int controllerNumber, + int controllerValue) { jassert (midiChannel > 0 && midiChannel <= 16); jassert (controllerNumber >= 0 && controllerNumber < 128); jassert (controllerValue >= 0 && controllerValue < 128); - return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); + return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue); } void MidiRPNDetector::reset() noexcept { - for (int i = 0; i < 16; ++i) + for (auto& state : states) { - states[i].parameterMSB = 0xff; - states[i].parameterLSB = 0xff; - states[i].resetValue(); - states[i].isNRPN = false; + state.parameterMSB = 0xff; + state.parameterLSB = 0xff; + state.resetValue(); + state.isNRPN = false; } } //============================================================================== -bool MidiRPNDetector::ChannelState::handleController (int channel, - int controllerNumber, - int value, - MidiRPNMessage& result) noexcept +std::optional MidiRPNDetector::ChannelState::handleController (int channel, + int controllerNumber, + int value) noexcept { switch (controllerNumber) { @@ -68,13 +72,11 @@ bool MidiRPNDetector::ChannelState::handleController (int channel, case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; - case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); - case 0x26: valueLSB = uint8 (value); break; - - default: break; + case 0x06: valueMSB = uint8 (value); valueLSB = 0xff; return sendIfReady (channel); + case 0x26: valueLSB = uint8 (value); return sendIfReady (channel); } - return false; + return {}; } void MidiRPNDetector::ChannelState::resetValue() noexcept @@ -84,32 +86,28 @@ void MidiRPNDetector::ChannelState::resetValue() noexcept } //============================================================================== -bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept +std::optional MidiRPNDetector::ChannelState::sendIfReady (int channel) noexcept { - if (parameterMSB < 0x80 && parameterLSB < 0x80) - { - if (valueMSB < 0x80) - { - result.channel = channel; - result.parameterNumber = (parameterMSB << 7) + parameterLSB; - result.isNRPN = isNRPN; + if (parameterMSB >= 0x80 || parameterLSB >= 0x80 || valueMSB >= 0x80) + return {}; - if (valueLSB < 0x80) - { - result.value = (valueMSB << 7) + valueLSB; - result.is14BitValue = true; - } - else - { - result.value = valueMSB; - result.is14BitValue = false; - } + MidiRPNMessage result{}; + result.channel = channel; + result.parameterNumber = (parameterMSB << 7) + parameterLSB; + result.isNRPN = isNRPN; - return true; - } + if (valueLSB < 0x80) + { + result.value = (valueMSB << 7) + valueLSB; + result.is14BitValue = true; + } + else + { + result.value = valueMSB; + result.is14BitValue = false; } - return false; + return result; } //============================================================================== @@ -132,25 +130,26 @@ MidiBuffer MidiRPNGenerator::generate (int midiChannel, jassert (parameterNumber >= 0 && parameterNumber < 16384); jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); - uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); - uint8 parameterMSB = uint8 (parameterNumber >> 7); + auto parameterLSB = uint8 (parameterNumber & 0x0000007f); + auto parameterMSB = uint8 (parameterNumber >> 7); uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); - uint8 channelByte = uint8 (0xb0 + midiChannel - 1); + auto channelByte = uint8 (0xb0 + midiChannel - 1); MidiBuffer buffer; buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); - // sending the value LSB is optional, but must come before sending the value MSB: + buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); + + // According to the MIDI spec, whenever a MSB is received, the corresponding LSB will + // be reset. Therefore, the LSB should be sent after the MSB. if (use14BitValue) buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); - buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); - return buffer; } @@ -168,134 +167,196 @@ class MidiRPNDetectorTests : public UnitTest void runTest() override { - beginTest ("7-bit RPN"); + // From the MIDI 1.0 spec: + // If 128 steps of resolution is sufficient the second byte (LSB) of the data value can be + // omitted. If both the MSB and LSB are sent initially, a subsequent fine adjustment only + // requires the sending of the LSB. The MSB does not have to be retransmitted. If a + // subsequent major adjustment is necessary the MSB must be transmitted again. When an MSB + // is received, the receiver should set its concept of the LSB to zero. + + beginTest ("Individual MSB is parsed as 7-bit"); + { + MidiRPNDetector detector; + expect (! detector.tryParse (2, 101, 0)); + expect (! detector.tryParse (2, 100, 7)); + + auto parsed = detector.tryParse (2, 6, 42); + expect (parsed.has_value()); + + expectEquals (parsed->channel, 2); + expectEquals (parsed->parameterNumber, 7); + expectEquals (parsed->value, 42); + expect (! parsed->isNRPN); + expect (! parsed->is14BitValue); + } + + beginTest ("LSB without preceding MSB is ignored"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (detector.parseControllerMessage (2, 6, 42, rpn)); - - expectEquals (rpn.channel, 2); - expectEquals (rpn.parameterNumber, 7); - expectEquals (rpn.value, 42); - expect (! rpn.isNRPN); - expect (! rpn.is14BitValue); + expect (! detector.tryParse (2, 101, 0)); + expect (! detector.tryParse (2, 100, 7)); + expect (! detector.tryParse (2, 38, 42)); } - beginTest ("14-bit RPN"); + beginTest ("LSB following MSB is parsed as 14-bit"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 100, 44, rpn)); - expect (! detector.parseControllerMessage (1, 101, 2, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); + expect (! detector.tryParse (1, 101, 2)); + expect (! detector.tryParse (1, 100, 44)); + + expect (detector.tryParse (1, 6, 1).has_value()); + + auto lsbParsed = detector.tryParse (1, 38, 94); + expect (lsbParsed.has_value()); + + expectEquals (lsbParsed->channel, 1); + expectEquals (lsbParsed->parameterNumber, 300); + expectEquals (lsbParsed->value, 222); + expect (! lsbParsed->isNRPN); + expect (lsbParsed->is14BitValue); + } + + beginTest ("Multiple LSB following MSB re-use the MSB"); + { + MidiRPNDetector detector; + expect (! detector.tryParse (1, 101, 2)); + expect (! detector.tryParse (1, 100, 43)); + + expect (detector.tryParse (1, 6, 1).has_value()); + + expect (detector.tryParse (1, 38, 94).has_value()); + expect (detector.tryParse (1, 38, 95).has_value()); + expect (detector.tryParse (1, 38, 96).has_value()); + + auto lsbParsed = detector.tryParse (1, 38, 97); + expect (lsbParsed.has_value()); + + expectEquals (lsbParsed->channel, 1); + expectEquals (lsbParsed->parameterNumber, 299); + expectEquals (lsbParsed->value, 225); + expect (! lsbParsed->isNRPN); + expect (lsbParsed->is14BitValue); + } + + beginTest ("Sending a new MSB resets the LSB"); + { + MidiRPNDetector detector; + expect (! detector.tryParse (1, 101, 3)); + expect (! detector.tryParse (1, 100, 43)); + + expect (detector.tryParse (1, 6, 1).has_value()); + expect (detector.tryParse (1, 38, 94).has_value()); + + auto newMsb = detector.tryParse (1, 6, 2); + expect (newMsb.has_value()); + + expectEquals (newMsb->channel, 1); + expectEquals (newMsb->parameterNumber, 427); + expectEquals (newMsb->value, 2); + expect (! newMsb->isNRPN); + expect (! newMsb->is14BitValue); } beginTest ("RPNs on multiple channels simultaneously"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 100, 44, rpn)); - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (1, 101, 2, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (2, 6, 42, rpn)); - - expectEquals (rpn.channel, 2); - expectEquals (rpn.parameterNumber, 7); - expectEquals (rpn.value, 42); - expect (! rpn.isNRPN); - expect (! rpn.is14BitValue); - - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); + expect (! detector.tryParse (1, 100, 44)); + expect (! detector.tryParse (2, 101, 0)); + expect (! detector.tryParse (1, 101, 2)); + expect (! detector.tryParse (2, 100, 7)); + expect (detector.tryParse (1, 6, 1).has_value()); + + auto channelTwo = detector.tryParse (2, 6, 42); + expect (channelTwo.has_value()); + + expectEquals (channelTwo->channel, 2); + expectEquals (channelTwo->parameterNumber, 7); + expectEquals (channelTwo->value, 42); + expect (! channelTwo->isNRPN); + expect (! channelTwo->is14BitValue); + + auto channelOne = detector.tryParse (1, 38, 94); + expect (channelOne.has_value()); + + expectEquals (channelOne->channel, 1); + expectEquals (channelOne->parameterNumber, 300); + expectEquals (channelOne->value, 222); + expect (! channelOne->isNRPN); + expect (channelOne->is14BitValue); } beginTest ("14-bit RPN with value within 7-bit range"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); - expect (! detector.parseControllerMessage (16, 101, 0, rpn)); - expect (! detector.parseControllerMessage (16, 38, 3, rpn)); - expect (detector.parseControllerMessage (16, 6, 0, rpn)); - - expectEquals (rpn.channel, 16); - expectEquals (rpn.parameterNumber, 0); - expectEquals (rpn.value, 3); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); + expect (! detector.tryParse (16, 100, 0)); + expect (! detector.tryParse (16, 101, 0)); + expect (detector.tryParse (16, 6, 0).has_value()); + + auto parsed = detector.tryParse (16, 38, 3); + expect (parsed.has_value()); + + expectEquals (parsed->channel, 16); + expectEquals (parsed->parameterNumber, 0); + expectEquals (parsed->value, 3); + expect (! parsed->isNRPN); + expect (parsed->is14BitValue); } beginTest ("invalid RPN (wrong order)"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 6, 42, rpn)); - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); + expect (! detector.tryParse (2, 6, 42)); + expect (! detector.tryParse (2, 101, 0)); + expect (! detector.tryParse (2, 100, 7)); } beginTest ("14-bit RPN interspersed with unrelated CC messages"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (16, 3, 80, rpn)); - expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); - expect (! detector.parseControllerMessage (16, 4, 81, rpn)); - expect (! detector.parseControllerMessage (16, 101, 0, rpn)); - expect (! detector.parseControllerMessage (16, 5, 82, rpn)); - expect (! detector.parseControllerMessage (16, 5, 83, rpn)); - expect (! detector.parseControllerMessage (16, 38, 3, rpn)); - expect (! detector.parseControllerMessage (16, 4, 84, rpn)); - expect (! detector.parseControllerMessage (16, 3, 85, rpn)); - expect (detector.parseControllerMessage (16, 6, 0, rpn)); - - expectEquals (rpn.channel, 16); - expectEquals (rpn.parameterNumber, 0); - expectEquals (rpn.value, 3); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); + expect (! detector.tryParse (16, 3, 80)); + expect (! detector.tryParse (16, 100, 0)); + expect (! detector.tryParse (16, 4, 81)); + expect (! detector.tryParse (16, 101, 0)); + expect (! detector.tryParse (16, 5, 82)); + expect (! detector.tryParse (16, 5, 83)); + expect (detector.tryParse (16, 6, 0).has_value()); + expect (! detector.tryParse (16, 4, 84).has_value()); + expect (! detector.tryParse (16, 3, 85).has_value()); + + auto parsed = detector.tryParse (16, 38, 3); + expect (parsed.has_value()); + + expectEquals (parsed->channel, 16); + expectEquals (parsed->parameterNumber, 0); + expectEquals (parsed->value, 3); + expect (! parsed->isNRPN); + expect (parsed->is14BitValue); } beginTest ("14-bit NRPN"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 98, 44, rpn)); - expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (rpn.isNRPN); - expect (rpn.is14BitValue); + expect (! detector.tryParse (1, 98, 44)); + expect (! detector.tryParse (1, 99 , 2)); + expect (detector.tryParse (1, 6, 1).has_value()); + + auto parsed = detector.tryParse (1, 38, 94); + expect (parsed.has_value()); + + expectEquals (parsed->channel, 1); + expectEquals (parsed->parameterNumber, 300); + expectEquals (parsed->value, 222); + expect (parsed->isNRPN); + expect (parsed->is14BitValue); } beginTest ("reset"); { MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); + expect (! detector.tryParse (2, 101, 0)); detector.reset(); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (! detector.parseControllerMessage (2, 6, 42, rpn)); + expect (! detector.tryParse (2, 100, 7)); + expect (! detector.tryParse (2, 6, 42)); } } }; @@ -346,25 +407,24 @@ class MidiRPNGeneratorTests : public UnitTest //============================================================================== void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) { - MidiRPNMessage result = MidiRPNMessage(); + std::optional result; MidiRPNDetector detector; for (const auto metadata : midiBuffer) { const auto midiMessage = metadata.getMessage(); - if (detector.parseControllerMessage (midiMessage.getChannel(), - midiMessage.getControllerNumber(), - midiMessage.getControllerValue(), - result)) - break; + result = detector.tryParse (midiMessage.getChannel(), + midiMessage.getControllerNumber(), + midiMessage.getControllerValue()); } - expectEquals (result.channel, expected.channel); - expectEquals (result.parameterNumber, expected.parameterNumber); - expectEquals (result.value, expected.value); - expect (result.isNRPN == expected.isNRPN); - expect (result.is14BitValue == expected.is14BitValue); + expect (result.has_value()); + expectEquals (result->channel, expected.channel); + expectEquals (result->parameterNumber, expected.parameterNumber); + expectEquals (result->value, expected.value); + expect (result->isNRPN == expected.isNRPN); + expect (result->is14BitValue == expected.is14BitValue); } }; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h index f5bfbef..dc63002 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h @@ -68,10 +68,10 @@ class JUCE_API MidiRPNDetector { public: /** Constructor. */ - MidiRPNDetector() noexcept; + MidiRPNDetector() noexcept = default; /** Destructor. */ - ~MidiRPNDetector() noexcept; + ~MidiRPNDetector() noexcept = default; /** Resets the RPN detector's internal state, so that it forgets about previously received MIDI CC messages. @@ -79,26 +79,39 @@ class JUCE_API MidiRPNDetector void reset() noexcept; //============================================================================== - /** Takes the next in a stream of incoming MIDI CC messages and returns true - if it forms the last of a sequence that makes an RPN or NPRN. - - If this returns true, then the RPNMessage object supplied will be - filled-out with the message's details. - (If it returns false then the RPNMessage object will be unchanged). - */ + /** @see tryParse() */ + [[deprecated ("Use tryParse() instead")]] bool parseControllerMessage (int midiChannel, int controllerNumber, int controllerValue, MidiRPNMessage& result) noexcept; + /** Takes the next in a stream of incoming MIDI CC messages and returns + a MidiRPNMessage if the current message produces a well-formed RPN or NRPN. + + Note that senders are expected to send the MSB before the LSB, but senders are + not required to send a LSB at all. Therefore, tryParse() will return a non-null + optional on all MSB messages (provided a parameter number has been set), and will + also return a non-null optional for each LSB that follows the initial MSB. + + This behaviour allows senders to transmit a single MSB followed by multiple LSB + messages to facilitate fine-tuning of parameters. + + The result of parsing a MSB will always be a 7-bit value. + The result of parsing a LSB that follows an MSB will always be a 14-bit value. + */ + std::optional tryParse (int midiChannel, + int controllerNumber, + int controllerValue); + private: //============================================================================== struct ChannelState { - bool handleController (int channel, int controllerNumber, - int value, MidiRPNMessage&) noexcept; + std::optional handleController (int channel, int controllerNumber, + int value) noexcept; void resetValue() noexcept; - bool sendIfReady (int channel, MidiRPNMessage&) noexcept; + std::optional sendIfReady (int channel) noexcept; uint8 parameterMSB = 0xff, parameterLSB = 0xff, valueMSB = 0xff, valueLSB = 0xff; bool isNRPN = false; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP.h index b3e9004..9f478e9 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP.h @@ -20,8 +20,6 @@ ============================================================================== */ -#include "../juce_MidiDataConcatenator.h" - #include "juce_UMPProtocols.h" #include "juce_UMPUtils.h" #include "juce_UMPacket.h" diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h index 977db80..9a6858e 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConversion.h @@ -27,6 +27,44 @@ namespace juce namespace universal_midi_packets { +/** Represents a MIDI message that happened at a particular time. + + Unlike MidiMessage, BytestreamMidiView is non-owning. +*/ +struct BytestreamMidiView +{ + constexpr BytestreamMidiView (Span bytesIn, double timestampIn) + : bytes (bytesIn), timestamp (timestampIn) {} + + /** Creates a view over the provided message. + + Note that the argument is a pointer, not a reference, in order to avoid taking a reference + to a temporary. + */ + explicit BytestreamMidiView (const MidiMessage* msg) + : bytes (unalignedPointerCast (msg->getRawData()), + static_cast (msg->getRawDataSize())), + timestamp (msg->getTimeStamp()) {} + + explicit BytestreamMidiView (const MidiMessageMetadata msg) + : bytes (unalignedPointerCast (msg.data), + static_cast (msg.numBytes)), + timestamp (msg.samplePosition) {} + + MidiMessage getMessage() const + { + return MidiMessage (bytes.data(), (int) bytes.size(), timestamp); + } + + bool isSysEx() const + { + return ! bytes.empty() && bytes.front() == std::byte { 0xf0 }; + } + + Span bytes; + double timestamp = 0.0; +}; + /** Functions to assist conversion of UMP messages to/from other formats, especially older 'bytestream' formatted MidiMessages. @@ -40,13 +78,17 @@ struct Conversion `callback` is a function which accepts a single View argument. */ template - static void toMidi1 (const MidiMessage& m, PacketCallbackFunction&& callback) + static void toMidi1 (const BytestreamMidiView& m, PacketCallbackFunction&& callback) { - const auto* data = m.getRawData(); + const auto size = m.bytes.size(); + + if (size <= 0) + return; + + const auto* data = m.bytes.data(); const auto firstByte = data[0]; - const auto size = m.getRawDataSize(); - if (firstByte != 0xf0) + if (firstByte != std::byte { 0xf0 }) { const auto mask = [size]() -> uint32_t { @@ -61,15 +103,15 @@ struct Conversion return 0x00000000; }(); - const auto extraByte = (uint8_t) ((((firstByte & 0xf0) == 0xf0) ? 0x1 : 0x2) << 0x4); + const auto extraByte = ((((firstByte & std::byte { 0xf0 }) == std::byte { 0xf0 }) ? std::byte { 0x1 } : std::byte { 0x2 }) << 0x4); const PacketX1 packet { mask & Utils::bytesToWord (extraByte, data[0], data[1], data[2]) }; callback (View (packet.data())); return; } - const auto numSysExBytes = m.getSysExDataSize(); + const auto numSysExBytes = (ssize_t) (size - 2); const auto numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes); - auto* dataOffset = m.getSysExData(); + auto* dataOffset = data + 1; if (numMessages <= 1) { @@ -78,9 +120,9 @@ struct Conversion return; } - constexpr auto byteIncrement = 6; + constexpr ssize_t byteIncrement = 6; - for (auto i = numSysExBytes; i > 0; i -= byteIncrement, dataOffset += byteIncrement) + for (auto i = static_cast (numSysExBytes); i > 0; i -= byteIncrement, dataOffset += byteIncrement) { const auto func = [&] { @@ -99,20 +141,6 @@ struct Conversion } } - /** Converts a MidiMessage to one or more messages in UMP format, using - the MIDI 1.0 Protocol. - - `packets` is an out-param to allow the caller to control - allocation/deallocation. Returning a new Packets object would - require every call to toMidi1 to allocate. With this version, no - allocations will occur, provided that `packets` has adequate reserved - space. - */ - static void toMidi1 (const MidiMessage& m, Packets& packets) - { - toMidi1 (m, [&] (const View& view) { packets.add (view); }); - } - /** Widens a 7-bit MIDI 1.0 value to a 8-bit MIDI 2.0 value. */ static uint8_t scaleTo8 (uint8_t word7Bit) { @@ -198,7 +226,7 @@ struct Conversion } const auto status = Utils::getStatus (firstWord); - const auto typeAndGroup = (uint8_t) ((0x2 << 0x4) | Utils::getGroup (firstWord)); + const auto typeAndGroup = ((std::byte { 0x2 } << 0x4) | std::byte { Utils::getGroup (firstWord) }); switch (status) { @@ -207,18 +235,18 @@ struct Conversion case 0xa: // poly pressure case 0xb: // control change { - const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); - const auto byte2 = (uint8_t) ((firstWord >> 0x08) & 0xff); - const auto byte3 = scaleTo7 (v[1]); + const auto statusAndChannel = std::byte ((firstWord >> 0x10) & 0xff); + const auto byte2 = std::byte ((firstWord >> 0x08) & 0xff); + const auto byte3 = std::byte { scaleTo7 (v[1]) }; // If this is a note-on, and the scaled byte is 0, // the scaled velocity should be 1 instead of 0 - const auto needsCorrection = status == 0x9 && byte3 == 0; - const auto correctedByte = (uint8_t) (needsCorrection ? 1 : byte3); + const auto needsCorrection = status == 0x9 && byte3 == std::byte { 0 }; + const auto correctedByte = needsCorrection ? std::byte { 1 } : byte3; const auto shouldIgnore = status == 0xb && [&] { - switch (byte2) + switch (uint8_t (byte2)) { case 0: case 6: @@ -247,13 +275,13 @@ struct Conversion case 0xd: // channel pressure { - const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); - const auto byte2 = scaleTo7 (v[1]); + const auto statusAndChannel = std::byte ((firstWord >> 0x10) & 0xff); + const auto byte2 = std::byte { scaleTo7 (v[1]) }; const PacketX1 packet { Utils::bytesToWord (typeAndGroup, statusAndChannel, byte2, - 0) }; + std::byte { 0 }) }; callback (View (packet.data())); return; } @@ -261,17 +289,17 @@ struct Conversion case 0x2: // rpn case 0x3: // nrpn { - const auto ccX = (uint8_t) (status == 0x2 ? 101 : 99); - const auto ccY = (uint8_t) (status == 0x2 ? 100 : 98); - const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); + const auto ccX = status == 0x2 ? std::byte { 101 } : std::byte { 99 }; + const auto ccY = status == 0x2 ? std::byte { 100 } : std::byte { 98 }; + const auto statusAndChannel = std::byte ((0xb << 0x4) | Utils::getChannel (firstWord)); const auto data = scaleTo14 (v[1]); const PacketX1 packets[] { - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccX, (uint8_t) ((firstWord >> 0x8) & 0x7f)) }, - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccY, (uint8_t) ((firstWord >> 0x0) & 0x7f)) }, - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 6, (uint8_t) ((data >> 0x7) & 0x7f)) }, - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 38, (uint8_t) ((data >> 0x0) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccX, std::byte ((firstWord >> 0x8) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccY, std::byte ((firstWord >> 0x0) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, std::byte { 6 }, std::byte ((data >> 0x7) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, std::byte { 38 }, std::byte ((data >> 0x0) & 0x7f)) }, }; for (const auto& packet : packets) @@ -284,24 +312,24 @@ struct Conversion { if (firstWord & 1) { - const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); + const auto statusAndChannel = std::byte ((0xb << 0x4) | Utils::getChannel (firstWord)); const auto secondWord = v[1]; const PacketX1 packets[] { - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 0, (uint8_t) ((secondWord >> 0x8) & 0x7f)) }, - PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 32, (uint8_t) ((secondWord >> 0x0) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, std::byte { 0 }, std::byte ((secondWord >> 0x8) & 0x7f)) }, + PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, std::byte { 32 }, std::byte ((secondWord >> 0x0) & 0x7f)) }, }; for (const auto& packet : packets) callback (View (packet.data())); } - const auto statusAndChannel = (uint8_t) ((0xc << 0x4) | Utils::getChannel (firstWord)); + const auto statusAndChannel = std::byte ((0xc << 0x4) | Utils::getChannel (firstWord)); const PacketX1 packet { Utils::bytesToWord (typeAndGroup, statusAndChannel, - (uint8_t) ((v[1] >> 0x18) & 0x7f), - 0) }; + std::byte ((v[1] >> 0x18) & 0x7f), + std::byte { 0 }) }; callback (View (packet.data())); return; } @@ -309,11 +337,11 @@ struct Conversion case 0xe: // pitch bend { const auto data = scaleTo14 (v[1]); - const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); + const auto statusAndChannel = std::byte ((firstWord >> 0x10) & 0xff); const PacketX1 packet { Utils::bytesToWord (typeAndGroup, statusAndChannel, - (uint8_t) (data & 0x7f), - (uint8_t) ((data >> 7) & 0x7f)) }; + std::byte (data & 0x7f), + std::byte ((data >> 7) & 0x7f)) }; callback (View (packet.data())); return; } diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h index 4a2b9b3..83f616b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPConverters.h @@ -35,7 +35,7 @@ namespace universal_midi_packets struct ToUMP1Converter { template - void convert (const MidiMessage& m, Fn&& fn) + void convert (const BytestreamMidiView& m, Fn&& fn) { Conversion::toMidi1 (m, std::forward (fn)); } @@ -56,7 +56,7 @@ namespace universal_midi_packets struct ToUMP2Converter { template - void convert (const MidiMessage& m, Fn&& fn) + void convert (const BytestreamMidiView& m, Fn&& fn) { Conversion::toMidi1 (m, [&] (const View& v) { @@ -88,6 +88,15 @@ namespace universal_midi_packets */ class GenericUMPConverter { + template + static void visit (This& t, Args&&... args) + { + if (t.mode == PacketProtocol::MIDI_1_0) + convertImpl (std::get<0> (t.converters), std::forward (args)...); + else + convertImpl (std::get<1> (t.converters), std::forward (args)...); + } + public: explicit GenericUMPConverter (PacketProtocol m) : mode (m) {} @@ -97,33 +106,43 @@ namespace universal_midi_packets std::get<1> (converters).reset(); } - template - void convert (const MidiMessage& m, Fn&& fn) + template + static void convertImpl (Converter& converter, const BytestreamMidiView& m, Fn&& fn) { - switch (mode) + converter.convert (m, std::forward (fn)); + } + + template + static void convertImpl (Converter& converter, const View& m, Fn&& fn) + { + converter.convert (m, std::forward (fn)); + } + + template + static void convertImpl (Converter& converter, Iterator b, Iterator e, Fn&& fn) + { + std::for_each (b, e, [&] (const auto& v) { - case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (m, std::forward (fn)); - case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (m, std::forward (fn)); - } + convertImpl (converter, v, fn); + }); + } + + template + void convert (const BytestreamMidiView& m, Fn&& fn) + { + visit (*this, m, std::forward (fn)); } template void convert (const View& v, Fn&& fn) { - switch (mode) - { - case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (v, std::forward (fn)); - case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (v, std::forward (fn)); - } + visit (*this, v, std::forward (fn)); } template void convert (Iterator begin, Iterator end, Fn&& fn) { - std::for_each (begin, end, [&] (const View& v) - { - convert (v, fn); - }); + visit (*this, begin, end, std::forward (fn)); } PacketProtocol getProtocol() const noexcept { return mode; } diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h index ee9d725..ad5c376 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPDispatcher.h @@ -123,7 +123,7 @@ class BytestreamToUMPDispatcher void handleIncomingMidiMessage (void*, const MidiMessage& msg) const { - Conversion::toMidi1 (msg, [&] (const View& view) + Conversion::toMidi1 (BytestreamMidiView (&msg), [&] (const View& view) { dispatch.converter.convert (view, *callbackPtr); }); diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h index a49b176..582456d 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPFactory.h @@ -45,7 +45,7 @@ struct Factory static PacketX2 makeSysEx (uint8_t group, uint8_t status, uint8_t numBytes, - const uint8_t* data) + const std::byte* data) { jassert (numBytes <= 6); @@ -250,28 +250,28 @@ struct Factory static PacketX2 makeSysExIn1Packet (uint8_t group, uint8_t numBytes, - const uint8_t* data) + const std::byte* data) { return Detail::makeSysEx (group, 0x0, numBytes, data); } static PacketX2 makeSysExStart (uint8_t group, uint8_t numBytes, - const uint8_t* data) + const std::byte* data) { return Detail::makeSysEx (group, 0x1, numBytes, data); } static PacketX2 makeSysExContinue (uint8_t group, uint8_t numBytes, - const uint8_t* data) + const std::byte* data) { return Detail::makeSysEx (group, 0x2, numBytes, data); } static PacketX2 makeSysExEnd (uint8_t group, uint8_t numBytes, - const uint8_t* data) + const std::byte* data) { return Detail::makeSysEx (group, 0x3, numBytes, data); } diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h index a765408..3106ac8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToBytestreamTranslator.h @@ -73,7 +73,10 @@ class Midi1ToBytestreamTranslator { // Utility messages don't translate to bytestream format if (Utils::getMessageType (firstWord) != 0x00) - callback (fromUmp (PacketX1 { firstWord }, time)); + { + const auto message = fromUmp (PacketX1 { firstWord }, time); + callback (BytestreamMidiView (&message)); + } break; } @@ -162,16 +165,14 @@ class Midi1ToBytestreamTranslator void startSysExMessage (double time) { pendingSysExTime = time; - pendingSysExData.push_back (0xf0); + pendingSysExData.push_back (std::byte { 0xf0 }); } template void terminateSysExMessage (MessageCallback&& callback) { - pendingSysExData.push_back (0xf7); - callback (MidiMessage (pendingSysExData.data(), - int (pendingSysExData.size()), - pendingSysExTime)); + pendingSysExData.push_back (std::byte { 0xf7 }); + callback (BytestreamMidiView (pendingSysExData, pendingSysExTime)); pendingSysExData.clear(); } @@ -206,7 +207,7 @@ class Midi1ToBytestreamTranslator return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8; } - std::vector pendingSysExData; + std::vector pendingSysExData; double pendingSysExTime = 0.0; }; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp index 8ff68de..9b4d86b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp @@ -28,14 +28,14 @@ namespace universal_midi_packets PacketX2 Midi1ToMidi2DefaultTranslator::processNoteOnOrOff (const HelperValues helpers) { const auto velocity = helpers.byte2; - const auto needsConversion = (helpers.byte0 >> 0x4) == 0x9 && velocity == 0; - const auto firstByte = needsConversion ? (uint8_t) ((0x8 << 0x4) | (helpers.byte0 & 0xf)) + const auto needsConversion = (helpers.byte0 & std::byte { 0xf0 }) == std::byte { 0x90 } && velocity == std::byte { 0 }; + const auto firstByte = needsConversion ? (std::byte { 0x80 } | (helpers.byte0 & std::byte { 0xf })) : helpers.byte0; return PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, 0), - (uint32_t) (Conversion::scaleTo16 (velocity) << 0x10) + Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, std::byte { 0 }), + (uint32_t) (Conversion::scaleTo16 (uint8_t (velocity)) << 0x10) }; } @@ -43,8 +43,8 @@ PacketX2 Midi1ToMidi2DefaultTranslator::processPolyPressure (const HelperValues { return PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, 0), - Conversion::scaleTo32 (helpers.byte2) + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, std::byte { 0 }), + Conversion::scaleTo32 (uint8_t (helpers.byte2)) }; } @@ -52,7 +52,7 @@ bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues hel PacketX2& packet) { const auto statusAndChannel = helpers.byte0; - const auto cc = helpers.byte1; + const auto cc = uint8_t (helpers.byte1); const auto shouldAccumulate = [&] { @@ -70,8 +70,8 @@ bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues hel return false; }(); - const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); - const auto channel = (uint8_t) (statusAndChannel & 0xf); + const auto group = (uint8_t) (helpers.typeAndGroup & std::byte { 0xf }); + const auto channel = (uint8_t) (statusAndChannel & std::byte { 0xf }); const auto byte = helpers.byte2; if (shouldAccumulate) @@ -86,13 +86,13 @@ bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues hel const auto msb = bytes[2]; const auto lsb = bytes[3]; - const auto value = (uint16_t) (((msb & 0x7f) << 7) | (lsb & 0x7f)); + const auto value = uint16_t ((uint16_t (msb & std::byte { 0x7f }) << 7) | uint16_t (lsb & std::byte { 0x7f })); const auto newStatus = (uint8_t) (accumulator.getKind() == PnKind::nrpn ? 0x3 : 0x2); packet = PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, (uint8_t) ((newStatus << 0x4) | channel), bank, index), + Utils::bytesToWord (helpers.typeAndGroup, std::byte ((newStatus << 0x4) | channel), bank, index), Conversion::scaleTo32 (value) }; return true; @@ -103,35 +103,41 @@ bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues hel if (cc == 0) { - groupBanks[group][channel].setMsb (byte); + groupBanks[group][channel].setMsb (uint8_t (byte)); return false; } if (cc == 32) { - groupBanks[group][channel].setLsb (byte); + groupBanks[group][channel].setLsb (uint8_t (byte)); return false; } packet = PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, cc, 0), - Conversion::scaleTo32 (helpers.byte2) + Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, std::byte { cc }, std::byte { 0 }), + Conversion::scaleTo32 (uint8_t (helpers.byte2)) }; return true; } PacketX2 Midi1ToMidi2DefaultTranslator::processProgramChange (const HelperValues helpers) const { - const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); - const auto channel = (uint8_t) (helpers.byte0 & 0xf); + const auto group = (uint8_t) (helpers.typeAndGroup & std::byte { 0xf }); + const auto channel = (uint8_t) (helpers.byte0 & std::byte { 0xf }); const auto bank = groupBanks[group][channel]; const auto valid = bank.isValid(); return PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, valid ? 1 : 0), - Utils::bytesToWord (helpers.byte1, 0, valid ? bank.getMsb() : 0, valid ? bank.getLsb() : 0) + Utils::bytesToWord (helpers.typeAndGroup, + helpers.byte0, + std::byte { 0 }, + valid ? std::byte { 1 } : std::byte { 0 }), + Utils::bytesToWord (helpers.byte1, + std::byte { 0 }, + valid ? std::byte { bank.getMsb() } : std::byte { 0 }, + valid ? std::byte { bank.getLsb() } : std::byte { 0 }) }; } @@ -139,8 +145,8 @@ PacketX2 Midi1ToMidi2DefaultTranslator::processChannelPressure (const HelperValu { return PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), - Conversion::scaleTo32 (helpers.byte1) + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, std::byte { 0 }, std::byte { 0 }), + Conversion::scaleTo32 (uint8_t (helpers.byte1)) }; } @@ -148,16 +154,16 @@ PacketX2 Midi1ToMidi2DefaultTranslator::processPitchBend (const HelperValues hel { const auto lsb = helpers.byte1; const auto msb = helpers.byte2; - const auto value = (uint16_t) (msb << 7 | lsb); + const auto value = uint16_t (uint16_t (msb) << 7 | uint16_t (lsb)); return PacketX2 { - Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, std::byte { 0 }, std::byte { 0 }), Conversion::scaleTo32 (value) }; } -bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, uint8_t byte) +bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, std::byte byte) { const auto isStart = cc == 99 || cc == 101; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h index 4267180..a70a75f 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h @@ -61,10 +61,10 @@ class Midi1ToMidi2DefaultTranslator const HelperValues helperValues { - (uint8_t) ((0x4 << 0x4) | Utils::getGroup (firstWord)), - (uint8_t) ((firstWord >> 0x10) & 0xff), - (uint8_t) ((firstWord >> 0x08) & 0x7f), - (uint8_t) ((firstWord >> 0x00) & 0x7f), + std::byte ((0x4 << 0x4) | Utils::getGroup (firstWord)), + std::byte ((firstWord >> 0x10) & 0xff), + std::byte ((firstWord >> 0x08) & 0x7f), + std::byte ((firstWord >> 0x00) & 0x7f), }; switch (Utils::getStatus (firstWord)) @@ -128,10 +128,10 @@ class Midi1ToMidi2DefaultTranslator struct HelperValues { - uint8_t typeAndGroup; - uint8_t byte0; - uint8_t byte1; - uint8_t byte2; + std::byte typeAndGroup; + std::byte byte0; + std::byte byte1; + std::byte byte2; }; static PacketX2 processNoteOnOrOff (const HelperValues helpers); @@ -147,13 +147,13 @@ class Midi1ToMidi2DefaultTranslator class PnAccumulator { public: - bool addByte (uint8_t cc, uint8_t byte); + bool addByte (uint8_t cc, std::byte byte); - const std::array& getBytes() const noexcept { return bytes; } + const std::array& getBytes() const noexcept { return bytes; } PnKind getKind() const noexcept { return kind; } private: - std::array bytes; + std::array bytes; uint8_t index = 0; PnKind kind = PnKind::nrpn; }; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp index 0d1fafc..6fbb6cb 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.cpp @@ -39,12 +39,12 @@ SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) return { - { { packet.getU8<2>(), - packet.getU8<3>(), - packet.getU8<4>(), - packet.getU8<5>(), - packet.getU8<6>(), - packet.getU8<7>() } }, + { { std::byte { packet.getU8<2>() }, + std::byte { packet.getU8<3>() }, + std::byte { packet.getU8<4>() }, + std::byte { packet.getU8<5>() }, + std::byte { packet.getU8<6>() }, + std::byte { packet.getU8<7>() } } }, jmin (numBytes, maxBytes) }; } diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h index a3f3e3f..962f9c2 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPSysEx7.h @@ -63,7 +63,7 @@ struct SysEx7 /** Holds the bytes from a single SysEx-7 packet. */ struct PacketBytes { - std::array data; + std::array data; uint8_t size; }; diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h index 1e899f7..96a5388 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMPUtils.h @@ -35,9 +35,12 @@ namespace universal_midi_packets struct Utils { /** Joins 4 bytes into a single 32-bit word. */ - static constexpr uint32_t bytesToWord (uint8_t a, uint8_t b, uint8_t c, uint8_t d) + static constexpr uint32_t bytesToWord (std::byte a, std::byte b, std::byte c, std::byte d) { - return uint32_t (a << 0x18 | b << 0x10 | c << 0x08 | d << 0x00); + return uint32_t (a) << 0x18 + | uint32_t (b) << 0x10 + | uint32_t (c) << 0x08 + | uint32_t (d) << 0x00; } /** Returns the expected number of 32-bit words in a Universal MIDI Packet, given diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp index 3012054..a0c88a8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/ump/juce_UMP_test.cpp @@ -48,18 +48,18 @@ class UniversalMidiPacketTests : public UnitTest forEachNonSysExTestMessage (random, [&] (const MidiMessage& m) { - Packets packets; - Conversion::toMidi1 (m, packets); + const auto packets = toMidi1 (m); expect (packets.size() == 1); // Make sure that the message type is correct - expect (Utils::getMessageType (packets.data()[0]) == ((m.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2)); + const auto msgType = Utils::getMessageType (packets.data()[0]); + expect (msgType == ((m.getRawData()[0] >> 0x4) == 0xf ? 0x1 : 0x2)); translator.dispatch (View {packets.data() }, 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - expect (equal (m, roundTripped)); + expect (equal (m, roundTripped.getMessage())); }); }); } @@ -68,8 +68,7 @@ class UniversalMidiPacketTests : public UnitTest { { // Zero length message - Packets packets; - Conversion::toMidi1 (createRandomSysEx (random, 0), packets); + const auto packets = toMidi1 (createRandomSysEx (random, 0)); expect (packets.size() == 2); expect (packets.data()[0] == 0x30000000); @@ -78,51 +77,50 @@ class UniversalMidiPacketTests : public UnitTest { const auto message = createRandomSysEx (random, 1); - Packets packets; - Conversion::toMidi1 (message, packets); + const auto packets = toMidi1 (message); expect (packets.size() == 2); const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x01, sysEx[0], 0)); + expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, + std::byte { 0x01 }, + std::byte { sysEx[0] }, + std::byte { 0 })); expect (packets.data()[1] == 0x00000000); } { const auto message = createRandomSysEx (random, 6); - Packets packets; - Conversion::toMidi1 (message, packets); + const auto packets = toMidi1 (message); expect (packets.size() == 2); const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x06, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); + expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x06 }, std::byte { sysEx[0] }, std::byte { sysEx[1] })); + expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] })); } { const auto message = createRandomSysEx (random, 12); - Packets packets; - Conversion::toMidi1 (message, packets); + const auto packets = toMidi1 (message); expect (packets.size() == 4); const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); - expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x36, sysEx[6], sysEx[7])); - expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11])); + expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x16 }, std::byte { sysEx[0] }, std::byte { sysEx[1] })); + expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] })); + expect (packets.data()[2] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x36 }, std::byte { sysEx[6] }, std::byte { sysEx[7] })); + expect (packets.data()[3] == Utils::bytesToWord (std::byte { sysEx[8] }, std::byte { sysEx[9] }, std::byte { sysEx[10] }, std::byte { sysEx[11] })); } { const auto message = createRandomSysEx (random, 13); - Packets packets; - Conversion::toMidi1 (message, packets); + const auto packets = toMidi1 (message); expect (packets.size() == 6); const auto* sysEx = message.getSysExData(); - expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x16, sysEx[0], sysEx[1])); - expect (packets.data()[1] == Utils::bytesToWord (sysEx[2], sysEx[3], sysEx[4], sysEx[5])); - expect (packets.data()[2] == Utils::bytesToWord (0x30, 0x26, sysEx[6], sysEx[7])); - expect (packets.data()[3] == Utils::bytesToWord (sysEx[8], sysEx[9], sysEx[10], sysEx[11])); - expect (packets.data()[4] == Utils::bytesToWord (0x30, 0x31, sysEx[12], 0)); + expect (packets.data()[0] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x16 }, std::byte { sysEx[0] }, std::byte { sysEx[1] })); + expect (packets.data()[1] == Utils::bytesToWord (std::byte { sysEx[2] }, std::byte { sysEx[3] }, std::byte { sysEx[4] }, std::byte { sysEx[5] })); + expect (packets.data()[2] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x26 }, std::byte { sysEx[6] }, std::byte { sysEx[7] })); + expect (packets.data()[3] == Utils::bytesToWord (std::byte { sysEx[8] }, std::byte { sysEx[9] }, std::byte { sysEx[10] }, std::byte { sysEx[11] })); + expect (packets.data()[4] == Utils::bytesToWord (std::byte { 0x30 }, std::byte { 0x31 }, std::byte { sysEx[12] }, std::byte { 0 })); expect (packets.data()[5] == 0x00000000); } } @@ -133,15 +131,15 @@ class UniversalMidiPacketTests : public UnitTest const auto checkRoundTrip = [&] (const MidiBuffer& expected) { for (const auto meta : expected) - Conversion::toMidi1 (meta.getMessage(), packets); + Conversion::toMidi1 (ump::BytestreamMidiView (meta), [&] (const auto p) { packets.add (p); }); MidiBuffer output; converter.dispatch (packets.data(), packets.data() + packets.size(), 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp)); }); packets.clear(); @@ -161,8 +159,7 @@ class UniversalMidiPacketTests : public UnitTest beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream"); { const auto sysEx = createRandomSysEx (random, 100); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); + const auto originalPackets = toMidi1 (sysEx); Packets modifiedPackets; @@ -183,9 +180,9 @@ class UniversalMidiPacketTests : public UnitTest converter.dispatch (modifiedPackets.data(), modifiedPackets.data() + modifiedPackets.size(), 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp)); }); // All Utility messages should have been ignored @@ -198,8 +195,7 @@ class UniversalMidiPacketTests : public UnitTest beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream"); { const auto sysEx = createRandomSysEx (random, 200); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); + const auto originalPackets = toMidi1 (sysEx); Packets modifiedPackets; MidiBuffer realtimeMessages; @@ -222,9 +218,9 @@ class UniversalMidiPacketTests : public UnitTest converter.dispatch (modifiedPackets.data(), modifiedPackets.data() + modifiedPackets.size(), 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp)); }); const auto numOutputs = output.getNumEvents(); @@ -258,8 +254,7 @@ class UniversalMidiPacketTests : public UnitTest beginTest ("UMP SysEx7 messages interspersed with System Realtime and Utility messages convert to bytestream"); { const auto sysEx = createRandomSysEx (random, 300); - Packets originalPackets; - Conversion::toMidi1 (sysEx, originalPackets); + const auto originalPackets = toMidi1 (sysEx); Packets modifiedPackets; MidiBuffer realtimeMessages; @@ -290,9 +285,9 @@ class UniversalMidiPacketTests : public UnitTest converter.dispatch (modifiedPackets.data(), modifiedPackets.data() + modifiedPackets.size(), 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp)); }); const auto numOutputs = output.getNumEvents(); @@ -336,19 +331,14 @@ class UniversalMidiPacketTests : public UnitTest Packets p; for (const auto meta : noteOn) - Conversion::toMidi1 (meta.getMessage(), p); + Conversion::toMidi1 (ump::BytestreamMidiView (meta), [&] (const auto packet) { p.add (packet); }); return p; }(); const auto sysEx = createRandomSysEx (random, 300); - const auto originalPackets = [&] - { - Packets p; - Conversion::toMidi1 (sysEx, p); - return p; - }(); + const auto originalPackets = toMidi1 (sysEx); const auto modifiedPackets = [&] { @@ -378,9 +368,9 @@ class UniversalMidiPacketTests : public UnitTest converter.dispatch (p.data(), p.data() + p.size(), 0, - [&] (const MidiMessage& roundTripped) + [&] (const BytestreamMidiView& roundTripped) { - output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + output.addEvent (roundTripped.getMessage(), int (roundTripped.timestamp)); }); }; @@ -390,8 +380,7 @@ class UniversalMidiPacketTests : public UnitTest expect (equal (output, noteOn)); const auto newSysEx = createRandomSysEx (random, 300); - Packets newSysExPackets; - Conversion::toMidi1 (newSysEx, newSysExPackets); + const auto newSysExPackets = toMidi1 (newSysEx); // If we push another midi event without interrupting it, // it should get through without being modified, @@ -678,12 +667,17 @@ class UniversalMidiPacketTests : public UnitTest beginTest ("MIDI 2 -> 1 messages which don't convert"); { - const uint8_t opcodes[] { 0x0, 0x1, 0x4, 0x5, 0x6, 0xf }; + const std::byte opcodes[] { std::byte { 0x0 }, + std::byte { 0x1 }, + std::byte { 0x4 }, + std::byte { 0x5 }, + std::byte { 0x6 }, + std::byte { 0xf } }; for (const auto opcode : opcodes) { Packets midi2; - midi2.add (PacketX2 { Utils::bytesToWord (0x40, (uint8_t) (opcode << 0x4), 0, 0), 0x0 }); + midi2.add (PacketX2 { Utils::bytesToWord (std::byte { 0x40 }, std::byte { opcode << 0x4 }, std::byte { 0 }, std::byte { 0 }), 0x0 }); checkMidi2ToMidi1Conversion (midi2, {}); } } @@ -785,7 +779,7 @@ class UniversalMidiPacketTests : public UnitTest for (const auto cc : CCs) { Packets midi1; - midi1.add (PacketX1 { Utils::bytesToWord (0x20, 0xb0, cc, 0x00) }); + midi1.add (PacketX1 { Utils::bytesToWord (std::byte { 0x20 }, std::byte { 0xb0 }, std::byte { cc }, std::byte { 0x00 }) }); checkMidi1ToMidi2Conversion (midi1, {}); } @@ -874,6 +868,13 @@ class UniversalMidiPacketTests : public UnitTest } private: + static Packets toMidi1 (const MidiMessage& msg) + { + Packets packets; + Conversion::toMidi1 (ump::BytestreamMidiView (&msg), [&] (const auto p) { packets.add (p); }); + return packets; + } + static Packets convertMidi2ToMidi1 (const Packets& midi2) { Packets r; @@ -934,10 +935,10 @@ class UniversalMidiPacketTests : public UnitTest { const auto status = random.nextInt (3); - return PacketX1 { Utils::bytesToWord (0, - uint8_t (status << 0x4), - uint8_t (status == 0 ? 0 : random.nextInt (0x100)), - uint8_t (status == 0 ? 0 : random.nextInt (0x100))) }; + return PacketX1 { Utils::bytesToWord (std::byte { 0 }, + std::byte (status << 0x4), + std::byte (status == 0 ? 0 : random.nextInt (0x100)), + std::byte (status == 0 ? 0 : random.nextInt (0x100))) }; } PacketX1 createRandomRealtimeUMP (Random& random) @@ -946,19 +947,19 @@ class UniversalMidiPacketTests : public UnitTest { switch (random.nextInt (6)) { - case 0: return 0xf8; - case 1: return 0xfa; - case 2: return 0xfb; - case 3: return 0xfc; - case 4: return 0xfe; - case 5: return 0xff; + case 0: return std::byte { 0xf8 }; + case 1: return std::byte { 0xfa }; + case 2: return std::byte { 0xfb }; + case 3: return std::byte { 0xfc }; + case 4: return std::byte { 0xfe }; + case 5: return std::byte { 0xff }; } jassertfalse; - return 0x00; + return std::byte { 0x00 }; }(); - return PacketX1 { Utils::bytesToWord (0x10, uint8_t (status), 0x00, 0x00) }; + return PacketX1 { Utils::bytesToWord (std::byte { 0x10 }, status, std::byte { 0x00 }, std::byte { 0x00 }) }; } template diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp index ddf31f6..fabcb04 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp @@ -107,7 +107,7 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer& outputAudio, int numSamples) { // you must set the sample rate before using this! - jassert (sampleRate != 0); + jassert (! approximatelyEqual (sampleRate, 0.0)); const ScopedLock sl (noteStateLock); @@ -144,7 +144,7 @@ template void MPESynthesiserBase::renderNextBlock (AudioBuffer&, //============================================================================== void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) { - if (sampleRate != newRate) + if (! approximatelyEqual (sampleRate, newRate)) { const ScopedLock sl (noteStateLock); instrument.releaseAllNotes(); diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index 9cc3523..8f42d00 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -108,14 +108,11 @@ void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message) if (! message.isController()) return; - MidiRPNMessage rpn; - - if (rpnDetector.parseControllerMessage (message.getChannel(), + if (auto parsed = rpnDetector.tryParse (message.getChannel(), message.getControllerNumber(), - message.getControllerValue(), - rpn)) + message.getControllerValue())) { - processRpnMessage (rpn); + processRpnMessage (*parsed); } } diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h index 0577db0..46969b8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h @@ -35,6 +35,8 @@ namespace juce It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and master pitchbends, respectively. + + @tags{Audio} */ struct MPEZone { diff --git a/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h b/JuceLibraryCode/modules/juce_audio_basics/native/juce_CoreAudioLayouts_mac.h similarity index 98% rename from JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h rename to JuceLibraryCode/modules/juce_audio_basics/native/juce_CoreAudioLayouts_mac.h index 16c4471..e13dab2 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h +++ b/JuceLibraryCode/modules/juce_audio_basics/native/juce_CoreAudioLayouts_mac.h @@ -263,13 +263,13 @@ struct CoreAudioLayouts } } - auto numChannels = tag & 0xffff; + const auto numChannels = tag & 0xffff; + if (tag >= coreAudioHOASN3DLayoutTag && tag <= (coreAudioHOASN3DLayoutTag | 0xffff)) { - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); + const auto ambisonicOrder = AudioChannelSet::getAmbisonicOrderForNumChannels (static_cast (numChannels)); - if (static_cast (ambisonicOrder) == sqrtMinusOne) + if (ambisonicOrder != -1) return AudioChannelSet::ambisonic (ambisonicOrder).getChannelTypes(); } diff --git a/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h b/JuceLibraryCode/modules/juce_audio_basics/native/juce_CoreAudioTimeConversions_mac.h similarity index 100% rename from JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioTimeConversions.h rename to JuceLibraryCode/modules/juce_audio_basics/native/juce_CoreAudioTimeConversions_mac.h diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp index a5a2494..956eb17 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp @@ -51,7 +51,7 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne { auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); - if (newSampleRate != sampleRate + if (! approximatelyEqual (newSampleRate, sampleRate) || bufferSizeNeeded != buffer.getNumSamples() || ! isPrepared) { diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp index ca8823c..8ef3c92 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp @@ -108,26 +108,6 @@ void MemoryAudioSource::setLooping (bool shouldLoop) //============================================================================== #if JUCE_UNIT_TESTS -static bool operator== (const AudioBuffer& a, const AudioBuffer& b) -{ - if (a.getNumChannels() != b.getNumChannels()) - return false; - - for (int channel = 0; channel < a.getNumChannels(); ++channel) - { - auto* aPtr = a.getReadPointer (channel); - auto* bPtr = b.getReadPointer (channel); - - if (std::vector (aPtr, aPtr + a.getNumSamples()) - != std::vector (bPtr, bPtr + b.getNumSamples())) - { - return false; - } - } - - return true; -} - struct MemoryAudioSourceTests : public UnitTest { MemoryAudioSourceTests() : UnitTest ("MemoryAudioSource", UnitTestCategories::audio) {} @@ -184,7 +164,7 @@ struct MemoryAudioSourceTests : public UnitTest play (source, channelInfo); for (int sample = 0; sample < buffer.getNumSamples(); ++sample) - expect (bufferToFill.getSample (0, sample + buffer.getNumSamples()) == buffer.getSample (0, sample)); + expectEquals (bufferToFill.getSample (0, sample + buffer.getNumSamples()), buffer.getSample (0, sample)); expect (! isSilent (bufferToFill)); } @@ -219,7 +199,7 @@ struct MemoryAudioSourceTests : public UnitTest for (int i = 0; i < 100; ++i) { play (source, channelInfo); - expect (bufferToFill.getSample (0, 0) == buffer.getSample (0, (i * blockSize) % buffer.getNumSamples())); + expectEquals (bufferToFill.getSample (0, 0), buffer.getSample (0, (i * blockSize) % buffer.getNumSamples())); } } } diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp index 9f92326..6c89a67 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp @@ -88,7 +88,7 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf localRatio = ratio; } - if (lastRatio != localRatio) + if (! approximatelyEqual (lastRatio, localRatio)) { createLowPass (localRatio); lastRatio = localRatio; diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp index ae7e91b..9e9d339 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp @@ -62,7 +62,7 @@ void ToneGeneratorAudioSource::releaseResources() void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) { - if (phasePerSample == 0.0) + if (approximatelyEqual (phasePerSample, 0.0)) phasePerSample = MathConstants::twoPi / (sampleRate / frequency); for (int i = 0; i < info.numSamples; ++i) diff --git a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 62a61f5..081406d 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -153,7 +153,7 @@ void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shoul //============================================================================== void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) { - if (sampleRate != newRate) + if (! approximatelyEqual (sampleRate, newRate)) { const ScopedLock sl (lock); allNotesOff (0, false); @@ -171,7 +171,7 @@ void Synthesiser::processNextBlock (AudioBuffer& outputAudio, int numSamples) { // must set the sample rate before using this! - jassert (sampleRate != 0); + jassert (! exactlyEqual (sampleRate, 0.0)); const int targetChannels = outputAudio.getNumChannels(); auto midiIterator = midiData.findNextSamplePosition (startSample); diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h index cdd3fd2..dd93631 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h @@ -60,6 +60,20 @@ class Decibels : minusInfinityDb; } + /** Restricts a gain value based on a lower bound specified in dBFS. + + This is useful if you want to make sure a gain value never reaches zero. + */ + template + static Type gainWithLowerBound (Type gain, Type lowerBoundDb) + { + // You probably want to use a negative decibel value or the gain will + // be restricted to boosting only! + jassert (lowerBoundDb < (Type) 0.0); + + return jmax ((Type) gain, Decibels::decibelsToGain (lowerBoundDb, lowerBoundDb - (Type) 1.0)); + } + //============================================================================== /** Converts a decibel reading to a string. diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp index fed8a40..0d40a22 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp @@ -23,6 +23,8 @@ namespace juce { +constexpr auto minimumDecibels = -300.0f; + IIRCoefficients::IIRCoefficients() noexcept { zeromem (coefficients, sizeof (coefficients)); @@ -44,7 +46,7 @@ IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexc IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) noexcept { - auto a = 1.0 / c4; + const auto a = 1.0 / c4; coefficients[0] = (float) (c1 * a); coefficients[1] = (float) (c2 * a); @@ -67,9 +69,9 @@ IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + const auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + const auto nSquared = n * n; + const auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1, c1 * 2.0, @@ -93,9 +95,9 @@ IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto n = std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + const auto n = std::tan (MathConstants::pi * frequency / sampleRate); + const auto nSquared = n * n; + const auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1, c1 * -2.0, @@ -119,9 +121,9 @@ IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + const auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + const auto nSquared = n * n; + const auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1 * n / Q, 0.0, @@ -145,9 +147,9 @@ IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + n / Q + nSquared); + const auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + const auto nSquared = n * n; + const auto c1 = 1.0 / (1.0 + n / Q + nSquared); return IIRCoefficients (c1 * (1.0 + nSquared), 2.0 * c1 * (1.0 - nSquared), @@ -171,9 +173,9 @@ IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + const auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + const auto nSquared = n * n; + const auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), c1 * 2.0 * (1.0 - nSquared), @@ -192,13 +194,13 @@ IIRCoefficients IIRCoefficients::makeLowShelf (double sampleRate, jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto aminus1 = A - 1.0; - auto aplus1 = A + 1.0; - auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; - auto coso = std::cos (omega); - auto beta = std::sin (omega) * std::sqrt (A) / Q; - auto aminus1TimesCoso = aminus1 * coso; + const auto A = std::sqrt (Decibels::gainWithLowerBound (gainFactor, minimumDecibels)); + const auto aminus1 = A - 1.0; + const auto aplus1 = A + 1.0; + const auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; + const auto coso = std::cos (omega); + const auto beta = std::sin (omega) * std::sqrt (A) / Q; + const auto aminus1TimesCoso = aminus1 * coso; return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta), A * 2.0 * (aminus1 - aplus1 * coso), @@ -217,13 +219,13 @@ IIRCoefficients IIRCoefficients::makeHighShelf (double sampleRate, jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto aminus1 = A - 1.0; - auto aplus1 = A + 1.0; - auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; - auto coso = std::cos (omega); - auto beta = std::sin (omega) * std::sqrt (A) / Q; - auto aminus1TimesCoso = aminus1 * coso; + const auto A = std::sqrt (Decibels::gainWithLowerBound (gainFactor, minimumDecibels)); + const auto aminus1 = A - 1.0; + const auto aplus1 = A + 1.0; + const auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; + const auto coso = std::cos (omega); + const auto beta = std::sin (omega) * std::sqrt (A) / Q; + const auto aminus1TimesCoso = aminus1 * coso; return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta), A * -2.0 * (aminus1 + aplus1 * coso), @@ -242,12 +244,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate, jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); jassert (Q > 0.0); - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto omega = (MathConstants::twoPi * jmax (frequency, 2.0)) / sampleRate; - auto alpha = 0.5 * std::sin (omega) / Q; - auto c2 = -2.0 * std::cos (omega); - auto alphaTimesA = alpha * A; - auto alphaOverA = alpha / A; + const auto A = std::sqrt (Decibels::gainWithLowerBound (gainFactor, minimumDecibels)); + const auto omega = (MathConstants::twoPi * jmax (frequency, 2.0)) / sampleRate; + const auto alpha = 0.5 * std::sin (omega) / Q; + const auto c2 = -2.0 * std::cos (omega); + const auto alphaTimesA = alpha * A; + const auto alphaOverA = alpha / A; return IIRCoefficients (1.0 + alphaTimesA, c2, diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Interpolators.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Interpolators.h index 1588834..801c344 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Interpolators.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Interpolators.h @@ -73,7 +73,7 @@ class Interpolators sign = (sincPosition < 0 ? -1 : 1); } - if (sincPosition == 0.0f) + if (approximatelyEqual (sincPosition, 0.0f)) result += inputs[samplePosition]; else if (sincPosition < floatCrossings && sincPosition > -floatCrossings) result += inputs[samplePosition] * windowedSinc (firstFrac, index); diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h index 613bb83..0497dd1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h @@ -237,7 +237,8 @@ class SmoothedValue : public SmoothedValueBase && initialValue == 0)); + jassert (! (std::is_same_v + && approximatelyEqual (initialValue, (FloatType) 0))); // Visual Studio can't handle base class initialisation with CRTP this->currentValue = initialValue; @@ -270,7 +271,7 @@ class SmoothedValue : public SmoothedValueBase target) + if (approximatelyEqual (newValue, this->target)) return; if (stepsToTarget <= 0) @@ -280,7 +281,8 @@ class SmoothedValue : public SmoothedValueBase && newValue == 0)); + jassert (! (std::is_same_v + && approximatelyEqual (newValue, (FloatType) 0))); this->target = newValue; this->countdown = stepsToTarget; diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 33b4571..5e238db 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -220,6 +220,12 @@ void AudioDeviceManager::audioDeviceListChanged() sendChangeMessage(); } +void AudioDeviceManager::midiDeviceListChanged() +{ + openLastRequestedMidiDevices (midiDeviceInfosFromXml, defaultMidiOutputDeviceInfo); + sendChangeMessage(); +} + //============================================================================== static void addIfNotNull (OwnedArray& list, AudioIODeviceType* const device) { @@ -430,65 +436,62 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, if (error.isNotEmpty() && selectDefaultDeviceOnFailure) error = initialise (numInputChansNeeded, numOutputChansNeeded, nullptr, false, preferredDefaultDeviceName); - midiDeviceInfosFromXml.clear(); enabledMidiInputs.clear(); - for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) - midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); - - auto isIdentifierAvailable = [] (const Array& available, const String& identifier) + const auto midiInputs = [&] { - for (auto& device : available) - if (device.identifier == identifier) - return true; + Array result; - return false; - }; + for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) + result.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); - auto getUpdatedIdentifierForName = [&] (const Array& available, const String& name) -> String - { - for (auto& device : available) - if (device.name == name) - return device.identifier; + return result; + }(); - return {}; - }; + const MidiDeviceInfo defaultOutputDeviceInfo (xml.getStringAttribute ("defaultMidiOutput"), + xml.getStringAttribute ("defaultMidiOutputDevice")); - auto inputs = MidiInput::getAvailableDevices(); + openLastRequestedMidiDevices (midiInputs, defaultOutputDeviceInfo); - for (auto& info : midiDeviceInfosFromXml) + return error; +} + +void AudioDeviceManager::openLastRequestedMidiDevices (const Array& desiredInputs, const MidiDeviceInfo& defaultOutput) +{ + const auto openDeviceIfAvailable = [&] (const Array& devices, + const MidiDeviceInfo& deviceToOpen, + auto&& doOpen) { - if (isIdentifierAvailable (inputs, info.identifier)) + const auto iterWithMatchingIdentifier = std::find_if (devices.begin(), devices.end(), [&] (const auto& x) { - setMidiInputDeviceEnabled (info.identifier, true); + return x.identifier == deviceToOpen.identifier; + }); + + if (iterWithMatchingIdentifier != devices.end()) + { + doOpen (deviceToOpen.identifier); + return; } - else + + const auto iterWithMatchingName = std::find_if (devices.begin(), devices.end(), [&] (const auto& x) { - auto identifier = getUpdatedIdentifierForName (inputs, info.name); + return x.name == deviceToOpen.name; + }); - if (identifier.isNotEmpty()) - setMidiInputDeviceEnabled (identifier, true); - } - } + if (iterWithMatchingName != devices.end()) + doOpen (iterWithMatchingName->identifier); + }; - MidiDeviceInfo defaultOutputDeviceInfo (xml.getStringAttribute ("defaultMidiOutput"), - xml.getStringAttribute ("defaultMidiOutputDevice")); + midiDeviceInfosFromXml = desiredInputs; - auto outputs = MidiOutput::getAvailableDevices(); + const auto inputs = MidiInput::getAvailableDevices(); - if (isIdentifierAvailable (outputs, defaultOutputDeviceInfo.identifier)) - { - setDefaultMidiOutputDevice (defaultOutputDeviceInfo.identifier); - } - else - { - auto identifier = getUpdatedIdentifierForName (outputs, defaultOutputDeviceInfo.name); + for (const auto& info : midiDeviceInfosFromXml) + openDeviceIfAvailable (inputs, info, [&] (const auto identifier) { setMidiInputDeviceEnabled (identifier, true); }); - if (identifier.isNotEmpty()) - setDefaultMidiOutputDevice (identifier); - } + const auto outputs = MidiOutput::getAvailableDevices(); - return error; + openDeviceIfAvailable (outputs, defaultOutput, [&] (const auto identifier) { setDefaultMidiOutputDevice (identifier); }); } String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index c2007b7..b827c39 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -499,6 +499,10 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster std::unique_ptr lastExplicitSettings; mutable bool listNeedsScanning = true; AudioBuffer tempBuffer; + MidiDeviceListConnection midiDeviceListConnection = MidiDeviceListConnection::make ([this] + { + midiDeviceListChanged(); + }); struct MidiCallbackInfo { @@ -537,6 +541,7 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster void audioDeviceErrorInt (const String&); void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); void audioDeviceListChanged(); + void midiDeviceListChanged(); String restartDevice (int blockSizeToUse, double sampleRateToUse, const BigInteger& ins, const BigInteger& outs); @@ -554,6 +559,7 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*); String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup*); + void openLastRequestedMidiDevices (const Array&, const MidiDeviceInfo&); AudioIODeviceType* findType (const String& inputName, const String& outputName); AudioIODeviceType* findType (const String& typeName); diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h index 15ba835..5be6873 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h @@ -25,7 +25,11 @@ namespace juce class AudioIODevice; -/** Additional information that may be passed to the AudioIODeviceCallback. */ +/** + Additional information that may be passed to the AudioIODeviceCallback. + + @tags{Audio} +*/ struct AudioIODeviceCallbackContext { /** If the host provides this information, this field will be set to point to diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index 38393a3..e3599a4 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -116,7 +116,7 @@ class JUCE_API AudioIODeviceType /** A class for receiving events when audio devices are inserted or removed. - You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object + You can register an AudioIODeviceType::Listener with an AudioIODeviceType object using the AudioIODeviceType::addListener() method, and it will be called when devices of that type are added or removed. diff --git a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp index 2d07436..f59dde1 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp @@ -46,9 +46,11 @@ #include "juce_audio_devices.h" #include "audio_io/juce_SampleRateHelpers.cpp" +#include "midi_io/juce_MidiDevices.cpp" //============================================================================== #if JUCE_MAC || JUCE_IOS + #include #include #include "midi_io/ump/juce_UMPBytestreamInputHandler.h" #include "midi_io/ump/juce_UMPU32InputHandler.h" @@ -63,8 +65,8 @@ #undef Point #undef Component - #include "native/juce_mac_CoreAudio.cpp" - #include "native/juce_mac_CoreMidi.mm" + #include "native/juce_CoreAudio_mac.cpp" + #include "native/juce_CoreMidi_mac.mm" #elif JUCE_IOS #import @@ -75,18 +77,18 @@ #import #endif - #include "native/juce_ios_Audio.cpp" - #include "native/juce_mac_CoreMidi.mm" + #include "native/juce_Audio_ios.cpp" + #include "native/juce_CoreMidi_mac.mm" //============================================================================== #elif JUCE_WINDOWS #if JUCE_WASAPI #include - #include "native/juce_win32_WASAPI.cpp" + #include "native/juce_WASAPI_windows.cpp" #endif #if JUCE_DIRECTSOUND - #include "native/juce_win32_DirectSound.cpp" + #include "native/juce_DirectSound_windows.cpp" #endif #if JUCE_USE_WINRT_MIDI && (JUCE_MSVC || JUCE_CLANG) @@ -113,7 +115,7 @@ #endif #include - #include "native/juce_win32_Midi.cpp" + #include "native/juce_Midi_windows.cpp" #if JUCE_ASIO /* This is very frustrating - we only need to use a handful of definitions from @@ -136,7 +138,7 @@ needed - so to simplify things, you could just copy these into your JUCE directory). */ #include - #include "native/juce_win32_ASIO.cpp" + #include "native/juce_ASIO_windows.cpp" #endif //============================================================================== @@ -150,8 +152,10 @@ If you don't have the ALSA library and don't want to build JUCE with audio support, just set the JUCE_ALSA flag to 0. */ + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-length-array") #include - #include "native/juce_linux_ALSA.cpp" + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #include "native/juce_ALSA_linux.cpp" #endif #if JUCE_JACK @@ -164,7 +168,7 @@ JUCE with low latency audio support, just set the JUCE_JACK flag to 0. */ #include - #include "native/juce_linux_JackAudio.cpp" + #include "native/juce_JackAudio_linux.cpp" #endif #if (JUCE_LINUX && JUCE_BELA) @@ -175,14 +179,14 @@ #include #include #include - #include "native/juce_linux_Bela.cpp" + #include "native/juce_Bela_linux.cpp" #endif #undef SIZEOF #if ! JUCE_BELA #include - #include "native/juce_linux_Midi.cpp" + #include "native/juce_Midi_linux.cpp" #endif //============================================================================== @@ -194,19 +198,19 @@ namespace juce RealtimeThreadFactory getAndroidRealtimeThreadFactory(); } // namespace juce -#include "native/juce_android_Audio.cpp" +#include "native/juce_Audio_android.cpp" #include - #include "native/juce_android_Midi.cpp" + #include "native/juce_Midi_android.cpp" #if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE - #include "native/juce_android_HighPerformanceAudioHelpers.h" + #include "native/juce_HighPerformanceAudioHelpers_android.h" #if JUCE_USE_ANDROID_OPENSLES #include #include #include - #include "native/juce_android_OpenSL.cpp" + #include "native/juce_OpenSL_android.cpp" #endif #if JUCE_USE_ANDROID_OBOE @@ -222,7 +226,7 @@ namespace juce #include JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #include "native/juce_android_Oboe.cpp" + #include "native/juce_Oboe_android.cpp" #endif #else // No audio library, so no way to create realtime threads. @@ -249,6 +253,5 @@ namespace juce #include "audio_io/juce_AudioIODevice.cpp" #include "audio_io/juce_AudioIODeviceType.cpp" #include "midi_io/juce_MidiMessageCollector.cpp" -#include "midi_io/juce_MidiDevices.cpp" #include "sources/juce_AudioSourcePlayer.cpp" #include "sources/juce_AudioTransportSource.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.h b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.h index 15d1d6b..2abac02 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.h +++ b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.h @@ -32,7 +32,7 @@ ID: juce_audio_devices vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE audio and MIDI I/O device classes description: Classes to play and record from audio and MIDI I/O devices website: http://www.juce.com/juce @@ -186,5 +186,5 @@ namespace juce #include "audio_io/juce_AudioDeviceManager.h" #if JUCE_IOS - #include "native/juce_ios_Audio.h" + #include "native/juce_Audio_ios.h" #endif diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp index 2c015e7..1c9f9a2 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp @@ -23,6 +23,81 @@ namespace juce { +class MidiDeviceListConnectionBroadcaster : private AsyncUpdater +{ +public: + ~MidiDeviceListConnectionBroadcaster() override + { + cancelPendingUpdate(); + } + + MidiDeviceListConnection::Key add (std::function callback) + { + JUCE_ASSERT_MESSAGE_THREAD + return callbacks.emplace (key++, std::move (callback)).first->first; + } + + void remove (const MidiDeviceListConnection::Key k) + { + JUCE_ASSERT_MESSAGE_THREAD + callbacks.erase (k); + } + + void notify() + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cancelPendingUpdate(); + + const State newState; + + if (std::exchange (lastNotifiedState, newState) != newState) + for (auto it = callbacks.begin(); it != callbacks.end();) + NullCheckedInvocation::invoke ((it++)->second); + } + else + { + triggerAsyncUpdate(); + } + } + + static auto& get() + { + static MidiDeviceListConnectionBroadcaster result; + return result; + } + +private: + MidiDeviceListConnectionBroadcaster() = default; + + class State + { + Array ins = MidiInput::getAvailableDevices(), outs = MidiOutput::getAvailableDevices(); + auto tie() const { return std::tie (ins, outs); } + + public: + bool operator== (const State& other) const { return tie() == other.tie(); } + bool operator!= (const State& other) const { return tie() != other.tie(); } + }; + + void handleAsyncUpdate() override + { + notify(); + } + + std::map> callbacks; + State lastNotifiedState; + MidiDeviceListConnection::Key key = 0; +}; + +//============================================================================== +MidiDeviceListConnection::~MidiDeviceListConnection() noexcept +{ + if (broadcaster != nullptr) + broadcaster->remove (key); +} + +//============================================================================== void MidiInputCallback::handlePartialSysexMessage ([[maybe_unused]] MidiInput* source, [[maybe_unused]] const uint8* messageData, [[maybe_unused]] int numBytesSoFar, diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h index 7888cac..806fc72 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h @@ -22,6 +22,87 @@ namespace juce { + +class MidiDeviceListConnectionBroadcaster; + +/** + To find out when the available MIDI devices change, call MidiDeviceListConnection::make(), + passing a lambda that will be called on each configuration change. + + To stop the lambda receiving callbacks, destroy the MidiDeviceListConnection instance returned + from make(), or call reset() on it. + + @code + // Start listening for configuration changes + auto connection = MidiDeviceListConnection::make ([] + { + // This will print a message when devices are connected/disconnected + DBG ("MIDI devices changed"); + }); + + // Stop listening + connection.reset(); + @endcode + + @tags{Audio} +*/ +class MidiDeviceListConnection +{ +public: + using Key = uint64_t; + + /** Constructs an inactive connection. + */ + MidiDeviceListConnection() = default; + + MidiDeviceListConnection (const MidiDeviceListConnection&) = delete; + MidiDeviceListConnection (MidiDeviceListConnection&& other) noexcept + : broadcaster (std::exchange (other.broadcaster, nullptr)), + key (std::exchange (other.key, Key{})) + { + } + + MidiDeviceListConnection& operator= (const MidiDeviceListConnection&) = delete; + MidiDeviceListConnection& operator= (MidiDeviceListConnection&& other) noexcept + { + MidiDeviceListConnection (std::move (other)).swap (*this); + return *this; + } + + ~MidiDeviceListConnection() noexcept; + + /** Clears this connection. + + If this object had an active connection, that connection will be deactivated, and the + corresponding callback will be removed from the MidiDeviceListConnectionBroadcaster. + */ + void reset() noexcept + { + MidiDeviceListConnection().swap (*this); + } + + /** Registers a function to be called whenever the midi device list changes. + + The callback will only be active for as long as the return MidiDeviceListConnection remains + alive. To stop receiving device change notifications, destroy the Connection object, e.g. + by allowing it to fall out of scope. + */ + static MidiDeviceListConnection make (std::function); + +private: + MidiDeviceListConnection (MidiDeviceListConnectionBroadcaster* b, const Key k) + : broadcaster (b), key (k) {} + + void swap (MidiDeviceListConnection& other) noexcept + { + std::swap (other.broadcaster, broadcaster); + std::swap (other.key, key); + } + + MidiDeviceListConnectionBroadcaster* broadcaster = nullptr; + Key key = {}; +}; + //============================================================================== /** This struct contains information about a MIDI input or output device. @@ -61,8 +142,9 @@ struct MidiDeviceInfo String identifier; //============================================================================== - bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; } - bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); } + auto tie() const { return std::tie (name, identifier); } + bool operator== (const MidiDeviceInfo& other) const noexcept { return tie() == other.tie(); } + bool operator!= (const MidiDeviceInfo& other) const noexcept { return tie() != other.tie(); } }; class MidiInputCallback; diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp index 2dbfe68..f6815fb 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp @@ -56,7 +56,7 @@ void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) // the messages that come in here need to be time-stamped correctly - see MidiInput // for details of what the number should be. - jassert (message.getTimeStamp() != 0); + jassert (! approximatelyEqual (message.getTimeStamp(), 0.0)); auto sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h index d76cd38..8f7f944 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h @@ -82,9 +82,9 @@ struct U32ToBytestreamHandler : public U32InputHandler void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override { - dispatcher.dispatch (begin, end, time, [this] (const MidiMessage& m) + dispatcher.dispatch (begin, end, time, [this] (const BytestreamMidiView& m) { - callback.handleIncomingMidiMessage (&input, m); + callback.handleIncomingMidiMessage (&input, m.getMessage()); }); } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java b/JuceLibraryCode/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java index 13433e7..bae47bf 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java +++ b/JuceLibraryCode/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; @@ -43,6 +44,7 @@ import android.bluetooth.BluetoothDevice; import android.media.midi.MidiOutputPort; import android.media.midi.MidiReceiver; +import android.os.Build; import android.os.ParcelUuid; import android.util.Log; import android.util.Pair; @@ -56,6 +58,7 @@ import java.util.List; import static android.content.Context.MIDI_SERVICE; +import static android.content.Context.BLUETOOTH_SERVICE; public class JuceMidiSupport { @@ -77,10 +80,18 @@ public interface JuceMidiPort String getName (); } + static BluetoothAdapter getDefaultBluetoothAdapter (Context ctx) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) + return BluetoothAdapter.getDefaultAdapter(); + + return ((BluetoothManager) ctx.getSystemService (BLUETOOTH_SERVICE)).getAdapter(); + } + //============================================================================== - public static class BluetoothManager extends ScanCallback + public static class BluetoothMidiManager extends ScanCallback { - BluetoothManager (Context contextToUse) + BluetoothMidiManager (Context contextToUse) { appContext = contextToUse; } @@ -92,7 +103,7 @@ public String[] getMidiBluetoothAddresses () public String getHumanReadableStringForBluetoothAddress (String address) { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + BluetoothDevice btDevice = getDefaultBluetoothAdapter (appContext).getRemoteDevice (address); return btDevice.getName (); } @@ -103,11 +114,11 @@ public int getBluetoothDeviceStatus (String address) public void startStopScan (boolean shouldStart) { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter (); + BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter (appContext); if (bluetoothAdapter == null) { - Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter"); + Log.d ("JUCE", "BluetoothMidiManager error: could not get default Bluetooth adapter"); return; } @@ -115,7 +126,7 @@ public void startStopScan (boolean shouldStart) if (bluetoothLeScanner == null) { - Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner"); + Log.d ("JUCE", "BluetoothMidiManager error: could not get Bluetooth LE scanner"); return; } @@ -140,7 +151,7 @@ public void startStopScan (boolean shouldStart) public boolean pairBluetoothMidiDevice (String address) { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + BluetoothDevice btDevice = getDefaultBluetoothAdapter (appContext).getRemoteDevice (address); if (btDevice == null) { @@ -543,12 +554,8 @@ public MidiDeviceManager (Context contextToUse) return; } - openPorts = new HashMap> (); - midiDevices = new ArrayList> (); - openTasks = new HashMap (); - btDevicesPairing = new HashMap (); - MidiDeviceInfo[] foundDevices = manager.getDevices (); + for (MidiDeviceInfo info : foundDevices) onDeviceAdded (info); @@ -810,6 +817,7 @@ public void removePort (MidiPortPath path) openPorts.remove (path); } + @Override public void onDeviceAdded (MidiDeviceInfo info) { // only add standard midi devices @@ -819,6 +827,7 @@ public void onDeviceAdded (MidiDeviceInfo info) manager.openDevice (info, this, null); } + @Override public void onDeviceRemoved (MidiDeviceInfo info) { synchronized (MidiDeviceManager.class) @@ -856,8 +865,11 @@ public void onDeviceRemoved (MidiDeviceInfo info) midiDevices.remove (devicePair); } } + + handleDevicesChanged(); } + @Override public void onDeviceStatusChanged (MidiDeviceStatus status) { } @@ -933,6 +945,7 @@ public void onDeviceOpenedDelayed (MidiDevice theDevice) BluetoothGatt gatt = openTasks.get (deviceID).getGatt (); openTasks.remove (deviceID); midiDevices.add (new Pair (theDevice, gatt)); + handleDevicesChanged(); } } else { @@ -973,7 +986,6 @@ public String getPortName (MidiPortPath path) { for (MidiDeviceInfo info : deviceInfos) { - int localIndex = 0; if (info.getId () == path.deviceId) { for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) @@ -1048,11 +1060,11 @@ private Pair getMidiDevicePairForId (int deviceId) } private MidiManager manager; - private HashMap btDevicesPairing; - private HashMap openTasks; - private ArrayList> midiDevices; + private HashMap btDevicesPairing = new HashMap(); + private HashMap openTasks = new HashMap(); + private ArrayList> midiDevices = new ArrayList>(); private MidiDeviceInfo[] deviceInfos; - private HashMap> openPorts; + private HashMap> openPorts = new HashMap>(); private Context appContext = null; } @@ -1070,9 +1082,9 @@ public static MidiDeviceManager getAndroidMidiDeviceManager (Context context) return midiDeviceManager; } - public static BluetoothManager getAndroidBluetoothManager (Context context) + public static BluetoothMidiManager getAndroidBluetoothManager (Context context) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter (); + BluetoothAdapter adapter = getDefaultBluetoothAdapter (context); if (adapter == null) return null; @@ -1083,12 +1095,15 @@ public static BluetoothManager getAndroidBluetoothManager (Context context) synchronized (JuceMidiSupport.class) { if (bluetoothManager == null) - bluetoothManager = new BluetoothManager (context); + bluetoothManager = new BluetoothMidiManager (context); } return bluetoothManager; } + // To be called when devices become (un)available + private native static void handleDevicesChanged(); + private static MidiDeviceManager midiDeviceManager = null; - private static BluetoothManager bluetoothManager = null; + private static BluetoothMidiManager bluetoothManager = null; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ALSA_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_ALSA_linux.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ASIO_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_ASIO_windows.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_android.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_ios.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_ios.cpp index b3b58ae..4f328b0 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_ios.cpp @@ -20,7 +20,7 @@ ============================================================================== */ -#include +#include namespace juce { @@ -196,7 +196,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE //============================================================================== #if JUCE_MODULE_AVAILABLE_juce_graphics - #include + #include #endif namespace juce { @@ -938,7 +938,7 @@ struct iOSAudioIODevice::Pimpl : public AsyncUpdater { if (! firstHostTime) { - if ((time->mSampleTime - lastSampleTime) != lastNumFrames) + if (! approximatelyEqual ((time->mSampleTime - lastSampleTime), (double) lastNumFrames)) xrun++; } else @@ -1159,7 +1159,7 @@ struct iOSAudioIODevice::Pimpl : public AsyncUpdater &desc, &dataSize); - if (desc.mSampleRate != 0 && desc.mSampleRate != sampleRate) + if (! approximatelyEqual (desc.mSampleRate, 0.0) && ! approximatelyEqual (desc.mSampleRate, sampleRate)) { JUCE_IOS_AUDIO_LOG ("Stream format has changed: Sample rate " << desc.mSampleRate); triggerAsyncUpdate(); diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.h b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_ios.h similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.h rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Audio_ios.h diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Bela_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Bela_linux.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp similarity index 97% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp index 785ee25..bc470fc 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreAudio_mac.cpp @@ -20,7 +20,7 @@ ============================================================================== */ -#include +#include namespace juce { @@ -966,7 +966,7 @@ class CoreAudioInternal : private Timer, jassert (timestamp == nullptr || (((timestamp->mFlags & kAudioTimeStampSampleTimeValid) != 0) && ((timestamp->mFlags & kAudioTimeStampHostTimeValid) != 0))); - if (previousSampleTime == invalidSampleTime) + if (exactlyEqual (previousSampleTime, invalidSampleTime)) previousSampleTime = timestamp != nullptr ? timestamp->mSampleTime : 0.0; if (timestamp != nullptr && std::fabs (previousSampleTime - timestamp->mSampleTime) >= 1.0) @@ -1091,7 +1091,7 @@ class CoreAudioInternal : private Timer, if (! updateDetailsFromDevice()) owner.stopInternal(); - else if ((oldBufferSize != bufferSize || oldSampleRate != sampleRate) && owner.shouldRestartDevice()) + else if ((oldBufferSize != bufferSize || ! approximatelyEqual (oldSampleRate, sampleRate)) && owner.shouldRestartDevice()) owner.restart(); } @@ -1113,39 +1113,51 @@ class CoreAudioInternal : private Timer, return noErr; } - static OSStatus deviceListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, - const AudioObjectPropertyAddress* pa, void* inClientData) + static OSStatus deviceListenerProc (AudioDeviceID /*inDevice*/, + UInt32 numAddresses, + const AudioObjectPropertyAddress* pa, + void* inClientData) { - auto intern = static_cast (inClientData); + auto& intern = *static_cast (inClientData); - switch (pa->mSelector) + const auto xruns = std::count_if (pa, pa + numAddresses, [] (const AudioObjectPropertyAddress& x) { - case kAudioDeviceProcessorOverload: - intern->xruns++; - break; + return x.mSelector == kAudioDeviceProcessorOverload; + }); - case kAudioDevicePropertyBufferSize: - case kAudioDevicePropertyBufferFrameSize: - case kAudioDevicePropertyNominalSampleRate: - case kAudioDevicePropertyStreamFormat: - case kAudioDevicePropertyDeviceIsAlive: - case kAudioStreamPropertyPhysicalFormat: - intern->deviceDetailsChanged(); - break; + intern.xruns += xruns; - case kAudioDevicePropertyDeviceHasChanged: - case kAudioObjectPropertyOwnedObjects: - intern->deviceRequestedRestart(); - break; + const auto detailsChanged = std::any_of (pa, pa + numAddresses, [] (const AudioObjectPropertyAddress& x) + { + constexpr UInt32 selectors[] + { + kAudioDevicePropertyBufferSize, + kAudioDevicePropertyBufferFrameSize, + kAudioDevicePropertyNominalSampleRate, + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyDeviceIsAlive, + kAudioStreamPropertyPhysicalFormat, + }; + + return std::find (std::begin (selectors), std::end (selectors), x.mSelector) != std::end (selectors); + }); - case kAudioDevicePropertyBufferSizeRange: - case kAudioDevicePropertyVolumeScalar: - case kAudioDevicePropertyMute: - case kAudioDevicePropertyPlayThru: - case kAudioDevicePropertyDataSource: - case kAudioDevicePropertyDeviceIsRunning: - break; - } + const auto requestedRestart = std::any_of (pa, pa + numAddresses, [] (const AudioObjectPropertyAddress& x) + { + constexpr UInt32 selectors[] + { + kAudioDevicePropertyDeviceHasChanged, + kAudioObjectPropertyOwnedObjects, + }; + + return std::find (std::begin (selectors), std::end (selectors), x.mSelector) != std::end (selectors); + }); + + if (detailsChanged) + intern.deviceDetailsChanged(); + + if (requestedRestart) + intern.deviceRequestedRestart(); return noErr; } @@ -1378,19 +1390,18 @@ class CoreAudioIODevice : public AudioIODevice, start (previousCallback); } - static OSStatus hardwareListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) + static OSStatus hardwareListenerProc (AudioDeviceID /*inDevice*/, + UInt32 numAddresses, + const AudioObjectPropertyAddress* pa, + void* inClientData) { - switch (pa->mSelector) + const auto detailsChanged = std::any_of (pa, pa + numAddresses, [] (const AudioObjectPropertyAddress& x) { - case kAudioHardwarePropertyDevices: - static_cast (inClientData)->deviceDetailsChanged(); - break; + return x.mSelector == kAudioHardwarePropertyDevices; + }); - case kAudioHardwarePropertyDefaultOutputDevice: - case kAudioHardwarePropertyDefaultInputDevice: - case kAudioHardwarePropertyDefaultSystemOutputDevice: - break; - } + if (detailsChanged) + static_cast (inClientData)->deviceDetailsChanged(); return noErr; } @@ -1577,7 +1588,7 @@ class AudioIODeviceCombiner : public AudioIODevice, { auto deviceSampleRate = d->getCurrentSampleRate(); - if (deviceSampleRate != sampleRateRequested) + if (! approximatelyEqual (deviceSampleRate, sampleRateRequested)) { if (! getAvailableSampleRates().contains (deviceSampleRate)) return; @@ -1912,7 +1923,7 @@ class AudioIODeviceCombiner : public AudioIODevice, for (auto& d : getDeviceWrappers()) { - if (d->getCurrentSampleRate() != currentSampleRate) + if (! approximatelyEqual (d->getCurrentSampleRate(), currentSampleRate)) { d->setCurrentSampleRate (currentSampleRate); anySampleRateChanges = true; diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm b/JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreMidi_mac.mm similarity index 94% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreMidi_mac.mm index 93ec5e3..aa65c85 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_CoreMidi_mac.mm @@ -71,10 +71,8 @@ static bool checkError (OSStatus err, [[maybe_unused]] int lineNum) { virtual ~SenderBase() noexcept = default; - virtual void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) = 0; + virtual void send (MIDIPortRef port, MIDIEndpointRef endpoint, const ump::BytestreamMidiView& m) = 0; virtual void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Iterator e) = 0; - - virtual ump::MidiProtocol getProtocol() const noexcept = 0; }; template @@ -84,11 +82,7 @@ static bool checkError (OSStatus err, [[maybe_unused]] int lineNum) template <> struct API_AVAILABLE (macos (11.0), ios (14.0)) Sender : public SenderBase { - explicit Sender (MIDIEndpointRef ep) - : umpConverter (getProtocolForEndpoint (ep)) - {} - - void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) override + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const ump::BytestreamMidiView& m) override { newSendImpl (port, endpoint, m); } @@ -98,14 +92,8 @@ void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Ite newSendImpl (port, endpoint, b, e); } - ump::MidiProtocol getProtocol() const noexcept override - { - return umpConverter.getProtocol() == ump::PacketProtocol::MIDI_2_0 ? ump::MidiProtocol::UMP_MIDI_2_0 - : ump::MidiProtocol::UMP_MIDI_1_0; - } - private: - ump::GenericUMPConverter umpConverter; + ump::ToUMP1Converter umpConverter; static ump::PacketProtocol getProtocolForEndpoint (MIDIEndpointRef ep) noexcept { @@ -119,9 +107,6 @@ void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Ite template void newSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, Params&&... params) { - // The converter protocol got out-of-sync with the device protocol - jassert (getProtocolForEndpoint (endpoint) == umpConverter.getProtocol()); - #if JUCE_IOS const MIDITimeStamp timeStamp = mach_absolute_time(); #else @@ -133,9 +118,10 @@ void newSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, Params&&... params const auto init = [&] { - end = MIDIEventListInit (&stackList, - umpConverter.getProtocol() == ump::PacketProtocol::MIDI_2_0 ? kMIDIProtocol_2_0 - : kMIDIProtocol_1_0); + // At the moment, we can only send MIDI 1.0 protocol. If the device is using MIDI + // 2.0 protocol (as may be the case for the IAC driver), we trust in the system to + // translate it. + end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); }; const auto send = [&] @@ -159,16 +145,19 @@ void newSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, Params&&... params init(); - umpConverter.convert (params..., [&] (const ump::View& view) + ump::GenericUMPConverter::convertImpl (umpConverter, params..., [&] (const auto& v) { - add (view); + umpConverter.convert (v, [&] (const ump::View& view) + { + add (view); - if (end != nullptr) - return; + if (end != nullptr) + return; - send(); - init(); - add (view); + send(); + init(); + add (view); + }); }); send(); @@ -180,9 +169,7 @@ void newSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, Params&&... params template <> struct Sender : public SenderBase { - explicit Sender (MIDIEndpointRef) {} - - void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) override + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const ump::BytestreamMidiView& m) override { oldSendImpl (port, endpoint, m); } @@ -191,22 +178,17 @@ void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Ite { std::for_each (b, e, [&] (const ump::View& v) { - bytestreamConverter.convert (v, 0.0, [&] (const MidiMessage& m) + bytestreamConverter.convert (v, 0.0, [&] (const ump::BytestreamMidiView& m) { send (port, endpoint, m); }); }); } - ump::MidiProtocol getProtocol() const noexcept override - { - return ump::MidiProtocol::bytestream; - } - private: ump::ToBytestreamConverter bytestreamConverter { 2048 }; - void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& message) + void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const ump::BytestreamMidiView& message) { #if JUCE_IOS const MIDITimeStamp timeStamp = mach_absolute_time(); @@ -217,7 +199,7 @@ void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& HeapBlock allocatedPackets; MIDIPacketList stackPacket; auto* packetToSend = &stackPacket; - auto dataSize = (size_t) message.getRawDataSize(); + auto dataSize = message.bytes.size(); if (message.isSysEx()) { @@ -234,7 +216,7 @@ void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& { p->timeStamp = timeStamp; p->length = (UInt16) jmin (maxPacketSize, bytesLeft); - memcpy (p->data, message.getRawData() + pos, p->length); + memcpy (p->data, message.bytes.data() + pos, p->length); pos += p->length; bytesLeft -= p->length; p = MIDIPacketNext (p); @@ -254,7 +236,7 @@ void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& auto& p = *(packetToSend->packet); p.timeStamp = timeStamp; p.length = (UInt16) dataSize; - memcpy (p.data, message.getRawData(), dataSize); + memcpy (p.data, message.bytes.data(), dataSize); } else { @@ -274,11 +256,11 @@ void oldSendImpl (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& template <> struct Sender { - explicit Sender (MIDIEndpointRef ep) - : sender (makeImpl (ep)) + Sender() + : sender (makeImpl()) {} - void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const ump::BytestreamMidiView& m) { sender->send (port, endpoint, m); } @@ -288,18 +270,13 @@ void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Ite sender->send (port, endpoint, b, e); } - ump::MidiProtocol getProtocol() const noexcept - { - return sender->getProtocol(); - } - private: - static std::unique_ptr makeImpl (MIDIEndpointRef ep) + static std::unique_ptr makeImpl() { if (@available (macOS 11, iOS 14, *)) - return std::make_unique> (ep); + return std::make_unique>(); - return std::make_unique> (ep); + return std::make_unique>(); } std::unique_ptr sender; @@ -369,7 +346,7 @@ Resource release() noexcept { public: MidiPortAndEndpoint (ScopedPortRef p, ScopedEndpointRef ep) noexcept - : port (std::move (p)), endpoint (std::move (ep)), sender (*endpoint) + : port (std::move (p)), endpoint (std::move (ep)) {} ~MidiPortAndEndpoint() noexcept @@ -379,7 +356,7 @@ Resource release() noexcept endpoint.release(); } - void send (const MidiMessage& m) + void send (const ump::BytestreamMidiView& m) { sender.send (*port, *endpoint, m); } @@ -392,11 +369,6 @@ void send (ump::Iterator b, ump::Iterator e) bool canStop() const noexcept { return *port != 0; } void stop() const { CHECK_ERROR (MIDIPortDisconnectSource (*port, *endpoint)); } - ump::MidiProtocol getProtocol() const noexcept - { - return sender.getProtocol(); - } - private: ScopedPortRef port; ScopedEndpointRef endpoint; @@ -579,9 +551,10 @@ static void enableSimulatorMidiSession() #endif } - static void globalSystemChangeCallback (const MIDINotification*, void*) + static void globalSystemChangeCallback (const MIDINotification* notification, void*) { - // TODO.. Should pass-on this notification.. + if (notification != nullptr && notification->messageID == kMIDIMsgSetupChanged) + MidiDeviceListConnectionBroadcaster::get().notify(); } static String getGlobalMidiClientName() @@ -594,9 +567,7 @@ static String getGlobalMidiClientName() static MIDIClientRef getGlobalMidiClient() { - static MIDIClientRef globalMidiClient = 0; - - if (globalMidiClient == 0) + static const auto globalMidiClient = [&] { // Since OSX 10.6, the MIDIClientCreate function will only work // correctly when called from the message thread! @@ -605,8 +576,10 @@ static MIDIClientRef getGlobalMidiClient() enableSimulatorMidiSession(); CFUniquePtr name (getGlobalMidiClientName().toCFString()); - CHECK_ERROR (MIDIClientCreate (name.get(), &globalSystemChangeCallback, nullptr, &globalMidiClient)); - } + MIDIClientRef result{}; + CHECK_ERROR (MIDIClientCreate (name.get(), globalSystemChangeCallback, nullptr, &result)); + return result; + }(); return globalMidiClient; } @@ -1297,7 +1270,13 @@ static CreatorFunctionPointers getCreatorFunctionPointers() void MidiOutput::sendMessageNow (const MidiMessage& message) { - internal->send (message); + internal->send (ump::BytestreamMidiView (&message)); +} + +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; } #undef CHECK_ERROR diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_DirectSound_windows.cpp similarity index 98% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_DirectSound_windows.cpp index 68826fd..bd11314 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_DirectSound_windows.cpp @@ -1035,8 +1035,14 @@ class DSoundAudioIODevice : public AudioIODevice, }; //============================================================================== -struct DSoundDeviceList +class DSoundDeviceList { + auto tie() const + { + return std::tie (outputDeviceNames, inputDeviceNames, outputGuids, inputGuids); + } + +public: StringArray outputDeviceNames, inputDeviceNames; Array outputGuids, inputGuids; @@ -1054,13 +1060,8 @@ struct DSoundDeviceList } } - bool operator!= (const DSoundDeviceList& other) const noexcept - { - return outputDeviceNames != other.outputDeviceNames - || inputDeviceNames != other.inputDeviceNames - || outputGuids != other.outputGuids - || inputGuids != other.inputGuids; - } + bool operator== (const DSoundDeviceList& other) const noexcept { return tie() == other.tie(); } + bool operator!= (const DSoundDeviceList& other) const noexcept { return tie() != other.tie(); } private: static BOOL enumProc (LPGUID lpGUID, String desc, StringArray& names, Array& guids) @@ -1213,13 +1214,11 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, } //============================================================================== -class DSoundAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector +class DSoundAudioIODeviceType : public AudioIODeviceType { public: DSoundAudioIODeviceType() - : AudioIODeviceType ("DirectSound"), - DeviceChangeDetector (L"DirectSound") + : AudioIODeviceType ("DirectSound") { initialiseDSoundFunctions(); } @@ -1274,19 +1273,17 @@ class DSoundAudioIODeviceType : public AudioIODeviceType, } private: + DeviceChangeDetector detector { L"DirectSound", [this] { systemDeviceChanged(); } }; DSoundDeviceList deviceList; bool hasScanned = false; - void systemDeviceChanged() override + void systemDeviceChanged() { DSoundDeviceList newList; newList.scan(); - if (newList != deviceList) - { - deviceList = newList; + if (std::exchange (deviceList, newList) != newList) callDeviceChangeListeners(); - } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h b/JuceLibraryCode/modules/juce_audio_devices/native/juce_HighPerformanceAudioHelpers_android.h similarity index 98% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_HighPerformanceAudioHelpers_android.h index b8a6195..0ba3b10 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_HighPerformanceAudioHelpers_android.h @@ -65,7 +65,7 @@ namespace AndroidHighPerformanceAudioHelpers static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate) { return ((requestedBufferSize % nativeBufferSize) == 0) - && (requestedSampleRate == getNativeSampleRate()) + && approximatelyEqual ((double) requestedSampleRate, getNativeSampleRate()) && isProAudioDevice(); } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_JackAudio_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_JackAudio_linux.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_android.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_android.cpp new file mode 100644 index 0000000..ad5e87b --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_android.cpp @@ -0,0 +1,1171 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +// This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23 +// See juce_core/native/java/README.txt on how to generate this byte-code. +constexpr unsigned char javaMidiByteCode[] +{ + 0x1f, 0x8b, 0x08, 0x08, 0xa3, 0xf2, 0xc6, 0x63, 0x00, 0x03, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0x95, 0x7c, + 0x09, 0x7c, 0xdb, 0x47, 0x95, 0xff, 0x9b, 0x9f, 0x7e, 0xb2, 0x6c, 0xf9, + 0x92, 0x64, 0x27, 0x76, 0x1c, 0xc7, 0x96, 0x8f, 0xc4, 0xf7, 0x15, 0xc7, + 0xa9, 0x13, 0x3b, 0xa9, 0xef, 0xc4, 0xb1, 0x73, 0xd4, 0x56, 0xd2, 0x36, + 0x2e, 0xdb, 0x2a, 0xb6, 0x12, 0xab, 0xb1, 0x25, 0x45, 0x92, 0xd3, 0x04, + 0x4a, 0x9b, 0x1e, 0x6c, 0x12, 0x08, 0x10, 0x4a, 0x9b, 0x96, 0x12, 0xd8, + 0xd2, 0x1b, 0x28, 0xd0, 0x76, 0x39, 0xca, 0x6e, 0x77, 0x29, 0x6c, 0x97, + 0x2d, 0xf7, 0xd5, 0x85, 0x70, 0x2d, 0xa5, 0x84, 0xa5, 0x94, 0x02, 0x61, + 0xf7, 0xbf, 0x6c, 0xe8, 0xbf, 0x90, 0xfd, 0xbe, 0x99, 0xf9, 0x49, 0x3f, + 0x3b, 0x6e, 0x53, 0x92, 0xcf, 0x57, 0x6f, 0xe6, 0xcd, 0x9b, 0x99, 0x37, + 0x33, 0x6f, 0xde, 0xbc, 0xf9, 0xfd, 0x24, 0x4f, 0x85, 0x0e, 0xb9, 0x5b, + 0xdb, 0x3b, 0xe8, 0xae, 0x83, 0xd9, 0xd5, 0xe5, 0x6f, 0x37, 0xff, 0xb8, + 0x6a, 0xcf, 0xe9, 0xa7, 0xdf, 0xb2, 0xa7, 0xbd, 0xf5, 0xf0, 0x6b, 0xab, + 0x1b, 0x4f, 0x9c, 0xba, 0xff, 0x6c, 0xe9, 0x30, 0x51, 0x8c, 0x88, 0x0e, + 0xed, 0x5a, 0xe3, 0x23, 0xfd, 0x6f, 0x74, 0x33, 0xd1, 0x32, 0xa1, 0xf8, + 0x9b, 0x80, 0xe7, 0x4d, 0xa2, 0xab, 0x40, 0xcf, 0x39, 0x89, 0x2a, 0x40, + 0x3d, 0x6e, 0xa2, 0xbf, 0x07, 0x2d, 0xce, 0x26, 0xca, 0x02, 0x7d, 0xdc, + 0x83, 0x3a, 0x97, 0x11, 0x7d, 0xdb, 0x4b, 0x74, 0xa6, 0x89, 0xe8, 0x47, + 0xc0, 0x4f, 0x81, 0x17, 0x81, 0x5f, 0x02, 0xd9, 0xcd, 0x44, 0x4b, 0x80, + 0x15, 0x40, 0x05, 0x50, 0x03, 0x34, 0x01, 0xfd, 0xc0, 0x6d, 0xc0, 0x73, + 0xc0, 0x59, 0xe0, 0x57, 0xc0, 0x6f, 0x80, 0x73, 0x80, 0xb7, 0x85, 0xa8, + 0x13, 0xe8, 0x02, 0x7a, 0x81, 0x00, 0x30, 0x0d, 0x1c, 0x04, 0x4e, 0x00, + 0xef, 0x05, 0xde, 0x0f, 0xdc, 0x03, 0x7c, 0x08, 0x78, 0x00, 0x78, 0x04, + 0x78, 0x0c, 0x78, 0x12, 0x38, 0x03, 0xfc, 0x19, 0xf0, 0xb7, 0x12, 0x8d, + 0x00, 0x37, 0x02, 0x1f, 0x05, 0xbe, 0x05, 0xbc, 0x0a, 0x94, 0xb7, 0x11, + 0x6d, 0x01, 0x66, 0x81, 0x93, 0xc0, 0x3f, 0x01, 0x2f, 0x02, 0x7f, 0x06, + 0xca, 0x56, 0x63, 0x3c, 0xc0, 0x01, 0xe0, 0x1e, 0xe0, 0x69, 0xe0, 0xb7, + 0xc0, 0xaa, 0x76, 0xa2, 0xcd, 0xc0, 0x2c, 0x70, 0x12, 0x78, 0x0c, 0xf8, + 0x22, 0xf0, 0x03, 0xe0, 0x1c, 0x60, 0xae, 0xc1, 0xbc, 0x01, 0xcd, 0xc0, + 0x14, 0x70, 0x37, 0xf0, 0x35, 0xe0, 0x4f, 0xc0, 0xa6, 0x0e, 0xa2, 0x77, + 0x03, 0xcf, 0x00, 0xbf, 0x03, 0xf2, 0xd7, 0x12, 0xb5, 0x03, 0xe3, 0xc0, + 0x41, 0xe0, 0x2e, 0xe0, 0x09, 0xe0, 0x39, 0xe0, 0xc7, 0xc0, 0x39, 0xc0, + 0xc4, 0x9c, 0x16, 0x03, 0x2d, 0xc0, 0x36, 0x60, 0x16, 0xb8, 0x1d, 0xf8, + 0x20, 0xf0, 0x29, 0xe0, 0x59, 0xe0, 0x87, 0xc0, 0xef, 0x00, 0x47, 0x27, + 0xfa, 0x06, 0x1a, 0x80, 0x0d, 0xc0, 0x36, 0x20, 0x08, 0x1c, 0x02, 0xde, + 0x05, 0x7c, 0x13, 0x78, 0x11, 0x38, 0x07, 0xfc, 0x09, 0xf0, 0xaf, 0x23, + 0xaa, 0x02, 0x6a, 0x80, 0x46, 0x60, 0x35, 0xb0, 0x1e, 0xd8, 0x08, 0xf4, + 0x03, 0xc3, 0xc0, 0x0e, 0x60, 0x27, 0xb0, 0x1b, 0xb8, 0x0e, 0xd8, 0x0b, + 0xcc, 0x02, 0x49, 0xe0, 0x10, 0xf0, 0x36, 0xe0, 0x66, 0xe0, 0x36, 0xe0, + 0x28, 0xf0, 0x71, 0xe0, 0x47, 0x00, 0xad, 0xc7, 0x7a, 0x03, 0xf5, 0xc0, + 0x5a, 0xa0, 0x1f, 0xd8, 0x0c, 0xec, 0x04, 0x82, 0x40, 0x18, 0x48, 0x02, + 0x27, 0x81, 0xfb, 0x81, 0xa7, 0x80, 0xaf, 0x02, 0x3f, 0x01, 0xce, 0x02, + 0xbf, 0x01, 0xfe, 0x1b, 0x78, 0x15, 0x10, 0x5d, 0x44, 0x6e, 0xa0, 0x08, + 0xa8, 0x00, 0x9a, 0x81, 0x36, 0xa0, 0x0b, 0xd8, 0x0c, 0x04, 0x80, 0x6b, + 0x80, 0x13, 0xc0, 0x29, 0xe0, 0xf3, 0xc0, 0x8f, 0x80, 0x9f, 0x03, 0xbf, + 0x05, 0xfe, 0x0b, 0xf8, 0x0b, 0xb7, 0xd1, 0x8d, 0x36, 0x80, 0x76, 0x60, + 0x07, 0xb0, 0x0f, 0xb8, 0x11, 0x38, 0x05, 0x7c, 0x02, 0xf8, 0x1c, 0xf0, + 0x55, 0xe0, 0x3f, 0x81, 0x3f, 0x00, 0x62, 0x03, 0x51, 0x2e, 0xb0, 0x14, + 0xe8, 0x05, 0x36, 0x03, 0xdb, 0x81, 0xab, 0x80, 0x1b, 0x81, 0x87, 0x80, + 0x6f, 0x01, 0x2f, 0x03, 0xc6, 0x46, 0xd8, 0x38, 0x50, 0x0c, 0xd4, 0x00, + 0xeb, 0x80, 0x7e, 0x60, 0x0c, 0x98, 0x04, 0xe6, 0x80, 0x9b, 0x80, 0x3b, + 0x81, 0x07, 0x80, 0xc7, 0x80, 0xcf, 0x02, 0xff, 0x02, 0x7c, 0x1d, 0xf8, + 0x77, 0xe0, 0x45, 0xe0, 0x15, 0xe0, 0x8f, 0xc0, 0x5f, 0x00, 0xd7, 0xe5, + 0xe8, 0x1f, 0x58, 0x06, 0x34, 0x02, 0xeb, 0x80, 0xcb, 0x81, 0x41, 0xe0, + 0x6a, 0x20, 0x0e, 0x1c, 0x03, 0xfe, 0x0e, 0x78, 0x0a, 0xf8, 0x2a, 0xf0, + 0x53, 0xe0, 0x15, 0xe0, 0x35, 0xa0, 0xa0, 0x87, 0xa8, 0x1a, 0xe8, 0x02, + 0x06, 0x80, 0x31, 0x60, 0x0a, 0xb8, 0x09, 0x38, 0x01, 0xdc, 0x0b, 0xdc, + 0x0f, 0x3c, 0x05, 0x7c, 0x03, 0xf8, 0x21, 0xf0, 0x12, 0x20, 0x7a, 0x89, + 0x96, 0x03, 0x2d, 0xc0, 0xe5, 0xc0, 0x16, 0x60, 0x27, 0x70, 0x0d, 0x70, + 0x00, 0x78, 0x37, 0xf0, 0x30, 0xf0, 0x04, 0xf0, 0x8f, 0xc0, 0x33, 0xc0, + 0x19, 0xe0, 0x05, 0xe0, 0x15, 0xc0, 0xd5, 0x47, 0x94, 0x03, 0x2c, 0x01, + 0xca, 0x81, 0x1a, 0xa0, 0x09, 0x18, 0x06, 0xc6, 0x81, 0x29, 0xe0, 0x00, + 0x70, 0x0b, 0x70, 0x14, 0x78, 0x0f, 0xf0, 0x01, 0xe0, 0x51, 0xe0, 0x93, + 0xc0, 0x53, 0xc0, 0x33, 0xc0, 0x57, 0x80, 0xef, 0x01, 0x2f, 0x03, 0xce, + 0x7e, 0xb4, 0x05, 0xac, 0x02, 0x5a, 0x80, 0x3c, 0x98, 0x58, 0x01, 0x29, + 0x5f, 0x54, 0x09, 0x54, 0x01, 0xd5, 0xc0, 0x4a, 0x60, 0x15, 0x50, 0x03, + 0xd4, 0x02, 0x75, 0x40, 0x3d, 0xd0, 0x00, 0x34, 0x02, 0x70, 0x45, 0x04, + 0xd7, 0x42, 0x70, 0x0b, 0x04, 0x17, 0x40, 0xd8, 0xea, 0x84, 0x6d, 0x4d, + 0xd8, 0xba, 0x84, 0x2d, 0x4a, 0xd8, 0x96, 0x84, 0x6d, 0x47, 0xd8, 0x4e, + 0x84, 0x2d, 0x42, 0xda, 0x94, 0x09, 0xe6, 0x43, 0x30, 0x09, 0xc2, 0x52, + 0x13, 0x96, 0x86, 0x30, 0xbd, 0x84, 0xa1, 0x12, 0x54, 0x23, 0xa8, 0x43, + 0x03, 0xc0, 0x20, 0x30, 0xa4, 0xfd, 0x25, 0xdc, 0x27, 0xc1, 0xad, 0xd2, + 0x16, 0x60, 0x84, 0xfd, 0x29, 0xb0, 0x15, 0xd8, 0x06, 0x6c, 0x07, 0x76, + 0x00, 0x57, 0x00, 0x63, 0xc0, 0x38, 0x10, 0x00, 0x76, 0x02, 0x57, 0x02, + 0x57, 0x03, 0xbb, 0x81, 0x6b, 0x80, 0xbf, 0x01, 0xae, 0x05, 0x82, 0xc0, + 0x1e, 0x60, 0x12, 0xb8, 0x1e, 0x78, 0x2b, 0xf0, 0x76, 0xe0, 0x26, 0xe0, + 0x66, 0xe0, 0x08, 0x70, 0x0b, 0x70, 0x2b, 0xa9, 0xb9, 0xb1, 0xfe, 0xe5, + 0x6b, 0xba, 0x03, 0x83, 0xf7, 0xe8, 0xf4, 0x55, 0x48, 0x2f, 0x03, 0x35, + 0x74, 0xbe, 0x4c, 0xa7, 0xaf, 0xd3, 0x7c, 0x87, 0x8d, 0xef, 0xd0, 0x75, + 0x99, 0x6f, 0x6a, 0xbe, 0x5f, 0xa7, 0xa7, 0x35, 0x3f, 0xcb, 0x26, 0x8f, + 0xe3, 0x80, 0x92, 0x9a, 0x9f, 0xa3, 0xf9, 0x25, 0x5a, 0xa7, 0x1b, 0x35, + 0xdf, 0xd2, 0x89, 0xd3, 0x1e, 0x5b, 0xba, 0xc0, 0x26, 0xbf, 0x44, 0xcb, + 0x97, 0xe8, 0x32, 0xab, 0x6e, 0xa9, 0xad, 0xaf, 0x32, 0xad, 0x5b, 0x89, + 0xd6, 0x89, 0x65, 0xca, 0x74, 0x7a, 0x87, 0x4e, 0x57, 0xe8, 0x71, 0xb1, + 0x4c, 0xb5, 0x96, 0x29, 0xd5, 0xe9, 0xdb, 0x91, 0x5e, 0xa1, 0xd3, 0x27, + 0xb4, 0xfc, 0x2a, 0x5b, 0xdd, 0x1a, 0x5d, 0x77, 0x39, 0x29, 0x5b, 0xba, + 0x47, 0xeb, 0xd0, 0x6a, 0xd3, 0xb3, 0xcd, 0xa6, 0xdb, 0x6a, 0x9b, 0x6e, + 0x9c, 0xbe, 0x0f, 0xfc, 0x72, 0x9d, 0x7e, 0xb4, 0x31, 0xcd, 0xb7, 0xe6, + 0xb3, 0xdd, 0xd6, 0x4e, 0xbb, 0x4d, 0x7f, 0x4e, 0x3f, 0x6e, 0x4b, 0x5b, + 0x63, 0x5c, 0x6b, 0xeb, 0x6b, 0xbd, 0xad, 0x2f, 0xb6, 0xcd, 0xa7, 0x34, + 0xbf, 0x5b, 0xf3, 0xd9, 0x46, 0x2e, 0xd7, 0xe9, 0xfd, 0x3a, 0xcd, 0x75, + 0x67, 0x74, 0xfa, 0x19, 0xa4, 0x67, 0x75, 0xfa, 0x39, 0xa4, 0x23, 0x3a, + 0xfd, 0x3c, 0xd2, 0x51, 0x9d, 0x3e, 0x8b, 0xf4, 0x41, 0x9d, 0x7e, 0xa5, + 0x51, 0xc5, 0x02, 0x9c, 0x3e, 0x8f, 0xf4, 0x01, 0xab, 0x7d, 0x6c, 0xa8, + 0xa4, 0x4e, 0xe7, 0x20, 0x3d, 0xa7, 0xd3, 0x85, 0xb6, 0xb4, 0xbf, 0x29, + 0xdd, 0x66, 0xa3, 0x2d, 0x7d, 0x9d, 0xad, 0xaf, 0x35, 0x36, 0x7e, 0x77, + 0x53, 0xba, 0xdf, 0x01, 0x1b, 0x7f, 0x87, 0x2d, 0x7d, 0x95, 0xad, 0xdf, + 0xeb, 0x6c, 0xfc, 0x69, 0x5b, 0xdd, 0x18, 0xd2, 0x37, 0x58, 0x63, 0xb7, + 0xc9, 0x1f, 0x43, 0xfa, 0x90, 0x4e, 0x9f, 0xb4, 0xd5, 0x7d, 0xdc, 0xa6, + 0x0f, 0xaf, 0x9d, 0x25, 0xff, 0xa8, 0x8d, 0xbf, 0xc3, 0x96, 0x3e, 0x6d, + 0xeb, 0xeb, 0x41, 0xa4, 0x13, 0x56, 0x3b, 0x48, 0x1f, 0xd6, 0xe9, 0xa7, + 0x9a, 0xd2, 0x73, 0xf5, 0x0c, 0xd2, 0x71, 0x9d, 0xfe, 0x7a, 0x93, 0xda, + 0xc3, 0x3d, 0x7a, 0x8d, 0xde, 0xa6, 0xd3, 0xbc, 0x46, 0x37, 0xea, 0xf4, + 0x59, 0x5b, 0xfa, 0x3e, 0x5b, 0xda, 0xb2, 0x9f, 0x7e, 0x5d, 0x97, 0xd3, + 0x03, 0x36, 0x7b, 0x18, 0xb4, 0xd9, 0xc3, 0x90, 0xe6, 0x97, 0xe8, 0xf4, + 0x8d, 0xda, 0x9e, 0xa7, 0x79, 0x5d, 0x60, 0x8d, 0x1f, 0xd1, 0xd4, 0x21, + 0x78, 0xaf, 0x78, 0xe8, 0x28, 0x31, 0x6d, 0xa7, 0x77, 0x4a, 0xba, 0x8e, + 0x4e, 0x48, 0x9a, 0x45, 0x42, 0xb0, 0x9f, 0x5d, 0x46, 0x7f, 0x4b, 0x4c, + 0x7b, 0xe8, 0xab, 0x92, 0x0a, 0xfa, 0x96, 0xa4, 0x35, 0xf4, 0x3f, 0x92, + 0xd6, 0xd2, 0xab, 0xc4, 0xbe, 0x78, 0x89, 0x94, 0xab, 0xd2, 0xfc, 0x2a, + 0xcd, 0x5f, 0xa9, 0xf3, 0x4c, 0x3d, 0x82, 0xf7, 0x9a, 0x49, 0xef, 0x25, + 0xa6, 0x5e, 0xfa, 0x9e, 0xa4, 0xaa, 0x7c, 0x95, 0x2e, 0xaf, 0xd1, 0xfa, + 0xd4, 0xc0, 0x13, 0xbf, 0x47, 0xd2, 0x01, 0xba, 0x57, 0xd2, 0x62, 0xfa, + 0x8e, 0xa4, 0x6b, 0xe9, 0xdf, 0x75, 0xf9, 0x7f, 0x6b, 0xfa, 0xff, 0x48, + 0xed, 0xd5, 0x0f, 0x48, 0xda, 0x43, 0x5f, 0xd7, 0xf9, 0x3f, 0x11, 0x9f, + 0x05, 0x15, 0xf4, 0x2e, 0x49, 0x6b, 0xe8, 0x79, 0x62, 0x9f, 0x97, 0x45, + 0x8f, 0x49, 0xea, 0xa0, 0x4f, 0x4a, 0x9a, 0x41, 0xff, 0x42, 0xec, 0xf3, + 0x32, 0xe9, 0x2e, 0x49, 0xab, 0xe8, 0x21, 0x4d, 0xff, 0x89, 0xd8, 0xe7, + 0x35, 0xd0, 0xfb, 0x35, 0xfd, 0xa0, 0xa4, 0x4e, 0x7a, 0x5c, 0xd2, 0xed, + 0x74, 0x01, 0xd4, 0x09, 0x7e, 0x06, 0x68, 0x26, 0x6a, 0x3c, 0x48, 0xec, + 0x17, 0x87, 0x29, 0x47, 0x30, 0xbd, 0x8c, 0xf2, 0x41, 0xdd, 0xba, 0x3c, + 0x3b, 0x45, 0xb3, 0xe9, 0xa4, 0xa4, 0x6e, 0xca, 0x42, 0x79, 0xae, 0x6e, + 0x2f, 0x4f, 0x97, 0xe7, 0x81, 0x73, 0x52, 0xd2, 0x1c, 0x72, 0x09, 0x45, + 0x33, 0x05, 0xfb, 0xcc, 0x3c, 0xfa, 0x28, 0x31, 0xad, 0xa4, 0x67, 0x41, + 0xbd, 0x5a, 0x2f, 0x2f, 0x3c, 0xeb, 0xe7, 0x24, 0xf5, 0xd0, 0xef, 0x24, + 0xf5, 0xd2, 0x7f, 0x81, 0xfa, 0xb4, 0xfe, 0x1c, 0xdc, 0x7f, 0x41, 0xd3, + 0x7f, 0x25, 0xe5, 0x6f, 0x3f, 0x2b, 0xe9, 0x38, 0x7d, 0x51, 0x52, 0x1f, + 0x7d, 0x49, 0xf3, 0xb9, 0x7c, 0x89, 0x6e, 0x77, 0x09, 0x4e, 0x2f, 0x13, + 0xfd, 0x2e, 0xd5, 0x7a, 0x15, 0xe3, 0xb4, 0x7a, 0x52, 0xd2, 0x36, 0x7a, + 0x45, 0xd2, 0x2e, 0xfa, 0xad, 0xa4, 0x1b, 0xe9, 0x35, 0x49, 0x37, 0xd0, + 0x12, 0xc1, 0xf6, 0xa7, 0xea, 0x2f, 0x83, 0xc5, 0xdf, 0xa9, 0xe9, 0x07, + 0xa5, 0x2d, 0xaa, 0x76, 0x4a, 0xa1, 0xff, 0x03, 0xd2, 0x66, 0x0b, 0xe8, + 0x7e, 0x62, 0x5f, 0x69, 0xd0, 0x3d, 0xd2, 0x1e, 0x87, 0x64, 0x79, 0x05, + 0xd6, 0x53, 0x51, 0x41, 0x1f, 0x97, 0x74, 0x15, 0x7d, 0x5a, 0xd2, 0x5d, + 0xf4, 0x8f, 0x92, 0x6e, 0xa6, 0x33, 0x92, 0x36, 0xd2, 0x4b, 0x92, 0x36, + 0xd1, 0xaf, 0x25, 0x1d, 0xa3, 0xf3, 0x92, 0x8e, 0x50, 0xae, 0xb4, 0xeb, + 0x4d, 0x54, 0x28, 0xed, 0xb7, 0x57, 0xb6, 0x57, 0xa9, 0xf5, 0x62, 0xfa, + 0x61, 0x49, 0xd5, 0xfc, 0x54, 0x22, 0x2a, 0xf8, 0x37, 0x49, 0xb7, 0xd1, + 0x37, 0x74, 0xf9, 0x59, 0x49, 0xb7, 0xd2, 0xcb, 0x92, 0x8e, 0x52, 0x86, + 0x50, 0xfc, 0x6c, 0x4d, 0xf3, 0x04, 0xdb, 0x77, 0x8f, 0x6c, 0xb7, 0x4a, + 0xb7, 0x5b, 0xa5, 0xdb, 0xad, 0xd2, 0xed, 0x56, 0xe9, 0xf6, 0xaa, 0x74, + 0xfd, 0x2a, 0x5d, 0xbf, 0x4a, 0xd7, 0xaf, 0xd6, 0xf5, 0xaa, 0xb5, 0x7c, + 0xb5, 0x96, 0xaf, 0xd6, 0xf2, 0xd5, 0x5a, 0xbe, 0x5a, 0xcb, 0xaf, 0x44, + 0xd4, 0x91, 0x21, 0xf7, 0xd1, 0x1a, 0xfa, 0xa1, 0xa4, 0x1d, 0xf4, 0x23, + 0x4d, 0x7f, 0x2c, 0x69, 0x3b, 0xfd, 0x44, 0xd2, 0xb5, 0xf4, 0x53, 0x4d, + 0xff, 0x43, 0xf3, 0x7f, 0xa9, 0xe9, 0x7f, 0x4a, 0xba, 0x9a, 0x7e, 0xa5, + 0xe9, 0x6f, 0xe4, 0xbe, 0xeb, 0x97, 0xed, 0xae, 0x42, 0xff, 0xef, 0x93, + 0xb4, 0x8a, 0x3e, 0x26, 0xa9, 0x4b, 0xde, 0xf5, 0xf8, 0x6c, 0xfc, 0x8c, + 0xa4, 0x88, 0xa6, 0x84, 0xda, 0x6f, 0x19, 0x72, 0xdf, 0xa9, 0xf1, 0xd6, + 0xc0, 0x52, 0xfe, 0x4e, 0xd2, 0x12, 0xba, 0x4f, 0xd2, 0x1a, 0x7a, 0x58, + 0x52, 0xb5, 0x7e, 0x35, 0xb0, 0x9b, 0x27, 0x24, 0xbd, 0x92, 0x9e, 0x92, + 0x74, 0x17, 0x7d, 0x5e, 0xd3, 0x7f, 0x90, 0xb4, 0x90, 0x9e, 0x96, 0x74, + 0x25, 0xfd, 0xb3, 0xa4, 0xa5, 0xf4, 0x8c, 0xa4, 0xeb, 0xe9, 0x67, 0x92, + 0xae, 0xa3, 0x17, 0x34, 0xfd, 0xb9, 0xe6, 0xbf, 0x28, 0x69, 0x37, 0xfd, + 0x42, 0xfb, 0x85, 0xdf, 0x4b, 0x5a, 0x44, 0xe7, 0x24, 0x5d, 0x46, 0x7f, + 0x90, 0x74, 0x07, 0xfd, 0x51, 0xd2, 0x56, 0xfa, 0x5f, 0xed, 0x47, 0xfe, + 0x22, 0xe9, 0x26, 0x2a, 0x10, 0xec, 0x1f, 0x9a, 0xe5, 0x38, 0x6a, 0x11, + 0x91, 0x9d, 0xd6, 0xfe, 0xe2, 0x2b, 0xd2, 0x4f, 0x34, 0x60, 0x47, 0x2a, + 0x9a, 0xa1, 0xe9, 0x6d, 0x92, 0x2e, 0xa5, 0x47, 0x24, 0x5d, 0x4e, 0x8f, + 0x4a, 0x6a, 0xd2, 0xa7, 0x74, 0xf9, 0x97, 0x89, 0x63, 0x82, 0x46, 0x29, + 0xdf, 0xa6, 0xdb, 0x69, 0xc3, 0xca, 0xf9, 0x04, 0xd3, 0x32, 0x2a, 0x12, + 0x1c, 0x03, 0xa8, 0xf6, 0x56, 0xeb, 0x79, 0x5b, 0x8d, 0x28, 0xe4, 0x13, + 0xc4, 0x67, 0xbd, 0xea, 0xbf, 0x1d, 0xf3, 0xff, 0x35, 0xe2, 0x58, 0x74, + 0x50, 0xca, 0x75, 0x60, 0x67, 0xf0, 0x3e, 0x59, 0xab, 0xeb, 0xad, 0x85, + 0xdc, 0x3b, 0x74, 0xfe, 0x0e, 0x9d, 0x3f, 0x25, 0x69, 0x2d, 0x7d, 0x53, + 0xe7, 0xff, 0x4c, 0x2a, 0x5e, 0x70, 0x0b, 0xa6, 0x3b, 0xc9, 0x2b, 0x38, + 0xa6, 0xad, 0xa3, 0x63, 0xc4, 0x71, 0xad, 0x6a, 0xa7, 0x53, 0xd7, 0xef, + 0x84, 0xfc, 0xdd, 0x92, 0xfa, 0x65, 0x3f, 0x9d, 0x88, 0x98, 0xbf, 0x2d, + 0x69, 0x05, 0xfd, 0x7f, 0xcd, 0xe7, 0xf6, 0xd6, 0xe9, 0x7a, 0xeb, 0x74, + 0xff, 0xeb, 0x74, 0x3f, 0xeb, 0x74, 0x3f, 0xeb, 0x74, 0x3f, 0xeb, 0xa1, + 0xff, 0x73, 0xc4, 0xb4, 0x9c, 0x7e, 0x40, 0x1c, 0x9f, 0x28, 0xbd, 0xba, + 0x35, 0xdd, 0xa0, 0xdb, 0xd9, 0x80, 0xe8, 0xd8, 0x10, 0x1c, 0x4f, 0xab, + 0xfc, 0x46, 0x6d, 0x77, 0x1c, 0xb3, 0x09, 0x6e, 0x93, 0xd4, 0xbf, 0x62, + 0xe0, 0x1a, 0x04, 0xd5, 0x5f, 0xc2, 0x21, 0x77, 0x6a, 0x48, 0xc5, 0x6b, + 0x22, 0x23, 0x1d, 0x6f, 0x71, 0xf9, 0x8d, 0x28, 0x3f, 0xaf, 0x0f, 0xc1, + 0x95, 0x9a, 0xef, 0xb4, 0x95, 0x9f, 0x40, 0x79, 0xe1, 0xa0, 0xca, 0xaf, + 0xd2, 0xfc, 0x8d, 0xb6, 0xf2, 0xd3, 0x28, 0x1f, 0xd1, 0xe5, 0x35, 0xba, + 0xff, 0xa5, 0x40, 0x4f, 0xa3, 0x2a, 0x7f, 0x0c, 0xe5, 0x71, 0x5d, 0x5e, + 0xab, 0xeb, 0xd9, 0xfb, 0x7f, 0x0d, 0xe5, 0x2f, 0xeb, 0xf2, 0x3a, 0x5d, + 0xdf, 0x5e, 0x9e, 0x83, 0x0b, 0x81, 0xa9, 0x0f, 0xe3, 0x4a, 0x5d, 0xce, + 0x67, 0xf2, 0x66, 0xdd, 0x7e, 0x29, 0xca, 0x9b, 0x75, 0x79, 0x95, 0xad, + 0xbe, 0x55, 0xbe, 0x06, 0xe5, 0xd7, 0xeb, 0x72, 0x07, 0xfc, 0x24, 0xc7, + 0xeb, 0xf7, 0xd5, 0xa9, 0x58, 0x37, 0xe0, 0x71, 0xd0, 0x01, 0xcf, 0x71, + 0xee, 0x85, 0x22, 0x7e, 0x03, 0xbc, 0x1c, 0x63, 0x9d, 0x91, 0x4d, 0x25, + 0x46, 0x21, 0xfa, 0xf8, 0x10, 0x1d, 0xf0, 0xaf, 0x86, 0xbc, 0xcf, 0xc8, + 0x37, 0x94, 0xe4, 0x31, 0x2d, 0xf9, 0x76, 0x48, 0xba, 0xc1, 0xb5, 0xda, + 0xfb, 0x58, 0x9d, 0x8a, 0xdf, 0xe7, 0x4b, 0xcd, 0x6a, 0xa9, 0x74, 0xbf, + 0x9f, 0xaa, 0x53, 0x31, 0xfc, 0x62, 0xfd, 0x46, 0x3c, 0x4e, 0xc8, 0xe4, + 0x18, 0x5e, 0x29, 0x2f, 0xa4, 0xfc, 0x67, 0x2c, 0x79, 0xbf, 0x93, 0x22, + 0x9e, 0x8f, 0x4a, 0x6b, 0xa9, 0xf2, 0x0e, 0xa1, 0xce, 0xc7, 0x70, 0x52, + 0xe5, 0xc0, 0x4a, 0x86, 0x70, 0x52, 0xa9, 0xf6, 0x79, 0xdc, 0x9f, 0xaf, + 0xe3, 0xb3, 0x10, 0xf1, 0x9b, 0x27, 0x0f, 0xf9, 0x4a, 0xd8, 0x55, 0xcc, + 0xc3, 0x7e, 0x64, 0x02, 0x6d, 0x4f, 0xf8, 0x1c, 0xf2, 0x2e, 0x61, 0xca, + 0x53, 0x9b, 0xe8, 0xcb, 0x75, 0x6a, 0x9d, 0xe2, 0x9e, 0x4f, 0x20, 0x9f, + 0xe3, 0x88, 0x7b, 0x3e, 0x0e, 0xea, 0x86, 0xcd, 0xe6, 0x82, 0xf7, 0x18, + 0xf3, 0xd0, 0x7e, 0x2e, 0xf9, 0xbc, 0x91, 0xd6, 0xf5, 0x38, 0xdf, 0x6a, + 0xcf, 0xe5, 0xe9, 0x51, 0xa8, 0x7f, 0xdc, 0x5f, 0x9e, 0xb4, 0x15, 0x87, + 0xe4, 0x7e, 0xab, 0x4e, 0xed, 0x1b, 0x9f, 0x67, 0xb5, 0xc3, 0x49, 0x3e, + 0x7f, 0xbb, 0xc3, 0x0b, 0x9d, 0x7d, 0xe8, 0x2f, 0x07, 0x6d, 0x66, 0x53, + 0xa0, 0x82, 0xc7, 0x60, 0xea, 0x31, 0x3f, 0x82, 0xfa, 0xbe, 0x9e, 0x76, + 0x47, 0x39, 0x95, 0x38, 0x78, 0xae, 0xc3, 0x72, 0xae, 0x1d, 0x56, 0x0d, + 0x47, 0xa7, 0xc3, 0x47, 0x81, 0x6a, 0x55, 0xc3, 0x21, 0x6b, 0x3c, 0x0a, + 0xbe, 0xce, 0x39, 0x22, 0xfe, 0x0d, 0x58, 0xe3, 0x3c, 0xc4, 0x0b, 0x86, + 0xbc, 0x4f, 0xfd, 0xb4, 0x4e, 0xdd, 0xff, 0x02, 0xd7, 0xa5, 0xe7, 0xb5, + 0x44, 0x14, 0x62, 0xfc, 0x99, 0x54, 0xe2, 0xca, 0x91, 0x6d, 0x1f, 0x80, + 0x7c, 0x44, 0x5e, 0x9a, 0x72, 0x34, 0x3f, 0x3b, 0xc5, 0xef, 0x74, 0x5d, + 0x46, 0x95, 0xc8, 0xc7, 0x3c, 0xf9, 0xf0, 0xd8, 0x25, 0xc2, 0x44, 0x2b, + 0x6d, 0xd0, 0x30, 0x47, 0x44, 0xfc, 0x5e, 0xf8, 0xc4, 0x4a, 0x91, 0x87, + 0xb2, 0x02, 0xd6, 0xd9, 0x17, 0xf1, 0x2f, 0xc1, 0x3e, 0xcb, 0x71, 0xf8, + 0xcc, 0x88, 0x7f, 0x29, 0xfc, 0x3e, 0x52, 0x2b, 0x39, 0x35, 0x4e, 0x55, + 0x95, 0x7d, 0xe8, 0xc1, 0x83, 0x16, 0x72, 0x5c, 0x5b, 0x5d, 0x86, 0x79, + 0xc0, 0xf3, 0x61, 0x5e, 0x51, 0x57, 0xc4, 0x53, 0xa8, 0xda, 0xea, 0xc9, + 0xa1, 0x58, 0xb0, 0x16, 0xf5, 0x72, 0xe9, 0x3a, 0x39, 0x77, 0x96, 0x5d, + 0xfc, 0xbe, 0x4e, 0xed, 0xd9, 0xf9, 0xf6, 0x73, 0x04, 0x76, 0x91, 0xa7, + 0x57, 0x56, 0xfd, 0x33, 0xe4, 0xfc, 0xe6, 0xa7, 0xec, 0xe3, 0x7f, 0xea, + 0xd4, 0x1d, 0x32, 0xe0, 0x77, 0x63, 0x7e, 0xb3, 0x51, 0x27, 0x21, 0xed, + 0x82, 0x6d, 0x22, 0x03, 0xff, 0xb9, 0xe6, 0xab, 0xda, 0x86, 0x62, 0x1e, + 0xbe, 0xb9, 0x4f, 0x08, 0x37, 0x4d, 0x18, 0x2e, 0x9a, 0x70, 0x64, 0xd3, + 0x6e, 0x33, 0x0b, 0xde, 0xf5, 0x1a, 0x91, 0xa9, 0x75, 0x11, 0xb2, 0xaf, + 0xcc, 0x7a, 0x15, 0x0b, 0x07, 0xfc, 0x2e, 0x69, 0x0b, 0x11, 0x0f, 0xdf, + 0xfe, 0x6b, 0x33, 0x4b, 0x70, 0xc6, 0x94, 0x08, 0xaf, 0x9c, 0x33, 0x8f, + 0xec, 0xb1, 0x13, 0x23, 0x55, 0xbd, 0xde, 0x0c, 0x8e, 0x8f, 0x26, 0xc0, + 0x9b, 0x40, 0xad, 0x3c, 0xb9, 0x26, 0xdc, 0x9e, 0x10, 0x2d, 0x08, 0xbb, + 0x84, 0xd4, 0xa3, 0x00, 0xed, 0xba, 0x40, 0x23, 0x1e, 0x8e, 0xe2, 0xa3, + 0x9e, 0x93, 0xda, 0x7e, 0x84, 0x6d, 0x7c, 0x96, 0x4d, 0xe5, 0x41, 0xf7, + 0x4c, 0xd0, 0x65, 0xf5, 0xca, 0x9e, 0x37, 0x64, 0xe7, 0xd0, 0xf8, 0x2d, + 0x59, 0xe4, 0x3a, 0xe2, 0x7a, 0xbf, 0x78, 0x50, 0x7c, 0xc6, 0xfc, 0xf2, + 0xc1, 0xcc, 0x3e, 0x2d, 0x6b, 0xa6, 0x6e, 0xd7, 0xe9, 0xfa, 0xd6, 0xfc, + 0x54, 0xd6, 0xab, 0x98, 0x4c, 0x69, 0xeb, 0xd1, 0x63, 0xca, 0xa1, 0x9d, + 0x15, 0x99, 0xb4, 0x0e, 0xe3, 0x8e, 0xe0, 0x82, 0xe5, 0xc7, 0x8c, 0x5e, + 0xe3, 0xcf, 0x9c, 0x57, 0xaf, 0xee, 0x0d, 0xea, 0x75, 0xca, 0x7a, 0xcd, + 0x5c, 0x8f, 0xac, 0x7a, 0x17, 0xad, 0x11, 0xa5, 0xd7, 0xa8, 0xb5, 0xde, + 0x5a, 0xa3, 0x5c, 0xcc, 0x56, 0xde, 0xbc, 0x35, 0x62, 0xdd, 0xb9, 0x66, + 0x47, 0xbd, 0x5a, 0xff, 0x98, 0xe7, 0xbd, 0x72, 0x8d, 0x72, 0xb1, 0x46, + 0x39, 0x58, 0xa3, 0x3c, 0xb4, 0x6e, 0xad, 0x4b, 0x4f, 0x6a, 0x5d, 0x72, + 0xf4, 0xba, 0x54, 0x2f, 0xba, 0x2e, 0xb9, 0x7a, 0x5d, 0xf2, 0x6c, 0xeb, + 0x82, 0xf6, 0x50, 0x6b, 0xf1, 0x75, 0x19, 0x49, 0xad, 0xcb, 0x96, 0x79, + 0xeb, 0xe2, 0xd4, 0xda, 0x5d, 0x51, 0xaf, 0x9e, 0x35, 0x04, 0x3c, 0xba, + 0xdf, 0x9e, 0x95, 0xe4, 0xef, 0x47, 0xbf, 0xec, 0x23, 0x1d, 0x4e, 0xc1, + 0xfd, 0xae, 0x5e, 0xa4, 0xed, 0x85, 0x6b, 0xb2, 0x18, 0xcf, 0x21, 0x6b, + 0x60, 0xc4, 0xf5, 0x24, 0xd7, 0x7b, 0x42, 0x78, 0x30, 0x07, 0x3c, 0x13, + 0x13, 0x46, 0xbe, 0x1c, 0xbb, 0xe3, 0xa2, 0x3a, 0x8b, 0xad, 0xf7, 0xeb, + 0xf1, 0x78, 0xfe, 0xa7, 0xea, 0x49, 0xce, 0xb5, 0xaf, 0xb2, 0xbd, 0x2a, + 0x5f, 0xee, 0xdf, 0x2c, 0xec, 0xdf, 0x00, 0xda, 0x8f, 0x78, 0x32, 0xf5, + 0x1e, 0xbf, 0x09, 0xb3, 0xf4, 0xd7, 0xb6, 0xfd, 0x66, 0x78, 0x8b, 0xe9, + 0xbf, 0x18, 0xcf, 0xb2, 0x9f, 0xd9, 0x7a, 0xf5, 0xdc, 0x28, 0xe0, 0xf1, + 0x4a, 0x9d, 0x9d, 0x72, 0xd6, 0x1c, 0x14, 0xaf, 0x57, 0x67, 0x6f, 0x29, + 0x3e, 0x57, 0x60, 0xcf, 0x4e, 0xf4, 0x16, 0x60, 0xa5, 0x9e, 0x44, 0x8f, + 0x6e, 0x23, 0xd0, 0xef, 0xa5, 0x4e, 0x07, 0xfc, 0x85, 0xc7, 0x05, 0x49, + 0xce, 0x45, 0x3c, 0x19, 0x48, 0x4d, 0xf4, 0x7a, 0x91, 0xf3, 0xa1, 0xcc, + 0x05, 0xce, 0x32, 0xc9, 0xf1, 0xc1, 0x82, 0xf2, 0x8d, 0x5c, 0x51, 0x46, + 0x35, 0x82, 0xb5, 0x5d, 0x8a, 0xd6, 0x4b, 0xb5, 0x4e, 0xfc, 0xbc, 0x25, + 0x43, 0xda, 0x5a, 0xd5, 0xcd, 0x2d, 0xe5, 0x55, 0x52, 0x27, 0xd6, 0xea, + 0xd6, 0x7a, 0xeb, 0x6c, 0xf3, 0xa1, 0x1d, 0x3e, 0x79, 0xb9, 0x2f, 0x3f, + 0xb1, 0xc5, 0xe6, 0x6b, 0x1d, 0xd9, 0x8b, 0x1d, 0xad, 0x57, 0xcf, 0x08, + 0x95, 0x8e, 0x01, 0xf4, 0xd5, 0x29, 0x2a, 0x64, 0x0d, 0x39, 0xc7, 0x9e, + 0x2a, 0x50, 0xb7, 0xe1, 0x6b, 0x6f, 0x5f, 0xbd, 0x14, 0xdc, 0x4a, 0xc9, + 0x2d, 0x31, 0xde, 0x0d, 0x2b, 0x6d, 0x65, 0x7f, 0x2a, 0xca, 0x85, 0x43, + 0xed, 0x2d, 0x8f, 0x29, 0xcb, 0xca, 0x10, 0x11, 0x95, 0xc9, 0x93, 0xde, + 0x4b, 0x4a, 0x63, 0x35, 0x37, 0xd5, 0x28, 0x6d, 0xd0, 0xf6, 0x24, 0x10, + 0x6f, 0x39, 0xa5, 0x06, 0x44, 0x77, 0xd4, 0xab, 0xe7, 0x66, 0x13, 0xf0, + 0xfb, 0x31, 0xcf, 0x13, 0xd2, 0x86, 0x0a, 0xb0, 0x87, 0x60, 0xff, 0xa6, + 0x57, 0xda, 0xa7, 0xda, 0x47, 0x1f, 0xd6, 0x7b, 0x5b, 0xeb, 0xd9, 0x56, + 0x80, 0x5e, 0xb3, 0x28, 0xb0, 0x1a, 0xfa, 0x1a, 0x38, 0x6f, 0xfc, 0x37, + 0x40, 0x9b, 0x32, 0xbe, 0x41, 0xda, 0xfa, 0xcc, 0x97, 0x7d, 0xb1, 0x67, + 0x57, 0xbd, 0x3e, 0x5c, 0xaf, 0x9e, 0x2f, 0x95, 0x22, 0xe2, 0x41, 0x1b, + 0xc1, 0x65, 0xd0, 0xfa, 0x34, 0x8f, 0x0f, 0xa7, 0xf3, 0x00, 0xe2, 0x85, + 0xc0, 0x24, 0x73, 0xee, 0xb5, 0x9f, 0xab, 0x66, 0xa7, 0xb9, 0xd4, 0x3a, + 0x57, 0xcd, 0x72, 0xd3, 0x4f, 0x67, 0x9c, 0x86, 0x08, 0x0c, 0x08, 0x2a, + 0x37, 0xbd, 0x72, 0x96, 0x70, 0xea, 0x99, 0x5b, 0x4d, 0x61, 0x88, 0xb1, + 0xda, 0x5f, 0xf1, 0xd8, 0xb9, 0xff, 0x5a, 0xa3, 0x46, 0xd4, 0x5e, 0x50, + 0x3a, 0x34, 0xc9, 0x9e, 0x9b, 0xa1, 0x83, 0x3a, 0x33, 0x9e, 0xac, 0x57, + 0xcf, 0xac, 0x02, 0x3b, 0x54, 0x5f, 0x3c, 0x7a, 0xee, 0x8b, 0x63, 0x84, + 0x4e, 0xd1, 0x2a, 0xfb, 0x22, 0x39, 0xaf, 0x7e, 0x0a, 0x20, 0x88, 0x2e, + 0x37, 0x54, 0x3f, 0x86, 0x8c, 0x35, 0x78, 0x7d, 0xdd, 0x0e, 0xdf, 0x9a, + 0xf6, 0xfe, 0x5f, 0x5d, 0xe0, 0xd5, 0x60, 0x6e, 0x89, 0x43, 0xad, 0xc6, + 0x6a, 0xa4, 0xcb, 0x11, 0xdb, 0x74, 0x1a, 0x3f, 0xbc, 0xc0, 0xab, 0x21, + 0x6b, 0xf8, 0xef, 0xa0, 0x3d, 0x58, 0xbf, 0x4e, 0xe3, 0x5b, 0x17, 0xbc, + 0xc2, 0x97, 0xe1, 0xcd, 0xc8, 0xd0, 0x16, 0xf2, 0xcf, 0xa9, 0x75, 0x97, + 0x73, 0xd1, 0xab, 0xb4, 0xe1, 0x15, 0x64, 0x6d, 0x84, 0xac, 0x53, 0x2c, + 0xb5, 0x11, 0xb2, 0x5d, 0x68, 0x53, 0x05, 0x6d, 0x1c, 0x4a, 0x1b, 0x79, + 0x9a, 0xc3, 0x9a, 0x30, 0x43, 0x8e, 0xf6, 0xf1, 0x97, 0x2e, 0x60, 0xe4, + 0x46, 0x19, 0xf9, 0x9c, 0x5e, 0x67, 0xae, 0x53, 0x8e, 0xdf, 0x69, 0x8d, + 0xbf, 0x46, 0xf6, 0x56, 0x4b, 0x19, 0xf2, 0xcc, 0x17, 0xf4, 0xd5, 0x7a, + 0xf5, 0xbc, 0x86, 0x35, 0x74, 0x4a, 0x9b, 0x7c, 0xbf, 0x1c, 0xbb, 0xcf, + 0xe8, 0x84, 0x25, 0xe5, 0x1b, 0xa5, 0x02, 0xfa, 0x08, 0x8e, 0xc2, 0x4c, + 0x19, 0xd5, 0x98, 0x54, 0x26, 0x10, 0xc7, 0xf5, 0x2c, 0xb5, 0x6a, 0x18, + 0x3e, 0x47, 0xa4, 0xf5, 0x41, 0xf2, 0x3b, 0x02, 0x3d, 0x4b, 0xc0, 0x2b, + 0x92, 0x33, 0x58, 0x69, 0xac, 0xa4, 0x58, 0xeb, 0x95, 0xd4, 0x67, 0xfa, + 0x0a, 0x22, 0x3d, 0x0e, 0x72, 0x56, 0xe5, 0x48, 0xcf, 0x1f, 0xe8, 0x4b, + 0xd5, 0x73, 0x72, 0xad, 0x31, 0xe2, 0x73, 0xfc, 0x26, 0xdc, 0xcb, 0xd0, + 0xaa, 0x03, 0xba, 0x8a, 0x1a, 0xa7, 0x3a, 0x67, 0x3b, 0xa4, 0x9e, 0x7d, + 0xd2, 0x7f, 0x72, 0x1c, 0xfd, 0xe3, 0x7a, 0x92, 0x31, 0xba, 0xcf, 0x13, + 0xdb, 0x71, 0x23, 0x55, 0xf4, 0x73, 0x84, 0xc7, 0x91, 0x1e, 0x97, 0xbd, + 0xa8, 0xfd, 0x1e, 0xc7, 0x61, 0x3e, 0x23, 0x36, 0x76, 0x23, 0xf5, 0x7b, + 0x72, 0x4c, 0xaf, 0xc9, 0xcf, 0x33, 0x78, 0x2f, 0xbe, 0x84, 0xf2, 0xb7, + 0xa4, 0xe6, 0xb6, 0x33, 0xc7, 0x44, 0x34, 0x56, 0x6b, 0xf8, 0x2a, 0x22, + 0xad, 0x49, 0x2a, 0xce, 0xca, 0xc9, 0xf2, 0x89, 0xce, 0xac, 0x11, 0x0a, + 0xdc, 0x5a, 0x82, 0x75, 0xfb, 0x08, 0x6e, 0x24, 0x3c, 0xd7, 0x18, 0x27, + 0xac, 0x67, 0xec, 0x8e, 0x52, 0x8a, 0xf9, 0xe3, 0x98, 0xaf, 0x1c, 0xf0, + 0xd6, 0x51, 0xa0, 0x52, 0x59, 0x40, 0xa7, 0xd1, 0x8e, 0x76, 0x0a, 0x69, + 0xec, 0x74, 0x19, 0xea, 0x94, 0xd2, 0x97, 0x60, 0x75, 0x9d, 0x66, 0x09, + 0x55, 0xe6, 0x54, 0x92, 0xeb, 0x4b, 0xae, 0xdb, 0x5c, 0x77, 0x99, 0x0f, + 0x1f, 0xcc, 0xd8, 0x08, 0xfd, 0x6a, 0x3d, 0x63, 0xf7, 0xb2, 0xcc, 0x72, + 0xba, 0x9f, 0x72, 0xdc, 0x9d, 0xee, 0x4c, 0xc8, 0x54, 0x51, 0xac, 0x67, + 0x98, 0x4e, 0xdc, 0x5b, 0x6b, 0xb8, 0x4a, 0x3b, 0x73, 0xf2, 0x28, 0xf0, + 0x4e, 0xd4, 0xcc, 0xee, 0x40, 0x4f, 0x77, 0xe2, 0xde, 0xc5, 0xf3, 0x72, + 0x6f, 0x36, 0x7a, 0xcf, 0x91, 0xf6, 0x9b, 0x85, 0xfd, 0x93, 0xa5, 0xc6, + 0x17, 0x90, 0x73, 0xb2, 0x9b, 0x9c, 0xda, 0x76, 0x5f, 0xab, 0x57, 0xef, + 0x15, 0x02, 0xad, 0xcb, 0x31, 0xab, 0x0f, 0x68, 0xdb, 0xfd, 0xe4, 0x02, + 0x1b, 0x5e, 0x6e, 0xb3, 0xe1, 0x36, 0xec, 0x50, 0xc8, 0xfa, 0xef, 0x87, + 0xa7, 0xe6, 0xfc, 0x2a, 0xd4, 0x9b, 0x92, 0x16, 0xc5, 0x96, 0xd9, 0x27, + 0x47, 0x7e, 0x96, 0x2d, 0x13, 0xb3, 0x97, 0x8e, 0x49, 0x33, 0x1a, 0xd4, + 0x73, 0xea, 0x88, 0x7f, 0x86, 0xad, 0xda, 0xb1, 0x60, 0x4f, 0x62, 0x65, + 0xf3, 0x6d, 0xb1, 0x6e, 0x2d, 0xda, 0xbc, 0x4d, 0xf6, 0xdd, 0xde, 0x7b, + 0xee, 0x82, 0x97, 0x7c, 0x0e, 0xaf, 0x83, 0xb4, 0xce, 0xd8, 0xfd, 0x0d, + 0x4a, 0x67, 0xb5, 0x16, 0x6c, 0x6d, 0xb0, 0x2b, 0xb1, 0x0e, 0xbb, 0x34, + 0xe6, 0xbf, 0x9e, 0xe3, 0x40, 0x68, 0x8c, 0xb8, 0xb7, 0x10, 0x1e, 0xd6, + 0x0c, 0xb4, 0x2d, 0x95, 0x2b, 0xd2, 0xcb, 0xf6, 0x58, 0xd9, 0x29, 0x4c, + 0xe9, 0x77, 0x63, 0xfe, 0xfd, 0x2c, 0x87, 0xf9, 0x56, 0x79, 0xd8, 0xb9, + 0x99, 0x6f, 0xe6, 0x9a, 0x98, 0x27, 0x73, 0xbe, 0x8d, 0xe7, 0xcb, 0x3e, + 0x0d, 0x2a, 0x6b, 0x50, 0xef, 0x33, 0x76, 0xbe, 0xb0, 0x42, 0x5a, 0xb0, + 0xcf, 0x83, 0xd5, 0xf7, 0x2b, 0xcb, 0x0e, 0x7c, 0xb7, 0x88, 0x2a, 0xaa, + 0x7c, 0xa6, 0xcf, 0xd9, 0xd1, 0xd1, 0x47, 0x43, 0x19, 0x86, 0x93, 0xf7, + 0x51, 0x06, 0xb9, 0x5d, 0x63, 0x2f, 0x95, 0x52, 0xfb, 0xd1, 0x0e, 0xe8, + 0x58, 0x81, 0x7c, 0x8e, 0xab, 0xe2, 0x90, 0x2f, 0xab, 0xe3, 0x78, 0x13, + 0x0d, 0xb9, 0x5d, 0x59, 0x11, 0x4f, 0x39, 0xc6, 0xed, 0xce, 0x6e, 0xcf, + 0xae, 0x44, 0x79, 0x99, 0x4c, 0x8f, 0xbd, 0x52, 0x46, 0xed, 0x5f, 0x83, + 0x0d, 0x79, 0x56, 0x20, 0x9f, 0x93, 0xc7, 0x63, 0xcb, 0x93, 0xb3, 0x93, + 0x27, 0x3d, 0x35, 0xda, 0xc8, 0x2b, 0xa1, 0xef, 0x4b, 0xdf, 0x90, 0x87, + 0x74, 0x79, 0x5e, 0x3b, 0xac, 0xde, 0x9b, 0x77, 0x26, 0x2b, 0x4b, 0xd4, + 0x3e, 0x7f, 0xc6, 0xe9, 0x14, 0xb5, 0x5f, 0x28, 0x13, 0x25, 0x38, 0xc8, + 0xbc, 0x79, 0xb9, 0x79, 0x65, 0x02, 0xbb, 0x36, 0xaf, 0xf6, 0x02, 0x9f, + 0xe2, 0x9b, 0x31, 0x9a, 0x5d, 0x29, 0x5f, 0xbd, 0x53, 0xcf, 0xa5, 0x41, + 0xad, 0x0d, 0xea, 0x3d, 0x8f, 0x9a, 0xcb, 0x98, 0x27, 0xc2, 0x2b, 0x20, + 0x26, 0xda, 0x8a, 0x30, 0x2f, 0x95, 0xb8, 0x45, 0xc4, 0x3c, 0xa7, 0xf4, + 0xaa, 0xf1, 0x7c, 0xf1, 0xaa, 0xb1, 0x77, 0xe4, 0x55, 0x33, 0x6d, 0x96, + 0x61, 0x4a, 0x4b, 0xa8, 0x95, 0x11, 0x55, 0x81, 0xb4, 0x84, 0xbb, 0x11, + 0x95, 0xf1, 0x1a, 0xb2, 0x27, 0x3e, 0xe0, 0x79, 0xb7, 0x3e, 0xdd, 0xef, + 0x86, 0xe7, 0xae, 0x7d, 0x91, 0x3d, 0x34, 0xd6, 0xd6, 0xac, 0x1a, 0xe0, + 0xfb, 0xd3, 0x87, 0xa8, 0x47, 0xfa, 0x61, 0xdc, 0x9f, 0xe4, 0x0a, 0x40, + 0x67, 0xb3, 0xf6, 0x02, 0xaf, 0x01, 0xfb, 0xf8, 0x81, 0x94, 0xce, 0xfd, + 0x72, 0xed, 0xd9, 0x96, 0xfa, 0x1b, 0x52, 0xfb, 0xd8, 0x1f, 0xe5, 0x96, + 0x71, 0x2e, 0x59, 0x65, 0x9b, 0xad, 0x32, 0x7f, 0xba, 0x2c, 0x5f, 0xdf, + 0x4d, 0xb6, 0x36, 0xa8, 0x77, 0x0e, 0xe9, 0x71, 0x05, 0xbe, 0x83, 0x95, + 0xf3, 0xf9, 0x70, 0x83, 0xe8, 0xa8, 0x6e, 0xa1, 0x21, 0xa7, 0x30, 0x79, + 0xa5, 0xe0, 0x59, 0x32, 0x2a, 0xf6, 0xf9, 0x32, 0x3b, 0x0e, 0x55, 0xd2, + 0x50, 0x56, 0x46, 0x26, 0xaf, 0x54, 0x16, 0xb9, 0xdd, 0xed, 0x2f, 0xe3, + 0xfe, 0xe1, 0xae, 0x95, 0x67, 0xae, 0x13, 0xab, 0xe5, 0x2b, 0x6d, 0xff, + 0xa6, 0x03, 0xfa, 0xfa, 0x72, 0x78, 0xfd, 0x20, 0x91, 0x15, 0xeb, 0xb9, + 0x85, 0xfe, 0xe1, 0x34, 0x8f, 0xf3, 0x3e, 0x3a, 0x93, 0x99, 0x29, 0x6a, + 0x7f, 0x76, 0xc6, 0x34, 0x45, 0xed, 0x77, 0xbd, 0x72, 0xbe, 0x79, 0x4c, + 0x57, 0x37, 0xa8, 0xf7, 0x68, 0x31, 0xcf, 0x2a, 0xe4, 0x2b, 0xb1, 0xb2, + 0x31, 0x0f, 0x3f, 0x6f, 0x9a, 0x68, 0x5d, 0x3a, 0x2f, 0xb7, 0xdc, 0xa6, + 0xe5, 0x44, 0xeb, 0xb2, 0x79, 0x65, 0x25, 0x34, 0xb1, 0x66, 0x09, 0xce, + 0xe3, 0xef, 0x62, 0xf6, 0x8a, 0xe5, 0x7a, 0x94, 0x9b, 0xc5, 0xe0, 0x15, + 0xd3, 0x3a, 0xd3, 0x4d, 0x25, 0x26, 0xdf, 0xe2, 0x42, 0x32, 0x0a, 0x35, + 0xa5, 0xdd, 0xd4, 0x48, 0x99, 0x8a, 0x1e, 0x9f, 0xe8, 0xc0, 0x9d, 0x63, + 0xc8, 0x30, 0xb1, 0x12, 0x73, 0xb8, 0xa1, 0x9f, 0x11, 0x42, 0xd4, 0xfe, + 0x6f, 0x00, 0xf5, 0x10, 0x4f, 0x20, 0x38, 0x5e, 0x03, 0x69, 0x2b, 0x86, + 0x88, 0x41, 0x4f, 0x7e, 0x9e, 0x12, 0xe8, 0x29, 0x46, 0x2f, 0x0d, 0x58, + 0x23, 0xeb, 0x54, 0x61, 0xef, 0x7b, 0x1f, 0xa4, 0x02, 0xbd, 0xca, 0x63, + 0xa8, 0xf3, 0xe5, 0x93, 0x0b, 0xce, 0x99, 0x42, 0xdb, 0x39, 0x03, 0x8f, + 0xd1, 0xaf, 0x3c, 0x46, 0x95, 0xcc, 0xb3, 0xc7, 0x98, 0x64, 0xbf, 0xf6, + 0x6b, 0x8e, 0x9f, 0x96, 0xc3, 0xd2, 0x02, 0x3d, 0x25, 0xaf, 0xe7, 0x7d, + 0xd0, 0x56, 0x89, 0xe5, 0x7d, 0x50, 0xb7, 0x16, 0x6d, 0x95, 0xd8, 0xda, + 0xe2, 0xbd, 0x75, 0x97, 0x3c, 0x17, 0xcb, 0x8d, 0x6a, 0xc8, 0x5d, 0xc1, + 0xed, 0xfe, 0x62, 0xa2, 0xb7, 0x04, 0x6d, 0x2e, 0x76, 0x1a, 0x57, 0x2e, + 0x38, 0x8d, 0x0d, 0xf8, 0x61, 0x8f, 0xa4, 0xe5, 0x86, 0x29, 0x23, 0x37, + 0xc3, 0x96, 0xe3, 0x13, 0x2e, 0xe0, 0x85, 0xbf, 0x16, 0x5e, 0x19, 0xb9, + 0xc1, 0x23, 0xfc, 0x58, 0xb5, 0xcb, 0xcf, 0x78, 0x39, 0x42, 0x37, 0x75, + 0x7c, 0x62, 0xc5, 0x07, 0xec, 0x63, 0x1b, 0x20, 0xf7, 0xfe, 0x94, 0xdd, + 0xde, 0x21, 0x4c, 0x6d, 0x7f, 0x27, 0x1b, 0xd4, 0xb9, 0xc4, 0x67, 0x3e, + 0xfb, 0x3c, 0x5f, 0x5b, 0xbb, 0x87, 0x9f, 0x04, 0x04, 0xb0, 0x8a, 0x3e, + 0xdc, 0x49, 0xeb, 0xa8, 0xd5, 0xc7, 0x31, 0x68, 0x96, 0x3c, 0x93, 0x1c, + 0x74, 0x37, 0xe4, 0x8f, 0xd9, 0xfc, 0x5c, 0x09, 0x5a, 0xe7, 0xf8, 0xcd, + 0xaf, 0xe2, 0x37, 0x58, 0x6d, 0x60, 0x6e, 0x39, 0x6e, 0xa9, 0xef, 0xe1, + 0xf1, 0x67, 0xb0, 0x97, 0x0b, 0x91, 0xdb, 0xb9, 0xce, 0x39, 0x43, 0x3a, + 0xc6, 0x73, 0xfa, 0xd6, 0xb6, 0x87, 0x36, 0xc1, 0x13, 0xa6, 0xa2, 0x3c, + 0xa7, 0x8a, 0x2b, 0xae, 0x48, 0x45, 0x79, 0x9d, 0x56, 0x94, 0xe7, 0x08, + 0xcc, 0x29, 0x4f, 0xd9, 0x81, 0x7a, 0x9d, 0x4e, 0x44, 0xac, 0x07, 0x96, + 0xca, 0x79, 0x6e, 0x93, 0xb2, 0x26, 0xa9, 0xf2, 0x87, 0x50, 0xee, 0x2a, + 0xa8, 0x2d, 0xb5, 0x62, 0x43, 0xf6, 0xb8, 0x45, 0xd2, 0xe3, 0x62, 0x1c, + 0x5e, 0xcc, 0x1b, 0x24, 0x55, 0xec, 0xc8, 0xbb, 0x97, 0x67, 0x50, 0xc8, + 0x99, 0xcb, 0x94, 0x91, 0x5c, 0x25, 0x4e, 0x8b, 0xd8, 0xf8, 0x1e, 0x3a, + 0x70, 0x34, 0x70, 0x28, 0xa5, 0xb9, 0xc1, 0xa7, 0x55, 0xad, 0xa8, 0xcc, + 0xdc, 0x00, 0x0b, 0xff, 0x14, 0x24, 0x3b, 0x4d, 0x27, 0x15, 0x18, 0xdf, + 0x76, 0xd5, 0x3a, 0x0a, 0x8c, 0x29, 0xba, 0xa5, 0x32, 0xd2, 0xf3, 0x38, + 0x15, 0xaf, 0x96, 0x91, 0x60, 0x26, 0x3c, 0x74, 0x26, 0xef, 0xa3, 0x41, + 0xb4, 0xcb, 0xb7, 0xa4, 0x65, 0x98, 0xd9, 0x49, 0x1d, 0x8b, 0x1a, 0xf4, + 0x0e, 0x71, 0x73, 0xcb, 0xe4, 0x3b, 0x84, 0x8c, 0x9f, 0x0d, 0x19, 0x4b, + 0x3f, 0xd9, 0xa0, 0xde, 0x63, 0xa5, 0xe7, 0xd0, 0x99, 0x9e, 0x43, 0x11, + 0xe8, 0x53, 0x5a, 0x08, 0xf9, 0x24, 0xe3, 0x23, 0x38, 0x99, 0xd8, 0xde, + 0x60, 0xaf, 0x7d, 0x58, 0x63, 0xff, 0x07, 0x10, 0x5b, 0xb8, 0x8d, 0x75, + 0xc6, 0x95, 0x34, 0x5f, 0xee, 0x7e, 0x1d, 0x97, 0xb1, 0x1d, 0x87, 0xe4, + 0x08, 0xd8, 0xbe, 0x75, 0xb9, 0xe0, 0x39, 0x2a, 0x90, 0xd1, 0x57, 0xa5, + 0xc3, 0x8f, 0x08, 0xa6, 0x9d, 0xc6, 0xa5, 0x3f, 0x6c, 0xa3, 0x03, 0xf2, + 0x8d, 0x52, 0x6d, 0x4b, 0x5a, 0x8b, 0xd7, 0x8b, 0xbd, 0x97, 0xeb, 0x55, + 0xb1, 0xcf, 0x72, 0xe1, 0x9b, 0x98, 0x65, 0xa7, 0x8a, 0x97, 0x11, 0xaf, + 0x71, 0x0c, 0xc4, 0xf3, 0x14, 0x41, 0xc9, 0x01, 0x7d, 0xaf, 0xe0, 0x77, + 0x81, 0x2a, 0xf6, 0x3e, 0x78, 0x73, 0x4b, 0xf2, 0x20, 0xee, 0x12, 0xea, + 0x0c, 0x78, 0xae, 0x41, 0xbd, 0x9b, 0xb7, 0xe6, 0xc8, 0xcf, 0x67, 0x91, + 0xe0, 0x98, 0x65, 0x99, 0xec, 0x73, 0x34, 0x15, 0xb5, 0x5a, 0x3b, 0xc3, + 0x57, 0xe0, 0x2b, 0x6c, 0x5f, 0xd3, 0x8e, 0x53, 0x33, 0x70, 0x97, 0xda, + 0xb9, 0x1c, 0x8d, 0xf1, 0xce, 0x55, 0xf4, 0xe3, 0xec, 0x1f, 0x33, 0x3a, + 0x33, 0x7e, 0x7f, 0x81, 0x77, 0x1b, 0xfb, 0xd3, 0xf2, 0x8c, 0x5a, 0x1a, + 0xdb, 0x57, 0x6a, 0xb5, 0x9e, 0xd9, 0x7e, 0xf4, 0x77, 0x17, 0x02, 0x77, + 0xaa, 0x9d, 0x3c, 0x25, 0x3d, 0x98, 0xda, 0xc9, 0xca, 0x9b, 0xa9, 0x9d, + 0x6c, 0x52, 0xed, 0x37, 0x3a, 0x8d, 0x4c, 0xbd, 0x2b, 0xf5, 0x6e, 0xbc, + 0x97, 0x57, 0xe6, 0x83, 0xf0, 0x70, 0x65, 0x7a, 0x3e, 0x31, 0x5e, 0xb7, + 0xdc, 0x7f, 0x6e, 0x2b, 0x3e, 0xdd, 0x89, 0x31, 0xbe, 0x25, 0xb5, 0xff, + 0xae, 0x59, 0xf4, 0x59, 0x99, 0xb5, 0x27, 0xff, 0xa3, 0x41, 0xdd, 0xb9, + 0xad, 0x3d, 0x58, 0xab, 0xf7, 0xa0, 0xf5, 0x3c, 0xe8, 0xac, 0x3e, 0x4f, + 0x02, 0x9e, 0x12, 0x69, 0xfd, 0x7e, 0xe9, 0x23, 0x0d, 0x69, 0x5d, 0x2f, + 0x37, 0xa8, 0xef, 0x36, 0xe8, 0x08, 0x04, 0x1e, 0x9a, 0x77, 0xb3, 0xf6, + 0x93, 0xb6, 0xb8, 0xa3, 0x53, 0x14, 0xe9, 0xfc, 0xfd, 0xc8, 0xab, 0xdd, + 0xb4, 0x70, 0xf5, 0x54, 0xf9, 0x43, 0x28, 0xd7, 0xb1, 0x09, 0x6a, 0xf1, + 0x2d, 0x6f, 0xaf, 0xdc, 0xeb, 0xec, 0x2f, 0x59, 0xc6, 0xb6, 0x67, 0x20, + 0x5b, 0x61, 0xc5, 0x3b, 0x88, 0x63, 0x8a, 0x69, 0xc1, 0x0d, 0x06, 0x76, + 0x78, 0x08, 0x76, 0xc8, 0x56, 0x61, 0xf2, 0xb3, 0x09, 0x3e, 0x15, 0xd0, + 0x22, 0x9f, 0x0a, 0x85, 0xca, 0x4a, 0x64, 0xb4, 0xc3, 0xb1, 0xc1, 0x00, + 0xda, 0xde, 0xa6, 0x6d, 0x84, 0xbf, 0xcb, 0x91, 0x2d, 0x6d, 0x64, 0xe2, + 0xe6, 0x96, 0xb1, 0x09, 0x39, 0x56, 0x79, 0x23, 0x6e, 0x54, 0xcf, 0xa4, + 0x7d, 0x38, 0x63, 0x10, 0xd3, 0xa3, 0xdd, 0xf7, 0xc9, 0x48, 0x81, 0x29, + 0x7a, 0x33, 0x56, 0x57, 0x30, 0xb7, 0xdc, 0xc4, 0x2a, 0xb7, 0x95, 0xd1, + 0x58, 0x1f, 0xe2, 0x98, 0x8a, 0x7c, 0xda, 0xd9, 0xb6, 0x82, 0x76, 0xf6, + 0xad, 0x40, 0x3a, 0x0b, 0xfc, 0x52, 0x1a, 0x1b, 0x40, 0x3c, 0xd4, 0xeb, + 0x90, 0x4f, 0xce, 0xac, 0xfb, 0x2a, 0x7c, 0x66, 0xa3, 0xf5, 0x1d, 0x98, + 0x36, 0x1a, 0xeb, 0x2d, 0xc5, 0xde, 0x3f, 0x42, 0xdf, 0xf5, 0x1e, 0xa1, + 0xb1, 0xfe, 0x32, 0xea, 0xa8, 0xc2, 0x09, 0xeb, 0x28, 0x70, 0xdc, 0x4c, + 0x8f, 0xb7, 0xc5, 0xfc, 0xef, 0x42, 0xfc, 0x10, 0xf1, 0x9c, 0x90, 0x37, + 0xde, 0x9d, 0xbd, 0x88, 0xc4, 0x30, 0x93, 0xbe, 0x3f, 0xe0, 0x04, 0xf6, + 0xde, 0x81, 0xa5, 0xcd, 0xa7, 0xf4, 0x73, 0x1c, 0x7e, 0x54, 0x96, 0x7e, + 0x8e, 0x73, 0xb5, 0x28, 0xa5, 0x6b, 0x8c, 0x15, 0x74, 0xb5, 0xa3, 0x2c, + 0xf5, 0x1c, 0x87, 0x68, 0xb9, 0x7e, 0x2e, 0x7d, 0x1d, 0x50, 0x80, 0xd3, + 0xa0, 0x03, 0xeb, 0x71, 0x40, 0xd6, 0xe6, 0x9b, 0x41, 0x09, 0x1d, 0x97, + 0xe7, 0xab, 0x3a, 0x5b, 0x78, 0xb5, 0xb2, 0xf5, 0x53, 0x61, 0x87, 0x3e, + 0x23, 0x2b, 0x1b, 0xd5, 0x73, 0x7c, 0x7e, 0xce, 0x89, 0x12, 0xd8, 0xcf, + 0x3a, 0xb4, 0xed, 0x15, 0xe9, 0x67, 0x9b, 0x9c, 0x2b, 0xa5, 0x3a, 0xd8, + 0xc3, 0x1e, 0x51, 0x4e, 0xeb, 0x70, 0xbb, 0xad, 0x14, 0xbc, 0x6a, 0x6b, + 0x30, 0xde, 0x30, 0x38, 0x65, 0xb4, 0xc7, 0x28, 0xc7, 0x3d, 0x2b, 0x17, + 0x11, 0x68, 0x8d, 0xe1, 0x93, 0xb5, 0x84, 0x7e, 0xb6, 0xce, 0xff, 0x2b, + 0xc8, 0xea, 0xab, 0x45, 0x7f, 0x0f, 0xc3, 0x3a, 0xf5, 0x0d, 0xd9, 0x3e, + 0x46, 0x0f, 0x7d, 0xac, 0x1e, 0xfc, 0xba, 0x87, 0x1a, 0xf4, 0x30, 0x2d, + 0x7b, 0xf0, 0xcb, 0x1e, 0xfc, 0xa9, 0x1e, 0xd4, 0x33, 0x3c, 0xee, 0x63, + 0x45, 0xaa, 0x0f, 0xeb, 0xdb, 0x28, 0xf3, 0xf7, 0x87, 0xf5, 0x4c, 0xaa, + 0xab, 0x51, 0x7d, 0x4f, 0x46, 0xcd, 0x63, 0xfa, 0x5b, 0x39, 0x16, 0xff, + 0x80, 0x7c, 0xcb, 0x9e, 0x47, 0xf7, 0x0a, 0x41, 0x79, 0xc2, 0xe3, 0x7b, + 0x0b, 0xc5, 0x90, 0xa0, 0xeb, 0xf1, 0xf1, 0x10, 0x8e, 0xa3, 0x3c, 0xba, + 0x16, 0x29, 0xe3, 0x1c, 0x75, 0x19, 0xef, 0xbb, 0xf9, 0x06, 0x7a, 0x4e, + 0x4a, 0x79, 0xf3, 0xe9, 0xef, 0x05, 0x74, 0x99, 0xde, 0x2f, 0x3c, 0x05, + 0xdd, 0x33, 0xa7, 0x68, 0x0e, 0xdc, 0x2f, 0x6f, 0x7b, 0x67, 0xe4, 0xd0, + 0xe1, 0xf0, 0xee, 0xdd, 0x13, 0x0f, 0x0d, 0x6f, 0xa7, 0x47, 0x58, 0x90, + 0x3e, 0x6a, 0xe0, 0xe3, 0x8c, 0x7c, 0x3e, 0x42, 0x79, 0xdd, 0x4d, 0x4d, + 0x4d, 0xdd, 0x74, 0x3b, 0x58, 0xdb, 0x8e, 0x5f, 0x3e, 0x72, 0x7d, 0x13, + 0xfd, 0x02, 0x22, 0xdd, 0x74, 0xaf, 0x61, 0x2a, 0xf3, 0x3c, 0x87, 0xec, + 0xb3, 0x23, 0xf4, 0xaa, 0x26, 0x5c, 0xf5, 0xb3, 0x86, 0x43, 0xd7, 0xa4, + 0x5f, 0xa6, 0xeb, 0x7d, 0xc1, 0xe0, 0x7a, 0xdf, 0x57, 0x65, 0xdb, 0x8e, + 0xd3, 0xc7, 0x1d, 0xd0, 0x25, 0x0f, 0x6d, 0x7f, 0xda, 0xc1, 0xac, 0x2d, + 0xdd, 0xf4, 0x2e, 0x3e, 0xd5, 0x7f, 0x8c, 0xcd, 0x3c, 0xda, 0x34, 0x3a, + 0xda, 0x3d, 0xd1, 0xb2, 0xad, 0xa7, 0xa5, 0x9b, 0xde, 0xcb, 0xcc, 0x57, + 0xc1, 0xdc, 0x30, 0x3a, 0x72, 0xf8, 0xc5, 0xfd, 0xf4, 0x5d, 0x87, 0xd2, + 0x6b, 0x37, 0x5a, 0xbf, 0x0b, 0x65, 0x1b, 0x8e, 0x76, 0xd3, 0x7b, 0x32, + 0x41, 0xef, 0x11, 0x85, 0xf9, 0xcf, 0x76, 0xd3, 0x5d, 0x19, 0x3c, 0x56, + 0x9f, 0xe7, 0xe8, 0xe1, 0x9f, 0xd3, 0xe9, 0x4c, 0xa4, 0x37, 0x08, 0x5f, + 0xfe, 0xcf, 0x9b, 0xe8, 0x2b, 0x4e, 0xa4, 0x47, 0x8e, 0x6f, 0x08, 0xb7, + 0x3c, 0x24, 0x7c, 0xbe, 0x47, 0x36, 0xd0, 0xc7, 0x9c, 0x06, 0xdf, 0x8d, + 0x3e, 0x05, 0x7e, 0x17, 0x9d, 0x37, 0xa5, 0x5a, 0xc7, 0x6f, 0x3c, 0xda, + 0xfc, 0xce, 0x77, 0x1f, 0x7f, 0xe4, 0xf8, 0xbd, 0x0f, 0xb7, 0xd1, 0x43, + 0xb2, 0xa5, 0xa5, 0x9e, 0xa3, 0xf4, 0x8e, 0x4c, 0x56, 0x15, 0xed, 0xd3, + 0x67, 0xb8, 0x95, 0x0d, 0xd7, 0x1f, 0x6a, 0x3e, 0xd5, 0x1c, 0x6e, 0xa6, + 0x1f, 0xb8, 0x90, 0x3b, 0xbc, 0xe1, 0xee, 0x63, 0x0f, 0xef, 0x1f, 0x19, + 0x1d, 0x09, 0x3f, 0x7b, 0x82, 0x7e, 0x69, 0x72, 0x79, 0xb8, 0x7c, 0x42, + 0x14, 0x7a, 0x0e, 0xfd, 0x6b, 0xd8, 0x98, 0xfb, 0x5b, 0xfa, 0x99, 0x89, + 0x5e, 0x7e, 0xc4, 0x1f, 0xe7, 0x59, 0x7c, 0xe2, 0xd4, 0x43, 0xe1, 0x4f, + 0x1f, 0xa4, 0x07, 0x58, 0xd0, 0xb8, 0xdd, 0xec, 0x3a, 0x74, 0xe8, 0xb0, + 0xf1, 0xa7, 0xb7, 0xdd, 0x30, 0x72, 0x76, 0xdb, 0x68, 0xf8, 0x7a, 0xfa, + 0x1c, 0x46, 0x34, 0xb1, 0x61, 0x42, 0x2c, 0xcd, 0xff, 0x5e, 0x53, 0x39, + 0x3e, 0x31, 0x90, 0x66, 0x60, 0xf4, 0xd0, 0x5b, 0x8f, 0x4f, 0x18, 0x33, + 0x57, 0xd3, 0x53, 0xac, 0xd3, 0x71, 0x3a, 0xc7, 0x64, 0xc3, 0xc8, 0xcc, + 0xb3, 0xc7, 0x7f, 0x31, 0x72, 0xec, 0xe8, 0xdb, 0x1f, 0x99, 0xe9, 0xbe, + 0xbc, 0xa7, 0x6d, 0xf7, 0xdd, 0xc2, 0xe3, 0xa1, 0x8f, 0x72, 0x27, 0x1b, + 0x8e, 0x3d, 0x7b, 0xec, 0xc2, 0xa9, 0x87, 0xb6, 0x8f, 0xfc, 0xa2, 0x1b, + 0x65, 0x7d, 0x3d, 0xf4, 0x45, 0x59, 0xe1, 0xd1, 0x91, 0x0f, 0x61, 0x3c, + 0xc7, 0x44, 0x61, 0x51, 0xf7, 0xc6, 0x89, 0xf2, 0x2b, 0xe9, 0x2c, 0x8f, + 0x86, 0x9e, 0xe0, 0x32, 0xfa, 0x35, 0xa7, 0xdf, 0xba, 0xe1, 0xd8, 0x23, + 0xdd, 0x1b, 0xaf, 0xd9, 0x3f, 0xd2, 0x7d, 0xcf, 0xfe, 0x91, 0xb7, 0x1d, + 0xdf, 0x48, 0xa7, 0x1d, 0x38, 0xef, 0x3f, 0xdb, 0x44, 0x77, 0x60, 0x01, + 0x44, 0x41, 0x3e, 0xbd, 0xc3, 0x91, 0x5a, 0xda, 0x9d, 0x90, 0x0f, 0x1f, + 0xa6, 0x2f, 0xf0, 0x1c, 0xbf, 0xeb, 0xad, 0x1b, 0x46, 0x0e, 0x95, 0x6f, + 0xa6, 0x27, 0x90, 0x69, 0x3c, 0xae, 0xd2, 0x97, 0xb3, 0xd9, 0xb2, 0x2d, + 0x17, 0x69, 0xfb, 0xae, 0xd6, 0x94, 0x9f, 0x1b, 0x19, 0x92, 0xb6, 0x2b, + 0x9f, 0x41, 0xea, 0xfc, 0xf0, 0x20, 0xda, 0x75, 0x6a, 0x99, 0x32, 0x4d, + 0x89, 0xac, 0xef, 0x9a, 0x09, 0xfd, 0xd4, 0x50, 0xc8, 0xef, 0xb1, 0x58, + 0x27, 0x8b, 0xf5, 0xae, 0xcb, 0x90, 0x74, 0xb5, 0xcc, 0xd7, 0x6a, 0xfe, + 0x60, 0x4a, 0x2e, 0x57, 0xd7, 0xed, 0x91, 0x9f, 0xaa, 0x8f, 0xde, 0x54, + 0x7d, 0xa1, 0xfb, 0x57, 0x28, 0x92, 0x7b, 0xd4, 0x20, 0xd2, 0xcf, 0x73, + 0xd3, 0xdf, 0x5d, 0x53, 0xbc, 0x0c, 0xcd, 0xcb, 0x90, 0x3c, 0x95, 0x76, + 0xa5, 0xda, 0x72, 0x6b, 0xea, 0xd5, 0xb4, 0x40, 0xcb, 0x14, 0xe8, 0x76, + 0x99, 0x57, 0x48, 0xe9, 0x77, 0x84, 0x86, 0xa4, 0x2b, 0xc8, 0x7a, 0xa7, + 0xa7, 0xee, 0xc0, 0x4b, 0xe4, 0x18, 0x4c, 0x52, 0xef, 0xa1, 0x6b, 0xb5, + 0xdf, 0xe4, 0x77, 0xe2, 0xa6, 0xa6, 0x5e, 0xed, 0x6f, 0xbb, 0x74, 0x9d, + 0x2e, 0x9c, 0x02, 0x6e, 0xdd, 0xde, 0x46, 0xed, 0x43, 0x7a, 0x74, 0x59, + 0xaf, 0xd6, 0xdf, 0x4c, 0xa5, 0xe5, 0xa8, 0x6b, 0xc9, 0x51, 0x3b, 0x5c, + 0x47, 0xa2, 0x8e, 0xaa, 0x5b, 0xdb, 0xfb, 0x3a, 0x5b, 0x07, 0x3b, 0x7a, + 0x9b, 0x06, 0x07, 0x06, 0x3b, 0x9b, 0xd6, 0xf4, 0xb5, 0xb7, 0x37, 0xf5, + 0x5e, 0xd6, 0xd1, 0xd6, 0xb4, 0xb6, 0x7f, 0xb0, 0x7d, 0xcd, 0x60, 0xff, + 0x9a, 0xfe, 0xcb, 0x5a, 0x5b, 0x29, 0xb3, 0x7b, 0x72, 0x26, 0x1c, 0x09, + 0x27, 0x37, 0x52, 0x46, 0xb7, 0xa2, 0xc6, 0xc6, 0x2e, 0x32, 0x37, 0x76, + 0xd5, 0xed, 0xe2, 0x4f, 0xa4, 0x0b, 0xfb, 0x66, 0xe6, 0x42, 0xc9, 0x68, + 0x34, 0x39, 0xbd, 0x35, 0x3c, 0x15, 0xde, 0x1a, 0x8c, 0x04, 0xf7, 0x85, + 0xe2, 0xb4, 0x71, 0x31, 0xae, 0x3f, 0x14, 0x8f, 0x47, 0xe3, 0xeb, 0xfd, + 0x93, 0xd1, 0xb9, 0x99, 0x29, 0x7f, 0x24, 0x9a, 0xf4, 0xef, 0x0b, 0x25, + 0xfd, 0x29, 0x49, 0xff, 0xe8, 0xa0, 0x3f, 0x31, 0x19, 0x8c, 0x44, 0x50, + 0xbf, 0xff, 0xcd, 0xd7, 0x9f, 0x0a, 0xed, 0x0d, 0xce, 0xcd, 0xd8, 0xdb, + 0x09, 0x4e, 0x05, 0x63, 0x49, 0x34, 0x52, 0x32, 0x30, 0x37, 0x3b, 0x7b, + 0x38, 0xc5, 0xdf, 0x14, 0x4c, 0x26, 0xfb, 0x83, 0x33, 0x33, 0x7b, 0x82, + 0x93, 0xfb, 0x49, 0x0c, 0x93, 0x31, 0x3c, 0x4a, 0x8e, 0xe1, 0xd1, 0x51, + 0x2a, 0x1f, 0xde, 0xee, 0x1f, 0x3c, 0x34, 0x19, 0x8a, 0x25, 0xc3, 0xd1, + 0x88, 0xff, 0x86, 0xe9, 0xf0, 0x4c, 0xc8, 0x3f, 0x39, 0x13, 0x4d, 0x84, + 0x23, 0xfb, 0xfc, 0xb1, 0x68, 0x3c, 0x49, 0x2b, 0x87, 0xb7, 0xbf, 0x5e, + 0xf9, 0x2c, 0xd4, 0x83, 0x0a, 0x07, 0xc3, 0x93, 0x21, 0x12, 0x5b, 0xc8, + 0xdc, 0xb2, 0xb3, 0x7f, 0x90, 0xbc, 0x5b, 0xe6, 0x26, 0x43, 0xac, 0xf8, + 0x70, 0x24, 0x36, 0x97, 0xdc, 0xc1, 0x4d, 0xf8, 0x2c, 0xd6, 0xf6, 0xb9, + 0xa4, 0xc5, 0xcb, 0xb1, 0x78, 0x32, 0x57, 0x68, 0xe5, 0xc6, 0xe7, 0x62, + 0xdc, 0x6b, 0xf3, 0xf5, 0xc1, 0x83, 0x41, 0x42, 0x40, 0x68, 0x8c, 0x0e, + 0x93, 0x63, 0x74, 0x58, 0x7e, 0xa0, 0x07, 0x7c, 0xec, 0x06, 0x0f, 0xba, + 0x8f, 0xe2, 0xc3, 0x1c, 0x1d, 0xdd, 0x3d, 0x4a, 0xd5, 0xa3, 0xc1, 0xc8, + 0x54, 0x3c, 0x1a, 0x9e, 0x6a, 0xd9, 0x63, 0x8d, 0xb6, 0x25, 0x35, 0xee, + 0x5e, 0x35, 0x1d, 0x5d, 0x54, 0xf5, 0x46, 0x52, 0x03, 0x72, 0x0c, 0x5d, + 0x54, 0xf1, 0x46, 0x42, 0x3c, 0x85, 0x5d, 0x54, 0x77, 0x29, 0x11, 0x6b, + 0x96, 0xbb, 0xa8, 0xe5, 0x92, 0xa2, 0xd3, 0xc1, 0x78, 0x70, 0x12, 0xea, + 0x85, 0x13, 0xc9, 0xf0, 0x64, 0x17, 0x35, 0x5c, 0xaa, 0xc2, 0x40, 0x28, + 0x31, 0x19, 0x0f, 0xc7, 0x92, 0x51, 0x0c, 0xe8, 0x0d, 0x87, 0xad, 0x8d, + 0x66, 0x71, 0x75, 0x67, 0x42, 0x69, 0xc1, 0xd1, 0xd0, 0xb8, 0xb2, 0xba, + 0xc5, 0x67, 0x08, 0xa2, 0x5c, 0x9e, 0x1e, 0xd3, 0xeb, 0xb4, 0xc7, 0x42, + 0x43, 0xe1, 0x19, 0x0c, 0xa5, 0xba, 0x6f, 0x2e, 0x3c, 0x33, 0xc5, 0xed, + 0x2d, 0x36, 0x99, 0xf3, 0x44, 0xdf, 0x50, 0x64, 0x2c, 0x94, 0x80, 0x61, + 0x2f, 0x3e, 0x27, 0x5a, 0x64, 0x3c, 0x94, 0x4c, 0xc2, 0x0c, 0x13, 0xe9, + 0x2e, 0xdf, 0x60, 0x08, 0x96, 0x70, 0x17, 0x2d, 0x4b, 0x09, 0x4d, 0x46, + 0x23, 0xc9, 0x50, 0x24, 0xd9, 0xd2, 0xcf, 0xf4, 0x10, 0x3a, 0x2b, 0x4f, + 0x15, 0xcd, 0x86, 0xa6, 0xc2, 0xc1, 0x16, 0x36, 0xf0, 0x16, 0x36, 0x4b, + 0xcb, 0x40, 0x1a, 0xdf, 0x58, 0x60, 0x38, 0xb2, 0x37, 0x5a, 0xcd, 0x06, + 0xcd, 0x09, 0xbb, 0x3a, 0xaf, 0x2b, 0xdd, 0x45, 0x2b, 0xdf, 0x58, 0x68, + 0x3c, 0x19, 0x4c, 0xce, 0x41, 0xeb, 0xca, 0xd7, 0x13, 0x4b, 0x6d, 0x33, + 0xbb, 0xc1, 0x2d, 0x90, 0xd1, 0xe6, 0x50, 0xad, 0x9a, 0x4c, 0xaf, 0xe6, + 0x65, 0x97, 0xaa, 0xb0, 0x3d, 0xa2, 0xaa, 0x6c, 0x8f, 0x85, 0x22, 0xa1, + 0xa9, 0x51, 0xd8, 0x69, 0x48, 0xda, 0x8a, 0xff, 0x12, 0x15, 0xdf, 0x60, + 0xec, 0x69, 0x1f, 0x60, 0x5f, 0xff, 0x05, 0x42, 0x63, 0xa1, 0xc9, 0x50, + 0xf8, 0x20, 0xb7, 0x53, 0x92, 0x12, 0x89, 0x26, 0x5a, 0xe4, 0x42, 0x57, + 0xef, 0x1a, 0x1c, 0x1b, 0x1f, 0xde, 0xbe, 0xad, 0x8b, 0x0a, 0xe6, 0x97, + 0x45, 0xa6, 0x66, 0xb0, 0x44, 0x85, 0x76, 0xe6, 0xe6, 0x20, 0x33, 0xd1, + 0x4c, 0x91, 0x9d, 0xbb, 0x23, 0x18, 0x9f, 0x0c, 0xcd, 0xec, 0x9c, 0x0b, + 0x4f, 0x75, 0x91, 0x2f, 0x55, 0x30, 0x97, 0x0c, 0xcf, 0xb4, 0x8c, 0x46, + 0xf7, 0xd9, 0xdb, 0x95, 0xbc, 0x1d, 0xc1, 0x70, 0x7c, 0x51, 0x66, 0x37, + 0xad, 0x19, 0x9d, 0x8c, 0xce, 0xb6, 0xc4, 0x67, 0x13, 0x33, 0x2d, 0xd7, + 0xc3, 0x83, 0xb5, 0x2c, 0x70, 0x63, 0xd5, 0x8b, 0x79, 0xf2, 0x2e, 0x6a, + 0xbb, 0x44, 0xad, 0x8b, 0x3c, 0x68, 0x17, 0xad, 0x7e, 0x93, 0x55, 0xec, + 0xb3, 0xdb, 0xf8, 0x26, 0xeb, 0x28, 0xe9, 0xd1, 0x4b, 0x48, 0xa7, 0x4d, + 0x32, 0x65, 0x4d, 0xaf, 0x7b, 0xc2, 0x74, 0xd1, 0xc0, 0x5f, 0xdd, 0x5a, + 0x9a, 0xc3, 0xc6, 0x16, 0x08, 0x26, 0xf6, 0x5f, 0x7a, 0xa2, 0x2e, 0x6a, + 0xe5, 0xd2, 0x83, 0xb6, 0x06, 0xbc, 0x23, 0x98, 0x9c, 0xe6, 0x0d, 0xff, + 0x86, 0xd2, 0xbc, 0xed, 0xa6, 0x82, 0x33, 0x07, 0xc3, 0xfb, 0x5b, 0xe0, + 0x24, 0xa3, 0xd8, 0x8a, 0x38, 0x04, 0x5b, 0x06, 0x23, 0xfa, 0x00, 0xec, + 0x9f, 0x09, 0x26, 0xb0, 0x35, 0xcb, 0x16, 0x91, 0x19, 0x66, 0x9f, 0xaa, + 0xcb, 0x2b, 0x16, 0x29, 0xdf, 0x1a, 0x9a, 0xdd, 0xa3, 0x05, 0x42, 0x10, + 0x59, 0xb1, 0x88, 0xc8, 0x78, 0x78, 0x5f, 0x04, 0x7b, 0x3f, 0x1e, 0xe2, + 0x4d, 0x70, 0x71, 0x71, 0x60, 0x3a, 0x1e, 0xbd, 0x01, 0x55, 0x97, 0x8c, + 0xf2, 0x59, 0xd9, 0x12, 0x8e, 0xb6, 0xd8, 0x0e, 0xea, 0x2e, 0xf2, 0x2a, + 0xf6, 0x4c, 0x30, 0xb2, 0xaf, 0x45, 0xeb, 0x51, 0x60, 0x63, 0x0d, 0xc3, + 0xe3, 0xc9, 0xf9, 0xf2, 0xd9, 0x98, 0xdb, 0xf7, 0x5c, 0x1f, 0x9a, 0x4c, + 0xce, 0xe7, 0x8d, 0x27, 0xe3, 0x18, 0x69, 0xaa, 0x1b, 0xc9, 0x93, 0x5d, + 0x07, 0xf7, 0xf0, 0x6e, 0x5b, 0x61, 0x63, 0xc7, 0x43, 0x7b, 0x5b, 0xae, + 0x0c, 0x05, 0xf7, 0x8f, 0x85, 0xf6, 0x86, 0xe2, 0xa1, 0xc8, 0xe4, 0xa5, + 0x8a, 0xbb, 0xad, 0x46, 0xe5, 0x86, 0xea, 0x8d, 0xc7, 0x83, 0x87, 0xd9, + 0xc3, 0x74, 0x2d, 0xce, 0xee, 0xb6, 0xd4, 0x4a, 0xb3, 0xd3, 0x63, 0x92, + 0xbc, 0xcd, 0xc1, 0x04, 0x0e, 0xbe, 0xd8, 0xa2, 0xcc, 0xee, 0x8b, 0x98, + 0x38, 0x10, 0x2e, 0x96, 0x04, 0xb3, 0x1b, 0x1e, 0x24, 0xcd, 0x1c, 0xc6, + 0x39, 0x15, 0x94, 0xe7, 0xad, 0xc7, 0xc6, 0x55, 0x6a, 0x2e, 0xe4, 0x74, + 0x53, 0xbe, 0x8d, 0x23, 0xdb, 0xf7, 0xda, 0x18, 0x81, 0xf0, 0x2c, 0x4f, + 0xf8, 0x92, 0x85, 0x2c, 0x65, 0xea, 0xde, 0x8b, 0x6c, 0x99, 0x7a, 0x2f, + 0x62, 0x2d, 0x1e, 0x04, 0xda, 0xa3, 0xc4, 0xc4, 0x61, 0xb8, 0xe8, 0x59, + 0x7f, 0x22, 0x14, 0x97, 0x51, 0x99, 0xef, 0xe2, 0x5d, 0x45, 0x39, 0xf6, + 0x2d, 0x40, 0xae, 0xf1, 0x81, 0x91, 0x6b, 0x87, 0xb7, 0x05, 0x68, 0xa5, + 0xfd, 0x94, 0x6c, 0xee, 0xef, 0x1d, 0x1d, 0xed, 0xeb, 0xed, 0x1f, 0xb9, + 0x36, 0x70, 0xf5, 0x8e, 0xc1, 0x6b, 0xb7, 0xf6, 0x06, 0xfa, 0x37, 0x5f, + 0x3b, 0xba, 0x7d, 0x3c, 0x40, 0x62, 0x17, 0x19, 0xbb, 0x10, 0x8e, 0xed, + 0x42, 0x00, 0x69, 0xee, 0x1a, 0xde, 0x3d, 0x4c, 0x19, 0xbb, 0xb6, 0x20, + 0x40, 0xdb, 0x02, 0x36, 0xc2, 0xb2, 0x5d, 0x88, 0xd7, 0xcc, 0x5d, 0x1c, + 0xb0, 0x39, 0x77, 0x49, 0x2e, 0x38, 0xf2, 0x83, 0xa5, 0x47, 0x55, 0x21, + 0xd2, 0x4e, 0xfe, 0xdc, 0xa2, 0x08, 0x82, 0xbc, 0x5d, 0xbb, 0x49, 0x20, + 0xae, 0x43, 0x63, 0x06, 0x02, 0x3a, 0x63, 0xa2, 0x8f, 0x2a, 0x27, 0x2e, + 0x1d, 0x3d, 0x34, 0x4d, 0xfc, 0x55, 0xa7, 0x71, 0xf5, 0x9b, 0x10, 0x87, + 0x45, 0x4c, 0x2c, 0xb2, 0x21, 0xe6, 0x31, 0xad, 0x1d, 0xe1, 0x0e, 0x4e, + 0x4e, 0x86, 0x12, 0x89, 0xea, 0x56, 0x5c, 0x15, 0xb2, 0x55, 0x7a, 0x68, + 0x26, 0xb8, 0x2f, 0x41, 0x8e, 0xe0, 0xd4, 0x14, 0x38, 0xaa, 0x2f, 0x19, + 0xde, 0xba, 0x83, 0xb1, 0x98, 0x0e, 0x32, 0x28, 0x23, 0x98, 0x60, 0x63, + 0xa1, 0xac, 0xd4, 0xb8, 0xa8, 0x24, 0x95, 0x1c, 0x1d, 0x94, 0xde, 0x47, + 0xad, 0xde, 0xce, 0x9d, 0xc3, 0x03, 0xe4, 0xd9, 0xb3, 0x20, 0xa6, 0xa3, + 0xc2, 0x3d, 0xf6, 0x43, 0x45, 0xe9, 0x9e, 0xb0, 0xc9, 0x5d, 0xab, 0x23, + 0x72, 0xcf, 0x9e, 0xa4, 0x2e, 0xe4, 0xb3, 0x0a, 0x2a, 0x53, 0xc6, 0x9e, + 0x24, 0x7b, 0x69, 0x72, 0xee, 0xe1, 0x73, 0x94, 0x32, 0x30, 0x97, 0x38, + 0x07, 0xc9, 0x39, 0x39, 0x13, 0x0a, 0xc6, 0x99, 0x44, 0x13, 0x21, 0x72, + 0x21, 0x2a, 0x8a, 0x60, 0xd4, 0x94, 0xad, 0x13, 0xb2, 0x4a, 0x26, 0xc7, + 0x4a, 0xc1, 0x70, 0x24, 0x21, 0xd9, 0x32, 0x35, 0x12, 0x3a, 0x4c, 0x62, + 0x8a, 0x32, 0x55, 0x77, 0xc3, 0x18, 0xf2, 0x54, 0x6a, 0x1e, 0x13, 0xe4, + 0x9e, 0x0a, 0x27, 0xac, 0x96, 0x32, 0x42, 0x07, 0xe6, 0x82, 0x33, 0x09, + 0x6a, 0xde, 0x1b, 0xc4, 0xf5, 0x61, 0xca, 0x9f, 0x8c, 0xfa, 0x27, 0xe3, + 0xa1, 0x60, 0x32, 0xe4, 0xdf, 0x33, 0x37, 0xa3, 0xef, 0x2d, 0xaa, 0xae, + 0x7f, 0x6f, 0x3c, 0x3a, 0x8b, 0x3b, 0xcc, 0x54, 0x1c, 0xb3, 0x49, 0x99, + 0x7b, 0xc3, 0x91, 0xe0, 0x4c, 0xf8, 0xad, 0x21, 0xaa, 0x40, 0x6a, 0x2a, + 0x3d, 0xdc, 0xa1, 0x68, 0xdc, 0x16, 0xe1, 0x2b, 0xe1, 0x72, 0x16, 0xb1, + 0x8c, 0x7c, 0x31, 0x01, 0xe7, 0xde, 0x70, 0x1c, 0xf3, 0xee, 0xe6, 0x2e, + 0xd4, 0x1a, 0x92, 0x03, 0xdb, 0x87, 0xdc, 0xf8, 0xd0, 0xd7, 0x04, 0x9d, + 0x56, 0xf2, 0xd9, 0x9c, 0x9e, 0x99, 0xe1, 0x05, 0x4c, 0x50, 0x09, 0x67, + 0xd4, 0x8a, 0x2e, 0x0c, 0xb2, 0x69, 0x79, 0xba, 0xec, 0xe2, 0x4d, 0xbc, + 0x84, 0x0b, 0x63, 0xb1, 0x99, 0xf0, 0xa4, 0x74, 0xdb, 0x96, 0x15, 0x14, + 0x80, 0x7d, 0x91, 0x86, 0xc5, 0x76, 0xa6, 0x3d, 0xfc, 0x93, 0xad, 0x5c, + 0x1c, 0xb3, 0x53, 0x26, 0xd8, 0xd2, 0xb9, 0x93, 0x17, 0xa9, 0x01, 0x75, + 0x19, 0xb4, 0xc6, 0x52, 0x92, 0x66, 0x2d, 0xbc, 0x0e, 0x51, 0x96, 0x2c, + 0x93, 0x66, 0x92, 0x97, 0x4a, 0xea, 0x85, 0x4b, 0xe5, 0x13, 0xe4, 0x42, + 0x5a, 0x2e, 0x7f, 0x1d, 0x12, 0x9b, 0xe7, 0x66, 0x39, 0x2c, 0xc7, 0x05, + 0x13, 0x9e, 0x5f, 0x4d, 0xe0, 0xa2, 0xd3, 0x0c, 0x51, 0xd8, 0xad, 0x24, + 0x53, 0xb2, 0x05, 0x6e, 0x97, 0x2a, 0x91, 0xe0, 0xd3, 0xf5, 0xa2, 0x89, + 0xda, 0x16, 0x9c, 0x65, 0xe6, 0xf0, 0x40, 0x82, 0x6a, 0x2e, 0x96, 0x91, + 0x21, 0xd0, 0x45, 0x82, 0xb5, 0x17, 0x0b, 0xaa, 0xc0, 0xe7, 0x22, 0xc9, + 0x65, 0x90, 0xe4, 0xe2, 0x85, 0x6a, 0x62, 0x70, 0x4b, 0x75, 0x91, 0xaa, + 0xc3, 0x7b, 0x04, 0xc3, 0xd1, 0x2a, 0x73, 0x0b, 0xd2, 0x00, 0x78, 0xf5, + 0x65, 0x26, 0xd7, 0xca, 0xcc, 0xf1, 0xc1, 0x4d, 0x1e, 0x9d, 0x65, 0x1f, + 0xca, 0xd5, 0x06, 0xe4, 0x5a, 0x28, 0x5b, 0x91, 0xa2, 0xf1, 0x68, 0x2c, + 0x14, 0x4f, 0x86, 0xd1, 0x4f, 0x3e, 0xb2, 0x63, 0xa1, 0xd9, 0x68, 0x32, + 0xa4, 0x67, 0x9c, 0xeb, 0x8e, 0x4b, 0x3f, 0xad, 0x37, 0xba, 0xec, 0x32, + 0x70, 0x38, 0x16, 0xa2, 0xc2, 0x69, 0x19, 0xb0, 0xea, 0xf9, 0xc7, 0xfd, + 0x30, 0xb2, 0x2f, 0x34, 0x45, 0xb9, 0x8a, 0xab, 0x83, 0x62, 0x72, 0x4d, + 0x07, 0x13, 0xdb, 0xd8, 0x88, 0x32, 0x91, 0x98, 0xee, 0x8f, 0x4e, 0x41, + 0xd5, 0x70, 0xa2, 0x5f, 0x6d, 0x36, 0x88, 0xbb, 0xc2, 0x89, 0xc1, 0xd9, + 0x58, 0xf2, 0x30, 0x27, 0xe4, 0xfc, 0x71, 0x71, 0xfa, 0x36, 0x9e, 0x19, + 0xd6, 0x67, 0x1a, 0x65, 0x72, 0xcc, 0xb3, 0x39, 0x8a, 0x4d, 0x91, 0xb1, + 0x3f, 0x74, 0x18, 0xbe, 0x9f, 0x5c, 0xb3, 0xda, 0x6c, 0x4d, 0x76, 0x8e, + 0xe4, 0x9e, 0x4d, 0xcd, 0x0f, 0x79, 0x67, 0x2f, 0xb2, 0xed, 0xec, 0x59, + 0x9b, 0x03, 0x32, 0x23, 0x3c, 0x4d, 0x66, 0x84, 0x15, 0xf3, 0x45, 0x23, + 0x7d, 0xc1, 0xe4, 0xe4, 0x74, 0xfa, 0x22, 0x97, 0xa0, 0x22, 0x18, 0xff, + 0xbc, 0xfb, 0xae, 0x35, 0xba, 0xc2, 0x85, 0x05, 0x6c, 0x63, 0xb4, 0x64, + 0x21, 0xf7, 0xca, 0x38, 0xf4, 0x96, 0xad, 0xa8, 0x81, 0x62, 0x33, 0xf1, + 0xe6, 0x08, 0xa9, 0x66, 0xc8, 0x13, 0x8d, 0xa4, 0x6f, 0xc7, 0xb2, 0x05, + 0xaf, 0x9d, 0xa3, 0x6a, 0xe7, 0x46, 0xf5, 0x7d, 0x06, 0x56, 0x80, 0x9e, + 0xf3, 0xa2, 0xf3, 0xae, 0x37, 0xdc, 0xa7, 0x3d, 0x3f, 0x10, 0x9a, 0x09, + 0x1e, 0x06, 0x3b, 0xdf, 0x62, 0xf3, 0x2a, 0x1e, 0xb4, 0xcb, 0xa9, 0xdd, + 0x69, 0x0d, 0xc4, 0x15, 0x8d, 0x0c, 0xcd, 0xcc, 0x25, 0xa6, 0x29, 0x27, + 0x1a, 0xd9, 0x9a, 0x9c, 0xb3, 0xd8, 0xd0, 0x8c, 0xf5, 0x51, 0x26, 0x30, + 0x96, 0x48, 0x84, 0xa9, 0x98, 0x39, 0x33, 0x61, 0xde, 0x47, 0x52, 0xaf, + 0xfe, 0xe8, 0x6c, 0x0c, 0x7e, 0x10, 0xb2, 0xa8, 0x29, 0x4f, 0x38, 0xe9, + 0x27, 0xad, 0x9c, 0x9a, 0x41, 0xca, 0x40, 0x2e, 0x14, 0x91, 0xf3, 0xa5, + 0xed, 0x26, 0x31, 0xc0, 0x3e, 0x16, 0xd7, 0x24, 0xc8, 0x16, 0xc0, 0xde, + 0x22, 0x0b, 0x9c, 0x07, 0xb9, 0x99, 0xa9, 0xd3, 0xb9, 0x9c, 0x4e, 0x5b, + 0x41, 0x11, 0x67, 0xe7, 0x5d, 0x32, 0xae, 0x0c, 0x27, 0xa7, 0x61, 0xc7, + 0xc5, 0x56, 0x41, 0xfa, 0x2a, 0xa1, 0x4b, 0x7c, 0x56, 0x89, 0x8d, 0x97, + 0xc7, 0x3c, 0xdb, 0x63, 0x9d, 0x2c, 0xce, 0xab, 0x6d, 0x20, 0x93, 0xec, + 0x8c, 0xe1, 0x13, 0xa2, 0x37, 0xb0, 0xbb, 0x2a, 0x88, 0x61, 0x93, 0x2d, + 0xd4, 0xb1, 0x64, 0x11, 0xe6, 0x78, 0x32, 0x14, 0x0b, 0xdc, 0x10, 0xa5, + 0xa2, 0x79, 0x65, 0xe9, 0xcd, 0x4a, 0x59, 0x31, 0x79, 0xac, 0x4f, 0x85, + 0x0e, 0x51, 0x66, 0xcc, 0x0a, 0x65, 0x1c, 0x6c, 0xea, 0x4b, 0xe3, 0xa1, + 0x7d, 0x7c, 0x4d, 0x8d, 0xcf, 0xbf, 0xeb, 0x52, 0x46, 0x5c, 0x2e, 0x1e, + 0xb9, 0x15, 0x95, 0xca, 0x2e, 0x8b, 0xe3, 0x60, 0x0a, 0x25, 0x92, 0x69, + 0x8b, 0xda, 0x11, 0x0f, 0x47, 0xb1, 0x22, 0x87, 0xc9, 0x11, 0x9f, 0x8b, + 0x90, 0xcb, 0x7a, 0x22, 0x97, 0x99, 0x98, 0x9c, 0x0e, 0x4d, 0xe1, 0xac, + 0xa2, 0x8c, 0x44, 0x08, 0xa7, 0xda, 0x14, 0x99, 0x09, 0x5e, 0x8a, 0x12, + 0xfe, 0x54, 0x8f, 0xc1, 0xa6, 0x83, 0x53, 0xfe, 0xe1, 0xed, 0xfe, 0x90, + 0x15, 0x81, 0xa3, 0x4e, 0x48, 0x1d, 0x59, 0x94, 0x9f, 0x08, 0xa5, 0x6e, + 0x46, 0x72, 0x97, 0x67, 0x83, 0xc1, 0x0b, 0xbb, 0x95, 0xb7, 0x6e, 0x1e, + 0x67, 0xf4, 0x81, 0x8f, 0x1b, 0x29, 0x5a, 0xe6, 0x03, 0xcf, 0x99, 0x48, + 0x06, 0x79, 0x3a, 0x25, 0x61, 0x59, 0xca, 0x55, 0xc9, 0x64, 0x34, 0x26, + 0xb3, 0x66, 0x02, 0x29, 0x74, 0x62, 0xe5, 0x33, 0x92, 0xd3, 0x61, 0xc4, + 0x22, 0xe4, 0x4a, 0x46, 0x65, 0x6c, 0x4c, 0x99, 0xc9, 0xa8, 0x3e, 0xe5, + 0x96, 0xcc, 0x45, 0x16, 0x9b, 0xf8, 0x65, 0x0b, 0xd8, 0xb6, 0xe9, 0x2d, + 0x9e, 0x8b, 0xbc, 0xce, 0x34, 0x3a, 0x0f, 0x06, 0x21, 0x4f, 0x2e, 0x49, + 0xb6, 0xef, 0xa5, 0xf8, 0x4d, 0x37, 0x0d, 0x74, 0xbe, 0xad, 0x92, 0xcb, + 0x30, 0xdc, 0xca, 0xf5, 0x95, 0x58, 0x92, 0xca, 0xc6, 0x4a, 0xdc, 0xaa, + 0x62, 0xe1, 0x19, 0x79, 0xe2, 0x35, 0xcd, 0x62, 0x94, 0x28, 0x88, 0x87, + 0x10, 0x65, 0x24, 0x42, 0x28, 0x84, 0xd7, 0x6a, 0xc2, 0x7c, 0x4e, 0xee, + 0x4f, 0xcc, 0xcd, 0x26, 0x2a, 0xd7, 0xef, 0x45, 0x68, 0x10, 0x6a, 0xac, + 0x9c, 0x0d, 0x47, 0x9a, 0x82, 0xb1, 0x70, 0xe5, 0xfa, 0xb6, 0xb5, 0x8d, + 0x95, 0xb0, 0xe9, 0x04, 0xea, 0xa2, 0x5a, 0x7b, 0x73, 0x7b, 0xf3, 0xea, + 0xd6, 0x26, 0x84, 0x07, 0x0d, 0xc1, 0x68, 0x22, 0xd6, 0x51, 0xf9, 0x76, + 0x32, 0x9a, 0xc4, 0x29, 0xa3, 0xd4, 0x2c, 0x12, 0x45, 0xd7, 0x15, 0x35, + 0x16, 0x65, 0xaa, 0xac, 0xa3, 0xe8, 0x2d, 0x45, 0xe3, 0x45, 0x2e, 0xa3, + 0x1e, 0x99, 0xe2, 0x3a, 0xa3, 0xc1, 0xb8, 0x5d, 0x98, 0x59, 0xdf, 0x17, + 0x45, 0x6e, 0x99, 0xac, 0xce, 0xca, 0x40, 0xba, 0x48, 0x95, 0xd6, 0xa8, + 0x52, 0x03, 0x9c, 0xdc, 0x74, 0x72, 0xaf, 0x6a, 0xc7, 0x85, 0x66, 0xc7, + 0x8b, 0x1c, 0x45, 0x3d, 0x45, 0xad, 0x45, 0x55, 0x68, 0x4f, 0x32, 0x9d, + 0x45, 0x46, 0x8a, 0xd1, 0xcc, 0x0c, 0x51, 0xbc, 0xc6, 0xd2, 0xc2, 0x28, + 0xba, 0xaa, 0x68, 0xb3, 0x25, 0x68, 0x16, 0x4d, 0xa0, 0x76, 0x95, 0x3d, + 0x7b, 0x45, 0xd1, 0x50, 0xba, 0x99, 0xab, 0x74, 0x33, 0x59, 0x16, 0x63, + 0x02, 0x75, 0x77, 0x15, 0x0d, 0x80, 0x91, 0xd2, 0x78, 0xca, 0x68, 0xe4, + 0x32, 0xa3, 0x78, 0x55, 0xf1, 0x4a, 0xc5, 0x75, 0x83, 0xbb, 0x4f, 0x71, + 0x33, 0x8a, 0x6b, 0x8a, 0x2b, 0x8a, 0x6b, 0x8b, 0xab, 0x8a, 0x2b, 0x8b, + 0xab, 0xd3, 0x75, 0x96, 0xa4, 0x93, 0x4b, 0x85, 0x43, 0x64, 0x99, 0x25, + 0x0e, 0xc3, 0x30, 0x84, 0xb1, 0xe6, 0xc8, 0x11, 0xf3, 0xd1, 0x95, 0x1d, + 0xe2, 0xbe, 0x6a, 0x21, 0x9e, 0x03, 0xce, 0x03, 0xcf, 0xaf, 0x14, 0xe2, + 0xe4, 0x2a, 0x21, 0x1e, 0x04, 0xbe, 0x5d, 0x25, 0xc4, 0x7d, 0x35, 0x42, + 0xbc, 0xc0, 0x8f, 0xdf, 0x33, 0xae, 0xb8, 0x25, 0x93, 0xc4, 0x22, 0x20, + 0x43, 0xb8, 0xf3, 0x0d, 0x71, 0xd2, 0x7f, 0xe5, 0x2d, 0x47, 0xcc, 0x17, + 0x9a, 0xaf, 0x12, 0x47, 0x5a, 0x84, 0xb8, 0x13, 0x78, 0x14, 0x78, 0x1a, + 0x38, 0x07, 0xdc, 0xde, 0x2a, 0xc4, 0x3d, 0xc0, 0x63, 0xc0, 0x33, 0xc0, + 0xf3, 0xad, 0x64, 0x0a, 0xa7, 0x17, 0x4a, 0x08, 0xae, 0xba, 0x07, 0x55, + 0x1f, 0x6c, 0x9f, 0x14, 0x47, 0xda, 0xd0, 0xf3, 0x6a, 0x21, 0x5e, 0x82, + 0xc8, 0x6b, 0x48, 0x7f, 0xbd, 0x9d, 0x5c, 0x2e, 0xdf, 0x12, 0x25, 0xa6, + 0xff, 0x4f, 0x43, 0xf6, 0x99, 0x5e, 0xc3, 0xb8, 0xb3, 0x43, 0x18, 0xdf, + 0x5e, 0xef, 0x30, 0x4e, 0xae, 0x01, 0xdd, 0xe8, 0x30, 0x4e, 0xaf, 0x75, + 0x1b, 0xe7, 0x3b, 0xc3, 0xe6, 0x4b, 0x7d, 0x0e, 0xf1, 0x74, 0x0f, 0xba, + 0xec, 0x76, 0x88, 0xdb, 0x41, 0xef, 0xeb, 0x31, 0xc4, 0xf9, 0x6e, 0x21, + 0x9e, 0xea, 0x82, 0x3a, 0x83, 0x42, 0xbc, 0x02, 0x3c, 0xbe, 0x19, 0x2a, + 0x6c, 0x41, 0x7e, 0x14, 0xe8, 0x14, 0xe2, 0x4c, 0xa7, 0x21, 0xee, 0xbc, + 0x0c, 0x23, 0x46, 0xfe, 0x1c, 0x70, 0x62, 0x2b, 0x39, 0x04, 0x16, 0x92, + 0xff, 0xdd, 0x22, 0xd0, 0xe1, 0xa3, 0x3b, 0x6e, 0xc5, 0xfc, 0x6c, 0x43, + 0xad, 0xed, 0x06, 0xb9, 0xa8, 0xdc, 0x2d, 0xdc, 0xb7, 0x8b, 0x63, 0x47, + 0xcc, 0x9f, 0x8c, 0x71, 0xe9, 0xb9, 0x31, 0x71, 0xcc, 0x7f, 0x6c, 0x5c, + 0x64, 0x9d, 0xb8, 0x42, 0x64, 0x9d, 0xbf, 0x42, 0x64, 0x7e, 0x7d, 0x87, + 0x78, 0xa7, 0x41, 0xe4, 0x34, 0xb2, 0xac, 0x6f, 0x08, 0x0b, 0xda, 0x08, + 0xc1, 0xa7, 0xeb, 0xcc, 0x5b, 0x0d, 0xba, 0x5c, 0xbc, 0x52, 0x27, 0xcc, + 0x67, 0xeb, 0x31, 0x25, 0x75, 0x86, 0xb8, 0xbd, 0x4e, 0x88, 0xb3, 0x48, + 0x9f, 0x07, 0x1e, 0x6c, 0x80, 0x9e, 0xc0, 0xb9, 0x06, 0xf4, 0xef, 0xca, + 0x91, 0xf5, 0x86, 0x79, 0x5a, 0x1b, 0xb7, 0x88, 0xdb, 0x9b, 0x84, 0xf9, + 0x93, 0x26, 0xe8, 0xdb, 0x88, 0xa9, 0x05, 0x5e, 0x43, 0xfa, 0xa9, 0x66, + 0xcc, 0x51, 0xb3, 0x28, 0xb2, 0xde, 0xc9, 0xf0, 0x9b, 0x8c, 0x07, 0xfa, + 0x15, 0x7d, 0xb2, 0x5f, 0xbd, 0xff, 0xf8, 0x02, 0xe8, 0x97, 0x6d, 0xe9, + 0xef, 0xe8, 0xf4, 0x4f, 0x41, 0xcf, 0xda, 0xd2, 0xbf, 0xd7, 0xf5, 0x5e, + 0xd5, 0x34, 0x7f, 0x40, 0x51, 0xbf, 0xa6, 0x75, 0x9a, 0x76, 0x6a, 0xba, + 0x49, 0xd3, 0x5d, 0x9a, 0xee, 0x1b, 0x50, 0xef, 0x57, 0xb8, 0x8f, 0x43, + 0x48, 0xdf, 0x36, 0x90, 0xee, 0xf3, 0x3d, 0x5a, 0xe6, 0x03, 0x36, 0xde, + 0x63, 0xb6, 0xf4, 0xe7, 0x90, 0x7e, 0x7c, 0x28, 0xfd, 0xfe, 0xc9, 0x7a, + 0x2f, 0xf5, 0xe8, 0x90, 0xfa, 0xdb, 0x11, 0xf7, 0x81, 0x3e, 0x6d, 0xfd, + 0x00, 0x5b, 0xff, 0x7b, 0x6e, 0x41, 0xfe, 0xf9, 0x05, 0x79, 0xff, 0x26, + 0xf5, 0x8e, 0xc7, 0x9a, 0x1b, 0x7e, 0xbf, 0xc4, 0x7f, 0x50, 0x42, 0xfe, + 0xad, 0x84, 0x4d, 0xea, 0xfd, 0x52, 0xf1, 0x26, 0xf5, 0x1b, 0xee, 0x4c, + 0xd0, 0x30, 0xe8, 0xb9, 0x21, 0xf5, 0x7b, 0xff, 0x17, 0x86, 0xd4, 0xef, + 0xfb, 0xcf, 0x0f, 0xa9, 0xdf, 0x99, 0xbf, 0x04, 0xda, 0xba, 0x69, 0x7e, + 0xfb, 0xdd, 0x0b, 0xf2, 0x03, 0x9b, 0xd2, 0x6b, 0xc0, 0xff, 0x7a, 0x74, + 0x7b, 0x3b, 0x16, 0xf0, 0x47, 0x34, 0xdf, 0x4b, 0xf3, 0xf9, 0x4c, 0xad, + 0xbf, 0x13, 0x64, 0x50, 0xfa, 0x6f, 0x05, 0xf1, 0x9c, 0x5a, 0x7f, 0x2f, + 0x88, 0xc7, 0xc2, 0xdf, 0x51, 0xe0, 0x9f, 0x44, 0xf0, 0xfc, 0x58, 0x7f, + 0x37, 0x88, 0xdf, 0xbe, 0x59, 0x7f, 0x3b, 0x48, 0xf8, 0xd5, 0xdf, 0xc5, + 0xe0, 0xbf, 0x1f, 0xe4, 0xf0, 0xab, 0xef, 0x84, 0xf3, 0xef, 0xdb, 0x84, + 0x47, 0x7d, 0x5f, 0x95, 0x7f, 0x87, 0x67, 0xf8, 0x55, 0x5f, 0xfc, 0xf7, + 0x85, 0x4c, 0xbf, 0x9a, 0x1b, 0xb6, 0x1b, 0xfe, 0x11, 0x1c, 0xb7, 0xc3, + 0xbf, 0x0f, 0x74, 0xfa, 0x95, 0x4e, 0xfc, 0x3b, 0x41, 0x87, 0x47, 0xbd, + 0xa7, 0x3b, 0x8d, 0x74, 0x86, 0x96, 0xe1, 0xdf, 0x11, 0xf2, 0xcb, 0x4a, + 0x96, 0xe1, 0xbf, 0x71, 0xf4, 0x7f, 0x27, 0xec, 0x58, 0xd2, 0x1c, 0x49, + 0x00, 0x00 +}; + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ + METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ + METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ + METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") + +DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (start, "start", "()V") \ + METHOD (stop, "stop", "()V") \ + METHOD (close, "close", "()V") \ + METHOD (sendMidi, "sendMidi", "([BII)V") \ + METHOD (getName, "getName", "()Ljava/lang/String;") + +DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23) +#undef JNI_CLASS_MEMBERS + +//============================================================================== +class MidiInput::Pimpl +{ +public: + Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) + : juceMidiInput (midiInput), + callback (midiInputCallback), + midiConcatenator (2048), + javaMidiDevice (LocalRef (getEnv()->CallObjectMethod (deviceManager, + MidiDeviceManager.openMidiInputPortWithID, + (jint) deviceID, + (jlong) this))) + { + } + + ~Pimpl() + { + if (jobject d = javaMidiDevice.get()) + { + getEnv()->CallVoidMethod (d, JuceMidiPort.close); + javaMidiDevice.clear(); + } + } + + bool isOpen() const noexcept + { + return javaMidiDevice != nullptr; + } + + void start() + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, JuceMidiPort.start); + } + + void stop() + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, JuceMidiPort.stop); + + callback = nullptr; + } + + String getName() const noexcept + { + if (jobject d = javaMidiDevice.get()) + return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); + + return {}; + } + + static void handleReceive (JNIEnv* env, Pimpl& myself, jbyteArray byteArray, jint offset, jint len, jlong timestamp) + { + jassert (byteArray != nullptr); + auto* data = env->GetByteArrayElements (byteArray, nullptr); + + std::vector buffer (static_cast (len)); + std::memcpy (buffer.data(), data + offset, static_cast (len)); + + myself.midiConcatenator.pushMidiData (buffer.data(), + len, + static_cast (timestamp) * 1.0e-9, + myself.juceMidiInput, + *myself.callback); + + env->ReleaseByteArrayElements (byteArray, data, 0); + } + +private: + MidiInput* juceMidiInput; + MidiInputCallback* callback; + MidiDataConcatenator midiConcatenator; + GlobalRef javaMidiDevice; +}; + +//============================================================================== +class MidiOutput::Pimpl +{ +public: + Pimpl (const LocalRef& midiDevice) + : javaMidiDevice (midiDevice) + { + } + + ~Pimpl() + { + if (jobject d = javaMidiDevice.get()) + { + getEnv()->CallVoidMethod (d, JuceMidiPort.close); + javaMidiDevice.clear(); + } + } + + void send (jbyteArray byteArray, jint offset, jint len) + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, + JuceMidiPort.sendMidi, + byteArray, offset, len); + } + + String getName() const noexcept + { + if (jobject d = javaMidiDevice.get()) + return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); + + return {}; + } + +private: + GlobalRef javaMidiDevice; +}; + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + CALLBACK (generatedCallback<&MidiInput::Pimpl::handleReceive>, "handleReceive", "(J[BIIJ)V" ) + +DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) +#undef JNI_CLASS_MEMBERS + +//============================================================================== +class AndroidMidiDeviceManager +{ +public: + AndroidMidiDeviceManager() = default; + + Array getDevices (bool input) + { + if (jobject dm = deviceManager.get()) + { + jobjectArray jDeviceNameAndIDs + = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs + : MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); + + // Create a local reference as converting this to a JUCE string will call into JNI + LocalRef localDeviceNameAndIDs (jDeviceNameAndIDs); + + auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); + deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); + + Array devices; + + for (int i = 0; i < deviceNameAndIDs.size(); i += 2) + devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); + + return devices; + } + + return {}; + } + + MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) + { + if (auto dm = deviceManager.get()) + { + auto androidMidiInput = std::make_unique (juceMidiInput, deviceID, callback, dm); + + if (androidMidiInput->isOpen()) + return androidMidiInput.release(); + + // Perhaps the device is already open + jassertfalse; + } + + return nullptr; + } + + MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) + { + if (auto dm = deviceManager.get()) + { + if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) + return new MidiOutput::Pimpl (LocalRef(javaMidiPort)); + + // Perhaps the port is already open + jassertfalse; + } + + return nullptr; + } + +private: + static void handleDevicesChanged (JNIEnv*, jclass) + { + MidiDeviceListConnectionBroadcaster::get().notify(); + } + + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + CALLBACK (handleDevicesChanged, "handleDevicesChanged", "()V" ) \ + STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode) + #undef JNI_CLASS_MEMBERS + + GlobalRef deviceManager { LocalRef (getEnv()->CallStaticObjectMethod (JuceMidiSupport, + JuceMidiSupport.getAndroidMidiDeviceManager, + getAppContext().get())) }; +}; + +//============================================================================== +Array MidiInput::getAvailableDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + AndroidMidiDeviceManager manager; + return manager.getDevices (true); +} + +MidiDeviceInfo MidiInput::getDefaultDevice() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) +{ + if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) + return {}; + + AndroidMidiDeviceManager manager; + + std::unique_ptr midiInput (new MidiInput ({}, deviceIdentifier)); + + if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) + { + midiInput->internal.reset (port); + midiInput->setName (port->getName()); + + return midiInput; + } + + return {}; +} + +StringArray MidiInput::getDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiInput::getDefaultDeviceIndex() +{ + return (getAndroidSDKVersion() < 23 ? -1 : 0); +} + +std::unique_ptr MidiInput::openDevice (int index, MidiInputCallback* callback) +{ + return openDevice (getAvailableDevices()[index].identifier, callback); +} + +MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) + : deviceInfo (deviceName, deviceIdentifier) +{ +} + +MidiInput::~MidiInput() = default; + +void MidiInput::start() +{ + if (auto* mi = internal.get()) + mi->start(); +} + +void MidiInput::stop() +{ + if (auto* mi = internal.get()) + mi->stop(); +} + +//============================================================================== +Array MidiOutput::getAvailableDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + AndroidMidiDeviceManager manager; + return manager.getDevices (false); +} + +MidiDeviceInfo MidiOutput::getDefaultDevice() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) +{ + if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) + return {}; + + AndroidMidiDeviceManager manager; + + if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) + { + std::unique_ptr midiOutput (new MidiOutput ({}, deviceIdentifier)); + midiOutput->internal.reset (port); + midiOutput->setName (port->getName()); + + return midiOutput; + } + + return {}; +} + +StringArray MidiOutput::getDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return (getAndroidSDKVersion() < 23 ? -1 : 0); +} + +std::unique_ptr MidiOutput::openDevice (int index) +{ + return openDevice (getAvailableDevices()[index].identifier); +} + +MidiOutput::~MidiOutput() +{ + stopBackgroundThread(); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + if (auto* androidMidi = internal.get()) + { + auto* env = getEnv(); + auto messageSize = message.getRawDataSize(); + + LocalRef messageContent (env->NewByteArray (messageSize)); + auto content = messageContent.get(); + + auto* rawBytes = env->GetByteArrayElements (content, nullptr); + std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); + env->ReleaseByteArrayElements (content, rawBytes, 0); + + androidMidi->send (content, (jint) 0, (jint) messageSize); + } +} + +MidiDeviceListConnection MidiDeviceListConnection::make (std::function callback) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (callback)) }; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_linux.cpp similarity index 69% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_linux.cpp index 07f318b..3883bfa 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_linux.cpp @@ -26,38 +26,32 @@ namespace juce #if JUCE_ALSA //============================================================================== -class AlsaClient : public ReferenceCountedObject +class AlsaClient { -public: - AlsaClient() + auto lowerBound (int portId) const { - jassert (instance == nullptr); - - snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); - - if (handle != nullptr) - { - snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); - snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); - clientId = snd_seq_client_id (handle); + const auto comparator = [] (const auto& port, const auto& id) { return port->getPortId() < id; }; + return std::lower_bound (ports.begin(), ports.end(), portId, comparator); + } - // It's good idea to pre-allocate a good number of elements - ports.ensureStorageAllocated (32); - } + auto findPortIterator (int portId) const + { + const auto iter = lowerBound (portId); + return (iter == ports.end() || (*iter)->getPortId() != portId) ? ports.end() : iter; } +public: ~AlsaClient() { - jassert (instance != nullptr); - instance = nullptr; + inputThread.reset(); jassert (activeCallbacks.get() == 0); - if (inputThread) - inputThread->stopThread (3000); - if (handle != nullptr) + { + snd_seq_delete_simple_port (handle, announcementsIn); snd_seq_close (handle); + } } static String getAlsaMidiName() @@ -72,15 +66,12 @@ class AlsaClient : public ReferenceCountedObject #endif } - using Ptr = ReferenceCountedObjectPtr; - //============================================================================== // represents an input or output port of the supplied AlsaClient struct Port { - Port (AlsaClient& c, bool forInput) noexcept - : client (c), isInput (forInput) - {} + explicit Port (bool forInput) noexcept + : isInput (forInput) {} ~Port() { @@ -91,21 +82,21 @@ class AlsaClient : public ReferenceCountedObject else snd_midi_event_free (midiParser); - snd_seq_delete_simple_port (client.get(), portId); + snd_seq_delete_simple_port (client->get(), portId); } } void connectWith (int sourceClient, int sourcePort) const noexcept { if (isInput) - snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort); + snd_seq_connect_from (client->get(), portId, sourceClient, sourcePort); else - snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort); + snd_seq_connect_to (client->get(), portId, sourceClient, sourcePort); } bool isValid() const noexcept { - return client.get() != nullptr && portId >= 0; + return client->get() != nullptr && portId >= 0; } void setupInput (MidiInput* input, MidiInputCallback* cb) @@ -123,15 +114,7 @@ class AlsaClient : public ReferenceCountedObject void enableCallback (bool enable) { - const auto oldValue = callbackEnabled.exchange (enable); - - if (oldValue != enable) - { - if (enable) - client.registerCallback(); - else - client.unregisterCallback(); - } + callbackEnabled = enable; } bool sendMessageNow (const MidiMessage& message) @@ -149,7 +132,7 @@ class AlsaClient : public ReferenceCountedObject auto numBytes = (long) message.getRawDataSize(); auto* data = message.getRawData(); - auto seqHandle = client.get(); + auto seqHandle = client->get(); bool success = true; while (numBytes > 0) @@ -188,7 +171,7 @@ class AlsaClient : public ReferenceCountedObject void createPort (const String& name, bool enableSubscription) { - if (auto seqHandle = client.get()) + if (auto seqHandle = client->get()) { const unsigned int caps = isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) @@ -217,7 +200,7 @@ class AlsaClient : public ReferenceCountedObject const String& getPortName() const { return portName; } private: - AlsaClient& client; + const std::shared_ptr client = AlsaClient::getInstance(); MidiInputCallback* callback = nullptr; snd_midi_event_t* midiParser = nullptr; @@ -230,36 +213,23 @@ class AlsaClient : public ReferenceCountedObject bool isInput = false; }; - static Ptr getInstance() - { - if (instance == nullptr) - instance = new AlsaClient(); - - return instance; - } - - void registerCallback() + static std::shared_ptr getInstance() { - if (inputThread == nullptr) - inputThread.reset (new MidiInputThread (*this)); - - if (++activeCallbacks == 1) - inputThread->startThread(); - } + static std::weak_ptr ptr; - void unregisterCallback() - { - jassert (activeCallbacks.get() > 0); + if (auto locked = ptr.lock()) + return locked; - if (--activeCallbacks == 0 && inputThread->isThreadRunning()) - inputThread->signalThreadShouldExit(); + std::shared_ptr result (new AlsaClient()); + ptr = result; + return result; } void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) { const ScopedLock sl (callbackLock); - if (auto* port = ports[event->dest.port]) + if (auto* port = findPort (event->dest.port)) port->handleIncomingMidiMessage (message); } @@ -267,7 +237,7 @@ class AlsaClient : public ReferenceCountedObject { const ScopedLock sl (callbackLock); - if (auto* port = ports[event->dest.port]) + if (auto* port = findPort (event->dest.port)) port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); } @@ -278,10 +248,13 @@ class AlsaClient : public ReferenceCountedObject { const ScopedLock sl (callbackLock); - auto port = new Port (*this, forInput); + auto port = new Port (forInput); port->createPort (name, enableSubscription); - ports.set (port->getPortId(), port); - incReferenceCount(); + + const auto iter = lowerBound (port->getPortId()); + jassert (iter == ports.end() || port->getPortId() < (*iter)->getPortId()); + ports.insert (iter, rawToUniquePtr (port)); + return port; } @@ -289,31 +262,96 @@ class AlsaClient : public ReferenceCountedObject { const ScopedLock sl (callbackLock); - ports.set (port->getPortId(), nullptr); - decReferenceCount(); + if (const auto iter = findPortIterator (port->getPortId()); iter != ports.end()) + ports.erase (iter); } private: + AlsaClient() + { + snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); + + if (handle != nullptr) + { + snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); + snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); + clientId = snd_seq_client_id (handle); + + // It's good idea to pre-allocate a good number of elements + ports.reserve (32); + + announcementsIn = snd_seq_create_simple_port (handle, + TRANS ("announcements").toRawUTF8(), + SND_SEQ_PORT_CAP_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + + inputThread.emplace (*this); + } + } + + Port* findPort (int portId) + { + if (const auto iter = findPortIterator (portId); iter != ports.end()) + return iter->get(); + + return nullptr; + } + snd_seq_t* handle = nullptr; int clientId = 0; - OwnedArray ports; + int announcementsIn = 0; + std::vector> ports; Atomic activeCallbacks; CriticalSection callbackLock; - static AlsaClient* instance; - //============================================================================== - class MidiInputThread : public Thread + class SequencerThread { public: - MidiInputThread (AlsaClient& c) - : Thread ("JUCE MIDI Input"), client (c) + explicit SequencerThread (AlsaClient& c) + : client (c) + { + } + + ~SequencerThread() noexcept { - jassert (client.get() != nullptr); + shouldStop = true; + thread.join(); } - void run() override + private: + // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread, + // there's a possibility that we'll deadlock in the following scenario: + // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time + // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton + // begins construction. During the constructor, an AlsaClient is created to iterate midi + // ins/outs. + // - The AlsaClient starts a new SequencerThread. If connections are updated, the + // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify() + // while the MidiDeviceListConnectionBroadcaster singleton is still being created. + // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been + // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor + // can't complete until the AlsaClient's destructor has run, which in turn requires the + // SequencerThread to join. + class UpdateNotifier : private AsyncUpdater { + public: + ~UpdateNotifier() override { cancelPendingUpdate(); } + using AsyncUpdater::triggerAsyncUpdate; + + private: + void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); } + }; + + AlsaClient& client; + MidiDataConcatenator concatenator { 2048 }; + std::atomic shouldStop { false }; + UpdateNotifier notifier; + std::thread thread { [this] + { + Thread::setCurrentThreadName ("JUCE MIDI Input"); + auto seqHandle = client.get(); const int maxEventSize = 16 * 1024; @@ -321,17 +359,20 @@ class AlsaClient : public ReferenceCountedObject if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) { - auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); - HeapBlock pfd (numPfds); - snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); + const ScopeGuard freeMidiEvent { [&] { snd_midi_event_free (midiParser); } }; - HeapBlock buffer (maxEventSize); + const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); + std::vector pfd (static_cast (numPfds)); + snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN); - while (! threadShouldExit()) + std::vector buffer (maxEventSize); + + while (! shouldStop) { - if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call + // This timeout shouldn't be too long, so that the program can exit in a timely manner + if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0) { - if (threadShouldExit()) + if (shouldStop) break; do @@ -340,44 +381,60 @@ class AlsaClient : public ReferenceCountedObject if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) { + const ScopeGuard freeInputEvent { [&] { snd_seq_free_event (inputEvent); } }; + + constexpr int systemEvents[] + { + SND_SEQ_EVENT_CLIENT_CHANGE, + SND_SEQ_EVENT_CLIENT_START, + SND_SEQ_EVENT_CLIENT_EXIT, + SND_SEQ_EVENT_PORT_CHANGE, + SND_SEQ_EVENT_PORT_START, + SND_SEQ_EVENT_PORT_EXIT, + SND_SEQ_EVENT_PORT_SUBSCRIBED, + SND_SEQ_EVENT_PORT_UNSUBSCRIBED, + }; + + const auto foundEvent = std::find (std::begin (systemEvents), + std::end (systemEvents), + inputEvent->type); + + if (foundEvent != std::end (systemEvents)) + { + notifier.triggerAsyncUpdate(); + continue; + } + // xxx what about SYSEXes that are too big for the buffer? - auto numBytes = snd_midi_event_decode (midiParser, buffer, - maxEventSize, inputEvent); + const auto numBytes = snd_midi_event_decode (midiParser, + buffer.data(), + maxEventSize, + inputEvent); snd_midi_event_reset_decode (midiParser); - concatenator.pushMidiData (buffer, (int) numBytes, + concatenator.pushMidiData (buffer.data(), (int) numBytes, Time::getMillisecondCounter() * 0.001, inputEvent, client); - - snd_seq_free_event (inputEvent); } } while (snd_seq_event_input_pending (seqHandle, 0) > 0); } } - - snd_midi_event_free (midiParser); } - } - - private: - AlsaClient& client; - MidiDataConcatenator concatenator { 2048 }; + } }; }; - std::unique_ptr inputThread; + std::optional inputThread; }; -AlsaClient* AlsaClient::instance = nullptr; - //============================================================================== static String getFormattedPortIdentifier (int clientId, int portId) { return String (clientId) + "-" + String (portId); } -static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, +static AlsaClient::Port* iterateMidiClient (AlsaClient& client, snd_seq_client_info_t* clientInfo, bool forInput, Array& devices, @@ -385,7 +442,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, { AlsaClient::Port* port = nullptr; - auto seqHandle = client->get(); + auto seqHandle = client.get(); snd_seq_port_info_t* portInfo = nullptr; snd_seq_port_info_alloca (&portInfo); @@ -412,7 +469,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, { if (portID != -1) { - port = client->createPort (portName, forInput, false); + port = client.createPort (portName, forInput, false); jassert (port->isValid()); port->connectWith (sourceClient, portID); break; @@ -450,8 +507,11 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, { if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) { - port = iterateMidiClient (client, clientInfo, forInput, - devices, deviceIdentifierToOpen); + port = iterateMidiClient (*client, + clientInfo, + forInput, + devices, + deviceIdentifierToOpen); if (port != nullptr) break; @@ -659,6 +719,18 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) internal->ptr->sendMessageNow (message); } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + // We capture the AlsaClient instance here to ensure that it remains alive for at least as long + // as the MidiDeviceListConnection. This is necessary because system change messages will only + // be processed when the AlsaClient's SequencerThread is running. + return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()] + { + NullCheckedInvocation::invoke (fn); + }) }; +} + //============================================================================== #else @@ -693,6 +765,12 @@ StringArray MidiOutput::getDevices() int MidiOutput::getDefaultDeviceIndex() { return 0;} std::unique_ptr MidiOutput::openDevice (int) { return {}; } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + #endif } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_windows.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_windows.cpp index e0c6fae..3119769 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Midi_windows.cpp @@ -103,7 +103,7 @@ struct MidiServiceType struct Win32MidiService : public MidiServiceType, private Timer { - Win32MidiService() {} + Win32MidiService() = default; Array getAvailableDevices (bool isInput) override { @@ -1871,6 +1871,10 @@ struct MidiService : public DeletedAtShutdown private: std::unique_ptr internal; + DeviceChangeDetector detector { L"JuceMidiDeviceDetector_", [] + { + MidiDeviceListConnectionBroadcaster::get().notify(); + } }; }; JUCE_IMPLEMENT_SINGLETON (MidiService) @@ -2013,4 +2017,10 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) internal->sendMessageNow (message); } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Oboe_android.cpp similarity index 98% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_Oboe_android.cpp index 194ec07..38152cb 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_Oboe_android.cpp @@ -478,7 +478,7 @@ class OboeAudioIODevice : public AudioIODevice auto nextState = oboe::StreamState::Started; int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond; - auto startResult = stream->requestStart(); + [[maybe_unused]] auto startResult = stream->requestStart(); JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + getOboeString (startResult)); startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos); @@ -751,8 +751,6 @@ class OboeAudioIODevice : public AudioIODevice void start() override { - audioCallbackGuard.set (0); - if (inputStream != nullptr) inputStream->start(); @@ -764,13 +762,10 @@ class OboeAudioIODevice : public AudioIODevice void stop() override { - while (! audioCallbackGuard.compareAndSetBool (1, 0)) - Thread::sleep (1); + const SpinLock::ScopedLockType lock { audioCallbackMutex }; inputStream = nullptr; outputStream = nullptr; - - audioCallbackGuard.set (0); } int getOutputLatencyInSamples() override { return outputLatency; } @@ -788,7 +783,9 @@ class OboeAudioIODevice : public AudioIODevice oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override { - if (audioCallbackGuard.compareAndSetBool (1, 0)) + const SpinLock::ScopedTryLockType lock { audioCallbackMutex }; + + if (lock.isLocked()) { if (stream == nullptr) return oboe::DataCallbackResult::Stop; @@ -797,8 +794,9 @@ class OboeAudioIODevice : public AudioIODevice jassert (stream->getDirection() == oboe::Direction::Output && stream == outputStream->getNativeStream()); // Read input from Oboe - inputStreamSampleBuffer.clear(); - inputStreamNativeBuffer.calloc (static_cast (numInputChannels * bufferSize)); + const auto expandedBufferSize = jmax (inputStreamNativeBuffer.size(), + static_cast (numInputChannels * jmax (bufferSize, numFrames))); + inputStreamNativeBuffer.resize (expandedBufferSize); if (inputStream != nullptr) { @@ -811,17 +809,17 @@ class OboeAudioIODevice : public AudioIODevice return oboe::DataCallbackResult::Continue; } - auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0); + auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.data(), numFrames, 0); if (result) { auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers - ::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(), + ::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.data(), inputStreamSampleBuffer, result.value()); if (! referringDirectlyToOboeData) - OboeAudioIODeviceBufferHelpers::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value()); + OboeAudioIODeviceBufferHelpers::convertFromOboe (inputStreamNativeBuffer.data(), inputStreamSampleBuffer, result.value()); } else { @@ -853,8 +851,6 @@ class OboeAudioIODevice : public AudioIODevice if (isOutputLatencyDetectionSupported) outputLatency = getLatencyFor (*outputStream); - - audioCallbackGuard.set (0); } return oboe::DataCallbackResult::Continue; @@ -944,13 +940,14 @@ class OboeAudioIODevice : public AudioIODevice if (error == oboe::Result::ErrorDisconnected) { - if (streamRestartGuard.compareAndSetBool (1, 0)) + const SpinLock::ScopedTryLockType streamRestartLock { streamRestartMutex }; + + if (streamRestartLock.isLocked()) { // Close, recreate, and start the stream, not much use in current one. // Use default device id, to let the OS pick the best ID (since our was disconnected). - while (! audioCallbackGuard.compareAndSetBool (1, 0)) - Thread::sleep (1); + const SpinLock::ScopedLockType audioCallbackLock { audioCallbackMutex }; outputStream = nullptr; outputStream.reset (new OboeStream (oboe::kUnspecified, @@ -963,18 +960,14 @@ class OboeAudioIODevice : public AudioIODevice this)); outputStream->start(); - - audioCallbackGuard.set (0); - streamRestartGuard.set (0); } } } - HeapBlock inputStreamNativeBuffer; + std::vector inputStreamNativeBuffer; AudioBuffer inputStreamSampleBuffer, outputStreamSampleBuffer; - Atomic audioCallbackGuard { 0 }, - streamRestartGuard { 0 }; + SpinLock audioCallbackMutex, streamRestartMutex; bool isInputLatencyDetectionSupported = false; int inputLatency = -1; diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_OpenSL_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_OpenSL_android.cpp diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp similarity index 96% rename from JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp rename to JuceLibraryCode/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp index 2addd3c..a224e19 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_WASAPI_windows.cpp @@ -1694,13 +1694,11 @@ class WASAPIAudioIODevice : public AudioIODevice, //============================================================================== -class WASAPIAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector +class WASAPIAudioIODeviceType : public AudioIODeviceType { public: - WASAPIAudioIODeviceType (WASAPIDeviceMode mode) + explicit WASAPIAudioIODeviceType (WASAPIDeviceMode mode) : AudioIODeviceType (getDeviceTypename (mode)), - DeviceChangeDetector (L"Windows Audio"), deviceMode (mode) { } @@ -1715,22 +1713,15 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, void scanForDevices() override { hasScanned = true; - - outputDeviceNames.clear(); - inputDeviceNames.clear(); - outputDeviceIds.clear(); - inputDeviceIds.clear(); - - scan (outputDeviceNames, inputDeviceNames, - outputDeviceIds, inputDeviceIds); + devices = scan(); } StringArray getDeviceNames (bool wantInputNames) const override { jassert (hasScanned); // need to call scanForDevices() before doing this - return wantInputNames ? inputDeviceNames - : outputDeviceNames; + return wantInputNames ? devices.inputDeviceNames + : devices.outputDeviceNames; } int getDefaultDeviceIndex (bool /*forInput*/) const override @@ -1744,8 +1735,8 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, jassert (hasScanned); // need to call scanForDevices() before doing this if (auto d = dynamic_cast (device)) - return asInput ? inputDeviceIds.indexOf (d->inputDeviceId) - : outputDeviceIds.indexOf (d->outputDeviceId); + return asInput ? devices.inputDeviceIds .indexOf (d->inputDeviceId) + : devices.outputDeviceIds.indexOf (d->outputDeviceId); return -1; } @@ -1759,16 +1750,16 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, std::unique_ptr device; - auto outputIndex = outputDeviceNames.indexOf (outputDeviceName); - auto inputIndex = inputDeviceNames.indexOf (inputDeviceName); + auto outputIndex = devices.outputDeviceNames.indexOf (outputDeviceName); + auto inputIndex = devices.inputDeviceNames .indexOf (inputDeviceName); if (outputIndex >= 0 || inputIndex >= 0) { device.reset (new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName, getTypeName(), - outputDeviceIds [outputIndex], - inputDeviceIds [inputIndex], + devices.outputDeviceIds[outputIndex], + devices.inputDeviceIds [inputIndex], deviceMode)); if (! device->initialise()) @@ -1779,10 +1770,24 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, } //============================================================================== - StringArray outputDeviceNames, outputDeviceIds; - StringArray inputDeviceNames, inputDeviceIds; + struct Devices + { + StringArray outputDeviceNames, outputDeviceIds; + StringArray inputDeviceNames, inputDeviceIds; + + auto tie() const + { + return std::tie (outputDeviceNames, outputDeviceIds, inputDeviceNames, inputDeviceIds); + } + + bool operator== (const Devices& other) const { return tie() == other.tie(); } + bool operator!= (const Devices& other) const { return tie() != other.tie(); } + }; + + Devices devices; private: + DeviceChangeDetector deviceChangeDetector { L"Windows Audio", [this] { systemDeviceChanged(); } }; WASAPIDeviceMode deviceMode; bool hasScanned = false; ComSmartPtr enumerator; @@ -1791,7 +1796,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, class ChangeNotificationClient : public ComBaseClassHelper { public: - ChangeNotificationClient (WASAPIAudioIODeviceType* d) + explicit ChangeNotificationClient (WASAPIAudioIODeviceType* d) : ComBaseClassHelper (0), device (d) {} JUCE_COMRESULT OnDeviceAdded (LPCWSTR) { return notify(); } @@ -1806,7 +1811,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, HRESULT notify() { if (device != nullptr) - device->triggerAsyncDeviceChangeCallback(); + device->deviceChangeDetector.triggerAsyncDeviceChangeCallback(); return S_OK; } @@ -1840,15 +1845,12 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, } //============================================================================== - void scan (StringArray& outDeviceNames, - StringArray& inDeviceNames, - StringArray& outDeviceIds, - StringArray& inDeviceIds) + Devices scan() { if (enumerator == nullptr) { if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - return; + return {}; notifyClient = new ChangeNotificationClient (this); enumerator->RegisterEndpointNotificationCallback (notifyClient); @@ -1862,7 +1864,9 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) && check (deviceCollection->GetCount (&numDevices)))) - return; + return {}; + + Devices result; for (UINT32 i = 0; i < numDevices; ++i) { @@ -1902,40 +1906,33 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, if (flow == eRender) { const int index = (deviceId == defaultRenderer) ? 0 : -1; - outDeviceIds.insert (index, deviceId); - outDeviceNames.insert (index, name); + result.outputDeviceIds.insert (index, deviceId); + result.outputDeviceNames.insert (index, name); } else if (flow == eCapture) { const int index = (deviceId == defaultCapture) ? 0 : -1; - inDeviceIds.insert (index, deviceId); - inDeviceNames.insert (index, name); + result.inputDeviceIds.insert (index, deviceId); + result.inputDeviceNames.insert (index, name); } } - inDeviceNames.appendNumbersToDuplicates (false, false); - outDeviceNames.appendNumbersToDuplicates (false, false); + result.inputDeviceNames .appendNumbersToDuplicates (false, false); + result.outputDeviceNames.appendNumbersToDuplicates (false, false); + + return result; } //============================================================================== - void systemDeviceChanged() override + void systemDeviceChanged() { - StringArray newOutNames, newInNames, newOutIds, newInIds; - scan (newOutNames, newInNames, newOutIds, newInIds); + const auto newDevices = scan(); - if (newOutNames != outputDeviceNames - || newInNames != inputDeviceNames - || newOutIds != outputDeviceIds - || newInIds != inputDeviceIds) + if (std::exchange (devices, newDevices) != newDevices) { hasScanned = true; - outputDeviceNames = newOutNames; - inputDeviceNames = newInNames; - outputDeviceIds = newOutIds; - inputDeviceIds = newInIds; + callDeviceChangeListeners(); } - - callDeviceChangeListeners(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp deleted file mode 100644 index dbcae83..0000000 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ /dev/null @@ -1,701 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or 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. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -// This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23 -// See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 javaMidiByteCode[] = -{31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220, -69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45, -201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81, -80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204, -156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248, -199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3, -250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200, -6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66, -224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205, -68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101, -192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188, -3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151, -1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191, -1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121, -224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239, -129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53, -224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224, -23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86, -224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4, -136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68, -21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60, -96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24, -184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64, -12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224, -108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216, -11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33, -252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132, -37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128, -29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127, -252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4, -210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216, -88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251, -208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62, -230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151, -159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190, -88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29, -109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163, -40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253, -51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110, -83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164, -197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167, -22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224, -216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117, -192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116, -189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199, -213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164, -159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105, -59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235, -217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221, -64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252, -94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124, -181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91, -243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89, -180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78, -199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79, -168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94, -247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232, -109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116, -187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158, -21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179, -82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159, -77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204, -127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209, -252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207, -117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7, -239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108, -203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251, -13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67, -92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168, -241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183, -49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55, -168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223, -13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247, -253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98, -237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252, -74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101, -230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78, -172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55, -170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81, -197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219, -5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95, -25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56, -227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203, -90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192, -127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114, -105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254, -27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220, -174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71, -137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66, -75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116, -221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105, -7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189, -10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158, -205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5, -196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253, -230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125, -196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68, -154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179, -252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30, -44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74, -79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90, -68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7, -41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161, -247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250, -105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8, -255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93, -66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13, -218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52, -151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9, -255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145, -154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4, -229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157, -220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200, -197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11, -164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218, -106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235, -122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21, -73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219, -210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66, -146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80, -43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211, -249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113, -244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240, -166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160, -208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39, -245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38, -158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253, -158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192, -208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246, -177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235, -81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111, -122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101, -44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3, -34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7, -114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176, -144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207, -231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236, -183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120, -152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96, -94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130, -243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129, -117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245, -172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122, -162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174, -240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245, -61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117, -234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179, -86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160, -171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238, -63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223, -90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238, -25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34, -104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196, -164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186, -169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118, -187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101, -11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47, -206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110, -157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91, -150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245, -125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62, -109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46, -153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185, -81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24, -83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121, -38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217, -95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134, -137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150, -129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73, -144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173, -205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43, -223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215, -241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186, -90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101, -19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153, -30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250, -215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27, -163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130, -249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34, -107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153, -167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127, -103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196, -38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138, -79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199, -199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163, -121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208, -151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163, -163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110, -231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221, -22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251, -233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70, -205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191, -173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251, -226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216, -165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116, -28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194, -76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181, -188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119, -77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190, -192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173, -88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168, -214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29, -200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176, -116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106, -254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219, -157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202, -19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105, -167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77, -146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76, -185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228, -29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198, -167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26, -74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80, -26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229, -198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249, -51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121, -246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199, -70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70, -102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200, -188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193, -216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201, -93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242, -252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175, -243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109, -27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140, -146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176, -68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213, -58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38, -226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184, -186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217, -34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38, -144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115, -200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38, -101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112, -214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28, -217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42, -76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223, -42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126, -208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227, -213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164, -147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205, -194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213, -163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83, -199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250, -77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74, -201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154, -244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127, -11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146, -29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35, -143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33, -46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163, -29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160, -245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102, -188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240, -91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253, -30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105, -137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122, -186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244, -155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137, -5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233, -249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228, -39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49, -177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227, -13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179, -189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94, -238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196, -0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23, -107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161, -223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57, -174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108, -143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240, -229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232, -105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198, -190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56, -124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72, -172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25, -117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118, -88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198, -127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184, -32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148, -46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179, -39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85, -105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48, -220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123, -137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182, -235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64, -244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110, -26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181, -74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15, -102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195, -186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132, -107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157, -74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12, -255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248, -248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0}; - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") - -DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ - METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ - METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ - METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (start, "start", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (close, "close", "()V") \ - METHOD (sendMidi, "sendMidi", "([BII)V") \ - METHOD (getName, "getName", "()Ljava/lang/String;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class MidiInput::Pimpl -{ -public: - Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) - : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), - javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, - (jint) deviceID, (jlong) this))) - { - } - - ~Pimpl() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - bool isOpen() const noexcept - { - return javaMidiDevice != nullptr; - } - - void start() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.start); - } - - void stop() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.stop); - - callback = nullptr; - } - - String getName() const noexcept - { - if (jobject d = javaMidiDevice.get()) - return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); - - return {}; - } - - void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) - { - auto* env = getEnv(); - - jassert (byteArray != nullptr); - auto* data = env->GetByteArrayElements (byteArray, nullptr); - - HeapBlock buffer (static_cast (len)); - std::memcpy (buffer.get(), data + offset, static_cast (len)); - - midiConcatenator.pushMidiData (buffer.get(), - len, static_cast (timestamp) * 1.0e-9, - juceMidiInput, *callback); - - env->ReleaseByteArrayElements (byteArray, data, 0); - } - - static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, - jint offset, jint len, jlong timestamp) - { - auto* myself = reinterpret_cast (host); - - myself->handleMidi (byteArray, offset, len, timestamp); - } - -private: - MidiInput* juceMidiInput; - MidiInputCallback* callback; - MidiDataConcatenator midiConcatenator; - GlobalRef javaMidiDevice; -}; - -//============================================================================== -class MidiOutput::Pimpl -{ -public: - Pimpl (const LocalRef& midiDevice) - : javaMidiDevice (midiDevice) - { - } - - ~Pimpl() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - void send (jbyteArray byteArray, jint offset, jint len) - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, - JuceMidiPort.sendMidi, - byteArray, offset, len); - } - - String getName() const noexcept - { - if (jobject d = javaMidiDevice.get()) - return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); - - return {}; - } - -private: - GlobalRef javaMidiDevice; -}; - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" ) - -DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class AndroidMidiDeviceManager -{ -public: - AndroidMidiDeviceManager() - : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, - JuceMidiSupport.getAndroidMidiDeviceManager, - getAppContext().get()))) - { - } - - Array getDevices (bool input) - { - if (jobject dm = deviceManager.get()) - { - jobjectArray jDeviceNameAndIDs - = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs - : MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); - - // Create a local reference as converting this to a JUCE string will call into JNI - LocalRef localDeviceNameAndIDs (jDeviceNameAndIDs); - - auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); - deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - - Array devices; - - for (int i = 0; i < deviceNameAndIDs.size(); i += 2) - devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); - - return devices; - } - - return {}; - } - - MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) - { - if (auto dm = deviceManager.get()) - { - auto androidMidiInput = std::make_unique (juceMidiInput, deviceID, callback, dm); - - if (androidMidiInput->isOpen()) - return androidMidiInput.release(); - } - - return nullptr; - } - - MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) - { - if (auto dm = deviceManager.get()) - if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) - return new MidiOutput::Pimpl (LocalRef(javaMidiPort)); - - return nullptr; - } - -private: - GlobalRef deviceManager; -}; - -//============================================================================== -Array MidiInput::getAvailableDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - AndroidMidiDeviceManager manager; - return manager.getDevices (true); -} - -MidiDeviceInfo MidiInput::getDefaultDevice() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) -{ - if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) - return {}; - - AndroidMidiDeviceManager manager; - - std::unique_ptr midiInput (new MidiInput ({}, deviceIdentifier)); - - if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) - { - midiInput->internal.reset (port); - midiInput->setName (port->getName()); - - return midiInput; - } - - return {}; -} - -StringArray MidiInput::getDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiInput::getDefaultDeviceIndex() -{ - return (getAndroidSDKVersion() < 23 ? -1 : 0); -} - -std::unique_ptr MidiInput::openDevice (int index, MidiInputCallback* callback) -{ - return openDevice (getAvailableDevices()[index].identifier, callback); -} - -MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) - : deviceInfo (deviceName, deviceIdentifier) -{ -} - -MidiInput::~MidiInput() = default; - -void MidiInput::start() -{ - if (auto* mi = internal.get()) - mi->start(); -} - -void MidiInput::stop() -{ - if (auto* mi = internal.get()) - mi->stop(); -} - -//============================================================================== -Array MidiOutput::getAvailableDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - AndroidMidiDeviceManager manager; - return manager.getDevices (false); -} - -MidiDeviceInfo MidiOutput::getDefaultDevice() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) -{ - if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) - return {}; - - AndroidMidiDeviceManager manager; - - if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) - { - std::unique_ptr midiOutput (new MidiOutput ({}, deviceIdentifier)); - midiOutput->internal.reset (port); - midiOutput->setName (port->getName()); - - return midiOutput; - } - - return {}; -} - -StringArray MidiOutput::getDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return (getAndroidSDKVersion() < 23 ? -1 : 0); -} - -std::unique_ptr MidiOutput::openDevice (int index) -{ - return openDevice (getAvailableDevices()[index].identifier); -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - if (auto* androidMidi = internal.get()) - { - auto* env = getEnv(); - auto messageSize = message.getRawDataSize(); - - LocalRef messageContent (env->NewByteArray (messageSize)); - auto content = messageContent.get(); - - auto* rawBytes = env->GetByteArrayElements (content, nullptr); - std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); - env->ReleaseByteArrayElements (content, rawBytes, 0); - - androidMidi->send (content, (jint) 0, (jint) messageSize); - } -} - -} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h index d6dbabb..c840c79 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h +++ b/JuceLibraryCode/modules/juce_audio_devices/sources/juce_AudioTransportSource.h @@ -93,7 +93,7 @@ class JUCE_API AudioTransportSource : public PositionableAudioSource, */ void setPosition (double newPosition); - /** Returns the position that the next data block will be read from + /** Returns the position that the next data block will be read from. This is a time in seconds. */ double getCurrentPosition() const; diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index 667a998..2e87e7e 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -25,8 +25,8 @@ #if JUCE_MAC || JUCE_IOS -#include -#include +#include +#include namespace juce { diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp index 6dddbc0..c087ae5 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp @@ -111,15 +111,16 @@ namespace FlacNamespace #endif JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", - "-Wshadow", "-Wdeprecated-register", - "-Wswitch-enum", - "-Wswitch-default", + "-Wfloat-equal", "-Wimplicit-fallthrough", - "-Wzero-as-null-pointer-constant", - "-Wsign-conversion", + "-Wlanguage-extension-token", "-Wredundant-decls", - "-Wlanguage-extension-token") + "-Wshadow", + "-Wsign-conversion", + "-Wswitch-default", + "-Wswitch-enum", + "-Wzero-as-null-pointer-constant") #if JUCE_INTEL #if JUCE_32BIT diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp index 98b37f8..0a4f70f 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp @@ -37,20 +37,21 @@ namespace OggVorbisNamespace #if JUCE_INCLUDE_OGGVORBIS_CODE || ! defined (JUCE_INCLUDE_OGGVORBIS_CODE) JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4305 4189 4706 4995 4365 4456 4457 4459 6297 6011 6001 6308 6255 6386 6385 6246 6387 6263 6262 28182) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", - "-Wshadow", - "-Wfloat-conversion", - "-Wdeprecated-register", + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-align", + "-Wconversion", "-Wdeprecated-declarations", - "-Wswitch-enum", - "-Wzero-as-null-pointer-constant", - "-Wsign-conversion", - "-Wswitch-default", - "-Wredundant-decls", + "-Wdeprecated-register", + "-Wfloat-conversion", + "-Wfloat-equal", + "-Wmaybe-uninitialized", "-Wmisleading-indentation", "-Wmissing-prototypes", - "-Wcast-align", - "-Wmaybe-uninitialized") + "-Wredundant-decls", + "-Wshadow", + "-Wsign-conversion", + "-Wswitch-default", + "-Wswitch-enum", + "-Wzero-as-null-pointer-constant") JUCE_BEGIN_NO_SANITIZE ("undefined") #include "oggvorbis/vorbisenc.h" diff --git a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index d1c1c20..553072d 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -948,7 +948,7 @@ namespace WavFileHelpers } }; - //============================================================================= + //============================================================================== namespace IXMLChunk { static const std::unordered_set aswgMetadataKeys diff --git a/JuceLibraryCode/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp b/JuceLibraryCode/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp index 2e70f2f..20d3ec4 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/format/juce_ARAAudioReaders.cpp @@ -67,7 +67,7 @@ void ARAAudioSourceReader::willUpdateAudioSourceProperties (ARAAudioSource* audi ARAAudioSource::PropertiesPtr newProperties) { if (audioSource->getSampleCount() != newProperties->sampleCount - || audioSource->getSampleRate() != newProperties->sampleRate + || ! exactlyEqual (audioSource->getSampleRate(), newProperties->sampleRate) || audioSource->getChannelCount() != newProperties->channelCount) { invalidate(); @@ -277,10 +277,10 @@ void ARAPlaybackRegionReader::willUpdatePlaybackRegionProperties (ARAPlaybackReg { jassert (ARA::contains (playbackRenderer->getPlaybackRegions(), playbackRegion)); - if ((playbackRegion->getStartInAudioModificationTime() != newProperties->startInModificationTime) - || (playbackRegion->getDurationInAudioModificationTime() != newProperties->durationInModificationTime) - || (playbackRegion->getStartInPlaybackTime() != newProperties->startInPlaybackTime) - || (playbackRegion->getDurationInPlaybackTime() != newProperties->durationInPlaybackTime) + if ((! exactlyEqual (playbackRegion->getStartInAudioModificationTime(), newProperties->startInModificationTime)) + || ! exactlyEqual (playbackRegion->getDurationInAudioModificationTime(), newProperties->durationInModificationTime) + || ! exactlyEqual (playbackRegion->getStartInPlaybackTime(), newProperties->startInPlaybackTime) + || ! exactlyEqual (playbackRegion->getDurationInPlaybackTime(), newProperties->durationInPlaybackTime) || (playbackRegion->isTimestretchEnabled() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretch) != 0)) || (playbackRegion->isTimeStretchReflectingTempo() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationTimestretchReflectingTempo) != 0)) || (playbackRegion->hasContentBasedFadeAtHead() != ((newProperties->transformationFlags & ARA::kARAPlaybackTransformationContentBasedFadeAtHead) != 0)) diff --git a/JuceLibraryCode/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp b/JuceLibraryCode/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp index 299ebf9..2a0d339 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp @@ -81,8 +81,14 @@ void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& i } else { - reader->read (info.buffer, info.startSample, - info.numSamples, start, true, true); + const auto samplesToRead = jlimit (int64{}, + (int64) info.numSamples, + reader->lengthInSamples - start); + + reader->read (info.buffer, info.startSample, (int) samplesToRead, start, true, true); + info.buffer->clear ((int) (info.startSample + samplesToRead), + (int) (info.numSamples - samplesToRead)); + nextPlayPos += info.numSamples; } } diff --git a/JuceLibraryCode/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp b/JuceLibraryCode/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp index eb2e77a..f357004 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp +++ b/JuceLibraryCode/modules/juce_audio_formats/format/juce_BufferingAudioFormatReader.cpp @@ -176,26 +176,6 @@ bool BufferingAudioReader::readNextBufferChunk() //============================================================================== #if JUCE_UNIT_TESTS -static bool operator== (const AudioBuffer& a, const AudioBuffer& b) -{ - if (a.getNumChannels() != b.getNumChannels() || a.getNumSamples() != b.getNumSamples()) - return false; - - for (int channel = 0; channel < a.getNumChannels(); ++channel) - { - auto* aPtr = a.getReadPointer (channel); - auto* bPtr = b.getReadPointer (channel); - - if (std::vector (aPtr, aPtr + a.getNumSamples()) - != std::vector (bPtr, bPtr + b.getNumSamples())) - { - return false; - } - } - - return true; -} - static bool isSilent (const AudioBuffer& b) { for (int channel = 0; channel < b.getNumChannels(); ++channel) @@ -207,15 +187,16 @@ static bool isSilent (const AudioBuffer& b) struct TestAudioFormatReader : public AudioFormatReader { - explicit TestAudioFormatReader (AudioBuffer& b) + explicit TestAudioFormatReader (const AudioBuffer* b) : AudioFormatReader (nullptr, {}), buffer (b) { + jassert (buffer != nullptr); sampleRate = 44100.0f; bitsPerSample = 32; usesFloatingPointData = true; - lengthInSamples = buffer.getNumSamples(); - numChannels = (unsigned int) buffer.getNumChannels(); + lengthInSamples = buffer->getNumSamples(); + numChannels = (unsigned int) buffer->getNumChannels(); } bool readSamples (int* const* destChannels, int numDestChannels, int startOffsetInDestBuffer, @@ -234,7 +215,7 @@ struct TestAudioFormatReader : public AudioFormatReader dest += startOffsetInDestBuffer; if (j < (int) numChannels) - FloatVectorOperations::copy (dest, buffer.getReadPointer (j, (int) startSampleInFile), numSamples); + FloatVectorOperations::copy (dest, buffer->getReadPointer (j, (int) startSampleInFile), numSamples); else FloatVectorOperations::clear (dest, numSamples); } @@ -243,9 +224,20 @@ struct TestAudioFormatReader : public AudioFormatReader return true; } - const AudioBuffer& buffer; + const AudioBuffer* buffer; }; +static AudioBuffer generateTestBuffer (Random& random, int bufferSize) +{ + AudioBuffer buffer { 2, bufferSize }; + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) + for (int sample = 0; sample < buffer.getNumSamples(); ++sample) + buffer.setSample (channel, sample, random.nextFloat()); + + return buffer; +} + class BufferingAudioReaderTests : public UnitTest { public: @@ -258,44 +250,54 @@ class BufferingAudioReaderTests : public UnitTest beginTest ("Timeout"); { - struct BlockingReader : public AudioFormatReader + struct BlockingReader : public TestAudioFormatReader { - BlockingReader() - : AudioFormatReader (nullptr, {}) + explicit BlockingReader (const AudioBuffer* b) + : TestAudioFormatReader (b) { - sampleRate = 44100.0f; - bitsPerSample = 32; - usesFloatingPointData = true; - lengthInSamples = 1024; - numChannels = 2; } - bool readSamples (int* const*, int, int, int64, int) override + bool readSamples (int* const* destChannels, + int numDestChannels, + int startOffsetInDestBuffer, + int64 startSampleInFile, + int numSamples) override { Thread::sleep (100); - return true; + return TestAudioFormatReader::readSamples (destChannels, numDestChannels, startOffsetInDestBuffer, startSampleInFile, numSamples); } }; - BufferingAudioReader bufferingReader (new BlockingReader(), timeSlice, 64); + Random random { getRandom() }; + + const auto blockingBuffer = generateTestBuffer (random, 1024); + expect (! isSilent (blockingBuffer)); + + BufferingAudioReader bufferingReader (new BlockingReader (&blockingBuffer), timeSlice, 64); bufferingReader.setReadTimeout (10); - AudioBuffer readBuffer { 2, 1024 }; + const auto originalBuffer = generateTestBuffer (random, 1024); + expect (! isSilent (originalBuffer)); + expect (originalBuffer != blockingBuffer); - readBuffer.clear(); - read (bufferingReader, readBuffer); + auto readBuffer = originalBuffer; + expect (readBuffer == originalBuffer); + read (bufferingReader, readBuffer); + expect (readBuffer != originalBuffer); expect (isSilent (readBuffer)); } beginTest ("Read samples"); { + Random random { getRandom() }; + for (auto i = 4; i < 18; ++i) { const auto backgroundBufferSize = 1 << i; - auto buffer = generateTestBuffer (backgroundBufferSize); + const auto buffer = generateTestBuffer (random, backgroundBufferSize); - BufferingAudioReader bufferingReader (new TestAudioFormatReader (buffer), timeSlice, backgroundBufferSize); + BufferingAudioReader bufferingReader (new TestAudioFormatReader (&buffer), timeSlice, backgroundBufferSize); bufferingReader.setReadTimeout (-1); AudioBuffer readBuffer { buffer.getNumChannels(), buffer.getNumSamples() }; @@ -307,19 +309,6 @@ class BufferingAudioReaderTests : public UnitTest } private: - AudioBuffer generateTestBuffer (int bufferSize) const - { - auto random = getRandom(); - - AudioBuffer buffer { 2, random.nextInt ({ bufferSize, bufferSize * 10 }) }; - - for (int channel = 0; channel < buffer.getNumChannels(); ++channel) - for (int sample = 0; sample < buffer.getNumSamples(); ++sample) - buffer.setSample (channel, sample, random.nextFloat()); - - return buffer; - } - void read (BufferingAudioReader& reader, AudioBuffer& readBuffer) { constexpr int blockSize = 1024; diff --git a/JuceLibraryCode/modules/juce_audio_formats/juce_audio_formats.h b/JuceLibraryCode/modules/juce_audio_formats/juce_audio_formats.h index 3d26658..7050616 100644 --- a/JuceLibraryCode/modules/juce_audio_formats/juce_audio_formats.h +++ b/JuceLibraryCode/modules/juce_audio_formats/juce_audio_formats.h @@ -35,7 +35,7 @@ ID: juce_audio_formats vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE audio file format codecs description: Classes for reading and writing various audio file formats. website: http://www.juce.com/juce diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp deleted file mode 100644 index 020ce62..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ /dev/null @@ -1,2598 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include "../utility/juce_CheckSettingMacros.h" - -#if JucePlugin_Build_AAX && (JUCE_INCLUDED_AAX_IN_MM || defined (_WIN32) || defined (_WIN64)) - -#include "../utility/juce_IncludeSystemHeaders.h" -#include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_WindowsHooks.h" - -#include - -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127 4512 4996) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor", - "-Wsign-conversion", - "-Wextra-semi", - "-Wshift-sign-overflow", - "-Wpragma-pack", - "-Wzero-as-null-pointer-constant", - "-Winconsistent-missing-destructor-override", - "-Wfour-char-constants", - "-Wtautological-overlap-compare", - "-Wdeprecated-declarations") - -#include - -static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p3p0_REVISION, "JUCE requires AAX SDK version 2.3.0 or higher"); - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined (AAX_SDK_2p3p1_REVISION) && AAX_SDK_2p3p1_REVISION <= AAX_SDK_CURRENT_REVISION - #include - #include -#endif - -#if defined (AAX_SDK_2p4p0_REVISION) && AAX_SDK_2p4p0_REVISION <= AAX_SDK_CURRENT_REVISION - #define JUCE_AAX_HAS_TRANSPORT_NOTIFICATION 1 -#else - #define JUCE_AAX_HAS_TRANSPORT_NOTIFICATION 0 -#endif - -#if JUCE_AAX_HAS_TRANSPORT_NOTIFICATION - #include -#endif - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants") - -#if JUCE_WINDOWS - #ifndef JucePlugin_AAXLibs_path - #error "You need to define the JucePlugin_AAXLibs_path macro. (This is best done via the Projucer)" - #endif - - #if JUCE_64BIT - #define JUCE_AAX_LIB "AAXLibrary_x64" - #else - #define JUCE_AAX_LIB "AAXLibrary" - #endif - - #if JUCE_DEBUG - #define JUCE_AAX_LIB_PATH "\\Debug\\" - #define JUCE_AAX_LIB_SUFFIX "_D" - #else - #define JUCE_AAX_LIB_PATH "\\Release\\" - #define JUCE_AAX_LIB_SUFFIX "" - #endif - - #pragma comment(lib, JucePlugin_AAXLibs_path JUCE_AAX_LIB_PATH JUCE_AAX_LIB JUCE_AAX_LIB_SUFFIX ".lib") -#endif - -#undef check - -#include "juce_AAX_Modifier_Injector.h" - -using namespace juce; - -#ifndef JucePlugin_AAX_Chunk_Identifier - #define JucePlugin_AAX_Chunk_Identifier 'juce' -#endif - -const int32_t juceChunkType = JucePlugin_AAX_Chunk_Identifier; - -//============================================================================== -namespace AAXClasses -{ - static int32 getAAXParamHash (AAX_CParamID paramID) noexcept - { - int32 result = 0; - - while (*paramID != 0) - result = (31 * result) + (*paramID++); - - return result; - } - - static void check (AAX_Result result) - { - jassertquiet (result == AAX_SUCCESS); - } - - // maps a channel index of an AAX format to an index of a juce format - struct AAXChannelStreamOrder - { - AAX_EStemFormat aaxStemFormat; - AudioChannelSet::ChannelType speakerOrder[10]; - }; - - static AAX_EStemFormat stemFormatForAmbisonicOrder (int order) - { - switch (order) - { - case 1: return AAX_eStemFormat_Ambi_1_ACN; - case 2: return AAX_eStemFormat_Ambi_2_ACN; - case 3: return AAX_eStemFormat_Ambi_3_ACN; - default: break; - } - - return AAX_eStemFormat_INT32_MAX; - } - - static AAXChannelStreamOrder aaxChannelOrder[] = - { - { AAX_eStemFormat_Mono, { AudioChannelSet::centre, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_Stereo, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_LCR, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_LCRS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::centreSurround, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_Quad, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_5_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_5_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, - AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_6_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_6_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, - AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, - AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, - AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_0_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::unknown } }, - - { AAX_eStemFormat_7_1_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, - AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, - - { AAX_eStemFormat_None, { AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, - AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown, AudioChannelSet::unknown } }, - }; - - static AAX_EStemFormat aaxFormats[] = - { - AAX_eStemFormat_Mono, - AAX_eStemFormat_Stereo, - AAX_eStemFormat_LCR, - AAX_eStemFormat_LCRS, - AAX_eStemFormat_Quad, - AAX_eStemFormat_5_0, - AAX_eStemFormat_5_1, - AAX_eStemFormat_6_0, - AAX_eStemFormat_6_1, - AAX_eStemFormat_7_0_SDDS, - AAX_eStemFormat_7_1_SDDS, - AAX_eStemFormat_7_0_DTS, - AAX_eStemFormat_7_1_DTS, - AAX_eStemFormat_7_0_2, - AAX_eStemFormat_7_1_2, - AAX_eStemFormat_Ambi_1_ACN, - AAX_eStemFormat_Ambi_2_ACN, - AAX_eStemFormat_Ambi_3_ACN - }; - - static AAX_EStemFormat getFormatForAudioChannelSet (const AudioChannelSet& set, bool ignoreLayout) noexcept - { - // if the plug-in ignores layout, it is ok to convert between formats only by their numchannnels - if (ignoreLayout) - { - auto numChannels = set.size(); - - switch (numChannels) - { - case 0: return AAX_eStemFormat_None; - case 1: return AAX_eStemFormat_Mono; - case 2: return AAX_eStemFormat_Stereo; - case 3: return AAX_eStemFormat_LCR; - case 4: return AAX_eStemFormat_Quad; - case 5: return AAX_eStemFormat_5_0; - case 6: return AAX_eStemFormat_5_1; - case 7: return AAX_eStemFormat_7_0_DTS; - case 8: return AAX_eStemFormat_7_1_DTS; - case 9: return AAX_eStemFormat_7_0_2; - case 10: return AAX_eStemFormat_7_1_2; - default: break; - } - - // check for ambisonics support - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); - - if (static_cast (ambisonicOrder) == sqrtMinusOne) - return stemFormatForAmbisonicOrder (ambisonicOrder); - - return AAX_eStemFormat_INT32_MAX; - } - - if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; - if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; - if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; - if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; - if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; - if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; - if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; - if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; - if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; - if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; - if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; - if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; - if (set == AudioChannelSet::create7point0SDDS()) return AAX_eStemFormat_7_0_SDDS; - if (set == AudioChannelSet::create7point1SDDS()) return AAX_eStemFormat_7_1_SDDS; - if (set == AudioChannelSet::create7point0point2()) return AAX_eStemFormat_7_0_2; - if (set == AudioChannelSet::create7point1point2()) return AAX_eStemFormat_7_1_2; - - auto order = set.getAmbisonicOrder(); - if (order >= 0) - return stemFormatForAmbisonicOrder (order); - - return AAX_eStemFormat_INT32_MAX; - } - - static AudioChannelSet channelSetFromStemFormat (AAX_EStemFormat format, bool ignoreLayout) noexcept - { - if (! ignoreLayout) - { - switch (format) - { - case AAX_eStemFormat_None: return AudioChannelSet::disabled(); - case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); - case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); - case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); - case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); - case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); - case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); - case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); - case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); - case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); - case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::create7point0SDDS(); - case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); - case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::create7point1SDDS(); - case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); - case AAX_eStemFormat_7_0_2: return AudioChannelSet::create7point0point2(); - case AAX_eStemFormat_7_1_2: return AudioChannelSet::create7point1point2(); - case AAX_eStemFormat_Ambi_1_ACN: return AudioChannelSet::ambisonic (1); - case AAX_eStemFormat_Ambi_2_ACN: return AudioChannelSet::ambisonic (2); - case AAX_eStemFormat_Ambi_3_ACN: return AudioChannelSet::ambisonic (3); - case AAX_eStemFormat_Reserved_1: - case AAX_eStemFormat_Reserved_2: - case AAX_eStemFormat_Reserved_3: - case AAX_eStemFormatNum: - case AAX_eStemFormat_Any: - case AAX_eStemFormat_INT32_MAX: - default: return AudioChannelSet::disabled(); - } - } - - return AudioChannelSet::discreteChannels (jmax (0, static_cast (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); - } - - static AAX_EMeterType getMeterTypeForCategory (AudioProcessorParameter::Category category) - { - switch (category) - { - case AudioProcessorParameter::inputMeter: return AAX_eMeterType_Input; - case AudioProcessorParameter::outputMeter: return AAX_eMeterType_Output; - case AudioProcessorParameter::compressorLimiterGainReductionMeter: return AAX_eMeterType_CLGain; - case AudioProcessorParameter::expanderGateGainReductionMeter: return AAX_eMeterType_EGGain; - case AudioProcessorParameter::analysisMeter: return AAX_eMeterType_Analysis; - case AudioProcessorParameter::genericParameter: - case AudioProcessorParameter::inputGain: - case AudioProcessorParameter::outputGain: - case AudioProcessorParameter::otherMeter: - default: return AAX_eMeterType_Other; - } - } - - static Colour getColourFromHighlightEnum (AAX_EHighlightColor colour) noexcept - { - switch (colour) - { - case AAX_eHighlightColor_Red: return Colours::red; - case AAX_eHighlightColor_Blue: return Colours::blue; - case AAX_eHighlightColor_Green: return Colours::green; - case AAX_eHighlightColor_Yellow: return Colours::yellow; - case AAX_eHighlightColor_Num: - default: jassertfalse; break; - } - - return Colours::black; - } - - static int juceChannelIndexToAax (int juceIndex, const AudioChannelSet& channelSet) - { - auto isAmbisonic = (channelSet.getAmbisonicOrder() >= 0); - auto currentLayout = getFormatForAudioChannelSet (channelSet, false); - int layoutIndex; - - if (isAmbisonic && currentLayout != AAX_eStemFormat_INT32_MAX) - return juceIndex; - - for (layoutIndex = 0; aaxChannelOrder[layoutIndex].aaxStemFormat != currentLayout; ++layoutIndex) - if (aaxChannelOrder[layoutIndex].aaxStemFormat == 0) return juceIndex; - - auto& channelOrder = aaxChannelOrder[layoutIndex]; - auto channelType = channelSet.getTypeOfChannel (static_cast (juceIndex)); - auto numSpeakers = numElementsInArray (channelOrder.speakerOrder); - - for (int i = 0; i < numSpeakers && channelOrder.speakerOrder[i] != 0; ++i) - if (channelOrder.speakerOrder[i] == channelType) - return i; - - return juceIndex; - } - - //============================================================================== - class JuceAAX_Processor; - - struct PluginInstanceInfo - { - PluginInstanceInfo (JuceAAX_Processor& p) : parameters (p) {} - - JuceAAX_Processor& parameters; - - JUCE_DECLARE_NON_COPYABLE (PluginInstanceInfo) - }; - - //============================================================================== - struct JUCEAlgorithmContext - { - float** inputChannels; - float** outputChannels; - int32_t* bufferSize; - int32_t* bypass; - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeIn; - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeOut; - #endif - - PluginInstanceInfo* pluginInstance; - int32_t* isPrepared; - float* const* meterTapBuffers; - int32_t* sideChainBuffers; - }; - - struct JUCEAlgorithmIDs - { - enum - { - inputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, inputChannels), - outputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, outputChannels), - bufferSize = AAX_FIELD_INDEX (JUCEAlgorithmContext, bufferSize), - bypass = AAX_FIELD_INDEX (JUCEAlgorithmContext, bypass), - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - midiNodeIn = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeIn), - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - midiNodeOut = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeOut), - #endif - - pluginInstance = AAX_FIELD_INDEX (JUCEAlgorithmContext, pluginInstance), - preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared), - - meterTapBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, meterTapBuffers), - - sideChainBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, sideChainBuffers) - }; - }; - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeIn; } - #else - static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext&) noexcept { return nullptr; } - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - AAX_IMIDINode* midiNodeOut; - static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeOut; } - #else - static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext&) noexcept { return nullptr; } - #endif - - //============================================================================== - class JuceAAX_Processor; - - class JuceAAX_GUI : public AAX_CEffectGUI, - public ModifierKeyProvider - { - public: - JuceAAX_GUI() = default; - ~JuceAAX_GUI() override { DeleteViewContainer(); } - - static AAX_IEffectGUI* AAX_CALLBACK Create() { return new JuceAAX_GUI(); } - - void CreateViewContents() override; - - void CreateViewContainer() override - { - CreateViewContents(); - - if (void* nativeViewToAttachTo = GetViewContainerPtr()) - { - #if JUCE_MAC - if (GetViewContainerType() == AAX_eViewContainer_Type_NSView) - #else - if (GetViewContainerType() == AAX_eViewContainer_Type_HWND) - #endif - { - component->setVisible (true); - component->addToDesktop (0, nativeViewToAttachTo); - - if (ModifierKeyReceiver* modReceiver = dynamic_cast (component->getPeer())) - modReceiver->setModifierKeyProvider (this); - } - } - } - - void DeleteViewContainer() override - { - if (component != nullptr) - { - JUCE_AUTORELEASEPOOL - { - if (auto* modReceiver = dynamic_cast (component->getPeer())) - modReceiver->removeModifierKeyProvider(); - - component->removeFromDesktop(); - component = nullptr; - } - } - } - - AAX_Result GetViewSize (AAX_Point* viewSize) const override - { - if (component != nullptr) - { - *viewSize = convertToHostBounds ({ (float) component->getHeight(), - (float) component->getWidth() }); - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; - } - - AAX_Result ParameterUpdated (AAX_CParamID) override - { - return AAX_SUCCESS; - } - - AAX_Result SetControlHighlightInfo (AAX_CParamID paramID, AAX_CBoolean isHighlighted, AAX_EHighlightColor colour) override - { - if (component != nullptr && component->pluginEditor != nullptr) - { - auto index = getParamIndexFromID (paramID); - - if (index >= 0) - { - AudioProcessorEditor::ParameterControlHighlightInfo info; - info.parameterIndex = index; - info.isHighlighted = (isHighlighted != 0); - info.suggestedColour = getColourFromHighlightEnum (colour); - - component->pluginEditor->setControlHighlight (info); - } - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; - } - - int getWin32Modifiers() const override - { - int modifierFlags = 0; - - if (auto* viewContainer = GetViewContainer()) - { - uint32 aaxViewMods = 0; - const_cast (viewContainer)->GetModifiers (&aaxViewMods); - - if ((aaxViewMods & AAX_eModifiers_Shift) != 0) modifierFlags |= ModifierKeys::shiftModifier; - if ((aaxViewMods & AAX_eModifiers_Alt ) != 0) modifierFlags |= ModifierKeys::altModifier; - } - - return modifierFlags; - } - - private: - //============================================================================== - int getParamIndexFromID (AAX_CParamID paramID) const noexcept; - AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept; - - //============================================================================== - static AAX_Point convertToHostBounds (AAX_Point pluginSize) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginSize; - - return { pluginSize.vert * desktopScale, - pluginSize.horz * desktopScale }; - } - - //============================================================================== - struct ContentWrapperComponent : public Component - { - ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor& plugin) - : owner (gui) - { - setOpaque (true); - setBroughtToFrontOnMouseClick (true); - - pluginEditor.reset (plugin.createEditorIfNeeded()); - addAndMakeVisible (pluginEditor.get()); - - if (pluginEditor != nullptr) - { - lastValidSize = pluginEditor->getLocalBounds(); - setBounds (lastValidSize); - pluginEditor->addMouseListener (this, true); - } - } - - ~ContentWrapperComponent() override - { - if (pluginEditor != nullptr) - { - PopupMenu::dismissAllActiveMenus(); - pluginEditor->removeMouseListener (this); - pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); - } - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - template - void callMouseMethod (const MouseEvent& e, MethodType method) - { - if (auto* vc = owner.GetViewContainer()) - { - auto parameterIndex = pluginEditor->getControlParameterIndex (*e.eventComponent); - - if (auto aaxParamID = owner.getAAXParamIDFromJuceIndex (parameterIndex)) - { - uint32_t mods = 0; - vc->GetModifiers (&mods); - - (vc->*method) (aaxParamID, mods); - } - } - } - - void mouseDown (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDown); } - void mouseUp (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseUp); } - void mouseDrag (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDrag); } - - void parentSizeChanged() override - { - resizeHostWindow(); - - if (pluginEditor != nullptr) - pluginEditor->repaint(); - } - - void childBoundsChanged (Component*) override - { - if (resizeHostWindow()) - { - setSize (pluginEditor->getWidth(), pluginEditor->getHeight()); - lastValidSize = getBounds(); - } - else - { - pluginEditor->setBoundsConstrained (pluginEditor->getBounds().withSize (lastValidSize.getWidth(), - lastValidSize.getHeight())); - } - } - - bool resizeHostWindow() - { - if (pluginEditor != nullptr) - { - auto newSize = convertToHostBounds ({ (float) pluginEditor->getHeight(), - (float) pluginEditor->getWidth() }); - - return owner.GetViewContainer()->SetViewSize (newSize) == AAX_SUCCESS; - } - - return false; - } - - std::unique_ptr pluginEditor; - JuceAAX_GUI& owner; - - #if JUCE_WINDOWS - WindowsHooks hooks; - #endif - juce::Rectangle lastValidSize; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) - }; - - std::unique_ptr component; - ScopedJuceInitialiser_GUI libraryInitialiser; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAAX_GUI) - }; - - // Copied here, because not all versions of the AAX SDK define all of these values - enum JUCE_AAX_EFrameRate : std::underlying_type_t - { - JUCE_AAX_eFrameRate_Undeclared = 0, - JUCE_AAX_eFrameRate_24Frame = 1, - JUCE_AAX_eFrameRate_25Frame = 2, - JUCE_AAX_eFrameRate_2997NonDrop = 3, - JUCE_AAX_eFrameRate_2997DropFrame = 4, - JUCE_AAX_eFrameRate_30NonDrop = 5, - JUCE_AAX_eFrameRate_30DropFrame = 6, - JUCE_AAX_eFrameRate_23976 = 7, - JUCE_AAX_eFrameRate_47952 = 8, - JUCE_AAX_eFrameRate_48Frame = 9, - JUCE_AAX_eFrameRate_50Frame = 10, - JUCE_AAX_eFrameRate_5994NonDrop = 11, - JUCE_AAX_eFrameRate_5994DropFrame = 12, - JUCE_AAX_eFrameRate_60NonDrop = 13, - JUCE_AAX_eFrameRate_60DropFrame = 14, - JUCE_AAX_eFrameRate_100Frame = 15, - JUCE_AAX_eFrameRate_11988NonDrop = 16, - JUCE_AAX_eFrameRate_11988DropFrame = 17, - JUCE_AAX_eFrameRate_120NonDrop = 18, - JUCE_AAX_eFrameRate_120DropFrame = 19 - }; - - static void AAX_CALLBACK algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); - - static Array activeProcessors; - - //============================================================================== - class JuceAAX_Processor : public AAX_CEffectParameters, - public juce::AudioPlayHead, - public AudioProcessorListener, - private AsyncUpdater - { - public: - JuceAAX_Processor() - : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_AAX)) - { - inParameterChangedCallback = false; - - pluginInstance->setPlayHead (this); - pluginInstance->addListener (this); - - rebuildChannelMapArrays(); - - AAX_CEffectParameters::GetNumberOfChunks (&juceChunkIndex); - activeProcessors.add (this); - } - - ~JuceAAX_Processor() override - { - activeProcessors.removeAllInstancesOf (this); - } - - static AAX_CEffectParameters* AAX_CALLBACK Create() - { - if (PluginHostType::jucePlugInIsRunningInAudioSuiteFn == nullptr) - { - PluginHostType::jucePlugInIsRunningInAudioSuiteFn = [] (AudioProcessor& processor) - { - for (auto* p : activeProcessors) - if (&p->getPluginInstance() == &processor) - return p->isInAudioSuite(); - - return false; - }; - } - - return new JuceAAX_Processor(); - } - - AAX_Result Uninitialize() override - { - cancelPendingUpdate(); - juceParameters.clear(); - - if (isPrepared && pluginInstance != nullptr) - { - isPrepared = false; - processingSidechainChange = false; - - pluginInstance->releaseResources(); - } - - return AAX_CEffectParameters::Uninitialize(); - } - - AAX_Result EffectInit() override - { - cancelPendingUpdate(); - check (Controller()->GetSampleRate (&sampleRate)); - processingSidechainChange = false; - auto err = preparePlugin(); - - if (err != AAX_SUCCESS) - return err; - - addAudioProcessorParameters(); - - return AAX_SUCCESS; - } - - AAX_Result GetNumberOfChunks (int32_t* numChunks) const override - { - // The juceChunk is the last chunk. - *numChunks = juceChunkIndex + 1; - return AAX_SUCCESS; - } - - AAX_Result GetChunkIDFromIndex (int32_t index, AAX_CTypeID* chunkID) const override - { - if (index != juceChunkIndex) - return AAX_CEffectParameters::GetChunkIDFromIndex (index, chunkID); - - *chunkID = juceChunkType; - return AAX_SUCCESS; - } - - AAX_Result GetChunkSize (AAX_CTypeID chunkID, uint32_t* oSize) const override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::GetChunkSize (chunkID, oSize); - - auto& chunkMemoryBlock = perThreadFilterData.get(); - - chunkMemoryBlock.data.reset(); - pluginInstance->getStateInformation (chunkMemoryBlock.data); - chunkMemoryBlock.isValid = true; - - *oSize = (uint32_t) chunkMemoryBlock.data.getSize(); - return AAX_SUCCESS; - } - - AAX_Result GetChunk (AAX_CTypeID chunkID, AAX_SPlugInChunk* oChunk) const override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::GetChunk (chunkID, oChunk); - - auto& chunkMemoryBlock = perThreadFilterData.get(); - - if (! chunkMemoryBlock.isValid) - return 20700; // AAX_ERROR_PLUGIN_API_INVALID_THREAD - - oChunk->fSize = (int32_t) chunkMemoryBlock.data.getSize(); - chunkMemoryBlock.data.copyTo (oChunk->fData, 0, chunkMemoryBlock.data.getSize()); - chunkMemoryBlock.isValid = false; - - return AAX_SUCCESS; - } - - AAX_Result SetChunk (AAX_CTypeID chunkID, const AAX_SPlugInChunk* chunk) override - { - if (chunkID != juceChunkType) - return AAX_CEffectParameters::SetChunk (chunkID, chunk); - - pluginInstance->setStateInformation ((void*) chunk->fData, chunk->fSize); - - // Notify Pro Tools that the parameters were updated. - // Without it a bug happens in these circumstances: - // * A preset is saved with the RTAS version of the plugin (".tfx" preset format). - // * The preset is loaded in PT 10 using the AAX version. - // * The session is then saved, and closed. - // * The saved session is loaded, but acting as if the preset was never loaded. - // IMPORTANT! If the plugin doesn't manage its own bypass parameter, don't try - // to overwrite the bypass parameter value. - auto numParameters = juceParameters.getNumParameters(); - - for (int i = 0; i < numParameters; ++i) - if (auto* juceParam = juceParameters.getParamForIndex (i)) - if (juceParam != ownedBypassParameter.get()) - if (auto paramID = getAAXParamIDFromJuceIndex (i)) - SetParameterNormalizedValue (paramID, juceParam->getValue()); - - return AAX_SUCCESS; - } - - AAX_Result ResetFieldData (AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override - { - switch (fieldIndex) - { - case JUCEAlgorithmIDs::pluginInstance: - { - auto numObjects = dataSize / sizeof (PluginInstanceInfo); - auto* objects = static_cast (data); - - jassert (numObjects == 1); // not sure how to handle more than one.. - - for (size_t i = 0; i < numObjects; ++i) - new (objects + i) PluginInstanceInfo (const_cast (*this)); - - break; - } - - case JUCEAlgorithmIDs::preparedFlag: - { - const_cast (this)->preparePlugin(); - - auto numObjects = dataSize / sizeof (uint32_t); - auto* objects = static_cast (data); - - for (size_t i = 0; i < numObjects; ++i) - objects[i] = 1; - - break; - } - - case JUCEAlgorithmIDs::meterTapBuffers: - { - // this is a dummy field only when there are no aaxMeters - jassert (aaxMeters.size() == 0); - - { - auto numObjects = dataSize / sizeof (float*); - auto* objects = static_cast (data); - - for (size_t i = 0; i < numObjects; ++i) - objects[i] = nullptr; - } - break; - } - } - - return AAX_SUCCESS; - } - - void setAudioProcessorParameter (AAX_CParamID paramID, double value) - { - if (auto* param = getParameterFromID (paramID)) - { - auto newValue = static_cast (value); - - if (newValue != param->getValue()) - { - param->setValue (newValue); - - inParameterChangedCallback = true; - param->sendValueChangedMessageToListeners (newValue); - } - } - } - - AAX_Result GetNumberOfChanges (int32_t* numChanges) const override - { - const auto result = AAX_CEffectParameters::GetNumberOfChanges (numChanges); - *numChanges += numSetDirtyCalls; - return result; - } - - AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override - { - auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); - setAudioProcessorParameter (paramID, value); - - return result; - } - - AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override - { - if (auto* param = getParameterFromID (paramID)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - *result = param->getValueForText (text.Get()); - return AAX_SUCCESS; - } - } - - return AAX_CEffectParameters::GetParameterValueFromString (paramID, result, text); - } - - AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getText ((float) value, maxLen).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const - { - if (auto* param = getParameterFromID (paramID)) - *result = getSafeNumberOfParameterSteps (*param); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override - { - if (auto* param = getParameterFromID (paramID)) - *result = (double) param->getValue(); - else - *result = 0.0; - - return AAX_SUCCESS; - } - - AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override - { - if (auto* p = mParameterManager.GetParameterByID (paramID)) - p->SetValueWithFloat ((float) newValue); - - setAudioProcessorParameter (paramID, (float) newValue); - - return AAX_SUCCESS; - } - - AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override - { - if (auto* param = getParameterFromID (paramID)) - { - auto newValue = param->getValue() + (float) newDeltaValue; - - setAudioProcessorParameter (paramID, jlimit (0.0f, 1.0f, newValue)); - - if (auto* p = mParameterManager.GetParameterByID (paramID)) - p->SetValueWithFloat (newValue); - } - - return AAX_SUCCESS; - } - - AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getName (maxLen).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override - { - if (auto* param = getParameterFromID (paramID)) - result->Set (param->getName (31).toRawUTF8()); - - return AAX_SUCCESS; - } - - AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override - { - if (auto* param = getParameterFromID (paramID)) - *result = (double) param->getDefaultValue(); - else - *result = 0.0; - - jassert (*result >= 0 && *result <= 1.0f); - - return AAX_SUCCESS; - } - - AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } - - Optional getPosition() const override - { - PositionInfo info; - - const AAX_ITransport& transport = *Transport(); - - info.setBpm ([&] - { - double bpm = 0.0; - - return transport.GetCurrentTempo (&bpm) == AAX_SUCCESS ? makeOptional (bpm) : nullopt; - }()); - - const auto signature = [&] - { - int32_t num = 4, den = 4; - - return transport.GetCurrentMeter (&num, &den) == AAX_SUCCESS - ? makeOptional (TimeSignature { (int) num, (int) den }) - : nullopt; - }(); - - info.setTimeSignature (signature); - - info.setIsPlaying ([&] - { - bool isPlaying = false; - - return transport.IsTransportPlaying (&isPlaying) == AAX_SUCCESS && isPlaying; - }()); - - info.setIsRecording (recordingState.get().orFallback (false)); - - const auto optionalTimeInSamples = [&info, &transport] - { - int64_t timeInSamples = 0; - return ((! info.getIsPlaying() && transport.GetTimelineSelectionStartPosition (&timeInSamples) == AAX_SUCCESS) - || transport.GetCurrentNativeSampleLocation (&timeInSamples) == AAX_SUCCESS) - ? makeOptional (timeInSamples) - : nullopt; - }(); - - info.setTimeInSamples (optionalTimeInSamples); - info.setTimeInSeconds ((float) optionalTimeInSamples.orFallback (0) / sampleRate); - - const auto tickPosition = [&] - { - int64_t ticks = 0; - - return ((info.getIsPlaying() && transport.GetCustomTickPosition (&ticks, optionalTimeInSamples.orFallback (0))) == AAX_SUCCESS) - || transport.GetCurrentTickPosition (&ticks) == AAX_SUCCESS - ? makeOptional (ticks) - : nullopt; - }(); - - info.setPpqPosition (tickPosition.hasValue() ? makeOptional (static_cast (*tickPosition) / 960'000.0) : nullopt); - - bool isLooping = false; - int64_t loopStartTick = 0, loopEndTick = 0; - - if (transport.GetCurrentLoopPosition (&isLooping, &loopStartTick, &loopEndTick) == AAX_SUCCESS) - { - info.setIsLooping (isLooping); - info.setLoopPoints (LoopPoints { (double) loopStartTick / 960000.0, (double) loopEndTick / 960000.0 }); - } - - AAX_EFrameRate frameRate; - int32_t offset; - - if (transport.GetTimeCodeInfo (&frameRate, &offset) == AAX_SUCCESS) - { - info.setFrameRate ([&]() -> Optional - { - switch ((JUCE_AAX_EFrameRate) frameRate) - { - case JUCE_AAX_eFrameRate_24Frame: return FrameRate().withBaseRate (24); - case JUCE_AAX_eFrameRate_23976: return FrameRate().withBaseRate (24).withPullDown(); - - case JUCE_AAX_eFrameRate_25Frame: return FrameRate().withBaseRate (25); - - case JUCE_AAX_eFrameRate_30NonDrop: return FrameRate().withBaseRate (30); - case JUCE_AAX_eFrameRate_30DropFrame: return FrameRate().withBaseRate (30).withDrop(); - case JUCE_AAX_eFrameRate_2997NonDrop: return FrameRate().withBaseRate (30).withPullDown(); - case JUCE_AAX_eFrameRate_2997DropFrame: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_48Frame: return FrameRate().withBaseRate (48); - case JUCE_AAX_eFrameRate_47952: return FrameRate().withBaseRate (48).withPullDown(); - - case JUCE_AAX_eFrameRate_50Frame: return FrameRate().withBaseRate (50); - - case JUCE_AAX_eFrameRate_60NonDrop: return FrameRate().withBaseRate (60); - case JUCE_AAX_eFrameRate_60DropFrame: return FrameRate().withBaseRate (60).withDrop(); - case JUCE_AAX_eFrameRate_5994NonDrop: return FrameRate().withBaseRate (60).withPullDown(); - case JUCE_AAX_eFrameRate_5994DropFrame: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_100Frame: return FrameRate().withBaseRate (100); - - case JUCE_AAX_eFrameRate_120NonDrop: return FrameRate().withBaseRate (120); - case JUCE_AAX_eFrameRate_120DropFrame: return FrameRate().withBaseRate (120).withDrop(); - case JUCE_AAX_eFrameRate_11988NonDrop: return FrameRate().withBaseRate (120).withPullDown(); - case JUCE_AAX_eFrameRate_11988DropFrame: return FrameRate().withBaseRate (120).withPullDown().withDrop(); - - case JUCE_AAX_eFrameRate_Undeclared: break; - } - - return {}; - }()); - } - - const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; - info.setEditOriginTime (makeOptional (effectiveRate != 0.0 ? offset / effectiveRate : offset)); - - { - int32_t bars{}, beats{}; - int64_t displayTicks{}; - - if (optionalTimeInSamples.hasValue() - && transport.GetBarBeatPosition (&bars, &beats, &displayTicks, *optionalTimeInSamples) == AAX_SUCCESS) - { - info.setBarCount (bars); - - if (signature.hasValue()) - { - const auto ticksSinceBar = static_cast (((beats - 1) * 4 * 960'000) / signature->denominator) + displayTicks; - - if (tickPosition.hasValue() && ticksSinceBar <= tickPosition) - { - const auto barStartInTicks = static_cast (*tickPosition - ticksSinceBar); - info.setPpqPositionOfLastBarStart (barStartInTicks / 960'000.0); - } - } - } - } - - return info; - } - - void audioProcessorParameterChanged (AudioProcessor* /*processor*/, int parameterIndex, float newValue) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - SetParameterNormalizedValue (paramID, (double) newValue); - } - - void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override - { - ++mNumPlugInChanges; - - if (details.parameterInfoChanged) - { - for (const auto* param : juceParameters) - if (auto* aaxParam = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (param->getParameterIndex()))) - syncParameterAttributes (aaxParam, param); - } - - if (details.latencyChanged) - check (Controller()->SetSignalLatency (processor->getLatencySamples())); - - if (details.nonParameterStateChanged) - ++numSetDirtyCalls; - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override - { - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - TouchParameter (paramID); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override - { - if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) - ReleaseParameter (paramID); - } - - AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override - { - switch (type) - { - case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; - case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; - - case AAX_eNotificationEvent_ASProcessingState: - { - if (data != nullptr && size == sizeof (AAX_EProcessingState)) - { - const auto state = *static_cast (data); - const auto nonRealtime = state == AAX_eProcessingState_StartPass - || state == AAX_eProcessingState_BeginPassGroup; - pluginInstance->setNonRealtime (nonRealtime); - } - - break; - } - - case AAX_eNotificationEvent_TrackNameChanged: - if (data != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = String::fromUTF8 (static_cast (data)->Get()); - - pluginInstance->updateTrackProperties (props); - } - break; - - case AAX_eNotificationEvent_SideChainBeingConnected: - case AAX_eNotificationEvent_SideChainBeingDisconnected: - { - processingSidechainChange = true; - sidechainDesired = (type == AAX_eNotificationEvent_SideChainBeingConnected); - updateSidechainState(); - break; - } - - #if JUCE_AAX_HAS_TRANSPORT_NOTIFICATION - case AAX_eNotificationEvent_TransportStateChanged: - if (data != nullptr) - { - const auto& info = *static_cast (data); - recordingState.set (info.mIsRecording); - } - break; - #endif - } - - return AAX_CEffectParameters::NotificationReceived (type, data, size); - } - - const float* getAudioBufferForInput (const float* const* inputs, int sidechain, int mainNumIns, int idx) const noexcept - { - jassert (idx < (mainNumIns + 1)); - - if (idx < mainNumIns) - return inputs[inputLayoutMap[idx]]; - - return (sidechain != -1 ? inputs[sidechain] : sideChainBuffer.data()); - } - - void process (const float* const* inputs, float* const* outputs, const int sideChainBufferIdx, - const int bufferSize, const bool bypass, - AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut, - float* const meterBuffers) - { - auto numIns = pluginInstance->getTotalNumInputChannels(); - auto numOuts = pluginInstance->getTotalNumOutputChannels(); - auto numMeters = aaxMeters.size(); - - const ScopedLock sl (pluginInstance->getCallbackLock()); - - bool isSuspended = [this, sideChainBufferIdx] - { - if (processingSidechainChange) - return true; - - bool processWantsSidechain = (sideChainBufferIdx != -1); - - if (hasSidechain && canDisableSidechain && (sidechainDesired != processWantsSidechain)) - { - sidechainDesired = processWantsSidechain; - processingSidechainChange = true; - triggerAsyncUpdate(); - return true; - } - - return pluginInstance->isSuspended(); - }(); - - if (isSuspended) - { - for (int i = 0; i < numOuts; ++i) - FloatVectorOperations::clear (outputs[i], bufferSize); - - if (meterBuffers != nullptr) - FloatVectorOperations::clear (meterBuffers, numMeters); - } - else - { - auto mainNumIns = pluginInstance->getMainBusNumInputChannels(); - auto sidechain = (pluginInstance->getChannelCountOfBus (true, 1) > 0 ? sideChainBufferIdx : -1); - auto numChans = jmax (numIns, numOuts); - - if (numChans == 0) - return; - - if (channelList.size() <= numChans) - channelList.insertMultiple (-1, nullptr, 1 + numChans - channelList.size()); - - float** channels = channelList.getRawDataPointer(); - - if (numOuts >= numIns) - { - for (int i = 0; i < numOuts; ++i) - channels[i] = outputs[outputLayoutMap[i]]; - - for (int i = 0; i < numIns; ++i) - memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); - - for (int i = numIns; i < numOuts; ++i) - zeromem (channels[i], (size_t) bufferSize * sizeof (float)); - - process (channels, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); - } - else - { - for (int i = 0; i < numOuts; ++i) - channels[i] = outputs[outputLayoutMap[i]]; - - for (int i = 0; i < numOuts; ++i) - memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); - - for (int i = numOuts; i < numIns; ++i) - channels[i] = const_cast (getAudioBufferForInput (inputs, sidechain, mainNumIns, i)); - - process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); - } - - if (meterBuffers != nullptr) - for (int i = 0; i < numMeters; ++i) - meterBuffers[i] = aaxMeters[i]->getValue(); - } - } - - //============================================================================== - // In aax, the format of the aux and sidechain buses need to be fully determined - // by the format on the main buses. This function tried to provide such a mapping. - // Returns false if the in/out main layout is not supported - static bool fullBusesLayoutFromMainLayout (const AudioProcessor& p, - const AudioChannelSet& mainInput, const AudioChannelSet& mainOutput, - AudioProcessor::BusesLayout& fullLayout) - { - auto currentLayout = getDefaultLayout (p, true); - bool success = p.checkBusesLayoutSupported (currentLayout); - jassertquiet (success); - - auto numInputBuses = p.getBusCount (true); - auto numOutputBuses = p.getBusCount (false); - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return false; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return false; - - // did this change the input again - if (numInputBuses > 0 && currentLayout.inputBuses.getReference (0) != mainInput) - return false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - - if (! AudioProcessor::containsLayout (currentLayout, configs)) - return false; - #endif - - bool foundValid = false; - { - auto onlyMains = currentLayout; - - for (int i = 1; i < numInputBuses; ++i) - onlyMains.inputBuses.getReference (i) = AudioChannelSet::disabled(); - - for (int i = 1; i < numOutputBuses; ++i) - onlyMains.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (onlyMains)) - { - foundValid = true; - fullLayout = onlyMains; - } - } - - if (numInputBuses > 1) - { - // can the first bus be a sidechain or disabled, if not then we can't use this layout combination - if (auto* bus = p.getBus (true, 1)) - if (! bus->isLayoutSupported (AudioChannelSet::mono(), ¤tLayout) && ! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - // can all the other inputs be disabled, if not then we can't use this layout combination - for (int i = 2; i < numInputBuses; ++i) - if (auto* bus = p.getBus (true, i)) - if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return foundValid; - - // recheck if the format is correct - if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) - || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) - return foundValid; - - auto& sidechainBus = currentLayout.inputBuses.getReference (1); - - if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) - return foundValid; - - for (int i = 2; i < numInputBuses; ++i) - if (! currentLayout.inputBuses.getReference (i).isDisabled()) - return foundValid; - } - - const bool hasSidechain = (numInputBuses > 1 && currentLayout.inputBuses.getReference (1) == AudioChannelSet::mono()); - - if (hasSidechain) - { - auto onlyMainsAndSidechain = currentLayout; - - for (int i = 1; i < numOutputBuses; ++i) - onlyMainsAndSidechain.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (onlyMainsAndSidechain)) - { - foundValid = true; - fullLayout = onlyMainsAndSidechain; - } - } - - if (numOutputBuses > 1) - { - auto copy = currentLayout; - int maxAuxBuses = jmin (16, numOutputBuses); - - for (int i = 1; i < maxAuxBuses; ++i) - copy.outputBuses.getReference (i) = mainOutput; - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - copy.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (p.checkBusesLayoutSupported (copy)) - { - fullLayout = copy; - foundValid = true; - } - else - { - for (int i = 1; i < maxAuxBuses; ++i) - if (currentLayout.outputBuses.getReference (i).isDisabled()) - return foundValid; - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - if (auto* bus = p.getBus (false, i)) - if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (true, 0)) - if (! bus->isLayoutSupported (mainInput, ¤tLayout)) - return foundValid; - - if (auto* bus = p.getBus (false, 0)) - if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) - return foundValid; - - if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) - || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) - return foundValid; - - if (numInputBuses > 1) - { - auto& sidechainBus = currentLayout.inputBuses.getReference (1); - - if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) - return foundValid; - } - - for (int i = maxAuxBuses; i < numOutputBuses; ++i) - if (! currentLayout.outputBuses.getReference (i).isDisabled()) - return foundValid; - - fullLayout = currentLayout; - foundValid = true; - } - } - - return foundValid; - } - - bool isInAudioSuite() - { - AAX_CBoolean res; - Controller()->GetIsAudioSuite (&res); - - return res > 0; - } - - private: - friend class JuceAAX_GUI; - friend void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); - - void process (float* const* channels, const int numChans, const int bufferSize, - const bool bypass, [[maybe_unused]] AAX_IMIDINode* midiNodeIn, [[maybe_unused]] AAX_IMIDINode* midiNodesOut) - { - AudioBuffer buffer (channels, numChans, bufferSize); - midiBuffer.clear(); - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - { - auto* midiStream = midiNodeIn->GetNodeBuffer(); - auto numMidiEvents = midiStream->mBufferSize; - - for (uint32_t i = 0; i < numMidiEvents; ++i) - { - auto& m = midiStream->mBuffer[i]; - jassert ((int) m.mTimestamp < bufferSize); - - midiBuffer.addEvent (m.mData, (int) m.mLength, - jlimit (0, (int) bufferSize - 1, (int) m.mTimestamp)); - } - } - #endif - - { - if (lastBufferSize != bufferSize) - { - lastBufferSize = bufferSize; - pluginInstance->setRateAndBufferSizeDetails (sampleRate, lastBufferSize); - - // we only call prepareToPlay here if the new buffer size is larger than - // the one used last time prepareToPlay was called. - // currently, this should never actually happen, because as of Pro Tools 12, - // the maximum possible value is 1024, and we call prepareToPlay with that - // value during initialisation. - if (bufferSize > maxBufferSize) - prepareProcessorWithSampleRateAndBufferSize (sampleRate, bufferSize); - } - - if (bypass && pluginInstance->getBypassParameter() == nullptr) - pluginInstance->processBlockBypassed (buffer, midiBuffer); - else - pluginInstance->processBlock (buffer, midiBuffer); - } - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - { - AAX_CMidiPacket packet; - packet.mIsImmediate = false; - - for (const auto metadata : midiBuffer) - { - jassert (isPositiveAndBelow (metadata.samplePosition, bufferSize)); - - if (metadata.numBytes <= 4) - { - packet.mTimestamp = (uint32_t) metadata.samplePosition; - packet.mLength = (uint32_t) metadata.numBytes; - memcpy (packet.mData, metadata.data, (size_t) metadata.numBytes); - - check (midiNodesOut->PostMIDIPacket (&packet)); - } - } - } - #endif - } - - bool isBypassPartOfRegularParemeters() const - { - auto& audioProcessor = getPluginInstance(); - - int n = juceParameters.getNumParameters(); - - if (auto* bypassParam = audioProcessor.getBypassParameter()) - for (int i = 0; i < n; ++i) - if (juceParameters.getParamForIndex (i) == bypassParam) - return true; - - return false; - } - - // Some older Pro Tools control surfaces (EUCON [PT version 12.4] and - // Avid S6 before version 2.1) cannot cope with a large number of - // parameter steps. - static int32_t getSafeNumberOfParameterSteps (const AudioProcessorParameter& param) - { - return jmin (param.getNumSteps(), 2048); - } - - void addAudioProcessorParameters() - { - auto& audioProcessor = getPluginInstance(); - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters(); - - juceParameters.update (audioProcessor, forceLegacyParamIDs); - - auto* bypassParameter = pluginInstance->getBypassParameter(); - - if (bypassParameter == nullptr) - { - ownedBypassParameter.reset (new AudioParameterBool (cDefaultMasterBypassID, "Master Bypass", false)); - bypassParameter = ownedBypassParameter.get(); - } - - if (! bypassPartOfRegularParams) - juceParameters.addNonOwning (bypassParameter); - - int parameterIndex = 0; - - for (auto* juceParam : juceParameters) - { - auto isBypassParameter = (juceParam == bypassParameter); - - auto category = juceParam->getCategory(); - auto paramID = isBypassParameter ? String (cDefaultMasterBypassID) - : juceParameters.getParamID (audioProcessor, parameterIndex); - - aaxParamIDs.add (paramID); - auto* aaxParamID = aaxParamIDs.getReference (parameterIndex++).toRawUTF8(); - - paramMap.set (AAXClasses::getAAXParamHash (aaxParamID), juceParam); - - // is this a meter? - if (((category & 0xffff0000) >> 16) == 2) - { - aaxMeters.add (juceParam); - continue; - } - - auto parameter = new AAX_CParameter (aaxParamID, - AAX_CString (juceParam->getName (31).toRawUTF8()), - juceParam->getDefaultValue(), - AAX_CLinearTaperDelegate(), - AAX_CNumberDisplayDelegate(), - juceParam->isAutomatable()); - - parameter->AddShortenedName (juceParam->getName (4).toRawUTF8()); - - auto parameterNumSteps = getSafeNumberOfParameterSteps (*juceParam); - parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); - - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous - : AAX_eParameterType_Discrete); - #else - parameter->SetType (juceParam->isDiscrete() ? AAX_eParameterType_Discrete - : AAX_eParameterType_Continuous); - #endif - - parameter->SetOrientation (juceParam->isOrientationInverted() - ? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax - | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) - : (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax - | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); - - mParameterManager.AddParameter (parameter); - - if (isBypassParameter) - mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass); - } - } - - bool getMainBusFormats (AudioChannelSet& inputSet, AudioChannelSet& outputSet) - { - auto& audioProcessor = getPluginInstance(); - - #if JucePlugin_IsMidiEffect - // MIDI effect plug-ins do not support any audio channels - jassert (audioProcessor.getTotalNumInputChannels() == 0 - && audioProcessor.getTotalNumOutputChannels() == 0); - - inputSet = outputSet = AudioChannelSet(); - return true; - #else - auto inputBuses = audioProcessor.getBusCount (true); - auto outputBuses = audioProcessor.getBusCount (false); - - AAX_EStemFormat inputStemFormat = AAX_eStemFormat_None; - check (Controller()->GetInputStemFormat (&inputStemFormat)); - - AAX_EStemFormat outputStemFormat = AAX_eStemFormat_None; - check (Controller()->GetOutputStemFormat (&outputStemFormat)); - - #if JucePlugin_IsSynth - if (inputBuses == 0) - inputStemFormat = AAX_eStemFormat_None; - #endif - - inputSet = (inputBuses > 0 ? channelSetFromStemFormat (inputStemFormat, false) : AudioChannelSet()); - outputSet = (outputBuses > 0 ? channelSetFromStemFormat (outputStemFormat, false) : AudioChannelSet()); - - if ((inputSet == AudioChannelSet::disabled() && inputStemFormat != AAX_eStemFormat_None) || (outputSet == AudioChannelSet::disabled() && outputStemFormat != AAX_eStemFormat_None) - || (inputSet != AudioChannelSet::disabled() && inputBuses == 0) || (outputSet != AudioChannelSet::disabled() && outputBuses == 0)) - return false; - - return true; - #endif - } - - AAX_Result preparePlugin() - { - auto& audioProcessor = getPluginInstance(); - auto oldLayout = audioProcessor.getBusesLayout(); - AudioChannelSet inputSet, outputSet; - - if (! getMainBusFormats (inputSet, outputSet)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AudioProcessor::BusesLayout newLayout; - - if (! fullBusesLayoutFromMainLayout (audioProcessor, inputSet, outputSet, newLayout)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - hasSidechain = (newLayout.getNumChannels (true, 1) == 1); - - if (hasSidechain) - { - sidechainDesired = true; - - auto disabledSidechainLayout = newLayout; - disabledSidechainLayout.inputBuses.getReference (1) = AudioChannelSet::disabled(); - - canDisableSidechain = audioProcessor.checkBusesLayoutSupported (disabledSidechainLayout); - - if (canDisableSidechain && ! lastSideChainState) - { - sidechainDesired = false; - newLayout = disabledSidechainLayout; - } - } - - if (isInAudioSuite()) - { - // AudioSuite doesn't support multiple output buses - for (int i = 1; i < newLayout.outputBuses.size(); ++i) - newLayout.outputBuses.getReference (i) = AudioChannelSet::disabled(); - - if (! audioProcessor.checkBusesLayoutSupported (newLayout)) - { - // your plug-in needs to support a single output bus if running in AudioSuite - jassertfalse; - - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - } - - const bool layoutChanged = (oldLayout != newLayout); - - if (layoutChanged) - { - if (! audioProcessor.setBusesLayout (newLayout)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - rebuildChannelMapArrays(); - } - - if (layoutChanged || (! isPrepared)) - { - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - prepareProcessorWithSampleRateAndBufferSize (sampleRate, lastBufferSize); - - midiBuffer.ensureSize (2048); - midiBuffer.clear(); - } - - check (Controller()->SetSignalLatency (audioProcessor.getLatencySamples())); - isPrepared = true; - - return AAX_SUCCESS; - } - - void rebuildChannelMapArrays() - { - auto& audioProcessor = getPluginInstance(); - - for (int dir = 0; dir < 2; ++dir) - { - bool isInput = (dir == 0); - auto& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; - layoutMap.clear(); - - auto numBuses = audioProcessor.getBusCount (isInput); - int chOffset = 0; - - for (int busIdx = 0; busIdx < numBuses; ++busIdx) - { - auto channelFormat = audioProcessor.getChannelLayoutOfBus (isInput, busIdx); - - if (channelFormat != AudioChannelSet::disabled()) - { - auto numChannels = channelFormat.size(); - - for (int ch = 0; ch < numChannels; ++ch) - layoutMap.add (juceChannelIndexToAax (ch, channelFormat) + chOffset); - - chOffset += numChannels; - } - } - } - } - - static void algorithmCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) - { - for (auto iter = instancesBegin; iter < instancesEnd; ++iter) - { - auto& i = **iter; - - int sideChainBufferIdx = i.pluginInstance->parameters.hasSidechain && i.sideChainBuffers != nullptr - ? static_cast (*i.sideChainBuffers) : -1; - - // sidechain index of zero is an invalid index - if (sideChainBufferIdx <= 0) - sideChainBufferIdx = -1; - - auto numMeters = i.pluginInstance->parameters.aaxMeters.size(); - float* const meterTapBuffers = (i.meterTapBuffers != nullptr && numMeters > 0 ? *i.meterTapBuffers : nullptr); - - i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx, - *(i.bufferSize), *(i.bypass) != 0, - getMidiNodeIn(i), getMidiNodeOut(i), - meterTapBuffers); - } - } - - void prepareProcessorWithSampleRateAndBufferSize (double sr, int bs) - { - maxBufferSize = jmax (maxBufferSize, bs); - - auto& audioProcessor = getPluginInstance(); - audioProcessor.setRateAndBufferSizeDetails (sr, maxBufferSize); - audioProcessor.prepareToPlay (sr, maxBufferSize); - sideChainBuffer.resize (static_cast (maxBufferSize)); - } - - //============================================================================== - void updateSidechainState() - { - if (! processingSidechainChange) - return; - - auto& audioProcessor = getPluginInstance(); - const auto sidechainActual = audioProcessor.getChannelCountOfBus (true, 1) > 0; - - if (hasSidechain && canDisableSidechain && sidechainDesired != sidechainActual) - { - lastSideChainState = sidechainDesired; - - if (isPrepared) - { - isPrepared = false; - audioProcessor.releaseResources(); - } - - if (auto* bus = audioProcessor.getBus (true, 1)) - bus->setCurrentLayout (lastSideChainState ? AudioChannelSet::mono() - : AudioChannelSet::disabled()); - - prepareProcessorWithSampleRateAndBufferSize (audioProcessor.getSampleRate(), maxBufferSize); - isPrepared = true; - } - - processingSidechainChange = false; - } - - void handleAsyncUpdate() override - { - updateSidechainState(); - } - - //============================================================================== - static AudioProcessor::CurveData::Type aaxCurveTypeToJUCE (AAX_CTypeID type) noexcept - { - switch (type) - { - case AAX_eCurveType_EQ: return AudioProcessor::CurveData::Type::EQ; - case AAX_eCurveType_Dynamics: return AudioProcessor::CurveData::Type::Dynamics; - case AAX_eCurveType_Reduction: return AudioProcessor::CurveData::Type::GainReduction; - default: break; - } - - return AudioProcessor::CurveData::Type::Unknown; - } - - uint32_t getAAXMeterIdForParamId (const String& paramID) const noexcept - { - int idx; - - for (idx = 0; idx < aaxMeters.size(); ++idx) - if (LegacyAudioParameter::getParamID (aaxMeters[idx], false) == paramID) - break; - - // you specified a parameter id in your curve but the parameter does not have the meter - // category - jassert (idx < aaxMeters.size()); - return 'Metr' + static_cast (idx); - } - - //============================================================================== - AAX_Result GetCurveData (AAX_CTypeID iCurveType, const float * iValues, uint32_t iNumValues, float * oValues ) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve) - { - if (oValues != nullptr && iValues != nullptr) - { - for (uint32_t i = 0; i < iNumValues; ++i) - oValues[i] = curve.curve (iValues[i]); - } - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AAX_Result GetCurveDataMeterIds (AAX_CTypeID iCurveType, uint32_t *oXMeterId, uint32_t *oYMeterId) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve && curve.xMeterID.isNotEmpty() && curve.yMeterID.isNotEmpty()) - { - if (oXMeterId != nullptr) *oXMeterId = getAAXMeterIdForParamId (curve.xMeterID); - if (oYMeterId != nullptr) *oYMeterId = getAAXMeterIdForParamId (curve.yMeterID); - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - AAX_Result GetCurveDataDisplayRange (AAX_CTypeID iCurveType, float *oXMin, float *oXMax, float *oYMin, float *oYMax) const override - { - auto curveType = aaxCurveTypeToJUCE (iCurveType); - - if (curveType != AudioProcessor::CurveData::Type::Unknown) - { - auto& audioProcessor = getPluginInstance(); - auto curve = audioProcessor.getResponseCurve (curveType); - - if (curve.curve) - { - if (oXMin != nullptr) *oXMin = curve.xRange.getStart(); - if (oXMax != nullptr) *oXMax = curve.xRange.getEnd(); - if (oYMin != nullptr) *oYMin = curve.yRange.getStart(); - if (oYMax != nullptr) *oYMax = curve.yRange.getEnd(); - - return AAX_SUCCESS; - } - } - - return AAX_ERROR_UNIMPLEMENTED; - } - - //============================================================================== - inline int getParamIndexFromID (AAX_CParamID paramID) const noexcept - { - if (auto* param = getParameterFromID (paramID)) - return LegacyAudioParameter::getParamIndex (getPluginInstance(), param); - - return -1; - } - - inline AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept - { - if (isPositiveAndBelow (index, aaxParamIDs.size())) - return aaxParamIDs.getReference (index).toRawUTF8(); - - return nullptr; - } - - AudioProcessorParameter* getParameterFromID (AAX_CParamID paramID) const noexcept - { - return paramMap [AAXClasses::getAAXParamHash (paramID)]; - } - - //============================================================================== - static AudioProcessor::BusesLayout getDefaultLayout (const AudioProcessor& p, bool enableAll) - { - AudioProcessor::BusesLayout defaultLayout; - - for (int dir = 0; dir < 2; ++dir) - { - bool isInput = (dir == 0); - auto numBuses = p.getBusCount (isInput); - auto& layouts = (isInput ? defaultLayout.inputBuses : defaultLayout.outputBuses); - - for (int i = 0; i < numBuses; ++i) - if (auto* bus = p.getBus (isInput, i)) - layouts.add (enableAll || bus->isEnabledByDefault() ? bus->getDefaultLayout() : AudioChannelSet()); - } - - return defaultLayout; - } - - static AudioProcessor::BusesLayout getDefaultLayout (AudioProcessor& p) - { - auto defaultLayout = getDefaultLayout (p, true); - - if (! p.checkBusesLayoutSupported (defaultLayout)) - defaultLayout = getDefaultLayout (p, false); - - // Your processor must support the default layout - jassert (p.checkBusesLayoutSupported (defaultLayout)); - return defaultLayout; - } - - void syncParameterAttributes (AAX_IParameter* aaxParam, const AudioProcessorParameter* juceParam) - { - if (juceParam == nullptr) - return; - - { - auto newName = juceParam->getName (31); - - if (aaxParam->Name() != newName.toRawUTF8()) - aaxParam->SetName (AAX_CString (newName.toRawUTF8())); - } - - { - auto newType = juceParam->isDiscrete() ? AAX_eParameterType_Discrete : AAX_eParameterType_Continuous; - - if (aaxParam->GetType() != newType) - aaxParam->SetType (newType); - } - - { - auto newNumSteps = static_cast (juceParam->getNumSteps()); - - if (aaxParam->GetNumberOfSteps() != newNumSteps) - aaxParam->SetNumberOfSteps (newNumSteps); - } - - { - auto defaultValue = juceParam->getDefaultValue(); - - if (! approximatelyEqual (static_cast (aaxParam->GetNormalizedDefaultValue()), defaultValue)) - aaxParam->SetNormalizedDefaultValue (defaultValue); - } - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - std::unique_ptr pluginInstance; - - static constexpr auto maxSamplesPerBlock = 1 << AAX_eAudioBufferLength_Max; - - bool isPrepared = false; - MidiBuffer midiBuffer; - Array channelList; - int32_t juceChunkIndex = 0, numSetDirtyCalls = 0; - AAX_CSampleRate sampleRate = 0; - int lastBufferSize = maxSamplesPerBlock, maxBufferSize = maxSamplesPerBlock; - bool hasSidechain = false, canDisableSidechain = false, lastSideChainState = false; - - /* Pro Tools 2021 sends TransportStateChanged on the main thread, but we read - the recording state on the audio thread. - I'm not sure whether Pro Tools ensures that these calls are mutually - exclusive, so to ensure there are no data races, we store the recording - state in an atomic int and convert it to/from an Optional as necessary. - */ - class RecordingState - { - public: - /* This uses Optional rather than std::optional for consistency with get() */ - void set (const Optional newState) - { - state.store (newState.hasValue() ? (flagValid | (*newState ? flagActive : 0)) - : 0, - std::memory_order_relaxed); - } - - /* PositionInfo::setIsRecording takes an Optional, so we use that type rather - than std::optional to avoid having to convert. - */ - Optional get() const - { - const auto loaded = state.load (std::memory_order_relaxed); - return ((loaded & flagValid) != 0) ? makeOptional ((loaded & flagActive) != 0) - : nullopt; - } - - private: - enum RecordingFlags - { - flagValid = 1 << 0, - flagActive = 1 << 1 - }; - - std::atomic state { 0 }; - }; - - RecordingState recordingState; - - std::atomic processingSidechainChange, sidechainDesired; - - std::vector sideChainBuffer; - Array inputLayoutMap, outputLayoutMap; - - Array aaxParamIDs; - HashMap paramMap; - LegacyAudioParametersWrapper juceParameters; - std::unique_ptr ownedBypassParameter; - - Array aaxMeters; - - struct ChunkMemoryBlock - { - juce::MemoryBlock data; - bool isValid; - }; - - // temporary filter data is generated in GetChunkSize - // and the size of the data returned. To avoid generating - // it again in GetChunk, we need to store it somewhere. - // However, as GetChunkSize and GetChunk can be called - // on different threads, we store it in thread dependent storage - // in a hash map with the thread id as a key. - mutable ThreadLocalValue perThreadFilterData; - CriticalSection perThreadDataLock; - - ThreadLocalValue inParameterChangedCallback; - - JUCE_DECLARE_NON_COPYABLE (JuceAAX_Processor) - }; - - //============================================================================== - void JuceAAX_GUI::CreateViewContents() - { - if (component == nullptr) - { - if (auto* params = dynamic_cast (GetEffectParameters())) - component.reset (new ContentWrapperComponent (*this, params->getPluginInstance())); - else - jassertfalse; - } - } - - int JuceAAX_GUI::getParamIndexFromID (AAX_CParamID paramID) const noexcept - { - if (auto* params = dynamic_cast (GetEffectParameters())) - return params->getParamIndexFromID (paramID); - - return -1; - } - - AAX_CParamID JuceAAX_GUI::getAAXParamIDFromJuceIndex (int index) const noexcept - { - if (auto* params = dynamic_cast (GetEffectParameters())) - return params->getAAXParamIDFromJuceIndex (index); - - return nullptr; - } - - //============================================================================== - struct AAXFormatConfiguration - { - AAXFormatConfiguration() noexcept {} - - AAXFormatConfiguration (AAX_EStemFormat inFormat, AAX_EStemFormat outFormat) noexcept - : inputFormat (inFormat), outputFormat (outFormat) {} - - AAX_EStemFormat inputFormat = AAX_eStemFormat_None, - outputFormat = AAX_eStemFormat_None; - - bool operator== (const AAXFormatConfiguration other) const noexcept - { - return inputFormat == other.inputFormat && outputFormat == other.outputFormat; - } - - bool operator< (const AAXFormatConfiguration other) const noexcept - { - return inputFormat == other.inputFormat ? (outputFormat < other.outputFormat) - : (inputFormat < other.inputFormat); - } - }; - - //============================================================================== - static int addAAXMeters (AudioProcessor& p, AAX_IEffectDescriptor& descriptor) - { - LegacyAudioParametersWrapper params; - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - params.update (p, forceLegacyParamIDs); - - int meterIdx = 0; - - for (auto* param : params) - { - auto category = param->getCategory(); - - // is this a meter? - if (((category & 0xffff0000) >> 16) == 2) - { - if (auto* meterProperties = descriptor.NewPropertyMap()) - { - meterProperties->AddProperty (AAX_eProperty_Meter_Type, getMeterTypeForCategory (category)); - meterProperties->AddProperty (AAX_eProperty_Meter_Orientation, AAX_eMeterOrientation_TopRight); - - descriptor.AddMeterDescription ('Metr' + static_cast (meterIdx++), - param->getName (1024).toRawUTF8(), meterProperties); - } - } - } - - return meterIdx; - } - - static void createDescriptor (AAX_IComponentDescriptor& desc, - const AudioProcessor::BusesLayout& fullLayout, - AudioProcessor& processor, - Array& pluginIds, - const int numMeters) - { - auto aaxInputFormat = getFormatForAudioChannelSet (fullLayout.getMainInputChannelSet(), false); - auto aaxOutputFormat = getFormatForAudioChannelSet (fullLayout.getMainOutputChannelSet(), false); - - #if JucePlugin_IsSynth - if (aaxInputFormat == AAX_eStemFormat_None) - aaxInputFormat = aaxOutputFormat; - #endif - - #if JucePlugin_IsMidiEffect - aaxInputFormat = aaxOutputFormat = AAX_eStemFormat_Mono; - #endif - - check (desc.AddAudioIn (JUCEAlgorithmIDs::inputChannels)); - check (desc.AddAudioOut (JUCEAlgorithmIDs::outputChannels)); - - check (desc.AddAudioBufferLength (JUCEAlgorithmIDs::bufferSize)); - check (desc.AddDataInPort (JUCEAlgorithmIDs::bypass, sizeof (int32_t))); - - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeIn, AAX_eMIDINodeType_LocalInput, - JucePlugin_Name, 0xffff)); - #endif - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect - check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeOut, AAX_eMIDINodeType_LocalOutput, - JucePlugin_Name " Out", 0xffff)); - #endif - - check (desc.AddPrivateData (JUCEAlgorithmIDs::pluginInstance, sizeof (PluginInstanceInfo))); - check (desc.AddPrivateData (JUCEAlgorithmIDs::preparedFlag, sizeof (int32_t))); - - if (numMeters > 0) - { - HeapBlock meterIDs (static_cast (numMeters)); - - for (int i = 0; i < numMeters; ++i) - meterIDs[i] = 'Metr' + static_cast (i); - - check (desc.AddMeters (JUCEAlgorithmIDs::meterTapBuffers, meterIDs.getData(), static_cast (numMeters))); - } - else - { - // AAX does not allow there to be any gaps in the fields of the algorithm context structure - // so just add a dummy one here if there aren't any meters - check (desc.AddPrivateData (JUCEAlgorithmIDs::meterTapBuffers, sizeof (uintptr_t))); - } - - // Create a property map - AAX_IPropertyMap* const properties = desc.NewPropertyMap(); - jassert (properties != nullptr); - - properties->AddProperty (AAX_eProperty_ManufacturerID, JucePlugin_AAXManufacturerCode); - properties->AddProperty (AAX_eProperty_ProductID, JucePlugin_AAXProductId); - - #if JucePlugin_AAXDisableBypass - properties->AddProperty (AAX_eProperty_CanBypass, false); - #else - properties->AddProperty (AAX_eProperty_CanBypass, true); - #endif - - properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast (aaxInputFormat)); - properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast (aaxOutputFormat)); - - // This value needs to match the RTAS wrapper's Type ID, so that - // the host knows that the RTAS/AAX plugins are equivalent. - const int32 pluginID = processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), - fullLayout.getMainOutputChannelSet(), - false); - - // The plugin id generated from your AudioProcessor's getAAXPluginIDForMainBusConfig callback - // it not unique. Please fix your implementation! - jassert (! pluginIds.contains (pluginID)); - pluginIds.add (pluginID); - - properties->AddProperty (AAX_eProperty_PlugInID_Native, pluginID); - - #if ! JucePlugin_AAXDisableAudioSuite - properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, - processor.getAAXPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), - fullLayout.getMainOutputChannelSet(), - true)); - #endif - - #if JucePlugin_AAXDisableMultiMono - properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, false); - #else - properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); - #endif - - #if JucePlugin_AAXDisableDynamicProcessing - properties->AddProperty (AAX_eProperty_Constraint_AlwaysProcess, true); - #endif - - #if JucePlugin_AAXDisableDefaultSettingsChunks - properties->AddProperty (AAX_eProperty_Constraint_DoNotApplyDefaultSettings, true); - #endif - - #if JucePlugin_AAXDisableSaveRestore - properties->AddProperty (AAX_eProperty_SupportsSaveRestore, false); - #endif - - #if JUCE_AAX_HAS_TRANSPORT_NOTIFICATION - properties->AddProperty (AAX_eProperty_ObservesTransportState, true); - #endif - - if (fullLayout.getChannelSet (true, 1) == AudioChannelSet::mono()) - { - check (desc.AddSideChainIn (JUCEAlgorithmIDs::sideChainBuffers)); - properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); - } - else - { - // AAX does not allow there to be any gaps in the fields of the algorithm context structure - // so just add a dummy one here if there aren't any side chains - check (desc.AddPrivateData (JUCEAlgorithmIDs::sideChainBuffers, sizeof (uintptr_t))); - } - - auto maxAuxBuses = jmax (0, jmin (15, fullLayout.outputBuses.size() - 1)); - - // add the output buses - // This is incredibly dumb: the output bus format must be well defined - // for every main bus in/out format pair. This means that there cannot - // be two configurations with different aux formats but - // identical main bus in/out formats. - for (int busIdx = 1; busIdx < maxAuxBuses + 1; ++busIdx) - { - auto set = fullLayout.getChannelSet (false, busIdx); - - if (set.isDisabled()) - break; - - auto auxFormat = getFormatForAudioChannelSet (set, true); - - if (auxFormat != AAX_eStemFormat_INT32_MAX && auxFormat != AAX_eStemFormat_None) - { - auto& name = processor.getBus (false, busIdx)->getName(); - check (desc.AddAuxOutputStem (0, static_cast (auxFormat), name.toRawUTF8())); - } - } - - check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); - } - - static bool hostSupportsStemFormat (AAX_EStemFormat stemFormat, const AAX_IFeatureInfo* featureInfo) - { - if (featureInfo != nullptr) - { - AAX_ESupportLevel supportLevel; - - if (featureInfo->SupportLevel (supportLevel) == AAX_SUCCESS && supportLevel == AAX_eSupportLevel_ByProperty) - { - std::unique_ptr props (featureInfo->AcquireProperties()); - - // Due to a bug in ProTools 12.8, ProTools thinks that AAX_eStemFormat_Ambi_1_ACN is not supported - // To workaround this bug, check if ProTools supports AAX_eStemFormat_Ambi_2_ACN, and, if yes, - // we can safely assume that it will also support AAX_eStemFormat_Ambi_1_ACN - if (stemFormat == AAX_eStemFormat_Ambi_1_ACN) - stemFormat = AAX_eStemFormat_Ambi_2_ACN; - - if (props != nullptr && props->GetProperty ((AAX_EProperty) stemFormat, (AAX_CPropertyValue*) &supportLevel) != 0) - return (supportLevel == AAX_eSupportLevel_Supported); - } - } - - return (AAX_STEM_FORMAT_INDEX (stemFormat) <= 12); - } - - static void getPlugInDescription (AAX_IEffectDescriptor& descriptor, [[maybe_unused]] const AAX_IFeatureInfo* featureInfo) - { - auto plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); - auto numInputBuses = plugin->getBusCount (true); - auto numOutputBuses = plugin->getBusCount (false); - - auto pluginNames = plugin->getAlternateDisplayNames(); - - pluginNames.insert (0, JucePlugin_Name); - - pluginNames.removeDuplicates (false); - - for (auto name : pluginNames) - descriptor.AddName (name.toRawUTF8()); - - descriptor.AddCategory (JucePlugin_AAXCategory); - - const int numMeters = addAAXMeters (*plugin, descriptor); - - #ifdef JucePlugin_AAXPageTableFile - // optional page table setting - define this macro in your project if you want - // to set this value - see Avid documentation for details about its format. - descriptor.AddResourceInfo (AAX_eResourceType_PageTable, JucePlugin_AAXPageTableFile); - #endif - - check (descriptor.AddProcPtr ((void*) JuceAAX_GUI::Create, kAAX_ProcPtrID_Create_EffectGUI)); - check (descriptor.AddProcPtr ((void*) JuceAAX_Processor::Create, kAAX_ProcPtrID_Create_EffectParameters)); - - Array pluginIds; - #if JucePlugin_IsMidiEffect - // MIDI effect plug-ins do not support any audio channels - jassert (numInputBuses == 0 && numOutputBuses == 0); - - if (auto* desc = descriptor.NewComponentDescriptor()) - { - createDescriptor (*desc, plugin->getBusesLayout(), *plugin, pluginIds, numMeters); - check (descriptor.AddComponent (desc)); - } - #else - const int numIns = numInputBuses > 0 ? numElementsInArray (aaxFormats) : 0; - const int numOuts = numOutputBuses > 0 ? numElementsInArray (aaxFormats) : 0; - - for (int inIdx = 0; inIdx < jmax (numIns, 1); ++inIdx) - { - auto aaxInFormat = numIns > 0 ? aaxFormats[inIdx] : AAX_eStemFormat_None; - auto inLayout = channelSetFromStemFormat (aaxInFormat, false); - - for (int outIdx = 0; outIdx < jmax (numOuts, 1); ++outIdx) - { - auto aaxOutFormat = numOuts > 0 ? aaxFormats[outIdx] : AAX_eStemFormat_None; - auto outLayout = channelSetFromStemFormat (aaxOutFormat, false); - - if (hostSupportsStemFormat (aaxInFormat, featureInfo) - && hostSupportsStemFormat (aaxOutFormat, featureInfo)) - { - AudioProcessor::BusesLayout fullLayout; - - if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) - continue; - - if (auto* desc = descriptor.NewComponentDescriptor()) - { - createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); - check (descriptor.AddComponent (desc)); - } - } - } - } - - // You don't have any supported layouts - jassert (pluginIds.size() > 0); - #endif - } -} // namespace AAXClasses - -void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) -{ - AAXClasses::JuceAAX_Processor::algorithmCallback (instancesBegin, instancesEnd); -} - -//============================================================================== -AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection*); -AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection* collection) -{ - ScopedJuceInitialiser_GUI libraryInitialiser; - - std::unique_ptr stemFormatFeatureInfo; - - if (const auto* hostDescription = collection->DescriptionHost()) - stemFormatFeatureInfo.reset (hostDescription->AcquireFeatureProperties (AAXATTR_ClientFeature_StemFormat)); - - if (auto* descriptor = collection->NewDescriptor()) - { - AAXClasses::getPlugInDescription (*descriptor, stemFormatFeatureInfo.get()); - collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); - - collection->SetManufacturerName (JucePlugin_Manufacturer); - collection->AddPackageName (JucePlugin_Desc); - collection->AddPackageName (JucePlugin_Name); - collection->SetPackageVersion (JucePlugin_VersionCode); - - return AAX_SUCCESS; - } - - return AAX_ERROR_NULL_OBJECT; -} - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -#if _MSC_VER || JUCE_MINGW -extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } -#endif - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm deleted file mode 100644 index 5fa7541..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ /dev/null @@ -1,2432 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include -#include "../utility/juce_CheckSettingMacros.h" - -#if JucePlugin_Build_AU - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshorten-64-to-32", - "-Wunused-parameter", - "-Wdeprecated-declarations", - "-Wsign-conversion", - "-Wconversion", - "-Woverloaded-virtual", - "-Wextra-semi", - "-Wcast-align", - "-Wshadow", - "-Wswitch-enum", - "-Wzero-as-null-pointer-constant", - "-Wnullable-to-nonnull-conversion", - "-Wgnu-zero-variadic-macro-arguments", - "-Wformat-pedantic", - "-Wdeprecated-anon-enum-enum-conversion") - -#include "../utility/juce_IncludeSystemHeaders.h" - -#include -#include -#include -#include -#include -#include "AudioUnitSDK/MusicDeviceBase.h" - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 - -#include "../utility/juce_IncludeModuleHeaders.h" - -#include -#include -#include -#include - -#if JucePlugin_Enable_ARA - #include - #include - #if ARA_SUPPORT_VERSION_1 - #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current JUCE ARA implementation" - #endif -#endif - -#include - -//============================================================================== -using namespace juce; - -static Array activePlugins, activeUIs; - -static const AudioUnitPropertyID juceFilterObjectPropertyID = 0x1a45ffe9; - -template <> struct ContainerDeletePolicy { static void destroy (const __CFString* o) { if (o != nullptr) CFRelease (o); } }; - -// make sure the audio processor is initialized before the AUBase class -struct AudioProcessorHolder -{ - AudioProcessorHolder (bool initialiseGUI) - { - if (initialiseGUI) - initialiseJuce_GUI(); - - juceFilter = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnit); - - // audio units do not have a notion of enabled or un-enabled buses - juceFilter->enableAllBuses(); - } - - std::unique_ptr juceFilter; -}; - -//============================================================================== -class JuceAU : public AudioProcessorHolder, - public ausdk::MusicDeviceBase, - public AudioProcessorListener, - public AudioProcessorParameter::Listener -{ -public: - JuceAU (AudioUnit component) - : AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0), - MusicDeviceBase (component, - (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true), - (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)) - { - inParameterChangedCallback = false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - juceFilter->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); - - for (int i = 0; i < numConfigs; ++i) - { - AUChannelInfo info; - - info.inChannels = configs[i][0]; - info.outChannels = configs[i][1]; - - channelInfo.add (info); - } - #else - channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); - #endif - - AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); - - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - juceFilter->addListener (this); - - addParameters(); - - activePlugins.add (this); - - zerostruct (auEvent); - auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance(); - auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global; - auEvent.mArgument.mParameter.mElement = 0; - - zerostruct (midiCallback); - - CreateElements(); - - if (syncAudioUnitWithProcessor() != noErr) - jassertfalse; - } - - ~JuceAU() override - { - if (bypassParam != nullptr) - bypassParam->removeListener (this); - - deleteActiveEditors(); - juceFilter = nullptr; - clearPresetsArray(); - - jassert (activePlugins.contains (this)); - activePlugins.removeFirstMatchingValue (this); - - if (activePlugins.size() + activeUIs.size() == 0) - shutdownJuce_GUI(); - } - - //============================================================================== - ComponentResult Initialize() override - { - ComponentResult err; - - if ((err = syncProcessorWithAudioUnit()) != noErr) - return err; - - if ((err = MusicDeviceBase::Initialize()) != noErr) - return err; - - mapper.alloc (*juceFilter); - pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true))); - - prepareToPlay(); - - return noErr; - } - - void Cleanup() override - { - MusicDeviceBase::Cleanup(); - - pulledSucceeded.free(); - mapper.release(); - - if (juceFilter != nullptr) - juceFilter->releaseResources(); - - audioBuffer.release(); - midiEvents.clear(); - incomingEvents.clear(); - prepared = false; - } - - ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) override - { - if (! prepared) - prepareToPlay(); - - if (juceFilter != nullptr) - juceFilter->reset(); - - return MusicDeviceBase::Reset (inScope, inElement); - } - - //============================================================================== - void prepareToPlay() - { - if (juceFilter != nullptr) - { - juceFilter->setRateAndBufferSizeDetails (getSampleRate(), (int) GetMaxFramesPerSlice()); - - audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (juceFilter.get()), (int) GetMaxFramesPerSlice() + 32); - juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); - - midiEvents.ensureSize (2048); - midiEvents.clear(); - incomingEvents.ensureSize (2048); - incomingEvents.clear(); - - prepared = true; - } - } - - //============================================================================== - bool BusCountWritable ([[maybe_unused]] AudioUnitScope scope) override - { - #ifdef JucePlugin_PreferredChannelConfigurations - return false; - #else - bool isInput; - - if (scopeToDirection (scope, isInput) != noErr) - return false; - - #if JucePlugin_IsMidiEffect - return false; - #elif JucePlugin_IsSynth - if (isInput) return false; - #endif - - const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput))); - #endif - } - - OSStatus SetBusCount (AudioUnitScope scope, UInt32 count) override - { - OSStatus err = noErr; - bool isInput; - - if ((err = scopeToDirection (scope, isInput)) != noErr) - return err; - - if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput)) - { - #ifdef JucePlugin_PreferredChannelConfigurations - return kAudioUnitErr_PropertyNotWritable; - #else - const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - - if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput)))) - return kAudioUnitErr_PropertyNotWritable; - - // we need to already create the underlying elements so that we can change their formats - err = MusicDeviceBase::SetBusCount (scope, count); - - if (err != noErr) - return err; - - // however we do need to update the format tag: we need to do the same thing in SetFormat, for example - const int requestedNumBus = static_cast (count); - { - (isInput ? currentInputLayout : currentOutputLayout).resize (requestedNumBus); - - int busNr; - - for (busNr = (busCount - 1); busNr != (requestedNumBus - 1); busNr += (requestedNumBus > busCount ? 1 : -1)) - { - if (requestedNumBus > busCount) - { - if (! juceFilter->addBus (isInput)) - break; - - err = syncAudioUnitWithChannelSet (isInput, busNr, - juceFilter->getBus (isInput, busNr + 1)->getDefaultLayout()); - if (err != noErr) - break; - } - else - { - if (! juceFilter->removeBus (isInput)) - break; - } - } - - err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); - } - - // was there an error? - if (err != noErr) - { - // restore bus state - const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1)) - { - if (busCount > newBusCount) - juceFilter->addBus (isInput); - else - juceFilter->removeBus (isInput); - } - - (isInput ? currentInputLayout : currentOutputLayout).resize (busCount); - MusicDeviceBase::SetBusCount (scope, static_cast (busCount)); - - return kAudioUnitErr_FormatNotSupported; - } - - // update total channel count - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - addSupportedLayoutTagsForDirection (isInput); - - if (err != noErr) - return err; - #endif - } - - return noErr; - } - - UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override - { - if (outInfo != nullptr) - *outInfo = channelInfo.getRawDataPointer(); - - return (UInt32) channelInfo.size(); - } - - //============================================================================== - ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - UInt32& outDataSize, - bool& outWritable) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - case juceFilterObjectPropertyID: - outWritable = false; - outDataSize = sizeof (void*) * 2; - return noErr; - - case kAudioUnitProperty_OfflineRender: - outWritable = true; - outDataSize = sizeof (UInt32); - return noErr; - - case kMusicDeviceProperty_InstrumentCount: - outDataSize = sizeof (UInt32); - outWritable = false; - return noErr; - - case kAudioUnitProperty_CocoaUI: - outDataSize = sizeof (AudioUnitCocoaViewInfo); - outWritable = true; - return noErr; - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallbackInfo: - outDataSize = sizeof (CFArrayRef); - outWritable = false; - return noErr; - - case kAudioUnitProperty_MIDIOutputCallback: - outDataSize = sizeof (AUMIDIOutputCallbackStruct); - outWritable = true; - return noErr; - #endif - - case kAudioUnitProperty_ParameterStringFromValue: - outDataSize = sizeof (AudioUnitParameterStringFromValue); - outWritable = false; - return noErr; - - case kAudioUnitProperty_ParameterValueFromString: - outDataSize = sizeof (AudioUnitParameterValueFromString); - outWritable = false; - return noErr; - - case kAudioUnitProperty_BypassEffect: - outDataSize = sizeof (UInt32); - outWritable = true; - return noErr; - - case kAudioUnitProperty_SupportsMPE: - outDataSize = sizeof (UInt32); - outWritable = false; - return noErr; - - #if JucePlugin_Enable_ARA - case ARA::kAudioUnitProperty_ARAFactory: - outWritable = false; - outDataSize = sizeof (ARA::ARAAudioUnitFactory); - return noErr; - case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: - outWritable = false; - outDataSize = sizeof (ARA::ARAAudioUnitPlugInExtensionBinding); - return noErr; - #endif - - default: break; - } - } - - return MusicDeviceBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); - } - - ComponentResult GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - case kAudioUnitProperty_ParameterClumpName: - - if (auto* clumpNameInfo = (AudioUnitParameterNameInfo*) outData) - { - if (juceFilter != nullptr) - { - auto clumpIndex = clumpNameInfo->inID - 1; - const auto* group = parameterGroups[(int) clumpIndex]; - auto name = group->getName(); - - while (group->getParent() != &juceFilter->getParameterTree()) - { - group = group->getParent(); - name = group->getName() + group->getSeparator() + name; - } - - clumpNameInfo->outName = name.toCFString(); - return noErr; - } - } - - // Failed to find a group corresponding to the clump ID. - jassertfalse; - break; - - //============================================================================== - #if JucePlugin_Enable_ARA - case ARA::kAudioUnitProperty_ARAFactory: - { - auto auFactory = static_cast (outData); - if (auFactory->inOutMagicNumber != ARA::kARAAudioUnitMagic) - return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics - - auFactory->outFactory = createARAFactory(); - return noErr; - } - - case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: - { - auto binding = static_cast (outData); - if (binding->inOutMagicNumber != ARA::kARAAudioUnitMagic) - return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics - - AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (juceFilter.get()); - binding->outPlugInExtension = araAudioProcessorExtension->bindToARA (binding->inDocumentControllerRef, binding->knownRoles, binding->assignedRoles); - if (binding->outPlugInExtension == nullptr) - return kAudioUnitErr_CannotDoInCurrentContext; // bindToARA() returns null if binding is already established - - return noErr; - } - #endif - - case juceFilterObjectPropertyID: - ((void**) outData)[0] = (void*) static_cast (juceFilter.get()); - ((void**) outData)[1] = (void*) this; - return noErr; - - case kAudioUnitProperty_OfflineRender: - *(UInt32*) outData = (juceFilter != nullptr && juceFilter->isNonRealtime()) ? 1 : 0; - return noErr; - - case kMusicDeviceProperty_InstrumentCount: - *(UInt32*) outData = 1; - return noErr; - - case kAudioUnitProperty_BypassEffect: - if (bypassParam != nullptr) - *(UInt32*) outData = (bypassParam->getValue() != 0.0f ? 1 : 0); - else - *(UInt32*) outData = isBypassed ? 1 : 0; - return noErr; - - case kAudioUnitProperty_SupportsMPE: - *(UInt32*) outData = (juceFilter != nullptr && juceFilter->supportsMPE()) ? 1 : 0; - return noErr; - - case kAudioUnitProperty_CocoaUI: - { - JUCE_AUTORELEASEPOOL - { - static JuceUICreationClass cls; - - // (NB: this may be the host's bundle, not necessarily the component's) - NSBundle* bundle = [NSBundle bundleForClass: cls.cls]; - - AudioUnitCocoaViewInfo* info = static_cast (outData); - info->mCocoaAUViewClass[0] = (CFStringRef) [juceStringToNS (class_getName (cls.cls)) retain]; - info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [bundle bundlePath]] retain]; - } - - return noErr; - } - - break; - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallbackInfo: - { - CFStringRef strs[1]; - strs[0] = CFSTR ("MIDI Callback"); - - CFArrayRef callbackArray = CFArrayCreate (nullptr, (const void**) strs, 1, &kCFTypeArrayCallBacks); - *(CFArrayRef*) outData = callbackArray; - return noErr; - } - #endif - - case kAudioUnitProperty_ParameterValueFromString: - { - if (AudioUnitParameterValueFromString* pv = (AudioUnitParameterValueFromString*) outData) - { - if (juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (pv->inParamID)) - { - const String text (String::fromCFString (pv->inString)); - - if (LegacyAudioParameter::isLegacy (param)) - pv->outValue = text.getFloatValue(); - else - pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); - - - return noErr; - } - } - } - } - break; - - case kAudioUnitProperty_ParameterStringFromValue: - { - if (AudioUnitParameterStringFromValue* pv = (AudioUnitParameterStringFromValue*) outData) - { - if (juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (pv->inParamID)) - { - const float value = (float) *(pv->inValue); - String text; - - if (LegacyAudioParameter::isLegacy (param)) - text = String (value); - else - text = param->getText (value / getMaximumParameterValue (param), 0); - - pv->outString = text.toCFString(); - - return noErr; - } - } - } - } - break; - - default: - break; - } - } - - return MusicDeviceBase::GetProperty (inID, inScope, inElement, outData); - } - - ComponentResult SetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - const void* inData, - UInt32 inDataSize) override - { - if (inScope == kAudioUnitScope_Global) - { - switch (inID) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - case kAudioUnitProperty_MIDIOutputCallback: - if (inDataSize < sizeof (AUMIDIOutputCallbackStruct)) - return kAudioUnitErr_InvalidPropertyValue; - - if (AUMIDIOutputCallbackStruct* callbackStruct = (AUMIDIOutputCallbackStruct*) inData) - midiCallback = *callbackStruct; - - return noErr; - #endif - - case kAudioUnitProperty_BypassEffect: - { - if (inDataSize < sizeof (UInt32)) - return kAudioUnitErr_InvalidPropertyValue; - - const bool newBypass = *((UInt32*) inData) != 0; - const bool currentlyBypassed = (bypassParam != nullptr ? (bypassParam->getValue() != 0.0f) : isBypassed); - - if (newBypass != currentlyBypassed) - { - if (bypassParam != nullptr) - bypassParam->setValueNotifyingHost (newBypass ? 1.0f : 0.0f); - else - isBypassed = newBypass; - - if (! currentlyBypassed && IsInitialized()) // turning bypass off and we're initialized - Reset (0, 0); - } - - return noErr; - } - - case kAudioUnitProperty_OfflineRender: - { - const auto shouldBeOffline = (*reinterpret_cast (inData) != 0); - - if (juceFilter != nullptr) - { - const auto isOffline = juceFilter->isNonRealtime(); - - if (isOffline != shouldBeOffline) - { - const ScopedLock sl (juceFilter->getCallbackLock()); - - juceFilter->setNonRealtime (shouldBeOffline); - - if (prepared) - juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); - } - } - - return noErr; - } - - case kAudioUnitProperty_AUHostIdentifier: - { - if (inDataSize < sizeof (AUHostVersionIdentifier)) - return kAudioUnitErr_InvalidPropertyValue; - - const auto* identifier = static_cast (inData); - PluginHostType::hostIdReportedByWrapper = String::fromCFString (identifier->hostName); - - return noErr; - } - - default: break; - } - } - - return MusicDeviceBase::SetProperty (inID, inScope, inElement, inData, inDataSize); - } - - //============================================================================== - ComponentResult SaveState (CFPropertyListRef* outData) override - { - ComponentResult err = MusicDeviceBase::SaveState (outData); - - if (err != noErr) - return err; - - jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); - - CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; - - if (juceFilter != nullptr) - { - juce::MemoryBlock state; - - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - juceFilter->getCurrentProgramStateInformation (state); - #else - juceFilter->getStateInformation (state); - #endif - - if (state.getSize() > 0) - { - CFUniquePtr ourState (CFDataCreate (kCFAllocatorDefault, (const UInt8*) state.getData(), (CFIndex) state.getSize())); - CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); - CFDictionarySetValue (dict, key.get(), ourState.get()); - } - } - - return noErr; - } - - ComponentResult RestoreState (CFPropertyListRef inData) override - { - const ScopedValueSetter scope { restoringState, true }; - - { - // Remove the data entry from the state to prevent the superclass loading the parameters - CFUniquePtr copyWithoutData (CFDictionaryCreateMutableCopy (nullptr, 0, (CFDictionaryRef) inData)); - CFDictionaryRemoveValue (copyWithoutData.get(), CFSTR (kAUPresetDataKey)); - ComponentResult err = MusicDeviceBase::RestoreState (copyWithoutData.get()); - - if (err != noErr) - return err; - } - - if (juceFilter != nullptr) - { - CFDictionaryRef dict = (CFDictionaryRef) inData; - CFDataRef data = nullptr; - - CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); - - bool valuePresent = CFDictionaryGetValueIfPresent (dict, key.get(), (const void**) &data); - - if (valuePresent) - { - if (data != nullptr) - { - const int numBytes = (int) CFDataGetLength (data); - const juce::uint8* const rawBytes = CFDataGetBytePtr (data); - - if (numBytes > 0) - { - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - juceFilter->setCurrentProgramStateInformation (rawBytes, numBytes); - #else - juceFilter->setStateInformation (rawBytes, numBytes); - #endif - } - } - } - } - - return noErr; - } - - //============================================================================== - bool busIgnoresLayout ([[maybe_unused]] bool isInput, [[maybe_unused]] int busNr) const - { - #ifdef JucePlugin_PreferredChannelConfigurations - return true; - #else - if (const AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr)) - { - AudioChannelSet discreteRangeSet; - const int n = bus->getDefaultLayout().size(); - for (int i = 0; i < n; ++i) - discreteRangeSet.addChannel ((AudioChannelSet::ChannelType) (256 + i)); - - // if the audioprocessor supports this it cannot - // really be interested in the bus layouts - return bus->isLayoutSupported (discreteRangeSet); - } - - return true; - #endif - } - - UInt32 GetAudioChannelLayout (AudioUnitScope scope, - AudioUnitElement element, - AudioChannelLayout* outLayoutPtr, - bool& outWritable) override - { - outWritable = false; - - const auto info = getElementInfo (scope, element); - - if (info.error != noErr) - return 0; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return 0; - - outWritable = true; - - const size_t sizeInBytes = sizeof (AudioChannelLayout) - sizeof (AudioChannelDescription); - - if (outLayoutPtr != nullptr) - { - zeromem (outLayoutPtr, sizeInBytes); - outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr); - } - - return sizeInBytes; - } - - std::vector GetChannelLayoutTags (AudioUnitScope inScope, AudioUnitElement inElement) override - { - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return {}; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return {}; - - return getSupportedBusLayouts (info.isInput, info.busNr); - } - - OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override - { - const auto info = getElementInfo (scope, element); - - if (info.error != noErr) - return info.error; - - if (busIgnoresLayout (info.isInput, info.busNr)) - return kAudioUnitErr_PropertyNotWritable; - - if (inLayout == nullptr) - return kAudioUnitErr_InvalidPropertyValue; - - auto& ioElement = IOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element); - - const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout); - const int currentNumChannels = static_cast (ioElement.NumberChannels()); - const int newChannelNum = newChannelSet.size(); - - if (currentNumChannels != newChannelNum) - return kAudioUnitErr_InvalidPropertyValue; - - // check if the new layout could be potentially set - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newChannelNum, configs)) - return kAudioUnitErr_FormatNotSupported; - #else - if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet)) - return kAudioUnitErr_FormatNotSupported; - #endif - - getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); - - return noErr; - } - - //============================================================================== - // When parameters are discrete we need to use integer values. - float getMaximumParameterValue ([[maybe_unused]] AudioProcessorParameter* juceParam) - { - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - return 1.0f; - #else - return juceParam->isDiscrete() ? (float) (juceParam->getNumSteps() - 1) : 1.0f; - #endif - } - - ComponentResult GetParameterInfo (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - AudioUnitParameterInfo& outParameterInfo) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inParameterID)) - { - outParameterInfo.unit = kAudioUnitParameterUnit_Generic; - outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString - | kAudioUnitParameterFlag_ValuesHaveStrings); - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; - #endif - - const String name = param->getName (1024); - - // Set whether the param is automatable (unnamed parameters aren't allowed to be automated) - if (name.isEmpty() || ! param->isAutomatable()) - outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; - - const bool isParameterDiscrete = param->isDiscrete(); - - if (! isParameterDiscrete) - outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; - - if (param->isMetaParameter()) - outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; - - auto parameterGroupHierarchy = juceFilter->getParameterTree().getGroupsForParameter (param); - - if (! parameterGroupHierarchy.isEmpty()) - { - outParameterInfo.flags |= kAudioUnitParameterFlag_HasClump; - outParameterInfo.clumpID = (UInt32) parameterGroups.indexOf (parameterGroupHierarchy.getLast()) + 1; - } - - // Is this a meter? - if ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2) - { - outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; - outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; - outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; - } - else - { - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (isParameterDiscrete) - outParameterInfo.unit = param->isBoolean() ? kAudioUnitParameterUnit_Boolean - : kAudioUnitParameterUnit_Indexed; - #endif - } - - MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); - - outParameterInfo.minValue = 0.0f; - outParameterInfo.maxValue = getMaximumParameterValue (param); - outParameterInfo.defaultValue = param->getDefaultValue() * getMaximumParameterValue (param); - jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue - && outParameterInfo.defaultValue <= outParameterInfo.maxValue); - - return noErr; - } - } - - return kAudioUnitErr_InvalidParameter; - } - - ComponentResult GetParameterValueStrings (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - CFArrayRef *outStrings) override - { - if (outStrings == nullptr) - return noErr; - - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inParameterID)) - { - if (param->isDiscrete()) - { - auto index = LegacyAudioParameter::getParamIndex (*juceFilter, param); - - if (auto* valueStrings = parameterValueStringArrays[index]) - { - *outStrings = CFArrayCreate (nullptr, - (const void **) valueStrings->getRawDataPointer(), - valueStrings->size(), - nullptr); - - return noErr; - } - } - } - } - - return kAudioUnitErr_InvalidParameter; - } - - ComponentResult GetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32& outValue) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inID)) - { - const auto normValue = param->getValue(); - - outValue = normValue * getMaximumParameterValue (param); - return noErr; - } - } - - return MusicDeviceBase::GetParameter (inID, inScope, inElement, outValue); - } - - ComponentResult SetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32 inValue, - UInt32 inBufferOffsetInFrames) override - { - if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) - { - if (auto* param = getParameterForAUParameterID (inID)) - { - auto value = inValue / getMaximumParameterValue (param); - - if (value != param->getValue()) - { - inParameterChangedCallback = true; - param->setValueNotifyingHost (value); - } - - return noErr; - } - } - - return MusicDeviceBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); - } - - // No idea what this method actually does or what it should return. Current Apple docs say nothing about it. - // (Note that this isn't marked 'override' in case older versions of the SDK don't include it) - bool CanScheduleParameters() const override { return false; } - - //============================================================================== - bool SupportsTail() override { return true; } - Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); } - - double getSampleRate() - { - if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0) - return Output (0).GetStreamFormat().mSampleRate; - - return 44100.0; - } - - Float64 GetLatency() override - { - const double rate = getSampleRate(); - jassert (rate > 0); - #if JucePlugin_Enable_ARA - jassert (juceFilter->getLatencySamples() == 0 || ! dynamic_cast (juceFilter.get())->isBoundToARA()); - #endif - return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; - } - - class ScopedPlayHead : private AudioPlayHead - { - public: - explicit ScopedPlayHead (JuceAU& juceAudioUnit) - : audioUnit (juceAudioUnit) - { - audioUnit.juceFilter->setPlayHead (this); - } - - ~ScopedPlayHead() override - { - audioUnit.juceFilter->setPlayHead (nullptr); - } - - private: - Optional getPosition() const override - { - PositionInfo info; - - info.setFrameRate ([this]() -> Optional - { - switch (audioUnit.lastTimeStamp.mSMPTETime.mType) - { - case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); - case kSMPTETimeType24: return FrameRate().withBaseRate (24); - case kSMPTETimeType25: return FrameRate().withBaseRate (25); - case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); - case kSMPTETimeType30: return FrameRate().withBaseRate (30); - case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); - case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - case kSMPTETimeType60: return FrameRate().withBaseRate (60); - case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); - case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); - case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - case kSMPTETimeType50: return FrameRate().withBaseRate (50); - default: break; - } - - return {}; - }()); - - double ppqPosition = 0.0; - double bpm = 0.0; - - if (audioUnit.CallHostBeatAndTempo (&ppqPosition, &bpm) == noErr) - { - info.setPpqPosition (ppqPosition); - info.setBpm (bpm); - } - - UInt32 outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat; - float num; - UInt32 den; - - if (audioUnit.CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, - &num, - &den, - &outCurrentMeasureDownBeat) == noErr) - { - info.setTimeSignature (TimeSignature { (int) num, (int) den }); - info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); - } - - double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; - Boolean playing = false, looping = false, playchanged; - - const auto setTimeInSamples = [&] (auto timeInSamples) - { - info.setTimeInSamples ((int64) (timeInSamples + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / audioUnit.getSampleRate()); - }; - - if (audioUnit.CallHostTransportState (&playing, - &playchanged, - &outCurrentSampleInTimeLine, - &looping, - &outCycleStartBeat, - &outCycleEndBeat) == noErr) - { - info.setIsPlaying (playing); - info.setIsLooping (looping); - info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); - setTimeInSamples (outCurrentSampleInTimeLine); - } - else - { - // If the host doesn't support this callback, then use the sample time from lastTimeStamp - setTimeInSamples (audioUnit.lastTimeStamp.mSampleTime); - } - - info.setHostTimeNs ((audioUnit.lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0 - ? makeOptional (audioUnit.timeConversions.hostTimeToNanos (audioUnit.lastTimeStamp.mHostTime)) - : nullopt); - - return info; - } - - JuceAU& audioUnit; - }; - - //============================================================================== - void sendAUEvent (const AudioUnitEventType type, const int juceParamIndex) - { - if (restoringState) - return; - - auEvent.mEventType = type; - auEvent.mArgument.mParameter.mParameterID = getAUParameterIDForIndex (juceParamIndex); - AUEventListenerNotify (nullptr, nullptr, &auEvent); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float /*newValue*/) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - sendAUEvent (kAudioUnitEvent_ParameterValueChange, index); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - audioProcessorChangedUpdater.update (details); - } - - //============================================================================== - // this will only ever be called by the bypass parameter - void parameterValueChanged (int, float) override - { - if (! restoringState) - PropertyChanged (kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); - } - - void parameterGestureChanged (int, bool) override {} - - //============================================================================== - bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override - { - const auto info = getElementInfo (scope, element); - - return ((! IsInitialized()) && (info.error == noErr)); - } - - bool ValidFormat (AudioUnitScope inScope, - AudioUnitElement inElement, - const AudioStreamBasicDescription& inNewFormat) override - { - // DSP Quattro incorrectly uses global scope for the ValidFormat call - if (inScope == kAudioUnitScope_Global) - return ValidFormat (kAudioUnitScope_Input, inElement, inNewFormat) - || ValidFormat (kAudioUnitScope_Output, inElement, inNewFormat); - - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return false; - - if (info.kind == BusKind::wrapperOnly) - return true; - - const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); - const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); - - if (newNumChannels == oldNumChannels) - return true; - - if ([[maybe_unused]] AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr)) - { - if (! MusicDeviceBase::ValidFormat (inScope, inElement, inNewFormat)) - return false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - return AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs); - #else - return bus->isNumberOfChannelsSupported (newNumChannels); - #endif - } - - return false; - } - - // AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed - OSStatus ChangeStreamFormat (AudioUnitScope inScope, - AudioUnitElement inElement, - const AudioStreamBasicDescription& inPrevFormat, - const AudioStreamBasicDescription& inNewFormat) override - { - const auto info = getElementInfo (inScope, inElement); - - if (info.error != noErr) - return info.error; - - AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr); - - const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); - const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs)) - return kAudioUnitErr_FormatNotSupported; - #endif - - // predict channel layout - const auto set = [&] - { - if (info.kind == BusKind::wrapperOnly) - return AudioChannelSet::discreteChannels (newNumChannels); - - if (newNumChannels != oldNumChannels) - return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels); - - return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr); - }(); - - if (set == AudioChannelSet()) - return kAudioUnitErr_FormatNotSupported; - - const auto err = MusicDeviceBase::ChangeStreamFormat (inScope, inElement, inPrevFormat, inNewFormat); - - if (err == noErr) - currentTag = CoreAudioLayouts::toCoreAudio (set); - - return err; - } - - //============================================================================== - ComponentResult Render (AudioUnitRenderActionFlags& ioActionFlags, - const AudioTimeStamp& inTimeStamp, - const UInt32 nFrames) override - { - lastTimeStamp = inTimeStamp; - - // prepare buffers - { - pullInputAudio (ioActionFlags, inTimeStamp, nFrames); - prepareOutputBuffers (nFrames); - audioBuffer.reset(); - } - - ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; - - const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - - // set buffer pointers to minimize copying - { - int chIdx = 0, numChannels = 0; - bool interleaved = false; - AudioBufferList* buffer = nullptr; - - // use output pointers - for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) - { - GetAudioBufferList (false, busIdx, buffer, interleaved, numChannels); - const int* outLayoutMap = mapper.get (false, busIdx); - - for (int ch = 0; ch < numChannels; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); - } - - // use input pointers on remaining channels - for (int busIdx = 0; chIdx < totalInChannels;) - { - int channelIndexInBus = juceFilter->getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); - const bool badData = ! pulledSucceeded[busIdx]; - - if (! badData) - GetAudioBufferList (true, busIdx, buffer, interleaved, numChannels); - - const int* inLayoutMap = mapper.get (true, busIdx); - - const int n = juceFilter->getChannelCountOfBus (true, busIdx); - for (int ch = channelIndexInBus; ch < n; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved || badData ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[ch]].mData)); - } - } - - // copy input - { - for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) - { - if (pulledSucceeded[busIdx]) - audioBuffer.set (busIdx, Input ((UInt32) busIdx).GetBufferList(), mapper.get (true, busIdx)); - else - audioBuffer.clearInputBus (busIdx, (int) nFrames); - } - - audioBuffer.clearUnusedChannels ((int) nFrames); - } - - // swap midi buffers - { - const ScopedLock sl (incomingMidiLock); - midiEvents.clear(); - incomingEvents.swapWith (midiEvents); - } - - // process audio - processBlock (audioBuffer.getBuffer (nFrames), midiEvents); - - // copy back - { - for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) - audioBuffer.get (busIdx, Output ((UInt32) busIdx).GetBufferList(), mapper.get (false, busIdx)); - } - - // process midi output - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - if (! midiEvents.isEmpty() && midiCallback.midiOutputCallback != nullptr) - pushMidiOutput (nFrames); - #endif - - midiEvents.clear(); - - return noErr; - } - - //============================================================================== - ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID*, UInt32, const MusicDeviceNoteParams&) override { return noErr; } - ComponentResult StopNote (MusicDeviceGroupID, NoteInstanceID, UInt32) override { return noErr; } - - //============================================================================== - OSStatus HandleMIDIEvent ([[maybe_unused]] UInt8 inStatus, - [[maybe_unused]] UInt8 inChannel, - [[maybe_unused]] UInt8 inData1, - [[maybe_unused]] UInt8 inData2, - [[maybe_unused]] UInt32 inStartFrame) override - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - const juce::uint8 data[] = { (juce::uint8) (inStatus | inChannel), - (juce::uint8) inData1, - (juce::uint8) inData2 }; - - const ScopedLock sl (incomingMidiLock); - incomingEvents.addEvent (data, 3, (int) inStartFrame); - return noErr; - #else - return kAudioUnitErr_PropertyNotInUse; - #endif - } - - OSStatus HandleSysEx ([[maybe_unused]] const UInt8* inData, [[maybe_unused]] UInt32 inLength) override - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - const ScopedLock sl (incomingMidiLock); - incomingEvents.addEvent (inData, (int) inLength, 0); - return noErr; - #else - return kAudioUnitErr_PropertyNotInUse; - #endif - } - - //============================================================================== - ComponentResult GetPresets (CFArrayRef* outData) const override - { - if (outData != nullptr) - { - const int numPrograms = juceFilter->getNumPrograms(); - - clearPresetsArray(); - presetsArray.insertMultiple (0, AUPreset(), numPrograms); - - CFMutableArrayRef presetsArrayRef = CFArrayCreateMutable (nullptr, numPrograms, nullptr); - - for (int i = 0; i < numPrograms; ++i) - { - String name (juceFilter->getProgramName(i)); - if (name.isEmpty()) - name = "Untitled"; - - AUPreset& p = presetsArray.getReference(i); - p.presetNumber = i; - p.presetName = name.toCFString(); - - CFArrayAppendValue (presetsArrayRef, &p); - } - - *outData = (CFArrayRef) presetsArrayRef; - } - - return noErr; - } - - OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override - { - const int numPrograms = juceFilter->getNumPrograms(); - const SInt32 chosenPresetNumber = (int) inNewFactoryPreset.presetNumber; - - if (chosenPresetNumber >= numPrograms) - return kAudioUnitErr_InvalidProperty; - - AUPreset chosenPreset; - chosenPreset.presetNumber = chosenPresetNumber; - chosenPreset.presetName = juceFilter->getProgramName (chosenPresetNumber).toCFString(); - - juceFilter->setCurrentProgram (chosenPresetNumber); - SetAFactoryPresetAsCurrent (chosenPreset); - - return noErr; - } - - //============================================================================== - class EditorCompHolder : public Component - { - public: - EditorCompHolder (AudioProcessorEditor* const editor) - { - addAndMakeVisible (editor); - - #if ! JucePlugin_EditorRequiresKeyboardFocus - setWantsKeyboardFocus (false); - #else - setWantsKeyboardFocus (true); - #endif - - setBounds (getSizeToContainChild()); - - lastBounds = getBounds(); - } - - ~EditorCompHolder() override - { - deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may - // have been transferred to another parent which takes over ownership. - } - - Rectangle getSizeToContainChild() - { - if (auto* editor = getChildComponent (0)) - return getLocalArea (editor, editor->getLocalBounds()); - - return {}; - } - - static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) - { - auto* editorCompHolder = new EditorCompHolder (editor); - auto r = convertToHostBounds (makeNSRect (editorCompHolder->getSizeToContainChild())); - - static JuceUIViewClass cls; - auto* view = [[cls.createInstance() initWithFrame: r] autorelease]; - - JuceUIViewClass::setFilter (view, filter); - JuceUIViewClass::setAU (view, au); - JuceUIViewClass::setEditor (view, editorCompHolder); - - [view setHidden: NO]; - [view setPostsFrameChangedNotifications: YES]; - - [[NSNotificationCenter defaultCenter] addObserver: view - selector: @selector (applicationWillTerminate:) - name: NSApplicationWillTerminateNotification - object: nil]; - activeUIs.add (view); - - editorCompHolder->addToDesktop (0, (void*) view); - editorCompHolder->setVisible (view); - - return view; - } - - void parentSizeChanged() override - { - resizeHostWindow(); - - if (auto* editor = getChildComponent (0)) - editor->repaint(); - } - - void childBoundsChanged (Component*) override - { - auto b = getSizeToContainChild(); - - if (lastBounds != b) - { - lastBounds = b; - setSize (jmax (32, b.getWidth()), jmax (32, b.getHeight())); - - resizeHostWindow(); - } - } - - bool keyPressed (const KeyPress&) override - { - if (getHostType().isAbletonLive()) - { - static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event - NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; - - if (lastEventTime != eventTime) - { - lastEventTime = eventTime; - - NSView* view = (NSView*) getWindowHandle(); - NSView* hostView = [view superview]; - NSWindow* hostWindow = [hostView window]; - - [hostWindow makeFirstResponder: hostView]; - [hostView keyDown: (NSEvent*) [NSApp currentEvent]]; - [hostWindow makeFirstResponder: view]; - } - } - - return false; - } - - void resizeHostWindow() - { - [CATransaction begin]; - [CATransaction setValue:(id) kCFBooleanTrue forKey:kCATransactionDisableActions]; - - auto rect = convertToHostBounds (makeNSRect (lastBounds)); - auto* view = (NSView*) getWindowHandle(); - - auto superRect = [[view superview] frame]; - superRect.size.width = rect.size.width; - superRect.size.height = rect.size.height; - - [[view superview] setFrame: superRect]; - [view setFrame: rect]; - [CATransaction commit]; - - [view setNeedsDisplay: YES]; - } - - private: - Rectangle lastBounds; - - JUCE_DECLARE_NON_COPYABLE (EditorCompHolder) - }; - - void deleteActiveEditors() - { - for (int i = activeUIs.size(); --i >= 0;) - { - id ui = (id) activeUIs.getUnchecked(i); - - if (JuceUIViewClass::getAU (ui) == this) - JuceUIViewClass::deleteEditor (ui); - } - } - - //============================================================================== - struct JuceUIViewClass : public ObjCClass - { - JuceUIViewClass() : ObjCClass ("JUCEAUView_") - { - addIvar ("filter"); - addIvar ("au"); - addIvar ("editor"); - - addMethod (@selector (dealloc), dealloc); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); - addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow); - addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow); - - registerClass(); - } - - static void deleteEditor (id self) - { - std::unique_ptr editorComp (getEditor (self)); - - if (editorComp != nullptr) - { - if (editorComp->getChildComponent(0) != nullptr - && activePlugins.contains (getAU (self))) // plugin may have been deleted before the UI - { - AudioProcessor* const filter = getIvar (self, "filter"); - filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); - } - - editorComp = nullptr; - setEditor (self, nullptr); - } - } - - static JuceAU* getAU (id self) { return getIvar (self, "au"); } - static EditorCompHolder* getEditor (id self) { return getIvar (self, "editor"); } - - static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } - static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } - static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } - - private: - static void dealloc (id self, SEL) - { - if (activeUIs.contains (self)) - shutdown (self); - - sendSuperclassMessage (self, @selector (dealloc)); - } - - static void applicationWillTerminate (id self, SEL, NSNotification*) - { - shutdown (self); - } - - static void shutdown (id self) - { - [[NSNotificationCenter defaultCenter] removeObserver: self]; - deleteEditor (self); - - jassert (activeUIs.contains (self)); - activeUIs.removeFirstMatchingValue (self); - - if (activePlugins.size() + activeUIs.size() == 0) - { - // there's some kind of component currently modal, but the host - // is trying to delete our plugin.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - - shutdownJuce_GUI(); - } - } - - static void viewDidMoveToWindow (id self, SEL) - { - if (NSWindow* w = [(NSView*) self window]) - { - [w setAcceptsMouseMovedEvents: YES]; - - if (EditorCompHolder* const editorComp = getEditor (self)) - [w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; - } - } - - static BOOL mouseDownCanMoveWindow (id, SEL) - { - return NO; - } - }; - - //============================================================================== - struct JuceUICreationClass : public ObjCClass - { - JuceUICreationClass() : ObjCClass ("JUCE_AUCocoaViewClass_") - { - addMethod (@selector (interfaceVersion), interfaceVersion); - addMethod (@selector (description), description); - addMethod (@selector (uiViewForAudioUnit:withSize:), uiViewForAudioUnit); - - addProtocol (@protocol (AUCocoaUIBase)); - - registerClass(); - } - - private: - static unsigned int interfaceVersion (id, SEL) { return 0; } - - static NSString* description (id, SEL) - { - return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; - } - - static NSView* uiViewForAudioUnit (id, SEL, AudioUnit inAudioUnit, NSSize) - { - void* pointers[2]; - UInt32 propertySize = sizeof (pointers); - - if (AudioUnitGetProperty (inAudioUnit, juceFilterObjectPropertyID, - kAudioUnitScope_Global, 0, pointers, &propertySize) == noErr) - { - if (AudioProcessor* filter = static_cast (pointers[0])) - if (AudioProcessorEditor* editorComp = filter->createEditorIfNeeded()) - { - #if JucePlugin_Enable_ARA - jassert (dynamic_cast (editorComp) != nullptr); - // for proper view embedding, ARA plug-ins must be resizable - jassert (editorComp->isResizable()); - #endif - return EditorCompHolder::createViewFor (filter, static_cast (pointers[1]), editorComp); - } - } - - return nil; - } - }; - -private: - //============================================================================== - /* The call to AUBase::PropertyChanged may allocate hence the need for this class */ - class AudioProcessorChangedUpdater final : private AsyncUpdater - { - public: - explicit AudioProcessorChangedUpdater (JuceAU& o) : owner (o) {} - ~AudioProcessorChangedUpdater() override { cancelPendingUpdate(); } - - void update (const ChangeDetails& details) - { - int flags = 0; - - if (details.latencyChanged) - flags |= latencyChangedFlag; - - if (details.parameterInfoChanged) - flags |= parameterInfoChangedFlag; - - if (details.programChanged) - flags |= programChangedFlag; - - if (flags != 0) - { - callbackFlags.fetch_or (flags); - - if (MessageManager::getInstance()->isThisTheMessageThread()) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); - } - } - - private: - void handleAsyncUpdate() override - { - const auto flags = callbackFlags.exchange (0); - - if ((flags & latencyChangedFlag) != 0) - owner.PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); - - if ((flags & parameterInfoChangedFlag) != 0) - { - owner.PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); - owner.PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); - } - - owner.PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); - - if ((flags & programChangedFlag) != 0) - { - owner.refreshCurrentPreset(); - owner.PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); - } - } - - JuceAU& owner; - - static constexpr int latencyChangedFlag = 1 << 0, - parameterInfoChangedFlag = 1 << 1, - programChangedFlag = 1 << 2; - - std::atomic callbackFlags { 0 }; - }; - - //============================================================================== - AudioUnitHelpers::CoreAudioBufferList audioBuffer; - MidiBuffer midiEvents, incomingEvents; - bool prepared = false, isBypassed = false, restoringState = false; - - //============================================================================== - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - static constexpr bool forceUseLegacyParamIDs = true; - #else - static constexpr bool forceUseLegacyParamIDs = false; - #endif - - //============================================================================== - LegacyAudioParametersWrapper juceParameters; - std::unordered_map paramMap; - Array auParamIDs; - Array parameterGroups; - - // Stores the parameter IDs in the order that they will be reported to the host. - std::vector cachedParameterList; - - //============================================================================== - // According to the docs, this is the maximum size of a MIDIPacketList. - static constexpr UInt32 packetListBytes = 65536; - - CoreAudioTimeConversions timeConversions; - AudioUnitEvent auEvent; - mutable Array presetsArray; - CriticalSection incomingMidiLock; - AUMIDIOutputCallbackStruct midiCallback; - AudioTimeStamp lastTimeStamp; - int totalInChannels, totalOutChannels; - HeapBlock pulledSucceeded; - HeapBlock packetList { packetListBytes, 1 }; - - ThreadLocalValue inParameterChangedCallback; - - AudioProcessorChangedUpdater audioProcessorChangedUpdater { *this }; - - //============================================================================== - Array channelInfo; - Array> supportedInputLayouts, supportedOutputLayouts; - Array currentInputLayout, currentOutputLayout; - - //============================================================================== - AudioUnitHelpers::ChannelRemapper mapper; - - //============================================================================== - OwnedArray> parameterValueStringArrays; - - //============================================================================== - AudioProcessorParameter* bypassParam = nullptr; - - //============================================================================== - static NSRect convertToHostBounds (NSRect pluginRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginRect; - - return NSMakeRect (static_cast (pluginRect.origin.x * desktopScale), - static_cast (pluginRect.origin.y * desktopScale), - static_cast (pluginRect.size.width * desktopScale), - static_cast (pluginRect.size.height * desktopScale)); - } - - static NSRect convertFromHostBounds (NSRect hostRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return hostRect; - - return NSMakeRect (static_cast (hostRect.origin.x / desktopScale), - static_cast (hostRect.origin.y / desktopScale), - static_cast (hostRect.size.width / desktopScale), - static_cast (hostRect.size.height / desktopScale)); - } - - //============================================================================== - void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept - { - const unsigned int numInputBuses = GetScope (kAudioUnitScope_Input).GetNumberOfElements(); - - for (unsigned int i = 0; i < numInputBuses; ++i) - { - auto& input = Input (i); - - const bool succeeded = (input.PullInput (flags, timestamp, i, nFrames) == noErr); - - if ((flags & kAudioUnitRenderAction_OutputIsSilence) != 0 && succeeded) - AudioUnitHelpers::clearAudioBuffer (input.GetBufferList()); - - pulledSucceeded[i] = succeeded; - } - } - - void prepareOutputBuffers (const UInt32 nFrames) noexcept - { - const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); - - for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx) - { - auto& output = Output (busIdx); - - if (output.WillAllocateBuffer()) - output.PrepareBuffer (nFrames); - - if (busIdx >= (UInt32) numProcessorBuses) - AudioUnitHelpers::clearAudioBuffer (output.GetBufferList()); - } - } - - void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept - { - const ScopedLock sl (juceFilter->getCallbackLock()); - const ScopedPlayHead playhead { *this }; - - if (juceFilter->isSuspended()) - { - buffer.clear(); - } - else if (bypassParam == nullptr && isBypassed) - { - juceFilter->processBlockBypassed (buffer, midiBuffer); - } - else - { - juceFilter->processBlock (buffer, midiBuffer); - } - } - - void pushMidiOutput ([[maybe_unused]] UInt32 nFrames) noexcept - { - MIDIPacket* end = nullptr; - - const auto init = [&] - { - end = MIDIPacketListInit (packetList); - }; - - const auto send = [&] - { - midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); - }; - - const auto add = [&] (const MidiMessageMetadata& metadata) - { - end = MIDIPacketListAdd (packetList, - packetListBytes, - end, - static_cast (metadata.samplePosition), - static_cast (metadata.numBytes), - metadata.data); - }; - - init(); - - for (const auto metadata : midiEvents) - { - jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); - - add (metadata); - - if (end == nullptr) - { - send(); - init(); - add (metadata); - - if (end == nullptr) - { - // If this is hit, the size of this midi packet exceeds the maximum size of - // a MIDIPacketList. Large SysEx messages should be broken up into smaller - // chunks. - jassertfalse; - init(); - } - } - } - - send(); - } - - void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) - { - auto* element = Element (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, static_cast (busIdx)).AsIOElement(); - jassert (element != nullptr); - - bufferList = &element->GetBufferList(); - - jassert (bufferList->mNumberBuffers > 0); - - interleaved = AudioUnitHelpers::isAudioBufferInterleaved (*bufferList); - numChannels = static_cast (interleaved ? bufferList->mBuffers[0].mNumberChannels : bufferList->mNumberBuffers); - } - - //============================================================================== - static OSStatus scopeToDirection (AudioUnitScope scope, bool& isInput) noexcept - { - isInput = (scope == kAudioUnitScope_Input); - - return (scope != kAudioUnitScope_Input - && scope != kAudioUnitScope_Output) - ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; - } - - enum class BusKind - { - processor, - wrapperOnly, - }; - - struct ElementInfo - { - int busNr; - BusKind kind; - bool isInput; - OSStatus error; - }; - - ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept - { - bool isInput = false; - OSStatus err; - - if ((err = scopeToDirection (scope, isInput)) != noErr) - return { {}, {}, {}, err }; - - const auto busIdx = static_cast (element); - - if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput))) - return { busIdx, BusKind::processor, isInput, noErr }; - - if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput))) - return { busIdx, BusKind::wrapperOnly, isInput, noErr }; - - return { {}, {}, {}, kAudioUnitErr_InvalidElement }; - } - - OSStatus GetParameterList (AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters) override - { - if (forceUseLegacyParamIDs || inScope != kAudioUnitScope_Global) - return MusicDeviceBase::GetParameterList (inScope, outParameterList, outNumParameters); - - outNumParameters = (UInt32) juceParameters.size(); - - if (outParameterList == nullptr) - return noErr; - - if (cachedParameterList.empty()) - { - struct ParamInfo - { - AudioUnitParameterID identifier; - int versionHint; - }; - - std::vector vec; - vec.reserve (juceParameters.size()); - - for (const auto* param : juceParameters) - vec.push_back ({ generateAUParameterID (*param), param->getVersionHint() }); - - std::sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.identifier < b.identifier; }); - std::stable_sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.versionHint < b.versionHint; }); - std::transform (vec.begin(), vec.end(), std::back_inserter (cachedParameterList), [] (auto x) { return x.identifier; }); - } - - std::copy (cachedParameterList.begin(), cachedParameterList.end(), outParameterList); - - return noErr; - } - - //============================================================================== - void addParameters() - { - parameterGroups = juceFilter->getParameterTree().getSubgroups (true); - - juceParameters.update (*juceFilter, forceUseLegacyParamIDs); - const int numParams = juceParameters.getNumParameters(); - - if (forceUseLegacyParamIDs) - { - Globals()->UseIndexedParameters (static_cast (numParams)); - } - else - { - for (auto* param : juceParameters) - { - const AudioUnitParameterID auParamID = generateAUParameterID (*param); - - // Consider yourself very unlucky if you hit this assertion. The hash codes of your - // parameter ids are not unique. - jassert (paramMap.find (static_cast (auParamID)) == paramMap.end()); - - auParamIDs.add (auParamID); - paramMap.emplace (static_cast (auParamID), param); - Globals()->SetParameter (auParamID, param->getValue()); - } - } - - #if JUCE_DEBUG - // Some hosts can't handle the huge numbers of discrete parameter values created when - // using the default number of steps. - for (auto* param : juceParameters) - if (param->isDiscrete()) - jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); - #endif - - parameterValueStringArrays.ensureStorageAllocated (numParams); - - for (auto* param : juceParameters) - { - OwnedArray* stringValues = nullptr; - - auto initialValue = param->getValue(); - bool paramIsLegacy = dynamic_cast (param) != nullptr; - - if (param->isDiscrete() && (! forceUseLegacyParamIDs)) - { - const auto numSteps = param->getNumSteps(); - stringValues = new OwnedArray(); - stringValues->ensureStorageAllocated (numSteps); - - const auto maxValue = getMaximumParameterValue (param); - - auto getTextValue = [param, paramIsLegacy] (float value) - { - if (paramIsLegacy) - { - param->setValue (value); - return param->getCurrentValueAsText(); - } - - return param->getText (value, 256); - }; - - for (int i = 0; i < numSteps; ++i) - { - auto value = (float) i / maxValue; - stringValues->add (CFStringCreateCopy (nullptr, (getTextValue (value).toCFString()))); - } - } - - if (paramIsLegacy) - param->setValue (initialValue); - - parameterValueStringArrays.add (stringValues); - } - - if ((bypassParam = juceFilter->getBypassParameter()) != nullptr) - bypassParam->addListener (this); - } - - //============================================================================== - static AudioUnitParameterID generateAUParameterID (const AudioProcessorParameter& param) - { - const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceUseLegacyParamIDs); - AudioUnitParameterID paramHash = static_cast (juceParamID.hashCode()); - - #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS - // studio one doesn't like negative parameters - paramHash &= ~(((AudioUnitParameterID) 1) << (sizeof (AudioUnitParameterID) * 8 - 1)); - #endif - - return forceUseLegacyParamIDs ? static_cast (juceParamID.getIntValue()) - : paramHash; - } - - inline AudioUnitParameterID getAUParameterIDForIndex (int paramIndex) const noexcept - { - return forceUseLegacyParamIDs ? static_cast (paramIndex) - : auParamIDs.getReference (paramIndex); - } - - AudioProcessorParameter* getParameterForAUParameterID (AudioUnitParameterID address) const noexcept - { - const auto index = static_cast (address); - - if (forceUseLegacyParamIDs) - return juceParameters.getParamForIndex (index); - - const auto iter = paramMap.find (index); - return iter != paramMap.end() ? iter->second : nullptr; - } - - //============================================================================== - OSStatus syncAudioUnitWithProcessor() - { - OSStatus err = noErr; - const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true); - const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false); - - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (numWrapperInputs))) != noErr) - return err; - - if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (numWrapperOutputs))) != noErr) - return err; - - addSupportedLayoutTags(); - - const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true); - const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false); - - for (int i = 0; i < numProcessorInputs; ++i) - if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) - return err; - - for (int i = 0; i < numProcessorOutputs; ++i) - if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) - return err; - - return noErr; - } - - OSStatus syncProcessorWithAudioUnit() - { - const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); - const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); - - const int numInputElements = static_cast (GetScope (kAudioUnitScope_Input). GetNumberOfElements()); - const int numOutputElements = static_cast (GetScope (kAudioUnitScope_Output).GetNumberOfElements()); - - AudioProcessor::BusesLayout requestedLayouts; - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const int n = (isInput ? numInputBuses : numOutputBuses); - const int numAUElements = (isInput ? numInputElements : numOutputElements); - Array& requestedBuses = (isInput ? requestedLayouts.inputBuses : requestedLayouts.outputBuses); - - for (int busIdx = 0; busIdx < n; ++busIdx) - { - const auto* element = (busIdx < numAUElements ? &IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busIdx) : nullptr); - const int numChannels = (element != nullptr ? static_cast (element->NumberChannels()) : 0); - - AudioChannelLayoutTag currentLayoutTag = isInput ? currentInputLayout[busIdx] : currentOutputLayout[busIdx]; - const int tagNumChannels = currentLayoutTag & 0xffff; - - if (numChannels != tagNumChannels) - return kAudioUnitErr_FormatNotSupported; - - requestedBuses.add (CoreAudioLayouts::fromCoreAudio (currentLayoutTag)); - } - } - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioProcessor::containsLayout (requestedLayouts, configs)) - return kAudioUnitErr_FormatNotSupported; - #endif - - if (! AudioUnitHelpers::setBusesLayout (juceFilter.get(), requestedLayouts)) - return kAudioUnitErr_FormatNotSupported; - - // update total channel count - totalInChannels = juceFilter->getTotalNumInputChannels(); - totalOutChannels = juceFilter->getTotalNumOutputChannels(); - - return noErr; - } - - OSStatus syncAudioUnitWithChannelSet (bool isInput, int busNr, const AudioChannelSet& channelSet) - { - const int numChannels = channelSet.size(); - - getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (channelSet); - - // is this bus activated? - if (numChannels == 0) - return noErr; - - auto& element = IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busNr); - - element.SetName ((CFStringRef) juceStringToNS (juceFilter->getBus (isInput, busNr)->getName())); - - const auto streamDescription = ausdk::ASBD::CreateCommonFloat32 (getSampleRate(), (UInt32) numChannels); - return element.SetStreamFormat (streamDescription); - } - - //============================================================================== - void clearPresetsArray() const - { - for (int i = presetsArray.size(); --i >= 0;) - CFRelease (presetsArray.getReference(i).presetName); - - presetsArray.clear(); - } - - void refreshCurrentPreset() - { - // this will make the AU host re-read and update the current preset name - // in case it was changed here in the plug-in: - - const int currentProgramNumber = juceFilter->getCurrentProgram(); - const String currentProgramName = juceFilter->getProgramName (currentProgramNumber); - - AUPreset currentPreset; - currentPreset.presetNumber = currentProgramNumber; - currentPreset.presetName = currentProgramName.toCFString(); - - SetAFactoryPresetAsCurrent (currentPreset); - } - - //============================================================================== - std::vector& getSupportedBusLayouts (bool isInput, int bus) noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } - const std::vector& getSupportedBusLayouts (bool isInput, int bus) const noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } - AudioChannelLayoutTag& getCurrentLayout (bool isInput, int bus) noexcept { return (isInput ? currentInputLayout : currentOutputLayout).getReference (bus); } - AudioChannelLayoutTag getCurrentLayout (bool isInput, int bus) const noexcept { return (isInput ? currentInputLayout : currentOutputLayout)[bus]; } - - //============================================================================== - std::vector getSupportedLayoutTagsForBus (bool isInput, int busNum) const - { - std::set tags; - - if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNum)) - { - #ifndef JucePlugin_PreferredChannelConfigurations - auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); - - for (auto tag : knownTags) - if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (tag))) - tags.insert (tag); - #endif - - // add discrete layout tags - int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); - - for (int ch = 0; ch < n; ++ch) - { - #ifdef JucePlugin_PreferredChannelConfigurations - const short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNum, ch, configs)) - tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); - #else - if (bus->isLayoutSupported (AudioChannelSet::discreteChannels (ch))) - tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); - #endif - } - } - - return std::vector (tags.begin(), tags.end()); - } - - void addSupportedLayoutTagsForDirection (bool isInput) - { - auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts; - layouts.clearQuick(); - auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput); - - for (int busNr = 0; busNr < numBuses; ++busNr) - layouts.add (getSupportedLayoutTagsForBus (isInput, busNr)); - } - - void addSupportedLayoutTags() - { - currentInputLayout.clear(); currentOutputLayout.clear(); - - currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)); - currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)); - - addSupportedLayoutTagsForDirection (true); - addSupportedLayoutTagsForDirection (false); - } - - static int maxChannelsToProbeFor() - { - return (getHostType().isLogic() ? 8 : 64); - } - - //============================================================================== - void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) - { - if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName - && juceFilter != nullptr && GetContextName() != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = String::fromCFString (GetContextName()); - - juceFilter->updateTrackProperties (props); - } - } - - static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, - AudioUnitScope scope, AudioUnitElement element) - { - static_cast (inRefCon)->auPropertyListener (propId, scope, element); - } - - JUCE_DECLARE_NON_COPYABLE (JuceAU) -}; - -//============================================================================== -#if JucePlugin_ProducesMidiOutput || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - #define FACTORY_BASE_CLASS ausdk::AUMusicDeviceFactory -#else - #define FACTORY_BASE_CLASS ausdk::AUBaseFactory -#endif - -AUSDK_COMPONENT_ENTRY (FACTORY_BASE_CLASS, JuceAU) - -#define JUCE_AU_ENTRY_POINT_NAME JUCE_CONCAT (JucePlugin_AUExportPrefix, Factory) - - extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc); -AUSDK_EXPORT extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc) -{ - return JuceAUFactory (inDesc); -} - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm deleted file mode 100644 index d20062c..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ /dev/null @@ -1,2027 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include "../utility/juce_CheckSettingMacros.h" - -#if JucePlugin_Build_AUv3 - -#if JUCE_MAC && ! (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) - #error AUv3 needs Deployment Target OS X 10.11 or higher to compile -#endif - -#if (JUCE_IOS && defined (__IPHONE_15_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_15_0) \ - || (JUCE_MAC && defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) - #define JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED 1 -#endif - -#ifndef __OBJC2__ - #error AUv3 needs Objective-C 2 support (compile with 64-bit) -#endif - -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 - -#include "../utility/juce_IncludeSystemHeaders.h" -#include "../utility/juce_IncludeModuleHeaders.h" - -#import -#import -#import - -#include -#include -#include -#include -#include - -#if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED - #include -#endif - -#define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) - -#if JUCE_IOS - #define JUCE_IOS_MAC_VIEW UIView -#else - #define JUCE_IOS_MAC_VIEW NSView -#endif - -#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) - -#include - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") - -using namespace juce; - -struct AudioProcessorHolder : public ReferenceCountedObject -{ - AudioProcessorHolder() = default; - explicit AudioProcessorHolder (std::unique_ptr p) : processor (std::move (p)) {} - AudioProcessor& operator*() noexcept { return *processor; } - AudioProcessor* operator->() noexcept { return processor.get(); } - AudioProcessor* get() noexcept { return processor.get(); } - - struct ViewConfig - { - double width; - double height; - bool hostHasMIDIController; - }; - - std::unique_ptr viewConfiguration; - - using Ptr = ReferenceCountedObjectPtr; - -private: - std::unique_ptr processor; - - AudioProcessorHolder& operator= (AudioProcessor*) = delete; - AudioProcessorHolder (AudioProcessorHolder&) = delete; - AudioProcessorHolder& operator= (AudioProcessorHolder&) = delete; -}; - -//============================================================================== -//=========================== The actual AudioUnit ============================= -//============================================================================== -class JuceAudioUnitv3 : public AudioProcessorListener, - public AudioPlayHead, - private AudioProcessorParameter::Listener -{ -public: - JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, - const AudioComponentDescription& descr, - AudioComponentInstantiationOptions options, - NSError** error) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wobjc-method-access") - : au ([getClass().createInstance() initWithComponentDescription: descr - options: options - error: error - juceClass: this]), - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - processorHolder (processor) - { - init(); - } - - JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**) - : au (audioUnit), - processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))) - { - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - initialiseJuce_GUI(); - - init(); - } - - ~JuceAudioUnitv3() override - { - auto& processor = getAudioProcessor(); - processor.removeListener (this); - - if (bypassParam != nullptr) - bypassParam->removeListener (this); - - removeEditor (processor); - } - - //============================================================================== - void init() - { - inParameterChangedCallback = false; - - AudioProcessor& processor = getAudioProcessor(); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast (maxFrames)); - - Array channelInfos; - - for (int i = 0; i < numConfigs; ++i) - { - AUChannelInfo channelInfo; - - channelInfo.inChannels = configs[i][0]; - channelInfo.outChannels = configs[i][1]; - - channelInfos.add (channelInfo); - } - #else - Array channelInfos = AudioUnitHelpers::getAUChannelInfo (processor); - #endif - - processor.setPlayHead (this); - - totalInChannels = processor.getTotalNumInputChannels(); - totalOutChannels = processor.getTotalNumOutputChannels(); - - { - channelCapabilities.reset ([[NSMutableArray alloc] init]); - - for (int i = 0; i < channelInfos.size(); ++i) - { - AUChannelInfo& info = channelInfos.getReference (i); - - [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.inChannels]]; - [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.outChannels]]; - } - } - - internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback); - - processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast (maxFrames)); - processor.prepareToPlay (kDefaultSampleRate, static_cast (maxFrames)); - processor.addListener (this); - - addParameters(); - addPresets(); - - addAudioUnitBusses (true); - addAudioUnitBusses (false); - } - - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } - - //============================================================================== - void reset() - { - midiMessages.clear(); - lastTimeStamp.mSampleTime = std::numeric_limits::max(); - lastTimeStamp.mFlags = 0; - } - - //============================================================================== - AUAudioUnitPreset* getCurrentPreset() const - { - return factoryPresets.getAtIndex (getAudioProcessor().getCurrentProgram()); - } - - void setCurrentPreset (AUAudioUnitPreset* preset) - { - getAudioProcessor().setCurrentProgram (static_cast ([preset number])); - } - - NSArray* getFactoryPresets() const - { - return factoryPresets.get(); - } - - NSDictionary* getFullState() const - { - NSMutableDictionary* retval = [[NSMutableDictionary alloc] init]; - - { - auto* superRetval = ObjCMsgSendSuper*> (au, @selector (fullState)); - - if (superRetval != nullptr) - [retval addEntriesFromDictionary:superRetval]; - } - - juce::MemoryBlock state; - - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - getAudioProcessor().getCurrentProgramStateInformation (state); - #else - getAudioProcessor().getStateInformation (state); - #endif - - if (state.getSize() > 0) - { - NSData* ourState = [[NSData alloc] initWithBytes: state.getData() - length: state.getSize()]; - - NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; - - [retval setObject: ourState - forKey: nsKey]; - - [nsKey release]; - [ourState release]; - } - - return [retval autorelease]; - } - - void setFullState (NSDictionary* state) - { - if (state == nullptr) - return; - - NSMutableDictionary* modifiedState = [[NSMutableDictionary alloc] init]; - [modifiedState addEntriesFromDictionary: state]; - - NSString* nsPresetKey = [[NSString alloc] initWithUTF8String: kAUPresetDataKey]; - [modifiedState removeObjectForKey: nsPresetKey]; - [nsPresetKey release]; - - ObjCMsgSendSuper (au, @selector (setFullState:), state); - - NSString* nsKey = [[NSString alloc] initWithUTF8String: JUCE_STATE_DICTIONARY_KEY]; - NSObject* obj = [modifiedState objectForKey: nsKey]; - [nsKey release]; - - if (obj != nullptr) - { - if ([obj isKindOfClass:[NSData class]]) - { - NSData* data = reinterpret_cast (obj); - const int numBytes = static_cast ([data length]); - const juce::uint8* const rawBytes = reinterpret_cast< const juce::uint8* const> ([data bytes]); - - if (numBytes > 0) - { - #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES - getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes); - #else - getAudioProcessor().setStateInformation (rawBytes, numBytes); - #endif - } - } - } - - [modifiedState release]; - } - - AUParameterTree* getParameterTree() const - { - return paramTree.get(); - } - - NSArray* parametersForOverviewWithCount (int count) const - { - auto* retval = [[[NSMutableArray alloc] init] autorelease]; - - for (const auto& address : addressForIndex) - { - if (static_cast (count) <= [retval count]) - break; - - [retval addObject: [NSNumber numberWithUnsignedLongLong: address]]; - } - - return retval; - } - - //============================================================================== - NSTimeInterval getLatency() const - { - auto& p = getAudioProcessor(); - return p.getLatencySamples() / p.getSampleRate(); - } - - NSTimeInterval getTailTime() const - { - return getAudioProcessor().getTailLengthSeconds(); - } - - //============================================================================== - AUAudioUnitBusArray* getInputBusses() const { return inputBusses.get(); } - AUAudioUnitBusArray* getOutputBusses() const { return outputBusses.get(); } - NSArray* getChannelCapabilities() const { return channelCapabilities.get(); } - - bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) - { - const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); - const int busIdx = static_cast ([auBus index]); - const int newNumChannels = static_cast ([format channelCount]); - - AudioProcessor& processor = getAudioProcessor(); - - if ([[maybe_unused]] AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) - { - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) - return false; - #else - const AVAudioChannelLayout* layout = [format channelLayout]; - const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); - - if (layoutTag != 0) - { - AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); - - if (newLayout.size() != newNumChannels) - return false; - - if (! bus->isLayoutSupported (newLayout)) - return false; - } - else - { - if (! bus->isNumberOfChannelsSupported (newNumChannels)) - return false; - } - #endif - - return true; - } - - return false; - } - - //============================================================================== - int getVirtualMIDICableCount() const - { - #if JucePlugin_WantsMidiInput - return 1; - #else - return 0; - #endif - } - - bool getSupportsMPE() const - { - return getAudioProcessor().supportsMPE(); - } - - NSArray* getMIDIOutputNames() const - { - #if JucePlugin_ProducesMidiOutput - return @[@"MIDI Out"]; - #else - return @[]; - #endif - } - - //============================================================================== - AUInternalRenderBlock getInternalRenderBlock() const { return internalRenderBlock; } - bool getRenderingOffline() const { return getAudioProcessor().isNonRealtime(); } - void setRenderingOffline (bool offline) - { - auto& processor = getAudioProcessor(); - auto isCurrentlyNonRealtime = processor.isNonRealtime(); - - if (isCurrentlyNonRealtime != offline) - { - ScopedLock callbackLock (processor.getCallbackLock()); - - processor.setNonRealtime (offline); - processor.prepareToPlay (processor.getSampleRate(), processor.getBlockSize()); - } - } - - bool getShouldBypassEffect() const - { - if (bypassParam != nullptr) - return (bypassParam->getValue() != 0.0f); - - return (ObjCMsgSendSuper (au, @selector (shouldBypassEffect)) == YES); - } - - void setShouldBypassEffect (bool shouldBypass) - { - if (bypassParam != nullptr) - bypassParam->setValue (shouldBypass ? 1.0f : 0.0f); - - ObjCMsgSendSuper (au, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO); - } - - //============================================================================== - NSString* getContextName() const { return juceStringToNS (contextName); } - void setContextName (NSString* str) - { - if (str != nullptr) - { - AudioProcessor::TrackProperties props; - props.name = nsStringToJuce (str); - - getAudioProcessor().updateTrackProperties (props); - } - } - - //============================================================================== - bool allocateRenderResourcesAndReturnError (NSError **outError) - { - AudioProcessor& processor = getAudioProcessor(); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) - return false; - - if (outError != nullptr) - *outError = nullptr; - - AudioProcessor::BusesLayout layouts; - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - const int n = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); - Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); - - AUAudioUnitBusArray* auBuses = (isInput ? [au inputBusses] : [au outputBusses]); - jassert ([auBuses count] == static_cast (n)); - - for (int busIdx = 0; busIdx < n; ++busIdx) - { - if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) - { - AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast (busIdx)] format]; - - AudioChannelSet newLayout; - const AVAudioChannelLayout* layout = [format channelLayout]; - const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); - - if (layoutTag != 0) - newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); - else - newLayout = bus->supportedLayoutWithChannels (static_cast ([format channelCount])); - - if (newLayout.isDisabled()) - return false; - - channelSets.add (newLayout); - } - } - } - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - - if (! AudioProcessor::containsLayout (layouts, configs)) - { - if (outError != nullptr) - *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; - - return false; - } - #endif - - if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts)) - { - if (outError != nullptr) - *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; - - return false; - } - - totalInChannels = processor.getTotalNumInputChannels(); - totalOutChannels = processor.getTotalNumOutputChannels(); - - allocateBusBuffer (true); - allocateBusBuffer (false); - - mapper.alloc (processor); - - audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (&processor), static_cast (maxFrames)); - - auto sampleRate = [&] - { - for (auto* buffer : { inputBusses.get(), outputBusses.get() }) - if ([buffer count] > 0) - return [[[buffer objectAtIndexedSubscript: 0] format] sampleRate]; - - return 44100.0; - }(); - - processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); - processor.prepareToPlay (sampleRate, static_cast (maxFrames)); - - midiMessages.ensureSize (2048); - midiMessages.clear(); - - hostMusicalContextCallback = [au musicalContextBlock]; - hostTransportStateCallback = [au transportStateBlock]; - - if (@available (macOS 10.13, iOS 11.0, *)) - midiOutputEventBlock = [au MIDIOutputEventBlock]; - - reset(); - - return true; - } - - void deallocateRenderResources() - { - midiOutputEventBlock = nullptr; - - hostMusicalContextCallback = nullptr; - hostTransportStateCallback = nullptr; - - getAudioProcessor().releaseResources(); - audioBuffer.release(); - - inBusBuffers. clear(); - outBusBuffers.clear(); - - mapper.release(); - - ObjCMsgSendSuper (au, @selector (deallocateRenderResources)); - } - - //============================================================================== - struct ScopedKeyChange - { - ScopedKeyChange (AUAudioUnit* a, NSString* k) - : au (a), key (k) - { - [au willChangeValueForKey: key]; - } - - ~ScopedKeyChange() - { - [au didChangeValueForKey: key]; - } - - AUAudioUnit* au; - NSString* key; - }; - - //============================================================================== - void audioProcessorChanged ([[maybe_unused]] AudioProcessor* processor, const ChangeDetails& details) override - { - if (details.programChanged) - { - { - ScopedKeyChange scope (au, @"allParameterValues"); - addPresets(); - } - - { - ScopedKeyChange scope (au, @"currentPreset"); - } - } - - if (details.latencyChanged) - { - ScopedKeyChange scope (au, @"latency"); - } - - if (details.parameterInfoChanged) - { - ScopedKeyChange scope (au, @"parameterTree"); - auto nodes = createParameterNodes (processor->getParameterTree()); - installNewParameterTree (std::move (nodes.nodeArray)); - } - } - - void sendParameterEvent (int idx, const float* newValue, AUParameterAutomationEventType type) - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (auto* juceParam = juceParameters.getParamForIndex (idx)) - { - if (auto* param = [paramTree.get() parameterWithAddress: getAUParameterAddressForIndex (idx)]) - { - const auto value = (newValue != nullptr ? *newValue : juceParam->getValue()) * getMaximumParameterValue (*juceParam); - - if (@available (macOS 10.12, iOS 10.0, *)) - { - [param setValue: value - originator: editorObserverToken.get() - atHostTime: lastTimeStamp.mHostTime - eventType: type]; - } - else if (type == AUParameterAutomationEventTypeValue) - { - [param setValue: value originator: editorObserverToken.get()]; - } - } - } - } - - void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override - { - sendParameterEvent (idx, &newValue, AUParameterAutomationEventTypeValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int idx) override - { - sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeTouch); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int idx) override - { - sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeRelease); - } - - //============================================================================== - Optional getPosition() const override - { - PositionInfo info; - info.setTimeInSamples ((int64) (lastTimeStamp.mSampleTime + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); - - info.setFrameRate ([this] - { - switch (lastTimeStamp.mSMPTETime.mType) - { - case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); - case kSMPTETimeType24: return FrameRate().withBaseRate (24); - case kSMPTETimeType25: return FrameRate().withBaseRate (25); - case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); - case kSMPTETimeType30: return FrameRate().withBaseRate (30); - case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); - case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - case kSMPTETimeType60: return FrameRate().withBaseRate (60); - case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); - case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); - case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); - case kSMPTETimeType50: return FrameRate().withBaseRate (50); - default: break; - } - - return FrameRate(); - }()); - - double num; - NSInteger den; - NSInteger outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat, bpm; - double ppqPosition; - - if (hostMusicalContextCallback != nullptr) - { - AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; - - if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) - { - info.setTimeSignature (TimeSignature { (int) num, (int) den }); - info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); - info.setBpm (bpm); - info.setPpqPosition (ppqPosition); - } - } - - double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; - AUHostTransportStateFlags flags; - - if (hostTransportStateCallback != nullptr) - { - AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; - - if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) - { - info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); - info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); - info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); - info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); - info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); - info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); - } - } - - if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) - info.setHostTimeNs (timeConversions.hostTimeToNanos (lastTimeStamp.mHostTime)); - - return info; - } - - //============================================================================== - static void removeEditor (AudioProcessor& processor) - { - ScopedLock editorLock (processor.getCallbackLock()); - - if (AudioProcessorEditor* editor = processor.getActiveEditor()) - { - processor.editorBeingDeleted (editor); - delete editor; - } - } - - AUAudioUnit* getAudioUnit() const { return au; } - -private: - struct Class : public ObjCClass - { - Class() : ObjCClass ("AUAudioUnit_") - { - addIvar ("cppObject"); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (initWithComponentDescription:options:error:juceClass:), [] (id _self, - SEL, - AudioComponentDescription descr, - AudioComponentInstantiationOptions options, - NSError** error, - JuceAudioUnitv3* juceAU) - { - AUAudioUnit* self = _self; - - self = ObjCMsgSendSuper (self, @selector(initWithComponentDescription:options:error:), descr, options, error); - - setThis (self, juceAU); - return self; - }); - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - addMethod (@selector (initWithComponentDescription:options:error:), [] (id _self, - SEL, - AudioComponentDescription descr, - AudioComponentInstantiationOptions options, - NSError** error) - { - AUAudioUnit* self = _self; - - self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); - - auto* juceAU = JuceAudioUnitv3::create (self, descr, options, error); - - setThis (self, juceAU); - return self; - }); - - addMethod (@selector (dealloc), [] (id self, SEL) - { - if (! MessageManager::getInstance()->isThisTheMessageThread()) - { - WaitableEvent deletionEvent; - - struct AUDeleter : public CallbackMessage - { - AUDeleter (id selfToDelete, WaitableEvent& event) - : parentSelf (selfToDelete), parentDeletionEvent (event) - { - } - - void messageCallback() override - { - delete _this (parentSelf); - parentDeletionEvent.signal(); - } - - id parentSelf; - WaitableEvent& parentDeletionEvent; - }; - - (new AUDeleter (self, deletionEvent))->post(); - deletionEvent.wait (-1); - } - else - { - delete _this (self); - } - }); - - //============================================================================== - addMethod (@selector (reset), [] (id self, SEL) { return _this (self)->reset(); }); - - //============================================================================== - addMethod (@selector (currentPreset), [] (id self, SEL) { return _this (self)->getCurrentPreset(); }); - addMethod (@selector (setCurrentPreset:), [] (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); }); - addMethod (@selector (factoryPresets), [] (id self, SEL) { return _this (self)->getFactoryPresets(); }); - addMethod (@selector (fullState), [] (id self, SEL) { return _this (self)->getFullState(); }); - addMethod (@selector (setFullState:), [] (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); }); - addMethod (@selector (parameterTree), [] (id self, SEL) { return _this (self)->getParameterTree(); }); - addMethod (@selector (parametersForOverviewWithCount:), [] (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); }); - - //============================================================================== - addMethod (@selector (latency), [] (id self, SEL) { return _this (self)->getLatency(); }); - addMethod (@selector (tailTime), [] (id self, SEL) { return _this (self)->getTailTime(); }); - - //============================================================================== - addMethod (@selector (inputBusses), [] (id self, SEL) { return _this (self)->getInputBusses(); }); - addMethod (@selector (outputBusses), [] (id self, SEL) { return _this (self)->getOutputBusses(); }); - addMethod (@selector (channelCapabilities), [] (id self, SEL) { return _this (self)->getChannelCapabilities(); }); - addMethod (@selector (shouldChangeToFormat:forBus:), [] (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; }); - - //============================================================================== - addMethod (@selector (virtualMIDICableCount), [] (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); }); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (supportsMPE), [] (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; }); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - if (@available (macOS 10.13, iOS 11.0, *)) - addMethod (@selector (MIDIOutputNames), [] (id self, SEL) { return _this (self)->getMIDIOutputNames(); }); - - //============================================================================== - addMethod (@selector (internalRenderBlock), [] (id self, SEL) { return _this (self)->getInternalRenderBlock(); }); - addMethod (@selector (canProcessInPlace), [] (id, SEL) { return NO; }); - addMethod (@selector (isRenderingOffline), [] (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; }); - addMethod (@selector (setRenderingOffline:), [] (id self, SEL, BOOL renderingOffline) { return _this (self)->setRenderingOffline (renderingOffline); }); - addMethod (@selector (shouldBypassEffect), [] (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; }); - addMethod (@selector (setShouldBypassEffect:), [] (id self, SEL, BOOL shouldBypass) { return _this (self)->setShouldBypassEffect (shouldBypass); }); - addMethod (@selector (allocateRenderResourcesAndReturnError:), [] (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }); - addMethod (@selector (deallocateRenderResources), [] (id self, SEL) { return _this (self)->deallocateRenderResources(); }); - - //============================================================================== - addMethod (@selector (contextName), [] (id self, SEL) { return _this (self)->getContextName(); }); - addMethod (@selector (setContextName:), [](id self, SEL, NSString* str) { return _this (self)->setContextName (str); }); - - //============================================================================== - if (@available (macOS 10.13, iOS 11.0, *)) - { - addMethod (@selector (supportedViewConfigurations:), [] (id self, SEL, NSArray* configs) - { - auto supportedViewIndices = [[NSMutableIndexSet alloc] init]; - auto n = [configs count]; - - if (auto* editor = _this (self)->getAudioProcessor().createEditorIfNeeded()) - { - // If you hit this assertion then your plug-in's editor is reporting that it doesn't support - // any host MIDI controller configurations! - jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); - - for (auto i = 0u; i < n; ++i) - { - if (auto viewConfiguration = [configs objectAtIndex: i]) - { - if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) - { - auto* constrainer = editor->getConstrainer(); - auto height = (int) [viewConfiguration height]; - auto width = (int) [viewConfiguration width]; - - const auto maxLimits = std::numeric_limits::max() / 2; - const Rectangle requestedBounds { width, height }; - auto modifiedBounds = requestedBounds; - constrainer->checkBounds (modifiedBounds, editor->getBounds().withZeroOrigin(), { maxLimits, maxLimits }, false, false, false, false); - - if (modifiedBounds == requestedBounds) - [supportedViewIndices addIndex: i]; - } - } - } - } - - return [supportedViewIndices autorelease]; - }); - - addMethod (@selector (selectViewConfiguration:), [] (id self, SEL, AUAudioUnitViewConfiguration* config) - { - _this (self)->processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); - }); - } - - registerClass(); - } - - //============================================================================== - static JuceAudioUnitv3* _this (id self) { return getIvar (self, "cppObject"); } - static void setThis (id self, JuceAudioUnitv3* cpp) { object_setInstanceVariable (self, "cppObject", cpp); } - }; - - static JuceAudioUnitv3* create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) - { - return new JuceAudioUnitv3 (audioUnit, descr, options, error); - } - - //============================================================================== - static Class& getClass() - { - static Class result; - return result; - } - - //============================================================================== - struct BusBuffer - { - BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) - : auBus (bus), - maxFrames (maxFramesPerBuffer), - numberOfChannels (static_cast ([[auBus format] channelCount])), - isInterleaved ([[auBus format] isInterleaved]) - { - alloc(); - } - - //============================================================================== - void alloc() - { - const int numBuffers = isInterleaved ? 1 : numberOfChannels; - int bytes = static_cast (sizeof (AudioBufferList)) - + ((numBuffers - 1) * static_cast (sizeof (::AudioBuffer))); - jassert (bytes > 0); - - bufferListStorage.calloc (static_cast (bytes)); - bufferList = reinterpret_cast (bufferListStorage.getData()); - - const int bufferChannels = isInterleaved ? numberOfChannels : 1; - scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames); - } - - void dealloc() - { - bufferList = nullptr; - bufferListStorage.free(); - scratchBuffer.setSize (0, 0); - } - - //============================================================================== - int numChannels() const noexcept { return numberOfChannels; } - bool interleaved() const noexcept { return isInterleaved; } - AudioBufferList* get() const noexcept { return bufferList; } - - //============================================================================== - void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept - { - const int numBuffers = isInterleaved ? 1 : numberOfChannels; - const bool isCompatible = isCompatibleWith (other); - - bufferList->mNumberBuffers = static_cast (numBuffers); - - for (int i = 0; i < numBuffers; ++i) - { - const UInt32 bufferChannels = static_cast (isInterleaved ? numberOfChannels : 1); - bufferList->mBuffers[i].mNumberChannels = bufferChannels; - bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData - : scratchBuffer.getWritePointer (i)); - bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float); - } - } - - //============================================================================== - bool isCompatibleWith (const AudioBufferList* other) const noexcept - { - if (other == nullptr) - return false; - - if (other->mNumberBuffers > 0) - { - const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other); - const int otherChannels = static_cast (otherInterleaved ? other->mBuffers[0].mNumberChannels - : other->mNumberBuffers); - - return otherInterleaved == isInterleaved - && numberOfChannels == otherChannels; - } - - return numberOfChannels == 0; - } - - private: - AUAudioUnitBus* auBus; - HeapBlock bufferListStorage; - AudioBufferList* bufferList = nullptr; - int maxFrames, numberOfChannels; - bool isInterleaved; - juce::AudioBuffer scratchBuffer; - }; - - class FactoryPresets - { - public: - using Presets = std::unique_ptr, NSObjectDeleter>; - - void set (Presets newPresets) - { - std::lock_guard lock (mutex); - std::swap (presets, newPresets); - } - - NSArray* get() const - { - std::lock_guard lock (mutex); - return presets.get(); - } - - AUAudioUnitPreset* getAtIndex (int index) const - { - std::lock_guard lock (mutex); - - if (index < (int) [presets.get() count]) - return [presets.get() objectAtIndex: (unsigned int) index]; - - return nullptr; - } - - private: - Presets presets; - mutable std::mutex mutex; - }; - - //============================================================================== - void addAudioUnitBusses (bool isInput) - { - std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); - AudioProcessor& processor = getAudioProcessor(); - const auto numWrapperBuses = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); - const auto numProcessorBuses = AudioUnitHelpers::getBusCount (processor, isInput); - - for (int i = 0; i < numWrapperBuses; ++i) - { - using AVAudioFormatPtr = std::unique_ptr; - - const auto audioFormat = [&]() -> AVAudioFormatPtr - { - const auto tag = i < numProcessorBuses ? CoreAudioLayouts::toCoreAudio (processor.getChannelLayoutOfBus (isInput, i)) - : kAudioChannelLayoutTag_Stereo; - const std::unique_ptr layout { [[AVAudioChannelLayout alloc] initWithLayoutTag: tag] }; - - if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate - channelLayout: layout.get()] }) - return format; - - const auto channels = i < numProcessorBuses ? processor.getChannelCountOfBus (isInput, i) - : 2; - - // According to the docs, this will fail if the number of channels is greater than 2. - if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate - channels: static_cast (channels)] }) - return format; - - jassertfalse; - return nullptr; - }(); - - using AUAudioUnitBusPtr = std::unique_ptr; - - const auto audioUnitBus = [&]() -> AUAudioUnitBusPtr - { - if (audioFormat != nullptr) - return AUAudioUnitBusPtr { [[AUAudioUnitBus alloc] initWithFormat: audioFormat.get() error: nullptr] }; - - jassertfalse; - return nullptr; - }(); - - if (audioUnitBus != nullptr) - [array.get() addObject: audioUnitBus.get()]; - } - - (isInput ? inputBusses : outputBusses).reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit: au - busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) - busses: array.get()]); - } - - // When parameters are discrete we need to use integer values. - static float getMaximumParameterValue ([[maybe_unused]] const AudioProcessorParameter& juceParam) - { - #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - return 1.0f; - #else - return juceParam.isDiscrete() ? (float) (juceParam.getNumSteps() - 1) : 1.0f; - #endif - } - - static auto createParameter (const AudioProcessorParameter& parameter) - { - const String name (parameter.getName (512)); - - AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; - AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString - | kAudioUnitParameterFlag_ValuesHaveStrings); - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; - #endif - - // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). - if (name.isEmpty() || ! parameter.isAutomatable()) - flags |= kAudioUnitParameterFlag_NonRealTime; - - const bool isParameterDiscrete = parameter.isDiscrete(); - - if (! isParameterDiscrete) - flags |= kAudioUnitParameterFlag_CanRamp; - - if (parameter.isMetaParameter()) - flags |= kAudioUnitParameterFlag_IsGlobalMeta; - - std::unique_ptr valueStrings; - - // Is this a meter? - if (((parameter.getCategory() & 0xffff0000) >> 16) == 2) - { - flags &= ~kAudioUnitParameterFlag_IsWritable; - flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; - unit = kAudioUnitParameterUnit_LinearGain; - } - else - { - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (parameter.isDiscrete()) - { - unit = parameter.isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; - auto maxValue = getMaximumParameterValue (parameter); - auto numSteps = parameter.getNumSteps(); - - // Some hosts can't handle the huge numbers of discrete parameter values created when - // using the default number of steps. - jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); - - valueStrings.reset ([NSMutableArray new]); - - for (int i = 0; i < numSteps; ++i) - [valueStrings.get() addObject: juceStringToNS (parameter.getText ((float) i / maxValue, 0))]; - } - #endif - } - - const auto address = generateAUParameterAddress (parameter); - - auto getParameterIdentifier = [¶meter] - { - if (const auto* paramWithID = dynamic_cast (¶meter)) - return paramWithID->paramID; - - // This could clash if any groups have been given integer IDs! - return String (parameter.getParameterIndex()); - }; - - std::unique_ptr param; - - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) - name: juceStringToNS (name) - address: address - min: 0.0f - max: getMaximumParameterValue (parameter) - unit: unit - unitName: nullptr - flags: flags - valueStrings: valueStrings.get() - dependentParameters: nullptr] - retain]); - } - - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - - [param.get() setValue: parameter.getDefaultValue()]; - return param; - } - - struct NodeArrayResult - { - std::unique_ptr, NSObjectDeleter> nodeArray { [NSMutableArray new] }; - - void addParameter (const AudioProcessorParameter&, std::unique_ptr auParam) - { - [nodeArray.get() addObject: [auParam.get() retain]]; - } - - void addGroup (const AudioProcessorParameterGroup& group, const NodeArrayResult& r) - { - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - [nodeArray.get() addObject: [[AUParameterTree createGroupWithIdentifier: juceStringToNS (group.getID()) - name: juceStringToNS (group.getName()) - children: r.nodeArray.get()] retain]]; - } - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - } - }; - - struct AddressedNodeArrayResult - { - NodeArrayResult nodeArray; - std::map addressForIndex; - - void addParameter (const AudioProcessorParameter& juceParam, std::unique_ptr auParam) - { - const auto index = juceParam.getParameterIndex(); - const auto address = [auParam.get() address]; - - if (const auto iter = addressForIndex.find (index); iter == addressForIndex.cend()) - addressForIndex.emplace (index, address); - else - jassertfalse; // If you hit this assertion then you have put a parameter in two groups. - - nodeArray.addParameter (juceParam, std::move (auParam)); - } - - void addGroup (const AudioProcessorParameterGroup& group, const AddressedNodeArrayResult& r) - { - nodeArray.addGroup (group, r.nodeArray); - - [[maybe_unused]] const auto initialSize = addressForIndex.size(); - addressForIndex.insert (r.addressForIndex.begin(), r.addressForIndex.end()); - [[maybe_unused]] const auto finalSize = addressForIndex.size(); - - // If this is hit, the same parameter index exists in multiple groups. - jassert (finalSize == initialSize + r.addressForIndex.size()); - } - }; - - template - static Result createParameterNodes (const AudioProcessorParameterGroup& group) - { - Result result; - - for (auto* node : group) - { - if (auto* childGroup = node->getGroup()) - { - result.addGroup (*childGroup, createParameterNodes (*childGroup)); - } - else if (auto* juceParam = node->getParameter()) - { - result.addParameter (*juceParam, createParameter (*juceParam)); - } - else - { - // No group or parameter at this node! - jassertfalse; - } - } - - return result; - } - - void addParameters() - { - auto& processor = getAudioProcessor(); - juceParameters.update (processor, forceLegacyParamIDs); - - if ((bypassParam = processor.getBypassParameter()) != nullptr) - bypassParam->addListener (this); - - auto nodes = createParameterNodes (processor.getParameterTree()); - installNewParameterTree (std::move (nodes.nodeArray.nodeArray)); - - // When we first create the parameter tree, we also create structures to allow lookup by index/address. - // These structures are not rebuilt, i.e. we assume that the parameter addresses and indices are stable. - // These structures aren't modified after creation, so there should be no need to synchronize access to them. - - addressForIndex = [&] - { - std::vector addresses (static_cast (processor.getParameters().size())); - - for (size_t i = 0; i < addresses.size(); ++i) - { - if (const auto iter = nodes.addressForIndex.find (static_cast (i)); iter != nodes.addressForIndex.cend()) - addresses[i] = iter->second; - else - jassertfalse; // Somehow, there's a parameter missing... - } - - return addresses; - }(); - - #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS - indexForAddress = [&] - { - std::map indices; - - for (const auto& [index, address] : nodes.addressForIndex) - { - if (const auto iter = indices.find (address); iter == indices.cend()) - indices.emplace (address, index); - else - jassertfalse; // The parameter at index 'iter->first' has the same address as the parameter at index 'index' - } - - return indices; - }(); - #endif - } - - void installNewParameterTree (std::unique_ptr, NSObjectDeleter> topLevelNodes) - { - editorObserverToken.reset(); - - @try - { - // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h - paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); - } - @catch (NSException* exception) - { - // Do you have duplicate identifiers in any of your groups or parameters, - // or do your identifiers have unusual characters in them? - jassertfalse; - } - - [paramTree.get() setImplementorValueObserver: paramObserver]; - [paramTree.get() setImplementorValueProvider: paramProvider]; - [paramTree.get() setImplementorStringFromValueCallback: stringFromValueProvider]; - [paramTree.get() setImplementorValueFromStringCallback: valueFromStringProvider]; - - if (getAudioProcessor().hasEditor()) - { - editorObserverToken = ObserverPtr ([paramTree.get() tokenByAddingParameterObserver: editorParamObserver], - ObserverDestructor { paramTree.get() }); - } - } - - void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) - { - if (value != juceParam->getValue()) - { - juceParam->setValue (value); - - inParameterChangedCallback = true; - juceParam->sendValueChangedMessageToListeners (value); - } - } - - void addPresets() - { - FactoryPresets::Presets newPresets { [[NSMutableArray alloc] init] }; - - const int n = getAudioProcessor().getNumPrograms(); - - for (int idx = 0; idx < n; ++idx) - { - String name = getAudioProcessor().getProgramName (idx); - - std::unique_ptr preset ([[AUAudioUnitPreset alloc] init]); - [preset.get() setName: juceStringToNS (name)]; - [preset.get() setNumber: static_cast (idx)]; - - [newPresets.get() addObject: preset.get()]; - } - - factoryPresets.set (std::move (newPresets)); - } - - //============================================================================== - void allocateBusBuffer (bool isInput) - { - OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; - busBuffers.clear(); - - const int n = AudioUnitHelpers::getBusCountForWrapper (getAudioProcessor(), isInput); - const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; - - for (int busIdx = 0; busIdx < n; ++busIdx) - busBuffers.add (new BusBuffer ([(isInput ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: static_cast (busIdx)], - static_cast (maxFrames))); - } - - //============================================================================== - void processEvents (const AURenderEvent *__nullable realtimeEventListHead, [[maybe_unused]] int numParams, AUEventSampleTime startTime) - { - for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) - { - switch (event->head.eventType) - { - case AURenderEventMIDI: - { - const AUMIDIEvent& midiEvent = event->MIDI; - midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast (midiEvent.eventSampleTime - startTime)); - } - break; - - #if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED - case AURenderEventMIDIEventList: - { - const auto& list = event->MIDIEventsList.eventList; - auto* packet = &list.packet[0]; - - for (uint32_t i = 0; i < list.numPackets; ++i) - { - converter.dispatch (reinterpret_cast (packet->words), - reinterpret_cast (packet->words + packet->wordCount), - static_cast (packet->timeStamp - (MIDITimeStamp) startTime), - [this] (const MidiMessage& message) { midiMessages.addEvent (message, int (message.getTimeStamp())); }); - - packet = MIDIEventPacketNext (packet); - } - } - break; - #endif - - case AURenderEventParameter: - case AURenderEventParameterRamp: - { - const AUParameterEvent& paramEvent = event->parameter; - - if (auto* p = getJuceParameterForAUAddress (paramEvent.parameterAddress)) - { - auto normalisedValue = paramEvent.value / getMaximumParameterValue (*p); - setAudioProcessorParameter (p, normalisedValue); - } - } - break; - - case AURenderEventMIDISysEx: - default: - break; - } - } - } - - AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, - NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead, - AURenderPullInputBlock __nullable pullInputBlock) - { - auto& processor = getAudioProcessor(); - jassert (static_cast (frameCount) <= getAudioProcessor().getBlockSize()); - - const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); - - if (lastTimeStamp.mSampleTime != timestamp->mSampleTime) - { - // process params and incoming midi (only once for a given timestamp) - midiMessages.clear(); - - const int numParams = juceParameters.getNumParameters(); - processEvents (realtimeEventListHead, numParams, static_cast (timestamp->mSampleTime)); - - lastTimeStamp = *timestamp; - - const auto numWrapperBusesIn = AudioUnitHelpers::getBusCountForWrapper (processor, true); - const auto numWrapperBusesOut = AudioUnitHelpers::getBusCountForWrapper (processor, false); - const auto numProcessorBusesIn = AudioUnitHelpers::getBusCount (processor, true); - - // prepare buffers - { - for (int busIdx = 0; busIdx < numWrapperBusesOut; ++busIdx) - { - BusBuffer& busBuffer = *outBusBuffers[busIdx]; - const bool canUseDirectOutput = - (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0); - - busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr); - - if (numProcessorBusesOut <= busIdx) - AudioUnitHelpers::clearAudioBuffer (*busBuffer.get()); - } - - for (int busIdx = 0; busIdx < numWrapperBusesIn; ++busIdx) - { - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - busBuffer.prepare (frameCount, busIdx < numWrapperBusesOut ? outBusBuffers[busIdx]->get() : nullptr); - } - - audioBuffer.reset(); - } - - // pull inputs - { - for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) - { - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr) - AudioUnitHelpers::clearAudioBuffer (*buffer); - - if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0) - AudioUnitHelpers::clearAudioBuffer (*buffer); - } - } - - // set buffer pointer to minimize copying - { - int chIdx = 0; - - for (int busIdx = 0; busIdx < numProcessorBusesOut; ++busIdx) - { - BusBuffer& busBuffer = *outBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - const bool interleaved = busBuffer.interleaved(); - const int numChannels = busBuffer.numChannels(); - - const int* outLayoutMap = mapper.get (false, busIdx); - - for (int ch = 0; ch < numChannels; ++ch) - audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); - } - - // use input pointers on remaining channels - - for (int busIdx = 0; chIdx < totalInChannels;) - { - const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); - - BusBuffer& busBuffer = *inBusBuffers[busIdx]; - AudioBufferList* buffer = busBuffer.get(); - - const int* inLayoutMap = mapper.get (true, busIdx); - audioBuffer.setBuffer (chIdx++, busBuffer.interleaved() ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[channelOffset]].mData)); - } - } - - // copy input - { - for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) - audioBuffer.set (busIdx, *inBusBuffers[busIdx]->get(), mapper.get (true, busIdx)); - - audioBuffer.clearUnusedChannels ((int) frameCount); - } - - // process audio - processBlock (audioBuffer.getBuffer (frameCount), midiMessages); - - // send MIDI - #if JucePlugin_ProducesMidiOutput - if (@available (macOS 10.13, iOS 11.0, *)) - { - if (auto midiOut = midiOutputEventBlock) - for (const auto metadata : midiMessages) - if (isPositiveAndBelow (metadata.samplePosition, frameCount)) - midiOut ((int64_t) metadata.samplePosition + (int64_t) (timestamp->mSampleTime + 0.5), - 0, - metadata.numBytes, - metadata.data); - } - #endif - } - - // copy back - if (outputBusNumber < numProcessorBusesOut && outputData != nullptr) - audioBuffer.get ((int) outputBusNumber, *outputData, mapper.get (false, (int) outputBusNumber)); - - return noErr; - } - - void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept - { - auto& processor = getAudioProcessor(); - const ScopedLock sl (processor.getCallbackLock()); - - if (processor.isSuspended()) - buffer.clear(); - else if (bypassParam == nullptr && [au shouldBypassEffect]) - processor.processBlockBypassed (buffer, midiBuffer); - else - processor.processBlock (buffer, midiBuffer); - } - - //============================================================================== - void valueChangedFromHost (AUParameter* param, AUValue value) - { - if (param != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - auto normalisedValue = value / getMaximumParameterValue (*p); - setAudioProcessorParameter (p, normalisedValue); - } - } - } - - AUValue getValue (AUParameter* param) const - { - if (param != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - return p->getValue() * getMaximumParameterValue (*p); - } - - return 0; - } - - void valueChangedForObserver (AUParameterAddress, AUValue) - { - // this will have already been handled by valueChangedFromHost - } - - NSString* stringFromValue (AUParameter* param, const AUValue* value) - { - String text; - - if (param != nullptr && value != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - if (LegacyAudioParameter::isLegacy (p)) - text = String (*value); - else - text = p->getText (*value / getMaximumParameterValue (*p), 0); - } - } - - return juceStringToNS (text); - } - - AUValue valueFromString (AUParameter* param, NSString* str) - { - if (param != nullptr && str != nullptr) - { - if (auto* p = getJuceParameterForAUAddress ([param address])) - { - const String text (nsStringToJuce (str)); - - if (LegacyAudioParameter::isLegacy (p)) - return text.getFloatValue(); - - return p->getValueForText (text) * getMaximumParameterValue (*p); - } - } - - return 0; - } - - //============================================================================== - // this is only ever called for the bypass parameter - void parameterValueChanged (int, float newValue) override - { - JuceAudioUnitv3::setShouldBypassEffect (newValue != 0.0f); - } - - void parameterGestureChanged (int, bool) override {} - - //============================================================================== - inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept - { - if (isPositiveAndBelow (paramIndex, addressForIndex.size())) - return addressForIndex[static_cast (paramIndex)]; - - return {}; - } - - inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept - { - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (address); - #else - if (const auto iter = indexForAddress.find (address); iter != indexForAddress.cend()) - return iter->second; - - return {}; - #endif - } - - static AUParameterAddress generateAUParameterAddress (const AudioProcessorParameter& param) - { - const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceLegacyParamIDs); - - return static_cast (forceLegacyParamIDs ? juceParamID.getIntValue() - : juceParamID.hashCode64()); - } - - AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept - { - return juceParameters.getParamForIndex (getJuceParameterIndexForAUAddress (address)); - } - - //============================================================================== - static constexpr double kDefaultSampleRate = 44100.0; - - struct ObserverDestructor - { - void operator() (AUParameterObserverToken ptr) const - { - if (ptr != nullptr) - [tree removeParameterObserver: ptr]; - } - - AUParameterTree* tree; - }; - - using ObserverPtr = std::unique_ptr, ObserverDestructor>; - - AUAudioUnit* au; - AudioProcessorHolder::Ptr processorHolder; - - int totalInChannels, totalOutChannels; - - CoreAudioTimeConversions timeConversions; - std::unique_ptr inputBusses, outputBusses; - - ObjCBlock paramObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedFromHost); - ObjCBlock paramProvider = CreateObjCBlock (this, &JuceAudioUnitv3::getValue); - ObjCBlock stringFromValueProvider = CreateObjCBlock (this, &JuceAudioUnitv3::stringFromValue); - ObjCBlock valueFromStringProvider = CreateObjCBlock (this, &JuceAudioUnitv3::valueFromString); - - #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS - std::map indexForAddress; - #endif - std::vector addressForIndex; - LegacyAudioParametersWrapper juceParameters; - - // to avoid recursion on parameter changes, we need to add an - // editor observer to do the parameter changes - std::unique_ptr paramTree; - ObjCBlock editorParamObserver = CreateObjCBlock (this, &JuceAudioUnitv3::valueChangedForObserver); - ObserverPtr editorObserverToken; - - std::unique_ptr, NSObjectDeleter> channelCapabilities; - - FactoryPresets factoryPresets; - - ObjCBlock internalRenderBlock; - - AudioUnitHelpers::CoreAudioBufferList audioBuffer; - AudioUnitHelpers::ChannelRemapper mapper; - - OwnedArray inBusBuffers, outBusBuffers; - MidiBuffer midiMessages; - AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; - - #if JUCE_AUV3_MIDI_EVENT_LIST_SUPPORTED - ump::ToBytestreamDispatcher converter { 2048 }; - #endif - - ObjCBlock hostMusicalContextCallback; - ObjCBlock hostTransportStateCallback; - - AudioTimeStamp lastTimeStamp; - - String contextName; - - ThreadLocalValue inParameterChangedCallback; - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - static constexpr bool forceLegacyParamIDs = true; - #else - static constexpr bool forceLegacyParamIDs = false; - #endif - AudioProcessorParameter* bypassParam = nullptr; -}; - -#if JUCE_IOS -namespace juce -{ -struct UIViewPeerControllerReceiver -{ - virtual ~UIViewPeerControllerReceiver(); - virtual void setViewController (UIViewController*) = 0; -}; -} -#endif - -//============================================================================== -class JuceAUViewController -{ -public: - JuceAUViewController (AUViewController* p) - : myself (p) - { - initialiseJuce_GUI(); - } - - ~JuceAUViewController() - { - JUCE_ASSERT_MESSAGE_THREAD - - if (processorHolder.get() != nullptr) - JuceAudioUnitv3::removeEditor (getAudioProcessor()); - } - - //============================================================================== - void loadView() - { - JUCE_ASSERT_MESSAGE_THREAD - - if (auto p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) - { - processorHolder = new AudioProcessorHolder (std::move (p)); - auto& processor = getAudioProcessor(); - - if (processor.hasEditor()) - { - if (AudioProcessorEditor* editor = processor.createEditorIfNeeded()) - { - preferredSize = editor->getBounds(); - - JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease]; - [myself setView: view]; - - #if JUCE_IOS - editor->setVisible (false); - #else - editor->setVisible (true); - #endif - - editor->addToDesktop (0, view); - - #if JUCE_IOS - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - [peerView setContentMode: UIViewContentModeTop]; - - if (auto* peer = dynamic_cast (editor->getPeer())) - peer->setViewController (myself); - #endif - } - } - } - } - - void viewDidLayoutSubviews() - { - if (auto holder = processorHolder.get()) - { - if ([myself view] != nullptr) - { - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - { - if (holder->viewConfiguration != nullptr) - editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); - - editor->setBounds (convertToRectInt ([[myself view] bounds])); - - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - { - #if JUCE_IOS - [peerView setNeedsDisplay]; - #else - [peerView setNeedsDisplay: YES]; - #endif - } - } - } - } - } - - void didReceiveMemoryWarning() - { - if (auto ptr = processorHolder.get()) - if (auto* processor = ptr->get()) - processor->memoryWarningReceived(); - } - - void viewDidAppear (bool) - { - if (processorHolder.get() != nullptr) - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - editor->setVisible (true); - } - - void viewDidDisappear (bool) - { - if (processorHolder.get() != nullptr) - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) - editor->setVisible (false); - } - - CGSize getPreferredContentSize() const - { - return CGSizeMake (static_cast (preferredSize.getWidth()), - static_cast (preferredSize.getHeight())); - } - - //============================================================================== - AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) - { - const auto holder = [&] - { - if (auto initialisedHolder = processorHolder.get()) - return initialisedHolder; - - waitForExecutionOnMainThread ([this] { [myself view]; }); - return processorHolder.get(); - }(); - - if (holder == nullptr) - return nullptr; - - return [(new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit() autorelease]; - } - -private: - template - static void waitForExecutionOnMainThread (Callback&& callback) - { - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - callback(); - return; - } - - std::promise promise; - - MessageManager::callAsync ([&] - { - callback(); - promise.set_value(); - }); - - promise.get_future().get(); - } - - // There's a chance that createAudioUnit will be called from a background - // thread while the processorHolder is being updated on the main thread. - class LockedProcessorHolder - { - public: - AudioProcessorHolder::Ptr get() const - { - const ScopedLock lock (mutex); - return holder; - } - - LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) - { - const ScopedLock lock (mutex); - holder = other; - return *this; - } - - private: - mutable CriticalSection mutex; - AudioProcessorHolder::Ptr holder; - }; - - //============================================================================== - AUViewController* myself; - LockedProcessorHolder processorHolder; - Rectangle preferredSize { 1, 1 }; - - //============================================================================== - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } -}; - -//============================================================================== -// necessary glue code -@interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController -@end - -@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) -{ - std::unique_ptr cpp; -} - -- (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp.reset (new JuceAUViewController (self)); return self; } -- (void) loadView { cpp->loadView(); } -- (AUAudioUnit *) createAudioUnitWithComponentDescription: (AudioComponentDescription) desc error: (NSError **) error { return cpp->createAudioUnit (desc, error); } -- (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); } - -// NSViewController and UIViewController have slightly different names for this function -- (void) viewDidLayoutSubviews { cpp->viewDidLayoutSubviews(); } -- (void) viewDidLayout { cpp->viewDidLayoutSubviews(); } - -- (void) didReceiveMemoryWarning { cpp->didReceiveMemoryWarning(); } -#if JUCE_IOS -- (void) viewDidAppear: (BOOL) animated { cpp->viewDidAppear (animated); [super viewDidAppear:animated]; } -- (void) viewDidDisappear: (BOOL) animated { cpp->viewDidDisappear (animated); [super viewDidDisappear:animated]; } -#endif -@end - -//============================================================================== -#if JUCE_IOS -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; } -void JUCE_CALLTYPE juce_switchToHostApplication() {} -Image JUCE_CALLTYPE juce_getIAAHostIcon (int) { return {}; } - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2TurtleDumpProgram.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2ManifestHelper.cpp similarity index 94% rename from JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2TurtleDumpProgram.cpp rename to JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2ManifestHelper.cpp index e8ffa07..dd9413d 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2TurtleDumpProgram.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2ManifestHelper.cpp @@ -39,7 +39,7 @@ #include HMODULE dlopen (const TCHAR* filename, int) { return LoadLibrary (filename); } FARPROC dlsym (HMODULE handle, const char* name) { return GetProcAddress (handle, name); } - void printError() + static void printError() { constexpr DWORD numElements = 256; TCHAR messageBuffer[numElements]{}; @@ -76,7 +76,7 @@ LPWSTR* argv = CommandLineToArgvW (GetCommandLineW(), &argc); }; - std::vector toUTF8 (const TCHAR* str) + static std::vector toUTF8 (const TCHAR* str) { const auto numBytes = WideCharToMultiByte (CP_UTF8, 0, str, -1, nullptr, 0, nullptr, nullptr); std::vector result (numBytes); @@ -86,7 +86,7 @@ #else #include - void printError() { printf ("%s\n", dlerror()); } + static void printError() { printf ("%s\n", dlerror()); } class ArgList { public: @@ -106,7 +106,7 @@ const char** argv = nullptr; }; - std::vector toUTF8 (const char* str) { return std::vector (str, str + std::strlen (str) + 1); } + static std::vector toUTF8 (const char* str) { return std::vector (str, str + std::strlen (str) + 1); } #endif // Replicating part of the LV2 header here so that we don't have to set up any diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp deleted file mode 100644 index 6fe2584..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/LV2/juce_LV2_Client.cpp +++ /dev/null @@ -1,1814 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#if JucePlugin_Build_LV2 && (! (JUCE_ANDROID || JUCE_IOS)) - -#ifndef _SCL_SECURE_NO_WARNINGS - #define _SCL_SECURE_NO_WARNINGS -#endif - -#ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS -#endif - -#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include -#include -#include - -#include -#include - -#include "JuceLV2Defines.h" -#include - -#include - -#define JUCE_TURTLE_RECALL_URI "https://lv2-extensions.juce.com/turtle_recall" - -#ifndef JucePlugin_LV2URI - #error "You need to define the JucePlugin_LV2URI value! If you're using the Projucer/CMake, the definition will be written into JuceLV2Defines.h automatically." -#endif - -namespace juce -{ -namespace lv2_client -{ - -constexpr auto uriSeparator = ":"; -const auto JucePluginLV2UriUi = String (JucePlugin_LV2URI) + uriSeparator + "UI"; -const auto JucePluginLV2UriState = String (JucePlugin_LV2URI) + uriSeparator + "StateString"; -const auto JucePluginLV2UriProgram = String (JucePlugin_LV2URI) + uriSeparator + "Program"; - -static const LV2_Feature* findMatchingFeature (const LV2_Feature* const* features, const char* uri) -{ - for (auto feature = features; *feature != nullptr; ++feature) - if (std::strcmp ((*feature)->URI, uri) == 0) - return *feature; - - return nullptr; -} - -static bool hasFeature (const LV2_Feature* const* features, const char* uri) -{ - return findMatchingFeature (features, uri) != nullptr; -} - -template -Data findMatchingFeatureData (const LV2_Feature* const* features, const char* uri) -{ - if (const auto* feature = findMatchingFeature (features, uri)) - return static_cast (feature->data); - - return {}; -} - -static const LV2_Options_Option* findMatchingOption (const LV2_Options_Option* options, LV2_URID urid) -{ - for (auto option = options; option->value != nullptr; ++option) - if (option->key == urid) - return option; - - return nullptr; -} - -class ParameterStorage : private AudioProcessorListener -{ -public: - ParameterStorage (AudioProcessor& proc, LV2_URID_Map map) - : processor (proc), - mapFeature (map), - legacyParameters (proc, false) - { - processor.addListener (this); - } - - ~ParameterStorage() override - { - processor.removeListener (this); - } - - /* This is the string that will be used to uniquely identify the parameter. - - This string will be written into the plugin's manifest as an IRI, so it must be - syntactically valid. - - We escape this string rather than writing the user-defined parameter ID directly to avoid - writing a malformed manifest in the case that user IDs contain spaces or other reserved - characters. This should allow users to keep the same param IDs for all plugin formats. - */ - static String getIri (const AudioProcessorParameter& param) - { - const auto urlSanitised = URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); - const auto ttlSanitised = lv2_shared::sanitiseStringAsTtlName (urlSanitised); - - // If this is hit, the parameter ID could not be represented directly in the plugin ttl. - // We'll replace offending characters with '_'. - jassert (urlSanitised == ttlSanitised); - - return ttlSanitised; - } - - void setValueFromHost (LV2_URID urid, float value) noexcept - { - const auto it = uridToIndexMap.find (urid); - - if (it == uridToIndexMap.end()) - { - // No such parameter. - jassertfalse; - return; - } - - if (auto* param = legacyParameters.getParamForIndex ((int) it->second)) - { - const auto scaledValue = [&] - { - if (auto* rangedParam = dynamic_cast (param)) - return rangedParam->convertTo0to1 (value); - - return value; - }(); - - if (scaledValue != param->getValue()) - { - ScopedValueSetter scope (ignoreCallbacks, true); - param->setValueNotifyingHost (scaledValue); - } - } - } - - struct Options - { - bool parameterValue, gestureBegin, gestureEnd; - }; - - static constexpr auto newClientValue = 1 << 0, - gestureBegan = 1 << 1, - gestureEnded = 1 << 2; - - template - void forEachChangedParameter (Callback&& callback) - { - stateCache.ifSet ([this, &callback] (size_t parameterIndex, float, uint32_t bits) - { - const Options options { (bits & newClientValue) != 0, - (bits & gestureBegan) != 0, - (bits & gestureEnded) != 0 }; - - callback (*legacyParameters.getParamForIndex ((int) parameterIndex), - indexToUridMap[parameterIndex], - options); - }); - } - -private: - void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float value) override - { - if (! ignoreCallbacks) - stateCache.setValueAndBits ((size_t) parameterIndex, value, newClientValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override - { - if (! ignoreCallbacks) - stateCache.setBits ((size_t) parameterIndex, gestureBegan); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override - { - if (! ignoreCallbacks) - stateCache.setBits ((size_t) parameterIndex, gestureEnded); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} - - AudioProcessor& processor; - const LV2_URID_Map mapFeature; - const LegacyAudioParametersWrapper legacyParameters; - const std::vector indexToUridMap = [&] - { - std::vector result; - - for (auto* param : legacyParameters) - { - jassert ((size_t) param->getParameterIndex() == result.size()); - - const auto uri = JucePlugin_LV2URI + String (uriSeparator) + getIri (*param); - const auto urid = mapFeature.map (mapFeature.handle, uri.toRawUTF8()); - result.push_back (urid); - } - - // If this is hit, some parameters have duplicate IDs. - // This may be because the IDs resolve to the same string when removing characters that - // are invalid in a TTL name. - jassert (std::set (result.begin(), result.end()).size() == result.size()); - - return result; - }(); - const std::map uridToIndexMap = [&] - { - std::map result; - size_t index = 0; - - for (const auto& urid : indexToUridMap) - result.emplace (urid, index++); - - return result; - }(); - FlaggedFloatCache<3> stateCache { (size_t) legacyParameters.getNumParameters() }; - bool ignoreCallbacks = false; - - JUCE_LEAK_DETECTOR (ParameterStorage) -}; - -enum class PortKind { seqInput, seqOutput, latencyOutput, freeWheelingInput, enabledInput }; - -struct PortIndices -{ - PortIndices (int numInputsIn, int numOutputsIn) - : numInputs (numInputsIn), numOutputs (numOutputsIn) {} - - int getPortIndexForAudioInput (int audioIndex) const noexcept - { - return audioIndex; - } - - int getPortIndexForAudioOutput (int audioIndex) const noexcept - { - return audioIndex + numInputs; - } - - int getPortIndexFor (PortKind p) const noexcept { return getMaxAudioPortIndex() + (int) p; } - - // Audio ports are numbered from 0 to numInputs + numOutputs - int getMaxAudioPortIndex() const noexcept { return numInputs + numOutputs; } - - int numInputs, numOutputs; -}; - -//============================================================================== -class PlayHead : public AudioPlayHead -{ -public: - PlayHead (LV2_URID_Map mapFeatureIn, double sampleRateIn) - : parser (mapFeatureIn), sampleRate (sampleRateIn) - { - } - - void invalidate() { info = nullopt; } - - void readNewInfo (const LV2_Atom_Event* event) - { - if (event->body.type != mLV2_ATOM__Object && event->body.type != mLV2_ATOM__Blank) - return; - - const auto* object = reinterpret_cast (&event->body); - - if (object->body.otype != mLV2_TIME__Position) - return; - - const LV2_Atom* atomFrame = nullptr; - const LV2_Atom* atomSpeed = nullptr; - const LV2_Atom* atomBar = nullptr; - const LV2_Atom* atomBeat = nullptr; - const LV2_Atom* atomBeatUnit = nullptr; - const LV2_Atom* atomBeatsPerBar = nullptr; - const LV2_Atom* atomBeatsPerMinute = nullptr; - - LV2_Atom_Object_Query query[] { { mLV2_TIME__frame, &atomFrame }, - { mLV2_TIME__speed, &atomSpeed }, - { mLV2_TIME__bar, &atomBar }, - { mLV2_TIME__beat, &atomBeat }, - { mLV2_TIME__beatUnit, &atomBeatUnit }, - { mLV2_TIME__beatsPerBar, &atomBeatsPerBar }, - { mLV2_TIME__beatsPerMinute, &atomBeatsPerMinute }, - LV2_ATOM_OBJECT_QUERY_END }; - - lv2_atom_object_query (object, query); - - info.emplace(); - - // Carla always seems to give us an integral 'beat' even though I'd expect - // it to be a floating-point value - - const auto numerator = parser.parseNumericAtom (atomBeatsPerBar); - const auto denominator = parser.parseNumericAtom (atomBeatUnit); - - if (numerator.hasValue() && denominator.hasValue()) - info->setTimeSignature (TimeSignature { (int) *numerator, (int) *denominator }); - - info->setBpm (parser.parseNumericAtom (atomBeatsPerMinute)); - info->setPpqPosition (parser.parseNumericAtom (atomBeat)); - info->setIsPlaying (parser.parseNumericAtom (atomSpeed).orFallback (0.0f) != 0.0f); - info->setBarCount (parser.parseNumericAtom (atomBar)); - - if (const auto parsed = parser.parseNumericAtom (atomFrame)) - { - info->setTimeInSamples (*parsed); - info->setTimeInSeconds ((double) *parsed / sampleRate); - } - } - - Optional getPosition() const override - { - return info; - } - -private: - lv2_shared::NumericAtomParser parser; - Optional info; - double sampleRate; - - #define X(str) const LV2_URID m##str = parser.map (str); - X (LV2_ATOM__Blank) - X (LV2_ATOM__Object) - X (LV2_TIME__Position) - X (LV2_TIME__beat) - X (LV2_TIME__beatUnit) - X (LV2_TIME__beatsPerBar) - X (LV2_TIME__beatsPerMinute) - X (LV2_TIME__frame) - X (LV2_TIME__speed) - X (LV2_TIME__bar) - #undef X - - JUCE_LEAK_DETECTOR (PlayHead) -}; - -//============================================================================== -class Ports -{ -public: - Ports (LV2_URID_Map map, int numInputsIn, int numOutputsIn) - : forge (map), - indices (numInputsIn, numOutputsIn), - mLV2_ATOM__Sequence (map.map (map.handle, LV2_ATOM__Sequence)) - { - audioBuffers.resize (static_cast (numInputsIn + numOutputsIn), nullptr); - } - - void connect (int port, void* data) - { - // The following is not UB _if_ data really points to an object with the expected type. - - if (port == indices.getPortIndexFor (PortKind::seqInput)) - { - inputData = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::seqOutput)) - { - outputData = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::latencyOutput)) - { - latency = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::freeWheelingInput)) - { - freeWheeling = static_cast (data); - } - else if (port == indices.getPortIndexFor (PortKind::enabledInput)) - { - enabled = static_cast (data); - } - else if (isPositiveAndBelow (port, indices.getMaxAudioPortIndex())) - { - audioBuffers[(size_t) port] = static_cast (data); - } - else - { - // This port was not declared! - jassertfalse; - } - } - - template - void forEachInputEvent (Callback&& callback) - { - if (inputData != nullptr && inputData->atom.type == mLV2_ATOM__Sequence) - for (const auto* event : lv2_shared::SequenceIterator { lv2_shared::SequenceWithSize { inputData } }) - callback (event); - } - - void prepareToWrite() - { - // Note: Carla seems to have a bug (verified with the eg-fifths plugin) where - // the output buffer size is incorrect on alternate calls. - forge.setBuffer (reinterpret_cast (outputData), outputData->atom.size); - } - - void writeLatency (int value) - { - if (latency != nullptr) - *latency = (float) value; - } - - const float* getBufferForAudioInput (int index) const noexcept - { - return audioBuffers[(size_t) indices.getPortIndexForAudioInput (index)]; - } - - float* getBufferForAudioOutput (int index) const noexcept - { - return audioBuffers[(size_t) indices.getPortIndexForAudioOutput (index)]; - } - - bool isFreeWheeling() const noexcept - { - if (freeWheeling != nullptr) - return *freeWheeling > 0.5f; - - return false; - } - - bool isEnabled() const noexcept - { - if (enabled != nullptr) - return *enabled > 0.5f; - - return true; - } - - lv2_shared::AtomForge forge; - PortIndices indices; - -private: - static constexpr auto numParamPorts = 3; - const LV2_Atom_Sequence* inputData = nullptr; - LV2_Atom_Sequence* outputData = nullptr; - float* latency = nullptr; - float* freeWheeling = nullptr; - float* enabled = nullptr; - std::vector audioBuffers; - const LV2_URID mLV2_ATOM__Sequence; - - JUCE_LEAK_DETECTOR (Ports) -}; - -class LV2PluginInstance : private AudioProcessorListener -{ -public: - LV2PluginInstance (double sampleRate, - int64_t maxBlockSize, - const char*, - LV2_URID_Map mapFeatureIn) - : mapFeature (mapFeatureIn), - playHead (mapFeature, sampleRate) - { - processor->addListener (this); - processor->setPlayHead (&playHead); - prepare (sampleRate, (int) maxBlockSize); - } - - void connect (uint32_t port, void* data) - { - ports.connect ((int) port, data); - } - - void activate() {} - - template - static void iterateAudioBuffer (AudioBuffer& ab, UnaryFunction fn) - { - float* const* sampleData = ab.getArrayOfWritePointers(); - - for (int c = ab.getNumChannels(); --c >= 0;) - for (int s = ab.getNumSamples(); --s >= 0;) - fn (sampleData[c][s]); - } - - static int countNaNs (AudioBuffer& ab) noexcept - { - int count = 0; - iterateAudioBuffer (ab, [&count] (float s) - { - if (std::isnan (s)) - ++count; - }); - - return count; - } - - void run (uint32_t numSteps) - { - // If this is hit, the host is trying to process more samples than it told us to prepare - jassert (static_cast (numSteps) <= processor->getBlockSize()); - - midi.clear(); - playHead.invalidate(); - audio.setSize (audio.getNumChannels(), static_cast (numSteps), true, false, true); - - ports.forEachInputEvent ([&] (const LV2_Atom_Event* event) - { - struct Callback - { - explicit Callback (LV2PluginInstance& s) : self (s) {} - - void setParameter (LV2_URID property, float value) const noexcept - { - self.parameters.setValueFromHost (property, value); - } - - // The host probably shouldn't send us 'touched' messages. - void gesture (LV2_URID, bool) const noexcept {} - - LV2PluginInstance& self; - }; - - patchSetHelper.processPatchSet (event, Callback { *this }); - - playHead.readNewInfo (event); - - if (event->body.type == mLV2_MIDI__MidiEvent) - midi.addEvent (event + 1, static_cast (event->body.size), static_cast (event->time.frames)); - }); - - processor->setNonRealtime (ports.isFreeWheeling()); - - for (auto i = 0, end = processor->getTotalNumInputChannels(); i < end; ++i) - audio.copyFrom (i, 0, ports.getBufferForAudioInput (i), audio.getNumSamples()); - - jassert (countNaNs (audio) == 0); - - { - const ScopedLock lock { processor->getCallbackLock() }; - - if (processor->isSuspended()) - { - for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) - { - const auto ptr = ports.getBufferForAudioOutput (i); - std::fill (ptr, ptr + numSteps, 0.0f); - } - } - else - { - const auto isEnabled = ports.isEnabled(); - - if (auto* param = processor->getBypassParameter()) - { - param->setValueNotifyingHost (isEnabled ? 0.0f : 1.0f); - processor->processBlock (audio, midi); - } - else if (isEnabled) - { - processor->processBlock (audio, midi); - } - else - { - processor->processBlockBypassed (audio, midi); - } - } - } - - for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) - { - const auto src = audio.getReadPointer (i); - const auto dst = ports.getBufferForAudioOutput (i); - - if (dst != nullptr) - std::copy (src, src + numSteps, dst); - } - - ports.prepareToWrite(); - auto* forge = ports.forge.get(); - lv2_shared::SequenceFrame sequence { forge, (uint32_t) 0 }; - - parameters.forEachChangedParameter ([&] (const AudioProcessorParameter& param, - LV2_URID paramUrid, - const ParameterStorage::Options& options) - { - const auto sendTouched = [&] (bool state) - { - // TODO Implement begin/end change gesture support once it's supported by LV2 - ignoreUnused (state); - }; - - if (options.gestureBegin) - sendTouched (true); - - if (options.parameterValue) - { - lv2_atom_forge_frame_time (forge, 0); - - lv2_shared::ObjectFrame object { forge, (uint32_t) 0, patchSetHelper.mLV2_PATCH__Set }; - - lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__property); - lv2_atom_forge_urid (forge, paramUrid); - - lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__value); - lv2_atom_forge_float (forge, [&] - { - if (auto* rangedParam = dynamic_cast (¶m)) - return rangedParam->convertFrom0to1 (rangedParam->getValue()); - - return param.getValue(); - }()); - } - - if (options.gestureEnd) - sendTouched (false); - }); - - if (shouldSendStateChange.exchange (false)) - { - lv2_atom_forge_frame_time (forge, 0); - lv2_shared::ObjectFrame { forge, (uint32_t) 0, mLV2_STATE__StateChanged }; - } - - for (const auto meta : midi) - { - const auto bytes = static_cast (meta.numBytes); - lv2_atom_forge_frame_time (forge, meta.samplePosition); - lv2_atom_forge_atom (forge, bytes, mLV2_MIDI__MidiEvent); - lv2_atom_forge_write (forge, meta.data, bytes); - } - - ports.writeLatency (processor->getLatencySamples()); - } - - void deactivate() {} - - LV2_State_Status store (LV2_State_Store_Function storeFn, - LV2_State_Handle handle, - uint32_t, - const LV2_Feature* const*) - { - MemoryBlock block; - processor->getStateInformation (block); - const auto text = block.toBase64Encoding(); - storeFn (handle, - mJucePluginLV2UriState, - text.toRawUTF8(), - text.getNumBytesAsUTF8() + 1, - mLV2_ATOM__String, - LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); - - return LV2_STATE_SUCCESS; - } - - LV2_State_Status retrieve (LV2_State_Retrieve_Function retrieveFn, - LV2_State_Handle handle, - uint32_t, - const LV2_Feature* const*) - { - size_t size = 0; - uint32_t type = 0; - uint32_t dataFlags = 0; - - // Try retrieving a port index (if this is a 'program' preset). - const auto* programData = retrieveFn (handle, mJucePluginLV2UriProgram, &size, &type, &dataFlags); - - if (programData != nullptr && type == mLV2_ATOM__Int && size == sizeof (int32_t)) - { - const auto programIndex = readUnaligned (programData); - processor->setCurrentProgram (programIndex); - return LV2_STATE_SUCCESS; - } - - // This doesn't seem to be a 'program' preset, try setting the full state from a string instead. - const auto* data = retrieveFn (handle, mJucePluginLV2UriState, &size, &type, &dataFlags); - - if (data == nullptr) - return LV2_STATE_ERR_NO_PROPERTY; - - if (type != mLV2_ATOM__String) - return LV2_STATE_ERR_BAD_TYPE; - - String text (static_cast (data), (size_t) size); - MemoryBlock block; - block.fromBase64Encoding (text); - processor->setStateInformation (block.getData(), (int) block.getSize()); - - return LV2_STATE_SUCCESS; - } - - std::unique_ptr createEditor() - { - return std::unique_ptr (processor->createEditorIfNeeded()); - } - - void editorBeingDeleted (AudioProcessorEditor* editor) - { - processor->editorBeingDeleted (editor); - } - - static std::unique_ptr createProcessorInstance() - { - auto result = createPluginFilterOfType (AudioProcessor::wrapperType_LV2); - - #if defined (JucePlugin_PreferredChannelConfigurations) - constexpr short channelConfigurations[][2] { JucePlugin_PreferredChannelConfigurations }; - - static_assert (numElementsInArray (channelConfigurations) > 0, - "JucePlugin_PreferredChannelConfigurations must contain at least one entry"); - static_assert (channelConfigurations[0][0] > 0 || channelConfigurations[0][1] > 0, - "JucePlugin_PreferredChannelConfigurations must have at least one input or output channel"); - result->setPlayConfigDetails (channelConfigurations[0][0], channelConfigurations[0][1], 44100.0, 1024); - - const auto desiredChannels = std::make_tuple (channelConfigurations[0][0], channelConfigurations[0][1]); - const auto actualChannels = std::make_tuple (result->getTotalNumInputChannels(), result->getTotalNumOutputChannels()); - - if (desiredChannels != actualChannels) - Logger::outputDebugString ("Failed to apply requested channel configuration!"); - #else - result->enableAllBuses(); - #endif - - return result; - } - -private: - void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - // Only check for non-parameter state here because: - // - Latency is automatically written every block. - // - There's no way for an LV2 plugin to report an internal program change. - // - Parameter info is hard-coded in the plugin's turtle description. - if (details.nonParameterStateChanged) - shouldSendStateChange = true; - } - - void prepare (double sampleRate, int maxBlockSize) - { - jassert (processor != nullptr); - processor->setRateAndBufferSizeDetails (sampleRate, maxBlockSize); - processor->prepareToPlay (sampleRate, maxBlockSize); - - const auto numChannels = jmax (processor->getTotalNumInputChannels(), - processor->getTotalNumOutputChannels()); - - midi.ensureSize (8192); - audio.setSize (numChannels, maxBlockSize); - audio.clear(); - } - - LV2_URID map (StringRef uri) const { return mapFeature.map (mapFeature.handle, uri); } - - ScopedJuceInitialiser_GUI scopedJuceInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - std::unique_ptr processor = createProcessorInstance(); - const LV2_URID_Map mapFeature; - ParameterStorage parameters { *processor, mapFeature }; - Ports ports { mapFeature, - processor->getTotalNumInputChannels(), - processor->getTotalNumOutputChannels() }; - lv2_shared::PatchSetHelper patchSetHelper { mapFeature, JucePlugin_LV2URI }; - PlayHead playHead; - MidiBuffer midi; - AudioBuffer audio; - std::atomic shouldSendStateChange { false }; - - #define X(str) const LV2_URID m##str = map (str); - X (JucePluginLV2UriProgram) - X (JucePluginLV2UriState) - X (LV2_ATOM__Int) - X (LV2_ATOM__String) - X (LV2_BUF_SIZE__maxBlockLength) - X (LV2_BUF_SIZE__sequenceSize) - X (LV2_MIDI__MidiEvent) - X (LV2_PATCH__Set) - X (LV2_STATE__StateChanged) - #undef X - - JUCE_LEAK_DETECTOR (LV2PluginInstance) -}; - -//============================================================================== -struct RecallFeature -{ - int (*doRecall) (const char*) = [] (const char* libraryPath) -> int - { - const ScopedJuceInitialiser_GUI scope; - const auto processor = LV2PluginInstance::createProcessorInstance(); - - const String pathString { CharPointer_UTF8 { libraryPath } }; - - const auto absolutePath = File::isAbsolutePath (pathString) ? File (pathString) - : File::getCurrentWorkingDirectory().getChildFile (pathString); - - const auto writers = { writeManifestTtl, writeDspTtl, writeUiTtl }; - - const auto wroteSuccessfully = [&processor, &absolutePath] (auto* fn) - { - const auto result = fn (*processor, absolutePath); - - if (! result.wasOk()) - std::cerr << result.getErrorMessage() << '\n'; - - return result.wasOk(); - }; - - return std::all_of (writers.begin(), writers.end(), wroteSuccessfully) ? 0 : 1; - }; - -private: - static String getPresetUri (int index) - { - return JucePlugin_LV2URI + String (uriSeparator) + "preset" + String (index + 1); - } - - static FileOutputStream openStream (const File& libraryPath, StringRef name) - { - return FileOutputStream { libraryPath.getSiblingFile (name + ".ttl") }; - } - - static Result prepareStream (FileOutputStream& stream) - { - if (const auto result = stream.getStatus(); ! result) - return result; - - stream.setPosition (0); - stream.truncate(); - return Result::ok(); - } - - static Result writeManifestTtl (AudioProcessor& proc, const File& libraryPath) - { - auto os = openStream (libraryPath, "manifest"); - - if (const auto result = prepareStream (os); ! result) - return result; - - os << "@prefix lv2: .\n" - "@prefix rdfs: .\n" - "@prefix pset: .\n" - "@prefix state: .\n" - "@prefix ui: .\n" - "@prefix xsd: .\n" - "\n" - "<" JucePlugin_LV2URI ">\n" - "\ta lv2:Plugin ;\n" - "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" - "\trdfs:seeAlso .\n"; - - if (proc.hasEditor()) - { - #if JUCE_MAC - #define JUCE_LV2_UI_KIND "CocoaUI" - #elif JUCE_WINDOWS - #define JUCE_LV2_UI_KIND "WindowsUI" - #elif JUCE_LINUX || JUCE_BSD - #define JUCE_LV2_UI_KIND "X11UI" - #else - #error "LV2 UI is unsupported on this platform" - #endif - - os << "\n" - "<" << JucePluginLV2UriUi << ">\n" - "\ta ui:" JUCE_LV2_UI_KIND " ;\n" - "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" - "\trdfs:seeAlso .\n" - "\n"; - } - - for (auto i = 0, end = proc.getNumPrograms(); i < end; ++i) - { - os << "<" << getPresetUri (i) << ">\n" - "\ta pset:Preset ;\n" - "\tlv2:appliesTo <" JucePlugin_LV2URI "> ;\n" - "\trdfs:label \"" << proc.getProgramName (i) << "\" ;\n" - "\tstate:state [ <" << JucePluginLV2UriProgram << "> \"" << i << "\"^^xsd:int ; ] .\n" - "\n"; - } - - return Result::ok(); - } - - static std::vector findAllSubgroupsDepthFirst (const AudioProcessorParameterGroup& group, - std::vector foundSoFar = {}) - { - foundSoFar.push_back (&group); - - for (auto* node : group) - { - if (auto* subgroup = node->getGroup()) - foundSoFar = findAllSubgroupsDepthFirst (*subgroup, std::move (foundSoFar)); - } - - return foundSoFar; - } - - using GroupSymbolMap = std::map; - - static String getFlattenedGroupSymbol (const AudioProcessorParameterGroup& group, String symbol = "") - { - if (auto* parent = group.getParent()) - return getFlattenedGroupSymbol (*parent, group.getID() + (symbol.isEmpty() ? "" : group.getSeparator() + symbol)); - - return symbol; - } - - static String getSymbolForGroup (const AudioProcessorParameterGroup& group) - { - const String allowedCharacters ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"); - const auto base = getFlattenedGroupSymbol (group); - - if (base.isEmpty()) - return {}; - - String copy; - - for (const auto character : base) - copy << String::charToString (allowedCharacters.containsChar (character) ? character : (juce_wchar) '_'); - - return "paramgroup_" + copy; - } - - static GroupSymbolMap getGroupsAndSymbols (const std::vector& groups) - { - std::set symbols; - GroupSymbolMap result; - - for (const auto& group : groups) - { - const auto symbol = [&] - { - const auto idealSymbol = getSymbolForGroup (*group); - - if (symbols.find (idealSymbol) == symbols.cend()) - return idealSymbol; - - for (auto i = 2; i < std::numeric_limits::max(); ++i) - { - const auto toTest = idealSymbol + "_" + String (i); - - if (symbols.find (toTest) == symbols.cend()) - return toTest; - } - - jassertfalse; - return String(); - }(); - - symbols.insert (symbol); - result.emplace (group, symbol); - } - - return result; - } - - template - static void visitAllParameters (const GroupSymbolMap& groups, Fn&& fn) - { - for (const auto& group : groups) - for (const auto* node : *group.first) - if (auto* param = node->getParameter()) - fn (group.second, *param); - } - - static Result writeDspTtl (AudioProcessor& proc, const File& libraryPath) - { - auto os = openStream (libraryPath, "dsp"); - - if (const auto result = prepareStream (os); ! result) - return result; - - os << "@prefix atom: .\n" - "@prefix bufs: .\n" - "@prefix doap: .\n" - "@prefix foaf: .\n" - "@prefix lv2: .\n" - "@prefix midi: .\n" - "@prefix opts: .\n" - "@prefix param: .\n" - "@prefix patch: .\n" - "@prefix pg: .\n" - "@prefix plug: <" JucePlugin_LV2URI << uriSeparator << "> .\n" - "@prefix pprop: .\n" - "@prefix rdfs: .\n" - "@prefix rdf: .\n" - "@prefix rsz: .\n" - "@prefix state: .\n" - "@prefix time: .\n" - "@prefix ui: .\n" - "@prefix units: .\n" - "@prefix urid: .\n" - "@prefix xsd: .\n" - "\n"; - - LegacyAudioParametersWrapper legacyParameters (proc, false); - - const auto allGroups = findAllSubgroupsDepthFirst (legacyParameters.getGroup()); - const auto groupsAndSymbols = getGroupsAndSymbols (allGroups); - - const auto parameterVisitor = [&] (const String& symbol, - const AudioProcessorParameter& param) - { - os << "plug:" << ParameterStorage::getIri (param) << "\n" - "\ta lv2:Parameter ;\n" - "\trdfs:label \"" << param.getName (1024) << "\" ;\n"; - - if (symbol.isNotEmpty()) - os << "\tpg:group plug:" << symbol << " ;\n"; - - os << "\trdfs:range atom:Float ;\n"; - - if (auto* rangedParam = dynamic_cast (¶m)) - { - os << "\tlv2:default " << rangedParam->convertFrom0to1 (rangedParam->getDefaultValue()) << " ;\n" - "\tlv2:minimum " << rangedParam->getNormalisableRange().start << " ;\n" - "\tlv2:maximum " << rangedParam->getNormalisableRange().end; - } - else - { - os << "\tlv2:default " << param.getDefaultValue() << " ;\n" - "\tlv2:minimum 0.0 ;\n" - "\tlv2:maximum 1.0"; - } - - // Avoid writing out loads of scale points for parameters with lots of steps - constexpr auto stepLimit = 1000; - const auto numSteps = param.getNumSteps(); - - if (param.isDiscrete() && 2 <= numSteps && numSteps < stepLimit) - { - os << "\t ;\n" - "\tlv2:portProperty lv2:enumeration " << (param.isBoolean() ? ", lv2:toggled " : "") << ";\n" - "\tlv2:scalePoint "; - - const auto maxIndex = numSteps - 1; - - for (int i = 0; i < numSteps; ++i) - { - const auto value = (float) i / (float) maxIndex; - const auto text = param.getText (value, 1024); - - os << (i != 0 ? ", " : "") << "[\n" - "\t\trdfs:label \"" << text << "\" ;\n" - "\t\trdf:value " << value << " ;\n" - "\t]"; - } - } - - os << " .\n\n"; - }; - - visitAllParameters (groupsAndSymbols, parameterVisitor); - - for (const auto& groupInfo : groupsAndSymbols) - { - if (groupInfo.second.isEmpty()) - continue; - - os << "plug:" << groupInfo.second << "\n" - "\ta pg:Group ;\n"; - - if (auto* parent = groupInfo.first->getParent()) - { - if (parent->getParent() != nullptr) - { - const auto it = groupsAndSymbols.find (parent); - - if (it != groupsAndSymbols.cend()) - os << "\tpg:subGroupOf plug:" << it->second << " ;\n"; - } - } - - os << "\tlv2:symbol \"" << groupInfo.second << "\" ;\n" - "\tlv2:name \"" << groupInfo.first->getName() << "\" .\n\n"; - } - - const auto getBaseBusName = [] (bool isInput) { return isInput ? "input_group_" : "output_group_"; }; - - for (const auto isInput : { true, false }) - { - const auto baseBusName = getBaseBusName (isInput); - const auto groupKind = isInput ? "InputGroup" : "OutputGroup"; - const auto busCount = proc.getBusCount (isInput); - - for (auto i = 0; i < busCount; ++i) - { - if (const auto* bus = proc.getBus (isInput, i)) - { - os << "plug:" << baseBusName << (i + 1) << "\n" - "\ta pg:" << groupKind << " ;\n" - "\tlv2:name \"" << bus->getName() << "\" ;\n" - "\tlv2:symbol \"" << baseBusName << (i + 1) << "\" .\n\n"; - } - } - } - - os << "<" JucePlugin_LV2URI ">\n"; - - if (proc.hasEditor()) - os << "\tui:ui <" << JucePluginLV2UriUi << "> ;\n"; - - const auto versionParts = StringArray::fromTokens (JucePlugin_VersionString, ".", ""); - - const auto getVersionOrZero = [&] (int indexFromBack) - { - const auto str = versionParts[versionParts.size() - indexFromBack]; - return str.isEmpty() ? 0 : str.getIntValue(); - }; - - const auto minorVersion = getVersionOrZero (2); - const auto microVersion = getVersionOrZero (1); - - os << "\ta " - #if JucePlugin_IsSynth - "lv2:InstrumentPlugin" - #else - "lv2:Plugin" - #endif - " ;\n" - "\tdoap:name \"" JucePlugin_Name "\" ;\n" - "\tdoap:description \"" JucePlugin_Desc "\" ;\n" - "\tlv2:minorVersion " << minorVersion << " ;\n" - "\tlv2:microVersion " << microVersion << " ;\n" - "\tdoap:maintainer [\n" - "\t\ta foaf:Person ;\n" - "\t\tfoaf:name \"" JucePlugin_Manufacturer "\" ;\n" - "\t\tfoaf:homepage <" JucePlugin_ManufacturerWebsite "> ;\n" - "\t\tfoaf:mbox <" JucePlugin_ManufacturerEmail "> ;\n" - "\t] ;\n" - "\tdoap:release [\n" - "\t\ta doap:Version ;\n" - "\t\tdoap:revision \"" JucePlugin_VersionString "\" ;\n" - "\t] ;\n" - "\tlv2:optionalFeature\n" - "\t\tlv2:hardRTCapable ;\n" - "\tlv2:extensionData\n" - "\t\tstate:interface ;\n" - "\tlv2:requiredFeature\n" - "\t\turid:map ,\n" - "\t\topts:options ,\n" - "\t\tbufs:boundedBlockLength ;\n"; - - for (const auto isInput : { true, false }) - { - const auto kind = isInput ? "mainInput" : "mainOutput"; - - if (proc.getBusCount (isInput) > 0) - os << "\tpg:" << kind << " plug:" << getBaseBusName (isInput) << "1 ;\n"; - } - - if (legacyParameters.size() != 0) - { - for (const auto header : { "writable", "readable" }) - { - os << "\tpatch:" << header; - - bool isFirst = true; - - for (const auto* param : legacyParameters) - { - os << (isFirst ? "" : " ,") << "\n\t\tplug:" << ParameterStorage::getIri (*param); - isFirst = false; - } - - os << " ;\n"; - } - } - - os << "\tlv2:port [\n"; - - const PortIndices indices (proc.getTotalNumInputChannels(), - proc.getTotalNumOutputChannels()); - - const auto designationMap = [&] - { - std::map result; - - for (const auto& pair : lv2_shared::channelDesignationMap) - result.emplace (pair.second, pair.first); - - return result; - }(); - - // TODO add support for specific audio group kinds - for (const auto isInput : { true, false }) - { - const auto baseBusName = getBaseBusName (isInput); - const auto portKind = isInput ? "InputPort" : "OutputPort"; - const auto portName = isInput ? "Audio In " : "Audio Out "; - const auto portSymbol = isInput ? "audio_in_" : "audio_out_"; - const auto busCount = proc.getBusCount (isInput); - - auto channelCounter = 0; - - for (auto busIndex = 0; busIndex < busCount; ++busIndex) - { - if (const auto* bus = proc.getBus (isInput, busIndex)) - { - const auto channelCount = bus->getNumberOfChannels(); - const auto optionalBus = ! bus->isEnabledByDefault(); - - for (auto channelIndex = 0; channelIndex < channelCount; ++channelIndex, ++channelCounter) - { - const auto portIndex = isInput ? indices.getPortIndexForAudioInput (channelCounter) - : indices.getPortIndexForAudioOutput (channelCounter); - - os << "\t\ta lv2:" << portKind << " , lv2:AudioPort ;\n" - "\t\tlv2:index " << portIndex << " ;\n" - "\t\tlv2:symbol \"" << portSymbol << (channelCounter + 1) << "\" ;\n" - "\t\tlv2:name \"" << portName << (channelCounter + 1) << "\" ;\n" - "\t\tpg:group plug:" << baseBusName << (busIndex + 1) << " ;\n"; - - if (optionalBus) - os << "\t\tlv2:portProperty lv2:connectionOptional ;\n"; - - const auto designation = bus->getCurrentLayout().getTypeOfChannel (channelIndex); - const auto it = designationMap.find (designation); - - if (it != designationMap.end()) - os << "\t\tlv2:designation <" << it->second << "> ;\n"; - - os << "\t] , [\n"; - } - } - } - } - - // In the event that the plugin decides to send all of its parameters in one go, - // we should ensure that the output buffer is large enough to accommodate, with some - // extra room for the sequence header, MIDI messages etc.. - const auto patchSetSizeBytes = 72; - const auto additionalSize = 8192; - const auto atomPortMinSize = proc.getParameters().size() * patchSetSizeBytes + additionalSize; - - os << "\t\ta lv2:InputPort , atom:AtomPort ;\n" - "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" - "\t\tatom:bufferType atom:Sequence ;\n" - "\t\tatom:supports\n"; - - #if ! JucePlugin_IsSynth && ! JucePlugin_IsMidiEffect - if (proc.acceptsMidi()) - #endif - os << "\t\t\tmidi:MidiEvent ,\n"; - - os << "\t\t\tpatch:Message ,\n" - "\t\t\ttime:Position ;\n" - "\t\tlv2:designation lv2:control ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqInput) << " ;\n" - "\t\tlv2:symbol \"in\" ;\n" - "\t\tlv2:name \"In\" ;\n" - "\t] , [\n" - "\t\ta lv2:OutputPort , atom:AtomPort ;\n" - "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" - "\t\tatom:bufferType atom:Sequence ;\n" - "\t\tatom:supports\n"; - - #if ! JucePlugin_IsMidiEffect - if (proc.producesMidi()) - #endif - os << "\t\t\tmidi:MidiEvent ,\n"; - - os << "\t\t\tpatch:Message ;\n" - "\t\tlv2:designation lv2:control ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqOutput) << " ;\n" - "\t\tlv2:symbol \"out\" ;\n" - "\t\tlv2:name \"Out\" ;\n" - "\t] , [\n" - "\t\ta lv2:OutputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:latency ;\n" - "\t\tlv2:symbol \"latency\" ;\n" - "\t\tlv2:name \"Latency\" ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::latencyOutput) << " ;\n" - "\t\tlv2:portProperty lv2:reportsLatency , lv2:integer , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t\tunits:unit units:frame ;\n" - "\t] , [\n" - "\t\ta lv2:InputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:freeWheeling ;\n" - "\t\tlv2:symbol \"freeWheeling\" ;\n" - "\t\tlv2:name \"Free Wheeling\" ;\n" - "\t\tlv2:default 0.0 ;\n" - "\t\tlv2:minimum 0.0 ;\n" - "\t\tlv2:maximum 1.0 ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::freeWheelingInput) << " ;\n" - "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t] , [\n" - "\t\ta lv2:InputPort , lv2:ControlPort ;\n" - "\t\tlv2:designation lv2:enabled ;\n" - "\t\tlv2:symbol \"enabled\" ;\n" - "\t\tlv2:name \"Enabled\" ;\n" - "\t\tlv2:default 1.0 ;\n" - "\t\tlv2:minimum 0.0 ;\n" - "\t\tlv2:maximum 1.0 ;\n" - "\t\tlv2:index " << indices.getPortIndexFor (PortKind::enabledInput) << " ;\n" - "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" - "\t] ;\n" - "\topts:supportedOption\n" - "\t\tbufs:maxBlockLength .\n"; - - return Result::ok(); - } - - static Result writeUiTtl (AudioProcessor& proc, const File& libraryPath) - { - if (! proc.hasEditor()) - return Result::ok(); - - auto os = openStream (libraryPath, "ui"); - - if (const auto result = prepareStream (os); ! result) - return result; - - const auto editorInstance = rawToUniquePtr (proc.createEditor()); - const auto resizeFeatureString = editorInstance->isResizable() ? "ui:resize" : "ui:noUserResize"; - - os << "@prefix lv2: .\n" - "@prefix opts: .\n" - "@prefix param: .\n" - "@prefix ui: .\n" - "@prefix urid: .\n" - "\n" - "<" << JucePluginLV2UriUi << ">\n" - "\tlv2:extensionData\n" - #if JUCE_LINUX || JUCE_BSD - "\t\tui:idleInterface ,\n" - #endif - "\t\topts:interface ,\n" - "\t\tui:noUserResize ,\n" // resize and noUserResize are always present in the extension data array - "\t\tui:resize ;\n" - "\n" - "\tlv2:requiredFeature\n" - #if JUCE_LINUX || JUCE_BSD - "\t\tui:idleInterface ,\n" - #endif - "\t\turid:map ,\n" - "\t\tui:parent ,\n" - "\t\t ;\n" - "\n" - "\tlv2:optionalFeature\n" - "\t\t" << resizeFeatureString << " ,\n" - "\t\topts:interface ,\n" - "\t\topts:options ;\n\n" - "\topts:supportedOption\n" - "\t\tui:scaleFactor ,\n" - "\t\tparam:sampleRate .\n"; - - return Result::ok(); - } - - JUCE_LEAK_DETECTOR (RecallFeature) -}; - -//============================================================================== -LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index) -{ - if (index != 0) - return nullptr; - - static const LV2_Descriptor descriptor - { - JucePlugin_LV2URI, // TODO some constexpr check that this is a valid URI in terms of RFC 3986 - [] (const LV2_Descriptor*, - double sampleRate, - const char* pathToBundle, - const LV2_Feature* const* features) -> LV2_Handle - { - const auto* mapFeature = findMatchingFeatureData (features, LV2_URID__map); - - if (mapFeature == nullptr) - { - // The host doesn't provide the 'urid map' feature - jassertfalse; - return nullptr; - } - - const auto boundedBlockLength = hasFeature (features, LV2_BUF_SIZE__boundedBlockLength); - - if (! boundedBlockLength) - { - // The host doesn't provide the 'bounded block length' feature - jassertfalse; - return nullptr; - } - - const auto* options = findMatchingFeatureData (features, LV2_OPTIONS__options); - - if (options == nullptr) - { - // The host doesn't provide the 'options' feature - jassertfalse; - return nullptr; - } - - const lv2_shared::NumericAtomParser parser { *mapFeature }; - const auto blockLengthUrid = mapFeature->map (mapFeature->handle, LV2_BUF_SIZE__maxBlockLength); - const auto blockSize = parser.parseNumericOption (findMatchingOption (options, blockLengthUrid)); - - if (! blockSize.hasValue()) - { - // The host doesn't specify a maximum block size - jassertfalse; - return nullptr; - } - - return new LV2PluginInstance { sampleRate, *blockSize, pathToBundle, *mapFeature }; - }, - [] (LV2_Handle instance, uint32_t port, void* data) - { - static_cast (instance)->connect (port, data); - }, - [] (LV2_Handle instance) { static_cast (instance)->activate(); }, - [] (LV2_Handle instance, uint32_t sampleCount) - { - static_cast (instance)->run (sampleCount); - }, - [] (LV2_Handle instance) { static_cast (instance)->deactivate(); }, - [] (LV2_Handle instance) - { - JUCE_AUTORELEASEPOOL - { - delete static_cast (instance); - } - }, - [] (const char* uri) -> const void* - { - const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; - - static RecallFeature recallFeature; - - static LV2_State_Interface stateInterface - { - [] (LV2_Handle instance, - LV2_State_Store_Function store, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -> LV2_State_Status - { - return static_cast (instance)->store (store, handle, flags, features); - }, - [] (LV2_Handle instance, - LV2_State_Retrieve_Function retrieve, - LV2_State_Handle handle, - uint32_t flags, - const LV2_Feature* const* features) -> LV2_State_Status - { - return static_cast (instance)->retrieve (retrieve, handle, flags, features); - } - }; - - static const LV2_Feature features[] { { JUCE_TURTLE_RECALL_URI, &recallFeature }, - { LV2_STATE__interface, &stateInterface } }; - - const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); - return it != std::end (features) ? it->data : nullptr; - } - }; - - return &descriptor; -} - -static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) -{ - if (options == nullptr || symap == nullptr) - return {}; - - const lv2_shared::NumericAtomParser parser { *symap }; - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto* scaleFactorOption = findMatchingOption (options, scaleFactorUrid); - return parser.parseNumericOption (scaleFactorOption); -} - -class LV2UIInstance : private Component, - private ComponentListener -{ -public: - LV2UIInstance (const char*, - const char*, - LV2UI_Write_Function writeFunctionIn, - LV2UI_Controller controllerIn, - LV2UI_Widget* widget, - LV2PluginInstance* pluginIn, - LV2UI_Widget parentIn, - const LV2_URID_Map* symapIn, - const LV2UI_Resize* resizeFeatureIn, - Optional scaleFactorIn) - : writeFunction (writeFunctionIn), - controller (controllerIn), - plugin (pluginIn), - parent (parentIn), - symap (symapIn), - resizeFeature (resizeFeatureIn), - scaleFactor (scaleFactorIn), - editor (plugin->createEditor()) - { - jassert (plugin != nullptr); - jassert (parent != nullptr); - jassert (editor != nullptr); - - if (editor == nullptr) - return; - - const auto bounds = getSizeToContainChild(); - setSize (bounds.getWidth(), bounds.getHeight()); - - addAndMakeVisible (*editor); - - setBroughtToFrontOnMouseClick (true); - setOpaque (true); - setVisible (false); - removeFromDesktop(); - addToDesktop (0, parent); - editor->addComponentListener (this); - - *widget = getWindowHandle(); - - setVisible (true); - - editor->setScaleFactor (getScaleFactor()); - requestResize(); - } - - ~LV2UIInstance() override - { - plugin->editorBeingDeleted (editor.get()); - } - - // This is called by the host when a parameter changes. - // We don't care, our UI will listen to the processor directly. - void portEvent (uint32_t, uint32_t, uint32_t, const void*) {} - - // Called when the host requests a resize - int resize (int width, int height) - { - const ScopedValueSetter scope (hostRequestedResize, true); - setSize (width, height); - return 0; - } - - // Called by the host to give us an opportunity to process UI events - void idleCallback() - { - #if JUCE_LINUX || JUCE_BSD - messageThread->processPendingEvents(); - #endif - } - - void resized() override - { - const ScopedValueSetter scope (hostRequestedResize, true); - - if (editor != nullptr) - { - const auto localArea = editor->getLocalArea (this, getLocalBounds()); - editor->setBoundsConstrained ({ localArea.getWidth(), localArea.getHeight() }); - } - } - - void paint (Graphics& g) override { g.fillAll (Colours::black); } - - uint32_t getOptions (LV2_Options_Option* options) - { - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; - - for (auto* opt = options; opt->key != 0; ++opt) - { - if (opt->context != LV2_OPTIONS_INSTANCE || opt->subject != 0 || opt->key != scaleFactorUrid) - continue; - - if (scaleFactor.hasValue()) - { - opt->type = floatUrid; - opt->size = sizeof (float); - opt->value = &(*scaleFactor); - } - } - - return LV2_OPTIONS_SUCCESS; - } - - uint32_t setOptions (const LV2_Options_Option* options) - { - const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; - - for (auto* opt = options; opt->key != 0; ++opt) - { - if (opt->context != LV2_OPTIONS_INSTANCE - || opt->subject != 0 - || opt->key != scaleFactorUrid - || opt->type != floatUrid - || opt->size != sizeof (float)) - { - continue; - } - - scaleFactor = *static_cast (opt->value); - updateScale(); - } - - return LV2_OPTIONS_SUCCESS; - } - -private: - void updateScale() - { - editor->setScaleFactor (getScaleFactor()); - requestResize(); - } - - Rectangle getSizeToContainChild() const - { - if (editor != nullptr) - return getLocalArea (editor.get(), editor->getLocalBounds()); - - return {}; - } - - float getScaleFactor() const noexcept - { - return scaleFactor.hasValue() ? *scaleFactor : 1.0f; - } - - void componentMovedOrResized (Component&, bool, bool wasResized) override - { - if (! hostRequestedResize && wasResized) - requestResize(); - } - - void write (uint32_t portIndex, uint32_t bufferSize, uint32_t portProtocol, const void* data) - { - writeFunction (controller, portIndex, bufferSize, portProtocol, data); - } - - void requestResize() - { - if (editor == nullptr) - return; - - const auto bounds = getSizeToContainChild(); - - if (resizeFeature == nullptr) - return; - - if (auto* fn = resizeFeature->ui_resize) - fn (resizeFeature->handle, bounds.getWidth(), bounds.getHeight()); - - setSize (bounds.getWidth(), bounds.getHeight()); - repaint(); - } - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - LV2UI_Write_Function writeFunction; - LV2UI_Controller controller; - LV2PluginInstance* plugin; - LV2UI_Widget parent; - const LV2_URID_Map* symap = nullptr; - const LV2UI_Resize* resizeFeature = nullptr; - Optional scaleFactor; - std::unique_ptr editor; - bool hostRequestedResize = false; - - JUCE_LEAK_DETECTOR (LV2UIInstance) -}; - -LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor (uint32_t index) -{ - if (index != 0) - return nullptr; - - static const LV2UI_Descriptor descriptor - { - JucePluginLV2UriUi.toRawUTF8(), // TODO some constexpr check that this is a valid URI in terms of RFC 3986 - [] (const LV2UI_Descriptor*, - const char* pluginUri, - const char* bundlePath, - LV2UI_Write_Function writeFunction, - LV2UI_Controller controller, - LV2UI_Widget* widget, - const LV2_Feature* const* features) -> LV2UI_Handle - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - auto* plugin = findMatchingFeatureData (features, LV2_INSTANCE_ACCESS_URI); - - if (plugin == nullptr) - { - // No instance access - jassertfalse; - return nullptr; - } - - auto* parent = findMatchingFeatureData (features, LV2_UI__parent); - - if (parent == nullptr) - { - // No parent access - jassertfalse; - return nullptr; - } - - auto* resizeFeature = findMatchingFeatureData (features, LV2_UI__resize); - - const auto* symap = findMatchingFeatureData (features, LV2_URID__map); - const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData (features, LV2_OPTIONS__options)); - - return new LV2UIInstance { pluginUri, - bundlePath, - writeFunction, - controller, - widget, - plugin, - parent, - symap, - resizeFeature, - scaleFactor }; - }, - [] (LV2UI_Handle ui) - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - JUCE_AUTORELEASEPOOL - { - delete static_cast (ui); - } - }, - [] (LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) - { - JUCE_ASSERT_MESSAGE_THREAD - static_cast (ui)->portEvent (portIndex, bufferSize, format, buffer); - }, - [] (const char* uri) -> const void* - { - const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; - - static LV2UI_Resize resize { nullptr, [] (LV2UI_Feature_Handle handle, int width, int height) -> int - { - JUCE_ASSERT_MESSAGE_THREAD - return static_cast (handle)->resize (width, height); - } }; - - static LV2UI_Idle_Interface idle { [] (LV2UI_Handle handle) - { - static_cast (handle)->idleCallback(); - return 0; - } }; - - static LV2_Options_Interface options - { - [] (LV2_Handle handle, LV2_Options_Option* optionsIn) - { - return static_cast (handle)->getOptions (optionsIn); - }, - [] (LV2_Handle handle, const LV2_Options_Option* optionsIn) - { - return static_cast (handle)->setOptions (optionsIn); - } - }; - - // We'll always define noUserResize and idle in the extension data array, but we'll - // only declare them in the ui.ttl if the UI is actually non-resizable, or requires - // idle callbacks. - // Well-behaved hosts should check the ttl before trying to search the - // extension-data array. - static const LV2_Feature features[] { { LV2_UI__resize, &resize }, - { LV2_UI__noUserResize, nullptr }, - { LV2_UI__idleInterface, &idle }, - { LV2_OPTIONS__interface, &options } }; - - const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); - return it != std::end (features) ? it->data : nullptr; - } - }; - - return &descriptor; -} - -} -} - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp deleted file mode 100644 index d16570f..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterApp.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include "../utility/juce_CheckSettingMacros.h" - -#include "../utility/juce_IncludeSystemHeaders.h" -#include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_WindowsHooks.h" - -#include -#include -#include - -// You can set this flag in your build if you need to specify a different -// standalone JUCEApplication class for your app to use. If you don't -// set it then by default we'll just create a simple one as below. -#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP - -#include "juce_StandaloneFilterWindow.h" - -namespace juce -{ - -//============================================================================== -class StandaloneFilterApp : public JUCEApplication -{ -public: - StandaloneFilterApp() - { - PropertiesFile::Options options; - - options.applicationName = getApplicationName(); - options.filenameSuffix = ".settings"; - options.osxLibrarySubFolder = "Application Support"; - #if JUCE_LINUX || JUCE_BSD - options.folderName = "~/.config"; - #else - options.folderName = ""; - #endif - - appProperties.setStorageParameters (options); - } - - const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } - const String getApplicationVersion() override { return JucePlugin_VersionString; } - bool moreThanOneInstanceAllowed() override { return true; } - void anotherInstanceStarted (const String&) override {} - - virtual StandaloneFilterWindow* createWindow() - { - #ifdef JucePlugin_PreferredChannelConfigurations - StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; - #endif - - return new StandaloneFilterWindow (getApplicationName(), - LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), - appProperties.getUserSettings(), - false, {}, nullptr - #ifdef JucePlugin_PreferredChannelConfigurations - , juce::Array (channels, juce::numElementsInArray (channels)) - #else - , {} - #endif - #if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE - , false - #endif - ); - } - - //============================================================================== - void initialise (const String&) override - { - mainWindow.reset (createWindow()); - - #if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE - Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); - #endif - - mainWindow->setVisible (true); - } - - void shutdown() override - { - mainWindow = nullptr; - appProperties.saveIfNeeded(); - } - - //============================================================================== - void systemRequestedQuit() override - { - if (mainWindow.get() != nullptr) - mainWindow->pluginHolder->savePluginState(); - - if (ModalComponentManager::getInstance()->cancelAllModalComponents()) - { - Timer::callAfterDelay (100, []() - { - if (auto app = JUCEApplicationBase::getInstance()) - app->systemRequestedQuit(); - }); - } - else - { - quit(); - } - } - -protected: - ApplicationProperties appProperties; - std::unique_ptr mainWindow; -}; - -} // namespace juce - -#if JucePlugin_Build_Standalone && JUCE_IOS - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -using namespace juce; - -bool JUCE_CALLTYPE juce_isInterAppAudioConnected() -{ - if (auto holder = StandalonePluginHolder::getInstance()) - return holder->isInterAppAudioConnected(); - - return false; -} - -void JUCE_CALLTYPE juce_switchToHostApplication() -{ - if (auto holder = StandalonePluginHolder::getInstance()) - holder->switchToHostApplication(); -} - -Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) -{ - if (auto holder = StandalonePluginHolder::getInstance()) - return holder->getIAAHostIcon (size); - - return Image(); -} - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -#endif - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index 3801208..bfdbda3 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -26,7 +26,7 @@ #pragma once #ifndef DOXYGEN - #include "../utility/juce_CreatePluginFilter.h" + #include #endif namespace juce @@ -209,9 +209,12 @@ class StandalonePluginHolder : private AudioIODeviceCallback, processor->getStateInformation (data); if (! fc.getResult().replaceWithData (data.getData(), data.getSize())) - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - TRANS("Error whilst saving"), - TRANS("Couldn't write to the specified file!")); + { + auto opts = MessageBoxOptions::makeOptionsOk (AlertWindow::WarningIcon, + TRANS ("Error whilst saving"), + TRANS ("Couldn't write to the specified file!")); + messageBox = AlertWindow::showScopedAsync (opts, nullptr); + } }); } @@ -234,11 +237,16 @@ class StandalonePluginHolder : private AudioIODeviceCallback, MemoryBlock data; if (fc.getResult().loadFileAsData (data)) + { processor->setStateInformation (data.getData(), (int) data.getSize()); + } else - AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, - TRANS("Error whilst loading"), - TRANS("Couldn't read from the specified file!")); + { + auto opts = MessageBoxOptions::makeOptionsOk (AlertWindow::WarningIcon, + TRANS ("Error whilst loading"), + TRANS ("Couldn't read from the specified file!")); + messageBox = AlertWindow::showScopedAsync (opts, nullptr); + } }); } @@ -417,6 +425,7 @@ class StandalonePluginHolder : private AudioIODeviceCallback, Array lastMidiDevices; std::unique_ptr stateFileChooser; + ScopedMessageBox messageBox; private: /* This class can be used to ensure that audio callbacks use buffers with a @@ -1080,57 +1089,17 @@ class StandaloneFilterWindow : public DocumentWindow, accordingly. The end result is that the peer is resized twice in a row to different sizes, which can appear glitchy/flickery to the user. */ - struct DecoratorConstrainer : public ComponentBoundsConstrainer + class DecoratorConstrainer : public BorderedComponentBoundsConstrainer { - void checkBounds (Rectangle& bounds, - const Rectangle& previousBounds, - const Rectangle& limits, - bool isStretchingTop, - bool isStretchingLeft, - bool isStretchingBottom, - bool isStretchingRight) override + public: + ComponentBoundsConstrainer* getWrappedConstrainer() const override { - auto* decorated = contentComponent != nullptr ? contentComponent->getEditorConstrainer() - : nullptr; + return contentComponent != nullptr ? contentComponent->getEditorConstrainer() : nullptr; + } - if (decorated != nullptr) - { - const auto border = contentComponent->computeBorder(); - const auto requestedBounds = bounds; - - border.subtractFrom (bounds); - decorated->checkBounds (bounds, - border.subtractedFrom (previousBounds), - limits, - isStretchingTop, - isStretchingLeft, - isStretchingBottom, - isStretchingRight); - border.addTo (bounds); - bounds = bounds.withPosition (requestedBounds.getPosition()); - - if (isStretchingTop && ! isStretchingBottom) - bounds = bounds.withBottomY (previousBounds.getBottom()); - - if (! isStretchingTop && isStretchingBottom) - bounds = bounds.withY (previousBounds.getY()); - - if (isStretchingLeft && ! isStretchingRight) - bounds = bounds.withRightX (previousBounds.getRight()); - - if (! isStretchingLeft && isStretchingRight) - bounds = bounds.withX (previousBounds.getX()); - } - else - { - ComponentBoundsConstrainer::checkBounds (bounds, - previousBounds, - limits, - isStretchingTop, - isStretchingLeft, - isStretchingBottom, - isStretchingRight); - } + BorderSize getAdditionalBorder() const override + { + return contentComponent != nullptr ? contentComponent->computeBorder() : BorderSize{}; } void setMainContentComponent (MainContentComponent* in) { contentComponent = in; } diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h b/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h index c95e840..2f50a7b 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h @@ -29,7 +29,7 @@ //============================================================================== #define UNITY_AUDIO_PLUGIN_API_VERSION 0x010401 -#if JUCE_MSVC +#if JUCE_WINDOWS #define UNITY_INTERFACE_API __stdcall #define UNITY_INTERFACE_EXPORT __declspec(dllexport) #else diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp deleted file mode 100644 index 97ea35d..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp +++ /dev/null @@ -1,774 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include - -#if JucePlugin_Build_Unity - -#include "../utility/juce_IncludeModuleHeaders.h" -#include - -#if JUCE_WINDOWS - #include "../utility/juce_IncludeSystemHeaders.h" -#endif - -#include "juce_UnityPluginInterface.h" - -//============================================================================== -namespace juce -{ - -typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); -extern createUnityPeerFunctionType juce_createUnityPeerFn; - -//============================================================================== -class UnityPeer : public ComponentPeer, - public AsyncUpdater -{ -public: - UnityPeer (Component& ed) - : ComponentPeer (ed, 0), - mouseWatcher (*this) - { - getEditor().setResizable (false, false); - } - - //============================================================================== - Rectangle getBounds() const override { return bounds; } - Point localToGlobal (Point relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } - Point globalToLocal (Point screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } - - using ComponentPeer::localToGlobal; - using ComponentPeer::globalToLocal; - - StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } - - void setBounds (const Rectangle& newBounds, bool) override - { - bounds = newBounds; - mouseWatcher.setBoundsToWatch (bounds); - } - - bool contains (Point localPos, bool) const override - { - if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) - && isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) - return true; - - return false; - } - - void handleAsyncUpdate() override - { - fillPixels(); - } - - //============================================================================== - AudioProcessorEditor& getEditor() { return *dynamic_cast (&getComponent()); } - - void setPixelDataHandle (uint8* handle, int width, int height) - { - pixelData = handle; - - textureWidth = width; - textureHeight = height; - - renderImage = Image (new UnityBitmapImage (pixelData, width, height)); - } - - // N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. - // It works for our purposes here but a much more efficient way would be to use a GL texture. - void fillPixels() - { - if (pixelData == nullptr) - return; - - LowLevelGraphicsSoftwareRenderer renderer (renderImage); - renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); - - handlePaint (renderer); - - for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) - { - auto r = pixelData[i + 2]; - auto g = pixelData[i + 1]; - auto b = pixelData[i + 0]; - - pixelData[i + 0] = r; - pixelData[i + 1] = g; - pixelData[i + 2] = b; - } - } - - void forwardMouseEvent (Point position, ModifierKeys mods) - { - ModifierKeys::currentModifiers = mods; - - handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, - juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); - } - - void forwardKeyPress (int code, String name, ModifierKeys mods) - { - ModifierKeys::currentModifiers = mods; - - handleKeyPress (getKeyPress (code, name)); - } - -private: - //============================================================================== - struct UnityBitmapImage : public ImagePixelData - { - UnityBitmapImage (uint8* data, int w, int h) - : ImagePixelData (Image::PixelFormat::ARGB, w, h), - imageData (data), - lineStride (width * pixelStride) - { - } - - std::unique_ptr createType() const override - { - return std::make_unique(); - } - - std::unique_ptr createLowLevelContext() override - { - return std::make_unique (Image (this)); - } - - void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override - { - const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; - bitmap.data = imageData + offset; - bitmap.size = (size_t) (lineStride * height) - offset; - bitmap.pixelFormat = pixelFormat; - bitmap.lineStride = lineStride; - bitmap.pixelStride = pixelStride; - } - - ImagePixelData::Ptr clone() override - { - auto im = new UnityBitmapImage (imageData, width, height); - - for (int i = 0; i < height; ++i) - memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); - - return im; - } - - uint8* imageData; - int pixelStride = 4, lineStride; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) - }; - - //============================================================================== - struct MouseWatcher : public Timer - { - MouseWatcher (ComponentPeer& o) : owner (o) {} - - void timerCallback() override - { - auto pos = Desktop::getMousePosition(); - - if (boundsToWatch.contains (pos) && pos != lastMousePos) - { - auto ms = Desktop::getInstance().getMainMouseSource(); - - if (! ms.getCurrentModifiers().isLeftButtonDown()) - owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, - juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); - - lastMousePos = pos; - } - - } - - void setBoundsToWatch (Rectangle b) - { - if (boundsToWatch != b) - boundsToWatch = b; - - startTimer (250); - } - - ComponentPeer& owner; - Rectangle boundsToWatch; - Point lastMousePos; - }; - - //============================================================================== - KeyPress getKeyPress (int keyCode, String name) - { - if (keyCode >= 32 && keyCode <= 64) - return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; - - if (keyCode >= 91 && keyCode <= 122) - return { keyCode, ModifierKeys::currentModifiers, name[0] }; - - if (keyCode >= 256 && keyCode <= 265) - return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; - - if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; - if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; - if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; - if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; - if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; - if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; - if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; - if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; - - return {}; - } - - //============================================================================== - Rectangle bounds; - MouseWatcher mouseWatcher; - - uint8* pixelData = nullptr; - int textureWidth, textureHeight; - Image renderImage; - - //============================================================================== - void setMinimised (bool) override {} - bool isMinimised() const override { return false; } - void setFullScreen (bool) override {} - bool isFullScreen() const override { return false; } - bool setAlwaysOnTop (bool) override { return false; } - void toFront (bool) override {} - void toBehind (ComponentPeer*) override {} - bool isFocused() const override { return true; } - void grabFocus() override {} - void* getNativeHandle() const override { return nullptr; } - OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } - BorderSize getFrameSize() const override { return {}; } - void setVisible (bool) override {} - void setTitle (const String&) override {} - void setIcon (const Image&) override {} - void textInputRequired (Point, TextInputTarget&) override {} - void setAlpha (float) override {} - void performAnyPendingRepaintsNow() override {} - void repaint (const Rectangle&) override {} - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) -}; - -static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } - -//============================================================================== -class AudioProcessorUnityWrapper -{ -public: - AudioProcessorUnityWrapper (bool isTemporary) - { - pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); - - if (! isTemporary && pluginInstance->hasEditor()) - { - pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); - pluginInstanceEditor->setVisible (true); - pluginInstanceEditor->addToDesktop (0); - } - - juceParameters.update (*pluginInstance, false); - } - - ~AudioProcessorUnityWrapper() - { - if (pluginInstanceEditor != nullptr) - { - pluginInstanceEditor->removeFromDesktop(); - - PopupMenu::dismissAllActiveMenus(); - pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); - pluginInstanceEditor = nullptr; - } - } - - void create (UnityAudioEffectState* state) - { - // only supported in Unity plugin API > 1.0 - if (state->structSize >= sizeof (UnityAudioEffectState)) - samplesPerBlock = static_cast (state->dspBufferSize); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - const int numConfigs = sizeof (configs) / sizeof (short[2]); - - jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - - pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); - #else - pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); - #endif - - pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); - - scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); - } - - void release() - { - pluginInstance->releaseResources(); - } - - void reset() - { - pluginInstance->reset(); - } - - void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) - { - // If the plugin has a bypass parameter, set it to the current bypass state - if (auto* param = pluginInstance->getBypassParameter()) - if (isBypassed != (param->getValue() >= 0.5f)) - param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); - - for (int pos = 0; pos < bufferSize;) - { - auto max = jmin (bufferSize - pos, samplesPerBlock); - processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); - - pos += max; - } - } - - void declareParameters (UnityAudioEffectDefinition& definition) - { - static std::unique_ptr parametersPtr; - static int numParams = 0; - - if (parametersPtr == nullptr) - { - numParams = (int) juceParameters.size(); - - parametersPtr.reset (static_cast (std::calloc (static_cast (numParams), - sizeof (UnityAudioParameterDefinition)))); - - parameterDescriptions.clear(); - - for (int i = 0; i < numParams; ++i) - { - auto* parameter = juceParameters.getParamForIndex (i); - auto& paramDef = parametersPtr.get()[i]; - - const auto nameLength = (size_t) numElementsInArray (paramDef.name); - const auto unitLength = (size_t) numElementsInArray (paramDef.unit); - - parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); - - if (parameter->getLabel().isNotEmpty()) - parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); - - parameterDescriptions.add (parameter->getName (15)); - paramDef.description = parameterDescriptions[i].toRawUTF8(); - - paramDef.defaultVal = parameter->getDefaultValue(); - paramDef.min = 0.0f; - paramDef.max = 1.0f; - paramDef.displayScale = 1.0f; - paramDef.displayExponent = 1.0f; - } - } - - definition.numParameters = static_cast (numParams); - definition.parameterDefintions = parametersPtr.get(); - } - - void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } - float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } - - String getParameterString (int index) const noexcept - { - auto* param = juceParameters.getParamForIndex (index); - return param->getText (param->getValue(), 16); - } - - int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } - int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } - - bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } - - UnityPeer& getEditorPeer() const - { - auto* peer = dynamic_cast (pluginInstanceEditor->getPeer()); - - jassert (peer != nullptr); - return *peer; - } - -private: - //============================================================================== - void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) - { - int ch; - for (ch = 0; ch < numInChannels; ++ch) - { - using DstSampleType = AudioData::Pointer; - using SrcSampleType = AudioData::Pointer; - - DstSampleType dstData (scratchBuffer.getWritePointer (ch)); - SrcSampleType srcData (inBuffer + ch, numInChannels); - dstData.convertSamples (srcData, bufferSize); - } - - for (; ch < numOutChannels; ++ch) - scratchBuffer.clear (ch, 0, bufferSize); - - { - const ScopedLock sl (pluginInstance->getCallbackLock()); - - if (pluginInstance->isSuspended()) - { - scratchBuffer.clear(); - } - else - { - MidiBuffer mb; - - if (isBypassed && pluginInstance->getBypassParameter() == nullptr) - pluginInstance->processBlockBypassed (scratchBuffer, mb); - else - pluginInstance->processBlock (scratchBuffer, mb); - } - } - - for (ch = 0; ch < numOutChannels; ++ch) - { - using DstSampleType = AudioData::Pointer; - using SrcSampleType = AudioData::Pointer; - - DstSampleType dstData (outBuffer + ch, numOutChannels); - SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); - dstData.convertSamples (srcData, bufferSize); - } - } - - //============================================================================== - std::unique_ptr pluginInstance; - std::unique_ptr pluginInstanceEditor; - - int samplesPerBlock = 1024; - StringArray parameterDescriptions; - - AudioBuffer scratchBuffer; - - LegacyAudioParametersWrapper juceParameters; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) -}; - -//============================================================================== -static HashMap& getWrapperMap() -{ - static HashMap wrapperMap; - return wrapperMap; -} - -static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) -{ - getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); -} - -static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) -{ - getWrapperMap().removeValue (wrapperToRemove); -} - -//============================================================================== -static UnityAudioEffectDefinition getEffectDefinition() -{ - const auto wrapper = std::make_unique (true); - const String originalName { JucePlugin_Name }; - const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; - - UnityAudioEffectDefinition result{}; - name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); - - result.structSize = sizeof (UnityAudioEffectDefinition); - result.parameterStructSize = sizeof (UnityAudioParameterDefinition); - - result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; - result.pluginVersion = JucePlugin_VersionCode; - - // effects must set this to 0, generators > 0 - result.channels = (wrapper->getNumInputChannels() != 0 ? 0 - : static_cast (wrapper->getNumOutputChannels())); - - wrapper->declareParameters (result); - - result.create = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = new AudioProcessorUnityWrapper (false); - pluginInstance->create (state); - - state->effectData = pluginInstance; - - onWrapperCreation (pluginInstance); - - return 0; - }; - - result.release = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->release(); - - onWrapperDeletion (pluginInstance); - delete pluginInstance; - - if (getWrapperMap().size() == 0) - shutdownJuce_GUI(); - - return 0; - }; - - result.reset = [] (UnityAudioEffectState* state) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->reset(); - - return 0; - }; - - result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) - { - ignoreUnused (state, pos); - return 0; - }; - - result.process = [] (UnityAudioEffectState* state, - float* inBuffer, - float* outBuffer, - unsigned int bufferSize, - int numInChannels, - int numOutChannels) - { - auto* pluginInstance = state->getEffectData(); - - if (pluginInstance != nullptr) - { - auto isPlaying = ((state->flags & stateIsPlaying) != 0); - auto isMuted = ((state->flags & stateIsMuted) != 0); - auto isPaused = ((state->flags & stateIsPaused) != 0); - - const auto bypassed = ! isPlaying || (isMuted || isPaused); - pluginInstance->process (inBuffer, outBuffer, static_cast (bufferSize), numInChannels, numOutChannels, bypassed); - } - else - { - FloatVectorOperations::clear (outBuffer, static_cast (bufferSize) * numOutChannels); - } - - return 0; - }; - - result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) - { - auto* pluginInstance = state->getEffectData(); - pluginInstance->setParameter (index, value); - - return 0; - }; - - result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) - { - auto* pluginInstance = state->getEffectData(); - *value = pluginInstance->getParameter (index); - - pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); - - return 0; - }; - - result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) - { - ignoreUnused (numSamples); - - const StringRef kindStr { kind }; - - if (kindStr == StringRef ("Editor")) - { - auto* pluginInstance = state->getEffectData(); - - buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; - } - else if (kindStr == StringRef ("ID")) - { - auto* pluginInstance = state->getEffectData(); - - for (HashMap::Iterator i (getWrapperMap()); i.next();) - { - if (i.getValue() == pluginInstance) - { - buffer[0] = (float) i.getKey(); - break; - } - } - - return 0; - } - else if (kindStr == StringRef ("Size")) - { - auto* pluginInstance = state->getEffectData(); - - auto& editor = pluginInstance->getEditorPeer().getEditor(); - - buffer[0] = (float) editor.getBounds().getWidth(); - buffer[1] = (float) editor.getBounds().getHeight(); - buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); - buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); - buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); - buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); - } - - return 0; - }; - - return result; -} - -} // namespace juce - -// From reading the example code, it seems that the triple indirection indicates -// an out-value of an array of pointers. That is, after calling this function, definitionsPtr -// should point to a pre-existing/static array of pointer-to-effect-definition. -UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) -{ - if (juce::getWrapperMap().size() == 0) - juce::initialiseJuce_GUI(); - - static std::once_flag flag; - std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); - - static auto definition = juce::getEffectDefinition(); - static UnityAudioEffectDefinition* definitions[] { &definition }; - *definitionsPtr = definitions; - - return 1; -} - -//============================================================================== -static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) -{ - int flags = 0; - - if (mouseDown) - { - if (mouseButton == 0) - flags |= juce::ModifierKeys::leftButtonModifier; - else if (mouseButton == 1) - flags |= juce::ModifierKeys::rightButtonModifier; - else if (mouseButton == 2) - flags |= juce::ModifierKeys::middleButtonModifier; - } - - if (mods == 0) - return flags; - - if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; - if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; - if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; - if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; - - return { flags }; -} - -//============================================================================== -static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) -{ - auto* wrapper = juce::getWrapperMap()[id]; - jassert (wrapper != nullptr); - - return wrapper; -} - -//============================================================================== -static void UNITY_INTERFACE_API onRenderEvent (int id) -{ - getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); -} - -UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() -{ - return onRenderEvent; -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) -{ - getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast (data), w, h); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) -{ - getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) -{ - getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); -} - -UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) -{ - getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); -} - -//============================================================================== -#if JUCE_WINDOWS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - - extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) - { - if (reason == DLL_PROCESS_ATTACH) - juce::Process::setCurrentModuleInstanceHandle (instance); - - return true; - } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp deleted file mode 100644 index 9be28af..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ /dev/null @@ -1,2256 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include -#include "../utility/juce_CheckSettingMacros.h" - -#if JucePlugin_Build_VST - -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) - -#include "../utility/juce_IncludeSystemHeaders.h" -#include - -#if JucePlugin_VersionCode < 0x010000 // Major < 0 - - #if (JucePlugin_VersionCode & 0x00FF00) > (9 * 0x100) // check if Minor number exceeds 9 - JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'minor' exceeding 9") - #endif - - #if (JucePlugin_VersionCode & 0xFF) > 9 // check if Bugfix number exceeds 9 - JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'bugfix' exceeding 9") - #endif - -#elif JucePlugin_VersionCode >= 0x650000 // Major >= 101 - - #if (JucePlugin_VersionCode & 0x00FF00) > (99 * 0x100) // check if Minor number exceeds 99 - JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'minor' exceeding 99") - #endif - - #if (JucePlugin_VersionCode & 0xFF) > 99 // check if Bugfix number exceeds 99 - JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'bugfix' exceeding 99") - #endif - -#endif - -#ifdef PRAGMA_ALIGN_SUPPORTED - #undef PRAGMA_ALIGN_SUPPORTED - #define PRAGMA_ALIGN_SUPPORTED 1 -#endif - -#if ! JUCE_MSVC - #define __cdecl -#endif - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", - "-Wshadow", - "-Wdeprecated-register", - "-Wdeprecated-declarations", - "-Wunused-parameter", - "-Wdeprecated-writable-strings", - "-Wnon-virtual-dtor", - "-Wzero-as-null-pointer-constant") -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) - -#define VST_FORCE_DEPRECATED 0 - -namespace Vst2 -{ -// If the following files cannot be found then you are probably trying to build -// a VST2 plug-in or a VST2-compatible VST3 plug-in. To do this you must have a -// VST2 SDK in your header search paths or use the "VST (Legacy) SDK Folder" -// field in the Projucer. The VST2 SDK can be obtained from the -// vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK or JUCE version 5.3.2. You -// also need a VST2 license from Steinberg to distribute VST2 plug-ins. -#include "pluginterfaces/vst2.x/aeffect.h" -#include "pluginterfaces/vst2.x/aeffectx.h" -} - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -#if JUCE_MSVC - #pragma pack (push, 8) -#endif - -#define JUCE_VSTINTERFACE_H_INCLUDED 1 -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include "../utility/juce_IncludeModuleHeaders.h" - -using namespace juce; - -#include "../utility/juce_WindowsHooks.h" -#include "../utility/juce_LinuxMessageThread.h" - -#include -#include - -#ifdef JUCE_MSVC - #pragma pack (pop) -#endif - -#undef MemoryBlock - -class JuceVSTWrapper; -static bool recursionCheck = false; - -namespace juce -{ - #if JUCE_MAC - extern JUCE_API void initialiseMacVST(); - extern JUCE_API void* attachComponentToWindowRefVST (Component*, void* parent, bool isNSView); - extern JUCE_API void detachComponentFromWindowRefVST (Component*, void* window, bool isNSView); - extern JUCE_API void setNativeHostWindowSizeVST (void* window, Component*, int newWidth, int newHeight, bool isNSView); - extern JUCE_API void checkWindowVisibilityVST (void* window, Component*, bool isNSView); - extern JUCE_API bool forwardCurrentKeyEventToHostVST (Component*, bool isNSView); - #if ! JUCE_64BIT - extern JUCE_API void updateEditorCompBoundsVST (Component*); - #endif - #endif - -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - extern JUCE_API double getScaleFactorForWindow (HWND); -#endif - - extern JUCE_API bool handleManufacturerSpecificVST2Opcode (int32, pointer_sized_int, void*, float); -} - - -//============================================================================== -#if JUCE_WINDOWS - -namespace -{ - // Returns the actual container window, unlike GetParent, which can also return a separate owner window. - static HWND getWindowParent (HWND w) noexcept { return GetAncestor (w, GA_PARENT); } - - static HWND findMDIParentOf (HWND w) - { - const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); - - while (w != nullptr) - { - auto parent = getWindowParent (w); - - if (parent == nullptr) - break; - - TCHAR windowType[32] = { 0 }; - GetClassName (parent, windowType, 31); - - if (String (windowType).equalsIgnoreCase ("MDIClient")) - return parent; - - RECT windowPos, parentPos; - GetWindowRect (w, &windowPos); - GetWindowRect (parent, &parentPos); - - auto dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); - auto dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); - - if (dw > 100 || dh > 100) - break; - - w = parent; - - if (dw == 2 * frameThickness) - break; - } - - return w; - } - - static int numActivePlugins = 0; - static bool messageThreadIsDefinitelyCorrect = false; -} - -#endif - -//============================================================================== -// Ableton Live host specific commands -struct AbletonLiveHostSpecific -{ - enum - { - KCantBeSuspended = (1 << 2) - }; - - uint32 magic; // 'AbLi' - int cmd; // 5 = realtime properties - size_t commandSize; // sizeof (int) - int flags; // KCantBeSuspended = (1 << 2) -}; - -//============================================================================== -/** - This is an AudioEffectX object that holds and wraps our AudioProcessor... -*/ -class JuceVSTWrapper : public AudioProcessorListener, - public AudioPlayHead, - private Timer, - private AudioProcessorParameter::Listener -{ -private: - //============================================================================== - template - struct VstTempBuffers - { - VstTempBuffers() {} - ~VstTempBuffers() { release(); } - - void release() noexcept - { - for (auto* c : tempChannels) - delete[] c; - - tempChannels.clear(); - } - - HeapBlock channels; - Array tempChannels; // see note in processReplacing() - juce::AudioBuffer processTempBuffer; - }; - - /** Use the same names as the VST SDK. */ - struct VstOpCodeArguments - { - int32 index; - pointer_sized_int value; - void* ptr; - float opt; - }; - -public: - //============================================================================== - JuceVSTWrapper (Vst2::audioMasterCallback cb, std::unique_ptr af) - : hostCallback (cb), - processor (std::move (af)) - { - inParameterChangedCallback = false; - - // VST-2 does not support disabling buses: so always enable all of them - processor->enableAllBuses(); - - findMaxTotalChannels (maxNumInChannels, maxNumOutChannels); - - // You must at least have some channels - jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); - - if (processor->isMidiEffect()) - maxNumInChannels = maxNumOutChannels = 2; - - #ifdef JucePlugin_PreferredChannelConfigurations - processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024); - #endif - - processor->setRateAndBufferSizeDetails (0, 0); - processor->setPlayHead (this); - processor->addListener (this); - - if (auto* juceParam = processor->getBypassParameter()) - juceParam->addListener (this); - - juceParameters.update (*processor, false); - - memset (&vstEffect, 0, sizeof (vstEffect)); - vstEffect.magic = 0x56737450 /* 'VstP' */; - vstEffect.dispatcher = (Vst2::AEffectDispatcherProc) dispatcherCB; - vstEffect.process = nullptr; - vstEffect.setParameter = (Vst2::AEffectSetParameterProc) setParameterCB; - vstEffect.getParameter = (Vst2::AEffectGetParameterProc) getParameterCB; - vstEffect.numPrograms = jmax (1, processor->getNumPrograms()); - vstEffect.numParams = juceParameters.getNumParameters(); - vstEffect.numInputs = maxNumInChannels; - vstEffect.numOutputs = maxNumOutChannels; - vstEffect.initialDelay = processor->getLatencySamples(); - vstEffect.object = this; - vstEffect.uniqueID = JucePlugin_VSTUniqueID; - - #ifdef JucePlugin_VSTChunkStructureVersion - vstEffect.version = JucePlugin_VSTChunkStructureVersion; - #else - vstEffect.version = JucePlugin_VersionCode; - #endif - - vstEffect.processReplacing = (Vst2::AEffectProcessProc) processReplacingCB; - vstEffect.processDoubleReplacing = (Vst2::AEffectProcessDoubleProc) processDoubleReplacingCB; - - vstEffect.flags |= Vst2::effFlagsHasEditor; - - vstEffect.flags |= Vst2::effFlagsCanReplacing; - if (processor->supportsDoublePrecisionProcessing()) - vstEffect.flags |= Vst2::effFlagsCanDoubleReplacing; - - vstEffect.flags |= Vst2::effFlagsProgramChunks; - - #if JucePlugin_IsSynth - vstEffect.flags |= Vst2::effFlagsIsSynth; - #else - if (processor->getTailLengthSeconds() == 0.0) - vstEffect.flags |= Vst2::effFlagsNoSoundInStop; - #endif - - #if JUCE_WINDOWS - ++numActivePlugins; - #endif - } - - ~JuceVSTWrapper() override - { - JUCE_AUTORELEASEPOOL - { - #if JUCE_LINUX || JUCE_BSD - MessageManagerLock mmLock; - #endif - - stopTimer(); - deleteEditor (false); - - hasShutdown = true; - - processor = nullptr; - - jassert (editorComp == nullptr); - - deleteTempChannels(); - - #if JUCE_WINDOWS - if (--numActivePlugins == 0) - messageThreadIsDefinitelyCorrect = false; - #endif - } - } - - Vst2::AEffect* getAEffect() noexcept { return &vstEffect; } - - template - void internalProcessReplacing (FloatType** inputs, FloatType** outputs, - int32 numSamples, VstTempBuffers& tmpBuffers) - { - const bool isMidiEffect = processor->isMidiEffect(); - - if (firstProcessCallback) - { - firstProcessCallback = false; - - // if this fails, the host hasn't called resume() before processing - jassert (isProcessing); - - // (tragically, some hosts actually need this, although it's stupid to have - // to do it here.) - if (! isProcessing) - resume(); - - processor->setNonRealtime (isProcessLevelOffline()); - - #if JUCE_WINDOWS - if (getHostType().isWavelab()) - { - int priority = GetThreadPriority (GetCurrentThread()); - - if (priority <= THREAD_PRIORITY_NORMAL && priority >= THREAD_PRIORITY_LOWEST) - processor->setNonRealtime (true); - } - #endif - } - - #if JUCE_DEBUG && ! (JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect) - const int numMidiEventsComingIn = midiEvents.getNumEvents(); - #endif - - { - const int numIn = processor->getTotalNumInputChannels(); - const int numOut = processor->getTotalNumOutputChannels(); - - const ScopedLock sl (processor->getCallbackLock()); - - if (processor->isSuspended()) - { - for (int i = 0; i < numOut; ++i) - if (outputs[i] != nullptr) - FloatVectorOperations::clear (outputs[i], numSamples); - } - else - { - updateCallbackContextInfo(); - - int i; - for (i = 0; i < numOut; ++i) - { - auto* chan = tmpBuffers.tempChannels.getUnchecked(i); - - if (chan == nullptr) - { - chan = outputs[i]; - - bool bufferPointerReusedForOtherChannels = false; - - for (int j = i; --j >= 0;) - { - if (outputs[j] == chan) - { - bufferPointerReusedForOtherChannels = true; - break; - } - } - - // if some output channels are disabled, some hosts supply the same buffer - // for multiple channels or supply a nullptr - this buggers up our method - // of copying the inputs over the outputs, so we need to create unique temp - // buffers in this case.. - if (bufferPointerReusedForOtherChannels || chan == nullptr) - { - chan = new FloatType [(size_t) blockSize * 2]; - tmpBuffers.tempChannels.set (i, chan); - } - } - - if (i < numIn) - { - if (chan != inputs[i]) - memcpy (chan, inputs[i], (size_t) numSamples * sizeof (FloatType)); - } - else - { - FloatVectorOperations::clear (chan, numSamples); - } - - tmpBuffers.channels[i] = chan; - } - - for (; i < numIn; ++i) - tmpBuffers.channels[i] = inputs[i]; - - { - const int numChannels = jmax (numIn, numOut); - AudioBuffer chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); - - if (isBypassed && processor->getBypassParameter() == nullptr) - processor->processBlockBypassed (chans, midiEvents); - else - processor->processBlock (chans, midiEvents); - } - - // copy back any temp channels that may have been used.. - for (i = 0; i < numOut; ++i) - if (auto* chan = tmpBuffers.tempChannels.getUnchecked(i)) - if (auto* dest = outputs[i]) - memcpy (dest, chan, (size_t) numSamples * sizeof (FloatType)); - } - } - - if (! midiEvents.isEmpty()) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - auto numEvents = midiEvents.getNumEvents(); - - outgoingEvents.ensureSize (numEvents); - outgoingEvents.clear(); - - for (const auto metadata : midiEvents) - { - jassert (metadata.samplePosition >= 0 && metadata.samplePosition < numSamples); - - outgoingEvents.addEvent (metadata.data, metadata.numBytes, metadata.samplePosition); - } - - // Send VST events to the host. - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterProcessEvents, 0, 0, outgoingEvents.events, 0); - #elif JUCE_DEBUG - /* This assertion is caused when you've added some events to the - midiMessages array in your processBlock() method, which usually means - that you're trying to send them somewhere. But in this case they're - getting thrown away. - - If your plugin does want to send midi messages, you'll need to set - the JucePlugin_ProducesMidiOutput macro to 1 in your - JucePluginCharacteristics.h file. - - If you don't want to produce any midi output, then you should clear the - midiMessages array at the end of your processBlock() method, to - indicate that you don't want any of the events to be passed through - to the output. - */ - jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); - #endif - - midiEvents.clear(); - } - } - - void processReplacing (float** inputs, float** outputs, int32 sampleFrames) - { - jassert (! processor->isUsingDoublePrecision()); - internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); - } - - static void processReplacingCB (Vst2::AEffect* vstInterface, float** inputs, float** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); - } - - void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames) - { - jassert (processor->isUsingDoublePrecision()); - internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); - } - - static void processDoubleReplacingCB (Vst2::AEffect* vstInterface, double** inputs, double** outputs, int32 sampleFrames) - { - getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); - } - - //============================================================================== - void resume() - { - if (processor != nullptr) - { - isProcessing = true; - - auto numInAndOutChannels = static_cast (vstEffect.numInputs + vstEffect.numOutputs); - floatTempBuffers .channels.calloc (numInAndOutChannels); - doubleTempBuffers.channels.calloc (numInAndOutChannels); - - auto currentRate = sampleRate; - auto currentBlockSize = blockSize; - - firstProcessCallback = true; - - processor->setNonRealtime (isProcessLevelOffline()); - processor->setRateAndBufferSizeDetails (currentRate, currentBlockSize); - - deleteTempChannels(); - - processor->prepareToPlay (currentRate, currentBlockSize); - - midiEvents.ensureSize (2048); - midiEvents.clear(); - - vstEffect.initialDelay = processor->getLatencySamples(); - - /** If this plug-in is a synth or it can receive midi events we need to tell the - host that we want midi. In the SDK this method is marked as deprecated, but - some hosts rely on this behaviour. - */ - if (vstEffect.flags & Vst2::effFlagsIsSynth || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect) - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterWantMidi, 0, 1, nullptr, 0); - } - - if (getHostType().isAbletonLive() - && hostCallback != nullptr - && processor->getTailLengthSeconds() == std::numeric_limits::infinity()) - { - AbletonLiveHostSpecific hostCmd; - - hostCmd.magic = 0x41624c69; // 'AbLi' - hostCmd.cmd = 5; - hostCmd.commandSize = sizeof (int); - hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended; - - hostCallback (&vstEffect, Vst2::audioMasterVendorSpecific, 0, 0, &hostCmd, 0.0f); - } - - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - outgoingEvents.ensureSize (512); - #endif - } - } - - void suspend() - { - if (processor != nullptr) - { - processor->releaseResources(); - outgoingEvents.freeEvents(); - - isProcessing = false; - floatTempBuffers.channels.free(); - doubleTempBuffers.channels.free(); - - deleteTempChannels(); - } - } - - void updateCallbackContextInfo() - { - const Vst2::VstTimeInfo* ti = nullptr; - - if (hostCallback != nullptr) - { - int32 flags = Vst2::kVstPpqPosValid | Vst2::kVstTempoValid - | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid - | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid - | Vst2::kVstClockValid | Vst2::kVstNanosValid; - - auto result = hostCallback (&vstEffect, Vst2::audioMasterGetTime, 0, flags, nullptr, 0); - ti = reinterpret_cast (result); - } - - if (ti == nullptr || ti->sampleRate <= 0) - { - currentPosition.reset(); - return; - } - - auto& info = currentPosition.emplace(); - info.setBpm ((ti->flags & Vst2::kVstTempoValid) != 0 ? makeOptional (ti->tempo) : nullopt); - - info.setTimeSignature ((ti->flags & Vst2::kVstTimeSigValid) != 0 ? makeOptional (TimeSignature { ti->timeSigNumerator, ti->timeSigDenominator }) - : nullopt); - - info.setTimeInSamples ((int64) (ti->samplePos + 0.5)); - info.setTimeInSeconds (ti->samplePos / ti->sampleRate); - info.setPpqPosition ((ti->flags & Vst2::kVstPpqPosValid) != 0 ? makeOptional (ti->ppqPos) : nullopt); - info.setPpqPositionOfLastBarStart ((ti->flags & Vst2::kVstBarsValid) != 0 ? makeOptional (ti->barStartPos) : nullopt); - - if ((ti->flags & Vst2::kVstSmpteValid) != 0) - { - info.setFrameRate ([&]() -> Optional - { - switch (ti->smpteFrameRate) - { - case Vst2::kVstSmpte24fps: return FrameRate().withBaseRate (24); - case Vst2::kVstSmpte239fps: return FrameRate().withBaseRate (24).withPullDown(); - - case Vst2::kVstSmpte25fps: return FrameRate().withBaseRate (25); - case Vst2::kVstSmpte249fps: return FrameRate().withBaseRate (25).withPullDown(); - - case Vst2::kVstSmpte30fps: return FrameRate().withBaseRate (30); - case Vst2::kVstSmpte30dfps: return FrameRate().withBaseRate (30).withDrop(); - case Vst2::kVstSmpte2997fps: return FrameRate().withBaseRate (30).withPullDown(); - case Vst2::kVstSmpte2997dfps: return FrameRate().withBaseRate (30).withPullDown().withDrop(); - - case Vst2::kVstSmpte60fps: return FrameRate().withBaseRate (60); - case Vst2::kVstSmpte599fps: return FrameRate().withBaseRate (60).withPullDown(); - - case Vst2::kVstSmpteFilm16mm: - case Vst2::kVstSmpteFilm35mm: return FrameRate().withBaseRate (24); - } - - return nullopt; - }()); - - const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; - info.setEditOriginTime (effectiveRate != 0.0 ? makeOptional (ti->smpteOffset / (80.0 * effectiveRate)) : nullopt); - } - - info.setIsRecording ((ti->flags & Vst2::kVstTransportRecording) != 0); - info.setIsPlaying ((ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0); - info.setIsLooping ((ti->flags & Vst2::kVstTransportCycleActive) != 0); - - info.setLoopPoints ((ti->flags & Vst2::kVstCyclePosValid) != 0 ? makeOptional (LoopPoints { ti->cycleStartPos, ti->cycleEndPos }) - : nullopt); - - info.setHostTimeNs ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); - } - - //============================================================================== - Optional getPosition() const override - { - return currentPosition; - } - - //============================================================================== - float getParameter (int32 index) const - { - if (auto* param = juceParameters.getParamForIndex (index)) - return param->getValue(); - - return 0.0f; - } - - static float getParameterCB (Vst2::AEffect* vstInterface, int32 index) - { - return getWrapper (vstInterface)->getParameter (index); - } - - void setParameter (int32 index, float value) - { - if (auto* param = juceParameters.getParamForIndex (index)) - setValueAndNotifyIfChanged (*param, value); - } - - static void setParameterCB (Vst2::AEffect* vstInterface, int32 index, float value) - { - getWrapper (vstInterface)->setParameter (index, value); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override - { - if (inParameterChangedCallback.get()) - { - inParameterChangedCallback = false; - return; - } - - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterAutomate, index, 0, nullptr, newValue); - } - - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterBeginEdit, index, 0, nullptr, 0); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - if (hostCallback != nullptr) - hostCallback (&vstEffect, Vst2::audioMasterEndEdit, index, 0, nullptr, 0); - } - - void parameterValueChanged (int, float newValue) override - { - // this can only come from the bypass parameter - isBypassed = (newValue >= 0.5f); - } - - void parameterGestureChanged (int, bool) override {} - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - hostChangeUpdater.update (details); - } - - bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const - { - if (processor->isMidiEffect()) - return false; - - int channelIdx, busIdx; - - // fill with default - properties.flags = 0; - properties.label[0] = 0; - properties.shortLabel[0] = 0; - properties.arrangementType = Vst2::kSpeakerArrEmpty; - - if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0) - { - auto& bus = *processor->getBus (direction, busIdx); - auto& channelSet = bus.getCurrentLayout(); - auto channelType = channelSet.getTypeOfChannel (channelIdx); - - properties.flags = Vst2::kVstPinIsActive | Vst2::kVstPinUseSpeaker; - properties.arrangementType = SpeakerMappings::channelSetToVstArrangementType (channelSet); - String label = bus.getName(); - - #ifdef JucePlugin_PreferredChannelConfigurations - label += " " + String (channelIdx); - #else - if (channelSet.size() > 1) - label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType); - #endif - - label.copyToUTF8 (properties.label, (size_t) (Vst2::kVstMaxLabelLen + 1)); - label.copyToUTF8 (properties.shortLabel, (size_t) (Vst2::kVstMaxShortLabelLen + 1)); - - if (channelType == AudioChannelSet::left - || channelType == AudioChannelSet::leftSurround - || channelType == AudioChannelSet::leftCentre - || channelType == AudioChannelSet::leftSurroundSide - || channelType == AudioChannelSet::topFrontLeft - || channelType == AudioChannelSet::topRearLeft - || channelType == AudioChannelSet::leftSurroundRear - || channelType == AudioChannelSet::wideLeft) - properties.flags |= Vst2::kVstPinIsStereo; - - return true; - } - - return false; - } - - //============================================================================== - void timerCallback() override - { - if (shouldDeleteEditor) - { - shouldDeleteEditor = false; - deleteEditor (true); - } - - { - ScopedLock lock (stateInformationLock); - - if (chunkMemoryTime > 0 - && chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000 - && ! recursionCheck) - { - chunkMemory.reset(); - chunkMemoryTime = 0; - } - } - - if (editorComp != nullptr) - editorComp->checkVisibility(); - } - - void setHasEditorFlag (bool shouldSetHasEditor) - { - auto hasEditor = (vstEffect.flags & Vst2::effFlagsHasEditor) != 0; - - if (shouldSetHasEditor == hasEditor) - return; - - if (shouldSetHasEditor) - vstEffect.flags |= Vst2::effFlagsHasEditor; - else - vstEffect.flags &= ~Vst2::effFlagsHasEditor; - } - - void createEditorComp() - { - if (hasShutdown || processor == nullptr) - return; - - if (editorComp == nullptr) - { - if (auto* ed = processor->createEditorIfNeeded()) - { - setHasEditorFlag (true); - editorComp.reset (new EditorCompWrapper (*this, *ed, editorScaleFactor)); - } - else - { - setHasEditorFlag (false); - } - } - - shouldDeleteEditor = false; - } - - void deleteEditor (bool canDeleteLaterIfModal) - { - JUCE_AUTORELEASEPOOL - { - PopupMenu::dismissAllActiveMenus(); - - jassert (! recursionCheck); - ScopedValueSetter svs (recursionCheck, true, false); - - if (editorComp != nullptr) - { - if (auto* modalComponent = Component::getCurrentlyModalComponent()) - { - modalComponent->exitModalState (0); - - if (canDeleteLaterIfModal) - { - shouldDeleteEditor = true; - return; - } - } - - editorComp->detachHostWindow(); - - if (auto* ed = editorComp->getEditorComp()) - processor->editorBeingDeleted (ed); - - editorComp = nullptr; - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin. You should try to avoid this happening.. - jassert (Component::getCurrentlyModalComponent() == nullptr); - } - } - } - - pointer_sized_int dispatcher (int32 opCode, VstOpCodeArguments args) - { - if (hasShutdown) - return 0; - - switch (opCode) - { - case Vst2::effOpen: return handleOpen (args); - case Vst2::effClose: return handleClose (args); - case Vst2::effSetProgram: return handleSetCurrentProgram (args); - case Vst2::effGetProgram: return handleGetCurrentProgram (args); - case Vst2::effSetProgramName: return handleSetCurrentProgramName (args); - case Vst2::effGetProgramName: return handleGetCurrentProgramName (args); - case Vst2::effGetParamLabel: return handleGetParameterLabel (args); - case Vst2::effGetParamDisplay: return handleGetParameterText (args); - case Vst2::effGetParamName: return handleGetParameterName (args); - case Vst2::effSetSampleRate: return handleSetSampleRate (args); - case Vst2::effSetBlockSize: return handleSetBlockSize (args); - case Vst2::effMainsChanged: return handleResumeSuspend (args); - case Vst2::effEditGetRect: return handleGetEditorBounds (args); - case Vst2::effEditOpen: return handleOpenEditor (args); - case Vst2::effEditClose: return handleCloseEditor (args); - case Vst2::effIdentify: return (pointer_sized_int) ByteOrder::bigEndianInt ("NvEf"); - case Vst2::effGetChunk: return handleGetData (args); - case Vst2::effSetChunk: return handleSetData (args); - case Vst2::effProcessEvents: return handlePreAudioProcessingEvents (args); - case Vst2::effCanBeAutomated: return handleIsParameterAutomatable (args); - case Vst2::effString2Parameter: return handleParameterValueForText (args); - case Vst2::effGetProgramNameIndexed: return handleGetProgramName (args); - case Vst2::effGetInputProperties: return handleGetInputPinProperties (args); - case Vst2::effGetOutputProperties: return handleGetOutputPinProperties (args); - case Vst2::effGetPlugCategory: return handleGetPlugInCategory (args); - case Vst2::effSetSpeakerArrangement: return handleSetSpeakerConfiguration (args); - case Vst2::effSetBypass: return handleSetBypass (args); - case Vst2::effGetEffectName: return handleGetPlugInName (args); - case Vst2::effGetProductString: return handleGetPlugInName (args); - case Vst2::effGetVendorString: return handleGetManufacturerName (args); - case Vst2::effGetVendorVersion: return handleGetManufacturerVersion (args); - case Vst2::effVendorSpecific: return handleManufacturerSpecific (args); - case Vst2::effCanDo: return handleCanPlugInDo (args); - case Vst2::effGetTailSize: return handleGetTailSize (args); - case Vst2::effKeysRequired: return handleKeyboardFocusRequired (args); - case Vst2::effGetVstVersion: return handleGetVstInterfaceVersion (args); - case Vst2::effGetCurrentMidiProgram: return handleGetCurrentMidiProgram (args); - case Vst2::effGetSpeakerArrangement: return handleGetSpeakerConfiguration (args); - case Vst2::effSetTotalSampleToProcess: return handleSetNumberOfSamplesToProcess (args); - case Vst2::effSetProcessPrecision: return handleSetSampleFloatType (args); - case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); - case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); - case Vst2::effEditIdle: return handleEditIdle(); - default: return 0; - } - } - - static pointer_sized_int dispatcherCB (Vst2::AEffect* vstInterface, int32 opCode, int32 index, - pointer_sized_int value, void* ptr, float opt) - { - auto* wrapper = getWrapper (vstInterface); - VstOpCodeArguments args = { index, value, ptr, opt }; - - if (opCode == Vst2::effClose) - { - wrapper->dispatcher (opCode, args); - delete wrapper; - return 1; - } - - return wrapper->dispatcher (opCode, args); - } - - //============================================================================== - // A component to hold the AudioProcessorEditor, and cope with some housekeeping - // chores when it changes or repaints. - struct EditorCompWrapper : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif - { - EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor, [[maybe_unused]] float initialScale) - : wrapper (w) - { - editor.setOpaque (true); - #if ! JUCE_MAC - editor.setScaleFactor (initialScale); - #endif - addAndMakeVisible (editor); - - auto editorBounds = getSizeToContainChild(); - setSize (editorBounds.getWidth(), editorBounds.getHeight()); - - #if JUCE_WINDOWS - if (! getHostType().isReceptor()) - addMouseListener (this, true); - #endif - - setOpaque (true); - } - - ~EditorCompWrapper() override - { - deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may - // have been transferred to another parent which takes over ownership. - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - void getEditorBounds (Vst2::ERect& bounds) - { - auto editorBounds = getSizeToContainChild(); - bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); - } - - void attachToHost (VstOpCodeArguments args) - { - setVisible (false); - - #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - addToDesktop (0, args.ptr); - hostWindow = (HostWindowType) args.ptr; - - #if JUCE_LINUX || JUCE_BSD - X11Symbols::getInstance()->xReparentWindow (display, - (Window) getWindowHandle(), - (HostWindowType) hostWindow, - 0, 0); - // The host is likely to attempt to move/resize the window directly after this call, - // and we need to ensure that the X server knows that our window has been attached - // before that happens. - X11Symbols::getInstance()->xFlush (display); - #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - checkHostWindowScaleFactor (true); - startTimer (500); - #endif - #elif JUCE_MAC - hostWindow = attachComponentToWindowRefVST (this, args.ptr, wrapper.useNSView); - #endif - - setVisible (true); - } - - void detachHostWindow() - { - #if JUCE_MAC - if (hostWindow != nullptr) - detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView); - #endif - - hostWindow = {}; - } - - void checkVisibility() - { - #if JUCE_MAC - if (hostWindow != nullptr) - checkWindowVisibilityVST (hostWindow, this, wrapper.useNSView); - #endif - } - - AudioProcessorEditor* getEditorComp() const noexcept - { - return dynamic_cast (getChildComponent (0)); - } - - void resized() override - { - if (auto* pluginEditor = getEditorComp()) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); - } - - lastBounds = newBounds; - } - - updateWindowSize(); - } - - #if JUCE_MAC && ! JUCE_64BIT - if (! wrapper.useNSView) - updateEditorCompBoundsVST (this); - #endif - } - - void parentSizeChanged() override - { - updateWindowSize(); - repaint(); - } - - void childBoundsChanged (Component*) override - { - if (resizingChild) - return; - - auto newBounds = getSizeToContainChild(); - - if (newBounds != lastBounds) - { - updateWindowSize(); - lastBounds = newBounds; - } - } - - juce::Rectangle getSizeToContainChild() - { - if (auto* pluginEditor = getEditorComp()) - return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); - - return {}; - } - - void resizeHostWindow (juce::Rectangle bounds) - { - auto rect = convertToHostBounds ({ 0, 0, (int16) bounds.getHeight(), (int16) bounds.getWidth() }); - const auto newWidth = rect.right - rect.left; - const auto newHeight = rect.bottom - rect.top; - - bool sizeWasSuccessful = false; - - if (auto host = wrapper.hostCallback) - { - auto status = host (wrapper.getAEffect(), Vst2::audioMasterCanDo, 0, 0, const_cast ("sizeWindow"), 0); - - if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive()) - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::audioMasterSizeWindow, - newWidth, newHeight, nullptr, 0) != 0); - } - } - - // some hosts don't support the sizeWindow call, so do it manually.. - if (! sizeWasSuccessful) - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - #if JUCE_MAC - setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView); - #elif JUCE_LINUX || JUCE_BSD - // (Currently, all linux hosts support sizeWindow, so this should never need to happen) - setSize (newWidth, newHeight); - #else - int dw = 0; - int dh = 0; - const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); - - HWND w = (HWND) getWindowHandle(); - - while (w != nullptr) - { - HWND parent = getWindowParent (w); - - if (parent == nullptr) - break; - - TCHAR windowType [32] = { 0 }; - GetClassName (parent, windowType, 31); - - if (String (windowType).equalsIgnoreCase ("MDIClient")) - break; - - RECT windowPos, parentPos; - GetWindowRect (w, &windowPos); - GetWindowRect (parent, &parentPos); - - if (w != (HWND) getWindowHandle()) - SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); - - dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); - dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); - - w = parent; - - if (dw == 2 * frameThickness) - break; - - if (dw > 100 || dh > 100) - w = nullptr; - } - - if (w != nullptr) - SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); - #endif - } - - #if JUCE_LINUX || JUCE_BSD - X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), - static_cast (rect.right - rect.left), - static_cast (rect.bottom - rect.top)); - #endif - } - - void setContentScaleFactor (float scale) - { - if (auto* pluginEditor = getEditorComp()) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); - } - - lastBounds = getSizeToContainChild(); - updateWindowSize(); - } - } - - #if JUCE_WINDOWS - void mouseDown (const MouseEvent&) override - { - broughtToFront(); - } - - void broughtToFront() override - { - // for hosts like nuendo, need to also pop the MDI container to the - // front when our comp is clicked on. - if (! isCurrentlyBlockedByAnotherModalComponent()) - if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) - SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - } - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor (bool force = false) - { - auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); - - if (force || (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, wrapper.editorScaleFactor))) - wrapper.handleSetContentScaleFactor (hostWindowScale, force); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif - #endif - - #if JUCE_MAC - bool keyPressed (const KeyPress&) override - { - // If we have an unused keypress, move the key-focus to a host window - // and re-inject the event.. - return forwardCurrentKeyEventToHostVST (this, wrapper.useNSView); - } - #endif - - private: - void updateWindowSize() - { - if (! resizingParent - && getEditorComp() != nullptr - && hostWindow != HostWindowType{}) - { - const auto editorBounds = getSizeToContainChild(); - resizeHostWindow (editorBounds); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - - // setSize() on linux causes renoise and energyxt to fail. - // We'll resize our peer during resizeHostWindow() instead. - #if ! (JUCE_LINUX || JUCE_BSD) - setSize (editorBounds.getWidth(), editorBounds.getHeight()); - #endif - - if (auto* p = getPeer()) - p->updateBounds(); - } - - #if JUCE_MAC - resizeHostWindow (editorBounds); // (doing this a second time seems to be necessary in tracktion) - #endif - } - } - - //============================================================================== - static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return rect; - - return { (int16) roundToInt (rect.top * desktopScale), - (int16) roundToInt (rect.left * desktopScale), - (int16) roundToInt (rect.bottom * desktopScale), - (int16) roundToInt (rect.right * desktopScale) }; - } - - //============================================================================== - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostEventLoop; - #endif - - //============================================================================== - JuceVSTWrapper& wrapper; - bool resizingChild = false, resizingParent = false; - - juce::Rectangle lastBounds; - - #if JUCE_LINUX || JUCE_BSD - using HostWindowType = ::Window; - ::Display* display = XWindowSystem::getInstance()->getDisplay(); - #elif JUCE_WINDOWS - using HostWindowType = HWND; - WindowsHooks hooks; - #else - using HostWindowType = void*; - #endif - - HostWindowType hostWindow = {}; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) - }; - - //============================================================================== -private: - struct HostChangeUpdater : private AsyncUpdater - { - explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} - ~HostChangeUpdater() override { cancelPendingUpdate(); } - - void update (const ChangeDetails& details) - { - if (details.latencyChanged) - { - owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); - callbackBits |= audioMasterIOChangedBit; - } - - if (details.parameterInfoChanged || details.programChanged) - callbackBits |= audioMasterUpdateDisplayBit; - - triggerAsyncUpdate(); - } - - private: - void handleAsyncUpdate() override - { - const auto callbacksToFire = callbackBits.exchange (0); - - if (auto* callback = owner.hostCallback) - { - struct FlagPair - { - Vst2::AudioMasterOpcodesX opcode; - int bit; - }; - - constexpr FlagPair pairs[] { { Vst2::audioMasterUpdateDisplay, audioMasterUpdateDisplayBit }, - { Vst2::audioMasterIOChanged, audioMasterIOChangedBit } }; - - for (const auto& pair : pairs) - if ((callbacksToFire & pair.bit) != 0) - callback (&owner.vstEffect, pair.opcode, 0, 0, nullptr, 0); - } - } - - static constexpr auto audioMasterUpdateDisplayBit = 1 << 0; - static constexpr auto audioMasterIOChangedBit = 1 << 1; - - JuceVSTWrapper& owner; - std::atomic callbackBits { 0 }; - }; - - static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } - - bool isProcessLevelOffline() - { - return hostCallback != nullptr - && (int32) hostCallback (&vstEffect, Vst2::audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0) == 4; - } - - static int32 convertHexVersionToDecimal (const unsigned int hexVersion) - { - #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY - return (int32) hexVersion; - #else - // Currently, only Cubase displays the version number to the user - // We are hoping here that when other DAWs start to display the version - // number, that they do so according to yfede's encoding table in the link - // below. If not, then this code will need an if (isSteinberg()) in the - // future. - int major = (hexVersion >> 16) & 0xff; - int minor = (hexVersion >> 8) & 0xff; - int bugfix = hexVersion & 0xff; - - // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867 - - // Encoding B - if (major < 1) - return major * 1000 + minor * 100 + bugfix * 10; - - // Encoding E - if (major > 100) - return major * 10000000 + minor * 100000 + bugfix * 1000; - - // Encoding D - return static_cast (hexVersion); - #endif - } - - //============================================================================== - #if JUCE_WINDOWS - // Workarounds for hosts which attempt to open editor windows on a non-GUI thread.. (Grrrr...) - static void checkWhetherMessageThreadIsCorrect() - { - auto host = getHostType(); - - if (host.isWavelab() || host.isCubaseBridged() || host.isPremiere()) - { - if (! messageThreadIsDefinitelyCorrect) - { - MessageManager::getInstance()->setCurrentThreadAsMessageThread(); - - struct MessageThreadCallback : public CallbackMessage - { - MessageThreadCallback (bool& tr) : triggered (tr) {} - void messageCallback() override { triggered = true; } - - bool& triggered; - }; - - (new MessageThreadCallback (messageThreadIsDefinitelyCorrect))->post(); - } - } - } - #else - static void checkWhetherMessageThreadIsCorrect() {} - #endif - - void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) - { - if (param.getValue() == newValue) - return; - - inParameterChangedCallback = true; - param.setValueNotifyingHost (newValue); - } - - //============================================================================== - template - void deleteTempChannels (VstTempBuffers& tmpBuffers) - { - tmpBuffers.release(); - - if (processor != nullptr) - tmpBuffers.tempChannels.insertMultiple (0, nullptr, vstEffect.numInputs - + vstEffect.numOutputs); - } - - void deleteTempChannels() - { - deleteTempChannels (floatTempBuffers); - deleteTempChannels (doubleTempBuffers); - } - - //============================================================================== - void findMaxTotalChannels (int& maxTotalIns, int& maxTotalOuts) - { - #ifdef JucePlugin_PreferredChannelConfigurations - int configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - maxTotalIns = maxTotalOuts = 0; - - for (auto& config : configs) - { - maxTotalIns = jmax (maxTotalIns, config[0]); - maxTotalOuts = jmax (maxTotalOuts, config[1]); - } - #else - auto numInputBuses = processor->getBusCount (true); - auto numOutputBuses = processor->getBusCount (false); - - if (numInputBuses > 1 || numOutputBuses > 1) - { - maxTotalIns = maxTotalOuts = 0; - - for (int i = 0; i < numInputBuses; ++i) - maxTotalIns += processor->getChannelCountOfBus (true, i); - - for (int i = 0; i < numOutputBuses; ++i) - maxTotalOuts += processor->getChannelCountOfBus (false, i); - } - else - { - maxTotalIns = numInputBuses > 0 ? processor->getBus (true, 0)->getMaxSupportedChannels (64) : 0; - maxTotalOuts = numOutputBuses > 0 ? processor->getBus (false, 0)->getMaxSupportedChannels (64) : 0; - } - #endif - } - - bool pluginHasSidechainsOrAuxs() const { return (processor->getBusCount (true) > 1 || processor->getBusCount (false) > 1); } - - //============================================================================== - /** Host to plug-in calls. */ - - pointer_sized_int handleOpen (VstOpCodeArguments) - { - // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. - setHasEditorFlag (processor->hasEditor()); - - return 0; - } - - pointer_sized_int handleClose (VstOpCodeArguments) - { - // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. - stopTimer(); - - if (MessageManager::getInstance()->isThisTheMessageThread()) - deleteEditor (false); - - return 0; - } - - pointer_sized_int handleSetCurrentProgram (VstOpCodeArguments args) - { - if (processor != nullptr && isPositiveAndBelow ((int) args.value, processor->getNumPrograms())) - processor->setCurrentProgram ((int) args.value); - - return 0; - } - - pointer_sized_int handleGetCurrentProgram (VstOpCodeArguments) - { - return (processor != nullptr && processor->getNumPrograms() > 0 ? processor->getCurrentProgram() : 0); - } - - pointer_sized_int handleSetCurrentProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && processor->getNumPrograms() > 0) - processor->changeProgramName (processor->getCurrentProgram(), (char*) args.ptr); - - return 0; - } - - pointer_sized_int handleGetCurrentProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && processor->getNumPrograms() > 0) - processor->getProgramName (processor->getCurrentProgram()).copyToUTF8 ((char*) args.ptr, 24 + 1); - - return 0; - } - - pointer_sized_int handleGetParameterLabel (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getLabel().copyToUTF8 ((char*) args.ptr, 24 + 1); - } - - return 0; - } - - pointer_sized_int handleGetParameterText (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getCurrentValueAsText().copyToUTF8 ((char*) args.ptr, 24 + 1); - } - - return 0; - } - - pointer_sized_int handleGetParameterName (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. - param->getName (32).copyToUTF8 ((char*) args.ptr, 32 + 1); - } - - return 0; - } - - pointer_sized_int handleSetSampleRate (VstOpCodeArguments args) - { - sampleRate = args.opt; - return 0; - } - - pointer_sized_int handleSetBlockSize (VstOpCodeArguments args) - { - blockSize = (int32) args.value; - return 0; - } - - pointer_sized_int handleResumeSuspend (VstOpCodeArguments args) - { - if (args.value) - resume(); - else - suspend(); - - return 0; - } - - pointer_sized_int handleGetEditorBounds (VstOpCodeArguments args) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - createEditorComp(); - - if (editorComp != nullptr) - { - editorComp->getEditorBounds (editorRect); - *((Vst2::ERect**) args.ptr) = &editorRect; - return (pointer_sized_int) &editorRect; - } - - return 0; - } - - pointer_sized_int handleOpenEditor (VstOpCodeArguments args) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - jassert (! recursionCheck); - - startTimerHz (4); // performs misc housekeeping chores - - deleteEditor (true); - createEditorComp(); - - if (editorComp != nullptr) - { - editorComp->attachToHost (args); - return 1; - } - - return 0; - } - - pointer_sized_int handleCloseEditor (VstOpCodeArguments) - { - checkWhetherMessageThreadIsCorrect(); - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - - deleteEditor (true); - - return 0; - } - - pointer_sized_int handleGetData (VstOpCodeArguments args) - { - if (processor == nullptr) - return 0; - - auto data = (void**) args.ptr; - bool onlyStoreCurrentProgramData = (args.index != 0); - - MemoryBlock block; - - if (onlyStoreCurrentProgramData) - processor->getCurrentProgramStateInformation (block); - else - processor->getStateInformation (block); - - // IMPORTANT! Don't call getStateInfo while holding this lock! - const ScopedLock lock (stateInformationLock); - - chunkMemory = std::move (block); - *data = (void*) chunkMemory.getData(); - - // because the chunk is only needed temporarily by the host (or at least you'd - // hope so) we'll give it a while and then free it in the timer callback. - chunkMemoryTime = juce::Time::getApproximateMillisecondCounter(); - - return (int32) chunkMemory.getSize(); - } - - pointer_sized_int handleSetData (VstOpCodeArguments args) - { - if (processor != nullptr) - { - void* data = args.ptr; - int32 byteSize = (int32) args.value; - bool onlyRestoreCurrentProgramData = (args.index != 0); - - { - const ScopedLock lock (stateInformationLock); - - chunkMemory.reset(); - chunkMemoryTime = 0; - } - - if (byteSize > 0 && data != nullptr) - { - if (onlyRestoreCurrentProgramData) - processor->setCurrentProgramStateInformation (data, byteSize); - else - processor->setStateInformation (data, byteSize); - } - } - - return 0; - } - - pointer_sized_int handlePreAudioProcessingEvents ([[maybe_unused]] VstOpCodeArguments args) - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - VSTMidiEventList::addEventsToMidiBuffer ((Vst2::VstEvents*) args.ptr, midiEvents); - return 1; - #else - return 0; - #endif - } - - pointer_sized_int handleIsParameterAutomatable (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - const bool isMeter = ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2); - return (param->isAutomatable() && (! isMeter) ? 1 : 0); - } - - return 0; - } - - pointer_sized_int handleParameterValueForText (VstOpCodeArguments args) - { - if (auto* param = juceParameters.getParamForIndex (args.index)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - setValueAndNotifyIfChanged (*param, param->getValueForText (String::fromUTF8 ((char*) args.ptr))); - return 1; - } - } - - return 0; - } - - pointer_sized_int handleGetProgramName (VstOpCodeArguments args) - { - if (processor != nullptr && isPositiveAndBelow (args.index, processor->getNumPrograms())) - { - processor->getProgramName (args.index).copyToUTF8 ((char*) args.ptr, 24 + 1); - return 1; - } - - return 0; - } - - pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args) - { - return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, true, args.index)) ? 1 : 0; - } - - pointer_sized_int handleGetOutputPinProperties (VstOpCodeArguments args) - { - return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, false, args.index)) ? 1 : 0; - } - - pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments) - { - return Vst2::JucePlugin_VSTCategory; - } - - pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args) - { - auto* pluginInput = reinterpret_cast (args.value); - auto* pluginOutput = reinterpret_cast (args.ptr); - - if (processor->isMidiEffect()) - return 0; - - auto numIns = processor->getBusCount (true); - auto numOuts = processor->getBusCount (false); - - if (pluginInput != nullptr && pluginInput->type >= 0) - { - // inconsistent request? - if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput).size() != pluginInput->numChannels) - return 0; - } - - if (pluginOutput != nullptr && pluginOutput->type >= 0) - { - // inconsistent request? - if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput).size() != pluginOutput->numChannels) - return 0; - } - - if (pluginInput != nullptr && pluginInput->numChannels > 0 && numIns == 0) - return 0; - - if (pluginOutput != nullptr && pluginOutput->numChannels > 0 && numOuts == 0) - return 0; - - auto layouts = processor->getBusesLayout(); - - if (pluginInput != nullptr && pluginInput-> numChannels >= 0 && numIns > 0) - layouts.getChannelSet (true, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput); - - if (pluginOutput != nullptr && pluginOutput->numChannels >= 0 && numOuts > 0) - layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (! AudioProcessor::containsLayout (layouts, configs)) - return 0; - #endif - - return processor->setBusesLayout (layouts) ? 1 : 0; - } - - pointer_sized_int handleSetBypass (VstOpCodeArguments args) - { - isBypassed = args.value != 0; - - if (auto* param = processor->getBypassParameter()) - param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); - - return 1; - } - - pointer_sized_int handleGetPlugInName (VstOpCodeArguments args) - { - String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1); - return 1; - } - - pointer_sized_int handleGetManufacturerName (VstOpCodeArguments args) - { - String (JucePlugin_Manufacturer).copyToUTF8 ((char*) args.ptr, 64 + 1); - return 1; - } - - pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments) - { - return convertHexVersionToDecimal (JucePlugin_VersionCode); - } - - pointer_sized_int handleManufacturerSpecific (VstOpCodeArguments args) - { - if (handleManufacturerSpecificVST2Opcode (args.index, args.value, args.ptr, args.opt)) - return 1; - - if (args.index == (int32) ByteOrder::bigEndianInt ("PreS") - && args.value == (int32) ByteOrder::bigEndianInt ("AeCs")) - return handleSetContentScaleFactor (args.opt); - - if (args.index == Vst2::effGetParamDisplay) - return handleCockosGetParameterText (args.value, args.ptr, args.opt); - - if (auto callbackHandler = dynamic_cast (processor.get())) - return callbackHandler->handleVstManufacturerSpecific (args.index, args.value, args.ptr, args.opt); - - return 0; - } - - pointer_sized_int handleCanPlugInDo (VstOpCodeArguments args) - { - auto text = (const char*) args.ptr; - auto matches = [=] (const char* s) { return strcmp (text, s) == 0; }; - - if (matches ("receiveVstEvents") - || matches ("receiveVstMidiEvent") - || matches ("receiveVstMidiEvents")) - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - return 1; - #else - return -1; - #endif - } - - if (matches ("sendVstEvents") - || matches ("sendVstMidiEvent") - || matches ("sendVstMidiEvents")) - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - return 1; - #else - return -1; - #endif - } - - if (matches ("receiveVstTimeInfo") - || matches ("conformsToWindowRules") - || matches ("supportsViewDpiScaling") - || matches ("bypass")) - { - return 1; - } - - // This tells Wavelab to use the UI thread to invoke open/close, - // like all other hosts do. - if (matches ("openCloseAnyThread")) - return -1; - - if (matches ("MPE")) - return processor->supportsMPE() ? 1 : 0; - - #if JUCE_MAC - if (matches ("hasCockosViewAsConfig")) - { - useNSView = true; - return (int32) 0xbeef0000; - } - #endif - - if (matches ("hasCockosExtensions")) - return (int32) 0xbeef0000; - - if (auto callbackHandler = dynamic_cast (processor.get())) - return callbackHandler->handleVstPluginCanDo (args.index, args.value, args.ptr, args.opt); - - return 0; - } - - pointer_sized_int handleGetTailSize (VstOpCodeArguments) - { - if (processor != nullptr) - { - int32 result; - - auto tailSeconds = processor->getTailLengthSeconds(); - - if (tailSeconds == std::numeric_limits::infinity()) - result = std::numeric_limits::max(); - else - result = static_cast (tailSeconds * sampleRate); - - return result; // Vst2 expects an int32 upcasted to a intptr_t here - } - - return 0; - } - - pointer_sized_int handleKeyboardFocusRequired (VstOpCodeArguments) - { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6326) - return (JucePlugin_EditorRequiresKeyboardFocus != 0) ? 1 : 0; - JUCE_END_IGNORE_WARNINGS_MSVC - } - - pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments) - { - return kVstVersion; - } - - pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments) - { - return -1; - } - - pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args) - { - auto** pluginInput = reinterpret_cast (args.value); - auto** pluginOutput = reinterpret_cast (args.ptr); - - if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect()) - return false; - - auto inputLayout = processor->getChannelLayoutOfBus (true, 0); - auto outputLayout = processor->getChannelLayoutOfBus (false, 0); - - const auto speakerBaseSize = offsetof (Vst2::VstSpeakerArrangement, speakers); - - cachedInArrangement .malloc (speakerBaseSize + (static_cast (inputLayout. size()) * sizeof (Vst2::VstSpeakerProperties)), 1); - cachedOutArrangement.malloc (speakerBaseSize + (static_cast (outputLayout.size()) * sizeof (Vst2::VstSpeakerProperties)), 1); - - *pluginInput = cachedInArrangement. getData(); - *pluginOutput = cachedOutArrangement.getData(); - - SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (true, 0), **pluginInput); - SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (false, 0), **pluginOutput); - - return 1; - } - - pointer_sized_int handleSetNumberOfSamplesToProcess (VstOpCodeArguments args) - { - return args.value; - } - - pointer_sized_int handleSetSampleFloatType (VstOpCodeArguments args) - { - if (! isProcessing) - { - if (processor != nullptr) - { - processor->setProcessingPrecision ((args.value == Vst2::kVstProcessPrecision64 - && processor->supportsDoublePrecisionProcessing()) - ? AudioProcessor::doublePrecision - : AudioProcessor::singlePrecision); - - return 1; - } - } - - return 0; - } - - pointer_sized_int handleSetContentScaleFactor ([[maybe_unused]] float scale, [[maybe_unused]] bool force = false) - { - checkWhetherMessageThreadIsCorrect(); - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #else - const MessageManagerLock mmLock; - #endif - - #if ! JUCE_MAC - if (force || ! approximatelyEqual (scale, editorScaleFactor)) - { - editorScaleFactor = scale; - - if (editorComp != nullptr) - editorComp->setContentScaleFactor (editorScaleFactor); - } - #endif - - return 1; - } - - pointer_sized_int handleCockosGetParameterText (pointer_sized_int paramIndex, - void* dest, - float value) - { - if (processor != nullptr && dest != nullptr) - { - if (auto* param = juceParameters.getParamForIndex ((int) paramIndex)) - { - if (! LegacyAudioParameter::isLegacy (param)) - { - String text (param->getText (value, 1024)); - memcpy (dest, text.toRawUTF8(), ((size_t) text.length()) + 1); - return 0xbeef; - } - } - } - - return 0; - } - - //============================================================================== - pointer_sized_int handleGetNumMidiInputChannels() - { - #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect - #ifdef JucePlugin_VSTNumMidiInputs - return JucePlugin_VSTNumMidiInputs; - #else - return 16; - #endif - #else - return 0; - #endif - } - - pointer_sized_int handleGetNumMidiOutputChannels() - { - #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect - #ifdef JucePlugin_VSTNumMidiOutputs - return JucePlugin_VSTNumMidiOutputs; - #else - return 16; - #endif - #else - return 0; - #endif - } - - pointer_sized_int handleEditIdle() - { - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - hostDrivenEventLoop->processPendingEvents(); - #endif - - return 0; - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - Vst2::audioMasterCallback hostCallback; - std::unique_ptr processor; - double sampleRate = 44100.0; - int32 blockSize = 1024; - Vst2::AEffect vstEffect; - CriticalSection stateInformationLock; - juce::MemoryBlock chunkMemory; - uint32 chunkMemoryTime = 0; - float editorScaleFactor = 1.0f; - std::unique_ptr editorComp; - Vst2::ERect editorRect; - MidiBuffer midiEvents; - VSTMidiEventList outgoingEvents; - Optional currentPosition; - - LegacyAudioParametersWrapper juceParameters; - - bool isProcessing = false, isBypassed = false, hasShutdown = false; - bool firstProcessCallback = true, shouldDeleteEditor = false; - - #if JUCE_MAC - #if JUCE_64BIT - bool useNSView = true; - #else - bool useNSView = false; - #endif - #endif - - VstTempBuffers floatTempBuffers; - VstTempBuffers doubleTempBuffers; - int maxNumInChannels = 0, maxNumOutChannels = 0; - - HeapBlock cachedInArrangement, cachedOutArrangement; - - ThreadLocalValue inParameterChangedCallback; - - HostChangeUpdater hostChangeUpdater { *this }; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) -}; - - -//============================================================================== -namespace -{ - Vst2::AEffect* pluginEntryPoint (Vst2::audioMasterCallback audioMaster) - { - JUCE_AUTORELEASEPOOL - { - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer hostDrivenEventLoop; - #endif - - try - { - if (audioMaster (nullptr, Vst2::audioMasterVersion, 0, 0, nullptr, 0) != 0) - { - std::unique_ptr processor { createPluginFilterOfType (AudioProcessor::wrapperType_VST) }; - auto* processorPtr = processor.get(); - auto* wrapper = new JuceVSTWrapper (audioMaster, std::move (processor)); - auto* aEffect = wrapper->getAEffect(); - - if (auto* callbackHandler = dynamic_cast (processorPtr)) - { - callbackHandler->handleVstHostCallbackAvailable ([audioMaster, aEffect] (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) - { - return audioMaster (aEffect, opcode, index, value, ptr, opt); - }); - } - - return aEffect; - } - } - catch (...) - {} - } - - return nullptr; - } -} - -#if ! JUCE_WINDOWS - #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) -#endif - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -//============================================================================== -// Mac startup code.. -#if JUCE_MAC - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - initialiseMacVST(); - return pluginEntryPoint (audioMaster); - } - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster) - { - initialiseMacVST(); - return pluginEntryPoint (audioMaster); - } - -//============================================================================== -// Linux startup code.. -#elif JUCE_LINUX || JUCE_BSD - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) asm ("main"); - JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) - { - return VSTPluginMain (audioMaster); - } - - // don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash! - __attribute__((constructor)) void myPluginInit() {} - __attribute__((destructor)) void myPluginFini() {} - -//============================================================================== -// Win32 startup code.. -#else - - extern "C" __declspec (dllexport) Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) - { - return pluginEntryPoint (audioMaster); - } - - #if ! defined (JUCE_64BIT) && JUCE_MSVC // (can't compile this on win64, but it's not needed anyway with VST2.4) - extern "C" __declspec (dllexport) int main (Vst2::audioMasterCallback audioMaster) - { - return (int) pluginEntryPoint (audioMaster); - } - #endif - - extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) - { - if (reason == DLL_PROCESS_ATTACH) - Process::setCurrentModuleInstanceHandle (instance); - - return true; - } -#endif - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -JUCE_END_IGNORE_WARNINGS_MSVC - -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm deleted file mode 100644 index 652cca3..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.mm +++ /dev/null @@ -1,305 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include - -#if JUCE_MAC - -#include "../utility/juce_CheckSettingMacros.h" - -#if JucePlugin_Build_VST || JucePlugin_Build_VST3 - -#include "../utility/juce_IncludeSystemHeaders.h" -#include "../utility/juce_IncludeModuleHeaders.h" - -//============================================================================== -namespace juce -{ - -#if ! JUCE_64BIT -JUCE_API void updateEditorCompBoundsVST (Component*); -void updateEditorCompBoundsVST (Component* comp) -{ - HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int) - comp->getProperties() ["dummyViewRef"].toString().getHexValue64(); - - HIRect r; - HIViewGetFrame (dummyView, &r); - HIViewRef root; - HIViewFindByID (HIViewGetRoot (HIViewGetWindow (dummyView)), kHIViewWindowContentID, &root); - HIViewConvertRect (&r, HIViewGetSuperview (dummyView), root); - - Rect windowPos; - GetWindowBounds (HIViewGetWindow (dummyView), kWindowContentRgn, &windowPos); - - comp->setTopLeftPosition ((int) (windowPos.left + r.origin.x), - (int) (windowPos.top + r.origin.y)); -} - -static pascal OSStatus viewBoundsChangedEvent (EventHandlerCallRef, EventRef, void* user) -{ - updateEditorCompBoundsVST ((Component*) user); - return noErr; -} - -static bool shouldManuallyCloseHostWindow() -{ - return getHostType().isCubase7orLater() || getHostType().isRenoise() || ((SystemStats::getOperatingSystemType() & 0xff) >= 12); -} -#endif - -//============================================================================== -JUCE_API void initialiseMacVST(); -void initialiseMacVST() -{ - #if ! JUCE_64BIT - NSApplicationLoad(); - #endif -} - -JUCE_API void* attachComponentToWindowRefVST (Component* comp, void* parentWindowOrView, bool isNSView); -void* attachComponentToWindowRefVST (Component* comp, void* parentWindowOrView, [[maybe_unused]] bool isNSView) -{ - JUCE_AUTORELEASEPOOL - { - #if ! JUCE_64BIT - if (! isNSView) - { - NSWindow* hostWindow = [[NSWindow alloc] initWithWindowRef: parentWindowOrView]; - - if (shouldManuallyCloseHostWindow()) - { - [hostWindow setReleasedWhenClosed: NO]; - } - else - { - [hostWindow retain]; - [hostWindow setReleasedWhenClosed: YES]; - } - - [hostWindow setCanHide: YES]; - - HIViewRef parentView = 0; - - WindowAttributes attributes; - GetWindowAttributes ((WindowRef) parentWindowOrView, &attributes); - - if ((attributes & kWindowCompositingAttribute) != 0) - { - HIViewRef root = HIViewGetRoot ((WindowRef) parentWindowOrView); - HIViewFindByID (root, kHIViewWindowContentID, &parentView); - - if (parentView == 0) - parentView = root; - } - else - { - GetRootControl ((WindowRef) parentWindowOrView, (ControlRef*) &parentView); - - if (parentView == 0) - CreateRootControl ((WindowRef) parentWindowOrView, (ControlRef*) &parentView); - } - - // It seems that the only way to successfully position our overlaid window is by putting a dummy - // HIView into the host's carbon window, and then catching events to see when it gets repositioned - HIViewRef dummyView = 0; - HIImageViewCreate (0, &dummyView); - HIRect r = { {0, 0}, { (float) comp->getWidth(), (float) comp->getHeight()} }; - HIViewSetFrame (dummyView, &r); - HIViewAddSubview (parentView, dummyView); - comp->getProperties().set ("dummyViewRef", String::toHexString ((pointer_sized_int) (void*) dummyView)); - - EventHandlerRef ref; - const EventTypeSpec kControlBoundsChangedEvent = { kEventClassControl, kEventControlBoundsChanged }; - InstallEventHandler (GetControlEventTarget (dummyView), NewEventHandlerUPP (viewBoundsChangedEvent), 1, &kControlBoundsChangedEvent, (void*) comp, &ref); - comp->getProperties().set ("boundsEventRef", String::toHexString ((pointer_sized_int) (void*) ref)); - - updateEditorCompBoundsVST (comp); - - #if ! JucePlugin_EditorRequiresKeyboardFocus - comp->addToDesktop (ComponentPeer::windowIsTemporary | ComponentPeer::windowIgnoresKeyPresses); - #else - comp->addToDesktop (ComponentPeer::windowIsTemporary); - #endif - - comp->setVisible (true); - comp->toFront (false); - - NSView* pluginView = (NSView*) comp->getWindowHandle(); - NSWindow* pluginWindow = [pluginView window]; - [pluginWindow setExcludedFromWindowsMenu: YES]; - [pluginWindow setCanHide: YES]; - - [hostWindow addChildWindow: pluginWindow - ordered: NSWindowAbove]; - [hostWindow orderFront: nil]; - [pluginWindow orderFront: nil]; - - return hostWindow; - } - #endif - - NSView* parentView = [(NSView*) parentWindowOrView retain]; - - #if JucePlugin_EditorRequiresKeyboardFocus - comp->addToDesktop (0, parentView); - #else - comp->addToDesktop (ComponentPeer::windowIgnoresKeyPresses, parentView); - #endif - - // (this workaround is because Wavelab provides a zero-size parent view..) - if ([parentView frame].size.height == 0) - [((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; - - comp->setVisible (true); - comp->toFront (false); - - [[parentView window] setAcceptsMouseMovedEvents: YES]; - return parentView; - } -} - -JUCE_API void detachComponentFromWindowRefVST (Component* comp, void* window, bool isNSView); -void detachComponentFromWindowRefVST (Component* comp, void* window, [[maybe_unused]] bool isNSView) -{ - JUCE_AUTORELEASEPOOL - { - #if ! JUCE_64BIT - if (! isNSView) - { - EventHandlerRef ref = (EventHandlerRef) (void*) (pointer_sized_int) - comp->getProperties() ["boundsEventRef"].toString().getHexValue64(); - RemoveEventHandler (ref); - - CFUniquePtr dummyView ((HIViewRef) (void*) (pointer_sized_int) - comp->getProperties() ["dummyViewRef"].toString().getHexValue64()); - - if (HIViewIsValid (dummyView.get())) - dummyView = nullptr; - - NSWindow* hostWindow = (NSWindow*) window; - NSView* pluginView = (NSView*) comp->getWindowHandle(); - NSWindow* pluginWindow = [pluginView window]; - - [pluginView retain]; - [hostWindow removeChildWindow: pluginWindow]; - [pluginWindow close]; - comp->removeFromDesktop(); - [pluginView release]; - - if (shouldManuallyCloseHostWindow()) - [hostWindow close]; - else - [hostWindow release]; - - #if JUCE_MODAL_LOOPS_PERMITTED - static bool needToRunMessageLoop = ! getHostType().isReaper(); - - // The event loop needs to be run between closing the window and deleting the plugin, - // presumably to let the cocoa objects get tidied up. Leaving out this line causes crashes - // in Live when you delete the plugin with its window open. - // (Doing it this way rather than using a single longer timeout means that we can guarantee - // how many messages will be dispatched, which seems to be vital in Reaper) - if (needToRunMessageLoop) - for (int i = 20; --i >= 0;) - MessageManager::getInstance()->runDispatchLoopUntil (1); - #endif - - return; - } - #endif - - comp->removeFromDesktop(); - [(id) window release]; - } -} - -JUCE_API void setNativeHostWindowSizeVST (void* window, Component* component, int newWidth, int newHeight, bool isNSView); -void setNativeHostWindowSizeVST (void* window, Component* component, int newWidth, int newHeight, [[maybe_unused]] bool isNSView) -{ - JUCE_AUTORELEASEPOOL - { - #if ! JUCE_64BIT - if (! isNSView) - { - if (HIViewRef dummyView = (HIViewRef) (void*) (pointer_sized_int) - component->getProperties() ["dummyViewRef"].toString().getHexValue64()) - { - HIRect frameRect; - HIViewGetFrame (dummyView, &frameRect); - frameRect.size.width = newWidth; - frameRect.size.height = newHeight; - HIViewSetFrame (dummyView, &frameRect); - } - - return; - } - #endif - - if (NSView* hostView = (NSView*) window) - { - const int dx = newWidth - component->getWidth(); - const int dy = newHeight - component->getHeight(); - - NSRect r = [hostView frame]; - r.size.width += dx; - r.size.height += dy; - r.origin.y -= dy; - [hostView setFrame: r]; - } - } -} - -JUCE_API void checkWindowVisibilityVST (void* window, Component* comp, bool isNSView); -void checkWindowVisibilityVST ([[maybe_unused]] void* window, - [[maybe_unused]] Component* comp, - [[maybe_unused]] bool isNSView) -{ - #if ! JUCE_64BIT - if (! isNSView) - comp->setVisible ([((NSWindow*) window) isVisible]); - #endif -} - -JUCE_API bool forwardCurrentKeyEventToHostVST (Component* comp, bool isNSView); -bool forwardCurrentKeyEventToHostVST ([[maybe_unused]] Component* comp, [[maybe_unused]] bool isNSView) -{ - #if ! JUCE_64BIT - if (! isNSView) - { - NSWindow* win = [(NSView*) comp->getWindowHandle() window]; - [[win parentWindow] makeKeyWindow]; - repostCurrentNSEvent(); - return true; - } - #endif - - return false; -} - -} // (juce namespace) - -#endif -#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.cpp new file mode 100644 index 0000000..0a3933b --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.cpp @@ -0,0 +1,73 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +// This suppresses a warning in juce_TargetPlatform.h +#ifndef JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED + #define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 +#endif + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc++98-compat-extra-semi", + "-Wdeprecated-declarations", + "-Wexpansion-to-defined", + "-Wfloat-equal", + "-Wformat", + "-Wmissing-prototypes", + "-Wpragma-pack", + "-Wredundant-decls", + "-Wshadow", + "-Wshadow-field", + "-Wshorten-64-to-32", + "-Wsign-conversion", + "-Wzero-as-null-pointer-constant") + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387 6031) + +#ifndef NOMINMAX + #define NOMINMAX 1 +#endif + +#if JUCE_MAC + #include +#elif JUCE_WINDOWS + #include +#elif JUCE_LINUX + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.mm similarity index 95% rename from JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm rename to JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.mm index c382d10..1753cd5 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST_utils.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3ManifestHelper.mm @@ -23,4 +23,4 @@ ============================================================================== */ -#include "VST/juce_VST_Wrapper.mm" +#include "juce_VST3ManifestHelper.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp deleted file mode 100644 index 54d975f..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ /dev/null @@ -1,4239 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include -#include - -//============================================================================== -#if JucePlugin_Build_VST3 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD) - -JUCE_BEGIN_NO_SANITIZE ("vptr") - -#if JUCE_PLUGINHOST_VST3 - #if JUCE_MAC - #include - #endif - #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY - #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 -#endif - -#include - -#undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY -#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 - -#include "../utility/juce_CheckSettingMacros.h" -#include "../utility/juce_IncludeSystemHeaders.h" -#include "../utility/juce_IncludeModuleHeaders.h" -#include "../utility/juce_WindowsHooks.h" -#include "../utility/juce_LinuxMessageThread.h" -#include -#include -#include - -#ifndef JUCE_VST3_CAN_REPLACE_VST2 - #define JUCE_VST3_CAN_REPLACE_VST2 1 -#endif - -#if JUCE_VST3_CAN_REPLACE_VST2 - - #if ! JUCE_MSVC && ! defined (__cdecl) - #define __cdecl - #endif - - namespace Vst2 - { - struct AEffect; - #include "pluginterfaces/vst2.x/vstfxstore.h" - } - -#endif - -#ifndef JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - #if JucePlugin_WantsMidiInput - #define JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS 1 - #endif -#endif - -#if JUCE_LINUX || JUCE_BSD - #include "juce_events/native/juce_linux_EventLoopInternal.h" - #include -#endif - -#if JUCE_MAC - #include -#endif - -//============================================================================== -#if JucePlugin_Enable_ARA - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wpragma-pack") - #include - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - #if ARA_SUPPORT_VERSION_1 - #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current implementation" - #endif - - DEF_CLASS_IID(ARA::IPlugInEntryPoint) - DEF_CLASS_IID(ARA::IPlugInEntryPoint2) - DEF_CLASS_IID(ARA::IMainFactory) -#endif - -namespace juce -{ - -using namespace Steinberg; - -//============================================================================== -#if JUCE_MAC - extern void initialiseMacVST(); - - #if ! JUCE_64BIT - extern void updateEditorCompBoundsVST (Component*); - #endif - - extern JUCE_API void* attachComponentToWindowRefVST (Component*, void* parentWindowOrView, bool isNSView); - extern JUCE_API void detachComponentFromWindowRefVST (Component*, void* nsWindow, bool isNSView); -#endif - -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - double getScaleFactorForWindow (HWND); -#endif - -//============================================================================== -#if JUCE_LINUX || JUCE_BSD - -enum class HostMessageThreadAttached { no, yes }; - -class HostMessageThreadState -{ -public: - template - void setStateWithAction (HostMessageThreadAttached stateIn, Callback&& action) - { - const std::lock_guard lock { m }; - state = stateIn; - action(); - } - - void assertHostMessageThread() - { - const std::lock_guard lock { m }; - - if (state == HostMessageThreadAttached::no) - return; - - JUCE_ASSERT_MESSAGE_THREAD - } - -private: - HostMessageThreadAttached state = HostMessageThreadAttached::no; - std::mutex m; -}; - -class EventHandler final : public Steinberg::Linux::IEventHandler, - private LinuxEventLoopInternal::Listener -{ -public: - EventHandler() - { - LinuxEventLoopInternal::registerLinuxEventLoopListener (*this); - } - - ~EventHandler() override - { - jassert (hostRunLoops.empty()); - - LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this); - - if (! messageThread->isRunning()) - hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::no, - [this]() { messageThread->start(); }); - } - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - return testFor (*this, targetIID, UniqueBase{}).extract (obj); - } - - void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override - { - updateCurrentMessageThread(); - LinuxEventLoopInternal::invokeEventLoopCallbackForFd (fd); - } - - //============================================================================== - void registerHandlerForFrame (IPlugFrame* plugFrame) - { - if (auto* runLoop = getRunLoopFromFrame (plugFrame)) - { - refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.insert (runLoop); }); - updateCurrentMessageThread(); - } - } - - void unregisterHandlerForFrame (IPlugFrame* plugFrame) - { - if (auto* runLoop = getRunLoopFromFrame (plugFrame)) - refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); }); - } - - /* Asserts if it can be established that the calling thread is different from the host's message - thread. - - On Linux this can only be determined if the host has already registered its run loop. Until - then JUCE messages are serviced by a background thread internal to the plugin. - */ - static void assertHostMessageThread() - { - hostMessageThreadState.assertHostMessageThread(); - } - -private: - //============================================================================== - /* Connects all known FDs to a single host event loop instance. */ - class AttachedEventLoop - { - public: - AttachedEventLoop() = default; - - AttachedEventLoop (Steinberg::Linux::IRunLoop* loopIn, Steinberg::Linux::IEventHandler* handlerIn) - : loop (loopIn), handler (handlerIn) - { - for (auto& fd : LinuxEventLoopInternal::getRegisteredFds()) - loop->registerEventHandler (handler, fd); - } - - AttachedEventLoop (AttachedEventLoop&& other) noexcept - { - swap (other); - } - - AttachedEventLoop& operator= (AttachedEventLoop&& other) noexcept - { - swap (other); - return *this; - } - - AttachedEventLoop (const AttachedEventLoop&) = delete; - AttachedEventLoop& operator= (const AttachedEventLoop&) = delete; - - ~AttachedEventLoop() - { - if (loop == nullptr) - return; - - loop->unregisterEventHandler (handler); - } - - private: - void swap (AttachedEventLoop& other) - { - std::swap (other.loop, loop); - std::swap (other.handler, handler); - } - - Steinberg::Linux::IRunLoop* loop = nullptr; - Steinberg::Linux::IEventHandler* handler = nullptr; - }; - - //============================================================================== - static Steinberg::Linux::IRunLoop* getRunLoopFromFrame (IPlugFrame* plugFrame) - { - Steinberg::Linux::IRunLoop* runLoop = nullptr; - - if (plugFrame != nullptr) - plugFrame->queryInterface (Steinberg::Linux::IRunLoop::iid, (void**) &runLoop); - - jassert (runLoop != nullptr); - return runLoop; - } - - void updateCurrentMessageThread() - { - if (! MessageManager::getInstance()->isThisTheMessageThread()) - { - if (messageThread->isRunning()) - messageThread->stop(); - - hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::yes, - [] { MessageManager::getInstance()->setCurrentThreadAsMessageThread(); }); - } - } - - void fdCallbacksChanged() override - { - // The set of active FDs has changed, so deregister from the current event loop and then - // re-register the current set of FDs. - refreshAttachedEventLoop ([]{}); - } - - /* Deregisters from any attached event loop, updates the set of known event loops, and then - attaches all FDs to the first known event loop. - - The same event loop instance is shared between all plugin instances. Every time an event - loop is added or removed, this function should be called to register all FDs with a - suitable event loop. - - Note that there's no API to deregister a single FD for a given event loop. Instead, we must - deregister all FDs, and then register all known FDs again. - */ - template - void refreshAttachedEventLoop (Callback&& modifyKnownRunLoops) - { - // Deregister the old event loop. - // It's important to call the destructor from the old attached loop before calling the - // constructor of the new attached loop. - attachedEventLoop = AttachedEventLoop(); - - modifyKnownRunLoops(); - - // If we still know about an extant event loop, attach to it. - if (hostRunLoops.begin() != hostRunLoops.end()) - attachedEventLoop = AttachedEventLoop (*hostRunLoops.begin(), this); - } - - SharedResourcePointer messageThread; - - std::atomic refCount { 1 }; - - std::multiset hostRunLoops; - AttachedEventLoop attachedEventLoop; - - static HostMessageThreadState hostMessageThreadState; - - //============================================================================== - JUCE_DECLARE_NON_MOVEABLE (EventHandler) - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) -}; - -HostMessageThreadState EventHandler::hostMessageThreadState; - -#endif - -static void assertHostMessageThread() -{ - #if JUCE_LINUX || JUCE_BSD - EventHandler::assertHostMessageThread(); - #else - JUCE_ASSERT_MESSAGE_THREAD - #endif -} - -//============================================================================== -class InParameterChangedCallbackSetter -{ -public: - explicit InParameterChangedCallbackSetter (bool& ref) - : inner ([&]() -> auto& { jassert (! ref); return ref; }(), true, false) {} - -private: - ScopedValueSetter inner; -}; - -template -static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor, - const TUID targetIID, - Member&& member) -{ - if (processor == nullptr) - return {}; - - void* obj = nullptr; - - if (auto* extensions = dynamic_cast (processor)) - { - const auto result = (extensions->*member) (targetIID, &obj); - return { result, obj }; - } - - return {}; -} - -static tresult extractResult (const QueryInterfaceResult& userInterface, - const InterfaceResultWithDeferredAddRef& juceInterface, - void** obj) -{ - if (userInterface.isOk() && juceInterface.isOk()) - { - // If you hit this assertion, you've provided a custom implementation of an interface - // that JUCE implements already. As a result, your plugin may not behave correctly. - // Consider removing your custom implementation. - jassertfalse; - - return userInterface.extract (obj); - } - - if (userInterface.isOk()) - return userInterface.extract (obj); - - return juceInterface.extract (obj); -} - -//============================================================================== -class JuceAudioProcessor : public Vst::IUnitInfo -{ -public: - explicit JuceAudioProcessor (AudioProcessor* source) noexcept - : audioProcessor (source) - { - setupParameters(); - } - - virtual ~JuceAudioProcessor() = default; - - AudioProcessor* get() const noexcept { return audioProcessor.get(); } - - JUCE_DECLARE_VST3_COM_QUERY_METHODS - JUCE_DECLARE_VST3_COM_REF_METHODS - - //============================================================================== - enum InternalParameters - { - paramPreset = 0x70727374, // 'prst' - paramMidiControllerOffset = 0x6d636d00, // 'mdm*' - paramBypass = 0x62797073 // 'byps' - }; - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override - { - return parameterGroups.size() + 1; - } - - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override - { - if (unitIndex == 0) - { - info.id = Vst::kRootUnitId; - info.parentUnitId = Vst::kNoParentUnitId; - info.programListId = getProgramListCount() > 0 - ? static_cast (programParamID) - : Vst::kNoProgramListId; - - toString128 (info.name, TRANS ("Root Unit")); - - return kResultTrue; - } - - if (auto* group = parameterGroups[unitIndex - 1]) - { - info.id = JuceAudioProcessor::getUnitID (group); - info.parentUnitId = JuceAudioProcessor::getUnitID (group->getParent()); - info.programListId = Vst::kNoProgramListId; - - toString128 (info.name, group->getName()); - - return kResultTrue; - } - - return kResultFalse; - } - - Steinberg::int32 PLUGIN_API getProgramListCount() override - { - if (audioProcessor->getNumPrograms() > 0) - return 1; - - return 0; - } - - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override - { - if (listIndex == 0) - { - info.id = static_cast (programParamID); - info.programCount = static_cast (audioProcessor->getNumPrograms()); - - toString128 (info.name, TRANS ("Factory Presets")); - - return kResultTrue; - } - - jassertfalse; - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override - { - if (listId == static_cast (programParamID) - && isPositiveAndBelow ((int) programIndex, audioProcessor->getNumPrograms())) - { - toString128 (name, audioProcessor->getProgramName ((int) programIndex)); - return kResultTrue; - } - - jassertfalse; - toString128 (name, juce::String()); - return kResultFalse; - } - - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } - tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } - tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } - Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } - - tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, Steinberg::int32, Steinberg::int32, Vst::UnitID& unitId) override - { - unitId = Vst::kRootUnitId; - return kResultOk; - } - - //============================================================================== - inline Vst::ParamID getVSTParamIDForIndex (int paramIndex) const noexcept - { - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (paramIndex); - #else - return vstParamIDs.getReference (paramIndex); - #endif - } - - AudioProcessorParameter* getParamForVSTParamID (Vst::ParamID paramID) const noexcept - { - return paramMap[static_cast (paramID)]; - } - - AudioProcessorParameter* getBypassParameter() const noexcept - { - return getParamForVSTParamID (bypassParamID); - } - - AudioProcessorParameter* getProgramParameter() const noexcept - { - return getParamForVSTParamID (programParamID); - } - - static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) - { - if (group == nullptr || group->getParent() == nullptr) - return Vst::kRootUnitId; - - // From the VST3 docs (also applicable to unit IDs!): - // Up to 2^31 parameters can be exported with id range [0, 2147483648] - // (the range [2147483649, 429496729] is reserved for host application). - auto unitID = group->getID().hashCode() & 0x7fffffff; - - // If you hit this assertion then your group ID is hashing to a value - // reserved by the VST3 SDK. Please use a different group ID. - jassert (unitID != Vst::kRootUnitId); - - return unitID; - } - - const Array& getParamIDs() const noexcept { return vstParamIDs; } - Vst::ParamID getBypassParamID() const noexcept { return bypassParamID; } - Vst::ParamID getProgramParamID() const noexcept { return programParamID; } - bool isBypassRegularParameter() const noexcept { return bypassIsRegularParameter; } - - int findCacheIndexForParamID (Vst::ParamID paramID) const noexcept { return vstParamIDs.indexOf (paramID); } - - void setParameterValue (Steinberg::int32 paramIndex, float value) - { - cachedParamValues.set (paramIndex, value); - } - - template - void forAllChangedParameters (Callback&& callback) - { - cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value) - { - callback (cachedParamValues.getParamID (index), value); - }); - } - - bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } - - //============================================================================== - static const FUID iid; - -private: - //============================================================================== - void setupParameters() - { - parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); - - #if JUCE_DEBUG - auto allGroups = parameterGroups; - allGroups.add (&audioProcessor->getParameterTree()); - std::unordered_set unitIDs; - - for (auto* group : allGroups) - { - auto insertResult = unitIDs.insert (getUnitID (group)); - - // If you hit this assertion then either a group ID is not unique or - // you are very unlucky and a hashed group ID is not unique - jassert (insertResult.second); - } - #endif - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - const bool forceLegacyParamIDs = true; - #else - const bool forceLegacyParamIDs = false; - #endif - - juceParameters.update (*audioProcessor, forceLegacyParamIDs); - auto numParameters = juceParameters.getNumParameters(); - - bool vst3WrapperProvidedBypassParam = false; - auto* bypassParameter = audioProcessor->getBypassParameter(); - - if (bypassParameter == nullptr) - { - vst3WrapperProvidedBypassParam = true; - ownedBypassParameter.reset (new AudioParameterBool ("byps", "Bypass", false)); - bypassParameter = ownedBypassParameter.get(); - } - - // if the bypass parameter is not part of the exported parameters that the plug-in supports - // then add it to the end of the list as VST3 requires the bypass parameter to be exported! - bypassIsRegularParameter = juceParameters.contains (audioProcessor->getBypassParameter()); - - if (! bypassIsRegularParameter) - juceParameters.addNonOwning (bypassParameter); - - int i = 0; - for (auto* juceParam : juceParameters) - { - bool isBypassParameter = (juceParam == bypassParameter); - - Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast (i++) - : generateVSTParamIDForParam (juceParam); - - if (isBypassParameter) - { - // we need to remain backward compatible with the old bypass id - if (vst3WrapperProvidedBypassParam) - { - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6240) - vstParamID = static_cast ((isUsingManagedParameters() && ! forceLegacyParamIDs) ? paramBypass : numParameters); - JUCE_END_IGNORE_WARNINGS_MSVC - } - - bypassParamID = vstParamID; - } - - vstParamIDs.add (vstParamID); - paramMap.set (static_cast (vstParamID), juceParam); - } - - auto numPrograms = audioProcessor->getNumPrograms(); - - if (numPrograms > 1) - { - ownedProgramParameter = std::make_unique ("juceProgramParameter", "Program", - 0, numPrograms - 1, - audioProcessor->getCurrentProgram()); - - juceParameters.addNonOwning (ownedProgramParameter.get()); - - if (forceLegacyParamIDs) - programParamID = static_cast (i++); - - vstParamIDs.add (programParamID); - paramMap.set (static_cast (programParamID), ownedProgramParameter.get()); - } - - cachedParamValues = CachedParamValues { { vstParamIDs.begin(), vstParamIDs.end() } }; - } - - Vst::ParamID generateVSTParamIDForParam (const AudioProcessorParameter* param) - { - auto juceParamID = LegacyAudioParameter::getParamID (param, false); - - #if JUCE_FORCE_USE_LEGACY_PARAM_IDS - return static_cast (juceParamID.getIntValue()); - #else - auto paramHash = static_cast (juceParamID.hashCode()); - - #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS - // studio one doesn't like negative parameters - paramHash &= ~(((Vst::ParamID) 1) << (sizeof (Vst::ParamID) * 8 - 1)); - #endif - - return paramHash; - #endif - } - - //============================================================================== - Array vstParamIDs; - CachedParamValues cachedParamValues; - Vst::ParamID bypassParamID = 0, programParamID = static_cast (paramPreset); - bool bypassIsRegularParameter = false; - - //============================================================================== - std::atomic refCount { 0 }; - std::unique_ptr audioProcessor; - - //============================================================================== - LegacyAudioParametersWrapper juceParameters; - HashMap paramMap; - std::unique_ptr ownedBypassParameter, ownedProgramParameter; - Array parameterGroups; - - JuceAudioProcessor() = delete; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) -}; - -class JuceVST3Component; - -static thread_local bool inParameterChangedCallback = false; - -static void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) -{ - if (param.getValue() == newValue) - return; - - const InParameterChangedCallbackSetter scopedSetter { inParameterChangedCallback }; - param.setValueNotifyingHost (newValue); -} - -//============================================================================== -class JuceVST3EditController : public Vst::EditController, - public Vst::IMidiMapping, - public Vst::IUnitInfo, - public Vst::ChannelContext::IInfoListener, - #if JucePlugin_Enable_ARA - public Presonus::IPlugInViewEmbedding, - #endif - public AudioProcessorListener, - private ComponentRestarter::Listener -{ -public: - explicit JuceVST3EditController (Vst::IHostApplication* host) - { - if (host != nullptr) - host->queryInterface (FUnknown::iid, (void**) &hostContext); - - blueCatPatchwork |= isBlueCatHost (host); - } - - //============================================================================== - static const FUID iid; - - //============================================================================== - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override") - - REFCOUNT_METHODS (ComponentBase) - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto userProvidedInterface = queryAdditionalInterfaces (getPluginInstance(), - targetIID, - &VST3ClientExtensions::queryIEditController); - - const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - - return extractResult (userProvidedInterface, juceProvidedInterface, obj); - } - - //============================================================================== - tresult PLUGIN_API initialize (FUnknown* context) override - { - if (hostContext != context) - hostContext = context; - - blueCatPatchwork |= isBlueCatHost (context); - - return kResultTrue; - } - - tresult PLUGIN_API terminate() override - { - if (auto* pluginInstance = getPluginInstance()) - pluginInstance->removeListener (this); - - audioProcessor = nullptr; - - return EditController::terminate(); - } - - //============================================================================== - struct Param : public Vst::Parameter - { - Param (JuceVST3EditController& editController, AudioProcessorParameter& p, - Vst::ParamID vstParamID, Vst::UnitID vstUnitID, - bool isBypassParameter) - : owner (editController), param (p) - { - info.id = vstParamID; - info.unitId = vstUnitID; - - updateParameterInfo(); - - info.stepCount = (Steinberg::int32) 0; - - #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE - if (param.isDiscrete()) - #endif - { - const int numSteps = param.getNumSteps(); - info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); - } - - info.defaultNormalizedValue = param.getDefaultValue(); - jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); - - // Is this a meter? - if ((((unsigned int) param.getCategory() & 0xffff0000) >> 16) == 2) - info.flags = Vst::ParameterInfo::kIsReadOnly; - else - info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; - - if (isBypassParameter) - info.flags |= Vst::ParameterInfo::kIsBypass; - - valueNormalized = info.defaultNormalizedValue; - } - - bool updateParameterInfo() - { - auto updateParamIfChanged = [] (Vst::String128& paramToUpdate, const String& newValue) - { - if (juce::toString (paramToUpdate) == newValue) - return false; - - toString128 (paramToUpdate, newValue); - return true; - }; - - auto anyUpdated = updateParamIfChanged (info.title, param.getName (128)); - anyUpdated |= updateParamIfChanged (info.shortTitle, param.getName (8)); - anyUpdated |= updateParamIfChanged (info.units, param.getLabel()); - - return anyUpdated; - } - - bool setNormalized (Vst::ParamValue v) override - { - v = jlimit (0.0, 1.0, v); - - if (v != valueNormalized) - { - valueNormalized = v; - - // Only update the AudioProcessor here if we're not playing, - // otherwise we get parallel streams of parameter value updates - // during playback - if (! owner.vst3IsPlaying) - setValueAndNotifyIfChanged (param, (float) v); - - changed(); - return true; - } - - return false; - } - - void toString (Vst::ParamValue value, Vst::String128 result) const override - { - if (LegacyAudioParameter::isLegacy (¶m)) - // remain backward-compatible with old JUCE code - toString128 (result, param.getCurrentValueAsText()); - else - toString128 (result, param.getText ((float) value, 128)); - } - - bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override - { - if (! LegacyAudioParameter::isLegacy (¶m)) - { - outValueNormalized = param.getValueForText (getStringFromVstTChars (text)); - return true; - } - - return false; - } - - static String getStringFromVstTChars (const Vst::TChar* text) - { - return juce::String (juce::CharPointer_UTF16 (reinterpret_cast (text))); - } - - Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; } - Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; } - - private: - JuceVST3EditController& owner; - AudioProcessorParameter& param; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) - }; - - //============================================================================== - struct ProgramChangeParameter : public Vst::Parameter - { - ProgramChangeParameter (AudioProcessor& p, Vst::ParamID vstParamID) - : owner (p) - { - jassert (owner.getNumPrograms() > 1); - - info.id = vstParamID; - toString128 (info.title, "Program"); - toString128 (info.shortTitle, "Program"); - toString128 (info.units, ""); - info.stepCount = owner.getNumPrograms() - 1; - info.defaultNormalizedValue = static_cast (owner.getCurrentProgram()) - / static_cast (info.stepCount); - info.unitId = Vst::kRootUnitId; - info.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; - } - - ~ProgramChangeParameter() override = default; - - bool setNormalized (Vst::ParamValue v) override - { - const auto programValue = getProgramValueFromNormalised (v); - - if (programValue != owner.getCurrentProgram()) - owner.setCurrentProgram (programValue); - - if (valueNormalized != v) - { - valueNormalized = v; - changed(); - - return true; - } - - return false; - } - - void toString (Vst::ParamValue value, Vst::String128 result) const override - { - toString128 (result, owner.getProgramName (roundToInt (value * info.stepCount))); - } - - bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override - { - auto paramValueString = getStringFromVstTChars (text); - auto n = owner.getNumPrograms(); - - for (int i = 0; i < n; ++i) - { - if (paramValueString == owner.getProgramName (i)) - { - outValueNormalized = static_cast (i) / info.stepCount; - return true; - } - } - - return false; - } - - static String getStringFromVstTChars (const Vst::TChar* text) - { - return String (CharPointer_UTF16 (reinterpret_cast (text))); - } - - Steinberg::int32 getProgramValueFromNormalised (Vst::ParamValue v) const - { - return jmin (info.stepCount, (Steinberg::int32) (v * (info.stepCount + 1))); - } - - Vst::ParamValue toPlain (Vst::ParamValue v) const override { return getProgramValueFromNormalised (v); } - Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v / info.stepCount; } - - private: - AudioProcessor& owner; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) - }; - - //============================================================================== - tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override - { - if (auto* instance = getPluginInstance()) - { - if (list != nullptr) - { - AudioProcessor::TrackProperties trackProperties; - - { - Vst::String128 channelName; - if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) - trackProperties.name = toString (channelName); - } - - { - int64 colour; - if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) - trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), - Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); - } - - - - if (MessageManager::getInstance()->isThisTheMessageThread()) - instance->updateTrackProperties (trackProperties); - else - MessageManager::callAsync ([trackProperties, instance] - { instance->updateTrackProperties (trackProperties); }); - } - } - - return kResultOk; - } - - //============================================================================== - #if JucePlugin_Enable_ARA - Steinberg::TBool PLUGIN_API isViewEmbeddingSupported() override - { - if (auto* pluginInstance = getPluginInstance()) - return (Steinberg::TBool) dynamic_cast (pluginInstance)->isEditorView(); - return (Steinberg::TBool) false; - } - - Steinberg::tresult PLUGIN_API setViewIsEmbedded (Steinberg::IPlugView* /*view*/, Steinberg::TBool /*embedded*/) override - { - return kResultOk; - } - #endif - - //============================================================================== - tresult PLUGIN_API setComponentState (IBStream* stream) override - { - // As an IEditController member, the host should only call this from the message thread. - assertHostMessageThread(); - - if (auto* pluginInstance = getPluginInstance()) - { - for (auto vstParamId : audioProcessor->getParamIDs()) - { - auto paramValue = [&] - { - if (vstParamId == audioProcessor->getProgramParamID()) - return EditController::plainParamToNormalized (audioProcessor->getProgramParamID(), - pluginInstance->getCurrentProgram()); - - return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); - }(); - - setParamNormalized (vstParamId, paramValue); - } - } - - if (auto* handler = getComponentHandler()) - handler->restartComponent (Vst::kParamValuesChanged); - - return Vst::EditController::setComponentState (stream); - } - - void setAudioProcessor (JuceAudioProcessor* audioProc) - { - if (audioProcessor != audioProc) - installAudioProcessor (audioProc); - } - - tresult PLUGIN_API connect (IConnectionPoint* other) override - { - if (other != nullptr && audioProcessor == nullptr) - { - auto result = ComponentBase::connect (other); - - if (! audioProcessor.loadFrom (other)) - sendIntMessage ("JuceVST3EditController", (Steinberg::int64) (pointer_sized_int) this); - else - installAudioProcessor (audioProcessor); - - return result; - } - - jassertfalse; - return kResultFalse; - } - - //============================================================================== - tresult PLUGIN_API getMidiControllerAssignment ([[maybe_unused]] Steinberg::int32 busIndex, - [[maybe_unused]] Steinberg::int16 channel, - [[maybe_unused]] Vst::CtrlNumber midiControllerNumber, - [[maybe_unused]] Vst::ParamID& resultID) override - { - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - resultID = midiControllerToParameter[channel][midiControllerNumber]; - return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments - #else - return kResultFalse; - #endif - } - - // Converts an incoming parameter index to a MIDI controller: - bool getMidiControllerForParameter (Vst::ParamID index, int& channel, int& ctrlNumber) - { - auto mappedIndex = static_cast (index - parameterToMidiControllerOffset); - - if (isPositiveAndBelow (mappedIndex, numElementsInArray (parameterToMidiController))) - { - auto& mc = parameterToMidiController[mappedIndex]; - - if (mc.channel != -1 && mc.ctrlNumber != -1) - { - channel = jlimit (1, 16, mc.channel + 1); - ctrlNumber = mc.ctrlNumber; - return true; - } - } - - return false; - } - - inline bool isMidiControllerParamID (Vst::ParamID paramID) const noexcept - { - return (paramID >= parameterToMidiControllerOffset - && isPositiveAndBelow (paramID - parameterToMidiControllerOffset, - static_cast (numElementsInArray (parameterToMidiController)))); - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitCount(); - - jassertfalse; - return 1; - } - - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitInfo (unitIndex, info); - - jassertfalse; - if (unitIndex == 0) - { - info.id = Vst::kRootUnitId; - info.parentUnitId = Vst::kNoParentUnitId; - info.programListId = Vst::kNoProgramListId; - - toString128 (info.name, TRANS ("Root Unit")); - - return kResultTrue; - } - - zerostruct (info); - return kResultFalse; - } - - Steinberg::int32 PLUGIN_API getProgramListCount() override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramListCount(); - - jassertfalse; - return 0; - } - - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramListInfo (listIndex, info); - - jassertfalse; - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramName (listId, programIndex, name); - - jassertfalse; - toString128 (name, juce::String()); - return kResultFalse; - } - - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Vst::CString attributeId, Vst::String128 attributeValue) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramInfo (listId, programIndex, attributeId, attributeValue); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override - { - if (audioProcessor != nullptr) - return audioProcessor->hasProgramPitchNames (listId, programIndex); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Steinberg::int16 midiPitch, Vst::String128 name) override - { - if (audioProcessor != nullptr) - return audioProcessor->getProgramPitchName (listId, programIndex, midiPitch, name); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override - { - if (audioProcessor != nullptr) - return audioProcessor->selectUnit (unitId); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, - Steinberg::IBStream* data) override - { - if (audioProcessor != nullptr) - return audioProcessor->setUnitProgramData (listOrUnitId, programIndex, data); - - jassertfalse; - return kResultFalse; - } - - Vst::UnitID PLUGIN_API getSelectedUnit() override - { - if (audioProcessor != nullptr) - return audioProcessor->getSelectedUnit(); - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, - Steinberg::int32 channel, Vst::UnitID& unitId) override - { - if (audioProcessor != nullptr) - return audioProcessor->getUnitByBus (type, dir, busIndex, channel, unitId); - - jassertfalse; - return kResultFalse; - } - - //============================================================================== - IPlugView* PLUGIN_API createView (const char* name) override - { - if (auto* pluginInstance = getPluginInstance()) - { - const auto mayCreateEditor = pluginInstance->hasEditor() - && name != nullptr - && std::strcmp (name, Vst::ViewType::kEditor) == 0 - && (pluginInstance->getActiveEditor() == nullptr - || getHostType().isAdobeAudition() - || getHostType().isPremiere()); - - if (mayCreateEditor) - return new JuceVST3Editor (*this, *audioProcessor); - } - - return nullptr; - } - - //============================================================================== - void beginGesture (Vst::ParamID vstParamId) - { - if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) - beginEdit (vstParamId); - } - - void endGesture (Vst::ParamID vstParamId) - { - if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) - endEdit (vstParamId); - } - - void paramChanged (Steinberg::int32 parameterIndex, Vst::ParamID vstParamId, double newValue) - { - if (inParameterChangedCallback || inSetState) - return; - - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - // NB: Cubase has problems if performEdit is called without setParamNormalized - EditController::setParamNormalized (vstParamId, newValue); - performEdit (vstParamId, newValue); - } - else - { - audioProcessor->setParameterValue (parameterIndex, (float) newValue); - } - } - - //============================================================================== - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override - { - beginGesture (audioProcessor->getVSTParamIDForIndex (index)); - } - - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override - { - endGesture (audioProcessor->getVSTParamIDForIndex (index)); - } - - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override - { - paramChanged (index, audioProcessor->getVSTParamIDForIndex (index), newValue); - } - - void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override - { - int32 flags = 0; - - if (details.parameterInfoChanged) - { - for (int32 i = 0; i < parameters.getParameterCount(); ++i) - if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) - if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) - flags |= Vst::kParamTitlesChanged; - } - - if (auto* pluginInstance = getPluginInstance()) - { - if (details.programChanged) - { - const auto programParameterId = audioProcessor->getProgramParamID(); - - if (audioProcessor->getParamForVSTParamID (programParameterId) != nullptr) - { - const auto currentProgram = pluginInstance->getCurrentProgram(); - const auto paramValue = roundToInt (EditController::normalizedParamToPlain (programParameterId, - EditController::getParamNormalized (programParameterId))); - - if (currentProgram != paramValue) - { - beginGesture (programParameterId); - paramChanged (audioProcessor->findCacheIndexForParamID (programParameterId), - programParameterId, - EditController::plainParamToNormalized (programParameterId, currentProgram)); - endGesture (programParameterId); - - flags |= Vst::kParamValuesChanged; - } - } - } - - auto latencySamples = pluginInstance->getLatencySamples(); - - #if JucePlugin_Enable_ARA - jassert (latencySamples == 0 || ! dynamic_cast (pluginInstance)->isBoundToARA()); - #endif - - if (details.latencyChanged && latencySamples != lastLatencySamples) - { - flags |= Vst::kLatencyChanged; - lastLatencySamples = latencySamples; - } - } - - if (details.nonParameterStateChanged) - flags |= pluginShouldBeMarkedDirtyFlag; - - if (inSetupProcessing) - flags &= Vst::kLatencyChanged; - - componentRestarter.restart (flags); - } - - //============================================================================== - AudioProcessor* getPluginInstance() const noexcept - { - if (audioProcessor != nullptr) - return audioProcessor->get(); - - return nullptr; - } - - static constexpr auto pluginShouldBeMarkedDirtyFlag = 1 << 16; - -private: - bool isBlueCatHost (FUnknown* context) const - { - // We can't use the normal PluginHostType mechanism here because that will give us the name - // of the host process. However, this plugin instance might be loaded in an instance of - // the BlueCat PatchWork host, which might itself be a plugin. - - VSTComSmartPtr host; - host.loadFrom (context); - - if (host == nullptr) - return false; - - Vst::String128 name; - - if (host->getName (name) != kResultOk) - return false; - - const auto hostName = toString (name); - return hostName.contains ("Blue Cat's VST3 Host"); - } - - friend class JuceVST3Component; - friend struct Param; - - //============================================================================== - VSTComSmartPtr audioProcessor; - - struct MidiController - { - int channel = -1, ctrlNumber = -1; - }; - - ComponentRestarter componentRestarter { *this }; - - enum { numMIDIChannels = 16 }; - Vst::ParamID parameterToMidiControllerOffset; - MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; - Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; - - void restartComponentOnMessageThread (int32 flags) override - { - if ((flags & pluginShouldBeMarkedDirtyFlag) != 0) - setDirty (true); - - flags &= ~pluginShouldBeMarkedDirtyFlag; - - if (auto* handler = componentHandler) - handler->restartComponent (flags); - } - - //============================================================================== - struct OwnedParameterListener : public AudioProcessorParameter::Listener - { - OwnedParameterListener (JuceVST3EditController& editController, - AudioProcessorParameter& parameter, - Vst::ParamID paramID, - int cacheIndex) - : owner (editController), - vstParamID (paramID), - parameterIndex (cacheIndex) - { - // We shouldn't be using an OwnedParameterListener for parameters that have - // been added directly to the AudioProcessor. We observe those via the - // normal audioProcessorParameterChanged mechanism. - jassert (parameter.getParameterIndex() == -1); - // The parameter must have a non-negative index in the parameter cache. - jassert (parameterIndex >= 0); - parameter.addListener (this); - } - - void parameterValueChanged (int, float newValue) override - { - owner.paramChanged (parameterIndex, vstParamID, newValue); - } - - void parameterGestureChanged (int, bool gestureIsStarting) override - { - if (gestureIsStarting) - owner.beginGesture (vstParamID); - else - owner.endGesture (vstParamID); - } - - JuceVST3EditController& owner; - const Vst::ParamID vstParamID = Vst::kNoParamId; - const int parameterIndex = -1; - }; - - std::vector> ownedParameterListeners; - - //============================================================================== - bool inSetState = false; - std::atomic vst3IsPlaying { false }, - inSetupProcessing { false }; - - int lastLatencySamples = 0; - bool blueCatPatchwork = isBlueCatHost (hostContext.get()); - - #if ! JUCE_MAC - float lastScaleFactorReceived = 1.0f; - #endif - - InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - SharedBase{}, - UniqueBase{}, - #if JucePlugin_Enable_ARA - UniqueBase{}, - #endif - SharedBase{}); - - if (result.isOk()) - return result; - - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - return { kResultOk, audioProcessor.get() }; - - return {}; - } - - void installAudioProcessor (const VSTComSmartPtr& newAudioProcessor) - { - audioProcessor = newAudioProcessor; - - if (auto* extensions = dynamic_cast (audioProcessor->get())) - { - extensions->setIComponentHandler (componentHandler); - extensions->setIHostApplication (hostContext.get()); - } - - if (auto* pluginInstance = getPluginInstance()) - { - lastLatencySamples = pluginInstance->getLatencySamples(); - - pluginInstance->addListener (this); - - // as the bypass is not part of the regular parameters we need to listen for it explicitly - if (! audioProcessor->isBypassRegularParameter()) - { - const auto paramID = audioProcessor->getBypassParamID(); - ownedParameterListeners.push_back (std::make_unique (*this, - *audioProcessor->getParamForVSTParamID (paramID), - paramID, - audioProcessor->findCacheIndexForParamID (paramID))); - } - - if (parameters.getParameterCount() <= 0) - { - auto n = audioProcessor->getParamIDs().size(); - - for (int i = 0; i < n; ++i) - { - auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); - - if (vstParamID == audioProcessor->getProgramParamID()) - continue; - - auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); - auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); - auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); - - parameters.addParameter (new Param (*this, *juceParam, vstParamID, unitID, - (vstParamID == audioProcessor->getBypassParamID()))); - } - - const auto programParamId = audioProcessor->getProgramParamID(); - - if (auto* programParam = audioProcessor->getParamForVSTParamID (programParamId)) - { - ownedParameterListeners.push_back (std::make_unique (*this, - *programParam, - programParamId, - audioProcessor->findCacheIndexForParamID (programParamId))); - - parameters.addParameter (new ProgramChangeParameter (*pluginInstance, audioProcessor->getProgramParamID())); - } - } - - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - parameterToMidiControllerOffset = static_cast (audioProcessor->isUsingManagedParameters() ? JuceAudioProcessor::paramMidiControllerOffset - : parameters.getParameterCount()); - - initialiseMidiControllerMappings(); - #endif - - audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); - } - } - - void initialiseMidiControllerMappings() - { - for (int c = 0, p = 0; c < numMIDIChannels; ++c) - { - for (int i = 0; i < Vst::kCountCtrlNumber; ++i, ++p) - { - midiControllerToParameter[c][i] = static_cast (p) + parameterToMidiControllerOffset; - parameterToMidiController[p].channel = c; - parameterToMidiController[p].ctrlNumber = i; - - parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)), - static_cast (p) + parameterToMidiControllerOffset, nullptr, 0, 0, - 0, Vst::kRootUnitId)); - } - } - } - - void sendIntMessage (const char* idTag, const Steinberg::int64 value) - { - jassert (hostContext != nullptr); - - if (auto* message = allocateMessage()) - { - const FReleaser releaser (message); - message->setMessageID (idTag); - message->getAttributes()->setInt (idTag, value); - sendMessage (message); - } - } - - class EditorContextMenu : public HostProvidedContextMenu - { - public: - EditorContextMenu (AudioProcessorEditor& editorIn, - VSTComSmartPtr contextMenuIn) - : editor (editorIn), contextMenu (contextMenuIn) {} - - PopupMenu getEquivalentPopupMenu() const override - { - using MenuItem = Steinberg::Vst::IContextMenuItem; - using MenuTarget = Steinberg::Vst::IContextMenuTarget; - - struct Submenu - { - PopupMenu menu; - String name; - bool enabled; - }; - - std::vector menuStack (1); - - for (int32_t i = 0, end = contextMenu->getItemCount(); i < end; ++i) - { - MenuItem item{}; - MenuTarget* target = nullptr; - contextMenu->getItem (i, item, &target); - - if ((item.flags & MenuItem::kIsGroupStart) == MenuItem::kIsGroupStart) - { - menuStack.push_back ({ PopupMenu{}, - toString (item.name), - (item.flags & MenuItem::kIsDisabled) == 0 }); - } - else if ((item.flags & MenuItem::kIsGroupEnd) == MenuItem::kIsGroupEnd) - { - const auto back = menuStack.back(); - menuStack.pop_back(); - - if (menuStack.empty()) - { - // malformed menu - jassertfalse; - return {}; - } - - menuStack.back().menu.addSubMenu (back.name, back.menu, back.enabled); - } - else if ((item.flags & MenuItem::kIsSeparator) == MenuItem::kIsSeparator) - { - menuStack.back().menu.addSeparator(); - } - else - { - VSTComSmartPtr ownedTarget (target); - const auto tag = item.tag; - menuStack.back().menu.addItem (toString (item.name), - (item.flags & MenuItem::kIsDisabled) == 0, - (item.flags & MenuItem::kIsChecked) != 0, - [ownedTarget, tag] { ownedTarget->executeMenuItem (tag); }); - } - } - - if (menuStack.size() != 1) - { - // malformed menu - jassertfalse; - return {}; - } - - return menuStack.back().menu; - } - - void showNativeMenu (Point pos) const override - { - const auto scaled = pos * Component::getApproximateScaleFactorForComponent (&editor); - contextMenu->popup (scaled.x, scaled.y); - } - - private: - AudioProcessorEditor& editor; - VSTComSmartPtr contextMenu; - }; - - class EditorHostContext : public AudioProcessorEditorHostContext - { - public: - EditorHostContext (JuceAudioProcessor& processorIn, - AudioProcessorEditor& editorIn, - Steinberg::Vst::IComponentHandler* handler, - Steinberg::IPlugView* viewIn) - : processor (processorIn), editor (editorIn), componentHandler (handler), view (viewIn) {} - - std::unique_ptr getContextMenuForParameter (const AudioProcessorParameter* parameter) const override - { - if (componentHandler == nullptr || view == nullptr) - return {}; - - Steinberg::FUnknownPtr handler (componentHandler); - - if (handler == nullptr) - return {}; - - const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0; - const auto menu = VSTComSmartPtr (handler->createContextMenu (view, &idToUse)); - return std::make_unique (editor, menu); - } - - private: - JuceAudioProcessor& processor; - AudioProcessorEditor& editor; - Steinberg::Vst::IComponentHandler* componentHandler = nullptr; - Steinberg::IPlugView* view = nullptr; - }; - - //============================================================================== - class JuceVST3Editor : public Vst::EditorView, - public Steinberg::IPlugViewContentScaleSupport, - private Timer - { - public: - JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) - : EditorView (&ec, nullptr), - owner (&ec), - pluginInstance (*p.get()) - { - createContentWrapperComponentIfNeeded(); - - #if JUCE_MAC - if (getHostType().type == PluginHostType::SteinbergCubase10) - cubase10Workaround.reset (new Cubase10WindowResizeWorkaround (*this)); - #endif - } - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto result = testFor (*this, targetIID, UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - return Vst::EditorView::queryInterface (targetIID, obj); - } - - REFCOUNT_METHODS (Vst::EditorView) - - //============================================================================== - tresult PLUGIN_API isPlatformTypeSupported (FIDString type) override - { - if (type != nullptr && pluginInstance.hasEditor()) - { - #if JUCE_WINDOWS - if (strcmp (type, kPlatformTypeHWND) == 0) - #elif JUCE_MAC - if (strcmp (type, kPlatformTypeNSView) == 0 || strcmp (type, kPlatformTypeHIView) == 0) - #elif JUCE_LINUX || JUCE_BSD - if (strcmp (type, kPlatformTypeX11EmbedWindowID) == 0) - #endif - return kResultTrue; - } - - return kResultFalse; - } - - tresult PLUGIN_API attached (void* parent, FIDString type) override - { - if (parent == nullptr || isPlatformTypeSupported (type) == kResultFalse) - return kResultFalse; - - #if JUCE_LINUX || JUCE_BSD - eventHandler->registerHandlerForFrame (plugFrame); - #endif - - systemWindow = parent; - - createContentWrapperComponentIfNeeded(); - - #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - // If the plugin was last opened at a particular scale, try to reapply that scale here. - // Note that we do this during attach(), rather than in JuceVST3Editor(). During the - // constructor, we don't have a host plugFrame, so - // ContentWrapperComponent::resizeHostWindow() won't do anything, and the content - // wrapper component will be left at the wrong size. - applyScaleFactor (StoredScaleFactor{}.withInternal (owner->lastScaleFactorReceived)); - - // Check the host scale factor *before* calling addToDesktop, so that the initial - // window size during addToDesktop is correct for the current platform scale factor. - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->checkHostWindowScaleFactor(); - #endif - - component->setOpaque (true); - component->addToDesktop (0, (void*) systemWindow); - component->setVisible (true); - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->startTimer (500); - #endif - - #else - isNSView = (strcmp (type, kPlatformTypeNSView) == 0); - macHostWindow = juce::attachComponentToWindowRefVST (component.get(), parent, isNSView); - #endif - - component->resizeHostWindow(); - attachedToParent(); - - // Life's too short to faff around with wave lab - if (getHostType().isWavelab()) - startTimer (200); - - return kResultTrue; - } - - tresult PLUGIN_API removed() override - { - if (component != nullptr) - { - #if JUCE_WINDOWS - component->removeFromDesktop(); - #elif JUCE_MAC - if (macHostWindow != nullptr) - { - juce::detachComponentFromWindowRefVST (component.get(), macHostWindow, isNSView); - macHostWindow = nullptr; - } - #endif - - component = nullptr; - } - - #if JUCE_LINUX || JUCE_BSD - eventHandler->unregisterHandlerForFrame (plugFrame); - #endif - - return CPluginView::removed(); - } - - tresult PLUGIN_API onSize (ViewRect* newSize) override - { - if (newSize != nullptr) - { - rect = convertFromHostBounds (*newSize); - - if (component != nullptr) - { - component->setSize (rect.getWidth(), rect.getHeight()); - - #if JUCE_MAC - if (cubase10Workaround != nullptr) - { - cubase10Workaround->triggerAsyncUpdate(); - } - else - #endif - { - if (auto* peer = component->getPeer()) - peer->updateBounds(); - } - } - - return kResultTrue; - } - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API getSize (ViewRect* size) override - { - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - if (getHostType().isAbletonLive() && systemWindow == nullptr) - return kResultFalse; - #endif - - if (size != nullptr && component != nullptr) - { - auto editorBounds = component->getSizeToContainChild(); - - *size = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); - return kResultTrue; - } - - return kResultFalse; - } - - tresult PLUGIN_API canResize() override - { - if (component != nullptr) - if (auto* editor = component->pluginEditor.get()) - if (editor->isResizable()) - return kResultTrue; - - return kResultFalse; - } - - tresult PLUGIN_API checkSizeConstraint (ViewRect* rectToCheck) override - { - if (rectToCheck != nullptr && component != nullptr) - { - if (auto* editor = component->pluginEditor.get()) - { - if (canResize() == kResultFalse) - { - // Ableton Live will call checkSizeConstraint even if the view returns false - // from canResize. Set the out param to an appropriate size for the editor - // and return. - auto constrainedRect = component->getLocalArea (editor, editor->getLocalBounds()) - .getSmallestIntegerContainer(); - - *rectToCheck = convertFromHostBounds (*rectToCheck); - rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); - rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); - *rectToCheck = convertToHostBounds (*rectToCheck); - } - else if (auto* constrainer = editor->getConstrainer()) - { - *rectToCheck = convertFromHostBounds (*rectToCheck); - - auto editorBounds = editor->getLocalArea (component.get(), - Rectangle::leftTopRightBottom (rectToCheck->left, rectToCheck->top, - rectToCheck->right, rectToCheck->bottom).toFloat()); - - auto minW = (float) constrainer->getMinimumWidth(); - auto maxW = (float) constrainer->getMaximumWidth(); - auto minH = (float) constrainer->getMinimumHeight(); - auto maxH = (float) constrainer->getMaximumHeight(); - - auto width = jlimit (minW, maxW, editorBounds.getWidth()); - auto height = jlimit (minH, maxH, editorBounds.getHeight()); - - auto aspectRatio = (float) constrainer->getFixedAspectRatio(); - - if (aspectRatio != 0.0) - { - bool adjustWidth = (width / height > aspectRatio); - - if (getHostType().type == PluginHostType::SteinbergCubase9) - { - auto currentEditorBounds = editor->getBounds().toFloat(); - - if (currentEditorBounds.getWidth() == width && currentEditorBounds.getHeight() != height) - adjustWidth = true; - else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) - adjustWidth = false; - } - - if (adjustWidth) - { - width = height * aspectRatio; - - if (width > maxW || width < minW) - { - width = jlimit (minW, maxW, width); - height = width / aspectRatio; - } - } - else - { - height = width / aspectRatio; - - if (height > maxH || height < minH) - { - height = jlimit (minH, maxH, height); - width = height * aspectRatio; - } - } - } - - auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)) - .getSmallestIntegerContainer(); - - rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); - rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); - - *rectToCheck = convertToHostBounds (*rectToCheck); - } - } - - return kResultTrue; - } - - jassertfalse; - return kResultFalse; - } - - tresult PLUGIN_API setContentScaleFactor ([[maybe_unused]] const Steinberg::IPlugViewContentScaleSupport::ScaleFactor factor) override - { - #if ! JUCE_MAC - const auto scaleToApply = [&] - { - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - // Cubase 10 only sends integer scale factors, so correct this for fractional scales - if (getHostType().type != PluginHostType::SteinbergCubase10) - return factor; - - const auto hostWindowScale = (Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getScaleFactorForWindow (static_cast (systemWindow)); - - if (hostWindowScale <= 0.0 || approximatelyEqual (factor, hostWindowScale)) - return factor; - - return hostWindowScale; - #else - return factor; - #endif - }(); - - applyScaleFactor (scaleFactor.withHost (scaleToApply)); - - return kResultTrue; - #else - return kResultFalse; - #endif - } - - private: - void timerCallback() override - { - stopTimer(); - - ViewRect viewRect; - getSize (&viewRect); - onSize (&viewRect); - } - - static ViewRect convertToHostBounds (ViewRect pluginRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return pluginRect; - - return { roundToInt ((float) pluginRect.left * desktopScale), - roundToInt ((float) pluginRect.top * desktopScale), - roundToInt ((float) pluginRect.right * desktopScale), - roundToInt ((float) pluginRect.bottom * desktopScale) }; - } - - static ViewRect convertFromHostBounds (ViewRect hostRect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return hostRect; - - return { roundToInt ((float) hostRect.left / desktopScale), - roundToInt ((float) hostRect.top / desktopScale), - roundToInt ((float) hostRect.right / desktopScale), - roundToInt ((float) hostRect.bottom / desktopScale) }; - } - - //============================================================================== - struct ContentWrapperComponent : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif - { - ContentWrapperComponent (JuceVST3Editor& editor) : owner (editor) - { - setOpaque (true); - setBroughtToFrontOnMouseClick (true); - } - - ~ContentWrapperComponent() override - { - if (pluginEditor != nullptr) - { - PopupMenu::dismissAllActiveMenus(); - pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); - } - } - - void createEditor (AudioProcessor& plugin) - { - pluginEditor.reset (plugin.createEditorIfNeeded()); - - #if JucePlugin_Enable_ARA - jassert (dynamic_cast (pluginEditor.get()) != nullptr); - // for proper view embedding, ARA plug-ins must be resizable - jassert (pluginEditor->isResizable()); - #endif - - if (pluginEditor != nullptr) - { - editorHostContext = std::make_unique (*owner.owner->audioProcessor, - *pluginEditor, - owner.owner->getComponentHandler(), - &owner); - - pluginEditor->setHostContext (editorHostContext.get()); - #if ! JUCE_MAC - pluginEditor->setScaleFactor (owner.scaleFactor.get()); - #endif - - addAndMakeVisible (pluginEditor.get()); - pluginEditor->setTopLeftPosition (0, 0); - - lastBounds = getSizeToContainChild(); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - setBounds (lastBounds); - } - - resizeHostWindow(); - } - else - { - // if hasEditor() returns true then createEditorIfNeeded has to return a valid editor - jassertfalse; - } - } - - void paint (Graphics& g) override - { - g.fillAll (Colours::black); - } - - juce::Rectangle getSizeToContainChild() - { - if (pluginEditor != nullptr) - return getLocalArea (pluginEditor.get(), pluginEditor->getLocalBounds()); - - return {}; - } - - void childBoundsChanged (Component*) override - { - if (resizingChild) - return; - - auto newBounds = getSizeToContainChild(); - - if (newBounds != lastBounds) - { - resizeHostWindow(); - - #if JUCE_LINUX || JUCE_BSD - if (getHostType().isBitwigStudio()) - repaint(); - #endif - - lastBounds = newBounds; - } - } - - void resized() override - { - if (pluginEditor != nullptr) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); - } - - lastBounds = newBounds; - } - } - } - - void parentSizeChanged() override - { - if (pluginEditor != nullptr) - { - resizeHostWindow(); - pluginEditor->repaint(); - } - } - - void resizeHostWindow() - { - if (pluginEditor != nullptr) - { - if (owner.plugFrame != nullptr) - { - auto editorBounds = getSizeToContainChild(); - auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - owner.plugFrame->resizeView (&owner, &newSize); - } - - auto host = getHostType(); - - #if JUCE_MAC - if (host.isWavelab() || host.isReaper() || owner.owner->blueCatPatchwork) - #else - if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio() || owner.owner->blueCatPatchwork) - #endif - setBounds (editorBounds.withPosition (0, 0)); - } - } - } - - void setEditorScaleFactor (float scale) - { - if (pluginEditor != nullptr) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); - } - - lastBounds = getSizeToContainChild(); - - resizeHostWindow(); - repaint(); - } - } - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor() - { - const auto estimatedScale = (float) getScaleFactorForWindow (static_cast (owner.systemWindow)); - - if (estimatedScale > 0.0) - owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale)); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif - - std::unique_ptr pluginEditor; - - private: - JuceVST3Editor& owner; - std::unique_ptr editorHostContext; - Rectangle lastBounds; - bool resizingChild = false, resizingParent = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) - }; - - void createContentWrapperComponentIfNeeded() - { - if (component == nullptr) - { - #if JUCE_LINUX || JUCE_BSD - const MessageManagerLock mmLock; - #endif - - component.reset (new ContentWrapperComponent (*this)); - component->createEditor (pluginInstance); - } - } - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - SharedResourcePointer eventHandler; - #endif - - VSTComSmartPtr owner; - AudioProcessor& pluginInstance; - - #if JUCE_LINUX || JUCE_BSD - struct MessageManagerLockedDeleter - { - template - void operator() (ObjectType* object) const noexcept - { - const MessageManagerLock mmLock; - delete object; - } - }; - - std::unique_ptr component; - #else - std::unique_ptr component; - #endif - - friend struct ContentWrapperComponent; - - #if JUCE_MAC - void* macHostWindow = nullptr; - bool isNSView = false; - - // On macOS Cubase 10 resizes the host window after calling onSize() resulting in the peer - // bounds being a step behind the plug-in. Calling updateBounds() asynchronously seems to fix things... - struct Cubase10WindowResizeWorkaround : public AsyncUpdater - { - Cubase10WindowResizeWorkaround (JuceVST3Editor& o) : owner (o) {} - - void handleAsyncUpdate() override - { - if (owner.component != nullptr) - if (auto* peer = owner.component->getPeer()) - peer->updateBounds(); - } - - JuceVST3Editor& owner; - }; - - std::unique_ptr cubase10Workaround; - #else - class StoredScaleFactor - { - public: - StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } - StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } - float get() const { return host.value_or (internal); } - - private: - std::optional host; - float internal = 1.0f; - }; - - void applyScaleFactor (const StoredScaleFactor newFactor) - { - const auto previous = std::exchange (scaleFactor, newFactor).get(); - - if (previous == scaleFactor.get()) - return; - - if (owner != nullptr) - owner->lastScaleFactorReceived = scaleFactor.get(); - - if (component != nullptr) - { - #if JUCE_LINUX || JUCE_BSD - const MessageManagerLock mmLock; - #endif - component->setEditorScaleFactor (scaleFactor.get()); - } - } - - StoredScaleFactor scaleFactor; - - #if JUCE_WINDOWS - WindowsHooks hooks; - #endif - - #endif - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Editor) - }; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) -}; - - -//============================================================================== -#if JucePlugin_Enable_ARA - class JuceARAFactory : public ARA::IMainFactory - { - public: - JuceARAFactory() = default; - virtual ~JuceARAFactory() = default; - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const ::Steinberg::TUID targetIID, void** obj) override - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - if (doUIDsMatch (targetIID, JuceARAFactory::iid)) - { - addRef(); - *obj = this; - return kResultOk; - } - - *obj = nullptr; - return kNoInterface; - } - - //---from ARA::IMainFactory------- - const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE - { - return createARAFactory(); - } - static const FUID iid; - - private: - //============================================================================== - std::atomic refCount { 1 }; - }; -#endif - -//============================================================================== -class JuceVST3Component : public Vst::IComponent, - public Vst::IAudioProcessor, - public Vst::IUnitInfo, - public Vst::IConnectionPoint, - public Vst::IProcessContextRequirements, - #if JucePlugin_Enable_ARA - public ARA::IPlugInEntryPoint, - public ARA::IPlugInEntryPoint2, - #endif - public AudioPlayHead -{ -public: - JuceVST3Component (Vst::IHostApplication* h) - : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3).release()), - host (h) - { - inParameterChangedCallback = false; - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - [[maybe_unused]] const int numConfigs = numElementsInArray (configs); - - jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); - - pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); - #endif - - // VST-3 requires your default layout to be non-discrete! - // For example, your default layout must be mono, stereo, quadrophonic - // and not AudioChannelSet::discreteChannels (2) etc. - jassert (checkBusFormatsAreNotDiscrete()); - - comPluginInstance = VSTComSmartPtr { new JuceAudioProcessor (pluginInstance) }; - - zerostruct (processContext); - - processSetup.maxSamplesPerBlock = 1024; - processSetup.processMode = Vst::kRealtime; - processSetup.sampleRate = 44100.0; - processSetup.symbolicSampleSize = Vst::kSample32; - - pluginInstance->setPlayHead (this); - - // Constructing the underlying static object involves dynamic allocation. - // This call ensures that the construction won't happen on the audio thread. - getHostType(); - } - - ~JuceVST3Component() override - { - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - - if (pluginInstance != nullptr) - if (pluginInstance->getPlayHead() == this) - pluginInstance->setPlayHead (nullptr); - } - - //============================================================================== - AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } - - //============================================================================== - static const FUID iid; - - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto userProvidedInterface = queryAdditionalInterfaces (&getPluginInstance(), - targetIID, - &VST3ClientExtensions::queryIAudioProcessor); - - const auto juceProvidedInterface = queryInterfaceInternal (targetIID); - - return extractResult (userProvidedInterface, juceProvidedInterface, obj); - } - - enum class CallPrepareToPlay { no, yes }; - - //============================================================================== - tresult PLUGIN_API initialize (FUnknown* hostContext) override - { - if (host != hostContext) - host.loadFrom (hostContext); - - processContext.sampleRate = processSetup.sampleRate; - preparePlugin (processSetup.sampleRate, (int) processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); - - return kResultTrue; - } - - tresult PLUGIN_API terminate() override - { - getPluginInstance().releaseResources(); - return kResultTrue; - } - - //============================================================================== - tresult PLUGIN_API connect (IConnectionPoint* other) override - { - if (other != nullptr && juceVST3EditController == nullptr) - juceVST3EditController.loadFrom (other); - - return kResultTrue; - } - - tresult PLUGIN_API disconnect (IConnectionPoint*) override - { - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - - juceVST3EditController = {}; - return kResultTrue; - } - - tresult PLUGIN_API notify (Vst::IMessage* message) override - { - if (message != nullptr && juceVST3EditController == nullptr) - { - Steinberg::int64 value = 0; - - if (message->getAttributes()->getInt ("JuceVST3EditController", value) == kResultTrue) - { - juceVST3EditController = VSTComSmartPtr { (JuceVST3EditController*) (pointer_sized_int) value }; - - if (juceVST3EditController != nullptr) - juceVST3EditController->setAudioProcessor (comPluginInstance); - else - jassertfalse; - } - } - - return kResultTrue; - } - - tresult PLUGIN_API getControllerClassId (TUID classID) override - { - memcpy (classID, JuceVST3EditController::iid, sizeof (TUID)); - return kResultTrue; - } - - //============================================================================== - tresult PLUGIN_API setActive (TBool state) override - { - const auto willBeActive = (state != 0); - - active = false; - // Some hosts may call setBusArrangements in response to calls made during prepareToPlay - // or releaseResources. Specifically, Wavelab 11.1 calls setBusArrangements in the same - // call stack when the AudioProcessor calls setLatencySamples inside prepareToPlay. - // In order for setBusArrangements to return successfully, the plugin must not be activated - // until after prepareToPlay has completely finished. - const ScopeGuard scope { [&] { active = willBeActive; } }; - - if (willBeActive) - { - const auto sampleRate = processSetup.sampleRate > 0.0 - ? processSetup.sampleRate - : getPluginInstance().getSampleRate(); - - const auto bufferSize = processSetup.maxSamplesPerBlock > 0 - ? (int) processSetup.maxSamplesPerBlock - : getPluginInstance().getBlockSize(); - - preparePlugin (sampleRate, bufferSize, CallPrepareToPlay::yes); - } - else - { - getPluginInstance().releaseResources(); - } - - return kResultOk; - } - - tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } - tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } - - //============================================================================== - bool isBypassed() const - { - if (auto* bypassParam = comPluginInstance->getBypassParameter()) - return bypassParam->getValue() >= 0.5f; - - return false; - } - - void setBypassed (bool shouldBeBypassed) - { - if (auto* bypassParam = comPluginInstance->getBypassParameter()) - setValueAndNotifyIfChanged (*bypassParam, shouldBeBypassed ? 1.0f : 0.0f); - } - - //============================================================================== - void writeJucePrivateStateInformation (MemoryOutputStream& out) - { - if (pluginInstance->getBypassParameter() == nullptr) - { - ValueTree privateData (kJucePrivateDataIdentifier); - - // for now we only store the bypass value - privateData.setProperty ("Bypass", var (isBypassed()), nullptr); - privateData.writeToStream (out); - } - } - - void setJucePrivateStateInformation (const void* data, int sizeInBytes) - { - if (pluginInstance->getBypassParameter() == nullptr) - { - if (comPluginInstance->getBypassParameter() != nullptr) - { - auto privateData = ValueTree::readFromData (data, static_cast (sizeInBytes)); - setBypassed (static_cast (privateData.getProperty ("Bypass", var (false)))); - } - } - } - - void getStateInformation (MemoryBlock& destData) - { - pluginInstance->getStateInformation (destData); - - // With bypass support, JUCE now needs to store private state data. - // Put this at the end of the plug-in state and add a few null characters - // so that plug-ins built with older versions of JUCE will hopefully ignore - // this data. Additionally, we need to add some sort of magic identifier - // at the very end of the private data so that JUCE has some sort of - // way to figure out if the data was stored with a newer JUCE version. - MemoryOutputStream extraData; - - extraData.writeInt64 (0); - writeJucePrivateStateInformation (extraData); - auto privateDataSize = (int64) (extraData.getDataSize() - sizeof (int64)); - extraData.writeInt64 (privateDataSize); - extraData << kJucePrivateDataIdentifier; - - // write magic string - destData.append (extraData.getData(), extraData.getDataSize()); - } - - void setStateInformation (const void* data, int sizeAsInt) - { - bool unusedState = false; - auto& flagToSet = juceVST3EditController != nullptr ? juceVST3EditController->inSetState : unusedState; - const ScopedValueSetter scope (flagToSet, true); - - auto size = (uint64) sizeAsInt; - - // Check if this data was written with a newer JUCE version - // and if it has the JUCE private data magic code at the end - auto jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier); - - if ((size_t) size >= jucePrivDataIdentifierSize + sizeof (int64)) - { - auto buffer = static_cast (data); - - String magic (CharPointer_UTF8 (buffer + size - jucePrivDataIdentifierSize), - CharPointer_UTF8 (buffer + size)); - - if (magic == kJucePrivateDataIdentifier) - { - // found a JUCE private data section - uint64 privateDataSize; - - std::memcpy (&privateDataSize, - buffer + ((size_t) size - jucePrivDataIdentifierSize - sizeof (uint64)), - sizeof (uint64)); - - privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize); - size -= privateDataSize + jucePrivDataIdentifierSize + sizeof (uint64); - - if (privateDataSize > 0) - setJucePrivateStateInformation (buffer + size, static_cast (privateDataSize)); - - size -= sizeof (uint64); - } - } - - if (size > 0) - pluginInstance->setStateInformation (data, static_cast (size)); - } - - //============================================================================== - #if JUCE_VST3_CAN_REPLACE_VST2 - bool loadVST2VstWBlock (const char* data, int size) - { - jassert (ByteOrder::bigEndianInt ("VstW") == htonl ((uint32) readUnaligned (data))); - jassert (1 == htonl ((uint32) readUnaligned (data + 8))); // version should be 1 according to Steinberg's docs - - auto headerLen = (int) htonl ((uint32) readUnaligned (data + 4)) + 8; - return loadVST2CcnKBlock (data + headerLen, size - headerLen); - } - - bool loadVST2CcnKBlock (const char* data, int size) - { - auto* bank = reinterpret_cast (data); - - jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); - jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); - jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); - jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); - - setStateInformation (bank->content.data.chunk, - jmin ((int) (size - (bank->content.data.chunk - data)), - (int) htonl ((uint32) bank->content.data.size))); - return true; - } - - bool loadVST3PresetFile (const char* data, int size) - { - if (size < 48) - return false; - - // At offset 4 there's a little-endian version number which seems to typically be 1 - // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" - auto chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); - jassert (memcmp (data + chunkListOffset, "List", 4) == 0); - auto entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); - jassert (entryCount > 0); - - for (int i = 0; i < entryCount; ++i) - { - auto entryOffset = chunkListOffset + 8 + 20 * i; - - if (entryOffset + 20 > size) - return false; - - if (memcmp (data + entryOffset, "Comp", 4) == 0) - { - // "Comp" entries seem to contain the data. - auto chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); - auto chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); - - if (static_cast (chunkOffset + chunkSize) > static_cast (size)) - { - jassertfalse; - return false; - } - - loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); - } - } - - return true; - } - - bool loadVST2CompatibleState (const char* data, int size) - { - if (size < 4) - return false; - - auto header = htonl ((uint32) readUnaligned (data)); - - if (header == ByteOrder::bigEndianInt ("VstW")) - return loadVST2VstWBlock (data, size); - - if (header == ByteOrder::bigEndianInt ("CcnK")) - return loadVST2CcnKBlock (data, size); - - if (memcmp (data, "VST3", 4) == 0) - { - // In Cubase 5, when loading VST3 .vstpreset files, - // we get the whole content of the files to load. - // In Cubase 7 we get just the contents within and - // we go directly to the loadVST2VstW codepath instead. - return loadVST3PresetFile (data, size); - } - - return false; - } - #endif - - void loadStateData (const void* data, int size) - { - #if JUCE_VST3_CAN_REPLACE_VST2 - if (loadVST2CompatibleState ((const char*) data, size)) - return; - #endif - setStateInformation (data, size); - } - - bool readFromMemoryStream (IBStream* state) - { - FUnknownPtr s (state); - Steinberg::int64 size = 0; - - if (s != nullptr - && s->getStreamSize (size) == kResultOk - && size > 0 - && size < 1024 * 1024 * 100) // (some hosts seem to return junk for the size) - { - MemoryBlock block (static_cast (size)); - - // turns out that Cubase 9 might give you the incorrect stream size :-( - Steinberg::int32 bytesRead = 1; - int len; - - for (len = 0; bytesRead > 0 && len < static_cast (block.getSize()); len += bytesRead) - if (state->read (block.getData(), static_cast (block.getSize()), &bytesRead) != kResultOk) - break; - - if (len == 0) - return false; - - block.setSize (static_cast (len)); - - // Adobe Audition CS6 hack to avoid trying to use corrupted streams: - if (getHostType().isAdobeAudition()) - if (block.getSize() >= 5 && memcmp (block.getData(), "VC2!E", 5) == 0) - return false; - - loadStateData (block.getData(), (int) block.getSize()); - return true; - } - - return false; - } - - bool readFromUnknownStream (IBStream* state) - { - MemoryOutputStream allData; - - { - const size_t bytesPerBlock = 4096; - HeapBlock buffer (bytesPerBlock); - - for (;;) - { - Steinberg::int32 bytesRead = 0; - auto status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); - - if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab())) - break; - - allData.write (buffer, static_cast (bytesRead)); - } - } - - const size_t dataSize = allData.getDataSize(); - - if (dataSize <= 0 || dataSize >= 0x7fffffff) - return false; - - loadStateData (allData.getData(), (int) dataSize); - return true; - } - - tresult PLUGIN_API setState (IBStream* state) override - { - // The VST3 spec requires that this function is called from the UI thread. - // If this assertion fires, your host is misbehaving! - assertHostMessageThread(); - - if (state == nullptr) - return kInvalidArgument; - - FUnknownPtr stateRefHolder (state); // just in case the caller hasn't properly ref-counted the stream object - - if (state->seek (0, IBStream::kIBSeekSet, nullptr) == kResultTrue) - { - if (! getHostType().isFruityLoops() && readFromMemoryStream (state)) - return kResultTrue; - - if (readFromUnknownStream (state)) - return kResultTrue; - } - - return kResultFalse; - } - - #if JUCE_VST3_CAN_REPLACE_VST2 - static tresult writeVST2Header (IBStream* state, bool bypassed) - { - auto writeVST2IntToState = [state] (uint32 n) - { - auto t = (int32) htonl (n); - return state->write (&t, 4); - }; - - auto status = writeVST2IntToState (ByteOrder::bigEndianInt ("VstW")); - - if (status == kResultOk) status = writeVST2IntToState (8); // header size - if (status == kResultOk) status = writeVST2IntToState (1); // version - if (status == kResultOk) status = writeVST2IntToState (bypassed ? 1 : 0); // bypass - - return status; - } - #endif - - tresult PLUGIN_API getState (IBStream* state) override - { - if (state == nullptr) - return kInvalidArgument; - - MemoryBlock mem; - getStateInformation (mem); - - #if JUCE_VST3_CAN_REPLACE_VST2 - tresult status = writeVST2Header (state, isBypassed()); - - if (status != kResultOk) - return status; - - const int bankBlockSize = 160; - Vst2::fxBank bank; - - zerostruct (bank); - bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); - bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); - bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); - bank.version = (int32) htonl (2); - bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); - bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); - bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); - - status = state->write (&bank, bankBlockSize); - - if (status != kResultOk) - return status; - #endif - - return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getUnitCount() override { return comPluginInstance->getUnitCount(); } - tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override { return comPluginInstance->getUnitInfo (unitIndex, info); } - Steinberg::int32 PLUGIN_API getProgramListCount() override { return comPluginInstance->getProgramListCount(); } - tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override { return comPluginInstance->getProgramListInfo (listIndex, info); } - tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override { return comPluginInstance->getProgramName (listId, programIndex, name); } - tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Vst::CString attributeId, Vst::String128 attributeValue) override { return comPluginInstance->getProgramInfo (listId, programIndex, attributeId, attributeValue); } - tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override { return comPluginInstance->hasProgramPitchNames (listId, programIndex); } - tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, - Steinberg::int16 midiPitch, Vst::String128 name) override { return comPluginInstance->getProgramPitchName (listId, programIndex, midiPitch, name); } - tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override { return comPluginInstance->selectUnit (unitId); } - tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, - Steinberg::IBStream* data) override { return comPluginInstance->setUnitProgramData (listOrUnitId, programIndex, data); } - Vst::UnitID PLUGIN_API getSelectedUnit() override { return comPluginInstance->getSelectedUnit(); } - tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, - Steinberg::int32 channel, Vst::UnitID& unitId) override { return comPluginInstance->getUnitByBus (type, dir, busIndex, channel, unitId); } - - //============================================================================== - Optional getPosition() const override - { - PositionInfo info; - info.setTimeInSamples (jmax ((juce::int64) 0, processContext.projectTimeSamples)); - info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processContext.sampleRate); - info.setIsRecording ((processContext.state & Vst::ProcessContext::kRecording) != 0); - info.setIsPlaying ((processContext.state & Vst::ProcessContext::kPlaying) != 0); - info.setIsLooping ((processContext.state & Vst::ProcessContext::kCycleActive) != 0); - - info.setBpm ((processContext.state & Vst::ProcessContext::kTempoValid) != 0 - ? makeOptional (processContext.tempo) - : nullopt); - - info.setTimeSignature ((processContext.state & Vst::ProcessContext::kTimeSigValid) != 0 - ? makeOptional (TimeSignature { processContext.timeSigNumerator, processContext.timeSigDenominator }) - : nullopt); - - info.setLoopPoints ((processContext.state & Vst::ProcessContext::kCycleValid) != 0 - ? makeOptional (LoopPoints { processContext.cycleStartMusic, processContext.cycleEndMusic }) - : nullopt); - - info.setPpqPosition ((processContext.state & Vst::ProcessContext::kProjectTimeMusicValid) != 0 - ? makeOptional (processContext.projectTimeMusic) - : nullopt); - - info.setPpqPositionOfLastBarStart ((processContext.state & Vst::ProcessContext::kBarPositionValid) != 0 - ? makeOptional (processContext.barPositionMusic) - : nullopt); - - info.setFrameRate ((processContext.state & Vst::ProcessContext::kSmpteValid) != 0 - ? makeOptional (FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) - .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) - .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0)) - : nullopt); - - info.setEditOriginTime (info.getFrameRate().hasValue() - ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) - : nullopt); - - info.setHostTimeNs ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0 - ? makeOptional ((uint64_t) processContext.systemTime) - : nullopt); - - return info; - } - - //============================================================================== - int getNumAudioBuses (bool isInput) const - { - int busCount = pluginInstance->getBusCount (isInput); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; - const int numConfigs = numElementsInArray (configs); - - bool hasOnlyZeroChannels = true; - - for (int i = 0; i < numConfigs && hasOnlyZeroChannels == true; ++i) - if (configs[i][isInput ? 0 : 1] != 0) - hasOnlyZeroChannels = false; - - busCount = jmin (busCount, hasOnlyZeroChannels ? 0 : 1); - #endif - - return busCount; - } - - //============================================================================== - Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override - { - if (type == Vst::kAudio) - return getNumAudioBuses (dir == Vst::kInput); - - if (type == Vst::kEvent) - { - #if JucePlugin_WantsMidiInput - if (dir == Vst::kInput) - return 1; - #endif - - #if JucePlugin_ProducesMidiOutput - if (dir == Vst::kOutput) - return 1; - #endif - } - - return 0; - } - - tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, - Steinberg::int32 index, Vst::BusInfo& info) override - { - if (type == Vst::kAudio) - { - if (index < 0 || index >= getNumAudioBuses (dir == Vst::kInput)) - return kResultFalse; - - if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) - { - info.mediaType = Vst::kAudio; - info.direction = dir; - info.channelCount = bus->getLastEnabledLayout().size(); - jassert (info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (getVst3SpeakerArrangement (bus->getLastEnabledLayout()))); - toString128 (info.name, bus->getName()); - - info.busType = [&] - { - const auto isFirstBus = (index == 0); - - if (dir == Vst::kInput) - { - if (isFirstBus) - { - if (auto* extensions = dynamic_cast (pluginInstance)) - return extensions->getPluginHasMainInput() ? Vst::kMain : Vst::kAux; - - return Vst::kMain; - } - - return Vst::kAux; - } - - #if JucePlugin_IsSynth - return Vst::kMain; - #else - return isFirstBus ? Vst::kMain : Vst::kAux; - #endif - }(); - - #ifdef JucePlugin_PreferredChannelConfigurations - info.flags = Vst::BusInfo::kDefaultActive; - #else - info.flags = (bus->isEnabledByDefault()) ? Vst::BusInfo::kDefaultActive : 0; - #endif - - return kResultTrue; - } - } - - if (type == Vst::kEvent) - { - info.flags = Vst::BusInfo::kDefaultActive; - - #if JucePlugin_WantsMidiInput - if (dir == Vst::kInput && index == 0) - { - info.mediaType = Vst::kEvent; - info.direction = dir; - - #ifdef JucePlugin_VSTNumMidiInputs - info.channelCount = JucePlugin_VSTNumMidiInputs; - #else - info.channelCount = 16; - #endif - - toString128 (info.name, TRANS("MIDI Input")); - info.busType = Vst::kMain; - return kResultTrue; - } - #endif - - #if JucePlugin_ProducesMidiOutput - if (dir == Vst::kOutput && index == 0) - { - info.mediaType = Vst::kEvent; - info.direction = dir; - - #ifdef JucePlugin_VSTNumMidiOutputs - info.channelCount = JucePlugin_VSTNumMidiOutputs; - #else - info.channelCount = 16; - #endif - - toString128 (info.name, TRANS("MIDI Output")); - info.busType = Vst::kMain; - return kResultTrue; - } - #endif - } - - zerostruct (info); - return kResultFalse; - } - - tresult PLUGIN_API activateBus (Vst::MediaType type, - Vst::BusDirection dir, - Steinberg::int32 index, - TBool state) override - { - // The host is misbehaving! The plugin must be deactivated before setting new arrangements. - jassert (! active); - - if (type == Vst::kEvent) - { - #if JucePlugin_WantsMidiInput - if (index == 0 && dir == Vst::kInput) - { - isMidiInputBusEnabled = (state != 0); - return kResultTrue; - } - #endif - - #if JucePlugin_ProducesMidiOutput - if (index == 0 && dir == Vst::kOutput) - { - isMidiOutputBusEnabled = (state != 0); - return kResultTrue; - } - #endif - - return kResultFalse; - } - - if (type == Vst::kAudio) - { - const auto numInputBuses = getNumAudioBuses (true); - const auto numOutputBuses = getNumAudioBuses (false); - - if (! isPositiveAndBelow (index, dir == Vst::kInput ? numInputBuses : numOutputBuses)) - return kResultFalse; - - // The host is allowed to enable/disable buses as it sees fit, so the plugin needs to be - // able to handle any set of enabled/disabled buses, including layouts for which - // AudioProcessor::isBusesLayoutSupported would return false. - // Our strategy is to keep track of the layout that the host last requested, and to - // attempt to apply that layout directly. - // If the layout isn't supported by the processor, we'll try enabling all the buses - // instead. - // If the host enables a bus that the processor refused to enable, then we'll ignore - // that bus (and return silence for output buses). If the host disables a bus that the - // processor refuses to disable, the wrapper will provide the processor with silence for - // input buses, and ignore the contents of output buses. - // Note that some hosts (old bitwig and cakewalk) may incorrectly call this function - // when the plugin is in an activated state. - if (dir == Vst::kInput) - bufferMapper.setInputBusHostActive ((size_t) index, state != 0); - else - bufferMapper.setOutputBusHostActive ((size_t) index, state != 0); - - AudioProcessor::BusesLayout desiredLayout; - - for (auto i = 0; i < numInputBuses; ++i) - desiredLayout.inputBuses.add (bufferMapper.getRequestedLayoutForInputBus ((size_t) i)); - - for (auto i = 0; i < numOutputBuses; ++i) - desiredLayout.outputBuses.add (bufferMapper.getRequestedLayoutForOutputBus ((size_t) i)); - - const auto prev = pluginInstance->getBusesLayout(); - - const auto busesLayoutSupported = [&] - { - #ifdef JucePlugin_PreferredChannelConfigurations - struct ChannelPair - { - short ins, outs; - - auto tie() const { return std::tie (ins, outs); } - bool operator== (ChannelPair x) const { return tie() == x.tie(); } - }; - - const auto countChannels = [] (auto& range) - { - return std::accumulate (range.begin(), range.end(), 0, [] (auto acc, auto set) - { - return acc + set.size(); - }); - }; - - const auto toShort = [] (int x) - { - jassert (0 <= x && x <= std::numeric_limits::max()); - return (short) x; - }; - - const ChannelPair requested { toShort (countChannels (desiredLayout.inputBuses)), - toShort (countChannels (desiredLayout.outputBuses)) }; - const ChannelPair configs[] = { JucePlugin_PreferredChannelConfigurations }; - return std::find (std::begin (configs), std::end (configs), requested) != std::end (configs); - #else - return pluginInstance->checkBusesLayoutSupported (desiredLayout); - #endif - }(); - - if (busesLayoutSupported) - pluginInstance->setBusesLayout (desiredLayout); - else - pluginInstance->enableAllBuses(); - - bufferMapper.updateActiveClientBuses (pluginInstance->getBusesLayout()); - - return kResultTrue; - } - - return kResultFalse; - } - - bool checkBusFormatsAreNotDiscrete() - { - auto numInputBuses = pluginInstance->getBusCount (true); - auto numOutputBuses = pluginInstance->getBusCount (false); - - for (int i = 0; i < numInputBuses; ++i) - { - auto layout = pluginInstance->getChannelLayoutOfBus (true, i); - - if (layout.isDiscreteLayout() && ! layout.isDisabled()) - return false; - } - - for (int i = 0; i < numOutputBuses; ++i) - { - auto layout = pluginInstance->getChannelLayoutOfBus (false, i); - - if (layout.isDiscreteLayout() && ! layout.isDisabled()) - return false; - } - - return true; - } - - tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, - Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override - { - if (active) - { - // The host is misbehaving! The plugin must be deactivated before setting new arrangements. - jassertfalse; - return kResultFalse; - } - - auto numInputBuses = pluginInstance->getBusCount (true); - auto numOutputBuses = pluginInstance->getBusCount (false); - - if (numIns > numInputBuses || numOuts > numOutputBuses) - return false; - - // see the following documentation to understand the correct way to react to this callback - // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 - - const auto requestedLayout = [&] - { - auto result = pluginInstance->getBusesLayout(); - - for (int i = 0; i < numIns; ++i) - result.getChannelSet (true, i) = getChannelSetForSpeakerArrangement (inputs[i]); - - for (int i = 0; i < numOuts; ++i) - result.getChannelSet (false, i) = getChannelSetForSpeakerArrangement (outputs[i]); - - return result; - }(); - - #ifdef JucePlugin_PreferredChannelConfigurations - short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; - if (! AudioProcessor::containsLayout (requestedLayout, configs)) - return kResultFalse; - #endif - - if (pluginInstance->checkBusesLayoutSupported (requestedLayout)) - { - if (! pluginInstance->setBusesLayoutWithoutEnabling (requestedLayout)) - return kResultFalse; - - bufferMapper.updateFromProcessor (*pluginInstance); - return kResultTrue; - } - - // apply layout changes in reverse order as Steinberg says we should prioritize main buses - const auto nextBest = [this, numInputBuses, numOutputBuses, &requestedLayout] - { - auto layout = pluginInstance->getBusesLayout(); - - for (auto busIdx = jmax (numInputBuses, numOutputBuses) - 1; busIdx >= 0; --busIdx) - for (const auto isInput : { true, false }) - if (auto* bus = pluginInstance->getBus (isInput, busIdx)) - bus->isLayoutSupported (requestedLayout.getChannelSet (isInput, busIdx), &layout); - - return layout; - }(); - - if (pluginInstance->setBusesLayoutWithoutEnabling (nextBest)) - bufferMapper.updateFromProcessor (*pluginInstance); - - return kResultFalse; - } - - tresult PLUGIN_API getBusArrangement (Vst::BusDirection dir, Steinberg::int32 index, Vst::SpeakerArrangement& arr) override - { - if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) - { - arr = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); - return kResultTrue; - } - - return kResultFalse; - } - - //============================================================================== - tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override - { - return (symbolicSampleSize == Vst::kSample32 - || (getPluginInstance().supportsDoublePrecisionProcessing() - && symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; - } - - Steinberg::uint32 PLUGIN_API getLatencySamples() override - { - return (Steinberg::uint32) jmax (0, getPluginInstance().getLatencySamples()); - } - - tresult PLUGIN_API setupProcessing (Vst::ProcessSetup& newSetup) override - { - ScopedInSetupProcessingSetter inSetupProcessingSetter (juceVST3EditController); - - if (canProcessSampleSize (newSetup.symbolicSampleSize) != kResultTrue) - return kResultFalse; - - processSetup = newSetup; - processContext.sampleRate = processSetup.sampleRate; - - getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 - ? AudioProcessor::doublePrecision - : AudioProcessor::singlePrecision); - getPluginInstance().setNonRealtime (newSetup.processMode == Vst::kOffline); - - preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); - - return kResultTrue; - } - - tresult PLUGIN_API setProcessing (TBool state) override - { - if (! state) - getPluginInstance().reset(); - - return kResultTrue; - } - - Steinberg::uint32 PLUGIN_API getTailSamples() override - { - auto tailLengthSeconds = getPluginInstance().getTailLengthSeconds(); - - if (tailLengthSeconds <= 0.0 || processSetup.sampleRate <= 0.0) - return Vst::kNoTail; - - if (tailLengthSeconds == std::numeric_limits::infinity()) - return Vst::kInfiniteTail; - - return (Steinberg::uint32) roundToIntAccurate (tailLengthSeconds * processSetup.sampleRate); - } - - //============================================================================== - void processParameterChanges (Vst::IParameterChanges& paramChanges) - { - jassert (pluginInstance != nullptr); - - struct ParamChangeInfo - { - Steinberg::int32 offsetSamples = 0; - double value = 0.0; - }; - - const auto getPointFromQueue = [] (Steinberg::Vst::IParamValueQueue* queue, Steinberg::int32 index) - { - ParamChangeInfo result; - return queue->getPoint (index, result.offsetSamples, result.value) == kResultTrue - ? makeOptional (result) - : nullopt; - }; - - const auto numParamsChanged = paramChanges.getParameterCount(); - - for (Steinberg::int32 i = 0; i < numParamsChanged; ++i) - { - if (auto* paramQueue = paramChanges.getParameterData (i)) - { - const auto vstParamID = paramQueue->getParameterId(); - const auto numPoints = paramQueue->getPointCount(); - - #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) - { - for (Steinberg::int32 point = 0; point < numPoints; ++point) - { - if (const auto change = getPointFromQueue (paramQueue, point)) - addParameterChangeToMidiBuffer (change->offsetSamples, vstParamID, change->value); - } - } - else - #endif - if (const auto change = getPointFromQueue (paramQueue, numPoints - 1)) - { - if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID)) - setValueAndNotifyIfChanged (*param, (float) change->value); - } - } - } - } - - void addParameterChangeToMidiBuffer (const Steinberg::int32 offsetSamples, const Vst::ParamID id, const double value) - { - // If the parameter is mapped to a MIDI CC message then insert it into the midiBuffer. - int channel, ctrlNumber; - - if (juceVST3EditController->getMidiControllerForParameter (id, channel, ctrlNumber)) - { - if (ctrlNumber == Vst::kAfterTouch) - midiBuffer.addEvent (MidiMessage::channelPressureChange (channel, - jlimit (0, 127, (int) (value * 128.0))), offsetSamples); - else if (ctrlNumber == Vst::kPitchBend) - midiBuffer.addEvent (MidiMessage::pitchWheel (channel, - jlimit (0, 0x3fff, (int) (value * 0x4000))), offsetSamples); - else - midiBuffer.addEvent (MidiMessage::controllerEvent (channel, - jlimit (0, 127, ctrlNumber), - jlimit (0, 127, (int) (value * 128.0))), offsetSamples); - } - } - - tresult PLUGIN_API process (Vst::ProcessData& data) override - { - if (pluginInstance == nullptr) - return kResultFalse; - - if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) - return kResultFalse; - - if (data.processContext != nullptr) - { - processContext = *data.processContext; - - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; - } - else - { - zerostruct (processContext); - - if (juceVST3EditController != nullptr) - juceVST3EditController->vst3IsPlaying = false; - } - - midiBuffer.clear(); - - if (data.inputParameterChanges != nullptr) - processParameterChanges (*data.inputParameterChanges); - - #if JucePlugin_WantsMidiInput - if (isMidiInputBusEnabled && data.inputEvents != nullptr) - MidiEventList::toMidiBuffer (midiBuffer, *data.inputEvents); - #endif - - if (getHostType().isWavelab()) - { - const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; - const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; - - if ((pluginInstance->getTotalNumInputChannels() + pluginInstance->getTotalNumOutputChannels()) > 0 - && (numInputChans + numOutputChans) == 0) - return kResultFalse; - } - - // If all of these are zero, the host is attempting to flush parameters without processing audio. - if (data.numSamples != 0 || data.numInputs != 0 || data.numOutputs != 0) - { - if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio (data); - else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio (data); - else jassertfalse; - } - - if (auto* changes = data.outputParameterChanges) - { - comPluginInstance->forAllChangedParameters ([&] (Vst::ParamID paramID, float value) - { - Steinberg::int32 queueIndex = 0; - - if (auto* queue = changes->addParameterData (paramID, queueIndex)) - { - Steinberg::int32 pointIndex = 0; - queue->addPoint (0, value, pointIndex); - } - }); - } - - #if JucePlugin_ProducesMidiOutput - if (isMidiOutputBusEnabled && data.outputEvents != nullptr) - MidiEventList::pluginToHostEventList (*data.outputEvents, midiBuffer); - #endif - - return kResultTrue; - } - -private: - InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - #if JucePlugin_Enable_ARA - UniqueBase{}, - UniqueBase{}, - #endif - SharedBase{}); - - if (result.isOk()) - return result; - - if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) - return { kResultOk, comPluginInstance.get() }; - - return {}; - } - - //============================================================================== - struct ScopedInSetupProcessingSetter - { - ScopedInSetupProcessingSetter (JuceVST3EditController* c) - : controller (c) - { - if (controller != nullptr) - controller->inSetupProcessing = true; - } - - ~ScopedInSetupProcessingSetter() - { - if (controller != nullptr) - controller->inSetupProcessing = false; - } - - private: - JuceVST3EditController* controller = nullptr; - }; - - //============================================================================== - template - void processAudio (Vst::ProcessData& data) - { - ClientRemappedBuffer remappedBuffer { bufferMapper, data }; - auto& buffer = remappedBuffer.buffer; - - jassert ((int) buffer.getNumChannels() == jmax (pluginInstance->getTotalNumInputChannels(), - pluginInstance->getTotalNumOutputChannels())); - - { - const ScopedLock sl (pluginInstance->getCallbackLock()); - - pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); - - #if JUCE_DEBUG && ! JucePlugin_ProducesMidiOutput - const int numMidiEventsComingIn = midiBuffer.getNumEvents(); - #endif - - if (pluginInstance->isSuspended()) - { - buffer.clear(); - } - else - { - // processBlockBypassed should only ever be called if the AudioProcessor doesn't - // return a valid parameter from getBypassParameter - if (pluginInstance->getBypassParameter() == nullptr && comPluginInstance->getBypassParameter()->getValue() >= 0.5f) - pluginInstance->processBlockBypassed (buffer, midiBuffer); - else - pluginInstance->processBlock (buffer, midiBuffer); - } - - #if JUCE_DEBUG && (! JucePlugin_ProducesMidiOutput) - /* This assertion is caused when you've added some events to the - midiMessages array in your processBlock() method, which usually means - that you're trying to send them somewhere. But in this case they're - getting thrown away. - - If your plugin does want to send MIDI messages, you'll need to set - the JucePlugin_ProducesMidiOutput macro to 1 in your - JucePluginCharacteristics.h file. - - If you don't want to produce any MIDI output, then you should clear the - midiMessages array at the end of your processBlock() method, to - indicate that you don't want any of the events to be passed through - to the output. - */ - jassert (midiBuffer.getNumEvents() <= numMidiEventsComingIn); - #endif - } - } - - //============================================================================== - Steinberg::uint32 PLUGIN_API getProcessContextRequirements() override - { - return kNeedSystemTime - | kNeedContinousTimeSamples - | kNeedProjectTimeMusic - | kNeedBarPositionMusic - | kNeedCycleMusic - | kNeedSamplesToNextClock - | kNeedTempo - | kNeedTimeSignature - | kNeedChord - | kNeedFrameRate - | kNeedTransportState; - } - - void preparePlugin (double sampleRate, int bufferSize, CallPrepareToPlay callPrepareToPlay) - { - auto& p = getPluginInstance(); - - p.setRateAndBufferSizeDetails (sampleRate, bufferSize); - - if (callPrepareToPlay == CallPrepareToPlay::yes) - p.prepareToPlay (sampleRate, bufferSize); - - midiBuffer.ensureSize (2048); - midiBuffer.clear(); - - bufferMapper.updateFromProcessor (p); - bufferMapper.prepare (bufferSize); - } - - //============================================================================== - #if JucePlugin_Enable_ARA - const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE - { - return createARAFactory(); - } - - const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentController (ARA::ARADocumentControllerRef /*controllerRef*/) SMTG_OVERRIDE - { - ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); - return nullptr; - } - - const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, - ARA::ARAPlugInInstanceRoleFlags knownRoles, ARA::ARAPlugInInstanceRoleFlags assignedRoles) SMTG_OVERRIDE - { - AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (pluginInstance); - return araAudioProcessorExtension->bindToARA (documentControllerRef, knownRoles, assignedRoles); - } - #endif - - //============================================================================== - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - std::atomic refCount { 1 }; - AudioProcessor* pluginInstance = nullptr; - - #if JUCE_LINUX || JUCE_BSD - template - struct LockedVSTComSmartPtr - { - LockedVSTComSmartPtr() = default; - LockedVSTComSmartPtr (const VSTComSmartPtr& ptrIn) : ptr (ptrIn) {} - LockedVSTComSmartPtr (const LockedVSTComSmartPtr&) = default; - LockedVSTComSmartPtr& operator= (const LockedVSTComSmartPtr&) = default; - - ~LockedVSTComSmartPtr() - { - const MessageManagerLock mmLock; - ptr = {}; - } - - T* operator->() const { return ptr.operator->(); } - T* get() const noexcept { return ptr.get(); } - operator T*() const noexcept { return ptr.get(); } - - template - bool loadFrom (Args&&... args) { return ptr.loadFrom (std::forward (args)...); } - - private: - VSTComSmartPtr ptr; - }; - - LockedVSTComSmartPtr host; - LockedVSTComSmartPtr comPluginInstance; - LockedVSTComSmartPtr juceVST3EditController; - #else - VSTComSmartPtr host; - VSTComSmartPtr comPluginInstance; - VSTComSmartPtr juceVST3EditController; - #endif - - /** - Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, - this object needs to be copied on every call to process() to be up-to-date... - */ - Vst::ProcessContext processContext; - Vst::ProcessSetup processSetup; - - MidiBuffer midiBuffer; - ClientBufferMapper bufferMapper; - - bool active = false; - - #if JucePlugin_WantsMidiInput - std::atomic isMidiInputBusEnabled { true }; - #endif - #if JucePlugin_ProducesMidiOutput - std::atomic isMidiOutputBusEnabled { true }; - #endif - - static const char* kJucePrivateDataIdentifier; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) -}; - -const char* JuceVST3Component::kJucePrivateDataIdentifier = "JUCEPrivateData"; - -//============================================================================== -JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4310) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wall") - -DECLARE_CLASS_IID (JuceAudioProcessor, 0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) -DEF_CLASS_IID (JuceAudioProcessor) - -#if JUCE_VST3_CAN_REPLACE_VST2 - // Defined in PluginUtilities.cpp - void getUUIDForVST2ID (bool, uint8[16]); - - static FUID getFUIDForVST2ID (bool forControllerUID) - { - TUID uuid; - getUUIDForVST2ID (forControllerUID, (uint8*) uuid); - return FUID (uuid); - } - const Steinberg::FUID JuceVST3Component ::iid (getFUIDForVST2ID (false)); - const Steinberg::FUID JuceVST3EditController::iid (getFUIDForVST2ID (true)); -#else - DECLARE_CLASS_IID (JuceVST3EditController, 0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceVST3EditController) - - DECLARE_CLASS_IID (JuceVST3Component, 0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceVST3Component) -#endif - -#if JucePlugin_Enable_ARA - DECLARE_CLASS_IID (JuceARAFactory, 0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) - DEF_CLASS_IID (JuceARAFactory) -#endif - -JUCE_END_IGNORE_WARNINGS_MSVC -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -//============================================================================== -bool initModule(); -bool initModule() -{ - #if JUCE_MAC - initialiseMacVST(); - #endif - - return true; -} - -bool shutdownModule(); -bool shutdownModule() -{ - return true; -} - -#undef JUCE_EXPORTED_FUNCTION - -#if JUCE_WINDOWS - #define JUCE_EXPORTED_FUNCTION -#else - #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) -#endif - -#if JUCE_WINDOWS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - - extern "C" __declspec (dllexport) bool InitDll() { return initModule(); } - extern "C" __declspec (dllexport) bool ExitDll() { return shutdownModule(); } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#elif JUCE_LINUX || JUCE_BSD - void* moduleHandle = nullptr; - int moduleEntryCounter = 0; - - JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle); - JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle) - { - if (++moduleEntryCounter == 1) - { - moduleHandle = sharedLibraryHandle; - return initModule(); - } - - return true; - } - - JUCE_EXPORTED_FUNCTION bool ModuleExit(); - JUCE_EXPORTED_FUNCTION bool ModuleExit() - { - if (--moduleEntryCounter == 0) - { - moduleHandle = nullptr; - return shutdownModule(); - } - - return true; - } -#elif JUCE_MAC - CFBundleRef globalBundleInstance = nullptr; - juce::uint32 numBundleRefs = 0; - juce::Array bundleRefs; - - enum { MaxPathLength = 2048 }; - char modulePath[MaxPathLength] = { 0 }; - void* moduleHandle = nullptr; - - JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref); - JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref) - { - if (ref != nullptr) - { - ++numBundleRefs; - CFRetain (ref); - - bundleRefs.add (ref); - - if (moduleHandle == nullptr) - { - globalBundleInstance = ref; - moduleHandle = ref; - - CFUniquePtr tempURL (CFBundleCopyBundleURL (ref)); - CFURLGetFileSystemRepresentation (tempURL.get(), true, (UInt8*) modulePath, MaxPathLength); - } - } - - return initModule(); - } - - JUCE_EXPORTED_FUNCTION bool bundleExit(); - JUCE_EXPORTED_FUNCTION bool bundleExit() - { - if (shutdownModule()) - { - if (--numBundleRefs == 0) - { - for (int i = 0; i < bundleRefs.size(); ++i) - CFRelease (bundleRefs.getUnchecked (i)); - - bundleRefs.clear(); - } - - return true; - } - - return false; - } -#endif - -//============================================================================== -/** This typedef represents VST3's createInstance() function signature */ -using CreateFunction = FUnknown* (*)(Vst::IHostApplication*); - -static FUnknown* createComponentInstance (Vst::IHostApplication* host) -{ - return static_cast (new JuceVST3Component (host)); -} - -static FUnknown* createControllerInstance (Vst::IHostApplication* host) -{ - return static_cast (new JuceVST3EditController (host)); -} - -#if JucePlugin_Enable_ARA - static FUnknown* createARAFactoryInstance (Vst::IHostApplication* /*host*/) - { - return static_cast (new JuceARAFactory()); - } -#endif - -//============================================================================== -struct JucePluginFactory; -static JucePluginFactory* globalFactory = nullptr; - -//============================================================================== -struct JucePluginFactory : public IPluginFactory3 -{ - JucePluginFactory() - : factoryInfo (JucePlugin_Manufacturer, JucePlugin_ManufacturerWebsite, - JucePlugin_ManufacturerEmail, Vst::kDefaultFactoryFlags) - { - } - - virtual ~JucePluginFactory() - { - if (globalFactory == this) - globalFactory = nullptr; - } - - //============================================================================== - bool registerClass (const PClassInfo2& info, CreateFunction createFunction) - { - if (createFunction == nullptr) - { - jassertfalse; - return false; - } - - auto entry = std::make_unique (info, createFunction); - entry->infoW.fromAscii (info); - - classes.push_back (std::move (entry)); - - return true; - } - - //============================================================================== - JUCE_DECLARE_VST3_COM_REF_METHODS - - tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override - { - const auto result = testForMultiple (*this, - targetIID, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}, - UniqueBase{}); - - if (result.isOk()) - return result.extract (obj); - - jassertfalse; // Something new? - *obj = nullptr; - return kNotImplemented; - } - - //============================================================================== - Steinberg::int32 PLUGIN_API countClasses() override - { - return (Steinberg::int32) classes.size(); - } - - tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) override - { - if (info == nullptr) - return kInvalidArgument; - - memcpy (info, &factoryInfo, sizeof (PFactoryInfo)); - return kResultOk; - } - - tresult PLUGIN_API getClassInfo (Steinberg::int32 index, PClassInfo* info) override - { - return getPClassInfo (index, info); - } - - tresult PLUGIN_API getClassInfo2 (Steinberg::int32 index, PClassInfo2* info) override - { - return getPClassInfo (index, info); - } - - tresult PLUGIN_API getClassInfoUnicode (Steinberg::int32 index, PClassInfoW* info) override - { - if (info != nullptr) - { - if (auto& entry = classes[(size_t) index]) - { - memcpy (info, &entry->infoW, sizeof (PClassInfoW)); - return kResultOk; - } - } - - return kInvalidArgument; - } - - tresult PLUGIN_API createInstance (FIDString cid, FIDString sourceIid, void** obj) override - { - ScopedJuceInitialiser_GUI libraryInitialiser; - - #if JUCE_LINUX || JUCE_BSD - SharedResourcePointer messageThread; - #endif - - *obj = nullptr; - - TUID tuid; - memcpy (tuid, sourceIid, sizeof (TUID)); - - #if VST_VERSION >= 0x030608 - auto sourceFuid = FUID::fromTUID (tuid); - #else - FUID sourceFuid; - sourceFuid = tuid; - #endif - - if (cid == nullptr || sourceIid == nullptr || ! sourceFuid.isValid()) - { - jassertfalse; // The host you're running in has severe implementation issues! - return kInvalidArgument; - } - - TUID iidToQuery; - sourceFuid.toTUID (iidToQuery); - - for (auto& entry : classes) - { - if (doUIDsMatch (entry->infoW.cid, cid)) - { - if (auto* instance = entry->createFunction (host)) - { - const FReleaser releaser (instance); - - if (instance->queryInterface (iidToQuery, obj) == kResultOk) - return kResultOk; - } - - break; - } - } - - return kNoInterface; - } - - tresult PLUGIN_API setHostContext (FUnknown* context) override - { - host.loadFrom (context); - - if (host != nullptr) - { - Vst::String128 name; - host->getName (name); - - return kResultTrue; - } - - return kNotImplemented; - } - -private: - //============================================================================== - std::atomic refCount { 1 }; - const PFactoryInfo factoryInfo; - VSTComSmartPtr host; - - //============================================================================== - struct ClassEntry - { - ClassEntry() noexcept {} - - ClassEntry (const PClassInfo2& info, CreateFunction fn) noexcept - : info2 (info), createFunction (fn) {} - - PClassInfo2 info2; - PClassInfoW infoW; - CreateFunction createFunction = {}; - bool isUnicode = false; - - private: - JUCE_DECLARE_NON_COPYABLE (ClassEntry) - }; - - std::vector> classes; - - //============================================================================== - template - tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) - { - if (info != nullptr) - { - zerostruct (*info); - - if (auto& entry = classes[(size_t) index]) - { - if (entry->isUnicode) - return kResultFalse; - - memcpy (info, (PClassInfoType*) &entry->info2, sizeof (PClassInfoType)); - return kResultOk; - } - } - - jassertfalse; - return kInvalidArgument; - } - - //============================================================================== - // no leak detector here to prevent it firing on shutdown when running in hosts that - // don't release the factory object correctly... - JUCE_DECLARE_NON_COPYABLE (JucePluginFactory) -}; - -} // namespace juce - -//============================================================================== -#ifndef JucePlugin_Vst3ComponentFlags - #if JucePlugin_IsSynth - #define JucePlugin_Vst3ComponentFlags Vst::kSimpleModeSupported - #else - #define JucePlugin_Vst3ComponentFlags 0 - #endif -#endif - -#ifndef JucePlugin_Vst3Category - #if JucePlugin_IsSynth - #define JucePlugin_Vst3Category Vst::PlugType::kInstrumentSynth - #else - #define JucePlugin_Vst3Category Vst::PlugType::kFx - #endif -#endif - -using namespace juce; - -//============================================================================== -// The VST3 plugin entry point. -extern "C" SMTG_EXPORT_SYMBOL IPluginFactory* PLUGIN_API GetPluginFactory() -{ - #if (JUCE_MSVC || (JUCE_WINDOWS && JUCE_CLANG)) && JUCE_32BIT - // Cunning trick to force this function to be exported. Life's too short to - // faff around creating .def files for this kind of thing. - // Unnecessary for 64-bit builds because those don't use decorated function names. - #pragma comment(linker, "/EXPORT:GetPluginFactory=_GetPluginFactory@0") - #endif - - if (globalFactory == nullptr) - { - globalFactory = new JucePluginFactory(); - - static const PClassInfo2 componentClass (JuceVST3Component::iid, - PClassInfo::kManyInstances, - kVstAudioEffectClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (componentClass, createComponentInstance); - - static const PClassInfo2 controllerClass (JuceVST3EditController::iid, - PClassInfo::kManyInstances, - kVstComponentControllerClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (controllerClass, createControllerInstance); - - #if JucePlugin_Enable_ARA - static const PClassInfo2 araFactoryClass (JuceARAFactory::iid, - PClassInfo::kManyInstances, - kARAMainFactoryClass, - JucePlugin_Name, - JucePlugin_Vst3ComponentFlags, - JucePlugin_Vst3Category, - JucePlugin_Manufacturer, - JucePlugin_VersionString, - kVstVersionString); - - globalFactory->registerClass (araFactoryClass, createARAFactoryInstance); - #endif - } - else - { - globalFactory->addRef(); - } - - return dynamic_cast (globalFactory); -} - -//============================================================================== -#if JUCE_WINDOWS -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -JUCE_END_NO_SANITIZE - -#endif //JucePlugin_Build_VST3 diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_CheckSettingMacros.h similarity index 100% rename from JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_CheckSettingMacros.h rename to JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_CheckSettingMacros.h diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_CreatePluginFilter.h similarity index 100% rename from JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_CreatePluginFilter.h rename to JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_CreatePluginFilter.h diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h similarity index 70% rename from JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h rename to JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h index e70d2c1..f52b7cf 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_IncludeModuleHeaders.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_IncludeModuleHeaders.h @@ -23,22 +23,12 @@ ============================================================================== */ +#pragma once + #include -#include "juce_CreatePluginFilter.h" - -namespace juce -{ - #define Component juce::Component - - #if JUCE_MAC - #define Point juce::Point - void repostCurrentNSEvent(); - #endif - - //============================================================================== - inline const PluginHostType& getHostType() - { - static PluginHostType hostType; - return hostType; - } -} + +#define Component juce::Component + +#if JUCE_MAC + #define Point juce::Point +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h similarity index 100% rename from JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h rename to JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_LinuxMessageThread.h similarity index 96% rename from JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h rename to JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_LinuxMessageThread.h index b7a26e5..d52edef 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_LinuxMessageThread.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_LinuxMessageThread.h @@ -25,10 +25,10 @@ #if JUCE_LINUX || JUCE_BSD -namespace juce +namespace juce::detail { -// Implemented in juce_linux_Messaging.cpp +// Implemented in juce_Messaging_linux.cpp bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); /** @internal */ @@ -112,6 +112,6 @@ class HostDrivenEventLoop SharedResourcePointer messageThread; }; -} // namespace juce +} // namespace juce::detail #endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h new file mode 100644 index 0000000..9dde07f --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_PluginUtilities.h @@ -0,0 +1,167 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#include + +namespace juce::detail +{ + +struct PluginUtilities +{ + PluginUtilities() = delete; + + static int getDesktopFlags (const AudioProcessorEditor& editor) + { + return editor.wantsLayerBackedView() + ? 0 + : ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering; + } + + static int getDesktopFlags (const AudioProcessorEditor* editor) + { + return editor != nullptr ? getDesktopFlags (*editor) : 0; + } + + static void addToDesktop (AudioProcessorEditor& editor, void* parent) + { + editor.addToDesktop (getDesktopFlags (editor), parent); + } + + static const PluginHostType& getHostType() + { + static PluginHostType hostType; + return hostType; + } + + #ifndef JUCE_VST3_CAN_REPLACE_VST2 + #define JUCE_VST3_CAN_REPLACE_VST2 1 + #endif + + // NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. + static void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) + { + #if JUCE_WINDOWS && ! JUCE_MINGW + const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; + #else + const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; + const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; + const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; + const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; + #endif + + char uidString[33]; + + const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); + char vstfxidStr[7] = { 0 }; + juce_sprintf (vstfxidStr, "%06X", vstfxid); + + juce_strcpy (uidString, vstfxidStr); + + char uidStr[9] = { 0 }; + juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); + juce_strcat (uidString, uidStr); + + char nameidStr[3] = { 0 }; + const size_t len = strlen (JucePlugin_Name); + + for (size_t i = 0; i <= 8; ++i) + { + juce::uint8 c = i < len ? static_cast (JucePlugin_Name[i]) : 0; + + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + juce_sprintf (nameidStr, "%02X", c); + juce_strcat (uidString, nameidStr); + } + + unsigned long p0; + unsigned int p1, p2; + unsigned int p3[8]; + + juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", + &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); + + union q0_u { + uint32 word; + uint8 bytes[4]; + } q0; + + union q1_u { + uint16 half; + uint8 bytes[2]; + } q1, q2; + + q0.word = static_cast (p0); + q1.half = static_cast (p1); + q2.half = static_cast (p2); + + // VST3 doesn't use COM compatible UUIDs on non windows platforms + #if ! JUCE_WINDOWS + q0.word = ByteOrder::swap (q0.word); + q1.half = ByteOrder::swap (q1.half); + q2.half = ByteOrder::swap (q2.half); + #endif + + for (int i = 0; i < 4; ++i) + uuid[i+0] = q0.bytes[i]; + + for (int i = 0; i < 2; ++i) + uuid[i+4] = q1.bytes[i]; + + for (int i = 0; i < 2; ++i) + uuid[i+6] = q2.bytes[i]; + + for (int i = 0; i < 8; ++i) + uuid[i+8] = static_cast (p3[i]); + } + + #if JucePlugin_Build_VST + static bool handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, + [[maybe_unused]] pointer_sized_int value, + [[maybe_unused]] void* ptr, + float) + { + #if JUCE_VST3_CAN_REPLACE_VST2 + if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) + && value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) + { + uint8 fuid[16]; + getUUIDForVST2ID (false, fuid); + ::memcpy (ptr, fuid, 16); + return true; + } + #endif + return false; + } + #endif +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h new file mode 100644 index 0000000..30f48f3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h @@ -0,0 +1,98 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +#if JUCE_MAC + +#include + +namespace juce::detail +{ + +struct VSTWindowUtilities +{ + VSTWindowUtilities() = delete; + + static void* attachComponentToWindowRefVST (Component* comp, + int desktopFlags, + void* parentWindowOrView) + { + JUCE_AUTORELEASEPOOL + { + NSView* parentView = [(NSView*) parentWindowOrView retain]; + + const auto defaultFlags = JucePlugin_EditorRequiresKeyboardFocus + ? 0 + : ComponentPeer::windowIgnoresKeyPresses; + comp->addToDesktop (desktopFlags | defaultFlags, parentView); + + // (this workaround is because Wavelab provides a zero-size parent view..) + if (approximatelyEqual ([parentView frame].size.height, 0.0)) + [((NSView*) comp->getWindowHandle()) setFrameOrigin: NSZeroPoint]; + + comp->setVisible (true); + comp->toFront (false); + + [[parentView window] setAcceptsMouseMovedEvents: YES]; + return parentView; + } + } + + static void detachComponentFromWindowRefVST (Component* comp, + void* window) + { + JUCE_AUTORELEASEPOOL + { + comp->removeFromDesktop(); + [(id) window release]; + } + } + + static void setNativeHostWindowSizeVST (void* window, + Component* component, + int newWidth, + int newHeight) + { + JUCE_AUTORELEASEPOOL + { + if (NSView* hostView = (NSView*) window) + { + const int dx = newWidth - component->getWidth(); + const int dy = newHeight - component->getHeight(); + + NSRect r = [hostView frame]; + r.size.width += dx; + r.size.height += dy; + r.origin.y -= dy; + [hostView setFrame: r]; + } + } + } +}; + +} // namespace juce::detail + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client.h b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client.h index 3ea1131..6405757 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client.h +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client.h @@ -35,9 +35,9 @@ ID: juce_audio_plugin_client vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE audio plugin wrapper classes - description: Classes for building VST, VST3, AU, AUv3 and AAX plugins. + description: Classes for building VST, VST3, AU, AUv3, LV2 and AAX plugins. website: http://www.juce.com/juce license: GPL/Commercial minimumCppStandard: 17 @@ -127,4 +127,4 @@ #define JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE (JUCE_IOS || JUCE_ANDROID) #endif -#include "utility/juce_CreatePluginFilter.h" +#include "detail/juce_CreatePluginFilter.h" diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp index a0c6f7d..3c9bac6 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.cpp @@ -23,4 +23,2683 @@ ============================================================================== */ -#include "AAX/juce_AAX_Wrapper.cpp" +#include +#include + +#if JucePlugin_Build_AAX + +#include +#include +#include + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127 4512 4996) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", + "-Wextra-semi", + "-Wfloat-equal", + "-Wfour-char-constants", + "-Winconsistent-missing-destructor-override", + "-Wnon-virtual-dtor", + "-Wpragma-pack", + "-Wshift-sign-overflow", + "-Wsign-conversion", + "-Wtautological-overlap-compare", + "-Wzero-as-null-pointer-constant", + "-Wdeprecated-copy-with-user-provided-dtor", + "-Wdeprecated") + +#include + +static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p4p0_REVISION, "JUCE requires AAX SDK version 2.4.0 or higher"); + +#if defined (AAX_SDK_2p5p0_REVISION) && AAX_SDK_2p5p0_REVISION <= AAX_SDK_CURRENT_REVISION + #define JUCE_AAX_SDK_2p5p0 1 +#else + #define JUCE_AAX_SDK_2p5p0 0 +#endif + +#define INITACFIDS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfour-char-constants") + +#undef check + +#include + +using namespace juce; + +#ifndef JucePlugin_AAX_Chunk_Identifier + #define JucePlugin_AAX_Chunk_Identifier 'juce' +#endif + +const int32_t juceChunkType = JucePlugin_AAX_Chunk_Identifier; + +//============================================================================== +namespace AAXClasses +{ + static int32 getAAXParamHash (AAX_CParamID paramID) noexcept + { + int32 result = 0; + + while (*paramID != 0) + result = (31 * result) + (*paramID++); + + return result; + } + + static void check (AAX_Result result) + { + jassertquiet (result == AAX_SUCCESS); + } + + // maps a channel index of an AAX format to an index of a juce format + struct AAXChannelStreamOrder + { + AAX_EStemFormat aaxStemFormat; + std::initializer_list speakerOrder; + }; + + static AAX_EStemFormat stemFormatForAmbisonicOrder (int order) + { + switch (order) + { + case 1: return AAX_eStemFormat_Ambi_1_ACN; + case 2: return AAX_eStemFormat_Ambi_2_ACN; + case 3: return AAX_eStemFormat_Ambi_3_ACN; + #if JUCE_AAX_SDK_2p5p0 + case 4: return AAX_eStemFormat_Ambi_4_ACN; + case 5: return AAX_eStemFormat_Ambi_5_ACN; + case 6: return AAX_eStemFormat_Ambi_6_ACN; + case 7: return AAX_eStemFormat_Ambi_7_ACN; + #endif + default: break; + } + + return AAX_eStemFormat_INT32_MAX; + } + + static AAXChannelStreamOrder aaxChannelOrder[] = + { + { AAX_eStemFormat_Mono, { AudioChannelSet::centre } }, + + { AAX_eStemFormat_Stereo, { AudioChannelSet::left, AudioChannelSet::right } }, + + { AAX_eStemFormat_LCR, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right } }, + + { AAX_eStemFormat_LCRS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::centreSurround } }, + + { AAX_eStemFormat_Quad, { AudioChannelSet::left, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround } }, + + { AAX_eStemFormat_5_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround } }, + + { AAX_eStemFormat_5_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::LFE } }, + + { AAX_eStemFormat_6_0, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, + AudioChannelSet::rightSurround } }, + + { AAX_eStemFormat_6_1, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::centreSurround, + AudioChannelSet::rightSurround, AudioChannelSet::LFE } }, + + { AAX_eStemFormat_7_0_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, + AudioChannelSet::leftSurround, AudioChannelSet::rightSurround } }, + + { AAX_eStemFormat_7_0_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear } }, + + { AAX_eStemFormat_7_1_SDDS, { AudioChannelSet::left, AudioChannelSet::leftCentre, AudioChannelSet::centre, AudioChannelSet::rightCentre, AudioChannelSet::right, + AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, AudioChannelSet::LFE } }, + + { AAX_eStemFormat_7_1_DTS, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE } }, + + { AAX_eStemFormat_7_0_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, + + { AAX_eStemFormat_7_1_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, + + #if JUCE_AAX_SDK_2p5p0 + { AAX_eStemFormat_5_0_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, + + { AAX_eStemFormat_5_1_2, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::LFE, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight } }, + + { AAX_eStemFormat_5_0_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_5_1_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurround, AudioChannelSet::rightSurround, + AudioChannelSet::LFE, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_7_0_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, + AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_7_1_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topFrontLeft, + AudioChannelSet::topFrontRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_7_0_6, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, + AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_7_1_6, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, + AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, AudioChannelSet::LFE, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, + AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_9_0_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::wideLeft, AudioChannelSet::wideRight, + AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, + AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_9_1_4, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::wideLeft, AudioChannelSet::wideRight, + AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, + AudioChannelSet::LFE, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_9_0_6, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::wideLeft, AudioChannelSet::wideRight, + AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, + AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, AudioChannelSet::topRearLeft, + AudioChannelSet::topRearRight } }, + + { AAX_eStemFormat_9_1_6, { AudioChannelSet::left, AudioChannelSet::centre, AudioChannelSet::right, AudioChannelSet::wideLeft, AudioChannelSet::wideRight, + AudioChannelSet::leftSurroundSide, AudioChannelSet::rightSurroundSide, AudioChannelSet::leftSurroundRear, AudioChannelSet::rightSurroundRear, + AudioChannelSet::LFE, AudioChannelSet::topFrontLeft, AudioChannelSet::topFrontRight, AudioChannelSet::topSideLeft, AudioChannelSet::topSideRight, + AudioChannelSet::topRearLeft, AudioChannelSet::topRearRight } }, + #endif + + { AAX_eStemFormat_None, {} }, + }; + + static AAX_EStemFormat aaxFormats[] = + { + AAX_eStemFormat_Mono, + AAX_eStemFormat_Stereo, + AAX_eStemFormat_LCR, + AAX_eStemFormat_LCRS, + AAX_eStemFormat_Quad, + AAX_eStemFormat_5_0, + AAX_eStemFormat_5_1, + AAX_eStemFormat_6_0, + AAX_eStemFormat_6_1, + AAX_eStemFormat_7_0_SDDS, + AAX_eStemFormat_7_1_SDDS, + AAX_eStemFormat_7_0_DTS, + AAX_eStemFormat_7_1_DTS, + AAX_eStemFormat_7_0_2, + AAX_eStemFormat_7_1_2, + AAX_eStemFormat_Ambi_1_ACN, + AAX_eStemFormat_Ambi_2_ACN, + AAX_eStemFormat_Ambi_3_ACN, + #if JUCE_AAX_SDK_2p5p0 + AAX_eStemFormat_5_0_2, + AAX_eStemFormat_5_1_2, + AAX_eStemFormat_5_0_4, + AAX_eStemFormat_5_1_4, + AAX_eStemFormat_7_0_4, + AAX_eStemFormat_7_1_4, + AAX_eStemFormat_7_0_6, + AAX_eStemFormat_7_1_6, + AAX_eStemFormat_9_0_4, + AAX_eStemFormat_9_1_4, + AAX_eStemFormat_9_0_6, + AAX_eStemFormat_9_1_6, + AAX_eStemFormat_Ambi_4_ACN, + AAX_eStemFormat_Ambi_5_ACN, + AAX_eStemFormat_Ambi_6_ACN, + AAX_eStemFormat_Ambi_7_ACN, + #endif + }; + + static AAX_EStemFormat getFormatForAudioChannelSet (const AudioChannelSet& set, bool ignoreLayout) noexcept + { + // if the plug-in ignores layout, it is ok to convert between formats only by their numchannnels + if (ignoreLayout) + { + auto numChannels = set.size(); + + switch (numChannels) + { + case 0: return AAX_eStemFormat_None; + case 1: return AAX_eStemFormat_Mono; + case 2: return AAX_eStemFormat_Stereo; + case 3: return AAX_eStemFormat_LCR; + case 4: return AAX_eStemFormat_Quad; + case 5: return AAX_eStemFormat_5_0; + case 6: return AAX_eStemFormat_5_1; + case 7: return AAX_eStemFormat_7_0_DTS; + case 8: return AAX_eStemFormat_7_1_DTS; + case 9: return AAX_eStemFormat_7_0_2; + case 10: return AAX_eStemFormat_7_1_2; + #if JUCE_AAX_SDK_2p5p0 + case 11: return AAX_eStemFormat_7_0_4; + case 12: return AAX_eStemFormat_7_1_4; + case 13: return AAX_eStemFormat_9_0_4; + case 14: return AAX_eStemFormat_9_1_4; + case 15: return AAX_eStemFormat_9_0_6; + case 16: return AAX_eStemFormat_9_1_6; + #endif + default: break; + } + + const auto maybeAmbisonicOrder = AudioChannelSet::getAmbisonicOrderForNumChannels (numChannels); + + if (maybeAmbisonicOrder != -1) + return stemFormatForAmbisonicOrder (maybeAmbisonicOrder); + + return AAX_eStemFormat_INT32_MAX; + } + + if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; + if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; + if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; + if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; + if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; + if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; + if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; + if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; + if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; + if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; + if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; + if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; + if (set == AudioChannelSet::create7point0SDDS()) return AAX_eStemFormat_7_0_SDDS; + if (set == AudioChannelSet::create7point1SDDS()) return AAX_eStemFormat_7_1_SDDS; + if (set == AudioChannelSet::create7point0point2()) return AAX_eStemFormat_7_0_2; + if (set == AudioChannelSet::create7point1point2()) return AAX_eStemFormat_7_1_2; + + #if JUCE_AAX_SDK_2p5p0 + if (set == AudioChannelSet::create5point0point2()) return AAX_eStemFormat_5_0_2; + if (set == AudioChannelSet::create5point1point2()) return AAX_eStemFormat_5_1_2; + if (set == AudioChannelSet::create5point0point4()) return AAX_eStemFormat_5_0_4; + if (set == AudioChannelSet::create5point1point4()) return AAX_eStemFormat_5_1_4; + if (set == AudioChannelSet::create7point0point4()) return AAX_eStemFormat_7_0_4; + if (set == AudioChannelSet::create7point1point4()) return AAX_eStemFormat_7_1_4; + if (set == AudioChannelSet::create7point0point6()) return AAX_eStemFormat_7_0_6; + if (set == AudioChannelSet::create7point1point6()) return AAX_eStemFormat_7_1_6; + if (set == AudioChannelSet::create9point0point4()) return AAX_eStemFormat_9_0_4; + if (set == AudioChannelSet::create9point1point4()) return AAX_eStemFormat_9_1_4; + if (set == AudioChannelSet::create9point0point6()) return AAX_eStemFormat_9_0_6; + if (set == AudioChannelSet::create9point1point6()) return AAX_eStemFormat_9_1_6; + #endif + + auto order = set.getAmbisonicOrder(); + if (order >= 0) + return stemFormatForAmbisonicOrder (order); + + return AAX_eStemFormat_INT32_MAX; + } + + static inline AudioChannelSet channelSetFromStemFormat (AAX_EStemFormat format, bool ignoreLayout) noexcept + { + if (! ignoreLayout) + { + switch (format) + { + case AAX_eStemFormat_None: return AudioChannelSet::disabled(); + case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); + case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); + case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); + case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); + case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); + case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); + case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); + case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); + case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); + case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::create7point0SDDS(); + case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); + case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::create7point1SDDS(); + case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); + case AAX_eStemFormat_7_0_2: return AudioChannelSet::create7point0point2(); + case AAX_eStemFormat_7_1_2: return AudioChannelSet::create7point1point2(); + case AAX_eStemFormat_Ambi_1_ACN: return AudioChannelSet::ambisonic (1); + case AAX_eStemFormat_Ambi_2_ACN: return AudioChannelSet::ambisonic (2); + case AAX_eStemFormat_Ambi_3_ACN: return AudioChannelSet::ambisonic (3); + + #if JUCE_AAX_SDK_2p5p0 + case AAX_eStemFormat_5_0_2: return AudioChannelSet::create5point0point2(); + case AAX_eStemFormat_5_1_2: return AudioChannelSet::create5point1point2(); + case AAX_eStemFormat_5_0_4: return AudioChannelSet::create5point0point4(); + case AAX_eStemFormat_5_1_4: return AudioChannelSet::create5point1point4(); + case AAX_eStemFormat_7_0_4: return AudioChannelSet::create7point0point4(); + case AAX_eStemFormat_7_1_4: return AudioChannelSet::create7point1point4(); + case AAX_eStemFormat_7_0_6: return AudioChannelSet::create7point0point6(); + case AAX_eStemFormat_7_1_6: return AudioChannelSet::create7point1point6(); + case AAX_eStemFormat_9_0_4: return AudioChannelSet::create9point0point4(); + case AAX_eStemFormat_9_1_4: return AudioChannelSet::create9point1point4(); + case AAX_eStemFormat_9_0_6: return AudioChannelSet::create9point0point6(); + case AAX_eStemFormat_9_1_6: return AudioChannelSet::create9point1point6(); + case AAX_eStemFormat_Ambi_4_ACN: return AudioChannelSet::ambisonic (4); + case AAX_eStemFormat_Ambi_5_ACN: return AudioChannelSet::ambisonic (5); + case AAX_eStemFormat_Ambi_6_ACN: return AudioChannelSet::ambisonic (6); + case AAX_eStemFormat_Ambi_7_ACN: return AudioChannelSet::ambisonic (7); + #else + case AAX_eStemFormat_Reserved_1: + case AAX_eStemFormat_Reserved_2: + case AAX_eStemFormat_Reserved_3: + #endif + + case AAX_eStemFormatNum: + case AAX_eStemFormat_Any: + case AAX_eStemFormat_INT32_MAX: + default: return AudioChannelSet::disabled(); + } + } + + return AudioChannelSet::discreteChannels (jmax (0, static_cast (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); + } + + static AAX_EMeterType getMeterTypeForCategory (AudioProcessorParameter::Category category) + { + switch (category) + { + case AudioProcessorParameter::inputMeter: return AAX_eMeterType_Input; + case AudioProcessorParameter::outputMeter: return AAX_eMeterType_Output; + case AudioProcessorParameter::compressorLimiterGainReductionMeter: return AAX_eMeterType_CLGain; + case AudioProcessorParameter::expanderGateGainReductionMeter: return AAX_eMeterType_EGGain; + case AudioProcessorParameter::analysisMeter: return AAX_eMeterType_Analysis; + case AudioProcessorParameter::genericParameter: + case AudioProcessorParameter::inputGain: + case AudioProcessorParameter::outputGain: + case AudioProcessorParameter::otherMeter: + default: return AAX_eMeterType_Other; + } + } + + static Colour getColourFromHighlightEnum (AAX_EHighlightColor colour) noexcept + { + switch (colour) + { + case AAX_eHighlightColor_Red: return Colours::red; + case AAX_eHighlightColor_Blue: return Colours::blue; + case AAX_eHighlightColor_Green: return Colours::green; + case AAX_eHighlightColor_Yellow: return Colours::yellow; + case AAX_eHighlightColor_Num: + default: jassertfalse; break; + } + + return Colours::black; + } + + static int juceChannelIndexToAax (int juceIndex, const AudioChannelSet& channelSet) + { + auto isAmbisonic = (channelSet.getAmbisonicOrder() >= 0); + auto currentLayout = getFormatForAudioChannelSet (channelSet, false); + int layoutIndex; + + if (isAmbisonic && currentLayout != AAX_eStemFormat_INT32_MAX) + return juceIndex; + + for (layoutIndex = 0; aaxChannelOrder[layoutIndex].aaxStemFormat != currentLayout; ++layoutIndex) + if (aaxChannelOrder[layoutIndex].aaxStemFormat == 0) return juceIndex; + + auto& channelOrder = aaxChannelOrder[layoutIndex]; + auto channelType = channelSet.getTypeOfChannel (static_cast (juceIndex)); + const auto& speakerOrder = channelOrder.speakerOrder; + + const auto it = std::find (std::cbegin (speakerOrder), std::cend (speakerOrder), channelType); + + if (it != std::cend (speakerOrder)) + return (int) std::distance (std::cbegin (speakerOrder), it); + + return juceIndex; + } + + //============================================================================== + class JuceAAX_Processor; + + struct PluginInstanceInfo + { + PluginInstanceInfo (JuceAAX_Processor& p) : parameters (p) {} + + JuceAAX_Processor& parameters; + + JUCE_DECLARE_NON_COPYABLE (PluginInstanceInfo) + }; + + //============================================================================== + struct JUCEAlgorithmContext + { + float** inputChannels; + float** outputChannels; + int32_t* bufferSize; + int32_t* bypass; + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeIn; + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeOut; + #endif + + PluginInstanceInfo* pluginInstance; + int32_t* isPrepared; + float* const* meterTapBuffers; + int32_t* sideChainBuffers; + }; + + struct JUCEAlgorithmIDs + { + enum + { + inputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, inputChannels), + outputChannels = AAX_FIELD_INDEX (JUCEAlgorithmContext, outputChannels), + bufferSize = AAX_FIELD_INDEX (JUCEAlgorithmContext, bufferSize), + bypass = AAX_FIELD_INDEX (JUCEAlgorithmContext, bypass), + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + midiNodeIn = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeIn), + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + midiNodeOut = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeOut), + #endif + + pluginInstance = AAX_FIELD_INDEX (JUCEAlgorithmContext, pluginInstance), + preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared), + + meterTapBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, meterTapBuffers), + + sideChainBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, sideChainBuffers) + }; + }; + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeIn; } + #else + static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext&) noexcept { return nullptr; } + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + AAX_IMIDINode* midiNodeOut; + static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeOut; } + #else + static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext&) noexcept { return nullptr; } + #endif + + //============================================================================== + class JuceAAX_Processor; + + class JuceAAX_GUI : public AAX_CEffectGUI, + public ModifierKeyProvider + { + public: + JuceAAX_GUI() = default; + ~JuceAAX_GUI() override { DeleteViewContainer(); } + + static AAX_IEffectGUI* AAX_CALLBACK Create() { return new JuceAAX_GUI(); } + + void CreateViewContents() override; + + void CreateViewContainer() override + { + CreateViewContents(); + + if (void* nativeViewToAttachTo = GetViewContainerPtr()) + { + #if JUCE_MAC + if (GetViewContainerType() == AAX_eViewContainer_Type_NSView) + #else + if (GetViewContainerType() == AAX_eViewContainer_Type_HWND) + #endif + { + component->setVisible (true); + component->addToDesktop (detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()), nativeViewToAttachTo); + + if (ModifierKeyReceiver* modReceiver = dynamic_cast (component->getPeer())) + modReceiver->setModifierKeyProvider (this); + } + } + } + + void DeleteViewContainer() override + { + if (component != nullptr) + { + JUCE_AUTORELEASEPOOL + { + if (auto* modReceiver = dynamic_cast (component->getPeer())) + modReceiver->removeModifierKeyProvider(); + + component->removeFromDesktop(); + component = nullptr; + } + } + } + + AAX_Result GetViewSize (AAX_Point* viewSize) const override + { + if (component != nullptr) + { + *viewSize = convertToHostBounds ({ (float) component->getHeight(), + (float) component->getWidth() }); + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; + } + + AAX_Result ParameterUpdated (AAX_CParamID) override + { + return AAX_SUCCESS; + } + + AAX_Result SetControlHighlightInfo (AAX_CParamID paramID, AAX_CBoolean isHighlighted, AAX_EHighlightColor colour) override + { + if (component != nullptr && component->pluginEditor != nullptr) + { + auto index = getParamIndexFromID (paramID); + + if (index >= 0) + { + AudioProcessorEditor::ParameterControlHighlightInfo info; + info.parameterIndex = index; + info.isHighlighted = (isHighlighted != 0); + info.suggestedColour = getColourFromHighlightEnum (colour); + + component->pluginEditor->setControlHighlight (info); + } + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; + } + + int getWin32Modifiers() const override + { + int modifierFlags = 0; + + if (auto* viewContainer = GetViewContainer()) + { + uint32 aaxViewMods = 0; + const_cast (viewContainer)->GetModifiers (&aaxViewMods); + + if ((aaxViewMods & AAX_eModifiers_Shift) != 0) modifierFlags |= ModifierKeys::shiftModifier; + if ((aaxViewMods & AAX_eModifiers_Alt ) != 0) modifierFlags |= ModifierKeys::altModifier; + } + + return modifierFlags; + } + + private: + //============================================================================== + int getParamIndexFromID (AAX_CParamID paramID) const noexcept; + AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept; + + //============================================================================== + static AAX_Point convertToHostBounds (AAX_Point pluginSize) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginSize; + + return { pluginSize.vert * desktopScale, + pluginSize.horz * desktopScale }; + } + + //============================================================================== + struct ContentWrapperComponent : public Component + { + ContentWrapperComponent (JuceAAX_GUI& gui, AudioProcessor& plugin) + : owner (gui) + { + setOpaque (true); + setBroughtToFrontOnMouseClick (true); + + pluginEditor.reset (plugin.createEditorIfNeeded()); + addAndMakeVisible (pluginEditor.get()); + + if (pluginEditor != nullptr) + { + lastValidSize = pluginEditor->getLocalBounds(); + setBounds (lastValidSize); + pluginEditor->addMouseListener (this, true); + } + } + + ~ContentWrapperComponent() override + { + if (pluginEditor != nullptr) + { + PopupMenu::dismissAllActiveMenus(); + pluginEditor->removeMouseListener (this); + pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); + } + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + template + void callMouseMethod (const MouseEvent& e, MethodType method) + { + if (auto* vc = owner.GetViewContainer()) + { + auto parameterIndex = pluginEditor->getControlParameterIndex (*e.eventComponent); + + if (auto aaxParamID = owner.getAAXParamIDFromJuceIndex (parameterIndex)) + { + uint32_t mods = 0; + vc->GetModifiers (&mods); + + (vc->*method) (aaxParamID, mods); + } + } + } + + void mouseDown (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDown); } + void mouseUp (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseUp); } + void mouseDrag (const MouseEvent& e) override { callMouseMethod (e, &AAX_IViewContainer::HandleParameterMouseDrag); } + + void parentSizeChanged() override + { + resizeHostWindow(); + + if (pluginEditor != nullptr) + pluginEditor->repaint(); + } + + void childBoundsChanged (Component*) override + { + if (resizeHostWindow()) + { + setSize (pluginEditor->getWidth(), pluginEditor->getHeight()); + lastValidSize = getBounds(); + } + else + { + pluginEditor->setBoundsConstrained (pluginEditor->getBounds().withSize (lastValidSize.getWidth(), + lastValidSize.getHeight())); + } + } + + bool resizeHostWindow() + { + if (pluginEditor != nullptr) + { + auto newSize = convertToHostBounds ({ (float) pluginEditor->getHeight(), + (float) pluginEditor->getWidth() }); + + return owner.GetViewContainer()->SetViewSize (newSize) == AAX_SUCCESS; + } + + return false; + } + + std::unique_ptr pluginEditor; + JuceAAX_GUI& owner; + + #if JUCE_WINDOWS + detail::WindowsHooks hooks; + #endif + juce::Rectangle lastValidSize; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) + }; + + std::unique_ptr component; + ScopedJuceInitialiser_GUI libraryInitialiser; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAAX_GUI) + }; + + // Copied here, because not all versions of the AAX SDK define all of these values + enum JUCE_AAX_EFrameRate : std::underlying_type_t + { + JUCE_AAX_eFrameRate_Undeclared = 0, + JUCE_AAX_eFrameRate_24Frame = 1, + JUCE_AAX_eFrameRate_25Frame = 2, + JUCE_AAX_eFrameRate_2997NonDrop = 3, + JUCE_AAX_eFrameRate_2997DropFrame = 4, + JUCE_AAX_eFrameRate_30NonDrop = 5, + JUCE_AAX_eFrameRate_30DropFrame = 6, + JUCE_AAX_eFrameRate_23976 = 7, + JUCE_AAX_eFrameRate_47952 = 8, + JUCE_AAX_eFrameRate_48Frame = 9, + JUCE_AAX_eFrameRate_50Frame = 10, + JUCE_AAX_eFrameRate_5994NonDrop = 11, + JUCE_AAX_eFrameRate_5994DropFrame = 12, + JUCE_AAX_eFrameRate_60NonDrop = 13, + JUCE_AAX_eFrameRate_60DropFrame = 14, + JUCE_AAX_eFrameRate_100Frame = 15, + JUCE_AAX_eFrameRate_11988NonDrop = 16, + JUCE_AAX_eFrameRate_11988DropFrame = 17, + JUCE_AAX_eFrameRate_120NonDrop = 18, + JUCE_AAX_eFrameRate_120DropFrame = 19 + }; + + static void AAX_CALLBACK algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); + + static Array activeProcessors; + + //============================================================================== + class JuceAAX_Processor : public AAX_CEffectParameters, + public juce::AudioPlayHead, + public AudioProcessorListener, + private AsyncUpdater + { + public: + JuceAAX_Processor() + : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_AAX)) + { + inParameterChangedCallback = false; + + pluginInstance->setPlayHead (this); + pluginInstance->addListener (this); + + rebuildChannelMapArrays(); + + AAX_CEffectParameters::GetNumberOfChunks (&juceChunkIndex); + activeProcessors.add (this); + } + + ~JuceAAX_Processor() override + { + activeProcessors.removeAllInstancesOf (this); + } + + static AAX_CEffectParameters* AAX_CALLBACK Create() + { + if (PluginHostType::jucePlugInIsRunningInAudioSuiteFn == nullptr) + { + PluginHostType::jucePlugInIsRunningInAudioSuiteFn = [] (AudioProcessor& processor) + { + for (auto* p : activeProcessors) + if (&p->getPluginInstance() == &processor) + return p->isInAudioSuite(); + + return false; + }; + } + + return new JuceAAX_Processor(); + } + + AAX_Result Uninitialize() override + { + cancelPendingUpdate(); + juceParameters.clear(); + + if (isPrepared && pluginInstance != nullptr) + { + isPrepared = false; + processingSidechainChange = false; + + pluginInstance->releaseResources(); + } + + return AAX_CEffectParameters::Uninitialize(); + } + + AAX_Result EffectInit() override + { + cancelPendingUpdate(); + check (Controller()->GetSampleRate (&sampleRate)); + processingSidechainChange = false; + auto err = preparePlugin(); + + if (err != AAX_SUCCESS) + return err; + + addAudioProcessorParameters(); + + return AAX_SUCCESS; + } + + AAX_Result GetNumberOfChunks (int32_t* numChunks) const override + { + // The juceChunk is the last chunk. + *numChunks = juceChunkIndex + 1; + return AAX_SUCCESS; + } + + AAX_Result GetChunkIDFromIndex (int32_t index, AAX_CTypeID* chunkID) const override + { + if (index != juceChunkIndex) + return AAX_CEffectParameters::GetChunkIDFromIndex (index, chunkID); + + *chunkID = juceChunkType; + return AAX_SUCCESS; + } + + AAX_Result GetChunkSize (AAX_CTypeID chunkID, uint32_t* oSize) const override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::GetChunkSize (chunkID, oSize); + + auto& chunkMemoryBlock = perThreadFilterData.get(); + + chunkMemoryBlock.data.reset(); + pluginInstance->getStateInformation (chunkMemoryBlock.data); + chunkMemoryBlock.isValid = true; + + *oSize = (uint32_t) chunkMemoryBlock.data.getSize(); + return AAX_SUCCESS; + } + + AAX_Result GetChunk (AAX_CTypeID chunkID, AAX_SPlugInChunk* oChunk) const override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::GetChunk (chunkID, oChunk); + + auto& chunkMemoryBlock = perThreadFilterData.get(); + + if (! chunkMemoryBlock.isValid) + return 20700; // AAX_ERROR_PLUGIN_API_INVALID_THREAD + + oChunk->fSize = (int32_t) chunkMemoryBlock.data.getSize(); + chunkMemoryBlock.data.copyTo (oChunk->fData, 0, chunkMemoryBlock.data.getSize()); + chunkMemoryBlock.isValid = false; + + return AAX_SUCCESS; + } + + AAX_Result SetChunk (AAX_CTypeID chunkID, const AAX_SPlugInChunk* chunk) override + { + if (chunkID != juceChunkType) + return AAX_CEffectParameters::SetChunk (chunkID, chunk); + + pluginInstance->setStateInformation ((void*) chunk->fData, chunk->fSize); + + // Notify Pro Tools that the parameters were updated. + // Without it a bug happens in these circumstances: + // * A preset is saved with the RTAS version of the plugin (".tfx" preset format). + // * The preset is loaded in PT 10 using the AAX version. + // * The session is then saved, and closed. + // * The saved session is loaded, but acting as if the preset was never loaded. + // IMPORTANT! If the plugin doesn't manage its own bypass parameter, don't try + // to overwrite the bypass parameter value. + auto numParameters = juceParameters.getNumParameters(); + + for (int i = 0; i < numParameters; ++i) + if (auto* juceParam = juceParameters.getParamForIndex (i)) + if (juceParam != ownedBypassParameter.get()) + if (auto paramID = getAAXParamIDFromJuceIndex (i)) + SetParameterNormalizedValue (paramID, juceParam->getValue()); + + return AAX_SUCCESS; + } + + AAX_Result ResetFieldData (AAX_CFieldIndex fieldIndex, void* data, uint32_t dataSize) const override + { + switch (fieldIndex) + { + case JUCEAlgorithmIDs::pluginInstance: + { + auto numObjects = dataSize / sizeof (PluginInstanceInfo); + auto* objects = static_cast (data); + + jassert (numObjects == 1); // not sure how to handle more than one.. + + for (size_t i = 0; i < numObjects; ++i) + new (objects + i) PluginInstanceInfo (const_cast (*this)); + + break; + } + + case JUCEAlgorithmIDs::preparedFlag: + { + const_cast (this)->preparePlugin(); + + auto numObjects = dataSize / sizeof (uint32_t); + auto* objects = static_cast (data); + + for (size_t i = 0; i < numObjects; ++i) + objects[i] = 1; + + break; + } + + case JUCEAlgorithmIDs::meterTapBuffers: + { + // this is a dummy field only when there are no aaxMeters + jassert (aaxMeters.size() == 0); + + { + auto numObjects = dataSize / sizeof (float*); + auto* objects = static_cast (data); + + for (size_t i = 0; i < numObjects; ++i) + objects[i] = nullptr; + } + break; + } + } + + return AAX_SUCCESS; + } + + void setAudioProcessorParameter (AAX_CParamID paramID, double value) + { + if (auto* param = getParameterFromID (paramID)) + { + auto newValue = static_cast (value); + + if (! approximatelyEqual (newValue, param->getValue())) + { + param->setValue (newValue); + + inParameterChangedCallback = true; + param->sendValueChangedMessageToListeners (newValue); + } + } + } + + AAX_Result GetNumberOfChanges (int32_t* numChanges) const override + { + const auto result = AAX_CEffectParameters::GetNumberOfChanges (numChanges); + *numChanges += numSetDirtyCalls; + return result; + } + + AAX_Result UpdateParameterNormalizedValue (AAX_CParamID paramID, double value, AAX_EUpdateSource source) override + { + auto result = AAX_CEffectParameters::UpdateParameterNormalizedValue (paramID, value, source); + setAudioProcessorParameter (paramID, value); + + return result; + } + + AAX_Result GetParameterValueFromString (AAX_CParamID paramID, double* result, const AAX_IString& text) const override + { + if (auto* param = getParameterFromID (paramID)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + *result = param->getValueForText (text.Get()); + return AAX_SUCCESS; + } + } + + return AAX_CEffectParameters::GetParameterValueFromString (paramID, result, text); + } + + AAX_Result GetParameterStringFromValue (AAX_CParamID paramID, double value, AAX_IString* result, int32_t maxLen) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getText ((float) value, maxLen).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNumberofSteps (AAX_CParamID paramID, int32_t* result) const + { + if (auto* param = getParameterFromID (paramID)) + *result = getSafeNumberOfParameterSteps (*param); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNormalizedValue (AAX_CParamID paramID, double* result) const override + { + if (auto* param = getParameterFromID (paramID)) + *result = (double) param->getValue(); + else + *result = 0.0; + + return AAX_SUCCESS; + } + + AAX_Result SetParameterNormalizedValue (AAX_CParamID paramID, double newValue) override + { + if (auto* p = mParameterManager.GetParameterByID (paramID)) + p->SetValueWithFloat ((float) newValue); + + setAudioProcessorParameter (paramID, (float) newValue); + + return AAX_SUCCESS; + } + + AAX_Result SetParameterNormalizedRelative (AAX_CParamID paramID, double newDeltaValue) override + { + if (auto* param = getParameterFromID (paramID)) + { + auto newValue = param->getValue() + (float) newDeltaValue; + + setAudioProcessorParameter (paramID, jlimit (0.0f, 1.0f, newValue)); + + if (auto* p = mParameterManager.GetParameterByID (paramID)) + p->SetValueWithFloat (newValue); + } + + return AAX_SUCCESS; + } + + AAX_Result GetParameterNameOfLength (AAX_CParamID paramID, AAX_IString* result, int32_t maxLen) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getName (maxLen).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterName (AAX_CParamID paramID, AAX_IString* result) const override + { + if (auto* param = getParameterFromID (paramID)) + result->Set (param->getName (31).toRawUTF8()); + + return AAX_SUCCESS; + } + + AAX_Result GetParameterDefaultNormalizedValue (AAX_CParamID paramID, double* result) const override + { + if (auto* param = getParameterFromID (paramID)) + *result = (double) param->getDefaultValue(); + else + *result = 0.0; + + jassert (*result >= 0 && *result <= 1.0f); + + return AAX_SUCCESS; + } + + AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } + + Optional getPosition() const override + { + PositionInfo info; + + const AAX_ITransport& transport = *Transport(); + + info.setBpm ([&] + { + double bpm = 0.0; + + return transport.GetCurrentTempo (&bpm) == AAX_SUCCESS ? makeOptional (bpm) : nullopt; + }()); + + const auto signature = [&] + { + int32_t num = 4, den = 4; + + return transport.GetCurrentMeter (&num, &den) == AAX_SUCCESS + ? makeOptional (TimeSignature { (int) num, (int) den }) + : nullopt; + }(); + + info.setTimeSignature (signature); + + info.setIsPlaying ([&] + { + bool isPlaying = false; + + return transport.IsTransportPlaying (&isPlaying) == AAX_SUCCESS && isPlaying; + }()); + + info.setIsRecording (recordingState.get().orFallback (false)); + + const auto optionalTimeInSamples = [&info, &transport] + { + int64_t timeInSamples = 0; + return ((! info.getIsPlaying() && transport.GetTimelineSelectionStartPosition (&timeInSamples) == AAX_SUCCESS) + || transport.GetCurrentNativeSampleLocation (&timeInSamples) == AAX_SUCCESS) + ? makeOptional (timeInSamples) + : nullopt; + }(); + + info.setTimeInSamples (optionalTimeInSamples); + info.setTimeInSeconds ((float) optionalTimeInSamples.orFallback (0) / sampleRate); + + const auto tickPosition = [&] + { + int64_t ticks = 0; + + return ((info.getIsPlaying() && transport.GetCustomTickPosition (&ticks, optionalTimeInSamples.orFallback (0))) == AAX_SUCCESS) + || transport.GetCurrentTickPosition (&ticks) == AAX_SUCCESS + ? makeOptional (ticks) + : nullopt; + }(); + + info.setPpqPosition (tickPosition.hasValue() ? makeOptional (static_cast (*tickPosition) / 960'000.0) : nullopt); + + bool isLooping = false; + int64_t loopStartTick = 0, loopEndTick = 0; + + if (transport.GetCurrentLoopPosition (&isLooping, &loopStartTick, &loopEndTick) == AAX_SUCCESS) + { + info.setIsLooping (isLooping); + info.setLoopPoints (LoopPoints { (double) loopStartTick / 960000.0, (double) loopEndTick / 960000.0 }); + } + + struct RateAndOffset + { + AAX_EFrameRate frameRate{}; + int64_t offset{}; + }; + + const auto timeCodeIfAvailable = [&]() -> std::optional + { + RateAndOffset result{}; + + if (transport.GetHDTimeCodeInfo (&result.frameRate, &result.offset) == AAX_SUCCESS) + return result; + + int32_t offset32{}; + + if (transport.GetTimeCodeInfo (&result.frameRate, &offset32) == AAX_SUCCESS) + { + result.offset = offset32; + return result; + } + + return {}; + }(); + + if (timeCodeIfAvailable.has_value()) + { + info.setFrameRate ([&]() -> Optional + { + switch ((JUCE_AAX_EFrameRate) timeCodeIfAvailable->frameRate) + { + case JUCE_AAX_eFrameRate_24Frame: return FrameRate().withBaseRate (24); + case JUCE_AAX_eFrameRate_23976: return FrameRate().withBaseRate (24).withPullDown(); + + case JUCE_AAX_eFrameRate_25Frame: return FrameRate().withBaseRate (25); + + case JUCE_AAX_eFrameRate_30NonDrop: return FrameRate().withBaseRate (30); + case JUCE_AAX_eFrameRate_30DropFrame: return FrameRate().withBaseRate (30).withDrop(); + case JUCE_AAX_eFrameRate_2997NonDrop: return FrameRate().withBaseRate (30).withPullDown(); + case JUCE_AAX_eFrameRate_2997DropFrame: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_48Frame: return FrameRate().withBaseRate (48); + case JUCE_AAX_eFrameRate_47952: return FrameRate().withBaseRate (48).withPullDown(); + + case JUCE_AAX_eFrameRate_50Frame: return FrameRate().withBaseRate (50); + + case JUCE_AAX_eFrameRate_60NonDrop: return FrameRate().withBaseRate (60); + case JUCE_AAX_eFrameRate_60DropFrame: return FrameRate().withBaseRate (60).withDrop(); + case JUCE_AAX_eFrameRate_5994NonDrop: return FrameRate().withBaseRate (60).withPullDown(); + case JUCE_AAX_eFrameRate_5994DropFrame: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_100Frame: return FrameRate().withBaseRate (100); + + case JUCE_AAX_eFrameRate_120NonDrop: return FrameRate().withBaseRate (120); + case JUCE_AAX_eFrameRate_120DropFrame: return FrameRate().withBaseRate (120).withDrop(); + case JUCE_AAX_eFrameRate_11988NonDrop: return FrameRate().withBaseRate (120).withPullDown(); + case JUCE_AAX_eFrameRate_11988DropFrame: return FrameRate().withBaseRate (120).withPullDown().withDrop(); + + case JUCE_AAX_eFrameRate_Undeclared: break; + } + + return {}; + }()); + } + + const auto offset = timeCodeIfAvailable.has_value() ? timeCodeIfAvailable->offset : int64_t{}; + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (makeOptional (effectiveRate != 0.0 ? offset / effectiveRate : offset)); + + { + int32_t bars{}, beats{}; + int64_t displayTicks{}; + + if (optionalTimeInSamples.hasValue() + && transport.GetBarBeatPosition (&bars, &beats, &displayTicks, *optionalTimeInSamples) == AAX_SUCCESS) + { + info.setBarCount (bars); + + if (signature.hasValue()) + { + const auto ticksSinceBar = static_cast (((beats - 1) * 4 * 960'000) / signature->denominator) + displayTicks; + + if (tickPosition.hasValue() && ticksSinceBar <= tickPosition) + { + const auto barStartInTicks = static_cast (*tickPosition - ticksSinceBar); + info.setPpqPositionOfLastBarStart (barStartInTicks / 960'000.0); + } + } + } + } + + return info; + } + + void audioProcessorParameterChanged (AudioProcessor* /*processor*/, int parameterIndex, float newValue) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + SetParameterNormalizedValue (paramID, (double) newValue); + } + + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override + { + ++mNumPlugInChanges; + + if (details.parameterInfoChanged) + { + for (const auto* param : juceParameters) + if (auto* aaxParam = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (param->getParameterIndex()))) + syncParameterAttributes (aaxParam, param); + } + + if (details.latencyChanged) + check (Controller()->SetSignalLatency (processor->getLatencySamples())); + + if (details.nonParameterStateChanged) + ++numSetDirtyCalls; + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override + { + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + TouchParameter (paramID); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override + { + if (auto paramID = getAAXParamIDFromJuceIndex (parameterIndex)) + ReleaseParameter (paramID); + } + + AAX_Result NotificationReceived (AAX_CTypeID type, const void* data, uint32_t size) override + { + switch (type) + { + case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; + case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; + + case AAX_eNotificationEvent_ASProcessingState: + { + if (data != nullptr && size == sizeof (AAX_EProcessingState)) + { + const auto state = *static_cast (data); + const auto nonRealtime = state == AAX_eProcessingState_StartPass + || state == AAX_eProcessingState_BeginPassGroup; + pluginInstance->setNonRealtime (nonRealtime); + } + + break; + } + + case AAX_eNotificationEvent_TrackNameChanged: + if (data != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = String::fromUTF8 (static_cast (data)->Get()); + + pluginInstance->updateTrackProperties (props); + } + break; + + case AAX_eNotificationEvent_SideChainBeingConnected: + case AAX_eNotificationEvent_SideChainBeingDisconnected: + { + processingSidechainChange = true; + sidechainDesired = (type == AAX_eNotificationEvent_SideChainBeingConnected); + updateSidechainState(); + break; + } + + case AAX_eNotificationEvent_TransportStateChanged: + if (data != nullptr) + { + const auto& info = *static_cast (data); + recordingState.set (info.mIsRecording); + } + break; + } + + return AAX_CEffectParameters::NotificationReceived (type, data, size); + } + + const float* getAudioBufferForInput (const float* const* inputs, int sidechain, int mainNumIns, int idx) const noexcept + { + jassert (idx < (mainNumIns + 1)); + + if (idx < mainNumIns) + return inputs[inputLayoutMap[idx]]; + + return (sidechain != -1 ? inputs[sidechain] : sideChainBuffer.data()); + } + + void process (const float* const* inputs, float* const* outputs, const int sideChainBufferIdx, + const int bufferSize, const bool bypass, + AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut, + float* const meterBuffers) + { + auto numIns = pluginInstance->getTotalNumInputChannels(); + auto numOuts = pluginInstance->getTotalNumOutputChannels(); + auto numMeters = aaxMeters.size(); + + const ScopedLock sl (pluginInstance->getCallbackLock()); + + bool isSuspended = [this, sideChainBufferIdx] + { + if (processingSidechainChange) + return true; + + bool processWantsSidechain = (sideChainBufferIdx != -1); + + if (hasSidechain && canDisableSidechain && (sidechainDesired != processWantsSidechain)) + { + sidechainDesired = processWantsSidechain; + processingSidechainChange = true; + triggerAsyncUpdate(); + return true; + } + + return pluginInstance->isSuspended(); + }(); + + if (isSuspended) + { + for (int i = 0; i < numOuts; ++i) + FloatVectorOperations::clear (outputs[i], bufferSize); + + if (meterBuffers != nullptr) + FloatVectorOperations::clear (meterBuffers, numMeters); + } + else + { + auto mainNumIns = pluginInstance->getMainBusNumInputChannels(); + auto sidechain = (pluginInstance->getChannelCountOfBus (true, 1) > 0 ? sideChainBufferIdx : -1); + auto numChans = jmax (numIns, numOuts); + + if (numChans == 0) + return; + + if (channelList.size() <= numChans) + channelList.insertMultiple (-1, nullptr, 1 + numChans - channelList.size()); + + float** channels = channelList.getRawDataPointer(); + + if (numOuts >= numIns) + { + for (int i = 0; i < numOuts; ++i) + channels[i] = outputs[outputLayoutMap[i]]; + + for (int i = 0; i < numIns; ++i) + memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); + + for (int i = numIns; i < numOuts; ++i) + zeromem (channels[i], (size_t) bufferSize * sizeof (float)); + + process (channels, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); + } + else + { + for (int i = 0; i < numOuts; ++i) + channels[i] = outputs[outputLayoutMap[i]]; + + for (int i = 0; i < numOuts; ++i) + memcpy (channels[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); + + for (int i = numOuts; i < numIns; ++i) + channels[i] = const_cast (getAudioBufferForInput (inputs, sidechain, mainNumIns, i)); + + process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); + } + + if (meterBuffers != nullptr) + for (int i = 0; i < numMeters; ++i) + meterBuffers[i] = aaxMeters[i]->getValue(); + } + } + + //============================================================================== + // In aax, the format of the aux and sidechain buses need to be fully determined + // by the format on the main buses. This function tried to provide such a mapping. + // Returns false if the in/out main layout is not supported + static bool fullBusesLayoutFromMainLayout (const AudioProcessor& p, + const AudioChannelSet& mainInput, const AudioChannelSet& mainOutput, + AudioProcessor::BusesLayout& fullLayout) + { + auto currentLayout = getDefaultLayout (p, true); + bool success = p.checkBusesLayoutSupported (currentLayout); + jassertquiet (success); + + auto numInputBuses = p.getBusCount (true); + auto numOutputBuses = p.getBusCount (false); + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return false; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return false; + + // did this change the input again + if (numInputBuses > 0 && currentLayout.inputBuses.getReference (0) != mainInput) + return false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + + if (! AudioProcessor::containsLayout (currentLayout, configs)) + return false; + #endif + + bool foundValid = false; + { + auto onlyMains = currentLayout; + + for (int i = 1; i < numInputBuses; ++i) + onlyMains.inputBuses.getReference (i) = AudioChannelSet::disabled(); + + for (int i = 1; i < numOutputBuses; ++i) + onlyMains.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (onlyMains)) + { + foundValid = true; + fullLayout = onlyMains; + } + } + + if (numInputBuses > 1) + { + // can the first bus be a sidechain or disabled, if not then we can't use this layout combination + if (auto* bus = p.getBus (true, 1)) + if (! bus->isLayoutSupported (AudioChannelSet::mono(), ¤tLayout) && ! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + // can all the other inputs be disabled, if not then we can't use this layout combination + for (int i = 2; i < numInputBuses; ++i) + if (auto* bus = p.getBus (true, i)) + if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return foundValid; + + // recheck if the format is correct + if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) + || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) + return foundValid; + + auto& sidechainBus = currentLayout.inputBuses.getReference (1); + + if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) + return foundValid; + + for (int i = 2; i < numInputBuses; ++i) + if (! currentLayout.inputBuses.getReference (i).isDisabled()) + return foundValid; + } + + const bool hasSidechain = (numInputBuses > 1 && currentLayout.inputBuses.getReference (1) == AudioChannelSet::mono()); + + if (hasSidechain) + { + auto onlyMainsAndSidechain = currentLayout; + + for (int i = 1; i < numOutputBuses; ++i) + onlyMainsAndSidechain.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (onlyMainsAndSidechain)) + { + foundValid = true; + fullLayout = onlyMainsAndSidechain; + } + } + + if (numOutputBuses > 1) + { + auto copy = currentLayout; + int maxAuxBuses = jmin (16, numOutputBuses); + + for (int i = 1; i < maxAuxBuses; ++i) + copy.outputBuses.getReference (i) = mainOutput; + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + copy.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (p.checkBusesLayoutSupported (copy)) + { + fullLayout = copy; + foundValid = true; + } + else + { + for (int i = 1; i < maxAuxBuses; ++i) + if (currentLayout.outputBuses.getReference (i).isDisabled()) + return foundValid; + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + if (auto* bus = p.getBus (false, i)) + if (! bus->isLayoutSupported (AudioChannelSet::disabled(), ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (true, 0)) + if (! bus->isLayoutSupported (mainInput, ¤tLayout)) + return foundValid; + + if (auto* bus = p.getBus (false, 0)) + if (! bus->isLayoutSupported (mainOutput, ¤tLayout)) + return foundValid; + + if ((numInputBuses > 0 && currentLayout.inputBuses .getReference (0) != mainInput) + || (numOutputBuses > 0 && currentLayout.outputBuses.getReference (0) != mainOutput)) + return foundValid; + + if (numInputBuses > 1) + { + auto& sidechainBus = currentLayout.inputBuses.getReference (1); + + if (sidechainBus != AudioChannelSet::mono() && sidechainBus != AudioChannelSet::disabled()) + return foundValid; + } + + for (int i = maxAuxBuses; i < numOutputBuses; ++i) + if (! currentLayout.outputBuses.getReference (i).isDisabled()) + return foundValid; + + fullLayout = currentLayout; + foundValid = true; + } + } + + return foundValid; + } + + bool isInAudioSuite() + { + AAX_CBoolean res; + Controller()->GetIsAudioSuite (&res); + + return res > 0; + } + + private: + friend class JuceAAX_GUI; + friend void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd); + + void process (float* const* channels, const int numChans, const int bufferSize, + const bool bypass, [[maybe_unused]] AAX_IMIDINode* midiNodeIn, [[maybe_unused]] AAX_IMIDINode* midiNodesOut) + { + AudioBuffer buffer (channels, numChans, bufferSize); + midiBuffer.clear(); + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + { + auto* midiStream = midiNodeIn->GetNodeBuffer(); + auto numMidiEvents = midiStream->mBufferSize; + + for (uint32_t i = 0; i < numMidiEvents; ++i) + { + auto& m = midiStream->mBuffer[i]; + jassert ((int) m.mTimestamp < bufferSize); + + midiBuffer.addEvent (m.mData, (int) m.mLength, + jlimit (0, (int) bufferSize - 1, (int) m.mTimestamp)); + } + } + #endif + + { + if (lastBufferSize != bufferSize) + { + lastBufferSize = bufferSize; + pluginInstance->setRateAndBufferSizeDetails (sampleRate, lastBufferSize); + + // we only call prepareToPlay here if the new buffer size is larger than + // the one used last time prepareToPlay was called. + // currently, this should never actually happen, because as of Pro Tools 12, + // the maximum possible value is 1024, and we call prepareToPlay with that + // value during initialisation. + if (bufferSize > maxBufferSize) + prepareProcessorWithSampleRateAndBufferSize (sampleRate, bufferSize); + } + + if (bypass && pluginInstance->getBypassParameter() == nullptr) + pluginInstance->processBlockBypassed (buffer, midiBuffer); + else + pluginInstance->processBlock (buffer, midiBuffer); + } + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + { + AAX_CMidiPacket packet; + packet.mIsImmediate = false; + + for (const auto metadata : midiBuffer) + { + jassert (isPositiveAndBelow (metadata.samplePosition, bufferSize)); + + if (metadata.numBytes <= 4) + { + packet.mTimestamp = (uint32_t) metadata.samplePosition; + packet.mLength = (uint32_t) metadata.numBytes; + memcpy (packet.mData, metadata.data, (size_t) metadata.numBytes); + + check (midiNodesOut->PostMIDIPacket (&packet)); + } + } + } + #endif + } + + bool isBypassPartOfRegularParemeters() const + { + auto& audioProcessor = getPluginInstance(); + + int n = juceParameters.getNumParameters(); + + if (auto* bypassParam = audioProcessor.getBypassParameter()) + for (int i = 0; i < n; ++i) + if (juceParameters.getParamForIndex (i) == bypassParam) + return true; + + return false; + } + + // Some older Pro Tools control surfaces (EUCON [PT version 12.4] and + // Avid S6 before version 2.1) cannot cope with a large number of + // parameter steps. + static int32_t getSafeNumberOfParameterSteps (const AudioProcessorParameter& param) + { + return jmin (param.getNumSteps(), 2048); + } + + void addAudioProcessorParameters() + { + auto& audioProcessor = getPluginInstance(); + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + auto bypassPartOfRegularParams = isBypassPartOfRegularParemeters(); + + juceParameters.update (audioProcessor, forceLegacyParamIDs); + + auto* bypassParameter = pluginInstance->getBypassParameter(); + + if (bypassParameter == nullptr) + { + ownedBypassParameter.reset (new AudioParameterBool (cDefaultMasterBypassID, "Master Bypass", false)); + bypassParameter = ownedBypassParameter.get(); + } + + if (! bypassPartOfRegularParams) + juceParameters.addNonOwning (bypassParameter); + + int parameterIndex = 0; + + for (auto* juceParam : juceParameters) + { + auto isBypassParameter = (juceParam == bypassParameter); + + auto category = juceParam->getCategory(); + auto paramID = isBypassParameter ? String (cDefaultMasterBypassID) + : juceParameters.getParamID (audioProcessor, parameterIndex); + + aaxParamIDs.add (paramID); + auto* aaxParamID = aaxParamIDs.getReference (parameterIndex++).toRawUTF8(); + + paramMap.set (AAXClasses::getAAXParamHash (aaxParamID), juceParam); + + // is this a meter? + if (((category & 0xffff0000) >> 16) == 2) + { + aaxMeters.add (juceParam); + continue; + } + + auto parameter = new AAX_CParameter (aaxParamID, + AAX_CString (juceParam->getName (31).toRawUTF8()), + juceParam->getDefaultValue(), + AAX_CLinearTaperDelegate(), + AAX_CNumberDisplayDelegate(), + juceParam->isAutomatable()); + + parameter->AddShortenedName (juceParam->getName (4).toRawUTF8()); + + auto parameterNumSteps = getSafeNumberOfParameterSteps (*juceParam); + parameter->SetNumberOfSteps ((uint32_t) parameterNumSteps); + + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + parameter->SetType (parameterNumSteps > 1000 ? AAX_eParameterType_Continuous + : AAX_eParameterType_Discrete); + #else + parameter->SetType (juceParam->isDiscrete() ? AAX_eParameterType_Discrete + : AAX_eParameterType_Continuous); + #endif + + parameter->SetOrientation (juceParam->isOrientationInverted() + ? (AAX_eParameterOrientation_RightMinLeftMax | AAX_eParameterOrientation_TopMinBottomMax + | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryRightMinLeftMax) + : (AAX_eParameterOrientation_LeftMinRightMax | AAX_eParameterOrientation_BottomMinTopMax + | AAX_eParameterOrientation_RotarySingleDotMode | AAX_eParameterOrientation_RotaryLeftMinRightMax)); + + mParameterManager.AddParameter (parameter); + + if (isBypassParameter) + mPacketDispatcher.RegisterPacket (aaxParamID, JUCEAlgorithmIDs::bypass); + } + } + + bool getMainBusFormats (AudioChannelSet& inputSet, AudioChannelSet& outputSet) + { + auto& audioProcessor = getPluginInstance(); + + #if JucePlugin_IsMidiEffect + // MIDI effect plug-ins do not support any audio channels + jassert (audioProcessor.getTotalNumInputChannels() == 0 + && audioProcessor.getTotalNumOutputChannels() == 0); + + inputSet = outputSet = AudioChannelSet(); + return true; + #else + auto inputBuses = audioProcessor.getBusCount (true); + auto outputBuses = audioProcessor.getBusCount (false); + + AAX_EStemFormat inputStemFormat = AAX_eStemFormat_None; + check (Controller()->GetInputStemFormat (&inputStemFormat)); + + AAX_EStemFormat outputStemFormat = AAX_eStemFormat_None; + check (Controller()->GetOutputStemFormat (&outputStemFormat)); + + #if JucePlugin_IsSynth + if (inputBuses == 0) + inputStemFormat = AAX_eStemFormat_None; + #endif + + inputSet = (inputBuses > 0 ? channelSetFromStemFormat (inputStemFormat, false) : AudioChannelSet()); + outputSet = (outputBuses > 0 ? channelSetFromStemFormat (outputStemFormat, false) : AudioChannelSet()); + + if ((inputSet == AudioChannelSet::disabled() && inputStemFormat != AAX_eStemFormat_None) || (outputSet == AudioChannelSet::disabled() && outputStemFormat != AAX_eStemFormat_None) + || (inputSet != AudioChannelSet::disabled() && inputBuses == 0) || (outputSet != AudioChannelSet::disabled() && outputBuses == 0)) + return false; + + return true; + #endif + } + + AAX_Result preparePlugin() + { + auto& audioProcessor = getPluginInstance(); + auto oldLayout = audioProcessor.getBusesLayout(); + AudioChannelSet inputSet, outputSet; + + if (! getMainBusFormats (inputSet, outputSet)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AudioProcessor::BusesLayout newLayout; + + if (! fullBusesLayoutFromMainLayout (audioProcessor, inputSet, outputSet, newLayout)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + hasSidechain = (newLayout.getNumChannels (true, 1) == 1); + + if (hasSidechain) + { + sidechainDesired = true; + + auto disabledSidechainLayout = newLayout; + disabledSidechainLayout.inputBuses.getReference (1) = AudioChannelSet::disabled(); + + canDisableSidechain = audioProcessor.checkBusesLayoutSupported (disabledSidechainLayout); + + if (canDisableSidechain && ! lastSideChainState) + { + sidechainDesired = false; + newLayout = disabledSidechainLayout; + } + } + + if (isInAudioSuite()) + { + // AudioSuite doesn't support multiple output buses + for (int i = 1; i < newLayout.outputBuses.size(); ++i) + newLayout.outputBuses.getReference (i) = AudioChannelSet::disabled(); + + if (! audioProcessor.checkBusesLayoutSupported (newLayout)) + { + // your plug-in needs to support a single output bus if running in AudioSuite + jassertfalse; + + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + } + + const bool layoutChanged = (oldLayout != newLayout); + + if (layoutChanged) + { + if (! audioProcessor.setBusesLayout (newLayout)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + rebuildChannelMapArrays(); + } + + if (layoutChanged || (! isPrepared)) + { + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + prepareProcessorWithSampleRateAndBufferSize (sampleRate, lastBufferSize); + + midiBuffer.ensureSize (2048); + midiBuffer.clear(); + } + + check (Controller()->SetSignalLatency (audioProcessor.getLatencySamples())); + isPrepared = true; + + return AAX_SUCCESS; + } + + void rebuildChannelMapArrays() + { + auto& audioProcessor = getPluginInstance(); + + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; + layoutMap.clear(); + + auto numBuses = audioProcessor.getBusCount (isInput); + int chOffset = 0; + + for (int busIdx = 0; busIdx < numBuses; ++busIdx) + { + auto channelFormat = audioProcessor.getChannelLayoutOfBus (isInput, busIdx); + + if (channelFormat != AudioChannelSet::disabled()) + { + auto numChannels = channelFormat.size(); + + for (int ch = 0; ch < numChannels; ++ch) + layoutMap.add (juceChannelIndexToAax (ch, channelFormat) + chOffset); + + chOffset += numChannels; + } + } + } + } + + static void algorithmCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) + { + for (auto iter = instancesBegin; iter < instancesEnd; ++iter) + { + auto& i = **iter; + + int sideChainBufferIdx = i.pluginInstance->parameters.hasSidechain && i.sideChainBuffers != nullptr + ? static_cast (*i.sideChainBuffers) : -1; + + // sidechain index of zero is an invalid index + if (sideChainBufferIdx <= 0) + sideChainBufferIdx = -1; + + auto numMeters = i.pluginInstance->parameters.aaxMeters.size(); + float* const meterTapBuffers = (i.meterTapBuffers != nullptr && numMeters > 0 ? *i.meterTapBuffers : nullptr); + + i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx, + *(i.bufferSize), *(i.bypass) != 0, + getMidiNodeIn(i), getMidiNodeOut(i), + meterTapBuffers); + } + } + + void prepareProcessorWithSampleRateAndBufferSize (double sr, int bs) + { + maxBufferSize = jmax (maxBufferSize, bs); + + auto& audioProcessor = getPluginInstance(); + audioProcessor.setRateAndBufferSizeDetails (sr, maxBufferSize); + audioProcessor.prepareToPlay (sr, maxBufferSize); + sideChainBuffer.resize (static_cast (maxBufferSize)); + } + + //============================================================================== + void updateSidechainState() + { + if (! processingSidechainChange) + return; + + auto& audioProcessor = getPluginInstance(); + const auto sidechainActual = audioProcessor.getChannelCountOfBus (true, 1) > 0; + + if (hasSidechain && canDisableSidechain && sidechainDesired != sidechainActual) + { + lastSideChainState = sidechainDesired; + + if (isPrepared) + { + isPrepared = false; + audioProcessor.releaseResources(); + } + + if (auto* bus = audioProcessor.getBus (true, 1)) + bus->setCurrentLayout (lastSideChainState ? AudioChannelSet::mono() + : AudioChannelSet::disabled()); + + prepareProcessorWithSampleRateAndBufferSize (audioProcessor.getSampleRate(), maxBufferSize); + isPrepared = true; + } + + processingSidechainChange = false; + } + + void handleAsyncUpdate() override + { + updateSidechainState(); + } + + //============================================================================== + static AudioProcessor::CurveData::Type aaxCurveTypeToJUCE (AAX_CTypeID type) noexcept + { + switch (type) + { + case AAX_eCurveType_EQ: return AudioProcessor::CurveData::Type::EQ; + case AAX_eCurveType_Dynamics: return AudioProcessor::CurveData::Type::Dynamics; + case AAX_eCurveType_Reduction: return AudioProcessor::CurveData::Type::GainReduction; + default: break; + } + + return AudioProcessor::CurveData::Type::Unknown; + } + + uint32_t getAAXMeterIdForParamId (const String& paramID) const noexcept + { + int idx; + + for (idx = 0; idx < aaxMeters.size(); ++idx) + if (LegacyAudioParameter::getParamID (aaxMeters[idx], false) == paramID) + break; + + // you specified a parameter id in your curve but the parameter does not have the meter + // category + jassert (idx < aaxMeters.size()); + return 'Metr' + static_cast (idx); + } + + //============================================================================== + AAX_Result GetCurveData (AAX_CTypeID iCurveType, const float * iValues, uint32_t iNumValues, float * oValues ) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve) + { + if (oValues != nullptr && iValues != nullptr) + { + for (uint32_t i = 0; i < iNumValues; ++i) + oValues[i] = curve.curve (iValues[i]); + } + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AAX_Result GetCurveDataMeterIds (AAX_CTypeID iCurveType, uint32_t *oXMeterId, uint32_t *oYMeterId) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve && curve.xMeterID.isNotEmpty() && curve.yMeterID.isNotEmpty()) + { + if (oXMeterId != nullptr) *oXMeterId = getAAXMeterIdForParamId (curve.xMeterID); + if (oYMeterId != nullptr) *oYMeterId = getAAXMeterIdForParamId (curve.yMeterID); + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + AAX_Result GetCurveDataDisplayRange (AAX_CTypeID iCurveType, float *oXMin, float *oXMax, float *oYMin, float *oYMax) const override + { + auto curveType = aaxCurveTypeToJUCE (iCurveType); + + if (curveType != AudioProcessor::CurveData::Type::Unknown) + { + auto& audioProcessor = getPluginInstance(); + auto curve = audioProcessor.getResponseCurve (curveType); + + if (curve.curve) + { + if (oXMin != nullptr) *oXMin = curve.xRange.getStart(); + if (oXMax != nullptr) *oXMax = curve.xRange.getEnd(); + if (oYMin != nullptr) *oYMin = curve.yRange.getStart(); + if (oYMax != nullptr) *oYMax = curve.yRange.getEnd(); + + return AAX_SUCCESS; + } + } + + return AAX_ERROR_UNIMPLEMENTED; + } + + //============================================================================== + inline int getParamIndexFromID (AAX_CParamID paramID) const noexcept + { + if (auto* param = getParameterFromID (paramID)) + return LegacyAudioParameter::getParamIndex (getPluginInstance(), param); + + return -1; + } + + inline AAX_CParamID getAAXParamIDFromJuceIndex (int index) const noexcept + { + if (isPositiveAndBelow (index, aaxParamIDs.size())) + return aaxParamIDs.getReference (index).toRawUTF8(); + + return nullptr; + } + + AudioProcessorParameter* getParameterFromID (AAX_CParamID paramID) const noexcept + { + return paramMap [AAXClasses::getAAXParamHash (paramID)]; + } + + //============================================================================== + static AudioProcessor::BusesLayout getDefaultLayout (const AudioProcessor& p, bool enableAll) + { + AudioProcessor::BusesLayout defaultLayout; + + for (int dir = 0; dir < 2; ++dir) + { + bool isInput = (dir == 0); + auto numBuses = p.getBusCount (isInput); + auto& layouts = (isInput ? defaultLayout.inputBuses : defaultLayout.outputBuses); + + for (int i = 0; i < numBuses; ++i) + if (auto* bus = p.getBus (isInput, i)) + layouts.add (enableAll || bus->isEnabledByDefault() ? bus->getDefaultLayout() : AudioChannelSet()); + } + + return defaultLayout; + } + + static AudioProcessor::BusesLayout getDefaultLayout (AudioProcessor& p) + { + auto defaultLayout = getDefaultLayout (p, true); + + if (! p.checkBusesLayoutSupported (defaultLayout)) + defaultLayout = getDefaultLayout (p, false); + + // Your processor must support the default layout + jassert (p.checkBusesLayoutSupported (defaultLayout)); + return defaultLayout; + } + + void syncParameterAttributes (AAX_IParameter* aaxParam, const AudioProcessorParameter* juceParam) + { + if (juceParam == nullptr) + return; + + { + auto newName = juceParam->getName (31); + + if (aaxParam->Name() != newName.toRawUTF8()) + aaxParam->SetName (AAX_CString (newName.toRawUTF8())); + } + + { + auto newType = juceParam->isDiscrete() ? AAX_eParameterType_Discrete : AAX_eParameterType_Continuous; + + if (aaxParam->GetType() != newType) + aaxParam->SetType (newType); + } + + { + auto newNumSteps = static_cast (juceParam->getNumSteps()); + + if (aaxParam->GetNumberOfSteps() != newNumSteps) + aaxParam->SetNumberOfSteps (newNumSteps); + } + + { + auto defaultValue = juceParam->getDefaultValue(); + + if (! approximatelyEqual (static_cast (aaxParam->GetNormalizedDefaultValue()), defaultValue)) + aaxParam->SetNormalizedDefaultValue (defaultValue); + } + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + std::unique_ptr pluginInstance; + + static constexpr auto maxSamplesPerBlock = 1 << AAX_eAudioBufferLength_Max; + + bool isPrepared = false; + MidiBuffer midiBuffer; + Array channelList; + int32_t juceChunkIndex = 0, numSetDirtyCalls = 0; + AAX_CSampleRate sampleRate = 0; + int lastBufferSize = maxSamplesPerBlock, maxBufferSize = maxSamplesPerBlock; + bool hasSidechain = false, canDisableSidechain = false, lastSideChainState = false; + + /* Pro Tools 2021 sends TransportStateChanged on the main thread, but we read + the recording state on the audio thread. + I'm not sure whether Pro Tools ensures that these calls are mutually + exclusive, so to ensure there are no data races, we store the recording + state in an atomic int and convert it to/from an Optional as necessary. + */ + class RecordingState + { + public: + /* This uses Optional rather than std::optional for consistency with get() */ + void set (const Optional newState) + { + state.store (newState.hasValue() ? (flagValid | (*newState ? flagActive : 0)) + : 0, + std::memory_order_relaxed); + } + + /* PositionInfo::setIsRecording takes an Optional, so we use that type rather + than std::optional to avoid having to convert. + */ + Optional get() const + { + const auto loaded = state.load (std::memory_order_relaxed); + return ((loaded & flagValid) != 0) ? makeOptional ((loaded & flagActive) != 0) + : nullopt; + } + + private: + enum RecordingFlags + { + flagValid = 1 << 0, + flagActive = 1 << 1 + }; + + std::atomic state { 0 }; + }; + + RecordingState recordingState; + + std::atomic processingSidechainChange, sidechainDesired; + + std::vector sideChainBuffer; + Array inputLayoutMap, outputLayoutMap; + + Array aaxParamIDs; + HashMap paramMap; + LegacyAudioParametersWrapper juceParameters; + std::unique_ptr ownedBypassParameter; + + Array aaxMeters; + + struct ChunkMemoryBlock + { + juce::MemoryBlock data; + bool isValid; + }; + + // temporary filter data is generated in GetChunkSize + // and the size of the data returned. To avoid generating + // it again in GetChunk, we need to store it somewhere. + // However, as GetChunkSize and GetChunk can be called + // on different threads, we store it in thread dependent storage + // in a hash map with the thread id as a key. + mutable ThreadLocalValue perThreadFilterData; + CriticalSection perThreadDataLock; + + ThreadLocalValue inParameterChangedCallback; + + JUCE_DECLARE_NON_COPYABLE (JuceAAX_Processor) + }; + + //============================================================================== + void JuceAAX_GUI::CreateViewContents() + { + if (component == nullptr) + { + if (auto* params = dynamic_cast (GetEffectParameters())) + component.reset (new ContentWrapperComponent (*this, params->getPluginInstance())); + else + jassertfalse; + } + } + + int JuceAAX_GUI::getParamIndexFromID (AAX_CParamID paramID) const noexcept + { + if (auto* params = dynamic_cast (GetEffectParameters())) + return params->getParamIndexFromID (paramID); + + return -1; + } + + AAX_CParamID JuceAAX_GUI::getAAXParamIDFromJuceIndex (int index) const noexcept + { + if (auto* params = dynamic_cast (GetEffectParameters())) + return params->getAAXParamIDFromJuceIndex (index); + + return nullptr; + } + + //============================================================================== + struct AAXFormatConfiguration + { + AAXFormatConfiguration() noexcept {} + + AAXFormatConfiguration (AAX_EStemFormat inFormat, AAX_EStemFormat outFormat) noexcept + : inputFormat (inFormat), outputFormat (outFormat) {} + + AAX_EStemFormat inputFormat = AAX_eStemFormat_None, + outputFormat = AAX_eStemFormat_None; + + bool operator== (const AAXFormatConfiguration other) const noexcept + { + return inputFormat == other.inputFormat && outputFormat == other.outputFormat; + } + + bool operator< (const AAXFormatConfiguration other) const noexcept + { + return inputFormat == other.inputFormat ? (outputFormat < other.outputFormat) + : (inputFormat < other.inputFormat); + } + }; + + //============================================================================== + static int addAAXMeters (AudioProcessor& p, AAX_IEffectDescriptor& descriptor) + { + LegacyAudioParametersWrapper params; + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + params.update (p, forceLegacyParamIDs); + + int meterIdx = 0; + + for (auto* param : params) + { + auto category = param->getCategory(); + + // is this a meter? + if (((category & 0xffff0000) >> 16) == 2) + { + if (auto* meterProperties = descriptor.NewPropertyMap()) + { + meterProperties->AddProperty (AAX_eProperty_Meter_Type, getMeterTypeForCategory (category)); + meterProperties->AddProperty (AAX_eProperty_Meter_Orientation, AAX_eMeterOrientation_TopRight); + + descriptor.AddMeterDescription ('Metr' + static_cast (meterIdx++), + param->getName (1024).toRawUTF8(), meterProperties); + } + } + } + + return meterIdx; + } + + static void createDescriptor (AAX_IComponentDescriptor& desc, + const AudioProcessor::BusesLayout& fullLayout, + AudioProcessor& processor, + Array& pluginIds, + const int numMeters) + { + auto aaxInputFormat = getFormatForAudioChannelSet (fullLayout.getMainInputChannelSet(), false); + auto aaxOutputFormat = getFormatForAudioChannelSet (fullLayout.getMainOutputChannelSet(), false); + + #if JucePlugin_IsSynth + if (aaxInputFormat == AAX_eStemFormat_None) + aaxInputFormat = aaxOutputFormat; + #endif + + #if JucePlugin_IsMidiEffect + aaxInputFormat = aaxOutputFormat = AAX_eStemFormat_Mono; + #endif + + check (desc.AddAudioIn (JUCEAlgorithmIDs::inputChannels)); + check (desc.AddAudioOut (JUCEAlgorithmIDs::outputChannels)); + + check (desc.AddAudioBufferLength (JUCEAlgorithmIDs::bufferSize)); + check (desc.AddDataInPort (JUCEAlgorithmIDs::bypass, sizeof (int32_t))); + + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeIn, AAX_eMIDINodeType_LocalInput, + JucePlugin_Name, 0xffff)); + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect + check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeOut, AAX_eMIDINodeType_LocalOutput, + JucePlugin_Name " Out", 0xffff)); + #endif + + check (desc.AddPrivateData (JUCEAlgorithmIDs::pluginInstance, sizeof (PluginInstanceInfo))); + check (desc.AddPrivateData (JUCEAlgorithmIDs::preparedFlag, sizeof (int32_t))); + + if (numMeters > 0) + { + HeapBlock meterIDs (static_cast (numMeters)); + + for (int i = 0; i < numMeters; ++i) + meterIDs[i] = 'Metr' + static_cast (i); + + check (desc.AddMeters (JUCEAlgorithmIDs::meterTapBuffers, meterIDs.getData(), static_cast (numMeters))); + } + else + { + // AAX does not allow there to be any gaps in the fields of the algorithm context structure + // so just add a dummy one here if there aren't any meters + check (desc.AddPrivateData (JUCEAlgorithmIDs::meterTapBuffers, sizeof (uintptr_t))); + } + + // Create a property map + AAX_IPropertyMap* const properties = desc.NewPropertyMap(); + jassert (properties != nullptr); + + properties->AddProperty (AAX_eProperty_ManufacturerID, JucePlugin_AAXManufacturerCode); + properties->AddProperty (AAX_eProperty_ProductID, JucePlugin_AAXProductId); + + #if JucePlugin_AAXDisableBypass + properties->AddProperty (AAX_eProperty_CanBypass, false); + #else + properties->AddProperty (AAX_eProperty_CanBypass, true); + #endif + + properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast (aaxInputFormat)); + properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast (aaxOutputFormat)); + + const auto& extensions = processor.getAAXClientExtensions(); + + // This value needs to match the RTAS wrapper's Type ID, so that + // the host knows that the RTAS/AAX plugins are equivalent. + const auto pluginID = extensions.getPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), + fullLayout.getMainOutputChannelSet(), + false); + + // The plugin id generated from your AudioProcessor's getAAXPluginIDForMainBusConfig callback + // it not unique. Please fix your implementation! + jassert (! pluginIds.contains (pluginID)); + pluginIds.add (pluginID); + + properties->AddProperty (AAX_eProperty_PlugInID_Native, pluginID); + + #if ! JucePlugin_AAXDisableAudioSuite + properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, + extensions.getPluginIDForMainBusConfig (fullLayout.getMainInputChannelSet(), + fullLayout.getMainOutputChannelSet(), + true)); + #endif + + #if JucePlugin_AAXDisableMultiMono + properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, false); + #else + properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); + #endif + + #if JucePlugin_AAXDisableDynamicProcessing + properties->AddProperty (AAX_eProperty_Constraint_AlwaysProcess, true); + #endif + + #if JucePlugin_AAXDisableDefaultSettingsChunks + properties->AddProperty (AAX_eProperty_Constraint_DoNotApplyDefaultSettings, true); + #endif + + #if JucePlugin_AAXDisableSaveRestore + properties->AddProperty (AAX_eProperty_SupportsSaveRestore, false); + #endif + + properties->AddProperty (AAX_eProperty_ObservesTransportState, true); + + if (fullLayout.getChannelSet (true, 1) == AudioChannelSet::mono()) + { + check (desc.AddSideChainIn (JUCEAlgorithmIDs::sideChainBuffers)); + properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); + } + else + { + // AAX does not allow there to be any gaps in the fields of the algorithm context structure + // so just add a dummy one here if there aren't any side chains + check (desc.AddPrivateData (JUCEAlgorithmIDs::sideChainBuffers, sizeof (uintptr_t))); + } + + auto maxAuxBuses = jmax (0, jmin (15, fullLayout.outputBuses.size() - 1)); + + // add the output buses + // This is incredibly dumb: the output bus format must be well defined + // for every main bus in/out format pair. This means that there cannot + // be two configurations with different aux formats but + // identical main bus in/out formats. + for (int busIdx = 1; busIdx < maxAuxBuses + 1; ++busIdx) + { + auto set = fullLayout.getChannelSet (false, busIdx); + + if (set.isDisabled()) + break; + + auto auxFormat = getFormatForAudioChannelSet (set, true); + + if (auxFormat != AAX_eStemFormat_INT32_MAX && auxFormat != AAX_eStemFormat_None) + { + auto& name = processor.getBus (false, busIdx)->getName(); + check (desc.AddAuxOutputStem (0, static_cast (auxFormat), name.toRawUTF8())); + } + } + + check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); + } + + static inline bool hostSupportsStemFormat (AAX_EStemFormat stemFormat, const AAX_IFeatureInfo* featureInfo) + { + if (featureInfo != nullptr) + { + AAX_ESupportLevel supportLevel; + + if (featureInfo->SupportLevel (supportLevel) == AAX_SUCCESS && supportLevel == AAX_eSupportLevel_ByProperty) + { + std::unique_ptr props (featureInfo->AcquireProperties()); + + // Due to a bug in ProTools 12.8, ProTools thinks that AAX_eStemFormat_Ambi_1_ACN is not supported + // To workaround this bug, check if ProTools supports AAX_eStemFormat_Ambi_2_ACN, and, if yes, + // we can safely assume that it will also support AAX_eStemFormat_Ambi_1_ACN + if (stemFormat == AAX_eStemFormat_Ambi_1_ACN) + stemFormat = AAX_eStemFormat_Ambi_2_ACN; + + if (props != nullptr && props->GetProperty ((AAX_EProperty) stemFormat, (AAX_CPropertyValue*) &supportLevel) != 0) + return (supportLevel == AAX_eSupportLevel_Supported); + } + } + + return (AAX_STEM_FORMAT_INDEX (stemFormat) <= 12); + } + + static void getPlugInDescription (AAX_IEffectDescriptor& descriptor, [[maybe_unused]] const AAX_IFeatureInfo* featureInfo) + { + auto plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); + auto numInputBuses = plugin->getBusCount (true); + auto numOutputBuses = plugin->getBusCount (false); + + auto pluginNames = plugin->getAlternateDisplayNames(); + + pluginNames.insert (0, JucePlugin_Name); + + pluginNames.removeDuplicates (false); + + for (auto name : pluginNames) + descriptor.AddName (name.toRawUTF8()); + + descriptor.AddCategory (JucePlugin_AAXCategory); + + const int numMeters = addAAXMeters (*plugin, descriptor); + + const auto& extensions = plugin->getAAXClientExtensions(); + + if (const auto searchPath = extensions.getPageFileSearchPath().getFullPathName(); searchPath.isNotEmpty()) + descriptor.AddResourceInfo (AAX_eResourceType_PageTableDir, searchPath.toRawUTF8()); + + if (const auto filename = extensions.getPageFileName(); filename.isNotEmpty()) + descriptor.AddResourceInfo (AAX_eResourceType_PageTable, filename.toRawUTF8()); + + check (descriptor.AddProcPtr ((void*) JuceAAX_GUI::Create, kAAX_ProcPtrID_Create_EffectGUI)); + check (descriptor.AddProcPtr ((void*) JuceAAX_Processor::Create, kAAX_ProcPtrID_Create_EffectParameters)); + + Array pluginIds; + #if JucePlugin_IsMidiEffect + // MIDI effect plug-ins do not support any audio channels + jassert (numInputBuses == 0 && numOutputBuses == 0); + + if (auto* desc = descriptor.NewComponentDescriptor()) + { + createDescriptor (*desc, plugin->getBusesLayout(), *plugin, pluginIds, numMeters); + check (descriptor.AddComponent (desc)); + } + #else + const int numIns = numInputBuses > 0 ? numElementsInArray (aaxFormats) : 0; + const int numOuts = numOutputBuses > 0 ? numElementsInArray (aaxFormats) : 0; + + for (int inIdx = 0; inIdx < jmax (numIns, 1); ++inIdx) + { + auto aaxInFormat = numIns > 0 ? aaxFormats[inIdx] : AAX_eStemFormat_None; + auto inLayout = channelSetFromStemFormat (aaxInFormat, false); + + for (int outIdx = 0; outIdx < jmax (numOuts, 1); ++outIdx) + { + auto aaxOutFormat = numOuts > 0 ? aaxFormats[outIdx] : AAX_eStemFormat_None; + auto outLayout = channelSetFromStemFormat (aaxOutFormat, false); + + if (hostSupportsStemFormat (aaxInFormat, featureInfo) + && hostSupportsStemFormat (aaxOutFormat, featureInfo)) + { + AudioProcessor::BusesLayout fullLayout; + + if (! JuceAAX_Processor::fullBusesLayoutFromMainLayout (*plugin, inLayout, outLayout, fullLayout)) + continue; + + if (auto* desc = descriptor.NewComponentDescriptor()) + { + createDescriptor (*desc, fullLayout, *plugin, pluginIds, numMeters); + check (descriptor.AddComponent (desc)); + } + } + } + } + + // You don't have any supported layouts + jassert (pluginIds.size() > 0); + #endif + } +} // namespace AAXClasses + +void AAX_CALLBACK AAXClasses::algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], const void* const instancesEnd) +{ + AAXClasses::JuceAAX_Processor::algorithmCallback (instancesBegin, instancesEnd); +} + +//============================================================================== +AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection*); +AAX_Result JUCE_CDECL GetEffectDescriptions (AAX_ICollection* collection) +{ + ScopedJuceInitialiser_GUI libraryInitialiser; + + std::unique_ptr stemFormatFeatureInfo; + + if (const auto* hostDescription = collection->DescriptionHost()) + stemFormatFeatureInfo.reset (hostDescription->AcquireFeatureProperties (AAXATTR_ClientFeature_StemFormat)); + + if (auto* descriptor = collection->NewDescriptor()) + { + AAXClasses::getPlugInDescription (*descriptor, stemFormatFeatureInfo.get()); + collection->AddEffect (JUCE_STRINGIFY (JucePlugin_AAXIdentifier), descriptor); + + collection->SetManufacturerName (JucePlugin_Manufacturer); + collection->AddPackageName (JucePlugin_Desc); + collection->AddPackageName (JucePlugin_Name); + collection->SetPackageVersion (JucePlugin_VersionCode); + + return AAX_SUCCESS; + } + + return AAX_ERROR_NULL_OBJECT; +} + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +//============================================================================== +#if _MSC_VER || JUCE_MINGW +extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } +#endif + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm index 400648e..b56134c 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX.mm @@ -24,4 +24,4 @@ */ #define JUCE_INCLUDED_AAX_IN_MM 1 -#include "AAX/juce_AAX_Wrapper.cpp" +#include "juce_audio_plugin_client_AAX.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp new file mode 100644 index 0000000..ce2d09b --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AAX_utils.cpp @@ -0,0 +1,107 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include + +#if JucePlugin_Build_AAX + +#include + +static_assert (AAX_SDK_CURRENT_REVISION >= AAX_SDK_2p4p0_REVISION, "JUCE requires AAX SDK version 2.4.0 or higher"); + +#if JUCE_INTEL || (JUCE_MAC && JUCE_ARM) + +#include + +// Utilities +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant") +#include +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", + "-Wextra-semi", + "-Wfloat-equal", + "-Winconsistent-missing-destructor-override", + "-Wshift-sign-overflow", + "-Wunused-parameter", + "-Wzero-as-null-pointer-constant", + "-Wfour-char-constants", + "-Wdeprecated-copy-with-user-provided-dtor", + "-Wdeprecated") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6001 6053 4996 5033 4068 4996) + +#include +#include + +#if defined(_WIN32) && ! defined(WIN32) + #define WIN32 +#endif +#include + +#include +#include +#include + +// Versioned Interfaces +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#else + #error "This version of the AAX SDK does not support the current platform." +#endif +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp index 01a62cb..4cd70b8 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp @@ -23,4 +23,26 @@ ============================================================================== */ -#include "ARA/juce_ARA_Wrapper.cpp" +#include +#include + +#if JucePlugin_Enable_ARA + +#include +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", + "-Wgnu-zero-variadic-macro-arguments", + "-Wmissing-prototypes", + "-Wfloat-equal") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100) + +#include +#include +#include +#include + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm index 8abeba8..e5cc286 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_1.mm @@ -23,4 +23,2599 @@ ============================================================================== */ -#include "AU/juce_AU_Wrapper.mm" +#include +#include +#include + +#if JucePlugin_Build_AU + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-align", + "-Wconversion", + "-Wdeprecated-anon-enum-enum-conversion", + "-Wdeprecated-declarations", + "-Wextra-semi", + "-Wfloat-equal", + "-Wformat-pedantic", + "-Wgnu-zero-variadic-macro-arguments", + "-Wnullable-to-nonnull-conversion", + "-Woverloaded-virtual", + "-Wshadow", + "-Wshorten-64-to-32", + "-Wsign-conversion", + "-Wswitch-enum", + "-Wunused-parameter", + "-Wzero-as-null-pointer-constant") + +#include + +#include +#include +#include +#include +#include +#include "AudioUnitSDK/MusicDeviceBase.h" + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + +#include + +#include +#include +#include +#include + +#if JucePlugin_Enable_ARA + #include + #include + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current JUCE ARA implementation" + #endif +#endif + +#include + +//============================================================================== +using namespace juce; + +static Array activePlugins, activeUIs; + +static const AudioUnitPropertyID juceFilterObjectPropertyID = 0x1a45ffe9; + +template <> struct ContainerDeletePolicy { static void destroy (const __CFString* o) { if (o != nullptr) CFRelease (o); } }; + +// make sure the audio processor is initialized before the AUBase class +struct AudioProcessorHolder +{ + AudioProcessorHolder (bool initialiseGUI) + { + if (initialiseGUI) + initialiseJuce_GUI(); + + juceFilter = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnit); + + // audio units do not have a notion of enabled or un-enabled buses + juceFilter->enableAllBuses(); + } + + std::unique_ptr juceFilter; +}; + +//============================================================================== +class JuceAU : public AudioProcessorHolder, + public ausdk::MusicDeviceBase, + public AudioProcessorListener, + public AudioProcessorParameter::Listener +{ +public: + JuceAU (AudioUnit component) + : AudioProcessorHolder (activePlugins.size() + activeUIs.size() == 0), + MusicDeviceBase (component, + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true), + (UInt32) AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)) + { + inParameterChangedCallback = false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + juceFilter->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); + + for (int i = 0; i < numConfigs; ++i) + { + AUChannelInfo info; + + info.inChannels = configs[i][0]; + info.outChannels = configs[i][1]; + + channelInfo.add (info); + } + #else + channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); + #endif + + AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); + + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + juceFilter->addListener (this); + + addParameters(); + + activePlugins.add (this); + + zerostruct (auEvent); + auEvent.mArgument.mParameter.mAudioUnit = GetComponentInstance(); + auEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global; + auEvent.mArgument.mParameter.mElement = 0; + + zerostruct (midiCallback); + + CreateElements(); + + if (syncAudioUnitWithProcessor() != noErr) + jassertfalse; + } + + ~JuceAU() override + { + if (bypassParam != nullptr) + bypassParam->removeListener (this); + + deleteActiveEditors(); + juceFilter = nullptr; + clearPresetsArray(); + + jassert (activePlugins.contains (this)); + activePlugins.removeFirstMatchingValue (this); + + if (activePlugins.size() + activeUIs.size() == 0) + shutdownJuce_GUI(); + } + + //============================================================================== + ComponentResult Initialize() override + { + ComponentResult err; + + if ((err = syncProcessorWithAudioUnit()) != noErr) + return err; + + if ((err = MusicDeviceBase::Initialize()) != noErr) + return err; + + mapper.alloc (*juceFilter); + pulledSucceeded.calloc (static_cast (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true))); + + prepareToPlay(); + + return noErr; + } + + void Cleanup() override + { + MusicDeviceBase::Cleanup(); + + pulledSucceeded.free(); + mapper.release(); + + if (juceFilter != nullptr) + juceFilter->releaseResources(); + + audioBuffer.release(); + midiEvents.clear(); + incomingEvents.clear(); + prepared = false; + } + + ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) override + { + if (! prepared) + prepareToPlay(); + + if (juceFilter != nullptr) + juceFilter->reset(); + + return MusicDeviceBase::Reset (inScope, inElement); + } + + //============================================================================== + void prepareToPlay() + { + if (juceFilter != nullptr) + { + juceFilter->setRateAndBufferSizeDetails (getSampleRate(), (int) GetMaxFramesPerSlice()); + + audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (juceFilter.get()), (int) GetMaxFramesPerSlice() + 32); + juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); + + midiEvents.ensureSize (2048); + midiEvents.clear(); + incomingEvents.ensureSize (2048); + incomingEvents.clear(); + + prepared = true; + } + } + + //============================================================================== + bool BusCountWritable ([[maybe_unused]] AudioUnitScope scope) override + { + #ifdef JucePlugin_PreferredChannelConfigurations + return false; + #else + bool isInput; + + if (scopeToDirection (scope, isInput) != noErr) + return false; + + #if JucePlugin_IsMidiEffect + return false; + #elif JucePlugin_IsSynth + if (isInput) return false; + #endif + + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + return (juceFilter->canAddBus (isInput) || (busCount > 0 && juceFilter->canRemoveBus (isInput))); + #endif + } + + OSStatus SetBusCount (AudioUnitScope scope, UInt32 count) override + { + OSStatus err = noErr; + bool isInput; + + if ((err = scopeToDirection (scope, isInput)) != noErr) + return err; + + if (count != (UInt32) AudioUnitHelpers::getBusCount (*juceFilter, isInput)) + { + #ifdef JucePlugin_PreferredChannelConfigurations + return kAudioUnitErr_PropertyNotWritable; + #else + const int busCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + + if ((! juceFilter->canAddBus (isInput)) && ((busCount == 0) || (! juceFilter->canRemoveBus (isInput)))) + return kAudioUnitErr_PropertyNotWritable; + + // we need to already create the underlying elements so that we can change their formats + err = MusicDeviceBase::SetBusCount (scope, count); + + if (err != noErr) + return err; + + // however we do need to update the format tag: we need to do the same thing in SetFormat, for example + const int requestedNumBus = static_cast (count); + { + (isInput ? currentInputLayout : currentOutputLayout).resize (requestedNumBus); + + int busNr; + + for (busNr = (busCount - 1); busNr != (requestedNumBus - 1); busNr += (requestedNumBus > busCount ? 1 : -1)) + { + if (requestedNumBus > busCount) + { + if (! juceFilter->addBus (isInput)) + break; + + err = syncAudioUnitWithChannelSet (isInput, busNr, + juceFilter->getBus (isInput, busNr + 1)->getDefaultLayout()); + if (err != noErr) + break; + } + else + { + if (! juceFilter->removeBus (isInput)) + break; + } + } + + err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); + } + + // was there an error? + if (err != noErr) + { + // restore bus state + const int newBusCount = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + for (int i = newBusCount; i != busCount; i += (busCount > newBusCount ? 1 : -1)) + { + if (busCount > newBusCount) + juceFilter->addBus (isInput); + else + juceFilter->removeBus (isInput); + } + + (isInput ? currentInputLayout : currentOutputLayout).resize (busCount); + MusicDeviceBase::SetBusCount (scope, static_cast (busCount)); + + return kAudioUnitErr_FormatNotSupported; + } + + // update total channel count + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + addSupportedLayoutTagsForDirection (isInput); + + if (err != noErr) + return err; + #endif + } + + return noErr; + } + + UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) override + { + if (outInfo != nullptr) + *outInfo = channelInfo.getRawDataPointer(); + + return (UInt32) channelInfo.size(); + } + + //============================================================================== + ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + UInt32& outDataSize, + bool& outWritable) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case juceFilterObjectPropertyID: + outWritable = false; + outDataSize = sizeof (void*) * 2; + return noErr; + + case kAudioUnitProperty_OfflineRender: + outWritable = true; + outDataSize = sizeof (UInt32); + return noErr; + + case kMusicDeviceProperty_InstrumentCount: + outDataSize = sizeof (UInt32); + outWritable = false; + return noErr; + + case kAudioUnitProperty_CocoaUI: + outDataSize = sizeof (AudioUnitCocoaViewInfo); + outWritable = true; + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = false; + return noErr; + + case kAudioUnitProperty_HostMIDIProtocol: + outDataSize = sizeof (SInt32); + outWritable = true; + return noErr; + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallbackInfo: + outDataSize = sizeof (CFArrayRef); + outWritable = false; + return noErr; + + case kAudioUnitProperty_MIDIOutputCallback: + outDataSize = sizeof (AUMIDIOutputCallbackStruct); + outWritable = true; + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + outDataSize = sizeof (AUMIDIEventListBlock); + outWritable = true; + return noErr; + #endif + #endif + + case kAudioUnitProperty_ParameterStringFromValue: + outDataSize = sizeof (AudioUnitParameterStringFromValue); + outWritable = false; + return noErr; + + case kAudioUnitProperty_ParameterValueFromString: + outDataSize = sizeof (AudioUnitParameterValueFromString); + outWritable = false; + return noErr; + + case kAudioUnitProperty_BypassEffect: + outDataSize = sizeof (UInt32); + outWritable = true; + return noErr; + + case kAudioUnitProperty_SupportsMPE: + outDataSize = sizeof (UInt32); + outWritable = false; + return noErr; + + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitFactory); + return noErr; + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + outWritable = false; + outDataSize = sizeof (ARA::ARAAudioUnitPlugInExtensionBinding); + return noErr; + #endif + + default: break; + } + } + + return MusicDeviceBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); + } + + ComponentResult GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + case kAudioUnitProperty_ParameterClumpName: + + if (auto* clumpNameInfo = (AudioUnitParameterNameInfo*) outData) + { + if (juceFilter != nullptr) + { + auto clumpIndex = clumpNameInfo->inID - 1; + const auto* group = parameterGroups[(int) clumpIndex]; + auto name = group->getName(); + + while (group->getParent() != &juceFilter->getParameterTree()) + { + group = group->getParent(); + name = group->getName() + group->getSeparator() + name; + } + + clumpNameInfo->outName = name.toCFString(); + return noErr; + } + } + + // Failed to find a group corresponding to the clump ID. + jassertfalse; + break; + + //============================================================================== + #if JucePlugin_Enable_ARA + case ARA::kAudioUnitProperty_ARAFactory: + { + auto auFactory = static_cast (outData); + if (auFactory->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + auFactory->outFactory = createARAFactory(); + return noErr; + } + + case ARA::kAudioUnitProperty_ARAPlugInExtensionBindingWithRoles: + { + auto binding = static_cast (outData); + if (binding->inOutMagicNumber != ARA::kARAAudioUnitMagic) + return kAudioUnitErr_InvalidProperty; // if the magic value isn't found, the property ID is re-used outside the ARA context with different, unsupported sematics + + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (juceFilter.get()); + binding->outPlugInExtension = araAudioProcessorExtension->bindToARA (binding->inDocumentControllerRef, binding->knownRoles, binding->assignedRoles); + if (binding->outPlugInExtension == nullptr) + return kAudioUnitErr_CannotDoInCurrentContext; // bindToARA() returns null if binding is already established + + return noErr; + } + #endif + + case juceFilterObjectPropertyID: + ((void**) outData)[0] = (void*) static_cast (juceFilter.get()); + ((void**) outData)[1] = (void*) this; + return noErr; + + case kAudioUnitProperty_OfflineRender: + *(UInt32*) outData = (juceFilter != nullptr && juceFilter->isNonRealtime()) ? 1 : 0; + return noErr; + + case kMusicDeviceProperty_InstrumentCount: + *(UInt32*) outData = 1; + return noErr; + + case kAudioUnitProperty_BypassEffect: + if (bypassParam != nullptr) + *(UInt32*) outData = (bypassParam->getValue() != 0.0f ? 1 : 0); + else + *(UInt32*) outData = isBypassed ? 1 : 0; + return noErr; + + case kAudioUnitProperty_SupportsMPE: + *(UInt32*) outData = (juceFilter != nullptr && juceFilter->supportsMPE()) ? 1 : 0; + return noErr; + + case kAudioUnitProperty_CocoaUI: + { + JUCE_AUTORELEASEPOOL + { + static JuceUICreationClass cls; + + // (NB: this may be the host's bundle, not necessarily the component's) + NSBundle* bundle = [NSBundle bundleForClass: cls.cls]; + + AudioUnitCocoaViewInfo* info = static_cast (outData); + info->mCocoaAUViewClass[0] = (CFStringRef) [juceStringToNS (class_getName (cls.cls)) retain]; + info->mCocoaAUViewBundleLocation = (CFURLRef) [[NSURL fileURLWithPath: [bundle bundlePath]] retain]; + } + + return noErr; + } + + break; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_AudioUnitMIDIProtocol: + { + // This will become configurable in the future + *static_cast (outData) = kMIDIProtocol_1_0; + return noErr; + } + #endif + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallbackInfo: + { + CFStringRef strs[1]; + strs[0] = CFSTR ("MIDI Callback"); + + CFArrayRef callbackArray = CFArrayCreate (nullptr, (const void**) strs, 1, &kCFTypeArrayCallBacks); + *(CFArrayRef*) outData = callbackArray; + return noErr; + } + #endif + + case kAudioUnitProperty_ParameterValueFromString: + { + if (AudioUnitParameterValueFromString* pv = (AudioUnitParameterValueFromString*) outData) + { + if (juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (pv->inParamID)) + { + const String text (String::fromCFString (pv->inString)); + + if (LegacyAudioParameter::isLegacy (param)) + pv->outValue = text.getFloatValue(); + else + pv->outValue = param->getValueForText (text) * getMaximumParameterValue (param); + + + return noErr; + } + } + } + } + break; + + case kAudioUnitProperty_ParameterStringFromValue: + { + if (AudioUnitParameterStringFromValue* pv = (AudioUnitParameterStringFromValue*) outData) + { + if (juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (pv->inParamID)) + { + const float value = (float) *(pv->inValue); + String text; + + if (LegacyAudioParameter::isLegacy (param)) + text = String (value); + else + text = param->getText (value / getMaximumParameterValue (param), 0); + + pv->outString = text.toCFString(); + + return noErr; + } + } + } + } + break; + + default: + break; + } + } + + return MusicDeviceBase::GetProperty (inID, inScope, inElement, outData); + } + + ComponentResult SetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + const void* inData, + UInt32 inDataSize) override + { + if (inScope == kAudioUnitScope_Global) + { + switch (inID) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + case kAudioUnitProperty_MIDIOutputCallback: + if (inDataSize < sizeof (AUMIDIOutputCallbackStruct)) + return kAudioUnitErr_InvalidPropertyValue; + + if (AUMIDIOutputCallbackStruct* callbackStruct = (AUMIDIOutputCallbackStruct*) inData) + midiCallback = *callbackStruct; + + return noErr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_MIDIOutputEventListCallback: + { + if (inDataSize != sizeof (AUMIDIEventListBlock)) + return kAudioUnitErr_InvalidPropertyValue; + + midiEventListBlock = ScopedMIDIEventListBlock::copy (*static_cast (inData)); + return noErr; + } + #endif + #endif + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case kAudioUnitProperty_HostMIDIProtocol: + { + if (inDataSize != sizeof (SInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + hostProtocol = *static_cast (inData); + return noErr; + } + #endif + + case kAudioUnitProperty_BypassEffect: + { + if (inDataSize < sizeof (UInt32)) + return kAudioUnitErr_InvalidPropertyValue; + + const bool newBypass = *((UInt32*) inData) != 0; + const bool currentlyBypassed = (bypassParam != nullptr ? (bypassParam->getValue() != 0.0f) : isBypassed); + + if (newBypass != currentlyBypassed) + { + if (bypassParam != nullptr) + bypassParam->setValueNotifyingHost (newBypass ? 1.0f : 0.0f); + else + isBypassed = newBypass; + + if (! currentlyBypassed && IsInitialized()) // turning bypass off and we're initialized + Reset (0, 0); + } + + return noErr; + } + + case kAudioUnitProperty_OfflineRender: + { + const auto shouldBeOffline = (*reinterpret_cast (inData) != 0); + + if (juceFilter != nullptr) + { + const auto isOffline = juceFilter->isNonRealtime(); + + if (isOffline != shouldBeOffline) + { + const ScopedLock sl (juceFilter->getCallbackLock()); + + juceFilter->setNonRealtime (shouldBeOffline); + + if (prepared) + juceFilter->prepareToPlay (getSampleRate(), (int) GetMaxFramesPerSlice()); + } + } + + return noErr; + } + + case kAudioUnitProperty_AUHostIdentifier: + { + if (inDataSize < sizeof (AUHostVersionIdentifier)) + return kAudioUnitErr_InvalidPropertyValue; + + const auto* identifier = static_cast (inData); + PluginHostType::hostIdReportedByWrapper = String::fromCFString (identifier->hostName); + + return noErr; + } + + default: break; + } + } + + return MusicDeviceBase::SetProperty (inID, inScope, inElement, inData, inDataSize); + } + + //============================================================================== + ComponentResult SaveState (CFPropertyListRef* outData) override + { + ComponentResult err = MusicDeviceBase::SaveState (outData); + + if (err != noErr) + return err; + + jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); + + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; + + if (juceFilter != nullptr) + { + juce::MemoryBlock state; + + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + juceFilter->getCurrentProgramStateInformation (state); + #else + juceFilter->getStateInformation (state); + #endif + + if (state.getSize() > 0) + { + CFUniquePtr ourState (CFDataCreate (kCFAllocatorDefault, (const UInt8*) state.getData(), (CFIndex) state.getSize())); + CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); + CFDictionarySetValue (dict, key.get(), ourState.get()); + } + } + + return noErr; + } + + ComponentResult RestoreState (CFPropertyListRef inData) override + { + const ScopedValueSetter scope { restoringState, true }; + + { + // Remove the data entry from the state to prevent the superclass loading the parameters + CFUniquePtr copyWithoutData (CFDictionaryCreateMutableCopy (nullptr, 0, (CFDictionaryRef) inData)); + CFDictionaryRemoveValue (copyWithoutData.get(), CFSTR (kAUPresetDataKey)); + ComponentResult err = MusicDeviceBase::RestoreState (copyWithoutData.get()); + + if (err != noErr) + return err; + } + + if (juceFilter != nullptr) + { + CFDictionaryRef dict = (CFDictionaryRef) inData; + CFDataRef data = nullptr; + + CFUniquePtr key (CFStringCreateWithCString (kCFAllocatorDefault, JUCE_STATE_DICTIONARY_KEY, kCFStringEncodingUTF8)); + + bool valuePresent = CFDictionaryGetValueIfPresent (dict, key.get(), (const void**) &data); + + if (valuePresent) + { + if (data != nullptr) + { + const int numBytes = (int) CFDataGetLength (data); + const juce::uint8* const rawBytes = CFDataGetBytePtr (data); + + if (numBytes > 0) + { + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + juceFilter->setCurrentProgramStateInformation (rawBytes, numBytes); + #else + juceFilter->setStateInformation (rawBytes, numBytes); + #endif + } + } + } + } + + return noErr; + } + + //============================================================================== + bool busIgnoresLayout ([[maybe_unused]] bool isInput, [[maybe_unused]] int busNr) const + { + #ifdef JucePlugin_PreferredChannelConfigurations + return true; + #else + if (const AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNr)) + { + AudioChannelSet discreteRangeSet; + const int n = bus->getDefaultLayout().size(); + for (int i = 0; i < n; ++i) + discreteRangeSet.addChannel ((AudioChannelSet::ChannelType) (256 + i)); + + // if the audioprocessor supports this it cannot + // really be interested in the bus layouts + return bus->isLayoutSupported (discreteRangeSet); + } + + return true; + #endif + } + + UInt32 GetAudioChannelLayout (AudioUnitScope scope, + AudioUnitElement element, + AudioChannelLayout* outLayoutPtr, + bool& outWritable) override + { + outWritable = false; + + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) + return 0; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return 0; + + outWritable = true; + + const size_t sizeInBytes = sizeof (AudioChannelLayout) - sizeof (AudioChannelDescription); + + if (outLayoutPtr != nullptr) + { + zeromem (outLayoutPtr, sizeInBytes); + outLayoutPtr->mChannelLayoutTag = getCurrentLayout (info.isInput, info.busNr); + } + + return sizeInBytes; + } + + std::vector GetChannelLayoutTags (AudioUnitScope inScope, AudioUnitElement inElement) override + { + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return {}; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return {}; + + return getSupportedBusLayouts (info.isInput, info.busNr); + } + + OSStatus SetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, const AudioChannelLayout* inLayout) override + { + const auto info = getElementInfo (scope, element); + + if (info.error != noErr) + return info.error; + + if (busIgnoresLayout (info.isInput, info.busNr)) + return kAudioUnitErr_PropertyNotWritable; + + if (inLayout == nullptr) + return kAudioUnitErr_InvalidPropertyValue; + + auto& ioElement = IOElement (info.isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, element); + + const AudioChannelSet newChannelSet = CoreAudioLayouts::fromCoreAudio (*inLayout); + const int currentNumChannels = static_cast (ioElement.NumberChannels()); + const int newChannelNum = newChannelSet.size(); + + if (currentNumChannels != newChannelNum) + return kAudioUnitErr_InvalidPropertyValue; + + // check if the new layout could be potentially set + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newChannelNum, configs)) + return kAudioUnitErr_FormatNotSupported; + #else + if (! juceFilter->getBus (info.isInput, info.busNr)->isLayoutSupported (newChannelSet)) + return kAudioUnitErr_FormatNotSupported; + #endif + + getCurrentLayout (info.isInput, info.busNr) = CoreAudioLayouts::toCoreAudio (newChannelSet); + + return noErr; + } + + //============================================================================== + // When parameters are discrete we need to use integer values. + float getMaximumParameterValue ([[maybe_unused]] AudioProcessorParameter* juceParam) + { + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + return 1.0f; + #else + return juceParam->isDiscrete() ? (float) (juceParam->getNumSteps() - 1) : 1.0f; + #endif + } + + ComponentResult GetParameterInfo (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inParameterID)) + { + outParameterInfo.unit = kAudioUnitParameterUnit_Generic; + outParameterInfo.flags = (UInt32) (kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString + | kAudioUnitParameterFlag_ValuesHaveStrings); + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + outParameterInfo.flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; + #endif + + const String name = param->getName (1024); + + // Set whether the param is automatable (unnamed parameters aren't allowed to be automated) + if (name.isEmpty() || ! param->isAutomatable()) + outParameterInfo.flags |= kAudioUnitParameterFlag_NonRealTime; + + const bool isParameterDiscrete = param->isDiscrete(); + + if (! isParameterDiscrete) + outParameterInfo.flags |= kAudioUnitParameterFlag_CanRamp; + + if (param->isMetaParameter()) + outParameterInfo.flags |= kAudioUnitParameterFlag_IsGlobalMeta; + + auto parameterGroupHierarchy = juceFilter->getParameterTree().getGroupsForParameter (param); + + if (! parameterGroupHierarchy.isEmpty()) + { + outParameterInfo.flags |= kAudioUnitParameterFlag_HasClump; + outParameterInfo.clumpID = (UInt32) parameterGroups.indexOf (parameterGroupHierarchy.getLast()) + 1; + } + + // Is this a meter? + if ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2) + { + outParameterInfo.flags &= ~kAudioUnitParameterFlag_IsWritable; + outParameterInfo.flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; + outParameterInfo.unit = kAudioUnitParameterUnit_LinearGain; + } + else + { + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (isParameterDiscrete) + outParameterInfo.unit = param->isBoolean() ? kAudioUnitParameterUnit_Boolean + : kAudioUnitParameterUnit_Indexed; + #endif + } + + MusicDeviceBase::FillInParameterName (outParameterInfo, name.toCFString(), true); + + outParameterInfo.minValue = 0.0f; + outParameterInfo.maxValue = getMaximumParameterValue (param); + outParameterInfo.defaultValue = param->getDefaultValue() * getMaximumParameterValue (param); + jassert (outParameterInfo.defaultValue >= outParameterInfo.minValue + && outParameterInfo.defaultValue <= outParameterInfo.maxValue); + + return noErr; + } + } + + return kAudioUnitErr_InvalidParameter; + } + + ComponentResult GetParameterValueStrings (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + CFArrayRef *outStrings) override + { + if (outStrings == nullptr) + return noErr; + + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inParameterID)) + { + if (param->isDiscrete()) + { + auto index = LegacyAudioParameter::getParamIndex (*juceFilter, param); + + if (auto* valueStrings = parameterValueStringArrays[index]) + { + *outStrings = CFArrayCreate (nullptr, + (const void **) valueStrings->getRawDataPointer(), + valueStrings->size(), + nullptr); + + return noErr; + } + } + } + } + + return kAudioUnitErr_InvalidParameter; + } + + ComponentResult GetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32& outValue) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inID)) + { + const auto normValue = param->getValue(); + + outValue = normValue * getMaximumParameterValue (param); + return noErr; + } + } + + return MusicDeviceBase::GetParameter (inID, inScope, inElement, outValue); + } + + ComponentResult SetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32 inValue, + UInt32 inBufferOffsetInFrames) override + { + if (inScope == kAudioUnitScope_Global && juceFilter != nullptr) + { + if (auto* param = getParameterForAUParameterID (inID)) + { + auto value = inValue / getMaximumParameterValue (param); + + if (! approximatelyEqual (value, param->getValue())) + { + inParameterChangedCallback = true; + param->setValueNotifyingHost (value); + } + + return noErr; + } + } + + return MusicDeviceBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); + } + + // No idea what this method actually does or what it should return. Current Apple docs say nothing about it. + // (Note that this isn't marked 'override' in case older versions of the SDK don't include it) + bool CanScheduleParameters() const override { return false; } + + //============================================================================== + bool SupportsTail() override { return true; } + Float64 GetTailTime() override { return juceFilter->getTailLengthSeconds(); } + + double getSampleRate() + { + if (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false) > 0) + return Output (0).GetStreamFormat().mSampleRate; + + return 44100.0; + } + + Float64 GetLatency() override + { + const double rate = getSampleRate(); + jassert (rate > 0); + #if JucePlugin_Enable_ARA + jassert (juceFilter->getLatencySamples() == 0 || ! dynamic_cast (juceFilter.get())->isBoundToARA()); + #endif + return rate > 0 ? juceFilter->getLatencySamples() / rate : 0; + } + + class ScopedPlayHead : private AudioPlayHead + { + public: + explicit ScopedPlayHead (JuceAU& juceAudioUnit) + : audioUnit (juceAudioUnit) + { + audioUnit.juceFilter->setPlayHead (this); + } + + ~ScopedPlayHead() override + { + audioUnit.juceFilter->setPlayHead (nullptr); + } + + private: + Optional getPosition() const override + { + PositionInfo info; + + info.setFrameRate ([this]() -> Optional + { + switch (audioUnit.lastTimeStamp.mSMPTETime.mType) + { + case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); + case kSMPTETimeType24: return FrameRate().withBaseRate (24); + case kSMPTETimeType25: return FrameRate().withBaseRate (25); + case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); + case kSMPTETimeType30: return FrameRate().withBaseRate (30); + case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); + case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + case kSMPTETimeType60: return FrameRate().withBaseRate (60); + case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); + case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); + case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + case kSMPTETimeType50: return FrameRate().withBaseRate (50); + default: break; + } + + return {}; + }()); + + double ppqPosition = 0.0; + double bpm = 0.0; + + if (audioUnit.CallHostBeatAndTempo (&ppqPosition, &bpm) == noErr) + { + info.setPpqPosition (ppqPosition); + info.setBpm (bpm); + } + + UInt32 outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat; + float num; + UInt32 den; + + if (audioUnit.CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, + &num, + &den, + &outCurrentMeasureDownBeat) == noErr) + { + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + Boolean playing = false, looping = false, playchanged; + + const auto setTimeInSamples = [&] (auto timeInSamples) + { + info.setTimeInSamples ((int64) (timeInSamples + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / audioUnit.getSampleRate()); + }; + + if (audioUnit.CallHostTransportState (&playing, + &playchanged, + &outCurrentSampleInTimeLine, + &looping, + &outCycleStartBeat, + &outCycleEndBeat) == noErr) + { + info.setIsPlaying (playing); + info.setIsLooping (looping); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + setTimeInSamples (outCurrentSampleInTimeLine); + } + else + { + // If the host doesn't support this callback, then use the sample time from lastTimeStamp + setTimeInSamples (audioUnit.lastTimeStamp.mSampleTime); + } + + info.setHostTimeNs ((audioUnit.lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0 + ? makeOptional (audioUnit.timeConversions.hostTimeToNanos (audioUnit.lastTimeStamp.mHostTime)) + : nullopt); + + return info; + } + + JuceAU& audioUnit; + }; + + //============================================================================== + void sendAUEvent (const AudioUnitEventType type, const int juceParamIndex) + { + if (restoringState) + return; + + auEvent.mEventType = type; + auEvent.mArgument.mParameter.mParameterID = getAUParameterIDForIndex (juceParamIndex); + AUEventListenerNotify (nullptr, nullptr, &auEvent); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float /*newValue*/) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + sendAUEvent (kAudioUnitEvent_ParameterValueChange, index); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + sendAUEvent (kAudioUnitEvent_BeginParameterChangeGesture, index); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + audioProcessorChangedUpdater.update (details); + } + + //============================================================================== + // this will only ever be called by the bypass parameter + void parameterValueChanged (int, float) override + { + if (! restoringState) + PropertyChanged (kAudioUnitProperty_BypassEffect, kAudioUnitScope_Global, 0); + } + + void parameterGestureChanged (int, bool) override {} + + //============================================================================== + bool StreamFormatWritable (AudioUnitScope scope, AudioUnitElement element) override + { + const auto info = getElementInfo (scope, element); + + return ((! IsInitialized()) && (info.error == noErr)); + } + + bool ValidFormat (AudioUnitScope inScope, + AudioUnitElement inElement, + const AudioStreamBasicDescription& inNewFormat) override + { + // DSP Quattro incorrectly uses global scope for the ValidFormat call + if (inScope == kAudioUnitScope_Global) + return ValidFormat (kAudioUnitScope_Input, inElement, inNewFormat) + || ValidFormat (kAudioUnitScope_Output, inElement, inNewFormat); + + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return false; + + if (info.kind == BusKind::wrapperOnly) + return true; + + const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); + const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); + + if (newNumChannels == oldNumChannels) + return true; + + if ([[maybe_unused]] AudioProcessor::Bus* bus = juceFilter->getBus (info.isInput, info.busNr)) + { + if (! MusicDeviceBase::ValidFormat (inScope, inElement, inNewFormat)) + return false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + return AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs); + #else + return bus->isNumberOfChannelsSupported (newNumChannels); + #endif + } + + return false; + } + + // AU requires us to override this for the sole reason that we need to find a default layout tag if the number of channels have changed + OSStatus ChangeStreamFormat (AudioUnitScope inScope, + AudioUnitElement inElement, + const AudioStreamBasicDescription& inPrevFormat, + const AudioStreamBasicDescription& inNewFormat) override + { + const auto info = getElementInfo (inScope, inElement); + + if (info.error != noErr) + return info.error; + + AudioChannelLayoutTag& currentTag = getCurrentLayout (info.isInput, info.busNr); + + const auto newNumChannels = static_cast (inNewFormat.mChannelsPerFrame); + const auto oldNumChannels = juceFilter->getChannelCountOfBus (info.isInput, info.busNr); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (*juceFilter, info.isInput, info.busNr, newNumChannels, configs)) + return kAudioUnitErr_FormatNotSupported; + #endif + + // predict channel layout + const auto set = [&] + { + if (info.kind == BusKind::wrapperOnly) + return AudioChannelSet::discreteChannels (newNumChannels); + + if (newNumChannels != oldNumChannels) + return juceFilter->getBus (info.isInput, info.busNr)->supportedLayoutWithChannels (newNumChannels); + + return juceFilter->getChannelLayoutOfBus (info.isInput, info.busNr); + }(); + + if (set == AudioChannelSet()) + return kAudioUnitErr_FormatNotSupported; + + const auto err = MusicDeviceBase::ChangeStreamFormat (inScope, inElement, inPrevFormat, inNewFormat); + + if (err == noErr) + currentTag = CoreAudioLayouts::toCoreAudio (set); + + return err; + } + + //============================================================================== + ComponentResult Render (AudioUnitRenderActionFlags& ioActionFlags, + const AudioTimeStamp& inTimeStamp, + const UInt32 nFrames) override + { + lastTimeStamp = inTimeStamp; + + // prepare buffers + { + pullInputAudio (ioActionFlags, inTimeStamp, nFrames); + prepareOutputBuffers (nFrames); + audioBuffer.reset(); + } + + ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; + + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + + // set buffer pointers to minimize copying + { + int chIdx = 0, numChannels = 0; + bool interleaved = false; + AudioBufferList* buffer = nullptr; + + // use output pointers + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + { + GetAudioBufferList (false, busIdx, buffer, interleaved, numChannels); + const int* outLayoutMap = mapper.get (false, busIdx); + + for (int ch = 0; ch < numChannels; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); + } + + // use input pointers on remaining channels + for (int busIdx = 0; chIdx < totalInChannels;) + { + int channelIndexInBus = juceFilter->getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); + const bool badData = ! pulledSucceeded[busIdx]; + + if (! badData) + GetAudioBufferList (true, busIdx, buffer, interleaved, numChannels); + + const int* inLayoutMap = mapper.get (true, busIdx); + + const int n = juceFilter->getChannelCountOfBus (true, busIdx); + for (int ch = channelIndexInBus; ch < n; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved || badData ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[ch]].mData)); + } + } + + // copy input + { + for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) + { + if (pulledSucceeded[busIdx]) + audioBuffer.set (busIdx, Input ((UInt32) busIdx).GetBufferList(), mapper.get (true, busIdx)); + else + audioBuffer.clearInputBus (busIdx, (int) nFrames); + } + + audioBuffer.clearUnusedChannels ((int) nFrames); + } + + // swap midi buffers + { + const ScopedLock sl (incomingMidiLock); + midiEvents.clear(); + incomingEvents.swapWith (midiEvents); + } + + // process audio + processBlock (audioBuffer.getBuffer (nFrames), midiEvents); + + // copy back + { + for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) + audioBuffer.get (busIdx, Output ((UInt32) busIdx).GetBufferList(), mapper.get (false, busIdx)); + } + + // process midi output + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + pushMidiOutput (nFrames); + #endif + + midiEvents.clear(); + + return noErr; + } + + //============================================================================== + ComponentResult StartNote (MusicDeviceInstrumentID, MusicDeviceGroupID, NoteInstanceID*, UInt32, const MusicDeviceNoteParams&) override { return noErr; } + ComponentResult StopNote (MusicDeviceGroupID, NoteInstanceID, UInt32) override { return noErr; } + + //============================================================================== + OSStatus HandleMIDIEvent ([[maybe_unused]] UInt8 inStatus, + [[maybe_unused]] UInt8 inChannel, + [[maybe_unused]] UInt8 inData1, + [[maybe_unused]] UInt8 inData2, + [[maybe_unused]] UInt32 inStartFrame) override + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + const juce::uint8 data[] = { (juce::uint8) (inStatus | inChannel), + (juce::uint8) inData1, + (juce::uint8) inData2 }; + + const ScopedLock sl (incomingMidiLock); + incomingEvents.addEvent (data, 3, (int) inStartFrame); + return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif + } + + OSStatus HandleSysEx ([[maybe_unused]] const UInt8* inData, [[maybe_unused]] UInt32 inLength) override + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + const ScopedLock sl (incomingMidiLock); + incomingEvents.addEvent (inData, (int) inLength, 0); + return noErr; + #else + return kAudioUnitErr_PropertyNotInUse; + #endif + } + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + OSStatus MIDIEventList (UInt32 inOffsetSampleFrame, const struct MIDIEventList* list) override + { + const ScopedLock sl (incomingMidiLock); + + auto* packet = &list->packet[0]; + + for (uint32_t i = 0; i < list->numPackets; ++i) + { + toBytestreamDispatcher.dispatch (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + static_cast (packet->timeStamp + inOffsetSampleFrame), + [this] (const ump::BytestreamMidiView& message) + { + incomingEvents.addEvent (message.getMessage(), (int) message.timestamp); + }); + + packet = MIDIEventPacketNext (packet); + } + + return noErr; + } + #endif + + //============================================================================== + ComponentResult GetPresets (CFArrayRef* outData) const override + { + if (outData != nullptr) + { + const int numPrograms = juceFilter->getNumPrograms(); + + clearPresetsArray(); + presetsArray.insertMultiple (0, AUPreset(), numPrograms); + + CFMutableArrayRef presetsArrayRef = CFArrayCreateMutable (nullptr, numPrograms, nullptr); + + for (int i = 0; i < numPrograms; ++i) + { + String name (juceFilter->getProgramName(i)); + if (name.isEmpty()) + name = "Untitled"; + + AUPreset& p = presetsArray.getReference(i); + p.presetNumber = i; + p.presetName = name.toCFString(); + + CFArrayAppendValue (presetsArrayRef, &p); + } + + *outData = (CFArrayRef) presetsArrayRef; + } + + return noErr; + } + + OSStatus NewFactoryPresetSet (const AUPreset& inNewFactoryPreset) override + { + const int numPrograms = juceFilter->getNumPrograms(); + const SInt32 chosenPresetNumber = (int) inNewFactoryPreset.presetNumber; + + if (chosenPresetNumber >= numPrograms) + return kAudioUnitErr_InvalidProperty; + + AUPreset chosenPreset; + chosenPreset.presetNumber = chosenPresetNumber; + chosenPreset.presetName = juceFilter->getProgramName (chosenPresetNumber).toCFString(); + + juceFilter->setCurrentProgram (chosenPresetNumber); + SetAFactoryPresetAsCurrent (chosenPreset); + + return noErr; + } + + //============================================================================== + class EditorCompHolder : public Component + { + public: + EditorCompHolder (AudioProcessorEditor* const editor) + { + addAndMakeVisible (editor); + + #if ! JucePlugin_EditorRequiresKeyboardFocus + setWantsKeyboardFocus (false); + #else + setWantsKeyboardFocus (true); + #endif + + setBounds (getSizeToContainChild()); + + lastBounds = getBounds(); + } + + ~EditorCompHolder() override + { + deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may + // have been transferred to another parent which takes over ownership. + } + + Rectangle getSizeToContainChild() + { + if (auto* editor = getChildComponent (0)) + return getLocalArea (editor, editor->getLocalBounds()); + + return {}; + } + + static NSView* createViewFor (AudioProcessor* filter, JuceAU* au, AudioProcessorEditor* const editor) + { + auto* editorCompHolder = new EditorCompHolder (editor); + auto r = convertToHostBounds (makeNSRect (editorCompHolder->getSizeToContainChild())); + + static JuceUIViewClass cls; + auto* view = [[cls.createInstance() initWithFrame: r] autorelease]; + + JuceUIViewClass::setFilter (view, filter); + JuceUIViewClass::setAU (view, au); + JuceUIViewClass::setEditor (view, editorCompHolder); + + [view setHidden: NO]; + [view setPostsFrameChangedNotifications: YES]; + + [[NSNotificationCenter defaultCenter] addObserver: view + selector: @selector (applicationWillTerminate:) + name: NSApplicationWillTerminateNotification + object: nil]; + activeUIs.add (view); + + editorCompHolder->addToDesktop (detail::PluginUtilities::getDesktopFlags (editor), view); + editorCompHolder->setVisible (true); + + return view; + } + + void parentSizeChanged() override + { + resizeHostWindow(); + + if (auto* editor = getChildComponent (0)) + editor->repaint(); + } + + void childBoundsChanged (Component*) override + { + auto b = getSizeToContainChild(); + + if (lastBounds != b) + { + lastBounds = b; + setSize (jmax (32, b.getWidth()), jmax (32, b.getHeight())); + + resizeHostWindow(); + } + } + + bool keyPressed (const KeyPress&) override + { + if (detail::PluginUtilities::getHostType().isAbletonLive()) + { + static NSTimeInterval lastEventTime = 0; // check we're not recursively sending the same event + NSTimeInterval eventTime = [[NSApp currentEvent] timestamp]; + + if (! approximatelyEqual (lastEventTime, eventTime)) + { + lastEventTime = eventTime; + + NSView* view = (NSView*) getWindowHandle(); + NSView* hostView = [view superview]; + NSWindow* hostWindow = [hostView window]; + + [hostWindow makeFirstResponder: hostView]; + [hostView keyDown: (NSEvent*) [NSApp currentEvent]]; + [hostWindow makeFirstResponder: view]; + } + } + + return false; + } + + void resizeHostWindow() + { + [CATransaction begin]; + [CATransaction setValue:(id) kCFBooleanTrue forKey:kCATransactionDisableActions]; + + auto rect = convertToHostBounds (makeNSRect (lastBounds)); + auto* view = (NSView*) getWindowHandle(); + + auto superRect = [[view superview] frame]; + superRect.size.width = rect.size.width; + superRect.size.height = rect.size.height; + + [[view superview] setFrame: superRect]; + [view setFrame: rect]; + [CATransaction commit]; + + [view setNeedsDisplay: YES]; + } + + private: + Rectangle lastBounds; + + JUCE_DECLARE_NON_COPYABLE (EditorCompHolder) + }; + + void deleteActiveEditors() + { + for (int i = activeUIs.size(); --i >= 0;) + { + id ui = (id) activeUIs.getUnchecked(i); + + if (JuceUIViewClass::getAU (ui) == this) + JuceUIViewClass::deleteEditor (ui); + } + } + + //============================================================================== + struct JuceUIViewClass : public ObjCClass + { + JuceUIViewClass() : ObjCClass ("JUCEAUView_") + { + addIvar ("filter"); + addIvar ("au"); + addIvar ("editor"); + + addMethod (@selector (dealloc), dealloc); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); + addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow); + addMethod (@selector (mouseDownCanMoveWindow), mouseDownCanMoveWindow); + + registerClass(); + } + + static void deleteEditor (id self) + { + std::unique_ptr editorComp (getEditor (self)); + + if (editorComp != nullptr) + { + if (editorComp->getChildComponent(0) != nullptr + && activePlugins.contains (getAU (self))) // plugin may have been deleted before the UI + { + AudioProcessor* const filter = getIvar (self, "filter"); + filter->editorBeingDeleted ((AudioProcessorEditor*) editorComp->getChildComponent(0)); + } + + editorComp = nullptr; + setEditor (self, nullptr); + } + } + + static JuceAU* getAU (id self) { return getIvar (self, "au"); } + static EditorCompHolder* getEditor (id self) { return getIvar (self, "editor"); } + + static void setFilter (id self, AudioProcessor* filter) { object_setInstanceVariable (self, "filter", filter); } + static void setAU (id self, JuceAU* au) { object_setInstanceVariable (self, "au", au); } + static void setEditor (id self, EditorCompHolder* e) { object_setInstanceVariable (self, "editor", e); } + + private: + static void dealloc (id self, SEL) + { + if (activeUIs.contains (self)) + shutdown (self); + + sendSuperclassMessage (self, @selector (dealloc)); + } + + static void applicationWillTerminate (id self, SEL, NSNotification*) + { + shutdown (self); + } + + static void shutdown (id self) + { + [[NSNotificationCenter defaultCenter] removeObserver: self]; + deleteEditor (self); + + jassert (activeUIs.contains (self)); + activeUIs.removeFirstMatchingValue (self); + + if (activePlugins.size() + activeUIs.size() == 0) + { + // there's some kind of component currently modal, but the host + // is trying to delete our plugin.. + jassert (Component::getCurrentlyModalComponent() == nullptr); + + shutdownJuce_GUI(); + } + } + + static void viewDidMoveToWindow (id self, SEL) + { + if (NSWindow* w = [(NSView*) self window]) + { + [w setAcceptsMouseMovedEvents: YES]; + + if (EditorCompHolder* const editorComp = getEditor (self)) + [w makeFirstResponder: (NSView*) editorComp->getWindowHandle()]; + } + } + + static BOOL mouseDownCanMoveWindow (id, SEL) + { + return NO; + } + }; + + //============================================================================== + struct JuceUICreationClass : public ObjCClass + { + JuceUICreationClass() : ObjCClass ("JUCE_AUCocoaViewClass_") + { + addMethod (@selector (interfaceVersion), interfaceVersion); + addMethod (@selector (description), description); + addMethod (@selector (uiViewForAudioUnit:withSize:), uiViewForAudioUnit); + + addProtocol (@protocol (AUCocoaUIBase)); + + registerClass(); + } + + private: + static unsigned int interfaceVersion (id, SEL) { return 0; } + + static NSString* description (id, SEL) + { + return [NSString stringWithString: nsStringLiteral (JucePlugin_Name)]; + } + + static NSView* uiViewForAudioUnit (id, SEL, AudioUnit inAudioUnit, NSSize) + { + void* pointers[2]; + UInt32 propertySize = sizeof (pointers); + + if (AudioUnitGetProperty (inAudioUnit, juceFilterObjectPropertyID, + kAudioUnitScope_Global, 0, pointers, &propertySize) == noErr) + { + if (AudioProcessor* filter = static_cast (pointers[0])) + if (AudioProcessorEditor* editorComp = filter->createEditorIfNeeded()) + { + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (editorComp) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (editorComp->isResizable()); + #endif + return EditorCompHolder::createViewFor (filter, static_cast (pointers[1]), editorComp); + } + } + + return nil; + } + }; + +private: + //============================================================================== + /* The call to AUBase::PropertyChanged may allocate hence the need for this class */ + class AudioProcessorChangedUpdater final : private AsyncUpdater + { + public: + explicit AudioProcessorChangedUpdater (JuceAU& o) : owner (o) {} + ~AudioProcessorChangedUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + int flags = 0; + + if (details.latencyChanged) + flags |= latencyChangedFlag; + + if (details.parameterInfoChanged) + flags |= parameterInfoChangedFlag; + + if (details.programChanged) + flags |= programChangedFlag; + + if (flags != 0) + { + callbackFlags.fetch_or (flags); + + if (MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + } + } + + private: + void handleAsyncUpdate() override + { + const auto flags = callbackFlags.exchange (0); + + if ((flags & latencyChangedFlag) != 0) + owner.PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); + + if ((flags & parameterInfoChangedFlag) != 0) + { + owner.PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); + owner.PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); + } + + owner.PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); + + if ((flags & programChangedFlag) != 0) + { + owner.refreshCurrentPreset(); + owner.PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + } + } + + JuceAU& owner; + + static constexpr int latencyChangedFlag = 1 << 0, + parameterInfoChangedFlag = 1 << 1, + programChangedFlag = 1 << 2; + + std::atomic callbackFlags { 0 }; + }; + + //============================================================================== + AudioUnitHelpers::CoreAudioBufferList audioBuffer; + MidiBuffer midiEvents, incomingEvents; + bool prepared = false, isBypassed = false, restoringState = false; + + //============================================================================== + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + static constexpr bool forceUseLegacyParamIDs = true; + #else + static constexpr bool forceUseLegacyParamIDs = false; + #endif + + //============================================================================== + LegacyAudioParametersWrapper juceParameters; + std::unordered_map paramMap; + Array auParamIDs; + Array parameterGroups; + + // Stores the parameter IDs in the order that they will be reported to the host. + std::vector cachedParameterList; + + //============================================================================== + // According to the docs, this is the maximum size of a MIDIPacketList. + static constexpr UInt32 packetListBytes = 65536; + + CoreAudioTimeConversions timeConversions; + AudioUnitEvent auEvent; + mutable Array presetsArray; + CriticalSection incomingMidiLock; + AUMIDIOutputCallbackStruct midiCallback; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + class ScopedMIDIEventListBlock + { + public: + ScopedMIDIEventListBlock() = default; + + ScopedMIDIEventListBlock (ScopedMIDIEventListBlock&& other) noexcept + : midiEventListBlock (std::exchange (other.midiEventListBlock, nil)) {} + + ScopedMIDIEventListBlock& operator= (ScopedMIDIEventListBlock&& other) noexcept + { + ScopedMIDIEventListBlock { std::move (other) }.swap (*this); + return *this; + } + + ~ScopedMIDIEventListBlock() + { + if (midiEventListBlock != nil) + [midiEventListBlock release]; + } + + static ScopedMIDIEventListBlock copy (AUMIDIEventListBlock b) + { + return ScopedMIDIEventListBlock { b }; + } + + explicit operator bool() const { return midiEventListBlock != nil; } + + void operator() (AUEventSampleTime eventSampleTime, uint8_t cable, const struct MIDIEventList * eventList) const + { + jassert (midiEventListBlock != nil); + midiEventListBlock (eventSampleTime, cable, eventList); + } + + private: + void swap (ScopedMIDIEventListBlock& other) noexcept + { + std::swap (other.midiEventListBlock, midiEventListBlock); + } + + explicit ScopedMIDIEventListBlock (AUMIDIEventListBlock b) : midiEventListBlock ([b copy]) {} + + AUMIDIEventListBlock midiEventListBlock = nil; + }; + + ScopedMIDIEventListBlock midiEventListBlock; + std::optional hostProtocol; + ump::ToUMP1Converter toUmp1Converter; + ump::ToBytestreamDispatcher toBytestreamDispatcher { 2048 }; + #endif + + AudioTimeStamp lastTimeStamp; + int totalInChannels, totalOutChannels; + HeapBlock pulledSucceeded; + HeapBlock packetList { packetListBytes, 1 }; + + ThreadLocalValue inParameterChangedCallback; + + AudioProcessorChangedUpdater audioProcessorChangedUpdater { *this }; + + //============================================================================== + Array channelInfo; + Array> supportedInputLayouts, supportedOutputLayouts; + Array currentInputLayout, currentOutputLayout; + + //============================================================================== + AudioUnitHelpers::ChannelRemapper mapper; + + //============================================================================== + OwnedArray> parameterValueStringArrays; + + //============================================================================== + AudioProcessorParameter* bypassParam = nullptr; + + //============================================================================== + static NSRect convertToHostBounds (NSRect pluginRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginRect; + + return NSMakeRect (static_cast (pluginRect.origin.x * desktopScale), + static_cast (pluginRect.origin.y * desktopScale), + static_cast (pluginRect.size.width * desktopScale), + static_cast (pluginRect.size.height * desktopScale)); + } + + static NSRect convertFromHostBounds (NSRect hostRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return hostRect; + + return NSMakeRect (static_cast (hostRect.origin.x / desktopScale), + static_cast (hostRect.origin.y / desktopScale), + static_cast (hostRect.size.width / desktopScale), + static_cast (hostRect.size.height / desktopScale)); + } + + //============================================================================== + void pullInputAudio (AudioUnitRenderActionFlags& flags, const AudioTimeStamp& timestamp, const UInt32 nFrames) noexcept + { + const unsigned int numInputBuses = GetScope (kAudioUnitScope_Input).GetNumberOfElements(); + + for (unsigned int i = 0; i < numInputBuses; ++i) + { + auto& input = Input (i); + + const bool succeeded = (input.PullInput (flags, timestamp, i, nFrames) == noErr); + + if ((flags & kAudioUnitRenderAction_OutputIsSilence) != 0 && succeeded) + AudioUnitHelpers::clearAudioBuffer (input.GetBufferList()); + + pulledSucceeded[i] = succeeded; + } + } + + void prepareOutputBuffers (const UInt32 nFrames) noexcept + { + const auto numProcessorBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + const auto numWrapperBuses = GetScope (kAudioUnitScope_Output).GetNumberOfElements(); + + for (UInt32 busIdx = 0; busIdx < numWrapperBuses; ++busIdx) + { + auto& output = Output (busIdx); + + if (output.WillAllocateBuffer()) + output.PrepareBuffer (nFrames); + + if (busIdx >= (UInt32) numProcessorBuses) + AudioUnitHelpers::clearAudioBuffer (output.GetBufferList()); + } + } + + void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept + { + const ScopedLock sl (juceFilter->getCallbackLock()); + const ScopedPlayHead playhead { *this }; + + if (juceFilter->isSuspended()) + { + buffer.clear(); + } + else if (bypassParam == nullptr && isBypassed) + { + juceFilter->processBlockBypassed (buffer, midiBuffer); + } + else + { + juceFilter->processBlock (buffer, midiBuffer); + } + } + + void pushMidiOutput ([[maybe_unused]] UInt32 nFrames) noexcept + { + if (midiEvents.isEmpty()) + return; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + if (@available (macOS 12.0, iOS 15.0, *)) + { + if (midiEventListBlock) + { + struct MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIEventListInit (&stackList, kMIDIProtocol_1_0); + }; + + const auto send = [&] + { + midiEventListBlock (static_cast (lastTimeStamp.mSampleTime), 0, &stackList); + }; + + const auto add = [&] (const ump::View& view, int timeStamp) + { + static_assert (sizeof (uint32_t) == sizeof (UInt32) + && alignof (uint32_t) == alignof (UInt32), + "If this fails, the cast below will be broken too!"); + using List = struct MIDIEventList; + end = MIDIEventListAdd (&stackList, + sizeof (List::packet), + end, + (MIDITimeStamp) timeStamp, + view.size(), + reinterpret_cast (view.data())); + }; + + init(); + + for (const auto metadata : midiEvents) + { + toUmp1Converter.convert (ump::BytestreamMidiView (metadata), [&] (const ump::View& view) + { + add (view, metadata.samplePosition); + + if (end != nullptr) + return; + + send(); + init(); + add (view, metadata.samplePosition); + }); + + } + + send(); + + return; + } + } + #endif + + if (midiCallback.midiOutputCallback) + { + MIDIPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIPacketListInit (packetList); + }; + + const auto send = [&] + { + midiCallback.midiOutputCallback (midiCallback.userData, &lastTimeStamp, 0, packetList); + }; + + const auto add = [&] (const MidiMessageMetadata& metadata) + { + end = MIDIPacketListAdd (packetList, + packetListBytes, + end, + static_cast (metadata.samplePosition), + static_cast (metadata.numBytes), + metadata.data); + }; + + init(); + + for (const auto metadata : midiEvents) + { + jassert (isPositiveAndBelow (metadata.samplePosition, nFrames)); + + add (metadata); + + if (end == nullptr) + { + send(); + init(); + add (metadata); + + if (end == nullptr) + { + // If this is hit, the size of this midi packet exceeds the maximum size of + // a MIDIPacketList. Large SysEx messages should be broken up into smaller + // chunks. + jassertfalse; + init(); + } + } + } + + send(); + } + } + + void GetAudioBufferList (bool isInput, int busIdx, AudioBufferList*& bufferList, bool& interleaved, int& numChannels) + { + auto* element = Element (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, static_cast (busIdx)).AsIOElement(); + jassert (element != nullptr); + + bufferList = &element->GetBufferList(); + + jassert (bufferList->mNumberBuffers > 0); + + interleaved = AudioUnitHelpers::isAudioBufferInterleaved (*bufferList); + numChannels = static_cast (interleaved ? bufferList->mBuffers[0].mNumberChannels : bufferList->mNumberBuffers); + } + + //============================================================================== + static OSStatus scopeToDirection (AudioUnitScope scope, bool& isInput) noexcept + { + isInput = (scope == kAudioUnitScope_Input); + + return (scope != kAudioUnitScope_Input + && scope != kAudioUnitScope_Output) + ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; + } + + enum class BusKind + { + processor, + wrapperOnly, + }; + + struct ElementInfo + { + int busNr; + BusKind kind; + bool isInput; + OSStatus error; + }; + + ElementInfo getElementInfo (AudioUnitScope scope, AudioUnitElement element) noexcept + { + bool isInput = false; + OSStatus err; + + if ((err = scopeToDirection (scope, isInput)) != noErr) + return { {}, {}, {}, err }; + + const auto busIdx = static_cast (element); + + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCount (*juceFilter, isInput))) + return { busIdx, BusKind::processor, isInput, noErr }; + + if (isPositiveAndBelow (busIdx, AudioUnitHelpers::getBusCountForWrapper (*juceFilter, isInput))) + return { busIdx, BusKind::wrapperOnly, isInput, noErr }; + + return { {}, {}, {}, kAudioUnitErr_InvalidElement }; + } + + OSStatus GetParameterList (AudioUnitScope inScope, AudioUnitParameterID* outParameterList, UInt32& outNumParameters) override + { + if (forceUseLegacyParamIDs || inScope != kAudioUnitScope_Global) + return MusicDeviceBase::GetParameterList (inScope, outParameterList, outNumParameters); + + outNumParameters = (UInt32) juceParameters.size(); + + if (outParameterList == nullptr) + return noErr; + + if (cachedParameterList.empty()) + { + struct ParamInfo + { + AudioUnitParameterID identifier; + int versionHint; + }; + + std::vector vec; + vec.reserve (juceParameters.size()); + + for (const auto* param : juceParameters) + vec.push_back ({ generateAUParameterID (*param), param->getVersionHint() }); + + std::sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.identifier < b.identifier; }); + std::stable_sort (vec.begin(), vec.end(), [] (auto a, auto b) { return a.versionHint < b.versionHint; }); + std::transform (vec.begin(), vec.end(), std::back_inserter (cachedParameterList), [] (auto x) { return x.identifier; }); + } + + std::copy (cachedParameterList.begin(), cachedParameterList.end(), outParameterList); + + return noErr; + } + + //============================================================================== + void addParameters() + { + parameterGroups = juceFilter->getParameterTree().getSubgroups (true); + + juceParameters.update (*juceFilter, forceUseLegacyParamIDs); + const int numParams = juceParameters.getNumParameters(); + + if (forceUseLegacyParamIDs) + { + Globals()->UseIndexedParameters (static_cast (numParams)); + } + else + { + for (auto* param : juceParameters) + { + const AudioUnitParameterID auParamID = generateAUParameterID (*param); + + // Consider yourself very unlucky if you hit this assertion. The hash codes of your + // parameter ids are not unique. + jassert (paramMap.find (static_cast (auParamID)) == paramMap.end()); + + auParamIDs.add (auParamID); + paramMap.emplace (static_cast (auParamID), param); + Globals()->SetParameter (auParamID, param->getValue()); + } + } + + #if JUCE_DEBUG + // Some hosts can't handle the huge numbers of discrete parameter values created when + // using the default number of steps. + for (auto* param : juceParameters) + if (param->isDiscrete()) + jassert (param->getNumSteps() != AudioProcessor::getDefaultNumParameterSteps()); + #endif + + parameterValueStringArrays.ensureStorageAllocated (numParams); + + for (auto* param : juceParameters) + { + OwnedArray* stringValues = nullptr; + + auto initialValue = param->getValue(); + bool paramIsLegacy = dynamic_cast (param) != nullptr; + + if (param->isDiscrete() && (! forceUseLegacyParamIDs)) + { + const auto numSteps = param->getNumSteps(); + stringValues = new OwnedArray(); + stringValues->ensureStorageAllocated (numSteps); + + const auto maxValue = getMaximumParameterValue (param); + + auto getTextValue = [param, paramIsLegacy] (float value) + { + if (paramIsLegacy) + { + param->setValue (value); + return param->getCurrentValueAsText(); + } + + return param->getText (value, 256); + }; + + for (int i = 0; i < numSteps; ++i) + { + auto value = (float) i / maxValue; + stringValues->add (CFStringCreateCopy (nullptr, (getTextValue (value).toCFString()))); + } + } + + if (paramIsLegacy) + param->setValue (initialValue); + + parameterValueStringArrays.add (stringValues); + } + + if ((bypassParam = juceFilter->getBypassParameter()) != nullptr) + bypassParam->addListener (this); + } + + //============================================================================== + static AudioUnitParameterID generateAUParameterID (const AudioProcessorParameter& param) + { + const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceUseLegacyParamIDs); + AudioUnitParameterID paramHash = static_cast (juceParamID.hashCode()); + + #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS + // studio one doesn't like negative parameters + paramHash &= ~(((AudioUnitParameterID) 1) << (sizeof (AudioUnitParameterID) * 8 - 1)); + #endif + + return forceUseLegacyParamIDs ? static_cast (juceParamID.getIntValue()) + : paramHash; + } + + inline AudioUnitParameterID getAUParameterIDForIndex (int paramIndex) const noexcept + { + return forceUseLegacyParamIDs ? static_cast (paramIndex) + : auParamIDs.getReference (paramIndex); + } + + AudioProcessorParameter* getParameterForAUParameterID (AudioUnitParameterID address) const noexcept + { + const auto index = static_cast (address); + + if (forceUseLegacyParamIDs) + return juceParameters.getParamForIndex (index); + + const auto iter = paramMap.find (index); + return iter != paramMap.end() ? iter->second : nullptr; + } + + //============================================================================== + OSStatus syncAudioUnitWithProcessor() + { + OSStatus err = noErr; + const auto numWrapperInputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true); + const auto numWrapperOutputs = AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false); + + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Input, static_cast (numWrapperInputs))) != noErr) + return err; + + if ((err = MusicDeviceBase::SetBusCount (kAudioUnitScope_Output, static_cast (numWrapperOutputs))) != noErr) + return err; + + addSupportedLayoutTags(); + + const auto numProcessorInputs = AudioUnitHelpers::getBusCount (*juceFilter, true); + const auto numProcessorOutputs = AudioUnitHelpers::getBusCount (*juceFilter, false); + + for (int i = 0; i < numProcessorInputs; ++i) + if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) + return err; + + for (int i = 0; i < numProcessorOutputs; ++i) + if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) + return err; + + return noErr; + } + + OSStatus syncProcessorWithAudioUnit() + { + const int numInputBuses = AudioUnitHelpers::getBusCount (*juceFilter, true); + const int numOutputBuses = AudioUnitHelpers::getBusCount (*juceFilter, false); + + const int numInputElements = static_cast (GetScope (kAudioUnitScope_Input). GetNumberOfElements()); + const int numOutputElements = static_cast (GetScope (kAudioUnitScope_Output).GetNumberOfElements()); + + AudioProcessor::BusesLayout requestedLayouts; + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = (isInput ? numInputBuses : numOutputBuses); + const int numAUElements = (isInput ? numInputElements : numOutputElements); + Array& requestedBuses = (isInput ? requestedLayouts.inputBuses : requestedLayouts.outputBuses); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + const auto* element = (busIdx < numAUElements ? &IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busIdx) : nullptr); + const int numChannels = (element != nullptr ? static_cast (element->NumberChannels()) : 0); + + AudioChannelLayoutTag currentLayoutTag = isInput ? currentInputLayout[busIdx] : currentOutputLayout[busIdx]; + const int tagNumChannels = currentLayoutTag & 0xffff; + + if (numChannels != tagNumChannels) + return kAudioUnitErr_FormatNotSupported; + + requestedBuses.add (CoreAudioLayouts::fromCoreAudio (currentLayoutTag)); + } + } + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioProcessor::containsLayout (requestedLayouts, configs)) + return kAudioUnitErr_FormatNotSupported; + #endif + + if (! AudioUnitHelpers::setBusesLayout (juceFilter.get(), requestedLayouts)) + return kAudioUnitErr_FormatNotSupported; + + // update total channel count + totalInChannels = juceFilter->getTotalNumInputChannels(); + totalOutChannels = juceFilter->getTotalNumOutputChannels(); + + return noErr; + } + + OSStatus syncAudioUnitWithChannelSet (bool isInput, int busNr, const AudioChannelSet& channelSet) + { + const int numChannels = channelSet.size(); + + getCurrentLayout (isInput, busNr) = CoreAudioLayouts::toCoreAudio (channelSet); + + // is this bus activated? + if (numChannels == 0) + return noErr; + + auto& element = IOElement (isInput ? kAudioUnitScope_Input : kAudioUnitScope_Output, (UInt32) busNr); + + element.SetName ((CFStringRef) juceStringToNS (juceFilter->getBus (isInput, busNr)->getName())); + + const auto streamDescription = ausdk::ASBD::CreateCommonFloat32 (getSampleRate(), (UInt32) numChannels); + return element.SetStreamFormat (streamDescription); + } + + //============================================================================== + void clearPresetsArray() const + { + for (int i = presetsArray.size(); --i >= 0;) + CFRelease (presetsArray.getReference(i).presetName); + + presetsArray.clear(); + } + + void refreshCurrentPreset() + { + // this will make the AU host re-read and update the current preset name + // in case it was changed here in the plug-in: + + const int currentProgramNumber = juceFilter->getCurrentProgram(); + const String currentProgramName = juceFilter->getProgramName (currentProgramNumber); + + AUPreset currentPreset; + currentPreset.presetNumber = currentProgramNumber; + currentPreset.presetName = currentProgramName.toCFString(); + + SetAFactoryPresetAsCurrent (currentPreset); + } + + //============================================================================== + std::vector& getSupportedBusLayouts (bool isInput, int bus) noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } + const std::vector& getSupportedBusLayouts (bool isInput, int bus) const noexcept { return (isInput ? supportedInputLayouts : supportedOutputLayouts).getReference (bus); } + AudioChannelLayoutTag& getCurrentLayout (bool isInput, int bus) noexcept { return (isInput ? currentInputLayout : currentOutputLayout).getReference (bus); } + AudioChannelLayoutTag getCurrentLayout (bool isInput, int bus) const noexcept { return (isInput ? currentInputLayout : currentOutputLayout)[bus]; } + + //============================================================================== + std::vector getSupportedLayoutTagsForBus (bool isInput, int busNum) const + { + std::set tags; + + if (AudioProcessor::Bus* bus = juceFilter->getBus (isInput, busNum)) + { + #ifndef JucePlugin_PreferredChannelConfigurations + auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); + + for (auto tag : knownTags) + if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (tag))) + tags.insert (tag); + #endif + + // add discrete layout tags + int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); + + for (int ch = 0; ch < n; ++ch) + { + #ifdef JucePlugin_PreferredChannelConfigurations + const short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (AudioUnitHelpers::isLayoutSupported (*juceFilter, isInput, busNum, ch, configs)) + tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); + #else + if (bus->isLayoutSupported (AudioChannelSet::discreteChannels (ch))) + tags.insert (static_cast ((int) kAudioChannelLayoutTag_DiscreteInOrder | ch)); + #endif + } + } + + return std::vector (tags.begin(), tags.end()); + } + + void addSupportedLayoutTagsForDirection (bool isInput) + { + auto& layouts = isInput ? supportedInputLayouts : supportedOutputLayouts; + layouts.clearQuick(); + auto numBuses = AudioUnitHelpers::getBusCount (*juceFilter, isInput); + + for (int busNr = 0; busNr < numBuses; ++busNr) + layouts.add (getSupportedLayoutTagsForBus (isInput, busNr)); + } + + void addSupportedLayoutTags() + { + currentInputLayout.clear(); currentOutputLayout.clear(); + + currentInputLayout. resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, true)); + currentOutputLayout.resize (AudioUnitHelpers::getBusCountForWrapper (*juceFilter, false)); + + addSupportedLayoutTagsForDirection (true); + addSupportedLayoutTagsForDirection (false); + } + + static int maxChannelsToProbeFor() + { + return (detail::PluginUtilities::getHostType().isLogic() ? 8 : 64); + } + + //============================================================================== + void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) + { + if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName + && juceFilter != nullptr && GetContextName() != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = String::fromCFString (GetContextName()); + + juceFilter->updateTrackProperties (props); + } + } + + static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, + AudioUnitScope scope, AudioUnitElement element) + { + static_cast (inRefCon)->auPropertyListener (propId, scope, element); + } + + JUCE_DECLARE_NON_COPYABLE (JuceAU) +}; + +//============================================================================== +#if JucePlugin_ProducesMidiOutput || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + #define FACTORY_BASE_CLASS ausdk::AUMusicDeviceFactory +#else + #define FACTORY_BASE_CLASS ausdk::AUBaseFactory +#endif + +AUSDK_COMPONENT_ENTRY (FACTORY_BASE_CLASS, JuceAU) + +#define JUCE_AU_ENTRY_POINT_NAME JUCE_CONCAT (JucePlugin_AUExportPrefix, Factory) + + extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc); +AUSDK_EXPORT extern "C" void* JUCE_AU_ENTRY_POINT_NAME (const AudioComponentDescription* inDesc) +{ + return JuceAUFactory (inDesc); +} + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm index d256213..feaf1a5 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm @@ -29,26 +29,29 @@ #include -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wparentheses", - "-Wextra-tokens", +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wambiguous-reversed-operator", + "-Wc99-extensions", + "-Wcast-align", "-Wcomment", "-Wconversion", - "-Wunused-parameter", - "-Wunused", + "-Wdeprecated-anon-enum-enum-conversion", "-Wextra-semi", + "-Wextra-tokens", + "-Wfloat-equal", "-Wformat-pedantic", + "-Wfour-char-constants", "-Wgnu-zero-variadic-macro-arguments", - "-Wshadow-all", - "-Wcast-align", - "-Wswitch-enum", - "-Wimplicit-fallthrough", - "-Wzero-as-null-pointer-constant", - "-Wnullable-to-nonnull-conversion", "-Wignored-qualifiers", - "-Wfour-char-constants", + "-Wimplicit-fallthrough", "-Wmissing-prototypes", - "-Wdeprecated-anon-enum-enum-conversion", - "-Wambiguous-reversed-operator") + "-Wnullable-to-nonnull-conversion", + "-Wparentheses", + "-Wshadow-all", + "-Wswitch-enum", + "-Wunknown-attributes", + "-Wunused", + "-Wunused-parameter", + "-Wzero-as-null-pointer-constant") // From MacOS 10.13 and iOS 11 Apple has (sensibly!) stopped defining a whole // set of functions with rather generic names. However, we still need a couple @@ -84,18 +87,18 @@ #endif -#include "AU/AudioUnitSDK/AUBase.cpp" -#include "AU/AudioUnitSDK/AUBuffer.cpp" -#include "AU/AudioUnitSDK/AUBufferAllocator.cpp" -#include "AU/AudioUnitSDK/AUEffectBase.cpp" -#include "AU/AudioUnitSDK/AUInputElement.cpp" -#include "AU/AudioUnitSDK/AUMIDIBase.cpp" -#include "AU/AudioUnitSDK/AUMIDIEffectBase.cpp" -#include "AU/AudioUnitSDK/AUOutputElement.cpp" -#include "AU/AudioUnitSDK/AUPlugInDispatch.cpp" -#include "AU/AudioUnitSDK/AUScopeElement.cpp" -#include "AU/AudioUnitSDK/ComponentBase.cpp" -#include "AU/AudioUnitSDK/MusicDeviceBase.cpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #undef verify #undef verify_noerr diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm index 847072f..620bff5 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm @@ -23,4 +23,1970 @@ ============================================================================== */ -#include "AU/juce_AUv3_Wrapper.mm" +#include +#include + +#if JucePlugin_Build_AUv3 + +#if JUCE_MAC && ! (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + #error AUv3 needs Deployment Target OS X 10.11 or higher to compile +#endif + +#ifndef __OBJC2__ + #error AUv3 needs Objective-C 2 support (compile with 64-bit) +#endif + +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 + +#include +#include + +#import +#import +#import + +#include +#include +#include +#include +#include + +#define JUCE_VIEWCONTROLLER_OBJC_NAME(x) JUCE_JOIN_MACRO (x, FactoryAUv3) + +#if JUCE_IOS + #define JUCE_IOS_MAC_VIEW UIView +#else + #define JUCE_IOS_MAC_VIEW NSView +#endif + +#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) + +#include + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") + +using namespace juce; + +struct AudioProcessorHolder : public ReferenceCountedObject +{ + AudioProcessorHolder() = default; + explicit AudioProcessorHolder (std::unique_ptr p) : processor (std::move (p)) {} + AudioProcessor& operator*() noexcept { return *processor; } + AudioProcessor* operator->() noexcept { return processor.get(); } + AudioProcessor* get() noexcept { return processor.get(); } + + struct ViewConfig + { + double width; + double height; + bool hostHasMIDIController; + }; + + std::unique_ptr viewConfiguration; + + using Ptr = ReferenceCountedObjectPtr; + +private: + std::unique_ptr processor; + + AudioProcessorHolder& operator= (AudioProcessor*) = delete; + AudioProcessorHolder (AudioProcessorHolder&) = delete; + AudioProcessorHolder& operator= (AudioProcessorHolder&) = delete; +}; + +//============================================================================== +//=========================== The actual AudioUnit ============================= +//============================================================================== +class JuceAudioUnitv3 : public AudioProcessorListener, + public AudioPlayHead, + private AudioProcessorParameter::Listener +{ +public: + JuceAudioUnitv3 (const AudioProcessorHolder::Ptr& processor, + const AudioComponentDescription& descr, + AudioComponentInstantiationOptions options, + NSError** error) + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wobjc-method-access") + : au ([getClass().createInstance() initWithComponentDescription: descr + options: options + error: error + juceClass: this]), + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + processorHolder (processor) + { + init(); + } + + JuceAudioUnitv3 (AUAudioUnit* audioUnit, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**) + : au (audioUnit), + processorHolder (new AudioProcessorHolder (createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3))) + { + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + initialiseJuce_GUI(); + + init(); + } + + ~JuceAudioUnitv3() override + { + auto& processor = getAudioProcessor(); + processor.removeListener (this); + + if (bypassParam != nullptr) + bypassParam->removeListener (this); + + removeEditor (processor); + } + + //============================================================================== + void init() + { + inParameterChangedCallback = false; + + AudioProcessor& processor = getAudioProcessor(); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + processor.setPlayConfigDetails (configs[0][0], configs[0][1], kDefaultSampleRate, static_cast (maxFrames)); + + Array channelInfos; + + for (int i = 0; i < numConfigs; ++i) + { + AUChannelInfo channelInfo; + + channelInfo.inChannels = configs[i][0]; + channelInfo.outChannels = configs[i][1]; + + channelInfos.add (channelInfo); + } + #else + Array channelInfos = AudioUnitHelpers::getAUChannelInfo (processor); + #endif + + processor.setPlayHead (this); + + totalInChannels = processor.getTotalNumInputChannels(); + totalOutChannels = processor.getTotalNumOutputChannels(); + + { + channelCapabilities.reset ([[NSMutableArray alloc] init]); + + for (int i = 0; i < channelInfos.size(); ++i) + { + AUChannelInfo& info = channelInfos.getReference (i); + + [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.inChannels]]; + [channelCapabilities.get() addObject: [NSNumber numberWithInteger: info.outChannels]]; + } + } + + internalRenderBlock = CreateObjCBlock (this, &JuceAudioUnitv3::renderCallback); + + processor.setRateAndBufferSizeDetails (kDefaultSampleRate, static_cast (maxFrames)); + processor.prepareToPlay (kDefaultSampleRate, static_cast (maxFrames)); + processor.addListener (this); + + addParameters(); + addPresets(); + + addAudioUnitBusses (true); + addAudioUnitBusses (false); + } + + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } + + //============================================================================== + void reset() + { + midiMessages.clear(); + lastTimeStamp.mSampleTime = std::numeric_limits::max(); + lastTimeStamp.mFlags = 0; + } + + //============================================================================== + AUAudioUnitPreset* getCurrentPreset() const + { + return factoryPresets.getAtIndex (getAudioProcessor().getCurrentProgram()); + } + + void setCurrentPreset (AUAudioUnitPreset* preset) + { + getAudioProcessor().setCurrentProgram (static_cast ([preset number])); + } + + NSArray* getFactoryPresets() const + { + return factoryPresets.get(); + } + + NSDictionary* getFullState() const + { + NSMutableDictionary* retval = [[NSMutableDictionary alloc] init]; + + { + auto* superRetval = ObjCMsgSendSuper*> (au, @selector (fullState)); + + if (superRetval != nullptr) + [retval addEntriesFromDictionary:superRetval]; + } + + juce::MemoryBlock state; + + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + getAudioProcessor().getCurrentProgramStateInformation (state); + #else + getAudioProcessor().getStateInformation (state); + #endif + + if (state.getSize() > 0) + { + [retval setObject: [[NSData alloc] initWithBytes: state.getData() length: state.getSize()] + forKey: @JUCE_STATE_DICTIONARY_KEY]; + } + + return [retval autorelease]; + } + + void setFullState (NSDictionary* state) + { + if (state == nullptr) + return; + + NSObject* obj = [state objectForKey: @JUCE_STATE_DICTIONARY_KEY]; + + if (obj == nullptr || ! [obj isKindOfClass: [NSData class]]) + return; + + auto* data = reinterpret_cast (obj); + const auto numBytes = static_cast ([data length]); + + if (numBytes <= 0) + return; + + auto* rawBytes = reinterpret_cast ([data bytes]); + + ScopedKeyChange scope (au, @"allParameterValues"); + + #if JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES + getAudioProcessor().setCurrentProgramStateInformation (rawBytes, numBytes); + #else + getAudioProcessor().setStateInformation (rawBytes, numBytes); + #endif + } + + AUParameterTree* getParameterTree() const + { + return paramTree.get(); + } + + NSArray* parametersForOverviewWithCount (int count) const + { + auto* retval = [[[NSMutableArray alloc] init] autorelease]; + + for (const auto& address : addressForIndex) + { + if (static_cast (count) <= [retval count]) + break; + + [retval addObject: [NSNumber numberWithUnsignedLongLong: address]]; + } + + return retval; + } + + //============================================================================== + NSTimeInterval getLatency() const + { + auto& p = getAudioProcessor(); + return p.getLatencySamples() / p.getSampleRate(); + } + + NSTimeInterval getTailTime() const + { + return getAudioProcessor().getTailLengthSeconds(); + } + + //============================================================================== + AUAudioUnitBusArray* getInputBusses() const { return inputBusses.get(); } + AUAudioUnitBusArray* getOutputBusses() const { return outputBusses.get(); } + NSArray* getChannelCapabilities() const { return channelCapabilities.get(); } + + bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) + { + const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); + const int busIdx = static_cast ([auBus index]); + const int newNumChannels = static_cast ([format channelCount]); + + AudioProcessor& processor = getAudioProcessor(); + + if ([[maybe_unused]] AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) + return false; + #else + const AVAudioChannelLayout* layout = [format channelLayout]; + const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); + + if (layoutTag != 0) + { + AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); + + if (newLayout.size() != newNumChannels) + return false; + + if (! bus->isLayoutSupported (newLayout)) + return false; + } + else + { + if (! bus->isNumberOfChannelsSupported (newNumChannels)) + return false; + } + #endif + + return true; + } + + return false; + } + + //============================================================================== + int getVirtualMIDICableCount() const + { + #if JucePlugin_WantsMidiInput + return 1; + #else + return 0; + #endif + } + + bool getSupportsMPE() const + { + return getAudioProcessor().supportsMPE(); + } + + NSArray* getMIDIOutputNames() const + { + #if JucePlugin_ProducesMidiOutput + return @[@"MIDI Out"]; + #else + return @[]; + #endif + } + + //============================================================================== + AUInternalRenderBlock getInternalRenderBlock() const { return internalRenderBlock; } + bool getRenderingOffline() const { return getAudioProcessor().isNonRealtime(); } + void setRenderingOffline (bool offline) + { + auto& processor = getAudioProcessor(); + auto isCurrentlyNonRealtime = processor.isNonRealtime(); + + if (isCurrentlyNonRealtime != offline) + { + ScopedLock callbackLock (processor.getCallbackLock()); + + processor.setNonRealtime (offline); + processor.prepareToPlay (processor.getSampleRate(), processor.getBlockSize()); + } + } + + bool getShouldBypassEffect() const + { + if (bypassParam != nullptr) + return (bypassParam->getValue() != 0.0f); + + return (ObjCMsgSendSuper (au, @selector (shouldBypassEffect)) == YES); + } + + void setShouldBypassEffect (bool shouldBypass) + { + if (bypassParam != nullptr) + bypassParam->setValue (shouldBypass ? 1.0f : 0.0f); + + ObjCMsgSendSuper (au, @selector (setShouldBypassEffect:), shouldBypass ? YES : NO); + } + + //============================================================================== + NSString* getContextName() const { return juceStringToNS (contextName); } + void setContextName (NSString* str) + { + if (str != nullptr) + { + AudioProcessor::TrackProperties props; + props.name = nsStringToJuce (str); + + getAudioProcessor().updateTrackProperties (props); + } + } + + //============================================================================== + bool allocateRenderResourcesAndReturnError (NSError **outError) + { + AudioProcessor& processor = getAudioProcessor(); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + if (ObjCMsgSendSuper (au, @selector (allocateRenderResourcesAndReturnError:), outError) == NO) + return false; + + if (outError != nullptr) + *outError = nullptr; + + AudioProcessor::BusesLayout layouts; + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + const int n = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); + Array& channelSets = (isInput ? layouts.inputBuses : layouts.outputBuses); + + AUAudioUnitBusArray* auBuses = (isInput ? [au inputBusses] : [au outputBusses]); + jassert ([auBuses count] == static_cast (n)); + + for (int busIdx = 0; busIdx < n; ++busIdx) + { + if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) + { + AVAudioFormat* format = [[auBuses objectAtIndexedSubscript:static_cast (busIdx)] format]; + + AudioChannelSet newLayout; + const AVAudioChannelLayout* layout = [format channelLayout]; + const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); + + if (layoutTag != 0) + newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); + else + newLayout = bus->supportedLayoutWithChannels (static_cast ([format channelCount])); + + if (newLayout.isDisabled()) + return false; + + channelSets.add (newLayout); + } + } + } + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + + if (! AudioProcessor::containsLayout (layouts, configs)) + { + if (outError != nullptr) + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; + + return false; + } + #endif + + if (! AudioUnitHelpers::setBusesLayout (&getAudioProcessor(), layouts)) + { + if (outError != nullptr) + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:kAudioUnitErr_FormatNotSupported userInfo:nullptr]; + + return false; + } + + totalInChannels = processor.getTotalNumInputChannels(); + totalOutChannels = processor.getTotalNumOutputChannels(); + + allocateBusBuffer (true); + allocateBusBuffer (false); + + mapper.alloc (processor); + + audioBuffer.prepare (AudioUnitHelpers::getBusesLayout (&processor), static_cast (maxFrames)); + + auto sampleRate = [&] + { + for (auto* buffer : { inputBusses.get(), outputBusses.get() }) + if ([buffer count] > 0) + return [[[buffer objectAtIndexedSubscript: 0] format] sampleRate]; + + return 44100.0; + }(); + + processor.setRateAndBufferSizeDetails (sampleRate, static_cast (maxFrames)); + processor.prepareToPlay (sampleRate, static_cast (maxFrames)); + + midiMessages.ensureSize (2048); + midiMessages.clear(); + + hostMusicalContextCallback = [au musicalContextBlock]; + hostTransportStateCallback = [au transportStateBlock]; + + if (@available (macOS 10.13, iOS 11.0, *)) + midiOutputEventBlock = [au MIDIOutputEventBlock]; + + reset(); + + return true; + } + + void deallocateRenderResources() + { + midiOutputEventBlock = nullptr; + + hostMusicalContextCallback = nullptr; + hostTransportStateCallback = nullptr; + + getAudioProcessor().releaseResources(); + audioBuffer.release(); + + inBusBuffers. clear(); + outBusBuffers.clear(); + + mapper.release(); + + ObjCMsgSendSuper (au, @selector (deallocateRenderResources)); + } + + //============================================================================== + struct ScopedKeyChange + { + ScopedKeyChange (AUAudioUnit* a, NSString* k) + : au (a), key (k) + { + [au willChangeValueForKey: key]; + } + + ~ScopedKeyChange() + { + [au didChangeValueForKey: key]; + } + + AUAudioUnit* au; + NSString* key; + }; + + //============================================================================== + void audioProcessorChanged ([[maybe_unused]] AudioProcessor* processor, const ChangeDetails& details) override + { + if (details.programChanged) + { + { + ScopedKeyChange scope (au, @"allParameterValues"); + addPresets(); + } + + { + ScopedKeyChange scope (au, @"currentPreset"); + } + } + + if (details.latencyChanged) + { + ScopedKeyChange scope (au, @"latency"); + } + + if (details.parameterInfoChanged) + { + ScopedKeyChange scope (au, @"parameterTree"); + auto nodes = createParameterNodes (processor->getParameterTree()); + installNewParameterTree (std::move (nodes.nodeArray)); + } + } + + void sendParameterEvent (int idx, const float* newValue, AUParameterAutomationEventType type) + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (auto* juceParam = juceParameters.getParamForIndex (idx)) + { + if (auto* param = [paramTree.get() parameterWithAddress: getAUParameterAddressForIndex (idx)]) + { + const auto value = (newValue != nullptr ? *newValue : juceParam->getValue()) * getMaximumParameterValue (*juceParam); + + if (@available (macOS 10.12, iOS 10.0, *)) + { + [param setValue: value + originator: editorObserverToken.get() + atHostTime: lastTimeStamp.mHostTime + eventType: type]; + } + else if (type == AUParameterAutomationEventTypeValue) + { + [param setValue: value originator: editorObserverToken.get()]; + } + } + } + } + + void audioProcessorParameterChanged (AudioProcessor*, int idx, float newValue) override + { + sendParameterEvent (idx, &newValue, AUParameterAutomationEventTypeValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int idx) override + { + sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeTouch); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int idx) override + { + sendParameterEvent (idx, nullptr, AUParameterAutomationEventTypeRelease); + } + + //============================================================================== + Optional getPosition() const override + { + PositionInfo info; + info.setTimeInSamples ((int64) (lastTimeStamp.mSampleTime + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); + + info.setFrameRate ([this] + { + switch (lastTimeStamp.mSMPTETime.mType) + { + case kSMPTETimeType2398: return FrameRate().withBaseRate (24).withPullDown(); + case kSMPTETimeType24: return FrameRate().withBaseRate (24); + case kSMPTETimeType25: return FrameRate().withBaseRate (25); + case kSMPTETimeType30Drop: return FrameRate().withBaseRate (30).withDrop(); + case kSMPTETimeType30: return FrameRate().withBaseRate (30); + case kSMPTETimeType2997: return FrameRate().withBaseRate (30).withPullDown(); + case kSMPTETimeType2997Drop: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + case kSMPTETimeType60: return FrameRate().withBaseRate (60); + case kSMPTETimeType60Drop: return FrameRate().withBaseRate (60).withDrop(); + case kSMPTETimeType5994: return FrameRate().withBaseRate (60).withPullDown(); + case kSMPTETimeType5994Drop: return FrameRate().withBaseRate (60).withPullDown().withDrop(); + case kSMPTETimeType50: return FrameRate().withBaseRate (50); + default: break; + } + + return FrameRate(); + }()); + + double num; + NSInteger den; + NSInteger outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat, bpm; + double ppqPosition; + + if (hostMusicalContextCallback != nullptr) + { + AUHostMusicalContextBlock musicalContextCallback = hostMusicalContextCallback; + + if (musicalContextCallback (&bpm, &num, &den, &ppqPosition, &outDeltaSampleOffsetToNextBeat, &outCurrentMeasureDownBeat)) + { + info.setTimeSignature (TimeSignature { (int) num, (int) den }); + info.setPpqPositionOfLastBarStart (outCurrentMeasureDownBeat); + info.setBpm (bpm); + info.setPpqPosition (ppqPosition); + } + } + + double outCurrentSampleInTimeLine = 0, outCycleStartBeat = 0, outCycleEndBeat = 0; + AUHostTransportStateFlags flags; + + if (hostTransportStateCallback != nullptr) + { + AUHostTransportStateBlock transportStateCallback = hostTransportStateCallback; + + if (transportStateCallback (&flags, &outCurrentSampleInTimeLine, &outCycleStartBeat, &outCycleEndBeat)) + { + info.setTimeInSamples ((int64) (outCurrentSampleInTimeLine + 0.5)); + info.setTimeInSeconds (*info.getTimeInSamples() / getAudioProcessor().getSampleRate()); + info.setIsPlaying ((flags & AUHostTransportStateMoving) != 0); + info.setIsLooping ((flags & AUHostTransportStateCycling) != 0); + info.setIsRecording ((flags & AUHostTransportStateRecording) != 0); + info.setLoopPoints (LoopPoints { outCycleStartBeat, outCycleEndBeat }); + } + } + + if ((lastTimeStamp.mFlags & kAudioTimeStampHostTimeValid) != 0) + info.setHostTimeNs (timeConversions.hostTimeToNanos (lastTimeStamp.mHostTime)); + + return info; + } + + //============================================================================== + static void removeEditor (AudioProcessor& processor) + { + ScopedLock editorLock (processor.getCallbackLock()); + + if (AudioProcessorEditor* editor = processor.getActiveEditor()) + { + processor.editorBeingDeleted (editor); + delete editor; + } + } + + AUAudioUnit* getAudioUnit() const { return au; } + +private: + struct Class : public ObjCClass + { + Class() : ObjCClass ("AUAudioUnit_") + { + addIvar ("cppObject"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (initWithComponentDescription:options:error:juceClass:), [] (id _self, + SEL, + AudioComponentDescription descr, + AudioComponentInstantiationOptions options, + NSError** error, + JuceAudioUnitv3* juceAU) + { + AUAudioUnit* self = _self; + + self = ObjCMsgSendSuper (self, @selector(initWithComponentDescription:options:error:), descr, options, error); + + setThis (self, juceAU); + return self; + }); + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + addMethod (@selector (initWithComponentDescription:options:error:), [] (id _self, + SEL, + AudioComponentDescription descr, + AudioComponentInstantiationOptions options, + NSError** error) + { + AUAudioUnit* self = _self; + + self = ObjCMsgSendSuper (self, @selector (initWithComponentDescription:options:error:), descr, options, error); + + auto* juceAU = JuceAudioUnitv3::create (self, descr, options, error); + + setThis (self, juceAU); + return self; + }); + + addMethod (@selector (dealloc), [] (id self, SEL) + { + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + WaitableEvent deletionEvent; + + struct AUDeleter : public CallbackMessage + { + AUDeleter (id selfToDelete, WaitableEvent& event) + : parentSelf (selfToDelete), parentDeletionEvent (event) + { + } + + void messageCallback() override + { + delete _this (parentSelf); + parentDeletionEvent.signal(); + } + + id parentSelf; + WaitableEvent& parentDeletionEvent; + }; + + (new AUDeleter (self, deletionEvent))->post(); + deletionEvent.wait (-1); + } + else + { + delete _this (self); + } + }); + + //============================================================================== + addMethod (@selector (reset), [] (id self, SEL) { return _this (self)->reset(); }); + + //============================================================================== + addMethod (@selector (currentPreset), [] (id self, SEL) { return _this (self)->getCurrentPreset(); }); + addMethod (@selector (setCurrentPreset:), [] (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); }); + addMethod (@selector (factoryPresets), [] (id self, SEL) { return _this (self)->getFactoryPresets(); }); + addMethod (@selector (fullState), [] (id self, SEL) { return _this (self)->getFullState(); }); + addMethod (@selector (setFullState:), [] (id self, SEL, NSDictionary* state) { return _this (self)->setFullState (state); }); + addMethod (@selector (parameterTree), [] (id self, SEL) { return _this (self)->getParameterTree(); }); + addMethod (@selector (parametersForOverviewWithCount:), [] (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast (count)); }); + + //============================================================================== + addMethod (@selector (latency), [] (id self, SEL) { return _this (self)->getLatency(); }); + addMethod (@selector (tailTime), [] (id self, SEL) { return _this (self)->getTailTime(); }); + + //============================================================================== + addMethod (@selector (inputBusses), [] (id self, SEL) { return _this (self)->getInputBusses(); }); + addMethod (@selector (outputBusses), [] (id self, SEL) { return _this (self)->getOutputBusses(); }); + addMethod (@selector (channelCapabilities), [] (id self, SEL) { return _this (self)->getChannelCapabilities(); }); + addMethod (@selector (shouldChangeToFormat:forBus:), [] (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; }); + + //============================================================================== + addMethod (@selector (virtualMIDICableCount), [] (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); }); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (supportsMPE), [] (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; }); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (@available (macOS 10.13, iOS 11.0, *)) + addMethod (@selector (MIDIOutputNames), [] (id self, SEL) { return _this (self)->getMIDIOutputNames(); }); + + //============================================================================== + addMethod (@selector (internalRenderBlock), [] (id self, SEL) { return _this (self)->getInternalRenderBlock(); }); + addMethod (@selector (canProcessInPlace), [] (id, SEL) { return NO; }); + addMethod (@selector (isRenderingOffline), [] (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; }); + addMethod (@selector (setRenderingOffline:), [] (id self, SEL, BOOL renderingOffline) { return _this (self)->setRenderingOffline (renderingOffline); }); + addMethod (@selector (shouldBypassEffect), [] (id self, SEL) { return _this (self)->getShouldBypassEffect() ? YES : NO; }); + addMethod (@selector (setShouldBypassEffect:), [] (id self, SEL, BOOL shouldBypass) { return _this (self)->setShouldBypassEffect (shouldBypass); }); + addMethod (@selector (allocateRenderResourcesAndReturnError:), [] (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; }); + addMethod (@selector (deallocateRenderResources), [] (id self, SEL) { return _this (self)->deallocateRenderResources(); }); + + //============================================================================== + addMethod (@selector (contextName), [] (id self, SEL) { return _this (self)->getContextName(); }); + addMethod (@selector (setContextName:), [](id self, SEL, NSString* str) { return _this (self)->setContextName (str); }); + + //============================================================================== + if (@available (macOS 10.13, iOS 11.0, *)) + { + addMethod (@selector (supportedViewConfigurations:), [] (id self, SEL, NSArray* configs) + { + auto supportedViewIndices = [[NSMutableIndexSet alloc] init]; + auto n = [configs count]; + + if (auto* editor = _this (self)->getAudioProcessor().createEditorIfNeeded()) + { + // If you hit this assertion then your plug-in's editor is reporting that it doesn't support + // any host MIDI controller configurations! + jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); + + for (auto i = 0u; i < n; ++i) + { + if (auto viewConfiguration = [configs objectAtIndex: i]) + { + if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) + { + auto* constrainer = editor->getConstrainer(); + auto height = (int) [viewConfiguration height]; + auto width = (int) [viewConfiguration width]; + + const auto maxLimits = std::numeric_limits::max() / 2; + const Rectangle requestedBounds { width, height }; + auto modifiedBounds = requestedBounds; + constrainer->checkBounds (modifiedBounds, editor->getBounds().withZeroOrigin(), { maxLimits, maxLimits }, false, false, false, false); + + if (modifiedBounds == requestedBounds) + [supportedViewIndices addIndex: i]; + } + } + } + } + + return [supportedViewIndices autorelease]; + }); + + addMethod (@selector (selectViewConfiguration:), [] (id self, SEL, AUAudioUnitViewConfiguration* config) + { + _this (self)->processorHolder->viewConfiguration.reset (new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }); + }); + } + + registerClass(); + } + + //============================================================================== + static JuceAudioUnitv3* _this (id self) { return getIvar (self, "cppObject"); } + static void setThis (id self, JuceAudioUnitv3* cpp) { object_setInstanceVariable (self, "cppObject", cpp); } + }; + + static JuceAudioUnitv3* create (AUAudioUnit* audioUnit, AudioComponentDescription descr, AudioComponentInstantiationOptions options, NSError** error) + { + return new JuceAudioUnitv3 (audioUnit, descr, options, error); + } + + //============================================================================== + static Class& getClass() + { + static Class result; + return result; + } + + //============================================================================== + struct BusBuffer + { + BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) + : auBus (bus), + maxFrames (maxFramesPerBuffer), + numberOfChannels (static_cast ([[auBus format] channelCount])), + isInterleaved ([[auBus format] isInterleaved]) + { + alloc(); + } + + //============================================================================== + void alloc() + { + const int numBuffers = isInterleaved ? 1 : numberOfChannels; + int bytes = static_cast (sizeof (AudioBufferList)) + + ((numBuffers - 1) * static_cast (sizeof (::AudioBuffer))); + jassert (bytes > 0); + + bufferListStorage.calloc (static_cast (bytes)); + bufferList = reinterpret_cast (bufferListStorage.getData()); + + const int bufferChannels = isInterleaved ? numberOfChannels : 1; + scratchBuffer.setSize (numBuffers, bufferChannels * maxFrames); + } + + void dealloc() + { + bufferList = nullptr; + bufferListStorage.free(); + scratchBuffer.setSize (0, 0); + } + + //============================================================================== + int numChannels() const noexcept { return numberOfChannels; } + bool interleaved() const noexcept { return isInterleaved; } + AudioBufferList* get() const noexcept { return bufferList; } + + //============================================================================== + void prepare (UInt32 nFrames, const AudioBufferList* other = nullptr) noexcept + { + const int numBuffers = isInterleaved ? 1 : numberOfChannels; + const bool isCompatible = isCompatibleWith (other); + + bufferList->mNumberBuffers = static_cast (numBuffers); + + for (int i = 0; i < numBuffers; ++i) + { + const UInt32 bufferChannels = static_cast (isInterleaved ? numberOfChannels : 1); + bufferList->mBuffers[i].mNumberChannels = bufferChannels; + bufferList->mBuffers[i].mData = (isCompatible ? other->mBuffers[i].mData + : scratchBuffer.getWritePointer (i)); + bufferList->mBuffers[i].mDataByteSize = nFrames * bufferChannels * sizeof (float); + } + } + + //============================================================================== + bool isCompatibleWith (const AudioBufferList* other) const noexcept + { + if (other == nullptr) + return false; + + if (other->mNumberBuffers > 0) + { + const bool otherInterleaved = AudioUnitHelpers::isAudioBufferInterleaved (*other); + const int otherChannels = static_cast (otherInterleaved ? other->mBuffers[0].mNumberChannels + : other->mNumberBuffers); + + return otherInterleaved == isInterleaved + && numberOfChannels == otherChannels; + } + + return numberOfChannels == 0; + } + + private: + AUAudioUnitBus* auBus; + HeapBlock bufferListStorage; + AudioBufferList* bufferList = nullptr; + int maxFrames, numberOfChannels; + bool isInterleaved; + juce::AudioBuffer scratchBuffer; + }; + + class FactoryPresets + { + public: + using Presets = std::unique_ptr, NSObjectDeleter>; + + void set (Presets newPresets) + { + std::lock_guard lock (mutex); + std::swap (presets, newPresets); + } + + NSArray* get() const + { + std::lock_guard lock (mutex); + return presets.get(); + } + + AUAudioUnitPreset* getAtIndex (int index) const + { + std::lock_guard lock (mutex); + + if (index < (int) [presets.get() count]) + return [presets.get() objectAtIndex: (unsigned int) index]; + + return nullptr; + } + + private: + Presets presets; + mutable std::mutex mutex; + }; + + //============================================================================== + void addAudioUnitBusses (bool isInput) + { + std::unique_ptr, NSObjectDeleter> array ([[NSMutableArray alloc] init]); + AudioProcessor& processor = getAudioProcessor(); + const auto numWrapperBuses = AudioUnitHelpers::getBusCountForWrapper (processor, isInput); + const auto numProcessorBuses = AudioUnitHelpers::getBusCount (processor, isInput); + + for (int i = 0; i < numWrapperBuses; ++i) + { + using AVAudioFormatPtr = std::unique_ptr; + + const auto audioFormat = [&]() -> AVAudioFormatPtr + { + const auto tag = i < numProcessorBuses ? CoreAudioLayouts::toCoreAudio (processor.getChannelLayoutOfBus (isInput, i)) + : kAudioChannelLayoutTag_Stereo; + const std::unique_ptr layout { [[AVAudioChannelLayout alloc] initWithLayoutTag: tag] }; + + if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate + channelLayout: layout.get()] }) + return format; + + const auto channels = i < numProcessorBuses ? processor.getChannelCountOfBus (isInput, i) + : 2; + + // According to the docs, this will fail if the number of channels is greater than 2. + if (auto format = AVAudioFormatPtr { [[AVAudioFormat alloc] initStandardFormatWithSampleRate: kDefaultSampleRate + channels: static_cast (channels)] }) + return format; + + jassertfalse; + return nullptr; + }(); + + using AUAudioUnitBusPtr = std::unique_ptr; + + const auto audioUnitBus = [&]() -> AUAudioUnitBusPtr + { + if (audioFormat != nullptr) + return AUAudioUnitBusPtr { [[AUAudioUnitBus alloc] initWithFormat: audioFormat.get() error: nullptr] }; + + jassertfalse; + return nullptr; + }(); + + if (audioUnitBus != nullptr) + [array.get() addObject: audioUnitBus.get()]; + } + + (isInput ? inputBusses : outputBusses).reset ([[AUAudioUnitBusArray alloc] initWithAudioUnit: au + busType: (isInput ? AUAudioUnitBusTypeInput : AUAudioUnitBusTypeOutput) + busses: array.get()]); + } + + // When parameters are discrete we need to use integer values. + static float getMaximumParameterValue ([[maybe_unused]] const AudioProcessorParameter& juceParam) + { + #if JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + return 1.0f; + #else + return juceParam.isDiscrete() ? (float) (juceParam.getNumSteps() - 1) : 1.0f; + #endif + } + + static auto createParameter (const AudioProcessorParameter& parameter) + { + const String name (parameter.getName (512)); + + AudioUnitParameterUnit unit = kAudioUnitParameterUnit_Generic; + AudioUnitParameterOptions flags = (UInt32) (kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString + | kAudioUnitParameterFlag_ValuesHaveStrings); + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + flags |= (UInt32) kAudioUnitParameterFlag_IsHighResolution; + #endif + + // Set whether the param is automatable (unnamed parameters aren't allowed to be automated). + if (name.isEmpty() || ! parameter.isAutomatable()) + flags |= kAudioUnitParameterFlag_NonRealTime; + + const bool isParameterDiscrete = parameter.isDiscrete(); + + if (! isParameterDiscrete) + flags |= kAudioUnitParameterFlag_CanRamp; + + if (parameter.isMetaParameter()) + flags |= kAudioUnitParameterFlag_IsGlobalMeta; + + std::unique_ptr valueStrings; + + // Is this a meter? + if (((parameter.getCategory() & 0xffff0000) >> 16) == 2) + { + flags &= ~kAudioUnitParameterFlag_IsWritable; + flags |= kAudioUnitParameterFlag_MeterReadOnly | kAudioUnitParameterFlag_DisplayLogarithmic; + unit = kAudioUnitParameterUnit_LinearGain; + } + else + { + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (parameter.isDiscrete()) + { + unit = parameter.isBoolean() ? kAudioUnitParameterUnit_Boolean : kAudioUnitParameterUnit_Indexed; + auto maxValue = getMaximumParameterValue (parameter); + auto numSteps = parameter.getNumSteps(); + + // Some hosts can't handle the huge numbers of discrete parameter values created when + // using the default number of steps. + jassert (numSteps != AudioProcessor::getDefaultNumParameterSteps()); + + valueStrings.reset ([NSMutableArray new]); + + for (int i = 0; i < numSteps; ++i) + [valueStrings.get() addObject: juceStringToNS (parameter.getText ((float) i / maxValue, 0))]; + } + #endif + } + + const auto address = generateAUParameterAddress (parameter); + + auto getParameterIdentifier = [¶meter] + { + if (const auto* paramWithID = dynamic_cast (¶meter)) + return paramWithID->getParameterID(); + + // This could clash if any groups have been given integer IDs! + return String (parameter.getParameterIndex()); + }; + + std::unique_ptr param; + + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + param.reset([[AUParameterTree createParameterWithIdentifier: juceStringToNS (getParameterIdentifier()) + name: juceStringToNS (name) + address: address + min: 0.0f + max: getMaximumParameterValue (parameter) + unit: unit + unitName: nullptr + flags: flags + valueStrings: valueStrings.get() + dependentParameters: nullptr] + retain]); + } + + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + + [param.get() setValue: parameter.getDefaultValue()]; + return param; + } + + struct NodeArrayResult + { + std::unique_ptr, NSObjectDeleter> nodeArray { [NSMutableArray new] }; + + void addParameter (const AudioProcessorParameter&, std::unique_ptr auParam) + { + [nodeArray.get() addObject: [auParam.get() retain]]; + } + + void addGroup (const AudioProcessorParameterGroup& group, const NodeArrayResult& r) + { + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + [nodeArray.get() addObject: [[AUParameterTree createGroupWithIdentifier: juceStringToNS (group.getID()) + name: juceStringToNS (group.getName()) + children: r.nodeArray.get()] retain]]; + } + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + } + }; + + struct AddressedNodeArrayResult + { + NodeArrayResult nodeArray; + std::map addressForIndex; + + void addParameter (const AudioProcessorParameter& juceParam, std::unique_ptr auParam) + { + const auto index = juceParam.getParameterIndex(); + const auto address = [auParam.get() address]; + + if (const auto iter = addressForIndex.find (index); iter == addressForIndex.cend()) + addressForIndex.emplace (index, address); + else + jassertfalse; // If you hit this assertion then you have put a parameter in two groups. + + nodeArray.addParameter (juceParam, std::move (auParam)); + } + + void addGroup (const AudioProcessorParameterGroup& group, const AddressedNodeArrayResult& r) + { + nodeArray.addGroup (group, r.nodeArray); + + [[maybe_unused]] const auto initialSize = addressForIndex.size(); + addressForIndex.insert (r.addressForIndex.begin(), r.addressForIndex.end()); + [[maybe_unused]] const auto finalSize = addressForIndex.size(); + + // If this is hit, the same parameter index exists in multiple groups. + jassert (finalSize == initialSize + r.addressForIndex.size()); + } + }; + + template + static Result createParameterNodes (const AudioProcessorParameterGroup& group) + { + Result result; + + for (auto* node : group) + { + if (auto* childGroup = node->getGroup()) + { + result.addGroup (*childGroup, createParameterNodes (*childGroup)); + } + else if (auto* juceParam = node->getParameter()) + { + result.addParameter (*juceParam, createParameter (*juceParam)); + } + else + { + // No group or parameter at this node! + jassertfalse; + } + } + + return result; + } + + void addParameters() + { + auto& processor = getAudioProcessor(); + juceParameters.update (processor, forceLegacyParamIDs); + + if ((bypassParam = processor.getBypassParameter()) != nullptr) + bypassParam->addListener (this); + + auto nodes = createParameterNodes (processor.getParameterTree()); + installNewParameterTree (std::move (nodes.nodeArray.nodeArray)); + + // When we first create the parameter tree, we also create structures to allow lookup by index/address. + // These structures are not rebuilt, i.e. we assume that the parameter addresses and indices are stable. + // These structures aren't modified after creation, so there should be no need to synchronize access to them. + + addressForIndex = [&] + { + std::vector addresses (static_cast (processor.getParameters().size())); + + for (size_t i = 0; i < addresses.size(); ++i) + { + if (const auto iter = nodes.addressForIndex.find (static_cast (i)); iter != nodes.addressForIndex.cend()) + addresses[i] = iter->second; + else + jassertfalse; // Somehow, there's a parameter missing... + } + + return addresses; + }(); + + #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS + indexForAddress = [&] + { + std::map indices; + + for (const auto& [index, address] : nodes.addressForIndex) + { + if (const auto iter = indices.find (address); iter == indices.cend()) + indices.emplace (address, index); + else + jassertfalse; // The parameter at index 'iter->first' has the same address as the parameter at index 'index' + } + + return indices; + }(); + #endif + } + + void installNewParameterTree (std::unique_ptr, NSObjectDeleter> topLevelNodes) + { + editorObserverToken.reset(); + + @try + { + // Create methods in AUParameterTree return unretained objects (!) -> see Apple header AUAudioUnitImplementation.h + paramTree.reset ([[AUParameterTree createTreeWithChildren: topLevelNodes.get()] retain]); + } + @catch (NSException* exception) + { + // Do you have duplicate identifiers in any of your groups or parameters, + // or do your identifiers have unusual characters in them? + jassertfalse; + } + + [paramTree.get() setImplementorValueObserver: ^(AUParameter* param, AUValue value) { this->valueChangedFromHost (param, value); }]; + [paramTree.get() setImplementorValueProvider: ^(AUParameter* param) { return this->getValue (param); }]; + [paramTree.get() setImplementorStringFromValueCallback: ^(AUParameter* param, const AUValue* value) { return this->stringFromValue (param, value); }]; + [paramTree.get() setImplementorValueFromStringCallback: ^(AUParameter* param, NSString* str) { return this->valueFromString (param, str); }]; + + if (getAudioProcessor().hasEditor()) + { + editorObserverToken = ObserverPtr ([paramTree.get() tokenByAddingParameterObserver: ^(AUParameterAddress, AUValue) + { + // this will have already been handled by valueChangedFromHost + }], + ObserverDestructor { paramTree.get() }); + } + } + + void setAudioProcessorParameter (AudioProcessorParameter* juceParam, float value) + { + if (! approximatelyEqual (value, juceParam->getValue())) + { + juceParam->setValue (value); + + inParameterChangedCallback = true; + juceParam->sendValueChangedMessageToListeners (value); + } + } + + void addPresets() + { + FactoryPresets::Presets newPresets { [[NSMutableArray alloc] init] }; + + const int n = getAudioProcessor().getNumPrograms(); + + for (int idx = 0; idx < n; ++idx) + { + String name = getAudioProcessor().getProgramName (idx); + + std::unique_ptr preset ([[AUAudioUnitPreset alloc] init]); + [preset.get() setName: juceStringToNS (name)]; + [preset.get() setNumber: static_cast (idx)]; + + [newPresets.get() addObject: preset.get()]; + } + + factoryPresets.set (std::move (newPresets)); + } + + //============================================================================== + void allocateBusBuffer (bool isInput) + { + OwnedArray& busBuffers = isInput ? inBusBuffers : outBusBuffers; + busBuffers.clear(); + + const int n = AudioUnitHelpers::getBusCountForWrapper (getAudioProcessor(), isInput); + const AUAudioFrameCount maxFrames = [au maximumFramesToRender]; + + for (int busIdx = 0; busIdx < n; ++busIdx) + busBuffers.add (new BusBuffer ([(isInput ? inputBusses.get() : outputBusses.get()) objectAtIndexedSubscript: static_cast (busIdx)], + static_cast (maxFrames))); + } + + //============================================================================== + void processEvents (const AURenderEvent *__nullable realtimeEventListHead, [[maybe_unused]] int numParams, AUEventSampleTime startTime) + { + for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) + { + switch (event->head.eventType) + { + case AURenderEventMIDI: + { + const AUMIDIEvent& midiEvent = event->MIDI; + midiMessages.addEvent (midiEvent.data, midiEvent.length, static_cast (midiEvent.eventSampleTime - startTime)); + } + break; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + case AURenderEventMIDIEventList: + { + const auto& list = event->MIDIEventsList.eventList; + auto* packet = &list.packet[0]; + + for (uint32_t i = 0; i < list.numPackets; ++i) + { + converter.dispatch (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + static_cast (packet->timeStamp - (MIDITimeStamp) startTime), + [this] (const ump::BytestreamMidiView& message) + { + midiMessages.addEvent (message.getMessage(), (int) message.timestamp); + }); + + packet = MIDIEventPacketNext (packet); + } + } + break; + #endif + + case AURenderEventParameter: + case AURenderEventParameterRamp: + { + const AUParameterEvent& paramEvent = event->parameter; + + if (auto* p = getJuceParameterForAUAddress (paramEvent.parameterAddress)) + { + auto normalisedValue = paramEvent.value / getMaximumParameterValue (*p); + setAudioProcessorParameter (p, normalisedValue); + } + } + break; + + case AURenderEventMIDISysEx: + default: + break; + } + } + } + + AUAudioUnitStatus renderCallback (AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp, AUAudioFrameCount frameCount, + NSInteger outputBusNumber, AudioBufferList* outputData, const AURenderEvent *__nullable realtimeEventListHead, + AURenderPullInputBlock __nullable pullInputBlock) + { + auto& processor = getAudioProcessor(); + jassert (static_cast (frameCount) <= getAudioProcessor().getBlockSize()); + + const auto numProcessorBusesOut = AudioUnitHelpers::getBusCount (processor, false); + + if (! approximatelyEqual (lastTimeStamp.mSampleTime, timestamp->mSampleTime)) + { + // process params and incoming midi (only once for a given timestamp) + midiMessages.clear(); + + const int numParams = juceParameters.getNumParameters(); + processEvents (realtimeEventListHead, numParams, static_cast (timestamp->mSampleTime)); + + lastTimeStamp = *timestamp; + + const auto numWrapperBusesIn = AudioUnitHelpers::getBusCountForWrapper (processor, true); + const auto numWrapperBusesOut = AudioUnitHelpers::getBusCountForWrapper (processor, false); + const auto numProcessorBusesIn = AudioUnitHelpers::getBusCount (processor, true); + + // prepare buffers + { + for (int busIdx = 0; busIdx < numWrapperBusesOut; ++busIdx) + { + BusBuffer& busBuffer = *outBusBuffers[busIdx]; + const bool canUseDirectOutput = + (busIdx == outputBusNumber && outputData != nullptr && outputData->mNumberBuffers > 0); + + busBuffer.prepare (frameCount, canUseDirectOutput ? outputData : nullptr); + + if (numProcessorBusesOut <= busIdx) + AudioUnitHelpers::clearAudioBuffer (*busBuffer.get()); + } + + for (int busIdx = 0; busIdx < numWrapperBusesIn; ++busIdx) + { + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + busBuffer.prepare (frameCount, busIdx < numWrapperBusesOut ? outBusBuffers[busIdx]->get() : nullptr); + } + + audioBuffer.reset(); + } + + // pull inputs + { + for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) + { + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + if (pullInputBlock == nullptr || pullInputBlock (actionFlags, timestamp, frameCount, busIdx, buffer) != noErr) + AudioUnitHelpers::clearAudioBuffer (*buffer); + + if (actionFlags != nullptr && (*actionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0) + AudioUnitHelpers::clearAudioBuffer (*buffer); + } + } + + // set buffer pointer to minimize copying + { + int chIdx = 0; + + for (int busIdx = 0; busIdx < numProcessorBusesOut; ++busIdx) + { + BusBuffer& busBuffer = *outBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + const bool interleaved = busBuffer.interleaved(); + const int numChannels = busBuffer.numChannels(); + + const int* outLayoutMap = mapper.get (false, busIdx); + + for (int ch = 0; ch < numChannels; ++ch) + audioBuffer.setBuffer (chIdx++, interleaved ? nullptr : static_cast (buffer->mBuffers[outLayoutMap[ch]].mData)); + } + + // use input pointers on remaining channels + + for (int busIdx = 0; chIdx < totalInChannels;) + { + const int channelOffset = processor.getOffsetInBusBufferForAbsoluteChannelIndex (true, chIdx, busIdx); + + BusBuffer& busBuffer = *inBusBuffers[busIdx]; + AudioBufferList* buffer = busBuffer.get(); + + const int* inLayoutMap = mapper.get (true, busIdx); + audioBuffer.setBuffer (chIdx++, busBuffer.interleaved() ? nullptr : static_cast (buffer->mBuffers[inLayoutMap[channelOffset]].mData)); + } + } + + // copy input + { + for (int busIdx = 0; busIdx < numProcessorBusesIn; ++busIdx) + audioBuffer.set (busIdx, *inBusBuffers[busIdx]->get(), mapper.get (true, busIdx)); + + audioBuffer.clearUnusedChannels ((int) frameCount); + } + + // process audio + processBlock (audioBuffer.getBuffer (frameCount), midiMessages); + + // send MIDI + #if JucePlugin_ProducesMidiOutput + if (@available (macOS 10.13, iOS 11.0, *)) + { + if (auto midiOut = midiOutputEventBlock) + for (const auto metadata : midiMessages) + if (isPositiveAndBelow (metadata.samplePosition, frameCount)) + midiOut ((int64_t) metadata.samplePosition + (int64_t) (timestamp->mSampleTime + 0.5), + 0, + metadata.numBytes, + metadata.data); + } + #endif + } + + // copy back + if (outputBusNumber < numProcessorBusesOut && outputData != nullptr) + audioBuffer.get ((int) outputBusNumber, *outputData, mapper.get (false, (int) outputBusNumber)); + + return noErr; + } + + void processBlock (juce::AudioBuffer& buffer, MidiBuffer& midiBuffer) noexcept + { + auto& processor = getAudioProcessor(); + const ScopedLock sl (processor.getCallbackLock()); + + if (processor.isSuspended()) + buffer.clear(); + else if (bypassParam == nullptr && [au shouldBypassEffect]) + processor.processBlockBypassed (buffer, midiBuffer); + else + processor.processBlock (buffer, midiBuffer); + } + + //============================================================================== + void valueChangedFromHost (AUParameter* param, AUValue value) + { + if (param != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + auto normalisedValue = value / getMaximumParameterValue (*p); + setAudioProcessorParameter (p, normalisedValue); + } + } + } + + AUValue getValue (AUParameter* param) const + { + if (param != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + return p->getValue() * getMaximumParameterValue (*p); + } + + return 0; + } + + NSString* stringFromValue (AUParameter* param, const AUValue* value) + { + String text; + + if (param != nullptr && value != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + if (LegacyAudioParameter::isLegacy (p)) + text = String (*value); + else + text = p->getText (*value / getMaximumParameterValue (*p), 0); + } + } + + return juceStringToNS (text); + } + + AUValue valueFromString (AUParameter* param, NSString* str) + { + if (param != nullptr && str != nullptr) + { + if (auto* p = getJuceParameterForAUAddress ([param address])) + { + const String text (nsStringToJuce (str)); + + if (LegacyAudioParameter::isLegacy (p)) + return text.getFloatValue(); + + return p->getValueForText (text) * getMaximumParameterValue (*p); + } + } + + return 0; + } + + //============================================================================== + // this is only ever called for the bypass parameter + void parameterValueChanged (int, float newValue) override + { + JuceAudioUnitv3::setShouldBypassEffect (newValue != 0.0f); + } + + void parameterGestureChanged (int, bool) override {} + + //============================================================================== + inline AUParameterAddress getAUParameterAddressForIndex (int paramIndex) const noexcept + { + if (isPositiveAndBelow (paramIndex, addressForIndex.size())) + return addressForIndex[static_cast (paramIndex)]; + + return {}; + } + + inline int getJuceParameterIndexForAUAddress (AUParameterAddress address) const noexcept + { + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (address); + #else + if (const auto iter = indexForAddress.find (address); iter != indexForAddress.cend()) + return iter->second; + + return {}; + #endif + } + + static AUParameterAddress generateAUParameterAddress (const AudioProcessorParameter& param) + { + const String& juceParamID = LegacyAudioParameter::getParamID (¶m, forceLegacyParamIDs); + + return static_cast (forceLegacyParamIDs ? juceParamID.getIntValue() + : juceParamID.hashCode64()); + } + + AudioProcessorParameter* getJuceParameterForAUAddress (AUParameterAddress address) const noexcept + { + return juceParameters.getParamForIndex (getJuceParameterIndexForAUAddress (address)); + } + + //============================================================================== + static constexpr double kDefaultSampleRate = 44100.0; + + struct ObserverDestructor + { + void operator() (AUParameterObserverToken ptr) const + { + if (ptr != nullptr) + [tree removeParameterObserver: ptr]; + } + + AUParameterTree* tree; + }; + + using ObserverPtr = std::unique_ptr, ObserverDestructor>; + + AUAudioUnit* au; + AudioProcessorHolder::Ptr processorHolder; + + int totalInChannels, totalOutChannels; + + CoreAudioTimeConversions timeConversions; + std::unique_ptr inputBusses, outputBusses; + + #if ! JUCE_FORCE_USE_LEGACY_PARAM_IDS + std::map indexForAddress; + #endif + std::vector addressForIndex; + LegacyAudioParametersWrapper juceParameters; + + // to avoid recursion on parameter changes, we need to add an + // editor observer to do the parameter changes + std::unique_ptr paramTree; + ObserverPtr editorObserverToken; + + std::unique_ptr, NSObjectDeleter> channelCapabilities; + + FactoryPresets factoryPresets; + + ObjCBlock internalRenderBlock; + + AudioUnitHelpers::CoreAudioBufferList audioBuffer; + AudioUnitHelpers::ChannelRemapper mapper; + + OwnedArray inBusBuffers, outBusBuffers; + MidiBuffer midiMessages; + AUMIDIOutputEventBlock midiOutputEventBlock = nullptr; + + #if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + ump::ToBytestreamDispatcher converter { 2048 }; + #endif + + ObjCBlock hostMusicalContextCallback; + ObjCBlock hostTransportStateCallback; + + AudioTimeStamp lastTimeStamp; + + String contextName; + + ThreadLocalValue inParameterChangedCallback; + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + static constexpr bool forceLegacyParamIDs = true; + #else + static constexpr bool forceLegacyParamIDs = false; + #endif + AudioProcessorParameter* bypassParam = nullptr; +}; + +#if JUCE_IOS +namespace juce +{ +struct UIViewPeerControllerReceiver +{ + virtual ~UIViewPeerControllerReceiver(); + virtual void setViewController (UIViewController*) = 0; +}; +} +#endif + +//============================================================================== +class JuceAUViewController +{ +public: + JuceAUViewController (AUViewController* p) + : myself (p) + { + initialiseJuce_GUI(); + } + + ~JuceAUViewController() + { + JUCE_ASSERT_MESSAGE_THREAD + + if (processorHolder.get() != nullptr) + JuceAudioUnitv3::removeEditor (getAudioProcessor()); + } + + //============================================================================== + void loadView() + { + JUCE_ASSERT_MESSAGE_THREAD + + if (auto p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) + { + processorHolder = new AudioProcessorHolder (std::move (p)); + auto& processor = getAudioProcessor(); + + if (processor.hasEditor()) + { + if (AudioProcessorEditor* editor = processor.createEditorIfNeeded()) + { + preferredSize = editor->getBounds(); + + JUCE_IOS_MAC_VIEW* view = [[[JUCE_IOS_MAC_VIEW alloc] initWithFrame: convertToCGRect (editor->getBounds())] autorelease]; + [myself setView: view]; + + #if JUCE_IOS + editor->setVisible (false); + #else + editor->setVisible (true); + #endif + + detail::PluginUtilities::addToDesktop (*editor, view); + + #if JUCE_IOS + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + [peerView setContentMode: UIViewContentModeTop]; + + if (auto* peer = dynamic_cast (editor->getPeer())) + peer->setViewController (myself); + #endif + } + } + } + } + + void viewDidLayoutSubviews() + { + if (auto holder = processorHolder.get()) + { + if ([myself view] != nullptr) + { + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + { + if (holder->viewConfiguration != nullptr) + editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); + + editor->setBounds (convertToRectInt ([[myself view] bounds])); + + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + { + #if JUCE_IOS + [peerView setNeedsDisplay]; + #else + [peerView setNeedsDisplay: YES]; + #endif + } + } + } + } + } + + void didReceiveMemoryWarning() + { + if (auto ptr = processorHolder.get()) + if (auto* processor = ptr->get()) + processor->memoryWarningReceived(); + } + + void viewDidAppear (bool) + { + if (processorHolder.get() != nullptr) + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + editor->setVisible (true); + } + + void viewDidDisappear (bool) + { + if (processorHolder.get() != nullptr) + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + editor->setVisible (false); + } + + CGSize getPreferredContentSize() const + { + return CGSizeMake (static_cast (preferredSize.getWidth()), + static_cast (preferredSize.getHeight())); + } + + //============================================================================== + AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) + { + const auto holder = [&] + { + if (auto initialisedHolder = processorHolder.get()) + return initialisedHolder; + + waitForExecutionOnMainThread ([this] { [myself view]; }); + return processorHolder.get(); + }(); + + if (holder == nullptr) + return nullptr; + + return [(new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit() autorelease]; + } + +private: + template + static void waitForExecutionOnMainThread (Callback&& callback) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + callback(); + return; + } + + std::promise promise; + + MessageManager::callAsync ([&] + { + callback(); + promise.set_value(); + }); + + promise.get_future().get(); + } + + // There's a chance that createAudioUnit will be called from a background + // thread while the processorHolder is being updated on the main thread. + class LockedProcessorHolder + { + public: + AudioProcessorHolder::Ptr get() const + { + const ScopedLock lock (mutex); + return holder; + } + + LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) + { + const ScopedLock lock (mutex); + holder = other; + return *this; + } + + private: + mutable CriticalSection mutex; + AudioProcessorHolder::Ptr holder; + }; + + //============================================================================== + AUViewController* myself; + LockedProcessorHolder processorHolder; + Rectangle preferredSize { 1, 1 }; + + //============================================================================== + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } +}; + +//============================================================================== +// necessary glue code +@interface JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) : AUViewController +@end + +@implementation JUCE_VIEWCONTROLLER_OBJC_NAME (JucePlugin_AUExportPrefix) +{ + std::unique_ptr cpp; +} + +- (instancetype) initWithNibName: (nullable NSString*) nib bundle: (nullable NSBundle*) bndl { self = [super initWithNibName: nib bundle: bndl]; cpp.reset (new JuceAUViewController (self)); return self; } +- (void) loadView { cpp->loadView(); } +- (AUAudioUnit *) createAudioUnitWithComponentDescription: (AudioComponentDescription) desc error: (NSError **) error { return cpp->createAudioUnit (desc, error); } +- (CGSize) preferredContentSize { return cpp->getPreferredContentSize(); } + +// NSViewController and UIViewController have slightly different names for this function +- (void) viewDidLayoutSubviews { cpp->viewDidLayoutSubviews(); } +- (void) viewDidLayout { cpp->viewDidLayoutSubviews(); } + +- (void) didReceiveMemoryWarning { cpp->didReceiveMemoryWarning(); } +#if JUCE_IOS +- (void) viewDidAppear: (BOOL) animated { cpp->viewDidAppear (animated); [super viewDidAppear:animated]; } +- (void) viewDidDisappear: (BOOL) animated { cpp->viewDidDisappear (animated); [super viewDidDisappear:animated]; } +#endif +@end + +//============================================================================== +#if JUCE_IOS +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +bool JUCE_CALLTYPE juce_isInterAppAudioConnected() { return false; } +void JUCE_CALLTYPE juce_switchToHostApplication() {} +Image JUCE_CALLTYPE juce_getIAAHostIcon (int) { return {}; } + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp index 1399025..918ae2b 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp @@ -23,4 +23,1793 @@ ============================================================================== */ -#include "LV2/juce_LV2_Client.cpp" +#if JucePlugin_Build_LV2 + +#ifndef _SCL_SECURE_NO_WARNINGS + #define _SCL_SECURE_NO_WARNINGS +#endif + +#ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS +#endif + +#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 +#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include +#include +#include +#include + +#include +#include + +#include "JuceLV2Defines.h" +#include + +#include + +#define JUCE_TURTLE_RECALL_URI "https://lv2-extensions.juce.com/turtle_recall" + +#ifndef JucePlugin_LV2URI + #error "You need to define the JucePlugin_LV2URI value! If you're using the Projucer/CMake, the definition will be written into JuceLV2Defines.h automatically." +#endif + +namespace juce +{ +namespace lv2_client +{ + +constexpr auto uriSeparator = ":"; +const auto JucePluginLV2UriUi = String (JucePlugin_LV2URI) + uriSeparator + "UI"; +const auto JucePluginLV2UriState = String (JucePlugin_LV2URI) + uriSeparator + "StateString"; +const auto JucePluginLV2UriProgram = String (JucePlugin_LV2URI) + uriSeparator + "Program"; + +static const LV2_Feature* findMatchingFeature (const LV2_Feature* const* features, const char* uri) +{ + for (auto feature = features; *feature != nullptr; ++feature) + if (std::strcmp ((*feature)->URI, uri) == 0) + return *feature; + + return nullptr; +} + +static bool hasFeature (const LV2_Feature* const* features, const char* uri) +{ + return findMatchingFeature (features, uri) != nullptr; +} + +template +Data findMatchingFeatureData (const LV2_Feature* const* features, const char* uri) +{ + if (const auto* feature = findMatchingFeature (features, uri)) + return static_cast (feature->data); + + return {}; +} + +static const LV2_Options_Option* findMatchingOption (const LV2_Options_Option* options, LV2_URID urid) +{ + for (auto option = options; option->value != nullptr; ++option) + if (option->key == urid) + return option; + + return nullptr; +} + +class ParameterStorage : private AudioProcessorListener +{ +public: + ParameterStorage (AudioProcessor& proc, LV2_URID_Map map) + : processor (proc), + mapFeature (map), + legacyParameters (proc, false) + { + processor.addListener (this); + } + + ~ParameterStorage() override + { + processor.removeListener (this); + } + + /* This is the string that will be used to uniquely identify the parameter. + + This string will be written into the plugin's manifest as an IRI, so it must be + syntactically valid. + + We escape this string rather than writing the user-defined parameter ID directly to avoid + writing a malformed manifest in the case that user IDs contain spaces or other reserved + characters. This should allow users to keep the same param IDs for all plugin formats. + */ + static String getIri (const AudioProcessorParameter& param) + { + const auto urlSanitised = URL::addEscapeChars (LegacyAudioParameter::getParamID (¶m, false), true); + const auto ttlSanitised = lv2_shared::sanitiseStringAsTtlName (urlSanitised); + + // If this is hit, the parameter ID could not be represented directly in the plugin ttl. + // We'll replace offending characters with '_'. + jassert (urlSanitised == ttlSanitised); + + return ttlSanitised; + } + + void setValueFromHost (LV2_URID urid, float value) noexcept + { + const auto it = uridToIndexMap.find (urid); + + if (it == uridToIndexMap.end()) + { + // No such parameter. + jassertfalse; + return; + } + + if (auto* param = legacyParameters.getParamForIndex ((int) it->second)) + { + const auto scaledValue = [&] + { + if (auto* rangedParam = dynamic_cast (param)) + return rangedParam->convertTo0to1 (value); + + return value; + }(); + + if (! approximatelyEqual (scaledValue, param->getValue())) + { + ScopedValueSetter scope (ignoreCallbacks, true); + param->setValueNotifyingHost (scaledValue); + } + } + } + + struct Options + { + bool parameterValue, gestureBegin, gestureEnd; + }; + + static constexpr auto newClientValue = 1 << 0, + gestureBegan = 1 << 1, + gestureEnded = 1 << 2; + + template + void forEachChangedParameter (Callback&& callback) + { + stateCache.ifSet ([this, &callback] (size_t parameterIndex, float, uint32_t bits) + { + const Options options { (bits & newClientValue) != 0, + (bits & gestureBegan) != 0, + (bits & gestureEnded) != 0 }; + + callback (*legacyParameters.getParamForIndex ((int) parameterIndex), + indexToUridMap[parameterIndex], + options); + }); + } + +private: + void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float value) override + { + if (! ignoreCallbacks) + stateCache.setValueAndBits ((size_t) parameterIndex, value, newClientValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override + { + if (! ignoreCallbacks) + stateCache.setBits ((size_t) parameterIndex, gestureBegan); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int parameterIndex) override + { + if (! ignoreCallbacks) + stateCache.setBits ((size_t) parameterIndex, gestureEnded); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} + + AudioProcessor& processor; + const LV2_URID_Map mapFeature; + const LegacyAudioParametersWrapper legacyParameters; + const std::vector indexToUridMap = [&] + { + std::vector result; + + for (auto* param : legacyParameters) + { + jassert ((size_t) param->getParameterIndex() == result.size()); + + const auto uri = JucePlugin_LV2URI + String (uriSeparator) + getIri (*param); + const auto urid = mapFeature.map (mapFeature.handle, uri.toRawUTF8()); + result.push_back (urid); + } + + // If this is hit, some parameters have duplicate IDs. + // This may be because the IDs resolve to the same string when removing characters that + // are invalid in a TTL name. + jassert (std::set (result.begin(), result.end()).size() == result.size()); + + return result; + }(); + const std::map uridToIndexMap = [&] + { + std::map result; + size_t index = 0; + + for (const auto& urid : indexToUridMap) + result.emplace (urid, index++); + + return result; + }(); + FlaggedFloatCache<3> stateCache { (size_t) legacyParameters.getNumParameters() }; + bool ignoreCallbacks = false; + + JUCE_LEAK_DETECTOR (ParameterStorage) +}; + +enum class PortKind { seqInput, seqOutput, latencyOutput, freeWheelingInput, enabledInput }; + +struct PortIndices +{ + PortIndices (int numInputsIn, int numOutputsIn) + : numInputs (numInputsIn), numOutputs (numOutputsIn) {} + + int getPortIndexForAudioInput (int audioIndex) const noexcept + { + return audioIndex; + } + + int getPortIndexForAudioOutput (int audioIndex) const noexcept + { + return audioIndex + numInputs; + } + + int getPortIndexFor (PortKind p) const noexcept { return getMaxAudioPortIndex() + (int) p; } + + // Audio ports are numbered from 0 to numInputs + numOutputs + int getMaxAudioPortIndex() const noexcept { return numInputs + numOutputs; } + + int numInputs, numOutputs; +}; + +//============================================================================== +class PlayHead : public AudioPlayHead +{ +public: + PlayHead (LV2_URID_Map mapFeatureIn, double sampleRateIn) + : parser (mapFeatureIn), sampleRate (sampleRateIn) + { + } + + void invalidate() { info = nullopt; } + + void readNewInfo (const LV2_Atom_Event* event) + { + if (event->body.type != mLV2_ATOM__Object && event->body.type != mLV2_ATOM__Blank) + return; + + const auto* object = reinterpret_cast (&event->body); + + if (object->body.otype != mLV2_TIME__Position) + return; + + const LV2_Atom* atomFrame = nullptr; + const LV2_Atom* atomSpeed = nullptr; + const LV2_Atom* atomBar = nullptr; + const LV2_Atom* atomBeat = nullptr; + const LV2_Atom* atomBeatUnit = nullptr; + const LV2_Atom* atomBeatsPerBar = nullptr; + const LV2_Atom* atomBeatsPerMinute = nullptr; + + LV2_Atom_Object_Query query[] { { mLV2_TIME__frame, &atomFrame }, + { mLV2_TIME__speed, &atomSpeed }, + { mLV2_TIME__bar, &atomBar }, + { mLV2_TIME__beat, &atomBeat }, + { mLV2_TIME__beatUnit, &atomBeatUnit }, + { mLV2_TIME__beatsPerBar, &atomBeatsPerBar }, + { mLV2_TIME__beatsPerMinute, &atomBeatsPerMinute }, + LV2_ATOM_OBJECT_QUERY_END }; + + lv2_atom_object_query (object, query); + + info.emplace(); + + // Carla always seems to give us an integral 'beat' even though I'd expect + // it to be a floating-point value + + const auto numerator = parser.parseNumericAtom (atomBeatsPerBar); + const auto denominator = parser.parseNumericAtom (atomBeatUnit); + + if (numerator.hasValue() && denominator.hasValue()) + info->setTimeSignature (TimeSignature { (int) *numerator, (int) *denominator }); + + info->setBpm (parser.parseNumericAtom (atomBeatsPerMinute)); + info->setPpqPosition (parser.parseNumericAtom (atomBeat)); + info->setIsPlaying (! approximatelyEqual (parser.parseNumericAtom (atomSpeed).orFallback (0.0f), 0.0f)); + info->setBarCount (parser.parseNumericAtom (atomBar)); + + if (const auto parsed = parser.parseNumericAtom (atomFrame)) + { + info->setTimeInSamples (*parsed); + info->setTimeInSeconds ((double) *parsed / sampleRate); + } + } + + Optional getPosition() const override + { + return info; + } + +private: + lv2_shared::NumericAtomParser parser; + Optional info; + double sampleRate; + + #define X(str) const LV2_URID m##str = parser.map (str); + X (LV2_ATOM__Blank) + X (LV2_ATOM__Object) + X (LV2_TIME__Position) + X (LV2_TIME__beat) + X (LV2_TIME__beatUnit) + X (LV2_TIME__beatsPerBar) + X (LV2_TIME__beatsPerMinute) + X (LV2_TIME__frame) + X (LV2_TIME__speed) + X (LV2_TIME__bar) + #undef X + + JUCE_LEAK_DETECTOR (PlayHead) +}; + +//============================================================================== +class Ports +{ +public: + Ports (LV2_URID_Map map, int numInputsIn, int numOutputsIn) + : forge (map), + indices (numInputsIn, numOutputsIn), + mLV2_ATOM__Sequence (map.map (map.handle, LV2_ATOM__Sequence)) + { + audioBuffers.resize (static_cast (numInputsIn + numOutputsIn), nullptr); + } + + void connect (int port, void* data) + { + // The following is not UB _if_ data really points to an object with the expected type. + + if (port == indices.getPortIndexFor (PortKind::seqInput)) + { + inputData = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::seqOutput)) + { + outputData = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::latencyOutput)) + { + latency = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::freeWheelingInput)) + { + freeWheeling = static_cast (data); + } + else if (port == indices.getPortIndexFor (PortKind::enabledInput)) + { + enabled = static_cast (data); + } + else if (isPositiveAndBelow (port, indices.getMaxAudioPortIndex())) + { + audioBuffers[(size_t) port] = static_cast (data); + } + else + { + // This port was not declared! + jassertfalse; + } + } + + template + void forEachInputEvent (Callback&& callback) + { + if (inputData != nullptr && inputData->atom.type == mLV2_ATOM__Sequence) + for (const auto* event : lv2_shared::SequenceIterator { lv2_shared::SequenceWithSize { inputData } }) + callback (event); + } + + void prepareToWrite() + { + // Note: Carla seems to have a bug (verified with the eg-fifths plugin) where + // the output buffer size is incorrect on alternate calls. + forge.setBuffer (reinterpret_cast (outputData), outputData->atom.size); + } + + void writeLatency (int value) + { + if (latency != nullptr) + *latency = (float) value; + } + + const float* getBufferForAudioInput (int index) const noexcept + { + return audioBuffers[(size_t) indices.getPortIndexForAudioInput (index)]; + } + + float* getBufferForAudioOutput (int index) const noexcept + { + return audioBuffers[(size_t) indices.getPortIndexForAudioOutput (index)]; + } + + bool isFreeWheeling() const noexcept + { + if (freeWheeling != nullptr) + return *freeWheeling > 0.5f; + + return false; + } + + bool isEnabled() const noexcept + { + if (enabled != nullptr) + return *enabled > 0.5f; + + return true; + } + + lv2_shared::AtomForge forge; + PortIndices indices; + +private: + static constexpr auto numParamPorts = 3; + const LV2_Atom_Sequence* inputData = nullptr; + LV2_Atom_Sequence* outputData = nullptr; + float* latency = nullptr; + float* freeWheeling = nullptr; + float* enabled = nullptr; + std::vector audioBuffers; + const LV2_URID mLV2_ATOM__Sequence; + + JUCE_LEAK_DETECTOR (Ports) +}; + +class LV2PluginInstance : private AudioProcessorListener +{ +public: + LV2PluginInstance (double sampleRate, + int64_t maxBlockSize, + const char*, + LV2_URID_Map mapFeatureIn) + : mapFeature (mapFeatureIn), + playHead (mapFeature, sampleRate) + { + processor->addListener (this); + processor->setPlayHead (&playHead); + prepare (sampleRate, (int) maxBlockSize); + } + + void connect (uint32_t port, void* data) + { + ports.connect ((int) port, data); + } + + void activate() {} + + template + static void iterateAudioBuffer (AudioBuffer& ab, UnaryFunction fn) + { + float* const* sampleData = ab.getArrayOfWritePointers(); + + for (int c = ab.getNumChannels(); --c >= 0;) + for (int s = ab.getNumSamples(); --s >= 0;) + fn (sampleData[c][s]); + } + + static int countNaNs (AudioBuffer& ab) noexcept + { + int count = 0; + iterateAudioBuffer (ab, [&count] (float s) + { + if (std::isnan (s)) + ++count; + }); + + return count; + } + + void run (uint32_t numSteps) + { + // If this is hit, the host is trying to process more samples than it told us to prepare + jassert (static_cast (numSteps) <= processor->getBlockSize()); + + midi.clear(); + playHead.invalidate(); + audio.setSize (audio.getNumChannels(), static_cast (numSteps), true, false, true); + + ports.forEachInputEvent ([&] (const LV2_Atom_Event* event) + { + struct Callback + { + explicit Callback (LV2PluginInstance& s) : self (s) {} + + void setParameter (LV2_URID property, float value) const noexcept + { + self.parameters.setValueFromHost (property, value); + } + + // The host probably shouldn't send us 'touched' messages. + void gesture (LV2_URID, bool) const noexcept {} + + LV2PluginInstance& self; + }; + + patchSetHelper.processPatchSet (event, Callback { *this }); + + playHead.readNewInfo (event); + + if (event->body.type == mLV2_MIDI__MidiEvent) + midi.addEvent (event + 1, static_cast (event->body.size), static_cast (event->time.frames)); + }); + + processor->setNonRealtime (ports.isFreeWheeling()); + + for (auto i = 0, end = processor->getTotalNumInputChannels(); i < end; ++i) + audio.copyFrom (i, 0, ports.getBufferForAudioInput (i), audio.getNumSamples()); + + jassert (countNaNs (audio) == 0); + + { + const ScopedLock lock { processor->getCallbackLock() }; + + if (processor->isSuspended()) + { + for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) + { + const auto ptr = ports.getBufferForAudioOutput (i); + std::fill (ptr, ptr + numSteps, 0.0f); + } + } + else + { + const auto isEnabled = ports.isEnabled(); + + if (auto* param = processor->getBypassParameter()) + { + param->setValueNotifyingHost (isEnabled ? 0.0f : 1.0f); + processor->processBlock (audio, midi); + } + else if (isEnabled) + { + processor->processBlock (audio, midi); + } + else + { + processor->processBlockBypassed (audio, midi); + } + } + } + + for (auto i = 0, end = processor->getTotalNumOutputChannels(); i < end; ++i) + { + const auto src = audio.getReadPointer (i); + const auto dst = ports.getBufferForAudioOutput (i); + + if (dst != nullptr) + std::copy (src, src + numSteps, dst); + } + + ports.prepareToWrite(); + auto* forge = ports.forge.get(); + lv2_shared::SequenceFrame sequence { forge, (uint32_t) 0 }; + + parameters.forEachChangedParameter ([&] (const AudioProcessorParameter& param, + LV2_URID paramUrid, + const ParameterStorage::Options& options) + { + const auto sendTouched = [&] (bool state) + { + // TODO Implement begin/end change gesture support once it's supported by LV2 + ignoreUnused (state); + }; + + if (options.gestureBegin) + sendTouched (true); + + if (options.parameterValue) + { + lv2_atom_forge_frame_time (forge, 0); + + lv2_shared::ObjectFrame object { forge, (uint32_t) 0, patchSetHelper.mLV2_PATCH__Set }; + + lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__property); + lv2_atom_forge_urid (forge, paramUrid); + + lv2_atom_forge_key (forge, patchSetHelper.mLV2_PATCH__value); + lv2_atom_forge_float (forge, [&] + { + if (auto* rangedParam = dynamic_cast (¶m)) + return rangedParam->convertFrom0to1 (rangedParam->getValue()); + + return param.getValue(); + }()); + } + + if (options.gestureEnd) + sendTouched (false); + }); + + if (shouldSendStateChange.exchange (false)) + { + lv2_atom_forge_frame_time (forge, 0); + lv2_shared::ObjectFrame { forge, (uint32_t) 0, mLV2_STATE__StateChanged }; + } + + for (const auto meta : midi) + { + const auto bytes = static_cast (meta.numBytes); + lv2_atom_forge_frame_time (forge, meta.samplePosition); + lv2_atom_forge_atom (forge, bytes, mLV2_MIDI__MidiEvent); + lv2_atom_forge_write (forge, meta.data, bytes); + } + + ports.writeLatency (processor->getLatencySamples()); + } + + void deactivate() {} + + LV2_State_Status store (LV2_State_Store_Function storeFn, + LV2_State_Handle handle, + uint32_t, + const LV2_Feature* const*) + { + MemoryBlock block; + processor->getStateInformation (block); + const auto text = block.toBase64Encoding(); + storeFn (handle, + mJucePluginLV2UriState, + text.toRawUTF8(), + text.getNumBytesAsUTF8() + 1, + mLV2_ATOM__String, + LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE); + + return LV2_STATE_SUCCESS; + } + + LV2_State_Status retrieve (LV2_State_Retrieve_Function retrieveFn, + LV2_State_Handle handle, + uint32_t, + const LV2_Feature* const*) + { + size_t size = 0; + uint32_t type = 0; + uint32_t dataFlags = 0; + + // Try retrieving a port index (if this is a 'program' preset). + const auto* programData = retrieveFn (handle, mJucePluginLV2UriProgram, &size, &type, &dataFlags); + + if (programData != nullptr && type == mLV2_ATOM__Int && size == sizeof (int32_t)) + { + const auto programIndex = readUnaligned (programData); + processor->setCurrentProgram (programIndex); + return LV2_STATE_SUCCESS; + } + + // This doesn't seem to be a 'program' preset, try setting the full state from a string instead. + const auto* data = retrieveFn (handle, mJucePluginLV2UriState, &size, &type, &dataFlags); + + if (data == nullptr) + return LV2_STATE_ERR_NO_PROPERTY; + + if (type != mLV2_ATOM__String) + return LV2_STATE_ERR_BAD_TYPE; + + String text (static_cast (data), (size_t) size); + MemoryBlock block; + block.fromBase64Encoding (text); + processor->setStateInformation (block.getData(), (int) block.getSize()); + + return LV2_STATE_SUCCESS; + } + + std::unique_ptr createEditor() + { + return std::unique_ptr (processor->createEditorIfNeeded()); + } + + void editorBeingDeleted (AudioProcessorEditor* editor) + { + processor->editorBeingDeleted (editor); + } + + static std::unique_ptr createProcessorInstance() + { + auto result = createPluginFilterOfType (AudioProcessor::wrapperType_LV2); + + #if defined (JucePlugin_PreferredChannelConfigurations) + constexpr short channelConfigurations[][2] { JucePlugin_PreferredChannelConfigurations }; + + static_assert (numElementsInArray (channelConfigurations) > 0, + "JucePlugin_PreferredChannelConfigurations must contain at least one entry"); + static_assert (channelConfigurations[0][0] > 0 || channelConfigurations[0][1] > 0, + "JucePlugin_PreferredChannelConfigurations must have at least one input or output channel"); + result->setPlayConfigDetails (channelConfigurations[0][0], channelConfigurations[0][1], 44100.0, 1024); + + const auto desiredChannels = std::make_tuple (channelConfigurations[0][0], channelConfigurations[0][1]); + const auto actualChannels = std::make_tuple (result->getTotalNumInputChannels(), result->getTotalNumOutputChannels()); + + if (desiredChannels != actualChannels) + Logger::outputDebugString ("Failed to apply requested channel configuration!"); + #else + result->enableAllBuses(); + #endif + + return result; + } + +private: + void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + // Only check for non-parameter state here because: + // - Latency is automatically written every block. + // - There's no way for an LV2 plugin to report an internal program change. + // - Parameter info is hard-coded in the plugin's turtle description. + if (details.nonParameterStateChanged) + shouldSendStateChange = true; + } + + void prepare (double sampleRate, int maxBlockSize) + { + jassert (processor != nullptr); + processor->setRateAndBufferSizeDetails (sampleRate, maxBlockSize); + processor->prepareToPlay (sampleRate, maxBlockSize); + + const auto numChannels = jmax (processor->getTotalNumInputChannels(), + processor->getTotalNumOutputChannels()); + + midi.ensureSize (8192); + audio.setSize (numChannels, maxBlockSize); + audio.clear(); + } + + LV2_URID map (StringRef uri) const { return mapFeature.map (mapFeature.handle, uri); } + + ScopedJuceInitialiser_GUI scopedJuceInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + std::unique_ptr processor = createProcessorInstance(); + const LV2_URID_Map mapFeature; + ParameterStorage parameters { *processor, mapFeature }; + Ports ports { mapFeature, + processor->getTotalNumInputChannels(), + processor->getTotalNumOutputChannels() }; + lv2_shared::PatchSetHelper patchSetHelper { mapFeature, JucePlugin_LV2URI }; + PlayHead playHead; + MidiBuffer midi; + AudioBuffer audio; + std::atomic shouldSendStateChange { false }; + + #define X(str) const LV2_URID m##str = map (str); + X (JucePluginLV2UriProgram) + X (JucePluginLV2UriState) + X (LV2_ATOM__Int) + X (LV2_ATOM__String) + X (LV2_BUF_SIZE__maxBlockLength) + X (LV2_BUF_SIZE__sequenceSize) + X (LV2_MIDI__MidiEvent) + X (LV2_PATCH__Set) + X (LV2_STATE__StateChanged) + #undef X + + JUCE_LEAK_DETECTOR (LV2PluginInstance) +}; + +//============================================================================== +struct RecallFeature +{ + int (*doRecall) (const char*) = [] (const char* libraryPath) -> int + { + const ScopedJuceInitialiser_GUI scope; + const auto processor = LV2PluginInstance::createProcessorInstance(); + + const String pathString { CharPointer_UTF8 { libraryPath } }; + + const auto absolutePath = File::isAbsolutePath (pathString) ? File (pathString) + : File::getCurrentWorkingDirectory().getChildFile (pathString); + + const auto writers = { writeManifestTtl, writeDspTtl, writeUiTtl }; + + const auto wroteSuccessfully = [&processor, &absolutePath] (auto* fn) + { + const auto result = fn (*processor, absolutePath); + + if (! result.wasOk()) + std::cerr << result.getErrorMessage() << '\n'; + + return result.wasOk(); + }; + + return std::all_of (writers.begin(), writers.end(), wroteSuccessfully) ? 0 : 1; + }; + +private: + static String getPresetUri (int index) + { + return JucePlugin_LV2URI + String (uriSeparator) + "preset" + String (index + 1); + } + + static FileOutputStream openStream (const File& libraryPath, StringRef name) + { + return FileOutputStream { libraryPath.getSiblingFile (name + ".ttl") }; + } + + static Result prepareStream (FileOutputStream& stream) + { + if (const auto result = stream.getStatus(); ! result) + return result; + + stream.setPosition (0); + stream.truncate(); + return Result::ok(); + } + + static Result writeManifestTtl (AudioProcessor& proc, const File& libraryPath) + { + auto os = openStream (libraryPath, "manifest"); + + if (const auto result = prepareStream (os); ! result) + return result; + + os << "@prefix lv2: .\n" + "@prefix rdfs: .\n" + "@prefix pset: .\n" + "@prefix state: .\n" + "@prefix ui: .\n" + "@prefix xsd: .\n" + "\n" + "<" JucePlugin_LV2URI ">\n" + "\ta lv2:Plugin ;\n" + "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" + "\trdfs:seeAlso .\n"; + + if (proc.hasEditor()) + { + #if JUCE_MAC + #define JUCE_LV2_UI_KIND "CocoaUI" + #elif JUCE_WINDOWS + #define JUCE_LV2_UI_KIND "WindowsUI" + #elif JUCE_LINUX || JUCE_BSD + #define JUCE_LV2_UI_KIND "X11UI" + #else + #error "LV2 UI is unsupported on this platform" + #endif + + os << "\n" + "<" << JucePluginLV2UriUi << ">\n" + "\ta ui:" JUCE_LV2_UI_KIND " ;\n" + "\tlv2:binary <" << URL::addEscapeChars (libraryPath.getFileName(), false) << "> ;\n" + "\trdfs:seeAlso .\n" + "\n"; + } + + for (auto i = 0, end = proc.getNumPrograms(); i < end; ++i) + { + os << "<" << getPresetUri (i) << ">\n" + "\ta pset:Preset ;\n" + "\tlv2:appliesTo <" JucePlugin_LV2URI "> ;\n" + "\trdfs:label \"" << proc.getProgramName (i) << "\" ;\n" + "\tstate:state [ <" << JucePluginLV2UriProgram << "> \"" << i << "\"^^xsd:int ; ] .\n" + "\n"; + } + + return Result::ok(); + } + + static std::vector findAllSubgroupsDepthFirst (const AudioProcessorParameterGroup& group, + std::vector foundSoFar = {}) + { + foundSoFar.push_back (&group); + + for (auto* node : group) + { + if (auto* subgroup = node->getGroup()) + foundSoFar = findAllSubgroupsDepthFirst (*subgroup, std::move (foundSoFar)); + } + + return foundSoFar; + } + + using GroupSymbolMap = std::map; + + static String getFlattenedGroupSymbol (const AudioProcessorParameterGroup& group, String symbol = "") + { + if (auto* parent = group.getParent()) + return getFlattenedGroupSymbol (*parent, group.getID() + (symbol.isEmpty() ? "" : group.getSeparator() + symbol)); + + return symbol; + } + + static String getSymbolForGroup (const AudioProcessorParameterGroup& group) + { + const String allowedCharacters ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"); + const auto base = getFlattenedGroupSymbol (group); + + if (base.isEmpty()) + return {}; + + String copy; + + for (const auto character : base) + copy << String::charToString (allowedCharacters.containsChar (character) ? character : (juce_wchar) '_'); + + return "paramgroup_" + copy; + } + + static GroupSymbolMap getGroupsAndSymbols (const std::vector& groups) + { + std::set symbols; + GroupSymbolMap result; + + for (const auto& group : groups) + { + const auto symbol = [&] + { + const auto idealSymbol = getSymbolForGroup (*group); + + if (symbols.find (idealSymbol) == symbols.cend()) + return idealSymbol; + + for (auto i = 2; i < std::numeric_limits::max(); ++i) + { + const auto toTest = idealSymbol + "_" + String (i); + + if (symbols.find (toTest) == symbols.cend()) + return toTest; + } + + jassertfalse; + return String(); + }(); + + symbols.insert (symbol); + result.emplace (group, symbol); + } + + return result; + } + + template + static void visitAllParameters (const GroupSymbolMap& groups, Fn&& fn) + { + for (const auto& group : groups) + for (const auto* node : *group.first) + if (auto* param = node->getParameter()) + fn (group.second, *param); + } + + static Result writeDspTtl (AudioProcessor& proc, const File& libraryPath) + { + auto os = openStream (libraryPath, "dsp"); + + if (const auto result = prepareStream (os); ! result) + return result; + + os << "@prefix atom: .\n" + "@prefix bufs: .\n" + "@prefix doap: .\n" + "@prefix foaf: .\n" + "@prefix lv2: .\n" + "@prefix midi: .\n" + "@prefix opts: .\n" + "@prefix param: .\n" + "@prefix patch: .\n" + "@prefix pg: .\n" + "@prefix plug: <" JucePlugin_LV2URI << uriSeparator << "> .\n" + "@prefix pprop: .\n" + "@prefix rdfs: .\n" + "@prefix rdf: .\n" + "@prefix rsz: .\n" + "@prefix state: .\n" + "@prefix time: .\n" + "@prefix ui: .\n" + "@prefix units: .\n" + "@prefix urid: .\n" + "@prefix xsd: .\n" + "\n"; + + LegacyAudioParametersWrapper legacyParameters (proc, false); + + const auto allGroups = findAllSubgroupsDepthFirst (legacyParameters.getGroup()); + const auto groupsAndSymbols = getGroupsAndSymbols (allGroups); + + const auto parameterVisitor = [&] (const String& symbol, + const AudioProcessorParameter& param) + { + os << "plug:" << ParameterStorage::getIri (param) << "\n" + "\ta lv2:Parameter ;\n" + "\trdfs:label \"" << param.getName (1024) << "\" ;\n"; + + if (symbol.isNotEmpty()) + os << "\tpg:group plug:" << symbol << " ;\n"; + + os << "\trdfs:range atom:Float ;\n"; + + if (auto* rangedParam = dynamic_cast (¶m)) + { + os << "\tlv2:default " << rangedParam->convertFrom0to1 (rangedParam->getDefaultValue()) << " ;\n" + "\tlv2:minimum " << rangedParam->getNormalisableRange().start << " ;\n" + "\tlv2:maximum " << rangedParam->getNormalisableRange().end; + } + else + { + os << "\tlv2:default " << param.getDefaultValue() << " ;\n" + "\tlv2:minimum 0.0 ;\n" + "\tlv2:maximum 1.0"; + } + + // Avoid writing out loads of scale points for parameters with lots of steps + constexpr auto stepLimit = 1000; + const auto numSteps = param.getNumSteps(); + + if (param.isDiscrete() && 2 <= numSteps && numSteps < stepLimit) + { + os << "\t ;\n" + "\tlv2:portProperty lv2:enumeration " << (param.isBoolean() ? ", lv2:toggled " : "") << ";\n" + "\tlv2:scalePoint "; + + const auto maxIndex = numSteps - 1; + + for (int i = 0; i < numSteps; ++i) + { + const auto value = (float) i / (float) maxIndex; + const auto text = param.getText (value, 1024); + + os << (i != 0 ? ", " : "") << "[\n" + "\t\trdfs:label \"" << text << "\" ;\n" + "\t\trdf:value " << value << " ;\n" + "\t]"; + } + } + + os << " .\n\n"; + }; + + visitAllParameters (groupsAndSymbols, parameterVisitor); + + for (const auto& groupInfo : groupsAndSymbols) + { + if (groupInfo.second.isEmpty()) + continue; + + os << "plug:" << groupInfo.second << "\n" + "\ta pg:Group ;\n"; + + if (auto* parent = groupInfo.first->getParent()) + { + if (parent->getParent() != nullptr) + { + const auto it = groupsAndSymbols.find (parent); + + if (it != groupsAndSymbols.cend()) + os << "\tpg:subGroupOf plug:" << it->second << " ;\n"; + } + } + + os << "\tlv2:symbol \"" << groupInfo.second << "\" ;\n" + "\tlv2:name \"" << groupInfo.first->getName() << "\" .\n\n"; + } + + const auto getBaseBusName = [] (bool isInput) { return isInput ? "input_group_" : "output_group_"; }; + + for (const auto isInput : { true, false }) + { + const auto baseBusName = getBaseBusName (isInput); + const auto groupKind = isInput ? "InputGroup" : "OutputGroup"; + const auto busCount = proc.getBusCount (isInput); + + for (auto i = 0; i < busCount; ++i) + { + if (const auto* bus = proc.getBus (isInput, i)) + { + os << "plug:" << baseBusName << (i + 1) << "\n" + "\ta pg:" << groupKind << " ;\n" + "\tlv2:name \"" << bus->getName() << "\" ;\n" + "\tlv2:symbol \"" << baseBusName << (i + 1) << "\" .\n\n"; + } + } + } + + os << "<" JucePlugin_LV2URI ">\n"; + + if (proc.hasEditor()) + os << "\tui:ui <" << JucePluginLV2UriUi << "> ;\n"; + + const auto versionParts = StringArray::fromTokens (JucePlugin_VersionString, ".", ""); + + const auto getVersionOrZero = [&] (int indexFromBack) + { + const auto str = versionParts[versionParts.size() - indexFromBack]; + return str.isEmpty() ? 0 : str.getIntValue(); + }; + + const auto minorVersion = getVersionOrZero (2); + const auto microVersion = getVersionOrZero (1); + + os << "\ta " + #if JucePlugin_IsSynth + "lv2:InstrumentPlugin" + #else + "lv2:Plugin" + #endif + " ;\n" + "\tdoap:name \"" JucePlugin_Name "\" ;\n" + "\tdoap:description \"" JucePlugin_Desc "\" ;\n" + "\tlv2:minorVersion " << minorVersion << " ;\n" + "\tlv2:microVersion " << microVersion << " ;\n" + "\tdoap:maintainer [\n" + "\t\ta foaf:Person ;\n" + "\t\tfoaf:name \"" JucePlugin_Manufacturer "\" ;\n" + "\t\tfoaf:homepage <" JucePlugin_ManufacturerWebsite "> ;\n" + "\t\tfoaf:mbox <" JucePlugin_ManufacturerEmail "> ;\n" + "\t] ;\n" + "\tdoap:release [\n" + "\t\ta doap:Version ;\n" + "\t\tdoap:revision \"" JucePlugin_VersionString "\" ;\n" + "\t] ;\n" + "\tlv2:optionalFeature\n" + "\t\tlv2:hardRTCapable ;\n" + "\tlv2:extensionData\n" + "\t\tstate:interface ;\n" + "\tlv2:requiredFeature\n" + "\t\turid:map ,\n" + "\t\topts:options ,\n" + "\t\tbufs:boundedBlockLength ;\n"; + + for (const auto isInput : { true, false }) + { + const auto kind = isInput ? "mainInput" : "mainOutput"; + + if (proc.getBusCount (isInput) > 0) + os << "\tpg:" << kind << " plug:" << getBaseBusName (isInput) << "1 ;\n"; + } + + if (legacyParameters.size() != 0) + { + for (const auto header : { "writable", "readable" }) + { + os << "\tpatch:" << header; + + bool isFirst = true; + + for (const auto* param : legacyParameters) + { + os << (isFirst ? "" : " ,") << "\n\t\tplug:" << ParameterStorage::getIri (*param); + isFirst = false; + } + + os << " ;\n"; + } + } + + os << "\tlv2:port [\n"; + + const PortIndices indices (proc.getTotalNumInputChannels(), + proc.getTotalNumOutputChannels()); + + const auto designationMap = [&] + { + std::map result; + + for (const auto& pair : lv2_shared::channelDesignationMap) + result.emplace (pair.second, pair.first); + + return result; + }(); + + // TODO add support for specific audio group kinds + for (const auto isInput : { true, false }) + { + const auto baseBusName = getBaseBusName (isInput); + const auto portKind = isInput ? "InputPort" : "OutputPort"; + const auto portName = isInput ? "Audio In " : "Audio Out "; + const auto portSymbol = isInput ? "audio_in_" : "audio_out_"; + const auto busCount = proc.getBusCount (isInput); + + auto channelCounter = 0; + + for (auto busIndex = 0; busIndex < busCount; ++busIndex) + { + if (const auto* bus = proc.getBus (isInput, busIndex)) + { + const auto channelCount = bus->getNumberOfChannels(); + const auto optionalBus = ! bus->isEnabledByDefault(); + + for (auto channelIndex = 0; channelIndex < channelCount; ++channelIndex, ++channelCounter) + { + const auto portIndex = isInput ? indices.getPortIndexForAudioInput (channelCounter) + : indices.getPortIndexForAudioOutput (channelCounter); + + os << "\t\ta lv2:" << portKind << " , lv2:AudioPort ;\n" + "\t\tlv2:index " << portIndex << " ;\n" + "\t\tlv2:symbol \"" << portSymbol << (channelCounter + 1) << "\" ;\n" + "\t\tlv2:name \"" << portName << (channelCounter + 1) << "\" ;\n" + "\t\tpg:group plug:" << baseBusName << (busIndex + 1) << " ;\n"; + + if (optionalBus) + os << "\t\tlv2:portProperty lv2:connectionOptional ;\n"; + + const auto designation = bus->getCurrentLayout().getTypeOfChannel (channelIndex); + const auto it = designationMap.find (designation); + + if (it != designationMap.end()) + os << "\t\tlv2:designation <" << it->second << "> ;\n"; + + os << "\t] , [\n"; + } + } + } + } + + // In the event that the plugin decides to send all of its parameters in one go, + // we should ensure that the output buffer is large enough to accommodate, with some + // extra room for the sequence header, MIDI messages etc.. + const auto patchSetSizeBytes = 72; + const auto additionalSize = 8192; + const auto atomPortMinSize = proc.getParameters().size() * patchSetSizeBytes + additionalSize; + + os << "\t\ta lv2:InputPort , atom:AtomPort ;\n" + "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" + "\t\tatom:bufferType atom:Sequence ;\n" + "\t\tatom:supports\n"; + + #if ! JucePlugin_IsSynth && ! JucePlugin_IsMidiEffect + if (proc.acceptsMidi()) + #endif + os << "\t\t\tmidi:MidiEvent ,\n"; + + os << "\t\t\tpatch:Message ,\n" + "\t\t\ttime:Position ;\n" + "\t\tlv2:designation lv2:control ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqInput) << " ;\n" + "\t\tlv2:symbol \"in\" ;\n" + "\t\tlv2:name \"In\" ;\n" + "\t] , [\n" + "\t\ta lv2:OutputPort , atom:AtomPort ;\n" + "\t\trsz:minimumSize " << atomPortMinSize << " ;\n" + "\t\tatom:bufferType atom:Sequence ;\n" + "\t\tatom:supports\n"; + + #if ! JucePlugin_IsMidiEffect + if (proc.producesMidi()) + #endif + os << "\t\t\tmidi:MidiEvent ,\n"; + + os << "\t\t\tpatch:Message ;\n" + "\t\tlv2:designation lv2:control ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::seqOutput) << " ;\n" + "\t\tlv2:symbol \"out\" ;\n" + "\t\tlv2:name \"Out\" ;\n" + "\t] , [\n" + "\t\ta lv2:OutputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:latency ;\n" + "\t\tlv2:symbol \"latency\" ;\n" + "\t\tlv2:name \"Latency\" ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::latencyOutput) << " ;\n" + "\t\tlv2:portProperty lv2:reportsLatency , lv2:integer , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t\tunits:unit units:frame ;\n" + "\t] , [\n" + "\t\ta lv2:InputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:freeWheeling ;\n" + "\t\tlv2:symbol \"freeWheeling\" ;\n" + "\t\tlv2:name \"Free Wheeling\" ;\n" + "\t\tlv2:default 0.0 ;\n" + "\t\tlv2:minimum 0.0 ;\n" + "\t\tlv2:maximum 1.0 ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::freeWheelingInput) << " ;\n" + "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t] , [\n" + "\t\ta lv2:InputPort , lv2:ControlPort ;\n" + "\t\tlv2:designation lv2:enabled ;\n" + "\t\tlv2:symbol \"enabled\" ;\n" + "\t\tlv2:name \"Enabled\" ;\n" + "\t\tlv2:default 1.0 ;\n" + "\t\tlv2:minimum 0.0 ;\n" + "\t\tlv2:maximum 1.0 ;\n" + "\t\tlv2:index " << indices.getPortIndexFor (PortKind::enabledInput) << " ;\n" + "\t\tlv2:portProperty lv2:toggled , lv2:connectionOptional , pprop:notOnGUI ;\n" + "\t] ;\n" + "\topts:supportedOption\n" + "\t\tbufs:maxBlockLength .\n"; + + return Result::ok(); + } + + static Result writeUiTtl (AudioProcessor& proc, const File& libraryPath) + { + if (! proc.hasEditor()) + return Result::ok(); + + auto os = openStream (libraryPath, "ui"); + + if (const auto result = prepareStream (os); ! result) + return result; + + const auto editorInstance = rawToUniquePtr (proc.createEditor()); + const auto resizeFeatureString = editorInstance->isResizable() ? "ui:resize" : "ui:noUserResize"; + + os << "@prefix lv2: .\n" + "@prefix opts: .\n" + "@prefix param: .\n" + "@prefix ui: .\n" + "@prefix urid: .\n" + "\n" + "<" << JucePluginLV2UriUi << ">\n" + "\tlv2:extensionData\n" + #if JUCE_LINUX || JUCE_BSD + "\t\tui:idleInterface ,\n" + #endif + "\t\topts:interface ,\n" + "\t\tui:noUserResize ,\n" // resize and noUserResize are always present in the extension data array + "\t\tui:resize ;\n" + "\n" + "\tlv2:requiredFeature\n" + #if JUCE_LINUX || JUCE_BSD + "\t\tui:idleInterface ,\n" + #endif + "\t\turid:map ,\n" + "\t\tui:parent ,\n" + "\t\t ;\n" + "\n" + "\tlv2:optionalFeature\n" + "\t\t" << resizeFeatureString << " ,\n" + "\t\topts:interface ,\n" + "\t\topts:options ;\n\n" + "\topts:supportedOption\n" + "\t\tui:scaleFactor ,\n" + "\t\tparam:sampleRate .\n"; + + return Result::ok(); + } + + JUCE_LEAK_DETECTOR (RecallFeature) +}; + +//============================================================================== +LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index) +{ + if (index != 0) + return nullptr; + + static const LV2_Descriptor descriptor + { + JucePlugin_LV2URI, // TODO some constexpr check that this is a valid URI in terms of RFC 3986 + [] (const LV2_Descriptor*, + double sampleRate, + const char* pathToBundle, + const LV2_Feature* const* features) -> LV2_Handle + { + const auto* mapFeature = findMatchingFeatureData (features, LV2_URID__map); + + if (mapFeature == nullptr) + { + // The host doesn't provide the 'urid map' feature + jassertfalse; + return nullptr; + } + + const auto boundedBlockLength = hasFeature (features, LV2_BUF_SIZE__boundedBlockLength); + + if (! boundedBlockLength) + { + // The host doesn't provide the 'bounded block length' feature + jassertfalse; + return nullptr; + } + + const auto* options = findMatchingFeatureData (features, LV2_OPTIONS__options); + + if (options == nullptr) + { + // The host doesn't provide the 'options' feature + jassertfalse; + return nullptr; + } + + const lv2_shared::NumericAtomParser parser { *mapFeature }; + const auto blockLengthUrid = mapFeature->map (mapFeature->handle, LV2_BUF_SIZE__maxBlockLength); + const auto blockSize = parser.parseNumericOption (findMatchingOption (options, blockLengthUrid)); + + if (! blockSize.hasValue()) + { + // The host doesn't specify a maximum block size + jassertfalse; + return nullptr; + } + + return new LV2PluginInstance { sampleRate, *blockSize, pathToBundle, *mapFeature }; + }, + [] (LV2_Handle instance, uint32_t port, void* data) + { + static_cast (instance)->connect (port, data); + }, + [] (LV2_Handle instance) { static_cast (instance)->activate(); }, + [] (LV2_Handle instance, uint32_t sampleCount) + { + static_cast (instance)->run (sampleCount); + }, + [] (LV2_Handle instance) { static_cast (instance)->deactivate(); }, + [] (LV2_Handle instance) + { + JUCE_AUTORELEASEPOOL + { + delete static_cast (instance); + } + }, + [] (const char* uri) -> const void* + { + const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; + + static RecallFeature recallFeature; + + static LV2_State_Interface stateInterface + { + [] (LV2_Handle instance, + LV2_State_Store_Function store, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) -> LV2_State_Status + { + return static_cast (instance)->store (store, handle, flags, features); + }, + [] (LV2_Handle instance, + LV2_State_Retrieve_Function retrieve, + LV2_State_Handle handle, + uint32_t flags, + const LV2_Feature* const* features) -> LV2_State_Status + { + return static_cast (instance)->retrieve (retrieve, handle, flags, features); + } + }; + + static const LV2_Feature features[] { { JUCE_TURTLE_RECALL_URI, &recallFeature }, + { LV2_STATE__interface, &stateInterface } }; + + const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); + return it != std::end (features) ? it->data : nullptr; + } + }; + + return &descriptor; +} + +static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) +{ + if (options == nullptr || symap == nullptr) + return {}; + + const lv2_shared::NumericAtomParser parser { *symap }; + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto* scaleFactorOption = findMatchingOption (options, scaleFactorUrid); + return parser.parseNumericOption (scaleFactorOption); +} + +class LV2UIInstance : private Component, + private ComponentListener +{ +public: + LV2UIInstance (const char*, + const char*, + LV2UI_Write_Function writeFunctionIn, + LV2UI_Controller controllerIn, + LV2UI_Widget* widget, + LV2PluginInstance* pluginIn, + LV2UI_Widget parentIn, + const LV2_URID_Map* symapIn, + const LV2UI_Resize* resizeFeatureIn, + Optional scaleFactorIn) + : writeFunction (writeFunctionIn), + controller (controllerIn), + plugin (pluginIn), + parent (parentIn), + symap (symapIn), + resizeFeature (resizeFeatureIn), + scaleFactor (scaleFactorIn), + editor (plugin->createEditor()) + { + jassert (plugin != nullptr); + jassert (parent != nullptr); + jassert (editor != nullptr); + + if (editor == nullptr) + return; + + const auto bounds = getSizeToContainChild(); + setSize (bounds.getWidth(), bounds.getHeight()); + + addAndMakeVisible (*editor); + + setBroughtToFrontOnMouseClick (true); + setOpaque (true); + setVisible (false); + removeFromDesktop(); + addToDesktop (detail::PluginUtilities::getDesktopFlags (editor.get()), parent); + editor->addComponentListener (this); + + *widget = getWindowHandle(); + + setVisible (true); + + editor->setScaleFactor (getScaleFactor()); + requestResize(); + } + + ~LV2UIInstance() override + { + plugin->editorBeingDeleted (editor.get()); + } + + // This is called by the host when a parameter changes. + // We don't care, our UI will listen to the processor directly. + void portEvent (uint32_t, uint32_t, uint32_t, const void*) {} + + // Called when the host requests a resize + int resize (int width, int height) + { + const ScopedValueSetter scope (hostRequestedResize, true); + setSize (width, height); + return 0; + } + + // Called by the host to give us an opportunity to process UI events + void idleCallback() + { + #if JUCE_LINUX || JUCE_BSD + messageThread->processPendingEvents(); + #endif + } + + void resized() override + { + const ScopedValueSetter scope (hostRequestedResize, true); + + if (editor != nullptr) + { + const auto localArea = editor->getLocalArea (this, getLocalBounds()); + editor->setBoundsConstrained ({ localArea.getWidth(), localArea.getHeight() }); + } + } + + void paint (Graphics& g) override { g.fillAll (Colours::black); } + + uint32_t getOptions (LV2_Options_Option* options) + { + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + + for (auto* opt = options; opt->key != 0; ++opt) + { + if (opt->context != LV2_OPTIONS_INSTANCE || opt->subject != 0 || opt->key != scaleFactorUrid) + continue; + + if (scaleFactor.hasValue()) + { + opt->type = floatUrid; + opt->size = sizeof (float); + opt->value = &(*scaleFactor); + } + } + + return LV2_OPTIONS_SUCCESS; + } + + uint32_t setOptions (const LV2_Options_Option* options) + { + const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + + for (auto* opt = options; opt->key != 0; ++opt) + { + if (opt->context != LV2_OPTIONS_INSTANCE + || opt->subject != 0 + || opt->key != scaleFactorUrid + || opt->type != floatUrid + || opt->size != sizeof (float)) + { + continue; + } + + scaleFactor = *static_cast (opt->value); + updateScale(); + } + + return LV2_OPTIONS_SUCCESS; + } + +private: + void updateScale() + { + editor->setScaleFactor (getScaleFactor()); + requestResize(); + } + + Rectangle getSizeToContainChild() const + { + if (editor != nullptr) + return getLocalArea (editor.get(), editor->getLocalBounds()); + + return {}; + } + + float getScaleFactor() const noexcept + { + return scaleFactor.hasValue() ? *scaleFactor : 1.0f; + } + + void componentMovedOrResized (Component&, bool, bool wasResized) override + { + if (! hostRequestedResize && wasResized) + requestResize(); + } + + void write (uint32_t portIndex, uint32_t bufferSize, uint32_t portProtocol, const void* data) + { + writeFunction (controller, portIndex, bufferSize, portProtocol, data); + } + + void requestResize() + { + if (editor == nullptr) + return; + + const auto bounds = getSizeToContainChild(); + + if (resizeFeature == nullptr) + return; + + if (auto* fn = resizeFeature->ui_resize) + fn (resizeFeature->handle, bounds.getWidth(), bounds.getHeight()); + + setSize (bounds.getWidth(), bounds.getHeight()); + repaint(); + } + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + LV2UI_Write_Function writeFunction; + LV2UI_Controller controller; + LV2PluginInstance* plugin; + LV2UI_Widget parent; + const LV2_URID_Map* symap = nullptr; + const LV2UI_Resize* resizeFeature = nullptr; + Optional scaleFactor; + std::unique_ptr editor; + bool hostRequestedResize = false; + + JUCE_LEAK_DETECTOR (LV2UIInstance) +}; + +LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor (uint32_t index) +{ + if (index != 0) + return nullptr; + + static const LV2UI_Descriptor descriptor + { + JucePluginLV2UriUi.toRawUTF8(), // TODO some constexpr check that this is a valid URI in terms of RFC 3986 + [] (const LV2UI_Descriptor*, + const char* pluginUri, + const char* bundlePath, + LV2UI_Write_Function writeFunction, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) -> LV2UI_Handle + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + auto* plugin = findMatchingFeatureData (features, LV2_INSTANCE_ACCESS_URI); + + if (plugin == nullptr) + { + // No instance access + jassertfalse; + return nullptr; + } + + auto* parent = findMatchingFeatureData (features, LV2_UI__parent); + + if (parent == nullptr) + { + // No parent access + jassertfalse; + return nullptr; + } + + auto* resizeFeature = findMatchingFeatureData (features, LV2_UI__resize); + + const auto* symap = findMatchingFeatureData (features, LV2_URID__map); + const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData (features, LV2_OPTIONS__options)); + + return new LV2UIInstance { pluginUri, + bundlePath, + writeFunction, + controller, + widget, + plugin, + parent, + symap, + resizeFeature, + scaleFactor }; + }, + [] (LV2UI_Handle ui) + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + JUCE_AUTORELEASEPOOL + { + delete static_cast (ui); + } + }, + [] (LV2UI_Handle ui, uint32_t portIndex, uint32_t bufferSize, uint32_t format, const void* buffer) + { + JUCE_ASSERT_MESSAGE_THREAD + static_cast (ui)->portEvent (portIndex, bufferSize, format, buffer); + }, + [] (const char* uri) -> const void* + { + const auto uriMatches = [&] (const LV2_Feature& f) { return std::strcmp (f.URI, uri) == 0; }; + + static LV2UI_Resize resize { nullptr, [] (LV2UI_Feature_Handle handle, int width, int height) -> int + { + JUCE_ASSERT_MESSAGE_THREAD + return static_cast (handle)->resize (width, height); + } }; + + static LV2UI_Idle_Interface idle { [] (LV2UI_Handle handle) + { + static_cast (handle)->idleCallback(); + return 0; + } }; + + static LV2_Options_Interface options + { + [] (LV2_Handle handle, LV2_Options_Option* optionsIn) + { + return static_cast (handle)->getOptions (optionsIn); + }, + [] (LV2_Handle handle, const LV2_Options_Option* optionsIn) + { + return static_cast (handle)->setOptions (optionsIn); + } + }; + + // We'll always define noUserResize and idle in the extension data array, but we'll + // only declare them in the ui.ttl if the UI is actually non-resizable, or requires + // idle callbacks. + // Well-behaved hosts should check the ttl before trying to search the + // extension-data array. + static const LV2_Feature features[] { { LV2_UI__resize, &resize }, + { LV2_UI__noUserResize, nullptr }, + { LV2_UI__idleInterface, &idle }, + { LV2_OPTIONS__interface, &options } }; + + const auto it = std::find_if (std::begin (features), std::end (features), uriMatches); + return it != std::end (features) ? it->data : nullptr; + } + }; + + return &descriptor; +} + +} +} + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp index 78bf9ef..341845e 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp @@ -31,7 +31,152 @@ #error To compile AudioUnitv3 and/or Standalone plug-ins, you need to add the juce_audio_utils and juce_audio_devices modules! #endif -#include "Standalone/juce_StandaloneFilterApp.cpp" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +// You can set this flag in your build if you need to specify a different +// standalone JUCEApplication class for your app to use. If you don't +// set it then by default we'll just create a simple one as below. +#if ! JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP + +#include + +namespace juce +{ + +//============================================================================== +class StandaloneFilterApp : public JUCEApplication +{ +public: + StandaloneFilterApp() + { + PropertiesFile::Options options; + + options.applicationName = getApplicationName(); + options.filenameSuffix = ".settings"; + options.osxLibrarySubFolder = "Application Support"; + #if JUCE_LINUX || JUCE_BSD + options.folderName = "~/.config"; + #else + options.folderName = ""; + #endif + + appProperties.setStorageParameters (options); + } + + const String getApplicationName() override { return CharPointer_UTF8 (JucePlugin_Name); } + const String getApplicationVersion() override { return JucePlugin_VersionString; } + bool moreThanOneInstanceAllowed() override { return true; } + void anotherInstanceStarted (const String&) override {} + + virtual StandaloneFilterWindow* createWindow() + { + #ifdef JucePlugin_PreferredChannelConfigurations + StandalonePluginHolder::PluginInOuts channels[] = { JucePlugin_PreferredChannelConfigurations }; + #endif + + return new StandaloneFilterWindow (getApplicationName(), + LookAndFeel::getDefaultLookAndFeel().findColour (ResizableWindow::backgroundColourId), + appProperties.getUserSettings(), + false, {}, nullptr + #ifdef JucePlugin_PreferredChannelConfigurations + , juce::Array (channels, juce::numElementsInArray (channels)) + #else + , {} + #endif + #if JUCE_DONT_AUTO_OPEN_MIDI_DEVICES_ON_MOBILE + , false + #endif + ); + } + + //============================================================================== + void initialise (const String&) override + { + mainWindow.reset (createWindow()); + + #if JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE + Desktop::getInstance().setKioskModeComponent (mainWindow.get(), false); + #endif + + mainWindow->setVisible (true); + } + + void shutdown() override + { + mainWindow = nullptr; + appProperties.saveIfNeeded(); + } + + //============================================================================== + void systemRequestedQuit() override + { + if (mainWindow.get() != nullptr) + mainWindow->pluginHolder->savePluginState(); + + if (ModalComponentManager::getInstance()->cancelAllModalComponents()) + { + Timer::callAfterDelay (100, []() + { + if (auto app = JUCEApplicationBase::getInstance()) + app->systemRequestedQuit(); + }); + } + else + { + quit(); + } + } + +protected: + ApplicationProperties appProperties; + std::unique_ptr mainWindow; +}; + +} // namespace juce + +#if JucePlugin_Build_Standalone && JUCE_IOS + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +using namespace juce; + +bool JUCE_CALLTYPE juce_isInterAppAudioConnected() +{ + if (auto holder = StandalonePluginHolder::getInstance()) + return holder->isInterAppAudioConnected(); + + return false; +} + +void JUCE_CALLTYPE juce_switchToHostApplication() +{ + if (auto holder = StandalonePluginHolder::getInstance()) + holder->switchToHostApplication(); +} + +Image JUCE_CALLTYPE juce_getIAAHostIcon (int size) +{ + if (auto holder = StandalonePluginHolder::getInstance()) + return holder->getIAAHostIcon (size); + + return Image(); +} + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +#endif + +#endif #if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP extern juce::JUCEApplicationBase* juce_CreateApplication(); diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp index 176697c..26106b4 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_Unity.cpp @@ -23,4 +23,755 @@ ============================================================================== */ -#include "Unity/juce_Unity_Wrapper.cpp" +#include + +#if JucePlugin_Build_Unity + +#include +#include + +#if JUCE_WINDOWS + #include +#endif + +#include + +#include + +//============================================================================== +namespace juce +{ + +typedef ComponentPeer* (*createUnityPeerFunctionType) (Component&); +extern createUnityPeerFunctionType juce_createUnityPeerFn; + +//============================================================================== +class UnityPeer : public ComponentPeer, + public AsyncUpdater +{ +public: + UnityPeer (Component& ed) + : ComponentPeer (ed, 0), + mouseWatcher (*this) + { + getEditor().setResizable (false, false); + } + + //============================================================================== + Rectangle getBounds() const override { return bounds; } + Point localToGlobal (Point relativePosition) override { return relativePosition + getBounds().getPosition().toFloat(); } + Point globalToLocal (Point screenPosition) override { return screenPosition - getBounds().getPosition().toFloat(); } + + using ComponentPeer::localToGlobal; + using ComponentPeer::globalToLocal; + + StringArray getAvailableRenderingEngines() override { return StringArray ("Software Renderer"); } + + void setBounds (const Rectangle& newBounds, bool) override + { + bounds = newBounds; + mouseWatcher.setBoundsToWatch (bounds); + } + + bool contains (Point localPos, bool) const override + { + if (isPositiveAndBelow (localPos.getX(), getBounds().getWidth()) + && isPositiveAndBelow (localPos.getY(), getBounds().getHeight())) + return true; + + return false; + } + + void handleAsyncUpdate() override + { + fillPixels(); + } + + //============================================================================== + AudioProcessorEditor& getEditor() { return *dynamic_cast (&getComponent()); } + + void setPixelDataHandle (uint8* handle, int width, int height) + { + pixelData = handle; + + textureWidth = width; + textureHeight = height; + + renderImage = Image (new UnityBitmapImage (pixelData, width, height)); + } + + // N.B. This is NOT an efficient way to do this and you shouldn't use this method in your own code. + // It works for our purposes here but a much more efficient way would be to use a GL texture. + void fillPixels() + { + if (pixelData == nullptr) + return; + + LowLevelGraphicsSoftwareRenderer renderer (renderImage); + renderer.addTransform (AffineTransform::verticalFlip ((float) getComponent().getHeight())); + + handlePaint (renderer); + + for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) + { + auto r = pixelData[i + 2]; + auto g = pixelData[i + 1]; + auto b = pixelData[i + 0]; + + pixelData[i + 0] = r; + pixelData[i + 1] = g; + pixelData[i + 2] = b; + } + } + + void forwardMouseEvent (Point position, ModifierKeys mods) + { + ModifierKeys::currentModifiers = mods; + + handleMouseEvent (juce::MouseInputSource::mouse, position, mods, juce::MouseInputSource::defaultPressure, + juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); + } + + void forwardKeyPress (int code, String name, ModifierKeys mods) + { + ModifierKeys::currentModifiers = mods; + + handleKeyPress (getKeyPress (code, name)); + } + +private: + //============================================================================== + struct UnityBitmapImage : public ImagePixelData + { + UnityBitmapImage (uint8* data, int w, int h) + : ImagePixelData (Image::PixelFormat::ARGB, w, h), + imageData (data), + lineStride (width * pixelStride) + { + } + + std::unique_ptr createType() const override + { + return std::make_unique(); + } + + std::unique_ptr createLowLevelContext() override + { + return std::make_unique (Image (this)); + } + + void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, [[maybe_unused]] Image::BitmapData::ReadWriteMode mode) override + { + const auto offset = (size_t) x * (size_t) pixelStride + (size_t) y * (size_t) lineStride; + bitmap.data = imageData + offset; + bitmap.size = (size_t) (lineStride * height) - offset; + bitmap.pixelFormat = pixelFormat; + bitmap.lineStride = lineStride; + bitmap.pixelStride = pixelStride; + } + + ImagePixelData::Ptr clone() override + { + auto im = new UnityBitmapImage (imageData, width, height); + + for (int i = 0; i < height; ++i) + memcpy (im->imageData + i * lineStride, imageData + i * lineStride, (size_t) lineStride); + + return im; + } + + uint8* imageData; + int pixelStride = 4, lineStride; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityBitmapImage) + }; + + //============================================================================== + struct MouseWatcher : public Timer + { + MouseWatcher (ComponentPeer& o) : owner (o) {} + + void timerCallback() override + { + auto pos = Desktop::getMousePosition(); + + if (boundsToWatch.contains (pos) && pos != lastMousePos) + { + auto ms = Desktop::getInstance().getMainMouseSource(); + + if (! ms.getCurrentModifiers().isLeftButtonDown()) + owner.handleMouseEvent (juce::MouseInputSource::mouse, owner.globalToLocal (pos.toFloat()), {}, + juce::MouseInputSource::defaultPressure, juce::MouseInputSource::defaultOrientation, juce::Time::currentTimeMillis()); + + lastMousePos = pos; + } + + } + + void setBoundsToWatch (Rectangle b) + { + if (boundsToWatch != b) + boundsToWatch = b; + + startTimer (250); + } + + ComponentPeer& owner; + Rectangle boundsToWatch; + Point lastMousePos; + }; + + //============================================================================== + KeyPress getKeyPress (int keyCode, String name) + { + if (keyCode >= 32 && keyCode <= 64) + return { keyCode, ModifierKeys::currentModifiers, juce::juce_wchar (keyCode) }; + + if (keyCode >= 91 && keyCode <= 122) + return { keyCode, ModifierKeys::currentModifiers, name[0] }; + + if (keyCode >= 256 && keyCode <= 265) + return { juce::KeyPress::numberPad0 + (keyCode - 256), ModifierKeys::currentModifiers, juce::String (keyCode - 256).getCharPointer()[0] }; + + if (keyCode == 8) return { juce::KeyPress::backspaceKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 127) return { juce::KeyPress::deleteKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 9) return { juce::KeyPress::tabKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 13) return { juce::KeyPress::returnKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 27) return { juce::KeyPress::escapeKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 32) return { juce::KeyPress::spaceKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 266) return { juce::KeyPress::numberPadDecimalPoint, ModifierKeys::currentModifiers, {} }; + if (keyCode == 267) return { juce::KeyPress::numberPadDivide, ModifierKeys::currentModifiers, {} }; + if (keyCode == 268) return { juce::KeyPress::numberPadMultiply, ModifierKeys::currentModifiers, {} }; + if (keyCode == 269) return { juce::KeyPress::numberPadSubtract, ModifierKeys::currentModifiers, {} }; + if (keyCode == 270) return { juce::KeyPress::numberPadAdd, ModifierKeys::currentModifiers, {} }; + if (keyCode == 272) return { juce::KeyPress::numberPadEquals, ModifierKeys::currentModifiers, {} }; + if (keyCode == 273) return { juce::KeyPress::upKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 274) return { juce::KeyPress::downKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 275) return { juce::KeyPress::rightKey, ModifierKeys::currentModifiers, {} }; + if (keyCode == 276) return { juce::KeyPress::leftKey, ModifierKeys::currentModifiers, {} }; + + return {}; + } + + //============================================================================== + Rectangle bounds; + MouseWatcher mouseWatcher; + + uint8* pixelData = nullptr; + int textureWidth, textureHeight; + Image renderImage; + + //============================================================================== + void setMinimised (bool) override {} + bool isMinimised() const override { return false; } + void setFullScreen (bool) override {} + bool isFullScreen() const override { return false; } + bool setAlwaysOnTop (bool) override { return false; } + void toFront (bool) override {} + void toBehind (ComponentPeer*) override {} + bool isFocused() const override { return true; } + void grabFocus() override {} + void* getNativeHandle() const override { return nullptr; } + OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } + BorderSize getFrameSize() const override { return {}; } + void setVisible (bool) override {} + void setTitle (const String&) override {} + void setIcon (const Image&) override {} + void textInputRequired (Point, TextInputTarget&) override {} + void setAlpha (float) override {} + void performAnyPendingRepaintsNow() override {} + void repaint (const Rectangle&) override {} + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UnityPeer) +}; + +static ComponentPeer* createUnityPeer (Component& c) { return new UnityPeer (c); } + +//============================================================================== +class AudioProcessorUnityWrapper +{ +public: + AudioProcessorUnityWrapper (bool isTemporary) + { + detail::RunningInUnity::state = true; + pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_Unity); + + if (! isTemporary && pluginInstance->hasEditor()) + { + pluginInstanceEditor.reset (pluginInstance->createEditorIfNeeded()); + pluginInstanceEditor->setVisible (true); + detail::PluginUtilities::addToDesktop (*pluginInstanceEditor, nullptr); + } + + juceParameters.update (*pluginInstance, false); + } + + ~AudioProcessorUnityWrapper() + { + if (pluginInstanceEditor != nullptr) + { + pluginInstanceEditor->removeFromDesktop(); + + PopupMenu::dismissAllActiveMenus(); + pluginInstanceEditor->processor.editorBeingDeleted (pluginInstanceEditor.get()); + pluginInstanceEditor = nullptr; + } + } + + void create (UnityAudioEffectState* state) + { + // only supported in Unity plugin API > 1.0 + if (state->structSize >= sizeof (UnityAudioEffectState)) + samplesPerBlock = static_cast (state->dspBufferSize); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + const int numConfigs = sizeof (configs) / sizeof (short[2]); + + jassertquiet (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + + pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], state->sampleRate, samplesPerBlock); + #else + pluginInstance->setRateAndBufferSizeDetails (state->sampleRate, samplesPerBlock); + #endif + + pluginInstance->prepareToPlay (state->sampleRate, samplesPerBlock); + + scratchBuffer.setSize (jmax (pluginInstance->getTotalNumInputChannels(), pluginInstance->getTotalNumOutputChannels()), samplesPerBlock); + } + + void release() + { + pluginInstance->releaseResources(); + } + + void reset() + { + pluginInstance->reset(); + } + + void process (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + // If the plugin has a bypass parameter, set it to the current bypass state + if (auto* param = pluginInstance->getBypassParameter()) + if (isBypassed != (param->getValue() >= 0.5f)) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + + for (int pos = 0; pos < bufferSize;) + { + auto max = jmin (bufferSize - pos, samplesPerBlock); + processBuffers (inBuffer + (pos * numInChannels), outBuffer + (pos * numOutChannels), max, numInChannels, numOutChannels, isBypassed); + + pos += max; + } + } + + void declareParameters (UnityAudioEffectDefinition& definition) + { + static std::unique_ptr parametersPtr; + static int numParams = 0; + + if (parametersPtr == nullptr) + { + numParams = (int) juceParameters.size(); + + parametersPtr.reset (static_cast (std::calloc (static_cast (numParams), + sizeof (UnityAudioParameterDefinition)))); + + parameterDescriptions.clear(); + + for (int i = 0; i < numParams; ++i) + { + auto* parameter = juceParameters.getParamForIndex (i); + auto& paramDef = parametersPtr.get()[i]; + + const auto nameLength = (size_t) numElementsInArray (paramDef.name); + const auto unitLength = (size_t) numElementsInArray (paramDef.unit); + + parameter->getName ((int) nameLength - 1).copyToUTF8 (paramDef.name, nameLength); + + if (parameter->getLabel().isNotEmpty()) + parameter->getLabel().copyToUTF8 (paramDef.unit, unitLength); + + parameterDescriptions.add (parameter->getName (15)); + paramDef.description = parameterDescriptions[i].toRawUTF8(); + + paramDef.defaultVal = parameter->getDefaultValue(); + paramDef.min = 0.0f; + paramDef.max = 1.0f; + paramDef.displayScale = 1.0f; + paramDef.displayExponent = 1.0f; + } + } + + definition.numParameters = static_cast (numParams); + definition.parameterDefintions = parametersPtr.get(); + } + + void setParameter (int index, float value) { juceParameters.getParamForIndex (index)->setValueNotifyingHost (value); } + float getParameter (int index) const noexcept { return juceParameters.getParamForIndex (index)->getValue(); } + + String getParameterString (int index) const noexcept + { + auto* param = juceParameters.getParamForIndex (index); + return param->getText (param->getValue(), 16); + } + + int getNumInputChannels() const noexcept { return pluginInstance->getTotalNumInputChannels(); } + int getNumOutputChannels() const noexcept { return pluginInstance->getTotalNumOutputChannels(); } + + bool hasEditor() const noexcept { return pluginInstance->hasEditor(); } + + UnityPeer& getEditorPeer() const + { + auto* peer = dynamic_cast (pluginInstanceEditor->getPeer()); + + jassert (peer != nullptr); + return *peer; + } + +private: + //============================================================================== + void processBuffers (float* inBuffer, float* outBuffer, int bufferSize, int numInChannels, int numOutChannels, bool isBypassed) + { + int ch; + for (ch = 0; ch < numInChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + DstSampleType dstData (scratchBuffer.getWritePointer (ch)); + SrcSampleType srcData (inBuffer + ch, numInChannels); + dstData.convertSamples (srcData, bufferSize); + } + + for (; ch < numOutChannels; ++ch) + scratchBuffer.clear (ch, 0, bufferSize); + + { + const ScopedLock sl (pluginInstance->getCallbackLock()); + + if (pluginInstance->isSuspended()) + { + scratchBuffer.clear(); + } + else + { + MidiBuffer mb; + + if (isBypassed && pluginInstance->getBypassParameter() == nullptr) + pluginInstance->processBlockBypassed (scratchBuffer, mb); + else + pluginInstance->processBlock (scratchBuffer, mb); + } + } + + for (ch = 0; ch < numOutChannels; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + DstSampleType dstData (outBuffer + ch, numOutChannels); + SrcSampleType srcData (scratchBuffer.getReadPointer (ch)); + dstData.convertSamples (srcData, bufferSize); + } + } + + //============================================================================== + std::unique_ptr pluginInstance; + std::unique_ptr pluginInstanceEditor; + + int samplesPerBlock = 1024; + StringArray parameterDescriptions; + + AudioBuffer scratchBuffer; + + LegacyAudioParametersWrapper juceParameters; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorUnityWrapper) +}; + +//============================================================================== +static HashMap& getWrapperMap() +{ + static HashMap wrapperMap; + return wrapperMap; +} + +static void onWrapperCreation (AudioProcessorUnityWrapper* wrapperToAdd) +{ + getWrapperMap().set (std::abs (Random::getSystemRandom().nextInt (65536)), wrapperToAdd); +} + +static void onWrapperDeletion (AudioProcessorUnityWrapper* wrapperToRemove) +{ + getWrapperMap().removeValue (wrapperToRemove); +} + +//============================================================================== +static UnityAudioEffectDefinition getEffectDefinition() +{ + const auto wrapper = std::make_unique (true); + const String originalName { JucePlugin_Name }; + const auto name = (! originalName.startsWithIgnoreCase ("audioplugin") ? "audioplugin_" : "") + originalName; + + UnityAudioEffectDefinition result{}; + name.copyToUTF8 (result.name, (size_t) numElementsInArray (result.name)); + + result.structSize = sizeof (UnityAudioEffectDefinition); + result.parameterStructSize = sizeof (UnityAudioParameterDefinition); + + result.apiVersion = UNITY_AUDIO_PLUGIN_API_VERSION; + result.pluginVersion = JucePlugin_VersionCode; + + // effects must set this to 0, generators > 0 + result.channels = (wrapper->getNumInputChannels() != 0 ? 0 + : static_cast (wrapper->getNumOutputChannels())); + + wrapper->declareParameters (result); + + result.create = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = new AudioProcessorUnityWrapper (false); + pluginInstance->create (state); + + state->effectData = pluginInstance; + + onWrapperCreation (pluginInstance); + + return 0; + }; + + result.release = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->release(); + + onWrapperDeletion (pluginInstance); + delete pluginInstance; + + if (getWrapperMap().size() == 0) + shutdownJuce_GUI(); + + return 0; + }; + + result.reset = [] (UnityAudioEffectState* state) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->reset(); + + return 0; + }; + + result.setPosition = [] (UnityAudioEffectState* state, unsigned int pos) + { + ignoreUnused (state, pos); + return 0; + }; + + result.process = [] (UnityAudioEffectState* state, + float* inBuffer, + float* outBuffer, + unsigned int bufferSize, + int numInChannels, + int numOutChannels) + { + auto* pluginInstance = state->getEffectData(); + + if (pluginInstance != nullptr) + { + auto isPlaying = ((state->flags & stateIsPlaying) != 0); + auto isMuted = ((state->flags & stateIsMuted) != 0); + auto isPaused = ((state->flags & stateIsPaused) != 0); + + const auto bypassed = ! isPlaying || (isMuted || isPaused); + pluginInstance->process (inBuffer, outBuffer, static_cast (bufferSize), numInChannels, numOutChannels, bypassed); + } + else + { + FloatVectorOperations::clear (outBuffer, static_cast (bufferSize) * numOutChannels); + } + + return 0; + }; + + result.setFloatParameter = [] (UnityAudioEffectState* state, int index, float value) + { + auto* pluginInstance = state->getEffectData(); + pluginInstance->setParameter (index, value); + + return 0; + }; + + result.getFloatParameter = [] (UnityAudioEffectState* state, int index, float* value, char* valueStr) + { + auto* pluginInstance = state->getEffectData(); + *value = pluginInstance->getParameter (index); + + pluginInstance->getParameterString (index).copyToUTF8 (valueStr, 15); + + return 0; + }; + + result.getFloatBuffer = [] (UnityAudioEffectState* state, const char* kind, float* buffer, int numSamples) + { + ignoreUnused (numSamples); + + const StringRef kindStr { kind }; + + if (kindStr == StringRef ("Editor")) + { + auto* pluginInstance = state->getEffectData(); + + buffer[0] = pluginInstance->hasEditor() ? 1.0f : 0.0f; + } + else if (kindStr == StringRef ("ID")) + { + auto* pluginInstance = state->getEffectData(); + + for (HashMap::Iterator i (getWrapperMap()); i.next();) + { + if (i.getValue() == pluginInstance) + { + buffer[0] = (float) i.getKey(); + break; + } + } + + return 0; + } + else if (kindStr == StringRef ("Size")) + { + auto* pluginInstance = state->getEffectData(); + + auto& editor = pluginInstance->getEditorPeer().getEditor(); + + buffer[0] = (float) editor.getBounds().getWidth(); + buffer[1] = (float) editor.getBounds().getHeight(); + buffer[2] = (float) editor.getConstrainer()->getMinimumWidth(); + buffer[3] = (float) editor.getConstrainer()->getMinimumHeight(); + buffer[4] = (float) editor.getConstrainer()->getMaximumWidth(); + buffer[5] = (float) editor.getConstrainer()->getMaximumHeight(); + } + + return 0; + }; + + return result; +} + +} // namespace juce + +// From reading the example code, it seems that the triple indirection indicates +// an out-value of an array of pointers. That is, after calling this function, definitionsPtr +// should point to a pre-existing/static array of pointer-to-effect-definition. +UNITY_INTERFACE_EXPORT int UNITY_INTERFACE_API UnityGetAudioEffectDefinitions (UnityAudioEffectDefinition*** definitionsPtr) +{ + if (juce::getWrapperMap().size() == 0) + juce::initialiseJuce_GUI(); + + static std::once_flag flag; + std::call_once (flag, [] { juce::juce_createUnityPeerFn = juce::createUnityPeer; }); + + static auto definition = juce::getEffectDefinition(); + static UnityAudioEffectDefinition* definitions[] { &definition }; + *definitionsPtr = definitions; + + return 1; +} + +//============================================================================== +static juce::ModifierKeys unityModifiersToJUCE (UnityEventModifiers mods, bool mouseDown, int mouseButton = -1) +{ + int flags = 0; + + if (mouseDown) + { + if (mouseButton == 0) + flags |= juce::ModifierKeys::leftButtonModifier; + else if (mouseButton == 1) + flags |= juce::ModifierKeys::rightButtonModifier; + else if (mouseButton == 2) + flags |= juce::ModifierKeys::middleButtonModifier; + } + + if (mods == 0) + return flags; + + if ((mods & UnityEventModifiers::shift) != 0) flags |= juce::ModifierKeys::shiftModifier; + if ((mods & UnityEventModifiers::control) != 0) flags |= juce::ModifierKeys::ctrlModifier; + if ((mods & UnityEventModifiers::alt) != 0) flags |= juce::ModifierKeys::altModifier; + if ((mods & UnityEventModifiers::command) != 0) flags |= juce::ModifierKeys::commandModifier; + + return { flags }; +} + +//============================================================================== +static juce::AudioProcessorUnityWrapper* getWrapperChecked (int id) +{ + auto* wrapper = juce::getWrapperMap()[id]; + jassert (wrapper != nullptr); + + return wrapper; +} + +//============================================================================== +static void UNITY_INTERFACE_API onRenderEvent (int id) +{ + getWrapperChecked (id)->getEditorPeer().triggerAsyncUpdate(); +} + +UNITY_INTERFACE_EXPORT renderCallback UNITY_INTERFACE_API getRenderCallback() +{ + return onRenderEvent; +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityInitialiseTexture (int id, void* data, int w, int h) +{ + getWrapperChecked (id)->getEditorPeer().setPixelDataHandle (reinterpret_cast (data), w, h); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDown (int id, float x, float y, UnityEventModifiers unityMods, int button) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseDrag (int id, float x, float y, UnityEventModifiers unityMods, int button) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, true, button)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityMouseUp (int id, float x, float y, UnityEventModifiers unityMods) +{ + getWrapperChecked (id)->getEditorPeer().forwardMouseEvent ({ x, y }, unityModifiersToJUCE (unityMods, false)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unityKeyEvent (int id, int code, UnityEventModifiers mods, const char* name) +{ + getWrapperChecked (id)->getEditorPeer().forwardKeyPress (code, name, unityModifiersToJUCE (mods, false)); +} + +UNITY_INTERFACE_EXPORT void UNITY_INTERFACE_API unitySetScreenBounds (int id, float x, float y, float w, float h) +{ + getWrapperChecked (id)->getEditorPeer().getEditor().setBounds ({ (int) x, (int) y, (int) w, (int) h }); +} + +//============================================================================== +#if JUCE_WINDOWS + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + + extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) + { + if (reason == DLL_PROCESS_ATTACH) + juce::Process::setCurrentModuleInstanceHandle (instance); + + return true; + } + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index 9b95758..9f743e6 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -23,4 +23,2186 @@ ============================================================================== */ -#include "VST/juce_VST_Wrapper.cpp" +#include +#include +#include + +#if JucePlugin_Build_VST + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) + +#include +#include + +#if JucePlugin_VersionCode < 0x010000 // Major < 0 + + #if (JucePlugin_VersionCode & 0x00FF00) > (9 * 0x100) // check if Minor number exceeds 9 + JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'minor' exceeding 9") + #endif + + #if (JucePlugin_VersionCode & 0xFF) > 9 // check if Bugfix number exceeds 9 + JUCE_COMPILER_WARNING ("When version has 'major' = 0, VST2 has trouble displaying 'bugfix' exceeding 9") + #endif + +#elif JucePlugin_VersionCode >= 0x650000 // Major >= 101 + + #if (JucePlugin_VersionCode & 0x00FF00) > (99 * 0x100) // check if Minor number exceeds 99 + JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'minor' exceeding 99") + #endif + + #if (JucePlugin_VersionCode & 0xFF) > 99 // check if Bugfix number exceeds 99 + JUCE_COMPILER_WARNING ("When version has 'major' > 100, VST2 has trouble displaying 'bugfix' exceeding 99") + #endif + +#endif + +#ifdef PRAGMA_ALIGN_SUPPORTED + #undef PRAGMA_ALIGN_SUPPORTED + #define PRAGMA_ALIGN_SUPPORTED 1 +#endif + +#if ! JUCE_MSVC && ! defined (__cdecl) + #define __cdecl +#endif + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wconversion", + "-Wshadow", + "-Wdeprecated-register", + "-Wdeprecated-declarations", + "-Wunused-parameter", + "-Wdeprecated-writable-strings", + "-Wnon-virtual-dtor", + "-Wzero-as-null-pointer-constant") +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) + +#define VST_FORCE_DEPRECATED 0 + +namespace Vst2 +{ +// If the following files cannot be found then you are probably trying to build +// a VST2 plug-in or a VST2-compatible VST3 plug-in. To do this you must have a +// VST2 SDK in your header search paths or use the "VST (Legacy) SDK Folder" +// field in the Projucer. The VST2 SDK can be obtained from the +// vstsdk3610_11_06_2018_build_37 (or older) VST3 SDK or JUCE version 5.3.2. You +// also need a VST2 license from Steinberg to distribute VST2 plug-ins. +#include "pluginterfaces/vst2.x/aeffect.h" +#include "pluginterfaces/vst2.x/aeffectx.h" +} + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +//============================================================================== +#if JUCE_MSVC + #pragma pack (push, 8) +#endif + +#define JUCE_VSTINTERFACE_H_INCLUDED 1 +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include + +using namespace juce; + +#include +#include +#include + +#include +#include + +#ifdef JUCE_MSVC + #pragma pack (pop) +#endif + +#undef MemoryBlock + +class JuceVSTWrapper; +static bool recursionCheck = false; + +namespace juce +{ + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + JUCE_API double getScaleFactorForWindow (HWND); + #endif +} + +//============================================================================== +#if JUCE_WINDOWS + +namespace +{ + // Returns the actual container window, unlike GetParent, which can also return a separate owner window. + static HWND getWindowParent (HWND w) noexcept { return GetAncestor (w, GA_PARENT); } + + static HWND findMDIParentOf (HWND w) + { + const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); + + while (w != nullptr) + { + auto parent = getWindowParent (w); + + if (parent == nullptr) + break; + + TCHAR windowType[32] = { 0 }; + GetClassName (parent, windowType, 31); + + if (String (windowType).equalsIgnoreCase ("MDIClient")) + return parent; + + RECT windowPos, parentPos; + GetWindowRect (w, &windowPos); + GetWindowRect (parent, &parentPos); + + auto dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); + auto dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); + + if (dw > 100 || dh > 100) + break; + + w = parent; + + if (dw == 2 * frameThickness) + break; + } + + return w; + } + + static int numActivePlugins = 0; + static bool messageThreadIsDefinitelyCorrect = false; +} + +#endif + +//============================================================================== +// Ableton Live host specific commands +struct AbletonLiveHostSpecific +{ + enum + { + KCantBeSuspended = (1 << 2) + }; + + uint32 magic; // 'AbLi' + int cmd; // 5 = realtime properties + size_t commandSize; // sizeof (int) + int flags; // KCantBeSuspended = (1 << 2) +}; + +//============================================================================== +/** + This is an AudioEffectX object that holds and wraps our AudioProcessor... +*/ +class JuceVSTWrapper : public AudioProcessorListener, + public AudioPlayHead, + private Timer, + private AudioProcessorParameter::Listener +{ +private: + //============================================================================== + template + struct VstTempBuffers + { + VstTempBuffers() {} + ~VstTempBuffers() { release(); } + + void release() noexcept + { + for (auto* c : tempChannels) + delete[] c; + + tempChannels.clear(); + } + + HeapBlock channels; + Array tempChannels; // see note in processReplacing() + juce::AudioBuffer processTempBuffer; + }; + + /** Use the same names as the VST SDK. */ + struct VstOpCodeArguments + { + int32 index; + pointer_sized_int value; + void* ptr; + float opt; + }; + +public: + //============================================================================== + JuceVSTWrapper (Vst2::audioMasterCallback cb, std::unique_ptr af) + : hostCallback (cb), + processor (std::move (af)) + { + inParameterChangedCallback = false; + + // VST-2 does not support disabling buses: so always enable all of them + processor->enableAllBuses(); + + findMaxTotalChannels (maxNumInChannels, maxNumOutChannels); + + // You must at least have some channels + jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); + + if (processor->isMidiEffect()) + maxNumInChannels = maxNumOutChannels = 2; + + #ifdef JucePlugin_PreferredChannelConfigurations + processor->setPlayConfigDetails (maxNumInChannels, maxNumOutChannels, 44100.0, 1024); + #endif + + processor->setRateAndBufferSizeDetails (0, 0); + processor->setPlayHead (this); + processor->addListener (this); + + if (auto* juceParam = processor->getBypassParameter()) + juceParam->addListener (this); + + juceParameters.update (*processor, false); + + memset (&vstEffect, 0, sizeof (vstEffect)); + vstEffect.magic = 0x56737450 /* 'VstP' */; + vstEffect.dispatcher = (Vst2::AEffectDispatcherProc) dispatcherCB; + vstEffect.process = nullptr; + vstEffect.setParameter = (Vst2::AEffectSetParameterProc) setParameterCB; + vstEffect.getParameter = (Vst2::AEffectGetParameterProc) getParameterCB; + vstEffect.numPrograms = jmax (1, processor->getNumPrograms()); + vstEffect.numParams = juceParameters.getNumParameters(); + vstEffect.numInputs = maxNumInChannels; + vstEffect.numOutputs = maxNumOutChannels; + vstEffect.initialDelay = processor->getLatencySamples(); + vstEffect.object = this; + vstEffect.uniqueID = JucePlugin_VSTUniqueID; + + #ifdef JucePlugin_VSTChunkStructureVersion + vstEffect.version = JucePlugin_VSTChunkStructureVersion; + #else + vstEffect.version = JucePlugin_VersionCode; + #endif + + vstEffect.processReplacing = (Vst2::AEffectProcessProc) processReplacingCB; + vstEffect.processDoubleReplacing = (Vst2::AEffectProcessDoubleProc) processDoubleReplacingCB; + + vstEffect.flags |= Vst2::effFlagsHasEditor; + + vstEffect.flags |= Vst2::effFlagsCanReplacing; + if (processor->supportsDoublePrecisionProcessing()) + vstEffect.flags |= Vst2::effFlagsCanDoubleReplacing; + + vstEffect.flags |= Vst2::effFlagsProgramChunks; + + #if JucePlugin_IsSynth + vstEffect.flags |= Vst2::effFlagsIsSynth; + #else + if (processor->getTailLengthSeconds() == 0.0) + vstEffect.flags |= Vst2::effFlagsNoSoundInStop; + #endif + + #if JUCE_WINDOWS + ++numActivePlugins; + #endif + } + + ~JuceVSTWrapper() override + { + JUCE_AUTORELEASEPOOL + { + #if JUCE_LINUX || JUCE_BSD + MessageManagerLock mmLock; + #endif + + stopTimer(); + deleteEditor (false); + + hasShutdown = true; + + processor = nullptr; + + jassert (editorComp == nullptr); + + deleteTempChannels(); + + #if JUCE_WINDOWS + if (--numActivePlugins == 0) + messageThreadIsDefinitelyCorrect = false; + #endif + } + } + + Vst2::AEffect* getAEffect() noexcept { return &vstEffect; } + + template + void internalProcessReplacing (FloatType** inputs, FloatType** outputs, + int32 numSamples, VstTempBuffers& tmpBuffers) + { + const bool isMidiEffect = processor->isMidiEffect(); + + if (firstProcessCallback) + { + firstProcessCallback = false; + + // if this fails, the host hasn't called resume() before processing + jassert (isProcessing); + + // (tragically, some hosts actually need this, although it's stupid to have + // to do it here.) + if (! isProcessing) + resume(); + + processor->setNonRealtime (isProcessLevelOffline()); + + #if JUCE_WINDOWS + if (detail::PluginUtilities::getHostType().isWavelab()) + { + int priority = GetThreadPriority (GetCurrentThread()); + + if (priority <= THREAD_PRIORITY_NORMAL && priority >= THREAD_PRIORITY_LOWEST) + processor->setNonRealtime (true); + } + #endif + } + + #if JUCE_DEBUG && ! (JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect) + const int numMidiEventsComingIn = midiEvents.getNumEvents(); + #endif + + { + const int numIn = processor->getTotalNumInputChannels(); + const int numOut = processor->getTotalNumOutputChannels(); + + const ScopedLock sl (processor->getCallbackLock()); + + if (processor->isSuspended()) + { + for (int i = 0; i < numOut; ++i) + if (outputs[i] != nullptr) + FloatVectorOperations::clear (outputs[i], numSamples); + } + else + { + updateCallbackContextInfo(); + + int i; + for (i = 0; i < numOut; ++i) + { + auto* chan = tmpBuffers.tempChannels.getUnchecked(i); + + if (chan == nullptr) + { + chan = outputs[i]; + + bool bufferPointerReusedForOtherChannels = false; + + for (int j = i; --j >= 0;) + { + if (outputs[j] == chan) + { + bufferPointerReusedForOtherChannels = true; + break; + } + } + + // if some output channels are disabled, some hosts supply the same buffer + // for multiple channels or supply a nullptr - this buggers up our method + // of copying the inputs over the outputs, so we need to create unique temp + // buffers in this case.. + if (bufferPointerReusedForOtherChannels || chan == nullptr) + { + chan = new FloatType [(size_t) blockSize * 2]; + tmpBuffers.tempChannels.set (i, chan); + } + } + + if (i < numIn) + { + if (chan != inputs[i]) + memcpy (chan, inputs[i], (size_t) numSamples * sizeof (FloatType)); + } + else + { + FloatVectorOperations::clear (chan, numSamples); + } + + tmpBuffers.channels[i] = chan; + } + + for (; i < numIn; ++i) + tmpBuffers.channels[i] = inputs[i]; + + { + const int numChannels = jmax (numIn, numOut); + AudioBuffer chans (tmpBuffers.channels, isMidiEffect ? 0 : numChannels, numSamples); + + if (isBypassed && processor->getBypassParameter() == nullptr) + processor->processBlockBypassed (chans, midiEvents); + else + processor->processBlock (chans, midiEvents); + } + + // copy back any temp channels that may have been used.. + for (i = 0; i < numOut; ++i) + if (auto* chan = tmpBuffers.tempChannels.getUnchecked(i)) + if (auto* dest = outputs[i]) + memcpy (dest, chan, (size_t) numSamples * sizeof (FloatType)); + } + } + + if (! midiEvents.isEmpty()) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + auto numEvents = midiEvents.getNumEvents(); + + outgoingEvents.ensureSize (numEvents); + outgoingEvents.clear(); + + for (const auto metadata : midiEvents) + { + jassert (metadata.samplePosition >= 0 && metadata.samplePosition < numSamples); + + outgoingEvents.addEvent (metadata.data, metadata.numBytes, metadata.samplePosition); + } + + // Send VST events to the host. + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterProcessEvents, 0, 0, outgoingEvents.events, 0); + #elif JUCE_DEBUG + /* This assertion is caused when you've added some events to the + midiMessages array in your processBlock() method, which usually means + that you're trying to send them somewhere. But in this case they're + getting thrown away. + + If your plugin does want to send midi messages, you'll need to set + the JucePlugin_ProducesMidiOutput macro to 1 in your + JucePluginCharacteristics.h file. + + If you don't want to produce any midi output, then you should clear the + midiMessages array at the end of your processBlock() method, to + indicate that you don't want any of the events to be passed through + to the output. + */ + jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); + #endif + + midiEvents.clear(); + } + } + + void processReplacing (float** inputs, float** outputs, int32 sampleFrames) + { + jassert (! processor->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); + } + + static void processReplacingCB (Vst2::AEffect* vstInterface, float** inputs, float** outputs, int32 sampleFrames) + { + getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); + } + + void processDoubleReplacing (double** inputs, double** outputs, int32 sampleFrames) + { + jassert (processor->isUsingDoublePrecision()); + internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); + } + + static void processDoubleReplacingCB (Vst2::AEffect* vstInterface, double** inputs, double** outputs, int32 sampleFrames) + { + getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); + } + + //============================================================================== + void resume() + { + if (processor != nullptr) + { + isProcessing = true; + + auto numInAndOutChannels = static_cast (vstEffect.numInputs + vstEffect.numOutputs); + floatTempBuffers .channels.calloc (numInAndOutChannels); + doubleTempBuffers.channels.calloc (numInAndOutChannels); + + auto currentRate = sampleRate; + auto currentBlockSize = blockSize; + + firstProcessCallback = true; + + processor->setNonRealtime (isProcessLevelOffline()); + processor->setRateAndBufferSizeDetails (currentRate, currentBlockSize); + + deleteTempChannels(); + + processor->prepareToPlay (currentRate, currentBlockSize); + + midiEvents.ensureSize (2048); + midiEvents.clear(); + + vstEffect.initialDelay = processor->getLatencySamples(); + + /** If this plug-in is a synth or it can receive midi events we need to tell the + host that we want midi. In the SDK this method is marked as deprecated, but + some hosts rely on this behaviour. + */ + if (vstEffect.flags & Vst2::effFlagsIsSynth || JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect) + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterWantMidi, 0, 1, nullptr, 0); + } + + if (detail::PluginUtilities::getHostType().isAbletonLive() + && hostCallback != nullptr + && std::isinf (processor->getTailLengthSeconds())) + { + AbletonLiveHostSpecific hostCmd; + + hostCmd.magic = 0x41624c69; // 'AbLi' + hostCmd.cmd = 5; + hostCmd.commandSize = sizeof (int); + hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended; + + hostCallback (&vstEffect, Vst2::audioMasterVendorSpecific, 0, 0, &hostCmd, 0.0f); + } + + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + outgoingEvents.ensureSize (512); + #endif + } + } + + void suspend() + { + if (processor != nullptr) + { + processor->releaseResources(); + outgoingEvents.freeEvents(); + + isProcessing = false; + floatTempBuffers.channels.free(); + doubleTempBuffers.channels.free(); + + deleteTempChannels(); + } + } + + void updateCallbackContextInfo() + { + const Vst2::VstTimeInfo* ti = nullptr; + + if (hostCallback != nullptr) + { + int32 flags = Vst2::kVstPpqPosValid | Vst2::kVstTempoValid + | Vst2::kVstBarsValid | Vst2::kVstCyclePosValid + | Vst2::kVstTimeSigValid | Vst2::kVstSmpteValid + | Vst2::kVstClockValid | Vst2::kVstNanosValid; + + auto result = hostCallback (&vstEffect, Vst2::audioMasterGetTime, 0, flags, nullptr, 0); + ti = reinterpret_cast (result); + } + + if (ti == nullptr || ti->sampleRate <= 0) + { + currentPosition.reset(); + return; + } + + auto& info = currentPosition.emplace(); + info.setBpm ((ti->flags & Vst2::kVstTempoValid) != 0 ? makeOptional (ti->tempo) : nullopt); + + info.setTimeSignature ((ti->flags & Vst2::kVstTimeSigValid) != 0 ? makeOptional (TimeSignature { ti->timeSigNumerator, ti->timeSigDenominator }) + : nullopt); + + info.setTimeInSamples ((int64) (ti->samplePos + 0.5)); + info.setTimeInSeconds (ti->samplePos / ti->sampleRate); + info.setPpqPosition ((ti->flags & Vst2::kVstPpqPosValid) != 0 ? makeOptional (ti->ppqPos) : nullopt); + info.setPpqPositionOfLastBarStart ((ti->flags & Vst2::kVstBarsValid) != 0 ? makeOptional (ti->barStartPos) : nullopt); + + if ((ti->flags & Vst2::kVstSmpteValid) != 0) + { + info.setFrameRate ([&]() -> Optional + { + switch (ti->smpteFrameRate) + { + case Vst2::kVstSmpte24fps: return FrameRate().withBaseRate (24); + case Vst2::kVstSmpte239fps: return FrameRate().withBaseRate (24).withPullDown(); + + case Vst2::kVstSmpte25fps: return FrameRate().withBaseRate (25); + case Vst2::kVstSmpte249fps: return FrameRate().withBaseRate (25).withPullDown(); + + case Vst2::kVstSmpte30fps: return FrameRate().withBaseRate (30); + case Vst2::kVstSmpte30dfps: return FrameRate().withBaseRate (30).withDrop(); + case Vst2::kVstSmpte2997fps: return FrameRate().withBaseRate (30).withPullDown(); + case Vst2::kVstSmpte2997dfps: return FrameRate().withBaseRate (30).withPullDown().withDrop(); + + case Vst2::kVstSmpte60fps: return FrameRate().withBaseRate (60); + case Vst2::kVstSmpte599fps: return FrameRate().withBaseRate (60).withPullDown(); + + case Vst2::kVstSmpteFilm16mm: + case Vst2::kVstSmpteFilm35mm: return FrameRate().withBaseRate (24); + } + + return nullopt; + }()); + + const auto effectiveRate = info.getFrameRate().hasValue() ? info.getFrameRate()->getEffectiveRate() : 0.0; + info.setEditOriginTime (! approximatelyEqual (effectiveRate, 0.0) ? makeOptional (ti->smpteOffset / (80.0 * effectiveRate)) : nullopt); + } + + info.setIsRecording ((ti->flags & Vst2::kVstTransportRecording) != 0); + info.setIsPlaying ((ti->flags & (Vst2::kVstTransportRecording | Vst2::kVstTransportPlaying)) != 0); + info.setIsLooping ((ti->flags & Vst2::kVstTransportCycleActive) != 0); + + info.setLoopPoints ((ti->flags & Vst2::kVstCyclePosValid) != 0 ? makeOptional (LoopPoints { ti->cycleStartPos, ti->cycleEndPos }) + : nullopt); + + info.setHostTimeNs ((ti->flags & Vst2::kVstNanosValid) != 0 ? makeOptional ((uint64_t) ti->nanoSeconds) : nullopt); + } + + //============================================================================== + Optional getPosition() const override + { + return currentPosition; + } + + //============================================================================== + float getParameter (int32 index) const + { + if (auto* param = juceParameters.getParamForIndex (index)) + return param->getValue(); + + return 0.0f; + } + + static float getParameterCB (Vst2::AEffect* vstInterface, int32 index) + { + return getWrapper (vstInterface)->getParameter (index); + } + + void setParameter (int32 index, float value) + { + if (auto* param = juceParameters.getParamForIndex (index)) + setValueAndNotifyIfChanged (*param, value); + } + + static void setParameterCB (Vst2::AEffect* vstInterface, int32 index, float value) + { + getWrapper (vstInterface)->setParameter (index, value); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override + { + if (inParameterChangedCallback.get()) + { + inParameterChangedCallback = false; + return; + } + + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterAutomate, index, 0, nullptr, newValue); + } + + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterBeginEdit, index, 0, nullptr, 0); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + if (hostCallback != nullptr) + hostCallback (&vstEffect, Vst2::audioMasterEndEdit, index, 0, nullptr, 0); + } + + void parameterValueChanged (int, float newValue) override + { + // this can only come from the bypass parameter + isBypassed = (newValue >= 0.5f); + } + + void parameterGestureChanged (int, bool) override {} + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + hostChangeUpdater.update (details); + } + + bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const + { + if (processor->isMidiEffect()) + return false; + + int channelIdx, busIdx; + + // fill with default + properties.flags = 0; + properties.label[0] = 0; + properties.shortLabel[0] = 0; + properties.arrangementType = Vst2::kSpeakerArrEmpty; + + if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0) + { + auto& bus = *processor->getBus (direction, busIdx); + auto& channelSet = bus.getCurrentLayout(); + auto channelType = channelSet.getTypeOfChannel (channelIdx); + + properties.flags = Vst2::kVstPinIsActive | Vst2::kVstPinUseSpeaker; + properties.arrangementType = SpeakerMappings::channelSetToVstArrangementType (channelSet); + String label = bus.getName(); + + #ifdef JucePlugin_PreferredChannelConfigurations + label += " " + String (channelIdx); + #else + if (channelSet.size() > 1) + label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType); + #endif + + label.copyToUTF8 (properties.label, (size_t) (Vst2::kVstMaxLabelLen + 1)); + label.copyToUTF8 (properties.shortLabel, (size_t) (Vst2::kVstMaxShortLabelLen + 1)); + + if (channelType == AudioChannelSet::left + || channelType == AudioChannelSet::leftSurround + || channelType == AudioChannelSet::leftCentre + || channelType == AudioChannelSet::leftSurroundSide + || channelType == AudioChannelSet::topFrontLeft + || channelType == AudioChannelSet::topRearLeft + || channelType == AudioChannelSet::leftSurroundRear + || channelType == AudioChannelSet::wideLeft) + properties.flags |= Vst2::kVstPinIsStereo; + + return true; + } + + return false; + } + + //============================================================================== + void timerCallback() override + { + if (shouldDeleteEditor) + { + shouldDeleteEditor = false; + deleteEditor (true); + } + + { + ScopedLock lock (stateInformationLock); + + if (chunkMemoryTime > 0 + && chunkMemoryTime < juce::Time::getApproximateMillisecondCounter() - 2000 + && ! recursionCheck) + { + chunkMemory.reset(); + chunkMemoryTime = 0; + } + } + } + + void setHasEditorFlag (bool shouldSetHasEditor) + { + auto hasEditor = (vstEffect.flags & Vst2::effFlagsHasEditor) != 0; + + if (shouldSetHasEditor == hasEditor) + return; + + if (shouldSetHasEditor) + vstEffect.flags |= Vst2::effFlagsHasEditor; + else + vstEffect.flags &= ~Vst2::effFlagsHasEditor; + } + + void createEditorComp() + { + if (hasShutdown || processor == nullptr) + return; + + if (editorComp == nullptr) + { + if (auto* ed = processor->createEditorIfNeeded()) + { + setHasEditorFlag (true); + editorComp.reset (new EditorCompWrapper (*this, *ed, editorScaleFactor)); + } + else + { + setHasEditorFlag (false); + } + } + + shouldDeleteEditor = false; + } + + void deleteEditor (bool canDeleteLaterIfModal) + { + JUCE_AUTORELEASEPOOL + { + PopupMenu::dismissAllActiveMenus(); + + jassert (! recursionCheck); + ScopedValueSetter svs (recursionCheck, true, false); + + if (editorComp != nullptr) + { + if (auto* modalComponent = Component::getCurrentlyModalComponent()) + { + modalComponent->exitModalState (0); + + if (canDeleteLaterIfModal) + { + shouldDeleteEditor = true; + return; + } + } + + editorComp->detachHostWindow(); + + if (auto* ed = editorComp->getEditorComp()) + processor->editorBeingDeleted (ed); + + editorComp = nullptr; + + // there's some kind of component currently modal, but the host + // is trying to delete our plugin. You should try to avoid this happening.. + jassert (Component::getCurrentlyModalComponent() == nullptr); + } + } + } + + pointer_sized_int dispatcher (int32 opCode, VstOpCodeArguments args) + { + if (hasShutdown) + return 0; + + switch (opCode) + { + case Vst2::effOpen: return handleOpen (args); + case Vst2::effClose: return handleClose (args); + case Vst2::effSetProgram: return handleSetCurrentProgram (args); + case Vst2::effGetProgram: return handleGetCurrentProgram (args); + case Vst2::effSetProgramName: return handleSetCurrentProgramName (args); + case Vst2::effGetProgramName: return handleGetCurrentProgramName (args); + case Vst2::effGetParamLabel: return handleGetParameterLabel (args); + case Vst2::effGetParamDisplay: return handleGetParameterText (args); + case Vst2::effGetParamName: return handleGetParameterName (args); + case Vst2::effSetSampleRate: return handleSetSampleRate (args); + case Vst2::effSetBlockSize: return handleSetBlockSize (args); + case Vst2::effMainsChanged: return handleResumeSuspend (args); + case Vst2::effEditGetRect: return handleGetEditorBounds (args); + case Vst2::effEditOpen: return handleOpenEditor (args); + case Vst2::effEditClose: return handleCloseEditor (args); + case Vst2::effIdentify: return (pointer_sized_int) ByteOrder::bigEndianInt ("NvEf"); + case Vst2::effGetChunk: return handleGetData (args); + case Vst2::effSetChunk: return handleSetData (args); + case Vst2::effProcessEvents: return handlePreAudioProcessingEvents (args); + case Vst2::effCanBeAutomated: return handleIsParameterAutomatable (args); + case Vst2::effString2Parameter: return handleParameterValueForText (args); + case Vst2::effGetProgramNameIndexed: return handleGetProgramName (args); + case Vst2::effGetInputProperties: return handleGetInputPinProperties (args); + case Vst2::effGetOutputProperties: return handleGetOutputPinProperties (args); + case Vst2::effGetPlugCategory: return handleGetPlugInCategory (args); + case Vst2::effSetSpeakerArrangement: return handleSetSpeakerConfiguration (args); + case Vst2::effSetBypass: return handleSetBypass (args); + case Vst2::effGetEffectName: return handleGetPlugInName (args); + case Vst2::effGetProductString: return handleGetPlugInName (args); + case Vst2::effGetVendorString: return handleGetManufacturerName (args); + case Vst2::effGetVendorVersion: return handleGetManufacturerVersion (args); + case Vst2::effVendorSpecific: return handleManufacturerSpecific (args); + case Vst2::effCanDo: return handleCanPlugInDo (args); + case Vst2::effGetTailSize: return handleGetTailSize (args); + case Vst2::effKeysRequired: return handleKeyboardFocusRequired (args); + case Vst2::effGetVstVersion: return handleGetVstInterfaceVersion (args); + case Vst2::effGetCurrentMidiProgram: return handleGetCurrentMidiProgram (args); + case Vst2::effGetSpeakerArrangement: return handleGetSpeakerConfiguration (args); + case Vst2::effSetTotalSampleToProcess: return handleSetNumberOfSamplesToProcess (args); + case Vst2::effSetProcessPrecision: return handleSetSampleFloatType (args); + case Vst2::effGetNumMidiInputChannels: return handleGetNumMidiInputChannels(); + case Vst2::effGetNumMidiOutputChannels: return handleGetNumMidiOutputChannels(); + case Vst2::effEditIdle: return handleEditIdle(); + default: return 0; + } + } + + static pointer_sized_int dispatcherCB (Vst2::AEffect* vstInterface, int32 opCode, int32 index, + pointer_sized_int value, void* ptr, float opt) + { + auto* wrapper = getWrapper (vstInterface); + VstOpCodeArguments args = { index, value, ptr, opt }; + + if (opCode == Vst2::effClose) + { + wrapper->dispatcher (opCode, args); + delete wrapper; + return 1; + } + + return wrapper->dispatcher (opCode, args); + } + + //============================================================================== + // A component to hold the AudioProcessorEditor, and cope with some housekeeping + // chores when it changes or repaints. + struct EditorCompWrapper : public Component + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + , public Timer + #endif + { + EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor, [[maybe_unused]] float initialScale) + : wrapper (w) + { + editor.setOpaque (true); + #if ! JUCE_MAC + editor.setScaleFactor (initialScale); + #endif + addAndMakeVisible (editor); + + auto editorBounds = getSizeToContainChild(); + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + + #if JUCE_WINDOWS + if (! detail::PluginUtilities::getHostType().isReceptor()) + addMouseListener (this, true); + #endif + + setOpaque (true); + } + + ~EditorCompWrapper() override + { + deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may + // have been transferred to another parent which takes over ownership. + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + void getEditorBounds (Vst2::ERect& bounds) + { + auto editorBounds = getSizeToContainChild(); + bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); + } + + void attachToHost (VstOpCodeArguments args) + { + setVisible (false); + + const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (getEditorComp()); + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + addToDesktop (desktopFlags, args.ptr); + hostWindow = (HostWindowType) args.ptr; + + #if JUCE_LINUX || JUCE_BSD + X11Symbols::getInstance()->xReparentWindow (display, + (Window) getWindowHandle(), + (HostWindowType) hostWindow, + 0, 0); + // The host is likely to attempt to move/resize the window directly after this call, + // and we need to ensure that the X server knows that our window has been attached + // before that happens. + X11Symbols::getInstance()->xFlush (display); + #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + checkHostWindowScaleFactor (true); + startTimer (500); + #endif + #elif JUCE_MAC + hostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (this, desktopFlags, args.ptr); + #endif + + setVisible (true); + } + + void detachHostWindow() + { + #if JUCE_MAC + if (hostWindow != nullptr) + detail::VSTWindowUtilities::detachComponentFromWindowRefVST (this, hostWindow); + #endif + + hostWindow = {}; + } + + AudioProcessorEditor* getEditorComp() const noexcept + { + return dynamic_cast (getChildComponent (0)); + } + + void resized() override + { + if (auto* pluginEditor = getEditorComp()) + { + if (! resizingParent) + { + auto newBounds = getLocalBounds(); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); + } + + lastBounds = newBounds; + } + + updateWindowSize(); + } + } + + void parentSizeChanged() override + { + updateWindowSize(); + repaint(); + } + + void childBoundsChanged (Component*) override + { + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + updateWindowSize(); + lastBounds = newBounds; + } + } + + juce::Rectangle getSizeToContainChild() + { + if (auto* pluginEditor = getEditorComp()) + return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); + + return {}; + } + + void resizeHostWindow (juce::Rectangle bounds) + { + auto rect = convertToHostBounds ({ 0, 0, (int16) bounds.getHeight(), (int16) bounds.getWidth() }); + const auto newWidth = rect.right - rect.left; + const auto newHeight = rect.bottom - rect.top; + + bool sizeWasSuccessful = false; + + if (auto host = wrapper.hostCallback) + { + auto status = host (wrapper.getAEffect(), Vst2::audioMasterCanDo, 0, 0, const_cast ("sizeWindow"), 0); + + if (status == (pointer_sized_int) 1 || detail::PluginUtilities::getHostType().isAbletonLive()) + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::audioMasterSizeWindow, + newWidth, newHeight, nullptr, 0) != 0); + } + } + + // some hosts don't support the sizeWindow call, so do it manually.. + if (! sizeWasSuccessful) + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + #if JUCE_MAC + detail::VSTWindowUtilities::setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight); + #elif JUCE_LINUX || JUCE_BSD + // (Currently, all linux hosts support sizeWindow, so this should never need to happen) + setSize (newWidth, newHeight); + #else + int dw = 0; + int dh = 0; + const int frameThickness = GetSystemMetrics (SM_CYFIXEDFRAME); + + HWND w = (HWND) getWindowHandle(); + + while (w != nullptr) + { + HWND parent = getWindowParent (w); + + if (parent == nullptr) + break; + + TCHAR windowType [32] = { 0 }; + GetClassName (parent, windowType, 31); + + if (String (windowType).equalsIgnoreCase ("MDIClient")) + break; + + RECT windowPos, parentPos; + GetWindowRect (w, &windowPos); + GetWindowRect (parent, &parentPos); + + if (w != (HWND) getWindowHandle()) + SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); + + dw = (parentPos.right - parentPos.left) - (windowPos.right - windowPos.left); + dh = (parentPos.bottom - parentPos.top) - (windowPos.bottom - windowPos.top); + + w = parent; + + if (dw == 2 * frameThickness) + break; + + if (dw > 100 || dh > 100) + w = nullptr; + } + + if (w != nullptr) + SetWindowPos (w, nullptr, 0, 0, newWidth + dw, newHeight + dh, + SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); + #endif + } + + #if JUCE_LINUX || JUCE_BSD + X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), + static_cast (rect.right - rect.left), + static_cast (rect.bottom - rect.top)); + #endif + } + + void setContentScaleFactor (float scale) + { + if (auto* pluginEditor = getEditorComp()) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + pluginEditor->setScaleFactor (scale); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); + } + + lastBounds = getSizeToContainChild(); + updateWindowSize(); + } + } + + #if JUCE_WINDOWS + void mouseDown (const MouseEvent&) override + { + broughtToFront(); + } + + void broughtToFront() override + { + // for hosts like nuendo, need to also pop the MDI container to the + // front when our comp is clicked on. + if (! isCurrentlyBlockedByAnotherModalComponent()) + if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) + SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void checkHostWindowScaleFactor (bool force = false) + { + auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); + + if (force || (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, wrapper.editorScaleFactor))) + wrapper.handleSetContentScaleFactor (hostWindowScale, force); + } + + void timerCallback() override + { + checkHostWindowScaleFactor(); + } + #endif + #endif + + private: + void updateWindowSize() + { + if (! resizingParent + && getEditorComp() != nullptr + && hostWindow != HostWindowType{}) + { + const auto editorBounds = getSizeToContainChild(); + resizeHostWindow (editorBounds); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + + // setSize() on linux causes renoise and energyxt to fail. + // We'll resize our peer during resizeHostWindow() instead. + #if ! (JUCE_LINUX || JUCE_BSD) + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + #endif + + if (auto* p = getPeer()) + p->updateBounds(); + } + + #if JUCE_MAC + resizeHostWindow (editorBounds); // (doing this a second time seems to be necessary in tracktion) + #endif + } + } + + //============================================================================== + static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return rect; + + return { (int16) roundToInt (rect.top * desktopScale), + (int16) roundToInt (rect.left * desktopScale), + (int16) roundToInt (rect.bottom * desktopScale), + (int16) roundToInt (rect.right * desktopScale) }; + } + + //============================================================================== + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostEventLoop; + #endif + + //============================================================================== + JuceVSTWrapper& wrapper; + bool resizingChild = false, resizingParent = false; + + juce::Rectangle lastBounds; + + #if JUCE_LINUX || JUCE_BSD + using HostWindowType = ::Window; + ::Display* display = XWindowSystem::getInstance()->getDisplay(); + #elif JUCE_WINDOWS + using HostWindowType = HWND; + detail::WindowsHooks hooks; + #else + using HostWindowType = void*; + #endif + + HostWindowType hostWindow = {}; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) + }; + + //============================================================================== +private: + struct HostChangeUpdater : private AsyncUpdater + { + explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} + ~HostChangeUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + if (details.latencyChanged) + { + owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); + callbackBits |= audioMasterIOChangedBit; + } + + if (details.parameterInfoChanged || details.programChanged) + callbackBits |= audioMasterUpdateDisplayBit; + + triggerAsyncUpdate(); + } + + private: + void handleAsyncUpdate() override + { + const auto callbacksToFire = callbackBits.exchange (0); + + if (auto* callback = owner.hostCallback) + { + struct FlagPair + { + Vst2::AudioMasterOpcodesX opcode; + int bit; + }; + + constexpr FlagPair pairs[] { { Vst2::audioMasterUpdateDisplay, audioMasterUpdateDisplayBit }, + { Vst2::audioMasterIOChanged, audioMasterIOChangedBit } }; + + for (const auto& pair : pairs) + if ((callbacksToFire & pair.bit) != 0) + callback (&owner.vstEffect, pair.opcode, 0, 0, nullptr, 0); + } + } + + static constexpr auto audioMasterUpdateDisplayBit = 1 << 0; + static constexpr auto audioMasterIOChangedBit = 1 << 1; + + JuceVSTWrapper& owner; + std::atomic callbackBits { 0 }; + }; + + static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } + + bool isProcessLevelOffline() + { + return hostCallback != nullptr + && (int32) hostCallback (&vstEffect, Vst2::audioMasterGetCurrentProcessLevel, 0, 0, nullptr, 0) == 4; + } + + static int32 convertHexVersionToDecimal (const unsigned int hexVersion) + { + #if JUCE_VST_RETURN_HEX_VERSION_NUMBER_DIRECTLY + return (int32) hexVersion; + #else + // Currently, only Cubase displays the version number to the user + // We are hoping here that when other DAWs start to display the version + // number, that they do so according to yfede's encoding table in the link + // below. If not, then this code will need an if (isSteinberg()) in the + // future. + int major = (hexVersion >> 16) & 0xff; + int minor = (hexVersion >> 8) & 0xff; + int bugfix = hexVersion & 0xff; + + // for details, see: https://forum.juce.com/t/issues-with-version-integer-reported-by-vst2/23867 + + // Encoding B + if (major < 1) + return major * 1000 + minor * 100 + bugfix * 10; + + // Encoding E + if (major > 100) + return major * 10000000 + minor * 100000 + bugfix * 1000; + + // Encoding D + return static_cast (hexVersion); + #endif + } + + //============================================================================== + #if JUCE_WINDOWS + // Workarounds for hosts which attempt to open editor windows on a non-GUI thread.. (Grrrr...) + static void checkWhetherMessageThreadIsCorrect() + { + auto host = detail::PluginUtilities::getHostType(); + + if (host.isWavelab() || host.isCubaseBridged() || host.isPremiere()) + { + if (! messageThreadIsDefinitelyCorrect) + { + MessageManager::getInstance()->setCurrentThreadAsMessageThread(); + + struct MessageThreadCallback : public CallbackMessage + { + MessageThreadCallback (bool& tr) : triggered (tr) {} + void messageCallback() override { triggered = true; } + + bool& triggered; + }; + + (new MessageThreadCallback (messageThreadIsDefinitelyCorrect))->post(); + } + } + } + #else + static void checkWhetherMessageThreadIsCorrect() {} + #endif + + void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) + { + if (approximatelyEqual (param.getValue(), newValue)) + return; + + inParameterChangedCallback = true; + param.setValueNotifyingHost (newValue); + } + + //============================================================================== + template + void deleteTempChannels (VstTempBuffers& tmpBuffers) + { + tmpBuffers.release(); + + if (processor != nullptr) + tmpBuffers.tempChannels.insertMultiple (0, nullptr, vstEffect.numInputs + + vstEffect.numOutputs); + } + + void deleteTempChannels() + { + deleteTempChannels (floatTempBuffers); + deleteTempChannels (doubleTempBuffers); + } + + //============================================================================== + void findMaxTotalChannels (int& maxTotalIns, int& maxTotalOuts) + { + #ifdef JucePlugin_PreferredChannelConfigurations + int configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + maxTotalIns = maxTotalOuts = 0; + + for (auto& config : configs) + { + maxTotalIns = jmax (maxTotalIns, config[0]); + maxTotalOuts = jmax (maxTotalOuts, config[1]); + } + #else + auto numInputBuses = processor->getBusCount (true); + auto numOutputBuses = processor->getBusCount (false); + + if (numInputBuses > 1 || numOutputBuses > 1) + { + maxTotalIns = maxTotalOuts = 0; + + for (int i = 0; i < numInputBuses; ++i) + maxTotalIns += processor->getChannelCountOfBus (true, i); + + for (int i = 0; i < numOutputBuses; ++i) + maxTotalOuts += processor->getChannelCountOfBus (false, i); + } + else + { + maxTotalIns = numInputBuses > 0 ? processor->getBus (true, 0)->getMaxSupportedChannels (64) : 0; + maxTotalOuts = numOutputBuses > 0 ? processor->getBus (false, 0)->getMaxSupportedChannels (64) : 0; + } + #endif + } + + bool pluginHasSidechainsOrAuxs() const { return (processor->getBusCount (true) > 1 || processor->getBusCount (false) > 1); } + + //============================================================================== + /** Host to plug-in calls. */ + + pointer_sized_int handleOpen (VstOpCodeArguments) + { + // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. + setHasEditorFlag (processor->hasEditor()); + + return 0; + } + + pointer_sized_int handleClose (VstOpCodeArguments) + { + // Note: most hosts call this on the UI thread, but wavelab doesn't, so be careful in here. + stopTimer(); + + if (MessageManager::getInstance()->isThisTheMessageThread()) + deleteEditor (false); + + return 0; + } + + pointer_sized_int handleSetCurrentProgram (VstOpCodeArguments args) + { + if (processor != nullptr && isPositiveAndBelow ((int) args.value, processor->getNumPrograms())) + processor->setCurrentProgram ((int) args.value); + + return 0; + } + + pointer_sized_int handleGetCurrentProgram (VstOpCodeArguments) + { + return (processor != nullptr && processor->getNumPrograms() > 0 ? processor->getCurrentProgram() : 0); + } + + pointer_sized_int handleSetCurrentProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && processor->getNumPrograms() > 0) + processor->changeProgramName (processor->getCurrentProgram(), (char*) args.ptr); + + return 0; + } + + pointer_sized_int handleGetCurrentProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && processor->getNumPrograms() > 0) + processor->getProgramName (processor->getCurrentProgram()).copyToUTF8 ((char*) args.ptr, 24 + 1); + + return 0; + } + + pointer_sized_int handleGetParameterLabel (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getLabel().copyToUTF8 ((char*) args.ptr, 24 + 1); + } + + return 0; + } + + pointer_sized_int handleGetParameterText (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getCurrentValueAsText().copyToUTF8 ((char*) args.ptr, 24 + 1); + } + + return 0; + } + + pointer_sized_int handleGetParameterName (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. + param->getName (32).copyToUTF8 ((char*) args.ptr, 32 + 1); + } + + return 0; + } + + pointer_sized_int handleSetSampleRate (VstOpCodeArguments args) + { + sampleRate = args.opt; + return 0; + } + + pointer_sized_int handleSetBlockSize (VstOpCodeArguments args) + { + blockSize = (int32) args.value; + return 0; + } + + pointer_sized_int handleResumeSuspend (VstOpCodeArguments args) + { + if (args.value) + resume(); + else + suspend(); + + return 0; + } + + pointer_sized_int handleGetEditorBounds (VstOpCodeArguments args) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + createEditorComp(); + + if (editorComp != nullptr) + { + editorComp->getEditorBounds (editorRect); + *((Vst2::ERect**) args.ptr) = &editorRect; + return (pointer_sized_int) &editorRect; + } + + return 0; + } + + pointer_sized_int handleOpenEditor (VstOpCodeArguments args) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + jassert (! recursionCheck); + + startTimerHz (4); // performs misc housekeeping chores + + deleteEditor (true); + createEditorComp(); + + if (editorComp != nullptr) + { + editorComp->attachToHost (args); + return 1; + } + + return 0; + } + + pointer_sized_int handleCloseEditor (VstOpCodeArguments) + { + checkWhetherMessageThreadIsCorrect(); + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + + deleteEditor (true); + + return 0; + } + + pointer_sized_int handleGetData (VstOpCodeArguments args) + { + if (processor == nullptr) + return 0; + + auto data = (void**) args.ptr; + bool onlyStoreCurrentProgramData = (args.index != 0); + + MemoryBlock block; + + if (onlyStoreCurrentProgramData) + processor->getCurrentProgramStateInformation (block); + else + processor->getStateInformation (block); + + // IMPORTANT! Don't call getStateInfo while holding this lock! + const ScopedLock lock (stateInformationLock); + + chunkMemory = std::move (block); + *data = (void*) chunkMemory.getData(); + + // because the chunk is only needed temporarily by the host (or at least you'd + // hope so) we'll give it a while and then free it in the timer callback. + chunkMemoryTime = juce::Time::getApproximateMillisecondCounter(); + + return (int32) chunkMemory.getSize(); + } + + pointer_sized_int handleSetData (VstOpCodeArguments args) + { + if (processor != nullptr) + { + void* data = args.ptr; + int32 byteSize = (int32) args.value; + bool onlyRestoreCurrentProgramData = (args.index != 0); + + { + const ScopedLock lock (stateInformationLock); + + chunkMemory.reset(); + chunkMemoryTime = 0; + } + + if (byteSize > 0 && data != nullptr) + { + if (onlyRestoreCurrentProgramData) + processor->setCurrentProgramStateInformation (data, byteSize); + else + processor->setStateInformation (data, byteSize); + } + } + + return 0; + } + + pointer_sized_int handlePreAudioProcessingEvents ([[maybe_unused]] VstOpCodeArguments args) + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + VSTMidiEventList::addEventsToMidiBuffer ((Vst2::VstEvents*) args.ptr, midiEvents); + return 1; + #else + return 0; + #endif + } + + pointer_sized_int handleIsParameterAutomatable (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + const bool isMeter = ((((unsigned int) param->getCategory() & 0xffff0000) >> 16) == 2); + return (param->isAutomatable() && (! isMeter) ? 1 : 0); + } + + return 0; + } + + pointer_sized_int handleParameterValueForText (VstOpCodeArguments args) + { + if (auto* param = juceParameters.getParamForIndex (args.index)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + setValueAndNotifyIfChanged (*param, param->getValueForText (String::fromUTF8 ((char*) args.ptr))); + return 1; + } + } + + return 0; + } + + pointer_sized_int handleGetProgramName (VstOpCodeArguments args) + { + if (processor != nullptr && isPositiveAndBelow (args.index, processor->getNumPrograms())) + { + processor->getProgramName (args.index).copyToUTF8 ((char*) args.ptr, 24 + 1); + return 1; + } + + return 0; + } + + pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args) + { + return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, true, args.index)) ? 1 : 0; + } + + pointer_sized_int handleGetOutputPinProperties (VstOpCodeArguments args) + { + return (processor != nullptr && getPinProperties (*(Vst2::VstPinProperties*) args.ptr, false, args.index)) ? 1 : 0; + } + + pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments) + { + return Vst2::JucePlugin_VSTCategory; + } + + pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args) + { + auto* pluginInput = reinterpret_cast (args.value); + auto* pluginOutput = reinterpret_cast (args.ptr); + + if (processor->isMidiEffect()) + return 0; + + auto numIns = processor->getBusCount (true); + auto numOuts = processor->getBusCount (false); + + if (pluginInput != nullptr && pluginInput->type >= 0) + { + // inconsistent request? + if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput).size() != pluginInput->numChannels) + return 0; + } + + if (pluginOutput != nullptr && pluginOutput->type >= 0) + { + // inconsistent request? + if (SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput).size() != pluginOutput->numChannels) + return 0; + } + + if (pluginInput != nullptr && pluginInput->numChannels > 0 && numIns == 0) + return 0; + + if (pluginOutput != nullptr && pluginOutput->numChannels > 0 && numOuts == 0) + return 0; + + auto layouts = processor->getBusesLayout(); + + if (pluginInput != nullptr && pluginInput-> numChannels >= 0 && numIns > 0) + layouts.getChannelSet (true, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput); + + if (pluginOutput != nullptr && pluginOutput->numChannels >= 0 && numOuts > 0) + layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (! AudioProcessor::containsLayout (layouts, configs)) + return 0; + #endif + + return processor->setBusesLayout (layouts) ? 1 : 0; + } + + pointer_sized_int handleSetBypass (VstOpCodeArguments args) + { + isBypassed = args.value != 0; + + if (auto* param = processor->getBypassParameter()) + param->setValueNotifyingHost (isBypassed ? 1.0f : 0.0f); + + return 1; + } + + pointer_sized_int handleGetPlugInName (VstOpCodeArguments args) + { + String (JucePlugin_Name).copyToUTF8 ((char*) args.ptr, 64 + 1); + return 1; + } + + pointer_sized_int handleGetManufacturerName (VstOpCodeArguments args) + { + String (JucePlugin_Manufacturer).copyToUTF8 ((char*) args.ptr, 64 + 1); + return 1; + } + + pointer_sized_int handleGetManufacturerVersion (VstOpCodeArguments) + { + return convertHexVersionToDecimal (JucePlugin_VersionCode); + } + + pointer_sized_int handleManufacturerSpecific (VstOpCodeArguments args) + { + if (detail::PluginUtilities::handleManufacturerSpecificVST2Opcode (args.index, args.value, args.ptr, args.opt)) + return 1; + + if (args.index == (int32) ByteOrder::bigEndianInt ("PreS") + && args.value == (int32) ByteOrder::bigEndianInt ("AeCs")) + return handleSetContentScaleFactor (args.opt); + + if (args.index == Vst2::effGetParamDisplay) + return handleCockosGetParameterText (args.value, args.ptr, args.opt); + + if (auto callbackHandler = processor->getVST2ClientExtensions()) + return callbackHandler->handleVstManufacturerSpecific (args.index, args.value, args.ptr, args.opt); + + return 0; + } + + pointer_sized_int handleCanPlugInDo (VstOpCodeArguments args) + { + auto text = (const char*) args.ptr; + auto matches = [=] (const char* s) { return strcmp (text, s) == 0; }; + + if (matches ("receiveVstEvents") + || matches ("receiveVstMidiEvent") + || matches ("receiveVstMidiEvents")) + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + return 1; + #else + return -1; + #endif + } + + if (matches ("sendVstEvents") + || matches ("sendVstMidiEvent") + || matches ("sendVstMidiEvents")) + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + return 1; + #else + return -1; + #endif + } + + if (matches ("receiveVstTimeInfo") + || matches ("conformsToWindowRules") + || matches ("supportsViewDpiScaling") + || matches ("bypass")) + { + return 1; + } + + // This tells Wavelab to use the UI thread to invoke open/close, + // like all other hosts do. + if (matches ("openCloseAnyThread")) + return -1; + + if (matches ("MPE")) + return processor->supportsMPE() ? 1 : 0; + + #if JUCE_MAC + if (matches ("hasCockosViewAsConfig")) + { + return (int32) 0xbeef0000; + } + #endif + + if (matches ("hasCockosExtensions")) + return (int32) 0xbeef0000; + + if (auto callbackHandler = processor->getVST2ClientExtensions()) + return callbackHandler->handleVstPluginCanDo (args.index, args.value, args.ptr, args.opt); + + return 0; + } + + pointer_sized_int handleGetTailSize (VstOpCodeArguments) + { + if (processor != nullptr) + { + int32 result; + + auto tailSeconds = processor->getTailLengthSeconds(); + + if (std::isinf (tailSeconds)) + result = std::numeric_limits::max(); + else + result = static_cast (tailSeconds * sampleRate); + + return result; // Vst2 expects an int32 upcasted to a intptr_t here + } + + return 0; + } + + pointer_sized_int handleKeyboardFocusRequired (VstOpCodeArguments) + { + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6326) + return (JucePlugin_EditorRequiresKeyboardFocus != 0) ? 1 : 0; + JUCE_END_IGNORE_WARNINGS_MSVC + } + + pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments) + { + return kVstVersion; + } + + pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments) + { + return -1; + } + + pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args) + { + auto** pluginInput = reinterpret_cast (args.value); + auto** pluginOutput = reinterpret_cast (args.ptr); + + if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect()) + return false; + + auto inputLayout = processor->getChannelLayoutOfBus (true, 0); + auto outputLayout = processor->getChannelLayoutOfBus (false, 0); + + const auto speakerBaseSize = offsetof (Vst2::VstSpeakerArrangement, speakers); + + cachedInArrangement .malloc (speakerBaseSize + (static_cast (inputLayout. size()) * sizeof (Vst2::VstSpeakerProperties)), 1); + cachedOutArrangement.malloc (speakerBaseSize + (static_cast (outputLayout.size()) * sizeof (Vst2::VstSpeakerProperties)), 1); + + *pluginInput = cachedInArrangement. getData(); + *pluginOutput = cachedOutArrangement.getData(); + + SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (true, 0), **pluginInput); + SpeakerMappings::channelSetToVstArrangement (processor->getChannelLayoutOfBus (false, 0), **pluginOutput); + + return 1; + } + + pointer_sized_int handleSetNumberOfSamplesToProcess (VstOpCodeArguments args) + { + return args.value; + } + + pointer_sized_int handleSetSampleFloatType (VstOpCodeArguments args) + { + if (! isProcessing) + { + if (processor != nullptr) + { + processor->setProcessingPrecision ((args.value == Vst2::kVstProcessPrecision64 + && processor->supportsDoublePrecisionProcessing()) + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + + return 1; + } + } + + return 0; + } + + pointer_sized_int handleSetContentScaleFactor ([[maybe_unused]] float scale, [[maybe_unused]] bool force = false) + { + checkWhetherMessageThreadIsCorrect(); + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #else + const MessageManagerLock mmLock; + #endif + + #if ! JUCE_MAC + if (force || ! approximatelyEqual (scale, editorScaleFactor)) + { + editorScaleFactor = scale; + + if (editorComp != nullptr) + editorComp->setContentScaleFactor (editorScaleFactor); + } + #endif + + return 1; + } + + pointer_sized_int handleCockosGetParameterText (pointer_sized_int paramIndex, + void* dest, + float value) + { + if (processor != nullptr && dest != nullptr) + { + if (auto* param = juceParameters.getParamForIndex ((int) paramIndex)) + { + if (! LegacyAudioParameter::isLegacy (param)) + { + String text (param->getText (value, 1024)); + memcpy (dest, text.toRawUTF8(), ((size_t) text.length()) + 1); + return 0xbeef; + } + } + } + + return 0; + } + + //============================================================================== + pointer_sized_int handleGetNumMidiInputChannels() + { + #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect + #ifdef JucePlugin_VSTNumMidiInputs + return JucePlugin_VSTNumMidiInputs; + #else + return 16; + #endif + #else + return 0; + #endif + } + + pointer_sized_int handleGetNumMidiOutputChannels() + { + #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect + #ifdef JucePlugin_VSTNumMidiOutputs + return JucePlugin_VSTNumMidiOutputs; + #else + return 16; + #endif + #else + return 0; + #endif + } + + pointer_sized_int handleEditIdle() + { + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + hostDrivenEventLoop->processPendingEvents(); + #endif + + return 0; + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + Vst2::audioMasterCallback hostCallback; + std::unique_ptr processor; + double sampleRate = 44100.0; + int32 blockSize = 1024; + Vst2::AEffect vstEffect; + CriticalSection stateInformationLock; + juce::MemoryBlock chunkMemory; + uint32 chunkMemoryTime = 0; + float editorScaleFactor = 1.0f; + std::unique_ptr editorComp; + Vst2::ERect editorRect; + MidiBuffer midiEvents; + VSTMidiEventList outgoingEvents; + Optional currentPosition; + + LegacyAudioParametersWrapper juceParameters; + + bool isProcessing = false, isBypassed = false, hasShutdown = false; + bool firstProcessCallback = true, shouldDeleteEditor = false; + + VstTempBuffers floatTempBuffers; + VstTempBuffers doubleTempBuffers; + int maxNumInChannels = 0, maxNumOutChannels = 0; + + HeapBlock cachedInArrangement, cachedOutArrangement; + + ThreadLocalValue inParameterChangedCallback; + + HostChangeUpdater hostChangeUpdater { *this }; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) +}; + + +//============================================================================== +namespace +{ + Vst2::AEffect* pluginEntryPoint (Vst2::audioMasterCallback audioMaster) + { + JUCE_AUTORELEASEPOOL + { + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer hostDrivenEventLoop; + #endif + + try + { + if (audioMaster (nullptr, Vst2::audioMasterVersion, 0, 0, nullptr, 0) != 0) + { + std::unique_ptr processor { createPluginFilterOfType (AudioProcessor::wrapperType_VST) }; + auto* processorPtr = processor.get(); + auto* wrapper = new JuceVSTWrapper (audioMaster, std::move (processor)); + auto* aEffect = wrapper->getAEffect(); + + if (auto* callbackHandler = processorPtr->getVST2ClientExtensions()) + { + callbackHandler->handleVstHostCallbackAvailable ([audioMaster, aEffect] (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) + { + return audioMaster (aEffect, opcode, index, value, ptr, opt); + }); + } + + return aEffect; + } + } + catch (...) + {} + } + + return nullptr; + } +} + +#if ! JUCE_WINDOWS + #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) +#endif + +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +//============================================================================== +// Mac startup code.. +#if JUCE_MAC + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_macho (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + +//============================================================================== +// Linux startup code.. +#elif JUCE_LINUX || JUCE_BSD + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) asm ("main"); + JUCE_EXPORTED_FUNCTION Vst2::AEffect* main_plugin (Vst2::audioMasterCallback audioMaster) + { + return VSTPluginMain (audioMaster); + } + + // don't put initialiseJuce_GUI or shutdownJuce_GUI in these... it will crash! + __attribute__((constructor)) void myPluginInit() {} + __attribute__((destructor)) void myPluginFini() {} + +//============================================================================== +// Win32 startup code.. +#else + + extern "C" __declspec (dllexport) Vst2::AEffect* VSTPluginMain (Vst2::audioMasterCallback audioMaster) + { + return pluginEntryPoint (audioMaster); + } + + #if ! defined (JUCE_64BIT) && JUCE_MSVC // (can't compile this on win64, but it's not needed anyway with VST2.4) + extern "C" __declspec (dllexport) int main (Vst2::audioMasterCallback audioMaster) + { + return (int) pluginEntryPoint (audioMaster); + } + #endif + + extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) + { + if (reason == DLL_PROCESS_ATTACH) + Process::setCurrentModuleInstanceHandle (instance); + + return true; + } +#endif + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +JUCE_END_IGNORE_WARNINGS_MSVC + +#endif diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm similarity index 94% rename from JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp rename to JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm index 83bc191..951bb0c 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_utils.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.mm @@ -23,4 +23,4 @@ ============================================================================== */ -#include "utility/juce_PluginUtilities.cpp" +#include "juce_audio_plugin_client_VST2.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index 22cf1fe..54d4021 100644 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -23,4 +23,4290 @@ ============================================================================== */ -#include "VST3/juce_VST3_Wrapper.cpp" +#include +#include + +//============================================================================== +#if JucePlugin_Build_VST3 + +JUCE_BEGIN_NO_SANITIZE ("vptr") + +#if JUCE_PLUGINHOST_VST3 + #if JUCE_MAC + #include + #endif + #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY + #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 +#endif + +#include + +#undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY +#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifndef JUCE_VST3_CAN_REPLACE_VST2 + #define JUCE_VST3_CAN_REPLACE_VST2 1 +#endif + +#if JUCE_VST3_CAN_REPLACE_VST2 + + #if ! JUCE_MSVC && ! defined (__cdecl) + #define __cdecl + #endif + + namespace Vst2 + { + struct AEffect; + #include "pluginterfaces/vst2.x/vstfxstore.h" + } + +#endif + +#ifndef JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + #if JucePlugin_WantsMidiInput + #define JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS 1 + #endif +#endif + +#if JUCE_LINUX || JUCE_BSD + #include + #include +#endif + +#if JUCE_MAC + #include +#endif + +//============================================================================== +#if JucePlugin_Enable_ARA + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wpragma-pack") + #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if ARA_SUPPORT_VERSION_1 + #error "Unsupported ARA version - only ARA version 2 and onward are supported by the current implementation" + #endif + + DEF_CLASS_IID(ARA::IPlugInEntryPoint) + DEF_CLASS_IID(ARA::IPlugInEntryPoint2) + DEF_CLASS_IID(ARA::IMainFactory) +#endif + +namespace juce +{ + +JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4310) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wall") + +#if JUCE_VST3_CAN_REPLACE_VST2 + static Steinberg::FUID getFUIDForVST2ID (bool forControllerUID) + { + Steinberg::TUID uuid; + detail::PluginUtilities::getUUIDForVST2ID (forControllerUID, (uint8*) uuid); + return Steinberg::FUID (uuid); + } +#endif + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + +using namespace Steinberg; + +//============================================================================== +#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + double getScaleFactorForWindow (HWND); +#endif + +//============================================================================== +#if JUCE_LINUX || JUCE_BSD + +enum class HostMessageThreadAttached { no, yes }; + +class HostMessageThreadState +{ +public: + template + void setStateWithAction (HostMessageThreadAttached stateIn, Callback&& action) + { + const std::lock_guard lock { m }; + state = stateIn; + action(); + } + + void assertHostMessageThread() + { + const std::lock_guard lock { m }; + + if (state == HostMessageThreadAttached::no) + return; + + JUCE_ASSERT_MESSAGE_THREAD + } + +private: + HostMessageThreadAttached state = HostMessageThreadAttached::no; + std::mutex m; +}; + +class EventHandler final : public Steinberg::Linux::IEventHandler, + private LinuxEventLoopInternal::Listener +{ +public: + EventHandler() + { + LinuxEventLoopInternal::registerLinuxEventLoopListener (*this); + } + + ~EventHandler() override + { + jassert (hostRunLoops.empty()); + + LinuxEventLoopInternal::deregisterLinuxEventLoopListener (*this); + + if (! messageThread->isRunning()) + hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::no, + [this]() { messageThread->start(); }); + } + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + return testFor (*this, targetIID, UniqueBase{}).extract (obj); + } + + void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor fd) override + { + updateCurrentMessageThread(); + LinuxEventLoopInternal::invokeEventLoopCallbackForFd (fd); + } + + //============================================================================== + void registerHandlerForFrame (IPlugFrame* plugFrame) + { + if (auto* runLoop = getRunLoopFromFrame (plugFrame)) + { + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.insert (runLoop); }); + updateCurrentMessageThread(); + } + } + + void unregisterHandlerForFrame (IPlugFrame* plugFrame) + { + if (auto* runLoop = getRunLoopFromFrame (plugFrame)) + refreshAttachedEventLoop ([this, runLoop] { hostRunLoops.erase (runLoop); }); + } + + /* Asserts if it can be established that the calling thread is different from the host's message + thread. + + On Linux this can only be determined if the host has already registered its run loop. Until + then JUCE messages are serviced by a background thread internal to the plugin. + */ + static void assertHostMessageThread() + { + hostMessageThreadState.assertHostMessageThread(); + } + +private: + //============================================================================== + /* Connects all known FDs to a single host event loop instance. */ + class AttachedEventLoop + { + public: + AttachedEventLoop() = default; + + AttachedEventLoop (Steinberg::Linux::IRunLoop* loopIn, Steinberg::Linux::IEventHandler* handlerIn) + : loop (loopIn), handler (handlerIn) + { + for (auto& fd : LinuxEventLoopInternal::getRegisteredFds()) + loop->registerEventHandler (handler, fd); + } + + AttachedEventLoop (AttachedEventLoop&& other) noexcept + { + swap (other); + } + + AttachedEventLoop& operator= (AttachedEventLoop&& other) noexcept + { + swap (other); + return *this; + } + + AttachedEventLoop (const AttachedEventLoop&) = delete; + AttachedEventLoop& operator= (const AttachedEventLoop&) = delete; + + ~AttachedEventLoop() + { + if (loop == nullptr) + return; + + loop->unregisterEventHandler (handler); + } + + private: + void swap (AttachedEventLoop& other) + { + std::swap (other.loop, loop); + std::swap (other.handler, handler); + } + + Steinberg::Linux::IRunLoop* loop = nullptr; + Steinberg::Linux::IEventHandler* handler = nullptr; + }; + + //============================================================================== + static Steinberg::Linux::IRunLoop* getRunLoopFromFrame (IPlugFrame* plugFrame) + { + Steinberg::Linux::IRunLoop* runLoop = nullptr; + + if (plugFrame != nullptr) + plugFrame->queryInterface (Steinberg::Linux::IRunLoop::iid, (void**) &runLoop); + + jassert (runLoop != nullptr); + return runLoop; + } + + void updateCurrentMessageThread() + { + if (! MessageManager::getInstance()->isThisTheMessageThread()) + { + if (messageThread->isRunning()) + messageThread->stop(); + + hostMessageThreadState.setStateWithAction (HostMessageThreadAttached::yes, + [] { MessageManager::getInstance()->setCurrentThreadAsMessageThread(); }); + } + } + + void fdCallbacksChanged() override + { + // The set of active FDs has changed, so deregister from the current event loop and then + // re-register the current set of FDs. + refreshAttachedEventLoop ([]{}); + } + + /* Deregisters from any attached event loop, updates the set of known event loops, and then + attaches all FDs to the first known event loop. + + The same event loop instance is shared between all plugin instances. Every time an event + loop is added or removed, this function should be called to register all FDs with a + suitable event loop. + + Note that there's no API to deregister a single FD for a given event loop. Instead, we must + deregister all FDs, and then register all known FDs again. + */ + template + void refreshAttachedEventLoop (Callback&& modifyKnownRunLoops) + { + // Deregister the old event loop. + // It's important to call the destructor from the old attached loop before calling the + // constructor of the new attached loop. + attachedEventLoop = AttachedEventLoop(); + + modifyKnownRunLoops(); + + // If we still know about an extant event loop, attach to it. + if (hostRunLoops.begin() != hostRunLoops.end()) + attachedEventLoop = AttachedEventLoop (*hostRunLoops.begin(), this); + } + + SharedResourcePointer messageThread; + + std::atomic refCount { 1 }; + + std::multiset hostRunLoops; + AttachedEventLoop attachedEventLoop; + + static HostMessageThreadState hostMessageThreadState; + + //============================================================================== + JUCE_DECLARE_NON_MOVEABLE (EventHandler) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) +}; + +HostMessageThreadState EventHandler::hostMessageThreadState; + +#endif + +static void assertHostMessageThread() +{ + #if JUCE_LINUX || JUCE_BSD + EventHandler::assertHostMessageThread(); + #else + JUCE_ASSERT_MESSAGE_THREAD + #endif +} + +//============================================================================== +class InParameterChangedCallbackSetter +{ +public: + explicit InParameterChangedCallbackSetter (bool& ref) + : inner ([&]() -> auto& { jassert (! ref); return ref; }(), true, false) {} + +private: + ScopedValueSetter inner; +}; + +template +static QueryInterfaceResult queryAdditionalInterfaces (AudioProcessor* processor, + const TUID targetIID, + Member&& member) +{ + if (processor == nullptr) + return {}; + + void* obj = nullptr; + + if (auto* extensions = processor->getVST3ClientExtensions()) + { + const auto result = (extensions->*member) (targetIID, &obj); + return { result, obj }; + } + + return {}; +} + +static tresult extractResult (const QueryInterfaceResult& userInterface, + const InterfaceResultWithDeferredAddRef& juceInterface, + void** obj) +{ + if (userInterface.isOk() && juceInterface.isOk()) + { + // If you hit this assertion, you've provided a custom implementation of an interface + // that JUCE implements already. As a result, your plugin may not behave correctly. + // Consider removing your custom implementation. + jassertfalse; + + return userInterface.extract (obj); + } + + if (userInterface.isOk()) + return userInterface.extract (obj); + + return juceInterface.extract (obj); +} + +//============================================================================== +class JuceAudioProcessor : public Vst::IUnitInfo +{ +public: + explicit JuceAudioProcessor (AudioProcessor* source) noexcept + : audioProcessor (source) + { + setupParameters(); + } + + virtual ~JuceAudioProcessor() = default; + + AudioProcessor* get() const noexcept { return audioProcessor.get(); } + + JUCE_DECLARE_VST3_COM_QUERY_METHODS + JUCE_DECLARE_VST3_COM_REF_METHODS + + //============================================================================== + enum InternalParameters + { + paramPreset = 0x70727374, // 'prst' + paramMidiControllerOffset = 0x6d636d00, // 'mdm*' + paramBypass = 0x62797073 // 'byps' + }; + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override + { + return parameterGroups.size() + 1; + } + + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override + { + if (unitIndex == 0) + { + info.id = Vst::kRootUnitId; + info.parentUnitId = Vst::kNoParentUnitId; + info.programListId = getProgramListCount() > 0 + ? static_cast (programParamID) + : Vst::kNoProgramListId; + + toString128 (info.name, TRANS ("Root Unit")); + + return kResultTrue; + } + + if (auto* group = parameterGroups[unitIndex - 1]) + { + info.id = JuceAudioProcessor::getUnitID (group); + info.parentUnitId = JuceAudioProcessor::getUnitID (group->getParent()); + info.programListId = Vst::kNoProgramListId; + + toString128 (info.name, group->getName()); + + return kResultTrue; + } + + return kResultFalse; + } + + Steinberg::int32 PLUGIN_API getProgramListCount() override + { + if (audioProcessor->getNumPrograms() > 0) + return 1; + + return 0; + } + + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override + { + if (listIndex == 0) + { + info.id = static_cast (programParamID); + info.programCount = static_cast (audioProcessor->getNumPrograms()); + + toString128 (info.name, TRANS ("Factory Presets")); + + return kResultTrue; + } + + jassertfalse; + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override + { + if (listId == static_cast (programParamID) + && isPositiveAndBelow ((int) programIndex, audioProcessor->getNumPrograms())) + { + toString128 (name, audioProcessor->getProgramName ((int) programIndex)); + return kResultTrue; + } + + jassertfalse; + toString128 (name, juce::String()); + return kResultFalse; + } + + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID, Steinberg::int32, Vst::CString, Vst::String128) override { return kNotImplemented; } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID, Steinberg::int32) override { return kNotImplemented; } + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID, Steinberg::int32, Steinberg::int16, Vst::String128) override { return kNotImplemented; } + tresult PLUGIN_API selectUnit (Vst::UnitID) override { return kNotImplemented; } + tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } + Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } + + tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, Steinberg::int32, Steinberg::int32, Vst::UnitID& unitId) override + { + unitId = Vst::kRootUnitId; + return kResultOk; + } + + //============================================================================== + inline Vst::ParamID getVSTParamIDForIndex (int paramIndex) const noexcept + { + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (paramIndex); + #else + return vstParamIDs.getReference (paramIndex); + #endif + } + + AudioProcessorParameter* getParamForVSTParamID (Vst::ParamID paramID) const noexcept + { + return paramMap[static_cast (paramID)]; + } + + AudioProcessorParameter* getBypassParameter() const noexcept + { + return getParamForVSTParamID (bypassParamID); + } + + AudioProcessorParameter* getProgramParameter() const noexcept + { + return getParamForVSTParamID (programParamID); + } + + static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) + { + if (group == nullptr || group->getParent() == nullptr) + return Vst::kRootUnitId; + + // From the VST3 docs (also applicable to unit IDs!): + // Up to 2^31 parameters can be exported with id range [0, 2147483648] + // (the range [2147483649, 429496729] is reserved for host application). + auto unitID = group->getID().hashCode() & 0x7fffffff; + + // If you hit this assertion then your group ID is hashing to a value + // reserved by the VST3 SDK. Please use a different group ID. + jassert (unitID != Vst::kRootUnitId); + + return unitID; + } + + const Array& getParamIDs() const noexcept { return vstParamIDs; } + Vst::ParamID getBypassParamID() const noexcept { return bypassParamID; } + Vst::ParamID getProgramParamID() const noexcept { return programParamID; } + bool isBypassRegularParameter() const noexcept { return bypassIsRegularParameter; } + + int findCacheIndexForParamID (Vst::ParamID paramID) const noexcept { return vstParamIDs.indexOf (paramID); } + + void setParameterValue (Steinberg::int32 paramIndex, float value) + { + cachedParamValues.set (paramIndex, value); + } + + template + void forAllChangedParameters (Callback&& callback) + { + cachedParamValues.ifSet ([&] (Steinberg::int32 index, float value) + { + callback (cachedParamValues.getParamID (index), value); + }); + } + + bool isUsingManagedParameters() const noexcept { return juceParameters.isUsingManagedParameters(); } + + //============================================================================== + inline static const FUID iid { TUID INLINE_UID (0x0101ABAB, 0xABCDEF01, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + +private: + //============================================================================== + void setupParameters() + { + parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); + + #if JUCE_DEBUG + auto allGroups = parameterGroups; + allGroups.add (&audioProcessor->getParameterTree()); + std::unordered_set unitIDs; + + for (auto* group : allGroups) + { + auto insertResult = unitIDs.insert (getUnitID (group)); + + // If you hit this assertion then either a group ID is not unique or + // you are very unlucky and a hashed group ID is not unique + jassert (insertResult.second); + } + #endif + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + const bool forceLegacyParamIDs = true; + #else + const bool forceLegacyParamIDs = false; + #endif + + juceParameters.update (*audioProcessor, forceLegacyParamIDs); + auto numParameters = juceParameters.getNumParameters(); + + bool vst3WrapperProvidedBypassParam = false; + auto* bypassParameter = audioProcessor->getBypassParameter(); + + if (bypassParameter == nullptr) + { + vst3WrapperProvidedBypassParam = true; + ownedBypassParameter.reset (new AudioParameterBool ("byps", "Bypass", false)); + bypassParameter = ownedBypassParameter.get(); + } + + // if the bypass parameter is not part of the exported parameters that the plug-in supports + // then add it to the end of the list as VST3 requires the bypass parameter to be exported! + bypassIsRegularParameter = juceParameters.contains (audioProcessor->getBypassParameter()); + + if (! bypassIsRegularParameter) + juceParameters.addNonOwning (bypassParameter); + + int i = 0; + for (auto* juceParam : juceParameters) + { + bool isBypassParameter = (juceParam == bypassParameter); + + Vst::ParamID vstParamID = forceLegacyParamIDs ? static_cast (i++) + : generateVSTParamIDForParam (juceParam); + + if (isBypassParameter) + { + // we need to remain backward compatible with the old bypass id + if (vst3WrapperProvidedBypassParam) + { + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6240) + vstParamID = static_cast ((isUsingManagedParameters() && ! forceLegacyParamIDs) ? paramBypass : numParameters); + JUCE_END_IGNORE_WARNINGS_MSVC + } + + bypassParamID = vstParamID; + } + + vstParamIDs.add (vstParamID); + paramMap.set (static_cast (vstParamID), juceParam); + } + + auto numPrograms = audioProcessor->getNumPrograms(); + + if (numPrograms > 1) + { + ownedProgramParameter = std::make_unique ("juceProgramParameter", "Program", + 0, numPrograms - 1, + audioProcessor->getCurrentProgram()); + + juceParameters.addNonOwning (ownedProgramParameter.get()); + + if (forceLegacyParamIDs) + programParamID = static_cast (i++); + + vstParamIDs.add (programParamID); + paramMap.set (static_cast (programParamID), ownedProgramParameter.get()); + } + + cachedParamValues = CachedParamValues { { vstParamIDs.begin(), vstParamIDs.end() } }; + } + + Vst::ParamID generateVSTParamIDForParam (const AudioProcessorParameter* param) + { + auto juceParamID = LegacyAudioParameter::getParamID (param, false); + + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS + return static_cast (juceParamID.getIntValue()); + #else + auto paramHash = static_cast (juceParamID.hashCode()); + + #if JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS + // studio one doesn't like negative parameters + paramHash &= ~(((Vst::ParamID) 1) << (sizeof (Vst::ParamID) * 8 - 1)); + #endif + + return paramHash; + #endif + } + + //============================================================================== + Array vstParamIDs; + CachedParamValues cachedParamValues; + Vst::ParamID bypassParamID = 0, programParamID = static_cast (paramPreset); + bool bypassIsRegularParameter = false; + + //============================================================================== + std::atomic refCount { 0 }; + std::unique_ptr audioProcessor; + + //============================================================================== + LegacyAudioParametersWrapper juceParameters; + HashMap paramMap; + std::unique_ptr ownedBypassParameter, ownedProgramParameter; + Array parameterGroups; + + JuceAudioProcessor() = delete; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceAudioProcessor) +}; + +class JuceVST3Component; + +static thread_local bool inParameterChangedCallback = false; + +static void setValueAndNotifyIfChanged (AudioProcessorParameter& param, float newValue) +{ + if (approximatelyEqual (param.getValue(), newValue)) + return; + + const InParameterChangedCallbackSetter scopedSetter { inParameterChangedCallback }; + param.setValueNotifyingHost (newValue); +} + +//============================================================================== +class JuceVST3EditController : public Vst::EditController, + public Vst::IMidiMapping, + public Vst::IUnitInfo, + public Vst::ChannelContext::IInfoListener, + #if JucePlugin_Enable_ARA + public Presonus::IPlugInViewEmbedding, + #endif + public AudioProcessorListener, + private ComponentRestarter::Listener +{ +public: + explicit JuceVST3EditController (Vst::IHostApplication* host) + { + if (host != nullptr) + host->queryInterface (FUnknown::iid, (void**) &hostContext); + + blueCatPatchwork |= isBlueCatHost (host); + } + + //============================================================================== + + #if JUCE_VST3_CAN_REPLACE_VST2 + inline static const FUID iid = getFUIDForVST2ID (true); + #else + inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0x1234ABCD, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + #endif + + //============================================================================== + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Winconsistent-missing-override") + + REFCOUNT_METHODS (ComponentBase) + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto userProvidedInterface = queryAdditionalInterfaces (getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIEditController); + + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); + + return extractResult (userProvidedInterface, juceProvidedInterface, obj); + } + + //============================================================================== + tresult PLUGIN_API initialize (FUnknown* context) override + { + if (hostContext != context) + hostContext = context; + + blueCatPatchwork |= isBlueCatHost (context); + + return kResultTrue; + } + + tresult PLUGIN_API terminate() override + { + if (auto* pluginInstance = getPluginInstance()) + pluginInstance->removeListener (this); + + audioProcessor = nullptr; + + return EditController::terminate(); + } + + //============================================================================== + struct Param : public Vst::Parameter + { + Param (JuceVST3EditController& editController, AudioProcessorParameter& p, + Vst::ParamID vstParamID, Vst::UnitID vstUnitID, + bool isBypassParameter) + : owner (editController), param (p) + { + info.id = vstParamID; + info.unitId = vstUnitID; + + updateParameterInfo(); + + info.stepCount = (Steinberg::int32) 0; + + #if ! JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE + if (param.isDiscrete()) + #endif + { + const int numSteps = param.getNumSteps(); + info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); + } + + info.defaultNormalizedValue = param.getDefaultValue(); + jassert (info.defaultNormalizedValue >= 0 && info.defaultNormalizedValue <= 1.0f); + + // Is this a meter? + if ((((unsigned int) param.getCategory() & 0xffff0000) >> 16) == 2) + info.flags = Vst::ParameterInfo::kIsReadOnly; + else + info.flags = param.isAutomatable() ? Vst::ParameterInfo::kCanAutomate : 0; + + if (isBypassParameter) + info.flags |= Vst::ParameterInfo::kIsBypass; + + valueNormalized = info.defaultNormalizedValue; + } + + bool updateParameterInfo() + { + auto updateParamIfChanged = [] (Vst::String128& paramToUpdate, const String& newValue) + { + if (juce::toString (paramToUpdate) == newValue) + return false; + + toString128 (paramToUpdate, newValue); + return true; + }; + + auto anyUpdated = updateParamIfChanged (info.title, param.getName (128)); + anyUpdated |= updateParamIfChanged (info.shortTitle, param.getName (8)); + anyUpdated |= updateParamIfChanged (info.units, param.getLabel()); + + return anyUpdated; + } + + bool setNormalized (Vst::ParamValue v) override + { + v = jlimit (0.0, 1.0, v); + + if (! approximatelyEqual (v, valueNormalized)) + { + valueNormalized = v; + + // Only update the AudioProcessor here if we're not playing, + // otherwise we get parallel streams of parameter value updates + // during playback + if (! owner.vst3IsPlaying) + setValueAndNotifyIfChanged (param, (float) v); + + changed(); + return true; + } + + return false; + } + + void toString (Vst::ParamValue value, Vst::String128 result) const override + { + if (LegacyAudioParameter::isLegacy (¶m)) + // remain backward-compatible with old JUCE code + toString128 (result, param.getCurrentValueAsText()); + else + toString128 (result, param.getText ((float) value, 128)); + } + + bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override + { + if (! LegacyAudioParameter::isLegacy (¶m)) + { + outValueNormalized = param.getValueForText (getStringFromVstTChars (text)); + return true; + } + + return false; + } + + static String getStringFromVstTChars (const Vst::TChar* text) + { + return juce::String (juce::CharPointer_UTF16 (reinterpret_cast (text))); + } + + Vst::ParamValue toPlain (Vst::ParamValue v) const override { return v; } + Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v; } + + private: + JuceVST3EditController& owner; + AudioProcessorParameter& param; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Param) + }; + + //============================================================================== + struct ProgramChangeParameter : public Vst::Parameter + { + ProgramChangeParameter (AudioProcessor& p, Vst::ParamID vstParamID) + : owner (p) + { + jassert (owner.getNumPrograms() > 1); + + info.id = vstParamID; + toString128 (info.title, "Program"); + toString128 (info.shortTitle, "Program"); + toString128 (info.units, ""); + info.stepCount = owner.getNumPrograms() - 1; + info.defaultNormalizedValue = static_cast (owner.getCurrentProgram()) + / static_cast (info.stepCount); + info.unitId = Vst::kRootUnitId; + info.flags = Vst::ParameterInfo::kIsProgramChange | Vst::ParameterInfo::kCanAutomate; + } + + ~ProgramChangeParameter() override = default; + + bool setNormalized (Vst::ParamValue v) override + { + const auto programValue = getProgramValueFromNormalised (v); + + if (programValue != owner.getCurrentProgram()) + owner.setCurrentProgram (programValue); + + if (! approximatelyEqual (valueNormalized, v)) + { + valueNormalized = v; + changed(); + + return true; + } + + return false; + } + + void toString (Vst::ParamValue value, Vst::String128 result) const override + { + toString128 (result, owner.getProgramName (roundToInt (value * info.stepCount))); + } + + bool fromString (const Vst::TChar* text, Vst::ParamValue& outValueNormalized) const override + { + auto paramValueString = getStringFromVstTChars (text); + auto n = owner.getNumPrograms(); + + for (int i = 0; i < n; ++i) + { + if (paramValueString == owner.getProgramName (i)) + { + outValueNormalized = static_cast (i) / info.stepCount; + return true; + } + } + + return false; + } + + static String getStringFromVstTChars (const Vst::TChar* text) + { + return String (CharPointer_UTF16 (reinterpret_cast (text))); + } + + Steinberg::int32 getProgramValueFromNormalised (Vst::ParamValue v) const + { + return jmin (info.stepCount, (Steinberg::int32) (v * (info.stepCount + 1))); + } + + Vst::ParamValue toPlain (Vst::ParamValue v) const override { return getProgramValueFromNormalised (v); } + Vst::ParamValue toNormalized (Vst::ParamValue v) const override { return v / info.stepCount; } + + private: + AudioProcessor& owner; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) + }; + + //============================================================================== + tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override + { + if (auto* instance = getPluginInstance()) + { + if (list != nullptr) + { + AudioProcessor::TrackProperties trackProperties; + + { + Vst::String128 channelName; + if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) + trackProperties.name = toString (channelName); + } + + { + Steinberg::int64 colour; + if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) + trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), + Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); + } + + + + if (MessageManager::getInstance()->isThisTheMessageThread()) + instance->updateTrackProperties (trackProperties); + else + MessageManager::callAsync ([trackProperties, instance] + { instance->updateTrackProperties (trackProperties); }); + } + } + + return kResultOk; + } + + //============================================================================== + #if JucePlugin_Enable_ARA + Steinberg::TBool PLUGIN_API isViewEmbeddingSupported() override + { + if (auto* pluginInstance = getPluginInstance()) + return (Steinberg::TBool) dynamic_cast (pluginInstance)->isEditorView(); + return (Steinberg::TBool) false; + } + + Steinberg::tresult PLUGIN_API setViewIsEmbedded (Steinberg::IPlugView* /*view*/, Steinberg::TBool /*embedded*/) override + { + return kResultOk; + } + #endif + + //============================================================================== + tresult PLUGIN_API setComponentState (IBStream* stream) override + { + // As an IEditController member, the host should only call this from the message thread. + assertHostMessageThread(); + + if (auto* pluginInstance = getPluginInstance()) + { + for (auto vstParamId : audioProcessor->getParamIDs()) + { + auto paramValue = [&] + { + if (vstParamId == audioProcessor->getProgramParamID()) + return EditController::plainParamToNormalized (audioProcessor->getProgramParamID(), + pluginInstance->getCurrentProgram()); + + return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); + }(); + + setParamNormalized (vstParamId, paramValue); + } + } + + if (auto* handler = getComponentHandler()) + handler->restartComponent (Vst::kParamValuesChanged); + + return Vst::EditController::setComponentState (stream); + } + + void setAudioProcessor (JuceAudioProcessor* audioProc) + { + if (audioProcessor != audioProc) + installAudioProcessor (audioProc); + } + + tresult PLUGIN_API connect (IConnectionPoint* other) override + { + if (other != nullptr && audioProcessor == nullptr) + { + auto result = ComponentBase::connect (other); + + if (! audioProcessor.loadFrom (other)) + sendIntMessage ("JuceVST3EditController", (Steinberg::int64) (pointer_sized_int) this); + else + installAudioProcessor (audioProcessor); + + return result; + } + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API getMidiControllerAssignment ([[maybe_unused]] Steinberg::int32 busIndex, + [[maybe_unused]] Steinberg::int16 channel, + [[maybe_unused]] Vst::CtrlNumber midiControllerNumber, + [[maybe_unused]] Vst::ParamID& resultID) override + { + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + resultID = midiControllerToParameter[channel][midiControllerNumber]; + return kResultTrue; // Returning false makes some hosts stop asking for further MIDI Controller Assignments + #else + return kResultFalse; + #endif + } + + // Converts an incoming parameter index to a MIDI controller: + bool getMidiControllerForParameter (Vst::ParamID index, int& channel, int& ctrlNumber) + { + auto mappedIndex = static_cast (index - parameterToMidiControllerOffset); + + if (isPositiveAndBelow (mappedIndex, numElementsInArray (parameterToMidiController))) + { + auto& mc = parameterToMidiController[mappedIndex]; + + if (mc.channel != -1 && mc.ctrlNumber != -1) + { + channel = jlimit (1, 16, mc.channel + 1); + ctrlNumber = mc.ctrlNumber; + return true; + } + } + + return false; + } + + inline bool isMidiControllerParamID (Vst::ParamID paramID) const noexcept + { + return (paramID >= parameterToMidiControllerOffset + && isPositiveAndBelow (paramID - parameterToMidiControllerOffset, + static_cast (numElementsInArray (parameterToMidiController)))); + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitCount(); + + jassertfalse; + return 1; + } + + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitInfo (unitIndex, info); + + jassertfalse; + if (unitIndex == 0) + { + info.id = Vst::kRootUnitId; + info.parentUnitId = Vst::kNoParentUnitId; + info.programListId = Vst::kNoProgramListId; + + toString128 (info.name, TRANS ("Root Unit")); + + return kResultTrue; + } + + zerostruct (info); + return kResultFalse; + } + + Steinberg::int32 PLUGIN_API getProgramListCount() override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramListCount(); + + jassertfalse; + return 0; + } + + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramListInfo (listIndex, info); + + jassertfalse; + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramName (listId, programIndex, name); + + jassertfalse; + toString128 (name, juce::String()); + return kResultFalse; + } + + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Vst::CString attributeId, Vst::String128 attributeValue) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramInfo (listId, programIndex, attributeId, attributeValue); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override + { + if (audioProcessor != nullptr) + return audioProcessor->hasProgramPitchNames (listId, programIndex); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Steinberg::int16 midiPitch, Vst::String128 name) override + { + if (audioProcessor != nullptr) + return audioProcessor->getProgramPitchName (listId, programIndex, midiPitch, name); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override + { + if (audioProcessor != nullptr) + return audioProcessor->selectUnit (unitId); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, + Steinberg::IBStream* data) override + { + if (audioProcessor != nullptr) + return audioProcessor->setUnitProgramData (listOrUnitId, programIndex, data); + + jassertfalse; + return kResultFalse; + } + + Vst::UnitID PLUGIN_API getSelectedUnit() override + { + if (audioProcessor != nullptr) + return audioProcessor->getSelectedUnit(); + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, + Steinberg::int32 channel, Vst::UnitID& unitId) override + { + if (audioProcessor != nullptr) + return audioProcessor->getUnitByBus (type, dir, busIndex, channel, unitId); + + jassertfalse; + return kResultFalse; + } + + //============================================================================== + IPlugView* PLUGIN_API createView (const char* name) override + { + if (auto* pluginInstance = getPluginInstance()) + { + const auto mayCreateEditor = pluginInstance->hasEditor() + && name != nullptr + && std::strcmp (name, Vst::ViewType::kEditor) == 0 + && (pluginInstance->getActiveEditor() == nullptr + || detail::PluginUtilities::getHostType().isAdobeAudition() + || detail::PluginUtilities::getHostType().isPremiere()); + + if (mayCreateEditor) + return new JuceVST3Editor (*this, *audioProcessor); + } + + return nullptr; + } + + //============================================================================== + void beginGesture (Vst::ParamID vstParamId) + { + if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) + beginEdit (vstParamId); + } + + void endGesture (Vst::ParamID vstParamId) + { + if (! inSetState && MessageManager::getInstance()->isThisTheMessageThread()) + endEdit (vstParamId); + } + + void paramChanged (Steinberg::int32 parameterIndex, Vst::ParamID vstParamId, double newValue) + { + if (inParameterChangedCallback || inSetState) + return; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + // NB: Cubase has problems if performEdit is called without setParamNormalized + EditController::setParamNormalized (vstParamId, newValue); + performEdit (vstParamId, newValue); + } + else + { + audioProcessor->setParameterValue (parameterIndex, (float) newValue); + } + } + + //============================================================================== + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override + { + beginGesture (audioProcessor->getVSTParamIDForIndex (index)); + } + + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override + { + endGesture (audioProcessor->getVSTParamIDForIndex (index)); + } + + void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override + { + paramChanged (index, audioProcessor->getVSTParamIDForIndex (index), newValue); + } + + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override + { + int32 flags = 0; + + if (details.parameterInfoChanged) + { + for (int32 i = 0; i < parameters.getParameterCount(); ++i) + if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) + if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) + flags |= Vst::kParamTitlesChanged; + } + + if (auto* pluginInstance = getPluginInstance()) + { + if (details.programChanged) + { + const auto programParameterId = audioProcessor->getProgramParamID(); + + if (audioProcessor->getParamForVSTParamID (programParameterId) != nullptr) + { + const auto currentProgram = pluginInstance->getCurrentProgram(); + const auto paramValue = roundToInt (EditController::normalizedParamToPlain (programParameterId, + EditController::getParamNormalized (programParameterId))); + + if (currentProgram != paramValue) + { + beginGesture (programParameterId); + paramChanged (audioProcessor->findCacheIndexForParamID (programParameterId), + programParameterId, + EditController::plainParamToNormalized (programParameterId, currentProgram)); + endGesture (programParameterId); + + flags |= Vst::kParamValuesChanged; + } + } + } + + auto latencySamples = pluginInstance->getLatencySamples(); + + #if JucePlugin_Enable_ARA + jassert (latencySamples == 0 || ! dynamic_cast (pluginInstance)->isBoundToARA()); + #endif + + if (details.latencyChanged && latencySamples != lastLatencySamples) + { + flags |= Vst::kLatencyChanged; + lastLatencySamples = latencySamples; + } + } + + if (details.nonParameterStateChanged) + flags |= pluginShouldBeMarkedDirtyFlag; + + if (inSetupProcessing) + flags &= Vst::kLatencyChanged; + + componentRestarter.restart (flags); + } + + //============================================================================== + AudioProcessor* getPluginInstance() const noexcept + { + if (audioProcessor != nullptr) + return audioProcessor->get(); + + return nullptr; + } + + static constexpr auto pluginShouldBeMarkedDirtyFlag = 1 << 16; + +private: + bool isBlueCatHost (FUnknown* context) const + { + // We can't use the normal PluginHostType mechanism here because that will give us the name + // of the host process. However, this plugin instance might be loaded in an instance of + // the BlueCat PatchWork host, which might itself be a plugin. + + VSTComSmartPtr host; + host.loadFrom (context); + + if (host == nullptr) + return false; + + Vst::String128 name; + + if (host->getName (name) != kResultOk) + return false; + + const auto hostName = toString (name); + return hostName.contains ("Blue Cat's VST3 Host"); + } + + friend class JuceVST3Component; + friend struct Param; + + //============================================================================== + VSTComSmartPtr audioProcessor; + + struct MidiController + { + int channel = -1, ctrlNumber = -1; + }; + + ComponentRestarter componentRestarter { *this }; + + enum { numMIDIChannels = 16 }; + Vst::ParamID parameterToMidiControllerOffset; + MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; + Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; + + void restartComponentOnMessageThread (int32 flags) override + { + if ((flags & pluginShouldBeMarkedDirtyFlag) != 0) + setDirty (true); + + flags &= ~pluginShouldBeMarkedDirtyFlag; + + if (auto* handler = componentHandler.get()) + handler->restartComponent (flags); + } + + //============================================================================== + struct OwnedParameterListener : public AudioProcessorParameter::Listener + { + OwnedParameterListener (JuceVST3EditController& editController, + AudioProcessorParameter& parameter, + Vst::ParamID paramID, + int cacheIndex) + : owner (editController), + vstParamID (paramID), + parameterIndex (cacheIndex) + { + // We shouldn't be using an OwnedParameterListener for parameters that have + // been added directly to the AudioProcessor. We observe those via the + // normal audioProcessorParameterChanged mechanism. + jassert (parameter.getParameterIndex() == -1); + // The parameter must have a non-negative index in the parameter cache. + jassert (parameterIndex >= 0); + parameter.addListener (this); + } + + void parameterValueChanged (int, float newValue) override + { + owner.paramChanged (parameterIndex, vstParamID, newValue); + } + + void parameterGestureChanged (int, bool gestureIsStarting) override + { + if (gestureIsStarting) + owner.beginGesture (vstParamID); + else + owner.endGesture (vstParamID); + } + + JuceVST3EditController& owner; + const Vst::ParamID vstParamID = Vst::kNoParamId; + const int parameterIndex = -1; + }; + + std::vector> ownedParameterListeners; + + //============================================================================== + bool inSetState = false; + std::atomic vst3IsPlaying { false }, + inSetupProcessing { false }; + + int lastLatencySamples = 0; + bool blueCatPatchwork = isBlueCatHost (hostContext.get()); + + #if ! JUCE_MAC + float lastScaleFactorReceived = 1.0f; + #endif + + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + SharedBase{}, + UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + #endif + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, audioProcessor.get() }; + + return {}; + } + + void installAudioProcessor (const VSTComSmartPtr& newAudioProcessor) + { + audioProcessor = newAudioProcessor; + + if (auto* extensions = audioProcessor->get()->getVST3ClientExtensions()) + { + extensions->setIComponentHandler (componentHandler); + extensions->setIHostApplication (hostContext.get()); + } + + if (auto* pluginInstance = getPluginInstance()) + { + lastLatencySamples = pluginInstance->getLatencySamples(); + + pluginInstance->addListener (this); + + // as the bypass is not part of the regular parameters we need to listen for it explicitly + if (! audioProcessor->isBypassRegularParameter()) + { + const auto paramID = audioProcessor->getBypassParamID(); + ownedParameterListeners.push_back (std::make_unique (*this, + *audioProcessor->getParamForVSTParamID (paramID), + paramID, + audioProcessor->findCacheIndexForParamID (paramID))); + } + + if (parameters.getParameterCount() <= 0) + { + auto n = audioProcessor->getParamIDs().size(); + + for (int i = 0; i < n; ++i) + { + auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); + + if (vstParamID == audioProcessor->getProgramParamID()) + continue; + + auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); + auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); + auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); + + parameters.addParameter (new Param (*this, *juceParam, vstParamID, unitID, + (vstParamID == audioProcessor->getBypassParamID()))); + } + + const auto programParamId = audioProcessor->getProgramParamID(); + + if (auto* programParam = audioProcessor->getParamForVSTParamID (programParamId)) + { + ownedParameterListeners.push_back (std::make_unique (*this, + *programParam, + programParamId, + audioProcessor->findCacheIndexForParamID (programParamId))); + + parameters.addParameter (new ProgramChangeParameter (*pluginInstance, audioProcessor->getProgramParamID())); + } + } + + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + parameterToMidiControllerOffset = static_cast (audioProcessor->isUsingManagedParameters() ? JuceAudioProcessor::paramMidiControllerOffset + : parameters.getParameterCount()); + + initialiseMidiControllerMappings(); + #endif + + audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); + } + } + + void initialiseMidiControllerMappings() + { + for (int c = 0, p = 0; c < numMIDIChannels; ++c) + { + for (int i = 0; i < Vst::kCountCtrlNumber; ++i, ++p) + { + midiControllerToParameter[c][i] = static_cast (p) + parameterToMidiControllerOffset; + parameterToMidiController[p].channel = c; + parameterToMidiController[p].ctrlNumber = i; + + parameters.addParameter (new Vst::Parameter (toString ("MIDI CC " + String (c) + "|" + String (i)), + static_cast (p) + parameterToMidiControllerOffset, nullptr, 0, 0, + 0, Vst::kRootUnitId)); + } + } + } + + void sendIntMessage (const char* idTag, const Steinberg::int64 value) + { + jassert (hostContext != nullptr); + + if (auto* message = allocateMessage()) + { + const FReleaser releaser (message); + message->setMessageID (idTag); + message->getAttributes()->setInt (idTag, value); + sendMessage (message); + } + } + + class EditorContextMenu : public HostProvidedContextMenu + { + public: + EditorContextMenu (AudioProcessorEditor& editorIn, + VSTComSmartPtr contextMenuIn) + : editor (editorIn), contextMenu (contextMenuIn) {} + + PopupMenu getEquivalentPopupMenu() const override + { + using MenuItem = Steinberg::Vst::IContextMenuItem; + using MenuTarget = Steinberg::Vst::IContextMenuTarget; + + struct Submenu + { + PopupMenu menu; + String name; + bool enabled; + }; + + std::vector menuStack (1); + + for (int32_t i = 0, end = contextMenu->getItemCount(); i < end; ++i) + { + MenuItem item{}; + MenuTarget* target = nullptr; + contextMenu->getItem (i, item, &target); + + if ((item.flags & MenuItem::kIsGroupStart) == MenuItem::kIsGroupStart) + { + menuStack.push_back ({ PopupMenu{}, + toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0 }); + } + else if ((item.flags & MenuItem::kIsGroupEnd) == MenuItem::kIsGroupEnd) + { + const auto back = menuStack.back(); + menuStack.pop_back(); + + if (menuStack.empty()) + { + // malformed menu + jassertfalse; + return {}; + } + + menuStack.back().menu.addSubMenu (back.name, back.menu, back.enabled); + } + else if ((item.flags & MenuItem::kIsSeparator) == MenuItem::kIsSeparator) + { + menuStack.back().menu.addSeparator(); + } + else + { + VSTComSmartPtr ownedTarget (target); + const auto tag = item.tag; + menuStack.back().menu.addItem (toString (item.name), + (item.flags & MenuItem::kIsDisabled) == 0, + (item.flags & MenuItem::kIsChecked) != 0, + [ownedTarget, tag] { ownedTarget->executeMenuItem (tag); }); + } + } + + if (menuStack.size() != 1) + { + // malformed menu + jassertfalse; + return {}; + } + + return menuStack.back().menu; + } + + void showNativeMenu (Point pos) const override + { + const auto scaled = pos * Component::getApproximateScaleFactorForComponent (&editor); + contextMenu->popup (scaled.x, scaled.y); + } + + private: + AudioProcessorEditor& editor; + VSTComSmartPtr contextMenu; + }; + + class EditorHostContext : public AudioProcessorEditorHostContext + { + public: + EditorHostContext (JuceAudioProcessor& processorIn, + AudioProcessorEditor& editorIn, + Steinberg::Vst::IComponentHandler* handler, + Steinberg::IPlugView* viewIn) + : processor (processorIn), editor (editorIn), componentHandler (handler), view (viewIn) {} + + std::unique_ptr getContextMenuForParameter (const AudioProcessorParameter* parameter) const override + { + if (componentHandler == nullptr || view == nullptr) + return {}; + + Steinberg::FUnknownPtr handler (componentHandler); + + if (handler == nullptr) + return {}; + + const auto idToUse = parameter != nullptr ? processor.getVSTParamIDForIndex (parameter->getParameterIndex()) : 0; + const auto menu = VSTComSmartPtr (handler->createContextMenu (view, &idToUse)); + return std::make_unique (editor, menu); + } + + private: + JuceAudioProcessor& processor; + AudioProcessorEditor& editor; + Steinberg::Vst::IComponentHandler* componentHandler = nullptr; + Steinberg::IPlugView* view = nullptr; + }; + + //============================================================================== + class JuceVST3Editor : public Vst::EditorView, + public Steinberg::IPlugViewContentScaleSupport, + private Timer + { + public: + JuceVST3Editor (JuceVST3EditController& ec, JuceAudioProcessor& p) + : EditorView (&ec, nullptr), + owner (&ec), + pluginInstance (*p.get()) + { + createContentWrapperComponentIfNeeded(); + + #if JUCE_MAC + if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase10) + cubase10Workaround.reset (new Cubase10WindowResizeWorkaround (*this)); + #endif + } + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto result = testFor (*this, targetIID, UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + return Vst::EditorView::queryInterface (targetIID, obj); + } + + REFCOUNT_METHODS (Vst::EditorView) + + //============================================================================== + tresult PLUGIN_API isPlatformTypeSupported (FIDString type) override + { + if (type != nullptr && pluginInstance.hasEditor()) + { + #if JUCE_WINDOWS + if (strcmp (type, kPlatformTypeHWND) == 0) + #elif JUCE_MAC + if (strcmp (type, kPlatformTypeNSView) == 0 || strcmp (type, kPlatformTypeHIView) == 0) + #elif JUCE_LINUX || JUCE_BSD + if (strcmp (type, kPlatformTypeX11EmbedWindowID) == 0) + #endif + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API attached (void* parent, FIDString type) override + { + if (parent == nullptr || isPlatformTypeSupported (type) == kResultFalse) + return kResultFalse; + + #if JUCE_LINUX || JUCE_BSD + eventHandler->registerHandlerForFrame (plugFrame); + #endif + + systemWindow = parent; + + createContentWrapperComponentIfNeeded(); + + const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()); + + #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD + // If the plugin was last opened at a particular scale, try to reapply that scale here. + // Note that we do this during attach(), rather than in JuceVST3Editor(). During the + // constructor, we don't have a host plugFrame, so + // ContentWrapperComponent::resizeHostWindow() won't do anything, and the content + // wrapper component will be left at the wrong size. + applyScaleFactor (StoredScaleFactor{}.withInternal (owner->lastScaleFactorReceived)); + + // Check the host scale factor *before* calling addToDesktop, so that the initial + // window size during addToDesktop is correct for the current platform scale factor. + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + component->checkHostWindowScaleFactor(); + #endif + + component->setOpaque (true); + component->addToDesktop (desktopFlags, systemWindow); + component->setVisible (true); + + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + component->startTimer (500); + #endif + + #else + macHostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (component.get(), desktopFlags, parent); + #endif + + component->resizeHostWindow(); + attachedToParent(); + + // Life's too short to faff around with wave lab + if (detail::PluginUtilities::getHostType().isWavelab()) + startTimer (200); + + return kResultTrue; + } + + tresult PLUGIN_API removed() override + { + if (component != nullptr) + { + #if JUCE_WINDOWS + component->removeFromDesktop(); + #elif JUCE_MAC + if (macHostWindow != nullptr) + { + detail::VSTWindowUtilities::detachComponentFromWindowRefVST (component.get(), macHostWindow); + macHostWindow = nullptr; + } + #endif + + component = nullptr; + } + + #if JUCE_LINUX || JUCE_BSD + eventHandler->unregisterHandlerForFrame (plugFrame); + #endif + + return CPluginView::removed(); + } + + tresult PLUGIN_API onSize (ViewRect* newSize) override + { + if (newSize != nullptr) + { + rect = convertFromHostBounds (*newSize); + + if (component != nullptr) + { + component->setSize (rect.getWidth(), rect.getHeight()); + + #if JUCE_MAC + if (cubase10Workaround != nullptr) + { + cubase10Workaround->triggerAsyncUpdate(); + } + else + #endif + { + if (auto* peer = component->getPeer()) + peer->updateBounds(); + } + } + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API getSize (ViewRect* size) override + { + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + if (detail::PluginUtilities::getHostType().isAbletonLive() && systemWindow == nullptr) + return kResultFalse; + #endif + + if (size != nullptr && component != nullptr) + { + auto editorBounds = component->getSizeToContainChild(); + + *size = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); + return kResultTrue; + } + + return kResultFalse; + } + + tresult PLUGIN_API canResize() override + { + if (component != nullptr) + if (auto* editor = component->pluginEditor.get()) + if (editor->isResizable()) + return kResultTrue; + + return kResultFalse; + } + + tresult PLUGIN_API checkSizeConstraint (ViewRect* rectToCheck) override + { + if (rectToCheck != nullptr && component != nullptr) + { + if (auto* editor = component->pluginEditor.get()) + { + if (canResize() == kResultFalse) + { + // Ableton Live will call checkSizeConstraint even if the view returns false + // from canResize. Set the out param to an appropriate size for the editor + // and return. + auto constrainedRect = component->getLocalArea (editor, editor->getLocalBounds()) + .getSmallestIntegerContainer(); + + *rectToCheck = convertFromHostBounds (*rectToCheck); + rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); + rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); + *rectToCheck = convertToHostBounds (*rectToCheck); + } + else if (auto* constrainer = editor->getConstrainer()) + { + *rectToCheck = convertFromHostBounds (*rectToCheck); + + auto editorBounds = editor->getLocalArea (component.get(), + Rectangle::leftTopRightBottom (rectToCheck->left, rectToCheck->top, + rectToCheck->right, rectToCheck->bottom).toFloat()); + + auto minW = (float) constrainer->getMinimumWidth(); + auto maxW = (float) constrainer->getMaximumWidth(); + auto minH = (float) constrainer->getMinimumHeight(); + auto maxH = (float) constrainer->getMaximumHeight(); + + auto width = jlimit (minW, maxW, editorBounds.getWidth()); + auto height = jlimit (minH, maxH, editorBounds.getHeight()); + + auto aspectRatio = (float) constrainer->getFixedAspectRatio(); + + if (! approximatelyEqual (aspectRatio, 0.0f)) + { + bool adjustWidth = (width / height > aspectRatio); + + if (detail::PluginUtilities::getHostType().type == PluginHostType::SteinbergCubase9) + { + auto currentEditorBounds = editor->getBounds().toFloat(); + + if (approximatelyEqual (currentEditorBounds.getWidth(), width) && ! approximatelyEqual (currentEditorBounds.getHeight(), height)) + adjustWidth = true; + else if (approximatelyEqual (currentEditorBounds.getHeight(), height) && ! approximatelyEqual (currentEditorBounds.getWidth(), width)) + adjustWidth = false; + } + + if (adjustWidth) + { + width = height * aspectRatio; + + if (width > maxW || width < minW) + { + width = jlimit (minW, maxW, width); + height = width / aspectRatio; + } + } + else + { + height = width / aspectRatio; + + if (height > maxH || height < minH) + { + height = jlimit (minH, maxH, height); + width = height * aspectRatio; + } + } + } + + auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)) + .getSmallestIntegerContainer(); + + rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); + rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); + + *rectToCheck = convertToHostBounds (*rectToCheck); + } + } + + return kResultTrue; + } + + jassertfalse; + return kResultFalse; + } + + tresult PLUGIN_API setContentScaleFactor ([[maybe_unused]] const Steinberg::IPlugViewContentScaleSupport::ScaleFactor factor) override + { + #if ! JUCE_MAC + const auto scaleToApply = [&] + { + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + // Cubase 10 only sends integer scale factors, so correct this for fractional scales + if (detail::PluginUtilities::getHostType().type != PluginHostType::SteinbergCubase10) + return factor; + + const auto hostWindowScale = (Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getScaleFactorForWindow (static_cast (systemWindow)); + + if (hostWindowScale <= 0.0 || approximatelyEqual (factor, hostWindowScale)) + return factor; + + return hostWindowScale; + #else + return factor; + #endif + }(); + + applyScaleFactor (scaleFactor.withHost (scaleToApply)); + + return kResultTrue; + #else + return kResultFalse; + #endif + } + + private: + void timerCallback() override + { + stopTimer(); + + ViewRect viewRect; + getSize (&viewRect); + onSize (&viewRect); + } + + static ViewRect convertToHostBounds (ViewRect pluginRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return pluginRect; + + return { roundToInt ((float) pluginRect.left * desktopScale), + roundToInt ((float) pluginRect.top * desktopScale), + roundToInt ((float) pluginRect.right * desktopScale), + roundToInt ((float) pluginRect.bottom * desktopScale) }; + } + + static ViewRect convertFromHostBounds (ViewRect hostRect) + { + auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); + + if (approximatelyEqual (desktopScale, 1.0f)) + return hostRect; + + return { roundToInt ((float) hostRect.left / desktopScale), + roundToInt ((float) hostRect.top / desktopScale), + roundToInt ((float) hostRect.right / desktopScale), + roundToInt ((float) hostRect.bottom / desktopScale) }; + } + + //============================================================================== + struct ContentWrapperComponent : public Component + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + , public Timer + #endif + { + ContentWrapperComponent (JuceVST3Editor& editor) : owner (editor) + { + setOpaque (true); + setBroughtToFrontOnMouseClick (true); + } + + ~ContentWrapperComponent() override + { + if (pluginEditor != nullptr) + { + PopupMenu::dismissAllActiveMenus(); + pluginEditor->processor.editorBeingDeleted (pluginEditor.get()); + } + } + + void createEditor (AudioProcessor& plugin) + { + pluginEditor.reset (plugin.createEditorIfNeeded()); + + #if JucePlugin_Enable_ARA + jassert (dynamic_cast (pluginEditor.get()) != nullptr); + // for proper view embedding, ARA plug-ins must be resizable + jassert (pluginEditor->isResizable()); + #endif + + if (pluginEditor != nullptr) + { + editorHostContext = std::make_unique (*owner.owner->audioProcessor, + *pluginEditor, + owner.owner->getComponentHandler(), + &owner); + + pluginEditor->setHostContext (editorHostContext.get()); + #if ! JUCE_MAC + pluginEditor->setScaleFactor (owner.scaleFactor.get()); + #endif + + addAndMakeVisible (pluginEditor.get()); + pluginEditor->setTopLeftPosition (0, 0); + + lastBounds = getSizeToContainChild(); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + setBounds (lastBounds); + } + + resizeHostWindow(); + } + else + { + // if hasEditor() returns true then createEditorIfNeeded has to return a valid editor + jassertfalse; + } + } + + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } + + juce::Rectangle getSizeToContainChild() + { + if (pluginEditor != nullptr) + return getLocalArea (pluginEditor.get(), pluginEditor->getLocalBounds()); + + return {}; + } + + void childBoundsChanged (Component*) override + { + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + resizeHostWindow(); + + #if JUCE_LINUX || JUCE_BSD + if (detail::PluginUtilities::getHostType().isBitwigStudio()) + repaint(); + #endif + + lastBounds = newBounds; + } + } + + void resized() override + { + if (pluginEditor != nullptr) + { + if (! resizingParent) + { + auto newBounds = getLocalBounds(); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); + } + + lastBounds = newBounds; + } + } + } + + void parentSizeChanged() override + { + if (pluginEditor != nullptr) + { + resizeHostWindow(); + pluginEditor->repaint(); + } + } + + void resizeHostWindow() + { + if (pluginEditor != nullptr) + { + if (owner.plugFrame != nullptr) + { + auto editorBounds = getSizeToContainChild(); + auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); + + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); + owner.plugFrame->resizeView (&owner, &newSize); + } + + auto host = detail::PluginUtilities::getHostType(); + + #if JUCE_MAC + if (host.isWavelab() || host.isReaper() || owner.owner->blueCatPatchwork) + #else + if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio() || owner.owner->blueCatPatchwork) + #endif + setBounds (editorBounds.withPosition (0, 0)); + } + } + } + + void setEditorScaleFactor (float scale) + { + if (pluginEditor != nullptr) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + pluginEditor->setScaleFactor (scale); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); + } + + lastBounds = getSizeToContainChild(); + + resizeHostWindow(); + repaint(); + } + } + + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + void checkHostWindowScaleFactor() + { + const auto estimatedScale = (float) getScaleFactorForWindow (static_cast (owner.systemWindow)); + + if (estimatedScale > 0.0) + owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale)); + } + + void timerCallback() override + { + checkHostWindowScaleFactor(); + } + #endif + + std::unique_ptr pluginEditor; + + private: + JuceVST3Editor& owner; + std::unique_ptr editorHostContext; + Rectangle lastBounds; + bool resizingChild = false, resizingParent = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) + }; + + void createContentWrapperComponentIfNeeded() + { + if (component == nullptr) + { + #if JUCE_LINUX || JUCE_BSD + const MessageManagerLock mmLock; + #endif + + component.reset (new ContentWrapperComponent (*this)); + component->createEditor (pluginInstance); + } + } + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + SharedResourcePointer eventHandler; + #endif + + VSTComSmartPtr owner; + AudioProcessor& pluginInstance; + + #if JUCE_LINUX || JUCE_BSD + struct MessageManagerLockedDeleter + { + template + void operator() (ObjectType* object) const noexcept + { + const MessageManagerLock mmLock; + delete object; + } + }; + + std::unique_ptr component; + #else + std::unique_ptr component; + #endif + + friend struct ContentWrapperComponent; + + #if JUCE_MAC + void* macHostWindow = nullptr; + + // On macOS Cubase 10 resizes the host window after calling onSize() resulting in the peer + // bounds being a step behind the plug-in. Calling updateBounds() asynchronously seems to fix things... + struct Cubase10WindowResizeWorkaround : public AsyncUpdater + { + Cubase10WindowResizeWorkaround (JuceVST3Editor& o) : owner (o) {} + + void handleAsyncUpdate() override + { + if (owner.component != nullptr) + if (auto* peer = owner.component->getPeer()) + peer->updateBounds(); + } + + JuceVST3Editor& owner; + }; + + std::unique_ptr cubase10Workaround; + #else + class StoredScaleFactor + { + public: + StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } + StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } + float get() const { return host.value_or (internal); } + + private: + std::optional host; + float internal = 1.0f; + }; + + void applyScaleFactor (const StoredScaleFactor newFactor) + { + const auto previous = std::exchange (scaleFactor, newFactor).get(); + + if (approximatelyEqual (previous, scaleFactor.get())) + return; + + if (owner != nullptr) + owner->lastScaleFactorReceived = scaleFactor.get(); + + if (component != nullptr) + { + #if JUCE_LINUX || JUCE_BSD + const MessageManagerLock mmLock; + #endif + component->setEditorScaleFactor (scaleFactor.get()); + } + } + + StoredScaleFactor scaleFactor; + + #if JUCE_WINDOWS + detail::WindowsHooks hooks; + #endif + + #endif + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Editor) + }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) +}; + + +//============================================================================== +#if JucePlugin_Enable_ARA + class JuceARAFactory : public ARA::IMainFactory + { + public: + JuceARAFactory() = default; + virtual ~JuceARAFactory() = default; + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const ::Steinberg::TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + if (doUIDsMatch (targetIID, JuceARAFactory::iid)) + { + addRef(); + *obj = this; + return kResultOk; + } + + *obj = nullptr; + return kNoInterface; + } + + //---from ARA::IMainFactory------- + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + + inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0xA1B2C3D4, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + + private: + //============================================================================== + std::atomic refCount { 1 }; + }; +#endif + +//============================================================================== +class JuceVST3Component : public Vst::IComponent, + public Vst::IAudioProcessor, + public Vst::IUnitInfo, + public Vst::IConnectionPoint, + public Vst::IProcessContextRequirements, + #if JucePlugin_Enable_ARA + public ARA::IPlugInEntryPoint, + public ARA::IPlugInEntryPoint2, + #endif + public AudioPlayHead +{ +public: + JuceVST3Component (Vst::IHostApplication* h) + : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3).release()), + host (h) + { + inParameterChangedCallback = false; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + [[maybe_unused]] const int numConfigs = numElementsInArray (configs); + + jassert (numConfigs > 0 && (configs[0][0] > 0 || configs[0][1] > 0)); + + pluginInstance->setPlayConfigDetails (configs[0][0], configs[0][1], 44100.0, 1024); + #endif + + // VST-3 requires your default layout to be non-discrete! + // For example, your default layout must be mono, stereo, quadrophonic + // and not AudioChannelSet::discreteChannels (2) etc. + jassert (checkBusFormatsAreNotDiscrete()); + + comPluginInstance = VSTComSmartPtr { new JuceAudioProcessor (pluginInstance) }; + + zerostruct (processContext); + + processSetup.maxSamplesPerBlock = 1024; + processSetup.processMode = Vst::kRealtime; + processSetup.sampleRate = 44100.0; + processSetup.symbolicSampleSize = Vst::kSample32; + + pluginInstance->setPlayHead (this); + + // Constructing the underlying static object involves dynamic allocation. + // This call ensures that the construction won't happen on the audio thread. + detail::PluginUtilities::getHostType(); + } + + ~JuceVST3Component() override + { + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + + if (pluginInstance != nullptr) + if (pluginInstance->getPlayHead() == this) + pluginInstance->setPlayHead (nullptr); + } + + //============================================================================== + AudioProcessor& getPluginInstance() const noexcept { return *pluginInstance; } + + //============================================================================== + #if JUCE_VST3_CAN_REPLACE_VST2 + inline static const FUID iid = getFUIDForVST2ID (false); + #else + inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0x9182FAEB, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + #endif + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto userProvidedInterface = queryAdditionalInterfaces (&getPluginInstance(), + targetIID, + &VST3ClientExtensions::queryIAudioProcessor); + + const auto juceProvidedInterface = queryInterfaceInternal (targetIID); + + return extractResult (userProvidedInterface, juceProvidedInterface, obj); + } + + enum class CallPrepareToPlay { no, yes }; + + //============================================================================== + tresult PLUGIN_API initialize (FUnknown* hostContext) override + { + if (host != hostContext) + host.loadFrom (hostContext); + + processContext.sampleRate = processSetup.sampleRate; + preparePlugin (processSetup.sampleRate, (int) processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); + + return kResultTrue; + } + + tresult PLUGIN_API terminate() override + { + getPluginInstance().releaseResources(); + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API connect (IConnectionPoint* other) override + { + if (other != nullptr && juceVST3EditController == nullptr) + juceVST3EditController.loadFrom (other); + + return kResultTrue; + } + + tresult PLUGIN_API disconnect (IConnectionPoint*) override + { + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + + juceVST3EditController = {}; + return kResultTrue; + } + + tresult PLUGIN_API notify (Vst::IMessage* message) override + { + if (message != nullptr && juceVST3EditController == nullptr) + { + Steinberg::int64 value = 0; + + if (message->getAttributes()->getInt ("JuceVST3EditController", value) == kResultTrue) + { + juceVST3EditController = VSTComSmartPtr { (JuceVST3EditController*) (pointer_sized_int) value }; + + if (juceVST3EditController != nullptr) + juceVST3EditController->setAudioProcessor (comPluginInstance); + else + jassertfalse; + } + } + + return kResultTrue; + } + + tresult PLUGIN_API getControllerClassId (TUID classID) override + { + memcpy (classID, JuceVST3EditController::iid, sizeof (TUID)); + return kResultTrue; + } + + //============================================================================== + tresult PLUGIN_API setActive (TBool state) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + const auto willBeActive = (state != 0); + + active = false; + // Some hosts may call setBusArrangements in response to calls made during prepareToPlay + // or releaseResources. Specifically, Wavelab 11.1 calls setBusArrangements in the same + // call stack when the AudioProcessor calls setLatencySamples inside prepareToPlay. + // In order for setBusArrangements to return successfully, the plugin must not be activated + // until after prepareToPlay has completely finished. + const ScopeGuard scope { [&] { active = willBeActive; } }; + + if (willBeActive) + { + const auto sampleRate = processSetup.sampleRate > 0.0 + ? processSetup.sampleRate + : getPluginInstance().getSampleRate(); + + const auto bufferSize = processSetup.maxSamplesPerBlock > 0 + ? (int) processSetup.maxSamplesPerBlock + : getPluginInstance().getBlockSize(); + + preparePlugin (sampleRate, bufferSize, CallPrepareToPlay::yes); + } + else + { + getPluginInstance().releaseResources(); + } + + return kResultOk; + } + + tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } + tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } + + //============================================================================== + bool isBypassed() const + { + if (auto* bypassParam = comPluginInstance->getBypassParameter()) + return bypassParam->getValue() >= 0.5f; + + return false; + } + + void setBypassed (bool shouldBeBypassed) + { + if (auto* bypassParam = comPluginInstance->getBypassParameter()) + setValueAndNotifyIfChanged (*bypassParam, shouldBeBypassed ? 1.0f : 0.0f); + } + + //============================================================================== + void writeJucePrivateStateInformation (MemoryOutputStream& out) + { + if (pluginInstance->getBypassParameter() == nullptr) + { + ValueTree privateData (kJucePrivateDataIdentifier); + + // for now we only store the bypass value + privateData.setProperty ("Bypass", var (isBypassed()), nullptr); + privateData.writeToStream (out); + } + } + + void setJucePrivateStateInformation (const void* data, int sizeInBytes) + { + if (pluginInstance->getBypassParameter() == nullptr) + { + if (comPluginInstance->getBypassParameter() != nullptr) + { + auto privateData = ValueTree::readFromData (data, static_cast (sizeInBytes)); + setBypassed (static_cast (privateData.getProperty ("Bypass", var (false)))); + } + } + } + + void getStateInformation (MemoryBlock& destData) + { + pluginInstance->getStateInformation (destData); + + // With bypass support, JUCE now needs to store private state data. + // Put this at the end of the plug-in state and add a few null characters + // so that plug-ins built with older versions of JUCE will hopefully ignore + // this data. Additionally, we need to add some sort of magic identifier + // at the very end of the private data so that JUCE has some sort of + // way to figure out if the data was stored with a newer JUCE version. + MemoryOutputStream extraData; + + extraData.writeInt64 (0); + writeJucePrivateStateInformation (extraData); + auto privateDataSize = (int64) (extraData.getDataSize() - sizeof (int64)); + extraData.writeInt64 (privateDataSize); + extraData << kJucePrivateDataIdentifier; + + // write magic string + destData.append (extraData.getData(), extraData.getDataSize()); + } + + void setStateInformation (const void* data, int sizeAsInt) + { + bool unusedState = false; + auto& flagToSet = juceVST3EditController != nullptr ? juceVST3EditController->inSetState : unusedState; + const ScopedValueSetter scope (flagToSet, true); + + auto size = (uint64) sizeAsInt; + + // Check if this data was written with a newer JUCE version + // and if it has the JUCE private data magic code at the end + auto jucePrivDataIdentifierSize = std::strlen (kJucePrivateDataIdentifier); + + if ((size_t) size >= jucePrivDataIdentifierSize + sizeof (int64)) + { + auto buffer = static_cast (data); + + String magic (CharPointer_UTF8 (buffer + size - jucePrivDataIdentifierSize), + CharPointer_UTF8 (buffer + size)); + + if (magic == kJucePrivateDataIdentifier) + { + // found a JUCE private data section + uint64 privateDataSize; + + std::memcpy (&privateDataSize, + buffer + ((size_t) size - jucePrivDataIdentifierSize - sizeof (uint64)), + sizeof (uint64)); + + privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize); + size -= privateDataSize + jucePrivDataIdentifierSize + sizeof (uint64); + + if (privateDataSize > 0) + setJucePrivateStateInformation (buffer + size, static_cast (privateDataSize)); + + size -= sizeof (uint64); + } + } + + if (size > 0) + pluginInstance->setStateInformation (data, static_cast (size)); + } + + //============================================================================== + #if JUCE_VST3_CAN_REPLACE_VST2 + bool loadVST2VstWBlock (const char* data, int size) + { + jassert (ByteOrder::bigEndianInt ("VstW") == htonl ((uint32) readUnaligned (data))); + jassert (1 == htonl ((uint32) readUnaligned (data + 8))); // version should be 1 according to Steinberg's docs + + auto headerLen = (int) htonl ((uint32) readUnaligned (data + 4)) + 8; + return loadVST2CcnKBlock (data + headerLen, size - headerLen); + } + + bool loadVST2CcnKBlock (const char* data, int size) + { + auto* bank = reinterpret_cast (data); + + jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); + jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); + jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); + jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); + + setStateInformation (bank->content.data.chunk, + jmin ((int) (size - (bank->content.data.chunk - data)), + (int) htonl ((uint32) bank->content.data.size))); + return true; + } + + bool loadVST3PresetFile (const char* data, int size) + { + if (size < 48) + return false; + + // At offset 4 there's a little-endian version number which seems to typically be 1 + // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" + auto chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); + jassert (memcmp (data + chunkListOffset, "List", 4) == 0); + auto entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); + jassert (entryCount > 0); + + for (int i = 0; i < entryCount; ++i) + { + auto entryOffset = chunkListOffset + 8 + 20 * i; + + if (entryOffset + 20 > size) + return false; + + if (memcmp (data + entryOffset, "Comp", 4) == 0) + { + // "Comp" entries seem to contain the data. + auto chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); + auto chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); + + if (static_cast (chunkOffset + chunkSize) > static_cast (size)) + { + jassertfalse; + return false; + } + + loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); + } + } + + return true; + } + + bool loadVST2CompatibleState (const char* data, int size) + { + if (size < 4) + return false; + + auto header = htonl ((uint32) readUnaligned (data)); + + if (header == ByteOrder::bigEndianInt ("VstW")) + return loadVST2VstWBlock (data, size); + + if (header == ByteOrder::bigEndianInt ("CcnK")) + return loadVST2CcnKBlock (data, size); + + if (memcmp (data, "VST3", 4) == 0) + { + // In Cubase 5, when loading VST3 .vstpreset files, + // we get the whole content of the files to load. + // In Cubase 7 we get just the contents within and + // we go directly to the loadVST2VstW codepath instead. + return loadVST3PresetFile (data, size); + } + + return false; + } + #endif + + void loadStateData (const void* data, int size) + { + #if JUCE_VST3_CAN_REPLACE_VST2 + if (loadVST2CompatibleState ((const char*) data, size)) + return; + #endif + setStateInformation (data, size); + } + + bool readFromMemoryStream (IBStream* state) + { + FUnknownPtr s (state); + Steinberg::int64 size = 0; + + if (s != nullptr + && s->getStreamSize (size) == kResultOk + && size > 0 + && size < 1024 * 1024 * 100) // (some hosts seem to return junk for the size) + { + MemoryBlock block (static_cast (size)); + + // turns out that Cubase 9 might give you the incorrect stream size :-( + Steinberg::int32 bytesRead = 1; + int len; + + for (len = 0; bytesRead > 0 && len < static_cast (block.getSize()); len += bytesRead) + if (state->read (block.getData(), static_cast (block.getSize()), &bytesRead) != kResultOk) + break; + + if (len == 0) + return false; + + block.setSize (static_cast (len)); + + // Adobe Audition CS6 hack to avoid trying to use corrupted streams: + if (detail::PluginUtilities::getHostType().isAdobeAudition()) + if (block.getSize() >= 5 && memcmp (block.getData(), "VC2!E", 5) == 0) + return false; + + loadStateData (block.getData(), (int) block.getSize()); + return true; + } + + return false; + } + + bool readFromUnknownStream (IBStream* state) + { + MemoryOutputStream allData; + + { + const size_t bytesPerBlock = 4096; + HeapBlock buffer (bytesPerBlock); + + for (;;) + { + Steinberg::int32 bytesRead = 0; + auto status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); + + if (bytesRead <= 0 || (status != kResultTrue && ! detail::PluginUtilities::getHostType().isWavelab())) + break; + + allData.write (buffer, static_cast (bytesRead)); + } + } + + const size_t dataSize = allData.getDataSize(); + + if (dataSize <= 0 || dataSize >= 0x7fffffff) + return false; + + loadStateData (allData.getData(), (int) dataSize); + return true; + } + + tresult PLUGIN_API setState (IBStream* state) override + { + // The VST3 spec requires that this function is called from the UI thread. + // If this assertion fires, your host is misbehaving! + assertHostMessageThread(); + + if (state == nullptr) + return kInvalidArgument; + + FUnknownPtr stateRefHolder (state); // just in case the caller hasn't properly ref-counted the stream object + + if (state->seek (0, IBStream::kIBSeekSet, nullptr) == kResultTrue) + { + if (! detail::PluginUtilities::getHostType().isFruityLoops() && readFromMemoryStream (state)) + return kResultTrue; + + if (readFromUnknownStream (state)) + return kResultTrue; + } + + return kResultFalse; + } + + #if JUCE_VST3_CAN_REPLACE_VST2 + static tresult writeVST2Header (IBStream* state, bool bypassed) + { + auto writeVST2IntToState = [state] (uint32 n) + { + auto t = (int32) htonl (n); + return state->write (&t, 4); + }; + + auto status = writeVST2IntToState (ByteOrder::bigEndianInt ("VstW")); + + if (status == kResultOk) status = writeVST2IntToState (8); // header size + if (status == kResultOk) status = writeVST2IntToState (1); // version + if (status == kResultOk) status = writeVST2IntToState (bypassed ? 1 : 0); // bypass + + return status; + } + #endif + + tresult PLUGIN_API getState (IBStream* state) override + { + if (state == nullptr) + return kInvalidArgument; + + MemoryBlock mem; + getStateInformation (mem); + + #if JUCE_VST3_CAN_REPLACE_VST2 + tresult status = writeVST2Header (state, isBypassed()); + + if (status != kResultOk) + return status; + + const int bankBlockSize = 160; + Vst2::fxBank bank; + + zerostruct (bank); + bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); + bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); + bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); + bank.version = (int32) htonl (2); + bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); + bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); + bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); + + status = state->write (&bank, bankBlockSize); + + if (status != kResultOk) + return status; + #endif + + return state->write (mem.getData(), (Steinberg::int32) mem.getSize()); + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getUnitCount() override { return comPluginInstance->getUnitCount(); } + tresult PLUGIN_API getUnitInfo (Steinberg::int32 unitIndex, Vst::UnitInfo& info) override { return comPluginInstance->getUnitInfo (unitIndex, info); } + Steinberg::int32 PLUGIN_API getProgramListCount() override { return comPluginInstance->getProgramListCount(); } + tresult PLUGIN_API getProgramListInfo (Steinberg::int32 listIndex, Vst::ProgramListInfo& info) override { return comPluginInstance->getProgramListInfo (listIndex, info); } + tresult PLUGIN_API getProgramName (Vst::ProgramListID listId, Steinberg::int32 programIndex, Vst::String128 name) override { return comPluginInstance->getProgramName (listId, programIndex, name); } + tresult PLUGIN_API getProgramInfo (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Vst::CString attributeId, Vst::String128 attributeValue) override { return comPluginInstance->getProgramInfo (listId, programIndex, attributeId, attributeValue); } + tresult PLUGIN_API hasProgramPitchNames (Vst::ProgramListID listId, Steinberg::int32 programIndex) override { return comPluginInstance->hasProgramPitchNames (listId, programIndex); } + tresult PLUGIN_API getProgramPitchName (Vst::ProgramListID listId, Steinberg::int32 programIndex, + Steinberg::int16 midiPitch, Vst::String128 name) override { return comPluginInstance->getProgramPitchName (listId, programIndex, midiPitch, name); } + tresult PLUGIN_API selectUnit (Vst::UnitID unitId) override { return comPluginInstance->selectUnit (unitId); } + tresult PLUGIN_API setUnitProgramData (Steinberg::int32 listOrUnitId, Steinberg::int32 programIndex, + Steinberg::IBStream* data) override { return comPluginInstance->setUnitProgramData (listOrUnitId, programIndex, data); } + Vst::UnitID PLUGIN_API getSelectedUnit() override { return comPluginInstance->getSelectedUnit(); } + tresult PLUGIN_API getUnitByBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 busIndex, + Steinberg::int32 channel, Vst::UnitID& unitId) override { return comPluginInstance->getUnitByBus (type, dir, busIndex, channel, unitId); } + + //============================================================================== + Optional getPosition() const override + { + PositionInfo info; + info.setTimeInSamples (jmax ((Steinberg::int64) 0, processContext.projectTimeSamples)); + info.setTimeInSeconds (static_cast (*info.getTimeInSamples()) / processContext.sampleRate); + info.setIsRecording ((processContext.state & Vst::ProcessContext::kRecording) != 0); + info.setIsPlaying ((processContext.state & Vst::ProcessContext::kPlaying) != 0); + info.setIsLooping ((processContext.state & Vst::ProcessContext::kCycleActive) != 0); + + info.setBpm ((processContext.state & Vst::ProcessContext::kTempoValid) != 0 + ? makeOptional (processContext.tempo) + : nullopt); + + info.setTimeSignature ((processContext.state & Vst::ProcessContext::kTimeSigValid) != 0 + ? makeOptional (TimeSignature { processContext.timeSigNumerator, processContext.timeSigDenominator }) + : nullopt); + + info.setLoopPoints ((processContext.state & Vst::ProcessContext::kCycleValid) != 0 + ? makeOptional (LoopPoints { processContext.cycleStartMusic, processContext.cycleEndMusic }) + : nullopt); + + info.setPpqPosition ((processContext.state & Vst::ProcessContext::kProjectTimeMusicValid) != 0 + ? makeOptional (processContext.projectTimeMusic) + : nullopt); + + info.setPpqPositionOfLastBarStart ((processContext.state & Vst::ProcessContext::kBarPositionValid) != 0 + ? makeOptional (processContext.barPositionMusic) + : nullopt); + + info.setFrameRate ((processContext.state & Vst::ProcessContext::kSmpteValid) != 0 + ? makeOptional (FrameRate().withBaseRate ((int) processContext.frameRate.framesPerSecond) + .withDrop ((processContext.frameRate.flags & Vst::FrameRate::kDropRate) != 0) + .withPullDown ((processContext.frameRate.flags & Vst::FrameRate::kPullDownRate) != 0)) + : nullopt); + + info.setEditOriginTime (info.getFrameRate().hasValue() + ? makeOptional ((double) processContext.smpteOffsetSubframes / (80.0 * info.getFrameRate()->getEffectiveRate())) + : nullopt); + + info.setHostTimeNs ((processContext.state & Vst::ProcessContext::kSystemTimeValid) != 0 + ? makeOptional ((uint64_t) processContext.systemTime) + : nullopt); + + return info; + } + + //============================================================================== + int getNumAudioBuses (bool isInput) const + { + int busCount = pluginInstance->getBusCount (isInput); + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; + const int numConfigs = numElementsInArray (configs); + + bool hasOnlyZeroChannels = true; + + for (int i = 0; i < numConfigs && hasOnlyZeroChannels == true; ++i) + if (configs[i][isInput ? 0 : 1] != 0) + hasOnlyZeroChannels = false; + + busCount = jmin (busCount, hasOnlyZeroChannels ? 0 : 1); + #endif + + return busCount; + } + + //============================================================================== + Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override + { + if (type == Vst::kAudio) + return getNumAudioBuses (dir == Vst::kInput); + + if (type == Vst::kEvent) + { + #if JucePlugin_WantsMidiInput + if (dir == Vst::kInput) + return 1; + #endif + + #if JucePlugin_ProducesMidiOutput + if (dir == Vst::kOutput) + return 1; + #endif + } + + return 0; + } + + tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, + Steinberg::int32 index, Vst::BusInfo& info) override + { + if (type == Vst::kAudio) + { + if (index < 0 || index >= getNumAudioBuses (dir == Vst::kInput)) + return kResultFalse; + + if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) + { + info.mediaType = Vst::kAudio; + info.direction = dir; + info.channelCount = bus->getLastEnabledLayout().size(); + + [[maybe_unused]] const auto lastEnabledVst3Layout = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); + jassert (lastEnabledVst3Layout.has_value() && info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (*lastEnabledVst3Layout)); + toString128 (info.name, bus->getName()); + + info.busType = [&] + { + const auto isFirstBus = (index == 0); + + if (dir == Vst::kInput) + { + if (isFirstBus) + { + if (auto* extensions = pluginInstance->getVST3ClientExtensions()) + return extensions->getPluginHasMainInput() ? Vst::kMain : Vst::kAux; + + return Vst::kMain; + } + + return Vst::kAux; + } + + #if JucePlugin_IsSynth + return Vst::kMain; + #else + return isFirstBus ? Vst::kMain : Vst::kAux; + #endif + }(); + + #ifdef JucePlugin_PreferredChannelConfigurations + info.flags = Vst::BusInfo::kDefaultActive; + #else + info.flags = (bus->isEnabledByDefault()) ? Vst::BusInfo::kDefaultActive : 0; + #endif + + return kResultTrue; + } + } + + if (type == Vst::kEvent) + { + info.flags = Vst::BusInfo::kDefaultActive; + + #if JucePlugin_WantsMidiInput + if (dir == Vst::kInput && index == 0) + { + info.mediaType = Vst::kEvent; + info.direction = dir; + + #ifdef JucePlugin_VSTNumMidiInputs + info.channelCount = JucePlugin_VSTNumMidiInputs; + #else + info.channelCount = 16; + #endif + + toString128 (info.name, TRANS("MIDI Input")); + info.busType = Vst::kMain; + return kResultTrue; + } + #endif + + #if JucePlugin_ProducesMidiOutput + if (dir == Vst::kOutput && index == 0) + { + info.mediaType = Vst::kEvent; + info.direction = dir; + + #ifdef JucePlugin_VSTNumMidiOutputs + info.channelCount = JucePlugin_VSTNumMidiOutputs; + #else + info.channelCount = 16; + #endif + + toString128 (info.name, TRANS("MIDI Output")); + info.busType = Vst::kMain; + return kResultTrue; + } + #endif + } + + zerostruct (info); + return kResultFalse; + } + + tresult PLUGIN_API activateBus (Vst::MediaType type, + Vst::BusDirection dir, + Steinberg::int32 index, + TBool state) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + // The host is misbehaving! The plugin must be deactivated before setting new arrangements. + jassert (! active); + + if (type == Vst::kEvent) + { + #if JucePlugin_WantsMidiInput + if (index == 0 && dir == Vst::kInput) + { + isMidiInputBusEnabled = (state != 0); + return kResultTrue; + } + #endif + + #if JucePlugin_ProducesMidiOutput + if (index == 0 && dir == Vst::kOutput) + { + isMidiOutputBusEnabled = (state != 0); + return kResultTrue; + } + #endif + + return kResultFalse; + } + + if (type == Vst::kAudio) + { + const auto numPublicInputBuses = getNumAudioBuses (true); + const auto numPublicOutputBuses = getNumAudioBuses (false); + + if (! isPositiveAndBelow (index, dir == Vst::kInput ? numPublicInputBuses : numPublicOutputBuses)) + return kResultFalse; + + // The host is allowed to enable/disable buses as it sees fit, so the plugin needs to be + // able to handle any set of enabled/disabled buses, including layouts for which + // AudioProcessor::isBusesLayoutSupported would return false. + // Our strategy is to keep track of the layout that the host last requested, and to + // attempt to apply that layout directly. + // If the layout isn't supported by the processor, we'll try enabling all the buses + // instead. + // If the host enables a bus that the processor refused to enable, then we'll ignore + // that bus (and return silence for output buses). If the host disables a bus that the + // processor refuses to disable, the wrapper will provide the processor with silence for + // input buses, and ignore the contents of output buses. + // Note that some hosts (old bitwig and cakewalk) may incorrectly call this function + // when the plugin is in an activated state. + if (dir == Vst::kInput) + bufferMapper.setInputBusHostActive ((size_t) index, state != 0); + else + bufferMapper.setOutputBusHostActive ((size_t) index, state != 0); + + AudioProcessor::BusesLayout desiredLayout; + + for (const auto isInput : { true, false }) + { + const auto numPublicBuses = isInput ? numPublicInputBuses : numPublicOutputBuses; + auto& layoutBuses = isInput ? desiredLayout.inputBuses : desiredLayout.outputBuses; + + for (auto i = 0; i < numPublicBuses; ++i) + { + layoutBuses.add (isInput ? bufferMapper.getRequestedLayoutForInputBus ((size_t) i) + : bufferMapper.getRequestedLayoutForOutputBus ((size_t) i)); + } + + while (layoutBuses.size() < pluginInstance->getBusCount (isInput)) + layoutBuses.add (AudioChannelSet::disabled()); + } + + const auto prev = pluginInstance->getBusesLayout(); + + const auto busesLayoutSupported = [&] + { + #ifdef JucePlugin_PreferredChannelConfigurations + struct ChannelPair + { + short ins, outs; + + auto tie() const { return std::tie (ins, outs); } + bool operator== (ChannelPair x) const { return tie() == x.tie(); } + }; + + const auto countChannels = [] (auto& range) + { + return std::accumulate (range.begin(), range.end(), 0, [] (auto acc, auto set) + { + return acc + set.size(); + }); + }; + + const auto toShort = [] (int x) + { + jassert (0 <= x && x <= std::numeric_limits::max()); + return (short) x; + }; + + const ChannelPair requested { toShort (countChannels (desiredLayout.inputBuses)), + toShort (countChannels (desiredLayout.outputBuses)) }; + const ChannelPair configs[] = { JucePlugin_PreferredChannelConfigurations }; + return std::find (std::begin (configs), std::end (configs), requested) != std::end (configs); + #else + return pluginInstance->checkBusesLayoutSupported (desiredLayout); + #endif + }(); + + if (busesLayoutSupported) + pluginInstance->setBusesLayout (desiredLayout); + else + pluginInstance->enableAllBuses(); + + bufferMapper.updateActiveClientBuses (pluginInstance->getBusesLayout()); + + return kResultTrue; + } + + return kResultFalse; + } + + bool checkBusFormatsAreNotDiscrete() + { + auto numInputBuses = pluginInstance->getBusCount (true); + auto numOutputBuses = pluginInstance->getBusCount (false); + + for (int i = 0; i < numInputBuses; ++i) + { + auto layout = pluginInstance->getChannelLayoutOfBus (true, i); + + if (layout.isDiscreteLayout() && ! layout.isDisabled()) + return false; + } + + for (int i = 0; i < numOutputBuses; ++i) + { + auto layout = pluginInstance->getChannelLayoutOfBus (false, i); + + if (layout.isDiscreteLayout() && ! layout.isDisabled()) + return false; + } + + return true; + } + + tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, + Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + if (active) + { + // The host is misbehaving! The plugin must be deactivated before setting new arrangements. + jassertfalse; + return kResultFalse; + } + + auto numInputBuses = pluginInstance->getBusCount (true); + auto numOutputBuses = pluginInstance->getBusCount (false); + + if (numIns > numInputBuses || numOuts > numOutputBuses) + return kResultFalse; + + // see the following documentation to understand the correct way to react to this callback + // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 + + const auto toLayoutsArray = [] (auto begin, auto end) -> std::optional> + { + Array result; + + for (auto it = begin; it != end; ++it) + { + const auto set = getChannelSetForSpeakerArrangement (*it); + + if (! set.has_value()) + return {}; + + result.add (*set); + } + + return result; + }; + + const auto optionalRequestedLayout = [&]() -> std::optional + { + const auto ins = toLayoutsArray (inputs, inputs + numIns); + const auto outs = toLayoutsArray (outputs, outputs + numOuts); + + if (! ins.has_value() || ! outs.has_value()) + return {}; + + AudioProcessor::BusesLayout result; + result.inputBuses = *ins; + result.outputBuses = *outs; + return result; + }(); + + if (! optionalRequestedLayout.has_value()) + return kResultFalse; + + const auto& requestedLayout = *optionalRequestedLayout; + + #ifdef JucePlugin_PreferredChannelConfigurations + short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; + if (! AudioProcessor::containsLayout (requestedLayout, configs)) + return kResultFalse; + #endif + + if (pluginInstance->checkBusesLayoutSupported (requestedLayout)) + { + if (! pluginInstance->setBusesLayoutWithoutEnabling (requestedLayout)) + return kResultFalse; + + bufferMapper.updateFromProcessor (*pluginInstance); + return kResultTrue; + } + + // apply layout changes in reverse order as Steinberg says we should prioritize main buses + const auto nextBest = [this, numInputBuses, numOutputBuses, &requestedLayout] + { + auto layout = pluginInstance->getBusesLayout(); + + for (auto busIdx = jmax (numInputBuses, numOutputBuses) - 1; busIdx >= 0; --busIdx) + for (const auto isInput : { true, false }) + if (auto* bus = pluginInstance->getBus (isInput, busIdx)) + bus->isLayoutSupported (requestedLayout.getChannelSet (isInput, busIdx), &layout); + + return layout; + }(); + + if (pluginInstance->setBusesLayoutWithoutEnabling (nextBest)) + bufferMapper.updateFromProcessor (*pluginInstance); + + return kResultFalse; + } + + tresult PLUGIN_API getBusArrangement (Vst::BusDirection dir, Steinberg::int32 index, Vst::SpeakerArrangement& arr) override + { + if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) + { + if (const auto arrangement = getVst3SpeakerArrangement (bus->getLastEnabledLayout())) + { + arr = *arrangement; + return kResultTrue; + } + + // There's a bus here, but we can't represent its layout in terms of VST3 speakers! + jassertfalse; + } + + return kResultFalse; + } + + //============================================================================== + tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override + { + return (symbolicSampleSize == Vst::kSample32 + || (getPluginInstance().supportsDoublePrecisionProcessing() + && symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; + } + + Steinberg::uint32 PLUGIN_API getLatencySamples() override + { + return (Steinberg::uint32) jmax (0, getPluginInstance().getLatencySamples()); + } + + tresult PLUGIN_API setupProcessing (Vst::ProcessSetup& newSetup) override + { + ScopedInSetupProcessingSetter inSetupProcessingSetter (juceVST3EditController); + + if (canProcessSampleSize (newSetup.symbolicSampleSize) != kResultTrue) + return kResultFalse; + + processSetup = newSetup; + processContext.sampleRate = processSetup.sampleRate; + + getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 + ? AudioProcessor::doublePrecision + : AudioProcessor::singlePrecision); + getPluginInstance().setNonRealtime (newSetup.processMode == Vst::kOffline); + + preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock, CallPrepareToPlay::no); + + return kResultTrue; + } + + tresult PLUGIN_API setProcessing (TBool state) override + { + if (! state) + getPluginInstance().reset(); + + return kResultTrue; + } + + Steinberg::uint32 PLUGIN_API getTailSamples() override + { + auto tailLengthSeconds = getPluginInstance().getTailLengthSeconds(); + + if (tailLengthSeconds <= 0.0 || processSetup.sampleRate <= 0.0) + return Vst::kNoTail; + + if (std::isinf (tailLengthSeconds)) + return Vst::kInfiniteTail; + + return (Steinberg::uint32) roundToIntAccurate (tailLengthSeconds * processSetup.sampleRate); + } + + //============================================================================== + void processParameterChanges (Vst::IParameterChanges& paramChanges) + { + jassert (pluginInstance != nullptr); + + struct ParamChangeInfo + { + Steinberg::int32 offsetSamples = 0; + double value = 0.0; + }; + + const auto getPointFromQueue = [] (Steinberg::Vst::IParamValueQueue* queue, Steinberg::int32 index) + { + ParamChangeInfo result; + return queue->getPoint (index, result.offsetSamples, result.value) == kResultTrue + ? makeOptional (result) + : nullopt; + }; + + const auto numParamsChanged = paramChanges.getParameterCount(); + + for (Steinberg::int32 i = 0; i < numParamsChanged; ++i) + { + if (auto* paramQueue = paramChanges.getParameterData (i)) + { + const auto vstParamID = paramQueue->getParameterId(); + const auto numPoints = paramQueue->getPointCount(); + + #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS + if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) + { + for (Steinberg::int32 point = 0; point < numPoints; ++point) + { + if (const auto change = getPointFromQueue (paramQueue, point)) + addParameterChangeToMidiBuffer (change->offsetSamples, vstParamID, change->value); + } + } + else + #endif + if (const auto change = getPointFromQueue (paramQueue, numPoints - 1)) + { + if (auto* param = comPluginInstance->getParamForVSTParamID (vstParamID)) + setValueAndNotifyIfChanged (*param, (float) change->value); + } + } + } + } + + void addParameterChangeToMidiBuffer (const Steinberg::int32 offsetSamples, const Vst::ParamID id, const double value) + { + // If the parameter is mapped to a MIDI CC message then insert it into the midiBuffer. + int channel, ctrlNumber; + + if (juceVST3EditController->getMidiControllerForParameter (id, channel, ctrlNumber)) + { + if (ctrlNumber == Vst::kAfterTouch) + midiBuffer.addEvent (MidiMessage::channelPressureChange (channel, + jlimit (0, 127, (int) (value * 128.0))), offsetSamples); + else if (ctrlNumber == Vst::kPitchBend) + midiBuffer.addEvent (MidiMessage::pitchWheel (channel, + jlimit (0, 0x3fff, (int) (value * 0x4000))), offsetSamples); + else + midiBuffer.addEvent (MidiMessage::controllerEvent (channel, + jlimit (0, 127, ctrlNumber), + jlimit (0, 127, (int) (value * 128.0))), offsetSamples); + } + } + + tresult PLUGIN_API process (Vst::ProcessData& data) override + { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + + if (pluginInstance == nullptr) + return kResultFalse; + + if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) + return kResultFalse; + + if (data.processContext != nullptr) + { + processContext = *data.processContext; + + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = (processContext.state & Vst::ProcessContext::kPlaying) != 0; + } + else + { + zerostruct (processContext); + + if (juceVST3EditController != nullptr) + juceVST3EditController->vst3IsPlaying = false; + } + + midiBuffer.clear(); + + if (data.inputParameterChanges != nullptr) + processParameterChanges (*data.inputParameterChanges); + + #if JucePlugin_WantsMidiInput + if (isMidiInputBusEnabled && data.inputEvents != nullptr) + MidiEventList::toMidiBuffer (midiBuffer, *data.inputEvents); + #endif + + if (detail::PluginUtilities::getHostType().isWavelab()) + { + const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; + const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; + + if ((pluginInstance->getTotalNumInputChannels() + pluginInstance->getTotalNumOutputChannels()) > 0 + && (numInputChans + numOutputChans) == 0) + return kResultFalse; + } + + // If all of these are zero, the host is attempting to flush parameters without processing audio. + if (data.numSamples != 0 || data.numInputs != 0 || data.numOutputs != 0) + { + if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio (data); + else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio (data); + else jassertfalse; + } + + if (auto* changes = data.outputParameterChanges) + { + comPluginInstance->forAllChangedParameters ([&] (Vst::ParamID paramID, float value) + { + Steinberg::int32 queueIndex = 0; + + if (auto* queue = changes->addParameterData (paramID, queueIndex)) + { + Steinberg::int32 pointIndex = 0; + queue->addPoint (0, value, pointIndex); + } + }); + } + + #if JucePlugin_ProducesMidiOutput + if (isMidiOutputBusEnabled && data.outputEvents != nullptr) + MidiEventList::pluginToHostEventList (*data.outputEvents, midiBuffer); + #endif + + return kResultTrue; + } + +private: + /* FL's Patcher implements the VST3 specification incorrectly, calls process() before/during + setActive(). + */ + class [[nodiscard]] FLStudioDIYSpecificationEnforcementLock + { + public: + explicit FLStudioDIYSpecificationEnforcementLock (CriticalSection& mutex) + { + static const auto lockRequired = PluginHostType().isFruityLoops(); + + if (lockRequired) + lock.emplace (mutex); + } + + private: + std::optional lock; + }; + + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + #if JucePlugin_Enable_ARA + UniqueBase{}, + UniqueBase{}, + #endif + SharedBase{}); + + if (result.isOk()) + return result; + + if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) + return { kResultOk, comPluginInstance.get() }; + + return {}; + } + + //============================================================================== + struct ScopedInSetupProcessingSetter + { + ScopedInSetupProcessingSetter (JuceVST3EditController* c) + : controller (c) + { + if (controller != nullptr) + controller->inSetupProcessing = true; + } + + ~ScopedInSetupProcessingSetter() + { + if (controller != nullptr) + controller->inSetupProcessing = false; + } + + private: + JuceVST3EditController* controller = nullptr; + }; + + //============================================================================== + template + void processAudio (Vst::ProcessData& data) + { + ClientRemappedBuffer remappedBuffer { bufferMapper, data }; + auto& buffer = remappedBuffer.buffer; + + jassert ((int) buffer.getNumChannels() == jmax (pluginInstance->getTotalNumInputChannels(), + pluginInstance->getTotalNumOutputChannels())); + + { + const ScopedLock sl (pluginInstance->getCallbackLock()); + + pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); + + #if JUCE_DEBUG && ! JucePlugin_ProducesMidiOutput + const int numMidiEventsComingIn = midiBuffer.getNumEvents(); + #endif + + if (pluginInstance->isSuspended()) + { + buffer.clear(); + } + else + { + // processBlockBypassed should only ever be called if the AudioProcessor doesn't + // return a valid parameter from getBypassParameter + if (pluginInstance->getBypassParameter() == nullptr && comPluginInstance->getBypassParameter()->getValue() >= 0.5f) + pluginInstance->processBlockBypassed (buffer, midiBuffer); + else + pluginInstance->processBlock (buffer, midiBuffer); + } + + #if JUCE_DEBUG && (! JucePlugin_ProducesMidiOutput) + /* This assertion is caused when you've added some events to the + midiMessages array in your processBlock() method, which usually means + that you're trying to send them somewhere. But in this case they're + getting thrown away. + + If your plugin does want to send MIDI messages, you'll need to set + the JucePlugin_ProducesMidiOutput macro to 1 in your + JucePluginCharacteristics.h file. + + If you don't want to produce any MIDI output, then you should clear the + midiMessages array at the end of your processBlock() method, to + indicate that you don't want any of the events to be passed through + to the output. + */ + jassert (midiBuffer.getNumEvents() <= numMidiEventsComingIn); + #endif + } + } + + //============================================================================== + Steinberg::uint32 PLUGIN_API getProcessContextRequirements() override + { + return kNeedSystemTime + | kNeedContinousTimeSamples + | kNeedProjectTimeMusic + | kNeedBarPositionMusic + | kNeedCycleMusic + | kNeedSamplesToNextClock + | kNeedTempo + | kNeedTimeSignature + | kNeedChord + | kNeedFrameRate + | kNeedTransportState; + } + + void preparePlugin (double sampleRate, int bufferSize, CallPrepareToPlay callPrepareToPlay) + { + auto& p = getPluginInstance(); + + p.setRateAndBufferSizeDetails (sampleRate, bufferSize); + + if (callPrepareToPlay == CallPrepareToPlay::yes) + p.prepareToPlay (sampleRate, bufferSize); + + midiBuffer.ensureSize (2048); + midiBuffer.clear(); + + bufferMapper.updateFromProcessor (p); + bufferMapper.prepare (bufferSize); + } + + //============================================================================== + #if JucePlugin_Enable_ARA + const ARA::ARAFactory* PLUGIN_API getFactory() SMTG_OVERRIDE + { + return createARAFactory(); + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentController (ARA::ARADocumentControllerRef /*controllerRef*/) SMTG_OVERRIDE + { + ARA_VALIDATE_API_STATE (false && "call is deprecated in ARA 2, host must not call this"); + return nullptr; + } + + const ARA::ARAPlugInExtensionInstance* PLUGIN_API bindToDocumentControllerWithRoles (ARA::ARADocumentControllerRef documentControllerRef, + ARA::ARAPlugInInstanceRoleFlags knownRoles, ARA::ARAPlugInInstanceRoleFlags assignedRoles) SMTG_OVERRIDE + { + AudioProcessorARAExtension* araAudioProcessorExtension = dynamic_cast (pluginInstance); + return araAudioProcessorExtension->bindToARA (documentControllerRef, knownRoles, assignedRoles); + } + #endif + + //============================================================================== + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + std::atomic refCount { 1 }; + AudioProcessor* pluginInstance = nullptr; + + #if JUCE_LINUX || JUCE_BSD + template + struct LockedVSTComSmartPtr + { + LockedVSTComSmartPtr() = default; + LockedVSTComSmartPtr (const VSTComSmartPtr& ptrIn) : ptr (ptrIn) {} + LockedVSTComSmartPtr (const LockedVSTComSmartPtr&) = default; + LockedVSTComSmartPtr& operator= (const LockedVSTComSmartPtr&) = default; + + ~LockedVSTComSmartPtr() + { + const MessageManagerLock mmLock; + ptr = {}; + } + + T* operator->() const { return ptr.operator->(); } + T* get() const noexcept { return ptr.get(); } + operator T*() const noexcept { return ptr.get(); } + + template + bool loadFrom (Args&&... args) { return ptr.loadFrom (std::forward (args)...); } + + private: + VSTComSmartPtr ptr; + }; + + LockedVSTComSmartPtr host; + LockedVSTComSmartPtr comPluginInstance; + LockedVSTComSmartPtr juceVST3EditController; + #else + VSTComSmartPtr host; + VSTComSmartPtr comPluginInstance; + VSTComSmartPtr juceVST3EditController; + #endif + + /** + Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, + this object needs to be copied on every call to process() to be up-to-date... + */ + Vst::ProcessContext processContext; + Vst::ProcessSetup processSetup; + + MidiBuffer midiBuffer; + ClientBufferMapper bufferMapper; + + bool active = false; + + #if JucePlugin_WantsMidiInput + std::atomic isMidiInputBusEnabled { true }; + #endif + #if JucePlugin_ProducesMidiOutput + std::atomic isMidiOutputBusEnabled { true }; + #endif + + inline static constexpr const char* kJucePrivateDataIdentifier = "JUCEPrivateData"; + CriticalSection flStudioDIYSpecificationEnforcementMutex; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) +}; + +//============================================================================== +bool initModule(); +bool initModule() +{ + return true; +} + +bool shutdownModule(); +bool shutdownModule() +{ + return true; +} + +#undef JUCE_EXPORTED_FUNCTION + +#if JUCE_WINDOWS + #define JUCE_EXPORTED_FUNCTION +#else + #define JUCE_EXPORTED_FUNCTION extern "C" __attribute__ ((visibility("default"))) +#endif + +#if JUCE_WINDOWS + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + + extern "C" __declspec (dllexport) bool InitDll() { return initModule(); } + extern "C" __declspec (dllexport) bool ExitDll() { return shutdownModule(); } + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#elif JUCE_LINUX || JUCE_BSD + void* moduleHandle = nullptr; + int moduleEntryCounter = 0; + + JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle); + JUCE_EXPORTED_FUNCTION bool ModuleEntry (void* sharedLibraryHandle) + { + if (++moduleEntryCounter == 1) + { + moduleHandle = sharedLibraryHandle; + return initModule(); + } + + return true; + } + + JUCE_EXPORTED_FUNCTION bool ModuleExit(); + JUCE_EXPORTED_FUNCTION bool ModuleExit() + { + if (--moduleEntryCounter == 0) + { + moduleHandle = nullptr; + return shutdownModule(); + } + + return true; + } +#elif JUCE_MAC + CFBundleRef globalBundleInstance = nullptr; + juce::uint32 numBundleRefs = 0; + juce::Array bundleRefs; + + enum { MaxPathLength = 2048 }; + char modulePath[MaxPathLength] = { 0 }; + void* moduleHandle = nullptr; + + JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref); + JUCE_EXPORTED_FUNCTION bool bundleEntry (CFBundleRef ref) + { + if (ref != nullptr) + { + ++numBundleRefs; + CFRetain (ref); + + bundleRefs.add (ref); + + if (moduleHandle == nullptr) + { + globalBundleInstance = ref; + moduleHandle = ref; + + CFUniquePtr tempURL (CFBundleCopyBundleURL (ref)); + CFURLGetFileSystemRepresentation (tempURL.get(), true, (UInt8*) modulePath, MaxPathLength); + } + } + + return initModule(); + } + + JUCE_EXPORTED_FUNCTION bool bundleExit(); + JUCE_EXPORTED_FUNCTION bool bundleExit() + { + if (shutdownModule()) + { + if (--numBundleRefs == 0) + { + for (int i = 0; i < bundleRefs.size(); ++i) + CFRelease (bundleRefs.getUnchecked (i)); + + bundleRefs.clear(); + } + + return true; + } + + return false; + } +#endif + +// See https://steinbergmedia.github.io/vst3_dev_portal/pages/FAQ/Compatibility+with+VST+2.x+or+VST+1.html +class JucePluginCompatibility : public IPluginCompatibility +{ +public: + virtual ~JucePluginCompatibility() = default; + + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API getCompatibilityJSON (IBStream* stream) override + { + const ScopedJuceInitialiser_GUI libraryInitialiser; + + auto filter = createPluginFilterOfType (AudioProcessor::WrapperType::wrapperType_VST3); + auto* extensions = filter->getVST3ClientExtensions(); + + if (extensions == nullptr || extensions->getCompatibleClasses().empty()) + return kResultFalse; + + DynamicObject::Ptr object { new DynamicObject }; + + // New iid is the ID of our Audio Effect class + object->setProperty ("New", String (VST3::UID (JuceVST3Component::iid).toString())); + object->setProperty ("Old", [&] + { + Array oldArray; + + for (const auto& uid : extensions->getCompatibleClasses()) + { + // All UIDs returned from getCompatibleClasses should be 32 characters long + jassert (uid.length() == 32); + + // All UIDs returned from getCompatibleClasses should be in hex notation + jassert (uid.containsOnly ("ABCDEF0123456789")); + + oldArray.add (uid); + } + + return oldArray; + }()); + + MemoryOutputStream memory; + JSON::writeToStream (memory, var { Array { object.get() } }); + return stream->write (memory.getMemoryBlock().getData(), (Steinberg::int32) memory.getDataSize()); + } + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + jassertfalse; // Something new? + *obj = nullptr; + return kNotImplemented; + } + + inline static const FUID iid { TUID INLINE_UID (0xABCDEF01, 0xC0DEF00D, JucePlugin_ManufacturerCode, JucePlugin_PluginCode) }; + +private: + std::atomic refCount { 1 }; +}; + +//============================================================================== +/** This typedef represents VST3's createInstance() function signature */ +using CreateFunction = FUnknown* (*)(Vst::IHostApplication*); + +//============================================================================== +struct JucePluginFactory : public IPluginFactory3 +{ + JucePluginFactory() + : factoryInfo (JucePlugin_Manufacturer, JucePlugin_ManufacturerWebsite, + JucePlugin_ManufacturerEmail, Vst::kDefaultFactoryFlags) {} + + virtual ~JucePluginFactory() = default; + + //============================================================================== + JUCE_DECLARE_VST3_COM_REF_METHODS + + tresult PLUGIN_API queryInterface (const TUID targetIID, void** obj) override + { + const auto result = testForMultiple (*this, + targetIID, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}, + UniqueBase{}); + + if (result.isOk()) + return result.extract (obj); + + jassertfalse; // Something new? + *obj = nullptr; + return kNotImplemented; + } + + //============================================================================== + Steinberg::int32 PLUGIN_API countClasses() override + { + return (Steinberg::int32) getClassEntries().size(); + } + + tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) override + { + if (info == nullptr) + return kInvalidArgument; + + memcpy (info, &factoryInfo, sizeof (PFactoryInfo)); + return kResultOk; + } + + tresult PLUGIN_API getClassInfo (Steinberg::int32 index, PClassInfo* info) override + { + return getPClassInfo (index, info); + } + + tresult PLUGIN_API getClassInfo2 (Steinberg::int32 index, PClassInfo2* info) override + { + return getPClassInfo (index, info); + } + + tresult PLUGIN_API getClassInfoUnicode (Steinberg::int32 index, PClassInfoW* info) override + { + if (info != nullptr) + { + memcpy (info, &getClassEntries()[static_cast (index)].infoW, sizeof (PClassInfoW)); + return kResultOk; + } + + return kInvalidArgument; + } + + tresult PLUGIN_API createInstance (FIDString cid, FIDString sourceIid, void** obj) override + { + ScopedJuceInitialiser_GUI libraryInitialiser; + + #if JUCE_LINUX || JUCE_BSD + SharedResourcePointer messageThread; + #endif + + *obj = nullptr; + + TUID tuid; + memcpy (tuid, sourceIid, sizeof (TUID)); + + #if VST_VERSION >= 0x030608 + auto sourceFuid = FUID::fromTUID (tuid); + #else + FUID sourceFuid; + sourceFuid = tuid; + #endif + + if (cid == nullptr || sourceIid == nullptr || ! sourceFuid.isValid()) + { + jassertfalse; // The host you're running in has severe implementation issues! + return kInvalidArgument; + } + + TUID iidToQuery; + sourceFuid.toTUID (iidToQuery); + + for (auto& entry : getClassEntries()) + { + if (doUIDsMatch (entry.infoW.cid, cid)) + { + if (auto* instance = entry.createFunction (host)) + { + const FReleaser releaser (instance); + + if (instance->queryInterface (iidToQuery, obj) == kResultOk) + return kResultOk; + } + + break; + } + } + + return kNoInterface; + } + + tresult PLUGIN_API setHostContext (FUnknown* context) override + { + host.loadFrom (context); + + if (host != nullptr) + { + Vst::String128 name; + host->getName (name); + + return kResultTrue; + } + + return kNotImplemented; + } + +private: + //============================================================================== + std::atomic refCount { 1 }; + const PFactoryInfo factoryInfo; + VSTComSmartPtr host; + + //============================================================================== + struct ClassEntry + { + ClassEntry (const PClassInfo2& info, CreateFunction fn) noexcept + : info2 (info), createFunction (fn) + { + infoW.fromAscii (info); + } + + PClassInfo2 info2; + PClassInfoW infoW; + CreateFunction createFunction = {}; + + private: + JUCE_DECLARE_NON_COPYABLE (ClassEntry) + }; + + static Span getClassEntries() + { + #ifndef JucePlugin_Vst3ComponentFlags + #if JucePlugin_IsSynth + #define JucePlugin_Vst3ComponentFlags Vst::kSimpleModeSupported + #else + #define JucePlugin_Vst3ComponentFlags 0 + #endif + #endif + + #ifndef JucePlugin_Vst3Category + #if JucePlugin_IsSynth + #define JucePlugin_Vst3Category Vst::PlugType::kInstrumentSynth + #else + #define JucePlugin_Vst3Category Vst::PlugType::kFx + #endif + #endif + + static const PClassInfo2 compatibilityClass { JucePluginCompatibility::iid, + PClassInfo::kManyInstances, + kPluginCompatibilityClass, + JucePlugin_Name, + 0, + "", + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString }; + + static const PClassInfo2 componentClass { JuceVST3Component::iid, + PClassInfo::kManyInstances, + kVstAudioEffectClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString }; + + static const PClassInfo2 controllerClass { JuceVST3EditController::iid, + PClassInfo::kManyInstances, + kVstComponentControllerClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString }; + #if JucePlugin_Enable_ARA + static const PClassInfo2 araFactoryClass { JuceARAFactory::iid, + PClassInfo::kManyInstances, + kARAMainFactoryClass, + JucePlugin_Name, + JucePlugin_Vst3ComponentFlags, + JucePlugin_Vst3Category, + JucePlugin_Manufacturer, + JucePlugin_VersionString, + kVstVersionString }; + #endif + + static const ClassEntry classEntries[] + { + ClassEntry { compatibilityClass, [] (Vst::IHostApplication*) -> Steinberg::FUnknown* + { + return new JucePluginCompatibility; + } }, + ClassEntry { componentClass, [] (Vst::IHostApplication* h) -> Steinberg::FUnknown* + { + return static_cast (new JuceVST3Component (h)); + } }, + ClassEntry { controllerClass, [] (Vst::IHostApplication* h) -> Steinberg::FUnknown* + { + return static_cast (new JuceVST3EditController (h)); + } }, + #if JucePlugin_Enable_ARA + ClassEntry { araFactoryClass, [] (Vst::IHostApplication*) -> Steinberg::FUnknown* + { + return static_cast (new JuceARAFactory); + } }, + #endif + }; + + return Span { classEntries }; + } + + //============================================================================== + template + tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) + { + if (info != nullptr) + { + zerostruct (*info); + memcpy (info, (PClassInfoType*) &getClassEntries()[static_cast (index)].info2, sizeof (PClassInfoType)); + return kResultOk; + } + + jassertfalse; + return kInvalidArgument; + } + + //============================================================================== + // no leak detector here to prevent it firing on shutdown when running in hosts that + // don't release the factory object correctly... + JUCE_DECLARE_NON_COPYABLE (JucePluginFactory) +}; + +} // namespace juce + +//============================================================================== +using namespace juce; + +//============================================================================== +// The VST3 plugin entry point. +extern "C" SMTG_EXPORT_SYMBOL IPluginFactory* PLUGIN_API GetPluginFactory() +{ + #if (JUCE_MSVC || (JUCE_WINDOWS && JUCE_CLANG)) && JUCE_32BIT + // Cunning trick to force this function to be exported. Life's too short to + // faff around creating .def files for this kind of thing. + // Unnecessary for 64-bit builds because those don't use decorated function names. + #pragma comment(linker, "/EXPORT:GetPluginFactory=_GetPluginFactory@0") + #endif + + return new JucePluginFactory(); +} + +//============================================================================== +#if JUCE_WINDOWS +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") + +extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID) { if (reason == DLL_PROCESS_ATTACH) Process::setCurrentModuleInstanceHandle (instance); return true; } + +JUCE_END_IGNORE_WARNINGS_GCC_LIKE +#endif + +JUCE_END_NO_SANITIZE + +#endif //JucePlugin_Build_VST3 diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm new file mode 100644 index 0000000..d7df9a0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.mm @@ -0,0 +1,26 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "juce_audio_plugin_client_VST3.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp b/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp deleted file mode 100644 index bf6f2f8..0000000 --- a/JuceLibraryCode/modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#if _MSC_VER || defined (__MINGW32__) || defined (__MINGW64__) - #include -#endif - -#include -#include "../utility/juce_CheckSettingMacros.h" -#include "juce_IncludeModuleHeaders.h" - -namespace juce -{ - -#if JucePlugin_Build_Unity - bool juce_isRunningInUnity(); - bool juce_isRunningInUnity() { return PluginHostType::getPluginLoadedAs() == AudioProcessor::wrapperType_Unity; } -#endif - -#ifndef JUCE_VST3_CAN_REPLACE_VST2 - #define JUCE_VST3_CAN_REPLACE_VST2 1 -#endif - -#if JucePlugin_Build_VST3 && JUCE_VST3_CAN_REPLACE_VST2 && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD) - #define VST3_REPLACEMENT_AVAILABLE 1 - - // NB: Nasty old-fashioned code in here because it's copied from the Steinberg example code. - void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]); - void getUUIDForVST2ID (bool forControllerUID, uint8 uuid[16]) - { - #if JUCE_MSVC - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { sprintf_s (head, numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&& head, auto&&... tail) { strcpy_s (head, numElementsInArray (head), tail...); }; - const auto juce_strcat = [] (auto&& head, auto&&... tail) { strcat_s (head, numElementsInArray (head), tail...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf_s (args...); }; - #else - const auto juce_sprintf = [] (auto&& head, auto&&... tail) { snprintf (head, (size_t) numElementsInArray (head), tail...); }; - const auto juce_strcpy = [] (auto&&... args) { strcpy (args...); }; - const auto juce_strcat = [] (auto&&... args) { strcat (args...); }; - const auto juce_sscanf = [] (auto&&... args) { sscanf (args...); }; - #endif - - char uidString[33]; - - const int vstfxid = (('V' << 16) | ('S' << 8) | (forControllerUID ? 'E' : 'T')); - char vstfxidStr[7] = { 0 }; - juce_sprintf (vstfxidStr, "%06X", vstfxid); - - juce_strcpy (uidString, vstfxidStr); - - char uidStr[9] = { 0 }; - juce_sprintf (uidStr, "%08X", JucePlugin_VSTUniqueID); - juce_strcat (uidString, uidStr); - - char nameidStr[3] = { 0 }; - const size_t len = strlen (JucePlugin_Name); - - for (size_t i = 0; i <= 8; ++i) - { - juce::uint8 c = i < len ? static_cast (JucePlugin_Name[i]) : 0; - - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - - juce_sprintf (nameidStr, "%02X", c); - juce_strcat (uidString, nameidStr); - } - - unsigned long p0; - unsigned int p1, p2; - unsigned int p3[8]; - - juce_sscanf (uidString, "%08lX%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X", - &p0, &p1, &p2, &p3[0], &p3[1], &p3[2], &p3[3], &p3[4], &p3[5], &p3[6], &p3[7]); - - union q0_u { - uint32 word; - uint8 bytes[4]; - } q0; - - union q1_u { - uint16 half; - uint8 bytes[2]; - } q1, q2; - - q0.word = static_cast (p0); - q1.half = static_cast (p1); - q2.half = static_cast (p2); - - // VST3 doesn't use COM compatible UUIDs on non windows platforms - #if ! JUCE_WINDOWS - q0.word = ByteOrder::swap (q0.word); - q1.half = ByteOrder::swap (q1.half); - q2.half = ByteOrder::swap (q2.half); - #endif - - for (int i = 0; i < 4; ++i) - uuid[i+0] = q0.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+4] = q1.bytes[i]; - - for (int i = 0; i < 2; ++i) - uuid[i+6] = q2.bytes[i]; - - for (int i = 0; i < 8; ++i) - uuid[i+8] = static_cast (p3[i]); - } -#else - #define VST3_REPLACEMENT_AVAILABLE 0 -#endif - -#if JucePlugin_Build_VST - bool JUCE_API handleManufacturerSpecificVST2Opcode (int32 index, pointer_sized_int value, void* ptr, float); - bool JUCE_API handleManufacturerSpecificVST2Opcode ([[maybe_unused]] int32 index, - [[maybe_unused]] pointer_sized_int value, - [[maybe_unused]] void* ptr, - float) - { - #if VST3_REPLACEMENT_AVAILABLE - if ((index == (int32) ByteOrder::bigEndianInt ("stCA") || index == (int32) ByteOrder::bigEndianInt ("stCa")) - && value == (int32) ByteOrder::bigEndianInt ("FUID") && ptr != nullptr) - { - uint8 fuid[16]; - getUUIDForVST2ID (false, fuid); - ::memcpy (ptr, fuid, 16); - return true; - } - #endif - return false; - } -#endif - -} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/JUCE_README.md b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/JUCE_README.md index d337f05..f3e3689 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/JUCE_README.md +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/JUCE_README.md @@ -1,6 +1,5 @@ This list details modifications made to the VST3 SDK in order to facilitate inclusion in JUCE. -- `#warning` directives were removed from fstring.cpp, as these cannot be - silenced with a `pragma GCC diagnostic ignored "-Wcpp"` when building with - g++. +- The main.cpp of moduleinfotool was updated to include information exported + by the plugin's IPluginCompatibility object, if present. diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/LICENSE.txt b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/LICENSE.txt index 5d521d2..6daa072 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/LICENSE.txt +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/LICENSE.txt @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- This license applies only to files referencing this license, for other files of the Software Development Kit the respective embedded license text @@ -38,4 +38,7 @@ OF THE POSSIBILITY OF SUCH DAMAGE. b) General Public License (GPL) Version 3 Details of these licenses can be found at: www.gnu.org/licenses/gpl-3.0.html +Please refer to the Steinberg VST usage guidelines for the use of VST, VST logo and VST +compatible logos: +https://steinbergmedia.github.io/vst3_dev_portal/pages/VST+3+Licensing/Usage+guidelines.html //---------------------------------------------------------------------------------- diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/README.md b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/README.md index b4f9580..191fe79 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/README.md +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/README.md @@ -1,10 +1,267 @@ -# Welcome to VST 3 SDK Interfaces +
+VST 3 SDK
-Here are located all VST interfaces definitions (including VST Component/Controller, UI, Test). +# Welcome to VST SDK 3.7.x + +## Table Of Contents + +1. [The VST SDK package](#100) +1. [System requirements](#200) +1. [About VST plug-ins in general](#300) +1. [About VST 3](#400) +1. [How to build VST 3](#500) +1. [Contributing](#600) +1. [License & Usage guidelines](#700) + +
+ +## The VST SDK package contains + +- VST 3 API +- VST 3 Implementation Helper Classes +- AAX, AU, AUv3 and VST 2 wrappers +- VST 3 plug-ins Examples + +The full VST 3 SDK is available [here!](https://www.steinberg.net/en/company/developers.html). It contains : + +- VST 3 plug-in Test Host Application/Validator, +- the **Steinberg VST 3 Plug-In SDK Licensing Agreement** that you have to sign if you want to develop or host **VST 3** plug-ins. + +
+ +## System requirements + +Supported Platforms: + +| Operating System |Architecture |Compiler | Notes| +| :------------------------------------ | :-----------------------: | :-------------------------------: | :-----------: | +|Windows 10/11 |x86, x86_64, arm64 |MSVC 2022, MSVC 2019 | | +|Windows 8.1 |x86, x86_64 |MSVC 2019, MSVC 2017 | | +|macOS 10.13, 10.14, 10.15, 11, 12, 13 |x86, x86_64, Apple Silicon |Xcode 10 - 14 | | +|iOS 13 - iOS 16 |arm64 |Xcode 11 - 14 | | +|Linux - Raspberry Pi OS (Buster) |arm32 |GCC 8.3 and higher |Visual Studio Code| +|Linux - Ubuntu 18.04 LTS |x86, x86_64 |GCC 8.3 and higher |Visual Studio Code, Qt Creator| +|Linux - Ubuntu 20.04 LTS |x86, x86_64 |GCC 8.3 and higher |Visual Studio Code, Qt Creator| + +--- +
+ +## About VST plug-ins in general + +A VST plug-in is an audio processing component that is utilized within a host application. This host application provides the audio or/and event streams that are processed by the plug-in's code. Generally speaking, a VST plug-in can take a stream of audio data, apply a process to the audio, and return the result to the host application. A VST plug-in performs its process normally using the processor of the computer. The audio stream is broken down into a series of blocks. The host supplies the blocks in sequence. The host and its current environment control the block-size. The VST plug-in maintains the status of all its own parameters relating to the running process: The host does not maintain any information about what the plug-in did with the last block of data it processed. + +From the host application's point of view, a VST plug-in is a black box with an arbitrary number of inputs, outputs (Event (MIDI) or Audio), and associated parameters. The host needs no implicit knowledge of the plug-in's process to be able to use it. The plug-in process can use whatever parameters it wishes, internally to the process, but depending on the capabilities of the host, it can allow the changes to user parameters to be automated by the host. + +The source code of a VST plug-in is platform independent, but the delivery system depends on the platform architecture: + +- On **Windows**, a VST plug-in is a multi-threaded DLL (Dynamic Link Library), recently packaged into a folder structure. +- On **Mac OS X**, a VST plug-in is a Mach-O Bundle +- On **Linux**, a VST plug-in is a package + +To learn more about VST you can: + +- subscribe to the [VST Developer Forum](https://sdk.steinberg.net) +- check the 3rd Party Developer Support section at [www.steinberg.net](https://www.steinberg.net/en/company/developers.html) +- check the VST 3 SDK online documentation under: [steinbergmedia.github.io/vst3_dev_portal](https://steinbergmedia.github.io/vst3_dev_portal/pages/index.html) +- check the online documentation under: [steinbergmedia.github.io/vst3_doc](https://steinbergmedia.github.io/vst3_doc) + + --- +
+ +## About VST 3 + +VST 3 is a general rework of the long-serving VST plug-in interface. It is not compatible with the older VST versions, but it includes some new features and possibilities. We have redesigned the API to make it not only far easier and more reliable for developers to work with, but have also provided completely new possibilities for plug-ins. These include: + +### 1. Improved Performance with the Silence Flag + +Processing can optionally be applied to plug-ins only when audio signals are present on their respective inputs, so VST 3 plug-ins can apply their processing economically and only when it is needed. + +### 2. Multiple Dynamic I/Os + +VST 3 plug-ins are no longer limited to a fixed number of inputs and outputs, and their I/O configuration can dynamically adapt to the channel configuration. Side-chains are also very easily realizable. This includes the possibility to deactivate unused busses after loading and even reactivate those when needed. This cleans up the mixer and further helps to reduce CPU load. + +### 3. Sample-accurate Automation + +VST 3 also features vastly improved parameter automation with sample accuracy and support for ramped automation data, allowing completely accurate and rapid parameter automation changes. + +### 4. Logical Parameter Organization + +The VST 3 plug-in parameters are displayed in a tree structure. Parameters are grouped into sections which represent the structure of the plug-in. Plug-ins can communicate their internal structure for the purpose of overview, but also for some associated functionality (eg. program-lists). + +### 5. Resizeable UI Editor + +VST 3 defines a way to allow resizing of the plug-in editor by a user. + +### 6. Mouse Over Support + +The host could ask the plug-in which parameter is under the mouse. + +### 7. Context Menu Support + +VST 3 defines a way to allow the host to add its own entries in the plug-in context menu of a specific parameter. + +### 8. Channel Context Information + +A VST 3 plug-in could access some channel information where it is instantiated: name, color, ... + +### 9. Note Expression + +VST 3 defines with Note Expression a new way of event controller editing. The plug-in is able to break free from the limitations of MIDI controller events by providing access to new VST 3 controller events that circumvent the laws of MIDI and provide articulation information for each individual note (event) in a polyphonic arrangement according to its noteId. + +### 10. 3D Support + +VST 3 supports new speaker configurations like Ambisonic, Atmos, Auro 3D or 22.2. + +### 11. Factory Concept + +VST 3 plug-in library could export multiple plug-ins and in this way replaces the shell concept of VST 2 (kPlugCategShell). + +### 12. Support Remote control Representation + +VST 3 plug-in can deliver a specific parameter mapping for remote controls like Nuage. + +### 13. Others + +While designing VST 3, we performed a careful analysis of the existing functionality of VST and rewrote the interfaces from scratch. In doing so, we focused a lot on providing clear interfaces and their documentation in order to avoid usage errors from the deepest possible layer. +Some more features implemented specifically for developers include: + +- More stable technical host/plug-in environment +- Advanced technical definition of the standard +- Modular approach +- Separation of UI and processing +- Advanced Preset System +- Multiple plug-ins per Library +- Test Host included +- Automated Testing Environment +- Validator (small command line Test Host) and plug-in examples code included + +--- +
+ +## How to build VST3 + +### Get the source code from GitHub + +```c +git clone --recursive https://github.com/steinbergmedia/vst3sdk.git +``` + +### Adding VST2 version + +The **VST 2 SDK** is not part anymore of the **VST 3 SDK**, you have to use an older version of the SDK and copy the vst2sdk folder into the VST_SDK folder. +In order to build a VST2 version of the plug-in and a VST3 at the same time, you need to copy the VST2 folder into the VST3 folder, simply run the following commands: + +- for macOS: + +```c +cd TheFolderWhereYouDownloadTheSDK +./copy_vst2_to_vst3_sdk.sh +``` + +- for Windows: + +```c +cd TheFolderWhereYouDownloadTheSDK +copy_vst2_to_vst3_sdk.bat +``` + +### Build the examples on Windows + +- Create a folder for the build and move to this folder (using cd): + +```c +mkdir build +cd build +``` + +- Generate the Solution/Projects: provide the path of the Project where CMakeLists.txt is located: + +```c +// examples: +cmake.exe -G "Visual Studio 17 2022" -A x64 ..\vst3sdk +// or without symbolic links +cmake.exe -G "Visual Studio 17 2022" -A x64 ..\vst3sdk -DSMTG_CREATE_PLUGIN_LINK=0 +// or by using the local user program folder (FOLDERID_UserProgramFilesCommon) as VST3 folder +cmake.exe -G "Visual Studio 17 2022" -A x64 -DSMTG_PLUGIN_TARGET_USER_PROGRAM_FILES_COMMON=1 +``` + +- Now you can build the plug-in (you can use Visual Studio too): + +```c +msbuild.exe vstsdk.sln +// (or alternatively for example for release) +cmake --build . --config Release +``` + +### Build the examples on macOS + +- Create a folder for the build and move to this folder (using cd): + +```c +mkdir build +cd build +``` + +- Generate the Solution/Projects: provide the path of the Project where CMakeLists.txt is located: + +```c +// For XCode: +cmake -GXcode ../vst3sdk +// Without XCode (here debug variant): +cmake -DCMAKE_BUILD_TYPE=Debug ../ +``` + +- Now you can build the plug-in (you can use XCode too): + +```c +xcodebuild +// (or alternatively for example for release) +cmake --build . --config Release +``` + +### Build the examples on Linux + +- Install the required packages [Package Requirements](https://steinbergmedia.github.io/vst3_dev_portal/pages/Getting+Started/How+to+setup+my+system.html#for-linux) +- Create a folder for the build and move to this folder (using cd): + +```c +mkdir build +cd build +``` + +- Generate the Solution/Projects: provide the path of the Project where CMakeLists.txt is located: + +```c +cmake ../vst3sdk +``` + +- Now you can build the plug-in: + +```c +make +// (or alternatively for example for release) +cmake --build . --config Release +``` + +### Build using cmake-gui + +- start the cmake-gui Application +- **Browse Source...**: select the folder vst3sdk +- **Browse Build...**: select a folder where the outputs (projects/...) will be created. Typically, a folder named "build" +- you can check the SMTG Options +- Press **Configure** +- Press **Generate** and the project will be created + +--- +
+ +## Contributing + +For bug reports and features requests, please visit the [VST Developer Forum](https://sdk.steinberg.net) + +--- +
## License & Usage guidelines More details are found at [www.steinberg.net/sdklicenses_vst3](http://www.steinberg.net/sdklicenses_vst3) - ----- -Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk) \ No newline at end of file diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/VST3_License_Agreement.pdf b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/VST3_License_Agreement.pdf index 04349f443ac0ef319815279f33081a172b677075..ad8c399b48ea9a2c29ff06d8457a76abe6c5fdfc 100644 GIT binary patch literal 135822 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 z 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
j|dIpGhIGy4z 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
nAEoVOt+$ zQJL}i7lk>6Qk9>^p44|2;(A&7Ae;7iDClqg;bYL;Yd6VS+a>`zd~3`ECv0b};c2h) z$aI-?!S!5!E=Ny=rMs3W9B^;)QGZHoj~>6czGzSn!R4=OY;46aP$dR@Q&ir6w#EpTEgy>zf}Q``1>cmgA`3N`=&O%zftX zC{9@@w_$2#40t$j#(xPLj&cigrAQ={Y#^u9uS%m59B+dD_j-y!Y z0ekdwn0IkyOe8^HN}aHge@HGXk(+8Awzh!CR&3#90>)xL1X1|!aw&`Owgom&V{Z)3 z!*%Y*!CU`c@u%9Cy^rb_i7wC;h@in{F!#d=bzW(HX5qc9*`xrCc^DwMV#wrwdgK4j zA}5BZhEM4;XRr7yZIrgZmwcigxtxn!LPTGTvI<>PTt&FtK|t3w(I#?zj22uM5_h9S)B_|7K-;So12mG?4<`$cZ(deP280x~19vh>=Ej@qzE% z<(*uKvnko4i};^+9%M*z7=+Ton~sIfFS|If-q$PpKgg$!J-jzsB&<9f=EJjrPE93X z7uk66(HjqYXte-?#G$V6ou%_i_oru0%=`Q0tV#cR^byrXz-9J zB2b~kg)rnJJyR_sj*chGCm&6iuRt=oQ2q2X#}@;-F#OXHw?m>;+(CXr5bc0cM>t;I z@X{L;>Aaq67atnNX|ZL=$?rT7wth)WsiOwt$O%nLzNV~!D2}I;IpIXi4nud7u*PB8k#sx_C+2F2Gdr}sjJ=!6xLxTXq(9$k&e#IXM3n zl&ZK`8!~;6*E2QLw`Wpz(6fj9U(!O?$dF0h!~h6aS(urbnIsHNjEwC`-T@zlOziCx z3~hz2EUc|84K3|SIR6H=T+EQj7U6#gZJC6v%&lydt#$PcnM4d7P4o?w#Dx%;lx218 z%t*Lc9*6yV_`9F#QkSdhuyLJ@)@4Z*oa!i{$BGRz9geI(>+6g6S_Ly3-NJ^ffKv=J z8+FB7gq*cRFr37u?RiM(?BM<|vcv9V39V_`Cao#aVaYP`L;_=aR^2AgV(rC(*}l{M zVN5*EN12ALtpeE3@h z(=h1SXVzT17%IrDBQSh4w=X~uGS7@0z8*iyVUs~+sFTsC3+GP1`Ai-nSIi4nr3;t_ z{F}BQ>%pT(BHAaS%$M#98&5=8h)+ZaZLsEYhmS?f2 zj~}{sP`ti?1sGewoncHa-A$X#8w>e&4iDw|orZR<5E&(sA!zq};c-{muetfkuX|yF z!_d4}be>*sF~=uBoYZJ+f2oj?NIvKIYVdpG`2lg{(8Vg$!;e*+X{T`mXCDF(JOGFi zb%UMN_S!K4f41BQD0xo1<3+9dd#{CZx0{smWi%c)@t})6@IY1d)MCXFn1c%pb4hh#vA; z&jzA$arx@j-nXIF!%7{Zg*uph8$!2)5)C`hz`HbiY;mVY=;1S{*mS0^!|?=f0X()1 ziKowRpI23rhJUrZy=TJybQNES!u2(q2jT}kg2UuVOyM$tGHG|+X)QOx=?kr%7_;Nf z7xY>+dkn-oZdV~s2nh{mCo<6!3<_AwJ&YNZHZD(YEiP_F96ywG_g#AE&3auQJ`s-g zX9T^~DVR%k8Qc@tsA8J}@mA>L%u0A%l+D$FL(Kpq`8-YTigCudfv9a|_cg&oT$YH~ zEQrHGRMWVB!LS&+8GQJJR1T~lie8HXEBoeWuEV`4H{DCF$VEN42sIN z#nq29!mfc5`tD)p8gQ(veHy=gfooY{9oShuu7dveC?8vbYewv)Qciz_1#wskx+$Xz zpCff|^nhRJcA6kM>hwb^bu15O-f6j=YiM-ooALPHw{fFi7hF9a5$X>CgzDoFX z;RBVOZLBV3XfU+|w$tf;(rW)1pXN1L$NR=a>+88v!COV%Y+*MUai{ZJ2{iQiWlmZ*M#_Pm=(kmjma_ zS3c}6H1C_1?@oF-VEjVMc|`MiC=-vRLVs#cnQ>`LF4)VN9Ee`@^25gOm^wt28}pel6B(O{<*3&0ygZumVeUaQ|XMm z5_aT$&b;=ogW==8&RU>5ulMLIamu1Q za8PSHO&_02+T1{An0Eb_60$7?EK(i~ngGNj>oHK-ZWn{x%c3cBZm(+=NX-$jljMUn zhwZ*GnTC%N?%xpG?X*nn0))3S=X2ZOxwp-)ZVyn|VV*^2|x8YSHMJ>~JRyEsxE z1N*_Viw)p~eF6F>FN|+Y4fQ!Z;{#l)Sg^l;4+B)VGc9&YAx-5ao6D)utSEIMzVGa*dDS?TmEIE9K-bHd-heppny43K&DIBwfF%a zjGJr^v@#<9U zeIsfYyKESvmB8DnVzqgwdPrcgv)SG?5QXjVX*3hEli2QZ!(~oFM!#+Q@3|J$ZTZE{ zuR0DBNaQ?D*)aUU(b|4@k^C_C>#C?4pVpmzD9oVGYN+2HO@94Dl@E(Tb@+vbvx<<9 z_SukiP>yBn_XT5$#gdyk%7fg#9Jf|LH-k$M;JjIR>BDZMl;F$)a7Qt5$q`6 zh{JFZxp&j~ioXcNeD|VnFVia^vG_=VM#?m>Nwg|k1S9;1M0e?jV5n;cjj7}TuM*6y zCf-dUd`$~?&$K+;D09cr#z!pwf0;tfH%(6x7sJ-6M_lzInA|Rxi}2@U ze7;!lgNAOoT{MS%^FDCClk+%cnsrn*TB)V@T&7*E>HH za${K^hgN@AgB{Y;W1^2;yAOXfg<>yV; zZLQE|n>w4T=#DTGa}`eo;pD$%tTOJ@uTSeb}k+R_B9_ zGB1J3D~n68*70n-XkfmKN1UoC9Z#0c*(`Id+Xo`>Wn~t~I6}Xab^>7{>7Yk_gQkZo z^e)w5zJANJDTA$+SnG7qbARc_^~|rx1EQjbfzy-RlP_PO?paN)Url8@P%N45OTA;L z>3TEA_JVOP4&S1p*rN-Ziqpd|*(>yYc^BQnaU_HlPid0Q1Br00a*3(zh_PcJ-Aa*exYL{bcN8p5ST=1@w2g0Z}E@B zC8sRv!hfAU!!gm$aF{4)^2DC>3ZmZ@?7)J_umG zGSy_ZSmYpf6I5%S?z%>Sl{zAC$iBoBGfA73^~o3(OptZbpz;2}qf|jy!$gq>fk`xf z~A zOzX*HP)D0R9R_u2Tc^(w5=xZDW))qu88RL$)eERB&hyV^!%zzw$Ei}XMrT1CqJfvx-p8U0^Ix^+2qr_ZM1OO$p`6kVDbZlbm%H3w*#-5(Gp z5?0K|UQ}s;Dy?4wAg!g(b*nE5QY1ul zx9Wnlv&qUzH6k;n>H_-<6zlQ`^z3pkwY&>aGNn$8{nlj91hvsA3bV|2uL@f18N;|r zv{dMu4>x#*9j-d&<~^+$nzl;Z5=vv$%lAqiyj`K+W~`ga>AqH4W74^d%HI_%6iUp{ zG_faN!V7l~{z(?u1eR2F_b#a5!4FB2_3PBMyQ)Z@loIJ0eGDLxb*@q%zS1eL0vbv47=@0r?yZm=na%nFd?C<`Nj55BhaTJhR znpIGv-jKC|RtRZ82~lgi+eRaPo3DI^kjz)9{`z-~uR^)~lvREi6vdY8+k!2CGhR6oiRF|3Yl`IH@UC=t{w0~qjn|0Ak=!6 zR^Wv18oaf{%U9}dajL+%rnN2ddZh&I>^WmKCSAq0icHRWh_GZH!l{g9ZiXJsgXb+Y zcxb=!O*#krTP2+?AX1pxPmV%vf_d-78+@5TTB(vA=xhe<;VsS36>82+h?hm(TOix; zcl_oBJZJjcEs-O*bP4FrT?|S`C#p{s26o0tkM@TPqK|bIi3oAWCB4w;VatS_yFxx_=> zya2Wsn+GOyV!}A}@}0Z%GcbJ)lodjsa2<19I`VC>DFSU72Vbd7PhO4kPrbDY>|Ykn zm(>&>!>XPZoJ0CO2V{A$)n4-1pFpx42DgL5wDF%D<2H>*37yRpFL-8!`b)kJw=M|TE$BZAG@d#vCEhhk zS?0mffUM*Eea?t~J*8yv?xPe|rF?zcX=TQXCRtSV<+n=*Tzhy$G{6~gR`-@Pc~-7H zG`e2{wRRaa6=T4lWX5g=OB+9!Y6}mr`Mi1Yc%D+-JJ=5`{GtWk+ln@g1Q9>yEBx+Y zYb&vve)}Kw%J2I;iKjwO2!z^CheSft1?X0-OrA;Cc^CO%$QQfdOJaz|e^?nhDMaI_XHqR1cm-=gAt^b8}b0YKM}O*xl|aGtFP`@|U1HqWn32pf`?g z{TPa_ z&*gkuHPPDt@WKBsk3`o@9t?jYv&>^I<`6|DxyqQT+Z6+^6pBg?_;D0gKikjzNoIN^d@EF|GTMjXR^ElebGTmt~*YA>%nO3(wjOi5oW;#04 zMhsC&zbw&w6ZgNU7N&U(dICryn=4N8dL3VYnL&36-pJJx zLxyEP737OKt&E(^{h!4dwV$OyW^lVD_UQ8&Q3N8zpUHFicboYoEgQc_+DVn~+|+;A z+`lnsEAY#s^D=2(EveT3uP9lrJK-;QFi1|4$pYb>LY0&Cq&vWgIcW~3dBwslp=>b2 z>ooO}(+i_QcSO7~uC?HkHJf~-f~?^Bd0uxM{LHu`f?BxNeBU8^(`{&xjOZ6!Hw2-P zg~upA0%v^l!Y%z`ipTR}&ip2!^-g05MaXk}QL-_vo}ez><^_T8$uCVNDd4r^(T&`A zP>8IzLb52b<+f@6u!AvuvYMqCJy06(n5YN$@48cJ?(>);kj(DCc8h&UxAmEudKPw| zX;A)F{dp~moVTz-n)9ycHh;nQXDYku)pJvAC=G`(lYKu3EVWaf1!eFaPa@t#DO+swueC#1 z(R1h0SVlQrPeJN62!7&1GL)AdImGhQ{u;4I|(D=ETYSAt9`*%q3wcKZAapm{D}jZlmwB zT*^zUj915#a5vs@MYz0vST5Ga!IsJ6Jh_Z0k8e|JK$=nzXU!LQyC<99b(=me=C|(b z83esw*Y6|;R*ZeRyx?|$fZ<5Z=AXFsP)?)~TuRZW^M+29#rY*2Z5MukgCW4vURHUE zJI$oaHJVHZ2)A&)OiJtKg4`hInh(;ap;sH^{t6TeaEtMzV^ z;NR42eW+Fu87LnLL)+hdP$XZV{y{8k&y!mqt3CaA@Pc-LtUZ$UH^eW54?>yxm2XlT z_k&tRcJIjh7p?@YBHxFVJ1y}Oom4(5xVRnr1*OLHE+k7@C}xb9`*e>hEk zr=C`nZAjf$6K1{>Sj$+|20I3oC}=>V)?+v)`m!N&?hS(rc2SAMlnA+HB zK$_xRy1Qe9wDhNk77sIy3`73l5~qjh3B)Mk;CrxDSrDTUF70!>_Y>oi>Q`7&J@KaM7S0mYq2PO0fTLX8LV+TUqrE)-%$i|HXVo>{LU zBF@1QIf-p}Esime&pDMg-^aTcHM?$n8%hHbUrb)N)jVFHAZ*nEAWxUizs8wfru_0} zwSlJt;7rQ4f$Sfpg+4Bqz%U%OAH3gC{NRs8<6E+7R4LS~Y{R{8K%lX8-Gd{xVO75? zzSSpzYgXDgv%hPA)Rgi^-g3j<#o&7ljUPoU$^zKR2Sp6_Z9e;^T7kqsRMrpj<>eH6 z&BxIU-*4wz1=Yn~;LbH#Egc)(xF`KMR!|vCVE1a^eY-asD~{C%ZM9L`2Ad6)sQlfI z4%1(|;HkkA+Q^O3{(Dw!y2?DMfWhkv-#)Een^d$(`s^Gg&*iwp#?BGO zLM@~~hSt2NxUPD!5+V<~_rtnwm^_AQ5BWXtmUgJ!fUZq!@@3b_YXhvU)Ge8deTdKO z;9rwczXa-yM^^pfJLAl^vp@M3mhyKdmg8(iu5=lnA3ib;khTlhe*NL3_E2<6JSoxR zMmn`sx?!t@iilNYs~iKh2+2~EU2-NSD>Qy)CvvcAB8Vb=JppT8TN><%`X&AOX7OguaAGJ$PA7b#P|WRmj2WYr>gby z*St2P87YN+QjRN;K>G$a7mu7`0c!5q}7yiJ?A! zvU2?~a?!m?1Cx*zn5Zu*LCaEyqYD?xO^% zPTPY(I+mlNeVM2y4ojO9LoaGNmu)@Y%={2WgflXt}rWePhq4nBqQ5Z++ zO$AV|n%u8A55q(P5A;@o-Bx5Hi}T*cyf}3ogA$;9jon0Eiv7!{4jcM8$E03nH>J*+ z$z2=9Ai+!vhBlpxcjmS94UuP-Wx~5AiIKK+!d+E=Qe?_&6R9%b(1P{U_>u4}kMm=} ziE_g;G@z6V!+M4gEQ@=_jY4k;QO6-pbn9uMh zcv$*b;#4|oR+nw7gVVJe{Pv*co`aT;9^&brog~A^HF)TE4ZDB(%>>t{Op1(QvcrLC{G-5*A~-5I8vddXL50{db@v7 zP<@f^$4wiOWx9yKhYu=Ib7|7v$?2Ii)T#^f)F9X}p%W+4R;x6Rqe3Y{hr7_0Oe1QY zwabU$9iAKi)Yb!G{9#Xa&*S8KVle?+7ghg>v|{htwgwt%-Gx^)AlPgT+ZvKsu;G`B z_7dhz>YG5KZ1?9T?U`(MrB`#oK#M(Xr-Nh?hgkX>G>#k*?hi0!nHX{juMMbG-T*14Bn;nV^#`6YO6x1L%> zS?1OkeS08f)Z-^(-=GWD54wQw3jPv7)JcIr-R${xhd1 z-J1?z&*t0HF8}gdhj*&r4oBl0h20`s<3SldL`$SpT_fa1F;2<}WG$0e)tULUI?voj>Nj{-4Z(=-LVz+r ziZBnU_}L#35eb_k%Qq_z$=d4pu0Ky;Y6w%!E_yeCAP#L`s_@ldlGUZt?6p5SP-L|R zd)3=n)b zKDC|JHZ?GxeP2fuC@IwHUvNL4Zn-o527cRQcnd!LWCeoBEtY{)e_?pNri!J7A~jl$ z#!@ZK4id}eA@yde$i0JPw$olCBV)l6N=P3@FrH=W0mE5FK7)?)uHyO*P<$V7TJWps z!_e2?<-c~>Z5Of`^zq$XT|c`*HBPsm26Mo6ahVuTb7aR03tuYo@C?-n3!@ z6o|fCD?4+GDlc94PDIf*H;0psH-}~%hlWPdNN0}9bgJ(SBOkRv-Q;`?_0~XK{!SQsn8kidx0OvlQ zGdQvBFJ=BNuN|B#_NgeM+{nC9Yf->M-OluMycPKK>z~ztGe^S`XQIN zp=1aQQDojI=#JTNt{+ZCnFw<3#Z#Nsst*@jFC)brk+SYu(w8GN*+IwqFibA`8#?>A zuF4P%ln{<=I*c&|@&F*U)_wvZEY`S;A7;klN4MM7gH2tjlabI{D6gfOvM2X<&wXh9xd76KJ z$Jw;ZGfyEhwFj2`fh7Bby)i~Soz<}`@aAIcM(X*F--F@Cw{(vEXrc!a?e#o}<#V)_ zP6PW0AYD_s_9~{{>G4E5*F2Dx4^+&fc5)jyL73}QbBmeVn!&5MEUlKQ@4XR2j0*b%yqNl?#5i$k;v49n%LF@x30knIb#hsATcgW*_ zWuwx^CM9!E%A?+z5=794N25|awSPH3yNaEw2sogExW?bUETDty$K2NdOY%p5L4Io1 zQhAo{7WQfD;IaR!03sOBD5kyyWo?59Vu7$kHGE=O0L2fVP%#`q22q0L`BOvOmnG%$ z(|`k%M~+xe93^}ryOD7k9vhiRYyQ#P_R?L&v0~Wcv16w9`QP@o?k`_=A?!&1wzt7w zkqm@9wz~X6e(D>;^hTLTnTE7e3DH0GzzJhqg%8*~5{aNa5y^PVg9xS`iAEuizjx}y zz9JZWBBFrwhyiVKC(nroxr!gV z1R-DuNw7U>3Hbt84oFX`Jkt5A`2GKSSWZw~1&^@_AZsud_(uB4gh-WbhXv{@;dS zDIMf;250^Jw_y_G>-<_=s$BGoTp>Z-r!!{bt0LCTLAE3-yNH5xqnSS%`jqH0&)jUF zHyyi0IDDgA$8}0lB9pb^L>aS1N=R3Zs6FRn#rWi)r`)Jv$y!=atH=oZii+v0(WP2( z#H}rzWbDb|3dK*zVEDASh&eJTvbDrQG=vArP3+6=E3HrQ&pFZNjk8gMuWp2fL+z=H zODo3N$pnrp54+G(<_IwWOm?CL#5+>Ch-*`cf1r442U*@^9Lq)hY!5{o;?dYVD zE=j4_p_R5E7qkj_B(j;1(N`}G~bA<0~uzr;It#+VQB3J6k; z*WfaoP>r7z6C#cuzH?EC#l$PJ69Ak~6e2m*@v_vb?-GMStVY`V85KMuR0DC>|HI6g zDAHT^ywp+%NE7wMzzQa7K=U8}c4bZZl0LX*(oj5Kp9V5=;Y>t94IZ%I*knT}$+&wE ziehe&c_#N|Hn0&r;_F*lWKh|eARF)>Mu9Poi#XkbX;lpo|7|7=dsWJ0!WMJds%l0a zZbE4;lhQwzc~rg?Qq%xAHIwV zePS%h8-D+py=gln;|Drm_xuuvYpNAhn=4e+qCV_@2W5+4r>63)1jpK2y!5ZX@7M&8 zWTz~Q{LRBU%sla8SsJ1;MtA(gy%av^hhA9c5>6Ul*&rf`C$yBvqMvLo9&jY~U@3Tbn7y@U&Sb7l$)V-ikq*Paw9-W}P4 zh1Okgl=NMUk`(4J0S0CpTQ){c)PhwkumE~H9OLHLQg3BNB~Xc_Tc|ShA#Q}; zmQrkMGC|BfTa-FIilbqAD?COavnva*EkzookSQGCy=Xg~FFlHslvdM%G2=p(GDndj zB{UK>N!)(Q@9Z{0fAtrh!{E+m?BSPTI-(RZ6jg@qLb(Sf#?;Yl`DlAxz=i zV0orVRu$uNh$XkXL!vC9wfBeN*jW7dRSWZNl zXc)*O3=4vh&`gz4f2)tYMq8804f~NL!lGh~^COo%5039$o+%|UP8Ke$sJm-C2*~-5quMtL3&T(j{M7AW*IkaWptVu*9d~D!b2l>Oip#|FljfheZWlZ5*je$F zSm~+knaCq~MMR~_7yK*Am=eQL%9hPC3^|5M+L#Qhnnuc(Oky~c9NsN=j~TJn#4t7( zg#jC~YL@0v(d7oDN1a{pg?rxx9)!%HMoZL*i`q$P(@3t*`Kttm7VsS?qq$}I!4;$4 zt1!NSVL;lpl(>Kc1uGY;s`!6V!Cp@Lt>xm5$R_J6QlvQD#}_w~ooy0;@Hj!%@(Kp0NNivsdV;xpv8F+9Ni2l{p0naa5UaRrKAvcss+68fH79-JZY=AUG_sW&<-+ovHi|8O~0lgz%3+ikPD? z4fwbdLanBlOcBeRp(m!>%LX}*?Rnh%3zWTB$oVa6OB6Gk{C`{6(g{(1#I&T~54OzQ zSylHeQjAk@yki$l7{zcnC>+5MObHBDfN1f*%rPGktdRLQhZ!^}_W0t!@X)MP`3xK-2` z5c($TLkKMvtgmBIAOhsVLY5{uTzU3=V4r{?0MYj>^AZKI04I#>13zHPF;xv;Wr5R?ns)}I>9N*4IB`IVjx*1e$2ah&5ve39KMH>=hDhid_ z$Ibct08LM1vrEQ{0FQiX43|PE+#p}Apv_i!SLsqvu2zAKhKwKBD^bZ4)-rvij9-z7 zqic~BD&|XJb&#Oxt!0v}P$Xr?JQ^BACIJ_!Ov4`=thXAASlRF33O8Z#c)T^c`A?6y zaHxLDN&0!t`Z5HSe5MeK_UHU~!?;J(7=wyzXmF(c9LcX9N|yqXW7&9E5_m=&H4Ld- z1l=e&mJ?{)%rY9Pm8)&if<-d0oc`K5;Q zYt5;)5iQ;qv7$!tqQ8LMDH7PBNc+Yq2sC~?v-C8)lGq0A4|q5Bo7_4#74FYFPoE9lf0CH)Dhmn8M0AeAW6d|VNnZ*PJ1q) zH|hQ}tVsWhRZp2C8EF_ptFio_$^mnRQfZ4u!^b~H< ziFoGigdtr6W_hMvvXtTor7?WZYl;pfmx5qUG}rM;2y$u_q+g|ULuginOt?toOSbCu z3M?OE#~lzkh3}v=j*Wcwp{MZR>aYIgWg^2a+hhkOXCM?=!hG@n zAiPRz=4pOhy-H{(qoA}mvqXV@$A8~XLR4tO`_Xa4`=7g*iVk7@pSEuZcEj_y6jtuu zjy4)8Dz?d2T-LZ%U6T}YxG(tPm0k3wG;eDgtXL~TD=M0hLHRy+dh5T>a4wP4*DpCl zd9AV)m|+q6U;XQ84%HgAooJi_RdxR_jU?FoJ@B-dC#^lI@P@FGQZ|KOgf}BsAp34< z76yI}%h&j{zJD`Arj4dZXp#t<2z0bN1;}SOHA(?=0g3(~p$@_JCF{*WvANkeIg>(h zDcpdgAiaAOhKkU-S9CFYA976WiqgQ#E_xaX-%a}oY6*P;2EI5Ova&kdv5EII?Qx-xavgt456PWM^+D^jnTR5*k zk{E>;5<%aw-8ga;&B~7}-pon7R$5r0D`x+~JkF{9v;(1b!u;zTHq{SLzj=g`h7-(% zlF}sa2o}m{zZe8n(Q!IOqD^mw$|lEuC^3L4@Qjiq3 zDfR(oUVK=raacdK`oAp#WtZJ`5oq&ynroARv>PN_;x&ifbBUE-FxzSg6jpo@(d)1M zC`zBXlg;1WNe!AW5p=R|%$-f2zr${v6-wGzmWI+nZuuSxtTG zKVA?whO7KtgHh>|=v^A7>c6~+m94fVy3U)Xkg|SqKr{n=M1gv4 zRWJ#fZ*@fu$*WSbw|o+zvl&Py;b@|w;eIe57-!uwjKM9Ha=3Eyn zYSK7CGm>gZi1eXR7LHUrmR$^8sLFV3T#Dpxu7n2ndWxmB4&LPT zh*SDh`rnKTY0hWPT8z<3I3~73eo1KH@O=S75|U_#d!UH$S`w4EU7D9W`I~*oF~>7H z=|ch^yvebrxJ+_N&-t5R$uVz;SU=BD#Sl^oX}oQ^r@(y5@xYN9b~0HCxneJP#617| zsO#|&#^62$)Zg6A?6cNt_-$F|%aDJjb7r9cX&)aEZb?3--ky>=Pl=w#Y|sDo&)Vw4 zbE7j1k^kY6@8iW=$jx3BHkNm+|Geh=c(E4p82Ig(|1YokLVnWb|K&Abaa&y%1mMaq zq06o1)t zc=}uK)l0wj&&;_0`#;{J5z$9mJY|gq9+lzYv|1@ZOr?=Flf5|Hz3E;%aoE9v`zj%} z#O-$E$7F^@_)h+Xhx>?tCr)hpXU>(j{7A<#!yM}*ea7S??aW2WFU+`()OQzNMrt)h zHrs=q_g6Z1jGTV$KY!m(l?wbW{VkfbaKqnN@(x#Ua~ND~%8A95IoK8{eOC(O*WPx` z$OB64fk(1cyVb^c>6a;OZ_2|YBDckT`*ZPJml@Y|a`EFBPu%Ian?cZ`gB~+3Ykyhc zT1lUa>8p>MPR+Ql0((Xe7QzT58q;V}+u@)Ebka!TPF0BNMh_(93Mo{*-}BY5Z(*D$0#)RR-UWdXfk}&3pf|6?VDtsO7Y0O4%Z?gV0iIn(s(0AcMCVgCAV%QJm8tBz z_$q#e+FQ_DfT^xo%sk)hW{R+9?f=8rTL#6|eBpx$1W0g~;O_2&1$Wor?(RAScXt`w zT|)5S4g+fswkg#`j+ePvEGH(={s!j zO*Is^83XO#hT!|F#$8)#JcmL(YY}}FADF&u<8f!l-UD0FLy~Un9gkWQr}#kkL+tn$ zbjQ1`u~P)wUX<+k^kDoRN&B$Y7e{J%%F^e;rme5P`q}lMjj-bp9e*AI)vvf>i#=Cy&9LG2lOhDI- zMLjm?-1C1P@kwdr62BC!8Ub4~QP-O9+R598i~&|;skzy7qBPNrFOD31;>RIT{$ z;GxDocCCZjz}76>GPfVcf)zyeXF*rV3bZ|hib!+(z3wC$Fc;C{Up&7}oZt(&Gm*MII|C30u5}L_nm4G_( z<0wEr%=wVf%O}}Dbc1<7VgEy*TDHOPpMAaRJWpiEK*$7#j6D3P&#@kEv;ek}!Trx? zK{ZdN+|i}~_ql^cE^y4WVvAX(?|(kUGuuSX8Z~{M4H5XC0H4}xqYydT#XoJyO+QAc z+|xZ=e*LPJsZd@kfR#n(_fxHN?h@Wx1Nk>?PIdbu==NWzMN+<&(<8%aQBUC0=hd^g zgbXKzQXYA>3>vwBvAvJ1YsKErcV`bCILF#H8)iq7IFeeXer2~FZsA@|g-7JIp5r)Z zJ^3|ovl?el{=<q+CaENH{56ua}#`ypn)KY<8CNCo8RH zwfFGVfjEDGe^+KzVaL_wSors`FZmqr=D7QlaCLbk*?W885Sx6!TPF&)rIvW*$(xW@-?4Cxjd>MWRYgJP7oT(i~ zbbmQMHEklWbqup}Sg}w2^*-Q6t{9`6i$`c4ed6t?(DYSiPr^ZSl|@Ubx_53mK%mRa ztacfEn|hff%hXiv3EUu|W4$Mft{Qn_F?fAYkk?9;mrz zbNGwkm&M9NgGc5?h?mN)Lbn=pb4m))!;o&|bT14XXl-E_2|qf}nFP+~#183|onRtq z-|EnjQ7i#27fdDciKUzq)%kKpR_x)$SKFk&xx^rUrL}Dwj+beXTtJ`n`+<^W6*z2o z>f`E;00*AOE%Gm#y0;bgt`(YjhMs7RtH=iGven%debh=mXdB-{rlMB`EFKMsMUbq` z{7l53F&|2>`>Q}vb;PKcujYNhcq0u-&~YsM#<`?On7VmMH+_3M^od9UJfCV>{&FmF zk87goT7S#0WhEq7pCM9p8?uOZ&(R;zP`+-rrFd+!4lZIOw#?>{dEz)Fb1dt??)l}MC|63G1ZZD;)=Q`5)%>eN8>j$5 zU~R~@&qZBc>lsyE&fprj5soMcUxo~h4ps5}HzTNs1}8PrcB@wpUGDI!-rP^MAXYj)5x5ogl+p?i&-V)F5N9+Z%>Dct$u1_s zKM1gz9_rY^V6gT16?c|nSVl_1T~i~lxJGA`Sb6MCruocW$MKu|>#Z0>UZK;O4gM5y zqZaJolEU+DCXbC3mx^&=oLy3KQpYj*a0W&GH;Q?Fu4Nlp>mqm$5BU>-yViC29PU&8 zL~O#pby!f~fzBb3R|aS=ySV9A0%G%=4HcT@_hJ!du!)*7l+TX1!~B^gB@zEzaht3n z@u#Z8DwdWS`NJij{f{9>P9;BZH+X5$MLoFOy(&!u{sgM_`!1Tj^JKUl#eAa$58HFZXVLMk5Q$t@c)^)&Qq)_TQ>6wKimd%S=va zj-OZWt@m7FWJBDH4!Sg1-AX({PM=zK*= zLUec5cv)bPhF#Q!8RLu0qQ~p+hW+7~Z8%DG3YGJC-8|0i)Vb_cbOqV7D!!FYeWJ+2 zktDM{PW2e%sUJTQ@E~5;GB6U**2d30CbKQm2=^dSK+Y?w(@FL)lxF^l<^i6Hv3Z&b z`8TKFQN*(+EmIRHn6pIO;=j6TS*dMtM@rlLckwIj_iEXkNvyAXJ-oP9+yZotWg4IN zXlLRdr{nh_f&6xZ{8F&mw$72xDY6Lu-<{D}RW7Nerjyw7>r)TD?> z<%@RaN!Q%JXyGt}1bo3LUd@dew4H$Sg^>!u)>H-Yaym`JaxQMhrs&TS9YZr^e}~X| z9%QL*Ue#gUFR|r{8bjG9EjM5$V;=0@h|?FXLF|^2&C_bjWFhtJB3M~61eOIj0F$uA zzeetWeoZ;?1b5)W(`6sZ_{H=k@9PJjP6h)k&{9rua^rLaX%PJ*8WtwOzF>lb= z!cl`=4nTyET?F+Wu?2zN<1cV^92R6$?b6kO_sG5|W?@@$tIj4ep{G*s@;GOCj$W*F zRFXj~)9-<;%@IPu$p2p58ZT=Oi4g-LS4)l`@TR!rc%pH(gf8Iq;iD%Uw1uNf+=ESO z!miycKV0}N=Ya!gL97kE1+2CxYH`%za9y`p#8LUYv8*peIy(# zn^o(Y>X#uJvV!Rv<)GE)1iA#R`?(3(K1BjWNjPK#qBFLbt=}9@Fq#aWw~|sB2>d>5 zVvn<5S(__EyeJos&f@qEMh)&^{!=sH>yyx5!>JJt4_M0aqzv}VQhl+5#lR}od>Bux z|C%u?<(>4U{E#V@A%$5=L?Sm<@Ct{p%^>bX`WmL09FbmP)bk*4Fj=f_h$38l!{N9K z`ME(`u|8jJ7w1JL?Fj8doyVq(S8e6Svi=NL8hPEVdMs{Zv~eSi9L%X{k7d?Rdmp#cJvw&aj@d3-7D{L_@ zk+e5KSoHor9DI=B1Cuhcr)`pRRwoP(x`CLNvVUP(WVM1PK6bDE~s)psDU z{o^_6E+8Up!{V+9VnnV=w>4j_6Z92td%!nz6;j6B5Tt_vc( zswyRNu-D#ncK>H%mcg%kG?5O*NRUue%r#b=PteMUKGcP%zSLNmyq}P&0ojYK{zXVQ z^p;~Cgv`WwAt*T_$5p7%K;gFD`PYU-ETeG-Bro6FfsqBS{nPJx@Wv_Ixaji;{_dcC z=IQ>Mps{;_DuFf8`?)Df4`ha9V<?Z_kq0bVo$hPCTHkFU!m=>^^`>L2Ka9fjJ&T&0-Y1mve-bj7_1YHN7xxHvSZks* zJTt+U+|{cYG7>Jv=e47kmUWvGiC}r#*{@nHhdSA>B93aj0pUIWq0B9@q-BCGTh{2=WIfstokWxc|^C~r_ z(sL~c8s);kFuLwS``GNDOX3irU7``1o*~UJ#qmuh?c}F(fPL@~8Ct@0LNDNThdj?1_+y6s`_+vA2< zlFN=%(^-Naqq5czY-IAM=rC>%o6ru3Kun8jRL(bEdD8;F)#`8r4S=LcCPI|j7JNw5 zgfajk6L*P&7ZMy~@mvjkFW{sM%|RwyO6rYZNxrd$;GX{jBw#0vkSjo>@%H28An^-C zqJhfc$iHc>s%Vt>;^V@zW=9l5uc1*-h>^6}vw+lv^9U1#jO-s-83Sa-Hk=q-ZYQF0 z{heQF!66rF083*dm&tbTT3|GfyJKHOwjxR5tY`IYhS}e?`S!3F_J^G_*e9IxT~CF& zR8C6_QMry!G3XpMWUHa7x5F-oJdd<+S!X4$5lM!mNE)PiH5o-HATvj$TsKhaFFjC1 z61-cb8xr20R}%6lg6rA&2LH|cw@HfNRBSu5I5%QyghuQCaH8)ub6}^Lq*1QIVqV|R zO>@WmS$OLRZk9S?DkW9{>9ygTg}lf_vGowW$0VrtKYvs~Rs>7v|F<{G|AL9EgXQ3(ZJ8V zuXpOvk}=u5@#-@>Qx+%t?@feY;V_s%ZS_4LZigeVIn$DI`!Bt9 zx@zD|$xrl^0}xMhWg8t)j;PEKLq2B#3t6kT3YLbo^>472w-RG=vABFey;d z?t;iB8PgK|U2S||8+O#UUQ2Rn_wIQ_&a0MY9|KifF6TTa-n4qYKo1rJ511-9q!(wM zF}&V->nRWxj@vNuf7uUmfZ%JP3y5P@-8MHUsK?p!);9d(nK%1EU*%y8U3fM!eK(b# z$372+@cMxo8ojvApUf&eNfw-XhsV?(CG)A@l}UHL%Z11-rjrK|Rk_11f_q6o(--!j z2=?_v&`3d`0Kj`Q#=u5AwvG3Mb1QxfGq=D-`9ICW#ZP}mUMhU{_zrDaYq7D3fLbZf zexU=v#;9oX_fWaHCFLFC+iWENIO(c?cJam^`?2nsv{I_?0CAvU)(O*O4o>}vP3#xM zEb&n=WGMPQ=4Nb{S@aRUADt1f135;c=Jn{i+E-?tD@DvbV6?%@x$}cru$4Kg(_g>;Ie{%jXsLCH( zaMS(QimNblaYz8hT#8VI#5WJH$%OJanG+}RXnh9Rah^kN1$9G}WW(p9vqZv1e3xi+ z1g$TN?K!FxA9q!q^h&*lVnV=Dcnhj7@WgZB;0W?E0ul|f3c%Y?&Gx^<$o|+xfy8_e zT7F_*6>cAeVAzng@pdL*pnANo3Ig`en#!S(DC-<%BSC>X7;_t{=3J|UaUVqi=}de5l|CCJ|I6Yt2iKYg#Z<^ZY@@@D zbIpjtj|T!PambOH-Xe?cfmC;g=)$vcv##Pdir;cTAi~7fx9`~0e;Mns8cHa2LSL*S z1cXycKO6kxZv>1O;i*Nsy?Ekyx24bBo;R4TY}4)0kxsCYQ~7BqZ0 z>=)CiI0ZLaeu3TbO+hOZF_o2`!PluL)5f{?ic)fwZvbPlA*~`@$XR1upB+U&l#~Nn z3vcl~Z7z*Vzfn8z*8xd97vVJ_xlwj!;@tPens~Xssl;I=YYTh^VyZ~-c7izHo0fyX z5oW4wIf?iY3DU(>I*{cMHM#5GK0+uuR%6mbY+R3(sW@eBLTHhrzLAG9Sl5DR>2X#^ z`7J>U&oLcia{iGLr&M2!Nh#-k;nN}~s+vq0&KnsaFuHzHyi{k3K01^e5ciJ#blOFs zpjcbhXoPe?=8ODEcafAAc7JTyL;k;{@Ja zvhwnF;Z3~yjE{od1UlR;4T>))`c+ms?g>|$c^Uv2<*P@Hyu?w|wR<=0>5i0c2%qL9 zOU?z)KaQxyZ_{taYNgm!?a|$toz=odW^480tVsEk!>8zL1d{tucA556T9Bl_5T;_# zgf0TUq|f)MO>#3%Xh$DJt+J@(xAZNv?|cnONt8(|y`+yw!t+L=4Z6Yn11$XO#kBJ| zkLu)xTj_jBW0)GN>T4#gKE#R8G7pnXIK7rl-ai>2`6wC#A23;+8*yy`;$d*kBK^%puF7$F+@eWzpUm*HL0H%N$D z%vrPb@r~3O&LG;}1n*>@Nt>yOv~EDgo1HmSfJeL=Hvm{D)+R+eCI+qU`jgSG_5%4a zaGM(?@@kFD8-D@q!8V#ak7?rBM>No936z`b;M728ufNNN zRe7-A{V*RKxPc>4A)H^jg9y5dNV~p9sTlxWQJTP&2Zh*7qBu22CA>$< z**|`*1Z~Qs>IzU3GA(@0ww;`to>eXO7bIICT?YyO!ZRpUtiI0~RX<2~`vXloJ=`@? z>ga-=J{Z8+-S!wZ8P{FO^^6zjiYMBEecfwes%RNz0FaA0KSm$49@RNwP-7|l7qq!I z4w8Sw0lZ~({F$>8R8pF9;#OLWqS=pWdiHLk>4qIM@j<&Rl3j~aTboHotJ+89u zlY3FgOnn^@K~n_P9&!d8rMQ+`X==(19^kN74O;ytm9MrN8twf!z@dXSu&*ODCQqWr z%0Qyq&4dS|g!%Jk?ec7)m7%NJuNDY@i42P`b2@D%l35WJM(p{g_6DQU z5kb^C((kKlXljA>EV)svyU9v+wA%nkRf2}nxSn%#P@^sTLqaTO9fx~J6ct-93^VhAS+zug>2bUv*p3EgQqX_E{4UT8C zntXYuyZ>0j&*}rdPtpbd4D02oUtCksp7W#e)lf6~CWT4ZEcNnnm3^dQxNhTpmvy@J zZO?8E(Te&t|9(pf39mgJ~p1p-uxaE%(*UlrFkaONAG!O;EzE0K6-(H<}+o!8o4!N zR4mDKb5hCf@YNkU3oB#E+2`t2wX>^zj)0#heoU5lqN`$uqO8#=$e{9zl9oFmrIXI$ z5b2fjfI=nT9%|C$@2;Qh8OoS9wbF`qkMx%YAe|&s`Zh@ns*`NNKjDUC(8k|IM_8_k z3V_=)1B086o96YLpQ=04yuWLFTGtD*;L68kj0K+_`B{pB3iPb10E+*Uocc89i0f8_ zd*d=e@i=rB#>L1&a7QNwx>?;31Gx~e>P2NrO47LmBoWuqON9M-7WjKWcXq~OQX64B z$?=|+roOn2dz_zxe<8(MH!iZ@_0~{uKG$*9e+@^-*!J|T!$;F4pihw;Sw6q%JTyN4 z6dzsWHorIgkbZ$lQmKf^8E0&vqGAkfQKmYtgVo8AW1!%uy_%5MM@&u=giXLMlp}q+~f3*6{ z>k41cIbGKDL5GIU1;JA8J4EvCvl%{T4Lc>~V(A>?9f#nU>o!Az;L6+d;xo;2Q-57m zreCYiL+s4TtfG?uQ0;kY$dAro;{}-Y&6f{V zPoLWg)~l5jW%uEiT3*!yIb!&^;}*v`N%@jWKhjQ`cfV;+wH)Zv|F{8gZ`Ul7!+j(Dosv*UdUHJ zYAvMH<)kYsdOduHG%_K?9WpL1GP4)6P5&BM+dIsn227aW$fqmi)?fQ*{v~=#Qep`G zagzJkEHa(k8A7v&oBy{cD{YWYX%^W&xwlAyDNSPNAz#Fo8>;YB8@6Sy;>UHR2MH`$ zrhAk+WYMj4v5saAXbHZxwcAY>gGd`UiqHD9}p|b4pVhY0Cfs1i03f>d&%Ks zYW()nkudhTWrWZ>aF{}p33rGAcuM4@LZUDUR zb_+&O!sHtSF0N}T&O@Y%uZw3qXG51OCv|2To-?m!GE zQGRoK&3YH}nk>X=_1dC&EB3|`tC`ED7Bv$jO?!| zxlYq6um3qswItZ*G3XXun~5v7V&(Ry+juUCD!ydshGf+VH3=){vub)G4g56*pT(g6 zYZ$+fe>pqwxOz}r3S6(`e4=gcIBp2&N)pNDTd4D1y7P?EMCmTMn3Q5Od}$8@VsM^m zud!?6w`wIeG{9$Jy&5F?8I-M3nryJZ-u!NTXp49IaIIP~aB^+XD4@~(yZ(YlnT%Q| zKciGXqfjaX!${YO3WssTV>q0dtIg!sAKmNtm8iVjld*BcM8~Z?ELDlnKqll3B=}mC zoLT11MJ69gK_Mrx&D5)$q?SJtmSN6Ux7SOcy0f44YZfy|)m;JKuR*o>?`vm}__H8> zG)29(@y1~|RDYh=RnPGx<$Exppj*q-(uauxR`%TCDQc7((?xdC(s_$;yN@&xT=%fn zVI|Nl9C_TXYq`ysoo9Ki)3!njYx=E+xn#I{@tlChRevEl-fh6!AlafJ)%$ts>6((y z7GIJ|A%cmMQYLJ7bP~$bF7S=%lrE0Sy6rx{a@xR1@aG%9nKpA(@!$daxF&Km5tkZu3PARPLsWda_<<-^>B|9kybOvCF)jSM$BO;NM{H;! zvZs`kal*qC`k?iqTmQp9Fj!HAzjj7s;VEs-c+lkrF7HhJBRXP`Y1_{PQ?Lq^6K2{r zEZLyDL?Ku$<8)I?|B`9{1U)VI6C8L;!+biy_}aRL9Bw3N;a#=pr@+nyT>6RStml=7 z3R2shj8VmOZexKBN&4{aHkPaa^^z4+@W*$Leb{HFt68yIOaTO)na=-7W6|U;ZBIz0~&K zd_##fLl(u+?%`Pxs^_S%7PkB z7cmBbY@ z1%=^wy%-1fP|@io{EYA?#vC7dd^QVePQ8(AeKz6tN4eNo){kNUFPmc zP0i#vxko=xJFAZm<~vAZ`a&Dt^cz`-%b;p{MjlfdZyvXdDsPFkzcnHLTLvQ&+=FHC^MFA!<(#~|e4q|w5$FKWIy z-xPOD*y5d|EopjThuV>{#tCA-slL93ek-N^9v7p(702zKL{(HbFAC^yw-qy;c*D(Qd zxTIW<&y#{yz*g z=?~NsR&f)=0_W<#mKg>(d3r<~6fVrB14g0o2%#RWR-cvZstg{QAIBc2Vw-3Fxu*#x z(>G)xLY0tn&4mg#yIzq0!DS{@POuTm3Ys=o;J4l!3+@UN<6$fDE4qfwJKlWEY5b!O zpZ&AG3ypiqT@_3jSzh($wNh&NhOHfLb!GY1o+jUZS@BD4BD2;uCxde~ex{jFA#PUy zi~VYc*ulxaf8$MgoRuMQMW&%o?oFUUXi~k4_9OMEQ>}HV<<@dcd>%)lZrMBffj;)# z=nlq0lurU^6b_x*);h%;q+AGd=~C{dM_rE&NVsC>)W_S7DAuS|ncJK-R)FN)HXcsu zn^Jmy@BrfBEQcJglWZOdu4>F7Go!7^{y1K@8&{?t+4FSP|vjsb^UpER=fWiK;m-}w3#*rRuK5Y2J)L)no(KU+;5%8MGUh?suu^HtTrzO!BeQEG&1Yu`_yTiLc zF#%osVoZ1Dr3K*W-M7*1V8-W-=9Ch*D1{N-N-{totWq@1uJkIm=JUp?ra3&y-25aWB5UGw+o{bC(i_17ZNbnErOdvzH1teJAu2!&VbL^+OE-9+ zjwCC;if%bxiG^*U^ej;T(y=!mMMT1pOW}Qe$iL@toE2tShSRRl`fJ0Cm3)|8J=XK- zA1`S<|I-QYFL0ma>37|kkmA4yXcfcs5RQ4HL_=@S+6s7IxBw-M10Fx&C%D9RDGML( zugL_xN7k5XePQ;3dtNp#7!a6o8QCd?csd#pFzueF{|a{wVn&e21P7FpY$<_CHqPc3 zpMyDf1C>(xPX-^W>a76EEFwVoxEb4_G38X7nOT%t7=X!K-~t>hJ$apM&nK- z+pUX3yo-V^9<7@pTZ}jkgCW06c3TP!y;wBrHeV;^yza(McwK+5Ki&%Dqxye%dVSp_ zzo9PhAG?wl>vk2UrzEJGtNffQ2>tZ-=tTZ`7+(LeRZfucmTbgle6uXX&GD3^!}70i z7`Tw`My`c%`2LjO1bE-(#C#`UsqztS8l@YsX>YPHSW8@we1d^!;5nT$8}UsQ5@AT_ z>~ge9OZ`vWpC6duRFl8{7@XHSt?7<}8~F4sU!}{$XpIuC(%rD_>a|9t;&G#%v7GXi z{~~)va)tGcc3xdLQX^rdUip-J@#LEK0oj1Z{epbsZ_3g&5&w@xesdhwx8GU3M%W!T zH2huEmqU6*(^zn7$k(aQe*N6<{xc^lzjO5>V#C92eN=Fm;#l_fn}ILXyTAj&A@#(+ zb@0-h*lY9BY__bPZOI{?Pb4PB^Rg5B_P`S=8lU;@f7Z7$`tTXDvTwjLnB*AT zqFXrV7Ft+A78IQ6KlHt|C??Y^uCsCus7i^c6G-yc;JnqX^nq{ASu0--5@^K2ttVp3 zveD;PdMtii98(F{A3WqH+u_c~v%ei%>240po(b(4Jq{(5AB)9ERy~G>!52xMkI0vJ z|3mm3#jP(RM|2>bwzU8}VH&RCS8%LR%!n!+QJAY1A%b-bC2C-auxeiCgF8?rX8!rO z?!7$AV_rT^xeJ%d1uY~`8$~OxK~LG0k(Xfu>6!D}j0II06NBC~G7xPtmCv{UMk^{! zHVo?zI|YgvcS8$Mu}KRIA@#Z`nsGtQwRwFxU&Vm2qh(fdJW%wvD67}8w*cMG9r;^X z^)t^A3zbC5>ts^E`ebZAxIfRBzP3_R2`JebmACn)>enZ4WbkiZs2jZ*X`>#aK5D3e zAbc!Py&X%rI$0TKtWsHob8)$cp>XD}Hht{BCj4)5PLu#`qP&z>46&uTkR{T^N|8T4 zb)tJUm$c<<)DKDi3KyldWuB>PdbyYIep#-r3J|R^D(2^(92(WPFC+B9UoSriw1AZz zC05qO4xEv342`_8Hxuh*IGS~M5{PI>O`c2SmC9q|A#b7XCm*T7j5{NS8|0r1`Vd~E zPNDg_(L`%F$=e1R#|)ZSb~Q84haJ(G$ERj=h#*q?%E+Z}{^%W&7?VOYON9a1MEVJW zOJX>>zlzS9L(L7CkGEc&1b@Hc@ zvG#S>oxWYC&ZOQ%C80>LS-GmnZ9+&|OwU2!C^>nn*qzoj(O8Qa%oh?Q0cn)%*S4pE zY(_k4kxf`Uf;mcSP zpYz*x=yo5>o&^1{_P^Yxws#zeXdouHxYxIDV!&qj^>6Z>V!GrY+aFRi zefSgMR3#;d`ONg%sG&r`r_*qWl(B_2qK2P$UfA}Qp0s>eq(P-kVEOGbPS6&#n!4vZ zO1@R)3_n!>g<$Wu*@c2yQ}G?H@bk-OQ`bv5kL{D*HM;hWq$HGJaP#xXjbftEBMFp^ z^cQO!uiDOsWh%2!I?#0Zw%xAw`oPTay7ylZnBRg<#k!8rLtP%F#2v~C*RJMcdR4a>vG@!@_hYudRLuA_){E-u#43^kkhcKU z2Q}yH-+k1PF%bW14RV56^D^$l9>&LdMaX;Sp;qH#@8bt6Y9H)< zTNd?jMt4*zZRSGpM?;_$S5N7m_8HhtUPWPooq{NFm%~x3=}*9F5ISl=o0Hr-W$!_2 z2M>RGIBGK|{=iEN(1PwRobi^Ru^#|*(>POq?IHRc)VWMA7Agz{S3?&&6GN^Jp&TCR z%O&opc&Wfam`&Z20UCFi$QS(9N>mDZ0iry0I4nLv|N3npu$cdg zzXUE#Tsv6cv%r-GC0JSqPC(tF`t}o}>oi$FS;KdF@gk17b`J{f$e;15O57?(M0LM* zb);tcYoX~G`CtTJSfRZW}V1S)#7 zD}W@HM@9Pw<~zh0g5CZ3HDN0#4~DN;52=sP!vi%c(g2@UuCHSTKsxPYg(%Wn>>UV1 zVj(^>x_QTIqrW|!YN28`9ITN*Av9`bi752jg7RA2ojn+Rsg!iWgopersv)7y_=}_6C~LVa*%WMdS&aT7j*4h2YDZAtqM9e`)RD-&2RNY#I|8fuj^8g`L)C{qOM|b(xPR?>-YqxBYo2Q^#;&C z>n9OQpN^%?`k+z|+qb(6YD9sD^15(ug4FUja=S*3i|AoscQgx!R#jn%Z~)WEXlc$o zy{unBF-h;#h$}xID6V%|4%4TI(h%fB#RJpc_F1v@5T(sTC6sTJAu^(CVltGN3E3G{ zLStlI5%w4mh3@1)tZ4Cz!2E?nY@FF|r}}I5Di1sE%tgroJPr<`5GwzVUltem`RVd! zapBnS1=9bLB{#Jm@aE5OX?d~b#Vu||gNGto8g^4fJy4Z%O7n_si(gvE=rPo zte$zM&c?z8x)k5y&T1(X-#WUEr2^3A5DPK5`?p@JZ$Qm{&p+gkQpcq0^cCaDkgi?w z&W_V^*~Gm^RN>ThX87kTGtj#dNQ<1aH6r@Nu2^K#D!{TStYm#1fHxQz%Ab-!*e)%J z+awF3R2I^xXvZ?OcO1F_NC52;+d8-BRC0g_z0t0MBqFjK4PwL|=5a`c804lB%dOn) zh%MrRl@g>rpt7xxAuu-Of!gZy+BxMa+MNp48x1QOm@W)4!!xFa@Xr_tI^gsnG93x# zIO=K=yM}@)O62eWv_TlFMc<<7Xz(@vYoz;EsUXD@5lqlziZjS++$Rj1o{fo3myg6N z=A$mCyp@bA8Q56w_T8>QQ(LAf&pnaT#%3_ojaq%3bMd*QpOF3y2URMvOQ9zsg^2Uc zZBkQ1uYTXV;gXr}#FqiJJW|77lit5GG49K=1wd?Lq3 ztW6T6u8fUcjoZPZBLXvvDf}Ylg{U(ueiNnzu9cBfl|5!LM zV=vV;PW5y4o})X$+~tLlZM|Mu9K!Ax6GeYDTOU**=9H2$=^}_hbeLmC_j&7v6IQom zc-%{~zyVUVZX9s3_%1aw{P!(UG{yi3t&IPGlfQUb{S{~bq(@y>`0+{ZWUWAtO1aoy zoKdnh98E~$4MOCMGRq=V!V%X?ja&gDT>OXsIr>%Dp3yz;pb{)Cucxu;{-1&IV4vm< z=&%BZ4J9VlaNfZWMS&3j0-ltIjkM0HIre{eTJAtLRq*01)RLDXyAR!rFUG$oOp&+bIVqAYtR8eNZ8ud$4oMH zK9-?jM`snp8=glu;@4IoAL*F$fuC!aA8n_aM!{MaOBQ`s3i&LscJ??Rdy)=2Q!ZPt5DXw$wIon*6STH(@y zPmt#3Lbc%ep@_#{P?L=Spru8hYv(lfbm`M4}6NUu0bj%Qk|GYM`62S7S_sZV>xl=$w2n~Tc;MFWf#)Ar*zIXMwHE9t-9 zg6axJReZfuQPcR#%;$BSXZpIdzQQSoSu}p*Iq`8Ssf7F>7HSs&UDsx{2kPY$^H7cb zq&%@)>y9Z9;tI6m=vnywIUHEstRdM_a*Koh%CVrduQCdt4aTkegHaA`$fCCza(6^= zX4KXEO*KGE)8~V9)Kz{3I|(DAzMM6D`9mFj0)VHF_r(EUq}V82}`q0fa0%?%bgz@si4l4!No7#p}o(b4;Ul}hhF(Pjr)89J;8}0aH&}*^;(`nc5s~0$Es|(u%F&JbPHX%Go;3v;9 zogO1Ii<3@6{(?Xb${q!5!MMIpBAThW_QFF~xzSxin=$g>cfxgLo$bVa*3?5ua+ppUY1gZSDkRT*t73n{&l@Z(H#Kv;pvZa|mX zN29=cp830#PC4v zB9qUY!J+&#qyvcy;|@On+2dBVzms`HoYm&AA?aTRDRhuK&Ak&!(GDZN z>OymLElzyK(zp^(doStcOh>tV0erYxCeLv8@Z-28UqmyQx35^>VhYib(GG0(N%*tX z|10yZ-o2_c9^Ts})@a8#Vj}uz4zT_*m_*W+b-&4(8dI5h9@H!Jn6kIHb)MrTc^wdXhCslG{g03 zb_IA>);EfX7YW?Yj?w4NpKM1@x%NM^*B<5GgIOf-l=Lqcg0px+Xdv>t#+G1 zgUWta?VA8&YKUjO2lsn=1D%ZP$h^m2=OE3okI8wDnDHCSm5O<2*8>NO6 zddT4W1jysDynwb{2Xw-noh@Taz?;BgQOWyPYt3y>#8`yE$>x>|P1=5#UuEWs`xi8M zY3NONL1Us4chf<#KbcMnb-4BSK~MJ**E1Ua?p)e1ZlnBi1|NK${1ldm@{1oFTv|+G zO2zZfg^ZO%YjUK8p1GoMOL|i8h3-8G@)y6os|1zxA{C$LEL0uhHO6f;LmRY2AQOu3 zCm{dYV*(9`LpPDk{Y$n!NzMmX=uZ=4vwX;94xIenuym*ciV8`Qa z!nEyKrOvWP(8aVhXhTs1?wgB#AI}D%F z8vomeWw)wiBs;9Yg&-Z|bwWb4%2VbqOe3}|=jlDJ$%$qI?DM=818NI%PI#>T;zvan zQ)K73R;68)l{2mV*s66m2hZ%QVz&LGKD!><<69M`btLE_9cil~K9MA}w{b;hcRNWU z5rItJk$bi%E5R+I_JvPJ8?Vgd3*@eQk;OJf+PNI%WA~U!SINHZ$8$~D*M+{bO zl@xH{>=q)wpNy5jqXb*kw4`akx7pD3#@mO_od?yHaZ|(sZa!InkMgOkIoSf9@h9_i zFD{3f>mx=6m86no=4(A<@*0qIa(=MdflkN%@p_aSw^^t4P5EJbU_fUIlXf?N=dm#Z zs1k?H{O`Kdr9H*)uiPcd5~|)-R$`98L5NV>P}%d|9&SwW$(2FQj<4dee%$NMv){$j zI<29OZV{fi>HRI*zTi{;S8+nl@pQ#8;(vd&+{C5e2D-rnw!lFTurXCs@H*dYRhSn$ z$WIjdnkQhmPM&i5`23w@7y~VqI7P~=_uGcu3zKr#hwuWViMHf~WA1?ei?MSG5-iNp zZQ8bN+qP}nuC#5d(zb2ewyjFrd8@l;=1$z{h&!+6^^db7*2em1;Sa;XL+e-2JnLHe zPrn(zQ_Fw=x=jwaBIo4YQuBF(5-hatOZRbapIYL*Hy2;Xr|RIm8@OVejgyWO=V!rT z#FQ{VPYp-S`P|KM?!>)HJa9r8O)4}G!*;)K+oKE`FTi@EhByY+|5_3iOv`skD*zr9 zTPVFyjb3LOHXboGU1B|8LNUGMl*c~JVqX3l7S^k{wE2j+X&yRRP=mgJj|e}W^lm#@ zV8CWi-I`?o+AB`s^dsp;C+o)b;axaG4;SQn-)vin@Bg29?tl76#D))k#Ilj)|DD3+ zA5xWnDqPsvSXuwC!iD8OKL1s?$e7xhJ6jNNvi?KALN8`%<810gFJ@!tZ2B+1%753Y zEbB&y3Lf5gpu zqx;((!JG|x(##h%T6=sw{nz;0vTnD;i7(SN-yJqLv#ZqLW}Wvv{8KOY~opZ%-BVbHg*rze{EPfD)v)VEL@*DyG# zj>pLaKRQ^>_U{!~{xJGtDEWv?zi*V>`yffg9(2nYN^j$IsMdS!E`xzEcWXTEDmGgX zu;brNU(A=!VD|yV{iWArIWe85ucfQ@D^DaJo(tag*1X$Wp{ffrsFSQyVJu}J{Cw8* z+<%@E>{&E~4YgOxWDy`{f!z~CNd3vSQ{^?pQ>kLVbB#eG?3!m|M(D1$C$X71hf%Escx+SV)a--ahCwoMBmOc_9agl+64m9>@B zQne@Ee^D>n6;F{VOPSGxY~)NSByr{*k|(+H^Y4+-a2D0g2sa7$ydm$$DqFicWGuWv zdo>EhkAR#|)ac#bL2#3Ea))u^mRe*ie1Ic9(tPqCO9D4G_jkZ*oS?{iI}5im`0HQX zdEBpNnm*L4ei$Cgoa2as1NP2U{$dIsR<5zr?q7^#3w8>qUI4QJpETAZNe*#xD#<3v zWG9a0^7L`MtIS$YpNmA-&&Tvznjk>PMw}WR5d0JU2d{1?pdV>)qxoSuQz>12DyOlV zW;$mQu<_I+FD}t;#QVXMkMyV4UBOo>AFN$8eh2m$uWIHBYcpHOuq+yFhG2dVA)Y`B zlikPp%7HvsRIA1SoZJsCvEXFl0X!=F&uxoMDuQnmcSc_yXHIidD+2;vGsFl&?v^Fj zU;GFfQ`-uTSAb0D~2=clt5NxsO44ALHoV^Ar^*(g%Dgj*{djRnMSi%Od zH+KO~4;sPhfeK}ay|+&1?bTmRftB)DSg%bS1!|Lu<}n$A>8#DAQ;NH07Tg8+f&&zD zaR-p1J}!bv@oT~u*Fi@O^vTPjAAI>N1Qj)ZAnl}s0X>QE;Y{!(WOIo(M1ahbJ{=Xk zv@J-C?l}kyT9DMT#4)P)SQp^lT-_f5#Cf7rQG&B1laVUeiH3Ht58w4pd(ivwg?)^dPvn}yPSE%`*7yVldl<48%4p9H(m@z$PWm^z-)eTMw2zpx zA_jiiG|dxaT*(HKba8rl53c%DhRn}vdNyGo##y#aidjp0=Zyo+{6C%eCgk|~|h=E0Q@2nj%5P7QNfSz`~^VM!P!=YX3# zYmytxdnkJxV4>6Qvt8Y1WC_;91a)vchs}Z~lke0!hH0Nw2;sPG{z&KQD_>T)O zSa&T$d?s}Rm8ezZ2MhsxCVfGfaUc1x%=OtxT#l!ZkIh}`>zWatBjK!iiApt-xpR^< zu-mZBKTklUob9KLAa&75g~)`ZjG2rw0TXT7R$Y`R^VWUvg)b#G57!_P#3Zt+=*B*q z4f+TS*{o_I>hdNg2-<-crz(TQaBhjn7OT{k4**MtaDFdTD+V|T^pV`FZg@Uuk$hrW zj0H4T14!E;t7)b_zs>v&O<-Oq2DczJop%2~c>=Q2$nSbTP91K)3Q4RJ)aV^@%_l53 zr@!u{=C=2A69wZQKdawmDm)vAj+Im1DL|!9$;Ajm6!;|$c9-O$iZ%zmg(nCbjXdZJ zuQu2v3W*8>xH=!oMKo3hDK>5rG}S>ThBLw$b>QC%{Z#0=6sMI@l9(dFi}0bE`)(QL zeX~|Oh5=cxkE&~+uYRd+@))!xFWG!TXI#>~CRIUy3ShxRJ8Tb0j6x2V=i#Jxw|A^Y zlU%h6iw^+=0&wTAGnNNBV&(!J0nt+^XTyw0-pD#&2FTx5m0$tizx?D3`^knciL7WO zYE~98S_bv8g>9!nm%r!I(s)sVX+zk>4n^4kL*Wfrw>+Dz7!EV6^)iyvDt=kIF>#A# z)We|yY+8x4^lCVCYQ^O5nOj*x#?%Y*s*z5%XaC#ubS=Fo7h3cHBZE^t}+Owg1kK^7DbPu ziR@v?po{CC*#O?oikKwBa#?EB1fD-{p`NQJ$Q;-_1gA8#GU8D0gF6iSmaIn|2d&*q zpZpLme@?!26^}RiMdXTBn4(7*M0C?}6;W1L)N~n$jA`eIZL5eqY0jE$Eq%SeMYKhq zX#K~xhIV^46ztXeYMr{BO-piJScOeUiHBzawl$@2DSxJUYvvFQdXfWg=0;SL?6kHhZrR-9X=FL9A_q@CrztBu30OJNFAEkO6mr zCb3W`6&ll~LjX&vmkJr7AjIDmpoe(YPHq z0d*e$`eIvd?Rv9Bal1=2*YHocnImXy8u{7e0rYg~n2ssiQZ;7`Z^lV%1v&g0X~I_J z*B3TZhmZhRBVeqHw@N)?Bg{^xlfT`+m^6r@_Pb_KWL@?tggUf`2Iwm}#*hRS>$H-p z?srt(S)-N<%zPV|^e6uw&OZgI;>SL2(63)#-hUKOb&PG~G%vnYdM_00h_5AV!6@B@ zd(}9>*Lu#50HJ&huK93j%oZP6{O09^g-fMmKpvWzwp!?T}n3!yXam7$A2?{Mk(YhX0oQkUgf}D7GnV5 zy?)t$HhPt~@psDaUcO$F|Eva1$<5fo{y6t)zdqr2^0$HaZhqm5R3cXWI|}&+Tl|AU zm>C%v{tH4Fng8K!{11`je^3Y`^FJ++{~rvw`imi3ZU2cOmJg(gKv=NOFV5te2Jo3M z@j(f~NM1p@BydI;Ye&1KV+Vz=iDh?dcZ3YCu3EGSqH6!(5XC__@7Lq~>9@nuT;IFI`a@x+O9ur<=z+KZ?(xxECwP%Vg7k} zvWL#RPqgP~XSeW4H={<^Hf@I=8(uKbN|CRh+v>)VQ~zE3npgsRtxnUEXogHUTxs%oif=Npa-RGHD|t=ZQZG1T7?Qu4mvT{4|A zf9;;p^+jTJs(hQooEFo_ZcoUN#t3r&zV|T$)?IDMHb|1huJqc)u*JYo#(?D;A~%?O zxx(GM*KbDd#@$Pac8P@xxTWZs+i~}+&SK_yeQd1-u^e8y<B+PVIzF%m? zW|#Vx9n~+OhWJeHCY>!nfANMtc9Yj61&Huz`}m%>=ZEC@frNxkCO4^D$lAfh|44@odziVgtJ~k;VrJB|Hbm~R(AQl z_pQfmR=Hr5C0oA=euf<%#-({BbR@FOc<`p#6lP$|&5^%nPH3nPaVc`gOIOp0r^PZl ztNwamm9P}Hwf+a&Ha1QOS$k`=ZYAAGK`Vu@aA8OV$T6wDc~Gz?P~CqUW02-z;cP9c zt2S8J7UFsikzb6Fjl+0Ooi$g$$59jL9%tT6N|t>bU>i&|2KEKamENXQg^|AMxMi7V zK_t7j95Wh$O#mCh*dv-^d{a75By6MtGG%!ULQ@rP09R4GS8h&F_bP(RtTBn8o?S=< z8FvdxwwM`_b`i1T3E{q(;)Y$6+O1O|?7D&z_ri`QX$jY2y&4bdH<6ev zNGq%fHTf2>_0@&O(L!w%+B~!kXAP+ANwW$loLa>M#n0O*>~^d2jbb^`nfI@$k4?Zn ztBuN2!y}Ic=pbMnJFAlH+!Wy+Q3@zDUC9hKfeFHWF5tn5nqP9Z)Cwp)-m)C|BhbB& zG>Ps$;sbo8A2(!>LPbwUT*QW_w%g%We;xk*2(r&k_RScOy@u1%1$JRJS4&nVgs^c1 z-JU5N5K6M>uO#ryDI`Mu>1TY5NcKS(GP6Q${#d+pJPhlv8`<#A>|wBlwvigSd& z5p>bf=MFmypb11XX)4RB2#efTcHIfy_$a;2&%$=c)I$F5O8S7cO!t#%7kd7#hfw!# zrFG)$g_s1}p+&QZ3#q9BUyxFw?NAVmwr(00>wI*q}LEADgq zL$pOke#G`~8-^j&Os1#XdI*i~3OY!{t>Mk|TY;s7n|C&5KX2_~cfT%r5^CDQx7ZR! zjfBg;Y5NDrF_M_1xv$$c(TpBA>k(wviV1K%VyUAJHmkhQmU>e*{GyE>=6rgh>6h~d zkFR5k!yN`3ILTQDKe6X=;p2Z!P(koCZnc?xe_yywZ3pt#F(}YPkAZ?LL_{SNk5`M^BKSnLf zF7v#%cFp@bF^hOP+@kNVDkQbm1&)IZP9p^9{=ne(M+@y71hVDoahOz_t!Fcph_p?Z zo~|MG%K2SeF&xI)-cp2e95A^iVXL?Tiqxz_=m3@~uJr`YASD2}UtVPw@fV&zwwgDO zI1#xBBA{@red66NBZXsi*Cov9l^nySU(o9D;ss78EYliM>y$p)It6&FKkq z$e&y5m{aS(uo-;RQol*iHvBRbgcbrW6cjoysmK&VCI5y^P@b#2KZZ+yP#jZ;97U2eyoS$~UbLiM zqC)o+$&)BiCay5n6X^HauMiDT+WMMm3Zn_8xq4Te+lasCS1io~5Gy3|C=}MZ1}iSp zC9I5m@e7@VBSGpd*H^8E5onL++9W?ltj5rfQ6a!`Ev?c)K=0ZAvO_y2S}&#?^1PW{ zrS)c=Q3LjNBNV8rghNn{+gWkm5*r@nUKe=_Z)}hL#f`VI3bf%jr@{#J|WZo$anK&Gzo}Rw9oZM|TXt>$AjF4!dhS zv!3AD)RAePv_6i{RlJ~h|C6%#@#LuiQMcZ_R#cI3azKo6rA_!+o9+aAcmI5qb1yZW z!nL$j(+r4!qUb&AMS$8xJpRsALXkl;VVxts;>m|=U2O)O80VjCQm>yE@pbGuW8BTi zU+FB%2xgE61-eBg&kC{5=#mNG)Qxh~KhyaKLUg)7duW3;tO?0oAUd8V6NyEBmRGbx z%q~;*hQz*9|9fu$>sun1!KyXixCO+Kt=qcr`E z1dAsO5t>w(l>f(4%6C{5I=&NWd1m5bB034fF= z{nUx>H9=RWtl=h)+~?8Wrppy_v67|?gM6dJ&$!b?56)80=!U?XQKIT3Rq|ztxieb0 zO2zh)I0I?n&y<9J>N`dUYI!EkfvA$%Qre z6gw>@yF6OuV^-LW#7H~=FJ93a+ca$(L{!=fg}N(uY2V|J@nV`EH>I3Dnuq(tkaz0K zZn7}Z29ZqxXr7#9#Ho42`@dl!_WunFE$eK?{$(Mb+`l84Rsg&xQK@8qF<>lNw?xoqLw4Y!{;~fCLpRDM zcS>E&YsU0w0sHM`l#wQ=l{1{8)2|evx%fMFxqLsbgZkgk9`5h1ey=YK2fdcrc!nCu z?mD6J_;}xsoBM}@p!_}`UL4swzMVh&9vHKyrRKZ7IDB7U`{&1o8Fyga7vij4UHChC z!vv}O$6?4kF_lZNe!VvIFH(u4*&hQYdWLeQ*y`=FnfpZAM{$H)8Jo_mvj*L=TW zqy4^CKYk8c@%ejxZtoiL1Iu6q>0ceHi}8VAfE*xh<^uNiARw6zO8(j ze;e>dEmBPX9`=Sg`OVJU0_o=eW{UaL7Ve@_2u7Jx*L@{yu?W7CU!Ra>EB>r(V{xND z1j=8hj^^m{H_vLE&n2WZ)3{!J2f`GLG26V0o)5RTD}C#%PWfFkic?Nr$Hw|Y1Pccr zc*YVm@7u&xm*-G58j5?w6T5P9fHx z=e+!PMU9}SVL0<=*&(sQ(T_t5z)SiS7+=mqPZBZg{w)D(6J8kl{Kmmyy}FilhpYLA zzp2vH+LENMSa%cE*w}LbY{se$MQG^u_!(?Ar^m_dl_STqVv@R4P65TexlOB)4rG%8R7#aGJ$!=(@@u;ig}lcH)uocEAT^ zAa4Bpbq9(L;=u*n6^_Ush0shQ_Bl9XAaq$9cY9b7X*f&OxBKt$2(GX7lrEdee}}|G zAgS7XvCAR8%<7T!=-4#unPJ|Kb${BF6%f@s2t^>eh#?DDm^aN184s+`X6X#AT`bcB7Dnc?B|Bn&}j<}-8G8FgN&~C)nFzYp~)~o+rgDY{%I0*;8T9MRyh>m~q6!CoE$v-66y~XT89Aja;aK+9@s0 zB)g&->)?|rWSAmGQ5o3#B{4%ulM=<-eVO?$*tAlT66S*~1Tv%GZ3$0IX08PVHKss{ zSN0@%m6QrfsXL(~A3UI{K&K0>=Kt`m)`$zZO;?+uO~u)ZgXCEAHw1bVewC-5kKa*3Ur8Bt2$9E;3EKfyh;r2&5HJ zAU%aJuWzNd-)hT}e+Sj?u1TMH-DiaGnn6|fn&>LBaxb`Rcniak)}0WYGHn81whBrd z;~~x@2<*32aE3_Pwl*N!8Im2C5QPxHb*X16V^ZQli`2fW3&?E;H+i)97xNHB+x<6- zCuZris2yyqvH6M3Ax2`ydfDN3th_=tWv)GBw27V~oGGn;ok>Y~IwwTvHZ!DtShHiF zG3aHc%SrS;sKkaNtJ5hZ(QfRsF*X-OY5KxN5+tlNq-TCllyA!B1X3lq(z)Tyf_|9^ zKMaHROXmcYjXSmI?34sswSk1b5gWXWXNI0WNV>wMDDO@ z-P!ERURnlUl)g)pU`#EPDI+VCLP7rO4#S^31T^06c}(rHv}BP?=MDk=S*n3@(M!7{ zl%l2aq-L33-b%7lV-anxY=}f)-iBqUXD4)+GQX5trQ#I~pRX8$k*uXpzbu40qUELX zaTTo&eq2Rn{@$SqgYE13^CpN$tUw4qA|ncn?0^yHk@GRm(3@^dfM$9fe!*Bf!K;}9 z*V()tn5axSMaO26K(H6@8A)~OtPLK(5dYa{dWw-(c7V!O46mF|FG5ky*KHz6T?kRE zA)tz0J*HO{rTK`m9tKEB9C!GmTU`DCBzN1lK zRXG1A8@*q@yai+;Or<=jCib8$R6P8qlY?i(;14vtQxdse|zzUaY`xQMobytyu zk|H7U+gB4|VK6a&!VlqRyZ~X7oMVCwg}&3jacTfj$~0y@|WTL8nH>|2%)v=J`13YM~^Gf zrr}(LRFfv*sk;9iN*Xoj%!iUNMFwd3r5X`)WQz^H&_Y_Nlya6Gju2Ff?W(9rvEHTg zy+V}fXcY$O^OM_H@i>hB3+iu(<09#yxcNHJSk@W^J+grMpxsihY2SZ0z&3C|f*QYS9C_by5%-IEFwZde`5Rmb+wwC3|SgeLyqjKnv4} z%^dMKUWvb}7F3hEsD{_2G{L-6YWu(*2WTU!oX-f=_7+4C5_GkPJsaz zoti`Fs}e?<0Q(vZyEHtLebb!7OZ|jCkoh7@I#^T&yn+^XKK{EDJgUyb__uk3Bs7Xm|esKLk<6^gckVx zSgS5RMt_aZ!E5vuRcry(k1N_CT&B}18Y9p)>kJaOG^_15b4NMdd-_zRv8ReXMUWS6qqI=~9^T z`=Wy?NX9x1`kA1jIhAkeG^0UEHVMV=tEL>wbi&~-kM#PVyQCbdps5VdAon(UH2og6 z>u=U-s29DD1I}Q)h20z+vlH2B$EIOyK}KjQXTja1kwnSIGaCge`B)>C`KOn*X?%cE z&7?aGlU{PoncmTIgEMp?Rk#@_Wgl$NvM9?de{5V#hwh5Hh3;33?K1H(+Iv0uOJ^k) z<-*oCjcU>=muPu)sytXH_tWT=th9zbE0aLrTbagIe*@wk)RVq9<9=@%GgTiUAgtLFV(N^b14ZtED=+)w*TsQ!`9D0 z1$f@;d+ByDB}K-{F$!46rVk}Pyhsq?f;Z=8a`(BY7tfi~CeGc+(~0gFTWA$*-)>1+ zRnq&yddE@yE2N>Gqb8?RO!dvCOFMm{_xb(3g2~FEDx?#3zH#P#MWRfI*4gZ&+Gq7Q zcqn$GQ`7ByC;AQV3pC&|q`Z>5BJl{Uu2d!2yk@kN@$fuMn}9B^G=<-I|F_=Ryusa?I?^zi;%TOx!FoJCZbFN^A4 zx>a^xSn+FqnVWySBxR-CzUT$>eBQlfhD|;Uy45wG#{Im1Qj>mv#=-xz_%xqIq!g%(x~QIqwd*l?Dap2D|eA zbu~CJ>SWOF**BVJeO{p(e)4|%NgW_P|5Zj8cDmiIc4{x9euL-AJM34ey{yupH4qvu zG1`z`_FXaXZR`Thl4{S!g?X*yB|5b43VFzTvf(IFzQ|K<*)}%4E6F=(x(jintDToU zmk>)0&=;H4@8oMNt6jETmk8BM1@OS&7zLbSc{4%utn@as0T!hjX=v|;!Rnx;!W!Ss^Gz>Oh8Yb!kt*QJ0PGCTaH)W>q=PdO!L4!Aff-P|!|Zk!)qxfs!6Nbf^b z{N)y17iwt_;(ZzkIH~w-5uE8`uM<n7sbgONQ7Rb3J=y%@k)$j_khG4uDPU$zJ^-1{J;us=*s#(hg+5h58s^(yU}lG| z&o^}shtibbEV6^v_}S!1&3ZO{Ftt<~jV*}t%(S&3;ngZ6=^Rg)g=2TWyfF?01@|SGYaEDet~!}#>K6M& zN)zE59J=|Q2YlXapbHF_2nKzkvxPq+41>?U>f(U^_U@X3-f4Lt2-GhdROV3cj{@4Z zQ+gN21puuTajScb4cYH>$D7ffU}DX@^Ja#Pi7p}J&5~USI*8d4`UIUa`|WgHE~ZK2 z`)4n?jM2Yc$s~0ZKdrmbi#cNGb-}o(n)pF=SyUTB1La|kYEijg+FM{liK1%`&sO6v zUbK3yTy-5hZvL|FAEYvnN3F&uX$p1}vX!z{#(U&^U89MnsmPA+(m{#=9n7|@j?gPH zKowCRH1`R;t;B>e3AkED>wch~LL`;~N_Xg9h$Jl~qk)?)(-zRXOsgLX1+nR6nzU_k z*=w!BL5Pwhs|&b`?SGnNG0OmR zZzei*;z^)p!>L=v8kC=hC_phltE=yZLx}9xf+D&N6vN3jgkV}IWn?J%*S(wFM$ujy z$9HM_Uzghi>;?@;xY)bZ#SHDO=_7KMivb&Bgp?V;xl4#ARBwb}w9<*FusQY2P$W0F ziUY`Bq9HPt0=WZTA~v;@;6auT6)4uh;X=__Uwr>M>(63{7Ho4!$?fHq z*)q)#3Owrg=8Q>t$pfOk?X)`YKehVTAg>m*$Wt_!6t}vX9AAnZP0%v6whH9Hv) z%NCgjVnI(OjN68UJW1MEz@UA6OPx&xNEbBmb1}+6iQ^Wfb*BNX2)pMz?h3i>k=Csa zdJGTX(rz-rB%TZ;g1PR|A+dR?sa=OqB6`!)aDy@@FUd!$iuyu`kbYI#U?Aq%{yL(u z4l?05)CU^!PXa76v;wZ=6|kO+Kciv7n7ff}uMocUzT9nN|YN_~}USUX2 zg2PraWja-MR|^kj=0yy5BJW@tNr!Mfm&61{j!b{RZH`=}EsmQTa`Spk zbfE*rcrlwnf2l6Z++aU1{%w!brw<%SpT*!9p^!?LT?faGkEe*l2M1>#*uhv{Q}&#M zg~T~Z02g9HDoGEM?BUoJTuR_JEPc$Fw<$Yf67vT!Js|#CCb{wSUTPAoJ$S!Qj~ha2 zA0|SpHi`5^SUJkZJXJAgiSSk^5sN3(Lygx`{Ay$`E^(?0LGwe*TpeG%p-Owea4el> zKYbd>Hgs;hNn2&U(u*XR@PiBns!g?^C^;IRM zb0OtGDI8g>)xV0KGn}0;FpU_c2`96Sv{iut-Fs|HUgwa~_=I5(-M@SVl7a5I8BxOf zM$zue+cP1(K1afWDvui3AkCqS4r4GW3k__t{OdY%8h7(azPpHb^GG0i_G>F%wWQ+l z;_oe>I=g7c$dj>8qiXo`g6uHk&2{fQ&f0aKeWkArI(?$0)$T>v)>oovdE|6Dz>J^q zTXG45BY-EJc}x0jie#m3<~w+X_LX_}4A-(kF^oY%`6P)vTkDy&3FGnCU= z8Q6#`YhU%Gqvp%gMO(rYmwLLzM1+Uc%c->x=msz@*?NBjs)9)pzy==X7=}#C_-=li zt0_VHjBT3uyhh~+7QfgvVt@(TEjV06Cd?<(oq!Ss69Mv$>t1NpLzY~gvH3BCQA7DW zAJbTR`+7YH@k+lD@2b z&jxOLzaGi#Ek+W|RIypF3f8HPM3*jH5scEnEFMW)@QRw0tr=sQyX>EQ{*5OUl)}P0gTdbyMth%h711 zb|m|~g|x8tXisUrPh^VYZ`*DoX%hkRA<7fX&RRmj4^9sJL!&rCeADzPLy(di9Y}44 zWglk%O<4hvmF#)6FdMZe#BXS0d85cbPKo5&RkyUP%u+pj{eo6w_d(NTQPyaK71Os> zZg|?V;Gwn@-m6i?Upn=q>M!cG)2>Yg%mRFEqrcsFRU>^S(EPD`equV^$Vc|kKWgp4 zI6sfb>`DY{UE%#&`DbqrcSCnKv3s$jnY`z|0kufa{MnS zty^8&dXoXg_oWu$vO4}Kt1uIG1c7@-BOiVf$P2>jti`qJaiBry=R1~!JBFU>Vni&Q zNSf>A#@opt;($`S8a|G{hQ60C^1Wc(7iuq#?lDoM#vwgwQ1$s-X3L!RrpEQRzFqAO z0t@_0occ>#?}U@feAgyKS%v4KbP$L4?1gR#Y}6^hq|jwg#4;KvRek5qZ;0hUDGGr6 znzEu)@FA&GBx)MTu;vgd$X85{j0a1a0YNA0YnBZw!>o)#q>YYl+vYww)x(mqEw!{3 z-yti)AduT7u46;uS`|n#i#^dYzMGjw*WcZKoZiu)_P?|y5cnWT;BueTIx|6vQPi#c zM+21F$t0;{>!YbtJ&W9LkVY+;=pNXL((p}V7bkKZF^W6#g;Z)z>%m{^C8MS;Be}KSH6U3S?|ZV%fAvv3spijEsp$e%&WL4 zUQw7Y(K=+{+T96{Fryp-s~HLv9Q3`R+pTbL1D;=bgz`s2@4|1XBkSk(*xJ7_%;kVR zZq9-^d5Z(?E#?RW8{rdm&$!95+YgqC9~=wxhM6!<>1`d90(%QEqm?sK7oC2zbNV^X zEwdm$e-L0t&gIhsKaAik9Mjsr3VOZX6pev9_#;?@^CBGx?cJkvCFP&I*l7&K0#aAkV%*J;^LTU%o@dV7(T5B(BIhF|i8URS1-*3;!{~ zT3ibvxZST#(hWqD|8%BP*y0$%zSe)ea~t(Nx6?1t{jRfoi7W_N zzOI(h8vn_HNKO3-ig>5t;6=EpS(A&5WTE7Y4;0b9P?=a0(SBEvYD|_(U;?I`5lc+Q zl~63G_9g(0Xo*y&EM^cC8xR1KjfBaW7$X{=0YF_KS;sUG8aeQw_XUP@?-&1fMc|)* z_CJaMCo|)Jb!wP7*#1WiVCMKoUHEs0i2re^$VkA%z`@S;-_H`ay`kMzmY2`;PN%$> z(wUPy_|urvgF%Ft0w8b1;RqoE1dt%&vOvaQJmLX7(!e3gM50!}@HMJ83WT(qvYJOtt@=-Eh2*ZeyztTio_}tBwr@Ij-N>^zy=uFkcdTohpEs-_AA zhMz%X(hquS%?~6_NzP+v6 z4Pjni9{GE-(@U#E+kE>4YXvlzUI=3^_fvAPK&pnhN~m1Px{1ptgK&E+eF_t;a{Ah# zV+-$w1OH|g#>6ykc08Mb(O$_{u+HH=Sz&(3a?yM$i#7Ap?@n7pndK`_GPNZ3Y0$QE76 zo&IMzC*rbk-rrcLpA2{~974RqorzsC-=5>=^u+Q>jmD<}Ygb1An2GxevBukAv;9aG zBaBB0zxP%%q+y3`hH5kKI&gZqF~Wss_hLPgstj|?cF4WP1|8oQ#zz$*U!;m|?_L2? zik=Gu;|Nf>8Uk#HR}Jmo6=!&V*ydxsV~uIaW(f=s*9Gllo?o%faH?XXNenBIL0v#Q z6Fi~~L+ytrXgRKFNr3DLQ`j3G=*M$^`onZ3VHg6Y$u^{$nM8*ud?sZuIS0)UaWy%e z*Z{H1ao)0GK3`50whPYsc1(_L7U0S?F;z+eu$>eRegx&9ps_E8J5s2ve$!W$*JyPV zIo%aRh8&v~Lra}N4?mpDr1M&zM4vB~$kD-|9m!8jW9DX~jy-gnw@`(D!_2^*j#R+^haMA2)U?IGnkvDZEr=lKO4*R3_ z;(IAnm{`3vSiGgS^fxdpK!iPZfChllc6>@6#mmKs@}?HF2V4usbHiH%jMHYigcD)~ z@zB;Z(JbIpYpSWMT|9pbe@aFboChdvh@j087FK{Nvf?J%|CS{0d=pNJh23!oCjK+% zVL2UeGM3Rnf!*H|WV61v_flBWbrvjO={)fKg4=eVdR8Q0smUUb*X-oslru3;Nmwy; z0`$4}8{{|g>(OC-yaZ0c#7Pf{6XOI;_`p3^@2BH$;#zR$5_<5c4av_DeqrUdhe zmw0e_Hq_RCzzQQTMW+Lpj$W@NoDF!bR>d?K0O>NmQ#SE2STbMJ1rrYLC6)_LT=^<+ z;$#N&%5fF!kLZGMs*C(kJADpDAV)H%KBoZSh~t#eD4<7wY%PE70q`(&5Yd-FP+t@X zQKAJ)=op#)UG-ceMiL8X{;gyv78Gz@MyimQTqC2V>1B(1g>-|~OcS2ypxmX-q9OKz zs?UZ&7n48`hpM6W0sL8Cquiir>+z8MVDIt^jRDf zk(37PLQ6#RDqa{_uCv@JTt!`NNp%H`>i6>MbT~4Sb$pnz$!}c-nYhXq1vUVU^6$nXF* zKB}dqaqFaY;dUHzuwxG$HR_QirL+(mB3~Oh6%#YD$%n1-sTjJVCzIxbz!T93GhYSd zvMStUaQGo`5{b70iUd`SG(#t4Z36oGN1tC6aF7l@wm#d)IPamWwPjwxfM$YiB0R|Li81W^|QWKa!YEK6^W z()TsHo#5^ACXx{rDq!usr^KUNVti`u$x3!?_dH^m%N1}Kz{q4B%N|yt?ohme z1hSy(m}cgMZ5v>0jRl@WDDuI3A{w2pw3Kkw)5Ng{ z3WpudF&eYX>El!B8k*KjgH!1x3S%e*`bp5tLeGu}2c>8+vy=v>aZ_gGMmuWH$ z-6&wVgqzDym$(W;5EhvbH=`b8NP?*yx@pkG#&dMarf~R|QPTboVebH4Nw9VeXC}67 zXJVc>nb@{%+qSJqGO_JUY}>YNW}48SND-YshR=O`MuCvN0S)-+utwIqwfsNCFmnlWXQ|XjNPcw5210pU4Qs zT25K&2TO?kQH!TmU?OSZUZugnW=i6_DDT*DYN zwF8an!I3T^7B3i|ScN{1)Wxs9tf?b*UNYFU;Wajia?WZWoj6&B5vWMbL9H*Pl(Qfa zS5_mfT>uHW4IS-%XMi_6SM^Yf8P3>pXk0(Z;#f~)nuo7dUn)mKr3Hesi5;uZYgTt} z&rwAH%vHPaStoT0ga8FBMFg zE>~Huc%Ym%pI2AgwU4XTOh(6Q)4lfZfo9UV?XjafyW!75H*f2L(5ATSQKQRv;Gcum zgSvOy#;CY>He)-Wlt(-_vV`rZ%fIB#2LCJ9`nd=%$*mhShY1n5UVjQvdB zZzt&e4E1wTxGIB;$%+cJbwJ(x$U>+xEq8^uBzYfr8$27nuHFMA{@l}6Dts=_(q(|P zI;y8^>fE2bc>Qn@TJ7jIkffDBYy+$LE(AzJVv+Z{dk+z@g{pOW2Th>^nCcKil%fSw*OZ6 zkt9xEoiSrxhl|Nh7D$G!gn&Iz2bfScEvpH#T5l{quRkxBvM{76q@32Db2!GKT`na) zL1ZaL&9E$y-9$Q#ZK%Fx)P!qL9tIunrN^j(L!%HiLVu>5&>p7&fF`rbIb~DUPTs!ZK!x)Z-dGWCKPfm;Tkq z`Ml$1oV!Soh{IZ-4kMYWdw3;td=F+JSS7-qr>AC&!&lHQ%eHVd-pvRO6XgsrJK>TR2rax!aDRDp4)4G+vV#9 z^xc@pZp?fHuB6)$N%_W0_mo5tdc$O;5#HtxZj~m3SsE9KBD9pTDr&0V;IK(HoAE=B^j2Z4Bwp)wE?PboAK`x`C8FbfwMCT7G<7u1Ne{N^*QgTCKd3=o1Z$D5h%pkS_8f0&d3RuR`4MAsT8|&7F!(toCdq+I>3U~0$)%!$6LEd&i ztWS>)^m?%Eg0YqD_3QdJ`E9V<3{Kj%Itw_^#R1_UI~KTy^qiRmGOAW7yOq3OS~IyG zNsOM-DD+ovgDF6=4^JE_8A}AEPzm;%n}yoEdfSgY&XCS_=cjdP<8A9Du?1s@>EOY1vb2%H=--Qa_6H~D=(Ke7HKLi)i^80 zQkq)s8=+jSC!MNsIPg9NXcOBR+%7MyR4nFJnM*5DT;QLWeg_THCfqIEKWdO=Yr$=$ z-2sjK6u{Y|+WuA`Z9)Rb8$X^Y?a5WP0HmVoO^>mJ8!MYIV_*vfxOGB@8MK01`q5`HV>Q+okEKzgey_P=*?7yc$>|B0H90aBdk zse*|^%LYo{3o!09qeCU_IBMtvOh=ad+f!ZgduH8JtmB1!Kd5TPG91!gjp4oc%@izTjUrYegBO9R_ z&sH&0jLHp8B3+=UJ(vY!GxoIxh5(~r29hb01BSuKX80y4*MtH|8Yx{GTW)SVGi5tC zh2U^1TW4{tpOUMyKyZR@6qTU@Af(|~3sC@p7gJH6t)MOHU7+Bw1?O0TvB*o!fs4R2 z&InXbki*M`bAUOazp=&xl99x@HUU4%hkCUzK?^CX{FA4N73hpmFqkirzvDvy`}mU# zWAa^d)+_it;dgBA3|)Recq$LE%@l1h83DKSO~sXOuoBf-B&=0{TsI?B)2O+Ls&pe1 zRz(yG+cbcp*iIIEEM#5w7_15V&YJBoxM;0<71qtVx|we~m~PE>4Hg!qD0F8`Fh6GP zib!Z(qQ<3J4{)AWv@aC~SBJd&JO4?JmHu^|ek&M@6~cOJok5LRyNCAcc1)4lrzpom zJoTL%t^ryT<(XOFnlC)XNGW{}_<|KYpRw>n+^i=5(!;nJ?%8csKynt2>riv1Q7^#* z(Lt+DF`gbFLbKOLk5TJJpcum@DUioTjbe|{JxRUlKr%M@cW?(t**t4FWkoXIsl|qk zzcn%h83i*RS3Q3`vWQse(>&Q{;xQ&6K$_%sflAlC-OcU&$sr;b&(OQzsmIAW2J~#E zu-W$quKk&c%x;_;f1y;8HGQ*^b&QHhB^oTp#CYw55s?MwZjefw?G&`w_aj=oI-gy* z^fSfLZu@V_vwj>_v&%1UUQ`oa4$Ge_UcK$8$V?K2<8U(VH!t1yOm+6Wuf~@pAHH|^ zj~q0ocxx}Ogmg2^@VnbFv4xs5REZXI=4FHDfOy||(|ff5<=#sqO0JG1B`rDiXTXZ7tam83;6) zR5=nkX5AO(-I{*_1jY-NUD;{CUasUol{dG$)fNQC{8_&I-YJi{g_C!Zwl!52ELSAn*rffnzF31Gzr|NDMe3 zRv>u5loUBtfnf()q6gCw{ZDLje_5QqJSb*6f%#MvjZ#btoZXb7TusZ9ulRqSrSBx$ zC?T}ruV#hBh#xXFyDVg&x^=%gmMXgiXBFxF;3q@KYeA_BT@LHB(DAk zp3^fhJtY{;kcmS1$b)2xWJm!Jo`mocmSWBixSkMnv^}}?^dSKi2kjp6uVDwK+E9QC zjFn(IB%@@w9`A)k)1tb!W9P3HoG#4ImzS27*!=!wB*S}qj~>6;`K}WkZ=>U!*fm`A zd(t3UBP(VfMuI-tBgPACvfZ-2mBl%Pcsze%!uXjPKx5LjE-gt0Ux|`Z3QdhX&ygNp zfuyAD@6CkA%Py;y09@ptO90-(?rTH{E}9#@FUM-2MTGf_%VnoKi#U}?21{VKFoJZ= z{)&2E$~6&fw_6}@uH=?LAd^ixO)|7R;i67Si&V5o`TcDByj7(>itf*DVK-{r2zyWg z%Nf(m^L8sPDXM})SB>4FCg^K`4%+?a_;0dhwf>feBo@D=W&C)DI0_58W%7E&h<>vv z(~MnDZIz@WQmlX=r$mq=jZGgrNTr6*}n+wYg7PY&TUU=UF3z*|=haGZoSlY0;S}2&Zf)r!7RT zygQ7qZ&oKWp=v^k(jg0~H0JCJ>NGQ;i=URQ@r6;P?I7}|$&F2oc zCp+FA(!Z1x?=r!?ADZCdxKI+zZifabWoZf1_IUI%w8k;H12N_;2_%M2CODG&vFdzB zcj|i7ER!=bH5)CR`n#!@5@wsiN%!>Uii;T3iOlzbwb-eq3cu<+9kjWxscE)1$r0D|Gp( z{RT4Ou{V*on{g@Q0z#aQo69Qt4*fXr&gEDU$az>!c}G%i`C8I0PlsK{q%N&qn%qFY z4X=Wj8ml4Oq(MD*?+#0O7!VFddRpIaFN3BJ=ix_Ed%~iPps(iiESVWEnl&h+n2@p2 zO&Y_lWS5g;c(xU@X#hpN+5i*oqHAOg?L+-7F_l$j%FNMUEI+>y0PQx#AH?{lP% zGrCN%+0&qE`)G|&s1YfgGOybeKpg;_6pnpZhY=PLD3l^mHpRgZE6Wg=CuEp|vSKuf(j;x*KrHUYYMzjWEX>Q?X0?gnz_Q1me1}=TL8PSDR?@Mv zfo=Uf%rXXJCcm@fc7}O;XCK!4WD2%s>+yMo4a|^!7N1Pvx)uZ2AqM7e0GIKq(=t=O z@uvFCt(>>4%d8kC++?g7RXn?dLH?Tu<^=Z2d+W_NXqo&?b~nVpJM=}b6Tihw=}I8y zb2F0%y?}VHUs{3-XQ;+aZ*IZ1_PARLl6T{7cUR=oUVqnke(Nf20oIn3nYXRcTum#n zU4NRl<3pU|kXG%3k%d(t8mR=LF%ti`Re`%{+{kjwGT~g2{x)s{nO4-#sBYti(k>vL zTxpZRG&?Imok#&g_9d)RE`P{GR81<@93p<31b)|f9{d)Tmu#jhP)LO^z9oMo6UvY0 zY}&9FErGtRo4VvwP!@+2BKH#X{3F^2 zwf(51J3sx$#E{Ur`?!RH5!IGu+*ZFc#y!b`l36#HH@TJ$kd0HGdr$H}@dE51Tc7<} zREL|k=UtN^s5VRVkpY%Bmb~89eb2>78?D6aWu=Gm88XVem$=SuFND6jZo;DmY70@~ z(so!ufK6C_G^1W=1sZB>t!Oh29o_o?^q{T8WH)haOx!w2cO@b9NgZi`(i)BZ**h#2 zZNTbk7h=oi%}xBVS$!y4lKTmlFdWw|au+eVw}vLKo90LSOKs9R_N9BCsnkH*h$Qyq zm=;54y@2=c1ooSX83~7GV@ABS9)}gl9l63HjS3!_LL%0|B1wh9AfbYpIKEpk>Tz;A zQ|P^n_-9?(wG3!Zha3)c|36!!pa!%V!$B(c+orU-r##_PNzhS$WHX?N|6I{pt&4b& zZr4OJ>5td^W-WsGg~PzET>c{%ADfCj+;|=I#35|!pl6DllulT__Ur+LGlN+bvz{QU zYNS~%fwTlKL_4`BYuvfV?p7rbj+n9Zr-abN5H`gU@~*F` zz>=FN9-CMeF5EfFhOG9_x85?e^UexOgY~xlj<$Ig-b&tkxo6zV3Ug!cr*xKxi|6?| z6-UiOdv552@rl6;8=otan3M>HRcSApefzzDs68mO1bLanzKf~+$8anbgJV?8L((_G z3H(Qpu`UM4s;)5}BBi#6gY?U`hiJ#zHha68YSU}}gDzW8=3XdOD8%E#8NSqSWXBfA z#djolLJJtMOtHa(u`<$J+$oT5$6&y6YJ*VsIyMe zfI1b!d4tSf(D08-$Ek!iN_E3MLoy7=%X!DO0x9D;R7^_RU6G$`*V7uDpZ5{3)@qsP zp?fwPg%+z7( zad~=qU^&Ys(@;+-Vk$AeJy05vr}2#pOYut`Gy63kOpM)PO;1*b)PDDi6|QKUgn@jB zZP(8sgLibuX+SZT@MFQ?tD;Yy81EJM`VNT%49tb`OrSGXr=A>RbSNszV`l_c8O4hi z-5Ke?XNzrMA!Qc_1epyLDLD^B9oWe zmX-Z&sqxuY^~DOw;kEhlj-nD~3kwm$@ks|tVt%iS_PIIUpol!~|vpW@J*|XP9O%>3g|Sp*fDJj@gYxTU0nGxGCOI7KU5qd|$U+ ziY1g8<*Exy%9Yc#EZ`!JS71oxS1B0JmNad~=)#{X(TBcw;OIMVxING%HSLA%GLDcZ zvrud#D{LHozAcE50i*Dky96!vd)fAr&w+RH6+5xbmpUedNz|zVu_Y;N-{+g;8wCm} zcw<$QL`fLbm)0`}&7L;~3b8VWTi5a9qs>Esy6;0h0A9$(piZKhg0#1cs@ksosoi$M zsFdm%FeekKeHf+=rE18#KaL5hpQQ*pF!;;yGadSq!8%?WkdG+Ba%EnQ>FHURuo)-% z-_?%0n`THyt>kYrdk>qd3c6i!3daQ0T8FXpI0jGZ)msCcT-zuG@|wlLIg)mGIM?(- z7&&Pbz2%0p>yhK9DRLtiUN)rPFr7VP*!Iwu20A1QB9FUKJixrTX^aVqh7HS5ED?gd zaO(y3dTo^Me`|q%=^M@Zbe#F|ckn6EWj6k#IuXLHr;h#Y-Kg!3-dg~l-*hPHDuM+i zDb157t10DR>k!17{!J0Bbv?OoaaFN=Hi!W+82v%mAth~?b(d%?BW;>;+&Gd|5<|Mc zFHTBgc3>x3bt&h`=W&Tm{5kK`+hB(bYlBQFa=M+f z&2}?ZfBkFeh0ft7G_=vZ{(9A%#9y~)8iSx_(A#~;d)DQK5i#x z*9hB&){s_54hN_TGwDPN;d~Hh5s9%HznD}4$`fcCj2ibG2@~c0H^5UF`4~cU{`>itYrHz&ucvpFO}bUm8X$)vLgqTvI<8j6DcM zUp)m|+eN4K+0v2N`tful@i4BtFyY%y{ZzPYr+_)Rr}q7XIe;4{`Wlh(*iQVo@rti~ zxBU3wukZ-!$sd+wUNi2ye8GFdti@QG75%-Kojit7-i*+>AEezdNk3*pgmq8u_7H<# zm!NMXG8D;NHXUcL2h-6Yv_+(Er}KF6c-BXfllSX!JlJ;~Lk#x7t$G3M1NpVGIpfbP z14(F4?esmx<6sTl=Cg?U+th8Kle!W8p|Z)s(@=Sc%<5jjj9=>~vS+~RtmwPJq_K~z zY$w3*m$zuJ+e`zldS3H>rmfSX|J0ZSOYJVwBn??14)MD1LbbQQj{ zirOUrXVv#JJQnH}!kyLmT@&qrD_ph8U)k9P!5Vw%13Hy;MFn#5v~d$U?TuK*e> zZ-l#r(`VZ6QMh2nypQUYC# zDFL{5<8?EA*v7-Glg^~t8WG(loiaJ&cd!kQ$Jo|`#~P*#TTtOIpjI4dM|8HXqO&Tq z`c>I1+BoYKt+A{*Q$cj;+iB<+$oEVItN9i7*`LQ6_SrUJbgx;F62?JobW;w>7e#++ z$pfiE)vX#ZBhi%e60(p>x{DPP9F_HENM%aEN-Oc@l2-ZVVUqgz8z>4j3l#T_TT7gI zMCVLU{XASeSD{ZIO#?^zTaSE+dDbw7{8G|8!G}_}A4+Jpd$KZ?2j4qW?RLUR zK18|&rV_N`E-U+;-Q(~Xzhi99g%$o%;DcJk=SgJsF7 z_~A!D$9T%D!d1+NUr?Rb_*hj4{`v$*Oy^Zd(_YGxr8UKhPu*yj;AYZ8>#@fxe%;{o zpd-ny8fuN9yJ(@fP;ISMhxfGU?Q*Psder=}q&-)eaVEwj$0z4CwHHQrYWP@Qr?>7L zGd8LZsEXD8(yq`y}T@k8h);+ilWIv{cNeG7|cDv3iT^k29ZpZdGuJjX*W< zn|zhuG+2b65Fd6=XWUfrK6s|E`g`he6I zbY(Rh2bL@vjT3&6cAd;7g-52IfB0KB@t9AUC_vybrEBMr4}60Xzu1+U+t4`Lh_c#m zcT52&G;=l<@#dzxB6A1B%^ob7#fo^0(Aj4daK5L)<^q-kVvyGk^ji7@8c{-OuUpSo zjL=`eZxC&}F=LSsN6BP3U(Br_81-X>q`x$Ks|J7Y4v zKHU0Q9X;9y?GJek+UGzJk`r?$-qio#5Kl?U|5S!Ro>f!um84G#9Vu%nyDQZtOy?P9 z8J_=|pUiJi9T*)ZrrD%2&qV(yCcXxfk{>>fqJ)#mE!i_k9o||SDCC(Er`c?G^SXRU zz!#0EI*PRPJPk70#Hv(lXw#prRbuIg2klv7za0g6xSeSCYwK0v_JXOzV&fuwC0_@R z=5qR&&swv6=k};t>5qQhSfFXGyu2vrJU4-0cdWYoBfOhIhpBVsvQM{uQi1V4U(F28 za$yo$CgxwAwL+H^^})~a(FqdieNb$y&pao=k*b5l>p}d(#5mrwTxM`I=>8arzi@j_ z@L5A&rILtiA%*fo_bE|~p9Wf(Sx6W{taapYb zcfR!^o?JcV6pM|;AC7}}LN9|eg$3uVC>YjEqQ~3=@&;wsw3Sh>kdHMbUG;;GV{2yK zW1fIK_Fd3XsbtoQ$EDyiudY`BiSVPLu{?X%NawEq3JE;i5Mh0!LIaIH< zF@z{0I_6R$4ir9ieFj(|hO!2`P!Z5}t%Ir9dTC7MuEP zpI4H9KP<#oj*s7Zq^onJYq&poe)JqbpSDGImKGGL1WW#)pr%gc3g>6IF^WvIK5zF7 zygUtKZgLcK2V84a7#weVr~~RB9~$lms@oHE)MPcf76o6_IbNScO_(}~mTOuX8rn;O zzNf3ZzYvA4%`XNE+MHdQU0PY&#G9Pbh*pvYQ8jqcXsACG#C3EeZ&Xgii_%R5C03YT zo}Fa&*;rZFnC~_TyFVW!Z#=+eE;V~&^ESfAR3K_fn3yc0;n+)zlH|8G6csd8v^xNA zO0XlJjz2aCz3d6O)Zg9LvkC!hHiC0cj|Q|)<&-s6Usr5*qLZ~dN{y&G+scS%o=5#L zehj56?@@1VOs~&21kI#wuFlQQ%!DNf{USbHWo>%gHnocTEsAUpv%Ih|C&BI?xz$RX z`E2ygt+h@gdVCTjA&eN;g~9643b5%;Gdi0{PBwNECh?FMH9iYOwz)W2A*m{AC*Zy*l|N-{-wKL)*8pVFN*V} z)zn9R=V0Hx_k=Pu@Nm(w;lJ@P4ywhC?LWUSF*Vni0PA|c-H=%yYuvMo+U*V^A(bED zm|&G3qTfK6Nd5;R)A#X5eyrp`gowg&kPr5d@!4bosOm+x_Kizm^r{fYb@SqMvJdYH z3N=lPf*hZROwSC_*=8Uyb+`9v4U3cJ?>Tu<&R?rYftCXuw_vBWY~sZx%XY6AMi zE_#1xCXS=>vnm_V8TP%y8t!+-&`0-YTI3~4R{0M##kcL-QyP^F zyNc`Zo5&v?{Kz6oJY^7b(Y3(ux8i>?ep^M`biy4#kl zkfb?WQtGR(!9Dd#{H;AQ`n@F>c1b&miJH(3|DBWfb~O}UeMq_uZl|9g+me7bFh~rQ zIX;O#YYg<;<@awC-LK!vM<2hZN`N8Q(DoeadBOFVj?=5U9UJZ=(=N zBV2a7a^s&SR8R7~fw>P)=?9Lu-0kIjzl7d>v=2<#Zl~7P33O^h{x)|*cYO57BMX6Q zYl#5|2aE}(b%hh_p!;-sT+-npP^A1pdN(0%4`s%!4qgcSIS-w|AHEv&WcY+zSqvksvb zfJ@*PnLJJ=7nnNShal2aDDC#4aE6Comh>Bco$K2e$JCljo$Gg2frT8GuBbUVptd^d zbv3yXUNR~B%6@5-r4SPi1a$;O7WK5xpw(6~``MAAGgcY`1tAep1q>h(8!!W~T(H!L ze~^vgVN+~FoUzyE0_du4LfsABj1;wI5rR(hmK$!Gs>^vwTX@8k7g*j6P$JjO;PhPJnRx!nPOP3wEPfmF3QwbyT~F2*$Q#HTE- zN-$jYi0jZ1WTS7lyACp5;J77fADs*2&4Rn-t8~0nZ_v3UhX{1FXjAKfKR+JS{J1@; zc5GBt^6tCs{MN*+@%(S-EdL~_{H3!nFtYs@QX3Q7Ujp0T<6nWb6y0o%=!9*o9R-aX z4D8Kp9c{kiYJFv;^{kBO`1$_+s3gtwjqLRt&1|d{jO@*fY4~j{4Wa%<-!gF^VE#&c z73b%-an+zVq7yNpJ+) z8ByGyaB6#(gH4qt!oP&3YDy^aDJwlamT(93hJ6A{IA4S^Lu%kZ0P6yW^TRs9470-b z0!{fzT&)(ICT?TgJk}ANcmucf+9uDFPZy1WA?_=8v%@~aHZP92T6O%Epy!|ptH*Z0+2zYRt@eIEzUlHwW)#aZt^08N?j=dRbw zEfiP{8<**0G4DQ?Bd7|}hZlLU42pwOxfE8&%MgojmO*7mXG2uBV&1?ihQO6+RBt`+ zSZzoEP}kl%$}1|hi4`afyCplFolR8}=#4raPsi?Gob>OwYs~Ms=H@T0Dqrc7qrI3q z=64Id7fx8|5BaPQ>C}&;xgsCZ>$@uLuDhxS-51U~zS&K}fvHZxDwuZ*&nmI^!6u9s z?7QWu&92RmDc&6eV$IiN#F7B08q4dGWy3z z;Ma37`rF<9m#%MS@8Bq4s%QVVM@#DcN1BBlicZDM(9zUEgO!z)fQ1D>z{JEv!2C5R z82=;x_t@V&3q3snBMUtND-oF=tDb+9|7-JKwtuwuEC095mo3!aw*2++w;liR z^>yy={$J^T*?x`Ge>&3o=P3Qpo5)vF|3Q<5qEoa{vNkiYF*N!|>%UlY|84ZL|KF0b zOzi(JN!kC8T*t(~O#ffGb?3^fHpD8pAGn*f(#pF?VVO(F9xV@%oQ<m606d8fwBdbKkf~3m+=3yYY zNH%R#=pb~cmd_K{d$kgMI=<)Zhp%3TE?G>2vsb^+aPv}rhz|45m)|o8KfNV~;?k9~ zK8=)|>TPxUj+W1h&##&^eTt)C3>YO*Vp}-)JRJ}V=CIa$?A98g*)Ui3h4v)GPNwkX zU#>;|{J_~`+Be@_aeB~(xLJ*jQC#ycnKkOVf~OZOJ!n`N-VzWcpeAq_ELYFsiuHoM zxvcgqJXpXE6by_05U5<_DqG;adUOemzURa1>lZDE2@e%YeEXa{y@P$BY!@(V;d8^v zEfOsP(aMp?pC*Y?i1Ze_oW49(xzl=KZ0FO-wwYZeIeJaZpPZ#Sqv60^gXG2jAoLK>y|iu*k3Xq^9+6 zS)UYQH0Tp##78I8`ZZ9$RCuj-r_2Mzy}LObb0bGX)&m@zsy7Ljge)KkTx<4xaUWKO-Upv3l=nW%B!)zIcVk z;!`xf=I($cEfzAhO}(tFx`kO=lC@bOvuXO!4_fyP0&MXBNSDDyBK87fvVnt3TR%e06ma_hHo2jUEwk}_nVqpwi3_1 z%jr`gecUcN(*ucW@QHxx7Q2{mKrXK*YpBI-@D zr_iq3mO9)z)SbNtxh=X`*lm=I(-bQhpWiigJPn+y@T_t_$hhx?x?fqNdH8r=@hQ%4 ztqEA2$8a5YIGcn=U6ibOXoG(D$s6v%Hv?<-Qe>P@x84*1viFr1;~09It%~rjJq>B^ zI82ZuCppk?s=j<;gZS{^f&2D3H>h`F+<9dO@@A7g%>UTEMg7_}82Q4F|H}4LYnv7y zyoBy8J4T?Zmj`aP_sPU`7vgEYE{NPs-}UMHBnvK&4LVm-hn$V-JckTtQ1?W^u_B{+N*LlZ)mTBe-GlprOQPnN@m=3~4`)WpRQCoXldO09h6 zw0iSYuU67LL&=>bPbRVKXiVF`8d$|MayTx$3^&zLyk4$V zs|oj9so=!Uu7)@wcQX{VfCa?Pp(_d=K&)w+x|VW;^D!wUI2efQTso|Z_Q>`{?m zc7h6dxrDJS)QWqfE#3=VHh?5ddg((1Wp;&N!{ zgSVZb?BMl4^?~d{V2%z@_>oNhbc0MY`B3_*lq=uU>Y>(r7b9=j{48c3*@W%f52i5K z)q@M?l2$_c)fD9xlt|!jn2D6GoF4THKCAU46q^BHIHbznkAWZCUWtqMT9;f7kI33694RZAH}f z#g{lo*XU2mSd$YIZo z!*E%?6j-HGVJEp_Zg5~9BYNu8!Pqp)@)WDCnk{FGXK2J|{k{1s-iALHoX*Wl%{S#1 zemlyje<-5kTka>&MwIQpBdq`PN%;?i&kFcY)e0tdrhfx={`1-Vp8#L*p8#LR*2r4H z%}U?K^54RG23GbjSkJ)t1Q2!4M@IPR| z{~i06k&)ql1NE_dGJOOHL6>jg>=$@|`iyik<8d_HQBWzXF#1*VnlJA4vP}F803#vR|)e|AAwf04xMd zEGz{72HyRnkIDZxc*p+tqWzES_;0im*#CF!1OV%Q*G`ykv(Ww0PM~jF{4-|A*u<3Z z$C#1Hgb}hS7CH?iRMQ?p3<5;Y*B29k(T{{gd?*&=N?P&iyL#@{`of(2X;2ZIKi8bP zltwkWMSqd0#XJ_3^6P?xdC9$;{Y^#P<70ioE}y796kqw zp7@PwjRh784ij?6aQs*`+&8u+w@NjX7skP{q8MMYy$2J@I;09A&lrR+xL?{J+Wx?# z`TN8LTnp=18ZTP|3W4>;1a0_wzMqLP-x9D$C#!L)rj3sn@4@nhsa4C>0Ij{p_9hh%Ph>%=V`R%I}_hF zpSCYaoaF~6iH(u+I(K=_^mfSL5g1=;;;~(&>@7U{}4wyrZ&cvok-sw`BOSR zL=U)eE#kI~iDmZ>IP1w*7}|Ftr%bYJS*0E=yvgY z=iM_9%%)o!;s)kn%aki2=yaTqXI@AnPSNHU6zR&<36v(XSY0MaQR_^`sk7wdH5J*s zM)t&>-MZI&H;w8bj1%OtN=B#5U89YBCDC^gD#GfXa|;^TRgo<#sWNC6npeu8ixxpF z^6YBVB6kDQQOe8mvUMFaBg3!7o=l~IMRt+&8>PAu6(xTJLHe_$j48E1gkP(a2JAqp zw&o(vx+sGa>%SrY>PRg7BgSmdCrRMTa#Wl+9nNySf_v^^k;^8`mOH75p}F)+jV%*r zc+NmRrsOG6_Y6WZ4W7AMvgFSmgaBitcoW;~uY+V)S0Mwb26BW@6lonCR?sptL^aX$ zNDH)qhI+9Y0Zfi%a=bL2=ErE{@5>l8^z|Sbzvyft>&8`G=rrnFE-UOL_&m+4Ie6}W z)zN0SS$Mivw~%RP*Q<9j&da>3rXU>N24y!tUV;=MzwPZ_#~y$PCeXcQQVg@z(Zf+; z1CIOON#F>6f1RS4z`^~PjkQUn^Z@J%kkeeo|!15zw!nYfv7S zgU~>$7N`#e%^1szz91LNFOkx7r*BZrf&tkWVfSOLcua~AzQEoeBk>@t!5kw+f*376 z45XSZO)eC(ismXGnYAPIYpS9JvOglBe`h07_r0`Te79RL07RsNO&SF)*;s>15sdC?gcIpv8uQqPBI(fc1lEU1fhlV@LsO5YS?c!d&{5Vu*~t1f;KWAIqE zj?Z#Bm*kRe_?+z;cJ+5_K(rAzPlBgA5ga81S;kv0Sm{vb1+E>RB@#}5b5`g7Sig@$aK3DJT4nk zHqK2ydnc=obs(}v4);?gsZEcbY*eq8i>lv7sj$p9<2T8|A1-mcVq)@sTM%`|KVV`+ z&zsz|{ni7mBTU$C@Kor_H@de}P5|=$#)G_N_RSfxTGLnj+t~L=sH;jKyKnm6MZY6_ zJAj#a^97Ro)`Ox>^s^Jl>wD;T7-)Kuu49sDsF(u1@cN|t<1DMyMyIkY*dIVaaU-%u z#a;aTIS5F7VvxBreU(83lC zx64hnqj8u$dH8h=XFujX9fmSz)LDJmIeV=eJ-n=uqEpwsSq*g8tpnyyAVkXj{d5o+ z*ucCH9=m=!J)f4nJFpj>jsZA*dMn^@~$# z_Df+eppz`HCaUVUj)UjOx3qMxi6z&j5l-ueXhw0%0w9~+S^<+>s`5AMEH>?Lr|3BX z9aZhn7aceo^%q_Qmh+Pj=w2{y=vkvyRNLEw@VH(-)gDWVbv9xg8sbppbA-x31G+D! zR6~WT>B%;F7y*3r!NmS*7DLQ|#Ay#?;7_;&?7s0@Ie#LeF~wqb8z}Cos+AK<^I)YX z=1pm&J+eKMLD9R}@%hKzK~oYpv|p_Lm>)`4pB!{Brf`$m+_MRnt_1~ID?6fF(qao+?skm)o{mrXTX~~h|Gr=puDk+ zxvpEkksxo`N_s0p)@}mwU0PRPI+TO(3}hHGKsuCD&(~Op971}nn_iMI^jGvB9&is+kA!soQmuG1)9QoEj zfoYFi__Cn&X+v9n+WV;+O%S7?6lNnq{U7%JJD$q_j~~Zds1%}-Eh?3HI5tUER<_LS zJ+ntBi4c`d%BGN=h|GqWT_GzXdt_wxyUumcb&f;5dVhYO?;qdq?fvd`IIi?GdZt*G!K|ii|@5i^(3~Q%x6bmL1BG!59(MBIwDRcN7arho>&w1+qHj<* zi+%fJ(%h2Gm%ht^+_-Fb#1rRyy8I&2vsfz*`rNSoh(qUglkD*iK`cYOrPxn6j2Dcj z1Yh9S+;}jw7oSnow=%JoEQB@1?E#_4IVT<>cv95^>Ngyr#~4mtmG@3=IVp(yr7#K4 zRhOQ=^>XJ&S-0T@=EA{OmWwr`SJSV#{$N`4v*;`<6HU2#qyOVW*JJOrNw2ayi{&&s z=@Ywh+a7En3p1{MY8NuJ3xE00W97<=XDW}(7vNF02>RgB@I3vrJLNXB-4q=~aolY> zyQ!OamY>Socp{$N7U!}4-rKJXuXO;fF?gBk=HsV`Xq) z6nWoc1#z68p8eh%jrU=s_YfohW&bQ262`YRbiqmyPw^^vR5EVsss+gBnV6d;O<(ma zk7{H4t? z^bF@MsRmRF!xOcaLKw`#UeL)$W2veNhW*?}m7wQVqRmo7NBD*f_)p8c2}$npJhhLE z(qf|&-pQ3^c6YDf)TQbPA5krKfBgzemf@_?{kLjz#ZUa|22`HU>c3DvweO8S$3WtH zIp_97-Lo<5R5imFU_yQ26q|fm`{|uijok}c8wVqj9>?wAf)LdS$>dbkaS?uVN zWvcLt=I7d8M{kOeXef5F<HG|dy*=Bd7cZ(@ z11>kj(C~Miy03AXF=uGdv{*FJVC|HY-<51mU5BN|PVaahn;V{K4vW|qY^-aaULEqx z;9xxDbj?Wz(B0e$f@FG&x#-2k_9f15hTdrJ>%=0nBv7ld`HCwqg0 z%JHpUx(whC^sx(&IWIhFpnO%N=&s~08P^UAVRHS+6Y_peJ}xZwnh~{{Aie2zQMGdl zLQY}txeHJ2CD?n2M7wO4++J-Cb~+bD{nAT?o=*rb8&WQKl-yx%z=!c`Q=Ic=Sj@CG zQ~j)PZKBWgQSGmrZui~sWNglembpzlV7(>wHprQ&V(qdCA>n!H*l0F4x3D7}9YIDo zr9n?9g5V}&W8Z!CjY-sHysk@D=ju_P>u(Vkkn?0aUQT-cMa~`~sU#x8mAX#4jKXip z6Wt|UpI|1eY>Xh5gziNbY8%ffpp{S7m4t=$Lx?t{uB7 z*K37@Pe;SEl84({9&#qj5L@Jn^B;MnOgArVXZdX3Sk-x&5tm#)>g%UcsKkbcJ2N#txvi3Ugpe)qFpu7Fo+`XLujw&5(M`cQ z-p+0>IdtriI*%U37_mB41nanQ5AJR2$|or)R|-o=toY{YBw^Ueo zOQB_~W5ap!b5O?p^-yx>)3NhE8z$enJB(B{+bjM`yF|%zPtWlre%zPo?+0xByp27b zIpk)!Z@fP8m4N)LBl%OJ5eEJvxlL9KqPyRd2Jmo>zT-~~ow+$sE=zf6KoE}4vRq2< z$S{(ga}+N}ZJ*~%kw$3GM+On?OBo_M@v3_i=H6?0Oubqe4(}mAII z7C3cAH^f{(KBR*9mNPYbup-4pD^{`t~ zwNj_`H-Q1#829r6tEcTl={cDS=qBIfmPsxp9#ek)Q8M~kN<++ZT(KMq(zG^1y{p3J z#+C6L(x(Jw7jONHi|@hrr1D;2`R-FdwPYgLicfgwsYw%l|EwWjvjR_iDnruf<*ND+ znY`l$ROhi%-X<%Nc*~99icS<6T(NSfoPYf(V62G3kDYM=zhtI+)a;rjb|qb4`(fHE zeU2Ir(_{l1##?u!lldNPo*Vkt!S^g+>)Zy#t(Y?7-J>vdIA{Mo)=m*9EcZBF|p zXV2o`kyQ4puVN$oDnm?;$MZ7lgvh1cd!O4x$dKvvfL|X!)huxE6oWs9rmLITH!Ba` zlz||n@~-@e`np>c)tRN;EIDR^p;=`~fj%Uw5gxbr?|FajIi+>RDu0@z=yr7#{3A>> zp+36WY|;8nJo}^BC87i^2~NFRPei&tH1*yz#TnqfXuWIbrY#=V;LY7Qo zgnrg{_o$QKRJyyjo(d;Ai3H9=B7DSzOr?3~=F`0uc^B`*IF$M4DKYqQ$NKJhuX^#o z-m}l*1AvKpx^HR9hZ7YQk6oU*3N zrmX{ga`~eB`cB~cIDM`cV#aUdBOi#nFQ)Lo=PbSs#cf|TA)T+&3> zDY4JG-1|EIwdHVJ;(^vY_h|bN*@W{Uk4dta4a}`4_qNL1yz$Qcrfa?b@vgQvhPGtV zXCA8${cz2#Ji?r<^UhZ}pxmTE_r#Nz>G!58F528bFp^Ieug)m?@@m9HKH;l|l{}TM zJJ|!Ge2>38-fJ>7o3tX3t!v&f?NH~$maMG45EGh8@#8Z#Tu;rRdw0?p@wkY>Q1YS3 z!#+d~hZ+iw@+RXMG;++pKl^D$JVnu@x~gGF_;z`Hb}-$yA;ZCpy9Hy%CGl#g{Chm( zyL3;IMB*_L2O5vRp$sI5vtoryW1FyQ>#jzeOucN&#}z!om+k*p&y$?+N5s8~PI(Th z^Gf4nGO06>vFvdlW4Zi#U*Jr3Fx9GdiLJeI>Rsa{W;9U^~=;yO5-%gQtnoeD^Cv>mYG8XbFZjAY;6iGaD7JGUR+t{6x zz+kkBo5V}S1V20nu$pi^iNC_>Ws9#j$3Ao2GQn?ZeG>2Kai=oTP%PaGggS0akuxc?^$I&V9|fUip?q@lcAtPSy$UVJmUi z-M1s;O56(#D>|y@f-^2B;I#+Yc`d>n+Enwc1X+HyzvC_8wrV--YWW#%^>a>4ONGF^ zXd!HQ(4ymo(WhkR7(9!`RnzZI`30804%?F*KYG0WTCUk@+4P>p2A?oHQR4JS&eu*K zUOh?>aCVcwSV%w@V8gh4X6R(FILX6a_2%!hUv*QYgZX=SA4$PTs}rdwrwu#pa8p0e zSiez~eNgRs28){`xc(^P*Jx|uqYOz-BLn1Vp100(sbIM&HL7DVDmBVtwJFIeV>v5n zr|ro&=$+atb?lJxZW^lis-pL_X%UGBKPZ)}`rWYMv#G_hP)gw6jhNtgY4l$&Jb)4NTa!~HhS)Jzd4hK8ugZt^t z=-XdK_7?mqn-_3*og%hx@-ygJ`Y01qJ2_WM^yQ_s={U_|Z{E!4QprO4jXc{f@A<$0 zxxqm(Dz+Z;>G6qiC#Si<4>R@bs^Q@RxdZh$RGdZ*$7Vmp^vIw5W*?gVtTeFQeKmu> z^OfUdiA2+p@ZKkj1P#v_QfZl9UU&KGfE(TW!{|WYJ?9LUr*cdb$Y)ciZ zG4%lWqNa-T$(I7J>Y4gIkZR11j3oUKppdI`0arw;`GpVbNmoYObFLky&^MEa@@{Rp`j z_*Pa$_C(;%dMt;Rq6sbS%A$4^`fs_TGP%SYy|ktG8+ubS?+YX_zBDDW|ATk0)c$J+ z%i{>Xka-52ylK5ZRY>;IwE4;2ON|$zh53$qb`xAac{bs#iUZR=t2^2(`>uDqn;^*Z zB9wm0b%}7?ZvPeU-e&}}3VHNGEOh&HbMCjB;_N+n*E{xPyHCJJK1nmmLxNNe%*PT# zU5&eP?RH5;)4VZW_?*VQkA#e=v1QGdcKNHTYq|@mJZZuirR2nlM~zXU~d1Xj{;0><_p)BOy{QBW=43ibIeT7kB9MoOLa@T%GrG*rPy%jN|kN` zP~lKIs#xtI?&6D|aI(B!2~dqVY>D}x=SW~tu9O4uG~kmUeE7lol5zHV{t*HfA2e-n3gmzwhK*UJRb(yvc5ES8@o=RNCjeHVv$ z$fO#8$mRvTy{>NxZXQ^-(=l#GEe4NHBP5mEyC9jh0&o+fK zz3f_P^*!O_Up(yUtVMfabUu3^}^KpLuQdTI}5_j3H`YSf4Vo{%+ znsifu-#s!CVsFH-F?Y^4UkL1lA~-k%4GK>rHLny@+p^UsScxx8P(3|xu6wn)qoY~Y zLR$6dnZ9qCI#;|*&4eQ41~2#!xE~X^|JnDxdFWs#R=M8nE@N`hM{iAGrd*|Gd;3TN zocMUI3HVP??}H?wXnzq z<2*m}#yI1%yZqyc`PXKuM(>jw9)ChrvMgFK!?Y-}s9aS%MCn83)*rEr%&nWT-*CQdn$nYdBAE)|Ue+$J-`3dl z+>7}9{*#GR%5#DjU!(U2>g-uCt39^rY!fu@$ZzBLC0t8WlZi3r`Z9j9CJ(P6QLblm zonNhqKyJuSLq4{gs($%VPk&R=gBL_0%uB!gv#Li2JyvV)YORfA z>lUuo#QEg?>d!CiCmD8O#-$g!Y zvsT6Fo@Jc4B^z#a{sPUD=)i+VG#`{48n(=Vg`R;6#>mz2z9kj^d?xtV=!fUg7iD4A zU13%ijZ;=k-o+1{_sQHtbpmfW62I%t5S1M<(_@X@9LWLaO=7FmGc=uz_83Z*8s$}e z+Y|ew+3Q9|OTsbF6q2;I5jzG6wNV%@c}|%i?pXfCOso!utap1}9FNf7jU%Cr8=YGg z!(jbx53_1-0TyL$8J^$A2g0tEd*~P+bYD3b*Rdz*_^zfs9JmCy2hZR}Ck33;^~b6S z!m7){@@BB^*mL{%U0@Q^MPHF;bcyBt$4|rpw;!x;37bd|^GNH_4CzL>I-Ym&Bh@iU zO-j0?iv9ewEDt^Xn=^R$hC^u&rd?`Rus&uAaNUTAtTod2=(60KQY^_&eYWez!wCV= zSDo4!vw@*aY$WMjI@Bb_`waEJH?k=1|DZ-v(MzL3PoeElMlcsKDHeID>sx56Uf>kH z55?4Z`=rT~jM;DSFBtE8|`W2cDUz=!%XA+brtv@Sw`O50ku$v;; zw{b&+$OnrlNmVCRtN1m>r&niENiu1>CUak7+YTsXn#^fw4WCPK`S|^u^SLiq^oolI z8k%3;E!S=;=(_MrcqZFi)dQ4p*OK{4{MD>Zd=~*b-9cverwf(j0!M$A3&r6t z>cmiG%vJKJDT-e2j&FMsZ~Z2ctrsVmxxSDo*||~S!aXNa&-R#*HWNkDs1qvOq6J zg4+~{tX?DL8oQjiZx>X>wu+72FidW@{NOMOuX@9i(B@ZXek_VcnWM#A_{BTpu9(9` zIdSx4e!1n*hbIjy{oAMZ-(hI*mN-b2G{x48;%V%o*jx^EF|v68;W$!~Uaw7xz-0GEsiC@67hloa?Hfc^T+UixlO~>v#L<0<0NS)eOD(XrrZXQ?XW*>(e%!Qad1$+vG5q znB_G{DNi&TyxB!H%2^l;<~)?o~FhD zUU#Pf+K{=7%0A%6nNgan-Yj1!g-ME#y5%#)y3V^zhIWOI=acUS`-?`WYn%D)?=Tk5 ziYHM%CzoJ;S4|IQddotgjqFv-`}egEF3L`;s0hyO8^jqlE)beAoAG&{u{1&KLAgli z5-Yv-WW_7m^@P|0SHa1JQ8PAb;C_|>Jymf3jGjhEq_C*KV93kU(v7DBOi3R5!XmMP zuhd#U^&8>N<=A_VWFPtKJ$d^ZsV~0RWw2-9fHu)hqTc9TMp#_5W#YR74_IHmd3(>; z@m{T7{8gDL`ZX;F7HilIb{EaqpL8aV-1b<$(u2bdbsKv{fT<U*qJhE=uF+i??wGoo@ap%fk8K(i?$8IdhSj zF?If8B+eDr-b9?Nn(|X62Z{N$6OPemU7U%1a_@X3jyE66_ge)xOMCuL#Mil8_#i2CUUcltNG82lzsIXh^#9v8X%(eaCNXA z;H+AnsH~HA4dL9cZ*q#*;>Y`mprLyfZ4Wym(n6$n3Hwg05-i;GiGHZY?vmD2W@Oa< zCHj4Tey(b+Eu0lKj5Fb~*Hl!#dr5wyov*mGd%~yas6&VVj-Za%qIluu zxUMUt%RKjgUVf?AZ(k@IV8GAwk(p=wO*avYNI>UMx=ms4hxQ}a&EUyTE;HmZ2g8gm z879AD^t~v2f>7t8mvQqgwv*lV8U?Lfma!KeUB8tf5s`eHUC6bJmFU9H@2kugocbu; zl=^1Pj66muhXW}_nfzJr-{;G{bL`sBVdfsiOKO@Wu7Mga%`*I*dv_O>hvwafPxeJ? z42V{n+VDKTcn|xyy#BAx6dlr1*n=eeqj@d z%5p@1@GdvMYgD70BPQdPGH=&~+;|h4eS+ai zcz}i9=NzdT4X!+O`ZlLEi%yR8QkB-^P|>I7c0Wodkd>i%cyVoVZMJ-}XQjob)GeSL zzb)(N{V2i3_cqZa4?Ir&+HIqAl3~~BJ@X=iSu2{0Ci-z=I{X2uE;6pXFZ{zK97G``bCCX^}HkMNXOm&K4zdGB@az4?edR)vuf?lgRF?-QFcA-vB+mv^;UC$o}H=_l-`GZAh* z=|^-0i}&1a+0^PghH4(HGsAH9fUkM5TcmR5s%8=+Lzs3e7)i8laBt$bnNt-mE&*MDdX~+pqIR zO$_SajT`Z*m!D4~)LrsF@32-C%*i@OE5^O&2h*xrFW!;kbI&ph8cceWS$&IXDH^{8 zk$3Cobgpt2ehH~wyaW%jn)`v>O%M>_+?dNZS!2T;MZ=`{Qp5cC`D>~lZ=Gutz*^Tyq<3!cmLyuy>lQ zl;L;ozB8mrf8N%yYh_Gwrbo>8OPRZ~h1B4({`<$A_g5rrKjEZG6-+h;zPHI)>(8h9 zB^BKIE8<|u@n_NWs;a%*-n6P-K81VXUeckvLVY9Hj0i3Yf_imTji1}i?+^I8MC=w(C4f=PA9C^Li*oj)o6y-Ob6`*Wla?ns z_r0}Q2ZzOeQt!zlNf+a$q8%Om{-e2VB)8b}Ledvc5_`+M`YvG>!_T>65;ty&*Jdv< z6k)5#G7%|cl((Mr_43}c0=H1fd=fnTUf=VueRV+mHT-0)aE^)qGM!v7rB1 z7crb7HOaA4jktuHHGAg*g;ln(9nVSU%D8LR*2;5(gW4Uh@x(H%KD_EXbZ4QNEE<>D z6)(B#DEld9|2MO>@wwuBb@;Dt`4*jA#BLTia3#{1JRsYSsH?lb-TB^KKY`=iJS_BH z=bTo$9#RR|M6BjX+g`9y#7|yPIVU* zm#nTN82NTZR;t}S&1szL!KRy=eWg@OPgnYWroze`11H^nXTdD2K4w8D`u&;MCn>#z z1i4OlbqT5vdyP;#dB);b9Mi(-Gbi}4uTO=*<#r>X7kA!0+=@c#Vsft(V|K6bCehL> z94q-2lu4i#a_mxxavn7b| zf5pIT`f%kD>)ZT9QwJE6d^7RaLwz11yXvD2QZkiNU$rH!`0f(@F~xtqC-F|3fxJq| z4TgGmr&0Wa;q`&-uk(97BXd8s1vaEEy(i5FzNY@_c{A}a$rjoD*)Az>b!(f32C%Om zk@lSQ*3RXp zXS9war{2(#e?%pfECMq)a7Ku;S+jWf6PIuJL{J=kuDqNo{J7bf!|!x)%{sqXa2j4) zv~d3PDD&;B0Z&cW!C9wJZHD>!%qI#J@S9c{npjk8$I8wPm2w%gvBo@|R!RA+_S3T_ zxWMjk=$SXB;^CCPJk;tuTVLN+aO2fqG2{#V>`@R#G~Z)jmtpp~wmhNu{ef@IyiQ+! zkh|Ru&bs;|E5hd>C1r1)1LemlmQWl{d>+b<(%!zql-IuYapB)@DwvNXep8Y{`=z!L zkFvv<{>9Nn892k5C8g!p-DiX=Q%Iwp=i5d31Xo{Nx(yF==OQ`XkXSLW9Q<%*Sa5C+ zKJ&~J9RKO8oK3Pxy>us&O>U!IoTXde>wV8|oL(e-OlVb${pnQqPpLOE(=X#!PX!5@ zgi8loP?Hi?9pk?yqwr=_=^Adu?MHf#rH^o!i81AUyJW#6Rda{grmxgyH@0n`3KMzF zo!f<3yu=lSvX5~M3Lk{yYA`AGE@&`C*WU>~I;pLe{Q$R4mg$Yn$tYZTrn`l{oEq;3 zgU)<=6m+Jd!143ts5+@Hp=b6SWbEH_kjV1blhM`G7|~-Kl_R#3%(Ja4KWpS>stX%P zuY{0$H&|voR>YRe8;Q?y&)}Q=e9vZ4Y4P-;^kUmNZfm#sy2$neF>MzH1n_I>pGV^P zhzvQf9e?*I!8R@y&EG^FJa*NyTBiB?w4(JQ zH*<87>M@fl+D+p(e)z9z$cN-12nrs(KpJ|*YGFU23^ zUQ6;Go6P)*MOnCNUGseQ>brD4rQAp7sGQpsuJpU^@BH8xBzc*_`Lb#1VKU9AbFZZd7eB447NC^EW+LT4CR2ADsM+1l&f*C!_ri|*sD0Yu zsbW99_ZW+|PvQ`NNU^(&2P5m;HJ_S8!|o)#mKU%~xxU<3Q>-{y$yezn5m{D4@oT9( zMBrWK$i=HwDZM(RqoG0_Nm$B!lD?vP4m__f?A)$h>#f&reU=Q*uyVruyL81J^NVA)?JOYyom|6g{sux^WX&QSTpr?T zS#P*{Tyz7>HDv4PWyY?Zyg63amC6vZa{tFn&o_^=MZ_G3OX4%y79D@_tbB6(%o^n` zdBJ++DQg4A4C~O)e6QL!$>S6qmWT9M2m^=KXyg0u2W9UMNbI^ zh{GMNpp}`|4|Yox#GfA>{nR{`W9z5MF%vkIw8B`^@Ob7$RD)bhl+5wq9*LI?&4gm& zzR!5*JWRiReU?u9K91c)T5{xhiw9ZutF9NN{sR(L!gUj89K#>AJ+fUbyjS+hwyQAx z8kGxw{LoE+W8wx{2k9kZK)Eh#|MuNuxqez-akj2?w8r3tH+{#je~yL zva=p{dwV6GgSzSmf>el1$F?eP1_51GNn4f*0ri54oPvox( zUvRe*d}nZZ zmT$SNm`MGR`@VouAM&5aSY~i%sIEP@zrw1h=AoKs+x20g|I=|((F=ziihgwU-1>sk zaC_9vIdo-C=39%5X2%=KL7JKHg)eNS_m(_D7VN0<(x=%Yht?iXr+GBYUd1W4uP#;M zc9F;51hA@4D!I7eo(*XD2`0|o1Z?X%=d&h!E4-#kR4#~z=$H~cI;9L{nq@g}W4 ziMv0^x4C%J9NZQ%m>c~CvT4_QvZ%R@z`QV-(vD7^I$1 zG`BkN^@H4!B;WI&UC-v4IY{#y39)~?Ja0jBp?p?9i7CJENU2%Y8q9$FCm&X46=~9y z_{U_j z*Y+izJ54j~wGh2gtKp5rwB}VFg=X8Oh(2}E@`5HifAQlscXs=i+k70!nOFZ6(q^37 z-kqh@-%Ea#i!GW=w&8?LPw(1USLykiDV;Zq?SPHcN{HJ59f@c7`N==ErTyr3!9Ja8 zKGYO1tkl9#Q}RJLN+`OM(Bw%()v9lvcSNtR{4$)$DoVivjY5U}2uyQhoR7nt6gMf1P~zLBD&jR08hdkb~3A`O#;Xj!zh? zx4&{;4jY`n;(w&}GC$A3|ISz8d%lb8&%5{~lJlM>CK|s}85SN0q|8HM!uWAuzvZaqxXI+_SE^%j z$7;gxJJL%!=3HOM3?`lUdifjIj~(?j`+) zJVz6y$q1FkzTdNV9FNsw7;A6s>UedG*UZwS`*U_u0@WT?S`JRzgcX!c`>|7@wIw_0X_~9;^h`%mVwBy3`k&)(C)pE2 z^J0^FE=9q5?mYwMuf3^iOMImJf>!aA0s+sln50Cny0Xm6$4@E~hgp>`(Wu@XwoFf2 z;b?Ivs#2C0W;LlUwS0b5fu1PgX~K&XJyY1n9xq0Qw=QJa@Yk9T)8ilDh2%=q*X#2J zCXk%Oy^R0SR3!Si-^rj$D{Dm@YnqFsQ6u7^Azuq5rnHU(uIbwU(yXQU9Cq2HOHwi< z+o5?{%QuTDeIfHK4b3%r%~DDRlcd7q9jVRprf*b4vMchEFZo~V=v4M=vwlb!u6N%; z#e+z*_SE2+j{FKD(^}04OBva@gNaU@6oRtHj!BTZ0D7h`j97#dHEFv>O}0EG(En*a;RW(FS)as)J>>( z2@@1Yq+}Y40@mgm-Cycb(NM&^s_9=oe(rJ9xwf{@i8syH{E80rsZaH^`MQ1dRg_!q zi1;kdJ6vKeQ&$k2PakCP^NV<4ynjaL$BRsw{jk)hH(J7a6(4>K(0S$mfkyoOrB@s^ zQfkAOLlxP&mdUc;9q)L_lkZSNQEz9VPGr_4Y4jlRs-$jm%~QRj@|R7|YMtdRVHa$v zm6d0BZR~#@tHoJCHJH(^nBT<0Dk;H2yk0IT-}A-kreL0aMnxLUNVOP_8msdn>c(bw zet8XAgmAp}y;k)7uG&=0tFPy+>~*@v?f6?)6K!lKu|mv}XU8R4{pAW!KPD z*{e4|*vjaC-L9sTrp9Nu$WTGI;ppji8Y>LT|ro(5coV!%<`Xum&ZCN+#_8y2K3{&c}JvkU^bfk{f?Jg&N z-w&7oai6WM)6(=y!qVF5BC1kyccpuTZ_d6mx=y2YcX3#yUugwvNhvmTztR&)7A>C! z5nm(53bQHI;40>Kd>6!z=G9Pf#+5Sfp`*AHcf`ql-IU6ss9f{$r!u6v5nJ`Ej3(w+i7VZ*&jdwD zxZED%33fHp7@u!hA$3BeOuRLLOEK59eI&1|8LrIrzdQkO(s`$6^`}S?PSfg7M(?C% z^Za4T#-8!QqTUI!$!A1CH$+d5h;$v1-FL}$jmufFiu%%!s{pT3YE=- z#!L@8BzQ?ObVmpzadezod_-W<)j{~HLquU!_2j52C0*Ug_{h23&@r2Q|E`9TDpNLP zZ@0B$KPe94`;eSVXKRfLUk)c`Hj=rAC;ohHx4DJ5xp)tu!}W1RkFNN3_a8LB;=hVy zGPyW58TrGd{dGauheUS)rm(0$ph7>g(<=zFDCFaK9+rH4b;hH z*6Urj$Xpc9iiZY!QhpaCX%nIsyyY1?xZ49agmvH9xm{*f-%r-umsg2ZmN#8E6_pU$ zMipgk@zY}$Pa~PLM4R~m+wM<|+fz`_|Q=XIiw2WAa zUgWX~ll*bprom zk7~u@mCuJEIuDX}Qp3emuTJwipp2Q2f(eDNMSgV|{L$>3gOW+l3}?l~i35)sgTshKz+aRiZg* z51tRG(O#{?ZqFV(R%qBwTgo!|K#SYmx7aZ3diNGxH`56-3P z^S)D@CHr}7^&L%%6-#)04>0-hQ&6y$>G#Ye=2iQE=oNMSrN?z5rMIPv9W_!REpwyzOI~Dq91z{ z*K;|!M|a%YopH|OmjTn#^AiqyY`Ry5aBSOyNZcho0{HWy#;w(s*au(Cs#njdKa2BV z9Iw95W+yDs*tq+bv zybgR{U|`(F@HxbQH^{&_#QNaSxs3sD8;^*!xYh^Jfdl7&-x2S#DbxBuJ78V#^@!^c zm|oTx&uz~N*&B}stu*lZAaomVtff7I9EReZzJI=8D9si`QEUhqxKR6c* zF}4yw3}*$RA8gy`$Hod7Zv(slWxNeyQsrj!gAAyIvmnOV{fE(m=!iuuf$4cBmPUY` zSqfU(nr!?I$g*jYY^>D32NANe1A}+gjgkYQuz&w|@PBwM;(h;L{d$&zh|sgc|Ni** z|BwCuXRUYZ+#_V*k%#y<5Ii>BTNFmk22L84XBK4z0zE?az>mP-PHx1oCg4Xn@GLmq z5;_GF4vftGy#_FTlp0(Vn69KEqibrQX9rAp61M=hX2Ab}Re(2VmbJ0cQ!uboXOeWH+R$!0ze_sQ@FztU6 zfRzRGD2NHM0vogc8v<;&I~xbm-`O}oU!-Q^0EfW=kKox4cmzWiH5+Id^1$Qzb)4(h zajsv73~@j}T3?TA{W>n>bz5Qvu+{lT&e*|V1mO(Mf_RU0w-d6m2jU`(1-Z!r!XYw1 zBBni4!(kv8XmCaT9p$ww%!(#<<_3JCBBG)!EP|XYEUdt|WHw-CGU5>qJO>AUfC~cu zfNe7z*iW(p8DsNrK<-EUjCc;tg}es*8}NEKuogSwwTRat{t2%?iXg5BvI8*d(M}z- zX=W8wHEMPaCQbk}3lqSl=0?azN-NKN1xOx%cp&{KWIzlENyat;0YE@t8=w#XK_I{* zH}Yo$VDK7*&>Mmw1VH>9_!~k%grMwf8{m=S1%IRGVqs?jG_VD|tc`)grgGLr*{au# zS_+U@b`%BB0SFU#MCb>gmIcUlNUpa)UB4C(6}%RqC&V>~*8(qO6+-@<9r&4jWCQ*PUc0^)XujOr z%E!sdvXgwEde-Fwj^?P@6rl1ib+utv5M3a2gRntpd|^kp970||hE4lhcUkuBWq@;W zGXdMV?Q{gHWkUw^gbIftp7|RcZG;Mh9uU3-$OK~C_#J_L!%q;8Ant8>XJzNw0W*^7 z4a{Jsfrda}mbg_Jyz`;1|$-He_3u3tSUn zlaTnoc^zPv;NKDUyrIht`$ep`88?u78ED+t(bqslZKMSlxP3?A1o6z@=n;@{6F&kE zLQcTn5YHj-g1QCpV!8(OD}?U1aJZr8Ei&!wU!Wp4Wdf5oicDMb{x-1?zypLnqzs67 zxM7nBc>tY*vH*VvWktln^=m-ABd%SKD?8|hjgyNBaHMT~41+o#Sy~1t0XbXQ+X1IT za~(Yc3*czPe97EO&uqOKL=EHSWCHd}>tC}1cm}ZL2fw)m&H?-koG#&juds7)0>@7v z8lxN@f!f`s+QHK!^6VpGZeRhN;($$(k-d!p@W#|&)&YJnq6R|%@Qp9P&SU*aNp)aX zh_WnjaQwaQ4)_r#TtRaaBTHr>Kq>MtAU=mQqB zjq*VpK&3@e9WZ-pZeYhx4MadxB3U81>pu_)(Z7hLJ`_1Lp<{sl7d8t*0$~HO4Gk?M zYyBl!I13XS3OFAmp0D9l3 z4k0wZeqcilORQjW12Lk9C00mAz=ToU(%i%nh<9L>otl*uD2x4$t1Ku~LRP{ARv3i0 zjszuYf*6o02BQ`_jSv>>u%Xh&0aO)% z?@roCOQ`fgR{D?htz!k^BN*S&>4Qj*2_lj{h)`he-NEVrO#ybcZuY2(1mAz%&Jfj5 zWIjX(1PQj=17hA@pU{Yki7HI`To2EvaDOKj^o+s^3AvasqUv==w}7mK2`thr){&sX zTgNP^jtt#sG(t~YqEr7eF|24X71Y;DOAQ04>mSCN9hlGqu-%f4;%}D=^K4h&H zVT_=l=vy7&M&hRQ+tUYi3y9fbLbAPEfNzD2Z=g!4ku;*~^MB12V0hZWZ!x&VM&w1+ z=kIKRzL#Nx#CS}kq3U%fw}9pyOl*Q~v5~h>;UO@9Y$D?^I*tEpwqS+COH9Zx(TFKq zux-s2Y>*iLXZm(@3&={CK<+@_X0`wY-QpGy=`lgvk}W_T?O=5~xW#6+K$;;nm`t|- zg1FuGfOg0h&^U>SDopxZkG!aM_d8o~qT3uK#$&?xAKU`65+<-ny<*B1Z0ndM)sgWS z4I=zMoI)^eqM-2-6EX}mZbV=d8bKha*#ZvPg#Veoo!kPNcQAq6o<0x*7*&wb8lArX znk`r%W{U~Q_HF^R=4`|_P^HvJ8qop$uh{|&PdoT6CVj3)UQ~Vl&K6wg82^uD3uxZK z#3ra$pc@M$4i(;Jwm`;Xbcp|JwqS$AOH9Zx(TFKqz_(@#I3&jZk8S~32@}X2=-bQ| zprGi%84i&i6T~gq0@TqCR=0y&Y-S747=RnAAT)G+Lfme9K*(&dUciFEfnp0YP`41b z)JJrPaC0MmN1k_KK&2B>NMz*zuLUcK$XktYj`cz!J5bJKM=4K0YwDOtM_9&owHo;E zTmu?4&}N9RZEEC(h#Cwo00YRsp}Z>`4#^-pFz~x#hgPv+gD{8*_MfYaAObLy!no1Y z12lsE#ikoYJ#aWsV1#VL!92IU1zl#{j#KcxL0G_X3Y9bVKO`XNQEJ0#IDtYE(AA@3 zW4CTMa8}f!9ngfjDeIq!+=%)p!3)HNgbb8Ef#z1=3zp$Piz{NM1nk}bGXT!1sG>mf z6oxmIy0l&$-bi=9-%e5;1dQ(C5N0r~CyroyJ*7ixPQYSd7DEHa4nenrZSKhGA3_j9wZibb=R5%Q{$pwSP&@C-fsQ0+gp(3{?Xil{LBxvKnHUNcAC`JraZ0dSL z-rpohs)N|jq7YO&nAQVbX2S+i7Q^T=kP!0E4E`sdVFyhP2$j%#g-AgWJ_EQEVBtW1 zLM8Dp9R}Im1zj8<5re~k*KMCTkq(2AL<|lCUbn+yU=l{;4j|6{mKp#CS~xWlGn%}A zNHrUAb|a6YEQ`!GNc3pSLbe>++76}+hV)R>#o$m9|I&8Q_XS|z;P%v^IUmOs5)pA? zb1w{l1mX@LH$W_k^}s=!TYsCsM7kwTfGv%G4;5paST}t@_3XeMcZB9qa01`EyBW?f zsb@p$$SzA%V_NUHL|qRO$T6)4h6>B3`D<5=a;9fe0eeRZ?aMbj=J%dQ-sNg`RcK`>#K?t-} zwa7?|)H?NgGct&?$dGe83j(fGC=h$-MAr9fQ_4h8v__Jba9 zId={*=;pJ&2m75~00-P47%(4JYCvcd9>K=$zu=J*L(~DWp+z0=x*gWr_JFY7VU7Y0 zBzlL%5azQbZUb8q(4v4(Q6f}N4VHQ25jzybZ|e>4NO4f9fJ2kpPU?a*5TPIN!T$K| z5pRFMN7^KGp9_aN&-QR2)dgqfHJnH3C*h4KUj*`i$O83+xqsMRnYXj6L!eCXv;1F2K}@F22#dm6 z>$;U98mtfvOej{4e9iW3Y=&^4ptl*!HWmk@2N9yVA@b54)&%_!27W#l86BYug4Tcv z5)R!?{X3aEpL@5E305Gskh%V>r20l57*MSQ`aaM|-Psu+r2dW|P!7Ni2l|>j2V-Pz z+>TTbHTr^oI5r>x90u!0C>@!)VaN}>pe_r(Vh7!Cl^c3Ru#739fi0ZdTg;9ugVLd~ z3_XtgGs_T7VbJ^*mbdyW!1BiV3D{mj%9}uq9N1qXYhY+53~@gUZwet7Q@Dfyfd{c& zMOzbb=|7SS3Wd5J9C~*AS91T@phf_Ee0`%Vgev?Wn(2NM ziflMUj|rIi=wRTb|3)arHaO^E|DOrnd6R)|t&lD_D52{+MX+eOS-e6PV30)wz>V#r{C-NGyePliETxiLkXotOnB zLd6KC!5tI@V1gXHH|!4BkR!3dz>lp09n{G53_K!_;;{7|Z=~M=e_wA2K*C3hc5rBE zVkhnHa2$t0TcI)Vvcr)WS&u+DkV9)R+XI3e$YFoz7(svg_0JB2pTe;DVfDbqS-Vf}Ae8*xM*njaKjO2H+-P9ET6ghMRVI-Re9#p+SJNq#C z%%(9QTU@p{BC<^cRdq0EM z8`8hnvp~BtFvJz~#JSOV12qYR18<%{wy*bqFWrHW-)8r_&IF|!*|~wH!#}l4?MO3H z7}OQv;0&q1pc#^Bwp4||+kD%Lx`ovB)Q3(gXkUm#1FiX>xg8{*es2d4fhI5;`*e&69;3MrmJ>ptl1KrrZy z*&R9FxYZ5SBZ6blIZ(|E2E93tY0(`G7bzR+if}GO%==$ChOB`B)fSF1cq)S9&GQ$u z_Je9;(7U4;-s!jDLc5oshKr2wXp8QgJ$E)-j9pCNFv|ax)SZ2F3#sde5U3R)NZmXZ z0v@(mL_?GR501MTVv(&aTXvqvT0W{>!JxOvFllOMO9h2NTM^89+lNOfL}tfeS%51c zkFLPc35=M)Id1(vB$x&ec^>(8NbdrEM;Z_Ch;ai9VQc#h8?;IRt@vS}VoO0~qiT;( zABYSQ%+U)f;GomLFfmA8$I#<|UQmISuP{J`7~OUS6)5=i1O;dSEU2JZ9WdbN?*b1* z0QANc7__*B0q#!BB4_D>X%f}W;M{+C!hrz8z%1HlQ()jhb$jcDFbnQbH);w9cZN_3 zVgv4e10`}`91?V_3~;*-g zx2}l6tKr-TU;GO@-f80mJwss-6`k9Sb0a#po5vsER_tcM43$*q=@-L0{U&wexDR~~ zfYkMoqiBmlJOR_9$WXZrsYqc^)dUBN+y5)6h*-M4s<)83zU_nx3Wp-$90-F4P z$iJKUXXCs8eGdewVAMyY00uqx{C848VbE5Dmas9ch?ak#u7u1Zh;J@H+#f|^Lp%Dx z!BNeBp)*L>*%A+-n>h>+cZ`SY`U4ss&>BbJ;DGV}hU_l_A~gPEz`Di0){_ZT_Xzu1 z&-mN)X`mU}&RZ&o_pYa1=$iqChRhjgW&x(??fqmuByQ}(&{o_WR}R@Pz@aUX+wU-T z{3s+08Ug--T`&r-cN}jRE`r_7EjhBfx=ra48pAQX*>7Gq(ia*F$RL5nD>MybS`--- zx8W5j59*3==ncYu=JmfFbvDcsDlmfA&0RV4Ly*WK2&&0KeGd~0o3Rl&>3vJiMQRY0 z2pBZfU|Mu%YB5IJKkN&(kB!J3F3__E7IT3|451ee{bs72G`CZ1glZpy&^yM)&0RQf ze*{H8aA@1&Un2W+Y-HW?6*T|9?yfaQjw3l62K=Z0_>T|Jf7YvovFv(xl_3})IUKC8 zdJq)7URw}^5!vF7mzp7lL#*Vp_yRtUfA|JIfKT8rG9#-aBcdKX+yMqFP%KVPR#s$W zyfPk{c|C)YjW`>W^h&<=L}jD>jX=?)Gbp}2BfyR%=7?a(-I$M?XRxVcH$RNAXng~Q}$=AVeQiaEwPPY_b0 zDW+Cx-qc+nR?np*1(rwt)AEpWj+rQs$m@vPHc$*>VS3GkO;If*l4|M4hagi@NBme= z7A5>zdAH0o7$+umR10~uf|w?J5E<}eh{5qev{D|doPf5utA)cGXE8h4+$!Cw4XeJm zz)P4_K8Px0GEFm&z%0>cmCjZMn`S5-O>Xi*#|0Y_KQiTB$+T zuxAz1wF1LuBRcHaN+~k8?sK|iAq~?Y^Qb&W@QArs6pAx~)#O+1S*mzojhTN;EY-_I z2F=Ve1uZg((J+&I#5Ty)6$Cqr(UU?oRZNb}CrlU}ZEMIp$db{SljC(E1^7s*BKdb* zI8Z8Q0jfcT>(CSXvv~EAeOq~##+@mZXl#15HKp>QyA%Q&Cro%9jo^hs6C6T0`#phL zmVfd~gj$DXtB^}OJ&CBJr%2X2x3ZO2d)yhd^W14rd-N-0sAbtebJs@>DFRzy+|m4r zM@{C;LBl(|TP@}G&Qooel9C3pzUi*0T{`|S&(W{onn-&fiP~7TL=N+Kb`xTOIYz^K zGjug#G(`~=v3iFWR-y0AptBf_Gs(fkm3T*WJoPOnA;^K49!(gH-XMie zV(FQgAJNH%nMP+YF7nA&3mM7tydS5bMhwQn449a;h$W5$1uBY$3yh;k_Z62^MEQjC z;v(aLi{le%q5YW zI4(O8YY=21zD##x)3IV>>QDxAwUn`VM6GWwan_<5bU_YB4K#&XM+moaYjcO=M6aHqw3MPfk5t!C5{|RGB6T5$Rtwjd*PKt7 zkUDOg;@MVX4pxrWh0N`vZ7*`RN45Z7y_8zIL4MX~n8mA?r`-na%;M_Kcs2PYbl&L+B&LCuzpk68x!mKCErJy=t7ICRc#o5nQWILR! z%4U+6%iV^0;uWh%W|Dh^dbN;*`O-Nox)S-A@v2)`O2i;+l$ca3T*-DMUys=G!{|}p zgo~}mJ85t~QZH-T+F($QlMWDWaJA@L+|F{f&C1^*q|?;%G9JDjlXW9urbO(=+i!4; zUBrF-BAKYPOY)LLTj?zEW@t{Gu9k9{$@PcT7lz&uwD`GTpYmelN{@{PX*!H`hS7u~^T2Z%jrBO0`4ZRwVa^l6B=g|QF^Quu!u^Ajs1&1lurk|B zpmH=mDla{>mCh6oREJgQLo-}F&nlzZjuZk>>mAbA%G(j9!HNL|UzCQlkQp%D6`tVl zQ75WxMk`C7Juvfy&RM3-ZuhF>Q71F2%%Er*9EE)q(_xeZk`5jZ&Si#?r!IKldFV_h zPAFj6GbO@(t@LVn@a1?^45JV!58Ut|Wp?oq8J$@{vv>zLw$jVz0qU>|eQ1UbXW1os z>!P9yON>+9gX2)Iui(jPux8n1!FqJp*Viz?{T0-q_SOX7){G*|mwA?5mNLKNDyK6e zGzA{@i+8?aD^vIHh|W9c7m+b5^$S#k(%Hm4>(n zsAZRh;)9p->~hpD@n{LKOMp7#v9Ot2jmLR*S;(koyH=D# zWUz72hY%TSdK4m86&DCeP%TtV%(F{!u>u^fo~XR^{8oBy@5nB9&@Q1wmDoKux1(p4 z*hO7@(1yMG>SdnQF0wp!nq5SGf40g;?c$~Sx6*-nM|9pnyQFeOjZV9J2@cxTcwAtY zRoZ0=kw@(k^QM)2l>W_WL`rs9sIpjKm!o!xw2Aw5JnbS6;Ftz$PP;7C8o1)n*DlE? zP3P<=VIx$62WDYP~hqA@D)F^y5FbCyb* zW_L8li(D$MG^xB2nKqP!&tPzIp=(*M;7M>#=cI}az0gt23na9`b3Oy#X)Gf&mj`Ox z%!s0bOn#4>nK)&g>mnlo(+fPKy=$56?t$lEvKEKLOJF$`A~7kC)bQZTF}a{+;&*vn zJXmK@nIcbgqp+uCWHieI)oPiAI)r&G6GK5k%Wzc!qgAo+x;r0cS6GNu4{PoU`&#CB zD+E&ia&0I>C?5LMN-xCeWC-c2-CUfmmSqOs^a4@TqS{E1EaG&097Q{ZZYcN8tqWOq z-+`e1_|X}9ofW1r(m(_fr)XGzm296S4B=SvFy|5mZ!44#O^27Dj#mfjmQJ0|2_v^Z za%Xuh^WZ&zd8t_`Y7p6+Cwzu~Y62E=+9Tj)r6dz`J@8z7vVhmX#%4iGtqc))@a1?^ zqEsVN%+-T*7Lloh8HkJtzzUHvq~QT-1ZgcRMm%t4`6R_{Xk7zw;-t!eJq^|>chwxf z$n;GQOmBTtHmF+P7_K4aMu%LGN}sZazI>f$`9)^JdH}~|PC3UKL=Mr?GKYU^I7vm! z7)dMmQ}{(p*#nsQ#n&&%i%$IF z=@)rMkq758zsOW54?Gv2zJ7@w^epZmgG3&DIUX1IWtDzOsisQs#HdX$e7yR)Rt7aZ zKplP&9OfZzmS0ljhSrt!i+2@AE2BcwV9n_lnR(@b>8)?9Ycp|&7_bTYMOVK{ud)Yc zzRt7yMP{yf07rh2HGV>5CpS9q>XrMoGOFRhxq@HrpkKu08DgIhfq9Yhix}O5FUR9N zzsO{^(-C>pFK#KGJh#XL)bfkWlJvlt=a-{?iNuK;={@}tf+QbhKghHz4@{R|eEq^F z-AH>|`j9=eCgx=%BXr9(!=Am0oZH$za2R6G&#dc?2B!E9J}O6M_>c zy4Uecf{MH@F*8hA#WG~$&Cn8v%og-On-!)pP7>+Wb6N)X`bP=Tg-S=CFj(W{cx+Q7 z1xC6K-^%l$JhbJ6kqcAhyO8N_9zJ=g!%ABIXYo(V8-%T1UXrgLhfgtr2c9QNWu=&~ zkT@nFm6AO9X&H}7lxjp0E_kjUr1OYm=}(AsbW$e+G#;Q1pU{fUfHTJ@@pI@w-~{5_ z))A@s`gUv2W17sxuVs3chc$0~Im=5W2Ss24(AZdHdZ?3884rCY@QciM^8n^$cC~qg zBjS~dj1-(78w0Opx|Ro?i_ahmHFJ0D;}`tW$>6{;9_RTbB%sX@=sY4>9)Uo;YTa(# znM7i0^+GZBZ1gF9j@LC7D%D=eZznyo9{!e1OPQawdVO8}5`l^QqLLgs85!{4JcVEA zpFJ858x9;o)F||6Z*o+*U=NFdXw63gQB2tx|v-aGB zX?Ymr*$kL2zxetk`J<7CsInWn;Q8gREU)uCzpT4L)&X{5jdZBbTqz0w=8Ab@sHKfzf|h&$M43Uft5vk4}fNb|!im0)uShyy90lb%J3|k=WRSPc@3$#w{0xaV90_ zdB%H;aoquXsmeI1P08Eel95?*X<59@1^s!nl8XUKu2m@0xQyI*rY#nQA&V!m($8Uz zID#uqtm18UI&B~$85#0)SmONp=Eo_lDbJz|uAMlNaz)9o(upIfO}dP$oH&x&zQ|C+ zi6be)u9m5*tA-k*TwqLd{U&65XHc`da}WITB=74)xVe&ET2sfcJDlGmmMn}Y%Q~EP997Xt-SjC)WHzO$z)D_vd|H$ z2@R1CCUXr=hD>vSjJ%&bnBoAE!qX&scKzzZkB6(MlhR>nu+B!U6m;}9W`S(Ex|7Sq zSTV5DjRFNqGNw=C$x4^=Wh3*@?^$_r#ebwWv~$UN%^5s#dC<8!>4DF5Ftq4rL zyg3|RTwl|)E^aVA`gz->6o?nsZx3Hwy!-Uioj8?iJjHzXe6u^heDmSvz>m!?oTnB4 z#t%E(arc`Fe>S_9XZVB13;e-DnY=VoXiIs?;&b%7$FFY=@89FYzd2mIKHN|f*$3e- zeCIR#+3eB^jsGAs8Sd+-T{CRN3G!lp`QgX+-)st;FeD9&XkOUArNI%`C;Yt;|AtKn zQi5R+jU~=dbYXyeUO zJR%8kKSIHVf8s-${6~D9Pk~8~2utLE4WGo39?WU{;3bML%<$dj+f9izW}C0%J3E%2 zk$g(N-QxHR-`$j)AUpXSX(7?X>~m9^{*f3;P8dD$iSg3=5zQrA@^^enD z{KO5X+|K*jbb+&dGIg^GO_vb!bEAI74XIQ8nW|a*l;#=5S;;C{Kfm47wEAX)$k;I( zM&=nd7Sh#~>bX%rV|;#(rChq5(LR}``Sbz4aB!0@9vnBSTx^~0iCg~+Slq-*+o0#z zi(sFO)g*K0pBOK$db*)$`5Ez>ZXQz4&GF|nuK)aY(_r=N_B>{X*zG!x!l|oOQtbJ2 zb8L?^mu_H~enxDi>jcyjuEv+)!An9DebX;1+Ms+&irefYn z2NI+*`ije}t4OPxRM(aYpi*H+N?1>cxGD81<;cXO2N>;arg5xhlYG#BrcJ_zHwoA@ zL~5~{gm0zCHOyLWf}UTzIlSNOUL4+Ef4F&dc)x8@+20q3*OwRM=ESNOu=m<+4{bt+ za$dfE4`CRHAxQ={clo-obWcY*Mv>lndi~+*cH0SHI+MKp`>($L_uv-%`os6PKflAa zBe=VQvz36TyZ+Gtbmn}{+J->W=i;cwj+l$-7+2Pgo>jUi;-rwFF zE`Hqn^q>FzxBvD0-T!^~-Xgs@R;Z{BQo=M8N~ zUjBfw9zXx|)6MQnZ11A{M&AtGBu(YoKP+?;%o4hF=%$l36S{F=DG|mby1%RL4dFpi z5w4 zRA@_NJ?!_@qgsma(a}8W%o8u7G+r&!cz<6Qk9NAIaZ_*cz%Z`W`YMcfeWLMhSf=se z-Y_1Geof<=Z66rMwVGLlan$oZ!bQoD<1H}WEy(e*xHptX2Z%7)JI>%0f4Gu7#G52|>!`dQsT{g<5#{%`=j7}qP33x5_rR$9PRbK^ z=q4(M@?1puz3n=qC)H?At)3nj$~AbasJx-uUxF-e+Tl^TB+Kt@-x+h}49Xw$zO$C+ ztE`vh4csY=^14|@`MvEtxwKLX7HtwdEGj?QTO0O4H5@98@?r&*-`mzRhItK@KkTii zNiba{SjdMTQC{~esQljco-r3kquf+}J}@f3llnyYWJ38GS$=Pu&lqqtD5njQw8EZV z++MuBeuIQHgr<=77G1r&uihQ59>2Q1yuSJ-#-4V+|JQ%pUVV7`HvUyWNB!>d@TbGg z^P9sD2PC^8oQ2@Y+2#9pZ!dmE_SnnI+qZ}9?ahY}3)+44;r8w2RS0=S_xbi=zlQz%#l_7ZKfC(j8bi}R53j?Bke0j0uWqg>Vnq>~ z%fnkl$zl-c#g;SnIOfRt<9zH#bed5*VA!_c8KA*4xLhgd1q8i7q>n;Alpe_aIP0M1 z^z^88&@Uba1;lYbPEy=_i(pa;@9n-k99}Ty*>RqeVf16AlHk=3oy{w^gqTP-)XQ$2;WhN@h@R2zdii? zr|X;7@Bb3!@)U8|+v}S&m9&aj;Y4Zs3aj`0=KA%ASBKPZqzsTvTJ`1}D-OyF{}Y8U z)evQY)rtz#Qx_O3d=k>+3=!f_&i)a}C_~W{O<9#g+5baP{jw;2`76N>KS8P>i?nO% z4Tj?XwjBb#?e>RlZ0m9C41^Q?x4q&v{F`#;@;0O;fV*WIs!&3;cHUMGlocY|ihkIe zZ}h`JVf(Bf%^i|MwS_Db)rL8nel1CvykE1YY|p%{4ALuaD?C`hu;$&0as%^s%Yrh) z@-|#?uW>pcK?_nc?^lM4L-RJQs=-UyQx==*H;}YbZ4Jq5)fNtbRBb)wj;gkf?2Eh& z`P~KpH{JLkGl|eht+Dt9J*>3tdkI1gak*X}S%<$@J@6OS}7U z+(tegQhyAt_8n#ayPEFpgudLkYODe!n{RS)R z`kpf8RKGnr9`ZH_9ZhTf(2|vw^(!hvlOVp3eJA^s5bdN%&CWvm!FL{3?XMvwm}FDnrC1X8rnr$IABq}TMR>^=>`4l8Vy5lX%}eL zco~LH(-p=u^F(_hhdHJy7R#{+V>HF^Q_XtceCw%Xzzq^9IrS-_OHa~mi}SHaR-n7hW}52D1- zBu+Lk-vC;SjOZL(VcTNXXb+Os@a+rAg3n>@2CV}?e-7>LA@TLQ`;g5o#{++a`$e-h z@Pg5+4Nep`uz;KmUiLku%F6M|qK8n_ZP@0S_2?U_7gF!mR&Ml_wmb2Sy@nZW$U-pV zg>&}vZ$Q79xebNhA7MNgA$mNRO?uskpzU~j%d5~)1_v;KHC>USF>47$Xynz8~WX< zF>(XPYFNBfAXM`%7*n_5z+8nFEQhs~OKiK%`ock~X&g=z@_Od;h59w$XbK}c$sjj) zK{?2+SlWeMXVwy~G$WgOd|AVcJ+_@M z)$l0(=)-wqIqpIB1}{UXfl~c?OGCXQv!S*u%!Br6ftX9GUCF3W985xUR zx7iCpD;S6^2kID&T&vo~@GmT2 zi)W}`vmS8QSy=YkX9UFaYzkMn zlhV@oVC_V>seYo5UZtzc*k=e=ov1ioSY0yq!O&>t#X4PH?F zYWNao$Qjv*^W+S#l50fMA4G)qo5B1m4XxF1!P|EmV^6|#H1vnF>&(8fhRv$w8muP6 z)2Qk-J_?+PXVpFYDIwFQ~xM zc#D9UUN?vX!_zH3Z2Gl2wgayy1V`SF>_58xL-Hzu(xGl!wP90Y`WYPs$C7~$ZQ(N3 zd^~hiFe515=zRd%I!!}p8}!^LwZhB;yJ>AFLctq4KpUKHYHqMZ4SYQ|z#3+FeKegy zg7x;@J**Y|jeWRPJHrdKjNUK6bz|c3XfraWfM-e55Zb`$`n$b}<)fdWKe%&joTMM@ zzK6E8nywJR)xJKA*J<62HUO>QLvDasj{&g>+t2uE5Fv14hD2HB8@-_+L>IJQpnx|~ z%V)HOds4G?gEgdm59nv&258%B8H+Y!^MjZXcF-`xFjjU1`q}jzV8SUI3H?B9W<0E> zZi79&)onVy1hcpUBlW@mkvyt~ho88+N5V2}J5PfU*)0F$9<_nLi ziG7ln)!0MGrqcWd_paj~2R$2@>(GgXazet?}GB-`FFmNcTfHz~FRm{ReO<7(2G;!wFtF%*fR+uz)=bUZA=SEMRKe z4?%hbGZBY|8OSoUMgbr5E_@mK8xZZrzADS!%omOy>-T|4Xug54q~a~O>anra^T1}v zzye=}ra!p;=>WtG&q#(a{01vl%P>sM@+Gim@rUr1`36EjnlH)iW99*SP02Ovw)QqQ z0o%uX1DTTse|s>9p6}k~DU^_ZN+u%IVe-DPlt8~852lK3v++S_0G)$|@ofAOQ97+x zA=O~k=8OC+z+jdR}4@BW<2DBsy50q)$0b*wf-=Q@SAt7tb;#M>rk+Q{GKRrVy2u zYw!~~K0zyED_mWs=@foKixaQ;tkUeKo;#B)B%BDi*oE{Is9ouH|nn4Blqu+%{H1j~3x#k&o!%W$%rnVA_Zw3wOMVzQVSEM{hA3oK@`n3=(nC5`OvKHWXvIWzai ztgEH9YVXR7%!rJ4M^-*nWQwBVbWHRd@GxYJ?|<+xOaMlJouMT>3@)&cx; zuwVWDW3cxC-=}x}GeTt(Cp#C%cW2)H7q_!@{`JBHVEZ*BgE%wb56{I}08D>GE6xgF zV*l0n9-x@JvxJJXfwRfGjf4sZfaAB3gbF8s^Uod{3Wk;@M!&|CvUy)sCQiWn{~yMR zj&?>WCeGRn?|ETRHF01s^vnQGRwjCO z9l-m#*gC%l;{^D<2n-UAb}shs)BR59?;8dc231D`TPOQpK^u86h^R1#nz&jRnJ7yL zzmKhIVCeMT<&V_MNN$Cj7=D1O>E7a%>gVN zoJ_y0oSYp^3~bwu4E{zC*3C8LVI>GfmgtBLOgo9yG->pCi<=8%Ye zujt8+(56?3-KYnTwoalGm6>{Bg(=69eF))_#^ky5`WXEv&L!&jDWo~7>68T(^(^IR zTYBxsL$@V5fA0)3R(%p}lKo6M(N=JHX|1ST6n`Qx4Bm(=OP;&1oYPbx zBkawr*WHhd^Mu)bp8zuEi|&;(ks<6_p$6_JO6vZ0uS#_2T zOD#pVY@+5m@5M=sFHxd$VCLv39BfzKq|bu=o+}r84^a_`?E!GyMR-p)_J*2wl(hF$JI+E9Fth?fC5!O0?C zM1wq`a8wKdnQsD2McFPbg?`ffkYfPutLfmZIy8Ej6`pd}rIx2$j-f0?NZs3zdihFFfNHdHPKeeDeeRnlfqPv2kxBQGZ8OZ1NCn zm@iMx(n_HEx2Xo-P0A7YJ32BCua4Am7nls}Hj)0aLEI<>@OzM_S=x^boRL+#$o8@= z2%rfxwLPwcLY}%=V7!Qh27>k(NmGfR8LI~S6K*Vt3beCuc{g2_IHU==Zlz0GMf=Ybmn*FGE?t?7wvKH;gy9TBaXN!>&sf_93DNIM9b5t z)75Io>qrJ8=c^Yq4X^NAzKegVNQ7%}G-AK0iGazPg0;UT@ubUx^{R>@kIJzKHjzt} zaAaI@z-TJNa4|L`XaqpKRzYzXLIG0E65J1FHvpX#7O2e>Zye{vBIQXW62v39_mumavpw`i$|-PA2pc*k>&O~z-i%7&Spq1@SNEZcUnd@p90-aQ-XNeV&SH4 zPIbr7Q;fVp`NVMb(TuBT^*cMoyP3$Kl#C-q_ba8LO^De|p{F z-4Q0*ubRffhf1(V&#rYV71K{g+8FTo5;NxWH3>6%gtpCQNVaWp2V-Fe!Lw zw2@*4ZkdLDnpny$n-B)UY#fq^?5a-ERd~OWvrkKGy7Mu!T?q#Hp(8&OwdYQZrrjY% zPIk5$H4Axo*L#rIcm_ekQJX!WIqy2KZ#(jdqVDzO+H;y%Qp*qbIXWi@g9UA+lmSCK z{q*(U(2+j;2%pJaVIWRuOF_{|1hZ8sD_ZrIgG1M(_BSgaVlQa$bs{H2ed%f6r3(6z z+8UO|r;&sklyw+!B=sEcBLG*{w+d{9urID6t6<)rO4m$XSdEr>)!6X$$PQu;GFjbR z?|>^4X-C&-DeDAJx^IK{vr0%)h9`E&>|(ip$n6cSVyv13T!io83rH^NL@*-Kf+Q5z zU2^M8jR|}>zi9WGWW4Y?m&s{b#1u4PT6WLj!1%!Z(YQcTK4dk0(cvV-P)-uUg1SG*%;m@GZ}yJ-h-4D&bjeg^3*a+8QMr* z?PgP-{Sd7;ne96e@j0us2qB-Wb(i=9*uC-1WSq2nv9-~3>zXuyamwCgm|mVE3Ob8y z1yK3iUiV88pp1BC;K*e+M9D&hXzMwD#p|(n4x-`2kyow*S{1N0R@u)7$+d0M=2|F< z63lFIK%*H(it&LjtI>i+QKZajoAUuxy5N*yjUXKt7o+D)=q}Q!!X3Cj?M*xv7gAIk zV0%H`g1vg^yW8mbk1o39t0CMdq~@gf1sZ3V4Lr)yM;vs2S`kMadZ-Db~#Tq~_jJ8H?WsYEusa!oh{4 zO1el)0;Rrzzcx0TPCdf!ujx3hp&pgMemM!x8aPfm+cmAgxt!0ub1N5- zs@v~%1&v9zP0ADHqhuIM2mS85DsC;8271)HJA8cr2;4HLvrFJ$rK5v=>VZT>dpbC!7CTcIa=fDAzS@Mh zG?%LLjoM%u@{KBph-Ol6UFF{-bW-*>_%p(61nP4~xu|(d!AonhvvPefP9V%?kv%XH;=W*#`C7x${`8;A> z^uu%Q0=fpje3GjgJ0g6ZSu8`!hwtuMkXIH{4z&~YLs-J=eT^?Hi-*ok2sQgHlt?B~ z-_rf|=KgcDND!v(XoEW(vH~H_52zbz*QC*j0+0@3A*`(sF0iUESc)M^*Y`x&g-r4T z0g7Cu%-{wnZO}NA70I-kuR9B7`%!P*z_jEmZ>{P=x4(}UrnDf!zssI6E_?(l*&A`1M;N*shk&_+^$V41QQL5} zmWA5cbk8=~{1_$faGkM!mgjg<)Q^5GyAhNw!q}0LLfwQqMHbAZ`pVK$WV|xwN`hE!gYWGOcEuG|ue@_fL)ykZD~eqY92+1${{$GaqZUG;02Ch$s0$JZyY1 z$ZLFSE~?$*Uh<7{p9ySFck(a@EHK3*_woVrRb^oigsEL!2qlLY*og?5k zY+w+#aCCAOF*k4ourR+vnLn?Yn3>M zy`S#QoPNzC_0EGVjD&2>tW5xn3_?yuzo-cZ8{<1b_~V+6`Ta)3z+TeC!p!`SCLvd| z-yQ+jn3>*rkozAuOdPB%zvol`qbDmPBZJ1DR=>tjaW=70dpG&rNBB4DpkrfX0sNW( zz{LDciQcDuryal6=AA*9Fu%w9n*seZ-e36TU-AAIpJ5P|W01FVv@x(|FnY&P|1|oA zWB#v3B0~Q*`UQplW%PfN0Q0{jApW-m=!EU8jsHLEfay2v`k#cr!TPrl{=`#%BembA z|KacdpoIT$@c)qV?cK;Fe z-w*2h)5`d-)JXiDn!mFmW&AFB3ug~H$v;(ZZ166@cNxAva(^IFRl5(i7Qax~yC#3X z^Dig<3ukJ-H^0k9SqJcs#Qc*_{# z{u%!NBl-RRUX6ZJ;{OQvZ~6UupJ4r4i9(JR2G(+_e@pQHvOzHYft~;R9pb%;|JMfb zCwcyd{Qje2{!-L`my^GI`adUwi;?XgTLaU-wgzTShX2|cSXuwEH88WYv;DIc{O?&a1ep;GM=#Q&-&1VV5o4A9r~wVQ{l*${6D zvMV$XdSu@>l%LfM`WL&D%N*Nc8}IWk1?%dS1?9&PrPXGA-3B$aVe_W zZO$RsFL3p!x@pu7VDP7W(%G3_&_lDzJZpfxWG#1ZEU$L2WOLPSH&%Q0{*ksg`%{hP zeq~i7rt)R@r1Wq_OGBAtwESrKx%`7dKv{-PkgCHraUo3dv8M|K0#N{yIFwqMKr5`) zBmTUuEdh3kr@xls4g53D^2YQUFh~a+_ZQ@96!r~G zv$MNUPttsKMMS7$AE{*;NW!=z@Axi-=+n}Zwt1^g+{Pem-2$|F1Kj(}dU1$OHS?}@ z%$7+)0u&L4^@*!3IA(n*C-IP37SVBg>1dT3$x&v@koo#g=84Tl%DcLHk6N`mhFwbh z&%Li9HcR2I;@jzWx)BDXA>MQrp9;EYh9qVpK|V{?Yo&j18Xh*FiHNE&Fa8_{GF;6fGRId!&5DK$Dq{WS!O;_k_4-Cq>-&)A@)EQdC-k#fUmzFeTgQP4xoMK zW{H?^wmXx zmMFG+tVadwzSZU7#cTsr71o$XHpFs(vcX%c`e$E^tL* zjSrR;DrxF=lL_y)l7fekT>rD`A3vf5o>aDqC%!0fQ<%}He06kaQl(CkDU>eVMU$D3 z9ZAb}DKa%JE}~|cEfnCr8W7w+ApGHIHxvw-m$VHJ^=XDPHDnfrEVz-B-OKoyO?Vc< zp8d39W!7{mt%I@eU7*#S5Kt=(~V-8-h2X6Iw@o8n^ZTY~?)#oATW-gJ1 zOPh=7Y%Dp;5xioTvqlwfaZ{o7sdGC~CTT><%(TsMJLo1t4|Dyd)?3>R{_+XSF7R?x z-{oV0|0LxM!ndsm$$oKDoBoMxzWNr^bIP{10KL$S4O*M?gnmsw=wkd{jKf+mvtmi- z7(8yr3_c*u zVkj-h`5kBJrP_M|Mf z%~A+m6I)0n{hPy)prV`KUVIiV*10WzqCQ|-v92zDYt@M|b|>G9qjS^u{#KnlW-__R zPp8LER6bdzhv;ja3~NmyRY-|^Am7K$u{YgZ?s4!L>#u3_DG^^aLkIcA4#K)$ue~15 zEta#A+Q~J}l+b851rbh220E6Fh9}deId~(Qh#hb#toI&mY5+d4+gF+`NUBe!q}IXU zRl!gnZCQvPxfT{bwe8em;Q7DS}RF1pZ!*J^sYhjXN>X#gj##xi~>hh5DJUPLskoCA6^)^i- zakDgr8lDcf8V48zrn(jX(a}v}Jyo}NnkM(t>u0;y&n)j(#WA~~Pr2SMIz*29Dp&j= z-X#uhW6gZ~5raRcLAxeG+0WL;c$=TnzL>Dyy#ZH zrf6R{$@6}oS4i;}qy+nD-Vy;D2lG3 zcvDQ_`*6W=Rt{Tip+*;ren(nOQBy0?z)`pVW3^D< zAI^YA%M>QGfz-fj1TUi%u1>Du0+-h55~t^VZhBxh1jGykjMI`xsW8SQiOwS9r&5nl zlA3p`oCgFPPu&VDhdy=7YEqOZBk}dc00hjCHbRF2z&{QelnPMO%pIntVJGOcK^9qSxK+^zb+bRj0zi6f(r z_HgA6@@pGd9te4+T=egs6RsD(w(I%*WFktGuoQ+db-!b*4dLQ>DWjq8ekOscm(mXn z$DK@}^boACmaYNc6qFQ6`6g`50!g!^>rBnUI+mO2z@`-@&Pf)Y1@pmX0{lp+gl$Pg!fqP;(mWkYtz#Q5Zgxr*9;&k4}Bdq@JFs4=|7 zxC3q&(Jla09C?1ZFXVnP?S6&tzAp4UdrDTs_1#wjHN%VT1Ftv;;}#c0!tugw z;}95v0Iq^WD|dj`M%{9~&ITTlX0puYfc*&QZ+sZuH%jsCANY{kY?Zc(znA2i2^}Et#W1k4do=v+A~kGIa`9m?LchF>Ho`Q1B6==f_EHADP`~ z_%vL80U6(T;Lf~l!Y%o|^=`sKop?xuNBfbCNKo{Nyu*I%T&u<@g!4Ksl1tzPWgmh?IrgcH$wI#gy7l7Eem|)Mm)29sm}=0 zyFB-e0@aBx!7Msuv@B-G^cXL7#;7&nPM_{Ij%x)doF)jrlw@X7DX0{xQGv#qnwpi4 z(Wr;B$a}yHLovEJ9>4iK4?UaE(1dR1rAzce%M&6+-F#{6gS7ZCnS%k*KhtABGdnH? zRDykf1~j+p;f;q2jduRvW@omLlrDJdXiXBRjqOMfxYc$bXsG5u(Kd=>4?2f;yR?Y~ zn;CD~D75~47HdVd9+Wrti;x`cmB8#Oc|*xj{rkX%VO*Hibvd)C#)bw(DJI>#;|8$L zHBdx|fn8hBq_y0GnikYiw?XO*KWDu%1r&r;stj{6FdL`rlb4C-WG7$mUjq^Cyo4^2 z4~FPHPQqS0oazn*PZ1kH@YEbWc8mqwI-wH`$g$K5yj4FppJJg`pj8tRnF3pgbT}ln zoZZ6I4#Ls;Dz4mvDs^hBUSK9c25qhpoF$9jBt#v5RSxt`q9;-`m%`&7(?S*OqKp+B z^y9eZ-M64b#2Ncx*gvnk$D{jUL1a&Pi_PY0)2X-f*>Vraob<0QD#5+0G7f{1x}dujI8I=#(vlfbQr-XlMiXNJD_EMeE!tZp~|OkhMK$riKk zYLv@DRzM@<>I3vW;!AEdN2>ZWNPCSNTTCFf8iga6%$j5fSx_P6{o{0L9t7e&NbMC_ z_W1X0iU5WTjccveJ8HeldiaGq_$Tz&=&XY1j19}HY{q!v%dQ zTr^=^^pFOwV!~cx2a@ECTC|3MX_F7IUAw>C8_LGLX>RMi7I6eU@tr^k?43s+6`W8f z|2Jxf0^?M{5VmbnR)Cn-&o%Wc8+;92T@j$O&|_f{Wp~weHrflk_i~2=_bQ z?g^7z-jTIzwdXNy9v!2T=v}R#jApeF$MaJ;-~MPGk~B|kHWI}}x)U~+#+=)!~Wqutgn-1d609hIC;VqB|fTicqamSQ3!RI|PZ$1lCWoNoz- z&LmoPHnp=340&|Ac6*_|_vW+tZf$&AUuX8j3qI!Sc3U-P_iHDMjx(SN zI9F{iy^iNo4yPu6tY-~S!mA|}D&Vxt$O6%;Xo=(sS;g$GyrcY`Zo=_Iox_pGV zv|rH-lRpS*MfyP`o>?Fz{#F3w%XG}MvpE)~q#WW?yCD+!&L(F*fsh)omF7(HP9>9} z*%3|2KIIw6g571qJjT<9>`j6^xGh25AVHqgLWV=|dJQh=TWEKf7I-z8+9$@^^P3AXteKXl$xdR#=asya3{+_J+ zdO=BXPl_R5Oam5^t<9OWPOzok@DDMv6Kmo8Xb(`QFjb9yZx;Rh%K}IwWd@ubvMU!r_@=vteyfN|-*XrzmX(wLy8bZDkC^!B*#HC_ zq_Qt_vmer(?LKeuZ7bWmaD#tKRxV|<-cN!1Q_i+HSNH49`qWoI>kTjKsebYY$gjgP z1N-xYR;8Mmx2SfcLzXej$6)0qwa5}Uyf{-BtP^nMF(Up(B%x8fiqMPMW>KL96C9pA z*)b;sS1v*p6vkvRA1IG`&qR)fQOX5R@vL@Z`E5*;3nz1(MsBd(2IB)W`u417H3X?6Di6EWW zMZ&|lFf5K}g|oQVB^2B{@ley)*rP0#r3R4~855`vpLicWo|@iPck1&b=JA6^zNUC+ zvCQ=fvT-yE#79pcE2$plnL0|SyKGW?Y(JtpL*WCSVUNZh;JHte@~<1J)K ziS!Z+u#^QvCoN!_z?K(NF2HnZ*0S93bFd--FKE`{SJ?z@JbAH$rutJITp#qW6jN6 z!Os5hzOwQ8H{rpGwyr!nYw9HFiLkWk2VEXOx&-H?5B}vNmpr)omz|Xzg7V_7@`ssb z?$)79qahj=m4v;cH0hDFbcgIryv|!2DWc3GhNu7#b&0Qst9z9vzGVvW_dZk59iCX= z%<;^!+~R!q{C1Bt!ke(dm#%h)vDM{ztP)Ft*)|F}O$K_qa(y0~Qj;kezB6^@8-)C- z#d~1*e#yi@&4STE^*taRe5b2SiW_TsgfGtdTAd3gEk&eFDjEvJ_GO9={>b#Pw=UjU zh#g4{T`3z+pfT*5kbC+2jDXr5O1~5=x2MyE^a1_Ugl7-3=vCNEtsDl{Mnsg#rj#3$ zxme_U5?)RuHX=b4)giGn%JRs%UVh&LB0W}wuVOEO$vybkReTZiV0Hx>HJ)o#k^QS#etH^ZPEqgvFwyu&p5;!;rMz3Hf}KD!qL&p{)stJTL4j4O^5==!#iOa z8?NTspPgO9l<4gG5kc%Let}PZ`nHmmKQsI73s8|#Xt}hHT z4fZJEI59JbKk2cgltyDWOG*mB*oLSjuy=|h`6o)5gnqOM>Lu8j4C^S9LKM;oJGd0M zlk`$cnQDvAL44Zs4>^N8&I<*5hRRoY|G}#fo;$C*22AIRvVKBD`j5FZtJ9Cd)-NMq zj}$lCQF6yX4M>7fC$12rH)eV-*-nmwX}MWRG0|~&lA2e(Dzt}ka$7I zA!HH(s8a~7g7P&GKQ7k?vtqj5SSVm(QYaMUlT4)xu@81{eRW;OmA*^0hJiU{qG0AN zJ~g=TtK$cHc4SF`9BYkSNrOOcuTP;1N4Rxe^?b4U>|5_PiqAYucS02N1ERID4Zhx* zBqD=|-Ry_bw5;Fpu}O)kYLVC71km% zJP;0+0fLK~JG|Slj_jm1Fz$Ek~`NG=DaJzm1$mWbRTNge# z9?uNj?lOBLRFG&Zw{yOFE+W~1;voLU64C6#QwJ~m+|Sn86mxe{xkRxGNGdg|q;Ou` zxa!wrcW@8#zL&zS9zUAMA@Hvy;#o^_jl3Ky*eF+ZM#Z^=VXDVvg?np5|p?w0t2u9wBRMSns-Z36v1DwT!d6@^}(u@N8mrA0nbi;8WI_(8& z0%7V1wRnQQJj?~-Shzkt#8Q3nSnHoEZ!-a>>a}vbOy1?{ScTiej%6jH7M+`tBElXK%?qPC1g7)U|y7!BF7h zv7iqt0@1NWTka>5%b=Vq7-Ov?c)V`~?7fk1idFAt&8+}Co%`XPPmnkZ9G-Nn;C3Y1 zF~%)LnnPS$=>jFD^#Y)T8cLwa-GMUJ%;UcA9l{!BK#20hY^rJ@oFkd5&k@{G&0F`x zkKMqi+*fmfvKvkn4G!{Xn;)UOQxeiamRzOotp~BFIJVy@^zNqR>_0jmuc<${k2L_T zXa^dO^z~&xqe#ji#ehDa2y#2)P*T+uTO3&1Rej8KH7$OfEbdApjzg0EI)mp#8Xjt^ieWt1m*q_!YS#T4=}Yj)=c4A`5pOZ;Q7-Gut&V zj+Y%alDQ6z?JhstJ_I(hw%1`IV5v;6MpyW!Ox4U_EV9Bueg)fL84}JwE&E}ut|rST zg!IK&tdN$BcoIrJSrI=5X`5|nYX#j(aWgP6J<^i?-1&>fLcUUuDm}6=JlVvHeW5QNa9l z&eiASJ;7tFtcX@|qHEolY@+~)IG0qlym^vz_Po}vA0xY%%W~FU1lpP~{hP>C@utFy zP|_|cU?U<(tHf**+N`7Flp+xlcDj)#v+?lX%6iHGMvlY0q`~YABy61eISxad`M?>w zg2fJ6-Iy)=s!z4w*d%)SP~e9t#b!xXtR(xxgMnh~TkTd$^f5Jd7|rAaQv>g+pPb`_ zf(*LP3bT0?c1SvbVhlZ0OHR?3#Z-0%5gHYA~qy zDu$0`05UOxwJ1e%JJlIq9s}zN<6qy{LsTHzsmD5jnOtW)Z+mMg72OrdE3UWBG03&! zm-9v0{QTtuC|-D)M`;rQ=p5@J$M+R1IRmQzelPvVMTGF&BRi`^tw^JiFi!l zWIvO6yp^!h0d&HMt2kOhh->gk2m~(307x8&VmW(3*4~y9ylbq`In1|3xn}MJmc8o= z724PBbX?R=Xv1wif$dWgwO6fAA6}MFK<&@2M4ckzAmxTdz%0#b;emOO)Iu8L2hLL} z`1Q=cnEQlpTs!7=C24OB$iZ=(MxgOhz9J+IcEViTNS(wEsHPIAz3G^CPO3c}T&M}mbVe6;^ z6PLDI_D$$54-tk7ri>E#r!5Urgb5F;32ku?W5PXRk@jPayYoV<{8kZK9mY8i>5Zcu z?|a)MpN~76F(u(i9B&zoWNXwARIc0BtOO;MV=nRekC_I|Gfk$`x@y>i68T6Ujkw&~ z)mzn7FA~`(!=FXZfOS{`{r)9SJ&_GMbZ9Dy~s z3Ll|sfTAr9oy_0GjJDy~N$1*U%Oe`@W5cK7B-$u5Qp9rg;()`!ixV0(fI-stb&L{v zcTRQe?#j{P+=l>fVuSQ)0AFOG5U)ij-9Xg3h{E9;L=KsYYZWp;PA`e#?%x^Y6_=Pp z4>|yXSBQ`%Sp1cYtiizs#>Rsu+c|rQ`U~!{j zwgiKfuOBpLXm()VJUmr0iF1uvqU{gRUShP|vgVW4aKkvDqVXIGx=GC}7Db|oDhtO> z^hqb;tzztewtG-3`dgjH-Wf=1hiI=^W;bq-TWk5Se7P&!T2 zI@NeG-jlM>h=(KdL%Q4E0OLEpkw}9Sdk%k!zH0}y*IV?MZu%q0@Fs)q^@J~=BY zVUD#5iIZ>D!Hh$cEyN}2w9;`j$kzMp?Ft*QT6+m325+C7jy8_w?qVM}EZGMVJQz^V z>_l5Jr4MhPXja-Ui(*gGAp6dU6&3a9I9QxuDvg53VRFQ8K_G&)2$eiHZcnxHl?zJsP3>#%GPr5&yKHC6NpnClv*)TB{DV>*XU zR}$yE^dfRZO}Wm`YUbS6UhoKXfOi^&x57&CF{YmVQXbB#kqx{A`%ct?8#XW$&d(e& zc%c=H%~7UX0yp_K)z}ye6#$Vd7ug*0lgAU+?VkNCa{i2Wsim{H)XG=J&2BdORa~PD zk=>2JJo1*^R$z1EN6!Lgv*9=P6G6EbY*9IXNDy$xhHb+`eeV*VJHyj`?-yOg+y=zN zB74m}AKw!HufVjg19q6^d4j<$3`g?xXes^A$J~glXE@g026p}D*+n|fT1^)=QTOa( z5V)>zi#7stOE1G7Jf2#vXLiS)_9s;w>|j`!Y#T|<@XCb`tRI~!G4T+xB=L472vnKM zzmm2aB<1oGqBTXORxEtI%G0}`lLIkFl2Kqw0L-YL!k|#>>=aH) z&c0s30{<6u%v&%A5;k!UNp>`h=kFyST{B8LzHJx6o}|6S)CikFvGs8)?%X?4d+34& zV395OP1@>tulr$2!F#`&gRD?1@Ypg(x|SDsVcX(Te7}J0B?(*t6(n;f*{@v>I>8|L zu5$C3E!JJb>}{V8Phsm-SaMa)v`K;l(ZGXp45C;s{P6}O*Ci+wS`avqU5@gIDP~Ph z%`_v!gb%cD2LC1&R7k#o+ENjnZO&46l2>lC=Nw-Wi#X03X- z012lQN?8c792QKCRpC?F#RP3L_?+Nl4Z9`tl2398cC0XldnlkbO^N@Fy4u zo1L@kWGv{}NSJawgLzWaj_LgV59!FQ`SiSlIH=Z)6j&mF&W>C+Ck2+{k$Mz{saKq8 zb}08!C38D%D%_f^yIbk|&lyiN_6u)T7rZ&A)YV?T=X`g`Y15IcWwPy^WB4Bg^S(JN ziD5438%5%$Gl{b|8OBQ({;XRkafJg*FoQfWP!Wkk7B*=!Jy(^A#jI}Dt@~N0wjH}i z%fnDqBHMZKkX6Z*&QAh7n6rE`p<_1jGNFFo{MP>I%=u}X&VTNuH#{tD#=9DA-u2Pn zpsqjsp@b{?v%7h{==ea3>20^Ma^8^w&J}{5aHn*CpTE@J-D!rKHPvk=-}eoMDJ(3k zOY89(Gpmrn)s*Qar4p{WhwAF)pK{hOv*=bg9N(|*s|NdeH!z+M15$d4xF~Gwq24Ms zZIAHq`kv6%J+0iHzPplyz4;+fgj?SfcNvKnT||eW-GD39N*Ch@_Ry?Y)7sX~KJI!n zme(>@t6MWjXYM6R1u!e3ek%oL#Z3Xso07YM*U=1~`^l&0E=sHAOx{U5o(BqY_*R$jo8MBw9&55isVz9x>dw!{I8`8m@vM9I=x`3z8 zNK{JRf3~ya(`b_MC=27LeSMuXb+e-D@u*;3E&aNFt5WD*oM1hh=W8@jgnc0U2>Rx< z9^M{7;vznM=-FP*K_W*jm`Y#epk2(T65s!D*f_J+HOJ4hcpPKxWLWOK)2z2<$Y5Jz z)&5edyM|xapH)LVJHWPH$PUb)f*)yWzCsvcc}<=hrG-7S7xCh4KR?NAhWPqiJ^wkG zTf`}6Q`q=Y@^Whi%CbmvwREP#$^Fr{o9$<}kCOYrS#Gb}I<{wwdFhnf?pSZXh&O0( zzolNW@U;HcrI#IFw~H5N=i)@e;dFFfUId9AGG$`{% z(+dQ%)%m)6Q8S=0J_`k<{lqx*J~Do9g^_OrgfNPoe7S)@8`3)4ge>KspC4ud651Q) zHjbp&AS)qM6L`Ya#=aN=+|o?>@xQSGOf5|*YMB(-J;laD&8*lXzwF6gK(d(@tVOz@ zFj&=o35kz*NuZ^wo~{THtdfv9omc6UFA|Ax)o4`LkN_ED2vM+9gRRM5Bd6x;nuMf? zdF76KEh%YREn6!UJI?yvEHce}b{L-ihFoIq$j6GwHeG5B@7(Sb&%L2M?znAqQ+i&T z$ADo{#rUn3p<#>k%WEZg?Y>}@`sAsT(rgv4zs7oW2X9toT4#jo9CH>nr4`hCnJx?Q z^Uu%HmR6KkqmYDD9jnFc5v>wj=pjQ(IFl{1_3VPVmiqRGiW`a>xqX{k{=NOj`cXv7 z(j>T65TPMY^V85fTXTeMH~!zn#rmu21cF zL4R{*%YP6Cc$TWP@hkfHxQ~D=&aW8L*$S?fP+h0;K&6>C*2RErPuB{Ym*j?RV+K&& zaih59<Nh#@F=-5&ZOc+*fjyt{^T73z-;Eqf6V8vcAZbkm-plO!mcH@BDLRTFYC#7dUI_z)<4I1!1G&YhjBuFSKZ@^XoK~)77gRl4Z}J!5O#q z&S@G^8+BAo`H`^ZStqj(Iqsf`Dr@V*%0qkhsE+&tTlJiDNimFW`Kj%D>V z{UJT*;%@xN?`NvtFI4$eJ}xSEpcI!6Ni|u1$a+omgv{%H>Q&ldzkP;vt?O5xHBfZ} zfu!h~jC4+q!`~XPQghX|AE%)LgTh_0&0KxRAewkOcyYZ9-tsIYY~T$8MerV8Wu zQKsj(jR;n@V3q6CkQe$19llq#6WgY-Yy)nvea>=HH87!tpOf72M}tmLo;zQwdS^K0 z`ZlTS$#il&x`JkHM&$K;x<@XC=WywFwM;7}OABV+?+2n8pU@{ENRHy*RlcUbP1}Fy zJdcKmjLelg&V|+?NPnx3H`bNuIFdPIqj^Y|@fve47nnsZ@g~t|R#(YjUYS3kowt{O z^mv+w(@AjwV@sG^l;y$XH1&X!M(A?{Fqr#wuC!9SRKI_=&-ygmtPu z!~jn-s$wsZ2N^Oilo^1)g+9gCZn5j$8}|Txi`X{g*s_`d83FnR|~wt3h5VOW=?ZG+HBJ++{% z(RlsK@f3@PZ=Q?}sVZj$dp9YMcNj%~m;(jIYnaM>fvJZj_gbayTG7g8ta>povOS%E z4iB@lQZ)cHBR7~t=Zxj(xR=VT*Yw4+xq_x5qD&PSNVsjL(C}LA0ldyItJn z;?}skySvl4ySsCd#%bIg8gJa)8~4Ut8+TdWo0*M$5i=VrQ&srvBzHfs$U@I3UwJd52bg3~G z`O}(;0evu97LxtQ?q;lPU&ZsZ`MG*?#W|Us>oqT3iQ~^(K_&1)Jrre1q#&fhS5&pHOHuVd8bm?I+poqB4bfTyy{rueEM)Q>rrG8Pr!YbiQ7=zVK_l# zeLd3h3RFrlmm9We(G4tDnQ*g)>R;)O)(o6d#8@CCQq%C|d3X07%ByrhM8sXUD5Dnp z(M+$sT=boiwyRVjejySa=ZC><=lr-a_uAqrU;Q_M_W9_`y1Mv8ev5AjHuH2_zmvPV zGl`p|7=rWu7Ln%t?Rr{}nYPrc#G6h#&^@Oj)9x#;^(8Pbw7EG%`p29L91V`l&X5+awW&pzpP~z*J zkL|*j?@`b0_-oXlf_VAcM4>=DvZR^F9omGcwftJsF>Y;~=Na>80Tzk4%VR6HxqK6N zBhjwz7rDN5Ze~=&bCFXTC%WY;2Ip4!Fkl zrYsc~NvsZQcct=R8kgO3 zffa)|SB$^OTpu|ZKTP$Y)+TSc{^O{#_eR)t?R_j|ieQ_U*VjkfSJbcl_(4HrG7P-b|Rwi_sSiDx8|K@oeYEENzUBz&knxPn zdE9aL(-j4i8mQsNgDU{)h1FZ&piivG)SamnhAHXOjaJEN9mufSq9+qq712ab_O!XS z>-RsBPwV5W;GKex{!v`~0SW&9Z-^QB4CsUoYeEi>fcKr(b(ord~D?EPT#Xbu^0RZLJs0n%?&fs*0IW`^qRVOIR#dY zDU2AaeO;U{T?@hyEqH^>6Q?_?%WtkF=s$gSZpwcC^0Vo?Az{>iwmg{lvW#JK61LA4 z6kF6AbIz{H`0m-IPuNu%C7XF#Z9K0tBV*J6pZiB>ntBCN*frk843DGze^IdyiH(#W zw{R9Te(*{-wW+;~7IMU=G6njHFy@Vn+kF1+{KgK>F}NJq+`l9r99tld(BoCo!BFP-akTQYE8^)D-Rz2e@ET{&+SAo! z$Qoj{Ox$R?bW_B8*HFcUM8-Fxo1^Z9ELqLu91HZb(5#n~j5TAWfF*zJ+rUPO`=b(_ z(_1U+(^vC~e}ATP^*Y^5WP5lwHm1u~{xRJ=!-JmWDap=$3;TOXbTGATrJdH#Z+N^7 z2}|fm*Sn$Xu?3gnJ1tQW@PP1xdz#d|I9}gp9fbbKK-ue%jQ-Aczvz2-4;O>gP~QD> z_}Fo@JU;ou7p&d)pvTiZ0jwR+%ZWISzTj?I+&qD@Wae{NYAbkgWM(idARdKJwq`Hf#R z2t)8l-FDMj@>;i%;<7P*Y%ckG(=_iyx@W53r=;u7kB7n^4)ot9w|0IYaOLe z3M>z+{kmyh-JHMr{V~gd6U(FOP7lMy;;7o2R1UqS3%VKUa?)kc$EY@XgG6EHfu&S; z+(|VdM%Bmw7jzoCu>-Tc&9v9Oq0y8e`QqV%!?fX*;m8)&rhqc;F;n?tQQJJ%so!n4 zNYmlXRh>_;u+jHzr-IKqd|}*OS7WcIobTpN3+qrY5eTsR0?Ma0#!sI_FZXJCf%;*$ z*_)0#7?M7VJO+uxZ11wndR0nI-yGuMyTg24)Qh#0T5QHhA{sE_)5Yt0 z{!z3qUPi1Cmks;Fs=f6P5`D+r3n5zf$)ufwVgGE&KDEFa0l;gv;4Rw6@otn1O& zexh!97hY8v?NuYAJOB#ss<@WNCu|lF?U>o#S(7~Wks%10)B;ZKh91&a71XqEd3Y1y=|AU_4!p^WniJA(PystO zjfw4x`rJO%C|YSJCB7j|uN+NT`RkP@td(am=mbdrlv`#MGp2JwGVpkcT3i(~gXD=b z(<&dSeWLOgRYX}hA{|QjAnBW-T?_~u1ZG}uceXw(cY47&zx_5 z`s>5l`In#B>rVOOT-@Io$xpVn0Zwuob`DHO0Yb&YMJU@sy^9`?G`&}h4M~fZRPTBp zgXPi=RQ{f-E5}bAuScgH<-nb*oq|5_)t{XCagLgfgvv2aFR-w%FzO)JZRMS`&kqtI zzvkE7td^;1PH0na`ZncQb z``2AvlHGeB8SilY!`ysP*kbxP=sJwc+xon~DLiXlkMd}Gttft+PoOuQASf(-?U4KxBvRh+vLfBnx+kv+S* zy<&2G3b~1dYE~z|KCZE&?rhIg4HtjsAuU;(?ncu4nUFgH3616Ov>JEW33h)UQu+Ac zs0fQ;u3rkpvI0(8btYM<=QF!qHZj@0&AEwIT6#Cm?SJPVeyO))oO5Daw(u@jA=agz z%2T^X$JBVlJ#L3rrmDje&U%o4*m3*kG+udrs+RG*ge5vISRM|VTHn2&zb;0g5O+ACv%_~`K0T%P!3)U9u~{mrK7~@} zSSbDJjgfLP+o@Bu1Y7CpqH*veQ?jd6ga{+e9%(_qK;5&biQ5F~2x*egpTf=W<}*Nf zPeu7Q$9meAS^Iu@XMYQ3zIHu>N{lgX>P)7|L7%8H`FLi}n|#)aqM5g1MC_UgN24bF zB}d1VU8;wGSMxdV=_B(|CcslmC48%fL)0>zGL09prg1{Zo@w8;t7{v*>dvjGe2IjA z%rg8pt>Gb;loUJW>%aTh{I&)ca-C~>9KAfa%~N|d_A`mig~lG(M%h$6thBC zA7Q`<-JQzfnihPwg5WTE>c_E&$4~Du?qlgZH-r=H75f$2`$P2mb~F~fg&&#**wM&F zaiC-C4on*ihZ8Q>_JHeK04jVgNLeI99)<%^+ZfTqa<##{Khpd5P(wrGdpreRcABjl z!&R`s750{70Mim!I`BDv_3FE50r_jKoUH6qs^#~NLppETkhzn2=hQ)MeXpJH zW3D@j&qEg;dDNVPf{0gsLHr5hFupInmC>FR{D%8CCDlTSGJp{4qq6WuInVuFY%P1F zrdsr2;bE20I~|@Z0c8e=D?m;{BK)@sh%Rxi1JY6G^LZN4#w%f@;=#(zHInd2#%4k( zH%|s&Cc)*6mE28w>&#r-k7M8l@TVh^Wiqy%`7sBeD8y1HVh+*VfUAilPSR z3V)u#v&;^LPP5INygyfAW0@KO*wSSCUba!E%6fYm^RbE*hYA+N2cdVPZTF*LG|ou! z|MvaYl@{ZEw)8#e%J1s44I^fVeHkF??eMo1FdsxNRc2MQa?3r|N%41^lF+-Gu#}Hz zTg4rtc5*(v3Yn{oq64OE)fl)45d5>S9$*oMwxEn{U!YGLXVEW)K*>`5QybNqf=kte zW@zV5S&T|O)pi_+!K^4!msr`4WuCv5949lVKIwCg3!edP$ zHELC}6Ey8)@jJBM$INK_l>$&Njj+I-o?DaDartYq2l)vdK zlvHr#bC|0e9Hwo$R-aTZ1!6^}`TGD&r;h{Bjx^*hOafyAm26)Mx4*GBqs$6r4HB&n z)58Y&tvFzvA1vXFy^z+(t^B3(M4t3omzJ##)oXwoW!ie!JZvP+RiAY|G;rL87 zZRZUW(qfwI*)G9ICb6=B4HT~^s4<&~pNvVl`2^g(7$1~f6h)-)P)|P#&@<&(p|BRD z^~DB)iP2{CKfhulYNMNXP!9U#g9#)f-*&*-o(&-M)k6a)1W=>JIM!tLL{5c8C?eSS zL6w-L;FbyHZNv-`pt2t-R7%%iU9(^-G|>QmfqPGtWhwnH@Wo5_acv^Tr z@SV}O8R^mx`Mux1`$XnxR_lhlMJ17C)L|+j*cg*{1z~MNu#gAx!(Pj`ij8=a8^fXY zjB`kkm+w`=+Mszsp$n^kvx?wDb71}`Ey1WDPVF<0ijaxdBA1Q*vZR!oHxsy>fAB+u zQJariEf|}W^I(-?NizweT1KdTBm!QH)$tbp%)%e+!$-k5KBr$s5B%8$y71&z+8>IEZ2R29i>Sgdzg4y2* zS|D7w`lU_tJ2@Yw=)3^N3S=U_(Z}noX${eduIEY^^5{&_POO4MA4K{+W}4@SaO!^M zSWN`Eo7WH{4psyz8s%`NfEz4xU5bCVKr+mg-6nzLX&TyJ+{74SH-Y|ohDriiLT)*h zD#}(`5;lr~tV)_y14y4?UFjaBT8^3nlEjJ(mNILFTmW;u4B`k<-b>ZfEtc})D~s4eCy1u0xLC?dQrhTMy}sVI)j8ecvSbllH~fP{@Y6VMYA5Lf`y5J5&w z65`p;oWnjHZb_%mjm#hwdvVs+7rRyQaY^<&Hd3{@bV`WCpRNu4AuO-FM~jWhamV!j zjVp~ohZUcLM9uRo9nMw0$4gA+7)?A8aX+I5u;8V?_*aC$86AqFZljUo*uy!jGtsg= zVsdE7Yllvi?7W1j6TloCs_*K$YXz2%))HMKZl~OoXSdJBeoeG!60zXqnN~5P)=x-98|+lVVhm*HT;gkD)dLJ$l)(wfiyWc zGUDfq{z9*7K=|`l)09~fRa?Q6Po$oDp(T=W>6zOl;P}e{Leaaz<&&)WToi8T3!}n# zf|=qDDL;f^D94?5^g%6{|;|J#$rM4e2bCQY>r-5@GdT|Bk zW%u075dC_{tbW$5eW44;7f`s?f{Q;{(4vV2zE!prDms|FGRyB%ZA5F{Derk(6{+me zvZthS;cq{~eD!U9Ci=JV#F9l@-${LNze`%r(`Hs(9Z(5`*g`d!wZg05p#Dh7w6!%` zP`5)q;aCkc`N8%Goyud|?ihFBfu35GBQo>&(q7fI1 zoiNxGwn?Y32uCM)L+v9Q$S%)kNr#w8AO1}YrZoofQHzZyH-vk@+sv#;(?)=m&)Gzo za5`=xp(m=^#6Y>g>XBE$8I!^#alowLC#tGPV^R=qQ}3X!!1|*v2SKO{jDx=pDS<_E z0<-f2Hrm}+$nnIA#?Y6q7*6^~1({F!SRs^#p3>x76GkV{PTSzL2sbcmMhmiPg^Ryfs7c_ z8y3S)SBj8k0xGcHlpXfFIz$Ts42}+j9piGf?%~X2ny!&{khtgm(waKYekX#C3KfB{ zk+8RhhBwYQ@p4*qfbSGFl}S!0 zWcMQok!6^|e}N-2I8Nz9sIUB4S;j(lwinlj5Hhx3%AjZqv43{}gP(?>!b_9H{f9-q zY0_uZgB6P4h%EO`I__IwCn&!CTaQ#&e3($P^PVJOrP3+;E}`}&>KJLDXI#-!}Xqp9GK;`1;7{!SolJ@O@aNS(ZoiGQbcf#oC{*xs`S z8OO0CO^5tDAoUNH?wZ&EbbinFLLZnnov=SVgc5YSf%;zr&9y^t3cAY^aCHGL_pVA9 zz($sroazZcp8{BS>a*Yc8SnXf!A~0m~;5Eck*J_TiQ#pJEZzN>`s4!(p zdaht$g5n!-yu`U2a2)eWeB>Wtyw<98iTGe>;xw%r^P$5Oit@NK9ZgK&k`-^EvI>+S zr=ZAEsbS)89+f4=BlLX`!^IHd>}!jXQ00*2;J`JPW6RTM5NEtgxs-p65ngg7M$lx*9Di1A=l5c-s$M62*AytA@SU1K1{!YeGMISGGD^wW-8%{C%%a!~i z$VVJTU0cPFL%ePZFFb{V!$ktx<*Z;zY?)m7b+4a%Tx=PKewkd`LQ=Cv?OtNifliY-h7#fb>F~K zr>8v^S*6_Aj|*r)E8*VhDbvB?q|yTW;jRe_D0Ab&u6$5z>=r)I3a9;^TqG4oEZ|Zs zw98(r=1R1RW~>j86ld^ORg$`;A7jJ4$Oql+yXTh5iX}4V&}v<@$R<~q<6JL-tf+qI z7wTG8lCfbXwD+8sxDQe(6nNs=9_W|b5)>&4Gb5VQ9{DtsA zr>N@TQg>o3<9Q))xHh%Kq$`_ul9!<482(C5A91($c2biOB;~i_5D?!eK5%WXlDu$J zWKYgV{H_@WZ0onp7*ftYHrub!rSkJ=j@A%37fa2>rQSH|>0Ca~OZDYwd*lQ_foc)F zyoTSB%^H5CFb6_V~JzCgV2{LATXaLk^9gl`4EXapAF>MtELA~udc+6H7; zfLN4~h%k9~d`XTf67k__O@vGy)uO? zE(DI--vq3{ToG}Y0uTe09Siv`!Zv;|PEefG9VkB`#pYCFoT3BFO$6%t{llA9fd1_8 zpomRx{JGgGV$`eB9TXO%Yx-c=ossHJZf>ANNIL6zrj*U>+#d#?m@a!8T)Tba!VL0r z{2ZcBi|hP^3*r^uRgPJtU1oDA_QOX^prceiZn`|b*IUKYdia47f=PEQl_>1K}l5h&KM*-%GK#W{ovzv!Gbe%qZSOO>vP^R?=AZn zA@!HKi<>-ON}Lo_3m&sQD-e#v4>7WT5{xC+T`Q!oKA-IM8>gND?sGv($>fq584&eE z&Kyzu`ASvFJ8b%03PvlW^YUg6Lz z(yhu}dx(f%yD2)<5)@jsU_r1evAP=GQxdZG3OD#SgqBbkn)M~ z@1@Mdzb3&IM|A8KjenP)=|t=s^_fAH?#M<0yAq1;0}@DgjjQ3K)PnoLcoqw$GbBeF z7<{cVaXwuY!iG=YMBVeQPXs0o%!Vh(jBy3(RD{<&K#y#-*Aakv?#eXZE-+!c&Ku(S?PCfRf4P-XhAAG^{bi z`44ux*lZ(4QL&53AWSooTR0)W{{5MlD|x+TWae&n7vhX>i+4ZAj1hI51sM6mOG$J` z@%%pg2UozIMGu?{Umc*numl2sNC@u$Dx3nuz-$y~O*{kpOU*H=X`gYc)EAz)4?3$7 zr6WNUYf4axMV_h;QvSTCn+Ch!fG%QqO?>r6!bh=8)3795F3F55Qg1<*EiXH%D+xVb zH0xGjh8-Kufp1jksTrjSV=*ld^$RQd#ggOJVeg2aM0S!SKna`T29ZGva(S?y-s&}$lt*-3G-KaLdY1fQQ9(wu5RqUbfL>)-)Ky35jH3!YEqSXBAAC2Z zPG(+Th+SUO@v$*UD1Z|UY_OAMRV-%Yr`8i9OPi{WQe#UR*SFGSH&)-E4q`xus%1Nw zeGoup7yn2-)4H!_$whSeT2FbKa_tOf=JX(ZEOW~kzqBBVY?nLaotZxt51r%CbOoC* z4@e#otCDMCYm>H&l$7_LXk!b8G_9slxZn9XUyOUoPiN@Iuah-9iM{{Vg0dsFJHb?< zh98(Jt|mn;p90vy6!!so&zP2v9)wAls~ysg|BO0p_{imC6EEgqFkT&3chKTAX_XNh zOs>A}Qe&%M{rBUp*`i`IB4+L#t5|o09BndGN~Wq%YlFi1n3jaXzt91AeDWCf1TtA`N?RfSYs1fe|WStzIx z)3rp(I2S{`r)w=N{;iNrQrUYj2rkH-Lp+@rK)^~I{C4}>Kt=AQl0d;aP9_r00|~W0 zSFe6XG#-QynxsaWw{CM-Fts$2f{QL^0&q`G!H!cx_JQi-$UO%A`4dtqH3R8IA-j{P zW!>>6*=0wJtMBp+>#O^CV5Wbx942=PRgCwl>n5$BQYDvpPh_bHW`1kJf^|%hWu-~# z!kSyf2NNt~2&i1iq$Jd^tV$=w6P<aG=iOAg^cu9cNsJ z%uF|%uLsM!&mf-t{+Cp?9;5o5cb=JU(>%IMsz)j|sb!Qh(>j-bwXS|+SdoLUHn_d! z6+%4Nu9lpSUJ<6j@aSvDDdcVn)yXaSQ@iK)XTG3)A23ldT;Mfavl*x}@>LSqjEPl} z^en;{mhdKeVp2cVdyv*b>RKIeX91=F8FVovdyAICGfm_Unq#u(#vuluTplSy*7p69W@wpa^2W-BmbrV+S^e%CUPf5xo=T8&yJn(b4?pE53 zx5r_|_@1BjEzy8LapeCL`NnwBxDv!SVA>8hE3ZiiEd`BNU3-U?gj)ySDKcWJRs1z? zj1Aqr#a&QB@d2GEx3ULgl@3Yivpg45y%g*nzODq1{rsqP3+yIB4-9R!+7V>_RA+T=J)DAGhL>mCqv zz%&;-w}tsFHRr@&UwuwU(2$1* zgt{N1kTAR_g?mNu%|-pf&K%hdwpcM7pd(pMZU&MoDC-F3#C-e>Za!dc}DCq{xQH~P^baxoTEkYmKWjH zpk-NUNy!JcOhtsgTu1KD%zMFH_1n9wRx{2=gKWJ+G$M0Pp}V70cL}G_dsT~=aTm+< zlhG#-o7TKj#u(bQJ?1KNgYzC3Hbnj-9`JkaJ3!M1M?F-g_^$_CYhthNK&^Z2k0^h8IZa$j41ligu@OO85N$dfVO7x+|D zRAqYM8X))lJ58>Zp}A`?>pjO@m(x9_cH&8bLKwS$8kaO zWwuRl$t74aXWzpoS)hs!*ru=A`Evo)6k2PphQD7WZTG@ARRqX0_c~_8|I&30_im^Y z<7`CC>wVMPJ{Io{{wzm|x(x`D4_tGog~O1c)7cbfVeQGo=x!fi_u@!gjTJ%@uUX)~ zhFhc>WhX6JP)i_>3YY2t3l|CS+86HnBzxN6E?FFI&;+{4Pzw}oLdbS&XoGni( z9%Eoo)o~vLVgX#By>npWq+QfT8*P+kMO(B%PR`88J(9Kh~^Nw7oCBm43fK{otf#$ZLRL}7Mzs?0}x7)j}?rGD!;OX%bwx^+Zjp@@_ZMP39vWJe| zwAUe?=qIXcv7UU6r~-* z7nWFm>mWIrx^Ymp!BF#D>@(nDEjMH@xRmqZ#+wf4r@RKU^CX22oQ;Zl7K$Y{*SsYc zjrC8s`b%zfc(F$s@SAYUZBhYk`%joXVU;d?2%uiAvCeI|^t2m1Nyx<9F%dH69$$2iO zmGLH1tN#5%)Xr2mtFb$55*0MBz;!i>SA+X%oEcu^JCUF=0IDM%rLL)1ABfb1Bn<@a zOuQ;gT}+J3swDD4QRnS?4Z0FER@3}>Xa`e=BZdLuc!;&y$-Xz0K#DyPW8`wUCMw-b z`LN?|;RK~%#Zz*C&-`n0t_Kq&#=GHNwo=`?`lkMRv6;S_l=su!TX5I6`yL0y<)YN1 zr(5NCDHG#G@znPFKq2TiF5e6Z#8#i9XAsLnfn)3)ETBtNs%u}Mg)Tw3gG)b4KmK7Xnz`q7e!SboCDauA?JC3db31qr%1Q9H1B3FS^~Z8x9rA%A zgP>==$2b$;H=^RG9s@+C!2-rIr9TJg|NOGr#uv^XcEn1u!uI18CLAs%viekVkJyB^ z+c)@}Udr+M zN~o^pu8UgMpK^Jd-eqn1Yqd(IC;1xZJyD53$xWTLJHD$c z#UD3T*ia&T4sk*O=jHWmTP>>&4$i*gChDxlN93-cdj1d35l8YHGAdlGsR6c85#j&F$kvQ;O$ep`rOX!TXcv%v6pUULGgka>HB-l*sN_Z%O1+{CJTf7J6}+NxFGP+qF2nj2DX=Vk}k-3PWTZ)1pU8lv*5p;;QV7j&t;|`$Ti5( z)3zf_D-k6yD0N&!&W-g4BlCh~%g#(Iubr6(w{e)?6|?>de(Ka{IfF@>z39~T#!9Go z%aDZ*T#K^+gST=7gy3`YCIy^(h;+S+J~03GO>gy14ZQ|%@8I@Mur45>;W=P$Zxj;y`)B_pH1j4 z>o+l+&F)fR*U|#D1el4`G(a|`tnl)>qtaJoI%3c(>nB@J4Nu`@FS)i&oT|9K-BjS^ z(7?tj(SI$v=Cc>QMrKMCo=VWgzjk;DI!9n?5&Hgy!U#zkrB4q4Trgo==z}Rte zobjKZVyDPThS~`oNX363nOTpXIM>0WM=!;|EeT!wzfFP@G;HyC4xP2#P-a~WzHm;h z39$Y9YI8Me>kegAGfr4j(!(STA+9Y6k~;CVKayyZz){Zt%^98xDK}#F-DwQ! zjE*Fu(f!?+9zpLzW1nMJ;(BdjctaXjJOlybLWr+0;j|0_O;J=tr0Bq<=y|z z#>Tc4us2gjne$e558mh6?1v8D+o4C*&4H_MOx@Y+9Z>tspeaJ~hIIv>CHN0|d)~-_ z7-PcUV1nk8 z7|Zlak~-%BxqpJFr4Rd=-~6CDx%rNnFd`jv)guY`C!n$z9gO692|`c0*0~L3YE@kIIOemU$cp+#eipr@t34H7xdR z@@(y^QadQ;awQl9umUL@1xbBsOP#@{P(^u+BNwM|1;7BZIe*aRr(SJrQ_TZX=++QUGQJt2Im-U4sXe*nW(xQQV*r9-rgJ>p^mnYP-TB;QOZ?P zYOzI~8ATpEz*QY!_68GmvY?NbocgzF+6&pyOwiYyjer6eF4c$zTjWDvESzye-+LNb zLDCfo@WxSr3RUHQ>Sy~;>i2)~v;7Al{QnQs`~M5do0XOH>-Z18$A1DuSlLLwj<4ib zS^vAq-mL$PJpR82?alUo#rync$^YY4{;xfUtekAz|J8H2p}*m{$B7=umGCJNzqIoU zR4gqU*(=ORGPG$BY@ghW8Y9R}lBJL}=o|Iu^SZS)nSf3k6`S02(qWIdLLa^F=W-~8 zlJh<=vUm|WbU??)y&|Mvjh#kPTifvX>iDy2@#bUs{gx@O=5dSB!GQ$3YW-l~WA!Ko zi!g|Gjq;U+ORRg9<*k!{{uQiyTJm;yL!hrB3oJgIbaaiqTL1ja z%=z?pbb-M5sN-4B=PN9th5{cMPe?D+^r1tXRc36!hn&%Wum3G?j38_RP>s zNgBWeRZ&@YQX4mk{OG3ZN>dMIC?m=x0PZd7;*@FOv4Homr1*uOAi!8O)hRDOo`%6_ z`P*wnIh`ShW|8hI$*xjKuzn9qs3P`xj><DB8igDnYjMk6rSZAzEf-Lh|ZQdN^_T5n+5 zO(IdY#(%SMpf$T9Lsc-4R+mYS$z##X3zi8cV&6w{xd2>29;VE{X0z0pK}YN-47=8S z*b727Gr2^+Uomaxa8b#n!w`BV+de6#49jXRxXWjojWzy4I}k754BaQMSw72#n=Q2c0Oqc!yO9OSc1`|!~#4kQGBWR2%JJ*^xJ3JSJATi@(A-pYf9!E z7?m>?nA*Ug(pF16dTLggzw<98T!F&V78DAZlvBlV^_SI0>^`Qt*r{;I)8ir5sX0d7 zdDs=_!#3KVDihT)SN!HnW|-uXbUW@6bSipl15n;G=71ezTM-udmgspRV3=M%DmJi} z?R(frC+Ex#LY^SsV4W!L^-LegS2s$mGq|XJi%6mCoO$?XgOakDVU_3^4xR6QFc5xN zIpF|uvXfk){p!O}b>0c*sJ{Welgdo(SP3m^WLAEx5SsF0=R@N`4A){{GTp7oOhih>b%sJ6Ea{wh4Xx&?O46?7h3 zcG#-QM$5$tryHcPZfSgQxClX#h6ut>H@OPhqsBGUtW?);er>12NagW|NYuhMbEX$R zeluhSSLYK_FIFo3T2z@xO_&L4^f+(h;^?g474Vo|X%yi-T{vB3t%=KrBml_+^CKJ9 z$ODTDs;;~o%x!uu{?54m$)J|9Rg-J5Csr{dY&}@ZnzcdoE!JVcB5Hc8)s%z2YTK{X zW~@fglW60r7W4S#NK^@LwRx+0ma{=)Te-xi=V&e9y#4^^IEcBu-0Qr4ifHdB^vrj% zsklU$8|gr8(ExYZL&K$PnYm+{HccbaN+C^hQR+1;n>GBG&(RUpbXcY+!aY#z&dBbeclC z~0-lhDND~Asf*xC_%!T(gtGyds zb`-b#5f8F;REiY>{U$xecy`F=PlivC7T(dD_QoNbb;}ri=9%1-LNq@8cTvnAUSx}j zC}v5w8e_9RB0AGk_X`ocbw=bHO0l>7skB+wG;auTz)HSqV>70|pP-2wT!x>}6os~3ds&?z z9#D;=T~&S`!nFDOj(U1G8^5$ z-;pAP<9e1lA3&zFU!|rY8?JyxbBTLII{_@GC#u?bT#vVuNe>9&1HUhaN3L#$8~Fyr zmzlta7jME}2pOxW%n9keSt8``tNK^e^kO-$Cp$}x-7TBwylc@FB+i=k`jgmxpMb(_ znVRf<)R+k;ycnHzdZ>Q-Z@>@WZcwaQ*hb*!0bNRZwA8p->wx>oB1w{%<|(Jls%`W1 zIB}hfS2>pdaI&Aw3zH7~eJ-w_2N>1RdTVKCdZyT+^v*^DN_`mUuG zqX0Vx&83EF2gH6pX_SA(+$c=AB7cgE`Y96DsB&*Jl&)qfqd!$uu**90S4b;EKWGZ1!H&-T>4{+z%P!WB+RWw%p=^5^U(0g9 zKmbefm%;_#W98=syA%>H`)14^D`If98^@}rNdJQtwqIQY33W>YCBw#VQ(Hj|TI`{_ z=ZDed!z7C03vJj;_Am57N5XVW!A$_UsVZT*?~0C9T~4@nWCu$0zcfP8e`%bS;y4bF zrf?ulr+m1PvICcEgssB-OwjNP@PAFUZ?LS+-;)u%W1U+&ic#Lf`dqTn zkQlj#Tm4I4UWni~yV%~HDBj|z8FKk2=WL%j=Y)GK+D)4|+x@H*XYTv>{K3>`M;|zd zAo5#o)Q-GvJv1J?j=*orruvNe4y`I@)}i9SN_f=yaHHm|Z+^!>g=aefdE54^ok+|y zURvAGTsBM*GBnNM?{lwWfF9|%=n@M|509C>ToJ%|F2_O*T1eFWeX+aA+V*N<iz#aF=f2&l;Onz@C5n6k(fNuH9GuB!Tk{RNiynkYiDCBdXTwb$; zvgR5!k9$oX!Ammai3?uY$5ky~b(Qi1Ii=+>Cd>9u!DiwT0*`==gNuhxNkvUVOUKT^ z$;HjX`vp3dl#-T_Ra4i{)Y8_`H8nH0u(YzaadmU|@bvQb2@MO6hy+DNCncw({!B~H z$Sf=>E-5W5uc&Nj{N2>t(%RPEKQK5nJTf{qJ~zLxxU{^oy0){sw|{W>f3Wu!P;qqa znrIU&KnTH|CP*N-TYyG_2Mrc9!6mqBBf%|rfFQxWA-FpPcXtUc-MBWlzwejxoqwH~ zd+yA+>&~4uYxQ1L)m>dxZ|&NzJ@2z?XLoNOc6xSxad~xpbNh!}NC4!&iS@m3|Bwsmi5sFJAu2P;PD_q;ISk@SbA!vWNz%k*6ZFo?3}IM67y~bk@h@*}ERHX9!Ag zwLRYA2DY1|?!&2;YTClyz6p)qc!lcJ@#z<6^bEolZ91wdRYtluGuV>OPtU4);H@g< z1ZSCf1XS{vyDt)Rdw-3QTyDdGLgaI&1sX(Rw>$B^Cukl{)lWO$eGqOqvB?G>Hy7jV zTh7d0x;3>9x;$I-y-HfIywM84r5ifq+a`yOC~FGOT0#fr({$PzP!7WhBX9BgKzFPj zQ{3p9#UX4vKMdK`iyF>8dnOX35Y+5vSg6XS>HrwC<7uaSoy+}n7_+~j5~NV&oaKm~ z|GQ0|azE@(?#bN_?v5j`bA$3N4UMwDyY}-o85qOZHixHK%DCahuf9G4+M_|2I@>)n z&P9T(Bay81n#B=w`@)00>D37;^6h=WpRaW4bG?oTw`h$ z6^3auF}Dyn#uEvm29-G)7dxuN3)$Q6KnR?uh$lhSyyVl&v6Km!Y1lcp({|5>FMYh; zWfq)B#KW%!AFL~{-9DoTMU4Y}r9y$Gq>q64lE^r|s+M&1Pi4m>IM-NOA&hrS>ct)z zL*_?JQz5ZOrkyX%r~zDZ5DJ(9h#dvpJ|&#>7N|Zwe^ZjVk>T2LsT#ABx-F~$TJVa1-uf%bf%`}i#|v&4IKrmCYyfsn6Ohs@O3DnNI7rydq&NP+{QF>3Wh~I)QN@S#Awk5_)1GcYHIqab$J*_zz>a&nO zGAWX`D#baN0K^L~lDjMVfMGTGcz(fROP)uh4^G+}yMtJSYFus&wM3;3T=oeXrJgl; zQ*O0im*0Zjn9%iZ$u{m)H)U#*6cl--36kZ&<2R$YFQkUr?Opw|Fud2hfMpsYjtrYmfl665Y!gXM2SF zqr^~teD*vfE#*uwAj8ZxIdS^Ts=Z;hH^9_asBIussNLm)@vu}(V%D75i~ms2Jv(8} zjF@Kr9>wW)yXg^t)?kRfP(>9Wd2MHP#$0waizgJbqBkUaTLr$@Kf^3{{zO$8iONe> z<%7F#fL+6vfj_xPi@JHHeE&2FUemIYT^Gc9v=M|xNWk;}=ut4}5jgE&>q}JAo3N{| zR+akjQi?zY>F@1{R?2m!9jN>Lvn@%ozFYI#*}U0=eNX=I;|txNsnTj6v`aG_jcwgU ze3%D^Ur!auNnqY)1nUdDFW%v|dA``;n!VaZ)cvp*Aq&Ru+Y4X6xU*yEZj5wa>u(0P z`|<7*@pVpguFM%iu-Q_C^_pn7(-K z_#P?er#M@c{kOQa&wjx5NVj`%p5*4zBfxU5=q{h3Zkt3l&DFccf_ZDNgqnQp_zLP(G1hnc@-V+&z=o@W4 zfhOwRZc+FqE?Ntz3ymc5M)o1~4RjUP86!$%vfMM}zW$hu+OqPME1RQ$Q%Y&_MOkdn z8em>BU}%0Xl#g64_l*8gsJnL=BYgbCzw?S=Oe;>mc_HX3nPl0;gN;z81h~cIJbABG z^ox)uZ0!7u&CtSd^j6Dy9sKlF*vppY1ik&|oXt2%oV#tC^7fFZ)SVa>Rh4uWIv@+= zmH5V@U4^^!$O7oXUXG0O5)iW!{tN054SW%j;NMClHG1_E=M=4}#ZWOsdt#?D?Ipb) z8m0Or?XL9@TeHZ94UHslPVrpc_d}K-19boOrv1ey1Ky&NyfJN^so&D-W=Bp_Cy=#@ z8jo#zLKz6%Esp1@i1fDZ!6j2udHGf+^#z0fd-|C0n%7TW#n>0zU9ncP?`X%ZEW3N@ z*FQOBuU$+JR{0U|-I3V-o?F|fK(i^Lh+K;NQ_*C0Y5{Y9MuQjLZpJe&XW=fJ-)NL` zk>+Q-ofevzmN2Tc=K^>)Au-2Ms}r@T$1NM6klM+P&urzUncIM3+bHe(m2|NpVbUCH zqTBA|#=Ov)x1qTo8!K64cLH}oIx97=XS&}z80>T8sqqv}srjCXJPBund_D~bXUsyZ z{iU6ioRGRlUu~Gc37|(w-^TXF`gqT0hi*)53UH5hUF-Ls-fCr!{Y~3A%uos5DR-w! zcGL+u)F!Pg>4`h-*b-v}3A2sIwCwxt64W+fl!IPY-o2guD$;#+vnW+<-4w_7 zeaG63vc&=!QjB;W?DFPCric7#4R#t1nMX@onXGiiGVa8n8t{T_apt$Pu}i%_IVt5O ze9ewlTa-6P$*g@z!r>cW6q!N`qF&)u(Hydn(4__njcThf>^xb*Ybu?4M0;v=AW%a-AyZ(Tprza~og!|4eFRHQYmU`dCy zr)A46R^Al5y7)-r8CQYutrlsC;$@lU%Dk>vq`Y!w9=cvjtAz~#@3T1B*PZN#(_CPYWAKv3vVt;TeP~#T%*w!c90g2dvjR8 zn6_=jQ9${)9YFt;v6^S)9vl?p0i3U)bko$ec0FS|CaL=MK;Xu((8#G>rzD@w>%pCg z6{JD4ueiW?KB!D-G5h{$WSp2XQl1{pkU20)XUbb6*D{&a&c#tfMblHmV)Pg*v15mcvL zg>xA@1ouH&JcinEE_4-%^wkbjq%!EZc#HGZk^0H2W(f_uPWx>(M0hvpxRnYWc9(!V z9~4Lp(zSc7rL8Iw806Igr>TZ-XMFQYwf8tN*x58$9;SP>9{}4qvxxNGd68}Cv(4X8 z$}rA7xKGJI_T&XS;eoI0_mFsY;Y039pO9Tt7b6GX5&7?gTO*%Ei5H}%(yCQgVnTJ8 z6{@)XA5Pd2QNr@%g%_(PJKxBx==+OcmV0sdzNsDl(e;K#Yy*`mgBUuP=znj97MgZ z_{l00<6J~iqq(iFb%v+5Rj?&Qsp&Mkfua1OW5m%%C>3x@Ugo9V@{l=UT{3T$raU`? z<&|XgA@;rF_l$ZLhjYS{3?gWB`Z_Zyb;a3=ZZMwSu<+1{@V5@9`tSHh9~uj~3~}~- zvFo7OxtE@%%@Isk=17CjPCo>izlpZ~nT7N6y08rz<-XY|%BP&fXe-*~zIr}f6?5-A zc&omG6zQI`VHhc_#c;xE3_dK8P?DbRz4vh~LTIYg6Jd>YA5ae*m?=FKSz`BEa}^8K z#Z(Yg4#hWppB?-9jRLeT7|q{!N6y0S*zy~(&x4~nPJvRVpKyWpn6NU`Y+*+Sx>80s z8~kN`d(yV=b$t+t$Q^cj;3PHT6`SF+2PNJZty6LQQEW#?4C=ncRN86iXC;zr@DRFK z`t2gfswGUo{d}=ZtmS9yqjYh_INrv$v!mNC#4|5RKgZay1*$VJ9*?ZTU$A3k zH1f8Bfv}PVtq`6lB00LrQlmN#G|I1zQpVs;54%TzXw)MBjB06NKo_B#;hq!pwX;}` zf%ZGX3V5nJ2w3(@XY6EZ2Gig+&jj1W>?{`O>LSBy_4{^n?qy5akuAgAAc`JL$GWoZ z?=D?WZjf)LN6D918oN1&NP=15EC?%BeUEHcC6loO_foR=NsfV)quw2MT*_Gc4g#90 z6nf%ssK}cxTySHqaeM4ViK_2p!b`P)EV_9hzs(Dp#x}|&)T-*bQ8_pqb{b__e^CkZ_j}<>Cnr5NyAl+ zK_cyhTL2gMTcNOLKBZ2t+plQZ1(D@Csh&1Qc`+`yv!rt=ryz}pFLP@URB z(5&9i?t)!}L}mNFR~!nJksvpJy9Q9q&LcpqrWQSge#ti+)E(XfW&4&abU6Ey@slM9 zM#j+cBY+w#RJrMunEW9euj@rmW>3r`KwGoTudLgXrH*9VV=nMe)Nt2Q=Bgp^FnIN6 zJA~^I5Me7EgRQ?=8cqv@(gxm|)q^k>d~$kRB}ug<0RDoz z49@p#7-s0>4Q$AMJ2H~*{tCQfXt_$b$RaI2m{0z)lNwxMrc zujM|`GVt{8xG%w6YBTVio_98z`ksW%WOdkKhvUAfMXlFOtM_n_7PSaQN<9>2SSxG> zF&pSD`9M@$7PpPDNRA1>I;lIownsudOPjAeuGrG9%XRy0+$8wtS) z_dfjyc50@a?=Z-JDKVibolc-?5Z3vJYW~+|Vx5Yb8Txr<;c5u8obsJrb)eoO;GP*9 zzxitQ>Jjh=DATW#XC@sh;P+rol`fNImin%I&-8!~_gV$7J_6u1w!5Na70^SjKGSU= zZymS%;%mGPC=RPGE3t#evJKGVQ-%&zlhY5QOxDzh&Q+J)m z<5VYH$x9b4XTng+)5v73ZFA$=x|O}J_GW7Du`G-x@|#R)kGPKCJpvF#wPmVRQ2nE& zl2?_k87hK&P%v>N??N9LB27RRBt@b@2`KDb+bR({Tdj0~$xWymSB_e*U{BsldM0bV z*QX0jLr;7)9+PLWn@XP_Snv=%1WK-N;NshmJJE60$|@39NM}{zS5p!$+jVU0^XD11 zR*QpgcvUBfAwS(}Uo)Pls~#3wm7DxgS@Z1WmrrgK84X+01M;-;xbbm16^U|kjCDT( zH*hfK+@me%o02y8g5-;W6`RQ_f4KV&cFow?nQjxhtqjxJn(;!Us0J|gE~i9t8$9fO zD63A|@ai&DxA>a{HJ0E^d1?XKJ0FA__-+%=u^xsy{O)_)64a-DC>8XU3uvT+g61|@ z;Us&w71kwbgUw*9{vc4IE8itdN4F2LIGyxp-lTz*u>`jCefo3i$ zfvVX5Yy&>d|HrV_A2nkCD7aGhus3D@@4;G}99;i^wK%y1|LburEczEbt;~{96o-b~QQ}eRKrUhqaJ@$R zokWQ7l#`UZJFG60bELQ{@}c_?fN24nX`z$Qu5qHd7+`t4T6lwMG74b|o=9rH+SsIz z-OEq97KkH1Uwk{}jd{`IW>&nI^|p62`i-gr?h&83?(;p{@kLcB!U?A!gl@smgLJ>C zv|hW*8y)6mF+S^mKq@b8KG^V%a=ukcB)utrH6k)X0N$0Z=pLAwucb7|77pyyan{7! zv*9w$tJAvaJR+ZUqi@Z~&W0WZc;FfK@q9}Wt_@4Gf0i5I_WbEVr01*;%uX)-f>P)W zJyP=*ws;7H9PT+5R30Kc zG7tJ!b)unTX8csZ$!+*U^Jnl$td0mXdWYjpEAHgzl}f?GsVqEUmE@o#%iYr}ORWCW z?@rKy$Rpr+&4Glanwn1xnZ0dJ__4jHz;xz^8S{YUD)aoPQW;5F3Lx|z+e0}Nb@RaB z9nN-Tly@P)G3n@=1lPSmhZvpngCo{zJIuYAv%t&68^z^b&&X#?nRf?g!yan=sC{7> z+9xRXWn+mMnzYAI8H#6{$Zp0rHx5mZ*R6dJqC`T%no*r=C)3LqqLlEc+SEpB!yjz$a8)+`E{Gv%U!|=(U+nVk2CSs zS+jKpnz{4hFh8o~Gokk7oTil22jy2hOO(vnO2Ji;mU=cQA^ZA&!! z2K_pGA0#0!0*2FVFXdE3 z`*w2B+0ZJ8Uc%f~JK*UWaG+uJnZ+=epv>)=lQ=h>Yu3@n=SucS)Gm~u-iiV?1J5cQ zK9~As>F;g|?{`5Vo_3dp{yZg)dD@+nZ9sS}b5WtqCtWYukIGGCs6f9SMT2Us#uyr< z)6WPnxbSOsI3qH@LE{}!keB^)(w8;`t0cfOr`6}I!ZFW9Iu4xPaNOb~-;zOKwB1Ct zLPmRL@0Rqw_Q*i91pDu0&dS*awc3K829=$|dIO)9yaf1CGXLt{q?K;8G5B>3os*l+ zp3*NLm4i*u7-A}U_3m!MZ;@|jzZmqDEq~C@+mQae>DC{M>*yand8L=ZdCz-Dv@n(W z2>8OQ>y?c?^&*MxnH{D019Mo0A=|KPWYZ`0=ee_N+yo8lYM56-_RczyMZ*i7-YgR) z2$;QUVVhr9Uxc4a)Z?S9_~IDZrH|Uqp{K@l`&u79$L(e~O#33GI@Gt%&J&7aq}P4t z*5)XCuqzVeT2?k%bkARxu^yr5z>wGmbZ5>0)0c+}6iR+9=u;?QPH!i^>jToQ%G)bABYVyKsu*HC*@7t+^bui zn`fzHqcyWxBIpO;c|p))Y~3O9*UH)Cq+s_#gPD0_Gu7U0K}0!%{mKQCL$a2Wo^q35 zzq6)@c@4tZpWpaX=`B=xD{L?d^A_YSL5DfqPN!)|1v@~=gf+wHX!&wAS<~@TH#7Q{ z7ufPU@7rqP6e5BdP%)miBxs~_X(R}JVf+z>Db2yW`wU0^EGASFXg*z9g8%{sJ~|x> z0`tL4P}tma9ouX-^UA>9mFz%GX(fNo5cSjgMHj60Rp`sUeN6d&r%qVgEre- z;4|Bldc(V&6JJ6X1sNIz(yrc)sk>+?7g4%IZ@IGFR372QU#LTmfa6WjASm7OV}n)a z#IlH}xe{li3tO$%eY7ta%Yw}O3rmfN=gg^fS~7Noc^BiSul0M8V4*;5Sa7CDP*iYY z{<64@)zhaixZ&&>#0-EGY5#hT@K_nG;JLy))msN8?j5Vcn+Uy{pR?N^%0D*C5lY09 zw14D|jP%MJX+iFU3r$utzcxx!Zn32E-WI4|Km%;emODs`6u(|e=PP5aWymAfFs~DeWu94r)N2v zjc4~iL1eqHOMgri?YfulOJ+Q0FH%WnNl2FLS+l>~hi5mAh9qG#uUO*+C>X8afg4-E zS%{E*_I2TWf4;%;UTE&m;oqGtzwDZBaSf_+EC=%}E^8HHmZ_h~I4!|FAjr&}^|SFl zIM$rquT{hK70DR0TO*;Je?cNpPGqcfMrL-B7Nwd~Sx#hAf`uk*2R3zn{%Wc_6NU)# zpD)&%<&P5XR9F$cUnub?<`Ud4H*`Qv=S- zTo{HPP_x6CjX4lX1@MoS|CQv0wD_UrlnOe710o|VnX#UaMpATw%GYxo@3^x}aSj3*tA+5>@ z30yvt+7LT5YICd9Ypu7lTg}w3?pa?ym0HZuU((YjI!R*Zw`sb$K07_HQ`Q=+GoP(< zGe0`ow)Hr@Y37yeOH=f6Y11KOkD2Lt#4$@yIa9WtCcwJOA|d?wHHxlzjF7gtzD?-c z!xekaa>wECy^hZwLzum5eUn${+?p!aqnJgZ<)$5kMfEI7pw8+OrTvXtBr$^7>)ho@ zH}opEv+Vl@frq}1LqeA8hmJ$iZ_i+Bk@yB7?$qw+7rA;O{{$NHT{5EXTVR9i^)m~N=I3Y%t8zeV|GK;D3>mLS-kI*0K>h7`En=cRWENO z8^+G6;v7suf8#i|3vWC#Sy0IlYgBHn)0&25W^WY_&hgf78HIOLnZpjMEDEIk&$ z^6STASXaj(idltq|7w>0S-mD%?SePL&BC<9&i7lQaTOG06cF<} zU@>{2mf)D!S!6eEA?gSBiszM+sv}afpUkr9 z67i`8?dDi?A9doFs&~-1&>ymY3pk<+MmJyB;+sFB{iHd3^Gp1@1qIW}93hA8+Zi-2 z)H*EAM0Pz=ty16BN zJG2+$)CW-AiV~^ayZ2#p&V^=pswpNne0GCQXqKg1VOJ#d!POO7YE0zipD_%5IjgIU zlg)-khRC|7vMz;dLTpS3N9jsJ(Ks=Go>~PAz0A9JN6i(?E%iVej3At~XOGcDyi2W7 zr@e|4{bdl+fL3Ieq046mXYfJJv_$7BZ{Rdp{U?Yf^}+UbkCo~oef`gm_O66%cWN8C z&kY`HQ^rFj=T1vBsM@yN2^mIL9wxrLXQtFKNM@ z(&W{}B${28KF=9*K&U;73!#H5OKEq`OkpU6#BNrbx9HXxmIvm{GUrR6sCg}re)&o* zwwx;bR!erdOk8W-DboHSKT0_IOHEf}pJ^^8GC|xTnMEvccRF9!S-;Egn-Qs1ZeEdo z(_;%7iv*kUIV4Rs<|v|N+qL>K0gEr8gyQA9j|nQRHo-p!;*MSq%jNPJ>DOKK^h&xJ zkUl{^KiOQn{~1$aL-0h4s+-f*?CTC{QMWI01fN!RF5M}c5*sn~6H(c3X`;Z|*bgr- z(Fz%y7!_L1GEzxcqOwIRqFsLlhvGg>!6VOqcf%nb((Vsc5}onG`8;`ur}Bu>>8$yr z*Cx!r3PW{Np;WYMnK0Oo&7pd{B<#h3PY?V1L=$XFwWfY8|LwQAe$n_}3dIET2EGx$ z&QpBS#2Q8(PN5y*F0pmdMT`O6Y3p<*Oz;)593gzLkM!N}8q7!x=83qEuTD|ioSj4 z-GBw2)omfcLsAr^Y;ZqjbZp$^gL0;k2QWo@atTdz0$tJ8hn@WL8x%yJa|XV6`2Msz zV*^C8%IO-?Bz2kG!Yv^agDA)l&w_>`CVF0tWYg3PuW-<@Stwx&t9=|yem6$$H;4~i z6}+h35>M6Xe=0!vh253;ifamAjJkY|M*G zRZ?RX2T>SKHkTfX;af4+AJh})DeVF--|{a7g1zd=gRTsaH}8gCrv*sZa}h;G)+m>@&^ge@rC@zzmvFG{)HkXM3;ZT^erU7$0Q&M+(|AhHmmYl z1?YvH`+KB@i&x1$prI9aAOZmka@Ps-EU8m&c2=+k7jdJ7C7M+}smI3a!4K$;ikC5a zuKev8!~8XDrq0zB!UU9)Ccag79mUCWWqv-}KShS)s((NbRa>cOjv>NKA4w-t!0p_p z;_R#w$^>Low`ehvvD}f&p`ypN(YtKTajSteAqBx@4Z64O-5mXc|!MkiBTX1`Urb&LnEKc!_T+evj%pzP; zGFp12_Q0+Dhw5rIc$L?tuePDa8W})S?UZ>Zz|2vh&k_H6 zlQ^75D9EapDxl>9R2JC6W2tSVaMO5I!)c3AIL^VNY9(_;bTQbqv|~3fib_y};p|Gg zrk-*MfBjA)C6%dwcfNSR>r2tFR(ezWceS;Kqkadc&Y=)>&w$~y@i4NLPZeLbQBHc7 z^?fh5x~o8CJ%VlIC6uzes}HRF!v|9?6>0JFhWgLuojZsu##6K38f$qC5DO}IAtvL#{~j+Y8e_WOki~rOd+|E! z0K*40@!d(UZl2@{8DhGmPvu!J5%oj2+%up4V*ud~Kl~qV>;El)z{~MZUJMUE*MA{^ z@LutglQlIL|DWFZ-&rzTg1j6YfAy9B1y1+~`1rpZO$KQW>FWQ$3IBV*2{-V+WEGzL zpY^}gdj7TU^M9pv&i4x+mR6VYYI5;L-#pB1NDP6I#9pXvv?z#QV{aTO#wnZ!D%IJZ z%2GZ8P7*;w;pZaUmri44DL|F!6ZWTfzX~gVz|AkRa8fNL;>Yj}&Grnj?n!eZ7k2HB zk0+Y?Gw*g&XBw!;##rwMznu?0?UNV?LjXHjI3djip!U^719;~&XJ*A{%$4=N?r0SJ zNrlFF091_=$neJY;}k7bnJtNLaCMm|gE#NY?-%4H`bYQtno=^`gE2FHqVhe2 zm(Cf_q3mI-gKX&q`rVsJwcf~!#khLgc~G2=y5i1hwo>k~uGgQzs~%Z6iJWqVQhM-V zPcP(4L)>L)@Lt)^w$o4AZs4lm(wGQEs|AgVIIhg8G4#-x^q7zuAEnhJA|dayp7f@1 zM{a|@T=)oalz>oq4S@sm6V0b}M3lX9BoEdr)iyMf5)-V|gCw20i@mrqPX3XZo`CD< zv#!*UbX)W0$#UvY3SyV`(#G4(&6xZ6H;~)?w*Hc>aDY65!iGqS{ZF>Mjx3bFCFVH) z4$&L6RIT-OjviUk!d}(k63IGQBja2wOK&bAbx>6!`9rVPE=x9Mg5mcfNzvW-uf#mG z9%5j$gy-Pkd))O$0G3W4QCfl6PifA}kf?WMi}aPLyM%-UG>jUmeh5-ax)|DPk|4Dq zdvv$8n0dgI`G$-+elYW_2mzPtGy802*xB_s0TD8{`abT*>f;e$cwNs1e9 zZdQnak8H<1b0sD7+S}M{7iTlA_lc$g)VJZ6pb$d}+jB!&1=y>DbYTjt%Av|1N>wTyH)C=tfT9jgV8T8>-g&Kes0i z{=WVHtRMH9(bu^*mGS#>uo7!m@%y)NAI4L+tz^xUR>bMJnw5mOIML4tkWPTm;fc(f zXau?l?u@xM+XL?kWnLk?Axv1B9lmu^Oe?m*g)Ye>K(E_Zx``5N(=O84V`|hhHK_go zyW0wVJBti(1YS=weFS7}fLGD)5$IO1O)@+XG3LE9p%8^@9~ieBY`Icayml%_|4gTH z1f7)0D>C&MF)~N5&E~6)aTn#^m)Mm+mJ5hLbkFJ1a-j zym>zY5XAozR(QXUgI$xTdK8t9y@TfSh7!&}tK{4(kOdf4%(56p7uQ!LvWrxWD61k| z@#epFgoEN9G;eIAvVZiGE;oVJ^(eLLS{kdDY|70-+*VF=nKK9cL?#iZF}!{L@9*~? z62L+c7|3E{vrfxt;@;gQ?dh0kF{Y&ihbW1+lK+(H89{hTfsVWzwP-hedBoO z(yL#uMBA93x1j5}L3pU6vs)i}97XA_E!XV`exICo>qa^V)8*i_QvkbF(0k5}TK#9h zX1}#l&h;JUVcl8|k-%#_L=CA2ybNmCdn+Lvl}|q;&)qs>>is}`;+uf9Go=yHMBaqq zT{8HgYzw|Mo&bt1KL3+_s(BU2y50nfj<<8bQy@sjq@*yxJ!VF0R0c?&USCPSB{jCk z*}}-eDSrg4qd^NEuu=`-PYSNpz@6CZ%D2QkZTG0k(DL9EmdY-21(xvG!a?ru0FhBE zz^T4cpZc{%SzqC93Ec{Q<5%)KkLSND0jJ~skx#IwkoL|Y8kNm;Q!F2wYre{%?9xT` zxzN%yNAv+H?nR8TdsFgt9s$Ps zYpspmo)w=k4Hj>pw24RY)qsikq(Q6Ta^*pf0E43YAb`aq;IH>22mZni6Fk;{c+S_D zz(LLgD(Ij`z*GJp(5~LZBLGSWLg*{;X2j@~*iJ^kV?}VnF;y{&q`dVLq+J+GYH&=B zF70<_xzB$1HyR{1t)zOKFXngIWEfsBTx-3&lYRvJs>}z^_QN&K45S&6kF>p6*CPDC zIqQ@5)ii>Gw5gs#V@atfCt@b1L7lC%&%{yip6-Q@Rm0FKKhd<57r5aXq?vy9)&Ijb zT#Mf`{M&U9fID&4LI3DmNcuepcGe>#t9^kQs2)n?J^s5@Bs|jh<7}Nf^G01rvqk3u zhR&o4Rzq3Arikc)vu&H96#zkO`9j{AdCq1DzQ)X4S21))Oa$@CesF!6gCi`IW?Eo# zPoJZm5cO)fV99@~d-c~CdIy2p&qSkwLIIwHfAXUL!H-5Pk$&V@^WQ$tp@gS9&=KQi zkdj2kNFqi``?c3*35ep_hi%hDVXmE6bFe3p;82ad6OI8VM*6p6JxR-PjHA=9V|m~f%)yi=%ZSnfFcs5!dL96ck7<<6{Sk6^0z4#nJ@(Y(At zG6+J@G1zME1auQvv#m}`c3#>-yY^1~_0Wo-3ca`9sbBY^hcDlHTH~7M}*D#`;;0Idsr&1J!YO>Q)7n*wE+b)n0DuP+K z^${5k2SrKuI}@N`g1;E0^qRCdis$UmDk+?P*(!Btg=+i+(=w^=^C%WqN-mt<)1;kN9|}>m*-h>an3c*5s||&Xmi`}YV@qISniwf$l&<5Sk=Fi z0RN48{3nVnmK_c_5~cE%XoJ2g+wN=h!f?-+tL)f2V;n(tm}za9a8XdSepfg3*%WV4 zsVO#OAB5cFO~pLvi|?(F)+yBN>HCAbHq73&5f!C-CkN;A1=Y=w7*Km$Rh&l)mt{;g zPI{ARcOJ!yEXfb^b7)-e_^%(a{+eoBPeqClSo~ln37ixMYq_>5&;KFW?KSob;6BCH z8i0d1n)%nkiK2p1&Gh+Vl<4J4)|zfJ!50}>ky^jN_`;}8YcVgXt;zXP1X->5Rix3> zZ(k?^?9cz`i{q(<3*|uY^R{)S8E|#Y8%O6f2@K7pR;L&CNR(pKW`1wL0U+#`byXof zp3O{Gc~TN*3VhN~M=SH$mC1>fIm*bF@&0rZLV9P913VrVSiwLnx46+!|dfbrnk0XOZ z4^MgDFx!Z^E_lb^A{zwd`;PhIHcAnX8-5s&8UE&$buT`;Cq`8#<&>%wea~K+U6a&c z(sN5Ygo_C0;{$Is@110rj-#a<_6vBB=n?TB7PEJ;s&wG_7f$8u?JlkV(dc-tSUSlAup5Z z^*jUjv}>*mTCyx#X(#C`rdc)J>C^<*%Dn=o$Zq&Iw7QyBpX_ha4o$M@!v`4!+tH8f zeslm0ERcgCRjoweex9+mgK5xLNTl`QNl@mgA`koRh4L07a^>HPLy&tw7AH4EJfpWa zpn+CvNZ!BO{LklK5?pe#{Y0A9lDq3BZGS*(3m3~6ymb_&a=-Wy#Q7fiP~*s7V`!J4 zp#%t`P$ixxci4q(s@G6a&9bQ*ASUif`9(-6+jrYF^V z&=$kA?_CG<p@P{hLIXw?xl9${D5VtCh4ed;drAQNuxsZ9g7ijI{#@xqBQo zA4OT?*DJ1=wa6DF(Kx~|L!-S^pX8A0H(tyiX=_Kw>4uur*2iId&8_5= zc|R>HK$8QpyxxP}+VE@H;qTQB&G5_cdm$Oc#dTLwCPX?-f^=X`Vi1Iu&Pib1e5{_Xa zCesN=5MZyv)~jJ!0lbv_RyRZbKO995icr8TKFmeM3sD!XX$8t&&g;}cSWQ%&exjJX zmv(=(WQl)smj8z>*h))A6 zPi3*=YV4`@NCTjLH75@d-Q?Zlh5E*ntnBKPwdNHJ88!HK`-h;uCD5n$^RfO90$Bsy7P*ULEzQDW9?Kyc)L z_I0dK)lai)l`J10w>V?)Hw%GHA9$eW7y>^B^A;k+Hhrvdqo! zcoiEVM@wWTA}5?W4kK#u*syUu(QI{`)D!${{)IBZ3v=L4^?*|W$9@F#vrT}O?Y6C( z%W>9L@8gbWA7(yqQ~|rc=o?exo_E zTRDMA5ez}LAR6Wa&uDj=`k)`Fym1U9rk}n_0lYCSnRg+S-cN#0h<$N6b^9|jCM=)N z91K*B;3$D~pe@#2I(;F>D~sxr<*dsXr{@$jIDa9 zzoC5;_t(}hP;5^h0D&4=@b)p50GU_OMgSqHCB1#IXzmzFZ!>5!vs;^b(gd2HM$<1c zI_Np~2+;Hktfsi2`v{xl)!mTk6Nsgo@Md^*^(M}1BFAErM5$rlp~(Kc?QshUWLI&)Tq^SpvANzLUqR1`P7RrwT> zda!4=z^i?&tN58R`vB$E-g-Or&%OS!_kU)|?A0mCJmn26?<(3mOiNdOdw4lM}-YFY>n|SG#BU z?Lc3T&hsC}giB;CvEdp^O$B0L+*H6=YipoVMSW}2R~b7yas}p$0W>+tS2&~lts%wr zmwuw(ldNI7+KYj|NR3C;q+G$|zOP=Dy>T~}`IaUNd|)!A#cQLrpe>FuG|Y2PvH%SK zX@J$n(B}-%9syg)mcZk>Gv4J?C@7?jRG4)=236ZqC}8y=A0T<7f@Yv>+}MbV zhtP{iZ%XgIB-8*QocFi){=TvIzTdd_jC;lz_ug^8|MG?;yk)Jq=6s%KK1=!q(*nV6 zYPbGdyPs+bSwrA?jg2diTT*CQzq03Bn}VsP;@wJvo6xzK=M?!{H+)2Fi?@ zfBNm(5qf6u!UBWnmL#K*cbWw&Cvd;Q6%frFXZkna9GhLOxiI?>J!I1J6}q304`}yK zvM;1<;~T2e3xEIoe5UWa;k2hHk)DqqGUu;`ZkA2W#>{iXo{??r05#SZ@|!IOuScvo_=ps5PB%~HnJU+m=m7~pP|!&fdJ)Kx zqQ#y)MFddS;tXE^`BT#t}PU(*R;9|z}8J*JQ zl?F<}m^!EPwmzr;H{&%4Za1woy1-%%i@pF?`1LC)vU zoz+5haqXh5>UgEhbOBAVW%($N6ER+tH z84dc%1m3bDcr_p`#~QyqnhVd2RN#b1c11Dh)sqt@=ud(46=us_Mw^_C3ccR{C4o!4een36Wxho=u-<@vND(PzQG(BJ;Ytg+jN4B_8;nR3u)3 z!T~5Jcyod3oJsWcmY$6F@1#UY+gDU!?AeVIf`Jf2&k#6b06+@Q#xo%O zp&R$0ctXu9P{$C-dWR5Lbvc%-FQ;CRx6yWrdm4vmkTqi=}?TEw$WHV zHk)%ubLqc-SRxIHvqay)hkDwPeC6urSANc-PNF?&2lA=(8vZBqyZ@QB-7Rxn{}<9$NKMd5yf!oKOg=DJy86Y{}TAv|I2CLP7qyfu>eE+c}_!*xB~=OHPSCfJuCA#W6sNL2&%tPVcv;JIf`U zYwh}_AJ3l*WcF%#PDaQPENNfYQ2Cy(cKZsH8TPSJ6%UTm0gL=CB{eE9Z5*>tnEE+k z`*~*3BAC1k2bS1ORc#Sse5aFhO48NIlAc0Sjq@e^;s+oZhS~JHlhBHga)E&A34U|m z#-iYHmt!(Sk|Q3#D3=DwI1=`?r6kw62Q8VO>&PUx{KRwUfN7NlZ`=XVy>6ma#R@E4 zx~Nu;^Dk9MPU>ENE#ycr<@M%u&X^x5eDKL=epANW@V8(O;g|(6>Kze9b_Zo}@!d6l znka+pO%$=5)sId0)aXh{KjpD|;@*3b)f1qoD5usR=U1TC{abL`$93b!j76QfwIq+& zS}b~r@_?Nq9O<4-nk+&>**P%9Ij1sm?z^l)WxvWltON7Q7_?%I)CGny(Q@Y5@+)8g zdh{0`6nyQU4vG2-N|sHqs>@nrR5HGtnqjTmHWxco<GLmfl1!IVW&&eQ(ugoypVUYp;qmpkK-)dzV2u9+Fn567Fb0 z6Dv@2PcH-b&sh|5x(4D+GRp?zcCOosc}rU_B80n%Tv#bmk6>6Gg$Gxl3`q!<4hnc` z)JGgc{^hOMe>nm^5M@vR#GL@lAPxge)0VGz`VMS%+j&aRaboVfLYkSdiHDrN?O^G3 zbQZ-EpxHhf$AIx`$ewyvt!tEKQqWPBQcLEZsS9kvpt=Y^yX4z6HeLx`H6l@^(Wg{{ zz?V|*+iKbf2`*MevcDPOw@D00aD=9NJ;M=U+Br)@+9^b#BcWIwaLTYbU@JjJ-VHhI zYQF*zhGK!+O$P2}TP*Fq^rb>)Q3-yl-@oZ2j&TUdrh#o7GL|KB6ZI>fm#l=?K;<4V0JmNw((5#yH9*ZqcV& zH?AJ!%t@}quHA5{1nEZbm7`2)h1$hTv}K zzn5NOpfue3u@?G2L;fXqj169Jm4vt^#Swgb&{$ z8(-^`;^@(!~O7#>k1?oO&d(l+se;!+wS&i57j`x7+U3jzcwbcyR$pgINDS8 zEoS8l*^xs8f{q{OOJ7`^k&ITgznJh0qhSq{T%ZvdZL-4;oD(zPZ;wfME>pgzzQyTs zP_YN;VFn#vfkIJ~;`4wnN*Q2Oq}Ebbzg>Gu-t`bOl8*`Ph^2yay?dvpd8eJCxT!wG z$7xG(e_IcygyBHv2Oi&0e{F@JesgIV7O<;14|0M9qPxoFn7Fp z1-gcF;SKS5aVyPv&-GH{ad4$Vx@I<8Xuca{D`Y9duCH*I; z#T?ZA4)erd_Ix7cR?XLWn?!$$hJeKf@=_F*&FmB6XRgKOTWxRdanpRf@9hN;dMD6! z*cnd-&{~NkMJ2fcJJI)7me&64$<A6-_`GZ8WHQafOo7|knMCT zo)biXuos=iI>T}%g`4k$lWUwGXYPf_BA)0qa^)yA0ei|liSi%)1y-2J95Qgj>_65L zS|Y8Fc1#VfAL!07712AthTEJ6Ra&7iSD=%Sna)?VVpiip1d5PnChcrhm5z_UT7(mF z#r z?>Um_M0TUDRS?vlxpX}Na-q%_uqiIQ`fSbR2Y?eEGH*7eBRJ&^ZPd$#|L8Aq_5QtQ zp)!RfrdRj?DfYDL8VsqH@=~6RaNTH3+aUbB90e*5Y_S6Q?s=UerLj!jPPQ1H329Bb z4Bi-JHZn}_`RnHV@z?4{-oGADSUlwI8SPug&_oUSlJ%(K~9o zXIG&6hxr6=Udqx&8H@rAoF{?F?4*e=;MFs*f!`KiK8*%ndic2#Y=35I1$^YLKvQB5 zX|3xmt0+9-Jl2-4F1whBFG;UJ-GzGHczIvQl2wRn7>_QQ>FtIUpN0Y8Ui(yeK_XnG zebg5tfqIRyhWeGu<>H~x)-{li%_7^wc@0cRY=N1GiMMU-YFxnZBSKqX$YRa#z>j90 zA@+frZr-e(%p0N~p?rVCQHhab6d)G|$+7c~F`=24v9dpJUtFXBf{F))=G+*C&x=~p zu&we+{na)S-3;31)t-XYrd(Wm zdLFj0Tj0CRv&!nODd|(;ZmtJ-w^jhjdgmhweDm$%{5d8Tpf;{REQpmn^D9s->eu85 zgaWnl8Sa5^v;cTdMA!V;y%f}*(4ja%7uFlEVkU$~aRN`c({~t*^X|ki;9sCop{SL& z0O7iQAaqG#Fe{AcZiMYottzs=EylT{n*&PBH>LDt8fJ{24D}2@VtS?uChnnxl0|A? z0$?A)Jx#~hafCq4tPhX0|A5|p~Dh;7L&*ja$=gF-C2(X0D*)8 zCeYjaLOGQ|y?D(ugtd?eb|3d19b6M4_mZbHEH@YD=Qr1T+j0h+wVdl+)5a#GJEFj9 zQ$6+3`H=|a5))maO73rer4@FUSg!-*l#oG)uglYs<+oOW5YZ<$-4hI7UH}5D$tegq zY9)08db|n22l6eQUp!j$z$Kc2@lAgXaDX#I?gHr@R|!xTyj;8jWwSS0Hl>}!U4h~! zf%#DCcm*1cH5XI7<|}a_1rW)x->*Qw)lo;LZifQ@{+27j#@tfKe} zuoo`&F=xh0Adji=fWda0Tl+~(reL= zANLeJ7>>I&<(}fkokHT~L)T4U=yef+Cc6vz4_@n^$6+pcBsmo=D{FoGGF})aK;yG0kW(6}?99X2Ouy}aCuUIf*shJjUf{fk z1tlB^y5%cXRLkpVr9la_HpIXbJbV6E7UAYciS;dkFU%s|EN9RX7CP?qZJ?g(c$x?>fZmGHgUAJv!`qW)$lX7c0D=AyS1Y+5B!&`f2 zx?rznQhYt`Eyc60jG%S!xef_xmvKjq{G~sxxZoZ%P~N8-W{P`1^mfvZ&GZw4YqBVL zBgpw-AyMq$5i)^NmHi`Vsw!U%y_KBswLPcjr|N4)=0cMU>fsWJD-iu=y>Fw|?Sw_C zUE*D>b|;0FH$&j*p$EyND{{AVp7JdtBXx^lbjK@7!L)+l%L?NQra9@`RhmBMRo??E% z8}jN@-|m8hAdNU8N>sc90F@cEU0*BNqMObcnwG zeLP_m=sDI19Rj=d4rqO;nvr4-i0Y&88)B;y3n)}5HC^mz%JSBG4CD(wi{pN4^wCpIB$Jv!HMwrkJ;W|}R) za+GGx+N0g#dTlS5p#847ud@s6+597c&ft!lJzU9Zf2ar12_mT=>}Yq!ZRYt70ac@F zi^_pK+8zIE_!q|>?U$ZDnhSP(dQ5`ouoZ4M*E2)>%gMORKlQ7h5=%MX! zGO#9tu5x_oL*+?gusbM$>vjd!ivCalb_(f*fjNBV_?Ux+yeq=`sXz_+&oH*6GTJ3Ol&KDVq*N*%_D;_1U9PG!-Ys84Q zxWx%h70PbqOMbp@eJXhZWEBa6Os=pNY8boEqoLzn+Rha9rR$GRUCKT^%}I)*@nqVN z!I%%DS-P2%8`w^w3r(B)rtdd3lt%@xJ92w3)lZfQ z28H6IyfCRCzD?vG6#lWVHoi^k3iNFm`Pug0-pQge7_0OZGK%ILJDQeVc~tjo>)J=6 zEg~W#6gm+52p_CP1tif!0KO*C>f=38#m1mGnmdZ*u8!Xm{`G0)<)lAQ%la-M&u`57 zS>OP22ZdFV1D3moc|cqW+yF2&G&FWfFe%$M^^KSLXRFt-c6G@U*TO_*X410^2!H^7 z8iCr7aytWDyqok)#;U$QGt)TGOi`Z7y`Va2Xk z6C+l7DXNCE?f(t{^oQKKgXF6B`MQ>`8NQ*0J%Bfy_>BwfTh^n;Fs4RjKTV#jXf(G3 zPpPE5;5{m9H5hE#$y-V4bY7fzrvb6t$GMBaM_Aay<0- z0ld+t4A2c;e8rJtk_#248>}m9Lk;SDR=kM^5Ud6x<~PxKnG5}d0({yOFZ=-C2YYwG z=MVQob`XNq_$hh_kJVVPdNNyegno5Mek@|?49B!oXs06IZ*O>~!vY(JMo@o=^;)w` zR$sNTboXT4v$W`yfOvr}MwoWpwB+xE<#y)&@&q#(Je0q9-AL690U=FvYsch-_yz24 z&%9rK&8F>}&&VYoBVy{&j*MhZ3CxQbSofWa((VU!Z5*g*tfR^}T!75)n~w?FzJ}Co zf}`Ai)0eZY{SXQyT3(}ga`@W$?#o2A3(gv&k~=KmNqg;0^F#FDq8-wIcW!OzZT)y2 zV}g!?TFc<|IMN=uNlmF}+&u^OGavPw0?5$?1X}E9eh+)RI4gR6_sB)!@??a1SoI^N z^qq>qg&QDSpEsB&b0)M|XDZudeX4xryZn#(dUdgj-D8tID+$hCsiTt;yP6$dIaA5s zovrFV9&~`fr9}RGqW%iw|GBa*G8`b&6{L~z7~|t?UWMmRugg;o*^bIFU=mxWcmpf# zL{Iis?d|NI-FrAK25NEXK|-+Km$YO%{bcIKUB9zys&j_FNGJR9V*zbVv;_@4&pa7y z-=)O-Zj7~JR?~j=Q?d-#THwdMEs%wSjw6=rbJP0Q%i1whRHc=UbnO^*X26 z+%w{M!}73HF0%l6V^{IIs@J{YU>iDFyJsp-WynD1K_`eImM7>p=O2F(*BtIWx&l3E zi}I_(6=nmqYsW0Kh3YNa_{URcGm_h{UP=vG5d`ocj_^WFGuyWMye~hg zrUc$7UxDD?JAI%Yc6ZWOPAWimLR6w#j=|tFgK=FSPxOOexxT(CyKKh`J~w0ro@>iX zuwvPER#VB+mM;!@c>5s)A_bnI#SVdg;QLW7hUc-HX1u%CfxwOzIzKl)gas+c4f)(k z4V$n^7Ltn<>I-Z3^77@gT3YXzr@*4xoHoyue;t@!AMb%Pk$~&6sK~!X=mN>&DE2Mz z8-4Al<7LPJG7T>Yq=iqKu0T$R&q!UgK^GKk?RS0nsA0MCMwl-H| zT|cnpU^0`-^cEPR{@ zlp+UB-!wCMDm*hkyW1Ni%cYw^z(G4%a!sh~^7XtH)(&CI<%d6guY*JmGNb^d90fT?5 z+VICmDS-+iSm-0PWlA1wcBZ($sa}<{xIvg0L?upOwY1-M>Cw&3RgM~2t5D0Tyjh$6 z=jrYaye&gm*>fK!HW>w7U)KP}+XwhQJ}%{6%nB zBUe^`(K|6iiOMHG+L&0Y4Hq1rd!0`f`wc)2xVAD+XdEsX*k1cQ!xk>W=;4(6>LVrj z-rwJZCz+Ed1v_fgXBbfUjZ|f_0artoGQ6AUl~|;?*_;V);UkK)hs={ zQ|wwNkf3Hc*X8k+v6U?V2|U*jFH9&7X9GJu*T!=;+2R=n1EA~Y`vA|x9*c!35CP!B zHVQ}V1v-FXt&l*t85~Fgtdar3Yw12;roW#+MOE^WW;B0}hG78TdMx&u7vS^yPfzsu z7+xC7zO7jo$TywM9iOCQXXz$V)sY0~6_E5X zEW*xjxjNuLz$yUnoi|Zr0NPQJjaVRt;k4{^6&VreV5|W?I2}-FfdC-rF9P#}P#fxq ze`6j?zyK^ACLeW3Ah_Fp^jDy-R)B`Sg%tG{1xt=bUXsg5;Xy#??U-1lBLFhVe>t64 z>_Z$MkYWx*1Jo}yQ1jI(|MwaF_dWXWarFPSxd!f^uM~{V-K4d)cryBa=R>3*0KzMl z?RCvqSu=e@`i;zQs2|zC-4FnImB33O^^*0CH?<=3e?Z1QXBV0XB>D<;QGwX0)cxlJ z9U`dZJQhCz-HyZC!X6n6LMt3_2wTW;4?+U@wcu}8l_L%af>Yo9a-ENv(i&!rxzLpS zbBqw<@Ozk!2@W1e{JODVFLhp3Uw)ot5A4u|@q0Uxi*By>gOzQ)V`_@15(?wiGFgrd z5#Mc1(XOtoi}#h{AVbLM5JOqUd52y3_`;v)CvIs}+;nQu-!{Gi(cqwYn&@Sn@b|X~ zCvI~SO1IqY>*=~?3*T=OhL5(3+9Xu>zW7k8rq03*@`s6GNcbm-nlL*8 zecHQ9j!h9qgN(b1Y~bMRNOQDGuv?*VtBYK+t{UD|skBqY@5IS8^nFP=8x{~~*>S@wDqw`d4vER9zMG++;i@^iffKoU91Z}r_MW(nkNXsmOrp2stOZNK(@SK)XC#qes( z-p62(-0uFu^qjV^D%}jgAW=xsL#Tuk3X4aW{ zHvwV&ryBkJd(ph%Ic(3Rx&7#Gza&uyd8K0JX>oGHa$@MsswP2Nhhm##%uMc<+H|UN zmpbvndSW1--je*-d8ogutpy+FmV-U-t5%NP*V2Gzfx2dc4SlL?*q3|l^p1|Ue6XNr zwYEfgv)zO_f6_d=j*jTpP&fTpt-Yv1zKyv`Gk4i~ep7c>>mGR69zy)(%kG1dlFY6% zYPc2KULv3`qa&D2d44TDuwYBQ^E$tC-o}an?8WQq*MCz+&{hoQ0HTOrj@j1oNJ_}ltb>V1Vk;OgZPJQC?wY8|z zGQjv(HHSPnmM=gkmEpnmXq5+*xD(GA_jB!ikrpI<6&f7EgB|wGQAiHybbaN_c?cnV zBy9F)z)L@`Ec?+?W|YWAee&Dk%Gn3pCZZjDvi2*>nTu=}I^aT8e&>?Eo$LcTCW~tU zZa<@H3)CQ$19 zb!K;E8I0}cNn8BX2-K4U^V+|>Op`_BBv=15)-3yp{l+zI5KZt!VBAq|dGPKwcga&5 zI)=mGTkKHV0`N`%2n-X+tfu84Lw3p-K~5x{#8@w`fa92 z*29c-iLXMzdp;}+X6_f7-jPU}SrdoUO?8jt7X!0}qtP7{WT1WgxWn3+xsSQksZV(HzbUzXSIn5YXT}@I% zx14?77$2dse2(&cCVzfM0(slsMi{V+JVG~T=5GTwcal=oQcjY`@v)!kRwUK?&9IU0 z+|u=0CXjp`H3s>=tbYdu-Fiy;*W66=d}F+V99qsal1 z1G69KiX!X&_D+F0y0YPW73mJUb$TRS+I^SWxTj+MvP+OaR|itK2<}n#beiw$HbXPP z_A*P$_!p6cjxM!$SAv~4{@1xdGKspKF2t)J^i*%_1yToeI?mJb@4Gees*eaPf0>?} zZ$GIZ%eW2ZrjeoiG6_Xup~`572%mce)*`+U3v-?{!SS)F!DIbb$rSKU`^{P+Qp%{5MLSAoq=FSkbOscB^gvfT)7(#Q=^OBQ&AEJn7_ z(=^AWYft7!Ew=$J#?m_n9d!$EC{4W!j@+tQQl% z1Ub|2hfO{)=~iOa#K{QIrh@oDB>Di}I7E@6zx9)a_2g>|_ZgcQftEMD^JqKiwaoGjVTH6zto6+KzU^%}72^U=9s6@lHu_N0)9wyqy&l)cl z?1MeCv641!EJnp8d%Us*Le%=W~vX2Lc8q`+P;@P&e<-WDl6RmY{2p=J|RhnGN3N}^2)OKwdR&A=yl(00Ei z7H9^AN48IOmbqf(2V|S zC918N2L?C<{x}~WK|g`jRzXm~^`4C6zO}Kl8@b2ibqhokvDSF!m8SznyX(}&M3b$l z-NaM0{AT%UIkx=fyF^2LgU#Xq?kLs>P*iCSm#kGA5^Xq>vidPU_E{67X-y}Q3_`AO zauLE8yaBt4EXJLA@rBkR)~W4xV|8PDqp!D%+2Lt_$5eINOy%XGM`5Er5`_bkQ|w>w z25Hqh9LJS$tsu+|U!KtCE>8=nh@-1(EG*>LPvqCrR>QV>9BKY zuTJ#~{{F}Kk9qF+%v5k)*fo7Uf6w(*af0pRPV8u>Omcd`W;0}nBSOncZ|-%1#YOE4 zVWUqT%T?}4>+C|Bm*l|qm*ESeg2&`{Le%V^jc{>?#}UTK-zAD0t7LnmitSuTi-s8_ zJ{xo3{=DX67cJb#Z-e7XJVv^+@pV-7s~Sz>Ik5&Ex>yT?AdS&=YxU&c;n$r`PfnD& zp~fejvT>56Jp(sDp2%3#N-mrhj{?G}l7#m%sU_CKow~X#gF;d)XiZ3hA-YINu$coZ zxI}K06Ee4(YD9hgIg2*Gw|-5B-K|q|wAO0!mw>%fReo#jx%-P{fVP3Xa1nZQOMx+C zR3B5z{C?=#Cy5Ulgf9^{nF^Y3wxr_$oPY>{$vD2vsNATHIZ@v3PW1S z&M;wjY_efdooW*KFzz9IrW4iBo$ob=*$+Kt^0|?pmqt+6IP#0)wxYUYgA&L?nvTF@ zDIXV>0z00906H?0ZpfAoAa;Uj0;>3kl}3-HS&I%oCbi;5Xo1a1SwHPuAA%|cr}*By zZY2n7@OJ4!HnP@7#%>`&m+fc8&NKJu>_kOo1^yvdZH5+cHWhu7TwOP+kJE3XqWl*w zwOpF%FbZYa*zZ0!mR<_lH&)z9{>s?aPs&OHnPYhhuyIAGPOSp0_YzO$(kIu#epPK- zlG1eko;L%3wRO>frP22{{6N2oo&Iz!xQ@?9;AocVrG=s_Op$t^wsFD7=fgn z;$NQ-QtnjwM91`kb(h79`{9n*ask1;Q?$1}5|NH8wz$0nx)@|Zb)Q8_c)f;pd8 zXq=2?_GPBCYeOLKfxfvEMRakugcpny*1Hrtu`a4b z&1G9VRSk@L!(PH(Q7!dO$N^HzKP7hCS>~r9U!O$_Edax}0^^{vP+J!ej@4xYd_ojZzrWf3?z^ZRzPpq+TC!g`-KSmN z!@ne|PJ5&Fj<14-7Et#;$53_h$s2AmOn7M+^|A`M5va~~sj~$vLmnxzXPWyiBhMhX zYrrToK%Db5fvY?xJF-2kkZ^6ono%L4h0?k&1Ih7BY>&DtS$(Yu2RrtU&6xmo)hf^e zH3HA?xCu9DH<+6JDIRakvlQWIm;fT$QY+ExtT2Vgs-l}N^U}Tiw{L1-G_ME>Q)FOd0+*d-UBzi7?VOETTdDeQ>GF5txo(6bI}JmSD9cbdE_pGhkcJ zNrr{aQE7TMevUN$kZ5g}|N1bfH4hGSkmpC>XbWsoRfB=8Y;tQ}xtydujEIbR&hFe@ z${tdMGx)k?fTB1GDXp}rVv}L9N%$R9_vhiBDWyvkMv-<4PdmB)enP;i>+4QO#6a;Y zx>sTHqQcgg2vVFb`jWop{(O1JF(H=`^M2T|HjDA^jVX^1w@9hvEy?=Sld&8p6)L&I zL9G61r1*9U1`>b^yOSzwvUX_pJT7vTI=Nd(@g@$aw&S2cs<`vA0)UI7A zS2}v0f=)>Mc`>n(f<92&yv=j7@>yHsG*9<<=qw|VOHC;;s{wTh7N%c0fs-hPM;a#p z11(YK1$9S*z|u2?Eo4FOdXsC0RLxlBJPq8U)7{JG6m1hJE+sj!VQ#AN_0ioEVDnjQ zeuvC_m|$h}!@jnuMlbVp*YW|L8a}bi6N6eNR$yi$I#35DayJ0A=IQ*oU_Q3H0Z;E< zR=T6#g0F`{&pHb8z3^n?L{0+Bx|?V~SB>+#?p5 z?ao5e#Vb@RbuFKrMeBCEZ1cYP%T?&6bScP6rpgb7JdMo1o}jrMUXt+aXa1d=LJt(0 z$G50CV(vt_>VWcx|E-nw-x{9&aXo*tKls0R{eSIC{pMXkdeN-z~{X#-aWxPGIcHJt0I72d(-!r~2us1$Fep=#ZZ`NEVMjD&= zS|VZ-W+`j3E`*X-NNRz@x@;hw+VlXW`P!Bk*7Pl`^ZPs{*Ysx+KOL{jyF}B$^uOSt z-7*fLRg6ayHBHSZ*F%MV3#!zu<{YAy?|^22O{|l1Q9y1(usDp?cqXscV(_8C&=ARZ z({9yF;+>`r=So3~I4&y-%B+j=h!r_D!4K!CaFtl%;Tgevj%HEX(6s1IXKgn5`++#a z;XUH;SNU5tr_`F<&aJj~P1(O4HD98s988%(d#eaR1|O?;iH7Es7#4W`L;Q#>g5&I< zy~1z5w$tScWo;fD(dmVVtmI4El^u8yp25VGE-7aZ=7^b9_FVUG^sxF0H`>vAI#$K< zXCm0nyB;k)&$oA1Jd%$^GfemZ%d<4)PBj*A9)GyP+kIrPVR+fv z@oX7*mC1ENqP6|`1m6%D{ruQtx=O4A(wzI#jr<-iNALPw*jU5@p2FycnAv2}N4r0^ z*09+Vw(i4zkhcJRw=IOavWd?yQ6pqfcWiIbY_#|TyLIHBFgb{WlkprYM&!J!f&@tEP9qiY}3eNp4Qbw z-gvD|)=cAYC+69jt)>OHW2yA*Pwb@oMnGSaJx2q2GEC6I?&M(Gqx>GEBPR$@h|5mP zZreSimB{UI>7bk)u2%8A-{}mcdio)QmS%%m(;>gH1e+f7$SJW`&XG@4J93JXzk(;d zNYjEYM6x+%=#w+$_1TQi`-<#AM(kY8Mr zyX9#3VVJGemEzA)pWkBojxkN)kJo#YS)<+_z3Sz0>+)kM)zk zuW7Wn$$Bl%`OC=geL}CtRHA5LgZZ8Gv*|eUrd(o+6vLY*JS7UDEVDw|#}Yliea8?zVq2o$BS zY7X_~qWsn50W_erE~TeB+0^Rwt?m*MTcLL5KN!lWnl%WoOZo0>!`E}NAWe*5`cmc% zQ`qK+h_=A^a-1FfH&MHgh0#1Et5A@I<;x#DiQIrw>W(#1A#rIhgkr?x_tvDlPW5DS zP1QV@P3`?cz@WnTpFdj@22)Rra~Me9uSRKK6L-bW zc6;MFp}e5@7zEWw6N45i^!doH1i8R^l}9IMT%0*e16!-XfYr zQ$)`nTx_{f4`@>t71BHlmE>T5&fi0=L#+mK?h?N|?FYGw8)Rx?9Mm-38e^YK@Q$L% z%{y)D=?5>2cIQV)ieNsvBX%=sEH|`Y)F^!xbiF`Ga-Z;1>24~rJ+1rB#-%job{q1^ zPT*}V^*T%Sz4ul%8lmmXy%=yv<)vZ!N?I+A1^e!_$6e{cWh3J~q*Vz{aH2|HL06!- zE6KTM8MXHtA!2IqLE(V^LKR&RbhqJx?R7lq8Um-30RLJf3mCCB=|P z!x_kv)Wb%vqP=myI=skudrG-E|IM$7!z@>RVMk@lt@`x53iRRgvG!vY0AB5gM2>l>nSR?V z1hvrW{lhdZ@~fNp`yCJm-%AQAB}G~|QMWr@Y9uFRXV=Qt9&grJ6uR~hv|htHq51)} zQVu`G&!CR99uD6L-6dl9qa!peZt2-}1)`?$UDktZ<289>5xad?pvOZ*M|DNTcwu

vcchxN8i9H{Qx<*2SfZe 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�LlS{9$2XK`ANiWxFug-@jw^?0orf5K3Ec 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

#)*Z>5_7r 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 fsOsm7*{yJB05rS(}}fIw5)X-&bAMiAINJs74~ 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 literal 196713 zcmd43RX|-qvNoCo2<|Sy39i94xCOTa3Bldn-62Sj1a~$b+#P~j(BSUw&RrWOGxMKw z=0Er0KHL|wvvzgK*VWy%s;W1IyoeY*GXpCeMMri=V@GvI8XOBT6S0-9DI6~^;|Bvv zBYR`wcfb=xMllm}djnfWF>@Vz0}%r~D}4igemFaOTLT>nIOpUp4e3aH4z%XsQsXaH zFU2~XzIl&$XGc4t6GKx%tbN(jLFe)DtAfD)On!Z-p&xA?qZM0kbmF}nt-e!2GkID_ z+U4keb$+mXw0+t@^my8_xqNlLeX!cPl;-G~_qyJ}_-6D}cx`;w{i&GC7v&D&hJ zGNYLM?bO2`sROCFwFEy>4wjqrfPtRuiw}vyGqC}{tEjo7yKcq zVEQ@d{q1&P!|B+3VaZ%_tcEpX%_L8RZP=#+;;N%esL=rIH)||D)G$PlExoEy=HfMW zkh6R|H|rgb?~GTsH+QosMAdC4NcP{`9lTW#MZ=7L+lRZk2k8ZM2UYR&of^j{PAPRJ$rDv#3Swt=L1gF{GUmyxiK^eGB zMPwwG%MJ~!G{$>IJ zS#~XdgS4eURMx{QvNzlVNF(%@X9aurZ1jg#%rnL@HEc%~9eQIPgJWKoN-_VcMx_uL8bN2+?TIz|oIJue=!v^MfUG$BOwyuG{+pJv*oFi2rRg z;z@Ls$aX5`^~?Jz1bBC-DU~BRIZ2Nk2ZaueHBxv~?gZ4RAp9~_b?(V&n)?vp8>G2{ zGB$UXK#JvhF`J?};bbORHDajp*Iyl(6zNs{NUXcUf6#ooNS=m`VJIOCU)$F6bnd)l zph&>v?VZ_DBX#bwLVgd@9%kVx|GJEZ=j-yaJJ})tr000?f@yF+C&~c9r2_SV-Hg)qSWFEm%_4w< z%;V9T-*{2m9lxqP8N|-qYL}4ThX5~}k@v}5Mu%FjZ=%fSJY4gLiEYY%?loQF`*7qi zbqQq(`Wsldt}2~A>3g{`>*`*T zJpaikgIPBy?tU6c5n=d4Ssdd9lq>f>vOk~g_`iNf;HktcASpv%`?5Zu$ZU38P97~6 zJkKys1D&_Q8laq^U0{a$qF$a>GWSvXuUJ!^=Dp<#-nNPt0LmLF`>4H<@Chx+&k?? zCNzL1d}*z$qra$}6%A>tVvZ?FIXL1j(`TvFolYa8`l6`-+4v^No@@BAAM(@pI-2*} zw>Bmgkh&k%ag{eJZ86Gpa7ZP$K&|v}0WHqcG?HJkFoIa$r;Eyh#|b%F9)S}qfrJ5#odh`IXQYIrdvdUlBA#rF}sv+fW0tay2>t`dMZZXqow`ribOrUrkz)|Q_~}u zPlhjkDu3N(pMwp#Gu)L}@NVpLdcH&lg7&ym7-ZjHt!l_M>4Z=g4r<4g`xR2N3d z{_scqt>CS*hp{8iMqMg>`6d?iEt3?U!rsSj5t$|A!*x5Pc*6zuGlnT7Jz2Y(k5iY! zW;yRa28$G3*|;&k+-JTbv9-3TiA>8potSPCn6S8jDkOo}IF+85h-X%dtk~$&iuhqA zXea7h@?wy}sq2Wa?OG3NX!xb8aA7Os%NPjNQwo;sW@*HZWuDiiQ94t?))RmHUM`|q zY7V5Q!nRqxBvzQUBwX3(v-co`IZkt}V?YRgn3M@>g7E3K1$~QCBILW zWwt#K();($*TEGrvt&PdsR67Za&qP6SVf}{uWhW#x5qK@nOQ$J(>{*eqcNH{>j6 zM1NW-PU~x-ZpKtK6TD<&N%e9&v>ZN@<5xfRjhRUC7I%Dd=#y{0_(#$w3B2*gy%~_? zG)?G|)R5-)-EEgoPCmdUCPXvT@`hCKPG?)eCRsJPpMdH)gO3uhtHN1IPTMEb=kjK- z?3R}G!cS+{XG2ML!AD8_D5lcNYVy_j{Jm%xJuS+gAx+nj zQ0!LF$+a14m7Ixx{7|7oRuze4&-u>8@+F}(nx-=OQMA>=cG?0n-yNKqHlq90A8ev` z5{Jcy%l9xR{$Y@wGoM+4d~YTMroGziVo8?A1G$U?>Z|W zKXewlc{zJs9<95X1a)h_j@_R)@>(Oa`wnMdssE1<7x)B@is8V)F!%!t2M0U&>*t4@ z{|cIwU91flKg#Kv8tBpHu|KI;X=kg`6=^TGNQw|GY2m?#VHXr#` z&Nwpv;tOkzT?{2y>L?T+_3f+qhz~D}9KIeu>0y&jX`r3npabJhxA{U2tXIq{SH%mc zdiqKwz>3meZwnefj1%)cf12Ybs>8G^_UUTdov6+GJ5_n8ptXuD=B;DGRn5bv3g z7v^eYHZkCU{ORGV=ZANwSLw6O0hZL@hY~c;59g5GJ1vfdF5|(%$qAp8zT9vzX-*AWCd7w!f55{y{eDh%ofM;rxIoa`<8u;<0E|d)jGS-`R&?9u@#Z zk*eO#YJ2UNfImz2BZM5M-SMJk-GkRcncGcr*)l4Rn|RR0-rZnj)zo77(j5oa9mF;7 z{1ZkY9hMTb(FlPZd1HQshdSNEUJp6D57+R171+9sgm320Z+#|+>THI_wAs&lB&~%x z>%o)X$@TrmAnl}O$L8M*5Z;UI5zC6}P>{>N?9K=fVA{-zAS z@OOWD5Q{cwHpyjZPhg{xZEBvkTn~Fj!sDWJw)QU6445RJXW_2sXPg`GT2^*n6FkIa z2#L&sI4ndpjQbZ13bC5*4xf?Ah89HDZI)+c-~7ULxHp9~lo-j*NHptplH?}m&irih z8O9ds0JeIGVG?EDWAAp-K`p)ozIWMjbQRIP`Osr;!Q>y}e>kewwNTe@^KWaJYRw*x z9#D_<-|Yi8=$jw4%~rN#!TwG7g3slebbgo@LjO@ucHVuH&$z~&iM#&dM&pK%`-j~V zY%_?iJ13&`X}G!FbxI6>8r<1tXtehdx$QQTv|C40z!OIQn$P;0bo9e%-Eog(VM)D? zxjy9nH*qn?gU@#bPS#HgOhQ9YPNP}E=C`Y6@n$HgC9eRqcg#`FB( z2}R-B;_Am9WmiWIefPL?4LDZjKDFPzz_kpp4(u$SRzd%JUTfj&*>4=k@vgoL;UwS|Cl+vk<~axQUul% zgoMu*J`h=1#%fXq`cq4HcG}&~R_(vw)3_#Wf8X%K`g*oR@K%90OW5s$xYPNq1PqtZ zxt3^xA~|asN=-U6t+s!?5eG_%`(gK}VWofBeMi`5M=oH#;CwkS;0Gx?>9_Lxumvao zMEKr)bJY4JetEswlnTI`(~2J&uz|jUbtbVMM!Xxo(MnP3cIZ>aT#0U>XKy?_N1Xtm zmks00S2p4Y5IJ*LH)nNAY`#r+%@AgqC0RV%RaIImI ze4q8gS%(kN!y;R(6i;R4G~=(o`K+_+3#OtkU<`^9|;Y$5!KynQp0O(il3IZ`yOeWL*2#Lh*55XD-m5*Lidl zJ7v-yIH)$Brj5@gZf>B_PrLrh2$^OOvy?}@1_1HsdJIIC+r<#~vS{+G8*cRii8&lr zqFk`Xh}}0v)9~MK_HT&ncA6)40m9oq=d;^)vn)*rw+ATfP|vfT(ThE=`TNbzuPKkm z-Nn(W80e3lU2Fg^tc&@7@-EQHmJu*G5BQ_bE}= zR;;iA`)~DMHKKx{r@GD@BkDRl19r&pl1SDL0oy~iY0JNClwug)e$ToJ7!ojN49IW^ zyB0s-gL0GUfpjK=0sKYkqzlpxqh&-*%K@UElhj~2z-pOpis$E){|9dlUtj@>lWTuq z65rk5aobw+D4fa)BU5y|yK_ojcZI%4jDkH9LjhJ;@LE_$iozYcw(YjtNF0~0^VnqD zkJF1{^&l+8Ztx=FoFe-<`j_Pe_)5)niNL^nwGH1l0=N6gMq2JY@xWU8bbSzc zpizfaT<9B7v)Ew zB@KENV{9$=VDDToxNom-B@!@kbKke{3!BbFe~WLr-nqwT&k|i69>{AZe#BoVn5P`_ z`<7Z|FnO5wIaXq<5vnRn6Mei`f<&(D$D5ukCb^s+_fv3RQ^VaeEDtwI-LbXMQ=4cf z18*iDzbj2GI^Rwo!Khvr6bcx)hJ|$;#8p&al#CrHx1JX&>u4ngHFi~XNPLhbsw@g7 zv&-Rn^K&vjPb~ORU8l@0n!~vPQO-X_7RR2wr|n`ZxB#* zU%DCda;IyzR%kC6v?I_2PN_FP&MlVrT=(VU!zbi+LMtD~8!Pe2elzk1i}qP7i}5uh92pU9=0wkziIlHIb#@2oQ*XJ2a{{ zM+FX(rK@7|T7I~Aw@8PocEo{_p=*`Lc0TNit~-=3J-BJu1XlJI#!qO(tH;F|olrw{ zF<3R9656wUmQR~SF4^|7y$RH4W@p!ZXdqR4>}WAIk#%RPc6x^RvDI71a|nwg`e34HTK?}Lt6fWg}_}- z{7mc=3;vO~`RO>lQfx`pN*mK2r^IV)!#pQl*kLK zn<((WF^cA`u;4ksi55_L=0e5 zKWxoBy7`iUQSQfNRn`-;`4V|K^=&zLQ%0fegK`|(KGDMYr(HXg>|_8?YJf7W>6}ME zg;8gecCmz%aXpC?;%Kv{UB5PU>-0rJLb2l5jDm|6efp!N8i>;3Jnw8K3?;u|oH99c z)YhS2OF$@LB{Had(sm-oEeK8eZthjrk;L=2Y6SXC& zF-YCy{s=dbuwp*;s#0^l!WtIry8A<({zq zCcUQ0oQ}6y|ED68Yf$3?mWM=5PVo@NzPDGvjfEk5V37361@BY_<>J% zO4)I>RZfkb@q#YBWQI2SA0`WtK>MhAsqt{`*C$yZT8Ox**^@&E5wR%x$r$pPflAmVs=7 z5`>61Z+`yu(fTBGEg^DsD_bj+F?G;x@6TAViuiQhZ6JOOD(S#Js=HrOzX`lN)NY}! z4+S*LAQgV#-4L1IPiDCrPX&o+A!om#VUe%W_`V3D@e)JkhumgX3BW{I_6tBOf>j6d zb+5RyI8&=$U`i)6ZTkW0E$dqnX#=N)!MuQ%j``w4FwI8taVP>dQ9x$zkMp$wm3SOX z6)I-X3H@CHZJXh6F1d1e71Lw>>DJ4ebP66htXhE`Vf9ha$9iCU$=p2(NCR|CWV>$M zf4o)8P^r9C6Qr3*Qc|oI`D3aku)jdQE(b@)F8f;3J0Cei>crS@P3BBc3ynNK(|q>| z)NIcX##OATOxJX{!877;)jm7tX-(g_RqU2f6026WSN!Ph3dxebZYryTtGLFfeHoRv zE0{0zBVEJ9o@@y(+&%awX=LM_q>8&YsGJ8sBvHn%L&NT>JZVx&r0e%n0Ewi1l??Wk z4mnM)u>^mWqWT-9Bd1mvQRxb5R>ZF~+Lz9S-}N#Eb-2SCLn2!Hejbs`gd)H-S6Bk-&@J1zH+d?A0YnC zz*6lfAhk3juS&HcV+AQ6QjZ*>+Iqi@O2m?K>oy$>qY9ed-{;EIT$aEY!Zk0T4`MA{J|6pO#WAjIQN zb^qZvbe3l5MlG72g<5KFF@`eT`W*kuN4wjrnX8Y^Eg#3LFHUbygU=5AGJDKJ>@ALSyJcsUhxU=hx_+lGD*lGYV6G{x-wC{-srI}y zLL!31WSI_QC_|z&;ErAvA@4)9>Q3c%9@$6n}b?`s(i6053FZ zQv@c2O4rirF5$a6Zw=A%m6}_e3UIDzZj0cq6r-NKWT?WRE#FrDki8xvESU>;`oS_M zU6=aN^A_@Mc)#Lp8V5UzqIMS$DNOArMIkjpz4zh`zDy^nP)-YUHiPu=mZt9tHRpbV zmr2zNlBxeYe)9sBGi~;k(DANh5t&tWbY+p!&Ofh?uSJazvZMH7Qd3z;-lb{#cmqPd zK=)1&YtniTS67*dm-bu z>p#2uvlx_{e&RZ|IvB)jD&s+AW?!%aoj>9Roy(4p? z?KG30y~1WW1fiUH}V*Z=vB|&ZdE0qiMMQQ zTITS%#6#M=0Ja#LM@Dm^H*soZJNId4cXZhhR&ag7wM?~XNVmbJa5SYHd?g=xa;ueo z>aLYzby_%IR*`=St9)K?4rvb@;N`(qbIE6a0?u;i-wut?#D8{-+cYA1<7_4`+fLDz zVm~eTpLOa=R%M`xF9B95c(*dL)IM7258Pl3Dbxib_TxJ`&u$;FHJpQJD=J&_ z!hgw=|JBacT5LDX@*nNW>-#c^r%d+-2(_ONiG;?B`CHXeIYu4lU8KihUo4PeMx(_y z@ACML=Q;25H#g;hq7`E>6Pycl(w2^?8aj*4l`*d4=B|X+3MC`9yWLY_oV(uTFGh1j z9yoraGmdWg6bi3lg^XvZkNtnK>8A;_ddWd5KbQ;4@ctOl?HOX)GVH(%-&j3keaplQZ?xj4oKl(6qp-$S}hEt|gt>X(Y z(d#V18o7F+e_-BEnfJw*RzgbR{?F?Cwx6L!qIbI`^62v!RRAKzpGmWM_nUde%^SZ) z+enn|-PDR~9^UG=g8Xu6y-b=`i>vhhD@vB@PWTHR^pcaLGC+7IU+H8$=?-vWN}R=R zTCs3TC>=`oI!(Fc^g=J!85M7cYXN<>W|NDQml0e)&+U$b{WI^98+`yGZ)W|K672XZ|gBh}FUeRp#8LoQ$bh%?8JhE4%QmoTc6|&S{Lyu&U1P&rH9OUcTz?od*;hnhsg?X9D4q9s687XmHUBjIC&`J z?|APx^(f>&PgaE)=J)#z2w9G@x$KJzgfa9$QQO_NqUV|4oH%(uCWLjBx+DzerPGZQ zF^JCEZS;MSO@3{aj&M8)bK@OXfWzyD>0)ghY?(C9lf!`g^ft8yq$vfl*L;Dud(ydG zx9Q_Te(R2&q51dgdL3lIim^|V6Wj&~7>rhL4#c&Eaw3l6P>4RCH?%6u&aY`{y6^)W z3;>??GD=hIadx+U3K!QslDDeG!c+c6&ZpJ#yI9GOxqM-2=>bI3537ru?$9$qUHxyJ z_>D>s*1JuDe^IgZp;$$vBY(^fZF~1ofoy@Qh)CF;CkG^>HT`t(&hG$Odqk~o@P=<5 zg);Oi-ljC{2epXo-jnq&TnSo5z7H#NUL+sd99aM47jNFsg9}(3jCJ+J^=+|3{EDA^ zotAzrNqvg#*2FSAAMUl#yv2jc55J=Ot4+M79k21;UCUYr^F`0fX#tj!?8on8ntZRW zdl~s3Pm|uMr50ovQ1w-ZnePPFFjTfek3swp)TdVMF_;y7U7s=gmfi)cAlKH?iwVo} z@Y7EWEv!@^P4OmK4p1&dyJsB?=E%E?jt&wEx6_liXzeD@`D8EUnI{*>>s> zdSr*@qxC})@0XX1RD{1(%zpIbfm^>h7xnb|3|Gk>pk31H6;m*njSqz#bk9|`WBrzD3 zSwyzHoNTZ0G@AbV?OcnXn%FCx*#@hnW1}1Q#G+$)<)H+2uXp3NZotQv?1}jw( zdo1k203d!R^)H{r7{)!M_rP1)p;kSb7LkeJu9Mfsot9#^ zWDeFLK9hrgb#mR(d~ZCGilOfxXTF{NNfu~|Um2K=GvztbrF?$)NZ3HyE?^t?<4Mh- z=#+Tk504w^losjwt!7F>R*|hTbd&-_OHp>oKQWo15$tn*gju zQ#YI{)-Q2+ZGNXG=le-Hu0#d}5XQRe%zHf0J57a04Dz41lM+&3E=5M3+gd`YB8)@~ zyon})`10AxwP@H|o{)xd5-Z(%;4S12B9TJLDs8>zym!F4WAl?o3R@}L*$V3+AzK?lz>N*A?K!c0bNLGT?=~IgZ`I2K& zH=~Hy-s<>sav%P8&pD{M#9ms{nS@; zU4s7Qdc6(r{?bft@0CbJP(bQ;_vq3?aoHIlA--kXdvAW#J~dIQQoTt9b+xKjDP_}< zK`!t{(4bp&UmW>v;5&nw2lWt~T7-8`AmiD{T3E778t_sI9M<+-O+(FbX19@CGBp){ z2uU%Q}|?sU1>wU&+5 zJ8o_u$20*X`C%0yAKguWd8 z_Knfj>8?j|*7L|~MD&sY*hwe00yvp>{=hV{}8Bt@F7fhf4O~frG8uznJIlp zAtsL9xP^%2G43WE^^sGrGSsoPm#3=977(cCp|jL& ztBGUp3=A*YikUX4ZUPCj++UisWw70s$t_kGbItg%SF9Sf>;nflz`^Pxr>g((keN?S zM+r5R%Yr$;p~05rdzw(S)amV#W~v6DyPRUPcjeL%+i5=IQfUISuJtd?b1&WesRbgk z%e&d#Ix1zQ54XN(+k?r!J$^Fu4Y^EwN(=<*W-qtf{n>8UPQkTVo>BG- zx{p9Mo3F4I`!~Iq>LA&seU zq;m6nwoE%4`zhVS-*Jw@ZjmkV^XWc>OC*$Cqhv-gPD%-+&6Ai_8F@6?FWg4!Hh5PJ z?%>~r0A+w=VIC6kvjGti37aF!w=0iHT59;NKTn{l-=vsb^lkz{9O}MQKEhC<)uq$S zwLcnAWVOEYs2y8lV6VuB`1-`;3mNsLeA0 zH9~?Jpy43ubL(kMV?EQ^_jP!I;(X2i1^4sm=6mCBcPxztw|A$Xt>&R}3O|6Wzfinh zQ-#t(k?PGyV=0zq2R};Z!1ZRz$i0Ikw$olCBV)l63UD7rFrH=00sUEe9=*2nuEP2b zP<$VBTJWpxL)X*W<-c~>Z5bKL0N18%f`=JB$e6dH`?2x5DaQm%8_&uMuaMtGQ~+hs zYsRZt-qb<@WU#$kD?77`%CBAbPDD{RH%AhWH;4Z?4iAr}lKeR;)vkIlhNDs;9Dddm$}KPjp6El z6Uk+9Z)AxNtm+F!XrK~N47_KZ-dS7>-GZr6f?CCQd4*wI;Z8+I#f>MA8RsrKv@_{h z_09G5fpZ_v`R+&SU&_2)UOO09tW!~V+0i+pmV$uC+MVg?c&ocFlWV6x69T|hc}pMU zY1|Ezv-cZelgXLao6VL$!$wfGl22K5H2vb;GVls|X8j2)c?Ih#JVggXaM+;P_>W-X zX?TLg!_8&4xOQbJb2t*x{AFF~fO{X8tR);kER+mER8cIBdS<^7rb?KNE5~g%=)iHP z(L}gk5}Uyl1}>`t{xUxy111jkTI#6YrUE*2-A7ma#+}C30vMaz`#C(B>fK;0?7)Qp ztJ?FSx?z{N;Uq8&QKa6;XpUJht{+cD840o<#8VpAsty-iFC)br5i{?b)0QJN*yoS; zp%`8CHnjI~T$R8UD8e1tv>Rgx%2_mTdU;5r!J(N?wOKbS-)EZma@2|8aT@`}5HTxcy~yvju;lwyS;C zgLVqsP@?K}_3m`2&K@Y6`X&(kLo~tBwvZE2+4qsYuA3u*SDx%UQ?0ARnn3bHRnuku z2H$G&hDZQ6na%6bBk5P5XiI{j`5`!n-b-~njJJBQo=2e}F~s9}8H48^k_x5L@9X$1afxwCZjbds`5Y{&E^UPqDCP&MCY&U_y6l**+I4CJjNz~uECghH`32ji1-_Q zocEJ^^Oi7GNM6q=$ysh5emnj=O#N3~1mv7z2Tbu#Q_I#E>j8l~bCG~~sPiyF{*Zi{ zvI`~2?e5%5KRQoWudmp4Y)EStgt<_T&jZpix8+r}4D#cYRw6k4Gpk4BQ*K8A$Ib*g z8T^Ha{q zeTuXhXKpr-n~vQg9KKPm7NLh)&G5piUYXK9Lss0$C4nb?;;R9K(lpL3$l8E2sc zU)>0egxXUTmXwdPlL{PJ9(JK7&%QwiFxiO~5N}W6BC1KYG8w9$5j1e}f1T^j4iVwn z2z$`k(%wNWU7TFLLnCcLCTJDrZe)m|^6!xDD0tP{@>_B({!VtN^6NW*LHuxS{u=Mx z8GSCqDtc6&9Gm zvB~;S;&JyNWQCjp^9=6GEMOyg!q>N?K)<3RK_=iojJiWVF5q+zrcp6~|2IkK_9_%f zZ<@_%DytZHxZg-~nUoA%=2H5WOHl#fR8Ov_1*>%}3oGZ-Xn=<9!Ru@^f;c-hy+)#Z zY`H&INMpA=>%>@+H~irXd*gOUdJ!5h?|H=z*OV(NHdiPr1$|im4$2n&PIU!~1jiZ+ zUfS1R_iO@)GE){t{^nurW}bMl%=J;}zjyq^z2raYg$Cy;;Na~ zq^R4F3guv{l!`Nlkq3*&87uNsoxli2j|u#?@cS7Wtc(6mN~reOBlp37Q21Pz!R(td!|7WN+QN99AFfnKDfB@k z*)l_UAbL}(mo$6?)Ezf-6Ul*wDu05ivmA%IvLn+wjYBhz0&a74y@UyQdu9p^WfD$s z-(Ul3zEqN-1kSPq{y{J1KuRRJB6<1S( zG2%j&GJYdQN~r%(m$g5tMJ|ZNLd|AT?hJ&fqJ~*D+x~!nM$*ChRZ5|q;eEEMScUv7 zF8T4Q5QcD0upHwgtFmz!cqX^IL!vApwf09~*qHtJRr2#}OT?nb6uaT$1i()<(G zZDPmmJ1f2tD?JrG6S>3)gp?|L!JSdY6zGmpwrrMRNHLVs#-x~4)Kb2r5+k8xux>ef zOz<@(2C>1&^jP3kvowc-COato+t~$Qxc6P)LC7phv_!4AsGXD+wdDG&zj9zGi0?oN z)h*KxrV!;pnc*!IJ>s^d#0AWJuu`FlvcI7+)^h4EO&51~HW^=$0)^>5zPLYGStbE+ zPXaQRi%?=d)O3s3dhM?Ej0K39A)uw?*d?iIjoRQ-5LxcP&WyqL-OEo+JuG8+AVS=iDFQGCL% zB$i8fYYdA<{LOPr?xy19ypWc4ga+Pkp`WuSPwGbdk4`AX6{!@E;g#*g*a z=x-%lN})*C_}Cb~aVd5x)f5SlGBh0;B*=YML`-mtzi5pZimu50Il@gkFbfv8>k&Lv zLb#XS~P{F+$y_y)GtnTSDICkErsD2McXL# z-oUMbhJetwnIA)FFrj@N69eJE7Zx%#$Y9E{9s>IW3;>9}XPOtwiv>8LXC3$fTaKxs zh&#E&6Fh4fI|7)fBEjU&hHB^&s+MLLSt`l~$uN97pA@B#6lwpUXgPSa!jOi>Z7J9g z8&i@i*F0^`=Le{|BAZxHASqv1!>HZ;7omhV!jaA za$aSTWDz^%^6XfG>}m~2SX;hxqG)vNckMp4oIAJ3oaC#NP6+jikO>#D zT=7<&F39pJcHEgKBljJW#U z=1rdXe^6ezCF3+Nu1+~LltED1n@Iws*Z$x4lMrQ^@P0IG@&1=CrlP}`|L5G-2fN{U zTna07Z$}%A78Kg#DJ-kss;o&0IXr+2y|M}hN^-Zh?-Xi8XhcO5(kb5OOmF>n3Fi{o zef^Td6xYgIf$0{Z|JA>qWvEoM?L=dPRMh+p8;G&^dthlYPFi|YU=5%prEKyIg*PKt z!251VCOUpL^Vj&)zJHsB3>yuR&_oe75y)tF@_C<;lqmW63vl!Y4s{5&FIjI63eC;N z$r$AeOJD{a1?k+Q(3OSOy`l@z`jBE`R}=?dchONx_-@)yP)XFTCUaLM zpMhHu2dw~_o0!iR9)6d3;9raDRL z`ZJO+)1rSFM+hnwjG++}aFrZcI&6m`lePzKxn{vD!;+5bMN>eDBA&a2`@)2UqJPVy zigjowj**Wc7W5t4jU!XmsQ9$v&6LP%rHL83V)if0>^%)icJQ5Jdn&A}Dd zpI|H$mn3>eFjGYP#lWeEj?*d-ZhFgCG&=snh(Q#QkIMcyI$~Mvr~0S|CIdNf5;dPm z=B2Pqu?{eDAW!syv$*p@fu$PaM3W;58G=$2M*-z> zWFNg92RFeUhz5sc_<@c_Bfb{P{%ol% zd&hK)Yz@EzprqMFjM-@#`GHw*)t4mA zfEYcW?2N^pxcY?IhWZIFt{*m3lTigkPa$zuO$#N?Fq*cNS#A&w8DhsMYSFaR85u-6 zFaZ*zQv58fDJDrz{V$AA=vNNhoa-s(mReX7 z+)<~rskFZt7n1BRoHgjbD_|Je4*7xKkpN$-L==)}gLx#6@LCd+xLul)J^7n`$u`F` zI_X2aJ9wLIPkx!`l$QNB!IEuWAF+O(u7WP47}9Xt_&|>Foa2EZG3a2l6mrE{@Q8W& z_fgl=BlMwta)`gVo0%6aRj}JK&X*zoOy|r%0NOr1db1_@lzMwk>O3cUp0Yjv*FS5k zk1vhR&_({IOTOTXx8R$-%xui>SpRv=_vvCS_%ZO?IR77B^JQiJe}Bza+*Zd04!H6Q z{{0MaHY}jSk)XEiO$%Z*jiNj>Rv3^EILf)Xc4au)H6&p zy=WY;)Z`=@B~2oMiW%R3=bl%C3|k)EQZMNaKc!YIx(%f)9Bz);54oi|@ohTAUp5|| z{?bKw?br5&3Fm+R$NP6g^wAbiX#>ckB0QW%GZ~1f)KmXtEsk_=y4Oq`wlm`(B*YfG z-HsMbrdx#XtU)y;8)2n(Zq!t{)XarID(racZH^$m|Ph{t&!6AB~X5Ct=9}Z z^C>;Bh_B@P-*D+;?{jjp~$B#qQ!t zOZ@d}&&E;~LrXl+iGgq-n9_C>SvQDQYMTn&b}ym!v&k+mGqP14vNIb$oMFKcCp8#W zPmS1slEJ#t(gDWmX-(-DBC@Z2kg4Id|7`(>#mJbU|TlLlD|&P?>j)|8}e{dK42JLZs;<_m5M z$^G)*O_ui`_m+!a(tipwxBlDYcn3AHOWqJ#gXD?>TTwz(Mftqbw_S%%^e(;5-eH4p zs-d{e7-;`C1>gT_+_kO7b0pNW9?@6vf$7T*9(Q)^J+Ku$BajuRp8xZRPf9D7_@!vg2-up5y54-(PToFb46q_g&CRA0rHN*IapWLIOl-2GYQ=vC z4>k6&YaP@Ewr1g$x&1g1tRS*K54uiPpzR@4M4IRCbtln)xzrcS-(jBZcSmIdA*90I z3nm)Oec3_*oe&uV(CFxW1R!l;z}B3v{{zT`W+b<|#S08vg{u$zPa?%iXeO6c0_w<* zqX79Z??Xl}pJW5k4dwxb{SSd^#RkKF?)AF!B9#pUs{jc)5JbaLm3Q+$~0R8(( z&=z#BA^_N$2lGEAksJF#TmJiSJ$lNb-`rnikphOP=mX1cKd5;t6mexDgC9Ddu|G*b zfCKI%-?kJ|Kcx)F^{gj^|4$zo9<^N182QN6L$L_}Arl-j^6;ZR$9lNY0@z9h_dlBj z)jXMUN0k%j|^u;J%LZ3*UsY- zGMp4jdF0tLXygLM_CK<&7kfY7oj-WsoM_u@njKH#NNSn-mEC%{g?l*_9+TI4j^m*9 zX`Da#4@c&(B87~pf0Xf-udYOfi5$% z+7<9^>Qxc}hcu-sg9XD$wO-^+t8$Z0XqEUIcU2rMU^`IRB1KKV6LxG{1@f#@hk0U# zsEC%iVCkgWDLhh7`uO#?Jy~N3&APCvi!;R<_A7zLBHddR`nMFqOzBmU2N<=gS#cv5yyDZIk}y5`+Ae*0ymtUZzEI5q;9{2TGPz;IQGD zkE=TZ9C!h@$iHan-d5bZR%qrKda5<9A{(g7R(D(UQ7iedZG0b@ie444bUY*$L9#yc zGZBBrd?>;0uL4EYF{5I>n)fB+jWi@d$FcAm=dvPU>edzA^zH4?Cn5>(LaJ%`%ZbE2 zu8F2={Vl(im5^Y4hDgTsUL90uK- zM=L8p`A@qo#bcXwa1kT1Wj2q@6UP~uV_64w&oAdhxl-CBK>O0OUOFwW=3gD!Km`Z_ zYeTkuF6zp9ZB2G_uia70P?3S@A6_~c$);<)XQk_obHS&Rl?t+r_Q4=O9ZrJ?KC z@cE^<6+uNbIH{4gSG{)Ra))2_=6b)1k7XHevSqgdeQTCtI}E`s;)kUs^uYyByo$9>A5 zh)wvn0SgK|)Hx#Z$^h+W7dPEXKy03~p+a-~UM#{4Hc?ZC^4Sr0m_M_mB;ubdZj)6c zW~(}^Vri+7KV0$I{}^)QRPqCNgO`_F)Pu|2tI{+;ZZF)=ex+`XA3S$}LsASnV)eH( zmY5jydxE^wNl`_#m40>iWs$EbXEm??a_f%;bdT z`FZu;dM_kKrgV$OA!}3)G#D8BFT1Lw+6xEz9wBKTL+JY!!KWgl7QYrv`m_)*I1f2e z*NKXY3+sM$GPDCAhH8*25O|*Vn7EbW<`Cm!%?a;sGP^^)=6%s&Q-6XE6AQz@vU^~6Ga}5 zB$@3=s>dKt{rItf2l3*zfsuf=Hh%62nQfUyxCe;>a$ZrLPO^ugH1k(95Aals&C^uK zzj+0ZBA$I|nVLYsoMqw`|Ft#CN^OffQrhOfOJ8ZfSIg#1Vtw83;l;J$7NB!1)A+nk zI}`sn9e)4`hin=)7nu?49qjBoLT20PbCAtk|#tIt{y_J4_=G@ucb1pO3HjBL zldPXQv}OARZCR!ptthcyUGx8y)3}v6R+=Q8od9ZsUPG7PHv0F+uPphqvh19c)U}eb=SQg*_Ov0A_ z8o2}dHRZ$;+<^~ISA8hsm(y3guOECm84R#M%Q?lBYikc}&vtV2f(s3`VD-%-bW2bz z7Z*!Y_-9fGX)(LcRDQYOd~Ms1{ga`Fo@f2BQ?uho%dVHT1gYe4sdK!aDGp%CT%@%H zpD#xU@dC=rv|=aBkv`csD63c?uSk`2NmhcX&2ELXx<>HylXST)LTB;Acqs>3p#jaN zJDS|AN2HHMX$ZjpgS2jtdkK`wgtf)wJQ{CZzf3_fr8uVy$7k1*LR zbCyG!iJ!;ahATz^5>LfJk8@fE1@zL--UAuW6~L43-fCs&N8^H*HA;laG|^OI-k`C? z;|9ANfCwSG2`Fao+L*y;$qG zB!gI{-ve8lBZPvH|2?}kUez2CBL+mSm7F}_O>xQbMB{7=UBc_bM^89t3rCl@2bh$zpXw6yfTd4kum6 z&kfp&_4#spI4?43$7mnwJho)KYAZKa^k=xz$m?#^V{sd!jT>p?XofuQnCDPlwkgbF zs^{v_t&@Dd3N~#?xwOOSh$0_JOT{tl#3Sx0hSSb_9?lkP$Z}H60e-p12ds3ivcpEC4@(7c=E{O2D zs+7pVUVF>g{hy6l2EXp{L^>EFK|)b6*I02OK`SHrNEf2|Qe$QEz#yOj*^jOMMMyaG zmSY`+%*1&qC^;g>RjANF;kMEF*M>waqj3f#FW=jNkp-@u?e{!<`0K8TR?bF~wcv+n#DQxvAt5 zxC7@2H)dw3fGlG6<8RdzhPDs2x~H@6C-9kL`H$jHT^}cUd>dYU%Hl8d2eky~G{#V= zwwnJ#H$tDMCAFhXBQCq0SFFPIgNgFazZRJL61C|{%|rIbFkP{s%4G#Xz#Up^V)PMU zNo~8t0~W1L_cdRu@3s(OS)BfQ(=pZ`M&bD$MNLlcQ_6+egbZfAw#D|PeZpPVnkWs= zOz;(V^;(9Egp2V-?dX+d-PS}RSl)K-t5(aAPIjvqJMur5=0+XW=3g#KaDVGO%U;&@ z-^n}HzHZ*lKH2ZUHGKNa%gE-6VD-b2%~O2`+F$^U>Z%DKaJuH@RXjXAj}97N<>nQYOm@)lqzO@%9t%eXtb<6-aH=NqrQX@TEvbvT9wK++@=AxdqFJ|t>F z832)qyTrju2@bM&u7k2bpjwsW*cq`NkT8d;SlQfSoi#t^kq7+mDyS#4iwu z1}cYR|EBq>qEX_@kBiTm9Z?LuhDJRhM$%@_0#cXGBTN)Bva_->2FQ$UI5D`~PDJJU zyT8(cLoU?-mc~XdlkMKMz-S(K$G(VcMUuoh&+6L@v%hT%?O`$O54-2EPdFEQo(grT zoR%1(avh&y&^cmhpgNl@>9_Naoa2$s?RZ);Zm1ru2dyDY&t zrpr5A*YXGv0-W|K>;ewhUq3GTNC6JmC}Vi4j%Nb%@1l*7N-aAU4&rqD40QQ?L8iDMy~C8165ou=R7Cgw0gck4;BLtm?}4<7iXO@ zyxw~2DG(Np+c5Hf(GPNf;A^1^h+|gWHa94!$Jz7NHvHpRF#ADYat@sJd{309W|1=L5Kh2K3RQT-k9oe+jVq+5lwNjq_ zLI;42QPJk_u5xor$~(cg*-ZX%+ExGT;*CG{W5Y9PwN&2$;y}Z!6Q;==oca@+*e{4# z;-g^5Q1rXc&DgH8=p%eTIwN2Qa*ReT=+Sqzug*MIM%s)9Xa-lg?I&&bI>^8elKaXZ zEHmlV7Ncpke8&9<%J>_i%9BYs5qb(WVw%S;&f}d@eV7V)fK)M9*00X^Xb;{AqA_5q zfQswBn|GXyV28t#)$zB<+qc9E>nu{}gx)sdhv%bB_nArxRlv%@Wd#8neH3eNuArUZ zru(lI*J0-3kN}MN6rl=V_)GhR?_6iG+>#F45=+ zT3?phb5tol?x{NIm3j}wgn*^+7FAu~iRZ(?5#(hABpPHDfVZKVoqvgu{jo~|iTNP3 z{KUR0+&&7yupw*XolL?&^>|+u1ni$Rl|v&@);Y{Zf&zCi<~CH(<9E8cJnq6c^3FN` ziV!p*EB@MF?3*BOqtg{`+#pTTi(qQvK8ga;nfCf?eKtt`m!)S8u5}HHsgQx#Mu%PJ znh}K`4+K`?kYhEyB^KR7sqPNZ#b@JYUBzz{zvX~Hgo*8M-?6FxGS*`?lu+t~zF0>H z2&a^OHu%Ti2pBQKQ;V9%T$ONHLald5{1=g0iUM*PoFlZTRB+=Q-nVj4@o@AlX!vr} zFQ!v*25z+c0=w&*f>tPEDl0vMuTxK^jdSl6rQ|B#0LEfNT0^*$v&Q;!ejEW&QVwV> zyv6slxiT*OM(w~~2PE-ag4cxPM%kT;rjXa9M`Xh*z9%pr& z-x9R=9Mdr-=N~C?M)lR0lyd$TJ}q*hs>zh$f{_sdqw6QdD|M#m<0H8NaqrkqXI&Hu zinV2(wzY@2)}wK)##(cj6T%5i{q7`lvMx;N1#pjYey2#kruu!Y9E?4Hzg=43$!0Gx zP*e(ayq&jMe*3rN)h?6AmlBC$F%3tsEbEoynIt9sE2{88(N{72_`5*h&nC+soWQ#) zR$ksNyopzz@lmjwK!>}fLGdL;zshRIJ>gn2PXi#MeC@cAmpF>LcK?Pw-I3A_;nRX- z$%Wv>#}T#o9r~?UtrWYeeY!id^IF)*Y^`3LRVklx_!NDOKyn|-F4LY$3zGB~!c+{J z&?Uf^^o2gPNp8jo?dZd(H5QfpmcGUI-LD}ji85)WSM(7{c-}~~K{uGQz{0;?OuL`+ zs7`OVl`fVwhN-ctzGl+uL!1aL^DxPT(`(t}{gVNbkD@W~0h49j(k5aRMUUxVl1Es7mazbd4~+|{N6`Z42oY|;t-A_;qa zJj85!ikpwKZx20#qCeE6a`5S3eh>XhqcP!}Hi0jEl&sq^^6hi}SxKakX(xVNry=&- z@bMj-1BO1xZSzl`>54AXhI-SKy`af}SA7Uze_TAe{!*s{BSa&=?`%x{D!gm@1_@D% zIcu&yzL7e^8ARKg;GOI@Xv zDi8L%ALfGtH&Eo8!Igdqo49->g!4;x5J7hlY1h{%H3Oh4N)x#9un?O`6sP96g!foE z`^T@nHT>)xBrp3?Mwv+SIbE?Juf@F)N8zA9dcm}14)%Q80>WArWv(U7&qdg;~ zjxOlgg8`h~ZI5A-aox3C&v=2Zc%mKH*S!{|ik4vp0J)s^WAs7mQJp6SHI~AEL7RW$ zAo)iez*|V$gub-r_*L4nH6Di#GZd@e=sT? z5k##c{l2?~rWR<=k{iXkm#kz*y90n!C1@y(>p4dUHQKU2B*bFYakz(6(M4GPl&ZCx z-=ASG{yc{ZlaZ~l$f3ILzo8CH)|R{vVI*FBCLI8jw@l0>6 zZ!+Nk?0M!b(`;}tkbQ3 z!vyN%RQO%Cz+R4BuCJO?Ks}<>J{dt==9JpF{?!46CM};JRY-qoZN~4xX98?nmXJ)o zBhm^P+~#Ow+N54umZiOtsO*FNJcnUe511*JaHDLDbONEsb@9!(c$5>hWa?G4n=<^` z)aF@rG02j24e=-Y3tijsvGG*)=69!H&Ueu(EijQjdM`Kwe+0_+(F+VTpDXj#$gLZr zVo9c(lS+1nukF%VSQ$&sJy)-(onIet1pGYpW3t2(T@yPJWsOcj29;lywA=|Popu(7 zNUxR$6e{`lP?IKqcl~6~P{zEal~%NOtiL<}=_H}jw@G49on#CC2{$BzHvTR;!g5_y z0Nj}w7~FE)GOy?SRNa~8{axeJhF*{bS3WLdEcoo$&r%drpl4MDQ2d|d)TenzT(=_J z8IFE$oS$@ zd~}i9!v63>`Xwewr6MM0oUw(9iZQfBnd+hrRwqZA`EWmZf#@KwjMsdUfs}Zk6fvSKg)k6+ZZTfz#pZf`3LpNh?*_u~G<4nL$7yDj9etZraFTkvCzH+2` z_S{ymQLU^fdjP-O@~R%l5yQ_Nw=~X4%9m6c-?VHkg#`XGDkjyp!2js~G@m-3L=ZU@Gs-LAbF^xrbWpeQ?3X>yu|0s(ZFkyZppRSZ!|Hnu3FVS0)5<}>Z z)7-~qk?G{l5Sk_2{J%w6X@hi1bIA6|y+smCX%a&Z`69mDP=%-3ux)!4Kdx&%NMOkd z-J{eIi*Bupbu@Drw;!%ir?hFyAFFhxZaxI*)$a-X0kNX&Fjcn%P^Z9xcnpfd zw_pS%OuiB0BpU?#{H0ZYph>eywLR7IzVjrb#D!l2pB=nTdpWQ7p!vnYk5ee_F2sNm zol$M#_Uis_TbWO=; zi!Vu~5W&PrDHFCkItk@z7x>0>N*70E-FBZ}Ic;Dh_>0ZoOq}?al_8*f5DJA!NKrmV zWXgvvq%2)>*>vYB0*E zTK!m(bu@rlDtF68Jy|cbZ|0Ev4qGwC*k!@aD|$iMwEfGQH)KPQ{O3Q`6UBbxV>UDq z*)vMYIN@Omeb7eHt^d(13|3U(uiX(@cuJdd9(1{Zt2jR!+V*q76s$t!gqgNY zOE%~pQ3zJcINj9pzhv6kpr=KDf zL27%GF{+r(Z7i@MNgv+L#*!6)yk6rmzCsTphUOzc4#(Wil_EGRgerlT-JqK6QDdsl z30i#zzR#LLip)Y)LFMi|42QFsPpErIJy)2oF^remF%4a zi2-J%>xXiJ5D9aWPw)e*a^U-&LE({$Sbc4#<}NOMSL^Pv=_O;fyOllQ%ikltm)icD zZz!>599Ex_(j1*4P?nzz!#;fJ-t_Fz3+bCN%MwWY+3Wv@I*Gdu_vS8nezlXIvY>|3 zMa)Kq7i+qE%w%9JkA4wn^SiPDWdoFV=ETR~Fz_P*dJ{yD_3$(`C+iJgD)IT)TN-k| zI?9nLtg^6dHU-!TjJjDFGxi?X{o?WdnosqM-fZdRopn1qsG6m3EA|>^Vg_^-*3SPu zfP_3GcFbtkUrvFTX8Me&K>f?ptsR1fzHig8Pq7`iUfvnb9gXpxQsWISbL~e;y77c} zL18#vFUEmARCKxtKO_8!F-MJ8EFjQMKtarv7XM59^%XE{{YDnzGN_uKk;jzAn+Nf-Ac&<*pL$6tx~ZaEVdzB; zPw@AW+OydDht^{^W=yo3I%S)ulh$fBZ4`3P|bj6bCo zwX#S;j&x{W{wlA)3WQ*odro)F_>b3qjnCvAC`ojCnFjT4_tufkhecQPk_<#^ClKB=#)-rW+= z-Et{lHy`PRg*v3~`iFzONG7+>fy1h{#XV{m%Tx!A-%Lfh(ZA!7O<+dM|3CWK|A(O_ z{egPIDsF;U;6nY^3c~;=PmhR$!ll`Cz$i2xA=IPQ>a&tvmBB;vcxAE`&3YOO;pw^w4~^EeW9%ihrs^|9|p zcQF>Dd=fyTaOl*w)+y#7UwlQ!ZkamKHg45u|}=R{MMYY0wnLY@n}-t zl+yEq2M`ZuCFEp-Wb0UPO=BLJ8Eswm$H|7>xH9#~?$5qr_&f<7R&*c$JMK2$3iO*@!R@xX?LEsA;$Zu+SMrCp9pmic2f%L{^eN34jS84v8 zh$QrS#m9fnW|Uu?QgSO(_l;6m%fsbL@r+*^8$?^$dN zj2=JxFiXFv^O6E^^OJ~(tclZYr#3rCZv+dp4MRJWGWXij&@&;0r~om9MbjiM-Qg7UwRx7i9B-0f8A;k)2YAXQL4T)9!ituW%P2W(0{$a6mc9wi2jh^L%0H zIhb=VP${MVbnvmN-U^`1A`vdw@>u&6n*Y)?t&A=DxJ>VhXkb2_Z zI(TVL?DYj{Hd|KDw&W1cClV9m1=)!Md*CS*jn6{&KkHi=efSJn**9RB@#3E=nB*AT zqFXrVHd6!D}j0II06NBC~G7xPtmCv~VMyo1K zHVhjNy9J6FcSDO%u}KRIA@#Z`nsGtQ^#y%7U&Vm2;}uqNJW%wvD67}8w*cMG9r@c? z^)t^Ai-i^*^U?WbkiZsGGeRX`>#aK5D3e zAbc!Py&X%rI$0TKtWsHob8)$cp>XD}Hht{BCj4)5PLu#`qP&z>46)_;kY&=PN|9Ng zI??@_E8224>W3tMh0D^~GSAd?z1%B!zbsc*1&G!d74!2?4vp&Dml68luUDT0TENPV z5~~|xht9}2hDP4lTZwft9L+jB2}CrcCeJ1EO69Tfkhf6x(~s0(#@!LaP4Z6$eF!g7 zXV84zXrgtT`vzzBsb1u@C;TTxEYdz_!lvDIT zVE5lf1Xh}y=01QCoo2PkzGF3tp3Q-;s?6H$F-iAmv(F*S=Q4L7x^RUvfskJ!B)UqE z%$snb=(OabQRFH`Pq)(TW#AUoo|hz1JU&eDPOM)aLbQDTyVC5CHrg>kxf`Uf;mcSP zpYz*x==K22o&^1{{=e9#ws#zeXdouHxPNZn#DLB48{gzR#dOI*wm+n3 z`tT>hsY*%^^O@&!*uLDPs$5L=8XhzOd~tKWX`}NP|k7!16m~oSvZiMNl7Td;O6I%8^uJSM-nI- z=`YqeUbWp1D^zBobfD?*9lJg4je(ir4e!r1SMaR85BN$R=$sV;Zsc5Afxu>*PXpp= zXum}ul0Zg-Q^&N1f?M>Bk5Wmovh2VR-UkLhx@u47aVY!?0Fro&o<~nR;_jGbz${~@ z{i1OnA_fTznv!lXn3G#uI_@Ioznp;^?nYieC}5^N=^fJ|khu90`u4G}bGJm7HS4|7 z;AJ~wVSWob73&5<4|REz5_c#oT)Uc&>2=*!#L_ba-H+M+N-^WtSTCxp6RSuHK;8mW zAJm+4aQ9J1#z6e5HOL8O-OIQWdl(<NlyTbJtl~kHUrmH z9E7~?+$e{@Ph{Kgl#kGt3f{%why8l+VdK_Mj4jsBUe0g{ja!3QLd|o|;Z?x?D%`5` zy-JQZ6WtG$O!B+xvR7PpB~)J*&Xx8^=nek4zufmP-EnN5)nDluL#@Wg-scZi)IQh; zwk+!5jP9sb+RTOGkA^@iuAb7__8HhtUPWPo-GV4_m!nau=}*9F5ISl=o0Hr-W$!_2 z7Y~1WIBF{={?JPd(1PwRobi^RaS#A>(>Pau?IHRc)VV@07Agz{S3{RN6GN_#pd23Q zUfW}=W@&&)O5|x5pfGAHL4vSCFzkUY@Eav~> zFM&%F*A5o=EO4zs36|D@6HvFPzWv1LI!zW(*6^KPyoh7I-GhQV@@KrN61U1RQQfaS z9jTfAT4=fy6_mkrh;*c`n0rp*K&s|*=rRnsOog^J$n z2_T8(QPIxAe1|wgu)9CMCTs`g!SEI9A@vb@c%Vi_8sO8)^>xeuNT;2w5Jh^6y#s+r zEQBZT!6Oy;UKQefNQp+uHW72Iuc0B(E;eJu7yc;)mDgK5(ZdjVSwWBOpAjYWgJ&9}6tDT&RJA+w+h5IZ zZXbM7l#e4mB{tVO$o&$7s(DOwiK~q$fK+0{1=yy~%&h@f`-nXWHw0f&XJRq}EyG@` z5UAah9()niDj87Ie)yTnl%pQxXw+0BXQH+%))si6!hX2KqBxMcZP*dq(RXkiAz4T zfDr<8pSo0Jel4+#sOwmqv}l>}IzGYaNT2k6y#e&k z`bosnXJct|KB&~g_U$f%8d2b(ye{0EAhkS>+^&(6B6=9uUCqLwHC0$59KdukTADLY zFY8xOOwv0w;@Zy#itAmL!}KYlGz9rb@zAuleNJp6L}@Eg3FRAQh>YmEm<%OmLUu-# z&=^@)ggpjCp*uMcD_Zgg|vOOhlX zt7o36v$1f2F2%RFb6N_;w~nr3sQ|Qj#6k@2{_WT58&I>~^AGvs)G_HgeZ_b(q(3fs z=O<~oY~tP{s&MK$GyDsc8R%UJq(#oz8WDYB*DSJW6=2yER z9CbB`T|+??C31KG+8~UzqHoc3H29kTb<%_DRFLAS2qtJU#TjHZ?h}Sh&&I^2%SYlB z^HCR6-b%)m3~a1-`)=2usV&o#=bp%EV>1})My?YY9R?=^IvP>?$@+B195<&aW%EnxVc65_7_e|CFpn>w7 z2$=PugSf|-PvrQB zwMl~1m2q?T%#8tFV*CWGeJwb@O-jqaW>B)~kL)H?It${UdTQ1XZQ=!CFk{6rAmN36 zQpb*I>bLs)$s9hpor$t1_S&Cl()oP!6}Ok{H=B_ z+Az$6;kQsjO(*v6$y0O;p(h(w8UQ@ufdvYKOI;Ae7Sp-bjo(Rf(%xsg4!n!>kA(v> z_DWsjOh0G;Il3dvU0xX3*6WqUA?%JZQS?`{^CV+?@M%J>gB`HPp;Uvu_Pden7=pPc4S)(Z5fl#Bhv z86{iC(S$VKAVkh6vn)X+9C5wW$Q2;M#k2g+(XYbxjP7}dm0)RkJ&i5*{~8z%_G#XP z4l8ijP-0>Y=N`>7A4@C!S-~+_Dwj8gu{<61MmBF_TQ4 zPh@D=(OCuYh8NI{__bBYM>^(x;OE=rN872UQLr||l11N@LOu(upFa-Bo+hJZ?@9myhr3cN}u>$Wqs+c}MWIXrS`Xg|1cR?YBR@A9l60vl^?1^*yq!ohIn zKewn;y``|qAbhXA#ef2_T&})|gIaf5FAuy$n7TsXUT<~nBmMt=y9(js-p8Sbdg>ZB zx)XyEN=q2^zw`qv3aVJ}AIllBlE(AomtibHAhJbKrbr7A5@&CeKzLj-+<_cTBP$uM zIV!A(+Y{r;I3DKY@}ds{P(9c3t}Bza)a_;{S>2~JVF;1@iHhGCmbNt*1vHHw_yaBb zfB=iad|Z|jq*ojz$1^R~nFKe310Wr|(x*LVN_@A%&BbMaq5;N=Y3K2RoSX=pmGs|e zL3ItID!$RFsA>FV=JN*5GkslJU*VL)92&pzy!bekR6>3b3$+V?u4}8>1NG{Od8o#I zQl40@b=MRKaRu6O^ele=91g5*){tx|xy3<$GMH4?kc~AorDolU&$K2`k{_K0l?G8`{L9g|E!q#z^tS*mK0y33*p3} z8=~u-_?^(C5O0<_3!#;!zh4NwivPj1Bxj(b4;UoldpD zU!x9RbxL4P2)`XoVoFAOJ#zy9kqD0K7c*2*gPg*h`)n?Vp`IP~NmEWi34(u0zX%TY zN<2EC3mV8cx1GM{%7I-B(AE3=YhhF(Pjr)89J;8}0aH&}*^;(^=Q=>lZj^s|(u%F&JbPHX%Go;3v;9 zogO1Ihm%f2{(?Xb${q!5!??aLBAThW_QFHgxY1ohn=$g>cf)mMo$bVaB&Xcou$4#PC4v zB9qUY!J+&#qyvcy;|oO1bO9>t{&6)R>?i%ihOpLQg>=2t`wsyi7UG>W%Ek z<2n&@>9VwNz$h%04|pnqs84mtv&ykEvd69JU^nxaIIGQJQ_{Z-Qs^LemU}0Zq8&zj z-G%1pTAcWdrEx8w_MXzsnT~V$0{C#XOrGKF;m2`HzKCWp?_9IM#T23=qaE5Dknm@# z|Ci=ny?a$@JiK>GtkF(z#6wc3nHKsE2#;?V9P-6)i>)aea zIZ1X^Vv@dBls~b);Qu4=!s{HnK5?|Ffw9ZyW{{qq?(aq-%TS`8WY6#C(irB>H`{bD z4`G}9DVI+?{YNj;ou-WwPFq#8-{qG1U{LjpNWaa+;YsgxsX1~;-8y9sKnsdXqZzJO zvn#;6w!TqByiDMJc8tDo{$x9P#&z(Sz4kcwF7%IjpI4$R&(+wwGsa-okK2Z{o}#A$ zVEPWSN_IEiOwTT{bz?*Xe}cz$`M;u~3D)L=BQR|&9U^;zlJ<-CoyD$~Cqeg;kjyNlgjhFBJl9IgEe~^Sos$R z7d?hkx^0=m2&U@C|1%wLKCF7+?aPTq=-l&%zI1n56#$-=^lnuPx+}Jl+8!X+Zmru4 z8dUbX>c9jTQ$sxGJ$Mk*!iRd9L}`YP2x6s@)ppzpwf{UjdLM7MRg2{l`ju)~lK2(TjO=p(Dx}*}xoq?Eo4?~n_(Dqb)iEBElLg-Qhg1x;@)gvH3&>N%J ztZKuyvMZbUsJRbsR_6G%i~Y8LnakrFA-ylRD7`PW`N$iJC9-KiE%JZ<)?(@LtL;C< zzWHtb9r9Ay*6(LhQ;|Y3wsqL#!(%W=Xr6OhD9u!&j#lL6U4V^8K_y&lbLBJyIzeua z34Je{LEyR)l~655a@6{}2v_I~H1T$UH=uGXD5Wj!&^4(NnCJ6p!KfH#4~l9KnY)|%U%h_MKR)2(e6nzVy3zsk&2_b+Jj z($Jgkg2qH8?xw?Je=?mE>Tv7tgP!gsuIDuT-MO@3+(!B33_kcg`6(^EUm2S=W400gAvkj$5f ze`UQ=GYQ%s^&75EE(L!tZrnRHB{b1Q=7akkq{wco4Yca&YG;37aes8RdGQ=4z>de; zf@#~gN}Xempo?j1(1xz=Rpeb&N@dOeQHFK%MD(=;J&Yv+0gjYysTKb{IaT zHU75`D{fWENOo9(i$OZb8-#>tm1oRfm_}?_F4B8klM~Gb*cW&!2Gkbko$y%w#gB_F zr^qgFtxCHpD`#5!u~qAC4xicA#BBRVefB(d#>qyW=I?~oed?HC`Z{v#2?{l$ryI1#K-TmpcK0DQ4DF&JT^}5)(HPPTj<`Ur-qV8t;w@lvsuly|oCC|INm{El% zSNfUTKJv$UG4I>Yz86nxlm=Ql1)oGs?r)Ly_?~)kL~&V1Q{+eRUtcs_MI|8mIwrAg z{QKP}jmRQ`*0^RXLOhwXeT9F&=kOY=5htELK5G*Uq98@%CrX%h3$5F}(J6)iLi6Cp zS`y-p*+0`G?S?~z)-7WC)Hd}TzB9k4lz{>|%yxJprxiU@^7+5UTj|`CZsT9wH+}P4 zoqMDluSN8$=ZSGMP25kInSz3sP{D#YG#;`P@Uq0amh>(0#tUUOtI*yJ+kC%jjWTLD zf$WOv=jvSsvLq^*mv53+0Pd8VsC_X^o+j&8?y$ApW4+-+v3(U(hkskdJOUXOmaBMl z1xR`6Zrj-~zP^F&5x?FWT(z^oLrxuf{*VWU+B`EOd42_4zERSx9NS54@n>TVxyQI#Y>UjX4L2Z)bUyAV1%6KN?VeZdi=Y23(&vtkyMy<+Uu39KB(0$eJ2tXG-to1ds2At!5~t@F zc2by^7RK; zPS~HpzAL}%&mrm9reE@LRnmx+r5xJ82foZY+hE2s7932bD$3sEvj!tedz#TNrqvsa zFP=iv$86eW`vuPEb^PNM|Xv}IE7)2HP3332et4$go!Q@~KS*fVIMyyYnjciJvm+@*uXOqv{g5piSqGzaLsB3% z%-0(zIBA1A+=VM8jRncq-x7mPK0bMlPNrrDFcwvIhU(yODo3jpF+>s$Fy|Nq)`8z~&0<2rb43)2Yo{Zhq-d#BQS>nDenJ)GZWNC1?rDf9N*A_4yWI|tON#>E zPvXg>$`w%{I9XyY`3>qbbq?77;5*UK@ru?DTyo9Fcd)&eFB8ZtNLGewcJ#q8ixnwm zhxi^oksNDdp@L0W=Apc_U_y_*B{yG1iJES`d4V1<3}Ifi(%z5cxwZ)7<6TuV=M2l~ zbvF_=Q;w+kYdybwa5pvT^Ib$Qf?*-&!uK;egd4Ec+ur4%&jhu3v(LTxW3U%NEI*cf zDXJHOCQ|Z^+d_Yp!3Dk5yeztyc37v6@qpaUAkA?{VBUfd0CUDUH^_Sd)BXMJ1P_utoR=9KBi!~3 z&JD6*h?Ata!L`UD)7tsl&hP1PFkdIfO)3>inXS&Kv;;-&T3d6Zve-1fy=n9R?qtu6 zyR@DXc^X?5wEVtS5BL5FX&-LPtRN}askG$iCgKPNjVM zQ8FfLm-yC^IA9z?K%kZ{4g$J`O8|>GQ+VRdSXr30bu)Tk>C0{dG`AjQN(zB1!nOt_ z*N`O|NOfHopF^TwFHO^K;M5N_~%`;oR2Nne3ti6t!utN!y>);=n@fEcA$Mt|0B zxnD0v*9khCMI~PTd^qR~ccQuNdz>yKiY3nb);R_@xUGAfdWKRqw6nKZmlm>bNo7Bt zjlmKmA~mk&(6TL6Thk)b+Hy3W#i`@s2`QUx3^vXAGQ5&H#lnx%Kz-8OVq&a{G;Q{N zS-)%~*Cn#lUUB(v8AlUbYR+6_pTRP><(k z>3sUY57bZXv_Z;(4@ak7})9_o8fkEpl+w+e%WgOHL9_BbYc#%02N!tZf=QQE$E^dK4#qc)bJAG z@LBy0?l2q8<$sDD{zL?SVh0u`W|n`44($IGI{aU-zp)T8N|{?*TDcK1vv3nJideh3 zDw(^8I@&uqI+#1S5pfYQiaOdkx~MuCo05&$R)2p9^;M?U}$00IC7`}4T}-k_ktARs}&L4jv+fXDxzCjsCP zkf0!7A1eTOFyNiYV8{RfNFxz)>CicQr3O!wEz3~CL-e#p3#)y`k5RHnN)eELk83h+ z_gnC4!UHysQ`@oUit&MOe9PbmZm#En5$2VnN)6vZP{!i<{mw8}X4HIqZR5sm%(UZ4 zBbym-*qbn?ntF0I(-5D*)JJ|hFS@*#RC?gBRn`91TG=H7Qm5#4;7uUPop5L?;XR5R zu0%boEPbUcnQ_e|IBEMrd9#%3?p>iyHx-xaGm-ABMeqcceDd@8QLlXi(Rc8qNz^_8 z0D!E?^9izsc54avN=tvbgaq2sS zG(?sVY4qTuVM3a&V-4Ao2Ub~cK1F>1Bw7-D8QT>Mm8SfstlLI^e|0z#FYdX&&u-N7 zE@eb`L`dd-c80*YU+`(kRbe=?|G@)pVV)maJH`v!wG&e`G>c~Es6iR|USe-w~11{SVS0zPx z!d7d@74|xHEqXD_5lAf{v3}5p+G=K;3vwA<&bXmg26Ht_o_b_bsW8^J*KlZb0%;s^ zwmqXnZ1nbKl4r-xT=*LY0aVY^Y4c{k1**=>HPuNv!Tcb;h4$IFmC_zu*!3jO)jgN* zWDhQujbI;87Yrx>5HL`1a8S5^DF_6p3jhi-DjG8cv9K}`2|5NT6N`w7abSGiUn&7w zvLHxF=DddPN{7nhJd+K{x?TIU>WrHJIlM_J>quGWf^jb=kTYzcZX=`vpGx7^ccKny zD69>&mP6yWvdJ=W0Kq+xRm*O+!o0H6D)kzoTVcA`E+$Tq1Tn=gc8H*0uDQ^~aukhb zTpCWu-r{FHck3hi*M2PmcSwIrl^IM7j`LRM`sUv%6zDZdQ5h~CWyDe&vA^p?BGJ)# zG|>~_U}XFqp%1K=wlPPof3fD)UhBPX0!3v3iV6w_2@3h&q5{BCAb_$GGYc!D6Opj= zTwyQ)WpxgW&##;LOI*-D;)dysDbQ%}GfcHPc-SLw;p`Zv=Y0U&DV`gRjSV~_8WL7A zrB_h5;KPup*pF#+nv1lD$5P}SGtTLoo^$8r>XYTm;>RXu=&Bx89RqNlpk@<zKQrDo~jc)?`6 zXiCVKey$^VQ!3Wn;Kjo^QsE#av0IuYCDqvH*;ii>aRq%Hq_Y;Gz%2IVSFxgnmoFdP zl_w6ZpS$MCsg=U@`O_1;$N=Ez0|E{X4*>@8m)?OL1MCUZKiz;vMB)tbFO}!lT_Llw z^=y;PTnj0wnz;P)QbG!XegK5om#-3}VY7~eTl{G6!?tL33{DM9(W}QTuM!*;Cxkx$ zHbSYqY$@bPPTLxF!d^2SgUsa;6Y0eXuf4X;4j|(;ocET8#4s8Z!i_snjnVelyX*2U z^&EKC%!ygZ-V24d5sG7130zZm51B9rLznwHqz>PXa7b`{Zv;DR2Zv25H`}X?&D!_A zY<;$wu^&W|DD>w<8!V1BH=iSht4!UnnOkb}hb&Ga#PMZ*T*y-KFW7(a=6;2mOkdm; zGqE?%y4ilWIXRayj}NLuB>Ixm_|tcB&2o$Nbs=jHkIHY$>?*IEWHp3lK)cpCM{vo{ zim~F1+T^yX%A=WN^NUO(t`Bsx-)SR^`p^N!-F5TkCH{gbCX<^DzaxynYuL{Z00vyl zDdU-~SpAP#qU-QFgqUVytPjBOV@At2f468hknw6N=iR;Y;m?ge#qc#8Rq%Fm?e^U1 zm{M64y@=37(GKchm9CUzTqTL`+Awbwswc^g*?Mpxst#2fUSPD@SxB?h@R7Z6$_dp| zA&QI@!3&krRqZ)(&`VRpZyC*vBgfzqtkigrm!(=dHrMr8@xv<64rF4uNgSh{k%m(- zthriBgNwX???Kcn^px~@yQ*p`Qm$mxg_?YXJJr>Fd1t7u9iKZ^X@5YL8N*5)_*Alm z-`Lm_tCY02MC52AF|xUoV9m0&{cuXhA-%+<@f|_0(1oY)8us+#Txi@3HqMbQ=Dw!1 znu2X?YG_QsGQQKc;;Wmij`rYs+nXy~RnX7mPu^D6t6;OGn6Bxnws+%{;^Da5mN`A7 z9DnB-8%qy>|RG*k+&Q z$SO*;wOU@KY}p5G#&O(eBIdZ{vmw?gFMA2HthMxUR?5Pj^DejMQW&C2=8EH*TQ`SG zj$aF&RrfUx7t%W3GdMwWB*`mA-Z7Q!IpkL0 zsyaf0+N}x}JGW)~zKcPGBr@Zk-k=L6I$&X`3F0jzjwhgT@t8bFyyQmV&1kQ9#N$2f z43*_$1Li=;up(yf2b=D}Fez@*7E5s_^B&5A;PrfNCZzh0or2JAZpB&_Aa~6p!Z``5 zx{bb}+2_w;xblnSm4T`tSK%GO|Aq(}+0MJhsu?mBxVQ2Yc~nS}AY*RsyDBr0&ZJsu zr%aGPf%`^$c#q3f4!SpWAP|%@K*U>mRGh~aVd4Ww{0tq$X@N0CW$R_iO3#h{jRI8e zy9jw$u~6ZXTmv9Kk&r!b{)W`zsc(t|@t!C(bL%tCMyegb{pVt)-V}`)sh%p`5X8*T z?_y-$17#tCy-dfX*|73~A}H1lOh4Kcys>!b#>VgSqOO*>O$zRPz-^Fheqa)f&O?`F zIhAJ*fwtntR==q9Y7mluy0tB#1yoEWi|yfE9jU7-r>0@MdMov)AIEn)8T0{|;=3L5y9NIK*#&{t zN{;f!T0tR!zW5)v{g<^W8zT$18R{Qrja){hYGRoylJI6YI)I66^>ecb zfdzt^OnwSaY7)}V@Fe zpF&N{8-p{4>ZO&^Rm_>JJ5^5DzmC;RV`vE0lgCEH4{60qwReDf>{Ech_cQ?th$*uH#`m;O-f1*^HZdee) z>Rf=x=m)@g!Ns;a^;!(0N!FUgL^Nh>z~0Bt{sHxYc1e_|2ezG9G(3z6 z-odflkd4&0tbtc$A!^UlK&4za{N>UtpRPe$a}hHv1YcJb+4wzwS*fm<4**bpO5o@O z3Ut}v!2IW5qZ2WJ*;rUPkjObcADKz0PHEdVF#bGNx=a=2eL01SnCh>4wgn z6dhC~N^rRW40F64!EGlvVUZTW5c-gLzoGFB0( z7%(+Z&;*eE>w7kg@jLM^leG;iJ6_+IwBeySqb&i5N8ywC{IU#sw`s&yi5_2(w*^ps zK?35+X&l0|pn1>G+tSIRxWJ8!8Q{QU67TL+<5P`>e;Z=S=^X$9)XN|ha;l#!$jt@Z zsHtCCCvk<+>Z~UiR6FIa^ITbpJjI>{<(a~Jhbo%!)B-ks2am6{`RO3r_<1j*21!i&=~&u=3h_Z(g#U)X{m+Ig?x_?9i~IJ1G_%eKl2_fP}?j%AS^QcCzC8V%51L zQ_@4E#)bLyH@bi+Bdky|IeZl2jy}mi8udMzaSzNV|MK2o$7S#U&)D&};Q`%+*H01Y zO#pb8P^|&$(+CEDwmQh@u3JD#W_KWw4Qz4}Ltg+7-X+}lULFgwRCrQtexr&|)Oieh zP*EI`z_&gWnvOn=!zgkGRhU$2j1xmtxYI(D;vOyJdPAkH! z{hErdSQ5|vjC1?aIWBGr=GyX+q|9`_<&%(j_Z{&g- z2vYoi1}?bS*xCR81zebbK>eS93rHv+wgLwF2iEulT>NLq{cGR>2d3(fApi1Ue*+gM z-w-SW-u7LhZ*#gjp*!vgPUn)VB4sP9+|Y^s+!QlHorY%7fHOF`93UGB01&&%^tSmi zLiT<4R>&=S-WiS5Q#-c&*6ygVB3t17vO0zZzLV3)eiq(G{^;-3-RM&BmEWghNNC#G z++bnR+?Is`ay5^tCE@!C!D2B7kS%$`po5bKmk9tspO_F=(py%~Ue!8XM(Eg9<-0E6 zp-64Je#oAiIB{@!nH^XdVxYCN&gCCXlfA>_r*D883>d3-n))PcIqSXcJ&ldhqRRNv zBzn$)U&NE+27LN*`#jLDJIXY60Rw?8@iUvl*puRVs-lv`PRs9ac{hXi6Q#AJ(nk|4 z0hzFela(aNw=XVLb=>cE0#UQ?N*lgD7IRC^MQ#esqmi{=tw(LD*|;^Yj;sO>1S@-4 zTp!hrt5Cl_{`d*Y;F`(7b}wEHvg^}MYm2+KF?(Ey{o3ZMKcLAo|9ZH&$e9x}*A_?V zS;MUcD}Bqtq2|i?`LJE5&9TD!6((g*EE%J=p7U4ItFosO@UbBiC`}75zLVX6z$RMO z4>G29;BZMiaB2$PNLSRm+eg|zoMyDh zToDWyY=D7-{F5vGZ2|yPG!%3UC}d0~p-)6?#3ZE5|Bcv;)M1vZyL%3uTlXo`$Q0#n z@p^(M5zmP4=N6|E%zSa=yN&M3(7n&My|(=TtXp!)aPNm*uIq1D!25R#DzshVxB*^! zXSBC1rv$RBw59VWy0^`kNgwDFRk!V3Z#0XXBu6YPJz?}u?Hj(!S?f7ZFxr5ftCgpM zH}i&MutKCg_E!*|a1;1pE{DmwNkkD#2bvz+-?mI3oyWgtCwJBlAbkMvwuk$zGranS zs~$s->~GnHC7Tr1zQ7q{ISKgWmyExOzt4D>n<-j;06ufp!>ZBg_Hii3AxDkwLV;lu z)e|v3b07!Xgod^ZELs?ZOWiZ9RPI!K02ETDX!QmN&?l*{o7>0Tw_$X;?ROKZgMEq1 zO6xifWs{fge-3thc}_<(#Y*dUBCY)ZfYGi(nx|^1q0u7HN=p8aOgsOo5>X{1-)5d9 z??*dTMmWBCPf#}k1*h1ID^nK|U4IOXvsl+An@DL+@!95NN0l!8+7WjXUEli28L;QA zPB&S{mSVd#PMg?lNEN(yOf(^`m-ZD>_*;7Yv6v*qsJUKpZkx#^ZK{U5`n}m2fYJ78-)I=4I`JIU`@RPsS*%{FbxXPxQtuD8$D7o z$PUTn1BfN+paPljjn{sQG2wY~t}`6}%IAUcHaf>fY5a($dH9QI{t7co|Jk1Bmkkz4 zsC} zeS~Mz^iq{Ll{gr;{#ql?t+HH+h#8_~exy~`ANmu1|2BVfokHL8UX&y0^La2QT}|;Y zYr&!9OCVwJtm*FU%7cvV8*}9eNEXwJP3)y(c%Gw+L;?MBTz@RRLcB@oC~w0WHC)B|g}$;_ zVQXh{^_ z5dMKP(rtdY455N^C3kV{O){iYyMiz_f5@(y?q7HLL?gDAi75F-Au8K?mSLXc)JO87 zI$#`W+r1aoLWRzWglXWLEUcLemBm6;^&)I^$dv}P(MK00#r!a3Of*``7SIkSPJxmc zHX#^a5LODtNO$qvIE;cQp`G=<-2TZpSW*58KtaKP$ob#H1JE&m10eojMrfo!?2JYD z8iUE$xranZ#UwDlZu>v^2gq=&>JQVq7L~oNwm~|WQUL!UopnL*827_#q;yy(pxrzl z;0)~%=MmtQBEkeW=)3S_&J6QM6(_KV_@<#>PJ416o{ zhm~yht0->B9z>FQHm5~#8#`bIA^G8&M9n&#=iOSbj#tcBN)C^ps^U zpS8B zB3HDRZBo^{>IX@xpzXvU$t``tbEse3^ek^vGpyzp>r4-OpG9Wa%=;&Ks**iimA%X&kLilfucKbdD`k`vYq>Jv_Qp%C0O; zHD0DcMXVruh-N-Cx;3#@!k65WC?k6}uB*7Jpc3A9lB2pYIFYpUFmca1{>1R{Y4lwo z!}9d5N1IbVK=I3CK(0xii~ixKz2jiRfxF%tw&8a>$;6WMmp5jXFP3qExRctv{)Cx$ zlAE8HNR8%fP#ZS!~BgP{s|QRvWGudDiBIlR*Ap5#vmnQVsXx| z>tPi#4&45Ch6d7`gSFv|Q`K02%1?>aqa>YbHTsgyKi!E4OupQ2^}$UyX|U@+EjRqu zg|jM34Dp?Y;tkrnCiTr$z2*!ZZj(q5Z=rpW6nEJcN7uH@9^P3B$1FAkHn4T-HW7Ve zeICXfY+But43eJqPi}=E#<(~ATAdTeMA}f!Q!6Jhq5=*-->SJ~yCX%oKRxaY1rU8U zaHwr+L~fLLb5$v`l+tL~khGhlNj6K=CdfL7(T@P57@T9x2tzh70>8`o02J-6>R8L0 ze>&!t80c2YQ-A->@wyAl>vR!lg;@JO;zd zF-Ia0Z2!pGxz4Iwh4HD&g?hA!1|773=Uv<5Zo!I*6!xvC1)>iZQ@@>xN~X{?%l$RE z2@>9s+h>MqF+V(=(LldbT@Max(~tHc;R*`gPTS$EXn-bW(shiEq?`Oz@l)m0!is=K zajwq@S;JUNtYm`^k%(U zItq6=bT4xmG7i`|->FcMAh=l4@2~-|a;8;>R7B62?t{b%?|GL|G7C?nj%R;ak=Q># zGz26Z6tEry-2MTg{}>SjiL!A#It#0aiZe11DU<4-VP)nTl1(VFXZs2xflO4%C8)6e zFQWnh01@e@5Q75@I6OK>E(A2GBcJ>_Qb;F3r1E= zS{n*pzZd$~W?%?j9%65G`R>g{m^X*EEL3l%78EDbCl%h#d~Hq)fO9_4-ns&s!j?of z1dH|LC#9VqKwf^&QO+_Mp1#rSnG)M?_Fq&|3$zgMR#Jwbrkme6Gumq}U%SZ{g*C<< ztIAGFBdsfDwl>n@uS`;FIT>ff6X!@3Mpe$+IUY*0uT>4yzZCIdrrWO?1Mb7J7WNS^ zP6qBu)n#ruX4LO0bKItvYjOOJztF5oV(eYN5luj@{476}q@Frf9?h|h5y^lPVd?bi zO13wNyxHcj%hISW9R7*Jt?)%A*6Q~VHipYuYMDTfM%4KCF8mJwyFF6Jg#N@ilOob$ z5m>YeVasd}_USl_Z%W4f(1>U&@5=Ygglqu}o2)w8aX}-adD{RVK3o_z~PA*2b!Uc2zWh}xy4a3Z7YkPQDHRwXj&Z{1~~=!s_5y973;{3 z#yv~3ZU0Me09rXqyzu9T`v`fMEwD0k-Zmebewn&e+DD~_fAIYsf?*d_0} zH0Ng1D4(rH3#jStAG1wK7~!#t-iYXkYDTJMgL$kT&1lljo-p2cxq2}RuMJ<=yLga$ z3#dr&sEtEKW;qed3qnOnV@a4ZNIsp$DK`@a*zffbz;b5Hlowonb11}{-R>UgO_U|c zszeZ)ljSqsr}VY9!|4q~XRZP1hDGipwnqB94MgSW8RRZKpO}{4IHgIHMhCWp)R%KQ zTi>L(AL1eyMcwo}%4OBG8OU1c6A(EWc;oupkErcEkkEa#4t`~qt&af(aM!wH(LK-c z8n2hF!6nDu>}#2C#>buh^l2RzH;EIDCRAW=$L2&@&@$z=U}x?Ffci^@HOnWBGGc)4 z#(zq3MkAq@Ja(H>@>iCT5&RXqDxN69$)OYDw8lY0C4LrD_MTZ1$fO(475<4 zvlkj_x6R6YR#s8O@9$N^9&0+ds5Dc2PS?O+ddj9pO7zRl7;*+Iu4|rfcwKJTmelwd zm!zn*TIK7P8&cxc3QnREXs<}q`$$Qm_g@mh#|`H2ezvbt|;RpPK>q{%w2LM5DohAuw2s}^`B~?@( zpR!@*n()meFKpEb!G9N@RcEz`ZZ!e}-eC2xwPsp zv@dxiP8JX~CVjJ&;_8c!md6GhDdKN{@L~}2w$ieM!UP79yq=SwOspr)ofCGA(3f?U zf6vLv#t1^2bV(xTR?x2l1C=^>zG#QE} zd|vO#_L#mp-w!UKsY#~_6$f(^i@v?9J7W4BENs6l%x{bae*pMsHZ1NOGT1%m#2MUD z=CN%(nqMn;vvJrDJ$&Dt#09Fr%Fwykj~N8h4H9;rlTIDWPPxe^<=OiaMNM2Nd^@=@{lrvi#!`A zN(3oij9>D4zi0z@)dbxm{`eAl-C(}51OTrqSi8DNek>;QS6v z=VgQkaH_@wj*}bSA!X)$E9`fLk2(!+lo15{XB@n+O2?PSf8FhZe1lXESda8udGsf8 zMuEH(=yjpa5$U)P&^%Nlz!914_6!mbCwNKW`DJylcGj-(GwKq@9^Lf7V9qUDi8f{& zM3yz6c}PnPkM`%)t0c~S0>2;%1*wcX_GpZH&LljxZ7@R)p_Qul<*Oj_3UdSSc=$r- zaccDx@xgw8db97%2Y{yCCMWO~bSCd-NOy-gGQO+z%b}unMP{!2Q1kfu3c=`OYfL#} z>0VkU9Dc5K0x&Ey@)^UtfvVgH>m#zm@f9s2rlZ}KMMh6?NQS9{E`=Op!~SpFtzK z;!1!FW?me>pqCpabrUQhQKd(CP+qUn2%hv9GXP^!KqDd4GXN7FGErQR#FN6dc-?7@`Na zDILXaWwbl>L=kIrevU}VMfDQ({aPmg1FR&WPH_Ontb%>~liXtMBpqZ3*io6xDQ}`4 zuCebiVP8rp0pAB8@L)GJANkAn6zxz%s1j_0ZqIUrL`CO5 z!vdq{3R#HKy;ILGss8Bd0CSc_eyWXTJOLtgoGwqr&gojCImpwb@|tPPhgQd#cKaom zb9e{TU)1u$t6Rx)8Tgx4TN=lhOzyK}H(K4J=G7A1=)d3zy_P}8*5@9R^0lo#&G3O2 zm6xa{G)J4}>D&3K0|Bq;gLg$_>xQJ7^mrJnjQU6Z8Af-=Dcx$CHZ%ktuOF3cKTAc_ z?kn%?q0|rd2I6#mx8nnzi>NnE!jDZ=Ww%up1sVirCG}8m>G(%)cNm}Fx3zKfiptm< zr#}~!vSF_JPJgy!2V9g-LY$e7%FjxE4;u$zl?}jr{l-Y!>h#e3<8aBtutP>Z&%f!3 zg8Iht#vJtEQuyH;IckIbrSN@LR+zg0gWAbd6V**T=e(;%oaG{9u;%wn+evu;9ngbK zC#z{%AlsaNFLuT+#hAO4;3N0U+<37?$=maC@*Aa| zh|wpKH#>*&()n2BG?KcYY9n+C4})bEVO zA&5f@J5OusPfbsTa+B|NRLh%HKY=Tn`0+QehP@f3 z?rqb=u><^{Z*D>$Tifi{sp-f7ew>p#`*zIDNu$&L@;=a~L#=)Y^= zRDajN|M;4}0V%-WGd6&}1_(G11;)=XZ3|ufJzwLmGI%N87<$u8QrtSU%Qy9r95^{( zW*oqgnfgt+83yv_?a*9Lia+B{X|#|4%E>gVdt9!^T1gIW{bTY%9u&t?gfIq|S%I>j z{>7`ZNk7^C!OXrv3>xZcT|`wlik%+cTwy|Bn`1{ob*x&QZCD1!3BG@%`#s~>G1rzd zZ{1GeLdDLALYbJ;;qYtz-BHU2AgKhrc1OxBuSsPxAfhul9#PmEIwBW?`YiwOb@oSL zQ_tq6PZoLAZ5$hw&?MlrW&)KO(zWRK*pm@Oq#*?|Gkxq=C9jNCxpTJXU?aD*WP88a z6KPLawD7*EgBC#o9S#&0TOLm4Y^pqj{Fp`i(WL2U#6MTfb%OnM)mz4bn<^vNMbV8c zmr-_y8QnOZQL{!mlPmGftg$w4mke&E!lsP?g(mz(v6QVuqR~TcO6XBe`I3s5w*F>8 z?7>{~N+6{CaJF`%Bx3NZ^?tjO3iO6Tko9ij;q}2rMo9r?mfXE%yAGghw>PB=irK_*ZQ|Ur(4FhdnU& zwvojNb)hT5;Tmn|idl(>x0*uDj#&W*- z�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%0Aq+AJhypz(-mP0^L6lxLUo9(;osVUH0# zxUudcnFt)n<{wC$>i82Nxp?$VLz%SFTsMd%-wk5WOwa`43$$$vYnB&OZPK4KjL>VA zNU1|Dm#l(YJPZMvtA+U7HDrQ%TqYlL$Xu;$GRAlYk;o8_-p$|C*Xc>%v|C+^wP|NO z4L`h9SL>mxU_AMz zr`tOetOmTsUK_(4t+;%E?XS!B5RpQAd zaVgPAq(T^a$4Gesa%EI3q)|tM)ZAO<8sRb&_*a*X!Oum}@!mdnLeV$3-(`Z3{wCWy zXfz8-egi*<4TTLVngqpEClN9na+nwCTCf5V3^BWKZ4V#wUT_0+03Y+FHy_fI59!wb z9HeG^LxPG@Sf~VzdN%OxWuUoK4ZbHSNuD~6)k7vR^rn&A!}5idM+(%(BM?L3VYMvz z1-0k(55eZ!`{+H#RE$5@8n$DSJlAIHN-T&(3=lrPyU{=6X!R!SX7 zMh1S;TJ_`cdjQp@q;n)4Dhoa0c=Cj$gshqdx@ia~2q%`mF^Hhh5l|r_;~fMOO3(^` z4XS|Ze*@Tu=#_|4++#d>foPe$1{A9(!sJeaO;ItELN%xwgEO5=oz6OQlUf8MY9Bh1 zi)xmLXKIK{4GFXzpIW^p)6{CWoBjFDlQQws7Mul#ruuD|Ztt-&x{tNS_94!^^m)C)yJa6bnvbl|1@gIAy|;N{v6Yx_>YliK!okBzg8na^aM@7)?wM3fyuwy+?(S zlIp{0=*0Lu7Tsj%Vi*wvgGQyw9)^HEbibFh!d_7frM$x;4$ElG&A8>mFW#TO@l9OR zXyy_E`zu71tx0_En`p0j6~bvogjJkjX= z_C$j)AWiF-iejlct{LRO@^6b>5?`OQj<~9sW3_8X(>e_*j1e`C>%v8OA#V>DwHbpy z3^fHiRKzGl;_>VWXzy^3`)3G+?2?%BBi++sm)GBopLS=dHbLm1AqA)3MWS{f5jTAI zk(~K%tAL}Pn`H2K8!lO!_2cC@zphiQ-f}^EO(MnPKggE|Q+j3+j1T>SW^-*hTl32Bv9cvcT%rg;4@<$&G*z*tG zfHX;Q&*zXgxRtp&sQ#K>8(&gnCeHk^Zl)ZK!#yz1q#Rfn4DI?r(eu2P$KUnw?)A5$ zi97j^w)4tQ_$_|>Coa=HRc-di&4+D#Oy75(@V@!2J~2pPf=#nWDgw%~Ej-jzvv~f;1qh^F=m@EVJSVm^O z)<5K&t42Lj9KrvP#ZU5%`{jNQE<|b zcdj351Tlz?hRHt35F234l7dM@;2V;-LAFD0NuNvg^96eYNp&$%MJdK zUYM=sn~$sX0YQZ8h2&9Gui(ge^IC+^ zrFa^L-Q`FXuY^exvZEB90yLY!svRdyqDB2a*>9RGr4X1sN{zDa*%U?8KvrfaadPsz zR%6TUPnMq&He@ILiKppZyZWP^+eB@^1;^h_E+3hdgDmY%PoA}zPJ(pyy^}r+@tv1H z@^X2uelvR#xm>rWSF<)-@0(LzYx8^@dY>^}>bgbiGz<2=#@_I?At}{~GPKzs&T0m8 zeNE61o8wwP3;y&6O4WhJncc>bUHE89PBl_Tdy4+nXnt-_LLm$tJcmhv&9*2qZ(>1+ z5$HQV6DZ{Er$O-sD5v~1IQ_y;V)1!hZM>df)onuH%O;=c@czV$KmwJXYs}UG>k}O^nD?oBO&Ysisy0bv_dk_)SSykOrDwy zQ_ zgI-X#9Y6~INpA@?uz@6K*~U#cOma{$S~~Wpx4Xq*--ml7|lj5W_QR-N9IsC z89rlnZCV6v2p1hOJ}b72SUGPZf&pUGs8n&YD%j1C_zKy5IOsc+c3wVzeT+6bf(^@GBL5v=)>ARcAH9 z(u7aWuHIS+U#~KjgE6`SeaQDpVFyu5r?&!2CNaO6M#?$~psG#0p|%;je=1=w*W z*ZX-47Y#dq4S%yeBP~MkJ%nd4YoeFPHDWwfY7@#Mnoi&in2_w@p=PNCivVoEWY~Bb ze-VJ3{{k=@Z95n+0)~ma?GJbbUnT49=_Pn|yQ2BjcV+Y@@}{dKQFt?Mo)Q9iUq|jR z&9;)wGgi`{b$P`=SE1js*VIxurnSq9f!v9r(yG+K2qJm($8&z{EjhzIUuu) z^X5^svokqcUGpYEmSi8?|J-pVPv!ihtdS7TMHF_Mr5G0E0vW`;fs9yd-fkzZX=@AR zD6$RJw2(F4;rhLjD}CkX{1Jc}LJ-W(0v zcY^c_CGc|L2NXkDjBkqd9cE3Sn0jzNQ_WD6+-#$YI(+lcqPP% z_^t&%3NszKzEoD&r+nQfn#3lK?zrD$XZtmsmRG({&+n-=rq&q3&c+V3kkag9ajVFa z|D@zDO)uRKwnwgsxK~M=8^_nFYjTu~-b5-#PO=X$qs_LPq4@6U6Y`@|{<}NDTRWib zV6JmF;8$j5*n zJDf#-jvuZvY9}RCE*l-9d#fY0z|z*j*B7~D;rHiE?28>~>GXsx{K?SOBPjOButjeK zJvB&mzb6Jq%Kn;9F6MS)#Z$2i7=QlmNl(#;a;{&pRj$=sM+pjFU%<&({{^54N%me&A|5L^%rq6#y7pSiP zw4-t;lCVx28xfaI{1-xJWawrQu1Ym;tkD+HoPB2_+A|6MQO1F6Js@sAq~b;pS~pIm zoGqKUtE(|uT+`#uWk^8`JvhhET(W5=Th0HV;n1qqs_Ny!?kH8SnY(#n?q^1Or^P9b z9c%2Rjs_j=9oUJxtw?5`cviDyOhX5}GN@=abt)PpTr3vLtZN`v27;Mm9g75SJunqaR}WCZ7oP>8)I${c0-C9*uCz^nNsAyi0aVno5MxF zYbZCM&?`^uBJ?nvgdNa3zMt=H7$!|-A%9IK%9YSaJDw3q5Py;>Etyn^nEvb7^*V3WKC7Bs~mrg z)~;<4w+#9-f=G5Ibk@C$oBGu&E^9 z&4b#*$g$IY0)4wQ>;8d5;@bJaYQ?h$7Zw*y5q=qEA$LQB5b_^-Mbc2Xv|N$*UD9dE zWT+tJ0uSm@Wf0V~9C(x}?^rpT`4wr&;@9dVaA<7@>YG-LZh(#HsdyoVCKK$P+THPz z2(J5kAJ0!JX|t`J9tN!L&)6XuZS|u`54h3q92}QbYbwJZPcJwlZeCIBcLC-DY_OVt z|E9{ww02=D0dac@c<}Gaok&^f9Uz$$bK2)?S6(B2Um}1t*%OR9o}9aWA*GAs1(x!f zRwAhud4^}M7)x1sP*zYeRI|0TY~)iFipDGOjwq=;+NxIGS2|A?axWCDWSfbjJBVZb z^7t#Lw%5wH5_{PNFYR3O7}x!>iDRX&jZhXF%Q>}YyW^(QP$n5MgRS+k5;vUkIkw`Q zjP0d4-5t%%LOz4)krd_D&If_>*9oHUzyOb?s2Y}|1^k2pSaz)G-`YU*CWXja70Ei3 zjH3ien1q|hyV1U=AfqH9^@VhwHpAO2m+`*x(-tdNx875b>;2+#tEGh5jr;B0Zcpf7 zVnil67Ins?>MWs#)_9Lojbk07^B@HT$NXm}AJ$*EX8LmkFVfR1&0@*L-J93D?i58i zhI&#IQyXTs4oN6#84<>Bfr^=NA%b!;>LJs$66;36Gr9daO$^ExS)A@N)dMPN1m)oY z--bp`{`i4orenF$VtG9Oth2UR!Bg1BlRb0c`AHY2=NmdS<>6|(llFOEaZ^(DzY+G% z(UCUa{^(?4b~3S@Ol)*Gv2EM7ZF6EL6HF$yZQHhO-OT$v?>YC}^}F}>TGhP^dso%7 z_O3skuFq$CH*Zr=Tvd*U_@r=0Psg+KqQrhg?lLqbvl(GHHO3&Zn1}(NKTsjYa+8tcBW2_Bu49Rgqy6sxs)6_bA-4%BW>q+}rvcRNF_C4%f6h)Ab zinzzpxwL3FGHXF3uT>^VSN1CrpOK)yURs!#@8p!Bdc1sVoMDurt3<}=ewaW=0TU;u z(UI(nF&c~QVqP@OSg4Ol#{oXZ^I z>k;_~us2o4JBnK**_-%1ZH7s&oGl}L&z|R_aO1pvdpFNSauo^ejJSh%sjO|NnOfYB zubod@wfNqLAzI!Tmjb2uFt5;$sN`?9-GG5t`t6W66wnCl>0{6A?u8i+1Q;Ju=z)5y zH`#kIqvc3WZQ{Li2T&;=+Jdab(2Te`xn(sxmzh6h_bmSV_EN4bb z<^~2Bb=vxuUN(E8On4?}DTm3Z%|3IgG_d2RiVugu#S9_`k&;@6&|3KUAfy^s#l9k5 z+?W6(VM~|7MQ62SW4m6i1#r^4j79U9*}nY*tD^pfS`?|+W6N?{5!_{tQ5-yPZu%!U z&HQ^s@T$43BB%(+S0oXAeVuH1a!&W0YupcH^?SlGzYF$!lGvZkzjt-!V}a5WF8S_m0Z%usJx` z`cu;JJXW0(Fcw)RpDxqRv{2J6m_F~hn;&Ta@M?{2QQ%lImgFf8?i0(c8k3fs*T&a0 zUYyUG<$v1IUC*N*#z^};>_Jau95Hx&)$T_?8@ImpU=*rBJ%cYoU1M3z97^>2QVXeT z^(Eztr*KT84A^E72>UnRmBXugXSG6(FGjbnD@b3ozHsmWI@3X1DZa>nZ~pjl4%0~+ zz9tRmIr;7^{pIwQ{?+lzyW~|2xGu?;81k!^uayj6O5;Ngo^2j> zG#tQyu~gyd&1Sh+yxK(1nlrnk6V`NK)scPni=hs0-Q5&w#g03SHW zyZZye#^Dm7NFrj3ocOWW@3vkL?4!^SY-8@w9}CL}U9+=klR2sy(4yM`>)fR4o0KFU z2#QkNw?9|;VusahBN?>)%kb7~61>VVQab>@XzNK&efGrP7^XF6E{@>XFkXj#ng=*; zE#AN6g(yAwnIY(71XAKB{(WZzpyS1RCwtu$ahVfmH0kOsY;f1Q(CU?E9|9{=@hohB zY(1o9YJwqm4l}ue5m|rXh)cWZEcZ^Dj}wn~E66|_KTj*kj&ZJaazc zi2T82A$Ts~rMkJf+E&ZTt&!EvUuOgdeqk<%A^&VZ&~NB0E~QdVwwQQV#48{?El> z@8QhU$nnw3Hp_BLUVQsrhJWgvHhLPPU?oOIs`Ol4^)YZh@n{wV_BUU)J)JxP-8eSP zWkBAqG*gY2Zx0l;+?~=Fv=|9hW@Cafyhf(}*sR^vR>Nkd<|@wRN{JTgKQlS+yCq#Q z@$OJjn|{jl^V;RJz&@7HcN=0)9NJ(k8L&4{xh=ruxj?ZP0N$DONe-8;!oXm)^w$FCi&_WRhVJm)+E zC-f?APfl$|rBt4%X<1YRf(Jq!wq#tt&Xb!d6sm`6%}k3@mu#0tw?!Qbt<>*PrZg30 zQNkdsz@;msO_({9g`&C@@9b5x+<}%J81un;${6-Nn=k>iaM>YSh8WU;TSa>8h`}B` zl?-r*&;n8z;uKlLaGDq#(xgZt-SmhMK)AhHj1lROtuy+_U_$AYMbzMV+dOD^5z#zc zKu;CDCoh};%2_!5D11QTsX_F7mo0|Ao~h*hRt}5IblOV~edyW7vv(S zwpx$VIW*}rzp)SZqw%%d@wF4O?3hAupM-Tec|P)tKF1qsx0fi6DQ!_)5OvJAh4s)< z3-<2|);Bt@>SHCPi7X8Oleu|iU>|4Vaht_F^O|EwwetYNprHXc`cD-!MJgYO^?1%e zYAIgt+bg(sE3gdxg_pgYhaSu{oyEwO;RQ~TKX^G@S8b6=<)Vc-x#El;xB#@2xv5dT`S+Ou`{B&NM|^!-HJGaWYho8k3_E;bLdRN?2}o>M=rZ&NVD* zV3-{d`x9Fxlt}v3t>fhevcud$jVJcrPXvG(wlKxS>tk z5P@PtE=w^>7B66Awvp}l*|H!V1Zb#;My6>_>vLh1QiZK6~bEZGHf&eI%xFboA| zYYs}b)4bCt=Sv>42|HoMhEo$;6XkL4rS!e!9+!jt(HCA4YVZAzR55t|bX)l!PgK|+ z<81vdysqjc(zJ=yXA3swt-VqrX^lf>Pj<;`=ZGMV93f$M?tXglFEFBT5;}9L0`=7lugi z_C=<8!#xPyBpxhq#%k90NRGFhgQhvud`cev$oDE3q=`$oHjx<&%xL>{6-*xVi|03xog86jAB##gZ;0RfQFAv0VV8IiVhfE72>qOw`J zUEUj8-;fs9#yH5sJ;Hv0K*-dP>Du6ZQyScZEers0ZNE33^ObFQ2m_3Xuv_R2{b&y& zAfR$&`ir*Wv z*cD2R zm?xP>ndc}!d-9fbz7M7MrMxZLUL#DF=k7}YxU1x6 zOyU!#MZ1qsK{p3wG84xoOCDSk#xx?i{zDRzioOgMBR_j}K0o6Dv;yqvF!ttQJ2I;Z zX6b8;s7~+VKW*nUdk}k&TC%wr$A0AH9{Z2*mF5xG;EE`@gs|vTA(=u#UsnMxzY$`t zF`<6f1c;Zb{=#jJ9u?jdLQdJmze1}KGx>qUQfWTh1RP!tp{6(W(}t~8&zbtlf)-0n zpz2%GNdX(F_W33#T1}TkP2Jj06E;IK0Kc;H-H^D7M4dv&VbXcOu+l|M*Z01FrFRM; zS|fIk8nL01-im^&w-EwRukjA?jl3oS`tVa+l8yRz4E++7cy;j$4LShC~Q@&(wGL+?$eP6eu6MEg<_YH!GYlo%D@W zY8ugoJo}wE5;pJ?03%=5;Z8-r(iCae*#_W>tpsmGxU-?}#Cdv%TsQm%POmZ6*ikKv z+)V9WS?%5y*UWH_goF@UsS~J01Q$9<+pUgtEI5eXMd~jdI79TR7Klvrsu)O2#6{cf z96p~S=%Em3Kvd9>z+a4%3&#c!16vgc4PDjuRhqxeC#aHN1HILEUb0l=hep$LZiNVD zQ%9$+^|=GN9^zV$y;BMST^$~uZDP(}U)zbfzUvh}ouu|5Q@3Irw?-N@Zqwdelnm*k zRG&&gv{UBDVmbk0=kUdW+|MAeANe99uVnHTy5-flq zK-wZax?E9x2n9J>MsG)Q*h)BcR)PlO&>#A0Lki*$aOTW{#DjuCM#>d9AS$zu>%@ui zKl`A0NGxP8OPR`NaM08%XowiO{iY}UVV@Hiw3vsH7 z0crU>oF?HyKnN)k=g6$o+h;=l3CZuk>2g`kLH<-Vb?^gBvL*d`MexB=2f(jC2_pg3 zIEv@sp!9LW$D4m*95fR=sSj@ST2c|Ai8Y7p}XU zAjN+&D76DTmk`P!Ko$`UKZplKumw;K-R-WfC~dc1IM*(-70Tqo4mGN~46aFWmSlB2 zuEYi3g(j)~ZYKWwX%+u0>t}Ya*`gb-5Z6aT=xwYZtk6Ur0@Ayt6S@TRp5d@l!Z1?<*dKs=H>pz|Jm-Op$m>qsvB>V3 zh+^FbXZaNj>Rf^|MW)V)#*hh%RHkuyJtQwFD?zH&*lY)$20V|b|A^ja}AARoB|BBpN7pp&eA8NGYk;;$LH|jj6<0Cz0`HxKb zod=h2M$*I}1ZhJ8`%6CSHD z{n4(gqWa2iSl*{SjpK=DE7!Rd7Z+Av=XpBVD2YolS1tFf`E#`FR=Ub5f#&kCo~RK{`Ao5ov?nr*0hWV?<|pH( z;}-Yica6Kpi*ld^+stM=huI!K7UM7(d2=jZKIPTV77uO+{Z!_;V##gyGfCZ|a965A zoiWo%%I>Jl{bU-#W6LJrxuW6RWeVqMtaxnY^z5pdOhcMJ1WDBWM6ooN%BmUfZq z+VV0C4{Re3D;;j9S7vc=^VfWKvYQ3mF0LLDFWxSx9tl+mziQ~nM{~7J+75LI-BIm7 zc)C3Huf9(61kljg=+>${HC+$s{A}y+g1Yu;|L}39@=0^sNyv-?x=ipcue}nRK66X@ z>44<7=Dc2k?_JXM7{MohriH$6<@J_|vwlsG&1Rc;u^M!{gmYb2QKr&)69m4$MWinY zXevH!XL+C{Dr_lT%s?mWTv^swt@|+Xo!L~p9b5Z&U0;}QA!rtLMEOJ(ywj%}yByX0 zRJRx7Fq|%uRw-EzB|Le|Mq=}T4AXVzI3bUcB&FDdQj3;>c9vSisz#o{X7GY-Prt<6 z8tgTJ#<@!kp5XBOT@_{ih6^UewY@pp>=GOG_@}QHko{}1KH!ABK`+~34szre+0~qL zNtGvaYFRVfN{RIOhKM%6C`Qkn@_}clrwX0m55bf)aXnDQOPA_wB-&~2c?3~~#zj|) zf5u`+v(SFqs4e@RJG~{W)?vBTx%ljmEtXb1lhGa}d`q2uh?&7hm(GOl0Lq2QB{umH z{?+-Nbd76mtU2M?>3Ibn8}7phXUuiAHw8k{hZQ{?pEi)8PeaY1hL=jDagFL-%<3;&Av1%oV2jk&HJWUAe%wB=%nPQGE8F`Jl?i+tUkR$ql#@-6Ohat@#tSZs{;B*PH|}e#nkt$;h_c@|t7A=V zye?fpQ{c}+Hmw}rM=Sf8T*=4o9C0O4bSJCRE{s{=kES)zSiG918qpY`vx@s zSZy?zt;zJXyM3AmMI39a*=yXV@xn_V$pWSn-b)>_X)?xvOJgD{vSh5!!9kkVtz2>S z@%B4mY2#y=pqVNSb>d|-pfuBkS?i_G9BeLdqBE>g$;09%N#YS6?yNRu321k>clWn- zaE|wm=qA)0=w*`XK~l2~a46yu&1|T2C##EvTjHfF&m1c%byn;3zkb>1eH7N8&)4&m zh;pFstiCSSw|lcFC~K;#bKO`TH4SFI$wx3xE=(`a&rQ1o&Cc++5y*B+j|LRf>WzDCj^=FTd4G45 zide#lsOqx6!_oQqQnZw{4OOpQ!$aMji=(qm((@bzxw!-=E)D2$LCZ44=X0sET(U|LMRqd-bTVj?ySB&Yn>J6h%_QOQ>YijeD?HW zsT$WqxvFJlS$z)AZgFL$UYGRLRYGP0_1JUSxyBB z@?$HBKO))7;tNSejjwCR(^mN>rPBI$@kZ0(+66MW2-4yNDB@!C8ERZiQ{eGcH7+?F zjU5gWb}}3L@u*HCqVPu~=~tMY2jS2L0u=^VHbz!%$+V-eN`Kq{3KL?our3YLv zDM*>F;s*75kGrmERCS0V=+{E(W-ri|tpP`5eIx-%uP5`v^mU)yWA5rLhzb&w?_`#G znDZ;4zmCqt4OxO5^RkpItjShvacH2_M4zjavA1jG;lfb1ZaV6&I(}CCDtT8F!;CIP z`9R${q*HwGv1meGiT_>(X&S~cPs{O6g*YE@_l_z=(rb}_vJgXR#k?S{p|qkMfKf`J z95}CN#_+@Aq);^q{m>5lelhxvZK_UE-#X8#lq3Oo>h;{9y{IcG`WY>CRx{ zprR|N$$$lS&3XBt52bwn#Y~N5{{{JG+H_u2WGX=RCmN?MiFpSu^BVurlDI{w=6q&x z6Vj9PbMl*LSTjwS(@~+9rA(xJnLJmJfK8fu>jjRIwN@c(%W$X0|@yylA+p`xE9G5VNsG%3{l?@*t}z*OPC5^Y&W7V?SCK|ExpiB zk%2(6;ps-#^~UQy$h(Hyc0uK#x?BnYgv12Pg=3ID(u(ay(CdVC-Nw=OLYDZiw3wlHEUrFibsQA$Hb?AUedt>$YZ`Ikw_Uj#}AU?rB@M1qlKE0G2 zb&<=oxrVDFe!_>=k{oq04iT1>KX?)n(igCiMkLi?rhuD5GaPYqv_c0m6 zKlsptcY`#!mp*(nKJx$}SEYG2*j7o~Wk4A{b!^g$uW--U`)H?*<7nw)~ zvzma+Hu0CKaCJYJu{!_!9!Ma zvK%1kqAt{Fq6bD*c%K+PnRiSYEL-z1Fv3oq^%Y@T!G8kA)BW#LdFBqq>n#FH8gf|N z_vmy?z9{!^DQ~j4(DJ+(3t$7RjUt@mEst?Ez4e_*`WU)b7PSy8wz&#VV&DIn3Rz;| zI2Gm|*RE!;QP=vThQLZZ{p|FK_=qTiWa_zR;gU`f5X8ndEL}vk1hm9>#m!+W3w`bS z`4}lAErbEoXI@xR22)A=qZ=a~Ib;RDw75LI{wqLYiPUK(;4b0-%qk)vLISO${&B4@ zn|7XsE?DHsXDz{rZobNX?KYmHi8fY??bmukKdN&%Qdhk&)XF@)FDx#Zg}5a$YrnBY z{aJ0v0zWj<;a9xq?#ZqGIdE`zkvrs4tnJ{zXZBUB35_!E5vqkgr-uHaG^dkv1W-@n z=9g$r(Yg*o?fPa4yc-eh?Xr~Exn2S?un#@-lIZ&f0Lml)1x42#+dIME3LI*6vVu?S zcLvI>0Pk_Jw>mV8A{Y(eQ7b2j@JAV>s)qotT*qwp*Rpu^7TzhGvOiu^fhIj4@JNY5 zkN*dJ>wi$I{{UQ>fvgPw4Z8n?yZ#TtRng7Xh)&qX+ELKR!NA_k*3riPpQ^N;l@Xl) z-`@|Fq?x{vy`H0)jkSW2y_xYR2ZE)Zi30)ir|_xB&u`buWteoi!YSNU`NG<+nVyRUOuG31Q*ju?jr8P zK(0=>&COy~cf8JXZhDW`YsyM&c26BXqrpwNP4aC)$5~lwHBlBf_qjwPZpcWZwn^W^ z<9YjN%&XZ-)WIbMRr|PB%F9+7@7rSL_RWz&ns<-}MpEl2YkDheGRA=elb4xw?xe!R z0B!qu@_+-^wN1~90Zh}JiNH_i({8U%?hUpc7Q>V3)!maGy@a6Lmy1lDtHGz~cgy|8 zx!y7(Gac5rGW)%*yi=-5g>srkBG^TT2^h@;z$W@Nwrk%bsjI|1d&O{`owMe`n@4$R z5B8ETAkNCHawAbwL{h-VuOqjEo&F8H3GNL%EBIYG^<4>G`W-9eodst!+|6Z^#tl&D z6ZMYq>@g5bbQ{aK}0>+s^T1NoVA6(rXO+$6UnAg17* zxMH$Hcp~T>!J6NvwCV6dHh*#>S2(w0PV?5~Sl|PO8|B$!mbBy}^ypB>VC~mS{>jmM z=h&~8fRyNW)N79C0Yr>)W+4nFPssO3n|1F|2OO<8)x%MJ^%ug|w=Bt>K!xWA?5cOT zt^Z@#0RP9Y{yRMA6rA)O|Hg`dj|2;~~>>V5hO!e&ldcUOJf61&s z7&;X*Lq}5w4K_A50wyLV0yaiQ0_M;7`71NA0|^*E+ga%8|0#?t^aRYGp8mJ~SN^yC z^B3m-CjWQ)UmL(rTmSa`wfndHTmQ#43+vxGVg8->Z{NRT{`UVn&tF^r>ir%2@48qS z=m|d8{*V6O_P>SY)8^m8!2X{#{>pZ{|AhblJ`q0c|2Ol?f1L;n41d{I{y(An4D9TT{|mam z=IP;yGI#gL&GfQv>Fiv?`C@ER-cKs7-;dv#lgeV+g&<6 z(?OQUdDDNs);T5gbv4L`2m%60^*wBAc^Mm22?Du9pj1~H$OCQIG&%6Mm;&j2j*mAM zUjjnry~RV1zY{JKV>g_h9-nz!aq~0QBg&^7o<}*_XMz!gHN>WE6 z)Xv6-I*Z`@cS0pb-dvxYnhuNeH99?a)@=(!$VYM=OephR=APfPSY^^S^u{TSV;OyE zMm0AGMI&cd&ty+QKb+~w+30+(6HCB9uVLoKo9%woNO<8^C8W56Fi=fEqkF_Q4!#9O zw$c^#XUVS(CV$ZVnw%n6CH&zn(C$l5ziU58L{kvZJGf^E)xA@qhc~OY&Ng_GvLR9q zO4GEXuSxjDO_v{mCh;75*X=qvLb>B7mo!g?D&)2`A><$*CO~QM#Ml+)I0w{iFyrL1 z>R$g(iqvBx$c4n!(ul7=cLedij3Lbp`wiNX$hpGq2+4&2c)NvMcL)kZo#7m~WK13k zM0MOLiHe??dkMPd9pzMvr+;fwJUODA04?!CRMI1kDefP?$jEJ4G0do+3KyPbvB)LOsez~ zHDUxk%m@~JG@H$3{8Wv}M3M>`6&+Q2d}3q_+8FXxIz5QVBrOu6O}bRBRQ0rjpAg#W zrx>eBrJ_*4&n{CZxm?+!)AH|>f8dnsNP~)Fdy^^YRJEk!x=iIwk>lMS!=nWnNQDYc zIb!4`<-+q5JE3oeJe$IEVm;f5zT*TXp^ic6s31i`4FSHlMt1+^N~6~!LhSseNU5ft z%cxe@=22V?Zxq-g+_Sy8EiLeg&9|f{G8Oy|1TOkukkNs`&?itG;2DRGlc+?{ltns| zU@J4LD7$|36P44GVq{X~s~OZEW-L+5n3YfBkD=yRqQar&;&D8ai?%eS8a#w@7F=K@ zU;3Vet4|dYTrRB+6R+TpIz-X3Zm&PWnGntz?oAs;(&D`gPmksB`KXK+{4dRE9(=!~nG7W6v9Ux|a`F$(?iw$+9 zX(92ySJR?(NzFZNn1PKTWflpm41BbyPjoT_?1ptv4*W>sR5^+)xZIx`3K8#?-+|3p z8x#s=PaAwyIll9Qq#CSI4Fd8R65o`-Dc4hc)VpxT`T2&I(~4s98#u`S`SBx?l3UrW zktC2b&uUSR8?}}xAy?OD8jTwNOiS(0aF)_oZKyo8nMMYQ)tPvSVR3vkRjz$;84;=f zC{<(LlioKx;3vV|B^M&uFA{k|7l+5O|oec3G(Ds1Z~&sDPUd>imKT0kOO{?b4BFQv=!`z;{?|}XDCdLF^sR3DAr5jK2sP|p$EG>I2 zWqH`Ts6C;e!{uQB2hhC?txcxGF|@ zX()UC{n~HH9O4&lsw9T7`@g^Y7p}y7&1KiZf-HFZp3rSq5`JweM5ISA@^ctzW|p$| zNWPITJbyuts8o`^eAqjlf_;mq;D(%jQp{E*5MckD-v)m>~1VDd-jfAh>mt_nDC*J=Ba{}?f zZ&d-wky2oFBV5*?jrgLp^_YV(Rg-L9|EAS=-Fg9HtcyZ{d-*GkKr5;CRQm02Ny;*| zg|t%Zs6SxEUn)T>!3YFEo79w5EOx))xt5wgDSl6f$t|h0UgMBQ(^mPBVYD@WccRXq zrCSvbbfB*q*AD_ZP;XU|O5)FmS9eE?A)*;4?(d=3Fo~N47jaT|y|(Js_gnAaCSY)) z4~~yqSkb0EN3d*OtT|uKp9kT;y=5H`327~8))(2VY@A!g-Cos{Zr!UC{_N**fv6dMDML0L)v6O)W86q7G3oVoXUA zxy_r`tw+*VL+X01{t63i0J}Od(wVY2BR;$2#zftPX@poGa>f@C1(3-$ngDaXJWi)= zv0I_(csm>I#vCDAGhIP7VWk`PdD=VQ%BG<_NIP9JMN}fjuN*s7XhgV-D`sE#rEJo~ zq^R~=%)7zdt@n%!$3zur%e9*>2J!q4zV>Ag124{Xk2P=a>*R3=BX8hqb-V&POn7)9 zjB_wdf2oT)wB&&oV&#f#mtrr0xHiX(-*5P2G|<=r%|Cr#9JG+AhZ+9gd3V2d6Bp$BD=qUqO^}eT!t{=1J>xAs$ zIFW}Kj;9Wl#0ZNAix=$Yx?>0(#QVBYmPuwxBocJTEO$t4sl{qeUV(K*ESf)7rOzM0 z2{Zy3>tT>SYcUy!CCsQ*0Q{rK0RVF+$(xRRQ0P*P5a!C z$0bSNE^j|nwVsTBw>>M!Vn_I%tykb2g$;|P*WAezho@pi4c{)kOgO6Nyf|j@%$?Ih z8r-5+dQJz?&bF1E%ii9i|4%U_E*v9@?uVnKlf2em);>2O#p{3 zrs!f^T}#)-Sp`oujfK5P5)gTzyfaWAU(bG!rDTp|Xv}0TY$Gq!HNao=doxS6*F854@VKAR9+^EJ zZUvx?Tr!_S|J`2>efc2 z6NH4Z_|4LT5?;JWVH~(&h^jGtF3otlUfKYCA(!%?3wV(<4K<1;{}MkH!?B`ybb342 zw+zOtcsJ+!~za zS=r$Q>270aG>v73QOaOUSuD80ES2--BPNqX%4m$uca?0K#^n!VTiq?H4xRSdA;ln_ z014+-hs!1=7VaBm2EbWTmU+A7O%!juG#!nOqJ~;M;{0s;b5C|Ue3gI8al6w&8>Ulx z<*nOAOj?7}r$gAnC5m-eqpE~%yYKJ2a;>jK08R}C2GfrV+(CxGDqQ`QFr$v)`g4qD zGg280E6r)f`3`F&A`$8R$DsZoF-~<9SKG5!+FepW(%HrCZhnFEU>CKW?BWB5{Do7@ ze%RNFjI%o0#f4DWS%ZVKwdss7Z!7y_gm_9LasLZx&vjJdw<}o0x@7xq#us*72%zca zkKG@ezB`+6kmSI{36Sj}Gjxa?EM<^H7&_|AqSGm{I#OKb`pas*HA?c%@$?8LnBlIHRFK;#C z-Y@A*BD^`9R)X%s+(Xk~XVUZ>4auhl7>*+2d~I;bVlf|3 zpZGmd*rvH7*Jr8IiEzZJqdKt`xSiMf9_ONXvlP(FcaF%0HQQ@hUt%Sl+@p+dw+M{l zN>{bhO*cFkJ80C6g0-ZTZJKmz*$jleZzS0Ahd}4BK~HS-b?6GAW#92HfL%5X-T)sk zv+sa?)8Eeksg2{~()4=V!BNr(t7u`Fhx zUdM?!OG6w%&!G!LRfj!J)HYc#A%)%NC5Y)TWeR$394P7ETTdB_PnpqRV@^nOYAo7= zf(;yZ5Ar#4u2Q%|bvXz?_uI&$=ebcRCtwjqTMEqfCZjmMl#W}!k;2ftMu^NC#g&fLLL;Z!784*c*`}P zRYb!Lb^V@yU}gbRsG6nB$Y0pT8B6%mq^Y?-Y5c8%O(Oj#?rzO7aqF7CFg#*_j~hu( zXUQr&Qr@;0VjzJn-w)mOAp&NwEmy>NzD4{%?=+ME*>A17zm=bLS zV~~2PRLGAp600rR6!>}NsZPC37>yc9FEd}8CNN)!H7-#HyX*%iG27_t_1e~GM+az2 z`GrHd_L(7XiN!P1{1oH0N+UKuh7V9EKg&o8Q_$fB%dyQs{x|B+kpza=VN(s%b)M&5 z>@(0YK`)rMI(UP|sBP->rPKO!$qejj_DR!5G4J%$MltMitXs;qQkuMldf{8@{SxM~ zIO?Bw1>b7p5*OlGm^r@C1IJ&DPE>?DRXvG|Pczi<5ZogVCe88{bR!`{T-M!UpU5PG zagGcQo8^8d2mkPRlBsCBg51zImI%5I%wIk)ce{i>8Wj(_nWeAUTGptC?_f-+eiAN` zp6k!zZ-cu2?GWFuKSV&QB22Jyai?#3yl2T3lS?sSqB@FEmC8+m9cZ~(4dUlUUXx3R z9c?1Oh<$G&z$+ELBpdIgXZw3m@EPb`JF+evZ7V;Y8Qx-&I6fU2?XRY{*ktXXoluci zWdV9KR=!mu5Dd@uww21JQ{k;QfW_{;Te;TFW%9J9qP$eDt+t^2apB{0`z!ww%!lW- z@^&~7#qMy1l+F~YB*08tv>quT8C^#gGr4q3gYMXv(+&QMb`WT@k0s?Jen7QPl38Rs z-W?X%xG4=I8A{RrHMHtb*34$Pt|(F}Gy1Ud>GZ8={c%R3BEx$JFl`Zml{BU!+ki80 zemRis_0%V#<9Yi$aq)b@3F)1R)njbAxO3bRioG9J^v5~BOTUveaOLc7aZTFAt2eBC zNx!F1ZFG4wI8+Wcy-oMF8%Wo>AGFdYdlK}O?vbMUK!J|yILO1SUz<*IKZqq!KMS3| zcGRPGu_lpPs0Jvdun*pU8iYibao3Hl?b-z!(ptF!%RpBW~Qnt|^0hCcvZ zkS@`c@A7LdY1OE=`n~`ioO>>f)+yJ3y|(Inv(qV7qp$u6IAM3=kI?8UGg39arSU`x z)R!*EYlyEb9r#*4Z($+J=n*_7-UC$z8~8jkeaedaE_gmXCz2HoyPRu1t(IoBbR5F# zD(J;0w&$8ucpk+x|7A{&Yfil#g1Pjc7vM4&Im|gcbcgg>!p=~ zL#z3A64@-ttnrG$^|wTIn~5WahgI3BmL)L*)uO`T#X<^7b0G6Byi3fk&VjcNyjf2> z^V!Kz7R)r$nAnb8UrmF@`=)nf>P2lt$$z;Ns2s?+X>nj=2v1u$jLoqNhi~%P37$DV zdn$|?gANU)>Zg38q|H#rr(iyK%rNdr{7ko^$+ovrr$$9=PBQ>;sP5$f#rg^32U8+U z!SruM#1ObNgIb5_!WXz)L0rZ4zX;jRw* zZZnPWQCcniHfEBU92V4ZyqYX_KA;CkAZH=u-i+UAAHF3F9m{IW@pnd7C9GMpq)J_% z=BCdgG90@02TdUd%){}2Y5YP!e_{9q>#Hr-m!U7GfFGaX3-TS>Q|s1|?~(3A-`L%O zIQyYXH6N%3d1$)zlHTF7y_hXyy_|=M#j{d})DD9CP*$olm0?I%;r4RlBX+>GN0nWK zG|XqxqzJQe6MV3A#I6=qHO$REPaY?fr@Id|aEQax-v#BJIbm6Yxr3CR1cLwQEa=-$ zb^R*+t9Bf-!L-2P1to>SVV<9?t}r>-V7O3{%#>yMi^qh@Ls|KfkHcQ!evrf)crvqR z$KI`nQbT)FbHLMi#lrmRRVg{$MceS1_qRedEx`JW!-W<=Nl4xR$aU@aY#gbeX&A=d z=Z6r+)W}WAJ)vGrU4|ruW!OZ^of@1De&A(ohjcrj1&sJ^sZIkuAeU0eC42BWv(&nutvWOExdj%Lv8?J_+#{; zqrDp)c#&8AM3W`#(Z>SScxxsP~B zeI7B!R1F=o#>K*Yh}l>cp|9MqO3C!zaWH+Av*Kv&=V1)9Q+c{tSmC^r0}EH(1*Ix<8{o~FQL&xz?7oe&qh#iM*34ORnHOV_ zKi@&40J5TKfE9eDpzG;^;hvzXi)kAwzb>tojMIjow|@x?yVS&rl}TUi7(~&S>F~Cn z+6e-GD>o^`jZ1Q?t1lof#DS}Ipoao4Yi~;r&wIS*jO%nyj~rkz-3!j;>Nu;dOOa67a)lZfLF~x>yJNz z-piZm>s`|SI@W6zg!R%_B9=;=fp@4!t|c3Ve|0@Ta*Y_0!_Zc>2jVGnum53BcZ$r%Z1CBUO&mh15j+h?hrV8-ai zj8`$CtA)pa$qpE4R@Qy=nunx&+>E7jQv5}IJ-#v{$0}rmPk}EwgiUY+|E2!d;dsA; zj^*1$+roRTySekzsGAj2yWQAiFc6r)`@VfYv*JI^>wR2Z*5NY11nKoXLF%{uhKR=< zU+QDCa&k$ZUkTHda|?vzCi$|Yb3Ez+`TFtPp(J&sudxMv^`E9lj$|a>Rz85=c|pjJ zGH=x^9cU5Nb=WNx2%WH3+;qz3L05G67e68K-CYDd2> zd5cd9*M#EJTH_;2>SuKQ$<`&V_WJJ4aEFH;F)bytv{XEk6jzPvwnF3s;lvG#`Pk+t z1#DgNd1muMERQWmUa6leH_g!5q#91groFFXFelAsf-nbGWv?nG++CqYXM{Ur$++5O zc`0e}!8J~+krkx)8(vvvM;93F$Y+M6 z&&N7jT*t&zeP1hjw)!Wv*V8h04{Ah}x%B&0Lxyyr?aVW>aFL3H6EI^im<`I)%FpZ! zrBu%;6L`NLm{gZ0u{|Dw2|O5{fs9D9-%PlSzXiA-y!vf7G{DoQ9$e_+VSDSMpKx5Q zY-Gk~GG6#izp*t+NnXR7b%>;Dyx3Vgb-itevN9=H%%_C4vgl3QIcKizIA7f%p>IiY zcqFy)@Q+d&Bx$W@#;)1NKq`CWbb~T+`(|w1cj9x~c7r~6Z2Cf4Uv)YI9A(oe0lRLU zdmwF)?(afJ_miizX?i!l!=TxPYO7MPGX&Hm{W34+bh_JBc# zuvlX@$fveFzho}kRZtPruo14weLrCUifiYgAAq7ujFO2F8TMiF5<)CRF%&tES+sHm zlmSrdo)fA3gDW3|3m2gb-7f!RX8qB^|1l!)GQS+|<+VFNcyAE*W`9$5dWOL;Sh2Co z{A`CGKGf;QLK0JDqg)#AN!25VnK+mMyerH-aCqeDA%r)H7_QjtQJ;F6;MVfR>mIBh zOZpoLRqxl^HZ1*-`*_PpW_x?LkNVKmha1x_t_du-Bv+CdUq4|kB1r6=29px-?1o)g zRt5}-5~ovG5Me6F^&U2^dNhXV0F+mZ#j0cp{dXlVln# zh!-7C-jfU#@CCQ(a5cu)169>Ct#_0+iNaa z$q!uaLhZ1VBujZotiF=eKcu3Cs?>rzOdQO@QpJH|)Q;%Q$Z_5LZDUE~kdN+!cvCIo zyoWLuyD z>mvKUght3d`|?gyR8pi;mXgpiXx|sP#Ihdp7ZR_bDndaGs}JSzvT6?ZZ3@`Fn)4C z(^*fF?nXC?E{j+*Cv-$T%UL&Hl)WuZiqO|9JGZ4ty$hZ5(lZT(Q+cC1cy~Af7guG* znD`#b6`l-R;P2$NYGu>Dj=jdOK4&xa>ztU9Xrr>iFB>OsoP6k@(?q>H1D(!x?G(4D z#f$}tR_o{JcKh@G)HQ}1BP~{$>&!Peu}>E=^3Qo^MHl@);g^1NFz9VC%vxx7!E9tnzpLSc zoF+e?+izF)))?0-cb3M@__Jk~M?)J=Ikm~IQJ*UhEYDfFFCH=`B536H1KuOQe+@l9 z$ok5q@(sV|EIM}a(X(EQH;4+Jd)+y9rmJAT(W|f)p4aMeM_h2f45>`JiuMDzF@{kM zN&|LE8kt))f%B&8OST5-H=gxy)W&Cxb^G63;khIGhV*K$j%C@#AMN~8dk;>| zxZd#7&tVlQf5r~nZzo%|AlS6ehNOMr`wY8XJlW2Edt|$IVShSU<^KrkwXsk4YniW? zeq6X<+57pQdp23W{>%IBh9>J zn(s;5YT=i$Sou7M)ifHZf9vjbAkTvZ$#zhRX1 z1krPD;kyP#Yswc5NeO+_FmA_gqwfQfH>B2k^`8whENq_N>YovrQ+T3lgJvykZKY*D zf^)Nz|NKom%J-%I)Hlh!=l>kbTmAF>zULd?4K`mA()(lJ*)-FO@G{}uJQWz+sy8E=`nS>`O1MS}5a%bAzgI-*&@O{eg;s8O1^RI3X&eNS{(8(cn`vLi$$Z-jWTd&&^{2`{ehga5vmkrjI z-#m1tk;|J=y!$UkTaTD{DKh3<_?p6|+HJS*I#qv?_TK!MlNY8R4lwc2zwo@PTiUnF zQ(E{M5gy%Lx&F=4izYAg{$Or>rp(4PcCSs)>zu};^&C4K zDjBoDwb)@#!Ld_$cl~-vh^A)NJ#y@xnr|JJ_}i!TpE#!~o=2=dVYzke*t@p(Z>1M5 zJ`xpob)x;&t4-rBu3X+fynSkn;m+{n@snNzxl1;8Xu8okp`Krtjh9|sy>jV-$8!5+ z!nwUWPx^h&T{pJ_le(`uJm^n<-z?X0o%3`juGf;pxcKgya!X>{UfA2PmD}i(aoSEx z?0nbd2k&0o*8Q8XJWwxbkYZ?hg8A$*Z8o(x>pQhc(~tFr|JMHCgEX_(5rw_DAw65C ze=cxdarss3Mg9Go76%o_SY8ZCxaFVsUjBEWyZQESgZK7)BlFAmpSrw4H-DK`o`v4# zTc+FAd1mypxgHR5<8SwsGu}B^{+1p6=EkH`vW`C27p5lpIdH=Q&TMMB4DCH+}Mi?mrd$>wc$AKl{p(an2jA2&D}X^b@A1t9P_6i!UDOk zH{A*yR$v`-&CzN9jY0>@@|2G=2TEqY4O@JEsl%k9hEK-~{{A_8*E!2ME$Veiw4c!| z_so!HS9A25k4}%-AGEftw5jJwv%B7FyRZIg@@Q9f#O&8OM|6y`-cD~Z(rA4Pi|TP{yFpO`Lyw$eqQes@g{M`^+(SS zbuW!xBi2bRa56unJ=|IDJ@jDL(sAoQUOs!&^YpY96W#y%EKKLr`>Jny?n;m0o3<}Z z)Xq&9yWmRJ!KEeXOH5L?G+y#8!SGnC4=)-YF)q2-SZC;#4ttyE#N5=28SmfFpTSWds1AIb)fCw3A1dn zizeUYiDP9y69w7C)SrawVfS@~<}b%>w%*#%M<~lOaM|pUbm!wN&uw3B$8JsNp5WwB zn($~^`l20&X87s%>v{0js;tJRoBo&(az$^2kHeP1O-jtZq=b)s)v39QR+Mf3o6#8q zOBRpTiq#DXh^~J~u&8Wa{etk-5BFqD^m^f{728v~DJCO)ZIe;$@BKDACpN?EnPSf1 zlI1sDUh=fObVc@7^F&9F3{VutWE)*L*`Gf(Lc9H@9{S_v)lYES*FQF+zi`~`JsBQv zv$wXhpC96HKJUbHyW0a`*mbwB_mAHmc6&$P_<1v(T*puCx%u!c>v^IJ6KC7Z8|}7k zPGbG(Yi-Mt+Z$-lYGaVBKX^gp4Bh_6vr9r-eD->4)p%Xr{>HhZ_N{L`%H`-vNBxE8 zj?OE;H&lG1ZP5VfK*Jqgr{mi=j2+Q-q4DS`v;3W|FS^$5aQwl*o%;_BIT`IbCM7lD z$ezHVecN2SZfG>^qF^rC)F~?c?~JquH`cvAziigv0^`}QS~x3WWe>hBe-_pE zz*g?9*qq!vqgHuVih;`#TLy)EbynPne7y*M_NK}6JKef1Il1S&jkm!&|AVc^F38b& zH{jqyX{(}QQwyGht;}pm{e8zfukyYhDY<-W_O@-^0#^3@`t{l%`^E$E+bs9qkeAzD zd+4yiZ5|CT=vBPzOYis@mPK)|*io`qfC@a$g3`J9gj^=*fRUocB)8&<5l?e(;V zDSI|W>fW+)*}LOyuLe;EwU5@{yy9NJ@7t|LUJ$1=JocX$mrdynFE@1)oNZt~a*kQQ z$DIl@dvBii@(+W=!p4Wk?me(Cx@qf!XFk5Wu&c#o?~I2Z{%Ck?nM-)iqYXW$HQnH3 z`po?C(xh%;YtHbn3hU^K{eL(|Rd_60zUrpsi2EboAL-=iC*ZmcS!OnKQp(Bh&->e4 z?K2~CU<5C%(S>PQ=MJ>1a4@QWGx%oy3RkN>ql0bYgvEM$?T5#7nPK5z)MTQgPP9jg z`O3>j&vOSpwwfg!ecx*4CjL3z*YKP@Z^@ON|=9-U`Z zG|B8kfWFnIOM-3_9<0CV&~v@+B7299gKgR*<@;!jh%p>I!~OotlBl)XIa&*k93EkF z$!z_c!ac!W6ZS@3Zr8u$@7>pl!-*}6S{?YqeQW0_i9^4%i=JrxC5ZP@udnyuXNgZ9 zTPbdR**)Gi*xTm(?H2kYlh=3nW}8+&Ci`0pkM@EGzO(0fJ;-*QXmNk^Mwf*8gYs7S ztvIQBI9vCDEMSy=%wA!;=A>DU;ke&A!kC7+Bntrj~ zI-+#{@&}G1_jHSLyb+Nzv*Yd2#xWAJ-fuF}$FAD%HDHk<@kY7%%KcH2KHQv1QEz1X zqKdM+CdEY!Obz-Y$gb(zK7oQK&2|NSoFX`p`C-pKr_Q2t%dYIIC<-_>sch{0g6!;! z0|Qh0x^5Gel$worbke^-_NeWloJP0DMBF%X&wBlC`O_gSJ|2)ij(w4T7J$qnoaD1(vy=jNJ zOMKV-7F94yun$7PK)Z&7iU-sfkvkL8}xuYXmr01Jj?TOt#%1ZWE_aWPc95!t3m0L8hcvWiS z!@TXY*Ep2xMj13I@AEip?V8Xf5fE@5^dRnp#8z}F{k}G4PCz^3mM08@GEEkD4=?Bu z{C2`r!)Hckvm)nK%zM#t#gL5r(D;YGY16+MT-h~iXGQnE$&F6sEyx?`<67!F-_iF& ziOJ&urQHjDa36*#7FXna@p~7LzGKq;klqQ!Bi~PHQkvHMv9aa1kS8abyxn(`K|?1#n7C>7Af1!^@(vfY9Jk)O7tlUHm@wz$3a4x5?`hw2jUAup z;hyn$u9wS~D%kQnHcT`sYiJR)R%I0| ztsg!tWhZCM(Dz(>o&AMjF53Ir?A^)fzQ0|Vt8VK?rdh3ZvSnw905`7xl0G?wy>`yc zdNFujp?84u=5D%ovRj|6|FQUr?YIs$Cy$>TYg?Z3wc@uKdE5GGsuaLy%W;8?Kqi-0 zR;CDqmFLja6$F|JY>YJ)*bFlE^>VAB%0?s>!>M>a@k?bk@Sv=UIvae1o^h(4@_VYv zQ}tp|jdeEbE=NA#w_EgWrWSAi@-fc}hW_4UxXaYl_MdkPXU|>TN$=)rQ@w#sLQVbjsVa<7$%5=_kGg2FLUN`WdYw9I&q_Pk#7wdaBfJ zL-xah0%>WHGqI{DctgdCS&8x?S914hTbGa85Ycj5%Q8QKb^6KdQ-=Bb3v;KG@we(8 z^344a82jnO<}}HK9iDeS?OL8!_A@N9Uq$_VoprL^o}-||=BKyEM4c^9pCts3xiu~I z@!icjrz^f*YHw%t>g%2+f?MZvcH5uQKh}3b?|c1t9iOaCsn5IM`=qx+M*G0^Q@*Fx zKN1!Fo9{i(CUbgw9vKvP;%;QDp>M3~nOVYT-ox^tnd7UR%n%PD7W&|iy$0JcvCVyhfZ8v-u~}1dJ%s>h0G~yi*jNk6u|7; zTN=?Y_wgA!D2Rc7ITKzNAJ;4E_E+y8p4_rgSEDO_5AFTA<6om*PkHv%xB}`hr}({! zEbl$UX^YOG%nkdrTo;FanEu!3SuJ}+9y9E|&Dqk|=zDsxXO}*1g~c+z)sHS}lyTMX zwyJ(j7uUR0T@6#cNNX&K5z|XzP#Fm-A3^0Is&W<7rY=!|G8j~Hf=WqnUnrTuWhxS> zma0Srm9VJGQlPvAjfXxH@QR*6{i)xh_b@)aq(<3KRRTkOMWs73F_h^@B)HrLeTF%; z5KQR3h7uNR6yOJhDJ3idq*)AQqx|}$nSdh@Ao4@`3Mzp^bxDlgl;6)qe1ZPxd6>oC z+QmcR8U#$U@D>qJR)mMK@D|HC@D~kf;Vl7LU!t}r2AEXWsQ{IkdEiamH1Sxc29*+74u zjdK7h3l#84L~Rv;zQF;mia3FKg;DdmP@rcM8EKo8ZBzXm<14#_7K?V-%-zfXMOuYxaCiJ1y~ zIYlk7NBwV5U-YZsd^XTod>Qea0-Gip;ZRF;$ivKPy& zOS#o(IwrV^`nXt~1~SY2vt2k4w02d-UR=cT;<%+IG9dq;;^My^jj_-X2&-|GBqNnV zDPlQz+)@)d$h9fa5hFoE0*AN-uh>0O4J>@Git4eEMSYc_mHHVZC5UrK?qI0+E}R zUlCEf3|=IYsgU7b3UA2v!g6zZaDW1)=3wg&Z>Dpw2EyG;u<*nlOp7S*3Kw=+-XefaE5o9m{6$U9Op0ye%?fgJGj1|*-0~8!FFrG?E$_N^uGzWpH zf`ZrsA>gD;peVkxmOu$$8oMCpfFKJWHQKmND9A!?e2JpD#6Ac1Ir}` zE*67CA*_PomNAf61{z|@?LW0dLx^ZgM?}aKQxU-_TBTj89utIsD#uF`T@05kmI^Tk z^0Rb_psWEkjBjbI)Cuw$BbY<2vmj*HiZaHdtb;X;XMw@Q@dslt?7bL+i7BI1M^dUJ zFbP;E)mNpjE71hggrOh_2p3Gr;EpM2WY94DbZ7v!{lyI+jorkr^j}=BS6z09Dn25}q5o8(5UkxFCQXU-g)^1goY< ziJG|3047CpY7CVybR^(n4#e{iw?n9mc7I{h7p`~|a3G}!)&*3Rffj{SwM2_aZE6BT z0~*y(8yk3J?LYw-U{U;BEy1dZ+SCMt1~BVHZFOJ@0t2KM6KH20UC{|EG=};@!oALd z!=f-HaL@pH)fC2aW3hnUemaE#)PGx^sS9}xVAh4g7&;PgSV>7>-OG`{kqfvCRnC#Z zRiXlpltpI>k!mS2EILyhtOf+Cp|d)sf+P}y&dBZ5Ev~3hlB>7ib)vI6;DCL1sRRzz zaHKMhl;B9^Ogt#G`Bm~#EPLI^O9mK}P)h(ZQ46wCo}haAT1-yvz~J-BPXm}W^Og!YcG!6Jsm@8jSs&I~8p2{&e8HI;hUC`HRFY&g0o9pIN62MS2C2nk_v^8w-Sm3u=gXCLAIPW71&QT9W@|bnY~iEIS4=1czOjX z4?~C{Rf)FM)LU*SnGGpYG0OR&JRr*XsZyZOCV}t8s1NL$qBJagt?UQ+KZwe};NVaV zmJjGu7e$r?V2IQJ*HRfYrjbr}{JvaMq2mYR3-iUNX_cfS*E?U!^O>{b_)= zI=Ye$9p#{yUI&7Zx{7Q~J{24Cs#6AaqbmZBR5qNUAu}EtmhA!vrRs_ZOK>S}rj7(w zyMER}7UH|Q!c`-IDZWGloOL6x+Uts{SjjTvm_nmplw+zp(ExsRa$Mr>Q?$MW~Krcf&9Cf z3aYMZfU|DYRlBq*_N}1y=ARlWs#rA2-4^8B8T_WG!oTDRs0yk9&brZ6?ZY5# zu3-ZQ>KkiCS7jbRD&j)vit-eTNX$o{D%V>=0_Kp&VYMZO6D|T8p=1=j(129sY73hI zFw3yoQro=BEP(Q`5FqBc}&fwC5wvJaT**!MtiNuv|`ou%3c zdQA)wN`=4XMr40#z19HD5`|1FLn?x>E{;k*$=D)6yZ;I1aFVb+jbW13f@R&fh0 zSy*$zh3XMCaHvj(00W3&$C_x1b|SG&g?G$tBU~6*H(yZ;WGIH_A0wlVfM7R_p+2>X zZZxTnPy{MNwP#Qg0ohH`q6tNEEaW4S5Fa6hB)vBPdH+IVGgj02eb(C68<4 zB`u+jQ&~Vz#;h6$nt!TkaV7t!2AvAHI!vW6X&IEH%veh#YL)`<{gnZi26ERd(^NbV zgxJe6)lp8FwQ!*L^?Tpmh=oxjI^59&N7P0NKGk59o-M(#AvhfH;6@fUON|_QFTl z@J0V)!9$0r(Fd|V6>YcRPh<~#APJ&9vI{;igQGyy1t0Ffks$trEi*c11fR6X^J^%9 zh`u8?Y0+^Q^oiUWkG9$%#opgu|kk&$e1L zfkLBJozFD&bqz-K9f&gm2%P~|yjWH9hMb=^BH52U=GXv*!q(RpjfHQ^K%e0Vv@+DP z_4QJWc1AZ`kg=q?&;y?e#P_ECMt47DS{!vDd~7hAwz zsOVQBrGA4p#L>|$Okg@L7(68|R>9tZj#_+X~$wquy7PNom6I14QEfQ7swPm2$GX`_i>JQY2x^JL-Y9psKu6MzE6+G7lbjqf zh(vwJ(swa+YB|za>eMFALC#*j)4^>32!If2zj)Su3ZG%FLGHdj9Wi(9g?xCU^mS{|vq}33~wR;22F-dEu>Xq;U?z~4E*)52$(FAR;|D%_N}Fwu@#VT@HsQLeo~*VN!V zYBf1mRPjm8^`g(}LRjFlssa?XqxUrz*P>Hl!9g>81C-~@Y6}I*@S<#R<=qHmJu1rn z;&zmQMz^mh&-~zwGs-`r>@~_Lqf9dNL6G7yVKg3m5EB3q+{T74`y%fHsx3(WI$=gB zW`_C(xCPdcH2*prCIJfPph^xQz5=BX6;<*g69U!X1~a!{SA~j`VpSnE^u_&w;`n4B zDx^mL5El@apdE2Rq^w33@nI}HCFql>bC9C1slJd8_b-U~Vu4TyH3I|q!ajU{pMDfo z4FkP`ct{|ZYQa#-KXCR|0&aF12e%1iLIQaL^oQd^wxRbZPC zT8uUc@>aAqnG9}GW3|D3YOFRH(nH3xa^$|~ZF1P-V79@fd+atLa;NlX34BzI(I!PZ zl8iP9x?qdZCPvw4dK)l|1s@?tAsxLRL7){7y-hAeF*~D84mS-l+azek$>=9RrDlva zF)D3jw8>GuD5H%)737RI8Cqh|+6cY?l3&a=IDgA(lkl1I5@1xAa}az21t#=4z|pbB z69}2|CxAk9g56JoLIB!$GO%BaIY5&Ll#HVG}s(mZ)uf z5dg!~Uyv3yk3=FA2vDCD5F$1|2$2k33(4pQg3oLd^I0-Zh=nYDBgAmXn)R#{EgWcb z!0nBwqL3J3{aOEcO;D6hn56Eo+9XQgPt z!01P4m;;O~v!9I5vYjA7Ou2`ggx;>T-AArZ!8SpbqSO~cQ z<=tqwK{vMTl*1i>jDB)~gf$+(hY|#|@g#(Z#a{`VZ&14+MJX-%v)~%pZBn$5r}dM; zVR~jiXk*HOKqio(lNt19g+jE_W3)j~%x+`R1-u}O&d`STg6QLkOt9=4EELdMlip8)j!`h%#4Ot>gZo=p_yUty<4O4_(?p*`DrCnIQZc%& zm(fqkhEEFj<1+d|h=fus^k*UZWy&u2ZkDZ+0U``LE&$C(`7p+KgoYRn)>_Pdu=r)d zCnIF&iembBa<=c5$we%?E0eQ%308<@O9(zIwt&+$VphybK)AuAGuS#7e+j-ERS(c$ zCg5UbCau8gvgk}er6~JZwhn?{VDX$ls}knC=%Qp+8(UroA-cJm0W(~A$9h(T%bw`C ziQq0c=CdLVc!;EE1Ay@?7*u8(EQy&i1n~w-HVCnZ4YL@Oi#48%=~p3UfM|u;PXg;a zR+|uAze|T%B1U;+Mw^6XHwdUDV$lVb3oQP^)mkiAV3oY!n^^ z1vtBV!R=G%#&7p 0) { dest.setFillSize ((result - 1) * sizeof (char16)); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fbuffer.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fbuffer.h index d0e2f39..0a8c1ff 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fbuffer.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fbuffer.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h new file mode 100644 index 0000000..653fb7c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fcommandline.h @@ -0,0 +1,366 @@ +//------------------------------------------------------------------------ +// Project : SDK Base +// Version : 1.0 +// +// Category : Helpers +// Filename : base/source/fcommandline.h +// Created by : Steinberg, 2007 +// Description : Very simple command-line parser. +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +//------------------------------------------------------------------------ +/** @file base/source/fcommandline.h + Very simple command-line parser. + @see Steinberg::CommandLine */ +//------------------------------------------------------------------------ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Steinberg { +//------------------------------------------------------------------------ +/** Very simple command-line parser. + +Parses the command-line into a CommandLine::VariablesMap.\n +The command-line parser uses CommandLine::Descriptions to define the available options. + +@b Example: +\code +#include "base/source/fcommandline.h" +#include + +int main (int argc, char* argv[]) +{ + using namespace std; + + CommandLine::Descriptions desc; + CommandLine::VariablesMap valueMap; + + desc.addOptions ("myTool") + ("help", "produce help message") + ("opt1", string(), "option 1") + ("opt2", string(), "option 2") + ; + CommandLine::parse (argc, argv, desc, valueMap); + + if (valueMap.hasError () || valueMap.count ("help")) + { + cout << desc << "\n"; + return 1; + } + if (valueMap.count ("opt1")) + { + cout << "Value of option 1 " << valueMap["opt1"] << "\n"; + } + if (valueMap.count ("opt2")) + { + cout << "Value of option 2 " << valueMap["opt2"] << "\n"; + } + return 0; +} +\endcode +@note +This is a "header only" implementation.\n +If you need the declarations in more than one cpp file, you have to define +@c SMTG_NO_IMPLEMENTATION in all but one file. + +*/ +//------------------------------------------------------------------------ +namespace CommandLine { + + //------------------------------------------------------------------------ + /** Command-line parsing result. + + This is the result of the parser.\n + - Use hasError() to check for errors.\n + - To test if a option was specified on the command-line use: count()\n + - To retrieve the value of an options, use operator [](const VariablesMapContainer::key_type k)\n + */ + //------------------------------------------------------------------------ + class VariablesMap + { + bool mParaError; + using VariablesMapContainer = std::map; + VariablesMapContainer mVariablesMapContainer; + public: + VariablesMap () : mParaError (false) {} ///< Constructor. Creates a empty VariablesMap. + bool hasError () const { return mParaError; } ///< Returns @c true when an error has occurred. + void setError () { mParaError = true; } ///< Sets the error state to @c true. + std::string& operator [](const VariablesMapContainer::key_type k); ///< Retrieve the value of option @c k. + const std::string& operator [](const VariablesMapContainer::key_type k) const; ///< Retrieve the value of option @c k. + VariablesMapContainer::size_type count (const VariablesMapContainer::key_type k) const; ///< Returns @c != @c 0 if command-line contains option @c k. + }; + + //! type of the list of elements on the command line that are not handled by options parsing + using FilesVector = std::vector; + + //------------------------------------------------------------------------ + /** The description of one single command-line option. + + Normally you rarely use a Description directly.\n + In most cases you will use the Descriptions::addOptions (const std::string&) method to create and add descriptions. + */ + //------------------------------------------------------------------------ + class Description : public std::string + { + public: + Description (const std::string& name, const std::string& help, const std::string& valueType ); ///< Construct a Description + std::string mHelp; ///< The help string for this option. + std::string mType; ///< The type of this option (kBool, kString). + static const std::string kBool; + static const std::string kString; + }; + //------------------------------------------------------------------------ + /** List of command-line option descriptions. + + Use addOptions(const std::string&) to add Descriptions. + */ + //------------------------------------------------------------------------ + class Descriptions + { + using DescriptionsList = std::deque; + DescriptionsList mDescriptions; + std::string mCaption; + public: + /** Sets the command-line tool caption and starts adding Descriptions. */ + Descriptions& addOptions (const std::string& caption = "", + std::initializer_list&& options = {}); + /** Parse the command-line. */ + bool parse (int ac, char* av[], VariablesMap& result, FilesVector* files = nullptr) const; + /** Print a brief description for the command-line tool into the stream @c os. */ + void print (std::ostream& os) const; + /** Add a new switch. Only */ + Descriptions& operator() (const std::string& name, const std::string& help); + /** Add a new option of type @c inType. Currently only std::string is supported. */ + template + Descriptions& operator () (const std::string& name, const Type& inType, std::string help); + }; + +//------------------------------------------------------------------------ +// If you need the declarations in more than one cpp file you have to define +// SMTG_NO_IMPLEMENTATION in all but one file. +//------------------------------------------------------------------------ +#ifndef SMTG_NO_IMPLEMENTATION + + //------------------------------------------------------------------------ + /*! If command-line contains option @c k more than once, only the last value will survive. */ + //------------------------------------------------------------------------ + std::string& VariablesMap::operator [](const VariablesMapContainer::key_type k) + { + return mVariablesMapContainer[k]; + } + + //------------------------------------------------------------------------ + /*! If command-line contains option @c k more than once, only the last value will survive. */ + //------------------------------------------------------------------------ + const std::string& VariablesMap::operator [](const VariablesMapContainer::key_type k) const + { + return (*const_cast(this))[k]; + } + + //------------------------------------------------------------------------ + VariablesMap::VariablesMapContainer::size_type VariablesMap::count (const VariablesMapContainer::key_type k) const + { + return mVariablesMapContainer.count (k); + } + + //------------------------------------------------------------------------ + /** Add a new option with a string as parameter. */ + //------------------------------------------------------------------------ + template <> Descriptions& Descriptions::operator() (const std::string& name, const std::string& inType, std::string help) + { + mDescriptions.emplace_back (name, help, inType); + return *this; + } + bool parse (int ac, char* av[], const Descriptions& desc, VariablesMap& result, FilesVector* files = nullptr); ///< Parse the command-line. + std::ostream& operator<< (std::ostream& os, const Descriptions& desc); ///< Make Descriptions stream able. + + const std::string Description::kBool = "bool"; + const std::string Description::kString = "string"; + + //------------------------------------------------------------------------ + /*! In most cases you will use the Descriptions::addOptions (const std::string&) method to create and add descriptions. + + @param[in] name of the option. + @param[in] help a help description for this option. + @param[out] valueType Description::kBool or Description::kString. + */ + Description::Description (const std::string& name, const std::string& help, const std::string& valueType) + : std::string (name) + , mHelp (help) + , mType (valueType) + { + } + +//------------------------------------------------------------------------ + /*! Returning a reference to *this, enables chaining of calls to operator()(const std::string&, + const std::string&). + @param[in] name of the added option. + @param[in] help a help description for this option. + @return a reference to *this. + */ + Descriptions& Descriptions::operator () (const std::string& name, const std::string& help) + { + mDescriptions.emplace_back (name, help, Description::kBool); + return *this; + } + +//------------------------------------------------------------------------ + /*! Usage example: + @code + CommandLine::Descriptions desc; + desc.addOptions ("myTool") // Set caption to "myTool" + ("help", "produce help message") // add switch -help + ("opt1", string(), "option 1") // add string option -opt1 + ("opt2", string(), "option 2") // add string option -opt2 + ; + @endcode + @note + The operator() is used for every additional option. + + Or with initializer list : + @code + CommandLine::Descriptions desc; + desc.addOptions ("myTool", // Set caption to "myTool" + {{"help", "produce help message", Description::kBool}, // add switch -help + {"opt1", "option 1", Description::kString}, // add string option -opt1 + {"opt2", "option 2", Description::kString}} // add string option -opt2 + ); + @endcode + @param[in] caption the caption of the command-line tool. + @param[in] options initializer list with options + @return a reverense to *this. + */ + Descriptions& Descriptions::addOptions (const std::string& caption, + std::initializer_list&& options) + { + mCaption = caption; + std::move (options.begin (), options.end (), std::back_inserter (mDescriptions)); + return *this; + } + + //------------------------------------------------------------------------ + /*! @param[in] ac count of command-line parameters + @param[in] av command-line as array of strings + @param[out] result the parsing result + @param[out] files optional list of elements on the command line that are not handled by options parsing + */ + bool Descriptions::parse (int ac, char* av[], VariablesMap& result, FilesVector* files) const + { + using namespace std; + + for (int i = 1; i < ac; i++) + { + string current = av[i]; + if (current[0] == '-') + { + int pos = current[1] == '-' ? 2 : 1; + current = current.substr (pos, string::npos); + + DescriptionsList::const_iterator found = + find (mDescriptions.begin (), mDescriptions.end (), current); + if (found != mDescriptions.end ()) + { + result[*found] = "true"; + if (found->mType != Description::kBool) + { + if (((i + 1) < ac) && *av[i + 1] != '-') + { + result[*found] = av[++i]; + } + else + { + result[*found] = "error!"; + result.setError (); + return false; + } + } + } + else + { + result.setError (); + return false; + } + } + else if (files) + files->push_back (av[i]); + } + return true; + } + +//------------------------------------------------------------------------ + /*! The description includes the help strings for all options. */ + //------------------------------------------------------------------------ + void Descriptions::print (std::ostream& os) const + { + if (!mCaption.empty ()) + os << mCaption << ":\n"; + + size_t maxLength = 0u; + std::for_each (mDescriptions.begin (), mDescriptions.end (), + [&] (const Description& d) { maxLength = std::max (maxLength, d.size ()); }); + + for (const Description& opt : mDescriptions) + { + os << "-" << opt; + for (auto s = opt.size (); s < maxLength; ++s) + os << " "; + os << " | " << opt.mHelp << "\n"; + } + } + +//------------------------------------------------------------------------ + std::ostream& operator<< (std::ostream& os, const Descriptions& desc) + { + desc.print (os); + return os; + } + + //------------------------------------------------------------------------ + /*! @param[in] ac count of command-line parameters + @param[in] av command-line as array of strings + @param[in] desc Descriptions including all allowed options + @param[out] result the parsing result + @param[out] files optional list of elements on the command line that are not handled by options parsing + */ + bool parse (int ac, char* av[], const Descriptions& desc, VariablesMap& result, FilesVector* files) + { + return desc.parse (ac, av, result, files); + } +#endif + +} //namespace CommandLine +} //namespace Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.cpp index 6355feb..9fe27d8 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.cpp @@ -11,7 +11,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -107,6 +107,8 @@ bool AmIBeingDebugged () #include #include #include +#include +#include #if SMTG_OS_WINDOWS #ifndef _WIN32_WINNT @@ -141,6 +143,20 @@ DebugPrintLogger gDebugPrintLogger = nullptr; static const int kDebugPrintfBufferSize = 10000; static bool neverDebugger = false; // so I can switch it off in the debugger... +static std::once_flag neverDebuggerEnvCheckFlag {}; + +//-------------------------------------------------------------------------- +static void initNeverDebugger () +{ + std::call_once (neverDebuggerEnvCheckFlag, [] () { + // add this environment variable to not stop in the debugger on ASSERT + if (std::getenv ("SMTG_DEBUG_IGNORE_ASSERT")) + { + neverDebugger = true; + } + }); +} + //-------------------------------------------------------------------------- static void printDebugString (const char* string) { @@ -193,6 +209,7 @@ void FDebugBreak (const char* format, ...) gPreAssertionHook (string); } + initNeverDebugger (); if (neverDebugger) return; if (AmIBeingDebugged ()) @@ -228,7 +245,7 @@ void FDebugBreak (const char* format, ...) void FPrintLastError (const char* file, int line) { #if SMTG_OS_WINDOWS - LPVOID lpMessageBuffer; + LPVOID lpMessageBuffer = nullptr; FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError (), MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMessageBuffer, 0, nullptr); @@ -291,12 +308,16 @@ void* operator new[] (size_t size, int, const char* file, int line) //------------------------------------------------------------------------ void operator delete (void* p, int, const char* file, int line) { + (void)file; + (void)line; ::operator delete (p); } //------------------------------------------------------------------------ void operator delete[] (void* p, int, const char* file, int line) { + (void)file; + (void)line; ::operator delete[] (p); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.h index a5d9923..3b376ae 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fdebug.h @@ -11,7 +11,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -97,6 +97,10 @@ bool AmIBeingDebugged (); if (!(f)) \ FDebugBreak ("%s(%d) : Assert failed: %s\n", __FILE__, __LINE__, #f); +#define SMTG_ASSERT_MSG(f, msg) \ + if (!(f)) \ + FDebugBreak ("%s(%d) : Assert failed: [%s] [%s]\n", __FILE__, __LINE__, #f, msg); + /** Send "comment" string to the debugger for display. */ #define SMTG_WARNING(comment) FDebugPrint ("%s(%d) : %s\n", __FILE__, __LINE__, comment); @@ -194,6 +198,7 @@ void* operator new (size_t, int, const char*, int); #else /** if DEVELOPMENT is not set, these macros will do nothing. */ #define SMTG_ASSERT(f) +#define SMTG_ASSERT_MSG(f, msg) #define SMTG_WARNING(s) #define SMTG_PRINTSYSERROR #define SMTG_DEBUGSTR(s) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.cpp index e085a34..a061556 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.cpp @@ -1,4 +1,5 @@ //------------------------------------------------------------------------ +// Flags : clang-format SMTGSequencer // Project : SDK Base // Version : 1.0 // @@ -9,28 +10,28 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, +// +// * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation +// this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Steinberg Media Technologies nor the names of its -// contributors may be used to endorse or promote products derived from this +// contributors may be used to endorse or promote products derived from this // software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. //----------------------------------------------------------------------------- @@ -39,6 +40,12 @@ #include "base/thread/include/flock.h" #include +#define SMTG_VALIDATE_DEPENDENCY_COUNT DEVELOPMENT // validating dependencyCount + +#if SMTG_DEPENDENCY_COUNT +#include "base/source/updatehandler.h" +#define SMTG_DEPENDENCY_CHECK_LEVEL 1 // 1 => minimal assert, 2 => full assert +#endif // SMTG_DEPENDENCY_COUNT namespace Steinberg { @@ -54,52 +61,132 @@ struct FObjectIIDInitializer // only can cast to their own objects // this initializer must be after the definition of FObject::iid, otherwise // the default constructor of FUID will clear the generated iid - FObjectIIDInitializer () - { - const_cast (FObject::iid).generate (); - } + FObjectIIDInitializer () { const_cast (FObject::iid).generate (); } } gFObjectIidInitializer; //------------------------------------------------------------------------ -uint32 PLUGIN_API FObject::addRef () -{ +FObject::~FObject () +{ +#if SMTG_DEPENDENCY_COUNT && DEVELOPMENT + static bool localNeverDebugger = false; +#endif + +#if DEVELOPMENT + if (refCount > 1) + FDebugPrint ("Refcount is %d when trying to delete %s\n", refCount, isA ()); +#endif + +#if SMTG_DEPENDENCY_COUNT +#if SMTG_DEPENDENCY_CHECK_LEVEL >= 1 + if (gUpdateHandler) + { +#if DEVELOPMENT + SMTG_ASSERT (dependencyCount == 0 || localNeverDebugger); +#endif // DEVELOPMENT + } +#endif +#endif // SMTG_DEPENDENCY_COUNT + +#if SMTG_VALIDATE_DEPENDENCY_COUNT + if (!gUpdateHandler || gUpdateHandler != UpdateHandler::instance (false)) + return; + + auto updateHandler = UpdateHandler::instance (); + if (!updateHandler || updateHandler == this) + return; + + SMTG_ASSERT ((updateHandler->checkDeferred (this) == false || localNeverDebugger) && + "'this' has scheduled a deferUpdate that was not yet delivered"); + + if (updateHandler->hasDependencies (this)) + { + SMTG_ASSERT ( + (false || localNeverDebugger) && + "Another object is still dependent on 'this'. This leads to zombie entries in the dependency map that can later crash."); + FDebugPrint ("Object still has dependencies %x %s\n", this, this->isA ()); + updateHandler->printForObject (this); + } +#endif // SMTG_VALIDATE_DEPENDENCY_COUNT +} + +//------------------------------------------------------------------------ +uint32 PLUGIN_API FObject::addRef () +{ return FUnknownPrivate::atomicAdd (refCount, 1); -} +} //------------------------------------------------------------------------ -uint32 PLUGIN_API FObject::release () +uint32 PLUGIN_API FObject::release () { if (FUnknownPrivate::atomicAdd (refCount, -1) == 0) { refCount = -1000; delete this; return 0; - } - return refCount; + } + return refCount; } //------------------------------------------------------------------------ tresult PLUGIN_API FObject::queryInterface (const TUID _iid, void** obj) { - QUERY_INTERFACE (_iid, obj, FUnknown::iid, FUnknown) - QUERY_INTERFACE (_iid, obj, IDependent::iid, IDependent) - QUERY_INTERFACE (_iid, obj, FObject::iid, FObject) + QUERY_INTERFACE (_iid, obj, FUnknown::iid, FUnknown) + QUERY_INTERFACE (_iid, obj, IDependent::iid, IDependent) + QUERY_INTERFACE (_iid, obj, FObject::iid, FObject) *obj = nullptr; - return kNoInterface; + return kNoInterface; } //------------------------------------------------------------------------ void FObject::addDependent (IDependent* dep) { - if (gUpdateHandler) - gUpdateHandler->addDependent (unknownCast (), dep); + if (!gUpdateHandler) + return; + + gUpdateHandler->addDependent (unknownCast (), dep); +#if SMTG_DEPENDENCY_COUNT + dependencyCount++; +#endif } //------------------------------------------------------------------------ void FObject::removeDependent (IDependent* dep) { - if (gUpdateHandler) +#if SMTG_DEPENDENCY_COUNT && DEVELOPMENT + static bool localNeverDebugger = false; +#endif + + if (!gUpdateHandler) + return; + +#if SMTG_DEPENDENCY_COUNT + if (gUpdateHandler != UpdateHandler::instance (false)) + { gUpdateHandler->removeDependent (unknownCast (), dep); + dependencyCount--; + return; + } +#if SMTG_DEPENDENCY_CHECK_LEVEL > 1 + SMTG_ASSERT ((dependencyCount > 0 || localNeverDebugger) && + "All dependencies have already been removed - mmichaelis 7/2021"); +#endif + size_t removeCount; + UpdateHandler::instance ()->removeDependent (unknownCast (), dep, removeCount); + if (removeCount == 0) + { +#if SMTG_DEPENDENCY_CHECK_LEVEL > 1 + SMTG_ASSERT (localNeverDebugger && "No dependency to remove - ygrabit 8/2021"); +#endif + } + else + { + SMTG_ASSERT ((removeCount == 1 || localNeverDebugger) && + "Duplicated dependencies established - mmichaelis 7/2021"); + } + dependencyCount -= (int16)removeCount; +#else + gUpdateHandler->removeDependent (unknownCast (), dep); +#endif // SMTG_DEPENDENCY_COUNT } //------------------------------------------------------------------------ @@ -123,61 +210,61 @@ void FObject::deferUpdate (int32 msg) //------------------------------------------------------------------------ /** Automatic creation and destruction of singleton instances. */ //------------------------------------------------------------------------ -namespace Singleton +namespace Singleton { +using ObjectVector = std::vector; +ObjectVector* singletonInstances = nullptr; +bool singletonsTerminated = false; +Steinberg::Base::Thread::FLock* singletonsLock; + +bool isTerminated () { - using ObjectVector = std::vector; - ObjectVector* singletonInstances = nullptr; - bool singletonsTerminated = false; - Steinberg::Base::Thread::FLock* singletonsLock; + return singletonsTerminated; +} - bool isTerminated () {return singletonsTerminated;} +void lockRegister () +{ + if (!singletonsLock) // assume first call not from multiple threads + singletonsLock = NEW Steinberg::Base::Thread::FLock; + singletonsLock->lock (); +} - void lockRegister () - { - if (!singletonsLock) // assume first call not from multiple threads - singletonsLock = NEW Steinberg::Base::Thread::FLock; - singletonsLock->lock (); - } - void unlockRegister () - { - singletonsLock->unlock (); - } +void unlockRegister () +{ + singletonsLock->unlock (); +} - void registerInstance (FObject** o) +void registerInstance (FObject** o) +{ + SMTG_ASSERT (singletonsTerminated == false) + if (singletonsTerminated == false) { - SMTG_ASSERT (singletonsTerminated == false) - if (singletonsTerminated == false) - { - if (singletonInstances == nullptr) - singletonInstances = NEW std::vector; - singletonInstances->push_back (o); - } + if (singletonInstances == nullptr) + singletonInstances = NEW std::vector; + singletonInstances->push_back (o); } +} - struct Deleter +struct Deleter +{ + ~Deleter () { - ~Deleter () + singletonsTerminated = true; + if (singletonInstances) { - singletonsTerminated = true; - if (singletonInstances) + for (Steinberg::FObject** obj : *singletonInstances) { - for (ObjectVector::iterator it = singletonInstances->begin (), - end = singletonInstances->end (); - it != end; ++it) - { - FObject** obj = (*it); - (*obj)->release (); - *obj = nullptr; - obj = nullptr; - } - - delete singletonInstances; - singletonInstances = nullptr; + (*obj)->release (); + *obj = nullptr; + obj = nullptr; } - delete singletonsLock; - singletonsLock = nullptr; + + delete singletonInstances; + singletonInstances = nullptr; } - } deleter; + delete singletonsLock; + singletonsLock = nullptr; + } +} deleter; } //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.h index 9bcf443..4165a7b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fobject.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,6 +45,8 @@ #include "pluginterfaces/base/iupdatehandler.h" #include "base/source/fdebug.h" // use of NEW +#define SMTG_DEPENDENCY_COUNT DEVELOPMENT + namespace Steinberg { //---------------------------------- @@ -82,10 +84,15 @@ class FObject : public IDependent { public: //------------------------------------------------------------------------ - FObject () : refCount (1) {} ///< default constructor... - FObject (const FObject&) : refCount (1) {} ///< overloaded constructor... - virtual ~FObject () {} ///< destructor... - FObject& operator = (const FObject&) { return *this; } ///< overloads operator "=" as the reference assignment + FObject () = default; ///< default constructor... + FObject (const FObject&) ///< overloaded constructor... + : refCount (1) +#if SMTG_DEPENDENCY_COUNT + , dependencyCount (0) +#endif + {} + FObject& operator= (const FObject&) { return *this; } ///< overloads operator "=" as the reference assignment + virtual ~FObject (); ///< destructor... // OBJECT_METHODS static inline FClassID getFClassID () {return "FObject";} ///< return Class ID as an ASCII string (statically) @@ -124,8 +131,10 @@ class FObject : public IDependent //------------------------------------------------------------------------ protected: - int32 refCount; ///< COM-model local reference count - + int32 refCount = 1; ///< COM-model local reference count +#if SMTG_DEPENDENCY_COUNT + int16 dependencyCount = 0; +#endif static IUpdateHandler* gUpdateHandler; }; @@ -341,7 +350,7 @@ namespace Singleton { virtual Steinberg::FClassID isA () const SMTG_OVERRIDE {return className::getFClassID ();} \ virtual bool isA (Steinberg::FClassID s) const SMTG_OVERRIDE {return isTypeOf (s, false);} \ virtual bool isTypeOf (Steinberg::FClassID s, bool askBaseClass = true) const SMTG_OVERRIDE \ - { return (classIDsEqual (s, #className) ? true : (askBaseClass ? baseClass::isTypeOf (s, true) : false)); } + { return (FObject::classIDsEqual (s, #className) ? true : (askBaseClass ? baseClass::isTypeOf (s, true) : false)); } //------------------------------------------------------------------------ /** Delegate refcount functions to BaseClass. diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.cpp index b9af91e..524956b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.cpp @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -187,6 +187,30 @@ bool FStreamer::readChar16 (char16& c) return false; } +//------------------------------------------------------------------------ +bool FStreamer::writeInt8 (int8 c) +{ + return writeRaw ((void*)&c, sizeof (int8)) == sizeof (int8); +} + +//------------------------------------------------------------------------ +bool FStreamer::readInt8 (int8& c) +{ + return readRaw ((void*)&c, sizeof (int8)) == sizeof (int8); +} + +//------------------------------------------------------------------------ +bool FStreamer::writeInt8u (uint8 c) +{ + return writeRaw ((void*)&c, sizeof (uint8)) == sizeof (uint8); +} + +//------------------------------------------------------------------------ +bool FStreamer::readInt8u (uint8& c) +{ + return readRaw ((void*)&c, sizeof (uint8)) == sizeof (uint8); +} + // int16 ----------------------------------------------------------------- //------------------------------------------------------------------------ bool FStreamer::writeInt16 (int16 i) @@ -563,6 +587,9 @@ TSize FStreamer::writeString8 (const char8* ptr, bool terminate) //------------------------------------------------------------------------ TSize FStreamer::readString8 (char8* ptr, TSize size) { + if (size < 1 || ptr == nullptr) + return 0; + TSize i = 0; char8 c = 0; while (i < size) @@ -570,18 +597,19 @@ TSize FStreamer::readString8 (char8* ptr, TSize size) if (readRaw ((void*)&c, sizeof (char)) != sizeof (char)) break; ptr[i] = c; - i++; if (c == '\n' || c == '\0') break; + i++; } - if (c == '\n' && ptr[i - 2] == '\r') - ptr[i - 2] = 0; - if (i < size) - ptr[i] = 0; - else - ptr[size - 1] = 0; + // remove at end \n (LF) or \r\n (CR+LF) + if (c == '\n') + { + if (i > 0 && ptr[i - 1] == '\r') + i--; + } + ptr[i] = 0; - return strlen (ptr); + return i; } //------------------------------------------------------------------------ @@ -630,7 +658,7 @@ int32 FStreamer::readStringUtf8 (tchar* ptr, int32 nChars) break; } - char8* source = tmp.int8Ptr (); + char8* source = tmp.str8 (); uint32 codePage = kCP_Default; // for legacy take default page if no utf8 bom is present... if (tmp.getFillSize () > 2) { diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.h index 750e5e1..8b97bb6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstreamer.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -84,10 +84,10 @@ class FStreamer bool writeChar16 (char16 c); bool readChar16 (char16& c); - bool writeInt8 (int8 c){return writeChar8 (c);} - bool readInt8 (int8& c){return readChar8 (c);} - bool writeInt8u (uint8 c){return writeUChar8 (c);} - bool readInt8u (uint8& c){return readUChar8 (c);} + bool writeInt8 (int8 c); + bool readInt8 (int8& c); + bool writeInt8u (uint8 c); + bool readInt8u (uint8& c); ///@} /** @name read and write int16. */ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.cpp index 7fae6b0..907baa7 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.cpp @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,8 +45,15 @@ #include #include #include +#include +#include +#include +#include #if SMTG_OS_WINDOWS +#ifndef NOMINMAX +#define NOMINMAX +#endif #include #ifdef _MSC_VER #pragma warning (disable : 4244) @@ -202,34 +209,32 @@ static bool fromCFStringRef (Steinberg::char8* dest, Steinberg::int32 destSize, #endif // SMTG_OS_MACOS #if SMTG_OS_WINDOWS -#define stricmp16 wcsicmp -#define strnicmp16 wcsnicmp -#define strrchr16 wcsrchr -#define sprintf16 swprintf -#define snprintf16 snwprintf -#define vsnprintf16 vsnwprintf -#define vsprintf16 wvsprintf -#define vfprintf16 vfwprintf -#define sscanf16 swscanf -#define toupper16 towupper -#define tolower16 towlower -#define isupper16 iswupper -#define islower16 iswlower -#define isspace16 iswspace -#define isalpha16 iswalpha -#define isdigit16 iswdigit -#define isalnum16 iswalnum - -#define stricmp _stricmp -#define strnicmp _strnicmp -#define snprintf _snprintf -#define vsnprintf _vsnprintf -#define snwprintf _snwprintf -#define vsnwprintf _vsnwprintf - -#define wtoi _wtoi -#define wtol _wtol -#define wtof _wtof +//----------------------------------------------------------------------------- +static inline int stricmp16 (const Steinberg::tchar* s1, const Steinberg::tchar* s2) +{ + return wcsicmp (Steinberg::wscast (s1), Steinberg::wscast (s2)); +} + +//----------------------------------------------------------------------------- +static inline int strnicmp16 (const Steinberg::tchar* s1, const Steinberg::tchar* s2, size_t l) +{ + return wcsnicmp (Steinberg::wscast (s1), Steinberg::wscast (s2), l); +} + +//----------------------------------------------------------------------------- +static inline int vsnwprintf (Steinberg::char16* buffer, size_t bufferSize, + const Steinberg::char16* format, va_list args) +{ + return _vsnwprintf (Steinberg::wscast (buffer), bufferSize, Steinberg::wscast (format), args); +} + +//----------------------------------------------------------------------------- +static inline Steinberg::int32 sprintf16 (Steinberg::char16* str, const Steinberg::char16* format, ...) +{ + va_list marker; + va_start (marker, format); + return vsnwprintf (str, -1, format, marker); +} #elif SMTG_OS_LINUX #include @@ -288,7 +293,7 @@ static inline int strnicmp16 (const Steinberg::char16* s1, const Steinberg::char //----------------------------------------------------------------------------- static inline int sprintf16 (Steinberg::char16* wcs, const Steinberg::char16* format, ...) { - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); return 0; } @@ -311,7 +316,7 @@ static inline int vsnwprintf (Steinberg::char16* wcs, size_t maxlen, //----------------------------------------------------------------------------- static inline Steinberg::char16* strrchr16 (const Steinberg::char16* str, Steinberg::char16 c) { - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); return nullptr; } @@ -533,6 +538,9 @@ bool ConstString::testChar16 (uint32 index, char16 c) const //----------------------------------------------------------------------------- bool ConstString::extract (String& result, uint32 idx, int32 n) const { + // AddressSanitizer : when extracting part of "this" on itself, it can lead to heap-use-after-free. + SMTG_ASSERT (this != static_cast (&result)) + if (len == 0|| idx >= len) return false; @@ -659,7 +667,7 @@ int32 ConstString::compare (const ConstString& str, int32 n, CompareMode mode) c return 0; return 1; } - else if (isEmpty ()) + if (isEmpty ()) return -1; if (!isWide && !str.isWide) @@ -668,33 +676,23 @@ int32 ConstString::compare (const ConstString& str, int32 n, CompareMode mode) c { if (isCaseSensitive (mode)) return strcmp (*this, str); - else - return stricmp (*this, str); - } - else - { - if (isCaseSensitive (mode)) - return strncmp (*this, str, n); - else - return strnicmp (*this, str, n); + return stricmp (*this, str); } + if (isCaseSensitive (mode)) + return strncmp (*this, str, n); + return strnicmp (*this, str, n); } - else if (isWide && str.isWide) + if (isWide && str.isWide) { if (n < 0) { if (isCaseSensitive (mode)) return strcmp16 (*this, str); - else - return stricmp16 (*this, str); - } - else - { - if (isCaseSensitive (mode)) - return strncmp16 (*this, str, n); - else - return strnicmp16 (*this, str, n); + return stricmp16 (*this, str); } + if (isCaseSensitive (mode)) + return strncmp16 (*this, str, n); + return strnicmp16 (*this, str, n); } return compareAt (0, str, n, mode); } @@ -717,7 +715,7 @@ int32 ConstString::compareAt (uint32 index, const ConstString& str, int32 n, Com return 0; return 1; } - else if (isEmpty ()) + if (isEmpty ()) return -1; if (!isWide && !str.isWide) @@ -738,18 +736,13 @@ int32 ConstString::compareAt (uint32 index, const ConstString& str, int32 n, Com { if (isCaseSensitive (mode)) return strcmp (toCompare, str); - else - return stricmp (toCompare, str); - } - else - { - if (isCaseSensitive (mode)) - return strncmp (toCompare, str, n); - else - return strnicmp (toCompare, str, n); + return stricmp (toCompare, str); } + if (isCaseSensitive (mode)) + return strncmp (toCompare, str, n); + return strnicmp (toCompare, str, n); } - else if (isWide && str.isWide) + if (isWide && str.isWide) { char16* toCompare = buffer16; if (index > 0) @@ -767,34 +760,25 @@ int32 ConstString::compareAt (uint32 index, const ConstString& str, int32 n, Com { if (isCaseSensitive (mode)) return strcmp16 (toCompare, str.text16 ()); - else - return stricmp16 (toCompare, str.text16 ()); - } - else - { - if (isCaseSensitive (mode)) - return strncmp16 (toCompare, str.text16 (), n); - else - return strnicmp16 (toCompare, str.text16 (), n); + return stricmp16 (toCompare, str.text16 ()); } + if (isCaseSensitive (mode)) + return strncmp16 (toCompare, str.text16 (), n); + return strnicmp16 (toCompare, str.text16 (), n); } - else + + if (isWide) { - if (isWide) - { - String tmp (str.text8 ()); - if (tmp.toWideString () == false) - return -1; - return compareAt (index, tmp, n, mode); - } - else - { - String tmp (text8 ()); - if (tmp.toWideString () == false) - return 1; - return tmp.compareAt (index, str, n, mode); - } + String tmp (str.text8 ()); + if (tmp.toWideString () == false) + return -1; + return compareAt (index, tmp, n, mode); } + + String tmp (text8 ()); + if (tmp.toWideString () == false) + return 1; + return tmp.compareAt (index, str, n, mode); } //------------------------------------------------------------------------ @@ -806,28 +790,23 @@ Steinberg::int32 ConstString::naturalCompare (const ConstString& str, CompareMod return 0; return 1; } - else if (isEmpty ()) + if (isEmpty ()) return -1; if (!isWide && !str.isWide) return strnatcmp8 (buffer8, str.text8 (), isCaseSensitive (mode)); - else if (isWide && str.isWide) + if (isWide && str.isWide) return strnatcmp16 (buffer16, str.text16 (), isCaseSensitive (mode)); - else + + if (isWide) { - if (isWide) - { - String tmp (str.text8 ()); - tmp.toWideString (); - return strnatcmp16 (buffer16, tmp.text16 (), isCaseSensitive (mode)); - } - else - { - String tmp (text8 ()); - tmp.toWideString (); - return strnatcmp16 (tmp.text16 (), str.text16 (), isCaseSensitive (mode)); - } + String tmp (str.text8 ()); + tmp.toWideString (); + return strnatcmp16 (buffer16, tmp.text16 (), isCaseSensitive (mode)); } + String tmp (text8 ()); + tmp.toWideString (); + return strnatcmp16 (tmp.text16 (), str.text16 (), isCaseSensitive (mode)); } //----------------------------------------------------------------------------- @@ -837,7 +816,7 @@ bool ConstString::startsWith (const ConstString& str, CompareMode mode /*= kCase { return isEmpty (); } - else if (isEmpty ()) + if (isEmpty ()) { return false; } @@ -851,13 +830,13 @@ bool ConstString::startsWith (const ConstString& str, CompareMode mode /*= kCase return strncmp (buffer8, str.buffer8, str.length ()) == 0; return strnicmp (buffer8, str.buffer8, str.length ()) == 0; } - else if (isWide && str.isWide) + if (isWide && str.isWide) { if (isCaseSensitive (mode)) return strncmp16 (buffer16, str.buffer16, str.length ()) == 0; return strnicmp16 (buffer16, str.buffer16, str.length ()) == 0; } - else if (isWide) + if (isWide) { String tmp (str.text8 ()); tmp.toWideString (); @@ -867,16 +846,13 @@ bool ConstString::startsWith (const ConstString& str, CompareMode mode /*= kCase return strncmp16 (buffer16, tmp.buffer16, tmp.length ()) == 0; return strnicmp16 (buffer16, tmp.buffer16, tmp.length ()) == 0; } - else - { - String tmp (text8 ()); - tmp.toWideString (); - if (str.length () > tmp.length ()) - return false; - if (isCaseSensitive (mode)) - return strncmp16 (tmp.buffer16, str.buffer16, str.length ()) == 0; - return strnicmp16 (tmp.buffer16, str.buffer16, str.length ()) == 0; - } + String tmp (text8 ()); + tmp.toWideString (); + if (str.length () > tmp.length ()) + return false; + if (isCaseSensitive (mode)) + return strncmp16 (tmp.buffer16, str.buffer16, str.length ()) == 0; + return strnicmp16 (tmp.buffer16, str.buffer16, str.length ()) == 0; } //----------------------------------------------------------------------------- @@ -886,7 +862,7 @@ bool ConstString::endsWith (const ConstString& str, CompareMode mode /*= kCaseSe { return isEmpty (); } - else if (isEmpty ()) + if (isEmpty ()) { return false; } @@ -900,13 +876,13 @@ bool ConstString::endsWith (const ConstString& str, CompareMode mode /*= kCaseSe return strncmp (buffer8 + (length () - str.length ()), str.buffer8, str.length ()) == 0; return strnicmp (buffer8 + (length () - str.length ()), str.buffer8, str.length ()) == 0; } - else if (isWide && str.isWide) + if (isWide && str.isWide) { if (isCaseSensitive (mode)) return strncmp16 (buffer16 + (length () - str.length ()), str.buffer16, str.length ()) == 0; return strnicmp16 (buffer16 + (length () - str.length ()), str.buffer16, str.length ()) == 0; } - else if (isWide) + if (isWide) { String tmp (str.text8 ()); tmp.toWideString (); @@ -916,16 +892,13 @@ bool ConstString::endsWith (const ConstString& str, CompareMode mode /*= kCaseSe return strncmp16 (buffer16 + (length () - tmp.length ()), tmp.buffer16, tmp.length ()) == 0; return strnicmp16 (buffer16 + (length () - tmp.length ()), tmp.buffer16, tmp.length ()) == 0; } - else - { - String tmp (text8 ()); - tmp.toWideString (); - if (str.length () > tmp.length ()) - return false; - if (isCaseSensitive (mode)) - return strncmp16 (tmp.buffer16 + (tmp.length () - str.length ()), str.buffer16, str.length ()) == 0; - return strnicmp16 (tmp.buffer16 + (tmp.length () - str.length ()), str.buffer16, str.length ()) == 0; - } + String tmp (text8 ()); + tmp.toWideString (); + if (str.length () > tmp.length ()) + return false; + if (isCaseSensitive (mode)) + return strncmp16 (tmp.buffer16 + (tmp.length () - str.length ()), str.buffer16, str.length ()) == 0; + return strnicmp16 (tmp.buffer16 + (tmp.length () - str.length ()), str.buffer16, str.length ()) == 0; } //----------------------------------------------------------------------------- @@ -968,7 +941,7 @@ int32 ConstString::findNext (int32 startIndex, const ConstString& str, int32 n, } return -1; } - else if (!isWide && !str.isWide) + if (!isWide && !str.isWide) { uint32 stringLength = str.length (); n = n < 0 ? stringLength : Min (n, stringLength); @@ -1199,7 +1172,7 @@ int32 ConstString::findPrev (int32 startIndex, const ConstString& str, int32 n, } return -1; } - else if (!isWide && !str.isWide) + if (!isWide && !str.isWide) { uint32 stringLength = str.length (); n = n < 0 ? stringLength : Min (n, stringLength); @@ -1305,13 +1278,11 @@ int32 ConstString::getFirstDifferent (const ConstString& str, CompareMode mode) return -1; return getFirstDifferent (tmp, mode); } - else - { - String tmp (text8 ()); - if (tmp.toWideString () == false) - return -1; - return tmp.getFirstDifferent (str, mode); - } + + String tmp (text8 ()); + if (tmp.toWideString () == false) + return -1; + return tmp.getFirstDifferent (str, mode); } uint32 len1 = len; @@ -1367,8 +1338,7 @@ bool ConstString::scanInt64 (int64& value, uint32 offset, bool scanToEnd) const if (isWide) return scanInt64_16 (buffer16 + offset, value, scanToEnd); - else - return scanInt64_8 (buffer8 + offset, value, scanToEnd); + return scanInt64_8 (buffer8 + offset, value, scanToEnd); } //----------------------------------------------------------------------------- @@ -1379,8 +1349,7 @@ bool ConstString::scanUInt64 (uint64& value, uint32 offset, bool scanToEnd) cons if (isWide) return scanUInt64_16 (buffer16 + offset, value, scanToEnd); - else - return scanUInt64_8 (buffer8 + offset, value, scanToEnd); + return scanUInt64_8 (buffer8 + offset, value, scanToEnd); } //----------------------------------------------------------------------------- @@ -1391,8 +1360,7 @@ bool ConstString::scanHex (uint8& value, uint32 offset, bool scanToEnd) const if (isWide) return scanHex_16 (buffer16 + offset, value, scanToEnd); - else - return scanHex_8 (buffer8 + offset, value, scanToEnd); + return scanHex_8 (buffer8 + offset, value, scanToEnd); } //----------------------------------------------------------------------------- @@ -1403,8 +1371,7 @@ bool ConstString::scanInt32 (int32& value, uint32 offset, bool scanToEnd) const if (isWide) return scanInt32_16 (buffer16 + offset, value, scanToEnd); - else - return scanInt32_8 (buffer8 + offset, value, scanToEnd); + return scanInt32_8 (buffer8 + offset, value, scanToEnd); } //----------------------------------------------------------------------------- @@ -1415,8 +1382,7 @@ bool ConstString::scanUInt32 (uint32& value, uint32 offset, bool scanToEnd) cons if (isWide) return scanUInt32_16 (buffer16 + offset, value, scanToEnd); - else - return scanUInt32_8 (buffer8 + offset, value, scanToEnd); + return scanUInt32_8 (buffer8 + offset, value, scanToEnd); } //----------------------------------------------------------------------------- @@ -1426,7 +1392,7 @@ bool ConstString::scanInt64_8 (const char8* text, int64& value, bool scanToEnd) { if (sscanf (text, "%" FORMAT_INT64A, &value) == 1) return true; - else if (scanToEnd == false) + if (scanToEnd == false) return false; text++; } @@ -1452,7 +1418,7 @@ bool ConstString::scanUInt64_8 (const char8* text, uint64& value, bool scanToEnd { if (sscanf (text, "%" FORMAT_UINT64A, &value) == 1) return true; - else if (scanToEnd == false) + if (scanToEnd == false) return false; text++; } @@ -1502,7 +1468,7 @@ bool ConstString::scanHex_8 (const char8* text, uint8& value, bool scanToEnd) value = (uint8)v; return true; } - else if (scanToEnd == false) + if (scanToEnd == false) return false; text++; } @@ -1557,7 +1523,7 @@ bool ConstString::scanFloat (double& value, uint32 offset, bool scanToEnd) const { if (sscanf (txt, "%lf", &value) == 1) return true; - else if (scanToEnd == false) + if (scanToEnd == false) return false; txt++; } @@ -1585,7 +1551,7 @@ char16 ConstString::toLower (char16 c) } return c; #elif SMTG_OS_LINUX - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); return c; #else return towlower (c); @@ -1613,7 +1579,7 @@ char16 ConstString::toUpper (char16 c) } return c; #elif SMTG_OS_LINUX - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); return c; #else return towupper (c); @@ -1759,8 +1725,7 @@ bool ConstString::isDigit (uint32 index) const if (isWide) return ConstString::isCharDigit (buffer16[index]); - else - return ConstString::isCharDigit (buffer8[index]); + return ConstString::isCharDigit (buffer8[index]); } //----------------------------------------------------------------------------- @@ -1867,7 +1832,7 @@ int32 ConstString::multiByteToWideString (char16* dest, const char8* source, int } int32 result = 0; #if SMTG_OS_WINDOWS - result = MultiByteToWideChar (sourceCodePage, MB_ERR_INVALID_CHARS, source, -1, dest, charCount); + result = MultiByteToWideChar (sourceCodePage, MB_ERR_INVALID_CHARS, source, -1, wscast (dest), charCount); #endif #if SMTG_OS_MACOS @@ -1911,7 +1876,7 @@ int32 ConstString::multiByteToWideString (char16* dest, const char8* source, int } else { - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); } #endif @@ -1924,7 +1889,7 @@ int32 ConstString::multiByteToWideString (char16* dest, const char8* source, int int32 ConstString::wideStringToMultiByte (char8* dest, const char16* wideString, int32 charCount, uint32 destCodePage) { #if SMTG_OS_WINDOWS - return WideCharToMultiByte (destCodePage, 0, wideString, -1, dest, charCount, nullptr, nullptr); + return WideCharToMultiByte (destCodePage, 0, wscast (wideString), -1, dest, charCount, nullptr, nullptr); #elif SMTG_OS_MACOS int32 result = 0; @@ -1991,13 +1956,12 @@ int32 ConstString::wideStringToMultiByte (char8* dest, const char16* wideString, } else { - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); } return result; #else -#warning DEPRECATED No Linux implementation - assert(false && "DEPRECATED No Linux implementation"); + assert (false && "DEPRECATED No Linux implementation"); return 0; #endif @@ -2013,7 +1977,7 @@ bool ConstString::isNormalized (UnicodeNormalization n) #ifdef UNICODE if (n != kUnicodeNormC) return false; - uint32 normCharCount = static_cast (FoldString (MAP_PRECOMPOSED, buffer16, len, nullptr, 0)); + uint32 normCharCount = static_cast (FoldString (MAP_PRECOMPOSED, wscast (buffer16), len, nullptr, 0)); return (normCharCount == len); #else return false; @@ -2043,11 +2007,27 @@ String::String () //----------------------------------------------------------------------------- String::String (const char8* str, MBCodePage codePage, int32 n, bool isTerminated) { - isWide = 0; + isWide = false; if (str) { - assign (str, n, isTerminated); - toWideString (codePage); + if (isTerminated && n >= 0 && str[n] != 0) + { + // isTerminated is not always set correctly + isTerminated = false; + } + + if (!isTerminated) + { + assign (str, n, isTerminated); + toWideString (codePage); + } + else + { + if (n < 0) + n = static_cast (strlen (str)); + if (n > 0) + _toWideString (str, n, codePage); + } } } @@ -2139,22 +2119,33 @@ void String::updateLength () //----------------------------------------------------------------------------- bool String::toWideString (uint32 sourceCodePage) +{ + if (!isWide && buffer8 && len > 0) + return _toWideString (buffer8, len, sourceCodePage); + isWide = true; + return true; +} + +//----------------------------------------------------------------------------- +bool String::_toWideString (const char8* src, int32 length, uint32 sourceCodePage) { if (!isWide) { - if (buffer8 && len > 0) + if (src && length > 0) { - int32 bytesNeeded = multiByteToWideString (nullptr, buffer8, 0, sourceCodePage) * sizeof (char16); + int32 bytesNeeded = multiByteToWideString (nullptr, src, 0, sourceCodePage) * sizeof (char16); if (bytesNeeded) { bytesNeeded += sizeof (char16); - char16* newStr = (char16*) malloc (bytesNeeded); - if (multiByteToWideString (newStr, buffer8, len + 1, sourceCodePage) <= 0) + char16* newStr = (char16*)malloc (bytesNeeded); + if (multiByteToWideString (newStr, src, length + 1, sourceCodePage) < 0) { free (newStr); return false; } - free (buffer8); + if (buffer8) + free (buffer8); + buffer16 = newStr; isWide = true; updateLength (); @@ -2254,8 +2245,8 @@ bool String::toMultiByte (uint32 destCodePage) //----------------------------------------------------------------------------- void String::fromUTF8 (const char8* utf8String) { - assign (utf8String); - toWideString (kCP_Utf8); + resize (0, false); + _toWideString (utf8String, static_cast (strlen (utf8String)), kCP_Utf8); } //----------------------------------------------------------------------------- @@ -2272,12 +2263,12 @@ bool String::normalize (UnicodeNormalization n) if (n != kUnicodeNormC) return false; - uint32 normCharCount = static_cast (FoldString (MAP_PRECOMPOSED, buffer16, len, nullptr, 0)); + uint32 normCharCount = static_cast (FoldString (MAP_PRECOMPOSED, wscast (buffer16), len, nullptr, 0)); if (normCharCount == len) return true; char16* newString = (char16*)malloc ((normCharCount + 1) * sizeof (char16)); - uint32 converterCount = static_cast (FoldString (MAP_PRECOMPOSED, buffer16, len, newString, normCharCount + 1)); + uint32 converterCount = static_cast (FoldString (MAP_PRECOMPOSED, wscast (buffer16), len, wscast (newString), normCharCount + 1)); if (converterCount != normCharCount) { free (newString); @@ -2410,12 +2401,10 @@ bool String::setChar8 (uint32 index, char8 c) len = index; return true; } - else - { - if (resize (index + 1, isWide, true) == false) - return false; - len = index + 1; - } + + if (resize (index + 1, isWide, true) == false) + return false; + len = index + 1; } if (index < len && buffer) @@ -2462,12 +2451,9 @@ bool String::setChar16 (uint32 index, char16 c) len = index; return true; } - else - { - if (resize (index + 1, isWide, true) == false) - return false; - len = index + 1; - } + if (resize (index + 1, isWide, true) == false) + return false; + len = index + 1; } if (index < len && buffer) @@ -2501,8 +2487,7 @@ String& String::assign (const ConstString& str, int32 n) { if (str.isWideString ()) return assign (str.text16 (), n < 0 ? str.length () : n); - else - return assign (str.text8 (), n < 0 ? str.length () : n); + return assign (str.text8 (), n < 0 ? str.length () : n); } //----------------------------------------------------------------------------- @@ -2598,8 +2583,7 @@ String& String::append (const ConstString& str, int32 n) { if (str.isWideString ()) return append (str.text16 (), n); - else - return append (str.text8 (), n); + return append (str.text8 (), n); } //----------------------------------------------------------------------------- @@ -2683,7 +2667,7 @@ String& String::append (const char8 c, int32 n) { return append (str, 1); } - else if (n > 1) + if (n > 1) { if (isWide) { @@ -2717,7 +2701,7 @@ String& String::append (const char16 c, int32 n) char16 str[] = {c, 0}; return append (str, 1); } - else if (n > 1) + if (n > 1) { if (!isWide) { @@ -2746,8 +2730,7 @@ String& String::insertAt (uint32 idx, const ConstString& str, int32 n) { if (str.isWideString ()) return insertAt (idx, str.text16 (), n); - else - return insertAt (idx, str.text8 (), n); + return insertAt (idx, str.text8 (), n); } //----------------------------------------------------------------------------- @@ -2825,8 +2808,7 @@ String& String::replace (uint32 idx, int32 n1, const ConstString& str, int32 n2) { if (str.isWideString ()) return replace (idx, n1, str.text16 (), n2); - else - return replace (idx, n1, str.text8 (), n2); + return replace (idx, n1, str.text8 (), n2); } // "replace" replaces n1 number of characters at the specified index with @@ -3369,55 +3351,44 @@ String& String::printInt64 (int64 value) } //----------------------------------------------------------------------------- -String& String::printFloat (double value) +String& String::printFloat (double value, uint32 maxPrecision) { + static constexpr auto kMaxAfterCommaResolution = 16; + // escape point for integer values, avoid unnecessary complexity later on + const bool withinInt64Boundaries = value <= std::numeric_limits::max () && value >= std::numeric_limits::lowest (); + if (withinInt64Boundaries && (maxPrecision == 0 || std::round (value) == value)) + return printInt64 (value); + + const auto absValue = std::abs (value); + const uint32 valueExponent = absValue >= 1 ? std::log10 (absValue) : -std::log10 (absValue) + 1; + + maxPrecision = std::min (kMaxAfterCommaResolution - valueExponent, maxPrecision); + if (isWide) - { - char16 string[kPrintfBufferSize]; - sprintf16 (string, STR16 ("%lf"), value); + printf (STR ("%s%dlf"), STR ("%."), maxPrecision); + else + printf ("%s%dlf", "%.", maxPrecision); - char16* pointPtr = strrchr16 (string, STR ('.')); - if (pointPtr) - { - pointPtr++; // keep 1st digit after point - int32 index = strlen16 (string) - 1; - char16 zero = STR16 ('0'); - while (pointPtr < (string + index)) - { - if (string[index] == zero) - { - string[index] = 0; - index--; - } - else - break; - } - } - return assign (string); - } + if (isWide) + printf (text16 (), value); else - { - char8 string[kPrintfBufferSize]; - sprintf (string, "%lf", value); + printf (text8 (), value); - char8* pointPtr = strrchr (string, '.'); - if (pointPtr) + // trim trail zeros + for (int32 i = length () - 1; i >= 0; i--) + { + if (isWide && testChar16 (i, '0') || testChar8 (i, '0')) + remove (i); + else if (isWide && testChar16(i,'.') || testChar8(i, '.')) { - pointPtr++; // keep 1st digit after point - int32 index = (int32) (strlen (string) - 1); - while (pointPtr < (string + index)) - { - if (string[index] == '0') - { - string[index] = 0; - index--; - } - else - break; - } + remove(i); + break; } - return assign (string); + else + break; } + + return *this; } //----------------------------------------------------------------------------- @@ -3461,17 +3432,19 @@ bool String::incrementTrailingNumber (uint32 width, tchar separator, uint32 minN } else { - char format[64]; - char trail[128]; + static constexpr auto kFormatSize = 64u; + static constexpr auto kTrailSize = 64u; + char format[kFormatSize]; + char trail[kTrailSize]; if (separator && isEmpty () == false) { - sprintf (format, "%%c%%0%uu", width); - sprintf (trail, format, separator, (uint32) number); + snprintf (format, kFormatSize, "%%c%%0%uu", width); + snprintf (trail, kTrailSize, format, separator, (uint32) number); } else { - sprintf (format, "%%0%uu", width); - sprintf (trail, format, (uint32) number); + snprintf (format, kFormatSize, "%%0%uu", width); + snprintf (trail, kTrailSize, format, (uint32) number); } append (trail); } @@ -3723,11 +3696,9 @@ unsigned char* String::toPascalString (unsigned char* buf) } return buf; } - else - { - *buf = 0; - return buf; - } + + *buf = 0; + return buf; } //----------------------------------------------------------------------------- @@ -3826,7 +3797,7 @@ void* ConstString::toCFStringRef (uint32 encoding, bool mutableCFString) const return (void*)CFStringCreateWithCString (kCFAllocator, "", encoding); } } - return 0; + return nullptr; } #endif @@ -3860,9 +3831,9 @@ template int32 tstrnatcmp (const T* s1, const T* s2, bool caseSensitiv { if (s1 == nullptr && s2 == nullptr) return 0; - else if (s1 == nullptr) + if (s1 == nullptr) return -1; - else if (s2 == nullptr) + if (s2 == nullptr) return 1; while (*s1 && *s2) @@ -3923,12 +3894,11 @@ template int32 tstrnatcmp (const T* s1, const T* s2, bool caseSensitiv if (*s1 == 0 && *s2 == 0) return 0; - else if (*s1 == 0) + if (*s1 == 0) return -1; - else if (*s2 == 0) + if (*s2 == 0) return 1; - else - return (int32)(*s1 - *s2); + return (int32)(*s1 - *s2); } //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.h index 80ff82a..7fc758a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/fstring.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -278,6 +278,11 @@ class ConstString bool isNormalized (UnicodeNormalization = kUnicodeNormC); ///< On PC only kUnicodeNormC is working +#if SMTG_OS_WINDOWS + ConstString (const wchar_t* str, int32 length = -1) : ConstString (wscast (str), length) {} + operator const wchar_t* () const { return wscast (text16 ());} +#endif + #if SMTG_OS_MACOS virtual void* toCFStringRef (uint32 encoding = 0xFFFF, bool mutableCFString = false) const; ///< CFString conversion #endif @@ -317,7 +322,7 @@ class String : public ConstString String (const ConstString& str, int32 n = -1); ///< assign n characters of str (-1: all) String (const FVariant& var); ///< assign from FVariant String (IString* str); ///< assign from IString - ~String (); + ~String () SMTG_OVERRIDE; #if SMTG_CPP11_STDLIBSUPPORT String (String&& str); @@ -416,7 +421,15 @@ class String : public ConstString // numbers----------------------------------------------------------------- String& printInt64 (int64 value); - String& printFloat (double value); + + /** + * @brief print a float into a string, trailing zeros will be trimmed + * @param value the floating value to be printed + * @param maxPrecision (optional) the max precision allowed for this, num of significant digits after the comma + * For instance printFloat (1.234, 2) => 1.23 + * @return the resulting string. + */ + String& printFloat (double value, uint32 maxPrecision = 6); /** Increment the trailing number if present else start with minNumber, width specifies the string width format (width 2 for number 3 is 03), applyOnlyFormat set to true will only format the string to the given width without incrementing the founded trailing number */ bool incrementTrailingNumber (uint32 width = 2, tchar separator = STR (' '), uint32 minNumber = 1, bool applyOnlyFormat = false); @@ -448,6 +461,11 @@ class String : public ConstString void fromUTF8 (const char8* utf8String); ///< Assigns from UTF8 string bool normalize (UnicodeNormalization = kUnicodeNormC); ///< On PC only kUnicodeNormC is working +#if SMTG_OS_WINDOWS + String (const wchar_t* str, int32 length = -1, bool isTerminated = true) : String (wscast (str), length, isTerminated) {} + String& operator= (const wchar_t* str) {return String::operator= (wscast (str)); } +#endif + #if SMTG_OS_MACOS virtual bool fromCFStringRef (const void*, uint32 encoding = 0xFFFF); ///< CFString conversion #endif @@ -458,6 +476,7 @@ class String : public ConstString bool resize (uint32 newSize, bool wide, bool fill = false); private: + bool _toWideString (const char8* src, int32 length, uint32 sourceCodePage = kCP_Default); void tryFreeBuffer (); bool checkToMultiByte (uint32 destCodePage = kCP_Default) const; // to remove debug code from inline - const_cast inside!!! }; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.cpp index 9213ebc..f4ac80c 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.cpp @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -56,7 +56,6 @@ using Steinberg::Base::Thread::FGuard; namespace Steinberg { DEF_CLASS_IID (IUpdateManager) -bool UpdateHandler::lockUpdates = false; namespace Update { const uint32 kHashSize = (1 << 8); // must be power of 2 (16 bytes * 256 == 4096) @@ -243,10 +242,17 @@ tresult PLUGIN_API UpdateHandler::addDependent (FUnknown* u, IDependent* _depend return kResultTrue; } - //------------------------------------------------------------------------ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* dependent) { + size_t eraseCount; + return removeDependent (u, dependent, eraseCount); +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* dependent, size_t& eraseCount) +{ + eraseCount = 0; IPtr unknown = Update::getUnknownBase (u); if (unknown == nullptr && dependent == nullptr) return kResultFalse; @@ -287,13 +293,16 @@ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* depe if ((*iterList) == dependent) #endif { + eraseCount = list.size (); if (list.size () == 1u) { listIsEmpty = true; break; } else + { iterList = list.erase (iterList); + } } else { @@ -322,11 +331,11 @@ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* depe { if (dependent == nullptr) // Remove all dependents of object { + eraseCount = iterList->second.size (); map.erase (iterList); } else // Remove one dependent { - int32 eraseCount = 0; Update::DependentList& dependentlist = (*iterList).second; Update::DependentListIter iterDependentlist = dependentlist.begin (); while (iterDependentlist != dependentlist.end ()) @@ -363,8 +372,6 @@ tresult PLUGIN_API UpdateHandler::removeDependent (FUnknown* u, IDependent* depe //------------------------------------------------------------------------ tresult UpdateHandler::doTriggerUpdates (FUnknown* u, int32 message, bool suppressUpdateDone) { - if (lockUpdates) - return kResultFalse; IPtr unknown = Update::getUnknownBase (u); if (!unknown) return kResultFalse; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.h index 538022e..e85a9bb 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/source/updatehandler.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -78,18 +78,22 @@ class UpdateHandler : public FObject, public IUpdateHandler, public IUpdateManag public: //------------------------------------------------------------------------------ UpdateHandler (); - ~UpdateHandler (); + ~UpdateHandler () SMTG_OVERRIDE; using FObject::addDependent; using FObject::removeDependent; using FObject::deferUpdate; // IUpdateHandler +//private: + friend class FObject; /** register \param dependent to get messages from \param object */ tresult PLUGIN_API addDependent (FUnknown* object, IDependent* dependent) SMTG_OVERRIDE; /** unregister \param dependent to get no messages from \param object */ + tresult PLUGIN_API removeDependent (FUnknown* object, IDependent* dependent, size_t& earseCount); tresult PLUGIN_API removeDependent (FUnknown* object, IDependent* dependent) SMTG_OVERRIDE; +public: /** send \param message to all dependents of \param object immediately */ tresult PLUGIN_API triggerUpdates (FUnknown* object, int32 message) SMTG_OVERRIDE; /** send \param message to all dependents of \param object when idle */ @@ -103,15 +107,24 @@ class UpdateHandler : public FObject, public IUpdateHandler, public IUpdateManag /// @cond ignore // obsolete functions kept for compatibility - void checkUpdates (FObject* object = nullptr) { triggerDeferedUpdates (object->unknownCast ()); } - void flushUpdates (FObject* object) { cancelUpdates (object->unknownCast ()); } + void checkUpdates (FObject* object = nullptr) + { + triggerDeferedUpdates (object ? object->unknownCast () : nullptr); + } + void flushUpdates (FObject* object) + { + if (object) + cancelUpdates (object->unknownCast ()); + } void deferUpdate (FObject* object, int32 message) { - deferUpdates (object->unknownCast (), message); + if (object) + deferUpdates (object->unknownCast (), message); } void signalChange (FObject* object, int32 message, bool suppressUpdateDone = false) { - doTriggerUpdates (object->unknownCast (), message, suppressUpdateDone); + if (object) + doTriggerUpdates (object->unknownCast (), message, suppressUpdateDone); } #if DEVELOPMENT bool checkDeferred (FUnknown* object); @@ -130,8 +143,6 @@ class UpdateHandler : public FObject, public IUpdateHandler, public IUpdateManag Steinberg::Base::Thread::FLock lock; Update::Table* table = nullptr; - friend struct LockUpdateDependencies; - static bool lockUpdates; }; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/include/flock.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/include/flock.h index bc45250..2ef35ba 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/include/flock.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/include/flock.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -99,7 +99,7 @@ class FLock : public ILock FLock (const char8* name = "FLock"); /** Lock destructor. */ - ~FLock (); + ~FLock () SMTG_OVERRIDE; //-- ILock ----------------------------------------------------------- void lock () SMTG_OVERRIDE; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/source/flock.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/source/flock.cpp index 8135cb6..e7c154b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/source/flock.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/base/thread/source/flock.cpp @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/helper.manifest b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/helper.manifest new file mode 100644 index 0000000..f4be0f3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/helper.manifest @@ -0,0 +1,10 @@ + + + + + + UTF-8 + + + + diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/LICENSE.txt b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/LICENSE.txt index 5d521d2..6daa072 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/LICENSE.txt +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/LICENSE.txt @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- This license applies only to files referencing this license, for other files of the Software Development Kit the respective embedded license text @@ -38,4 +38,7 @@ OF THE POSSIBILITY OF SUCH DAMAGE. b) General Public License (GPL) Version 3 Details of these licenses can be found at: www.gnu.org/licenses/gpl-3.0.html +Please refer to the Steinberg VST usage guidelines for the use of VST, VST logo and VST +compatible logos: +https://steinbergmedia.github.io/vst3_dev_portal/pages/VST+3+Licensing/Usage+guidelines.html //---------------------------------------------------------------------------------- diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/README.md b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/README.md index b4f9580..8a864b6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/README.md +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/README.md @@ -1,10 +1,10 @@ # Welcome to VST 3 SDK Interfaces -Here are located all VST interfaces definitions (including VST Component/Controller, UI, Test). +Here are located all **VST 3** interfaces definitions (including VST Component/Controller, UI, Test). ## License & Usage guidelines More details are found at [www.steinberg.net/sdklicenses_vst3](http://www.steinberg.net/sdklicenses_vst3) ---- -Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk) \ No newline at end of file +Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.cpp index 4891cef..29e453a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.cpp @@ -44,7 +44,7 @@ const char16* ConstStringTable::getString (const char8* str) const } //---------------------------------------------------------------------------- -const char16 ConstStringTable::getString (const char8 str) const +char16 ConstStringTable::getString (const char8 str) const { std::map::iterator iter = charMap->find (str); if (iter != charMap->end ()) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.h index e19cae6..f945022 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/conststringtable.h @@ -32,7 +32,7 @@ class ConstStringTable /** Returns a char16 string of a ASCII string literal*/ const char16* getString (const char8* str) const; /** Returns a char16 character of a ASCII character */ - const char16 getString (const char8 str) const; + char16 getString (const char8 str) const; protected: ConstStringTable (); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/falignpush.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/falignpush.h index 5ff11f2..49eb573 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/falignpush.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/falignpush.h @@ -26,6 +26,10 @@ #elif defined __BORLANDC__ #pragma -a8 #elif SMTG_OS_WINDOWS + //! @brief warning C4103: alignment changed after including header, may be due to missing #pragma pack(pop) + #ifdef _MSC_VER + #pragma warning(disable : 4103) + #endif #pragma pack(push) #if SMTG_PLATFORM_64 #pragma pack(16) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fplatform.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fplatform.h index dea70d0..7b4a78d 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fplatform.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fplatform.h @@ -16,6 +16,7 @@ #pragma once +// values for BYTEORDER according to the used platform #define kLittleEndian 0 #define kBigEndian 1 @@ -38,21 +39,39 @@ //----------------------------------------------------------------------------- #if defined (_WIN32) //----------------------------------------------------------------------------- - // ARM32 AND ARM64 (WINDOWS) - #if (defined(_M_ARM64) || defined(_M_ARM)) - #define SMTG_OS_WINDOWS_ARM 1 - #endif - #define SMTG_OS_LINUX 0 #define SMTG_OS_MACOS 0 #define SMTG_OS_WINDOWS 1 #define SMTG_OS_IOS 0 #define SMTG_OS_OSX 0 - #define SMTG_CPU_X86 _M_IX86 - #define SMTG_CPU_X86_64 _M_AMD64 - #define SMTG_CPU_ARM (_M_ARM && !_M_ARM64) - #define SMTG_CPU_ARM_64 _M_ARM64 + #if defined(_M_IX86) + #define SMTG_CPU_X86 1 + #else + #define SMTG_CPU_X86 0 + #endif + #if defined(_M_AMD64) + #define SMTG_CPU_X86_64 1 + #else + #define SMTG_CPU_X86_64 0 + #endif + #if defined(_M_ARM) + #define SMTG_CPU_ARM 1 + #else + #define SMTG_CPU_ARM 0 + #endif + #if defined(_M_ARM64) + #define SMTG_CPU_ARM_64 1 + #else + #define SMTG_CPU_ARM_64 0 + #endif + #if defined(_M_ARM64EC) + #define SMTG_CPU_ARM_64EC 1 + #else + #define SMTG_CPU_ARM_64EC 0 + #endif + + #define SMTG_OS_WINDOWS_ARM (SMTG_CPU_ARM_64EC || SMTG_CPU_ARM_64 || SMTG_CPU_ARM) #define BYTEORDER kLittleEndian @@ -61,18 +80,19 @@ #define SMTG_PTHREADS 0 #define SMTG_EXPORT_SYMBOL __declspec (dllexport) + #define SMTG_HIDDEN_SYMBOL #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #ifdef _MSC_VER - #pragma warning (disable : 4244) // Conversion from 'type1' to 'type2', possible loss of data. - #pragma warning (disable : 4250) // Inheritance via dominance is allowed - #pragma warning (disable : 4996) // deprecated functions + #pragma warning (disable : 4244) //! @brief warning C4244: Conversion from 'type1' to 'type2', possible loss of data. + #pragma warning (disable : 4250) //! @brief warning C4250: Inheritance via dominance is allowed + #pragma warning (disable : 4996) //! @brief warning C4996: deprecated functions - #pragma warning (3 : 4189) // local variable is initialized but not referenced - #pragma warning (3 : 4238) // nonstandard extension used : class rvalue used as lvalue + #pragma warning (3 : 4189) //! @brief warning C4189: local variable is initialized but not referenced + #pragma warning (3 : 4238) //! @brief warning C4238: nonstandard extension used : class rvalue used as lvalue #endif #if defined (_WIN64) || defined (_M_ARM64) @@ -86,26 +106,49 @@ #endif #ifdef __cplusplus - #define SMTG_CPP11 __cplusplus >= 201103L || _MSC_VER > 1600 || SMTG_INTEL_CXX11_MODE + #define SMTG_CPP11 (__cplusplus >= 201103L || _MSC_VER > 1600 || SMTG_INTEL_CXX11_MODE) #define SMTG_CPP11_STDLIBSUPPORT SMTG_CPP11 - #define SMTG_HAS_NOEXCEPT _MSC_VER >= 1900 || (SMTG_INTEL_CXX11_MODE && SMTG_INTEL_COMPILER >= 1300) - #endif + #define SMTG_CPP14 (__cplusplus >= 201402L || ((_MSC_FULL_VER >= 190024210L) && (_MSVC_LANG >= 201402L))) + #define SMTG_CPP17 (__cplusplus >= 201703L || ((_MSC_FULL_VER >= 190024210L) && (_MSVC_LANG >= 201703L))) + #define SMTG_CPP20 (__cplusplus >= 202002L) + #define SMTG_HAS_NOEXCEPT ((_MSC_FULL_VER >= 190023026L) || (SMTG_INTEL_CXX11_MODE && SMTG_INTEL_COMPILER >= 1300)) + #if ((_MSC_FULL_VER >= 190024210L) || (SMTG_INTEL_CXX11_MODE && SMTG_INTEL_COMPILER >= 1500) || (defined(__MINGW32__) && SMTG_CPP11)) + #define SMTG_HAS_CPP11_CONSTEXPR 1 + #else + #define SMTG_HAS_CPP11_CONSTEXPR 0 + #endif + #if (((_MSC_VER >= 1915L) && (_MSVC_LANG >= 201402L)) || (SMTG_INTEL_CXX11_MODE && SMTG_INTEL_COMPILER > 1700) || (defined(__MINGW32__) && SMTG_CPP14)) + #define SMTG_HAS_CPP14_CONSTEXPR 1 + #else + #define SMTG_HAS_CPP14_CONSTEXPR 0 + #endif + #endif //__cplusplus #define SMTG_DEPRECATED_ATTRIBUTE(message) __declspec (deprecated ("Is Deprecated: " message)) //----------------------------------------------------------------------------- // LINUX //----------------------------------------------------------------------------- #elif __gnu_linux__ || __linux__ - #define SMTG_OS_LINUX 1 - #define SMTG_OS_MACOS 0 - #define SMTG_OS_WINDOWS 0 - #define SMTG_OS_IOS 0 - #define SMTG_OS_OSX 0 + #define SMTG_OS_LINUX 1 + #define SMTG_OS_MACOS 0 + #define SMTG_OS_WINDOWS 0 + #define SMTG_OS_WINDOWS_ARM 0 + #define SMTG_OS_IOS 0 + #define SMTG_OS_OSX 0 #define SMTG_CPU_X86 __i386__ #define SMTG_CPU_X86_64 __x86_64__ - #define SMTG_CPU_ARM __arm__ - #define SMTG_CPU_ARM_64 __aarch64__ + #if defined(__arm__) + #define SMTG_CPU_ARM __arm__ + #else + #define SMTG_CPU_ARM 0 + #endif + #if defined(__aarch64__) + #define SMTG_CPU_ARM_64 __aarch64__ + #else + #define SMTG_CPU_ARM_64 0 + #endif + #define SMTG_CPU_ARM_64EC 0 #include #if __BYTE_ORDER == __LITTLE_ENDIAN @@ -119,6 +162,7 @@ #define SMTG_PTHREADS 1 #define SMTG_EXPORT_SYMBOL __attribute__ ((visibility ("default"))) + #define SMTG_HIDDEN_SYMBOL __attribute__ ((visibility ("hidden"))) #if __LP64__ #define SMTG_PLATFORM_64 1 @@ -131,28 +175,35 @@ #ifndef SMTG_CPP11 #error unsupported compiler #endif + #define SMTG_CPP14 (__cplusplus >= 201402L) + #define SMTG_CPP17 (__cplusplus >= 201703L) + #define SMTG_CPP20 (__cplusplus >= 202002L) #if defined(__GNUG__) && __GNUG__ < 8 #define SMTG_CPP11_STDLIBSUPPORT 0 #else #define SMTG_CPP11_STDLIBSUPPORT 1 #endif #define SMTG_HAS_NOEXCEPT 1 - #endif + #define SMTG_HAS_CPP11_CONSTEXPR SMTG_CPP11 + #define SMTG_HAS_CPP14_CONSTEXPR SMTG_CPP14 + #endif // __cplusplus //----------------------------------------------------------------------------- // Mac and iOS //----------------------------------------------------------------------------- #elif __APPLE__ #include - #define SMTG_OS_LINUX 0 - #define SMTG_OS_MACOS 1 - #define SMTG_OS_WINDOWS 0 - #define SMTG_OS_IOS TARGET_OS_IPHONE - #define SMTG_OS_OSX TARGET_OS_MAC && !TARGET_OS_IPHONE - - #define SMTG_CPU_X86 TARGET_CPU_X86 - #define SMTG_CPU_X86_64 TARGET_CPU_X86_64 - #define SMTG_CPU_ARM TARGET_CPU_ARM - #define SMTG_CPU_ARM_64 TARGET_CPU_ARM64 + #define SMTG_OS_LINUX 0 + #define SMTG_OS_MACOS 1 + #define SMTG_OS_WINDOWS 0 + #define SMTG_OS_WINDOWS_ARM 0 + #define SMTG_OS_IOS TARGET_OS_IPHONE + #define SMTG_OS_OSX TARGET_OS_MAC && !TARGET_OS_IPHONE + + #define SMTG_CPU_X86 TARGET_CPU_X86 + #define SMTG_CPU_X86_64 TARGET_CPU_X86_64 + #define SMTG_CPU_ARM TARGET_CPU_ARM + #define SMTG_CPU_ARM_64 TARGET_CPU_ARM64 + #define SMTG_CPU_ARM_64EC 0 #if !SMTG_OS_IOS #ifndef __CF_USE_FRAMEWORK_INCLUDES__ @@ -178,6 +229,7 @@ #define SMTG_PTHREADS 1 #define SMTG_EXPORT_SYMBOL __attribute__ ((visibility ("default"))) + #define SMTG_HIDDEN_SYMBOL __attribute__ ((visibility ("hidden"))) #if !defined(__PLIST__) && !defined(SMTG_DISABLE_DEFAULT_DIAGNOSTICS) #ifdef __clang__ @@ -203,6 +255,9 @@ #ifdef __cplusplus #include #define SMTG_CPP11 (__cplusplus >= 201103L || SMTG_INTEL_CXX11_MODE) + #define SMTG_CPP14 (__cplusplus >= 201402L) + #define SMTG_CPP17 (__cplusplus >= 201703L) + #define SMTG_CPP20 (__cplusplus >= 202002L) #if defined (_LIBCPP_VERSION) && SMTG_CPP11 #define SMTG_CPP11_STDLIBSUPPORT 1 #define SMTG_HAS_NOEXCEPT 1 @@ -210,7 +265,12 @@ #define SMTG_CPP11_STDLIBSUPPORT 0 #define SMTG_HAS_NOEXCEPT 0 #endif - #endif + #define SMTG_HAS_CPP11_CONSTEXPR SMTG_CPP11 + #define SMTG_HAS_CPP14_CONSTEXPR SMTG_CPP14 + #endif // __cplusplus +//----------------------------------------------------------------------------- +// Unknown Platform +//----------------------------------------------------------------------------- #else #pragma error unknown platform #endif @@ -236,11 +296,22 @@ //----------------------------------------------------------------------------- #if SMTG_CPP11 #define SMTG_OVERRIDE override -#define SMTG_CONSTEXPR constexpr #else #define SMTG_OVERRIDE +#endif + +#if SMTG_HAS_CPP11_CONSTEXPR +#define SMTG_CONSTEXPR constexpr +#else #define SMTG_CONSTEXPR #endif + +#if SMTG_HAS_CPP14_CONSTEXPR +#define SMTG_CONSTEXPR14 constexpr +#else +#define SMTG_CONSTEXPR14 +#endif + #if SMTG_HAS_NOEXCEPT #define SMTG_NOEXCEPT noexcept #else diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fstrdefs.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fstrdefs.h index 00eaa1d..fea4522 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fstrdefs.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/fstrdefs.h @@ -25,11 +25,7 @@ // 16 bit string operations #if SMTG_CPP11 // if c++11 unicode string literals #define SMTG_CPP11_CAT_PRIVATE_DONT_USE(a,b) a ## b - #if SMTG_OS_WINDOWS - #define STR16(x) SMTG_CPP11_CAT_PRIVATE_DONT_USE(L,x) - #else - #define STR16(x) SMTG_CPP11_CAT_PRIVATE_DONT_USE(u,x) - #endif + #define STR16(x) SMTG_CPP11_CAT_PRIVATE_DONT_USE(u,x) #else #include "conststringtable.h" #define STR16(x) Steinberg::ConstStringTable::instance ()->getString (x) @@ -99,19 +95,19 @@ namespace Steinberg { //---------------------------------------------------------------------------- -static const tchar kEmptyString[] = { 0 }; -static const char8 kEmptyString8[] = { 0 }; -static const char16 kEmptyString16[] = { 0 }; +static SMTG_CONSTEXPR const tchar kEmptyString[] = { 0 }; +static SMTG_CONSTEXPR const char8 kEmptyString8[] = { 0 }; +static SMTG_CONSTEXPR const char16 kEmptyString16[] = { 0 }; #ifdef UNICODE -static const tchar kInfiniteSymbol[] = { 0x221E, 0 }; +static SMTG_CONSTEXPR const tchar kInfiniteSymbol[] = { 0x221E, 0 }; #else -static const tchar* const kInfiniteSymbol = STR ("oo"); +static SMTG_CONSTEXPR const tchar* const kInfiniteSymbol = STR ("oo"); #endif //---------------------------------------------------------------------------- template -inline int32 _tstrlen (const T* wcs) +inline SMTG_CONSTEXPR14 int32 _tstrlen (const T* wcs) { const T* eos = wcs; @@ -121,13 +117,13 @@ inline int32 _tstrlen (const T* wcs) return (int32) (eos - wcs - 1); } -inline int32 tstrlen (const tchar* str) {return _tstrlen (str);} -inline int32 strlen8 (const char8* str) {return _tstrlen (str);} -inline int32 strlen16 (const char16* str) {return _tstrlen (str);} +inline SMTG_CONSTEXPR14 int32 tstrlen (const tchar* str) {return _tstrlen (str);} +inline SMTG_CONSTEXPR14 int32 strlen8 (const char8* str) {return _tstrlen (str);} +inline SMTG_CONSTEXPR14 int32 strlen16 (const char16* str) {return _tstrlen (str);} //---------------------------------------------------------------------------- template -inline int32 _tstrcmp (const T* src, const T* dst) +inline SMTG_CONSTEXPR14 int32 _tstrcmp (const T* src, const T* dst) { while (*src == *dst && *dst) { @@ -137,30 +133,29 @@ inline int32 _tstrcmp (const T* src, const T* dst) if (*src == 0 && *dst == 0) return 0; - else if (*src == 0) + if (*src == 0) return -1; - else if (*dst == 0) + if (*dst == 0) return 1; - else - return (int32) (*src - *dst); + return (int32) (*src - *dst); } -inline int32 tstrcmp (const tchar* src, const tchar* dst) {return _tstrcmp (src, dst);} -inline int32 strcmp8 (const char8* src, const char8* dst) {return _tstrcmp (src, dst);} -inline int32 strcmp16 (const char16* src, const char16* dst) {return _tstrcmp (src, dst);} +inline SMTG_CONSTEXPR14 int32 tstrcmp (const tchar* src, const tchar* dst) {return _tstrcmp (src, dst);} +inline SMTG_CONSTEXPR14 int32 strcmp8 (const char8* src, const char8* dst) {return _tstrcmp (src, dst);} +inline SMTG_CONSTEXPR14 int32 strcmp16 (const char16* src, const char16* dst) {return _tstrcmp (src, dst);} template -inline int32 strcmpT (const T* first, const T* last); +inline SMTG_CONSTEXPR14 int32 strcmpT (const T* first, const T* last); template <> -inline int32 strcmpT (const char8* first, const char8* last) { return _tstrcmp (first, last); } +inline SMTG_CONSTEXPR14 int32 strcmpT (const char8* first, const char8* last) { return _tstrcmp (first, last); } template <> -inline int32 strcmpT (const char16* first, const char16* last) { return _tstrcmp (first, last); } +inline SMTG_CONSTEXPR14 int32 strcmpT (const char16* first, const char16* last) { return _tstrcmp (first, last); } //---------------------------------------------------------------------------- template -inline int32 _tstrncmp (const T* first, const T* last, uint32 count) +inline SMTG_CONSTEXPR14 int32 _tstrncmp (const T* first, const T* last, uint32 count) { if (count == 0) return 0; @@ -173,43 +168,42 @@ inline int32 _tstrncmp (const T* first, const T* last, uint32 count) if (*first == 0 && *last == 0) return 0; - else if (*first == 0) + if (*first == 0) return -1; - else if (*last == 0) + if (*last == 0) return 1; - else - return (int32) (*first - *last); + return (int32) (*first - *last); } -inline int32 tstrncmp (const tchar* first, const tchar* last, uint32 count) {return _tstrncmp (first, last, count);} -inline int32 strncmp8 (const char8* first, const char8* last, uint32 count) {return _tstrncmp (first, last, count);} -inline int32 strncmp16 (const char16* first, const char16* last, uint32 count) {return _tstrncmp (first, last, count);} +inline SMTG_CONSTEXPR14 int32 tstrncmp (const tchar* first, const tchar* last, uint32 count) {return _tstrncmp (first, last, count);} +inline SMTG_CONSTEXPR14 int32 strncmp8 (const char8* first, const char8* last, uint32 count) {return _tstrncmp (first, last, count);} +inline SMTG_CONSTEXPR14 int32 strncmp16 (const char16* first, const char16* last, uint32 count) {return _tstrncmp (first, last, count);} template -inline int32 strncmpT (const T* first, const T* last, uint32 count); +inline SMTG_CONSTEXPR14 int32 strncmpT (const T* first, const T* last, uint32 count); template <> -inline int32 strncmpT (const char8* first, const char8* last, uint32 count) { return _tstrncmp (first, last, count); } +inline SMTG_CONSTEXPR14 int32 strncmpT (const char8* first, const char8* last, uint32 count) { return _tstrncmp (first, last, count); } template <> -inline int32 strncmpT (const char16* first, const char16* last, uint32 count) {return _tstrncmp (first, last, count); } +inline SMTG_CONSTEXPR14 int32 strncmpT (const char16* first, const char16* last, uint32 count) {return _tstrncmp (first, last, count); } //---------------------------------------------------------------------------- template -inline T* _tstrcpy (T* dst, const T* src) +inline SMTG_CONSTEXPR14 T* _tstrcpy (T* dst, const T* src) { T* cp = dst; while ((*cp++ = *src++) != 0) // copy string ; return dst; } -inline tchar* tstrcpy (tchar* dst, const tchar* src) {return _tstrcpy (dst, src);} -inline char8* strcpy8 (char8* dst, const char8* src) {return _tstrcpy (dst, src);} -inline char16* strcpy16 (char16* dst, const char16* src) {return _tstrcpy (dst, src);} +inline SMTG_CONSTEXPR14 tchar* tstrcpy (tchar* dst, const tchar* src) {return _tstrcpy (dst, src);} +inline SMTG_CONSTEXPR14 char8* strcpy8 (char8* dst, const char8* src) {return _tstrcpy (dst, src);} +inline SMTG_CONSTEXPR14 char16* strcpy16 (char16* dst, const char16* src) {return _tstrcpy (dst, src);} //---------------------------------------------------------------------------- template -inline T* _tstrncpy (T* dest, const T* source, uint32 count) +inline SMTG_CONSTEXPR14 T* _tstrncpy (T* dest, const T* source, uint32 count) { T* start = dest; while (count && (*dest++ = *source++) != 0) // copy string @@ -223,13 +217,13 @@ inline T* _tstrncpy (T* dest, const T* source, uint32 count) return start; } -inline tchar* tstrncpy (tchar* dest, const tchar* source, uint32 count) {return _tstrncpy (dest, source, count);} -inline char8* strncpy8 (char8* dest, const char8* source, uint32 count) {return _tstrncpy (dest, source, count);} -inline char16* strncpy16 (char16* dest, const char16* source, uint32 count) {return _tstrncpy (dest, source, count);} +inline SMTG_CONSTEXPR14 tchar* tstrncpy (tchar* dest, const tchar* source, uint32 count) {return _tstrncpy (dest, source, count);} +inline SMTG_CONSTEXPR14 char8* strncpy8 (char8* dest, const char8* source, uint32 count) {return _tstrncpy (dest, source, count);} +inline SMTG_CONSTEXPR14 char16* strncpy16 (char16* dest, const char16* source, uint32 count) {return _tstrncpy (dest, source, count);} //---------------------------------------------------------------------------- template -inline T* _tstrcat (T* dst, const T* src) +inline SMTG_CONSTEXPR14 T* _tstrcat (T* dst, const T* src) { T* cp = dst; @@ -242,12 +236,12 @@ inline T* _tstrcat (T* dst, const T* src) return dst; } -inline tchar* tstrcat (tchar* dst, const tchar* src) {return _tstrcat (dst, src); } -inline char8* strcat8 (char8* dst, const char8* src) {return _tstrcat (dst, src); } -inline char16* strcat16 (char16* dst, const char16* src) {return _tstrcat (dst, src); } +inline SMTG_CONSTEXPR14 tchar* tstrcat (tchar* dst, const tchar* src) {return _tstrcat (dst, src); } +inline SMTG_CONSTEXPR14 char8* strcat8 (char8* dst, const char8* src) {return _tstrcat (dst, src); } +inline SMTG_CONSTEXPR14 char16* strcat16 (char16* dst, const char16* src) {return _tstrcat (dst, src); } //---------------------------------------------------------------------------- -inline void str8ToStr16 (char16* dst, const char8* src, int32 n = -1) +inline SMTG_CONSTEXPR14 void str8ToStr16 (char16* dst, const char8* src, int32 n = -1) { int32 i = 0; for (;;) @@ -278,12 +272,20 @@ inline void str8ToStr16 (char16* dst, const char8* src, int32 n = -1) } //------------------------------------------------------------------------ -inline bool FIDStringsEqual (FIDString id1, FIDString id2) +inline SMTG_CONSTEXPR14 bool FIDStringsEqual (FIDString id1, FIDString id2) { return (id1 && id2) ? (strcmp8 (id1, id2) == 0) : false; } -static const uint32 kPrintfBufferSize = 4096; +static SMTG_CONSTEXPR const uint32 kPrintfBufferSize = 4096; + +#if SMTG_OS_WINDOWS +/* cast between wchar_t and char16 */ +inline wchar_t* wscast (char16* s) { static_assert (sizeof (wchar_t) == sizeof (char16), ""); return reinterpret_cast (s); } +inline char16* wscast (wchar_t* s) { static_assert (sizeof (wchar_t) == sizeof (char16), ""); return reinterpret_cast (s);} +inline const wchar_t* wscast (const char16* s) { static_assert (sizeof (wchar_t) == sizeof (char16), ""); return reinterpret_cast (s); } +inline const char16* wscast (const wchar_t* s) { static_assert (sizeof (wchar_t) == sizeof (char16), ""); return reinterpret_cast (s); } +#endif //------------------------------------------------------------------------ } // namespace Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ftypes.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ftypes.h index 1f95bd1..133dbba 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ftypes.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ftypes.h @@ -18,6 +18,8 @@ #include "fplatform.h" +#include + //#define UNICODE_OFF // disable / enable unicode #ifdef UNICODE_OFF @@ -37,38 +39,26 @@ namespace Steinberg //----------------------------------------------------------------- // Integral Types typedef char int8; - typedef unsigned char uint8; + typedef uint8_t uint8; typedef unsigned char uchar; - typedef short int16; - typedef unsigned short uint16; + typedef int16_t int16; + typedef uint16_t uint16; -#if SMTG_OS_WINDOWS && !defined(__GNUC__) - typedef long int32; - typedef unsigned long uint32; -#else - typedef int int32; - typedef unsigned int uint32; -#endif + typedef int32_t int32; + typedef uint32_t uint32; - static const int32 kMaxLong = 0x7fffffff; - static const int32 kMinLong = (-0x7fffffff - 1); - static const int32 kMaxInt32 = kMaxLong; - static const int32 kMinInt32 = kMinLong; - static const uint32 kMaxInt32u = 0xffffffff; - -#if SMTG_OS_WINDOWS && !defined(__GNUC__) - typedef __int64 int64; - typedef unsigned __int64 uint64; - static const int64 kMaxInt64 = 9223372036854775807i64; - static const int64 kMinInt64 = (-9223372036854775807i64 - 1); -#else - typedef long long int64; - typedef unsigned long long uint64; - static const int64 kMaxInt64 = 0x7fffffffffffffffLL; - static const int64 kMinInt64 = (-0x7fffffffffffffffLL-1); -#endif - static const uint64 kMaxInt64u = uint64 (0xffffffff) | (uint64 (0xffffffff) << 32); + static const int32 kMaxInt32 = INT32_MAX; + static const int32 kMinInt32 = INT32_MIN; + static const int32 kMaxLong = kMaxInt32; + static const int32 kMinLong = kMinInt32; + static const uint32 kMaxInt32u = UINT32_MAX; + + typedef int64_t int64; + typedef uint64_t uint64; + static const int64 kMaxInt64 = INT64_MAX; + static const int64 kMinInt64 = INT64_MIN; + static const uint64 kMaxInt64u = UINT64_MAX; //----------------------------------------------------------------- // other Semantic Types @@ -91,15 +81,7 @@ namespace Steinberg //------------------------------------------------------------------ // Char / Strings typedef char char8; -#ifdef _NATIVE_WCHAR_T_DEFINED - typedef __wchar_t char16; -#elif defined(__MINGW32__) - typedef wchar_t char16; -#elif SMTG_CPP11 typedef char16_t char16; -#else - typedef int16 char16; -#endif #ifdef UNICODE typedef char16 tchar; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp index 23ca64e..b88fc5b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.cpp @@ -406,17 +406,23 @@ void FUID::toRegistryString (char8* string) const char8 s5[13]; Steinberg::toString8 (s5, data, 10, 16); - sprintf (string, "{%s-%s-%s-%s-%s}", s1, s2, s3, s4, s5); + snprintf (string, 40, "{%s-%s-%s-%s-%s}", s1, s2, s3, s4, s5); #endif } //------------------------------------------------------------------------ void FUID::print (char8* string, int32 style) const { - if (!string) // no string: debug output + print (style, string, 62); +} + +//------------------------------------------------------------------------ +void FUID::print (int32 style, char8* string, size_t stringBufferSize) const +{ + if (!string || stringBufferSize == 0) // no string: debug output { char8 str[128]; - print (str, style); + print (style, str, 128); #if SMTG_OS_WINDOWS OutputDebugStringA (str); @@ -433,21 +439,25 @@ void FUID::print (char8* string, int32 style) const switch (style) { case kINLINE_UID: - sprintf (string, "INLINE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4); + snprintf (string, stringBufferSize, "INLINE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, + l2, l3, l4); break; case kDECLARE_UID: - sprintf (string, "DECLARE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4); + snprintf (string, stringBufferSize, "DECLARE_UID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, + l2, l3, l4); break; case kFUID: - sprintf (string, "FUID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, l4); + snprintf (string, stringBufferSize, "FUID (0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, + l4); break; case kCLASS_UID: default: - sprintf (string, "DECLARE_CLASS_IID (Interface, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, - l2, l3, l4); + snprintf (string, stringBufferSize, + "DECLARE_CLASS_IID (Interface, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", l1, l2, l3, + l4); break; } } @@ -467,7 +477,7 @@ static void toString8 (char8* string, const char* data, int32 i1, int32 i2) for (int32 i = i1; i < i2; i++) { char8 s[3]; - sprintf (s, "%02X", (uint8)data[i]); + snprintf (s, 3, "%02X", (uint8)data[i]); strcat (string, s); } } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.h index d5a5cfc..56cf292 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/funknown.h @@ -60,19 +60,18 @@ #endif //------------------------------------------------------------------------ -#define DECLARE_UID(name, l1, l2, l3, l4) ::Steinberg::TUID name = INLINE_UID (l1, l2, l3, l4); +#define DECLARE_UID(name, l1, l2, l3, l4) SMTG_CONSTEXPR14 ::Steinberg::TUID name = INLINE_UID (l1, l2, l3, l4); //------------------------------------------------------------------------ #define EXTERN_UID(name) extern const ::Steinberg::TUID name; #ifdef INIT_CLASS_IID -#define DECLARE_CLASS_IID(ClassName, l1, l2, l3, l4) \ - static const ::Steinberg::TUID ClassName##_iid = INLINE_UID (l1, l2, l3, l4); \ - \ -const ::Steinberg::FUID ClassName::iid (ClassName##_iid); +#define DECLARE_CLASS_IID(ClassName, l1, l2, l3, l4) \ + static SMTG_CONSTEXPR14 const ::Steinberg::TUID ClassName##_iid = INLINE_UID (l1, l2, l3, l4); \ + const ::Steinberg::FUID ClassName::iid (ClassName##_iid); #else #define DECLARE_CLASS_IID(ClassName, l1, l2, l3, l4) \ - static const ::Steinberg::TUID ClassName##_iid = INLINE_UID (l1, l2, l3, l4); + static SMTG_CONSTEXPR14 const ::Steinberg::TUID ClassName##_iid = INLINE_UID (l1, l2, l3, l4); #endif #define DEF_CLASS_IID(ClassName) const ::Steinberg::FUID ClassName::iid (ClassName##_iid); @@ -207,7 +206,16 @@ typedef int64 LARGE_INT; // obsolete //------------------------------------------------------------------------ // FUID class declaration //------------------------------------------------------------------------ -typedef int8 TUID[16]; ///< plain UID type +typedef char TUID[16]; ///< plain UID type + +#if SMTG_CPP14 +//------------------------------------------------------------------------ +inline SMTG_CONSTEXPR14 void copyTUID (char* dst, const char* src) +{ + for (auto i = 0; i < 16; ++i) + dst[i] = src[i]; +} +#endif //------------------------------------------------------------------------ /* FUnknown private */ @@ -301,12 +309,18 @@ class FUID kCLASS_UID ///< "DECLARE_CLASS_IID (Interface, 0x00000000, 0x00000000, 0x00000000, 0x00000000)" }; /** Prints the UID to a string (or debug output if string is NULL). - \param string is the output string if not NULL. - \param style can be chosen from the FUID::UIDPrintStyle enumeration. */ + \param style can be chosen from the FUID::UIDPrintStyle enumeration. + \param string is the output string if not NULL. + \param stringBufferSize is the size of the output string */ + void print (int32 style, char8* string = nullptr, size_t stringBufferSize = 0) const; + +#if SMTG_CPP17 + [[deprecated ("Use the print method with the buffer size")]] +#endif void print (char8* string = nullptr, int32 style = kINLINE_UID) const; template - inline explicit FUID (const int8 (&uid)[N]) + inline explicit FUID (const char (&uid)[N]) { #if SMTG_CPP11_STDLIBSUPPORT static_assert (N == sizeof (TUID), "only TUID allowed"); @@ -442,7 +456,7 @@ using VoidT = typename Void::Type; //------------------------------------------------------------------------ /** * This type trait detects if a class has an @c iid member variable. It is used to detect if - * the FUID and DECLARE_CLASS_IID method or the SKI::UID method is used. + * the FUID and DECLARE_CLASS_IID method or the U::UID method is used. */ template struct HasIIDType : std::false_type @@ -459,7 +473,7 @@ struct HasIIDType> : std::true_type } // FUnknownPrivate //------------------------------------------------------------------------ -/** @return the TUID for a SKI interface which uses the SKI::UID method. */ +/** @return the TUID for an interface which uses the U::UID method. */ template ::value>::type* = nullptr> const TUID& getTUID () @@ -468,7 +482,7 @@ const TUID& getTUID () } //------------------------------------------------------------------------ -/** @return the TUID for a SKI interface which uses the FUID and DECLARE_CLASS_IID method. */ +/** @return the TUID for an interface which uses the FUID and DECLARE_CLASS_IID method. */ template ::value>::type* = nullptr> const TUID& getTUID () diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/futils.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/futils.h index 5ce9e29..e5d8a5f 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/futils.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/futils.h @@ -27,34 +27,39 @@ inline const T& Min (const T& a, const T& b) return b < a ? b : a; } +//---------------------------------------------------------------------------- template inline const T& Max (const T& a, const T& b) { return a < b ? b : a; } +//---------------------------------------------------------------------------- template inline T Abs (const T& value) { return (value >= (T)0) ? value : -value; } +//---------------------------------------------------------------------------- template inline T Sign (const T& value) { return (value == (T)0) ? 0 : ((value >= (T)0) ? 1 : -1); } +//---------------------------------------------------------------------------- template inline T Bound (T minval, T maxval, T x) { if (x < minval) return minval; - else if (x > maxval) + if (x > maxval) return maxval; return x; } +//---------------------------------------------------------------------------- template void Swap (T& t1, T& t2) { @@ -63,6 +68,7 @@ void Swap (T& t1, T& t2) t2 = tmp; } +//---------------------------------------------------------------------------- template bool IsApproximateEqual (T t1, T t2, T epsilon) { @@ -76,18 +82,21 @@ bool IsApproximateEqual (T t1, T t2, T epsilon) return false; } +//---------------------------------------------------------------------------- template inline T ToNormalized (const T& value, const int32 numSteps) { return value / T (numSteps); } +//---------------------------------------------------------------------------- template inline int32 FromNormalized (const T& norm, const int32 numSteps) { return Min (numSteps, int32 (norm * (numSteps + 1))); } +//---------------------------------------------------------------------------- // Four character constant #ifndef CCONST #define CCONST(a, b, c, d) \ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ipluginbase.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ipluginbase.h index 10325fe..36bc263 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ipluginbase.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/ipluginbase.h @@ -38,8 +38,10 @@ class IPluginBase: public FUnknown public: //------------------------------------------------------------------------ /** The host passes a number of interfaces as context to initialize the plug-in class. - @note Extensive memory allocations etc. should be performed in this method rather than in the class' constructor! - If the method does NOT return kResultOk, the object is released immediately. In this case terminate is not called! */ + \param context, passed by the host, is mandatory and should implement IHostApplication + @note Extensive memory allocations etc. should be performed in this method rather than in + the class' constructor! If the method does NOT return kResultOk, the object is released + immediately. In this case terminate is not called! */ virtual tresult PLUGIN_API initialize (FUnknown* context) = 0; /** This function is called before the plug-in is unloaded and can be used for @@ -62,11 +64,23 @@ struct PFactoryInfo //------------------------------------------------------------------------ enum FactoryFlags { - kNoFlags = 0, ///< Nothing - kClassesDiscardable = 1 << 0, ///< The number of exported classes can change each time the Module is loaded. If this flag is set, the host does not cache class information. This leads to a longer startup time because the host always has to load the Module to get the current class information. - kLicenseCheck = 1 << 1, ///< Class IDs of components are interpreted as Syncrosoft-License (LICENCE_UID). Loaded in a Steinberg host, the module will not be loaded when the license is not valid - kComponentNonDiscardable = 1 << 3, ///< Component will not be unloaded until process exit - kUnicode = 1 << 4 ///< Components have entirely unicode encoded strings. (True for VST 3 plug-ins so far) + /** Nothing */ + kNoFlags = 0, + + /** The number of exported classes can change each time the Module is loaded. If this flag + is set, the host does not cache class information. This leads to a longer startup time + because the host always has to load the Module to get the current class information. */ + kClassesDiscardable = 1 << 0, + + /** This flag is deprecated, do not use anymore, resp. it will get ignored from + Cubase/Nuendo 12 and later. */ + kLicenseCheck = 1 << 1, + + /** Component will not be unloaded until process exit */ + kComponentNonDiscardable = 1 << 3, + + /** Components have entirely unicode encoded strings (True for VST 3 plug-ins so far). */ + kUnicode = 1 << 4 }; enum @@ -77,12 +91,16 @@ struct PFactoryInfo }; //------------------------------------------------------------------------ - char8 vendor[kNameSize]; ///< e.g. "Steinberg Media Technologies" - char8 url[kURLSize]; ///< e.g. "http://www.steinberg.de" - char8 email[kEmailSize]; ///< e.g. "info@steinberg.de" - int32 flags; ///< (see above) + char8 vendor[kNameSize]; ///< e.g. "Steinberg Media Technologies" + char8 url[kURLSize]; ///< e.g. "http://www.steinberg.de" + char8 email[kEmailSize]; ///< e.g. "info@steinberg.de" + int32 flags; ///< (see FactoryFlags above) //------------------------------------------------------------------------ - PFactoryInfo (const char8* _vendor, const char8* _url, const char8* _email, int32 _flags) + SMTG_CONSTEXPR14 PFactoryInfo (const char8* _vendor, const char8* _url, const char8* _email, + int32 _flags) +#if SMTG_CPP14 + : vendor (), url (), email (), flags () +#endif { strncpy8 (vendor, _vendor, kNameSize); strncpy8 (url, _url, kURLSize); @@ -117,16 +135,32 @@ struct PClassInfo kNameSize = 64 }; //------------------------------------------------------------------------ - TUID cid; ///< Class ID 16 Byte class GUID - int32 cardinality; ///< cardinality of the class, set to kManyInstances (see \ref ClassCardinality) - char8 category[kCategorySize]; ///< class category, host uses this to categorize interfaces - char8 name[kNameSize]; ///< class name, visible to the user + /** Class ID 16 Byte class GUID */ + TUID cid; + + /** Cardinality of the class, set to kManyInstances (see \ref PClassInfo::ClassCardinality) */ + int32 cardinality; + + /** Class category, host uses this to categorize interfaces */ + char8 category[kCategorySize]; + + /** Class name, visible to the user */ + char8 name[kNameSize]; + //------------------------------------------------------------------------ - PClassInfo (const TUID _cid, int32 _cardinality, const char8* _category, const char8* _name) + SMTG_CONSTEXPR14 PClassInfo (const TUID _cid, int32 _cardinality, const char8* _category, + const char8* _name) +#if SMTG_CPP14 + : cid (), cardinality (), category (), name () +#endif { +#if SMTG_CPP14 + copyTUID (cid, _cid); +#else memset (this, 0, sizeof (PClassInfo)); memcpy (cid, _cid, sizeof (TUID)); +#endif if (_category) strncpy8 (category, _category, kCategorySize); if (_name) @@ -162,8 +196,9 @@ class IPluginFactory : public FUnknown /** Fill a PFactoryInfo structure with information about the plug-in vendor. */ virtual tresult PLUGIN_API getFactoryInfo (PFactoryInfo* info) = 0; - /** Returns the number of exported classes by this factory. - If you are using the CPluginFactory implementation provided by the SDK, it returns the number of classes you registered with CPluginFactory::registerClass. */ + /** Returns the number of exported classes by this factory. If you are using the CPluginFactory + * implementation provided by the SDK, it returns the number of classes you registered with + * CPluginFactory::registerClass. */ virtual int32 PLUGIN_API countClasses () = 0; /** Fill a PClassInfo structure with information about the class at the specified index. */ @@ -186,10 +221,17 @@ DECLARE_CLASS_IID (IPluginFactory, 0x7A4D811C, 0x52114A1F, 0xAED9D2EE, 0x0B43BF9 struct PClassInfo2 { //------------------------------------------------------------------------ - TUID cid; ///< Class ID 16 Byte class GUID - int32 cardinality; ///< cardinality of the class, set to kManyInstances (see \ref PClassInfo::ClassCardinality) - char8 category[PClassInfo::kCategorySize]; ///< class category, host uses this to categorize interfaces - char8 name[PClassInfo::kNameSize]; ///< class name, visible to the user + /** Class ID 16 Byte class GUID */ + TUID cid; + + /** Cardinality of the class, set to kManyInstances (see \ref PClassInfo::ClassCardinality) */ + int32 cardinality; + + /** Class category, host uses this to categorize interfaces */ + char8 category[PClassInfo::kCategorySize]; + + /** Class name, visible to the user */ + char8 name[PClassInfo::kNameSize]; enum { kVendorSize = 64, @@ -197,20 +239,44 @@ struct PClassInfo2 kSubCategoriesSize = 128 }; - uint32 classFlags; ///< flags used for a specific category, must be defined where category is defined - char8 subCategories[kSubCategoriesSize]; ///< module specific subcategories, can be more than one, logically added by the \c OR operator - char8 vendor[kVendorSize]; ///< overwrite vendor information from factory info - char8 version[kVersionSize]; ///< Version string (e.g. "1.0.0.512" with Major.Minor.Subversion.Build) - char8 sdkVersion[kVersionSize]; ///< SDK version used to build this class (e.g. "VST 3.0") + /** flags used for a specific category, must be defined where category is defined */ + uint32 classFlags; + /** module specific subcategories, can be more than one, logically added by the OR operator */ + char8 subCategories[kSubCategoriesSize]; + + /** overwrite vendor information from factory info */ + char8 vendor[kVendorSize]; + + /** Version string (e.g. "1.0.0.512" with Major.Minor.Subversion.Build) */ + char8 version[kVersionSize]; + + /** SDK version used to build this class (e.g. "VST 3.0") */ + char8 sdkVersion[kVersionSize]; //------------------------------------------------------------------------ - PClassInfo2 (const TUID _cid, int32 _cardinality, const char8* _category, const char8* _name, - int32 _classFlags, const char8* _subCategories, const char8* _vendor, const char8* _version, - const char8* _sdkVersion) + SMTG_CONSTEXPR14 PClassInfo2 (const TUID _cid, int32 _cardinality, const char8* _category, + const char8* _name, int32 _classFlags, + const char8* _subCategories, const char8* _vendor, + const char8* _version, const char8* _sdkVersion) +#if SMTG_CPP14 + : cid () + , cardinality () + , category () + , name () + , classFlags () + , subCategories () + , vendor () + , version () + , sdkVersion () +#endif { +#if SMTG_CPP14 + copyTUID (cid, _cid); +#else memset (this, 0, sizeof (PClassInfo2)); memcpy (cid, _cid, sizeof (TUID)); +#endif cardinality = _cardinality; if (_category) strncpy8 (category, _category, PClassInfo::kCategorySize); @@ -281,19 +347,44 @@ struct PClassInfoW kSubCategoriesSize = 128 }; - uint32 classFlags; ///< flags used for a specific category, must be defined where category is defined - char8 subCategories[kSubCategoriesSize];///< module specific subcategories, can be more than one, logically added by the \c OR operator - char16 vendor[kVendorSize]; ///< overwrite vendor information from factory info - char16 version[kVersionSize]; ///< Version string (e.g. "1.0.0.512" with Major.Minor.Subversion.Build) - char16 sdkVersion[kVersionSize]; ///< SDK version used to build this class (e.g. "VST 3.0") - -//------------------------------------------------------------------------ - PClassInfoW (const TUID _cid, int32 _cardinality, const char8* _category, const char16* _name, - int32 _classFlags, const char8* _subCategories, const char16* _vendor, const char16* _version, - const char16* _sdkVersion) + /** flags used for a specific category, must be defined where category is defined */ + uint32 classFlags; + + /** module specific subcategories, can be more than one, logically added by the OR operator */ + char8 subCategories[kSubCategoriesSize]; + + /** overwrite vendor information from factory info */ + char16 vendor[kVendorSize]; + + /** Version string (e.g. "1.0.0.512" with Major.Minor.Subversion.Build) */ + char16 version[kVersionSize]; + + /** SDK version used to build this class (e.g. "VST 3.0") */ + char16 sdkVersion[kVersionSize]; + +//------------------------------------------------------------------------ + SMTG_CONSTEXPR14 PClassInfoW (const TUID _cid, int32 _cardinality, const char8* _category, + const char16* _name, int32 _classFlags, + const char8* _subCategories, const char16* _vendor, + const char16* _version, const char16* _sdkVersion) +#if SMTG_CPP14 + : cid () + , cardinality () + , category () + , name () + , classFlags () + , subCategories () + , vendor () + , version () + , sdkVersion () +#endif { +#if SMTG_CPP14 + copyTUID (cid, _cid); +#else memset (this, 0, sizeof (PClassInfoW)); memcpy (cid, _cid, sizeof (TUID)); +#endif cardinality = _cardinality; if (_category) strncpy8 (category, _category, PClassInfo::kCategorySize); @@ -326,9 +417,13 @@ struct PClassInfoW PClassInfoW () { memset (this, 0, sizeof (PClassInfoW)); } #endif - void fromAscii (const PClassInfo2& ci2) + SMTG_CONSTEXPR14 void fromAscii (const PClassInfo2& ci2) { +#if SMTG_CPP14 + copyTUID (cid, ci2.cid); +#else memcpy (cid, ci2.cid, sizeof (TUID)); +#endif cardinality = ci2.cardinality; strncpy8 (category, ci2.category, PClassInfo::kCategorySize); str8ToStr16 (name, ci2.name, PClassInfo::kNameSize); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/iplugincompatibility.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/iplugincompatibility.h new file mode 100644 index 0000000..d3f67e9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/base/iplugincompatibility.h @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// Project : SDK Core +// +// Category : SDK Core Interfaces +// Filename : pluginterfaces/base/iplugincompatibility.h +// Created by : Steinberg, 02/2022 +// Description : Basic Plug-in Interfaces +// +//----------------------------------------------------------------------------- +// This file is part of a Steinberg SDK. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this distribution +// and at www.steinberg.net/sdklicenses. +// No part of the SDK, including this file, may be copied, modified, propagated, +// or distributed except according to the terms contained in the LICENSE file. +//----------------------------------------------------------------------------- + +#pragma once + +#include "ibstream.h" + +//------------------------------------------------------------------------ +namespace Steinberg { + +//------------------------------------------------------------------------ +/** moduleinfo.json + +The moduleinfo.json describes the contents of the plug-in in a JSON5 compatible format (See https://json5.org/). +It contains the factory info (see PFactoryInfo), the contained classes (see PClassInfo), the +included snapshots and a list of compatibility of the included classes. + +An example moduleinfo.json: + +\code +{ + "Name": "", + "Version": "1.0", + "Factory Info": { + "Vendor": "Steinberg Media Technologies", + "URL": "http://www.steinberg.net", + "E-Mail": "mailto:info@steinberg.de", + "Flags": { + "Unicode": true, + "Classes Discardable": false, + "Component Non Discardable": false, + }, + }, + "Compatibility": [ + { + "New": "B9F9ADE1CD9C4B6DA57E61E3123535FD", + "Old": [ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", // just an example + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", // another example + ], + }, + ], + "Classes": [ + { + "CID": "B9F9ADE1CD9C4B6DA57E61E3123535FD", + "Category": "Audio Module Class", + "Name": "AGainSimple VST3", + "Vendor": "Steinberg Media Technologies", + "Version": "1.3.0.1", + "SDKVersion": "VST 3.7.4", + "Sub Categories": [ + "Fx", + ], + "Class Flags": 0, + "Cardinality": 2147483647, + "Snapshots": [ + ], + }, + ], +} +\endcode + +*/ + +#define kPluginCompatibilityClass "Plugin Compatibility Class" + +//------------------------------------------------------------------------ +/** optional interface to query the compatibility of the plug-ins classes +- [plug imp] + +A plug-in can add a class with this interface to its class factory if it cannot provide a +moduleinfo.json file in its plug-in package/bundle where the compatibility is normally part of. + +If the module contains a moduleinfo.json the host will ignore this class. + +The class must write into the stream an UTF-8 encoded json description of the compatibility of +the other classes in the factory. + +It is expected that the JSON5 written starts with an array: +\code +[ + { + "New": "B9F9ADE1CD9C4B6DA57E61E3123535FD", + "Old": [ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", // just an example + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", // another example + ], + }, +] +\endcode +*/ +class IPluginCompatibility : public FUnknown +{ +public: + /** get the compatibility stream + * @param stream the stream the plug-in must write the UTF8 encoded JSON5 compatibility + * string. + * @return kResultTrue on success + */ + virtual tresult PLUGIN_API getCompatibilityJSON (IBStream* stream) = 0; + +//------------------------------------------------------------------------ + static const FUID iid; +}; + +DECLARE_CLASS_IID (IPluginCompatibility, 0x4AFD4B6A, 0x35D7C240, 0xA5C31414, 0xFB7D15E6) + +//------------------------------------------------------------------------ +} // Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/gui/iplugviewcontentscalesupport.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/gui/iplugviewcontentscalesupport.h index 532c941..f529031 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/gui/iplugviewcontentscalesupport.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/gui/iplugviewcontentscalesupport.h @@ -36,16 +36,24 @@ namespace Steinberg { This interface communicates the content scale factor from the host to the plug-in view on systems where plug-ins cannot get this information directly like Microsoft Windows. -The host calls setContentScaleFactor directly after the plug-in view is attached and when the scale -factor changes (system change or window moved to another screen with different scaling settings). -The host could call setContentScaleFactor in a different context, for example: scaling the -plug-in editor for better readability. +The host calls setContentScaleFactor directly before or after the plug-in view is attached and when +the scale factor changes while the view is attached (system change or window moved to another screen +with different scaling settings). + +The host may call setContentScaleFactor in a different context, for example: scaling the plug-in +editor for better readability. + When a plug-in handles this (by returning kResultTrue), it needs to scale the width and height of -its view by the scale factor and inform the host via a IPlugFrame::resizeView(), the host will then +its view by the scale factor and inform the host via a IPlugFrame::resizeView(). The host will then call IPlugView::onSize(). Note that the host is allowed to call setContentScaleFactor() at any time the IPlugView is valid. - */ +If this happens before the IPlugFrame object is set on your view, make sure that when the host calls +IPlugView::getSize() afterwards you return the size of your view for that new scale factor. + +It is recommended to implement this interface on Microsoft Windows to let the host know that the +plug-in is able to render in different scalings. +*/ class IPlugViewContentScaleSupport : public FUnknown { public: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstattributes.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstattributes.h index fb63b30..7d4e68f 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstattributes.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstattributes.h @@ -55,7 +55,7 @@ class IAttributeList : public FUnknown /** Gets float value. */ virtual tresult PLUGIN_API getFloat (AttrID id, double& value) = 0; - /** Sets string value (UTF16) (should be null-terminated!). */ + /** Sets string value (UTF16) (must be null-terminated!). */ virtual tresult PLUGIN_API setString (AttrID id, const TChar* string) = 0; /** Gets string value (UTF16). Note that Size is in Byte, not the string Length! @@ -106,7 +106,7 @@ tresult PLUGIN_API MyPlugin::setState (IBStream* state) UString128 tmp (string); char ascii[128]; tmp.toAscii (ascii, 128); - if (!strncmp (ascii, StateType::kProject, strlen (StateType::kProject))) + if (strncmp (ascii, StateType::kProject, strlen (StateType::kProject)) == 0) { // we are in project loading context... } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstaudioprocessor.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstaudioprocessor.h index f2dd5e7..82d550e 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstaudioprocessor.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstaudioprocessor.h @@ -46,48 +46,48 @@ namespace PlugType \defgroup plugType Plug-in Type used for subCategories */ /*@{*/ //------------------------------------------------------------------------ -const CString kFxAnalyzer = "Fx|Analyzer"; ///< Scope, FFT-Display, Loudness Processing... -const CString kFxDelay = "Fx|Delay"; ///< Delay, Multi-tap Delay, Ping-Pong Delay... -const CString kFxDistortion = "Fx|Distortion"; ///< Amp Simulator, Sub-Harmonic, SoftClipper... -const CString kFxDynamics = "Fx|Dynamics"; ///< Compressor, Expander, Gate, Limiter, Maximizer, Tape Simulator, EnvelopeShaper... -const CString kFxEQ = "Fx|EQ"; ///< Equalization, Graphical EQ... -const CString kFxFilter = "Fx|Filter"; ///< WahWah, ToneBooster, Specific Filter,... -const CString kFx = "Fx"; ///< others type (not categorized) -const CString kFxInstrument = "Fx|Instrument"; ///< Fx which could be loaded as Instrument too -const CString kFxInstrumentExternal = "Fx|Instrument|External"; ///< Fx which could be loaded as Instrument too and is external (wrapped Hardware) -const CString kFxSpatial = "Fx|Spatial"; ///< MonoToStereo, StereoEnhancer,... -const CString kFxGenerator = "Fx|Generator"; ///< Tone Generator, Noise Generator... -const CString kFxMastering = "Fx|Mastering"; ///< Dither, Noise Shaping,... -const CString kFxModulation = "Fx|Modulation"; ///< Phaser, Flanger, Chorus, Tremolo, Vibrato, AutoPan, Rotary, Cloner... -const CString kFxPitchShift = "Fx|Pitch Shift"; ///< Pitch Processing, Pitch Correction, Vocal Tuning... -const CString kFxRestoration = "Fx|Restoration"; ///< Denoiser, Declicker,... -const CString kFxReverb = "Fx|Reverb"; ///< Reverberation, Room Simulation, Convolution Reverb... -const CString kFxSurround = "Fx|Surround"; ///< dedicated to surround processing: LFE Splitter, Bass Manager... -const CString kFxTools = "Fx|Tools"; ///< Volume, Mixer, Tuner... -const CString kFxNetwork = "Fx|Network"; ///< using Network - -const CString kInstrument = "Instrument"; ///< Effect used as instrument (sound generator), not as insert -const CString kInstrumentDrum = "Instrument|Drum"; ///< Instrument for Drum sounds -const CString kInstrumentExternal = "Instrument|External";///< External Instrument (wrapped Hardware) -const CString kInstrumentPiano = "Instrument|Piano"; ///< Instrument for Piano sounds -const CString kInstrumentSampler = "Instrument|Sampler"; ///< Instrument based on Samples -const CString kInstrumentSynth = "Instrument|Synth"; ///< Instrument based on Synthesis -const CString kInstrumentSynthSampler = "Instrument|Synth|Sampler"; ///< Instrument based on Synthesis and Samples - -const CString kSpatial = "Spatial"; ///< used for SurroundPanner -const CString kSpatialFx = "Spatial|Fx"; ///< used for SurroundPanner and as insert effect -const CString kOnlyRealTime = "OnlyRT"; ///< indicates that it supports only realtime process call, no processing faster than realtime -const CString kOnlyOfflineProcess = "OnlyOfflineProcess"; ///< used for plug-in offline processing (will not work as normal insert plug-in) -const CString kOnlyARA = "OnlyARA"; ///< used for plug-ins that require ARA to operate (will not work as normal insert plug-in) - -const CString kNoOfflineProcess = "NoOfflineProcess"; ///< will be NOT used for plug-in offline processing (will work as normal insert plug-in) -const CString kUpDownMix = "Up-Downmix"; ///< used for Mixconverter/Up-Mixer/Down-Mixer -const CString kAnalyzer = "Analyzer"; ///< Meter, Scope, FFT-Display, not selectable as insert plug-in -const CString kAmbisonics = "Ambisonics"; ///< used for Ambisonics channel (FX or Panner/Mixconverter/Up-Mixer/Down-Mixer when combined with other category) - -const CString kMono = "Mono"; ///< used for Mono only plug-in [optional] -const CString kStereo = "Stereo"; ///< used for Stereo only plug-in [optional] -const CString kSurround = "Surround"; ///< used for Surround only plug-in [optional] +SMTG_CONSTEXPR const CString kFx = "Fx"; ///< others type (not categorized) +SMTG_CONSTEXPR const CString kFxAnalyzer = "Fx|Analyzer"; ///< Scope, FFT-Display, Loudness Processing... +SMTG_CONSTEXPR const CString kFxDelay = "Fx|Delay"; ///< Delay, Multi-tap Delay, Ping-Pong Delay... +SMTG_CONSTEXPR const CString kFxDistortion = "Fx|Distortion"; ///< Amp Simulator, Sub-Harmonic, SoftClipper... +SMTG_CONSTEXPR const CString kFxDynamics = "Fx|Dynamics"; ///< Compressor, Expander, Gate, Limiter, Maximizer, Tape Simulator, EnvelopeShaper... +SMTG_CONSTEXPR const CString kFxEQ = "Fx|EQ"; ///< Equalization, Graphical EQ... +SMTG_CONSTEXPR const CString kFxFilter = "Fx|Filter"; ///< WahWah, ToneBooster, Specific Filter,... +SMTG_CONSTEXPR const CString kFxGenerator = "Fx|Generator"; ///< Tone Generator, Noise Generator... +SMTG_CONSTEXPR const CString kFxInstrument = "Fx|Instrument"; ///< Fx which could be loaded as Instrument too +SMTG_CONSTEXPR const CString kFxInstrumentExternal = "Fx|Instrument|External"; ///< Fx which could be loaded as Instrument too and is external (wrapped Hardware) +SMTG_CONSTEXPR const CString kFxMastering = "Fx|Mastering"; ///< Dither, Noise Shaping,... +SMTG_CONSTEXPR const CString kFxModulation = "Fx|Modulation"; ///< Phaser, Flanger, Chorus, Tremolo, Vibrato, AutoPan, Rotary, Cloner... +SMTG_CONSTEXPR const CString kFxNetwork = "Fx|Network"; ///< using Network +SMTG_CONSTEXPR const CString kFxPitchShift = "Fx|Pitch Shift"; ///< Pitch Processing, Pitch Correction, Vocal Tuning... +SMTG_CONSTEXPR const CString kFxRestoration = "Fx|Restoration"; ///< Denoiser, Declicker,... +SMTG_CONSTEXPR const CString kFxReverb = "Fx|Reverb"; ///< Reverberation, Room Simulation, Convolution Reverb... +SMTG_CONSTEXPR const CString kFxSpatial = "Fx|Spatial"; ///< MonoToStereo, StereoEnhancer,... +SMTG_CONSTEXPR const CString kFxSurround = "Fx|Surround"; ///< dedicated to surround processing: LFE Splitter, Bass Manager... +SMTG_CONSTEXPR const CString kFxTools = "Fx|Tools"; ///< Volume, Mixer, Tuner... +SMTG_CONSTEXPR const CString kFxVocals = "Fx|Vocals"; ///< Tools dedicated to vocals + +SMTG_CONSTEXPR const CString kInstrument = "Instrument"; ///< Effect used as instrument (sound generator), not as insert +SMTG_CONSTEXPR const CString kInstrumentDrum = "Instrument|Drum"; ///< Instrument for Drum sounds +SMTG_CONSTEXPR const CString kInstrumentExternal = "Instrument|External";///< External Instrument (wrapped Hardware) +SMTG_CONSTEXPR const CString kInstrumentPiano = "Instrument|Piano"; ///< Instrument for Piano sounds +SMTG_CONSTEXPR const CString kInstrumentSampler = "Instrument|Sampler"; ///< Instrument based on Samples +SMTG_CONSTEXPR const CString kInstrumentSynth = "Instrument|Synth"; ///< Instrument based on Synthesis +SMTG_CONSTEXPR const CString kInstrumentSynthSampler = "Instrument|Synth|Sampler"; ///< Instrument based on Synthesis and Samples + +SMTG_CONSTEXPR const CString kAmbisonics = "Ambisonics"; ///< used for Ambisonics channel (FX or Panner/Mixconverter/Up-Mixer/Down-Mixer when combined with other category) +SMTG_CONSTEXPR const CString kAnalyzer = "Analyzer"; ///< Meter, Scope, FFT-Display, not selectable as insert plug-in +SMTG_CONSTEXPR const CString kNoOfflineProcess = "NoOfflineProcess"; ///< will be NOT used for plug-in offline processing (will work as normal insert plug-in) +SMTG_CONSTEXPR const CString kOnlyARA = "OnlyARA"; ///< used for plug-ins that require ARA to operate (will not work as normal insert plug-in) +SMTG_CONSTEXPR const CString kOnlyOfflineProcess = "OnlyOfflineProcess"; ///< used for plug-in offline processing (will not work as normal insert plug-in) +SMTG_CONSTEXPR const CString kOnlyRealTime = "OnlyRT"; ///< indicates that it supports only realtime process call, no processing faster than realtime +SMTG_CONSTEXPR const CString kSpatial = "Spatial"; ///< used for SurroundPanner +SMTG_CONSTEXPR const CString kSpatialFx = "Spatial|Fx"; ///< used for SurroundPanner and as insert effect +SMTG_CONSTEXPR const CString kUpDownMix = "Up-Downmix"; ///< used for Mixconverter/Up-Mixer/Down-Mixer + +SMTG_CONSTEXPR const CString kMono = "Mono"; ///< used for Mono only plug-in [optional] +SMTG_CONSTEXPR const CString kStereo = "Stereo"; ///< used for Stereo only plug-in [optional] +SMTG_CONSTEXPR const CString kSurround = "Surround"; ///< used for Surround only plug-in [optional] //------------------------------------------------------------------------ /*@}*/ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsteditcontroller.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsteditcontroller.h index 7bd1a48..d7c8925 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsteditcontroller.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsteditcontroller.h @@ -146,7 +146,14 @@ enum RestartFlags * The host ask the plug-in for the new routing with IComponent::getRoutingInfo, \ref vst3Routing * see IComponent * [SDK 3.6.6] */ - kRoutingInfoChanged = 1 << 9 + kRoutingInfoChanged = 1 << 9, + + /** Key switches has changed (info, count) + * Either the Key switches info, the count of Key switches has changed. + * The host invalidates all caches of Key switches infos and asks the edit controller (IKeyswitchController) for the current ones. + * See IKeyswitchController + * [SDK 3.7.3] */ + kKeyswitchChanged = 1 << 10 }; //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterchanges.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterchanges.h index ce9dec0..1d216c1 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterchanges.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterchanges.h @@ -128,7 +128,7 @@ class IParameterChanges : public FUnknown /** Adds a new parameter queue with a given ID at the end of the list, returns it and its index in the parameter changes list. */ - virtual IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID& id, int32& index /*out*/) = 0; + virtual IParamValueQueue* PLUGIN_API addParameterData (const ParamID& id, int32& index /*out*/) = 0; //------------------------------------------------------------------------ static const FUID iid; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterfunctionname.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterfunctionname.h index 8e9a5ab..ca2f2a5 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterfunctionname.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivstparameterfunctionname.h @@ -26,7 +26,6 @@ //------------------------------------------------------------------------ namespace Steinberg { namespace Vst { - namespace FunctionNameType { //-------------------------------------------------------------------- const CString kCompGainReduction = "Comp:GainReduction"; /** */ @@ -45,6 +44,12 @@ namespace FunctionNameType { const CString kRandomize = "Randomize"; /** Allow to assign some randomized values to some parameters in a controlled way*/ + /// Panner Type + const CString kPanPosCenterX = "PanPosCenterX"; ///< Gravity point X-axis [0, 1]=>[L-R] (for stereo: middle between left and right) + const CString kPanPosCenterY = "PanPosCenterY"; ///< Gravity point Y-axis [0, 1]=>[Front-Rear] + const CString kPanPosCenterZ = "PanPosCenterZ"; ///< Gravity point Z-axis [0, 1]=>[Bottom-Top] + + } // FunctionNameType //------------------------------------------------------------------------ @@ -55,10 +60,11 @@ namespace FunctionNameType { - [released: 3.7.0] - [optional] -This interface allows the host to get a parameter associated to a specific meaning (a functionName) for a given unit. -The host can use this information, for example, for drawing a Gain Reduction meter in its own UI. -In order to get the plain value of this parameter, the host should use the IEditController::normalizedParamToPlain. -The host can automatically map parameters to dedicated UI controls, such as the wet-dry mix knob or Randomize button. +This interface allows the host to get a parameter associated to a specific meaning (a functionName) +for a given unit. The host can use this information, for example, for drawing a Gain Reduction meter +in its own UI. In order to get the plain value of this parameter, the host should use the +IEditController::normalizedParamToPlain. The host can automatically map parameters to dedicated UI +controls, such as the wet-dry mix knob or Randomize button. \section IParameterFunctionNameExample Example @@ -70,38 +76,39 @@ The host can automatically map parameters to dedicated UI controls, such as the in MyController class declaration class MyController : public Vst::EditController, public Vst::IParameterFunctionName { - ... - tresult PLUGIN_API getParameterIDFromFunctionName (UnitID unitID, FIDString functionName, - Vst::ParamID& paramID) override; - ... - - OBJ_METHODS (MyController, Vst::EditController) - DEFINE_INTERFACES - ... - DEF_INTERFACE (Vst::IParameterFunctionName) - END_DEFINE_INTERFACES (Vst::EditController) - ... + ... + tresult PLUGIN_API getParameterIDFromFunctionName (UnitID unitID, FIDString functionName, + Vst::ParamID& paramID) override; + ... + + OBJ_METHODS (MyController, Vst::EditController) + DEFINE_INTERFACES + ... + DEF_INTERFACE (Vst::IParameterFunctionName) + END_DEFINE_INTERFACES (Vst::EditController) + DELEGATE_REFCOUNT (Vst::EditController) + ... } #include "ivstparameterfunctionname.h" namespace Steinberg { - namespace Vst { - DEF_CLASS_IID (IParameterFunctionName) - } + namespace Vst { + DEF_CLASS_IID (IParameterFunctionName) + } } //------------------------------------------------------------------------ -tresult PLUGIN_API MyController::getParameterIDFromFunctionName (UnitID unitID, FIDString functionName, - Vst::ParamID& paramID) +tresult PLUGIN_API MyController::getParameterIDFromFunctionName (UnitID unitID, FIDString +functionName, Vst::ParamID& paramID) { - using namespace Vst; + using namespace Vst; - paramID = kNoParamId; + paramID = kNoParamId; - if (unitID == kRootUnitId && FIDStringsEqual (functionName, kCompGainReduction)) - paramID = kMyGainReductionId; + if (unitID == kRootUnitId && FIDStringsEqual (functionName, kCompGainReduction)) + paramID = kMyGainReductionId; - return (paramID != kNoParamId) ? kResultOk : kResultFalse; + return (paramID != kNoParamId) ? kResultOk : kResultFalse; } //--- a host implementation example: -------------------- @@ -109,14 +116,15 @@ tresult PLUGIN_API MyController::getParameterIDFromFunctionName (UnitID unitID, FUnknownPtr functionName (mEditController->getIEditController ()); if (functionName) { - Vst::ParamID paramID; - if (functionName->getParameterIDFromFunctionName (Vst::FunctionNameType::kCompGainReduction, paramID) == kResultTrue) - { - // paramID could be cached for performance issue - ParamValue norm = mEditController->getIEditController ()->getParamNormalized (paramID); - ParamValue plain = mEditController->getIEditController ()->normalizedParamToPlain (paramID, norm); - // plain is something like -6 (-6dB) - } + Vst::ParamID paramID; + if (functionName->getParameterIDFromFunctionName (kRootUnitId, + Vst::FunctionNameType::kCompGainReduction, paramID) == kResultTrue) + { + // paramID could be cached for performance issue + ParamValue norm = mEditController->getIEditController ()->getParamNormalized (paramID); + ParamValue plain = mEditController->getIEditController ()->normalizedParamToPlain (paramID, norm); + // plain is something like -6 (-6dB) + } } \endcode */ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsttestplugprovider.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsttestplugprovider.h index eb9923f..ffbcec4 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsttestplugprovider.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/ivsttestplugprovider.h @@ -9,7 +9,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2020, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vstspeaker.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vstspeaker.h index 0daa490..0d21684 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vstspeaker.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vstspeaker.h @@ -76,6 +76,15 @@ const Speaker kSpeakerACN12 = (Speaker)1 << 46; ///< Ambisonic ACN 12 const Speaker kSpeakerACN13 = (Speaker)1 << 47; ///< Ambisonic ACN 13 const Speaker kSpeakerACN14 = (Speaker)1 << 48; ///< Ambisonic ACN 14 const Speaker kSpeakerACN15 = (Speaker)1 << 49; ///< Ambisonic ACN 15 +const Speaker kSpeakerACN16 = (Speaker)1 << 50; ///< Ambisonic ACN 16 +const Speaker kSpeakerACN17 = (Speaker)1 << 51; ///< Ambisonic ACN 17 +const Speaker kSpeakerACN18 = (Speaker)1 << 52; ///< Ambisonic ACN 18 +const Speaker kSpeakerACN19 = (Speaker)1 << 53; ///< Ambisonic ACN 19 +const Speaker kSpeakerACN20 = (Speaker)1 << 54; ///< Ambisonic ACN 20 +const Speaker kSpeakerACN21 = (Speaker)1 << 55; ///< Ambisonic ACN 21 +const Speaker kSpeakerACN22 = (Speaker)1 << 56; ///< Ambisonic ACN 22 +const Speaker kSpeakerACN23 = (Speaker)1 << 57; ///< Ambisonic ACN 23 +const Speaker kSpeakerACN24 = (Speaker)1 << 58; ///< Ambisonic ACN 24 const Speaker kSpeakerTsl = (Speaker)1 << 24; ///< Top Side Left (Tsl) const Speaker kSpeakerTsr = (Speaker)1 << 25; ///< Top Side Right (Tsr) @@ -103,6 +112,7 @@ namespace SpeakerArr { //------------------------------------------------------------------------ /** Speaker Arrangement Definitions. +* for example: 5.0.5.3 for 5x Middle + 0x LFE + 5x Top + 3x Bottom \ingroup speakerArrangements */ /*@{*/ const SpeakerArrangement kEmpty = 0; ///< empty arrangement @@ -116,45 +126,46 @@ const SpeakerArrangement kStereoTF = kSpeakerTfl | kSpeakerTfr; ///< Tfl Tfr const SpeakerArrangement kStereoTS = kSpeakerTsl | kSpeakerTsr; ///< Tsl Tsr const SpeakerArrangement kStereoTR = kSpeakerTrl | kSpeakerTrr; ///< Trl Trr const SpeakerArrangement kStereoBF = kSpeakerBfl | kSpeakerBfr; ///< Bfl Bfr -const SpeakerArrangement kCineFront = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLc | kSpeakerRc; ///< L R C Lc Rc +/** L R C Lc Rc */ +const SpeakerArrangement kCineFront = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLc | kSpeakerRc; -/** L R C */ +/** L R C */ // 3.0 const SpeakerArrangement k30Cine = kSpeakerL | kSpeakerR | kSpeakerC; -/** L R C Lfe */ -const SpeakerArrangement k31Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe; +/** L R C Lfe */ // 3.1 +const SpeakerArrangement k31Cine = k30Cine | kSpeakerLfe; /** L R S */ const SpeakerArrangement k30Music = kSpeakerL | kSpeakerR | kSpeakerCs; /** L R Lfe S */ -const SpeakerArrangement k31Music = kSpeakerL | kSpeakerR | kSpeakerLfe | kSpeakerCs; -/** L R C S (LCRS) */ +const SpeakerArrangement k31Music = k30Music | kSpeakerLfe; +/** L R C S */ // LCRS const SpeakerArrangement k40Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerCs; -/** L R C Lfe S (LCRS+Lfe) */ -const SpeakerArrangement k41Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerCs; -/** L R Ls Rs (Quadro) */ +/** L R C Lfe S */ // LCRS+Lfe +const SpeakerArrangement k41Cine = k40Cine | kSpeakerLfe; +/** L R Ls Rs */ // 4.0 (Quadro) const SpeakerArrangement k40Music = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs; -/** L R Lfe Ls Rs (Quadro+Lfe) */ -const SpeakerArrangement k41Music = kSpeakerL | kSpeakerR | kSpeakerLfe | kSpeakerLs | kSpeakerRs; -/** L R C Ls Rs */ +/** L R Lfe Ls Rs */ // 4.1 (Quadro+Lfe) +const SpeakerArrangement k41Music = k40Music | kSpeakerLfe; +/** L R C Ls Rs */ // 5.0 (ITU 0+5+0.0 Sound System B) const SpeakerArrangement k50 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs; -/** L R C Lfe Ls Rs */ -const SpeakerArrangement k51 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs; +/** L R C Lfe Ls Rs */ // 5.1 (ITU 0+5+0.1 Sound System B) +const SpeakerArrangement k51 = k50 | kSpeakerLfe; /** L R C Ls Rs Cs */ const SpeakerArrangement k60Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerCs; /** L R C Lfe Ls Rs Cs */ -const SpeakerArrangement k61Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerCs; +const SpeakerArrangement k61Cine = k60Cine | kSpeakerLfe; /** L R Ls Rs Sl Sr */ const SpeakerArrangement k60Music = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr; /** L R Lfe Ls Rs Sl Sr */ -const SpeakerArrangement k61Music = kSpeakerL | kSpeakerR | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr; +const SpeakerArrangement k61Music = k60Music | kSpeakerLfe; /** L R C Ls Rs Lc Rc */ const SpeakerArrangement k70Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc; /** L R C Lfe Ls Rs Lc Rc */ -const SpeakerArrangement k71Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc; +const SpeakerArrangement k71Cine = k70Cine | kSpeakerLfe; const SpeakerArrangement k71CineFullFront = k71Cine; -/** L R C Ls Rs Sl Sr */ +/** L R C Ls Rs Sl Sr */ // (ITU 0+7+0.0 Sound System I) const SpeakerArrangement k70Music = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr; -/** L R C Lfe Ls Rs Sl Sr */ -const SpeakerArrangement k71Music = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr; +/** L R C Lfe Ls Rs Sl Sr */ // (ITU 0+7+0.1 Sound System I) +const SpeakerArrangement k71Music = k70Music | kSpeakerLfe; /** L R C Lfe Ls Rs Lcs Rcs */ const SpeakerArrangement k71CineFullRear = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLcs | kSpeakerRcs; @@ -165,76 +176,110 @@ const SpeakerArrangement k71Proximity = kSpeakerL | kSpeakerR | kSpeakerC | /** L R C Ls Rs Lc Rc Cs */ const SpeakerArrangement k80Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs; /** L R C Lfe Ls Rs Lc Rc Cs */ -const SpeakerArrangement k81Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs; +const SpeakerArrangement k81Cine = k80Cine | kSpeakerLfe; /** L R C Ls Rs Cs Sl Sr */ const SpeakerArrangement k80Music = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerCs | kSpeakerSl | kSpeakerSr; /** L R C Lfe Ls Rs Cs Sl Sr */ -const SpeakerArrangement k81Music = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerCs | kSpeakerSl | kSpeakerSr; +const SpeakerArrangement k81Music = k80Music | kSpeakerLfe; /** L R C Ls Rs Lc Rc Sl Sr */ const SpeakerArrangement k90Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerSl | kSpeakerSr; /** L R C Lfe Ls Rs Lc Rc Sl Sr */ -const SpeakerArrangement k91Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | - kSpeakerSl | kSpeakerSr; +const SpeakerArrangement k91Cine = k90Cine | kSpeakerLfe; /** L R C Ls Rs Lc Rc Cs Sl Sr */ const SpeakerArrangement k100Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs | kSpeakerSl | kSpeakerSr; /** L R C Lfe Ls Rs Lc Rc Cs Sl Sr */ -const SpeakerArrangement k101Cine = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs | - kSpeakerSl | kSpeakerSr; +const SpeakerArrangement k101Cine = k100Cine | kSpeakerLfe; -/** First-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization */ +/** First-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (4 channels) */ const SpeakerArrangement kAmbi1stOrderACN = kSpeakerACN0 | kSpeakerACN1 | kSpeakerACN2 | kSpeakerACN3; -/** Second-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization */ +/** Second-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (9 channels) */ const SpeakerArrangement kAmbi2cdOrderACN = kAmbi1stOrderACN | kSpeakerACN4 | kSpeakerACN5 | kSpeakerACN6 | kSpeakerACN7 | kSpeakerACN8; -/** Third-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization */ +/** Third-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (16 channels) */ const SpeakerArrangement kAmbi3rdOrderACN = kAmbi2cdOrderACN | kSpeakerACN9 | kSpeakerACN10 | kSpeakerACN11 | kSpeakerACN12 | kSpeakerACN13 | kSpeakerACN14 | kSpeakerACN15; - +/** Fourth-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (25 channels) */ +const SpeakerArrangement kAmbi4thOrderACN = kAmbi3rdOrderACN | kSpeakerACN16 | kSpeakerACN17 | kSpeakerACN18 | kSpeakerACN19 | kSpeakerACN20 | + kSpeakerACN21 | kSpeakerACN22 | kSpeakerACN23 | kSpeakerACN24; +/** Fifth-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (36 channels) */ +const SpeakerArrangement kAmbi5thOrderACN = 0x000FFFFFFFFF; +/** Sixth-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (49 channels) */ +const SpeakerArrangement kAmbi6thOrderACN = 0x0001FFFFFFFFFFFF; +/** Seventh-Order with Ambisonic Channel Number (ACN) ordering and SN3D normalization (64 channels) */ +const SpeakerArrangement kAmbi7thOrderACN = 0xFFFFFFFFFFFFFFFF; /*-----------*/ /* 3D formats */ /*-----------*/ /** L R Ls Rs Tfl Tfr Trl Trr */ // 4.0.4 const SpeakerArrangement k80Cube = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | kSpeakerTfl| kSpeakerTfr| kSpeakerTrl | kSpeakerTrr; +const SpeakerArrangement k40_4 = k80Cube; + /** L R C Lfe Ls Rs Cs Tc */ // 6.1.1 const SpeakerArrangement k71CineTopCenter = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerCs | kSpeakerTc; + /** L R C Lfe Ls Rs Cs Tfc */ // 6.1.1 const SpeakerArrangement k71CineCenterHigh = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerCs | kSpeakerTfc; -/** L R C Lfe Ls Rs Tfl Tfr */ // 5.1.2 -const SpeakerArrangement k71CineFrontHigh = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerTfl | kSpeakerTfr; -const SpeakerArrangement k71MPEG3D = k71CineFrontHigh; -/** L R C Lfe Ls Rs Tsl Tsr */ // 5.1.2 -const SpeakerArrangement k71CineSideHigh = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerTsl | kSpeakerTsr; -/** L R Lfe Ls Rs Tfl Tfc Tfr Bfc */ // 4.1.4 +/** L R C Ls Rs Tfl Tfr */ // 5.0.2 (ITU 2+5+0.0 Sound System C) +const SpeakerArrangement k70CineFrontHigh = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerTfl | kSpeakerTfr; +const SpeakerArrangement k70MPEG3D = k70CineFrontHigh; +const SpeakerArrangement k50_2 = k70CineFrontHigh; + +/** L R C Lfe Ls Rs Tfl Tfr */ // 5.1.2 (ITU 2+5+0.1 Sound System C) +const SpeakerArrangement k71CineFrontHigh = k70CineFrontHigh | kSpeakerLfe; +const SpeakerArrangement k71MPEG3D = k71CineFrontHigh; +const SpeakerArrangement k51_2 = k71CineFrontHigh; + +/** L R C Ls Rs Tsl Tsr */ // 5.0.2 (Side) +const SpeakerArrangement k70CineSideHigh = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerTsl | kSpeakerTsr; +const SpeakerArrangement k50_2_TS = k70CineSideHigh; + +/** L R C Lfe Ls Rs Tsl Tsr */ // 5.1.2 (Side) +const SpeakerArrangement k71CineSideHigh = k70CineSideHigh | kSpeakerLfe; +const SpeakerArrangement k51_2_TS = k71CineSideHigh; + +/** L R Lfe Ls Rs Tfl Tfc Tfr Bfc */ // 4.1.3.1 const SpeakerArrangement k81MPEG3D = kSpeakerL | kSpeakerR | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerBfc; +const SpeakerArrangement k41_4_1 = k81MPEG3D; -/** L R C Ls Rs Tfl Tfr Trl Trr */ // 5.0.4 +/** L R C Ls Rs Tfl Tfr Trl Trr */ // 5.0.4 (ITU 4+5+0.0 Sound System D) const SpeakerArrangement k90 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerTfl| kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; const SpeakerArrangement k50_4 = k90; -/** L R C Lfe Ls Rs Tfl Tfr Trl Trr */ // 5.1.4 -const SpeakerArrangement k91 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | - kSpeakerTfl| kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; + +/** L R C Lfe Ls Rs Tfl Tfr Trl Trr */ // 5.1.4 (ITU 4+5+0.1 Sound System D) +const SpeakerArrangement k91 = k90 | kSpeakerLfe; const SpeakerArrangement k51_4 = k91; +/** L R C Ls Rs Tfl Tfr Trl Trr Bfc */ // 5.0.4.1 (ITU 4+5+1.0 Sound System E) +const SpeakerArrangement k50_4_1 = k50_4 | kSpeakerBfc; + +/** L R C Lfe Ls Rs Tfl Tfr Trl Trr Bfc */ // 5.1.4.1 (ITU 4+5+1.1 Sound System E) +const SpeakerArrangement k51_4_1 = k50_4_1 | kSpeakerLfe; + /** L R C Ls Rs Sl Sr Tsl Tsr */ // 7.0.2 const SpeakerArrangement k70_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | kSpeakerTsl | kSpeakerTsr; /** L R C Lfe Ls Rs Sl Sr Tsl Tsr */ // 7.1.2 -const SpeakerArrangement k71_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | - kSpeakerSl | kSpeakerSr | kSpeakerTsl | kSpeakerTsr; +const SpeakerArrangement k71_2 = k70_2 | kSpeakerLfe; const SpeakerArrangement k91Atmos = k71_2; // 9.1 Dolby Atmos (3D) -/** L R C Ls Rs Sl Sr Tfl Tfr Trl Trr */ // 7.0.4 +/** L R C Ls Rs Sl Sr Tfl Tfr Trc */ // 7.0.3 (ITU 3+7+0.0 Sound System F) +const SpeakerArrangement k70_3 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerSl | kSpeakerSr | kSpeakerTfl | kSpeakerTfr | kSpeakerTrc; + +/** L R C Lfe Ls Rs Sl Sr Tfl Tfr Trc Lfe2 */ // 7.2.3 (ITU 3+7+0.2 Sound System F) +const SpeakerArrangement k72_3 = k70_3 | kSpeakerLfe | kSpeakerLfe2; + +/** L R C Ls Rs Sl Sr Tfl Tfr Trl Trr */ // 7.0.4 (ITU 4+7+0.0 Sound System J) const SpeakerArrangement k70_4 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R C Lfe Ls Rs Sl Sr Tfl Tfr Trl Trr */ // 7.1.4 -const SpeakerArrangement k71_4 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | - kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; +/** L R C Lfe Ls Rs Sl Sr Tfl Tfr Trl Trr */ // 7.1.4 (ITU 4+7+0.1 Sound System J) +const SpeakerArrangement k71_4 = k70_4 | kSpeakerLfe; const SpeakerArrangement k111MPEG3D = k71_4; /** L R C Ls Rs Sl Sr Tfl Tfr Trl Trr Tsl Tsr */ // 7.0.6 @@ -243,19 +288,15 @@ const SpeakerArrangement k70_6 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerTsl | kSpeakerTsr; /** L R C Lfe Ls Rs Sl Sr Tfl Tfr Trl Trr Tsl Tsr */ // 7.1.6 -const SpeakerArrangement k71_6 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | - kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | - kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerTsl | kSpeakerTsr; +const SpeakerArrangement k71_6 = k70_6 | kSpeakerLfe; -/** L R C Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr */ // 9.0.4 +/** L R C Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr */ // 9.0.4 (ITU 4+9+0.0 Sound System G) const SpeakerArrangement k90_4 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerSl | kSpeakerSr | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R C Lfe Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr */ // 9.1.4 -const SpeakerArrangement k91_4 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | - kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerSl | kSpeakerSr | - kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; +/** L R C Lfe Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr */ // 9.1.4 (ITU 4+9+0.1 Sound System G) +const SpeakerArrangement k91_4 = k90_4 | kSpeakerLfe; /** L R C Lfe Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr Tsl Tsr */ // 9.0.6 const SpeakerArrangement k90_6 = kSpeakerL | kSpeakerR | kSpeakerC | @@ -263,53 +304,118 @@ const SpeakerArrangement k90_6 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerTsl | kSpeakerTsr; /** L R C Lfe Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr Tsl Tsr */ // 9.1.6 -const SpeakerArrangement k91_6 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | - kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerSl | kSpeakerSr | - kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerTsl | kSpeakerTsr; +const SpeakerArrangement k91_6 = k90_6 | kSpeakerLfe; -/** L R C Ls Rs Tc Tfl Tfr Trl Trr */ // 5.0.5 +/** L R C Ls Rs Tc Tfl Tfr Trl Trr */ // 5.0.5 (10.0 Auro-3D) const SpeakerArrangement k100 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerTc | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R C Lfe Ls Rs Tc Tfl Tfr Trl Trr */ // 5.1.5 -const SpeakerArrangement k101 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | - kSpeakerTc | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; +const SpeakerArrangement k50_5 = k100; + +/** L R C Lfe Ls Rs Tc Tfl Tfr Trl Trr */ // 5.1.5 (10.1 Auro-3D) +const SpeakerArrangement k101 = k50_5 | kSpeakerLfe; const SpeakerArrangement k101MPEG3D = k101; +const SpeakerArrangement k51_5 = k101; /** L R C Lfe Ls Rs Tfl Tfc Tfr Trl Trr Lfe2 */ // 5.2.5 const SpeakerArrangement k102 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerTfl| kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerLfe2; +const SpeakerArrangement k52_5 = k102; -/** L R C Ls Rs Tc Tfl Tfc Tfr Trl Trr */ // 5.0.6 +/** L R C Ls Rs Tc Tfl Tfc Tfr Trl Trr */ // 5.0.6 (11.0 Auro-3D) const SpeakerArrangement k110 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R C Lfe Ls Rs Tc Tfl Tfc Tfr Trl Trr */ // 5.1.6 -const SpeakerArrangement k111 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | - kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; +const SpeakerArrangement k50_6 = k110; + +/** L R C Lfe Ls Rs Tc Tfl Tfc Tfr Trl Trr */ // 5.1.6 (11.1 Auro-3D) +const SpeakerArrangement k111 = k110 | kSpeakerLfe; +const SpeakerArrangement k51_6 = k111; /** L R C Lfe Ls Rs Lc Rc Tfl Tfc Tfr Trl Trr Lfe2 */ // 7.2.5 const SpeakerArrangement k122 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerTfl| kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerLfe2; -/** L R C Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr */ // 7.0.6 +const SpeakerArrangement k72_5 = k122; + +/** L R C Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr */ // 7.0.6 (13.0 Auro-3D) const SpeakerArrangement k130 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R C Lfe Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr */ // 7.1.6 -const SpeakerArrangement k131 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | - kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; -/** L R Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr Brl Brr */ // 6.0.4.4 -const SpeakerArrangement k140 = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | +/** L R C Lfe Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr */ // 7.1.6 (13.1 Auro-3D) +const SpeakerArrangement k131 = k130 | kSpeakerLfe; + +/** L R Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr Brl Brr */ // 6.0.4.4 +const SpeakerArrangement k140 = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | kSpeakerBfl | kSpeakerBfr | kSpeakerBrl | kSpeakerBrr; +const SpeakerArrangement k60_4_4 = k140; -/** L R C Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Tsl Tsr Bfl Bfc Bfr */ // 10.0.9.3 +/** L R C Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Tsl Tsr Bfl Bfc Bfr */ // 10.0.9.3 (ITU 9+10+3.0 Sound System H) const SpeakerArrangement k220 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs | kSpeakerSl | kSpeakerSr | kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrc | kSpeakerTrr | kSpeakerTsl | kSpeakerTsr | kSpeakerBfl| kSpeakerBfc | kSpeakerBfr; +const SpeakerArrangement k100_9_3 = k220; -/** L R C Lfe Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Lfe2 Tsl Tsr Bfl Bfc Bfr */ // 10.2.9.3 +/** L R C Lfe Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Lfe2 Tsl Tsr Bfl Bfc Bfr */ // 10.2.9.3 (ITU 9+10+3.2 Sound System H) const SpeakerArrangement k222 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLfe | kSpeakerLs | kSpeakerRs | kSpeakerLc | kSpeakerRc | kSpeakerCs | kSpeakerSl | kSpeakerSr | kSpeakerTc | kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrc | kSpeakerTrr | kSpeakerLfe2 | kSpeakerTsl | kSpeakerTsr | kSpeakerBfl| kSpeakerBfc | kSpeakerBfr; +const SpeakerArrangement k102_9_3 = k222; + +/** L R C Ls Rs Tfl Tfc Tfr Trl Trr Bfl Bfc Bfr */ // 5.0.5.3 +const SpeakerArrangement k50_5_3 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfc | kSpeakerBfr; + +/** L R C Lfe Ls Rs Tfl Tfc Tfr Trl Trr Bfl Bfc Bfr */ // 5.1.5.3 +const SpeakerArrangement k51_5_3 = k50_5_3 | kSpeakerLfe; + +/** L R C Ls Rs Tsl Tsr Bfl Bfr */ // 5.0.2.2 +const SpeakerArrangement k50_2_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTsl | kSpeakerTsr | + kSpeakerBfl | kSpeakerBfr; + +/** L R C Ls Rs Tfl Tfr Trl Trr Bfl Bfr */ // 5.0.4.2 +const SpeakerArrangement k50_4_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr; + +/** L R C Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr */ // 7.0.4.2 +const SpeakerArrangement k70_4_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | kSpeakerSl | kSpeakerSr | + kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr; + +/** L R C Ls Rs Tfl Tfc Tfr Trl Trr */ // 5.0.5.0 (Sony 360RA) +const SpeakerArrangement k50_5_Sony = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr; + +/** C Sl Sr Cs Tsl Tsr Bsl Bsr */ // 4.0.2.2 (Sony 360RA) +const SpeakerArrangement k40_2_2 = kSpeakerC | kSpeakerSl | kSpeakerSr | kSpeakerCs | + kSpeakerTsl | kSpeakerTsr | + kSpeakerBsl | kSpeakerBsr; + +/** L R Ls Rs Tfl Tfr Trl Trr Bfl Bfr */ // 4.0.4.2 (Sony 360RA) +const SpeakerArrangement k40_4_2 = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr; + +/** L R C Ls Rs Tfl Tfc Tfr Bfl Bfr */ // 5.0.3.2 (Sony 360RA) +const SpeakerArrangement k50_3_2 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | + kSpeakerBfl | kSpeakerBfr; + +/** L R C Tfl Tfc Tfr Trl Trr Bfl Bfr */ // 3.0.5.2 (Sony 360RA) +const SpeakerArrangement k30_5_2 = kSpeakerL | kSpeakerR | kSpeakerC | + kSpeakerTfl | kSpeakerTfc | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr; + +/** L R Ls Rs Tfl Tfr Trl Trr Bfl Bfr Brl Brr */ // 4.0.4.4 (Sony 360RA) +const SpeakerArrangement k40_4_4 = kSpeakerL | kSpeakerR | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr | kSpeakerBrl | kSpeakerBrr; + +/** L R C Ls Rs Tfl Tfr Trl Trr Bfl Bfr Brl Brr */ // 5.0.4.4 (Sony 360RA) +const SpeakerArrangement k50_4_4 = kSpeakerL | kSpeakerR | kSpeakerC | kSpeakerLs | kSpeakerRs | + kSpeakerTfl | kSpeakerTfr | kSpeakerTrl | kSpeakerTrr | + kSpeakerBfl | kSpeakerBfr | kSpeakerBrl | kSpeakerBrr; //------------------------------------------------------------------------ /** Speaker Arrangement String Representation. @@ -352,47 +458,69 @@ const CString kString71Music = "7.1"; const CString kString71MusicOld = "7.1 Music (Dolby)"; const CString kString71CineTopCenter = "7.1 Cine Top Center"; const CString kString71CineCenterHigh = "7.1 Cine Center High"; -const CString kString71CineFrontHigh = "7.1 Cine Front High"; -const CString kString71CineSideHigh = "7.1 Cine Side High"; -const CString kString71CineFullRear = "7.1 Cine Full Rear"; -const CString kString71Proximity = "7.1 Proximity"; +const CString kString71CineFullRear = "7.1 Cine Full Rear"; +const CString kString51_2 = "5.1.2"; +const CString kString50_2 = "5.0.2"; +const CString kString50_2TopSide = "5.0.2 Top Side"; +const CString kString51_2TopSide = "5.1.2 Top Side"; +const CString kString71Proximity = "7.1 Proximity"; const CString kString80Cine = "8.0 Cine"; const CString kString80Music = "8.0 Music"; -const CString kString80Cube = "8.0 Cube"; +const CString kString40_4 = "8.0 Cube"; const CString kString81Cine = "8.1 Cine"; const CString kString81Music = "8.1 Music"; const CString kString90Cine = "9.0 Cine"; const CString kString91Cine = "9.1 Cine"; const CString kString100Cine = "10.0 Cine"; const CString kString101Cine = "10.1 Cine"; -const CString kString102 = "10.2 Experimental"; -const CString kString122 = "12.2"; +const CString kString52_5 = "5.2.5"; +const CString kString72_5 = "12.2"; const CString kString50_4 = "5.0.4"; const CString kString51_4 = "5.1.4"; -const CString kString70_2 = "7.0.2"; +const CString kString50_4_1 = "5.0.4.1"; +const CString kString51_4_1 = "5.1.4.1"; +const CString kString70_2 = "7.0.2"; const CString kString71_2 = "7.1.2"; +const CString kString70_3 = "7.0.3"; +const CString kString72_3 = "7.2.3"; const CString kString70_4 = "7.0.4"; const CString kString71_4 = "7.1.4"; -const CString kString70_6 = "7.0.6"; +const CString kString70_6 = "7.0.6"; const CString kString71_6 = "7.1.6"; -const CString kString90_4 = "9.0.4"; +const CString kString90_4 = "9.0.4"; const CString kString91_4 = "9.1.4"; -const CString kString90_6 = "9.0.6"; +const CString kString90_6 = "9.0.6"; const CString kString91_6 = "9.1.6"; -const CString kString100 = "10.0 Auro-3D"; -const CString kString101 = "10.1 Auro-3D"; -const CString kString110 = "11.0 Auro-3D"; -const CString kString111 = "11.1 Auro-3D"; +const CString kString50_5 = "10.0 Auro-3D"; +const CString kString51_5 = "10.1 Auro-3D"; +const CString kString50_6 = "11.0 Auro-3D"; +const CString kString51_6 = "11.1 Auro-3D"; const CString kString130 = "13.0 Auro-3D"; const CString kString131 = "13.1 Auro-3D"; -const CString kString81MPEG = "8.1 MPEG"; -const CString kString140 = "14.0"; +const CString kString41_4_1 = "8.1 MPEG"; +const CString kString60_4_4 = "14.0"; +const CString kString220 = "22.0"; const CString kString222 = "22.2"; -const CString kString220 = "22.0"; +const CString kString50_5_3 = "5.0.5.3"; +const CString kString51_5_3 = "5.1.5.3"; +const CString kString50_2_2 = "5.0.2.2"; +const CString kString50_4_2 = "5.0.4.2"; +const CString kString70_4_2 = "7.0.4.2"; +const CString kString50_5_Sony = "5.0.5 Sony"; +const CString kString40_2_2 = "4.0.3.2"; +const CString kString40_4_2 = "4.0.4.2"; +const CString kString50_3_2 = "5.0.3.2"; +const CString kString30_5_2 = "3.0.5.2"; +const CString kString40_4_4 = "4.0.4.4"; +const CString kString50_4_4 = "5.0.4.4"; + const CString kStringAmbi1stOrder = "1st Order Ambisonics"; const CString kStringAmbi2cdOrder = "2nd Order Ambisonics"; const CString kStringAmbi3rdOrder = "3rd Order Ambisonics"; - +const CString kStringAmbi4thOrder = "4th Order Ambisonics"; +const CString kStringAmbi5thOrder = "5th Order Ambisonics"; +const CString kStringAmbi6thOrder = "6th Order Ambisonics"; +const CString kStringAmbi7thOrder = "7th Order Ambisonics"; /*@}*/ //------------------------------------------------------------------------ @@ -432,21 +560,27 @@ const CString kString80CineS = "L R C Ls Rs Lc Rc Cs"; const CString kString80MusicS = "L R C Ls Rs Cs Sl Sr"; const CString kString81CineS = "L R C LFE Ls Rs Lc Rc Cs"; const CString kString81MusicS = "L R C LFE Ls Rs Cs Sl Sr"; -const CString kString80CubeS = "L R Ls Rs Tfl Tfr Trl Trr"; +const CString kString40_4S = "L R Ls Rs Tfl Tfr Trl Trr"; const CString kString71CineTopCenterS = "L R C LFE Ls Rs Cs Tc"; const CString kString71CineCenterHighS = "L R C LFE Ls Rs Cs Tfc"; -const CString kString71CineFrontHighS = "L R C LFE Ls Rs Tfl Tfl"; -const CString kString71CineSideHighS = "L R C LFE Ls Rs Tsl Tsl"; -const CString kString71CineFullRearS = "L R C LFE Ls Rs Lcs Rcs"; -const CString kString71ProximityS = "L R C LFE Ls Rs Pl Pr"; +const CString kString71CineFullRearS = "L R C LFE Ls Rs Lcs Rcs"; +const CString kString50_2S = "L R C Ls Rs Tfl Tfr"; +const CString kString51_2S = "L R C LFE Ls Rs Tfl Tfr"; +const CString kString50_2TopSideS = "L R C Ls Rs Tsl Tsr"; +const CString kString51_2TopSideS = "L R C LFE Ls Rs Tsl Tsr"; +const CString kString71ProximityS = "L R C LFE Ls Rs Pl Pr"; const CString kString90CineS = "L R C Ls Rs Lc Rc Sl Sr"; -const CString kString91CineS = "L R C Lfe Ls Rs Lc Rc Sl Sr"; +const CString kString91CineS = "L R C LFE Ls Rs Lc Rc Sl Sr"; const CString kString100CineS = "L R C Ls Rs Lc Rc Cs Sl Sr"; -const CString kString101CineS = "L R C Lfe Ls Rs Lc Rc Cs Sl Sr"; +const CString kString101CineS = "L R C LFE Ls Rs Lc Rc Cs Sl Sr"; const CString kString50_4S = "L R C Ls Rs Tfl Tfr Trl Trr"; -const CString kString51_4S = "L R C LFE Ls Rs Tfl Tfr Trl Trr"; +const CString kString51_4S = "L R C LFE Ls Rs Tfl Tfr Trl Trr"; +const CString kString50_4_1S = "L R C Ls Rs Tfl Tfr Trl Trr Bfc"; +const CString kString51_4_1S = "L R C LFE Ls Rs Tfl Tfr Trl Trr Bfc"; const CString kString70_2S = "L R C Ls Rs Sl Sr Tsl Tsr"; const CString kString71_2S = "L R C LFE Ls Rs Sl Sr Tsl Tsr"; +const CString kString70_3S = "L R C Ls Rs Sl Sr Tfl Tfr Trc"; +const CString kString72_3S = "L R C LFE Ls Rs Sl Sr Tfl Tfr Trc LFE2"; const CString kString70_4S = "L R C Ls Rs Sl Sr Tfl Tfr Trl Trr"; const CString kString71_4S = "L R C LFE Ls Rs Sl Sr Tfl Tfr Trl Trr"; const CString kString70_6S = "L R C Ls Rs Sl Sr Tfl Tfr Trl Trr Tsl Tsr"; @@ -455,22 +589,38 @@ const CString kString90_4S = "L R C Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr"; const CString kString91_4S = "L R C LFE Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr"; const CString kString90_6S = "L R C Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr Tsl Tsr"; const CString kString91_6S = "L R C LFE Ls Rs Lc Rc Sl Sr Tfl Tfr Trl Trr Tsl Tsr"; -const CString kString100S = "L R C Ls Rs Tc Tfl Tfr Trl Trr"; -const CString kString101S = "L R C LFE Ls Rs Tc Tfl Tfr Trl Trr"; -const CString kString110S = "L R C Ls Rs Tc Tfl Tfc Tfr Trl Trr"; -const CString kString111S = "L R C LFE Ls Rs Tc Tfl Tfc Tfr Trl Trr"; +const CString kString50_5S = "L R C Ls Rs Tc Tfl Tfr Trl Trr"; +const CString kString51_5S = "L R C LFE Ls Rs Tc Tfl Tfr Trl Trr"; +const CString kString50_5_SonyS = "L R C Ls Rs Tfl Tfc Tfr Trl Trr"; +const CString kString50_6S = "L R C Ls Rs Tc Tfl Tfc Tfr Trl Trr"; +const CString kString51_6S = "L R C LFE Ls Rs Tc Tfl Tfc Tfr Trl Trr"; const CString kString130S = "L R C Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr"; const CString kString131S = "L R C LFE Ls Rs Sl Sr Tc Tfl Tfc Tfr Trl Trr"; -const CString kString102S = "L R C LFE Ls Rs Tfl Tfc Tfr Trl Trr LFE2"; -const CString kString122S = "L R C LFE Ls Rs Lc Rc Tfl Tfc Tfr Trl Trr LFE2"; -const CString kString81MPEGS = "L R LFE Ls Rs Tfl Tfc Tfr Bfc"; -const CString kString140S = "L R Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr Brl Brr"; -const CString kString222S = "L R C LFE Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr LFE2 Tsl Tsr Bfl Bfc Bfr"; -const CString kString220S = "L R C Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Tsl Tsr Bfl Bfc Bfr"; - -const CString kStringAmbi1stOrderS = "0 1 2 3"; -const CString kStringAmbi2cdOrderS = "0 1 2 3 4 5 6 7 8"; -const CString kStringAmbi3rdOrderS = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"; +const CString kString52_5S = "L R C LFE Ls Rs Tfl Tfc Tfr Trl Trr LFE2"; +const CString kString72_5S = "L R C LFE Ls Rs Lc Rc Tfl Tfc Tfr Trl Trr LFE2"; +const CString kString41_4_1S = "L R LFE Ls Rs Tfl Tfc Tfr Bfc"; +const CString kString30_5_2S = "L R C Tfl Tfc Tfr Trl Trr Bfl Bfr"; +const CString kString40_2_2S = "C Sl Sr Cs Tfc Tsl Tsr Trc"; +const CString kString40_4_2S = "L R Ls Rs Tfl Tfr Trl Trr Bfl Bfr"; +const CString kString40_4_4S = "L R Ls Rs Tfl Tfr Trl Trr Bfl Bfr Brl Brr"; +const CString kString50_4_4S = "L R C Ls Rs Tfl Tfr Trl Trr Bfl Bfr Brl Brr"; +const CString kString60_4_4S = "L R Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr Brl Brr"; +const CString kString50_5_3S = "L R C Ls Rs Tfl Tfc Tfr Trl Trr Bfl Bfc Bfr"; +const CString kString51_5_3S = "L R C LFE Ls Rs Tfl Tfc Tfr Trl Trr Bfl Bfc Bfr"; +const CString kString50_2_2S = "L R C Ls Rs Tsl Tsr Bfl Bfr"; +const CString kString50_3_2S = "L R C Ls Rs Tfl Tfc Tfr Bfl Bfr"; +const CString kString50_4_2S = "L R C Ls Rs Tfl Tfr Trl Trr Bfl Bfr"; +const CString kString70_4_2S = "L R C Ls Rs Sl Sr Tfl Tfr Trl Trr Bfl Bfr"; +const CString kString222S = "L R C LFE Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr LFE2 Tsl Tsr Bfl Bfc Bfr"; +const CString kString220S = "L R C Ls Rs Lc Rc Cs Sl Sr Tc Tfl Tfc Tfr Trl Trc Trr Tsl Tsr Bfl Bfc Bfr"; + +const CString kStringAmbi1stOrderS = "0 1 2 3"; +const CString kStringAmbi2cdOrderS = "0 1 2 3 4 5 6 7 8"; +const CString kStringAmbi3rdOrderS = "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15"; +const CString kStringAmbi4thOrderS = "0..24"; +const CString kStringAmbi5thOrderS = "0..35"; +const CString kStringAmbi6thOrderS = "0..48"; +const CString kStringAmbi7thOrderS = "0..63"; /*@}*/ //------------------------------------------------------------------------ @@ -566,8 +716,9 @@ inline bool hasTopSpeakers (const SpeakerArrangement& arr) /** Returns true if arrangement contains bottom (lower layer) speakers */ inline bool hasBottomSpeakers (const SpeakerArrangement& arr) { - if (arr & kSpeakerBfl || arr & kSpeakerBfc || arr & kSpeakerBfl || arr & kSpeakerBfc || - arr & kSpeakerBfr) + if (arr & kSpeakerBfl || arr & kSpeakerBfc || arr & kSpeakerBfr || + arr & kSpeakerBsl || arr & kSpeakerBsr || + arr & kSpeakerBrr || arr & kSpeakerBrl || arr & kSpeakerBrc) return true; return false; } @@ -607,16 +758,39 @@ inline bool is3D (const SpeakerArrangement& arr) } //------------------------------------------------------------------------ -/** Returns true if arrangement is a Auro configuration. */ +/** Returns true if arrangement is a Ambisonic configuration. */ inline bool isAmbisonics (const SpeakerArrangement& arr) { - if (arr == kAmbi1stOrderACN || arr == kAmbi2cdOrderACN || arr == kAmbi3rdOrderACN) + if (arr == kAmbi1stOrderACN || arr == kAmbi2cdOrderACN || arr == kAmbi3rdOrderACN || + arr == kAmbi4thOrderACN || arr == kAmbi5thOrderACN || arr == kAmbi6thOrderACN || + arr == kAmbi7thOrderACN) { return true; } return false; } + +//------------------------------------------------------------------------ +/** Converts a speaker of a Ambisonic order 1 to 4 to a Ambisonic order 7 (5 to 7) (return 0 when out of range).*/ +inline Speaker convertSpeaker_Ambi_1234Order_to_Ambi567Order (Speaker speaker_1234_order) +{ + int32 idx = getSpeakerIndex (speaker_1234_order, kAmbi4thOrderACN); + if (idx < 0) + return 0; + return (Speaker)1 << idx; +} + +//------------------------------------------------------------------------ +/** Converts a speaker of a Ambisonic order 5 to 7 to a Ambisonic order 4 (1 to 4) (return 0 when out of range).*/ +inline Speaker convertSpeaker_Ambi_567Order_to_Ambi1234Order (Speaker speaker_567_order) +{ + int32 idx = getSpeakerIndex (speaker_567_order, kAmbi7thOrderACN); + if (idx < 0) + return 0; + return getSpeaker (kAmbi4thOrderACN, idx); +} + //------------------------------------------------------------------------ /** Returns the speaker arrangement associated to a string representation. Returns kEmpty if no associated arrangement is known. */ @@ -690,20 +864,24 @@ inline SpeakerArrangement getSpeakerArrangementFromString (CString arrStr) return k81Cine; if (!strcmp8 (arrStr, kString81Music)) return k81Music; - if (!strcmp8 (arrStr, kString102)) - return k102; - if (!strcmp8 (arrStr, kString122)) - return k122; - if (!strcmp8 (arrStr, kString80Cube)) - return k80Cube; + if (!strcmp8 (arrStr, kString52_5)) + return k52_5; + if (!strcmp8 (arrStr, kString72_5)) + return k72_5; + if (!strcmp8 (arrStr, kString40_4)) + return k40_4; if (!strcmp8 (arrStr, kString71CineTopCenter)) return k71CineTopCenter; if (!strcmp8 (arrStr, kString71CineCenterHigh)) return k71CineCenterHigh; - if (!strcmp8 (arrStr, kString71CineFrontHigh)) - return k71CineFrontHigh; - if (!strcmp8 (arrStr, kString71CineSideHigh)) - return k71CineSideHigh; + if (!strcmp8 (arrStr, kString50_2)) + return k50_2; + if (!strcmp8 (arrStr, kString51_2)) + return k51_2; + if (!strcmp8 (arrStr, kString50_2TopSide)) + return k50_2_TS; + if (!strcmp8 (arrStr, kString51_2TopSide)) + return k51_2_TS; if (!strcmp8 (arrStr, kString71CineFullRear)) return k71CineFullRear; if (!strcmp8 (arrStr, kString90Cine)) @@ -718,12 +896,20 @@ inline SpeakerArrangement getSpeakerArrangementFromString (CString arrStr) return k50_4; if (!strcmp8 (arrStr, kString51_4)) return k51_4; - if (!strcmp8 (arrStr, kString81MPEG)) - return k81MPEG3D; + if (!strcmp8 (arrStr, kString50_4_1)) + return k50_4_1; + if (!strcmp8 (arrStr, kString51_4_1)) + return k51_4_1; + if (!strcmp8 (arrStr, kString41_4_1)) + return k41_4_1; if (!strcmp8 (arrStr, kString70_2)) return k70_2; if (!strcmp8 (arrStr, kString71_2)) return k71_2; + if (!strcmp8 (arrStr, kString70_3)) + return k70_3; + if (!strcmp8 (arrStr, kString72_3)) + return k72_3; if (!strcmp8 (arrStr, kString70_4)) return k70_4; if (!strcmp8 (arrStr, kString71_4)) @@ -740,30 +926,64 @@ inline SpeakerArrangement getSpeakerArrangementFromString (CString arrStr) return k90_6; if (!strcmp8 (arrStr, kString91_6)) return k91_6; - if (!strcmp8 (arrStr, kString100)) - return k100; - if (!strcmp8 (arrStr, kString101)) - return k101; - if (!strcmp8 (arrStr, kString110)) - return k110; - if (!strcmp8 (arrStr, kString111)) - return k111; + if (!strcmp8 (arrStr, kString50_5)) + return k50_5; + if (!strcmp8 (arrStr, kString51_5)) + return k51_5; + if (!strcmp8 (arrStr, kString50_6)) + return k50_6; + if (!strcmp8 (arrStr, kString51_6)) + return k51_6; if (!strcmp8 (arrStr, kString130)) return k130; if (!strcmp8 (arrStr, kString131)) return k131; - if (!strcmp8 (arrStr, kString140)) - return k140; + if (!strcmp8 (arrStr, kString60_4_4)) + return k60_4_4; if (!strcmp8 (arrStr, kString222)) return k222; if (!strcmp8 (arrStr, kString220)) return k220; + if (!strcmp8 (arrStr, kString50_5_3)) + return k50_5_3; + if (!strcmp8 (arrStr, kString51_5_3)) + return k51_5_3; + if (!strcmp8 (arrStr, kString50_2_2)) + return k50_2_2; + if (!strcmp8 (arrStr, kString50_4_2)) + return k50_4_2; + if (!strcmp8 (arrStr, kString70_4_2)) + return k70_4_2; + + if (!strcmp8 (arrStr, kString50_5_Sony)) + return k50_5_Sony; + if (!strcmp8 (arrStr, kString40_2_2)) + return k40_2_2; + if (!strcmp8 (arrStr, kString40_4_2)) + return k40_4_2; + if (!strcmp8 (arrStr, kString50_3_2)) + return k50_3_2; + if (!strcmp8 (arrStr, kString30_5_2)) + return k30_5_2; + if (!strcmp8 (arrStr, kString40_4_4)) + return k40_4_4; + if (!strcmp8 (arrStr, kString50_4_4)) + return k50_4_4; + if (!strcmp8 (arrStr, kStringAmbi1stOrder)) return kAmbi1stOrderACN; if (!strcmp8 (arrStr, kStringAmbi2cdOrder)) return kAmbi2cdOrderACN; if (!strcmp8 (arrStr, kStringAmbi3rdOrder)) return kAmbi3rdOrderACN; + if (!strcmp8 (arrStr, kStringAmbi4thOrder)) + return kAmbi4thOrderACN; + if (!strcmp8 (arrStr, kStringAmbi5thOrder)) + return kAmbi5thOrderACN; + if (!strcmp8 (arrStr, kStringAmbi6thOrder)) + return kAmbi6thOrderACN; + if (!strcmp8 (arrStr, kStringAmbi7thOrder)) + return kAmbi7thOrderACN; return kEmpty; } @@ -775,6 +995,7 @@ inline CString getSpeakerArrangementString (SpeakerArrangement arr, bool withSpe switch (arr) { case kMono: return withSpeakersName ? kStringMonoS : kStringMono; + //--- Stereo pairs--- case kStereo: return withSpeakersName ? kStringStereoS : kStringStereo; case kStereoSurround: return withSpeakersName ? kStringStereoRS : kStringStereoR; case kStereoCenter: return withSpeakersName ? kStringStereoCS : kStringStereoC; @@ -784,54 +1005,61 @@ inline CString getSpeakerArrangementString (SpeakerArrangement arr, bool withSpe case kStereoTS: return withSpeakersName ? kStringStereoTSS : kStringStereoTS; case kStereoTR: return withSpeakersName ? kStringStereoTRS : kStringStereoTR; case kStereoBF: return withSpeakersName ? kStringStereoBFS : kStringStereoBF; + + //--- --- case kCineFront: return withSpeakersName ? kStringCineFrontS : kStringCineFront; case k30Cine: return withSpeakersName ? kString30CineS : kString30Cine; - case k30Music: return withSpeakersName ? kString30MusicS : kString30Music; case k31Cine: return withSpeakersName ? kString31CineS : kString31Cine; + case k30Music: return withSpeakersName ? kString30MusicS : kString30Music; case k31Music: return withSpeakersName ? kString31MusicS : kString31Music; case k40Cine: return withSpeakersName ? kString40CineS : kString40Cine; - case k40Music: return withSpeakersName ? kString40MusicS : kString40Music; case k41Cine: return withSpeakersName ? kString41CineS : kString41Cine; + case k40Music: return withSpeakersName ? kString40MusicS : kString40Music; case k41Music: return withSpeakersName ? kString41MusicS : kString41Music; case k50: return withSpeakersName ? kString50S : kString50; case k51: return withSpeakersName ? kString51S : kString51; case k60Cine: return withSpeakersName ? kString60CineS : kString60Cine; - case k60Music: return withSpeakersName ? kString60MusicS : kString60Music; case k61Cine: return withSpeakersName ? kString61CineS : kString61Cine; + case k60Music: return withSpeakersName ? kString60MusicS : kString60Music; case k61Music: return withSpeakersName ? kString61MusicS : kString61Music; case k70Cine: return withSpeakersName ? kString70CineS : kString70Cine; - case k70Music: return withSpeakersName ? kString70MusicS : kString70Music; case k71Cine: return withSpeakersName ? kString71CineS : kString71Cine; + case k70Music: return withSpeakersName ? kString70MusicS : kString70Music; case k71Music: return withSpeakersName ? kString71MusicS : kString71Music; case k71Proximity: return withSpeakersName ? kString71ProximityS : kString71Proximity; case k80Cine: return withSpeakersName ? kString80CineS : kString80Cine; - case k80Music: return withSpeakersName ? kString80MusicS : kString80Music; case k81Cine: return withSpeakersName ? kString81CineS : kString81Cine; + case k80Music: return withSpeakersName ? kString80MusicS : kString80Music; case k81Music: return withSpeakersName ? kString81MusicS : kString81Music; - case k81MPEG3D: return withSpeakersName ? kString81MPEGS : kString81MPEG; - case k102: return withSpeakersName ? kString102S : kString102; - case k122: return withSpeakersName ? kString122S : kString122; - case k80Cube: return withSpeakersName ? kString80CubeS : kString80Cube; - case k71CineTopCenter: return withSpeakersName ? kString71CineTopCenterS : kString71CineTopCenter; - case k71CineCenterHigh: return withSpeakersName ? kString71CineCenterHighS : kString71CineCenterHigh; - case k71CineFrontHigh: return withSpeakersName ? kString71CineFrontHighS : kString71CineFrontHigh; - case k71CineSideHigh: return withSpeakersName ? kString71CineSideHighS : kString71CineSideHigh; - case k71CineFullRear: return withSpeakersName ? kString71CineFullRearS : kString71CineFullRear; - case k90Cine: return withSpeakersName ? kString90CineS : kString90Cine; + case k71CineFullRear: return withSpeakersName ? kString71CineFullRearS : kString71CineFullRear; + case k90Cine: return withSpeakersName ? kString90CineS : kString90Cine; case k91Cine: return withSpeakersName ? kString91CineS : kString91Cine; - case k100Cine: return withSpeakersName ? kString100CineS : kString100Cine; + case k100Cine: return withSpeakersName ? kString100CineS : kString100Cine; case k101Cine: return withSpeakersName ? kString101CineS : kString101Cine; - case k100: return withSpeakersName ? kString100S : kString100; - case k101: return withSpeakersName ? kString101S : kString101; - case k110: return withSpeakersName ? kString110S : kString110; - case k111: return withSpeakersName ? kString111S : kString111; + //---With Tops --- + case k71CineTopCenter: return withSpeakersName ? kString71CineTopCenterS : kString71CineTopCenter; + case k71CineCenterHigh: return withSpeakersName ? kString71CineCenterHighS : kString71CineCenterHigh; + case k50_2_TS: return withSpeakersName ? kString50_2TopSideS : kString50_2TopSide; + case k51_2_TS: return withSpeakersName ? kString51_2TopSideS : kString51_2TopSide; + + case k40_4: return withSpeakersName ? kString40_4S : kString40_4; + case k50_2: return withSpeakersName ? kString50_2S : kString50_2; + case k51_2: return withSpeakersName ? kString51_2S : kString51_2; case k50_4: return withSpeakersName ? kString50_4S : kString50_4; case k51_4: return withSpeakersName ? kString51_4S : kString51_4; + case k50_5: return withSpeakersName ? kString50_5S : kString50_5; + case k51_5: return withSpeakersName ? kString51_5S : kString51_5; + case k52_5: return withSpeakersName ? kString52_5S : kString52_5; + case k50_6: return withSpeakersName ? kString50_6S : kString50_6; + case k51_6: return withSpeakersName ? kString51_6S : kString51_6; case k70_2: return withSpeakersName ? kString70_2S : kString70_2; case k71_2: return withSpeakersName ? kString71_2S : kString71_2; + case k70_3: return withSpeakersName ? kString70_3S : kString70_3; + case k72_3: return withSpeakersName ? kString72_3S : kString72_3; case k70_4: return withSpeakersName ? kString70_4S : kString70_4; case k71_4: return withSpeakersName ? kString71_4S : kString71_4; + case k72_5: return withSpeakersName ? kString72_5S : kString72_5; case k70_6: return withSpeakersName ? kString70_6S : kString70_6; case k71_6: return withSpeakersName ? kString71_6S : kString71_6; case k90_4: return withSpeakersName ? kString90_4S : kString90_4; @@ -840,19 +1068,44 @@ inline CString getSpeakerArrangementString (SpeakerArrangement arr, bool withSpe case k91_6: return withSpeakersName ? kString91_6S : kString91_6; case k130: return withSpeakersName ? kString130S : kString130; case k131: return withSpeakersName ? kString131S : kString131; - case k140: return withSpeakersName ? kString140S : kString140; - case k222: return withSpeakersName ? kString222S : kString222; + + //--- With Tops and Bottoms --- + case k41_4_1: return withSpeakersName ? kString41_4_1S : kString41_4_1; + case k50_4_1: return withSpeakersName ? kString50_4_1S : kString50_4_1; + case k51_4_1: return withSpeakersName ? kString51_4_1S : kString51_4_1; + case k50_5_3: return withSpeakersName ? kString50_5_3S : kString50_5_3; + case k51_5_3: return withSpeakersName ? kString51_5_3S : kString51_5_3; + case k50_2_2: return withSpeakersName ? kString50_2_2S : kString50_2_2; + case k50_4_2: return withSpeakersName ? kString50_4_2S : kString50_4_2; + case k60_4_4: return withSpeakersName ? kString60_4_4S : kString60_4_4; + case k70_4_2: return withSpeakersName ? kString70_4_2S : kString70_4_2; + + case k50_5_Sony: return withSpeakersName ? kString50_5_SonyS : kString50_5_Sony; + case k40_2_2: return withSpeakersName ? kString40_2_2S : kString40_2_2; + case k40_4_2: return withSpeakersName ? kString40_4_2S : kString40_4_2; + case k50_3_2: return withSpeakersName ? kString50_3_2S : kString50_3_2; + case k30_5_2: return withSpeakersName ? kString30_5_2S : kString30_5_2; + case k40_4_4: return withSpeakersName ? kString40_4_4S : kString40_4_4; + case k50_4_4: return withSpeakersName ? kString50_4_4S : kString50_4_4; + case k220: return withSpeakersName ? kString220S : kString220; - break; + case k222: return withSpeakersName ? kString222S : kString222; } - + //--- Ambisonics --- if (arr == kAmbi1stOrderACN) return withSpeakersName ? kStringAmbi1stOrderS : kStringAmbi1stOrder; if (arr == kAmbi2cdOrderACN) return withSpeakersName ? kStringAmbi2cdOrderS : kStringAmbi2cdOrder; if (arr == kAmbi3rdOrderACN) return withSpeakersName ? kStringAmbi3rdOrderS : kStringAmbi3rdOrder; - + if (arr == kAmbi4thOrderACN) + return withSpeakersName ? kStringAmbi4thOrderS : kStringAmbi4thOrder; + if (arr == kAmbi5thOrderACN) + return withSpeakersName ? kStringAmbi5thOrderS : kStringAmbi5thOrder; + if (arr == kAmbi6thOrderACN) + return withSpeakersName ? kStringAmbi6thOrderS : kStringAmbi6thOrder; + if (arr == kAmbi7thOrderACN) + return withSpeakersName ? kStringAmbi7thOrderS : kStringAmbi7thOrder; return kStringEmpty; } @@ -955,6 +1208,24 @@ inline CString getSpeakerShortName (const SpeakerArrangement& arr, int32 index) return "14"; if (speaker == kSpeakerACN15) return "15"; + if (speaker == kSpeakerACN16) + return "16"; + if (speaker == kSpeakerACN17) + return "17"; + if (speaker == kSpeakerACN18) + return "18"; + if (speaker == kSpeakerACN19) + return "19"; + if (speaker == kSpeakerACN20) + return "20"; + if (speaker == kSpeakerACN21) + return "21"; + if (speaker == kSpeakerACN22) + return "22"; + if (speaker == kSpeakerACN23) + return "23"; + if (speaker == kSpeakerACN24) + return "24"; if (speaker == kSpeakerTsl) return "Tsl"; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vsttypes.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vsttypes.h index 15c1dbe..081177f 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vsttypes.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst/vsttypes.h @@ -22,19 +22,25 @@ namespace Steinberg { namespace Vst { //------------------------------------------------------------------------ -/** VST3 SDK Version */ +/** VST 3 SDK Version */ #ifndef kVstVersionString -#define kVstVersionString "VST 3.7.2" ///< SDK version for PClassInfo2 +#define kVstVersionString "VST 3.7.8" ///< SDK version for PClassInfo2 #endif #define kVstVersionMajor 3 #define kVstVersionMinor 7 -#define kVstVersionSub 2 +#define kVstVersionSub 8 #define VST_VERSION ((kVstVersionMajor << 16) | (kVstVersionMinor << 8) | kVstVersionSub) // Versions History which allows to write such code: // #if VST_VERSION >= VST_3_6_5_VERSION +#define VST_3_7_8_VERSION 0x030708 +#define VST_3_7_7_VERSION 0x030707 +#define VST_3_7_6_VERSION 0x030706 +#define VST_3_7_5_VERSION 0x030705 +#define VST_3_7_4_VERSION 0x030704 +#define VST_3_7_3_VERSION 0x030703 #define VST_3_7_2_VERSION 0x030702 #define VST_3_7_1_VERSION 0x030701 #define VST_3_7_0_VERSION 0x030700 @@ -100,6 +106,41 @@ typedef uint64 Speaker; ///< Bit for one speaker /*@}*/ +static SMTG_CONSTEXPR const FIDString SDKVersionString = kVstVersionString; + +static SMTG_CONSTEXPR const uint32 SDKVersionMajor = kVstVersionMajor; +static SMTG_CONSTEXPR const uint32 SDKVersionMinor = kVstVersionMinor; +static SMTG_CONSTEXPR const uint32 SDKVersionSub = kVstVersionSub; + +static SMTG_CONSTEXPR const uint32 SDKVersion = + ((SDKVersionMajor << 16) | (SDKVersionMinor << 8) | SDKVersionSub); + +// Versions History which allows to write such code: +// if constexpr (SDKVersion >= SDKVersion_3_6_5) { ... } +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_7 = VST_3_7_7_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_6 = VST_3_7_6_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_5 = VST_3_7_5_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_4 = VST_3_7_4_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_3 = VST_3_7_3_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_2 = VST_3_7_2_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_1 = VST_3_7_1_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_7_0 = VST_3_7_0_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_14 = VST_3_6_14_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_13 = VST_3_6_13_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_12 = VST_3_6_12_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_11 = VST_3_6_11_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_10 = VST_3_6_10_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_9 = VST_3_6_9_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_8 = VST_3_6_8_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_7 = VST_3_6_7_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_6 = VST_3_6_6_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_5 = VST_3_6_5_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_6_0 = VST_3_6_0_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_5_0 = VST_3_5_0_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_1_0 = VST_3_1_0_VERSION; +static SMTG_CONSTEXPR const uint32 SDKVersion_3_0_0 = VST_3_0_0_VERSION; + //------------------------------------------------------------------------ } // namespace Vst } // namespace Steinberg + diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/LICENSE.txt b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/LICENSE.txt index fd2eb70..f9c531e 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/LICENSE.txt +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/LICENSE.txt @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/README.md b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/README.md index acf33ef..e7c8a93 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/README.md +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/README.md @@ -2,12 +2,12 @@ Here are located: -- helper classes implementing VST3 Interfaces -- samples of VST3 Hosting and VST3 Plug-Ins -- AAX Wrapper -- AU Wrapper -- AUv3 Wrapper -- VST2 Wrapper +- helper classes implementing **VST 3** Interfaces +- samples of **VST 3** Hosting and **VST 3** plug-ins +- **AAX** Wrapper +- **AU** Wrapper +- **AUv3** Wrapper +- **VST 2** Wrapper - InterAppAudio ## License & Usage guidelines @@ -15,4 +15,4 @@ Here are located: More details are found at [VST 3 SDK public_sdk License](https://forums.steinberg.net/t/vst-3-sdk-public-sdk-license/695592) ---- -Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk) \ No newline at end of file +Return to [VST 3 SDK](https://github.com/steinbergmedia/vst3sdk) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp new file mode 100644 index 0000000..2d568ae --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/samples/vst-utilities/moduleinfotool/source/main.cpp @@ -0,0 +1,439 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfotool +// Filename : public.sdk/samples/vst-utilities/moduleinfotool/main.cpp +// Created by : Steinberg, 12/2021 +// Description : main entry point +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "base/source/fcommandline.h" +#include "pluginterfaces/base/fplatform.h" +#include "pluginterfaces/base/iplugincompatibility.h" +#include "pluginterfaces/vst/vsttypes.h" +#include "public.sdk/source/common/memorystream.h" +#include "public.sdk/source/common/readfile.h" +#include "public.sdk/source/vst/hosting/module.h" +#include "public.sdk/source/vst/moduleinfo/moduleinfocreator.h" +#include "public.sdk/source/vst/moduleinfo/moduleinfoparser.h" +#include "public.sdk/source/vst/utility/stringconvert.h" +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace ModuleInfoTool { +namespace { + +//------------------------------------------------------------------------ +constexpr auto BUILD_INFO = "moduleinfotool 1.0.0 [Built " __DATE__ "]"; + +//------------------------------------------------------------------------ +//-- Options +constexpr auto optHelp = "help"; +constexpr auto optCreate = "create"; +constexpr auto optValidate = "validate"; +constexpr auto optModuleVersion = "version"; +constexpr auto optModulePath = "path"; +constexpr auto optInfoPath = "infopath"; +constexpr auto optModuleCompatPath = "compat"; +constexpr auto optOutputPath = "output"; + +//------------------------------------------------------------------------ +void printUsage (std::ostream& s) +{ + s << "Usage:\n"; + s << " moduleinfotool -create -version VERSION -path MODULE_PATH [-compat PATH -output PATH]\n"; + s << " moduleinfotool -validate -path MODULE_PATH [-infopath PATH]\n"; +} + +//------------------------------------------------------------------------ +std::optional openAndParseCompatJSON (const std::string& path) +{ + auto data = readFile (path); + if (data.empty ()) + { + std::cout << "Can not read '" << path << "'\n"; + printUsage (std::cout); + return {}; + } + std::stringstream error; + auto result = ModuleInfoLib::parseCompatibilityJson (data, &error); + if (!result) + { + std::cout << "Can not parse '" << path << "'\n"; + std::cout << error.str (); + printUsage (std::cout); + return {}; + } + return result; +} + +//------------------------------------------------------------------------ +std::optional loadCompatibilityFromModule (const VST3::Hosting::Module& module) +{ + const auto& factory = module.getFactory(); + const auto& infos = factory.classInfos(); + + const auto iter = std::find_if (infos.begin(), infos.end(), [&] (const auto& info) + { + return info.category() == kPluginCompatibilityClass; + }); + + if (iter == infos.end()) + return {}; + + const auto compatibility = factory.createInstance (iter->ID()); + + if (compatibility == nullptr) + return {}; + + Steinberg::MemoryStream stream; + + if (compatibility->getCompatibilityJSON (&stream) != kResultOk) + return {}; + + const std::string_view streamView (stream.getData(), stream.getSize()); + + return ModuleInfoLib::parseCompatibilityJson (streamView, nullptr); +} + +//------------------------------------------------------------------------ +int createJSON (const std::optional& compat, + const std::string& modulePath, const std::string& moduleVersion, + std::ostream& outStream) +{ + std::string errorStr; + auto module = VST3::Hosting::Module::create (modulePath, errorStr); + if (!module) + { + std::cout << errorStr; + return 1; + } + auto moduleInfo = ModuleInfoLib::createModuleInfo (*module, false); + if (compat) + moduleInfo.compatibility = *compat; + else if (auto loaded = loadCompatibilityFromModule (*module)) + moduleInfo.compatibility = *loaded; + + moduleInfo.version = moduleVersion; + + std::stringstream output; + ModuleInfoLib::outputJson (moduleInfo, output); + auto str = output.str (); + outStream << str; + return 0; +} + +//------------------------------------------------------------------------ +struct validate_error : std::exception +{ + validate_error (const std::string& str) : str (str) {} + const char* what () const noexcept override { return str.data (); } + +private: + std::string str; +}; + +//------------------------------------------------------------------------ +void validate (const ModuleInfo& moduleInfo, const VST3::Hosting::Module& module) +{ + auto factory = module.getFactory (); + auto factoryInfo = factory.info (); + auto classInfoList = factory.classInfos (); + auto snapshotList = module.getSnapshots (module.getPath ()); + + if (factoryInfo.vendor () != moduleInfo.factoryInfo.vendor) + throw validate_error ("factoryInfo.vendor different: " + moduleInfo.factoryInfo.vendor); + if (factoryInfo.url () != moduleInfo.factoryInfo.url) + throw validate_error ("factoryInfo.url different: " + moduleInfo.factoryInfo.url); + if (factoryInfo.email () != moduleInfo.factoryInfo.email) + throw validate_error ("factoryInfo.email different: " + moduleInfo.factoryInfo.email); + if (factoryInfo.flags () != moduleInfo.factoryInfo.flags) + throw validate_error ("factoryInfo.flags different: " + + std::to_string (moduleInfo.factoryInfo.flags)); + + for (const auto& ci : moduleInfo.classes) + { + auto cid = VST3::UID::fromString (ci.cid); + if (!cid) + throw validate_error ("could not parse class UID: " + ci.cid); + auto it = std::find_if (classInfoList.begin (), classInfoList.end (), + [&] (const auto& el) { return el.ID () == *cid; }); + if (it == classInfoList.end ()) + throw validate_error ("cannot find CID in class list: " + ci.cid); + if (it->name () != ci.name) + throw validate_error ("class name different: " + ci.name); + if (it->category () != ci.category) + throw validate_error ("class category different: " + ci.category); + if (it->vendor () != ci.vendor) + throw validate_error ("class vendor different: " + ci.vendor); + if (it->version () != ci.version) + throw validate_error ("class version different: " + ci.version); + if (it->sdkVersion () != ci.sdkVersion) + throw validate_error ("class sdkVersion different: " + ci.sdkVersion); + if (it->subCategories () != ci.subCategories) + throw validate_error ("class subCategories different: " /* + ci.subCategories*/); + if (it->cardinality () != ci.cardinality) + throw validate_error ("class cardinality different: " + + std::to_string (ci.cardinality)); + if (it->classFlags () != ci.flags) + throw validate_error ("class flags different: " + std::to_string (ci.flags)); + classInfoList.erase (it); + + auto snapshotListIt = std::find_if (snapshotList.begin (), snapshotList.end (), + [&] (const auto& el) { return el.uid == *cid; }); + if (snapshotListIt == snapshotList.end () && !ci.snapshots.empty ()) + throw validate_error ("cannot find snapshots for: " + ci.cid); + for (const auto& snapshot : ci.snapshots) + { + auto snapshotIt = std::find_if ( + snapshotListIt->images.begin (), snapshotListIt->images.end (), + [&] (const auto& el) { return el.scaleFactor == snapshot.scaleFactor; }); + if (snapshotIt == snapshotListIt->images.end ()) + throw validate_error ("cannot find snapshots for scale factor: " + + std::to_string (snapshot.scaleFactor)); + std::string_view path (snapshotIt->path); + if (path.find (module.getPath ()) == 0) + path.remove_prefix (module.getPath ().size () + 1); + if (path != snapshot.path) + throw validate_error ("cannot find snapshots with path: " + snapshot.path); + snapshotListIt->images.erase (snapshotIt); + } + if (snapshotListIt != snapshotList.end () && !snapshotListIt->images.empty ()) + { + std::string errorStr = "Missing Snapshots in moduleinfo:\n"; + for (const auto& s : snapshotListIt->images) + { + errorStr += s.path + '\n'; + } + throw validate_error (errorStr); + } + if (snapshotListIt != snapshotList.end ()) + snapshotList.erase (snapshotListIt); + } + if (!classInfoList.empty ()) + throw validate_error ("Missing classes in moduleinfo"); + if (!snapshotList.empty ()) + throw validate_error ("Missing snapshots in moduleinfo"); +} + +//------------------------------------------------------------------------ +int validate (const std::string& modulePath, std::string infoJsonPath) +{ + if (infoJsonPath.empty ()) + { + auto path = VST3::Hosting::Module::getModuleInfoPath (modulePath); + if (!path) + { + std::cerr << "Module does not contain a moduleinfo.json: '" << modulePath << "'" + << '\n'; + return 1; + } + infoJsonPath = *path; + } + + auto data = readFile (infoJsonPath); + if (data.empty ()) + { + std::cerr << "Empty or non existing file: '" << infoJsonPath << "'" << '\n'; + printUsage (std::cout); + return 1; + } + auto moduleInfo = ModuleInfoLib::parseJson (data, &std::cerr); + if (moduleInfo) + { + std::string errorStr; + auto module = VST3::Hosting::Module::create (modulePath, errorStr); + if (!module) + { + std::cerr << errorStr; + printUsage (std::cout); + return 1; + } + try + { + validate (*moduleInfo, *module); + } + catch (const std::exception& exc) + { + std::cerr << "Error:\n" << exc.what () << '\n'; + printUsage (std::cout); + return 1; + } + return 0; + } + printUsage (std::cout); + return 1; +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +int run (int argc, char* argv[]) +{ + // parse command line + CommandLine::Descriptions desc; + CommandLine::VariablesMap valueMap; + CommandLine::FilesVector files; + + using Description = CommandLine::Description; + desc.addOptions ( + BUILD_INFO, + { + {optCreate, "Create moduleinfo", Description::kBool}, + {optValidate, "Validate moduleinfo", Description::kBool}, + {optModuleVersion, "Module version", Description::kString}, + {optModulePath, "Path to module", Description::kString}, + {optInfoPath, "Path to moduleinfo.json", Description::kString}, + {optModuleCompatPath, "Path to compatibility.json", Description::kString}, + {optOutputPath, "Write json to file instead of stdout", Description::kString}, + {optHelp, "Print help", Description::kBool}, + }); + CommandLine::parse (argc, argv, desc, valueMap, &files); + + bool isCreate = valueMap.count (optCreate) != 0 && valueMap.count (optModuleVersion) != 0 && + valueMap.count (optModulePath) != 0; + bool isValidate = valueMap.count (optValidate) && valueMap.count (optModulePath) != 0; + + if (valueMap.hasError () || valueMap.count (optHelp) || !(isCreate || isValidate)) + { + std::cout << '\n' << desc << '\n'; + printUsage (std::cout); + return 1; + } + + int result = 1; + + const auto& modulePath = valueMap[optModulePath]; + if (isCreate) + { + auto* outputStream = &std::cout; + std::optional compat; + if (valueMap.count (optModuleCompatPath) != 0) + { + const auto& compatPath = valueMap[optModuleCompatPath]; + compat = openAndParseCompatJSON (compatPath); + if (!compat) + return 1; + } + bool writeToFile = false; + if (valueMap.count (optOutputPath) != 0) + { + writeToFile = true; +#if SMTG_OS_WINDOWS + auto tmp = VST3::StringConvert::convert (valueMap[optOutputPath]); + auto outputFile = reinterpret_cast (tmp.data ()); +#else + auto outputFile = valueMap[optOutputPath]; +#endif + auto ostream = new std::ofstream (outputFile); + + if (ostream->is_open ()) + outputStream = ostream; + else + { + std::cout << "Cannot create output file: " << valueMap[optOutputPath] << '\n'; + return result; + } + } + const auto& moduleVersion = valueMap[optModuleVersion]; + result = createJSON (compat, modulePath, moduleVersion, *outputStream); + if (writeToFile) + delete outputStream; + } + else if (isValidate) + { + std::string moduleInfoJsonPath; + if (valueMap.count (optInfoPath) != 0) + moduleInfoJsonPath = valueMap[optInfoPath]; + result = validate (modulePath, moduleInfoJsonPath); + } + return result; +} + +//------------------------------------------------------------------------ +} // ModuleInfoTool +} // Steinberg + +//------------------------------------------------------------------------ +#if SMTG_OS_WINDOWS +//------------------------------------------------------------------------ +#include +#include + +//------------------------------------------------------------------------ +using Utf8String = std::string; + +//------------------------------------------------------------------------ +using Utf8Args = std::vector; +Utf8Args toUtf8Args (int argc, wchar_t* wargv[]) +{ + Utf8Args utf8Args; + for (int i = 0; i < argc; i++) + { + auto str = reinterpret_cast(wargv[i]); + utf8Args.push_back (VST3::StringConvert::convert (str)); + } + + return utf8Args; +} + +//------------------------------------------------------------------------ +using Utf8ArgPtrs = std::vector; +Utf8ArgPtrs toUtf8ArgPtrs (Utf8Args& utf8Args) +{ + Utf8ArgPtrs utf8ArgPtrs; + for (auto& el : utf8Args) + { + utf8ArgPtrs.push_back (el.data ()); + } + + return utf8ArgPtrs; +} + +//------------------------------------------------------------------------ +int wmain (int argc, wchar_t* wargv[]) +{ + Utf8Args utf8Args = toUtf8Args (argc, wargv); + Utf8ArgPtrs utf8ArgPtrs = toUtf8ArgPtrs (utf8Args); + + char** argv = &(utf8ArgPtrs.at (0)); + return Steinberg::ModuleInfoTool::run (argc, argv); +} +#else +int main (int argc, char* argv[]) +{ + return Steinberg::ModuleInfoTool::run (argc, argv); +} +#endif diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.cpp index 92e82fa..5b6c245 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -36,7 +36,7 @@ #include "memorystream.h" #include "pluginterfaces/base/futils.h" -#include +#include namespace Steinberg { diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.h index e080c3f..489ace5 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/memorystream.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.cpp index 1a5ff33..02dd9fd 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -51,6 +51,7 @@ CPluginView::CPluginView (const ViewRect* _rect) //------------------------------------------------------------------------ CPluginView::~CPluginView () { + setFrame (nullptr); } //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.h index 417c532..ce131b9 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/pluginview.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -51,7 +51,7 @@ class CPluginView : public FObject, public IPlugView public: //------------------------------------------------------------------------ CPluginView (const ViewRect* rect = nullptr); - virtual ~CPluginView (); + ~CPluginView () SMTG_OVERRIDE; /** Returns its current frame rectangle. */ const ViewRect& getRect () const { return rect; } @@ -109,7 +109,7 @@ class CPluginView : public FObject, public IPlugView protected: ViewRect rect; void* systemWindow {nullptr}; - IPlugFrame* plugFrame {nullptr}; + IPtr plugFrame; }; } // namespace diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.cpp new file mode 100644 index 0000000..5f60457 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : readfile +// Filename : public.sdk/source/common/readfile.cpp +// Created by : Steinberg, 3/2023 +// Description : read file routine +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "readfile.h" +#include "public.sdk/source/vst/utility/stringconvert.h" +#include "pluginterfaces/base/fplatform.h" +#include +#include + +namespace Steinberg { + +//------------------------------------------------------------------------ +std::string readFile (const std::string& path) +{ +#if SMTG_OS_WINDOWS + auto u16Path = VST3::StringConvert::convert (path); + std::ifstream file (reinterpret_cast (u16Path.data ()), + std::ios_base::in | std::ios_base::binary); +#else + std::ifstream file (path, std::ios_base::in | std::ios_base::binary); +#endif + if (!file.is_open ()) + return {}; + +#if SMTG_CPP17 + auto size = file.seekg (0, std::ios_base::end).tellg (); + file.seekg (0, std::ios_base::beg); + std::string data; + data.resize (size); + file.read (data.data (), data.size ()); + if (file.bad ()) + return {}; + return data; +#else + std::stringstream buffer; + buffer << file.rdbuf (); + return buffer.str (); +#endif +} + +//------------------------------------------------------------------------ +} // namespace Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.h new file mode 100644 index 0000000..fdc7105 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/common/readfile.h @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format SMTGSequencer +// Project : VST SDK +// +// Category : readfile +// Filename : public.sdk/source/common/readfile.h +// Created by : Steinberg, 3/2023 +// Description : read file routine +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include + +namespace Steinberg { + +//------------------------------------------------------------------------ +/** Reads entire file content +\ingroup sdkBase + +Returns entire file content at the given path +\endcode +*/ +std::string readFile (const std::string& path); + +//------------------------------------------------------------------------ + +} // namespace Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.cpp index 30a2e31..e04987e 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.cpp @@ -4,37 +4,38 @@ // Category : Helpers // Filename : public.sdk/source/vst/hosting/hostclasses.cpp // Created by : Steinberg, 03/05/2008. -// Description : VST 3 hostclasses, example implementations for IHostApplication, IAttributeList and IMessage +// Description : VST 3 hostclasses, example impl. for IHostApplication, IAttributeList and IMessage // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, +// +// * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation +// this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Steinberg Media Technologies nor the names of its -// contributors may be used to endorse or promote products derived from this +// contributors may be used to endorse or promote products derived from this // software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. //----------------------------------------------------------------------------- #include "hostclasses.h" +#include "public.sdk/source/vst/utility/stringconvert.h" #include @@ -46,31 +47,34 @@ HostApplication::HostApplication () { FUNKNOWN_CTOR - mPlugInterfaceSupport = owned (NEW PlugInterfaceSupport); + mPlugInterfaceSupport = owned (new PlugInterfaceSupport); } //----------------------------------------------------------------------------- tresult PLUGIN_API HostApplication::getName (String128 name) { - String str ("My VST3 HostApplication"); - str.copyTo16 (name, 0, 127); - return kResultTrue; + return VST3::StringConvert::convert ("My VST3 HostApplication", name) ? kResultTrue : + kInternalError; } //----------------------------------------------------------------------------- tresult PLUGIN_API HostApplication::createInstance (TUID cid, TUID _iid, void** obj) { - FUID classID (FUID::fromTUID (cid)); - FUID interfaceID (FUID::fromTUID (_iid)); - if (classID == IMessage::iid && interfaceID == IMessage::iid) + if (FUnknownPrivate::iidEqual (cid, IMessage::iid) && + FUnknownPrivate::iidEqual (_iid, IMessage::iid)) { *obj = new HostMessage; return kResultTrue; } - else if (classID == IAttributeList::iid && interfaceID == IAttributeList::iid) + if (FUnknownPrivate::iidEqual (cid, IAttributeList::iid) && + FUnknownPrivate::iidEqual (_iid, IAttributeList::iid)) { - *obj = new HostAttributeList; - return kResultTrue; + if (auto al = HostAttributeList::make ()) + { + *obj = al.take (); + return kResultTrue; + } + return kOutOfMemory; } *obj = nullptr; return kResultFalse; @@ -106,17 +110,12 @@ uint32 PLUGIN_API HostApplication::release () //----------------------------------------------------------------------------- IMPLEMENT_FUNKNOWN_METHODS (HostMessage, IMessage, IMessage::iid) //----------------------------------------------------------------------------- -HostMessage::HostMessage () : messageId (nullptr), attributeList (nullptr) -{ - FUNKNOWN_CTOR -} +HostMessage::HostMessage () {FUNKNOWN_CTOR} //----------------------------------------------------------------------------- -HostMessage::~HostMessage () +HostMessage::~HostMessage () noexcept { setMessageID (nullptr); - if (attributeList) - attributeList->release (); FUNKNOWN_DTOR } @@ -144,38 +143,51 @@ void PLUGIN_API HostMessage::setMessageID (const char* mid) IAttributeList* PLUGIN_API HostMessage::getAttributes () { if (!attributeList) - attributeList = new HostAttributeList; + attributeList = HostAttributeList::make (); return attributeList; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -class HostAttribute +struct HostAttributeList::Attribute { -public: - enum Type + enum class Type { + kUninitialized, kInteger, kFloat, kString, kBinary }; + Attribute () = default; - HostAttribute (int64 value) : size (0), type (kInteger) { v.intValue = value; } - HostAttribute (double value) : size (0), type (kFloat) { v.floatValue = value; } - /** size is in code unit (count of TChar) */ - HostAttribute (const TChar* value, uint32 sizeInCodeUnit) : size (sizeInCodeUnit), type (kString) + Attribute (int64 value) : type (Type::kInteger) { v.intValue = value; } + Attribute (double value) : type (Type::kFloat) { v.floatValue = value; } + /* size is in code unit (count of TChar) */ + Attribute (const TChar* value, uint32 sizeInCodeUnit) + : size (sizeInCodeUnit), type (Type::kString) { v.stringValue = new TChar[sizeInCodeUnit]; memcpy (v.stringValue, value, sizeInCodeUnit * sizeof (TChar)); } - HostAttribute (const void* value, uint32 sizeInBytes) : size (sizeInBytes), type (kBinary) + Attribute (const void* value, uint32 sizeInBytes) : size (sizeInBytes), type (Type::kBinary) { v.binaryValue = new char[sizeInBytes]; memcpy (v.binaryValue, value, sizeInBytes); } - ~HostAttribute () + Attribute (Attribute&& o) { *this = std::move (o); } + Attribute& operator= (Attribute&& o) + { + v = o.v; + size = o.size; + type = o.type; + o.size = 0; + o.type = Type::kUninitialized; + o.v = {}; + return *this; + } + ~Attribute () noexcept { if (size) delete[] v.binaryValue; @@ -183,7 +195,7 @@ class HostAttribute int64 intValue () const { return v.intValue; } double floatValue () const { return v.floatValue; } - /** sizeInCodeUnit is in code unit (count of TChar) */ + /* sizeInCodeUnit is in code unit (count of TChar) */ const TChar* stringValue (uint32& sizeInCodeUnit) { sizeInCodeUnit = size; @@ -197,7 +209,7 @@ class HostAttribute Type getType () const { return type; } -protected: +private: union v { int64 intValue; @@ -205,58 +217,48 @@ class HostAttribute TChar* stringValue; char* binaryValue; } v; - uint32 size; - Type type; + uint32 size {0}; + Type type {Type::kUninitialized}; }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- IMPLEMENT_FUNKNOWN_METHODS (HostAttributeList, IAttributeList, IAttributeList::iid) + //----------------------------------------------------------------------------- -HostAttributeList::HostAttributeList () +IPtr HostAttributeList::make () { - FUNKNOWN_CTOR + return owned (new HostAttributeList); } //----------------------------------------------------------------------------- -HostAttributeList::~HostAttributeList () -{ - auto it = list.rbegin (); - while (it != list.rend ()) - { - delete it->second; - it++; - } - FUNKNOWN_DTOR -} +HostAttributeList::HostAttributeList () {FUNKNOWN_CTOR} //----------------------------------------------------------------------------- -void HostAttributeList::removeAttrID (AttrID aid) +HostAttributeList::~HostAttributeList () noexcept { - auto it = list.find (aid); - if (it != list.end ()) - { - delete it->second; - list.erase (it); - } + FUNKNOWN_DTOR } //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::setInt (AttrID aid, int64 value) { - removeAttrID (aid); - list[aid] = new HostAttribute (value); + if (!aid) + return kInvalidArgument; + list[aid] = Attribute (value); return kResultTrue; } //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::getInt (AttrID aid, int64& value) { + if (!aid) + return kInvalidArgument; auto it = list.find (aid); - if (it != list.end () && it->second) + if (it != list.end () && it->second.getType () == Attribute::Type::kInteger) { - value = it->second->intValue (); + value = it->second.intValue (); return kResultTrue; } return kResultFalse; @@ -265,18 +267,21 @@ tresult PLUGIN_API HostAttributeList::getInt (AttrID aid, int64& value) //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::setFloat (AttrID aid, double value) { - removeAttrID (aid); - list[aid] = new HostAttribute (value); + if (!aid) + return kInvalidArgument; + list[aid] = Attribute (value); return kResultTrue; } //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::getFloat (AttrID aid, double& value) { + if (!aid) + return kInvalidArgument; auto it = list.find (aid); - if (it != list.end () && it->second) + if (it != list.end () && it->second.getType () == Attribute::Type::kFloat) { - value = it->second->floatValue (); + value = it->second.floatValue (); return kResultTrue; } return kResultFalse; @@ -285,20 +290,24 @@ tresult PLUGIN_API HostAttributeList::getFloat (AttrID aid, double& value) //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::setString (AttrID aid, const TChar* string) { - removeAttrID (aid); + if (!aid) + return kInvalidArgument; // + 1 for the null-terminate - list[aid] = new HostAttribute (string, String (string).length () + 1); + auto length = tstrlen (string) + 1; + list[aid] = Attribute (string, length); return kResultTrue; } //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::getString (AttrID aid, TChar* string, uint32 sizeInBytes) { + if (!aid) + return kInvalidArgument; auto it = list.find (aid); - if (it != list.end () && it->second) + if (it != list.end () && it->second.getType () == Attribute::Type::kString) { uint32 sizeInCodeUnit = 0; - const TChar* _string = it->second->stringValue (sizeInCodeUnit); + const TChar* _string = it->second.stringValue (sizeInCodeUnit); memcpy (string, _string, std::min (sizeInCodeUnit * sizeof (TChar), sizeInBytes)); return kResultTrue; } @@ -308,22 +317,26 @@ tresult PLUGIN_API HostAttributeList::getString (AttrID aid, TChar* string, uint //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::setBinary (AttrID aid, const void* data, uint32 sizeInBytes) { - removeAttrID (aid); - list[aid] = new HostAttribute (data, sizeInBytes); + if (!aid) + return kInvalidArgument; + list[aid] = Attribute (data, sizeInBytes); return kResultTrue; } //----------------------------------------------------------------------------- tresult PLUGIN_API HostAttributeList::getBinary (AttrID aid, const void*& data, uint32& sizeInBytes) { + if (!aid) + return kInvalidArgument; auto it = list.find (aid); - if (it != list.end () && it->second) + if (it != list.end () && it->second.getType () == Attribute::Type::kBinary) { - data = it->second->binaryValue (sizeInBytes); + data = it->second.binaryValue (sizeInBytes); return kResultTrue; } sizeInBytes = 0; return kResultFalse; } -} -} // namespace + +} // Vst +} // Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.h index 7b105a2..4a254ca 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/hostclasses.h @@ -4,32 +4,32 @@ // Category : Helpers // Filename : public.sdk/source/vst/hosting/hostclasses.h // Created by : Steinberg, 03/05/2008. -// Description : VST 3 hostclasses, example implementations for IHostApplication, IAttributeList and IMessage +// Description : VST 3 hostclasses, example impl. for IHostApplication, IAttributeList and IMessage // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, +// +// * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation +// this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Steinberg Media Technologies nor the names of its -// contributors may be used to endorse or promote products derived from this +// contributors may be used to endorse or promote products derived from this // software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED // OF THE POSSIBILITY OF SUCH DAMAGE. //----------------------------------------------------------------------------- @@ -37,9 +37,10 @@ #pragma once #include "public.sdk/source/vst/hosting/pluginterfacesupport.h" -#include "base/source/fstring.h" #include "pluginterfaces/vst/ivsthostapplication.h" #include +#include +#include namespace Steinberg { namespace Vst { @@ -52,64 +53,66 @@ class HostApplication : public IHostApplication { public: HostApplication (); - virtual ~HostApplication () { FUNKNOWN_DTOR } + virtual ~HostApplication () noexcept {FUNKNOWN_DTOR} //--- IHostApplication --------------- - tresult PLUGIN_API getName (String128 name) SMTG_OVERRIDE; - tresult PLUGIN_API createInstance (TUID cid, TUID _iid, void** obj) SMTG_OVERRIDE; + tresult PLUGIN_API getName (String128 name) override; + tresult PLUGIN_API createInstance (TUID cid, TUID _iid, void** obj) override; DECLARE_FUNKNOWN_METHODS PlugInterfaceSupport* getPlugInterfaceSupport () const { return mPlugInterfaceSupport; } -protected: +private: IPtr mPlugInterfaceSupport; }; -class HostAttribute; //------------------------------------------------------------------------ -/** Implementation's example of IAttributeList. +/** Example, ready to use implementation of IAttributeList. \ingroup hostingBase */ -class HostAttributeList : public IAttributeList +class HostAttributeList final : public IAttributeList { public: - HostAttributeList (); - virtual ~HostAttributeList (); + /** make a new attribute list instance */ + static IPtr make (); - tresult PLUGIN_API setInt (AttrID aid, int64 value) SMTG_OVERRIDE; - tresult PLUGIN_API getInt (AttrID aid, int64& value) SMTG_OVERRIDE; - tresult PLUGIN_API setFloat (AttrID aid, double value) SMTG_OVERRIDE; - tresult PLUGIN_API getFloat (AttrID aid, double& value) SMTG_OVERRIDE; - tresult PLUGIN_API setString (AttrID aid, const TChar* string) SMTG_OVERRIDE; - tresult PLUGIN_API getString (AttrID aid, TChar* string, uint32 sizeInBytes) SMTG_OVERRIDE; - tresult PLUGIN_API setBinary (AttrID aid, const void* data, uint32 sizeInBytes) SMTG_OVERRIDE; - tresult PLUGIN_API getBinary (AttrID aid, const void*& data, uint32& sizeInBytes) SMTG_OVERRIDE; + tresult PLUGIN_API setInt (AttrID aid, int64 value) override; + tresult PLUGIN_API getInt (AttrID aid, int64& value) override; + tresult PLUGIN_API setFloat (AttrID aid, double value) override; + tresult PLUGIN_API getFloat (AttrID aid, double& value) override; + tresult PLUGIN_API setString (AttrID aid, const TChar* string) override; + tresult PLUGIN_API getString (AttrID aid, TChar* string, uint32 sizeInBytes) override; + tresult PLUGIN_API setBinary (AttrID aid, const void* data, uint32 sizeInBytes) override; + tresult PLUGIN_API getBinary (AttrID aid, const void*& data, uint32& sizeInBytes) override; + virtual ~HostAttributeList () noexcept; DECLARE_FUNKNOWN_METHODS -protected: - void removeAttrID (AttrID aid); - std::map list; +private: + HostAttributeList (); + + struct Attribute; + std::map list; }; //------------------------------------------------------------------------ -/** Implementation's example of IMessage. +/** Example implementation of IMessage. \ingroup hostingBase */ -class HostMessage : public IMessage +class HostMessage final : public IMessage { public: HostMessage (); - virtual ~HostMessage (); + virtual ~HostMessage () noexcept; - const char* PLUGIN_API getMessageID () SMTG_OVERRIDE; - void PLUGIN_API setMessageID (const char* messageID) SMTG_OVERRIDE; - IAttributeList* PLUGIN_API getAttributes () SMTG_OVERRIDE; + const char* PLUGIN_API getMessageID () override; + void PLUGIN_API setMessageID (const char* messageID) override; + IAttributeList* PLUGIN_API getAttributes () override; DECLARE_FUNKNOWN_METHODS -protected: - char* messageId; - HostAttributeList* attributeList; +private: + char* messageId {nullptr}; + IPtr attributeList; }; //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp new file mode 100644 index 0000000..9ce5b57 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.cpp @@ -0,0 +1,340 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/hosting/module.cpp +// Created by : Steinberg, 08/2016 +// Description : hosting module classes +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "module.h" +#include "public.sdk/source/vst/utility/stringconvert.h" +#include "public.sdk/source/vst/utility/optional.h" +#include +#include + +//------------------------------------------------------------------------ +namespace VST3 { +namespace Hosting { + +//------------------------------------------------------------------------ +FactoryInfo::FactoryInfo (PFactoryInfo&& other) noexcept +{ + *this = std::move (other); +} + +//------------------------------------------------------------------------ +FactoryInfo& FactoryInfo::operator= (FactoryInfo&& other) noexcept +{ + info = std::move (other.info); + other.info = {}; + return *this; +} + +//------------------------------------------------------------------------ +FactoryInfo& FactoryInfo::operator= (PFactoryInfo&& other) noexcept +{ + info = std::move (other); + other = {}; + return *this; +} + +//------------------------------------------------------------------------ +std::string FactoryInfo::vendor () const noexcept +{ + return StringConvert::convert (info.vendor, PFactoryInfo::kNameSize); +} + +//------------------------------------------------------------------------ +std::string FactoryInfo::url () const noexcept +{ + return StringConvert::convert (info.url, PFactoryInfo::kURLSize); +} + +//------------------------------------------------------------------------ +std::string FactoryInfo::email () const noexcept +{ + return StringConvert::convert (info.email, PFactoryInfo::kEmailSize); +} + +//------------------------------------------------------------------------ +Steinberg::int32 FactoryInfo::flags () const noexcept +{ + return info.flags; +} + +//------------------------------------------------------------------------ +bool FactoryInfo::classesDiscardable () const noexcept +{ + return (info.flags & PFactoryInfo::kClassesDiscardable) != 0; +} + +//------------------------------------------------------------------------ +bool FactoryInfo::licenseCheck () const noexcept +{ + return (info.flags & PFactoryInfo::kLicenseCheck) != 0; +} + +//------------------------------------------------------------------------ +bool FactoryInfo::componentNonDiscardable () const noexcept +{ + return (info.flags & PFactoryInfo::kComponentNonDiscardable) != 0; +} + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +PluginFactory::PluginFactory (const PluginFactoryPtr& factory) noexcept : factory (factory) +{ +} + +//------------------------------------------------------------------------ +void PluginFactory::setHostContext (Steinberg::FUnknown* context) const noexcept +{ + if (auto f = Steinberg::FUnknownPtr (factory)) + f->setHostContext (context); +} + +//------------------------------------------------------------------------ +FactoryInfo PluginFactory::info () const noexcept +{ + Steinberg::PFactoryInfo i; + factory->getFactoryInfo (&i); + return FactoryInfo (std::move (i)); +} + +//------------------------------------------------------------------------ +uint32_t PluginFactory::classCount () const noexcept +{ + auto count = factory->countClasses (); + assert (count >= 0); + return static_cast (count); +} + +//------------------------------------------------------------------------ +PluginFactory::ClassInfos PluginFactory::classInfos () const noexcept +{ + auto count = classCount (); + Optional factoryInfo; + ClassInfos classes; + classes.reserve (count); + auto f3 = Steinberg::FUnknownPtr (factory); + auto f2 = Steinberg::FUnknownPtr (factory); + Steinberg::PClassInfo ci; + Steinberg::PClassInfo2 ci2; + Steinberg::PClassInfoW ci3; + for (uint32_t i = 0; i < count; ++i) + { + if (f3 && f3->getClassInfoUnicode (i, &ci3) == Steinberg::kResultTrue) + classes.emplace_back (ci3); + else if (f2 && f2->getClassInfo2 (i, &ci2) == Steinberg::kResultTrue) + classes.emplace_back (ci2); + else if (factory->getClassInfo (i, &ci) == Steinberg::kResultTrue) + classes.emplace_back (ci); + auto& classInfo = classes.back (); + if (classInfo.vendor ().empty ()) + { + if (!factoryInfo) + factoryInfo = Optional (info ()); + classInfo.get ().vendor = factoryInfo->vendor (); + } + } + return classes; +} + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +const UID& ClassInfo::ID () const noexcept +{ + return data.classID; +} + +//------------------------------------------------------------------------ +int32_t ClassInfo::cardinality () const noexcept +{ + return data.cardinality; +} + +//------------------------------------------------------------------------ +const std::string& ClassInfo::category () const noexcept +{ + return data.category; +} + +//------------------------------------------------------------------------ +const std::string& ClassInfo::name () const noexcept +{ + return data.name; +} + +//------------------------------------------------------------------------ +const std::string& ClassInfo::vendor () const noexcept +{ + return data.vendor; +} + +//------------------------------------------------------------------------ +const std::string& ClassInfo::version () const noexcept +{ + return data.version; +} + +//------------------------------------------------------------------------ +const std::string& ClassInfo::sdkVersion () const noexcept +{ + return data.sdkVersion; +} + +//------------------------------------------------------------------------ +const ClassInfo::SubCategories& ClassInfo::subCategories () const noexcept +{ + return data.subCategories; +} + +//------------------------------------------------------------------------ +Steinberg::uint32 ClassInfo::classFlags () const noexcept +{ + return data.classFlags; +} + +//------------------------------------------------------------------------ +ClassInfo::ClassInfo (const PClassInfo& info) noexcept +{ + data.classID = info.cid; + data.cardinality = info.cardinality; + data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); + data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); +} + +//------------------------------------------------------------------------ +ClassInfo::ClassInfo (const PClassInfo2& info) noexcept +{ + data.classID = info.cid; + data.cardinality = info.cardinality; + data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); + data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); + data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize); + data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize); + data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize); + parseSubCategories ( + StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize)); + data.classFlags = info.classFlags; +} + +//------------------------------------------------------------------------ +ClassInfo::ClassInfo (const PClassInfoW& info) noexcept +{ + data.classID = info.cid; + data.cardinality = info.cardinality; + data.category = StringConvert::convert (info.category, PClassInfo::kCategorySize); + data.name = StringConvert::convert (info.name, PClassInfo::kNameSize); + data.vendor = StringConvert::convert (info.vendor, PClassInfo2::kVendorSize); + data.version = StringConvert::convert (info.version, PClassInfo2::kVersionSize); + data.sdkVersion = StringConvert::convert (info.sdkVersion, PClassInfo2::kVersionSize); + parseSubCategories ( + StringConvert::convert (info.subCategories, PClassInfo2::kSubCategoriesSize)); + data.classFlags = info.classFlags; +} + +//------------------------------------------------------------------------ +void ClassInfo::parseSubCategories (const std::string& str) noexcept +{ + std::stringstream stream (str); + std::string item; + while (std::getline (stream, item, '|')) + data.subCategories.emplace_back (std::move (item)); +} + +//------------------------------------------------------------------------ +std::string ClassInfo::subCategoriesString () const noexcept +{ + std::string result; + if (data.subCategories.empty ()) + return result; + result = data.subCategories[0]; + for (auto index = 1u; index < data.subCategories.size (); ++index) + result += "|" + data.subCategories[index]; + return result; +} + +//------------------------------------------------------------------------ +namespace { + +//------------------------------------------------------------------------ +std::pair rangeOfScaleFactor (const std::string& name) +{ + auto result = std::make_pair (std::string::npos, std::string::npos); + size_t xIndex = name.find_last_of ('x'); + if (xIndex == std::string::npos) + return result; + + size_t indicatorIndex = name.find_last_of ('_'); + if (indicatorIndex == std::string::npos) + return result; + if (xIndex < indicatorIndex) + return result; + result.first = indicatorIndex + 1; + result.second = xIndex; + return result; +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +Optional Module::Snapshot::decodeScaleFactor (const std::string& name) +{ + auto range = rangeOfScaleFactor (name); + if (range.first == std::string::npos || range.second == std::string::npos) + return {}; + std::string tmp (name.data () + range.first, range.second - range.first); + std::istringstream sstream (tmp); + sstream.imbue (std::locale::classic ()); + sstream.precision (static_cast (3)); + double result; + sstream >> result; + return Optional (result); +} + +//------------------------------------------------------------------------ +Optional Module::Snapshot::decodeUID (const std::string& filename) +{ + if (filename.size () < 45) + return {}; + if (filename.find ("_snapshot") != 32) + return {}; + auto uidStr = filename.substr (0, 32); + return UID::fromString (uidStr); +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h new file mode 100644 index 0000000..36b3afa --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module.h @@ -0,0 +1,214 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/hosting/module.h +// Created by : Steinberg, 08/2016 +// Description : hosting module classes +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "../utility/uid.h" +#include "pluginterfaces/base/ipluginbase.h" +#include +#include + +//------------------------------------------------------------------------ +namespace VST3 { +namespace Hosting { + +//------------------------------------------------------------------------ +class FactoryInfo +{ +public: +//------------------------------------------------------------------------ + using PFactoryInfo = Steinberg::PFactoryInfo; + + FactoryInfo () noexcept {} + ~FactoryInfo () noexcept {} + FactoryInfo (const FactoryInfo&) noexcept = default; + FactoryInfo (PFactoryInfo&&) noexcept; + FactoryInfo (FactoryInfo&&) noexcept = default; + FactoryInfo& operator= (const FactoryInfo&) noexcept = default; + FactoryInfo& operator= (FactoryInfo&&) noexcept; + FactoryInfo& operator= (PFactoryInfo&&) noexcept; + + std::string vendor () const noexcept; + std::string url () const noexcept; + std::string email () const noexcept; + Steinberg::int32 flags () const noexcept; + bool classesDiscardable () const noexcept; + bool licenseCheck () const noexcept; + bool componentNonDiscardable () const noexcept; + + PFactoryInfo& get () noexcept { return info; } +//------------------------------------------------------------------------ +private: + PFactoryInfo info {}; +}; + +//------------------------------------------------------------------------ +class ClassInfo +{ +public: +//------------------------------------------------------------------------ + using SubCategories = std::vector; + using PClassInfo = Steinberg::PClassInfo; + using PClassInfo2 = Steinberg::PClassInfo2; + using PClassInfoW = Steinberg::PClassInfoW; + +//------------------------------------------------------------------------ + ClassInfo () noexcept {} + explicit ClassInfo (const PClassInfo& info) noexcept; + explicit ClassInfo (const PClassInfo2& info) noexcept; + explicit ClassInfo (const PClassInfoW& info) noexcept; + ClassInfo (const ClassInfo&) = default; + ClassInfo& operator= (const ClassInfo&) = default; + ClassInfo (ClassInfo&&) = default; + ClassInfo& operator= (ClassInfo&&) = default; + + const UID& ID () const noexcept; + int32_t cardinality () const noexcept; + const std::string& category () const noexcept; + const std::string& name () const noexcept; + const std::string& vendor () const noexcept; + const std::string& version () const noexcept; + const std::string& sdkVersion () const noexcept; + const SubCategories& subCategories () const noexcept; + std::string subCategoriesString () const noexcept; + Steinberg::uint32 classFlags () const noexcept; + + struct Data + { + UID classID; + int32_t cardinality; + std::string category; + std::string name; + std::string vendor; + std::string version; + std::string sdkVersion; + SubCategories subCategories; + Steinberg::uint32 classFlags = 0; + }; + + Data& get () noexcept { return data; } +//------------------------------------------------------------------------ +private: + void parseSubCategories (const std::string& str) noexcept; + Data data {}; +}; + +//------------------------------------------------------------------------ +class PluginFactory +{ +public: +//------------------------------------------------------------------------ + using ClassInfos = std::vector; + using PluginFactoryPtr = Steinberg::IPtr; + +//------------------------------------------------------------------------ + explicit PluginFactory (const PluginFactoryPtr& factory) noexcept; + + void setHostContext (Steinberg::FUnknown* context) const noexcept; + + FactoryInfo info () const noexcept; + uint32_t classCount () const noexcept; + ClassInfos classInfos () const noexcept; + + template + Steinberg::IPtr createInstance (const UID& classID) const noexcept; + + const PluginFactoryPtr& get () const noexcept { return factory; } +//------------------------------------------------------------------------ +private: + PluginFactoryPtr factory; +}; + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +class Module +{ +public: +//------------------------------------------------------------------------ + struct Snapshot + { + struct ImageDesc + { + double scaleFactor {1.}; + std::string path; + }; + UID uid; + std::vector images; + + static Optional decodeScaleFactor (const std::string& path); + static Optional decodeUID (const std::string& filename); + }; + + using Ptr = std::shared_ptr; + using PathList = std::vector; + using SnapshotList = std::vector; + +//------------------------------------------------------------------------ + static Ptr create (const std::string& path, std::string& errorDescription); + static PathList getModulePaths (); + static SnapshotList getSnapshots (const std::string& modulePath); + /** get the path to the module info json file if it exists */ + static Optional getModuleInfoPath (const std::string& modulePath); + + const std::string& getName () const noexcept { return name; } + const std::string& getPath () const noexcept { return path; } + const PluginFactory& getFactory () const noexcept { return factory; } + bool isBundle () const noexcept { return hasBundleStructure; } +//------------------------------------------------------------------------ +protected: + virtual ~Module () noexcept = default; + virtual bool load (const std::string& path, std::string& errorDescription) = 0; + + PluginFactory factory {nullptr}; + std::string name; + std::string path; + bool hasBundleStructure {true}; +}; + +//------------------------------------------------------------------------ +template +inline Steinberg::IPtr PluginFactory::createInstance (const UID& classID) const noexcept +{ + T* obj = nullptr; + if (factory->createInstance (classID.data (), T::iid, reinterpret_cast (&obj)) == + Steinberg::kResultTrue) + return Steinberg::owned (obj); + return nullptr; +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp new file mode 100644 index 0000000..404054c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_linux.cpp @@ -0,0 +1,369 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/hosting/module_linux.cpp +// Created by : Steinberg, 08/2016 +// Description : hosting module classes (linux implementation) +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "module.h" +#include "../utility/optional.h" +#include "../utility/stringconvert.h" +#include +#include +#include +#include +#include + +#if SMTG_CPP17 + +#if __has_include() +#define USE_EXPERIMENTAL_FS 0 +#elif __has_include() +#define USE_EXPERIMENTAL_FS 1 +#endif + +#else // !SMTG_CPP17 + +#define USE_EXPERIMENTAL_FS 1 + +#endif // SMTG_CPP17 + +#if USE_EXPERIMENTAL_FS == 1 + +#include +namespace filesystem = std::experimental::filesystem; + +#else // USE_FILESYSTEM == 0 + +#include +namespace filesystem = std::filesystem; + +#endif // USE_FILESYSTEM + +//------------------------------------------------------------------------ +extern "C" { +using ModuleEntryFunc = bool (PLUGIN_API*) (void*); +using ModuleExitFunc = bool (PLUGIN_API*) (); +} + +//------------------------------------------------------------------------ +namespace VST3 { +namespace Hosting { + +using Path = filesystem::path; + +//------------------------------------------------------------------------ +namespace { + +//------------------------------------------------------------------------ +Optional getCurrentMachineName () +{ + struct utsname unameData; + + int res = uname (&unameData); + if (res != 0) + return {}; + + return {unameData.machine}; +} + +//------------------------------------------------------------------------ +Optional getApplicationPath () +{ + std::string appPath = ""; + + pid_t pid = getpid (); + char buf[10]; + sprintf (buf, "%d", pid); + std::string _link = "/proc/"; + _link.append (buf); + _link.append ("/exe"); + char proc[1024]; + int ch = readlink (_link.c_str (), proc, 1024); + if (ch == -1) + return {}; + + proc[ch] = 0; + appPath = proc; + std::string::size_type t = appPath.find_last_of ("/"); + appPath = appPath.substr (0, t); + + return Path {appPath}; +} + +//------------------------------------------------------------------------ +class LinuxModule : public Module +{ +public: + template + T getFunctionPointer (const char* name) + { + return reinterpret_cast (dlsym (mModule, name)); + } + + ~LinuxModule () override + { + factory = PluginFactory (nullptr); + + if (mModule) + { + if (auto moduleExit = getFunctionPointer ("ModuleExit")) + moduleExit (); + + dlclose (mModule); + } + } + + static Optional getSOPath (const std::string& inPath) + { + Path modulePath {inPath}; + if (!filesystem::is_directory (modulePath)) + return {}; + + auto stem = modulePath.stem (); + + modulePath /= "Contents"; + if (!filesystem::is_directory (modulePath)) + return {}; + + // use the Machine Hardware Name (from uname cmd-line) as prefix for "-linux" + auto machine = getCurrentMachineName (); + if (!machine) + return {}; + + modulePath /= *machine + "-linux"; + if (!filesystem::is_directory (modulePath)) + return {}; + + stem.replace_extension (".so"); + modulePath /= stem; + return Optional (std::move (modulePath)); + } + + bool load (const std::string& inPath, std::string& errorDescription) override + { + auto modulePath = getSOPath (inPath); + if (!modulePath) + { + errorDescription = inPath + " is not a module directory."; + return false; + } + + mModule = dlopen (reinterpret_cast (modulePath->generic_string ().data ()), + RTLD_LAZY); + if (!mModule) + { + errorDescription = "dlopen failed.\n"; + errorDescription += dlerror (); + return false; + } + // ModuleEntry is mandatory + auto moduleEntry = getFunctionPointer ("ModuleEntry"); + if (!moduleEntry) + { + errorDescription = + "The shared library does not export the required 'ModuleEntry' function"; + return false; + } + // ModuleExit is mandatory + auto moduleExit = getFunctionPointer ("ModuleExit"); + if (!moduleExit) + { + errorDescription = + "The shared library does not export the required 'ModuleExit' function"; + return false; + } + auto factoryProc = getFunctionPointer ("GetPluginFactory"); + if (!factoryProc) + { + errorDescription = + "The shared library does not export the required 'GetPluginFactory' function"; + return false; + } + + if (!moduleEntry (mModule)) + { + errorDescription = "Calling 'ModuleEntry' failed"; + return false; + } + auto f = Steinberg::FUnknownPtr (owned (factoryProc ())); + if (!f) + { + errorDescription = "Calling 'GetPluginFactory' returned nullptr"; + return false; + } + factory = PluginFactory (f); + return true; + } + + void* mModule {nullptr}; +}; + +//------------------------------------------------------------------------ +void findFilesWithExt (const std::string& path, const std::string& ext, Module::PathList& pathList, + bool recursive = true) +{ + try + { + for (auto& p : filesystem::directory_iterator (path)) + { + if (p.path ().extension () == ext) + { + pathList.push_back (p.path ().generic_string ()); + } + else if (recursive && p.status ().type () == filesystem::file_type::directory) + { + findFilesWithExt (p.path (), ext, pathList); + } + } + } + catch (...) + { + } +} + +//------------------------------------------------------------------------ +void findModules (const std::string& path, Module::PathList& pathList) +{ + findFilesWithExt (path, ".vst3", pathList); +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +Module::Ptr Module::create (const std::string& path, std::string& errorDescription) +{ + auto _module = std::make_shared (); + if (_module->load (path, errorDescription)) + { + _module->path = path; + auto it = std::find_if (path.rbegin (), path.rend (), + [] (const std::string::value_type& c) { return c == '/'; }); + if (it != path.rend ()) + _module->name = {it.base (), path.end ()}; + return _module; + } + return nullptr; +} + +//------------------------------------------------------------------------ +Module::PathList Module::getModulePaths () +{ + /* VST3 component locations on linux : + * User privately installed : $HOME/.vst3/ + * Distribution installed : /usr/lib/vst3/ + * Locally installed : /usr/local/lib/vst3/ + * Application : /$APPFOLDER/vst3/ + */ + + const auto systemPaths = {"/usr/lib/vst3/", "/usr/local/lib/vst3/"}; + + PathList list; + if (auto homeDir = getenv ("HOME")) + { + filesystem::path homePath (homeDir); + homePath /= ".vst3"; + findModules (homePath.generic_string (), list); + } + for (auto path : systemPaths) + findModules (path, list); + + // application level + auto appPath = getApplicationPath (); + if (appPath) + { + *appPath /= "vst3"; + findModules (appPath->generic_string (), list); + } + + return list; +} + +//------------------------------------------------------------------------ +Module::SnapshotList Module::getSnapshots (const std::string& modulePath) +{ + SnapshotList result; + filesystem::path path (modulePath); + path /= "Contents"; + path /= "Resources"; + path /= "Snapshots"; + PathList pngList; + findFilesWithExt (path, ".png", pngList, false); + for (auto& png : pngList) + { + filesystem::path p (png); + auto filename = p.filename ().generic_string (); + auto uid = Snapshot::decodeUID (filename); + if (!uid) + continue; + auto scaleFactor = 1.; + if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename)) + scaleFactor = *decodedScaleFactor; + + Module::Snapshot::ImageDesc desc; + desc.scaleFactor = scaleFactor; + desc.path = std::move (png); + bool found = false; + for (auto& entry : result) + { + if (entry.uid != *uid) + continue; + found = true; + entry.images.emplace_back (std::move (desc)); + break; + } + if (found) + continue; + Module::Snapshot snapshot; + snapshot.uid = *uid; + snapshot.images.emplace_back (std::move (desc)); + result.emplace_back (std::move (snapshot)); + } + return result; +} + +//------------------------------------------------------------------------ +Optional Module::getModuleInfoPath (const std::string& modulePath) +{ + filesystem::path path (modulePath); + path /= "Contents"; + path /= "Resources"; + path /= "moduleinfo.json"; + if (filesystem::exists (path)) + return {path.generic_string ()}; + return {}; +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm new file mode 100644 index 0000000..49a7155 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_mac.mm @@ -0,0 +1,390 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/hosting/module_mac.mm +// Created by : Steinberg, 08/2016 +// Description : hosting module classes (macOS implementation) +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#import "module.h" + +#import +#import + +#if !__has_feature(objc_arc) +#error this file needs to be compiled with automatic reference counting enabled +#endif + +//------------------------------------------------------------------------ +extern "C" { +typedef bool (*BundleEntryFunc) (CFBundleRef); +typedef bool (*BundleExitFunc) (); +} + +//------------------------------------------------------------------------ +namespace VST3 { +namespace Hosting { + +//------------------------------------------------------------------------ +namespace { + +//------------------------------------------------------------------------ +template +class CFPtr +{ +public: + inline CFPtr (const T& obj = nullptr) : obj (obj) {} + inline CFPtr (CFPtr&& other) { *this = other; } + inline ~CFPtr () + { + if (obj) + CFRelease (obj); + } + + inline CFPtr& operator= (CFPtr&& other) + { + obj = other.obj; + other.obj = nullptr; + return *this; + } + inline CFPtr& operator= (const T& o) + { + if (obj) + CFRelease (obj); + obj = o; + return *this; + } + inline operator T () const { return obj; } // act as T +private: + CFPtr (const CFPtr& other) = delete; + CFPtr& operator= (const CFPtr& other) = delete; + + T obj = nullptr; +}; + +//------------------------------------------------------------------------ +class MacModule : public Module +{ +public: + template + T getFunctionPointer (const char* name) + { + assert (bundle); + CFPtr functionName ( + CFStringCreateWithCString (kCFAllocatorDefault, name, kCFStringEncodingASCII)); + return reinterpret_cast (CFBundleGetFunctionPointerForName (bundle, functionName)); + } + + bool loadInternal (const std::string& path, std::string& errorDescription) + { + CFPtr url (CFURLCreateFromFileSystemRepresentation ( + kCFAllocatorDefault, reinterpret_cast (path.data ()), path.length (), + true)); + if (!url) + return false; + bundle = CFBundleCreate (kCFAllocatorDefault, url); + CFErrorRef error = nullptr; + if (!bundle || !CFBundleLoadExecutableAndReturnError (bundle, &error)) + { + if (error) + { + CFPtr errorString (CFErrorCopyDescription (error)); + if (errorString) + { + auto stringLength = CFStringGetLength (errorString); + auto maxSize = + CFStringGetMaximumSizeForEncoding (stringLength, kCFStringEncodingUTF8); + auto buffer = std::make_unique (maxSize); + if (CFStringGetCString (errorString, buffer.get (), maxSize, + kCFStringEncodingUTF8)) + errorDescription = buffer.get (); + CFRelease (error); + } + } + else + { + errorDescription = "Could not create Bundle for path: " + path; + } + return false; + } + // bundleEntry is mandatory + auto bundleEntry = getFunctionPointer ("bundleEntry"); + if (!bundleEntry) + { + errorDescription = "Bundle does not export the required 'bundleEntry' function"; + return false; + } + // bundleExit is mandatory + auto bundleExit = getFunctionPointer ("bundleExit"); + if (!bundleExit) + { + errorDescription = "Bundle does not export the required 'bundleExit' function"; + return false; + } + auto factoryProc = getFunctionPointer ("GetPluginFactory"); + if (!factoryProc) + { + errorDescription = "Bundle does not export the required 'GetPluginFactory' function"; + return false; + } + if (!bundleEntry (bundle)) + { + errorDescription = "Calling 'bundleEntry' failed"; + return false; + } + auto f = owned (factoryProc ()); + if (!f) + { + errorDescription = "Calling 'GetPluginFactory' returned nullptr"; + return false; + } + factory = PluginFactory (f); + return true; + } + + bool load (const std::string& path, std::string& errorDescription) override + { + if (!path.empty () && path[0] != '/') + { + auto buffer = std::make_unique (PATH_MAX); + auto workDir = getcwd (buffer.get (), PATH_MAX); + if (workDir) + { + std::string wd (workDir); + wd += "/"; + return loadInternal (wd + path, errorDescription); + } + } + return loadInternal (path, errorDescription); + } + + ~MacModule () override + { + factory = PluginFactory (nullptr); + + if (bundle) + { + if (auto bundleExit = getFunctionPointer ("bundleExit")) + bundleExit (); + } + } + + CFPtr bundle; +}; + +//------------------------------------------------------------------------ +void findModulesInDirectory (NSURL* dirUrl, Module::PathList& result) +{ + dirUrl = [dirUrl URLByResolvingSymlinksInPath]; + if (!dirUrl) + return; + NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] + enumeratorAtURL: dirUrl + includingPropertiesForKeys:nil + options:NSDirectoryEnumerationSkipsPackageDescendants + errorHandler:nil]; + for (NSURL* url in enumerator) + { + if ([[[url lastPathComponent] pathExtension] isEqualToString:@"vst3"]) + { + CFPtr archs ( + CFBundleCopyExecutableArchitecturesForURL (static_cast (url))); + if (archs) + result.emplace_back ([url.path UTF8String]); + } + else + { + id resValue; + if (![url getResourceValue:&resValue forKey:NSURLIsSymbolicLinkKey error:nil]) + continue; + if (!static_cast (resValue).boolValue) + continue; + auto resolvedUrl = [url URLByResolvingSymlinksInPath]; + if (![resolvedUrl getResourceValue:&resValue forKey:NSURLIsDirectoryKey error:nil]) + continue; + if (!static_cast (resValue).boolValue) + continue; + findModulesInDirectory (resolvedUrl, result); + } + } +} + +//------------------------------------------------------------------------ +void getModules (NSSearchPathDomainMask domain, Module::PathList& result) +{ + NSURL* libraryUrl = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory + inDomain:domain + appropriateForURL:nil + create:NO + error:nil]; + if (libraryUrl == nil) + return; + NSURL* audioUrl = [libraryUrl URLByAppendingPathComponent:@"Audio"]; + if (audioUrl == nil) + return; + NSURL* plugInsUrl = [audioUrl URLByAppendingPathComponent:@"Plug-Ins"]; + if (plugInsUrl == nil) + return; + NSURL* vst3Url = + [[plugInsUrl URLByAppendingPathComponent:@"VST3"] URLByResolvingSymlinksInPath]; + if (vst3Url == nil) + return; + findModulesInDirectory (vst3Url, result); +} + +//------------------------------------------------------------------------ +void getApplicationModules (Module::PathList& result) +{ + auto bundle = CFBundleGetMainBundle (); + if (!bundle) + return; + auto bundleUrl = static_cast (CFBridgingRelease (CFBundleCopyBundleURL (bundle))); + if (!bundleUrl) + return; + auto resUrl = [bundleUrl URLByAppendingPathComponent:@"Contents"]; + if (!resUrl) + return; + auto vst3Url = [resUrl URLByAppendingPathComponent:@"VST3"]; + if (!vst3Url) + return; + findModulesInDirectory (vst3Url, result); +} + +//------------------------------------------------------------------------ +void getModuleSnapshots (const std::string& path, Module::SnapshotList& result) +{ + auto nsString = [NSString stringWithUTF8String:path.data ()]; + if (!nsString) + return; + auto bundleUrl = [NSURL fileURLWithPath:nsString]; + if (!bundleUrl) + return; + auto urls = [NSBundle URLsForResourcesWithExtension:@"png" + subdirectory:@"Snapshots" + inBundleWithURL:bundleUrl]; + if (!urls || [urls count] == 0) + return; + + for (NSURL* url in urls) + { + std::string fullpath ([[url path] UTF8String]); + std::string filename ([[[url path] lastPathComponent] UTF8String]); + auto uid = Module::Snapshot::decodeUID (filename); + if (!uid) + continue; + + auto scaleFactor = 1.; + if (auto decodedScaleFactor = Module::Snapshot::decodeScaleFactor (filename)) + scaleFactor = *decodedScaleFactor; + + Module::Snapshot::ImageDesc desc; + desc.scaleFactor = scaleFactor; + desc.path = std::move (fullpath); + bool found = false; + for (auto& entry : result) + { + if (entry.uid != *uid) + continue; + found = true; + entry.images.emplace_back (std::move (desc)); + break; + } + if (found) + continue; + Module::Snapshot snapshot; + snapshot.uid = *uid; + snapshot.images.emplace_back (std::move (desc)); + result.emplace_back (std::move (snapshot)); + } +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +Module::Ptr Module::create (const std::string& path, std::string& errorDescription) +{ + auto module = std::make_shared (); + if (module->load (path, errorDescription)) + { + module->path = path; + auto it = std::find_if (path.rbegin (), path.rend (), + [] (const std::string::value_type& c) { return c == '/'; }); + if (it != path.rend ()) + module->name = {it.base (), path.end ()}; + return std::move (module); + } + return nullptr; +} + +//------------------------------------------------------------------------ +Module::PathList Module::getModulePaths () +{ + PathList list; + getModules (NSUserDomainMask, list); + getModules (NSLocalDomainMask, list); + // TODO getModules (NSNetworkDomainMask, list); + getApplicationModules (list); + return list; +} + +//------------------------------------------------------------------------ +Module::SnapshotList Module::getSnapshots (const std::string& modulePath) +{ + SnapshotList list; + getModuleSnapshots (modulePath, list); + return list; +} + +//------------------------------------------------------------------------ +Optional Module::getModuleInfoPath (const std::string& modulePath) +{ + auto nsString = [NSString stringWithUTF8String:modulePath.data ()]; + if (!nsString) + return {}; + auto bundleUrl = [NSURL fileURLWithPath:nsString]; + if (!bundleUrl) + return {}; + auto moduleInfoUrl = [NSBundle URLForResource:@"moduleinfo" + withExtension:@"json" + subdirectory:nullptr + inBundleWithURL:bundleUrl]; + if (!moduleInfoUrl) + return {}; + NSError* error = nil; + if ([moduleInfoUrl checkResourceIsReachableAndReturnError:&error]) + return {std::string (moduleInfoUrl.fileSystemRepresentation)}; + return {}; +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp new file mode 100644 index 0000000..77990fd --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/module_win32.cpp @@ -0,0 +1,647 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/hosting/module_win32.cpp +// Created by : Steinberg, 08/2016 +// Description : hosting module classes (win32 implementation) +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "../utility/optional.h" +#include "../utility/stringconvert.h" +#include "module.h" + +#include +#include + +#include +#include + +#if SMTG_CPP17 + +#if __has_include() +#define USE_FILESYSTEM 1 +#elif __has_include() +#define USE_FILESYSTEM 0 +#endif + +#else // !SMTG_CPP17 + +#define USE_FILESYSTEM 0 + +#endif // SMTG_CPP17 + +#if USE_FILESYSTEM == 1 + +#include +namespace filesystem = std::filesystem; + +#else // USE_FILESYSTEM == 0 + +// The header is deprecated. It is superseded by the C++17 +// header. You can define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING to silence the +// warning, otherwise the build will fail in VS2019 16.3.0 +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#include +namespace filesystem = std::experimental::filesystem; + +#endif // USE_FILESYSTEM + +#pragma comment(lib, "Shell32") + +//------------------------------------------------------------------------ +extern "C" { +using InitModuleFunc = bool (PLUGIN_API*) (); +using ExitModuleFunc = bool (PLUGIN_API*) (); +} + +//------------------------------------------------------------------------ +namespace VST3 { +namespace Hosting { + +constexpr unsigned long kIPPathNameMax = 1024; + +//------------------------------------------------------------------------ +namespace { + +#define USE_OLE !USE_FILESYSTEM + +// for testing only +#if 0 // DEVELOPMENT +#define LOG_ENABLE 1 +#else +#define LOG_ENABLE 0 +#endif + +#if SMTG_PLATFORM_64 + +#if SMTG_OS_WINDOWS_ARM + +#if SMTG_CPU_ARM_64EC +constexpr auto architectureString = "arm64ec-win"; +constexpr auto architectureX64String = "x86_64-win"; +#else // !SMTG_CPU_ARM_64EC +constexpr auto architectureString = "arm64-win"; +#endif // SMTG_CPU_ARM_64EC + +constexpr auto architectureArm64XString = "arm64x-win"; + +#else // !SMTG_OS_WINDOWS_ARM +constexpr auto architectureString = "x86_64-win"; +#endif // SMTG_OS_WINDOWS_ARM + +#else // !SMTG_PLATFORM_64 + +#if SMTG_OS_WINDOWS_ARM +constexpr auto architectureString = "arm-win"; +#else // !SMTG_OS_WINDOWS_ARM +constexpr auto architectureString = "x86-win"; +#endif // SMTG_OS_WINDOWS_ARM + +#endif // SMTG_PLATFORM_64 + +#if USE_OLE +//------------------------------------------------------------------------ +struct Ole +{ + static Ole& instance () + { + static Ole gInstance; + return gInstance; + } + +private: + Ole () { OleInitialize (nullptr); } + ~Ole () { OleUninitialize (); } +}; +#endif // USE_OLE + +//------------------------------------------------------------------------ +class Win32Module : public Module +{ +public: + template + T getFunctionPointer (const char* name) + { + return reinterpret_cast (GetProcAddress (mModule, name)); + } + + ~Win32Module () override + { + factory = PluginFactory (nullptr); + + if (mModule) + { + // ExitDll is optional + if (auto dllExit = getFunctionPointer ("ExitDll")) + dllExit (); + + FreeLibrary ((HMODULE)mModule); + } + } + + //--- ----------------------------------------------------------------------- + HINSTANCE loadAsPackage (const std::string& inPath, const char* archString = architectureString) + { + filesystem::path p (inPath); + auto filename = p.filename (); + p /= "Contents"; + p /= archString; + p /= filename; + auto wideStr = StringConvert::convert (p.string ()); + HINSTANCE instance = LoadLibraryW (reinterpret_cast (wideStr.data ())); +#if SMTG_CPU_ARM_64EC + if (instance == nullptr) + instance = loadAsPackage (inPath, architectureArm64XString); + if (instance == nullptr) + instance = loadAsPackage (inPath, architectureX64String); +#endif + return instance; + } + + //--- ----------------------------------------------------------------------- + HINSTANCE loadAsDll (const std::string& inPath, std::string& errorDescription) + { + auto wideStr = StringConvert::convert (inPath); + HINSTANCE instance = LoadLibraryW (reinterpret_cast (wideStr.data ())); + if (instance == nullptr) + { + auto lastError = GetLastError (); + LPVOID lpMessageBuffer {nullptr}; + if (FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, lastError, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&lpMessageBuffer, 0, nullptr) > 0) + { + errorDescription = "LoadLibray failed: " + std::string ((char*)lpMessageBuffer); + LocalFree (lpMessageBuffer); + } + else + { + errorDescription = + "LoadLibrary failed with error number : " + std::to_string (lastError); + } + } + else + { + hasBundleStructure = false; + } + return instance; + } + + //--- ----------------------------------------------------------------------- + bool load (const std::string& inPath, std::string& errorDescription) override + { + // filesystem::u8path is deprecated in C++20 +#if SMTG_CPP20 + const filesystem::path tmp (inPath); +#else + const filesystem::path tmp = filesystem::u8path (inPath); +#endif + if (filesystem::is_directory (tmp)) + { + // try as package (bundle) + mModule = loadAsPackage (inPath); + } + else + { + // try old definition without package + mModule = loadAsDll (inPath, errorDescription); + } + if (mModule == nullptr) + return false; + + auto factoryProc = getFunctionPointer ("GetPluginFactory"); + if (!factoryProc) + { + errorDescription = "The dll does not export the required 'GetPluginFactory' function"; + return false; + } + // InitDll is optional + auto dllEntry = getFunctionPointer ("InitDll"); + if (dllEntry && !dllEntry ()) + { + errorDescription = "Calling 'InitDll' failed"; + return false; + } + auto f = Steinberg::FUnknownPtr (owned (factoryProc ())); + if (!f) + { + errorDescription = "Calling 'GetPluginFactory' returned nullptr"; + return false; + } + factory = PluginFactory (f); + return true; + } + + HINSTANCE mModule {nullptr}; +}; + +//------------------------------------------------------------------------ +bool openVST3Package (const filesystem::path& p, const char* archString, + filesystem::path* result = nullptr) +{ + auto path = p; + path /= "Contents"; + path /= archString; + path /= p.filename (); + auto hFile = CreateFileW (reinterpret_cast (path.c_str ()), GENERIC_READ, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFile != INVALID_HANDLE_VALUE) + { + CloseHandle (hFile); + if (result) + *result = path; + return true; + } + return false; +} + +//------------------------------------------------------------------------ +bool checkVST3Package (const filesystem::path& p, filesystem::path* result = nullptr, + const char* archString = architectureString) +{ + if (openVST3Package (p, archString, result)) + return true; + +#if SMTG_CPU_ARM_64EC + if (openVST3Package (p, architectureArm64XString, result)) + return true; + if (openVST3Package (p, architectureX64String, result)) + return true; +#endif + return false; +} + +//------------------------------------------------------------------------ +bool isFolderSymbolicLink (const filesystem::path& p) +{ +#if USE_FILESYSTEM + if (/*filesystem::exists (p) &&*/ filesystem::is_symlink (p)) + return true; +#else + std::wstring wString = p.generic_wstring (); + auto attrib = GetFileAttributesW (reinterpret_cast (wString.c_str ())); + if (attrib & FILE_ATTRIBUTE_REPARSE_POINT) + { + auto hFile = CreateFileW (reinterpret_cast (wString.c_str ()), GENERIC_READ, + FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + return true; + CloseHandle (hFile); + } +#endif + return false; +} + +//------------------------------------------------------------------------ +Optional getKnownFolder (REFKNOWNFOLDERID folderID) +{ + PWSTR wideStr {}; + if (FAILED (SHGetKnownFolderPath (folderID, 0, nullptr, &wideStr))) + return {}; + return StringConvert::convert (Steinberg::wscast (wideStr)); +} + +//------------------------------------------------------------------------ +VST3::Optional resolveShellLink (const filesystem::path& p) +{ +#if USE_FILESYSTEM + return {filesystem::read_symlink (p).lexically_normal ()}; +#elif USE_OLE + Ole::instance (); + + IShellLink* shellLink = nullptr; + if (!SUCCEEDED (CoCreateInstance (CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, reinterpret_cast (&shellLink)))) + return {}; + + IPersistFile* persistFile = nullptr; + if (!SUCCEEDED ( + shellLink->QueryInterface (IID_IPersistFile, reinterpret_cast (&persistFile)))) + return {}; + + if (!SUCCEEDED (persistFile->Load (p.wstring ().data (), STGM_READ))) + return {}; + + if (!SUCCEEDED (shellLink->Resolve (nullptr, MAKELONG (SLR_NO_UI, 500)))) + return {}; + + WCHAR resolvedPath[kIPPathNameMax]; + if (!SUCCEEDED (shellLink->GetPath (resolvedPath, kIPPathNameMax, nullptr, SLGP_SHORTPATH))) + return {}; + + std::wstring longPath; + longPath.resize (kIPPathNameMax); + auto numChars = + GetLongPathNameW (resolvedPath, const_cast (longPath.data ()), kIPPathNameMax); + if (!numChars) + return {}; + longPath.resize (numChars); + + persistFile->Release (); + shellLink->Release (); + + return {filesystem::path (longPath)}; +#else + return {}; +#endif +} + +//------------------------------------------------------------------------ +void addToPathList (Module::PathList& pathList, const std::string& toAdd) +{ +#if LOG_ENABLE + std::cout << "=> add: " << toAdd << "\n"; +#endif + + pathList.push_back (toAdd); +} + +//------------------------------------------------------------------------ +void findFilesWithExt (const filesystem::path& path, const std::string& ext, + Module::PathList& pathList, bool recursive = true) +{ + for (auto& p : filesystem::directory_iterator (path)) + { +#if USE_FILESYSTEM + filesystem::path finalPath (p); + if (isFolderSymbolicLink (p)) + { + if (auto res = resolveShellLink (p)) + { + finalPath = *res; + if (!filesystem::exists (finalPath)) + continue; + } + else + continue; + } + const auto& cpExt = finalPath.extension (); + if (cpExt == ext) + { + filesystem::path result; + if (checkVST3Package (finalPath, &result)) + { + addToPathList (pathList, result.generic_string ()); + continue; + } + } + + if (filesystem::is_directory (finalPath)) + { + if (recursive) + findFilesWithExt (finalPath, ext, pathList, recursive); + } + else if (cpExt == ext) + addToPathList (pathList, finalPath.generic_string ()); +#else + const auto& cp = p.path (); + const auto& cpExt = cp.extension (); + if (cpExt == ext) + { + if ((p.status ().type () == filesystem::file_type::directory) || + isFolderSymbolicLink (p)) + { + filesystem::path result; + if (checkVST3Package (p, &result)) + { + addToPathList (pathList, result.generic_u8string ()); + continue; + } + findFilesWithExt (cp, ext, pathList, recursive); + } + else + addToPathList (pathList, cp.generic_u8string ()); + } + else if (recursive) + { + if (p.status ().type () == filesystem::file_type::directory) + { + findFilesWithExt (cp, ext, pathList, recursive); + } + else if (cpExt == ".lnk") + { + if (auto resolvedLink = resolveShellLink (cp)) + { + if (resolvedLink->extension () == ext) + { + if (filesystem::is_directory (*resolvedLink) || + isFolderSymbolicLink (*resolvedLink)) + { + filesystem::path result; + if (checkVST3Package (*resolvedLink, &result)) + { + addToPathList (pathList, result.generic_u8string ()); + continue; + } + findFilesWithExt (*resolvedLink, ext, pathList, recursive); + } + else + addToPathList (pathList, resolvedLink->generic_u8string ()); + } + else if (filesystem::is_directory (*resolvedLink)) + { + const auto& str = resolvedLink->generic_u8string (); + if (cp.generic_u8string ().compare (0, str.size (), str.data (), + str.size ()) != 0) + findFilesWithExt (*resolvedLink, ext, pathList, recursive); + } + } + } + } +#endif + } +} + +//------------------------------------------------------------------------ +void findModules (const filesystem::path& path, Module::PathList& pathList) +{ + if (filesystem::exists (path)) + findFilesWithExt (path, ".vst3", pathList); +} + +//------------------------------------------------------------------------ +Optional getContentsDirectoryFromModuleExecutablePath ( + const std::string& modulePath) +{ + filesystem::path path (modulePath); + + path = path.parent_path (); + if (path.filename () != architectureString) + return {}; + path = path.parent_path (); + if (path.filename () != "Contents") + return {}; + + return Optional {std::move (path)}; +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +Module::Ptr Module::create (const std::string& path, std::string& errorDescription) +{ + auto _module = std::make_shared (); + if (_module->load (path, errorDescription)) + { + _module->path = path; + auto it = std::find_if (path.rbegin (), path.rend (), + [] (const std::string::value_type& c) { return c == '/'; }); + if (it != path.rend ()) + _module->name = {it.base (), path.end ()}; + return _module; + } + return nullptr; +} + +//------------------------------------------------------------------------ +Module::PathList Module::getModulePaths () +{ + // find plug-ins located in common/VST3 + PathList list; + if (auto knownFolder = getKnownFolder (FOLDERID_UserProgramFilesCommon)) + { + filesystem::path path (*knownFolder); + path.append ("VST3"); +#if LOG_ENABLE + std::cout << "Check folder: " << path << "\n"; +#endif + findModules (path, list); + } + + if (auto knownFolder = getKnownFolder (FOLDERID_ProgramFilesCommon)) + { + filesystem::path path (*knownFolder); + path.append ("VST3"); +#if LOG_ENABLE + std::cout << "Check folder: " << path << "\n"; +#endif + findModules (path, list); + } + + // find plug-ins located in VST3 (application folder) + WCHAR modulePath[kIPPathNameMax]; + GetModuleFileNameW (nullptr, modulePath, kIPPathNameMax); + auto appPath = StringConvert::convert (Steinberg::wscast (modulePath)); + filesystem::path path (appPath); + path = path.parent_path (); + path = path.append ("VST3"); +#if LOG_ENABLE + std::cout << "Check folder: " << path << "\n"; +#endif + findModules (path, list); + + return list; +} + +//------------------------------------------------------------------------ +Optional Module::getModuleInfoPath (const std::string& modulePath) +{ + auto path = getContentsDirectoryFromModuleExecutablePath (modulePath); + if (!path) + { + filesystem::path p; + if (!checkVST3Package ({modulePath}, &p)) + return {}; + p = p.parent_path (); + p = p.parent_path (); + path = Optional {p}; + } + + *path /= "Resources"; + *path /= "moduleinfo.json"; + + if (filesystem::exists (*path)) + { + return {path->generic_string ()}; + } + return {}; +} + +//------------------------------------------------------------------------ +Module::SnapshotList Module::getSnapshots (const std::string& modulePath) +{ + SnapshotList result; + auto path = getContentsDirectoryFromModuleExecutablePath (modulePath); + if (!path) + { + filesystem::path p; + if (!checkVST3Package ({modulePath}, &p)) + return result; + p = p.parent_path (); + p = p.parent_path (); + path = Optional (p); + } + + *path /= "Resources"; + *path /= "Snapshots"; + + if (filesystem::exists (*path) == false) + return result; + + PathList pngList; + findFilesWithExt (*path, ".png", pngList, false); + for (auto& png : pngList) + { + filesystem::path p (png); + auto filename = p.filename ().generic_string (); + auto uid = Snapshot::decodeUID (filename); + if (!uid) + continue; + auto scaleFactor = 1.; + if (auto decodedScaleFactor = Snapshot::decodeScaleFactor (filename)) + scaleFactor = *decodedScaleFactor; + + Module::Snapshot::ImageDesc desc; + desc.scaleFactor = scaleFactor; + desc.path = std::move (png); + bool found = false; + for (auto& entry : result) + { + if (entry.uid != *uid) + continue; + found = true; + entry.images.emplace_back (std::move (desc)); + break; + } + if (found) + continue; + Module::Snapshot snapshot; + snapshot.uid = *uid; + snapshot.images.emplace_back (std::move (desc)); + result.emplace_back (std::move (snapshot)); + } + return result; +} + +//------------------------------------------------------------------------ +} // Hosting +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.cpp index c4d2a66..adabdb9 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -43,12 +43,14 @@ #include +//----------------------------------------------------------------------------- namespace Steinberg { namespace Vst { //----------------------------------------------------------------------------- PlugInterfaceSupport::PlugInterfaceSupport () { + FUNKNOWN_CTOR // add minimum set //---VST 3.0.0-------------------------------- @@ -117,9 +119,16 @@ void PlugInterfaceSupport::addPlugInterfaceSupported (const TUID _iid) //----------------------------------------------------------------------------- bool PlugInterfaceSupport::removePlugInterfaceSupported (const TUID _iid) { - return std::remove (mFUIDArray.begin (), mFUIDArray.end (), FUID::fromTUID (_iid)) != - mFUIDArray.end (); + auto uid = FUID::fromTUID (_iid); + auto it = std::find (mFUIDArray.begin (), mFUIDArray.end (), uid); + if (it == mFUIDArray.end ()) + return false; + mFUIDArray.erase (it); + return true; } -} -} // namespace +IMPLEMENT_FUNKNOWN_METHODS (PlugInterfaceSupport, IPlugInterfaceSupport, IPlugInterfaceSupport::iid) + +//----------------------------------------------------------------------------- +} // Vst +} // Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.h index ad204b7..a88cbf6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/hosting/pluginterfacesupport.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -39,19 +39,19 @@ #include "pluginterfaces/vst/ivstpluginterfacesupport.h" #include -#include "base/source/fobject.h" namespace Steinberg { namespace Vst { //------------------------------------------------------------------------ -/** Implementation's example of IPlugInterfaceSupport. +/** Example implementation of IPlugInterfaceSupport. \ingroup hostingBase */ -class PlugInterfaceSupport : public FObject, public IPlugInterfaceSupport +class PlugInterfaceSupport : public IPlugInterfaceSupport { public: PlugInterfaceSupport (); + virtual ~PlugInterfaceSupport () = default; //--- IPlugInterfaceSupport --------- tresult PLUGIN_API isPlugInterfaceSupported (const TUID _iid) SMTG_OVERRIDE; @@ -59,11 +59,7 @@ class PlugInterfaceSupport : public FObject, public IPlugInterfaceSupport void addPlugInterfaceSupported (const TUID _iid); bool removePlugInterfaceSupported (const TUID _iid); - OBJ_METHODS (PlugInterfaceSupport, FObject) - REFCOUNT_METHODS (FObject) - DEFINE_INTERFACES - DEF_INTERFACE (IPlugInterfaceSupport) - END_DEFINE_INTERFACES (FObject) + DECLARE_FUNKNOWN_METHODS private: std::vector mFUIDArray; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md new file mode 100644 index 0000000..6c93872 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/ReadMe.md @@ -0,0 +1,43 @@ + +# ModuleInfoLib + +This is a c++17 library to parse and create the Steinberg moduleinfo.json files. + +## Parsing + +To parse a moduleinfo.json file you need to include the following files to your project: + +* moduleinfoparser.cpp +* moduleinfoparser.h +* moduleinfo.h +* json.h +* jsoncxx.h + +And add a header search path to the root folder of the VST SDK. + +Now to parse a moduleinfo.json file in code you need to read the moduleinfo.json into a memory buffer and call + +``` c++ +auto moduleInfo = ModuleInfoLib::parseCompatibilityJson (std::string_view (buffer, bufferSize), &std::cerr); +``` + +Afterwards if parsing succeeded the moduleInfo optional has a value containing the ModuleInfo. + +## Creating + +The VST3 SDK contains the moduleinfotool utility that can create moduleinfo.json files from VST3 modules. + +To add this capability to your own project you need to link to the sdk_hosting library from the SDK and include the following files to your project: + +* moduleinfocreator.cpp +* moduleinfocreator.h +* moduleinfo.h + +Additionally you need to add the module platform implementation from the hosting directory (module_win32.cpp, module_mac.mm or module_linux.cpp). + +Now you can use the two methods in moduleinfocreator.h to create a moduleinfo.json file: + +``` c++ +auto moduleInfo = ModuleInfoLib::createModuleInfo (module, false); +ModuleInfoLib::outputJson (moduleInfo, std::cout); +``` diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h new file mode 100644 index 0000000..da8a6ad --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/json.h @@ -0,0 +1,3403 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/json.h. +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to . +*/ + +#ifndef SHEREDOM_JSON_H_INCLUDED +#define SHEREDOM_JSON_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push) + +/* disable warning: no function prototype given: converting '()' to '(void)' */ +#pragma warning(disable : 4255) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) + +/* disable warning: 'bytes padding added after construct' */ +#pragma warning(disable : 4820) +#endif + +#include +#include + +#if defined(_MSC_VER) || defined(__MINGW32__) +#define json_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define json_weak __attribute__((weak)) +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct json_value_s; +struct json_parse_result_s; + +enum json_parse_flags_e { + json_parse_flags_default = 0, + + /* allow trailing commas in objects and arrays. For example, both [true,] and + {"a" : null,} would be allowed with this option on. */ + json_parse_flags_allow_trailing_comma = 0x1, + + /* allow unquoted keys for objects. For example, {a : null} would be allowed + with this option on. */ + json_parse_flags_allow_unquoted_keys = 0x2, + + /* allow a global unbracketed object. For example, a : null, b : true, c : {} + would be allowed with this option on. */ + json_parse_flags_allow_global_object = 0x4, + + /* allow objects to use '=' instead of ':' between key/value pairs. For + example, a = null, b : true would be allowed with this option on. */ + json_parse_flags_allow_equals_in_object = 0x8, + + /* allow that objects don't have to have comma separators between key/value + pairs. */ + json_parse_flags_allow_no_commas = 0x10, + + /* allow c-style comments (either variants) to be ignored in the input JSON + file. */ + json_parse_flags_allow_c_style_comments = 0x20, + + /* deprecated flag, unused. */ + json_parse_flags_deprecated = 0x40, + + /* record location information for each value. */ + json_parse_flags_allow_location_information = 0x80, + + /* allow strings to be 'single quoted'. */ + json_parse_flags_allow_single_quoted_strings = 0x100, + + /* allow numbers to be hexadecimal. */ + json_parse_flags_allow_hexadecimal_numbers = 0x200, + + /* allow numbers like +123 to be parsed. */ + json_parse_flags_allow_leading_plus_sign = 0x400, + + /* allow numbers like .0123 or 123. to be parsed. */ + json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800, + + /* allow Infinity, -Infinity, NaN, -NaN. */ + json_parse_flags_allow_inf_and_nan = 0x1000, + + /* allow multi line string values. */ + json_parse_flags_allow_multi_line_strings = 0x2000, + + /* allow simplified JSON to be parsed. Simplified JSON is an enabling of a set + of other parsing options. */ + json_parse_flags_allow_simplified_json = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_global_object | + json_parse_flags_allow_equals_in_object | + json_parse_flags_allow_no_commas), + + /* allow JSON5 to be parsed. JSON5 is an enabling of a set of other parsing + options. */ + json_parse_flags_allow_json5 = + (json_parse_flags_allow_trailing_comma | + json_parse_flags_allow_unquoted_keys | + json_parse_flags_allow_c_style_comments | + json_parse_flags_allow_single_quoted_strings | + json_parse_flags_allow_hexadecimal_numbers | + json_parse_flags_allow_leading_plus_sign | + json_parse_flags_allow_leading_or_trailing_decimal_point | + json_parse_flags_allow_inf_and_nan | + json_parse_flags_allow_multi_line_strings) +}; + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to malloc for the entire encoding. + * Returns 0 if an error occurred (malformed JSON input, or malloc failed). */ +json_weak struct json_value_s *json_parse(const void *src, size_t src_size); + +/* Parse a JSON text file, returning a pointer to the root of the JSON + * structure. json_parse performs 1 call to alloc_func_ptr for the entire + * encoding. Returns 0 if an error occurred (malformed JSON input, or malloc + * failed). If an error occurred, the result struct (if not NULL) will explain + * the type of error, and the location in the input it occurred. If + * alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *, size_t), void *user_data, + struct json_parse_result_s *result); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to malloc for the entire encoding. + */ +json_weak struct json_value_s * +json_extract_value(const struct json_value_s *value); + +/* Extracts a value and all the data that makes it up into a newly created + * value. json_extract_value performs 1 call to alloc_func_ptr for the entire + * encoding. If alloc_func_ptr is null then malloc is used. */ +json_weak struct json_value_s * +json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, size_t), void *user_data); + +/* Write out a minified JSON utf-8 string. This string is an encoding of the + * minimal string characters required to still encode the same data. + * json_write_minified performs 1 call to malloc for the entire encoding. Return + * 0 if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_minified(const struct json_value_s *value, + size_t *out_size); + +/* Write out a pretty JSON utf-8 string. This string is encoded such that the + * resultant JSON is pretty in that it is easily human readable. The indent and + * newline parameters allow a user to specify what kind of indentation and + * newline they want (two spaces / three spaces / tabs? \r, \n, \r\n ?). Both + * indent and newline can be NULL, indent defaults to two spaces (" "), and + * newline defaults to linux newlines ('\n' as the newline character). + * json_write_pretty performs 1 call to malloc for the entire encoding. Return 0 + * if an error occurred (malformed JSON input, or malloc failed). The out_size + * parameter is optional as the utf-8 string is null terminated. */ +json_weak void *json_write_pretty(const struct json_value_s *value, + const char *indent, const char *newline, + size_t *out_size); + +/* Reinterpret a JSON value as a string. Returns null is the value was not a + * string. */ +json_weak struct json_string_s * +json_value_as_string(struct json_value_s *const value); + +/* Reinterpret a JSON value as a number. Returns null is the value was not a + * number. */ +json_weak struct json_number_s * +json_value_as_number(struct json_value_s *const value); + +/* Reinterpret a JSON value as an object. Returns null is the value was not an + * object. */ +json_weak struct json_object_s * +json_value_as_object(struct json_value_s *const value); + +/* Reinterpret a JSON value as an array. Returns null is the value was not an + * array. */ +json_weak struct json_array_s * +json_value_as_array(struct json_value_s *const value); + +/* Whether the value is true. */ +json_weak int json_value_is_true(const struct json_value_s *const value); + +/* Whether the value is false. */ +json_weak int json_value_is_false(const struct json_value_s *const value); + +/* Whether the value is null. */ +json_weak int json_value_is_null(const struct json_value_s *const value); + +/* The various types JSON values can be. Used to identify what a value is. */ +enum json_type_e { + json_type_string, + json_type_number, + json_type_object, + json_type_array, + json_type_true, + json_type_false, + json_type_null +}; + +/* A JSON string value. */ +struct json_string_s { + /* utf-8 string */ + const char *string; + /* The size (in bytes) of the string */ + size_t string_size; +}; + +/* A JSON string value (extended). */ +struct json_string_ex_s { + /* The JSON string this extends. */ + struct json_string_s string; + + /* The character offset for the value in the JSON input. */ + size_t offset; + + /* The line number for the value in the JSON input. */ + size_t line_no; + + /* The row number for the value in the JSON input, in bytes. */ + size_t row_no; +}; + +/* A JSON number value. */ +struct json_number_s { + /* ASCII string containing representation of the number. */ + const char *number; + /* the size (in bytes) of the number. */ + size_t number_size; +}; + +/* an element of a JSON object. */ +struct json_object_element_s { + /* the name of this element. */ + struct json_string_s *name; + /* the value of this element. */ + struct json_value_s *value; + /* the next object element (can be NULL if the last element in the object). */ + struct json_object_element_s *next; +}; + +/* a JSON object value. */ +struct json_object_s { + /* a linked list of the elements in the object. */ + struct json_object_element_s *start; + /* the number of elements in the object. */ + size_t length; +}; + +/* an element of a JSON array. */ +struct json_array_element_s { + /* the value of this element. */ + struct json_value_s *value; + /* the next array element (can be NULL if the last element in the array). */ + struct json_array_element_s *next; +}; + +/* a JSON array value. */ +struct json_array_s { + /* a linked list of the elements in the array. */ + struct json_array_element_s *start; + /* the number of elements in the array. */ + size_t length; +}; + +/* a JSON value. */ +struct json_value_s { + /* a pointer to either a json_string_s, json_number_s, json_object_s, or. */ + /* json_array_s. Should be cast to the appropriate struct type based on what. + */ + /* the type of this value is. */ + void *payload; + /* must be one of json_type_e. If type is json_type_true, json_type_false, or. + */ + /* json_type_null, payload will be NULL. */ + size_t type; +}; + +/* a JSON value (extended). */ +struct json_value_ex_s { + /* the JSON value this extends. */ + struct json_value_s value; + + /* the character offset for the value in the JSON input. */ + size_t offset; + + /* the line number for the value in the JSON input. */ + size_t line_no; + + /* the row number for the value in the JSON input, in bytes. */ + size_t row_no; +}; + +/* a parsing error code. */ +enum json_parse_error_e { + /* no error occurred (huzzah!). */ + json_parse_error_none = 0, + + /* expected either a comma or a closing '}' or ']' to close an object or. */ + /* array! */ + json_parse_error_expected_comma_or_closing_bracket, + + /* colon separating name/value pair was missing! */ + json_parse_error_expected_colon, + + /* expected string to begin with '"'! */ + json_parse_error_expected_opening_quote, + + /* invalid escaped sequence in string! */ + json_parse_error_invalid_string_escape_sequence, + + /* invalid number format! */ + json_parse_error_invalid_number_format, + + /* invalid value! */ + json_parse_error_invalid_value, + + /* reached end of buffer before object/array was complete! */ + json_parse_error_premature_end_of_buffer, + + /* string was malformed! */ + json_parse_error_invalid_string, + + /* a call to malloc, or a user provider allocator, failed. */ + json_parse_error_allocator_failed, + + /* the JSON input had unexpected trailing characters that weren't part of the. + */ + /* JSON value. */ + json_parse_error_unexpected_trailing_characters, + + /* catch-all error for everything else that exploded (real bad chi!). */ + json_parse_error_unknown +}; + +/* error report from json_parse_ex(). */ +struct json_parse_result_s { + /* the error code (one of json_parse_error_e). */ + size_t error; + + /* the character offset for the error in the JSON input. */ + size_t error_offset; + + /* the line number for the error in the JSON input. */ + size_t error_line_no; + + /* the row number for the error, in bytes. */ + size_t error_row_no; +}; + +#ifdef __cplusplus +} /* extern "C". */ +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#define json_uintmax_t unsigned __int64 +#else +#include +#define json_uintmax_t uintmax_t +#endif + +#if defined(_MSC_VER) +#define json_strtoumax _strtoui64 +#else +#define json_strtoumax strtoumax +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define json_null nullptr +#else +#define json_null 0 +#endif + +#if defined(__clang__) +#pragma clang diagnostic push + +/* we do one big allocation via malloc, then cast aligned slices of this for. */ +/* our structures - we don't have a way to tell the compiler we know what we. */ +/* are doing, so disable the warning instead! */ +#pragma clang diagnostic ignored "-Wcast-align" + +/* We use C style casts everywhere. */ +#pragma clang diagnostic ignored "-Wold-style-cast" + +/* We need long long for strtoull. */ +#pragma clang diagnostic ignored "-Wc++11-long-long" + +/* Who cares if nullptr doesn't work with C++98, we don't use it there! */ +#pragma clang diagnostic ignored "-Wc++98-compat" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#elif defined(_MSC_VER) +#pragma warning(push) + +/* disable 'function selected for inline expansion' warning. */ +#pragma warning(disable : 4711) + +/* disable '#pragma warning: there is no warning number' warning. */ +#pragma warning(disable : 4619) + +/* disable 'warning number not a valid compiler warning' warning. */ +#pragma warning(disable : 4616) + +/* disable 'Compiler will insert Spectre mitigation for memory load if + * /Qspectre. */ +/* switch specified' warning. */ +#pragma warning(disable : 5045) +#endif + +struct json_parse_state_s { + const char *src; + size_t size; + size_t offset; + size_t flags_bitset; + char *data; + char *dom; + size_t dom_size; + size_t data_size; + size_t line_no; /* line counter for error reporting. */ + size_t line_offset; /* (offset-line_offset) is the character number (in + bytes). */ + size_t error; +}; + +json_weak int json_hexadecimal_digit(const char c); +int json_hexadecimal_digit(const char c) { + if ('0' <= c && c <= '9') { + return c - '0'; + } + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + if ('A' <= c && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +json_weak int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result); +int json_hexadecimal_value(const char *c, const unsigned long size, + unsigned long *result) { + const char *p; + int digit; + + if (size > sizeof(unsigned long) * 2) { + return 0; + } + + *result = 0; + for (p = c; (unsigned long)(p - c) < size; ++p) { + *result <<= 4; + digit = json_hexadecimal_digit(*p); + if (digit < 0 || digit > 15) { + return 0; + } + *result |= (unsigned char)digit; + } + return 1; +} + +json_weak int json_skip_whitespace(struct json_parse_state_s *state); +int json_skip_whitespace(struct json_parse_state_s *state) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + + /* the only valid whitespace according to ECMA-404 is ' ', '\n', '\r' and + * '\t'. */ + switch (src[offset]) { + default: + return 0; + case ' ': + case '\r': + case '\t': + case '\n': + break; + } + + do { + switch (src[offset]) { + default: + /* Update offset. */ + state->offset = offset; + return 1; + case ' ': + case '\r': + case '\t': + break; + case '\n': + state->line_no++; + state->line_offset = offset; + break; + } + + offset++; + } while (offset < size); + + /* Update offset. */ + state->offset = offset; + return 1; +} + +json_weak int json_skip_c_style_comments(struct json_parse_state_s *state); +int json_skip_c_style_comments(struct json_parse_state_s *state) { + /* do we have a comment?. */ + if ('/' == state->src[state->offset]) { + /* skip '/'. */ + state->offset++; + + if ('/' == state->src[state->offset]) { + /* we had a comment of the form //. */ + + /* skip second '/'. */ + state->offset++; + + while (state->offset < state->size) { + switch (state->src[state->offset]) { + default: + /* skip the character in the comment. */ + state->offset++; + break; + case '\n': + /* if we have a newline, our comment has ended! Skip the newline. */ + state->offset++; + + /* we entered a newline, so move our line info forward. */ + state->line_no++; + state->line_offset = state->offset; + return 1; + } + } + + /* we reached the end of the JSON file! */ + return 1; + } else if ('*' == state->src[state->offset]) { + /* we had a comment in the C-style long form. */ + + /* skip '*'. */ + state->offset++; + + while (state->offset + 1 < state->size) { + if (('*' == state->src[state->offset]) && + ('/' == state->src[state->offset + 1])) { + /* we reached the end of our comment! */ + state->offset += 2; + return 1; + } else if ('\n' == state->src[state->offset]) { + /* we entered a newline, so move our line info forward. */ + state->line_no++; + state->line_offset = state->offset; + } + + /* skip character within comment. */ + state->offset++; + } + + /* Comment wasn't ended correctly which is a failure. */ + return 1; + } + } + + /* we didn't have any comment, which is ok too! */ + return 0; +} + +json_weak int json_skip_all_skippables(struct json_parse_state_s *state); +int json_skip_all_skippables(struct json_parse_state_s *state) { + /* skip all whitespace and other skippables until there are none left. note + * that the previous version suffered from read past errors should. the + * stream end on json_skip_c_style_comments eg. '{"a" ' with comments flag. + */ + + int did_consume = 0; + const size_t size = state->size; + + if (json_parse_flags_allow_c_style_comments & state->flags_bitset) { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + + /* This should really be checked on access, not in front of every call. + */ + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume |= json_skip_c_style_comments(state); + } while (0 != did_consume); + } else { + do { + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + did_consume = json_skip_whitespace(state); + } while (0 != did_consume); + } + + if (state->offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); + +json_weak int json_get_string_size(struct json_parse_state_s *state, + size_t is_key); +int json_get_string_size(struct json_parse_state_s *state, size_t is_key) { + size_t offset = state->offset; + const size_t size = state->size; + size_t data_size = 0; + const char *const src = state->src; + const int is_single_quote = '\'' == src[offset]; + const char quote_to_use = is_single_quote ? '\'' : '"'; + const size_t flags_bitset = state->flags_bitset; + unsigned long codepoint; + unsigned long high_surrogate = 0; + + if ((json_parse_flags_allow_location_information & flags_bitset) != 0 && + is_key != 0) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + if ('"' != src[offset]) { + /* if we are allowed single quoted strings check for that too. */ + if (!((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + is_single_quote)) { + state->error = json_parse_error_expected_opening_quote; + state->offset = offset; + return 1; + } + } + + /* skip leading '"' or '\''. */ + offset++; + + while ((offset < size) && (quote_to_use != src[offset])) { + /* add space for the character. */ + data_size++; + + switch (src[offset]) { + default: + break; + case '\0': + case '\t': + state->error = json_parse_error_invalid_string; + state->offset = offset; + return 1; + } + + if ('\\' == src[offset]) { + /* skip reverse solidus character. */ + offset++; + + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset; + return 1; + } + + switch (src[offset]) { + default: + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + /* all valid characters! */ + offset++; + break; + case 'u': + if (!(offset + 5 < size)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + codepoint = 0; + if (!json_hexadecimal_value(&src[offset + 1], 4, &codepoint)) { + /* escaped unicode sequences must contain 4 hexadecimal digits! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + /* Valid sequence! + * see: https://en.wikipedia.org/wiki/UTF-8#Invalid_code_points. + * 1 7 U + 0000 U + 007F 0xxxxxxx. + * 2 11 U + 0080 U + 07FF 110xxxxx + * 10xxxxxx. + * 3 16 U + 0800 U + FFFF 1110xxxx + * 10xxxxxx 10xxxxxx. + * 4 21 U + 10000 U + 10FFFF 11110xxx + * 10xxxxxx 10xxxxxx 10xxxxxx. + * Note: the high and low surrogate halves used by UTF-16 (U+D800 + * through U+DFFF) and code points not encodable by UTF-16 (those after + * U+10FFFF) are not legal Unicode values, and their UTF-8 encoding must + * be treated as an invalid byte sequence. */ + + if (high_surrogate != 0) { + /* we previously read the high half of the \uxxxx\uxxxx pair, so now + * we expect the low half. */ + if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate range. */ + data_size += 3; + high_surrogate = 0; + } else { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + } else if (codepoint <= 0x7f) { + data_size += 0; + } else if (codepoint <= 0x7ff) { + data_size += 1; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate range. */ + /* The codepoint is the first half of a "utf-16 surrogate pair". so we + * need the other half for it to be valid: \uHHHH\uLLLL. */ + if (offset + 11 > size || '\\' != src[offset + 5] || + 'u' != src[offset + 6]) { + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + high_surrogate = codepoint; + } else if (codepoint >= 0xd800 && + codepoint <= 0xdfff) { /* low surrogate range. */ + /* we did not read the other half before. */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } else { + data_size += 2; + } + /* escaped codepoints after 0xffff are supported in json through utf-16 + * surrogate pairs: \uD83D\uDD25 for U+1F525. */ + + offset += 5; + break; + } + } else if (('\r' == src[offset]) || ('\n' == src[offset])) { + if (!(json_parse_flags_allow_multi_line_strings & flags_bitset)) { + /* invalid escaped unicode sequence! */ + state->error = json_parse_error_invalid_string_escape_sequence; + state->offset = offset; + return 1; + } + + offset++; + } else { + /* skip character (valid part of sequence). */ + offset++; + } + } + + /* If the offset is equal to the size, we had a non-terminated string! */ + if (offset == size) { + state->error = json_parse_error_premature_end_of_buffer; + state->offset = offset - 1; + return 1; + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* add enough space to store the string. */ + state->data_size += data_size; + + /* one more byte for null terminator ending the string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int is_valid_unquoted_key_char(const char c); +int is_valid_unquoted_key_char(const char c) { + return (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || ('_' == c)); +} + +json_weak int json_get_key_size(struct json_parse_state_s *state); +int json_get_key_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + + if (json_parse_flags_allow_unquoted_keys & flags_bitset) { + size_t offset = state->offset; + const size_t size = state->size; + const char *const src = state->src; + size_t data_size = state->data_size; + + /* if we are allowing unquoted keys, first grok for a quote... */ + if ('"' == src[offset]) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else if ((json_parse_flags_allow_single_quoted_strings & flags_bitset) && + ('\'' == src[offset])) { + /* ... if we got a comma, just parse the key as a string as normal. */ + return json_get_string_size(state, 1); + } else { + while ((offset < size) && is_valid_unquoted_key_char(src[offset])) { + offset++; + data_size++; + } + + /* one more byte for null terminator ending the string! */ + data_size++; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_string_ex_s); + } else { + state->dom_size += sizeof(struct json_string_s); + } + + /* update offset. */ + state->offset = offset; + + /* update data_size. */ + state->data_size = data_size; + + return 0; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + return json_get_string_size(state, 1); + } +} + +json_weak int json_get_object_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_object_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + int found_closing_brace = 0; + + if (is_global_object) { + /* if we found an opening '{' of an object, we actually have a normal JSON + * object at the root of the DOM... */ + if (!json_skip_all_skippables(state) && '{' == state->src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + if ('{' != src[state->offset]) { + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '{'. */ + state->offset++; + } + + state->dom_size += sizeof(struct json_object_s); + + if ((state->offset == size) && !is_global_object) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + do { + if (!is_global_object) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + found_closing_brace = 1; + + /* finished the object! */ + break; + } + } else { + /* we don't require brackets, so that means the object ends when the input + * stream ends! */ + if (json_skip_all_skippables(state)) { + break; + } + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (json_parse_flags_allow_no_commas & flags_bitset) { + /* we don't require a comma, and we didn't find one, which is ok! */ + allow_comma = 0; + } else { + /* otherwise we are required to have a comma, and we found none. */ + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_key_size(state)) { + /* key parsing failed! */ + state->error = json_parse_error_invalid_string; + return 1; + } + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + const char current = src[state->offset]; + if ((':' != current) && ('=' != current)) { + state->error = json_parse_error_expected_colon; + return 1; + } + } else { + if (':' != src[state->offset]) { + state->error = json_parse_error_expected_colon; + return 1; + } + } + + /* skip colon. */ + state->offset++; + + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + if ((state->offset == size) && !is_global_object && !found_closing_brace) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + state->dom_size += sizeof(struct json_object_element_s) * elements; + + return 0; +} + +json_weak int json_get_array_size(struct json_parse_state_s *state); +int json_get_array_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t elements = 0; + int allow_comma = 0; + const char *const src = state->src; + const size_t size = state->size; + + if ('[' != src[state->offset]) { + /* expected array to begin with leading '['. */ + state->error = json_parse_error_unknown; + return 1; + } + + /* skip leading '['. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_s); + + while (state->offset < size) { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + state->dom_size += sizeof(struct json_array_element_s) * elements; + + /* finished the object! */ + return 0; + } + + /* if we parsed at least once element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + } else if (!(json_parse_flags_allow_no_commas & flags_bitset)) { + state->error = json_parse_error_expected_comma_or_closing_bracket; + return 1; + } + + if (json_parse_flags_allow_trailing_comma & flags_bitset) { + allow_comma = 0; + continue; + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + } + } + + if (json_get_value_size(state, /* is_global_object = */ 0)) { + /* value parsing failed! */ + return 1; + } + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } + + /* we consumed the entire input before finding the closing ']' of the array! + */ + state->error = json_parse_error_premature_end_of_buffer; + return 1; +} + +json_weak int json_get_number_size(struct json_parse_state_s *state); +int json_get_number_size(struct json_parse_state_s *state) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + int had_leading_digits = 0; + const char *const src = state->src; + + state->dom_size += sizeof(struct json_number_s); + + if ((json_parse_flags_allow_hexadecimal_numbers & flags_bitset) && + (offset + 1 < size) && ('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* skip the leading 0x that identifies a hexadecimal number. */ + offset += 2; + + /* consume hexadecimal digits. */ + while ((offset < size) && (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F'))) { + offset++; + } + } else { + int found_sign = 0; + int inf_or_nan = 0; + + if ((offset < size) && + (('-' == src[offset]) || + ((json_parse_flags_allow_leading_plus_sign & flags_bitset) && + ('+' == src[offset])))) { + /* skip valid leading '-' or '+'. */ + offset++; + + found_sign = 1; + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const char inf[9] = "Infinity"; + const size_t inf_strlen = sizeof(inf) - 1; + const char nan[4] = "NaN"; + const size_t nan_strlen = sizeof(nan) - 1; + + if (offset + inf_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < inf_strlen; i++) { + if (inf[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'Infinity' keyword! */ + offset += inf_strlen; + + inf_or_nan = 1; + } + } + + if (offset + nan_strlen < size) { + int found = 1; + size_t i; + for (i = 0; i < nan_strlen; i++) { + if (nan[i] != src[offset + i]) { + found = 0; + break; + } + } + + if (found) { + /* We found our special 'NaN' keyword! */ + offset += nan_strlen; + + inf_or_nan = 1; + } + } + } + + if (found_sign && !inf_or_nan && (offset < size) && + !('0' <= src[offset] && src[offset] <= '9')) { + /* check if we are allowing leading '.'. */ + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + ('.' != src[offset])) { + /* a leading '-' must be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + if ((offset < size) && ('0' == src[offset])) { + /* skip valid '0'. */ + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + + if ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + /* a leading '0' must not be immediately followed by any digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* the main digits of our number next. */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + + /* we need to record whether we had any leading digits for checks later. + */ + had_leading_digits = 1; + } + + if ((offset < size) && ('.' == src[offset])) { + offset++; + + if (!('0' <= src[offset] && src[offset] <= '9')) { + if (!(json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) || + !had_leading_digits) { + /* a decimal point must be followed by at least one digit. */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + /* a decimal point can be followed by more digits of course! */ + while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')) { + offset++; + } + } + + if ((offset < size) && ('e' == src[offset] || 'E' == src[offset])) { + /* our number has an exponent! Skip 'e' or 'E'. */ + offset++; + + if ((offset < size) && ('-' == src[offset] || '+' == src[offset])) { + /* skip optional '-' or '+'. */ + offset++; + } + + if ((offset < size) && !('0' <= src[offset] && src[offset] <= '9')) { + /* an exponent must have at least one digit! */ + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + + /* consume exponent digits. */ + do { + offset++; + } while ((offset < size) && ('0' <= src[offset] && src[offset] <= '9')); + } + } + + if (offset < size) { + switch (src[offset]) { + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ',': + case ']': + /* all of the above are ok. */ + break; + case '=': + if (json_parse_flags_allow_equals_in_object & flags_bitset) { + break; + } + + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + default: + state->error = json_parse_error_invalid_number_format; + state->offset = offset; + return 1; + } + } + + state->data_size += offset - state->offset; + + /* one more byte for null terminator ending the number string! */ + state->data_size++; + + /* update offset. */ + state->offset = offset; + + return 0; +} + +json_weak int json_get_value_size(struct json_parse_state_s *state, + int is_global_object); +int json_get_value_size(struct json_parse_state_s *state, + int is_global_object) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + size_t offset; + const size_t size = state->size; + + if (json_parse_flags_allow_location_information & flags_bitset) { + state->dom_size += sizeof(struct json_value_ex_s); + } else { + state->dom_size += sizeof(struct json_value_s); + } + + if (is_global_object) { + return json_get_object_size(state, /* is_global_object = */ 1); + } else { + if (json_skip_all_skippables(state)) { + state->error = json_parse_error_premature_end_of_buffer; + return 1; + } + + /* can cache offset now. */ + offset = state->offset; + + switch (src[offset]) { + case '"': + return json_get_string_size(state, 0); + case '\'': + if (json_parse_flags_allow_single_quoted_strings & flags_bitset) { + return json_get_string_size(state, 0); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + case '{': + return json_get_object_size(state, /* is_global_object = */ 0); + case '[': + return json_get_array_size(state); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_get_number_size(state); + case '+': + if (json_parse_flags_allow_leading_plus_sign & flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + case '.': + if (json_parse_flags_allow_leading_or_trailing_decimal_point & + flags_bitset) { + return json_get_number_size(state); + } else { + /* invalid value! */ + state->error = json_parse_error_invalid_number_format; + return 1; + } + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + state->offset += 5; + return 0; + } else if ((offset + 4) <= size && 'n' == state->src[offset + 0] && + 'u' == state->src[offset + 1] && + 'l' == state->src[offset + 2] && + 'l' == state->src[offset + 3]) { + state->offset += 4; + return 0; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + return json_get_number_size(state); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + return json_get_number_size(state); + } + + /* invalid value! */ + state->error = json_parse_error_invalid_value; + return 1; + } + } +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); + +json_weak void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_string(struct json_parse_state_s *state, + struct json_string_s *string) { + size_t offset = state->offset; + size_t bytes_written = 0; + const char *const src = state->src; + const char quote_to_use = '\'' == src[offset] ? '\'' : '"'; + char *data = state->data; + unsigned long high_surrogate = 0; + unsigned long codepoint; + + string->string = data; + + /* skip leading '"' or '\''. */ + offset++; + + while (quote_to_use != src[offset]) { + if ('\\' == src[offset]) { + /* skip the reverse solidus. */ + offset++; + + switch (src[offset++]) { + default: + return; /* we cannot ever reach here. */ + case 'u': { + codepoint = 0; + if (!json_hexadecimal_value(&src[offset], 4, &codepoint)) { + return; /* this shouldn't happen as the value was already validated. + */ + } + + offset += 4; + + if (codepoint <= 0x7fu) { + data[bytes_written++] = (char)codepoint; /* 0xxxxxxx. */ + } else if (codepoint <= 0x7ffu) { + data[bytes_written++] = + (char)(0xc0u | (codepoint >> 6)); /* 110xxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else if (codepoint >= 0xd800 && + codepoint <= 0xdbff) { /* high surrogate. */ + high_surrogate = codepoint; + continue; /* we need the low half to form a complete codepoint. */ + } else if (codepoint >= 0xdc00 && + codepoint <= 0xdfff) { /* low surrogate. */ + /* combine with the previously read half to obtain the complete + * codepoint. */ + const unsigned long surrogate_offset = + 0x10000u - (0xD800u << 10) - 0xDC00u; + codepoint = (high_surrogate << 10) + codepoint + surrogate_offset; + high_surrogate = 0; + data[bytes_written++] = + (char)(0xF0u | (codepoint >> 18)); /* 11110xxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 12) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } else { + /* we assume the value was validated and thus is within the valid + * range. */ + data[bytes_written++] = + (char)(0xe0u | (codepoint >> 12)); /* 1110xxxx. */ + data[bytes_written++] = + (char)(0x80u | ((codepoint >> 6) & 0x3fu)); /* 10xxxxxx. */ + data[bytes_written++] = + (char)(0x80u | (codepoint & 0x3fu)); /* 10xxxxxx. */ + } + } break; + case '"': + data[bytes_written++] = '"'; + break; + case '\\': + data[bytes_written++] = '\\'; + break; + case '/': + data[bytes_written++] = '/'; + break; + case 'b': + data[bytes_written++] = '\b'; + break; + case 'f': + data[bytes_written++] = '\f'; + break; + case 'n': + data[bytes_written++] = '\n'; + break; + case 'r': + data[bytes_written++] = '\r'; + break; + case 't': + data[bytes_written++] = '\t'; + break; + case '\r': + data[bytes_written++] = '\r'; + + /* check if we have a "\r\n" sequence. */ + if ('\n' == src[offset]) { + data[bytes_written++] = '\n'; + offset++; + } + + break; + case '\n': + data[bytes_written++] = '\n'; + break; + } + } else { + /* copy the character. */ + data[bytes_written++] = src[offset++]; + } + } + + /* skip trailing '"' or '\''. */ + offset++; + + /* record the size of the string. */ + string->string_size = bytes_written; + + /* add null terminator to string. */ + data[bytes_written++] = '\0'; + + /* move data along. */ + state->data += bytes_written; + + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string); +void json_parse_key(struct json_parse_state_s *state, + struct json_string_s *string) { + if (json_parse_flags_allow_unquoted_keys & state->flags_bitset) { + const char *const src = state->src; + char *const data = state->data; + size_t offset = state->offset; + + /* if we are allowing unquoted keys, check for quoted anyway... */ + if (('"' == src[offset]) || ('\'' == src[offset])) { + /* ... if we got a quote, just parse the key as a string as normal. */ + json_parse_string(state, string); + } else { + size_t size = 0; + + string->string = state->data; + + while (is_valid_unquoted_key_char(src[offset])) { + data[size++] = src[offset++]; + } + + /* add null terminator to string. */ + data[size] = '\0'; + + /* record the size of the string. */ + string->string_size = size++; + + /* move data along. */ + state->data += size; + + /* update offset. */ + state->offset = offset; + } + } else { + /* we are only allowed to have quoted keys, so just parse a string! */ + json_parse_string(state, string); + } +} + +json_weak void json_parse_object(struct json_parse_state_s *state, + int is_global_object, + struct json_object_s *object); +void json_parse_object(struct json_parse_state_s *state, int is_global_object, + struct json_object_s *object) { + const size_t flags_bitset = state->flags_bitset; + const size_t size = state->size; + const char *const src = state->src; + size_t elements = 0; + int allow_comma = 0; + struct json_object_element_s *previous = json_null; + + if (is_global_object) { + /* if we skipped some whitespace, and then found an opening '{' of an. */ + /* object, we actually have a normal JSON object at the root of the DOM... + */ + if ('{' == src[state->offset]) { + /* . and we don't actually have a global object after all! */ + is_global_object = 0; + } + } + + if (!is_global_object) { + /* skip leading '{'. */ + state->offset++; + } + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + while (state->offset < size) { + struct json_object_element_s *element = json_null; + struct json_string_s *string = json_null; + struct json_value_s *value = json_null; + + if (!is_global_object) { + (void)json_skip_all_skippables(state); + + if ('}' == src[state->offset]) { + /* skip trailing '}'. */ + state->offset++; + + /* finished the object! */ + break; + } + } else { + if (json_skip_all_skippables(state)) { + /* global object ends when the file ends! */ + break; + } + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_object_element_s *)state->dom; + + state->dom += sizeof(struct json_object_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our object. */ + object->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_string_ex_s *string_ex = + (struct json_string_ex_s *)state->dom; + state->dom += sizeof(struct json_string_ex_s); + + string_ex->offset = state->offset; + string_ex->line_no = state->line_no; + string_ex->row_no = state->offset - state->line_offset; + + string = &(string_ex->string); + } else { + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + } + + element->name = string; + + (void)json_parse_key(state, string); + + (void)json_skip_all_skippables(state); + + /* skip colon or equals. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + if (json_parse_flags_allow_location_information & flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed a name/value pair! */ + elements++; + allow_comma = 1; + } + + /* if we had at least one element, end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + object->start = json_null; + } + + object->length = elements; +} + +json_weak void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array); +void json_parse_array(struct json_parse_state_s *state, + struct json_array_s *array) { + const char *const src = state->src; + const size_t size = state->size; + size_t elements = 0; + int allow_comma = 0; + struct json_array_element_s *previous = json_null; + + /* skip leading '['. */ + state->offset++; + + (void)json_skip_all_skippables(state); + + /* reset elements. */ + elements = 0; + + do { + struct json_array_element_s *element = json_null; + struct json_value_s *value = json_null; + + (void)json_skip_all_skippables(state); + + if (']' == src[state->offset]) { + /* skip trailing ']'. */ + state->offset++; + + /* finished the array! */ + break; + } + + /* if we parsed at least one element previously, grok for a comma. */ + if (allow_comma) { + if (',' == src[state->offset]) { + /* skip comma. */ + state->offset++; + allow_comma = 0; + continue; + } + } + + element = (struct json_array_element_s *)state->dom; + + state->dom += sizeof(struct json_array_element_s); + + if (json_null == previous) { + /* this is our first element, so record it in our array. */ + array->start = element; + } else { + previous->next = element; + } + + previous = element; + + if (json_parse_flags_allow_location_information & state->flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state->dom; + state->dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state->offset; + value_ex->line_no = state->line_no; + value_ex->row_no = state->offset - state->line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + } + + element->value = value; + + json_parse_value(state, /* is_global_object = */ 0, value); + + /* successfully parsed an array element! */ + elements++; + allow_comma = 1; + } while (state->offset < size); + + /* end the linked list. */ + if (previous) { + previous->next = json_null; + } + + if (0 == elements) { + array->start = json_null; + } + + array->length = elements; +} + +json_weak void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number); +void json_parse_number(struct json_parse_state_s *state, + struct json_number_s *number) { + const size_t flags_bitset = state->flags_bitset; + size_t offset = state->offset; + const size_t size = state->size; + size_t bytes_written = 0; + const char *const src = state->src; + char *data = state->data; + + number->number = data; + + if (json_parse_flags_allow_hexadecimal_numbers & flags_bitset) { + if (('0' == src[offset]) && + (('x' == src[offset + 1]) || ('X' == src[offset + 1]))) { + /* consume hexadecimal digits. */ + while ((offset < size) && + (('0' <= src[offset] && src[offset] <= '9') || + ('a' <= src[offset] && src[offset] <= 'f') || + ('A' <= src[offset] && src[offset] <= 'F') || + ('x' == src[offset]) || ('X' == src[offset]))) { + data[bytes_written++] = src[offset++]; + } + } + } + + while (offset < size) { + int end = 0; + + switch (src[offset]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + case 'e': + case 'E': + case '+': + case '-': + data[bytes_written++] = src[offset++]; + break; + default: + end = 1; + break; + } + + if (0 != end) { + break; + } + } + + if (json_parse_flags_allow_inf_and_nan & flags_bitset) { + const size_t inf_strlen = 8; /* = strlen("Infinity");. */ + const size_t nan_strlen = 3; /* = strlen("NaN");. */ + + if (offset + inf_strlen < size) { + if ('I' == src[offset]) { + size_t i; + /* We found our special 'Infinity' keyword! */ + for (i = 0; i < inf_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + + if (offset + nan_strlen < size) { + if ('N' == src[offset]) { + size_t i; + /* We found our special 'NaN' keyword! */ + for (i = 0; i < nan_strlen; i++) { + data[bytes_written++] = src[offset++]; + } + } + } + } + + /* record the size of the number. */ + number->number_size = bytes_written; + /* add null terminator to number string. */ + data[bytes_written++] = '\0'; + /* move data along. */ + state->data += bytes_written; + /* update offset. */ + state->offset = offset; +} + +json_weak void json_parse_value(struct json_parse_state_s *state, + int is_global_object, + struct json_value_s *value); +void json_parse_value(struct json_parse_state_s *state, int is_global_object, + struct json_value_s *value) { + const size_t flags_bitset = state->flags_bitset; + const char *const src = state->src; + const size_t size = state->size; + size_t offset; + + (void)json_skip_all_skippables(state); + + /* cache offset now. */ + offset = state->offset; + + if (is_global_object) { + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 1, + (struct json_object_s *)value->payload); + } else { + switch (src[offset]) { + case '"': + case '\'': + value->type = json_type_string; + value->payload = state->dom; + state->dom += sizeof(struct json_string_s); + json_parse_string(state, (struct json_string_s *)value->payload); + break; + case '{': + value->type = json_type_object; + value->payload = state->dom; + state->dom += sizeof(struct json_object_s); + json_parse_object(state, /* is_global_object = */ 0, + (struct json_object_s *)value->payload); + break; + case '[': + value->type = json_type_array; + value->payload = state->dom; + state->dom += sizeof(struct json_array_s); + json_parse_array(state, (struct json_array_s *)value->payload); + break; + case '-': + case '+': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + break; + default: + if ((offset + 4) <= size && 't' == src[offset + 0] && + 'r' == src[offset + 1] && 'u' == src[offset + 2] && + 'e' == src[offset + 3]) { + value->type = json_type_true; + value->payload = json_null; + state->offset += 4; + } else if ((offset + 5) <= size && 'f' == src[offset + 0] && + 'a' == src[offset + 1] && 'l' == src[offset + 2] && + 's' == src[offset + 3] && 'e' == src[offset + 4]) { + value->type = json_type_false; + value->payload = json_null; + state->offset += 5; + } else if ((offset + 4) <= size && 'n' == src[offset + 0] && + 'u' == src[offset + 1] && 'l' == src[offset + 2] && + 'l' == src[offset + 3]) { + value->type = json_type_null; + value->payload = json_null; + state->offset += 4; + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 3) <= size && 'N' == src[offset + 0] && + 'a' == src[offset + 1] && 'N' == src[offset + 2]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } else if ((json_parse_flags_allow_inf_and_nan & flags_bitset) && + (offset + 8) <= size && 'I' == src[offset + 0] && + 'n' == src[offset + 1] && 'f' == src[offset + 2] && + 'i' == src[offset + 3] && 'n' == src[offset + 4] && + 'i' == src[offset + 5] && 't' == src[offset + 6] && + 'y' == src[offset + 7]) { + value->type = json_type_number; + value->payload = state->dom; + state->dom += sizeof(struct json_number_s); + json_parse_number(state, (struct json_number_s *)value->payload); + } + break; + } + } +} + +struct json_value_s * +json_parse_ex(const void *src, size_t src_size, size_t flags_bitset, + void *(*alloc_func_ptr)(void *user_data, size_t size), + void *user_data, struct json_parse_result_s *result) { + struct json_parse_state_s state; + void *allocation; + struct json_value_s *value; + size_t total_size; + int input_error; + + if (result) { + result->error = json_parse_error_none; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + if (json_null == src) { + /* invalid src pointer was null! */ + return json_null; + } + + state.src = (const char *)src; + state.size = src_size; + state.offset = 0; + state.line_no = 1; + state.line_offset = 0; + state.error = json_parse_error_none; + state.dom_size = 0; + state.data_size = 0; + state.flags_bitset = flags_bitset; + + input_error = json_get_value_size( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset)); + + if (0 == input_error) { + json_skip_all_skippables(&state); + + if (state.offset != state.size) { + /* our parsing didn't have an error, but there are characters remaining in + * the input that weren't part of the JSON! */ + + state.error = json_parse_error_unexpected_trailing_characters; + input_error = 1; + } + } + + if (input_error) { + /* parsing value's size failed (most likely an invalid JSON DOM!). */ + if (result) { + result->error = state.error; + result->error_offset = state.offset; + result->error_line_no = state.line_no; + result->error_row_no = state.offset - state.line_offset; + } + return json_null; + } + + /* our total allocation is the combination of the dom and data sizes (we. */ + /* first encode the structure of the JSON, and then the data referenced by. */ + /* the JSON values). */ + total_size = state.dom_size + state.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + if (json_null == allocation) { + /* malloc failed! */ + if (result) { + result->error = json_parse_error_allocator_failed; + result->error_offset = 0; + result->error_line_no = 0; + result->error_row_no = 0; + } + + return json_null; + } + + /* reset offset so we can reuse it. */ + state.offset = 0; + + /* reset the line information so we can reuse it. */ + state.line_no = 1; + state.line_offset = 0; + + state.dom = (char *)allocation; + state.data = state.dom + state.dom_size; + + if (json_parse_flags_allow_location_information & state.flags_bitset) { + struct json_value_ex_s *value_ex = (struct json_value_ex_s *)state.dom; + state.dom += sizeof(struct json_value_ex_s); + + value_ex->offset = state.offset; + value_ex->line_no = state.line_no; + value_ex->row_no = state.offset - state.line_offset; + + value = &(value_ex->value); + } else { + value = (struct json_value_s *)state.dom; + state.dom += sizeof(struct json_value_s); + } + + json_parse_value( + &state, (int)(json_parse_flags_allow_global_object & state.flags_bitset), + value); + + return (struct json_value_s *)allocation; +} + +struct json_value_s *json_parse(const void *src, size_t src_size) { + return json_parse_ex(src, src_size, json_parse_flags_default, json_null, + json_null, json_null); +} + +struct json_extract_result_s { + size_t dom_size; + size_t data_size; +}; + +struct json_value_s *json_extract_value(const struct json_value_s *value) { + return json_extract_value_ex(value, json_null, json_null); +} + +json_weak struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number); +json_weak struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string); +json_weak struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object); +json_weak struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array); +json_weak struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value); + +struct json_extract_result_s +json_extract_get_number_size(const struct json_number_s *const number) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_number_s); + result.data_size = number->number_size; + return result; +} + +struct json_extract_result_s +json_extract_get_string_size(const struct json_string_s *const string) { + struct json_extract_result_s result; + result.dom_size = sizeof(struct json_string_s); + result.data_size = string->string_size + 1; + return result; +} + +struct json_extract_result_s +json_extract_get_object_size(const struct json_object_s *const object) { + struct json_extract_result_s result; + size_t i; + const struct json_object_element_s *element = object->start; + + result.dom_size = sizeof(struct json_object_s) + + (sizeof(struct json_object_element_s) * object->length); + result.data_size = 0; + + for (i = 0; i < object->length; i++) { + const struct json_extract_result_s string_result = + json_extract_get_string_size(element->name); + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += string_result.dom_size; + result.data_size += string_result.data_size; + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_array_size(const struct json_array_s *const array) { + struct json_extract_result_s result; + size_t i; + const struct json_array_element_s *element = array->start; + + result.dom_size = sizeof(struct json_array_s) + + (sizeof(struct json_array_element_s) * array->length); + result.data_size = 0; + + for (i = 0; i < array->length; i++) { + const struct json_extract_result_s value_result = + json_extract_get_value_size(element->value); + + result.dom_size += value_result.dom_size; + result.data_size += value_result.data_size; + + element = element->next; + } + + return result; +} + +struct json_extract_result_s +json_extract_get_value_size(const struct json_value_s *const value) { + struct json_extract_result_s result = {0, 0}; + + switch (value->type) { + default: + break; + case json_type_object: + result = json_extract_get_object_size( + (const struct json_object_s *)value->payload); + break; + case json_type_array: + result = json_extract_get_array_size( + (const struct json_array_s *)value->payload); + break; + case json_type_number: + result = json_extract_get_number_size( + (const struct json_number_s *)value->payload); + break; + case json_type_string: + result = json_extract_get_string_size( + (const struct json_string_s *)value->payload); + break; + } + + result.dom_size += sizeof(struct json_value_s); + + return result; +} + +struct json_extract_state_s { + char *dom; + char *data; +}; + +json_weak void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value); +void json_extract_copy_value(struct json_extract_state_s *const state, + const struct json_value_s *const value) { + struct json_string_s *string; + struct json_number_s *number; + struct json_object_s *object; + struct json_array_s *array; + struct json_value_s *new_value; + + memcpy(state->dom, value, sizeof(struct json_value_s)); + new_value = (struct json_value_s *)state->dom; + state->dom += sizeof(struct json_value_s); + new_value->payload = state->dom; + + if (json_type_string == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + } else if (json_type_number == value->type) { + memcpy(state->dom, value->payload, sizeof(struct json_number_s)); + number = (struct json_number_s *)state->dom; + state->dom += sizeof(struct json_number_s); + + memcpy(state->data, number->number, number->number_size); + number->number = state->data; + state->data += number->number_size; + } else if (json_type_object == value->type) { + struct json_object_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_object_s)); + object = (struct json_object_s *)state->dom; + state->dom += sizeof(struct json_object_s); + + element = object->start; + object->start = (struct json_object_element_s *)state->dom; + + for (i = 0; i < object->length; i++) { + struct json_value_s *previous_value; + struct json_object_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_object_element_s)); + element = (struct json_object_element_s *)state->dom; + state->dom += sizeof(struct json_object_element_s); + + string = element->name; + memcpy(state->dom, string, sizeof(struct json_string_s)); + string = (struct json_string_s *)state->dom; + state->dom += sizeof(struct json_string_s); + element->name = string; + + memcpy(state->data, string->string, string->string_size + 1); + string->string = state->data; + state->data += string->string_size + 1; + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_object_element_s *)state->dom; + } + } + } else if (json_type_array == value->type) { + struct json_array_element_s *element; + size_t i; + + memcpy(state->dom, value->payload, sizeof(struct json_array_s)); + array = (struct json_array_s *)state->dom; + state->dom += sizeof(struct json_array_s); + + element = array->start; + array->start = (struct json_array_element_s *)state->dom; + + for (i = 0; i < array->length; i++) { + struct json_value_s *previous_value; + struct json_array_element_s *previous_element; + + memcpy(state->dom, element, sizeof(struct json_array_element_s)); + element = (struct json_array_element_s *)state->dom; + state->dom += sizeof(struct json_array_element_s); + + previous_value = element->value; + element->value = (struct json_value_s *)state->dom; + json_extract_copy_value(state, previous_value); + + previous_element = element; + element = element->next; + + if (element) { + previous_element->next = (struct json_array_element_s *)state->dom; + } + } + } +} + +struct json_value_s *json_extract_value_ex(const struct json_value_s *value, + void *(*alloc_func_ptr)(void *, + size_t), + void *user_data) { + void *allocation; + struct json_extract_result_s result; + struct json_extract_state_s state; + size_t total_size; + + if (json_null == value) { + /* invalid value was null! */ + return json_null; + } + + result = json_extract_get_value_size(value); + total_size = result.dom_size + result.data_size; + + if (json_null == alloc_func_ptr) { + allocation = malloc(total_size); + } else { + allocation = alloc_func_ptr(user_data, total_size); + } + + state.dom = (char *)allocation; + state.data = state.dom + result.dom_size; + + json_extract_copy_value(&state, value); + + return (struct json_value_s *)allocation; +} + +struct json_string_s *json_value_as_string(struct json_value_s *const value) { + if (value->type != json_type_string) { + return json_null; + } + + return (struct json_string_s *)value->payload; +} + +struct json_number_s *json_value_as_number(struct json_value_s *const value) { + if (value->type != json_type_number) { + return json_null; + } + + return (struct json_number_s *)value->payload; +} + +struct json_object_s *json_value_as_object(struct json_value_s *const value) { + if (value->type != json_type_object) { + return json_null; + } + + return (struct json_object_s *)value->payload; +} + +struct json_array_s *json_value_as_array(struct json_value_s *const value) { + if (value->type != json_type_array) { + return json_null; + } + + return (struct json_array_s *)value->payload; +} + +int json_value_is_true(const struct json_value_s *const value) { + return value->type == json_type_true; +} + +int json_value_is_false(const struct json_value_s *const value) { + return value->type == json_type_false; +} + +int json_value_is_null(const struct json_value_s *const value) { + return value->type == json_type_null; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); + +json_weak int json_write_get_number_size(const struct json_number_s *number, + size_t *size); +int json_write_get_number_size(const struct json_number_s *number, + size_t *size) { + json_uintmax_t parsed_number; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* the number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + *size += i; + return 0; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '+' or '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf) { + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + *size += 22; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *size += 1; + } + } + + return 0; + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan) { + /* NaN becomes 1 because JSON can't support it. */ + *size += 1; + + return 0; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a leading decimal point. */ + *size += 1; + goto cleanup; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + /* 1 + because we had a trailing decimal point. */ + *size += 1; + goto cleanup; + } + +cleanup: + *size += number->number_size; /* the actual string of the number. */ + + /* if we had a leading '+' we don't record it in the JSON output. */ + if ('+' == number->number[0]) { + *size -= 1; + } + + return 0; +} + +json_weak int json_write_get_string_size(const struct json_string_s *string, + size_t *size); +int json_write_get_string_size(const struct json_string_s *string, + size_t *size) { + size_t i; + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + *size += 2; + break; + default: + *size += 1; + break; + } + } + + *size += 2; /* need to encode the surrounding '"' characters. */ + + return 0; +} + +json_weak int +json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size); +int json_write_minified_get_array_size(const struct json_array_s *array, + size_t *size) { + struct json_array_element_s *element; + + *size += 2; /* '[' and ']'. */ + + if (1 < array->length) { + *size += array->length - 1; /* ','s seperate each element. */ + } + + for (element = array->start; json_null != element; element = element->next) { + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size); +int json_write_minified_get_object_size(const struct json_object_s *object, + size_t *size) { + struct json_object_element_s *element; + + *size += 2; /* '{' and '}'. */ + + *size += object->length; /* ':'s seperate each name/value pair. */ + + if (1 < object->length) { + *size += object->length - 1; /* ','s seperate each element. */ + } + + for (element = object->start; json_null != element; element = element->next) { + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + if (json_write_minified_get_value_size(element->value, size)) { + /* value was malformed! */ + return 1; + } + } + + return 0; +} + +json_weak int +json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size); +int json_write_minified_get_value_size(const struct json_value_s *value, + size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_minified_get_array_size( + (struct json_array_s *)value->payload, size); + case json_type_object: + return json_write_minified_get_object_size( + (struct json_object_s *)value->payload, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); + +json_weak char *json_write_number(const struct json_number_s *number, + char *data); +char *json_write_number(const struct json_number_s *number, char *data) { + json_uintmax_t parsed_number, backup; + size_t i; + + if (number->number_size >= 2) { + switch (number->number[1]) { + default: + break; + case 'x': + case 'X': + /* The number is a json_parse_flags_allow_hexadecimal_numbers hexadecimal + * so we have to do extra work to convert it to a non-hexadecimal for JSON + * output. */ + parsed_number = json_strtoumax(number->number, json_null, 0); + + /* We need a copy of parsed number twice, so take a backup of it. */ + backup = parsed_number; + + i = 0; + + while (0 != parsed_number) { + parsed_number /= 10; + i++; + } + + /* Restore parsed_number to its original value stored in the backup. */ + parsed_number = backup; + + /* Now use backup to take a copy of i, or the length of the string. */ + backup = i; + + do { + *(data + i - 1) = '0' + (char)(parsed_number % 10); + parsed_number /= 10; + i--; + } while (0 != parsed_number); + + data += backup; + + return data; + } + } + + /* check to see if the number has leading/trailing decimal point. */ + i = 0; + + /* skip any leading '-'. */ + if ((i < number->number_size) && + (('+' == number->number[i]) || ('-' == number->number[i]))) { + i++; + } + + /* check if we have infinity. */ + if ((i < number->number_size) && ('I' == number->number[i])) { + const char *inf = "Infinity"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *inf++; + + /* Check if we found the Infinity string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *inf++) { + const char *dbl_max; + + /* if we had a leading '-' we need to record it in the JSON output. */ + if ('-' == number->number[0]) { + *data++ = '-'; + } + + /* Inf becomes 1.7976931348623158e308 because JSON can't support it. */ + for (dbl_max = "1.7976931348623158e308"; '\0' != *dbl_max; dbl_max++) { + *data++ = *dbl_max; + } + + return data; + } + } + + /* check if we have nan. */ + if ((i < number->number_size) && ('N' == number->number[i])) { + const char *nan = "NaN"; + size_t k; + + for (k = i; k < number->number_size; k++) { + const char c = *nan++; + + /* Check if we found the NaN string! */ + if ('\0' == c) { + break; + } else if (c != number->number[k]) { + break; + } + } + + if ('\0' == *nan++) { + /* NaN becomes 0 because JSON can't support it. */ + *data++ = '0'; + return data; + } + } + + /* if we had a leading decimal point. */ + if ((i < number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* insert a '0' to fix the leading decimal point for JSON output. */ + *data++ = '0'; + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; + } + + for (; i < number->number_size; i++) { + const char c = number->number[i]; + if (!('0' <= c && c <= '9')) { + break; + } + } + + /* if we had a trailing decimal point. */ + if ((i + 1 == number->number_size) && ('.' == number->number[i])) { + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + /* output the leading '-' if we had one. */ + if ('-' == number->number[i]) { + *data++ = '-'; + i++; + } + + /* and output the rest of the number as normal. */ + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + /* insert a '0' to fix the trailing decimal point for JSON output. */ + *data++ = '0'; + + return data; + } + + i = 0; + + /* skip any leading '+'. */ + if ('+' == number->number[i]) { + i++; + } + + for (; i < number->number_size; i++) { + *data++ = number->number[i]; + } + + return data; +} + +json_weak char *json_write_string(const struct json_string_s *string, + char *data); +char *json_write_string(const struct json_string_s *string, char *data) { + size_t i; + + *data++ = '"'; /* open the string. */ + + for (i = 0; i < string->string_size; i++) { + switch (string->string[i]) { + case '"': + *data++ = '\\'; /* escape the control character. */ + *data++ = '"'; + break; + case '\\': + *data++ = '\\'; /* escape the control character. */ + *data++ = '\\'; + break; + case '\b': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'b'; + break; + case '\f': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'f'; + break; + case '\n': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'n'; + break; + case '\r': + *data++ = '\\'; /* escape the control character. */ + *data++ = 'r'; + break; + case '\t': + *data++ = '\\'; /* escape the control character. */ + *data++ = 't'; + break; + default: + *data++ = string->string[i]; + break; + } + } + + *data++ = '"'; /* close the string. */ + + return data; +} + +json_weak char *json_write_minified_array(const struct json_array_s *array, + char *data); +char *json_write_minified_array(const struct json_array_s *array, char *data) { + struct json_array_element_s *element = json_null; + + *data++ = '['; /* open the array. */ + + for (element = array->start; json_null != element; element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_minified_object(const struct json_object_s *object, + char *data); +char *json_write_minified_object(const struct json_object_s *object, + char *data) { + struct json_object_element_s *element = json_null; + + *data++ = '{'; /* open the object. */ + + for (element = object->start; json_null != element; element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + *data++ = ':'; /* ':'s seperate each name/value pair. */ + + data = json_write_minified_value(element->value, data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_minified_value(const struct json_value_s *value, + char *data); +char *json_write_minified_value(const struct json_value_s *value, char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_minified_array((struct json_array_s *)value->payload, + data); + case json_type_object: + return json_write_minified_object((struct json_object_s *)value->payload, + data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_minified(const struct json_value_s *value, size_t *out_size) { + size_t size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_write_minified_get_value_size(value, &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_minified_value(value, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); + +json_weak int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_array_size(const struct json_array_s *array, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_array_element_s *element; + + *size += 1; /* '['. */ + + if (0 < array->length) { + /* if we have any elements we need to add a newline after our '['. */ + *size += newline_size; + + *size += array->length - 1; /* ','s seperate each element. */ + + for (element = array->start; json_null != element; + element = element->next) { + /* each element gets an indent. */ + *size += (depth + 1) * indent_size; + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + + /* each element gets a newline too. */ + *size += newline_size; + } + + /* since we wrote out some elements, need to add a newline and indentation. + */ + /* to the trailing ']'. */ + *size += depth * indent_size; + } + + *size += 1; /* ']'. */ + + return 0; +} + +json_weak int +json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size); +int json_write_pretty_get_object_size(const struct json_object_s *object, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + struct json_object_element_s *element; + + *size += 1; /* '{'. */ + + if (0 < object->length) { + *size += newline_size; /* need a newline next. */ + + *size += object->length - 1; /* ','s seperate each element. */ + + for (element = object->start; json_null != element; + element = element->next) { + /* each element gets an indent and newline. */ + *size += (depth + 1) * indent_size; + *size += newline_size; + + if (json_write_get_string_size(element->name, size)) { + /* string was malformed! */ + return 1; + } + + *size += 3; /* seperate each name/value pair with " : ". */ + + if (json_write_pretty_get_value_size(element->value, depth + 1, + indent_size, newline_size, size)) { + /* value was malformed! */ + return 1; + } + } + + *size += depth * indent_size; + } + + *size += 1; /* '}'. */ + + return 0; +} + +json_weak int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, + size_t *size); +int json_write_pretty_get_value_size(const struct json_value_s *value, + size_t depth, size_t indent_size, + size_t newline_size, size_t *size) { + switch (value->type) { + default: + /* unknown value type found! */ + return 1; + case json_type_number: + return json_write_get_number_size((struct json_number_s *)value->payload, + size); + case json_type_string: + return json_write_get_string_size((struct json_string_s *)value->payload, + size); + case json_type_array: + return json_write_pretty_get_array_size( + (struct json_array_s *)value->payload, depth, indent_size, newline_size, + size); + case json_type_object: + return json_write_pretty_get_object_size( + (struct json_object_s *)value->payload, depth, indent_size, + newline_size, size); + case json_type_true: + *size += 4; /* the string "true". */ + return 0; + case json_type_false: + *size += 5; /* the string "false". */ + return 0; + case json_type_null: + *size += 4; /* the string "null". */ + return 0; + } +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); + +json_weak char *json_write_pretty_array(const struct json_array_s *array, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_array(const struct json_array_s *array, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_array_element_s *element; + + *data++ = '['; /* open the array. */ + + if (0 < array->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = array->start; json_null != element; + element = element->next) { + if (element != array->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = ']'; /* close the array. */ + + return data; +} + +json_weak char *json_write_pretty_object(const struct json_object_s *object, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_object(const struct json_object_s *object, size_t depth, + const char *indent, const char *newline, + char *data) { + size_t k, m; + struct json_object_element_s *element; + + *data++ = '{'; /* open the object. */ + + if (0 < object->length) { + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (element = object->start; json_null != element; + element = element->next) { + if (element != object->start) { + *data++ = ','; /* ','s seperate each element. */ + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + } + + for (k = 0; k < depth + 1; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + + data = json_write_string(element->name, data); + + if (json_null == data) { + /* string was malformed! */ + return json_null; + } + + /* " : "s seperate each name/value pair. */ + *data++ = ' '; + *data++ = ':'; + *data++ = ' '; + + data = json_write_pretty_value(element->value, depth + 1, indent, newline, + data); + + if (json_null == data) { + /* value was malformed! */ + return json_null; + } + } + + for (k = 0; '\0' != newline[k]; k++) { + *data++ = newline[k]; + } + + for (k = 0; k < depth; k++) { + for (m = 0; '\0' != indent[m]; m++) { + *data++ = indent[m]; + } + } + } + + *data++ = '}'; /* close the object. */ + + return data; +} + +json_weak char *json_write_pretty_value(const struct json_value_s *value, + size_t depth, const char *indent, + const char *newline, char *data); +char *json_write_pretty_value(const struct json_value_s *value, size_t depth, + const char *indent, const char *newline, + char *data) { + switch (value->type) { + default: + /* unknown value type found! */ + return json_null; + case json_type_number: + return json_write_number((struct json_number_s *)value->payload, data); + case json_type_string: + return json_write_string((struct json_string_s *)value->payload, data); + case json_type_array: + return json_write_pretty_array((struct json_array_s *)value->payload, depth, + indent, newline, data); + case json_type_object: + return json_write_pretty_object((struct json_object_s *)value->payload, + depth, indent, newline, data); + case json_type_true: + data[0] = 't'; + data[1] = 'r'; + data[2] = 'u'; + data[3] = 'e'; + return data + 4; + case json_type_false: + data[0] = 'f'; + data[1] = 'a'; + data[2] = 'l'; + data[3] = 's'; + data[4] = 'e'; + return data + 5; + case json_type_null: + data[0] = 'n'; + data[1] = 'u'; + data[2] = 'l'; + data[3] = 'l'; + return data + 4; + } +} + +void *json_write_pretty(const struct json_value_s *value, const char *indent, + const char *newline, size_t *out_size) { + size_t size = 0; + size_t indent_size = 0; + size_t newline_size = 0; + char *data = json_null; + char *data_end = json_null; + + if (json_null == value) { + return json_null; + } + + if (json_null == indent) { + indent = " "; /* default to two spaces. */ + } + + if (json_null == newline) { + newline = "\n"; /* default to linux newlines. */ + } + + while ('\0' != indent[indent_size]) { + ++indent_size; /* skip non-null terminating characters. */ + } + + while ('\0' != newline[newline_size]) { + ++newline_size; /* skip non-null terminating characters. */ + } + + if (json_write_pretty_get_value_size(value, 0, indent_size, newline_size, + &size)) { + /* value was malformed! */ + return json_null; + } + + size += 1; /* for the '\0' null terminating character. */ + + data = (char *)malloc(size); + + if (json_null == data) { + /* malloc failed! */ + return json_null; + } + + data_end = json_write_pretty_value(value, 0, indent, newline, data); + + if (json_null == data_end) { + /* bad chi occurred! */ + free(data); + return json_null; + } + + /* null terminated the string. */ + *data_end = '\0'; + + if (json_null != out_size) { + *out_size = size; + } + + return data; +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#endif /* SHEREDOM_JSON_H_INCLUDED. */ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h new file mode 100644 index 0000000..979c2f8 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/jsoncxx.h @@ -0,0 +1,427 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : +// Filename : public.sdk/source/vst/moduleinfo/jsoncxx.h +// Created by : Steinberg, 12/2021 +// Description : +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "json.h" +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) || __has_include() +#include +#define SMTG_HAS_CHARCONV +#endif + +//------------------------------------------------------------------------ +namespace JSON { +namespace Detail { + +//------------------------------------------------------------------------ +template +struct Base +{ + explicit Base (JsonT* o) : object_ (o) {} + explicit Base (const Base& o) : object_ (o.object_) {} + + Base& operator= (const Base& o) = default; + + operator JsonT* () const { return object_; } + JsonT* jsonValue () const { return object_; } + +protected: + Base () : object_ (nullptr) {} + + JsonT* object_; +}; + +//------------------------------------------------------------------------ +template +struct Iterator +{ + explicit Iterator (JsonElement el) : el (el) {} + + bool operator== (const Iterator& other) const { return other.el == el; } + bool operator!= (const Iterator& other) const { return other.el != el; } + + const JsonElement& operator* () const { return el; } + const JsonElement& operator-> () const { return el; } + + Iterator& operator++ () + { + if (el) + el = el.next (); + return *this; + } + + Iterator operator++ (int) + { + auto it = Iterator (el); + operator++ (); + return it; + } + +private: + JsonElement el; +}; + +//------------------------------------------------------------------------ +} // Detail + +struct Object; +struct Array; +struct String; +struct Number; +struct Boolean; + +//------------------------------------------------------------------------ +enum class Type +{ + Object, + Array, + String, + Number, + True, + False, + Null, +}; + +//------------------------------------------------------------------------ +struct SourceLocation +{ + size_t offset; + size_t line; + size_t row; +}; + +//------------------------------------------------------------------------ +struct Value : Detail::Base +{ + using Detail::Base::Base; + using VariantT = std::variant; + + std::optional asObject () const; + std::optional asArray () const; + std::optional asString () const; + std::optional asNumber () const; + std::optional asBoolean () const; + std::optional asNull () const; + + VariantT asVariant () const; + Type type () const; + + SourceLocation getSourceLocation () const; +}; + +//------------------------------------------------------------------------ +struct Boolean +{ + Boolean (size_t type) : value (type == json_type_true) {} + + operator bool () const { return value; } + +private: + bool value; +}; + +//------------------------------------------------------------------------ +struct String : Detail::Base +{ + using Detail::Base::Base; + + std::string_view text () const { return {jsonValue ()->string, jsonValue ()->string_size}; } + + SourceLocation getSourceLocation () const; +}; + +//------------------------------------------------------------------------ +struct Number : Detail::Base +{ + using Detail::Base::Base; + + std::string_view text () const { return {jsonValue ()->number, jsonValue ()->number_size}; } + + std::optional getInteger () const; + std::optional getDouble () const; +}; + +//------------------------------------------------------------------------ +struct ObjectElement : Detail::Base +{ + using Detail::Base::Base; + + String name () const { return String (jsonValue ()->name); } + Value value () const { return Value (jsonValue ()->value); } + + ObjectElement next () const { return ObjectElement (jsonValue ()->next); } +}; + +//------------------------------------------------------------------------ +struct Object : Detail::Base +{ + using Detail::Base::Base; + using Iterator = Detail::Iterator; + + size_t size () const { return jsonValue ()->length; } + + Iterator begin () const { return Iterator (ObjectElement (jsonValue ()->start)); } + Iterator end () const { return Iterator (ObjectElement (nullptr)); } +}; + +//------------------------------------------------------------------------ +struct ArrayElement : Detail::Base +{ + using Detail::Base::Base; + + Value value () const { return Value (jsonValue ()->value); } + + ArrayElement next () const { return ArrayElement (jsonValue ()->next); } +}; + +//------------------------------------------------------------------------ +struct Array : Detail::Base +{ + using Detail::Base::Base; + using Iterator = Detail::Iterator; + + size_t size () const { return jsonValue ()->length; } + + Iterator begin () const { return Iterator (ArrayElement (jsonValue ()->start)); } + Iterator end () const { return Iterator (ArrayElement (nullptr)); } +}; + +//------------------------------------------------------------------------ +struct Document : Value +{ + static std::variant parse (std::string_view data) + { + auto allocate = [] (void*, size_t allocSize) { return std::malloc (allocSize); }; + json_parse_result_s parse_result {}; + auto value = json_parse_ex (data.data (), data.size (), + json_parse_flags_allow_json5 | + json_parse_flags_allow_location_information, + allocate, nullptr, &parse_result); + if (value) + return Document (value); + return parse_result; + } + ~Document () noexcept + { + if (object_) + std::free (object_); + } + + Document (Document&& doc) noexcept { *this = std::move (doc); } + Document& operator= (Document&& doc) noexcept + { + std::swap (object_, doc.object_); + return *this; + } + +private: + using Value::Value; +}; + +//------------------------------------------------------------------------ +inline std::optional Value::asObject () const +{ + if (type () != Type::Object) + return {}; + return Object (json_value_as_object (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asArray () const +{ + if (type () != Type::Array) + return {}; + return Array (json_value_as_array (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asString () const +{ + if (type () != Type::String) + return {}; + return String (json_value_as_string (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asNumber () const +{ + if (type () != Type::Number) + return {}; + return Number (json_value_as_number (jsonValue ())); +} + +//------------------------------------------------------------------------ +inline std::optional Value::asBoolean () const +{ + if (type () == Type::True || type () == Type::False) + return Boolean (jsonValue ()->type); + return {}; +} + +//------------------------------------------------------------------------ +inline std::optional Value::asNull () const +{ + if (type () != Type::Null) + return {}; + return nullptr; +} + +//------------------------------------------------------------------------ +inline Type Value::type () const +{ + switch (jsonValue ()->type) + { + case json_type_string: return Type::String; + case json_type_number: return Type::Number; + case json_type_object: return Type::Object; + case json_type_array: return Type::Array; + case json_type_true: return Type::True; + case json_type_false: return Type::False; + case json_type_null: return Type::Null; + } + assert (false); + return Type::Null; +} + +//------------------------------------------------------------------------ +inline Value::VariantT Value::asVariant () const +{ + switch (type ()) + { + case Type::String: return *asString (); + case Type::Number: return *asNumber (); + case Type::Object: return *asObject (); + case Type::Array: return *asArray (); + case Type::True: return *asBoolean (); + case Type::False: return *asBoolean (); + case Type::Null: return *asNull (); + } + assert (false); + return nullptr; +} + +//------------------------------------------------------------------------ +inline SourceLocation Value::getSourceLocation () const +{ + auto exValue = reinterpret_cast (jsonValue ()); + return {exValue->offset, exValue->line_no, exValue->row_no}; +} + +//------------------------------------------------------------------------ +inline SourceLocation String::getSourceLocation () const +{ + auto exValue = reinterpret_cast (jsonValue ()); + return {exValue->offset, exValue->line_no, exValue->row_no}; +} + +//------------------------------------------------------------------------ +inline std::optional Number::getInteger () const +{ +#if defined(SMTG_HAS_CHARCONV) + int64_t result {0}; + auto res = std::from_chars (jsonValue ()->number, + jsonValue ()->number + jsonValue ()->number_size, result); + if (res.ec == std::errc ()) + return result; + return {}; +#else + int64_t result {0}; + std::string str (jsonValue ()->number, jsonValue ()->number + jsonValue ()->number_size); + if (std::sscanf (str.data (), "%lld", &result) != 1) + return {}; + return result; +#endif +} + +//------------------------------------------------------------------------ +inline std::optional Number::getDouble () const +{ +#if 1 // clang still has no floting point from_chars version + size_t ctrl {0}; + auto result = std::stod (std::string (jsonValue ()->number, jsonValue ()->number_size), &ctrl); + if (ctrl > 0) + return result; +#else + double result {0.}; + auto res = std::from_chars (jsonValue ()->number, + jsonValue ()->number + jsonValue ()->number_size, result); + if (res.ec == std::errc ()) + return result; +#endif + return {}; +} + +//------------------------------------------------------------------------ +inline std::string_view errorToString (json_parse_error_e error) +{ + switch (error) + { + case json_parse_error_e::json_parse_error_none: return {}; + case json_parse_error_e::json_parse_error_expected_comma_or_closing_bracket: + return "json_parse_error_expected_comma_or_closing_bracket"; + case json_parse_error_e::json_parse_error_expected_colon: + return "json_parse_error_expected_colon"; + case json_parse_error_e::json_parse_error_expected_opening_quote: + return "json_parse_error_expected_opening_quote"; + case json_parse_error_e::json_parse_error_invalid_string_escape_sequence: + return "json_parse_error_invalid_string_escape_sequence"; + case json_parse_error_e::json_parse_error_invalid_number_format: + return "json_parse_error_invalid_number_format"; + case json_parse_error_e::json_parse_error_invalid_value: + return "json_parse_error_invalid_value"; + case json_parse_error_e::json_parse_error_premature_end_of_buffer: + return "json_parse_error_premature_end_of_buffer"; + case json_parse_error_e::json_parse_error_invalid_string: + return "json_parse_error_invalid_string"; + case json_parse_error_e::json_parse_error_allocator_failed: + return "json_parse_error_allocator_failed"; + case json_parse_error_e::json_parse_error_unexpected_trailing_characters: + return "json_parse_error_unexpected_trailing_characters"; + case json_parse_error_e::json_parse_error_unknown: return "json_parse_error_unknown"; + } + return {}; +} + +//------------------------------------------------------------------------ +} // JSON diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h new file mode 100644 index 0000000..81b93f7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfo.h @@ -0,0 +1,100 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfo +// Filename : public.sdk/source/vst/moduleinfo/moduleinfo.h +// Created by : Steinberg, 12/2021 +// Description : +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { + +//------------------------------------------------------------------------ +struct ModuleInfo +{ +//------------------------------------------------------------------------ + struct FactoryInfo + { + std::string vendor; + std::string url; + std::string email; + int32_t flags {0}; + }; + +//------------------------------------------------------------------------ + struct Snapshot + { + double scaleFactor {1.}; + std::string path; + }; + using SnapshotList = std::vector; + +//------------------------------------------------------------------------ + struct ClassInfo + { + std::string cid; + std::string category; + std::string name; + std::string vendor; + std::string version; + std::string sdkVersion; + std::vector subCategories; + SnapshotList snapshots; + int32_t cardinality {0x7FFFFFFF}; + uint32_t flags {0}; + }; + +//------------------------------------------------------------------------ + struct Compatibility + { + std::string newCID; + std::vector oldCID; + }; + + using ClassList = std::vector; + using CompatibilityList = std::vector; + + std::string name; + std::string version; + FactoryInfo factoryInfo; + ClassList classes; + CompatibilityList compatibility; +}; + +//------------------------------------------------------------------------ +} // Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp new file mode 100644 index 0000000..8f12829 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp @@ -0,0 +1,309 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfo +// Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp +// Created by : Steinberg, 12/2021 +// Description : utility functions to create moduleinfo json files +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "moduleinfocreator.h" +#include "jsoncxx.h" +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg::ModuleInfoLib { +using namespace VST3; +namespace { + +//------------------------------------------------------------------------ +struct JSON5Writer +{ +private: + std::ostream& stream; + bool beautify; + bool lastIsComma {false}; + int32_t intend {0}; + + void doBeautify () + { + if (beautify) + { + stream << '\n'; + for (int i = 0; i < intend; ++i) + stream << " "; + } + } + + void writeComma () + { + if (lastIsComma) + return; + stream << ","; + lastIsComma = true; + } + void startObject () + { + stream << "{"; + ++intend; + lastIsComma = false; + } + void endObject () + { + --intend; + doBeautify (); + stream << "}"; + lastIsComma = false; + } + void startArray () + { + stream << "["; + ++intend; + lastIsComma = false; + } + void endArray () + { + --intend; + doBeautify (); + stream << "]"; + lastIsComma = false; + } + +public: + JSON5Writer (std::ostream& stream, bool beautify = true) : stream (stream), beautify (beautify) + { + } + + void string (std::string_view str) + { + stream << "\"" << str << "\""; + lastIsComma = false; + } + + void boolean (bool val) + { + stream << (val ? "true" : "false"); + lastIsComma = false; + } + + template + void value (ValueT val) + { + stream << val; + lastIsComma = false; + } + + template + void object (Proc proc) + { + startObject (); + proc (); + endObject (); + } + + template + void array (Iterator begin, Iterator end, Proc proc) + { + startArray (); + while (begin != end) + { + doBeautify (); + proc (begin); + ++begin; + writeComma (); + } + endArray (); + } + + template + void keyValue (std::string_view key, Proc proc) + { + doBeautify (); + string (key); + stream << ": "; + proc (); + writeComma (); + } +}; + +//------------------------------------------------------------------------ +void writeSnapshots (const ModuleInfo::SnapshotList& snapshots, JSON5Writer& w) +{ + w.keyValue ("Snapshots", [&] () { + w.array (snapshots.begin (), snapshots.end (), [&] (const auto& el) { + w.object ([&] () { + w.keyValue ("Scale Factor", [&] () { w.value (el->scaleFactor); }); + w.keyValue ("Path", [&] () { w.string (el->path); }); + }); + }); + }); +} + +//------------------------------------------------------------------------ +void writeClassInfo (const ModuleInfo::ClassInfo& cls, JSON5Writer& w) +{ + w.keyValue ("CID", [&] () { w.string (cls.cid); }); + w.keyValue ("Category", [&] () { w.string (cls.category); }); + w.keyValue ("Name", [&] () { w.string (cls.name); }); + w.keyValue ("Vendor", [&] () { w.string (cls.vendor); }); + w.keyValue ("Version", [&] () { w.string (cls.version); }); + w.keyValue ("SDKVersion", [&] () { w.string (cls.sdkVersion); }); + const auto& sc = cls.subCategories; + if (!sc.empty ()) + { + w.keyValue ("Sub Categories", [&] () { + w.array (sc.begin (), sc.end (), [&] (const auto& cat) { w.string (*cat); }); + }); + } + w.keyValue ("Class Flags", [&] () { w.value (cls.flags); }); + w.keyValue ("Cardinality", [&] () { w.value (cls.cardinality); }); + writeSnapshots (cls.snapshots, w); +} + +//------------------------------------------------------------------------ +void writePluginCompatibility (const ModuleInfo::CompatibilityList& compat, JSON5Writer& w) +{ + if (compat.empty ()) + return; + w.keyValue ("Compatibility", [&] () { + w.array (compat.begin (), compat.end (), [&] (auto& el) { + w.object ([&] () { + w.keyValue ("New", [&] () { w.string (el->newCID); }); + w.keyValue ("Old", [&] () { + w.array (el->oldCID.begin (), el->oldCID.end (), + [&] (auto& oldEl) { w.string (*oldEl); }); + }); + }); + }); + }); +} + +//------------------------------------------------------------------------ +void writeFactoryInfo (const ModuleInfo::FactoryInfo& fi, JSON5Writer& w) +{ + w.keyValue ("Factory Info", [&] () { + w.object ([&] () { + w.keyValue ("Vendor", [&] () { w.string (fi.vendor); }); + w.keyValue ("URL", [&] () { w.string (fi.url); }); + w.keyValue ("E-Mail", [&] () { w.string (fi.email); }); + w.keyValue ("Flags", [&] () { + w.object ([&] () { + w.keyValue ("Unicode", + [&] () { w.boolean (fi.flags & PFactoryInfo::kUnicode); }); + w.keyValue ("Classes Discardable", [&] () { + w.boolean (fi.flags & PFactoryInfo::kClassesDiscardable); + }); + w.keyValue ("Component Non Discardable", [&] () { + w.boolean (fi.flags & PFactoryInfo::kComponentNonDiscardable); + }); + }); + }); + }); + }); +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses) +{ + ModuleInfo info; + + const auto& factory = module.getFactory (); + auto factoryInfo = factory.info (); + + info.name = module.getName (); + auto pos = info.name.find_last_of ('.'); + if (pos != std::string::npos) + info.name.erase (pos); + + info.factoryInfo.vendor = factoryInfo.vendor (); + info.factoryInfo.url = factoryInfo.url (); + info.factoryInfo.email = factoryInfo.email (); + info.factoryInfo.flags = factoryInfo.flags (); + + if (factoryInfo.classesDiscardable () == false || + (factoryInfo.classesDiscardable () && includeDiscardableClasses)) + { + auto snapshots = VST3::Hosting::Module::getSnapshots (module.getPath ()); + for (const auto& ci : factory.classInfos ()) + { + ModuleInfo::ClassInfo classInfo; + classInfo.cid = ci.ID ().toString (); + classInfo.category = ci.category (); + classInfo.name = ci.name (); + classInfo.vendor = ci.vendor (); + classInfo.version = ci.version (); + classInfo.sdkVersion = ci.sdkVersion (); + classInfo.subCategories = ci.subCategories (); + classInfo.cardinality = ci.cardinality (); + classInfo.flags = ci.classFlags (); + auto snapshotIt = std::find_if (snapshots.begin (), snapshots.end (), + [&] (const auto& el) { return el.uid == ci.ID (); }); + if (snapshotIt != snapshots.end ()) + { + for (auto& s : snapshotIt->images) + { + std::string_view path (s.path); + if (path.find (module.getPath ()) == 0) + path.remove_prefix (module.getPath ().size () + 1); + classInfo.snapshots.emplace_back ( + ModuleInfo::Snapshot {s.scaleFactor, {path.data (), path.size ()}}); + } + snapshots.erase (snapshotIt); + } + info.classes.emplace_back (std::move (classInfo)); + } + } + return info; +} + +//------------------------------------------------------------------------ +void outputJson (const ModuleInfo& info, std::ostream& output) +{ + JSON5Writer w (output); + w.object ([&] () { + w.keyValue ("Name", [&] () { w.string (info.name); }); + w.keyValue ("Version", [&] () { w.string (info.version); }); + writeFactoryInfo (info.factoryInfo, w); + writePluginCompatibility (info.compatibility, w); + w.keyValue ("Classes", [&] () { + w.array (info.classes.begin (), info.classes.end (), + [&] (const auto& cls) { w.object ([&] () { writeClassInfo (*cls, w); }); }); + }); + }); +} + +//------------------------------------------------------------------------ +} // Steinberg::ModuleInfoLib diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h new file mode 100644 index 0000000..f28716a --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfocreator.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfo +// Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.h +// Created by : Steinberg, 12/2021 +// Description : utility functions to create moduleinfo json files +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "moduleinfo.h" +#include "public.sdk/source/vst/hosting/module.h" +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg::ModuleInfoLib { + +//------------------------------------------------------------------------ +/** create a ModuleInfo from a module + * + * @param module module to create the module info from + * @param includeDiscardableClasses if true adds the current available classes to the module info + * @return a ModuleInfo struct with the classes and factory info of the module + */ +ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses); + +//------------------------------------------------------------------------ +/** output the ModuleInfo as json to the stream + * + * @param info module info + * @param output output stream + */ +void outputJson (const ModuleInfo& info, std::ostream& output); + +//------------------------------------------------------------------------ +} // Steinberg::ModuelInfoLib diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp new file mode 100644 index 0000000..0d09d2b --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp @@ -0,0 +1,537 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfo +// Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp +// Created by : Steinberg, 01/2022 +// Description : utility functions to parse moduleinfo json files +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "moduleinfoparser.h" +#include "jsoncxx.h" +#include "pluginterfaces/base/ipluginbase.h" +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg::ModuleInfoLib { +namespace { + +//------------------------------------------------------------------------ +void printJsonParseError (json_parse_result_s& parseResult, std::ostream& errorOut) +{ + errorOut << "error : " + << JSON::errorToString (static_cast (parseResult.error)) << '\n'; + errorOut << "offset : " << parseResult.error_offset << '\n'; + errorOut << "line no: " << parseResult.error_line_no << '\n'; + errorOut << "row no : " << parseResult.error_row_no << '\n'; +} + +//------------------------------------------------------------------------ +struct parse_error : std::exception +{ + parse_error (const std::string& str, const JSON::Value& value) + : str (str), location (value.getSourceLocation ()) + { + addLocation (location); + } + parse_error (const std::string& str, const JSON::String& value) + : str (str), location (value.getSourceLocation ()) + { + addLocation (location); + } + const char* what () const noexcept override { return str.data (); } + +private: + void addLocation (const JSON::SourceLocation& loc) + { + str += '\n'; + str += "offset:"; + str += std::to_string (loc.offset); + str += '\n'; + str += "line:"; + str += std::to_string (loc.line); + str += '\n'; + str += "row:"; + str += std::to_string (loc.row); + str += '\n'; + } + + std::string str; + JSON::SourceLocation location; +}; + +//------------------------------------------------------------------------ +struct ModuleInfoJsonParser +{ + ModuleInfoJsonParser () = default; + + std::string_view getText (const JSON::Value& value) const + { + if (auto str = value.asString ()) + return str->text (); + throw parse_error ("Expect a String here", value); + } + + template + T getInteger (const JSON::Value& value) const + { + if (auto number = value.asNumber ()) + { + if (auto result = number->getInteger ()) + { + if (result > static_cast (std::numeric_limits::max ()) || + result < static_cast (std::numeric_limits::min ())) + throw parse_error ("Value is out of range here", value); + return static_cast (*result); + } + throw parse_error ("Expect an Integer here", value); + } + throw parse_error ("Expect a Number here", value); + } + + double getDouble (const JSON::Value& value) const + { + if (auto number = value.asNumber ()) + { + if (auto result = number->getDouble ()) + return *result; + throw parse_error ("Expect a Double here", value); + } + throw parse_error ("Expect a Number here", value); + } + + void parseFactoryInfo (const JSON::Value& value) + { + enum ParsedBits + { + Vendor = 1 << 0, + URL = 1 << 1, + EMail = 1 << 2, + Flags = 1 << 3, + }; + uint32_t parsed {0}; + if (auto obj = value.asObject ()) + { + for (const auto& el : *obj) + { + auto elementName = el.name ().text (); + if (elementName == "Vendor") + { + if (parsed & ParsedBits::Vendor) + throw parse_error ("Only one 'Vendor' key allowed", el.name ()); + parsed |= ParsedBits::Vendor; + info.factoryInfo.vendor = getText (el.value ()); + } + else if (elementName == "URL") + { + if (parsed & ParsedBits::URL) + throw parse_error ("Only one 'URL' key allowed", el.name ()); + parsed |= ParsedBits::URL; + info.factoryInfo.url = getText (el.value ()); + } + else if (elementName == "E-Mail") + { + if (parsed & ParsedBits::EMail) + throw parse_error ("Only one 'E-Mail' key allowed", el.name ()); + parsed |= ParsedBits::EMail; + info.factoryInfo.email = getText (el.value ()); + } + else if (elementName == "Flags") + { + if (parsed & ParsedBits::Flags) + throw parse_error ("Only one 'Flags' key allowed", el.name ()); + auto flags = el.value ().asObject (); + if (!flags) + throw parse_error ("Expect 'Flags' to be a JSON Object", el.name ()); + for (const auto& flag : *flags) + { + auto flagName = flag.name ().text (); + auto flagValue = flag.value ().asBoolean (); + if (!flagValue) + throw parse_error ("Flag must be a boolean", flag.value ()); + if (flagName == "Classes Discardable") + { + if (*flagValue) + info.factoryInfo.flags |= PFactoryInfo::kClassesDiscardable; + } + else if (flagName == "Component Non Discardable") + { + if (*flagValue) + info.factoryInfo.flags |= PFactoryInfo::kComponentNonDiscardable; + } + else if (flagName == "Unicode") + { + if (*flagValue) + info.factoryInfo.flags |= PFactoryInfo::kUnicode; + } + else + throw parse_error ("Unknown flag", flag.name ()); + } + parsed |= ParsedBits::Flags; + } + } + } + if (!(parsed & ParsedBits::Vendor)) + throw std::logic_error ("Missing 'Vendor' in Factory Info"); + if (!(parsed & ParsedBits::URL)) + throw std::logic_error ("Missing 'URL' in Factory Info"); + if (!(parsed & ParsedBits::EMail)) + throw std::logic_error ("Missing 'EMail' in Factory Info"); + if (!(parsed & ParsedBits::Flags)) + throw std::logic_error ("Missing 'Flags' in Factory Info"); + } + + void parseClasses (const JSON::Value& value) + { + enum ParsedBits + { + CID = 1 << 0, + Category = 1 << 1, + Name = 1 << 2, + Vendor = 1 << 3, + Version = 1 << 4, + SDKVersion = 1 << 5, + SubCategories = 1 << 6, + ClassFlags = 1 << 7, + Snapshots = 1 << 8, + Cardinality = 1 << 9, + }; + + auto array = value.asArray (); + if (!array) + throw parse_error ("Expect Classes Array", value); + for (const auto& classInfoEl : *array) + { + auto classInfo = classInfoEl.value ().asObject (); + if (!classInfo) + throw parse_error ("Expect Class Object", classInfoEl.value ()); + + ModuleInfo::ClassInfo ci {}; + + uint32_t parsed {0}; + + for (const auto& el : *classInfo) + { + auto elementName = el.name ().text (); + if (elementName == "CID") + { + if (parsed & ParsedBits::CID) + throw parse_error ("Only one 'CID' key allowed", el.name ()); + ci.cid = getText (el.value ()); + parsed |= ParsedBits::CID; + } + else if (elementName == "Category") + { + if (parsed & ParsedBits::Category) + throw parse_error ("Only one 'Category' key allowed", el.name ()); + ci.category = getText (el.value ()); + parsed |= ParsedBits::Category; + } + else if (elementName == "Name") + { + if (parsed & ParsedBits::Name) + throw parse_error ("Only one 'Name' key allowed", el.name ()); + ci.name = getText (el.value ()); + parsed |= ParsedBits::Name; + } + else if (elementName == "Vendor") + { + if (parsed & ParsedBits::Vendor) + throw parse_error ("Only one 'Vendor' key allowed", el.name ()); + ci.vendor = getText (el.value ()); + parsed |= ParsedBits::Vendor; + } + else if (elementName == "Version") + { + if (parsed & ParsedBits::Version) + throw parse_error ("Only one 'Version' key allowed", el.name ()); + ci.version = getText (el.value ()); + parsed |= ParsedBits::Version; + } + else if (elementName == "SDKVersion") + { + if (parsed & ParsedBits::SDKVersion) + throw parse_error ("Only one 'SDKVersion' key allowed", el.name ()); + ci.sdkVersion = getText (el.value ()); + parsed |= ParsedBits::SDKVersion; + } + else if (elementName == "Sub Categories") + { + if (parsed & ParsedBits::SubCategories) + throw parse_error ("Only one 'Sub Categories' key allowed", el.name ()); + auto subCatArr = el.value ().asArray (); + if (!subCatArr) + throw parse_error ("Expect Array here", el.value ()); + for (const auto& catEl : *subCatArr) + { + auto cat = getText (catEl.value ()); + ci.subCategories.emplace_back (cat); + } + parsed |= ParsedBits::SubCategories; + } + else if (elementName == "Class Flags") + { + if (parsed & ParsedBits::ClassFlags) + throw parse_error ("Only one 'Class Flags' key allowed", el.name ()); + ci.flags = getInteger (el.value ()); + parsed |= ParsedBits::ClassFlags; + } + else if (elementName == "Cardinality") + { + if (parsed & ParsedBits::Cardinality) + throw parse_error ("Only one 'Cardinality' key allowed", el.name ()); + ci.cardinality = getInteger (el.value ()); + parsed |= ParsedBits::Cardinality; + } + else if (elementName == "Snapshots") + { + if (parsed & ParsedBits::Snapshots) + throw parse_error ("Only one 'Snapshots' key allowed", el.name ()); + auto snapArr = el.value ().asArray (); + if (!snapArr) + throw parse_error ("Expect Array here", el.value ()); + for (const auto& snapEl : *snapArr) + { + auto snap = snapEl.value ().asObject (); + if (!snap) + throw parse_error ("Expect Object here", snapEl.value ()); + ModuleInfo::Snapshot snapshot; + for (const auto& spEl : *snap) + { + auto spElName = spEl.name ().text (); + if (spElName == "Path") + snapshot.path = getText (spEl.value ()); + else if (spElName == "Scale Factor") + snapshot.scaleFactor = getDouble (spEl.value ()); + else + throw parse_error ("Unexpected key", spEl.name ()); + } + if (snapshot.scaleFactor == 0. || snapshot.path.empty ()) + throw parse_error ("Missing Snapshot keys", snapEl.value ()); + ci.snapshots.emplace_back (std::move (snapshot)); + } + parsed |= ParsedBits::Snapshots; + } + else + throw parse_error ("Unexpected key", el.name ()); + } + if (!(parsed & ParsedBits::CID)) + throw parse_error ("'CID' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::Category)) + throw parse_error ("'Category' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::Name)) + throw parse_error ("'Name' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::Vendor)) + throw parse_error ("'Vendor' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::Version)) + throw parse_error ("'Version' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::SDKVersion)) + throw parse_error ("'SDK Version' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::ClassFlags)) + throw parse_error ("'Class Flags' key missing", classInfoEl.value ()); + if (!(parsed & ParsedBits::Cardinality)) + throw parse_error ("'Cardinality' key missing", classInfoEl.value ()); + info.classes.emplace_back (std::move (ci)); + } + } + + void parseCompatibility (const JSON::Value& value) + { + auto arr = value.asArray (); + if (!arr) + throw parse_error ("Expect Array here", value); + for (const auto& el : *arr) + { + auto obj = el.value ().asObject (); + if (!obj) + throw parse_error ("Expect Object here", el.value ()); + + ModuleInfo::Compatibility compat; + for (const auto& objEl : *obj) + { + auto elementName = objEl.name ().text (); + if (elementName == "New") + compat.newCID = getText (objEl.value ()); + else if (elementName == "Old") + { + auto oldElArr = objEl.value ().asArray (); + if (!oldElArr) + throw parse_error ("Expect Array here", objEl.value ()); + for (const auto& old : *oldElArr) + { + compat.oldCID.emplace_back (getText (old.value ())); + } + } + } + if (compat.newCID.empty ()) + throw parse_error ("Expect New CID here", el.value ()); + if (compat.oldCID.empty ()) + throw parse_error ("Expect Old CID here", el.value ()); + info.compatibility.emplace_back (std::move (compat)); + } + } + + void parse (const JSON::Document& doc) + { + auto docObj = doc.asObject (); + if (!docObj) + throw parse_error ("Unexpected", doc); + + enum ParsedBits + { + Name = 1 << 0, + Version = 1 << 1, + FactoryInfo = 1 << 2, + Compatibility = 1 << 3, + Classes = 1 << 4, + }; + + uint32_t parsed {0}; + for (const auto& el : *docObj) + { + auto elementName = el.name ().text (); + if (elementName == "Name") + { + if (parsed & ParsedBits::Name) + throw parse_error ("Only one 'Name' key allowed", el.name ()); + parsed |= ParsedBits::Name; + info.name = getText (el.value ()); + } + else if (elementName == "Version") + { + if (parsed & ParsedBits::Version) + throw parse_error ("Only one 'Version' key allowed", el.name ()); + parsed |= ParsedBits::Version; + info.version = getText (el.value ()); + } + else if (elementName == "Factory Info") + { + if (parsed & ParsedBits::FactoryInfo) + throw parse_error ("Only one 'Factory Info' key allowed", el.name ()); + parseFactoryInfo (el.value ()); + parsed |= ParsedBits::FactoryInfo; + } + else if (elementName == "Compatibility") + { + if (parsed & ParsedBits::Compatibility) + throw parse_error ("Only one 'Compatibility' key allowed", el.name ()); + parseCompatibility (el.value ()); + parsed |= ParsedBits::Compatibility; + } + else if (elementName == "Classes") + { + if (parsed & ParsedBits::Classes) + throw parse_error ("Only one 'Classes' key allowed", el.name ()); + parseClasses (el.value ()); + parsed |= ParsedBits::Classes; + } + else + { + throw parse_error ("Unexpected JSON Token", el.name ()); + } + } + if (!(parsed & ParsedBits::Name)) + throw std::logic_error ("'Name' key missing"); + if (!(parsed & ParsedBits::Version)) + throw std::logic_error ("'Version' key missing"); + if (!(parsed & ParsedBits::FactoryInfo)) + throw std::logic_error ("'Factory Info' key missing"); + if (!(parsed & ParsedBits::Classes)) + throw std::logic_error ("'Classes' key missing"); + } + + ModuleInfo&& takeInfo () { return std::move (info); } + +private: + ModuleInfo info; +}; + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +std::optional parseJson (std::string_view jsonData, std::ostream* optErrorOutput) +{ + auto docVar = JSON::Document::parse (jsonData); + if (auto res = std::get_if (&docVar)) + { + if (optErrorOutput) + printJsonParseError (*res, *optErrorOutput); + return {}; + } + auto doc = std::get_if (&docVar); + assert (doc); + try + { + ModuleInfoJsonParser parser; + parser.parse (*doc); + return parser.takeInfo (); + } + catch (std::exception& error) + { + if (optErrorOutput) + *optErrorOutput << error.what () << '\n'; + return {}; + } + // unreachable +} + +//------------------------------------------------------------------------ +std::optional parseCompatibilityJson (std::string_view jsonData, + std::ostream* optErrorOutput) +{ + auto docVar = JSON::Document::parse (jsonData); + if (auto res = std::get_if (&docVar)) + { + if (optErrorOutput) + printJsonParseError (*res, *optErrorOutput); + return {}; + } + auto doc = std::get_if (&docVar); + assert (doc); + try + { + ModuleInfoJsonParser parser; + parser.parseCompatibility (*doc); + return parser.takeInfo ().compatibility; + } + catch (std::exception& error) + { + if (optErrorOutput) + *optErrorOutput << error.what () << '\n'; + return {}; + } + // unreachable +} + +//------------------------------------------------------------------------ +} // Steinberg::ModuelInfoLib diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h new file mode 100644 index 0000000..9b426da --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/moduleinfo/moduleinfoparser.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// Flags : clang-format SMTGSequencer +// +// Category : moduleinfo +// Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.h +// Created by : Steinberg, 01/2022 +// Description : utility functions to parse moduleinfo json files +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "moduleinfo.h" +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg::ModuleInfoLib { + +//------------------------------------------------------------------------ +/** parse a json formatted string to a ModuleInfo struct + * + * @param jsonData a string view to a json formatted string + * @param optErrorOutput optional error output stream where to print parse error + * @return ModuleInfo if parsing succeeded + */ +std::optional parseJson (std::string_view jsonData, std::ostream* optErrorOutput); + +//------------------------------------------------------------------------ +/** parse a json formatted string to a ModuleInfo::CompatibilityList + * + * @param jsonData a string view to a json formatted string + * @param optErrorOutput optional error output stream where to print parse error + * @return ModuleInfo::CompatibilityList if parsing succeeded + */ +std::optional parseCompatibilityJson (std::string_view jsonData, + std::ostream* optErrorOutput); + +//------------------------------------------------------------------------ +} // Steinberg::ModuelInfoLib diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h new file mode 100644 index 0000000..7ea6a3c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/optional.h @@ -0,0 +1,135 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/utility/optional.h +// Created by : Steinberg, 08/2016 +// Description : optional helper +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +//------------------------------------------------------------------------ +namespace VST3 { + +//------------------------------------------------------------------------ +template +struct Optional +{ + Optional () noexcept : valid (false) {} + explicit Optional (const T& v) noexcept : _value (v), valid (true) {} + Optional (T&& v) noexcept : _value (std::move (v)), valid (true) {} + + Optional (Optional&& other) noexcept { *this = std::move (other); } + Optional& operator= (Optional&& other) noexcept + { + valid = other.valid; + _value = std::move (other._value); + return *this; + } + + explicit operator bool () const noexcept + { + setValidationChecked (); + return valid; + } + + const T& operator* () const noexcept + { + checkValid (); + return _value; + } + + const T* operator-> () const noexcept + { + checkValid (); + return &_value; + } + + T& operator* () noexcept + { + checkValid (); + return _value; + } + + T* operator-> () noexcept + { + checkValid (); + return &_value; + } + + T&& value () noexcept + { + checkValid (); + return move (_value); + } + + const T& value () const noexcept + { + checkValid (); + return _value; + } + + void swap (T& other) noexcept + { + checkValid (); + auto tmp = std::move (other); + other = std::move (_value); + _value = std::move (tmp); + } + +private: + T _value {}; + bool valid; + +#if !defined(NDEBUG) + mutable bool validationChecked {false}; +#endif + + void setValidationChecked () const + { +#if !defined(NDEBUG) + validationChecked = true; +#endif + } + void checkValid () const + { +#if !defined(NDEBUG) + assert (validationChecked); +#endif + } +}; + +//------------------------------------------------------------------------ +} diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.cpp new file mode 100644 index 0000000..6bc7f3a --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.cpp @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/utility/stringconvert.cpp +// Created by : Steinberg, 11/2014 +// Description : c++11 unicode string convert functions +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "stringconvert.h" +#include +#include +#include + +//------------------------------------------------------------------------ +namespace VST3 { +namespace StringConvert { + +//------------------------------------------------------------------------ +namespace { + +#if defined(_MSC_VER) && _MSC_VER >= 1900 +#define USE_WCHAR_AS_UTF16TYPE +using UTF16Type = wchar_t; +#else +using UTF16Type = char16_t; +#endif + +using Converter = std::wstring_convert, UTF16Type>; + +//------------------------------------------------------------------------ +Converter& converter () +{ + static Converter conv; + return conv; +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +std::u16string convert (const std::string& utf8Str) +{ +#if defined(USE_WCHAR_AS_UTF16TYPE) + auto wstr = converter ().from_bytes (utf8Str); + return {wstr.data (), wstr.data () + wstr.size ()}; +#else + return converter ().from_bytes (utf8Str); +#endif +} + +//------------------------------------------------------------------------ +bool convert (const std::string& utf8Str, Steinberg::Vst::String128 str) +{ + return convert (utf8Str, str, 128); +} + +//------------------------------------------------------------------------ +bool convert (const std::string& utf8Str, Steinberg::Vst::TChar* str, uint32_t maxCharacters) +{ + auto ucs2 = convert (utf8Str); + if (ucs2.length () < maxCharacters) + { + ucs2.copy (reinterpret_cast (str), ucs2.length ()); + str[ucs2.length ()] = 0; + return true; + } + return false; +} + +//------------------------------------------------------------------------ +std::string convert (const Steinberg::Vst::TChar* str) +{ + return converter ().to_bytes (reinterpret_cast (str)); +} + +//------------------------------------------------------------------------ +std::string convert (const Steinberg::Vst::TChar* str, uint32_t max) +{ + std::string result; + if (str) + { + Steinberg::Vst::TChar tmp[2] {}; + for (uint32_t i = 0; i < max; ++i, ++str) + { + tmp[0] = *str; + if (tmp[0] == 0) + break; + result += convert (tmp); + } + } + return result; +} + +//------------------------------------------------------------------------ +std::string convert (const std::u16string& str) +{ + return converter ().to_bytes (reinterpret_cast (str.data ()), + reinterpret_cast (str.data () + str.size ())); +} + +//------------------------------------------------------------------------ +std::string convert (const char* str, uint32_t max) +{ + std::string result; + if (str) + { + result.reserve (max); + for (uint32_t i = 0; i < max; ++i, ++str) + { + if (*str == 0) + break; + result += *str; + } + } + return result; +} + +//------------------------------------------------------------------------ +} // String +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.h new file mode 100644 index 0000000..d2d4678 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/stringconvert.h @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/utility/stringconvert.h +// Created by : Steinberg, 11/2014 +// Description : c++11 unicode string convert functions +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "pluginterfaces/vst/vsttypes.h" +#include + +//------------------------------------------------------------------------ +namespace VST3 { +namespace StringConvert { + +//------------------------------------------------------------------------ +/** + * convert an UTF-8 string to an UTF-16 string + * + * @param utf8Str UTF-8 string + * + * @return UTF-16 string + */ +extern std::u16string convert (const std::string& utf8Str); + +//------------------------------------------------------------------------ +/** + * convert an UTF-8 string to an UTF-16 string buffer with max 127 characters + * + * @param utf8Str UTF-8 string + * @param str UTF-16 string buffer + * + * @return true on success + */ +extern bool convert (const std::string& utf8Str, Steinberg::Vst::String128 str); + +//------------------------------------------------------------------------ +/** + * convert an UTF-8 string to an UTF-16 string buffer + * + * @param utf8Str UTF-8 string + * @param str UTF-16 string buffer + * @param maxCharacters max characters that fit into str + * + * @return true on success + */ +extern bool convert (const std::string& utf8Str, Steinberg::Vst::TChar* str, + uint32_t maxCharacters); + +//------------------------------------------------------------------------ +/** + * convert an UTF-16 string buffer to an UTF-8 string + * + * @param str UTF-16 string buffer + * + * @return UTF-8 string + */ +extern std::string convert (const Steinberg::Vst::TChar* str); + +//------------------------------------------------------------------------ +/** + * convert an UTF-16 string buffer to an UTF-8 string + * + * @param str UTF-16 string buffer + * @param max maximum characters in str + * + * @return UTF-8 string + */ +extern std::string convert (const Steinberg::Vst::TChar* str, uint32_t max); + +//------------------------------------------------------------------------ +/** + * convert an UTF-16 string to an UTF-8 string + * + * @param str UTF-16 string + * + * @return UTF-8 string + */ +extern std::string convert (const std::u16string& str); + +//------------------------------------------------------------------------ +/** + * convert a ASCII string buffer to an UTF-8 string + * + * @param str ASCII string buffer + * @param max maximum characters in str + * + * @return UTF-8 string + */ +extern std::string convert (const char* str, uint32_t max); + +//------------------------------------------------------------------------ +} // StringConvert + +//------------------------------------------------------------------------ +inline const Steinberg::Vst::TChar* toTChar (const std::u16string& str) +{ + return reinterpret_cast (str.data ()); +} + +//------------------------------------------------------------------------ +/** + * convert an number to an UTF-16 string + * + * @param value number + * + * @return UTF-16 string + */ +template +std::u16string toString (NumberT value) +{ + auto u8str = std::to_string (value); + return StringConvert::convert (u8str); +} + +//------------------------------------------------------------------------ +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h new file mode 100644 index 0000000..2f8ed8b --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/utility/uid.h @@ -0,0 +1,294 @@ +//----------------------------------------------------------------------------- +// Project : VST SDK +// +// Category : Helpers +// Filename : public.sdk/source/vst/utility/uid.h +// Created by : Steinberg, 08/2016 +// Description : UID +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "optional.h" +#include "pluginterfaces/base/funknown.h" +#include + +//------------------------------------------------------------------------ +namespace VST3 { + +//------------------------------------------------------------------------ +struct UID +{ +#if defined(SMTG_OS_WINDOWS) && SMTG_OS_WINDOWS == 1 + static constexpr bool defaultComFormat = true; +#else + static constexpr bool defaultComFormat = false; +#endif + + using TUID = Steinberg::TUID; + + constexpr UID () noexcept = default; + UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat = defaultComFormat) + noexcept; + UID (const TUID& uid) noexcept; + UID (const UID& uid) noexcept; + UID& operator= (const UID& uid) noexcept; + UID& operator= (const TUID& uid) noexcept; + + constexpr const TUID& data () const noexcept; + constexpr size_t size () const noexcept; + + std::string toString (bool comFormat = defaultComFormat) const noexcept; + + template + static Optional fromString (const StringT& str, + bool comFormat = defaultComFormat) noexcept; + + static UID fromTUID (const TUID _uid) noexcept; +//------------------------------------------------------------------------ +private: + Steinberg::TUID _data {}; + + struct GUID + { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; + }; +}; + +//------------------------------------------------------------------------ +inline bool operator== (const UID& uid1, const UID& uid2) +{ + const uint64_t* p1 = reinterpret_cast (uid1.data ()); + const uint64_t* p2 = reinterpret_cast (uid2.data ()); + return p1[0] == p2[0] && p1[1] == p2[1]; +} + +//------------------------------------------------------------------------ +inline bool operator!= (const UID& uid1, const UID& uid2) +{ + return !(uid1 == uid2); +} + +//------------------------------------------------------------------------ +inline bool operator< (const UID& uid1, const UID& uid2) +{ + const uint64_t* p1 = reinterpret_cast (uid1.data ()); + const uint64_t* p2 = reinterpret_cast (uid2.data ()); + return (p1[0] < p2[0]) && (p1[1] < p2[1]); +} + +//------------------------------------------------------------------------ +inline UID::UID (uint32_t l1, uint32_t l2, uint32_t l3, uint32_t l4, bool comFormat) noexcept +{ + if (comFormat) + { + _data[0] = static_cast ((l1 & 0x000000FF)); + _data[1] = static_cast ((l1 & 0x0000FF00) >> 8); + _data[2] = static_cast ((l1 & 0x00FF0000) >> 16); + _data[3] = static_cast ((l1 & 0xFF000000) >> 24); + _data[4] = static_cast ((l2 & 0x00FF0000) >> 16); + _data[5] = static_cast ((l2 & 0xFF000000) >> 24); + _data[6] = static_cast ((l2 & 0x000000FF)); + _data[7] = static_cast ((l2 & 0x0000FF00) >> 8); + _data[8] = static_cast ((l3 & 0xFF000000) >> 24); + _data[9] = static_cast ((l3 & 0x00FF0000) >> 16); + _data[10] = static_cast ((l3 & 0x0000FF00) >> 8); + _data[11] = static_cast ((l3 & 0x000000FF)); + _data[12] = static_cast ((l4 & 0xFF000000) >> 24); + _data[13] = static_cast ((l4 & 0x00FF0000) >> 16); + _data[14] = static_cast ((l4 & 0x0000FF00) >> 8); + _data[15] = static_cast ((l4 & 0x000000FF)); + } + else + { + _data[0] = static_cast ((l1 & 0xFF000000) >> 24); + _data[1] = static_cast ((l1 & 0x00FF0000) >> 16); + _data[2] = static_cast ((l1 & 0x0000FF00) >> 8); + _data[3] = static_cast ((l1 & 0x000000FF)); + _data[4] = static_cast ((l2 & 0xFF000000) >> 24); + _data[5] = static_cast ((l2 & 0x00FF0000) >> 16); + _data[6] = static_cast ((l2 & 0x0000FF00) >> 8); + _data[7] = static_cast ((l2 & 0x000000FF)); + _data[8] = static_cast ((l3 & 0xFF000000) >> 24); + _data[9] = static_cast ((l3 & 0x00FF0000) >> 16); + _data[10] = static_cast ((l3 & 0x0000FF00) >> 8); + _data[11] = static_cast ((l3 & 0x000000FF)); + _data[12] = static_cast ((l4 & 0xFF000000) >> 24); + _data[13] = static_cast ((l4 & 0x00FF0000) >> 16); + _data[14] = static_cast ((l4 & 0x0000FF00) >> 8); + _data[15] = static_cast ((l4 & 0x000000FF)); + } +} + +//------------------------------------------------------------------------ +inline UID::UID (const TUID& uid) noexcept +{ + *this = uid; +} + +//------------------------------------------------------------------------ +inline UID::UID (const UID& uid) noexcept +{ + *this = uid; +} + +//------------------------------------------------------------------------ +inline UID& UID::operator= (const UID& uid) noexcept +{ + *this = uid.data (); + return *this; +} + +//------------------------------------------------------------------------ +inline UID& UID::operator= (const TUID& uid) noexcept +{ + uint64_t* p1 = reinterpret_cast (_data); + const uint64_t* p2 = reinterpret_cast (uid); + p1[0] = p2[0]; + p1[1] = p2[1]; + return *this; +} + +//------------------------------------------------------------------------ +inline constexpr auto UID::data () const noexcept -> const TUID& +{ + return _data; +} + +//------------------------------------------------------------------------ +inline constexpr size_t UID::size () const noexcept +{ + return sizeof (TUID); +} + +//------------------------------------------------------------------------ +inline std::string UID::toString (bool comFormat) const noexcept +{ + std::string result; + result.reserve (32); + if (comFormat) + { + const auto& g = reinterpret_cast (_data); + + char tmp[21] {}; + snprintf (tmp, 21, "%08X%04X%04X", g->Data1, g->Data2, g->Data3); + result = tmp; + + for (uint32_t i = 0; i < 8; ++i) + { + char s[3] {}; + snprintf (s, 3, "%02X", g->Data4[i]); + result += s; + } + } + else + { + for (uint32_t i = 0; i < 16; ++i) + { + char s[3] {}; + snprintf (s, 3, "%02X", static_cast (_data[i])); + result += s; + } + } + return result; +} + +//------------------------------------------------------------------------ +template +inline Optional UID::fromString (const StringT& str, bool comFormat) noexcept +{ + if (str.length () != 32) + return {}; + // TODO: this is a copy from FUID. there are no input validation checks !!! + if (comFormat) + { + TUID uid {}; + GUID g; + char s[33]; + + strcpy (s, str.data ()); + s[8] = 0; + sscanf (s, "%x", &g.Data1); + strcpy (s, str.data () + 8); + s[4] = 0; + sscanf (s, "%hx", &g.Data2); + strcpy (s, str.data () + 12); + s[4] = 0; + sscanf (s, "%hx", &g.Data3); + + memcpy (uid, &g, 8); + + for (uint32_t i = 8; i < 16; ++i) + { + char s2[3] {}; + s2[0] = str[i * 2]; + s2[1] = str[i * 2 + 1]; + + int32_t d = 0; + sscanf (s2, "%2x", &d); + uid[i] = static_cast (d); + } + return {uid}; + } + else + { + TUID uid {}; + for (uint32_t i = 0; i < 16; ++i) + { + char s[3] {}; + s[0] = str[i * 2]; + s[1] = str[i * 2 + 1]; + + int32_t d = 0; + sscanf (s, "%2x", &d); + uid[i] = static_cast (d); + } + return {uid}; + } +} + +//------------------------------------------------------------------------ +inline UID UID::fromTUID (const TUID _uid) noexcept +{ + UID result; + + uint64_t* p1 = reinterpret_cast (result._data); + const uint64_t* p2 = reinterpret_cast (_uid); + p1[0] = p2[0]; + p1[1] = p2[1]; + + return result; +} + +//------------------------------------------------------------------------ +} // VST3 diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.cpp index 2615b87..8f6ff44 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -50,7 +50,8 @@ Bus::Bus (const TChar* _name, BusType _busType, int32 _flags) //------------------------------------------------------------------------ bool Bus::getInfo (BusInfo& info) { - name.copyTo16 (info.name, 0, str16BufferSize (info.name) - 1); + memset (info.name, 0, sizeof (String128)); + name.copy (info.name, 128); info.busType = busType; info.flags = flags; return true; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.h index c3c3c21..f1e7ad4 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstbus.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -37,10 +37,13 @@ #pragma once #include "base/source/fobject.h" -#include "base/source/fstring.h" #include "pluginterfaces/vst/ivstcomponent.h" #include "pluginterfaces/vst/ivstaudioprocessor.h" +#include +#if SMTG_CPP_17 +#include +#endif #include //------------------------------------------------------------------------ @@ -64,8 +67,13 @@ class Bus: public FObject /** Activates the bus. */ void setActive (TBool state) { active = state; } +#if SMTG_CPP_17 /** Sets a new name for this bus. */ - void setName (String newName) { name = newName; } + void setName (std::u16string_view newName) { name = newName; } +#else + /** Sets a new name for this bus. */ + void setName (const std::u16string& newName) { name = newName; } +#endif /** Sets a new busType for this bus. */ void setBusType (BusType newBusType) { busType = newBusType; } @@ -79,7 +87,7 @@ class Bus: public FObject OBJ_METHODS (Vst::Bus, FObject) //------------------------------------------------------------------------ protected: - String name; ///< name + std::u16string name; ///< name BusType busType; ///< kMain or kAux, see \ref BusTypes int32 flags; ///< flags, see \ref BusInfo::BusFlags TBool active; ///< activation state diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.cpp index 0c46feb..57e03d0 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -70,7 +70,7 @@ BusList* Component::getBusList (MediaType type, BusDirection dir) { if (type == kAudio) return dir == kInput ? &audioInputs : &audioOutputs; - else if (type == kEvent) + if (type == kEvent) return dir == kInput ? &eventInputs : &eventOutputs; return nullptr; } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.h index 7994534..a1009d4 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponent.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -59,6 +59,7 @@ class Component : public ComponentBase, public IComponent //---Internal Methods------- /** Sets the controller Class ID associated to its component. */ void setControllerClass (const FUID& cid) { controllerClass = cid; } + void setControllerClass (const TUID& cid) { controllerClass = FUID::fromTUID (cid); } /** Removes all Audio Busses. */ tresult removeAudioBusses (); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.cpp index 4d2443c..c82ac39 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -159,7 +159,7 @@ tresult ComponentBase::sendTextMessage (const char8* text) const } //------------------------------------------------------------------------ -tresult ComponentBase::sendMessageID (const char* messageID) const +tresult ComponentBase::sendMessageID (const char8* messageID) const { if (auto msg = owned (allocateMessage ())) { diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.h index 64474bd..e71b857 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstcomponentbase.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.cpp index a88483c..4342c7b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -48,7 +48,7 @@ KnobMode EditController::hostKnobMode = kCircularMode; //------------------------------------------------------------------------ // EditController Implementation //------------------------------------------------------------------------ -EditController::EditController () : componentHandler (nullptr), componentHandler2 (nullptr) +EditController::EditController () { } @@ -63,17 +63,8 @@ tresult PLUGIN_API EditController::terminate () { parameters.removeAll (); - if (componentHandler) - { - componentHandler->release (); - componentHandler = nullptr; - } - - if (componentHandler2) - { - componentHandler2->release (); - componentHandler2 = nullptr; - } + componentHandler.reset (); + componentHandler2.reset (); return ComponentBase::terminate (); } @@ -189,25 +180,11 @@ tresult PLUGIN_API EditController::setComponentHandler (IComponentHandler* newHa return kResultTrue; } - if (componentHandler) - { - componentHandler->release (); - } - componentHandler = newHandler; - if (componentHandler) - { - componentHandler->addRef (); - } + componentHandler2.reset (); - // try to get the extended version - if (componentHandler2) - { - componentHandler2->release (); - componentHandler2 = nullptr; - } - - if (newHandler) + // try to get the extended version + if (newHandler) { newHandler->queryInterface (IComponentHandler2::iid, (void**)&componentHandler2); } @@ -302,10 +279,6 @@ tresult EditController::requestOpenEditor (FIDString name) EditorView::EditorView (EditController* _controller, ViewRect* size) : CPluginView (size), controller (_controller) { - if (controller) - { - controller->addRef (); - } } //------------------------------------------------------------------------ @@ -314,7 +287,7 @@ EditorView::~EditorView () if (controller) { controller->editorDestroyed (this); - controller->release (); + controller = nullptr; } } @@ -340,7 +313,7 @@ void EditorView::removedFromParent () //------------------------------------------------------------------------ // EditControllerEx1 implementation //------------------------------------------------------------------------ -EditControllerEx1::EditControllerEx1 () : selectedUnit (kRootUnitId) +EditControllerEx1::EditControllerEx1 () { UpdateHandler::instance (); } @@ -376,6 +349,8 @@ bool EditControllerEx1::addUnit (Unit* unit) //------------------------------------------------------------------------ tresult PLUGIN_API EditControllerEx1::getUnitInfo (int32 unitIndex, UnitInfo& info /*out*/) { + if (unitIndex < 0 || unitIndex >= static_cast (units.size ())) + return kResultFalse; if (Unit* unit = units.at (unitIndex)) { info = unit->getInfo (); @@ -586,9 +561,10 @@ tresult ProgramList::getProgramInfo (int32 programIndex, CString attributeId, StringMap::const_iterator it = programInfos[programIndex].find (attributeId); if (it != programInfos[programIndex].end ()) { - if (!it->second.isEmpty ()) + if (!it->second.empty ()) { - it->second.copyTo16 (value, 0, 128); + memset (value, 0, sizeof (String128)); + it->second.copy (value, 128); return kResultTrue; } } @@ -601,7 +577,8 @@ tresult ProgramList::getProgramName (int32 programIndex, String128 name /*out*/) { if (programIndex >= 0 && programIndex < static_cast (programNames.size ())) { - programNames.at (programIndex).copyTo16 (name, 0, 128); + memset (name, 0, sizeof (String128)); + programNames.at (programIndex).copy (name, 128); return kResultTrue; } return kResultFalse; @@ -633,7 +610,7 @@ Parameter* ProgramList::getParameter () unitId); for (const auto& programName : programNames) { - listParameter->appendString (programName); + listParameter->appendString (programName.data ()); } parameter = listParameter; } @@ -711,7 +688,8 @@ tresult ProgramListWithPitchNames::getPitchName (int32 programIndex, int16 midiP PitchNameMap::const_iterator it = pitchNames[programIndex].find (midiPitch); if (it != pitchNames[programIndex].end ()) { - it->second.copyTo16 (name, 0, 128); + memset (name, 0, sizeof (String128)); + it->second.copy (name, 128); return kResultTrue; } } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.h index d672784..9fcb0e6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vsteditcontroller.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -39,13 +39,13 @@ #include "public.sdk/source/vst/vstcomponentbase.h" #include "public.sdk/source/vst/vstparameters.h" #include "public.sdk/source/common/pluginview.h" -#include "base/source/fstring.h" #include "pluginterfaces/vst/ivsteditcontroller.h" #include "pluginterfaces/vst/ivstunits.h" -#include #include +#include +#include //------------------------------------------------------------------------ namespace Steinberg { @@ -125,8 +125,8 @@ class EditController : public ComponentBase, public IEditController, public IEdi REFCOUNT_METHODS (ComponentBase) //------------------------------------------------------------------------ protected: - IComponentHandler* componentHandler; - IComponentHandler2* componentHandler2; + IPtr componentHandler; + IPtr componentHandler2; ParameterContainer parameters; @@ -153,7 +153,7 @@ class EditorView : public CPluginView //------------------------------------------------------------------------ protected: - EditController* controller; + IPtr controller; }; //------------------------------------------------------------------------ @@ -241,8 +241,8 @@ class ProgramList : public FObject OBJ_METHODS (ProgramList, FObject) //------------------------------------------------------------------------ protected: - using StringMap = std::map; - using StringVector = std::vector; + using StringMap = std::map; + using StringVector = std::vector; using ProgramInfoVector = std::vector; ProgramListInfo info; UnitID unitId; @@ -275,7 +275,7 @@ class ProgramListWithPitchNames : public ProgramList OBJ_METHODS (ProgramListWithPitchNames, ProgramList) protected: - using PitchNameMap = std::map; + using PitchNameMap = std::map; using PitchNamesVector = std::vector; PitchNamesVector pitchNames; }; @@ -366,7 +366,7 @@ class EditControllerEx1 : public EditController, public IUnitInfo UnitVector units; ProgramListVector programLists; ProgramIndexMap programIndexMap; - UnitID selectedUnit; + UnitID selectedUnit {kRootUnitId}; }; //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstinitiids.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstinitiids.cpp index aa06547..c5ad8b0 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstinitiids.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstinitiids.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -36,6 +36,7 @@ #include "pluginterfaces/base/funknown.h" +#include "pluginterfaces/base/iplugincompatibility.h" #include "pluginterfaces/vst/ivstaudioprocessor.h" #include "pluginterfaces/vst/ivstautomationstate.h" #include "pluginterfaces/vst/ivstchannelcontextinfo.h" @@ -58,86 +59,87 @@ //------------------------------------------------------------------------ namespace Steinberg { -namespace Vst { //----VST 3.0-------------------------------- -DEF_CLASS_IID (IComponent) -DEF_CLASS_IID (IAudioProcessor) -DEF_CLASS_IID (IUnitData) -DEF_CLASS_IID (IProgramListData) +DEF_CLASS_IID (Vst::IComponent) +DEF_CLASS_IID (Vst::IAudioProcessor) +DEF_CLASS_IID (Vst::IUnitData) +DEF_CLASS_IID (Vst::IProgramListData) -DEF_CLASS_IID (IEditController) -DEF_CLASS_IID (IUnitInfo) +DEF_CLASS_IID (Vst::IEditController) +DEF_CLASS_IID (Vst::IUnitInfo) -DEF_CLASS_IID (IConnectionPoint) +DEF_CLASS_IID (Vst::IConnectionPoint) -DEF_CLASS_IID (IComponentHandler) -DEF_CLASS_IID (IUnitHandler) +DEF_CLASS_IID (Vst::IComponentHandler) +DEF_CLASS_IID (Vst::IUnitHandler) -DEF_CLASS_IID (IParamValueQueue) -DEF_CLASS_IID (IParameterChanges) +DEF_CLASS_IID (Vst::IParamValueQueue) +DEF_CLASS_IID (Vst::IParameterChanges) -DEF_CLASS_IID (IEventList) -DEF_CLASS_IID (IMessage) +DEF_CLASS_IID (Vst::IEventList) +DEF_CLASS_IID (Vst::IMessage) -DEF_CLASS_IID (IHostApplication) -DEF_CLASS_IID (IAttributeList) +DEF_CLASS_IID (Vst::IHostApplication) +DEF_CLASS_IID (Vst::IAttributeList) //----VST 3.0.1-------------------------------- -DEF_CLASS_IID (IMidiMapping) +DEF_CLASS_IID (Vst::IMidiMapping) //----VST 3.0.2-------------------------------- -DEF_CLASS_IID (IParameterFinder) +DEF_CLASS_IID (Vst::IParameterFinder) //----VST 3.1---------------------------------- -DEF_CLASS_IID (IComponentHandler2) -DEF_CLASS_IID (IEditController2) -DEF_CLASS_IID (IAudioPresentationLatency) -DEF_CLASS_IID (IVst3ToVst2Wrapper) -DEF_CLASS_IID (IVst3ToAUWrapper) +DEF_CLASS_IID (Vst::IComponentHandler2) +DEF_CLASS_IID (Vst::IEditController2) +DEF_CLASS_IID (Vst::IAudioPresentationLatency) +DEF_CLASS_IID (Vst::IVst3ToVst2Wrapper) +DEF_CLASS_IID (Vst::IVst3ToAUWrapper) //----VST 3.5---------------------------------- -DEF_CLASS_IID (INoteExpressionController) -DEF_CLASS_IID (IKeyswitchController) -DEF_CLASS_IID (IContextMenuTarget) -DEF_CLASS_IID (IContextMenu) -DEF_CLASS_IID (IComponentHandler3) -DEF_CLASS_IID (IEditControllerHostEditing) -DEF_CLASS_IID (IXmlRepresentationController) +DEF_CLASS_IID (Vst::INoteExpressionController) +DEF_CLASS_IID (Vst::IKeyswitchController) +DEF_CLASS_IID (Vst::IContextMenuTarget) +DEF_CLASS_IID (Vst::IContextMenu) +DEF_CLASS_IID (Vst::IComponentHandler3) +DEF_CLASS_IID (Vst::IEditControllerHostEditing) +DEF_CLASS_IID (Vst::IXmlRepresentationController) //----VST 3.6---------------------------------- -DEF_CLASS_IID (IInterAppAudioHost) -DEF_CLASS_IID (IInterAppAudioConnectionNotification) -DEF_CLASS_IID (IInterAppAudioPresetManager) -DEF_CLASS_IID (IStreamAttributes) +DEF_CLASS_IID (Vst::IInterAppAudioHost) +DEF_CLASS_IID (Vst::IInterAppAudioConnectionNotification) +DEF_CLASS_IID (Vst::IInterAppAudioPresetManager) +DEF_CLASS_IID (Vst::IStreamAttributes) //----VST 3.6.5-------------------------------- -DEF_CLASS_IID (ChannelContext::IInfoListener) -DEF_CLASS_IID (IPrefetchableSupport) -DEF_CLASS_IID (IUnitHandler2) -DEF_CLASS_IID (IAutomationState) +DEF_CLASS_IID (Vst::ChannelContext::IInfoListener) +DEF_CLASS_IID (Vst::IPrefetchableSupport) +DEF_CLASS_IID (Vst::IUnitHandler2) +DEF_CLASS_IID (Vst::IAutomationState) //----VST 3.6.8-------------------------------- -DEF_CLASS_IID (IComponentHandlerBusActivation) -DEF_CLASS_IID (IVst3ToAAXWrapper) +DEF_CLASS_IID (Vst::IComponentHandlerBusActivation) +DEF_CLASS_IID (Vst::IVst3ToAAXWrapper) //----VST 3.6.11-------------------------------- -DEF_CLASS_IID (INoteExpressionPhysicalUIMapping) +DEF_CLASS_IID (Vst::INoteExpressionPhysicalUIMapping) //----VST 3.6.12-------------------------------- -DEF_CLASS_IID (IMidiLearn) -DEF_CLASS_IID (IPlugInterfaceSupport) -DEF_CLASS_IID (IVst3WrapperMPESupport) +DEF_CLASS_IID (Vst::IMidiLearn) +DEF_CLASS_IID (Vst::IPlugInterfaceSupport) +DEF_CLASS_IID (Vst::IVst3WrapperMPESupport) //----VST 3.6.13-------------------------------- -DEF_CLASS_IID (ITestPlugProvider) +DEF_CLASS_IID (Vst::ITestPlugProvider) //----VST 3.7----------------------------------- -DEF_CLASS_IID (IParameterFunctionName) -DEF_CLASS_IID (IProcessContextRequirements) -DEF_CLASS_IID (IProgress) -DEF_CLASS_IID (ITestPlugProvider2) +DEF_CLASS_IID (Vst::IParameterFunctionName) +DEF_CLASS_IID (Vst::IProcessContextRequirements) +DEF_CLASS_IID (Vst::IProgress) +DEF_CLASS_IID (Vst::ITestPlugProvider2) + + +//----VST 3.7.5--------------------------------- +DEF_CLASS_IID (IPluginCompatibility) -//------------------------------------------------------------------------ -} // Vst } // Steinberg diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.cpp index 400f6e3..dd08351 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -45,14 +45,13 @@ namespace Vst { //------------------------------------------------------------------------ // Parameter Implementation //------------------------------------------------------------------------ -Parameter::Parameter () : valueNormalized (0.), precision (4) +Parameter::Parameter () { - info = {}; } //------------------------------------------------------------------------ Parameter::Parameter (const ParameterInfo& info) -: info (info), valueNormalized (info.defaultNormalizedValue), precision (4) +: info (info), valueNormalized (info.defaultNormalizedValue) { } @@ -60,10 +59,7 @@ Parameter::Parameter (const ParameterInfo& info) Parameter::Parameter (const TChar* title, ParamID tag, const TChar* units, ParamValue defaultValueNormalized, int32 stepCount, int32 flags, UnitID unitID, const TChar* shortTitle) -: precision (4) { - info = {}; - UString (info.title, str16BufferSize (String128)).assign (title); if (units) UString (info.units, str16BufferSize (String128)).assign (units); @@ -302,7 +298,7 @@ bool StringListParameter::replaceString (int32 index, const String128 string) //------------------------------------------------------------------------ void StringListParameter::toString (ParamValue _valueNormalized, String128 string) const { - int32 index = (int32)toPlain (_valueNormalized); + int32 index = static_cast (toPlain (_valueNormalized)); if (const TChar* valueString = strings.at (index)) { UString (string, str16BufferSize (String128)).assign (valueString); @@ -345,7 +341,7 @@ ParamValue StringListParameter::toNormalized (ParamValue plainValue) const //------------------------------------------------------------------------ // ParameterContainer Implementation //------------------------------------------------------------------------ -ParameterContainer::ParameterContainer () : params (nullptr) +ParameterContainer::ParameterContainer () { } @@ -389,6 +385,14 @@ Parameter* ParameterContainer::addParameter (const ParameterInfo& info) return nullptr; } +//------------------------------------------------------------------------ +Parameter* ParameterContainer::getParameterByIndex (int32 index) const +{ + if (!params || index < 0 || index >= static_cast (params->size ())) + return nullptr; + return params->at (index); +} + //------------------------------------------------------------------------ Parameter* ParameterContainer::getParameter (ParamID tag) const { diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.h index 51d1b4a..88742e2 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstparameters.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -98,9 +98,9 @@ class Parameter : public FObject OBJ_METHODS (Parameter, FObject) //------------------------------------------------------------------------ protected: - ParameterInfo info; - ParamValue valueNormalized; - int32 precision; + ParameterInfo info {0}; + ParamValue valueNormalized {0.}; + int32 precision {4}; }; //------------------------------------------------------------------------ @@ -212,7 +212,7 @@ class ParameterContainer int32 getParameterCount () const { return params ? static_cast (params->size ()) : 0; } /** Gets parameter by index. */ - Parameter* getParameterByIndex (int32 index) const { return params ? params->at (index) : nullptr; } + Parameter* getParameterByIndex (int32 index) const; /** Removes all parameters. */ void removeAll () @@ -231,7 +231,7 @@ class ParameterContainer protected: using ParameterPtrVector = std::vector>; using IndexMap = std::map; - ParameterPtrVector* params; + ParameterPtrVector* params {nullptr}; IndexMap id2index; }; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.cpp index 1adb8e4..3a47c17 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.cpp @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -35,10 +35,8 @@ //----------------------------------------------------------------------------- #include "vstpresetfile.h" - #include - namespace Steinberg { namespace Vst { @@ -178,7 +176,7 @@ bool PresetFile::loadPreset (IBStream* stream, const FUID& classID, IComponent* } //------------------------------------------------------------------------ -PresetFile::PresetFile (IBStream* stream) : stream (stream), entryCount (0) +PresetFile::PresetFile (IBStream* stream) : stream (stream) { memset (entries, 0, sizeof (entries)); @@ -490,13 +488,12 @@ bool PresetFile::storeComponentState (IBStream* componentStream) bool PresetFile::restoreComponentState (IComponent* component) { const Entry* e = getEntry (kComponentState); - if (e) - { - auto* readOnlyBStream = new ReadOnlyBStream (stream, e->offset, e->size); - FReleaser readOnlyBStreamReleaser (readOnlyBStream); - return verify (component->setState (readOnlyBStream)); - } - return false; + if (!e) + return false; + + auto readOnlyBStream = owned (new ReadOnlyBStream (stream, e->offset, e->size)); + return verify (component->setState (readOnlyBStream)); + } //------------------------------------------------------------------------ @@ -505,8 +502,7 @@ bool PresetFile::restoreComponentState (IEditController* editController) const Entry* e = getEntry (kComponentState); if (e) { - auto* readOnlyBStream = new ReadOnlyBStream (stream, e->offset, e->size); - FReleaser readOnlyBStreamReleaser (readOnlyBStream); + auto readOnlyBStream = owned (new ReadOnlyBStream (stream, e->offset, e->size)); return verify (editController->setComponentState (readOnlyBStream)); } return false; @@ -546,8 +542,7 @@ bool PresetFile::restoreControllerState (IEditController* editController) const Entry* e = getEntry (kControllerState); if (e) { - auto* readOnlyBStream = new ReadOnlyBStream (stream, e->offset, e->size); - FReleaser readOnlyBStreamReleaser (readOnlyBStream); + auto readOnlyBStream = owned (new ReadOnlyBStream (stream, e->offset, e->size)); return verify (editController->setState (readOnlyBStream)); } return false; @@ -603,9 +598,8 @@ bool PresetFile::restoreProgramData (IProgramListData* programListData, return false; int32 alreadyRead = sizeof (int32); - auto* readOnlyBStream = - new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead); - FReleaser readOnlyBStreamReleaser (readOnlyBStream); + auto readOnlyBStream = owned ( + new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead)); return programListData && verify (programListData->setProgramData ( savedProgramListID, programIndex, readOnlyBStream)); } @@ -639,9 +633,8 @@ bool PresetFile::restoreProgramData (IUnitData* unitData, UnitID* unitId) return false; int32 alreadyRead = sizeof (int32); - auto* readOnlyBStream = - new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead); - FReleaser readOnlyStreamReleaser (readOnlyBStream); + auto readOnlyBStream = owned ( + new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead)); return (unitData && verify (unitData->setUnitData (savedUnitID, readOnlyBStream))); } } @@ -662,9 +655,8 @@ bool PresetFile::restoreProgramData (IUnitInfo* unitInfo, int32 unitProgramListI return false; int32 alreadyRead = sizeof (int32); - auto* readOnlyBStream = - new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead); - FReleaser readOnlyStreamReleaser (readOnlyBStream); + auto readOnlyBStream = owned ( + new ReadOnlyBStream (stream, e->offset + alreadyRead, e->size - alreadyRead)); return (unitInfo && unitInfo->setUnitProgramData (unitProgramListID, programIndex, readOnlyBStream)); } @@ -788,7 +780,7 @@ tresult PLUGIN_API ReadOnlyBStream::read (void* buffer, int32 numBytes, int32* n if (!sourceStream) return kNotInitialized; - int32 maxBytesToRead = (int32) (sectionSize - seekPosition); + int32 maxBytesToRead = static_cast (sectionSize - seekPosition); if (numBytes > maxBytesToRead) numBytes = maxBytesToRead; if (numBytes <= 0) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.h index c68da25..2a9f83f 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/VST3_SDK/public.sdk/source/vst/vstpresetfile.h @@ -8,7 +8,7 @@ // //----------------------------------------------------------------------------- // LICENSE -// (c) 2021, Steinberg Media Technologies GmbH, All Rights Reserved +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved //----------------------------------------------------------------------------- // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: @@ -124,7 +124,7 @@ class PresetFile TSize size; }; - IBStream* getStream () { return stream; } ///< Returns the associated stream. + IBStream* getStream () const { return stream; } ///< Returns the associated stream. const FUID& getClassID () const { return classID; } ///< Returns the associated classID (component ID: Processor part (not the controller!)). void setClassID (const FUID& uid) { classID = uid; }///< Sets the associated classID (component ID: Processor part (not the controller!)). @@ -223,7 +223,7 @@ class PresetFile FUID classID; ///< classID is the FUID of the component (processor) part enum { kMaxEntries = 128 }; Entry entries[kMaxEntries]; - int32 entryCount; + int32 entryCount {0}; }; //------------------------------------------------------------------------ diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_ARAHosting.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_ARAHosting.h index 1494de6..19f13d1 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_ARAHosting.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_ARAHosting.h @@ -107,6 +107,8 @@ struct ConversionFunctions when the lifetime of the helper class object ends. You shouldn't use this class directly but instead inherit from the helper classes. + + @tags{ARA} */ template class ManagedARAHandle @@ -279,6 +281,8 @@ class AudioModification : public ManagedARAHandle= __IPHONE_15_0) \ + || (JUCE_MAC && defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0) + #define JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED 1 +#else + #define JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED 0 +#endif + +#include + +#if JUCE_APPLE_MIDI_EVENT_LIST_SUPPORTED + #include +#endif + namespace juce { @@ -359,7 +373,10 @@ struct AudioUnitHelpers } auto layout = processor.getBusesLayout(); - auto maxNumChanToCheckFor = 9; + + // The 'standard' layout with the most channels defined is AudioChannelSet::create9point1point6(). + // This value should be updated if larger standard channel layouts are added in the future. + constexpr auto maxNumChanToCheckFor = 16; auto defaultInputs = processor.getChannelCountOfBus (true, 0); auto defaultOutputs = processor.getChannelCountOfBus (false, 0); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 78476e9..c0ee689 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -42,9 +42,8 @@ #include -#include -#include -#include +#include +#include #include "juce_AU_Shared.h" namespace juce @@ -507,40 +506,16 @@ static bool hasARAExtension ([[maybe_unused]] AudioUnit audioUnit) static void createAudioUnit (VersionedAudioComponent versionedComponent, AudioUnitCreationCallback callback) { - struct AUAsyncInitializationCallback - { - typedef void (^AUCompletionCallbackBlock)(AudioComponentInstance, OSStatus); - - explicit AUAsyncInitializationCallback (AudioUnitCreationCallback inOriginalCallback) - : originalCallback (std::move (inOriginalCallback)) - { - block = CreateObjCBlock (this, &AUAsyncInitializationCallback::completion); - } - - AUCompletionCallbackBlock getBlock() noexcept { return block; } - - void completion (AudioComponentInstance audioUnit, OSStatus err) - { - originalCallback (audioUnit, err); - - delete this; - } - - double sampleRate; - int framesPerBuffer; - AudioUnitCreationCallback originalCallback; - - ObjCBlock block; - }; - - auto callbackBlock = new AUAsyncInitializationCallback (std::move (callback)); - if (versionedComponent.isAUv3) { if (@available (macOS 10.11, *)) { - AudioComponentInstantiate (versionedComponent.audioComponent, kAudioComponentInstantiation_LoadOutOfProcess, - callbackBlock->getBlock()); + AudioComponentInstantiate (versionedComponent.audioComponent, + kAudioComponentInstantiation_LoadOutOfProcess, + ^(AudioComponentInstance audioUnit, OSStatus err) + { + callback (audioUnit, err); + }); return; } @@ -548,7 +523,7 @@ void completion (AudioComponentInstance audioUnit, OSStatus err) AudioComponentInstance audioUnit; auto err = AudioComponentInstanceNew (versionedComponent.audioComponent, &audioUnit); - callbackBlock->completion (err != noErr ? nullptr : audioUnit, err); + callback (err != noErr ? nullptr : audioUnit, err); } struct AudioComponentResult @@ -1272,7 +1247,7 @@ void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override AudioUnitGetProperty (audioUnit, kAudioUnitProperty_SampleRate, scope, static_cast (i), &sampleRate, &sampleRateSize); - if (sampleRate != sr) + if (! approximatelyEqual (sampleRate, sr)) { if (isAUv3) // setting kAudioUnitProperty_SampleRate fails on AUv3s { @@ -2616,9 +2591,6 @@ void updateBypass (bool processBlockBypassedCalled) { addAndMakeVisible (wrapper); - viewControllerCallback = - CreateObjCBlock (this, &AudioUnitPluginWindowCocoa::requestViewControllerCallback); - setOpaque (true); setVisible (true); setSize (100, 100); @@ -2674,7 +2646,6 @@ void childBoundsChanged (Component*) override AudioUnitFormatHelpers::AutoResizingNSViewComponent wrapper; typedef void (^ViewControllerCallbackBlock)(AUViewControllerBase *); - ObjCBlock viewControllerCallback; bool waitingForViewCallback = false; @@ -2728,12 +2699,9 @@ bool createView ([[maybe_unused]] bool createGenericViewIfNeeded) && dataSize == sizeof (ViewControllerCallbackBlock)) { waitingForViewCallback = true; - ViewControllerCallbackBlock callback; - callback = viewControllerCallback; - - ViewControllerCallbackBlock* info = &callback; + auto callback = ^(AUViewControllerBase* controller) { this->requestViewControllerCallback (controller); }; - if (noErr == AudioUnitSetProperty (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, info, dataSize)) + if (noErr == AudioUnitSetProperty (plugin.audioUnit, kAudioUnitProperty_RequestViewController, kAudioUnitScope_Global, 0, &callback, dataSize)) return true; waitingForViewCallback = false; @@ -2771,7 +2739,7 @@ void requestViewControllerCallback (AUViewControllerBase* controller) if (@available (macOS 10.11, *)) size = [controller preferredContentSize]; - if (size.width == 0 || size.height == 0) + if (approximatelyEqual (size.width, 0.0) || approximatelyEqual (size.height, 0.0)) size = controller.view.frame.size; return CGSizeMake (jmax ((CGFloat) 20.0f, size.width), diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index e68aabb..5c895a3 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -457,7 +457,7 @@ class LADSPAPluginInstance final : public AudioPluginInstance { const ScopedLock sl (pluginInstance.lock); - if (paramValue.unscaled != newValue) + if (! approximatelyEqual (paramValue.unscaled, newValue)) paramValue = ParameterValue (getNewParamScaled (interface->PortRangeHints [paramID], newValue), newValue); } } diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp index 7217b2c..1af344a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp @@ -28,7 +28,7 @@ #include "juce_LV2Common.h" #include "juce_LV2Resources.h" -#include +#include #include @@ -2579,15 +2579,8 @@ class UiInstanceArgs File bundlePath; URL pluginUri; - auto withBundlePath (File v) const noexcept { return with (&UiInstanceArgs::bundlePath, std::move (v)); } - auto withPluginUri (URL v) const noexcept { return with (&UiInstanceArgs::pluginUri, std::move (v)); } - -private: - template - UiInstanceArgs with (Member UiInstanceArgs::* member, Member value) const noexcept - { - return juce::lv2_host::with (*this, member, std::move (value)); - } + auto withBundlePath (File v) const noexcept { return withMember (*this, &UiInstanceArgs::bundlePath, std::move (v)); } + auto withPluginUri (URL v) const noexcept { return withMember (*this, &UiInstanceArgs::pluginUri, std::move (v)); } }; static File bundlePathFromUri (const char* uri) @@ -2614,7 +2607,7 @@ class UiInstance mLV2_UI__floatProtocol (map.map (LV2_UI__floatProtocol)), mLV2_ATOM__atomTransfer (map.map (LV2_ATOM__atomTransfer)), mLV2_ATOM__eventTransfer (map.map (LV2_ATOM__eventTransfer)), - instance (makeInstance (args.pluginUri, args.bundlePath, features)), + instance (makeInstance (args, features)), idleCallback (getExtensionData (world, LV2_UI__idleInterface)) { jassert (descriptor != nullptr); @@ -2682,14 +2675,14 @@ class UiInstance using Instance = std::unique_ptr; using Idle = int (*) (LV2UI_Handle); - Instance makeInstance (const URL& pluginUri, const File& bundlePath, const LV2_Feature* const* features) + Instance makeInstance (const UiInstanceArgs& args, const LV2_Feature* const* features) { if (descriptor->get() == nullptr) return { nullptr, [] (LV2UI_Handle) {} }; return Instance { descriptor->get()->instantiate (descriptor->get(), - pluginUri.toString (false).toRawUTF8(), - File::addTrailingSeparator (bundlePath.getFullPathName()).toRawUTF8(), + args.pluginUri.toString (true).toRawUTF8(), + File::addTrailingSeparator (args.bundlePath.getFullPathName()).toRawUTF8(), writeFunction, this, &widget, @@ -3287,7 +3280,7 @@ class ConfiguredEditorComponent : public Component, { if (auto* r = ref.getComponent()) { - if (std::exchange (r->nativeScaleFactor, platformScale) == platformScale) + if (approximatelyEqual (std::exchange (r->nativeScaleFactor, platformScale), platformScale)) return; r->nativeScaleFactor = platformScale; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp index 380dadf..0f0fc3b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LV2SupportLibs.cpp @@ -31,6 +31,8 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wc99-extensions", "-Wdeprecated-declarations", "-Wextra-semi", "-Wfloat-conversion", + "-Wfloat-equal", + "-Wformat-overflow", "-Wimplicit-float-conversion", "-Wimplicit-int-conversion", "-Wmicrosoft-include", @@ -114,3 +116,6 @@ using namespace Utils; #pragma pop_macro ("nil") } // extern "C" + +JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_END_IGNORE_WARNINGS_GCC_LIKE diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp index 4d66ad4..98950a7 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp @@ -29,7 +29,7 @@ namespace juce JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) -class LegacyAudioParameter : public AudioProcessorParameter +class LegacyAudioParameter : public HostedAudioProcessorParameter { public: LegacyAudioParameter (AudioProcessor& audioProcessorToUse, int audioParameterIndex) @@ -54,7 +54,7 @@ class LegacyAudioParameter : public AudioProcessorParameter bool isMetaParameter() const override { return processor->isMetaParameter (parameterIndex); } Category getCategory() const override { return processor->getParameterCategory (parameterIndex); } String getCurrentValueAsText() const override { return processor->getParameterText (parameterIndex); } - String getParamID() const { return processor->getParameterID (parameterIndex); } + String getParameterID() const override { return processor->getParameterID (parameterIndex); } //============================================================================== float getValueForText (const String&) const override @@ -101,12 +101,12 @@ class LegacyAudioParameter : public AudioProcessorParameter static String getParamID (const AudioProcessorParameter* param, bool forceLegacyParamIDs) noexcept { if (auto* legacy = dynamic_cast (param)) - return forceLegacyParamIDs ? String (legacy->parameterIndex) : legacy->getParamID(); + return forceLegacyParamIDs ? String (legacy->parameterIndex) : legacy->getParameterID(); - if (auto* paramWithID = dynamic_cast (param)) + if (auto* paramWithID = dynamic_cast (param)) { if (! forceLegacyParamIDs) - return paramWithID->paramID; + return paramWithID->getParameterID(); } if (param != nullptr) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Common.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Common.h index 2fc4be4..473e2a7 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -197,7 +197,7 @@ static inline Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg return arrangement; } -static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept +static std::optional getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept { switch (type) { @@ -241,6 +241,15 @@ static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, Audio case AudioChannelSet::ambisonicACN13: return Steinberg::Vst::kSpeakerACN13; case AudioChannelSet::ambisonicACN14: return Steinberg::Vst::kSpeakerACN14; case AudioChannelSet::ambisonicACN15: return Steinberg::Vst::kSpeakerACN15; + case AudioChannelSet::ambisonicACN16: return Steinberg::Vst::kSpeakerACN16; + case AudioChannelSet::ambisonicACN17: return Steinberg::Vst::kSpeakerACN17; + case AudioChannelSet::ambisonicACN18: return Steinberg::Vst::kSpeakerACN18; + case AudioChannelSet::ambisonicACN19: return Steinberg::Vst::kSpeakerACN19; + case AudioChannelSet::ambisonicACN20: return Steinberg::Vst::kSpeakerACN20; + case AudioChannelSet::ambisonicACN21: return Steinberg::Vst::kSpeakerACN21; + case AudioChannelSet::ambisonicACN22: return Steinberg::Vst::kSpeakerACN22; + case AudioChannelSet::ambisonicACN23: return Steinberg::Vst::kSpeakerACN23; + case AudioChannelSet::ambisonicACN24: return Steinberg::Vst::kSpeakerACN24; case AudioChannelSet::topSideLeft: return Steinberg::Vst::kSpeakerTsl; case AudioChannelSet::topSideRight: return Steinberg::Vst::kSpeakerTsr; case AudioChannelSet::bottomFrontLeft: return Steinberg::Vst::kSpeakerBfl; @@ -254,15 +263,6 @@ static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, Audio case AudioChannelSet::discreteChannel0: return Steinberg::Vst::kSpeakerM; - case AudioChannelSet::ambisonicACN16: - case AudioChannelSet::ambisonicACN17: - case AudioChannelSet::ambisonicACN18: - case AudioChannelSet::ambisonicACN19: - case AudioChannelSet::ambisonicACN20: - case AudioChannelSet::ambisonicACN21: - case AudioChannelSet::ambisonicACN22: - case AudioChannelSet::ambisonicACN23: - case AudioChannelSet::ambisonicACN24: case AudioChannelSet::ambisonicACN25: case AudioChannelSet::ambisonicACN26: case AudioChannelSet::ambisonicACN27: @@ -274,17 +274,44 @@ static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, Audio case AudioChannelSet::ambisonicACN33: case AudioChannelSet::ambisonicACN34: case AudioChannelSet::ambisonicACN35: + case AudioChannelSet::ambisonicACN36: + case AudioChannelSet::ambisonicACN37: + case AudioChannelSet::ambisonicACN38: + case AudioChannelSet::ambisonicACN39: + case AudioChannelSet::ambisonicACN40: + case AudioChannelSet::ambisonicACN41: + case AudioChannelSet::ambisonicACN42: + case AudioChannelSet::ambisonicACN43: + case AudioChannelSet::ambisonicACN44: + case AudioChannelSet::ambisonicACN45: + case AudioChannelSet::ambisonicACN46: + case AudioChannelSet::ambisonicACN47: + case AudioChannelSet::ambisonicACN48: + case AudioChannelSet::ambisonicACN49: + case AudioChannelSet::ambisonicACN50: + case AudioChannelSet::ambisonicACN51: + case AudioChannelSet::ambisonicACN52: + case AudioChannelSet::ambisonicACN53: + case AudioChannelSet::ambisonicACN54: + case AudioChannelSet::ambisonicACN55: + case AudioChannelSet::ambisonicACN56: + case AudioChannelSet::ambisonicACN57: + case AudioChannelSet::ambisonicACN58: + case AudioChannelSet::ambisonicACN59: + case AudioChannelSet::ambisonicACN60: + case AudioChannelSet::ambisonicACN61: + case AudioChannelSet::ambisonicACN62: + case AudioChannelSet::ambisonicACN63: case AudioChannelSet::wideLeft: case AudioChannelSet::wideRight: case AudioChannelSet::unknown: break; } - auto channelIndex = static_cast (type) - (static_cast (AudioChannelSet::discreteChannel0) + 6ull); - return (1ull << (channelIndex + 33ull /* last speaker in vst layout + 1 */)); + return {}; } -static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept +static std::optional getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept { switch (type) { @@ -324,6 +351,15 @@ static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArran case Steinberg::Vst::kSpeakerACN13: return AudioChannelSet::ambisonicACN13; case Steinberg::Vst::kSpeakerACN14: return AudioChannelSet::ambisonicACN14; case Steinberg::Vst::kSpeakerACN15: return AudioChannelSet::ambisonicACN15; + case Steinberg::Vst::kSpeakerACN16: return AudioChannelSet::ambisonicACN16; + case Steinberg::Vst::kSpeakerACN17: return AudioChannelSet::ambisonicACN17; + case Steinberg::Vst::kSpeakerACN18: return AudioChannelSet::ambisonicACN18; + case Steinberg::Vst::kSpeakerACN19: return AudioChannelSet::ambisonicACN19; + case Steinberg::Vst::kSpeakerACN20: return AudioChannelSet::ambisonicACN20; + case Steinberg::Vst::kSpeakerACN21: return AudioChannelSet::ambisonicACN21; + case Steinberg::Vst::kSpeakerACN22: return AudioChannelSet::ambisonicACN22; + case Steinberg::Vst::kSpeakerACN23: return AudioChannelSet::ambisonicACN23; + case Steinberg::Vst::kSpeakerACN24: return AudioChannelSet::ambisonicACN24; case Steinberg::Vst::kSpeakerTsl: return AudioChannelSet::topSideLeft; case Steinberg::Vst::kSpeakerTsr: return AudioChannelSet::topSideRight; case Steinberg::Vst::kSpeakerLcs: return AudioChannelSet::leftSurroundRear; @@ -340,12 +376,7 @@ static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArran case Steinberg::Vst::kSpeakerBrr: return AudioChannelSet::bottomRearRight; } - auto channelType = BigInteger (static_cast (type)).findNextSetBit (0); - - // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (channelType >= 33); - - return static_cast (static_cast (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33)); + return {}; } namespace detail @@ -394,6 +425,8 @@ namespace detail { k70_6, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, // The VST3 layout uses 'left/right' and 'left-of-center/right-of-center', but the JUCE layout uses 'left/right' and 'wide-left/wide-right'. + { k91_4, { X::wideLeft, X::wideRight, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, + { k90_4, { X::wideLeft, X::wideRight, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } }, { k91_6, { X::wideLeft, X::wideRight, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, { k90_6, { X::wideLeft, X::wideRight, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } }, }; @@ -406,7 +439,7 @@ namespace detail inline bool isLayoutTableValid() { for (const auto& item : detail::layoutTable) - if ((size_t) countNumberOfBits (item.arrangement) != item.channelOrder.size()) + if ((size_t) countNumberOfBits ((uint64) item.arrangement) != item.channelOrder.size()) return false; std::set arrangements; @@ -423,7 +456,7 @@ inline bool isLayoutTableValid() }); } -static Array getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) +static std::optional> getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) { using namespace Steinberg::Vst; using namespace Steinberg::Vst::SpeakerArr; @@ -445,12 +478,34 @@ static Array getSpeakerOrder (Steinberg::Vst::Spea result.ensureStorageAllocated (channels); for (auto i = 0; i < channels; ++i) - result.add (getChannelType (arr, getSpeaker (arr, i))); + if (const auto t = getChannelType (arr, getSpeaker (arr, i))) + result.add (*t); + + if (getChannelCount (arr) == result.size()) + return result; - return result; + return {}; } -static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept +struct Ambisonics +{ + struct Mapping + { + Steinberg::Vst::SpeakerArrangement arrangement; + AudioChannelSet channelSet; + }; + + inline static const Mapping mappings[] + { + { Steinberg::Vst::SpeakerArr::kAmbi5thOrderACN, AudioChannelSet::ambisonic (5) }, + { Steinberg::Vst::SpeakerArr::kAmbi6thOrderACN, AudioChannelSet::ambisonic (6) }, + { Steinberg::Vst::SpeakerArr::kAmbi7thOrderACN, AudioChannelSet::ambisonic (7) }, + }; + + Ambisonics() = delete; +}; + +static std::optional getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept { using namespace Steinberg::Vst::SpeakerArr; @@ -458,6 +513,10 @@ static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const Audio std::call_once (detail::layoutTableCheckedFlag, [] { jassert (isLayoutTableValid()); }); #endif + for (const auto& mapping : Ambisonics::mappings) + if (channels == mapping.channelSet) + return mapping.arrangement; + const auto channelSetMatches = [&channels] (const auto& layoutPair) { return AudioChannelSet::channelSetWithChannels (layoutPair.channelOrder) == channels; @@ -470,21 +529,28 @@ static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const Audio Steinberg::Vst::SpeakerArrangement result = 0; for (const auto& type : channels.getChannelTypes()) - result |= getSpeakerType (channels, type); + if (const auto t = getSpeakerType (channels, type)) + result |= *t; - return result; + if (getChannelCount (result) == channels.size()) + return result; + + return {}; } -inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept +inline std::optional getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept { using namespace Steinberg::Vst::SpeakerArr; - const auto result = AudioChannelSet::channelSetWithChannels (getSpeakerOrder (arr)); + for (const auto& mapping : Ambisonics::mappings) + if (arr == mapping.arrangement) + return mapping.channelSet; - // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (result.size() == getChannelCount (arr)); + if (const auto order = getSpeakerOrder (arr)) + return AudioChannelSet::channelSetWithChannels (*order); - return result; + // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum + return {}; } //============================================================================== @@ -522,7 +588,21 @@ struct ChannelMapping */ static std::vector makeChannelIndices (const AudioChannelSet& juceArrangement) { - const auto order = getSpeakerOrder (getVst3SpeakerArrangement (juceArrangement)); + const auto order = [&] + { + const auto fallback = juceArrangement.getChannelTypes(); + const auto vst3Arrangement = getVst3SpeakerArrangement (juceArrangement); + + if (! vst3Arrangement.has_value()) + return fallback; + + const auto reordered = getSpeakerOrder (*vst3Arrangement); + + if (! reordered.has_value() || AudioChannelSet::channelSetWithChannels (*reordered) != juceArrangement) + return fallback; + + return *reordered; + }(); std::vector result; @@ -613,7 +693,9 @@ static int countValidBuses (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) })); } -template +enum class Direction { input, output }; + +template static bool validateLayouts (Iterator first, Iterator last, const std::vector& map) { if ((size_t) std::distance (first, last) > map.size()) @@ -623,12 +705,24 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector{}, *it); - const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); + auto& bus = *it; + auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); + const auto expectedJuceChannels = (int) mapIterator->size(); + const auto actualVstChannels = (int) bus.numChannels; + const auto limit = jmin (expectedJuceChannels, actualVstChannels); + const auto anyChannelIsNull = std::any_of (busPtr, busPtr + limit, [] (auto* ptr) { return ptr == nullptr; }); + constexpr auto isInput = direction == Direction::input; + + const auto channelCountIsUsable = isInput ? expectedJuceChannels <= actualVstChannels + : actualVstChannels <= expectedJuceChannels; // Null channels are allowed if the bus is inactive - if (mapIterator->isHostActive() && (anyChannelIsNull || (int) mapIterator->size() != it->numChannels)) + if (mapIterator->isHostActive() && (anyChannelIsNull || ! channelCountIsUsable)) return false; + + // If this is hit, the destination bus has fewer channels than the source bus. + // As a result, some channels will 'go missing', and channel layouts may be invalid. + jassert (actualVstChannels == expectedJuceChannels); } // If the host didn't provide the full complement of buses, it must be because the other @@ -669,10 +763,10 @@ class ClientBufferMapperData // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here const auto vstInputs = countValidBuses (data.inputs, data.numInputs); - if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) + if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) return getBlankBuffer (usedChannels, (int) data.numSamples); - setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); + setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); setUpOutputChannels (scratchBuffer, outputMap, channels); const auto channelPtr = channels.empty() ? scratchBuffer.getArrayOfWritePointers() @@ -690,7 +784,7 @@ class ClientBufferMapperData { for (size_t busIndex = 0; busIndex < map.size(); ++busIndex) { - const auto mapping = map[busIndex]; + const auto& mapping = map[busIndex]; if (! mapping.isClientActive()) continue; @@ -702,7 +796,12 @@ class ClientBufferMapperData if (mapping.isHostActive() && busIndex < vstInputs) { - auto** busPtr = getAudioBusPointer (detail::Tag{}, data.inputs[busIndex]); + auto& bus = data.inputs[busIndex]; + + // Every JUCE channel must have a VST3 channel counterpart + jassert (mapping.size() <= static_cast (bus.numChannels)); + + auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) { @@ -921,7 +1020,7 @@ class ClientRemappedBuffer // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here const auto vstOutputs = (size_t) countValidBuses (data.outputs, data.numOutputs); - if (validateLayouts (data.outputs, data.outputs + vstOutputs, *outputMap)) + if (validateLayouts (data.outputs, data.outputs + vstOutputs, *outputMap)) copyToHostOutputBuses (vstOutputs); else clearHostOutputBuses (vstOutputs); @@ -940,9 +1039,12 @@ class ClientRemappedBuffer { auto& bus = data.outputs[i]; + // Every VST3 channel must have a JUCE channel counterpart + jassert (static_cast (bus.numChannels) <= mapping.size()); + if (mapping.isClientActive()) { - for (size_t j = 0; j < mapping.size(); ++j) + for (size_t j = 0; j < static_cast (bus.numChannels); ++j) { auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; const auto juceChannel = juceBusOffset + (size_t) mapping.getJuceChannelForVst3Channel ((int) j); @@ -951,7 +1053,7 @@ class ClientRemappedBuffer } else { - for (size_t j = 0; j < mapping.size(); ++j) + for (size_t j = 0; j < static_cast (bus.numChannels); ++j) { auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; FloatVectorOperations::clear (hostChannel, (size_t) data.numSamples); diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Headers.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Headers.h index 2277965..b4d26e3 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Headers.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3Headers.h @@ -32,44 +32,49 @@ // Wow, those Steinberg guys really don't worry too much about compiler warnings. JUCE_BEGIN_IGNORE_WARNINGS_LEVEL_MSVC (0, 4505 4702 6011 6031 6221 6386 6387 6330 6001 28199) -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", - "-Wnon-virtual-dtor", - "-Wdeprecated", - "-Wreorder", - "-Wunsequenced", - "-Wint-to-pointer-cast", - "-Wunused-parameter", +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-W#warnings", + "-Wcast-align", + "-Wclass-memaccess", + "-Wcomma", "-Wconversion", - "-Woverloaded-virtual", - "-Wshadow", - "-Wdeprecated-register", - "-Wunused-function", - "-Wsign-conversion", - "-Wsign-compare", + "-Wcpp", "-Wdelete-non-virtual-dtor", + "-Wdeprecated", + "-Wdeprecated-copy-dtor", "-Wdeprecated-declarations", + "-Wdeprecated-register", + "-Wextra", "-Wextra-semi", - "-Wmissing-braces", - "-Wswitch-default", - "-Wshadow-field", - "-Wpragma-pack", - "-Wcomma", - "-Wzero-as-null-pointer-constant", - "-Winconsistent-missing-destructor-override", - "-Wcast-align", + "-Wfloat-equal", + "-Wformat", + "-Wformat-truncation=", + "-Wformat=", "-Wignored-qualifiers", + "-Winconsistent-missing-destructor-override", + "-Wint-to-pointer-cast", + "-Wlogical-op-parentheses", + "-Wmaybe-uninitialized", + "-Wmissing-braces", "-Wmissing-field-initializers", - "-Wformat=", - "-Wformat", - "-Wpedantic", - "-Wextra", - "-Wclass-memaccess", "-Wmissing-prototypes", + "-Wnon-virtual-dtor", + "-Woverloaded-virtual", + "-Wparentheses", + "-Wpedantic", + "-Wpragma-pack", + "-Wredundant-decls", + "-Wreorder", + "-Wshadow", + "-Wshadow-field", + "-Wsign-compare", + "-Wsign-conversion", + "-Wswitch-default", "-Wtype-limits", - "-Wcpp", - "-W#warnings", - "-Wmaybe-uninitialized", - "-Wunused-but-set-variable") + "-Wunsequenced", + "-Wunused-but-set-variable", + "-Wunused-function", + "-Wunused-parameter", + "-Wzero-as-null-pointer-constant") #undef DEVELOPMENT #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values @@ -87,6 +92,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", #include #include #include + #include #include #include #include @@ -107,6 +113,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", #include #include #include + #include #include #include @@ -115,6 +122,10 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", // needed for VST_VERSION #include + #ifndef NOMINMAX + #define NOMINMAX // Some of the steinberg sources don't set this before including windows.h + #endif + #include #include #include @@ -137,6 +148,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", #include #endif +#pragma push_macro ("True") +#undef True +#pragma push_macro ("False") +#undef False + #include #include #include @@ -144,18 +160,24 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-copy-dtor", #include #include #include - #include #include + #include #include #include - #include + #include + #include + #include + #include #include - #include #include #include + #include + #include #include #include - #include + +#pragma pop_macro ("True") +#pragma pop_macro ("False") #if VST_VERSION >= 0x03060c // 3.6.12 #include diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 053a38a..5e3d1e4 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -94,7 +94,6 @@ static int warnOnFailureIfImplemented (int result) noexcept #define warnOnFailureIfImplemented(x) x #endif -enum class Direction { input, output }; enum class MediaKind { audio, event }; static Vst::MediaType toVstType (MediaKind x) { return x == MediaKind::audio ? Vst::kAudio : Vst::kEvent; } @@ -189,6 +188,66 @@ static void fillDescriptionWith (PluginDescription& description, ObjectType& obj description.manufacturerName = toString (object.vendor).trim(); } +static std::vector createPluginDescriptions (const File& pluginFile, const Steinberg::ModuleInfo& info) +{ + std::vector result; + + const auto araMainFactoryClassNames = [&] + { + std::unordered_set factories; + + #if JUCE_PLUGINHOST_ARA && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX) + for (const auto& c : info.classes) + if (c.category == kARAMainFactoryClass) + factories.insert (CharPointer_UTF8 (c.name.c_str())); + #endif + + return factories; + }(); + + for (const auto& c : info.classes) + { + if (c.category != kVstAudioEffectClass) + continue; + + PluginDescription description; + + description.fileOrIdentifier = pluginFile.getFullPathName(); + description.lastFileModTime = pluginFile.getLastModificationTime(); + description.lastInfoUpdateTime = Time::getCurrentTime(); + description.manufacturerName = CharPointer_UTF8 (info.factoryInfo.vendor.c_str()); + description.name = CharPointer_UTF8 (info.name.c_str()); + description.descriptiveName = CharPointer_UTF8 (info.name.c_str()); + description.pluginFormatName = "VST3"; + description.numInputChannels = 0; + description.numOutputChannels = 0; + description.hasARAExtension = araMainFactoryClassNames.find (description.name) != araMainFactoryClassNames.end(); + + const auto uid = VST3::UID::fromString (c.cid); + + if (! uid) + continue; + + description.deprecatedUid = getHashForRange (uid->data()); + description.uniqueId = getHashForRange (getNormalisedTUID (uid->data())); + + StringArray categories; + + for (const auto& category : c.subCategories) + categories.add (CharPointer_UTF8 (category.c_str())); + + description.category = categories.joinIntoString ("|"); + + description.isInstrument = std::any_of (c.subCategories.begin(), + c.subCategories.end(), + [] (const auto& subcategory) { return subcategory == "Instrument"; }); + + result.push_back (description); + } + + return result; +} + static void createPluginDescription (PluginDescription& description, const File& pluginFile, const String& company, const String& name, const PClassInfo& info, PClassInfo2* info2, PClassInfoW* infoW, @@ -377,7 +436,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 //============================================================================== tresult PLUGIN_API requestOpenEditor ([[maybe_unused]] FIDString name) override { - jassertfalse; + // This request cannot currently be surfaced in the JUCE public API return kResultFalse; } @@ -814,26 +873,49 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 }; //============================================================================== -struct DescriptionFactory +struct DescriptionLister { - DescriptionFactory (VST3HostContext* host, IPluginFactory* pluginFactory) - : vst3HostContext (host), factory (pluginFactory) + static std::vector tryLoadFast (const File& file, const File& moduleinfo) { - jassert (pluginFactory != nullptr); + if (! moduleinfo.existsAsFile()) + return {}; + + MemoryBlock mb; + + if (! moduleinfo.loadFileAsData (mb)) + return {}; + + const std::string_view blockAsStringView (static_cast (mb.getData()), mb.getSize()); + const auto parsed = Steinberg::ModuleInfoLib::parseJson (blockAsStringView, nullptr); + + if (! parsed) + return {}; + + return createPluginDescriptions (file, *parsed); } - virtual ~DescriptionFactory() {} + static std::vector findDescriptionsFast (const File& file) + { + const auto moduleinfoNewLocation = file.getChildFile ("Contents").getChildFile ("Resources").getChildFile ("moduleinfo.json"); + + if (const auto loaded = tryLoadFast (file, moduleinfoNewLocation); ! loaded.empty()) + return loaded; + + return tryLoadFast (file, file.getChildFile ("Contents").getChildFile ("moduleinfo.json")); + } - Result findDescriptionsAndPerform (const File& file) + static std::vector findDescriptionsSlow (VST3HostContext& host, + IPluginFactory& factory, + const File& file) { + std::vector result; + StringArray foundNames; PFactoryInfo factoryInfo; - factory->getFactoryInfo (&factoryInfo); + factory.getFactoryInfo (&factoryInfo); auto companyName = toString (factoryInfo.vendor).trim(); - Result result (Result::ok()); - - auto numClasses = factory->countClasses(); + auto numClasses = factory.countClasses(); // Every ARA::IMainFactory must have a matching Steinberg::IComponent. // The match is determined by the two classes having the same name. @@ -843,7 +925,7 @@ struct DescriptionFactory for (Steinberg::int32 i = 0; i < numClasses; ++i) { PClassInfo info; - factory->getClassInfo (i, &info); + factory.getClassInfo (i, &info); if (std::strcmp (info.category, kARAMainFactoryClass) == 0) araMainFactoryClassNames.insert (info.name); } @@ -852,7 +934,7 @@ struct DescriptionFactory for (Steinberg::int32 i = 0; i < numClasses; ++i) { PClassInfo info; - factory->getClassInfo (i, &info); + factory.getClassInfo (i, &info); if (std::strcmp (info.category, kVstAudioEffectClass) != 0) continue; @@ -869,13 +951,13 @@ struct DescriptionFactory VSTComSmartPtr pf2; VSTComSmartPtr pf3; - if (pf2.loadFrom (factory)) + if (pf2.loadFrom (&factory)) { info2.reset (new PClassInfo2()); pf2->getClassInfo2 (i, info2.get()); } - if (pf3.loadFrom (factory)) + if (pf3.loadFrom (&factory)) { infoW.reset (new PClassInfoW()); pf3->getClassInfoUnicode (i, infoW.get()); @@ -889,9 +971,9 @@ struct DescriptionFactory { VSTComSmartPtr component; - if (component.loadFrom (factory, info.cid)) + if (component.loadFrom (&factory, info.cid)) { - if (component->initialize (vst3HostContext->getFUnknown()) == kResultOk) + if (component->initialize (host.getFUnknown()) == kResultOk) { auto numInputs = getNumSingleDirectionChannelsFor (component, Direction::input); auto numOutputs = getNumSingleDirectionChannelsFor (component, Direction::output); @@ -916,41 +998,11 @@ struct DescriptionFactory desc.hasARAExtension = true; if (desc.uniqueId != 0) - result = performOnDescription (desc); - - if (result.failed()) - break; + result.push_back (desc); } return result; } - - virtual Result performOnDescription (PluginDescription&) = 0; - -private: - VSTComSmartPtr vst3HostContext; - VSTComSmartPtr factory; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) -}; - -struct DescriptionLister : public DescriptionFactory -{ - DescriptionLister (VST3HostContext* host, IPluginFactory* pluginFactory) - : DescriptionFactory (host, pluginFactory) - { - } - - Result performOnDescription (PluginDescription& desc) - { - list.add (new PluginDescription (desc)); - return Result::ok(); - } - - OwnedArray list; - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionLister) }; //============================================================================== @@ -1357,9 +1409,11 @@ struct VST3ModuleHandle : public ReferenceCountedObject if (std::strcmp (info.category, kVstAudioEffectClass) != 0) continue; + const auto uniqueId = getHashForRange (getNormalisedTUID (info.cid)); + const auto deprecatedUid = getHashForRange (info.cid); + if (toString (info.name).trim() == description.name - && (getHashForRange (getNormalisedTUID (info.cid)) == description.uniqueId - || getHashForRange (info.cid) == description.deprecatedUid)) + && (uniqueId == description.uniqueId || deprecatedUid == description.deprecatedUid)) { name = description.name; return true; @@ -1442,9 +1496,10 @@ static std::shared_ptr getARAFactory (VST3ModuleHandle& m } //============================================================================== -struct VST3PluginWindow : public AudioProcessorEditor, - private ComponentMovementWatcher, - private IPlugFrame +struct VST3PluginWindow final : public AudioProcessorEditor, + private ComponentMovementWatcher, + private ComponentBoundsConstrainer, + private IPlugFrame { VST3PluginWindow (AudioPluginInstance* owner, IPlugView* pluginView) : AudioProcessorEditor (owner), @@ -1457,12 +1512,15 @@ struct VST3PluginWindow : public AudioProcessorEditor, setSize (10, 10); setOpaque (true); setVisible (true); + setConstrainer (this); warnOnFailure (view->setFrame (this)); view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); setContentScaleFactor(); resizeToFit(); + + setResizable (view->canResize() == kResultTrue, false); } ~VST3PluginWindow() override @@ -1528,6 +1586,19 @@ struct VST3PluginWindow : public AudioProcessorEditor, bool keyPressed (const KeyPress& /*key*/) override { return true; } private: + void checkBounds (Rectangle& bounds, + const Rectangle&, + const Rectangle&, + bool, + bool, + bool, + bool) override + { + auto rect = componentToVST3Rect (bounds); + view->checkSizeConstraint (&rect); + bounds = vst3ToComponentRect (rect); + } + //============================================================================== void componentPeerChanged() override {} @@ -2173,7 +2244,7 @@ class ParameterChanges : public Vst::IParameterChanges for (const auto* item : queues) { auto* ptr = item->ptr.get(); - callback (ptr->getParameterIndex(), ptr->get()); + callback (ptr->getParameterIndex(), ptr->getParameterId(), ptr->get()); } } @@ -2459,34 +2530,33 @@ class VST3PluginInstance final : public AudioPluginInstance return module != nullptr ? module->getName() : String(); } - void repopulateArrangements (Array& inputArrangements, Array& outputArrangements) const + std::vector getActualArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector result; - auto numInputAudioBuses = getBusCount (true); - auto numOutputAudioBuses = getBusCount (false); + const auto numBuses = getBusCount (isInput); - for (int i = 0; i < numInputAudioBuses; ++i) - inputArrangements.add (getArrangementForBus (processor, true, i)); + for (auto i = 0; i < numBuses; ++i) + result.push_back (getArrangementForBus (processor, isInput, i)); - for (int i = 0; i < numOutputAudioBuses; ++i) - outputArrangements.add (getArrangementForBus (processor, false, i)); + return result; } - void processorLayoutsToArrangements (Array& inputArrangements, Array& outputArrangements) + std::optional> busLayoutsToArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector result; - auto numInputBuses = getBusCount (true); - auto numOutputBuses = getBusCount (false); + const auto numBuses = getBusCount (isInput); - for (int i = 0; i < numInputBuses; ++i) - inputArrangements.add (getVst3SpeakerArrangement (getBus (true, i)->getLastEnabledLayout())); + for (auto i = 0; i < numBuses; ++i) + { + if (const auto arr = getVst3SpeakerArrangement (getBus (isInput, i)->getLastEnabledLayout())) + result.push_back (*arr); + else + return {}; + } - for (int i = 0; i < numOutputBuses; ++i) - outputArrangements.add (getVst3SpeakerArrangement (getBus (false, i)->getLastEnabledLayout())); + return result; } void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override @@ -2500,7 +2570,7 @@ class VST3PluginInstance final : public AudioPluginInstance // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins: if (isActive - && getSampleRate() == newSampleRate + && approximatelyEqual (getSampleRate(), newSampleRate) && getBlockSize() == estimatedSamplesPerBlock) return; @@ -2521,21 +2591,21 @@ class VST3PluginInstance final : public AudioPluginInstance holder->initialise(); - Array inputArrangements, outputArrangements; - processorLayoutsToArrangements (inputArrangements, outputArrangements); + auto inArrangements = busLayoutsToArrangements (true) .value_or (std::vector{}); + auto outArrangements = busLayoutsToArrangements (false).value_or (std::vector{}); // Some plug-ins will crash if you pass a nullptr to setBusArrangements! SpeakerArrangement nullArrangement = {}; - auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer(); - auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer(); + auto* inData = inArrangements .empty() ? &nullArrangement : inArrangements .data(); + auto* outData = outArrangements.empty() ? &nullArrangement : outArrangements.data(); - warnOnFailure (processor->setBusArrangements (inputArrangementData, inputArrangements.size(), - outputArrangementData, outputArrangements.size())); + warnOnFailure (processor->setBusArrangements (inData, static_cast (inArrangements .size()), + outData, static_cast (outArrangements.size()))); - Array actualInArr, actualOutArr; - repopulateArrangements (actualInArr, actualOutArr); + const auto inArrActual = getActualArrangements (true); + const auto outArrActual = getActualArrangements (false); - jassert (actualInArr == inputArrangements && actualOutArr == outputArrangements); + jassert (inArrActual == inArrangements && outArrActual == outArrangements); // Needed for having the same sample rate in processBlock(); some plugins need this! setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock); @@ -2683,9 +2753,12 @@ class VST3PluginInstance final : public AudioPluginInstance processor->process (data); - outputParameterChanges->forEach ([&] (Steinberg::int32 index, float value) + outputParameterChanges->forEach ([&] (Steinberg::int32 index, Vst::ParamID id, float value) { - parameterDispatcher.push (index, value); + cachedParamValues.setWithoutNotifying (index, value); + + if (auto* param = getParameterForID (id)) + param->setValueWithoutUpdatingProcessor (value); }); midiMessages.clear(); @@ -2707,9 +2780,8 @@ class VST3PluginInstance final : public AudioPluginInstance // not much we can do to check the layout while the audio processor is running // Let's at least check if it is a VST3 compatible layout - for (int dir = 0; dir < 2; ++dir) + for (const auto isInput : { true, false }) { - bool isInput = (dir == 0); auto n = getBusCount (isInput); for (int i = 0; i < n; ++i) @@ -2722,9 +2794,8 @@ class VST3PluginInstance final : public AudioPluginInstance bool syncBusLayouts (const BusesLayout& layouts) const { - for (int dir = 0; dir < 2; ++dir) + for (const auto isInput : { true, false }) { - bool isInput = (dir == 0); auto n = getBusCount (isInput); const Vst::BusDirection vstDir = (isInput ? Vst::kInput : Vst::kOutput); @@ -2737,34 +2808,49 @@ class VST3PluginInstance final : public AudioPluginInstance } } - Array inputArrangements, outputArrangements; - - for (int i = 0; i < layouts.inputBuses.size(); ++i) + const auto getPotentialArrangements = [&] (bool isInput) -> std::optional> { - const auto& requested = layouts.getChannelSet (true, i); - inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)); - } + std::vector result; + + for (int i = 0; i < layouts.getBuses (isInput).size(); ++i) + { + const auto& requested = layouts.getChannelSet (isInput, i); + + if (const auto arr = getVst3SpeakerArrangement (requested.isDisabled() ? getBus (isInput, i)->getLastEnabledLayout() : requested)) + result.push_back (*arr); + else + return {}; + } - for (int i = 0; i < layouts.outputBuses.size(); ++i) + return result; + }; + + auto inArrangements = getPotentialArrangements (true); + auto outArrangements = getPotentialArrangements (false); + + if (! inArrangements.has_value() || ! outArrangements.has_value()) { - const auto& requested = layouts.getChannelSet (false, i); - outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested)); + // This bus layout can't be represented as a VST3 speaker arrangement + return false; } + auto& inputArrangements = *inArrangements; + auto& outputArrangements = *outArrangements; + // Some plug-ins will crash if you pass a nullptr to setBusArrangements! Vst::SpeakerArrangement nullArrangement = {}; - auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer(); - auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer(); + auto* inputArrangementData = inputArrangements .empty() ? &nullArrangement : inputArrangements .data(); + auto* outputArrangementData = outputArrangements.empty() ? &nullArrangement : outputArrangements.data(); - if (processor->setBusArrangements (inputArrangementData, inputArrangements.size(), - outputArrangementData, outputArrangements.size()) != kResultTrue) + if (processor->setBusArrangements (inputArrangementData, static_cast (inputArrangements .size()), + outputArrangementData, static_cast (outputArrangements.size())) != kResultTrue) return false; // check if the layout matches the request - Array actualIn, actualOut; - repopulateArrangements (actualIn, actualOut); + const auto inArrActual = getActualArrangements (true); + const auto outArrActual = getActualArrangements (false); - return (actualIn == inputArrangements && actualOut == outputArrangements); + return (inArrActual == inputArrangements && outArrActual == outputArrangements); } bool canApplyBusesLayout (const BusesLayout& layouts) const override @@ -3007,7 +3093,7 @@ class VST3PluginInstance final : public AudioPluginInstance { if (componentStream != nullptr) { - int64 result; + Steinberg::int64 result; componentStream->seek (0, IBStream::kIBSeekSet, &result); setComponentStateAndResetParameters (*componentStream); } @@ -3368,9 +3454,8 @@ class VST3PluginInstance final : public AudioPluginInstance VSTComSmartPtr processor; processor.loadFrom (component.get()); - for (int dirIdx = 0; dirIdx < 2; ++dirIdx) + for (const auto isInput : { true, false }) { - const bool isInput = (dirIdx == 0); const Vst::BusDirection dir = (isInput ? Vst::kInput : Vst::kOutput); const int numBuses = component->getBusCount (Vst::kAudio, dir); @@ -3386,7 +3471,8 @@ class VST3PluginInstance final : public AudioPluginInstance Vst::SpeakerArrangement arr; if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk) - layout = getChannelSetForSpeakerArrangement (arr); + if (const auto set = getChannelSetForSpeakerArrangement (arr)) + layout = *set; busProperties.addBus (isInput, toString (info.name), layout, (info.flags & Vst::BusInfo::kDefaultActive) != 0); @@ -3418,7 +3504,7 @@ class VST3PluginInstance final : public AudioPluginInstance // call was processBlockBypassed, otherwise do nothing if (processBlockBypassedCalled) { - if (bypassParam != nullptr && (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass)) + if (bypassParam != nullptr && (approximatelyEqual (bypassParam->getValue(), 0.0f) || ! lastProcessBlockCallWasBypass)) bypassParam->setValue (1.0f); } else @@ -3604,7 +3690,7 @@ tresult VST3HostContext::performEdit (Vst::ParamID paramID, Vst::ParamValue valu param->setValueNotifyingHost ((float) valueNormalised); // did the plug-in already update the parameter internally - if (plugin->editController->getParamNormalized (paramID) != (float) valueNormalised) + if (! approximatelyEqual (plugin->editController->getParamNormalized (paramID), valueNormalised)) return plugin->editController->setParamNormalized (paramID, valueNormalised); return kResultTrue; @@ -3777,7 +3863,18 @@ bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, cons void VST3PluginFormat::findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) { - if (fileMightContainThisPluginType (fileOrIdentifier)) + if (! fileMightContainThisPluginType (fileOrIdentifier)) + return; + + if (const auto fast = DescriptionLister::findDescriptionsFast (File (fileOrIdentifier)); ! fast.empty()) + { + for (const auto& d : fast) + results.add (new PluginDescription (d)); + + return; + } + + for (const auto& file : getLibraryPaths (fileOrIdentifier)) { /** Since there is no apparent indication if a VST3 plugin is a shell or not, @@ -3785,21 +3882,16 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray& resul for every housed plugin. */ - VSTComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) - .getPluginFactory()); + VSTComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file) + .getPluginFactory()); - if (pluginFactory != nullptr) - { - VSTComSmartPtr host (new VST3HostContext()); - DescriptionLister lister (host, pluginFactory); - lister.findDescriptionsAndPerform (File (fileOrIdentifier)); + if (pluginFactory == nullptr) + continue; - results.addCopiesOf (lister.list); - } - else - { - jassertfalse; - } + VSTComSmartPtr host (new VST3HostContext()); + + for (const auto& d : DescriptionLister::findDescriptionsSlow (*host, *pluginFactory, File (file))) + results.add (new PluginDescription (d)); } } @@ -3820,13 +3912,12 @@ void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& descripti } static std::unique_ptr createVST3Instance (VST3PluginFormat& format, - const PluginDescription& description) + const PluginDescription& description, + const File& file) { if (! format.fileMightContainThisPluginType (description.fileOrIdentifier)) return nullptr; - const File file { description.fileOrIdentifier }; - struct ScopedWorkingDirectory { ~ScopedWorkingDirectory() { previousWorkingDirectory.setAsCurrentWorkingDirectory(); } @@ -3854,15 +3945,33 @@ static std::unique_ptr createVST3Instance (VST3PluginFormat return instance; } +StringArray VST3PluginFormat::getLibraryPaths (const String& fileOrIdentifier) +{ + #if JUCE_WINDOWS + if (! File (fileOrIdentifier).existsAsFile()) + { + StringArray files; + recursiveFileSearch (files, fileOrIdentifier, true); + return files; + } + #endif + + return { fileOrIdentifier }; +} + void VST3PluginFormat::createPluginInstance (const PluginDescription& description, double, int, PluginCreationCallback callback) { - auto result = createVST3Instance (*this, description); - - const auto errorMsg = result == nullptr ? TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3") - : String(); + for (const auto& file : getLibraryPaths (description.fileOrIdentifier)) + { + if (auto result = createVST3Instance (*this, description, file)) + { + callback (std::move (result), {}); + return; + } + } - callback (std::move (result), errorMsg); + callback (nullptr, TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3")); } bool VST3PluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const @@ -3874,12 +3983,7 @@ bool VST3PluginFormat::fileMightContainThisPluginType (const String& fileOrIdent { auto f = File::createFileWithoutCheckingPath (fileOrIdentifier); - return f.hasFileExtension (".vst3") - #if JUCE_MAC || JUCE_LINUX || JUCE_BSD - && f.exists(); - #else - && f.existsAsFile(); - #endif + return f.hasFileExtension (".vst3") && f.exists(); } String VST3PluginFormat::getNameOfPluginFromIdentifier (const String& fileOrIdentifier) diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h index dbc58c3..ab99a3f 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.h @@ -76,6 +76,7 @@ class JUCE_API VST3PluginFormat : public AudioPluginFormat int initialBufferSize, PluginCreationCallback) override; bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const override; void recursiveFileSearch (StringArray&, const File&, bool recursive); + StringArray getLibraryPaths (const String&); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) }; diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp index 9650b8c..f70d855 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp @@ -550,6 +550,23 @@ class VST3PluginFormatTests : public UnitTest expect (clientBuffers[3].channelBuffers64[0] == nullptr); } } + + beginTest ("Speaker layout conversions"); + { + using namespace Steinberg::Vst::SpeakerArr; + + for (const auto& [channelSet, arr] : { std::tuple (AudioChannelSet::ambisonic (1), kAmbi1stOrderACN), + std::tuple (AudioChannelSet::ambisonic (2), kAmbi2cdOrderACN), + std::tuple (AudioChannelSet::ambisonic (3), kAmbi3rdOrderACN), + std::tuple (AudioChannelSet::ambisonic (4), kAmbi4thOrderACN), + std::tuple (AudioChannelSet::ambisonic (5), kAmbi5thOrderACN), + std::tuple (AudioChannelSet::ambisonic (6), kAmbi6thOrderACN), + std::tuple (AudioChannelSet::ambisonic (7), kAmbi7thOrderACN), }) + { + expect (getVst3SpeakerArrangement (channelSet) == arr); + expect (channelSet == getChannelSetForSpeakerArrangement (arr)); + } + } } private: @@ -584,7 +601,7 @@ class VST3PluginFormatTests : public UnitTest bool allMatch (int channel, float value) const { const auto& buf = buffers[(size_t) channel]; - return std::all_of (buf.begin(), buf.end(), [&] (auto x) { return x == value; }); + return std::all_of (buf.begin(), buf.end(), [&] (auto x) { return exactlyEqual (x, value); }); } bool isClear (int channel) const @@ -607,13 +624,13 @@ class VST3PluginFormatTests : public UnitTest static bool channelStartsWithValue (Steinberg::Vst::AudioBusBuffers& bus, size_t index, float value) { - return bus.channelBuffers32[index][0] == value; + return exactlyEqual (bus.channelBuffers32[index][0], value); } static bool allMatch (const AudioBuffer& buf, int index, float value) { const auto* ptr = buf.getReadPointer (index); - return std::all_of (ptr, ptr + buf.getNumSamples(), [&] (auto x) { return x == value; }); + return std::all_of (ptr, ptr + buf.getNumSamples(), [&] (auto x) { return exactlyEqual (x, value); }); } struct MultiBusBuffers diff --git a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index b1f1cad..fd79cbf 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -904,7 +904,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, { const ScopedLock sl (pluginInstance.lock); - if (effect->getParameter (effect, getParameterIndex()) != newValue) + if (! approximatelyEqual (effect->getParameter (effect, getParameterIndex()), newValue)) effect->setParameter (effect, getParameterIndex(), newValue); } } @@ -2004,7 +2004,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, void setValue (float newValue) override { - currentValue = (newValue != 0.0f); + currentValue = (! approximatelyEqual (newValue, 0.0f)); if (parent.vstSupportsBypass) parent.dispatch (Vst2::effSetBypass, 0, currentValue ? 1 : 0, nullptr, 0.0f); @@ -2028,7 +2028,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, float getValue() const override { return currentValue; } float getDefaultValue() const override { return 0.0f; } String getName (int /*maximumStringLength*/) const override { return "Bypass"; } - String getText (float value, int) const override { return (value != 0.0f ? TRANS("On") : TRANS("Off")); } + String getText (float value, int) const override { return (! approximatelyEqual (value, 0.0f) ? TRANS("On") : TRANS("Off")); } bool isAutomatable() const override { return true; } bool isDiscrete() const override { return true; } bool isBoolean() const override { return true; } @@ -2740,7 +2740,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, { if (processBlockBypassedCalled) { - if (bypassParam->getValue() == 0.0f || ! lastProcessBlockCallWasBypass) + if (approximatelyEqual (bypassParam->getValue(), 0.0f) || ! lastProcessBlockCallWasBypass) bypassParam->setValue (1.0f); } else @@ -2805,13 +2805,14 @@ struct VSTPluginWindow : public AudioProcessorEditor, ~VSTPluginWindow() override { + activeVSTWindows.removeFirstMatchingValue (this); + closePluginWindow(); #if JUCE_MAC cocoaWrapper.reset(); #endif - activeVSTWindows.removeFirstMatchingValue (this); plugin.editorBeingDeleted (this); } @@ -3037,8 +3038,8 @@ struct VSTPluginWindow : public AudioProcessorEditor, void broughtToFront() override { - activeVSTWindows.removeFirstMatchingValue (this); - activeVSTWindows.add (this); + if (activeVSTWindows.removeFirstMatchingValue (this) != -1) + activeVSTWindows.add (this); #if JUCE_MAC dispatch (Vst2::effEditTop, 0, 0, nullptr, 0); diff --git a/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.cpp b/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.cpp index 6814cf6..c076843 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.cpp @@ -43,7 +43,9 @@ //============================================================================== #if (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) && (JUCE_LINUX || JUCE_BSD) + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wvariadic-macros") #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #include #include #undef KeyPress @@ -220,7 +222,8 @@ struct NSViewComponentWithParent : public NSViewComponent, #include "utilities/juce_AudioProcessorValueTreeState.cpp" #include "utilities/juce_PluginHostType.cpp" #include "utilities/juce_NativeScaleFactorNotifier.cpp" -#include "utilities/juce_VSTCallbackHandler.cpp" +#include "utilities/juce_AAXClientExtensions.cpp" +#include "utilities/juce_VST2ClientExtensions.cpp" #include "utilities/ARA/juce_ARA_utils.cpp" #include "format_types/juce_LV2PluginFormat.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.h b/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.h index f1caca3..5e17dac 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.h +++ b/JuceLibraryCode/modules/juce_audio_processors/juce_audio_processors.h @@ -35,7 +35,7 @@ ID: juce_audio_processors vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE audio processor classes description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. website: http://www.juce.com/juce @@ -130,7 +130,8 @@ #endif //============================================================================== -#include "utilities/juce_VSTCallbackHandler.h" +#include "utilities/juce_AAXClientExtensions.h" +#include "utilities/juce_VST2ClientExtensions.h" #include "utilities/juce_VST3ClientExtensions.h" #include "utilities/juce_NativeScaleFactorNotifier.h" #include "format_types/juce_ARACommon.h" diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 7d5c074..a98f225 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -437,18 +437,20 @@ void AudioProcessor::validateParameter (AudioProcessorParameter* param) See the documentation for AudioProcessorParameter(int) for more information. */ #if JucePlugin_Build_AU - jassert (wrapperType == wrapperType_Undefined || param->getVersionHint() != 0); + static std::once_flag flag; + if (wrapperType != wrapperType_Undefined && param->getVersionHint() == 0) + std::call_once (flag, [] { jassertfalse; }); #endif } void AudioProcessor::checkForDuplicateTrimmedParamID ([[maybe_unused]] AudioProcessorParameter* param) { #if JUCE_DEBUG && ! JUCE_DISABLE_CAUTIOUS_PARAMETER_ID_CHECKING - if (auto* withID = dynamic_cast (param)) + if (auto* withID = dynamic_cast (param)) { constexpr auto maximumSafeAAXParameterIdLength = 31; - const auto paramID = withID->paramID; + const auto paramID = withID->getParameterID(); // If you hit this assertion, a parameter name is too long to be supported // by the AAX plugin format. @@ -476,9 +478,9 @@ void AudioProcessor::checkForDuplicateTrimmedParamID ([[maybe_unused]] AudioProc void AudioProcessor::checkForDuplicateParamID ([[maybe_unused]] AudioProcessorParameter* param) { #if JUCE_DEBUG - if (auto* withID = dynamic_cast (param)) + if (auto* withID = dynamic_cast (param)) { - auto insertResult = paramIDs.insert (withID->paramID); + auto insertResult = paramIDs.insert (withID->getParameterID()); // If you hit this assertion then the parameter ID is not unique jassert (insertResult.second); @@ -1152,7 +1154,7 @@ void AudioProcessor::Bus::updateChannelCount() noexcept void AudioProcessor::BusesProperties::addBus (bool isInput, const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault) { - jassert (dfltLayout.size() != 0); + jassert (! dfltLayout.isDisabled()); BusProperties props; @@ -1181,55 +1183,6 @@ AudioProcessor::BusesProperties AudioProcessor::BusesProperties::withOutput (con return retval; } -//============================================================================== -int32 AudioProcessor::getAAXPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, - const AudioChannelSet& mainOutputLayout, - const bool idForAudioSuite) const -{ - int uniqueFormatId = 0; - - for (int dir = 0; dir < 2; ++dir) - { - const bool isInput = (dir == 0); - auto& set = (isInput ? mainInputLayout : mainOutputLayout); - int aaxFormatIndex = 0; - - const AudioChannelSet sets[] - { - AudioChannelSet::disabled(), - AudioChannelSet::mono(), - AudioChannelSet::stereo(), - AudioChannelSet::createLCR(), - AudioChannelSet::createLCRS(), - AudioChannelSet::quadraphonic(), - AudioChannelSet::create5point0(), - AudioChannelSet::create5point1(), - AudioChannelSet::create6point0(), - AudioChannelSet::create6point1(), - AudioChannelSet::create7point0(), - AudioChannelSet::create7point1(), - AudioChannelSet::create7point0SDDS(), - AudioChannelSet::create7point1SDDS(), - AudioChannelSet::create7point0point2(), - AudioChannelSet::create7point1point2(), - AudioChannelSet::ambisonic (1), - AudioChannelSet::ambisonic (2), - AudioChannelSet::ambisonic (3) - }; - - const auto index = (int) std::distance (std::begin (sets), std::find (std::begin (sets), std::end (sets), set)); - - if (index != numElementsInArray (sets)) - aaxFormatIndex = index; - else - jassertfalse; - - uniqueFormatId = (uniqueFormatId << 8) | aaxFormatIndex; - } - - return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; -} - //============================================================================== const char* AudioProcessor::getWrapperTypeDescription (AudioProcessor::WrapperType type) noexcept { @@ -1248,6 +1201,63 @@ const char* AudioProcessor::getWrapperTypeDescription (AudioProcessor::WrapperTy } } +//============================================================================== +VST2ClientExtensions* AudioProcessor::getVST2ClientExtensions() +{ + if (auto* extensions = dynamic_cast (this)) + { + // To silence this jassert there are two options: + // + // 1. - Override AudioProcessor::getVST2ClientExtensions() and + // return the "this" pointer. + // + // - This option has the advantage of being quick and easy, + // and avoids the above dynamic_cast. + // + // 2. - Create a new object that inherits from VST2ClientExtensions. + // + // - Port your existing functionality from the AudioProcessor + // to the new object. + // + // - Return a pointer to the object in AudioProcessor::getVST2ClientExtensions(). + // + // - This option has the advantage of allowing you to break + // up your AudioProcessor into smaller composable objects. + jassertfalse; + return extensions; + } + + return nullptr; +} + +VST3ClientExtensions* AudioProcessor::getVST3ClientExtensions() +{ + if (auto* extensions = dynamic_cast (this)) + { + // To silence this jassert there are two options: + // + // 1. - Override AudioProcessor::getVST3ClientExtensions() and + // return the "this" pointer. + // + // - This option has the advantage of being quick and easy, + // and avoids the above dynamic_cast. + // + // 2. - Create a new object that inherits from VST3ClientExtensions. + // + // - Port your existing functionality from the AudioProcessor + // to the new object. + // + // - Return a pointer to the object in AudioProcessor::getVST3ClientExtensions(). + // + // - This option has the advantage of allowing you to break + // up your AudioProcessor into smaller composable objects. + jassertfalse; + return extensions; + } + + return nullptr; +} + //============================================================================== JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) @@ -1412,8 +1422,8 @@ const String AudioProcessor::getParameterName (int index) String AudioProcessor::getParameterID (int index) { // Don't use getParamChecked here, as this must also work for legacy plug-ins - if (auto* p = dynamic_cast (getParameters()[index])) - return p->paramID; + if (auto* p = dynamic_cast (getParameters()[index])) + return p->getParameterID(); return String (index); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 4baf65f..654a208 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -40,9 +40,11 @@ namespace juce plugin, you should implement a global function called createPluginFilter() which creates and returns a new instance of your subclass. + @see AAXClientExtensions, VST2ClientExtensions, VST3ClientExtensions + @tags{Audio} */ -class JUCE_API AudioProcessor +class JUCE_API AudioProcessor : private AAXClientExtensions { protected: struct BusesProperties; @@ -309,29 +311,37 @@ class JUCE_API AudioProcessor */ struct BusesLayout { + private: + template + static auto& getBuses (This& t, bool isInput) { return isInput ? t.inputBuses : t.outputBuses; } + + public: /** An array containing the list of input buses that this processor supports. */ Array inputBuses; /** An array containing the list of output buses that this processor supports. */ Array outputBuses; + auto& getBuses (bool isInput) const { return getBuses (*this, isInput); } + auto& getBuses (bool isInput) { return getBuses (*this, isInput); } + /** Get the number of channels of a particular bus */ int getNumChannels (bool isInput, int busIndex) const noexcept { - auto& bus = (isInput ? inputBuses : outputBuses); + auto& bus = getBuses (isInput); return isPositiveAndBelow (busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; } /** Get the channel set of a particular bus */ AudioChannelSet& getChannelSet (bool isInput, int busIndex) noexcept { - return (isInput ? inputBuses : outputBuses).getReference (busIndex); + return getBuses (isInput).getReference (busIndex); } /** Get the channel set of a particular bus */ AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept { - return (isInput ? inputBuses : outputBuses)[busIndex]; + return getBuses (isInput)[busIndex]; } /** Get the input channel layout on the main bus. */ @@ -1168,18 +1178,30 @@ class JUCE_API AudioProcessor void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; //============================================================================== - /** AAX plug-ins need to report a unique "plug-in id" for every audio layout - configuration that your AudioProcessor supports on the main bus. Override this - function if you want your AudioProcessor to use a custom "plug-in id" (for example - to stay backward compatible with older versions of JUCE). - - The default implementation will compute a unique integer from the input and output - layout and add this value to the 4 character code 'jcaa' (for native AAX) or 'jyaa' - (for AudioSuite plug-ins). - */ - virtual int32 getAAXPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, - const AudioChannelSet& mainOutputLayout, - bool idForAudioSuite) const; + /** Returns a reference to an object that implements AAX specific information regarding + this AudioProcessor. + */ + virtual AAXClientExtensions& getAAXClientExtensions() { return *this; } + + /** Returns a non-owning pointer to an object that implements VST2 specific information + regarding this AudioProcessor. + + By default, for backwards compatibility, this will attempt to dynamic-cast this + AudioProcessor to VST2ClientExtensions. + It is recommended to override this function to return a pointer directly to an object + of the correct type in order to avoid this dynamic cast. + */ + virtual VST2ClientExtensions* getVST2ClientExtensions(); + + /** Returns a non-owning pointer to an object that implements VST3 specific information + regarding this AudioProcessor. + + By default, for backwards compatibility, this will attempt to dynamic-cast this + AudioProcessor to VST3ClientExtensions. + It is recommended to override this function to return a pointer directly to an object + of the correct type in order to avoid this dynamic cast. + */ + virtual VST3ClientExtensions* getVST3ClientExtensions(); //============================================================================== /** Some plug-ins support sharing response curve data with the host so that it can diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index 962f3cb..6d45cc1 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -225,4 +225,16 @@ ComponentPeer* AudioProcessorEditor::createNewPeer ([[maybe_unused]] int styleFl return Component::createNewPeer (styleFlags, nativeWindow); } +bool AudioProcessorEditor::wantsLayerBackedView() const +{ + #if JUCE_MODULE_AVAILABLE_juce_opengl && JUCE_MAC + if (@available (macOS 10.14, *)) + return true; + + return false; + #else + return true; + #endif +} + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index a6eb952..6bc285a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -206,6 +206,23 @@ class JUCE_API AudioProcessorEditor : public Component */ std::unique_ptr resizableCorner; + /** The plugin wrapper will call this function to decide whether to use a layer-backed view to + host the editor on macOS and iOS. + + Layer-backed views generally provide better performance, and are recommended in most + situations. However, on older macOS versions (confirmed on 10.12 and 10.13), displaying an + OpenGL context inside a layer-backed view can lead to deadlocks, so it is recommended to + avoid layer-backed views when using OpenGL on these OS versions. + + The default behaviour of this function is to return false if and only if the juce_opengl + module is present and the current platform is macOS 10.13 or earlier. + + You may want to override this behaviour if your plugin has an option to enable and disable + OpenGL rendering. If you know your plugin editor will never use OpenGL rendering, you can + set this function to return true in all situations. + */ + virtual bool wantsLayerBackedView() const; + private: //============================================================================== struct AudioProcessorEditorListener : public ComponentListener diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index eaf5a73..22b1547 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -139,6 +139,15 @@ class Connections using Connection = AudioProcessorGraph::Connection; using NodeAndChannel = AudioProcessorGraph::NodeAndChannel; +private: + static auto equalRange (const std::set& pins, const NodeID node) + { + return std::equal_range (pins.cbegin(), pins.cend(), node, ImplicitNode::compare); + } + + using Map = std::map>; + +public: static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex; bool addConnection (const Nodes& n, const Connection& c) @@ -179,7 +188,7 @@ class Connections for (auto& pair : sourcesForDestination) { - const auto range = std::equal_range (pair.second.cbegin(), pair.second.cend(), n, ImplicitNode::compare); + const auto range = equalRange (pair.second, n); result |= range.first != range.second; pair.second.erase (range.first, range.second); } @@ -231,8 +240,8 @@ class Connections return std::any_of (matchingDestinations.first, matchingDestinations.second, [srcID] (const auto& pair) { - const auto iter = std::lower_bound (pair.second.cbegin(), pair.second.cend(), srcID, ImplicitNode::compare); - return iter != pair.second.cend() && iter->nodeID == srcID; + const auto [begin, end] = equalRange (pair.second, srcID); + return begin != end; }); } @@ -276,9 +285,44 @@ class Connections bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; } bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; } -private: - using Map = std::map>; + class DestinationsForSources + { + public: + explicit DestinationsForSources (Map m) : map (std::move (m)) {} + + bool isSourceConnectedToDestinationNodeIgnoringChannel (const NodeAndChannel& source, NodeID dest, int channel) const + { + if (const auto destIter = map.find (source); destIter != map.cend()) + { + const auto [begin, end] = equalRange (destIter->second, dest); + return std::any_of (begin, end, [&] (const NodeAndChannel& nodeAndChannel) + { + return nodeAndChannel != NodeAndChannel { dest, channel }; + }); + } + + return false; + } + + private: + Map map; + }; + + /* Reverses the graph, to allow fast lookup by source. + This is expensive, don't call this more than necessary! + */ + auto getDestinationsForSources() const + { + Map destinationsForSources; + + for (const auto& [destination, sources] : sourcesForDestination) + for (const auto& source : sources) + destinationsForSources[source].insert (destination); + return DestinationsForSources (std::move (destinationsForSources)); + } + +private: struct SearchState { std::set visited; @@ -351,14 +395,14 @@ class NodeStates /* Called from prepareToPlay and releaseResources with the PrepareSettings that should be used next time the graph is rebuilt. */ - void setState (Optional newSettings) + void setState (std::optional newSettings) { const std::lock_guard lock (mutex); next = newSettings; } /* Call from the audio thread only. */ - Optional getLastRequestedSettings() const { return next; } + std::optional getLastRequestedSettings() const { return next; } /* Call from the main thread only! @@ -375,7 +419,7 @@ class NodeStates Returns the settings that were applied to the nodes. */ - Optional applySettings (const Nodes& n) + std::optional applySettings (const Nodes& n) { const auto settingsChanged = [this] { @@ -411,7 +455,7 @@ class NodeStates preparedNodes.clear(); } - if (current.hasValue()) + if (current.has_value()) { for (const auto& node : n.getNodes()) { @@ -430,10 +474,24 @@ class NodeStates return current; } + /* Call from the main thread to indicate that a node has been removed from the graph. + */ + void removeNode (const NodeID n) + { + preparedNodes.erase (n); + } + + /* Call from the main thread to indicate that all nodes have been removed from the graph. + */ + void clear() + { + preparedNodes.clear(); + } + private: std::mutex mutex; std::set preparedNodes; - Optional current, next; + std::optional current, next; }; //============================================================================== @@ -442,8 +500,17 @@ struct GraphRenderSequence { using Node = AudioProcessorGraph::Node; + struct GlobalIO + { + AudioBuffer& audioIn; + AudioBuffer& audioOut; + MidiBuffer& midiIn; + MidiBuffer& midiOut; + }; + struct Context { + GlobalIO globalIO; AudioPlayHead* audioPlayHead; int numSamples; }; @@ -482,7 +549,12 @@ struct GraphRenderSequence currentMidiOutputBuffer.clear(); { - const Context context { audioPlayHead, numSamples }; + const Context context { { *currentAudioInputBuffer, + currentAudioOutputBuffer, + *currentMidiInputBuffer, + currentMidiOutputBuffer }, + audioPlayHead, + numSamples }; for (const auto& op : renderOps) op->process (context); @@ -690,7 +762,30 @@ struct GraphRenderSequence int totalNumChans, int midiBuffer) { - renderOps.push_back (std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer)); + auto op = [&]() -> std::unique_ptr + { + if (auto* ioNode = dynamic_cast (node->getProcessor())) + { + switch (ioNode->getType()) + { + case AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode: + return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + + case AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode: + return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + + case AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode: + return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + + case AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode: + return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + } + } + + return std::make_unique (node, audioChannelsUsed, totalNumChans, midiBuffer); + }(); + + renderOps.push_back (std::move (op)); } void prepareBuffers (int blockSize) @@ -738,12 +833,12 @@ struct GraphRenderSequence virtual void process (const Context&) = 0; }; - struct ProcessOp : public RenderOp + struct NodeOp : public RenderOp { - ProcessOp (const Node::Ptr& n, - const Array& audioChannelsUsed, - int totalNumChans, - int midiBufferIndex) + NodeOp (const Node::Ptr& n, + const Array& audioChannelsUsed, + int totalNumChans, + int midiBufferIndex) : node (n), processor (*n->getProcessor()), audioChannelsToUse (audioChannelsUsed), @@ -754,7 +849,7 @@ struct GraphRenderSequence audioChannelsToUse.add (0); } - void prepare (FloatType* const* renderBuffer, MidiBuffer* buffers) override + void prepare (FloatType* const* renderBuffer, MidiBuffer* buffers) final { for (size_t i = 0; i < audioChannels.size(); ++i) audioChannels[i] = renderBuffer[audioChannelsToUse.getUnchecked ((int) i)]; @@ -762,7 +857,7 @@ struct GraphRenderSequence midiBuffer = buffers + midiBufferToUse; } - void process (const Context& c) override + void process (const Context& c) final { processor.setPlayHead (c.audioPlayHead); @@ -777,64 +872,140 @@ struct GraphRenderSequence AudioBuffer buffer { audioChannels.data(), numAudioChannels, c.numSamples }; - const ScopedLock lock (processor.getCallbackLock()); - if (processor.isSuspended()) + { buffer.clear(); + } else - callProcess (buffer, *midiBuffer); + { + const auto bypass = node->isBypassed() && processor.getBypassParameter() == nullptr; + processWithBuffer (c.globalIO, bypass, buffer, *midiBuffer); + } + } + + virtual void processWithBuffer (const GlobalIO&, bool bypass, AudioBuffer& audio, MidiBuffer& midi) = 0; + + const Node::Ptr node; + AudioProcessor& processor; + MidiBuffer* midiBuffer = nullptr; + + Array audioChannelsToUse; + std::vector audioChannels; + const int midiBufferToUse; + }; + + struct ProcessOp : public NodeOp + { + using NodeOp::NodeOp; + + void processWithBuffer (const GlobalIO&, bool bypass, AudioBuffer& audio, MidiBuffer& midi) final + { + callProcess (bypass, audio, midi); } - void callProcess (AudioBuffer& buffer, MidiBuffer& midi) + void callProcess (bool bypass, AudioBuffer& buffer, MidiBuffer& midi) { - if (processor.isUsingDoublePrecision()) + if (this->processor.isUsingDoublePrecision()) { tempBufferDouble.makeCopyOf (buffer, true); - process (*node, tempBufferDouble, midi); + processImpl (bypass, this->processor, tempBufferDouble, midi); buffer.makeCopyOf (tempBufferDouble, true); } else { - process (*node, buffer, midi); + processImpl (bypass, this->processor, buffer, midi); } } - void callProcess (AudioBuffer& buffer, MidiBuffer& midi) + void callProcess (bool bypass, AudioBuffer& buffer, MidiBuffer& midi) { - if (processor.isUsingDoublePrecision()) + if (this->processor.isUsingDoublePrecision()) { - process (*node, buffer, midi); + processImpl (bypass, this->processor, buffer, midi); } else { tempBufferFloat.makeCopyOf (buffer, true); - process (*node, tempBufferFloat, midi); + processImpl (bypass, this->processor, tempBufferFloat, midi); buffer.makeCopyOf (tempBufferFloat, true); } } template - static void process (const Node& node, AudioBuffer& audio, MidiBuffer& midi) + static void processImpl (bool bypass, AudioProcessor& p, AudioBuffer& audio, MidiBuffer& midi) { - if (node.isBypassed() && node.getProcessor()->getBypassParameter() == nullptr) - node.getProcessor()->processBlockBypassed (audio, midi); + if (bypass) + p.processBlockBypassed (audio, midi); else - node.getProcessor()->processBlock (audio, midi); + p.processBlock (audio, midi); } - const Node::Ptr node; - AudioProcessor& processor; - MidiBuffer* midiBuffer = nullptr; - - Array audioChannelsToUse; - std::vector audioChannels; AudioBuffer tempBufferFloat, tempBufferDouble; - const int midiBufferToUse; + }; + + struct MidiInOp : public NodeOp + { + using NodeOp::NodeOp; + + void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer& audio, MidiBuffer& midi) final + { + if (! bypass) + midi.addEvents (g.midiIn, 0, audio.getNumSamples(), 0); + } + }; + + struct MidiOutOp : public NodeOp + { + using NodeOp::NodeOp; + + void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer& audio, MidiBuffer& midi) final + { + if (! bypass) + g.midiOut.addEvents (midi, 0, audio.getNumSamples(), 0); + } + }; + + struct AudioInOp : public NodeOp + { + using NodeOp::NodeOp; + + void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer& audio, MidiBuffer&) final + { + if (bypass) + return; + + for (int i = jmin (g.audioIn.getNumChannels(), audio.getNumChannels()); --i >= 0;) + audio.copyFrom (i, 0, g.audioIn, i, 0, audio.getNumSamples()); + } + }; + + struct AudioOutOp : public NodeOp + { + using NodeOp::NodeOp; + + void processWithBuffer (const GlobalIO& g, bool bypass, AudioBuffer& audio, MidiBuffer&) final + { + if (bypass) + return; + + for (int i = jmin (g.audioOut.getNumChannels(), audio.getNumChannels()); --i >= 0;) + g.audioOut.addFrom (i, 0, audio, i, 0, audio.getNumSamples()); + } }; std::vector> renderOps; }; +//============================================================================== +struct SequenceAndLatency +{ + using RenderSequenceVariant = std::variant, + GraphRenderSequence>; + + RenderSequenceVariant sequence; + int latencySamples = 0; +}; + //============================================================================== class RenderSequenceBuilder { @@ -846,19 +1017,12 @@ class RenderSequenceBuilder static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex; - template - static auto build (const Nodes& n, const Connections& c) + template + static SequenceAndLatency build (const Nodes& n, const Connections& c) { - RenderSequence sequence; + GraphRenderSequence sequence; const RenderSequenceBuilder builder (n, c, sequence); - - struct SequenceAndLatency - { - RenderSequence sequence; - int latencySamples = 0; - }; - - return SequenceAndLatency { std::move (sequence), builder.totalLatency }; + return { std::move (sequence), builder.totalLatency }; } private: @@ -961,6 +1125,7 @@ class RenderSequenceBuilder //============================================================================== template int findBufferForInputAudioChannel (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, const int inputChan, @@ -998,7 +1163,7 @@ class RenderSequenceBuilder jassert (bufIndex >= 0); } - if (inputChan < numOuts && isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (inputChan < numOuts && isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { // can't mess up this channel because it's needed later by another node, // so we need to use a copy of it.. @@ -1025,7 +1190,7 @@ class RenderSequenceBuilder { auto sourceBufIndex = getBufferContaining (src); - if (sourceBufIndex >= 0 && ! isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (sourceBufIndex >= 0 && ! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { // we've found one of our input chans that can be re-used.. reusableInputIndex = i; @@ -1079,7 +1244,7 @@ class RenderSequenceBuilder if (nodeDelay < maxLatency) { - if (! isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { sequence.addDelayChannelOp (srcIndex, maxLatency - nodeDelay); } @@ -1105,6 +1270,7 @@ class RenderSequenceBuilder template int findBufferForInputMidiChannel (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, int ourRenderingIndex) @@ -1131,7 +1297,7 @@ class RenderSequenceBuilder if (midiBufferToUse >= 0) { - if (isBufferNeededLater (c, ourRenderingIndex, midiChannelIndex, src)) + if (isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. @@ -1160,7 +1326,7 @@ class RenderSequenceBuilder auto sourceBufIndex = getBufferContaining (src); if (sourceBufIndex >= 0 - && ! isBufferNeededLater (c, ourRenderingIndex, midiChannelIndex, src)) + && ! isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src)) { // we've found one of our input buffers that can be re-used.. reusableInputIndex = i; @@ -1209,6 +1375,7 @@ class RenderSequenceBuilder template void createRenderingOpsForNode (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, const int ourRenderingIndex) @@ -1225,6 +1392,7 @@ class RenderSequenceBuilder { // get a list of all the inputs to this node auto index = findBufferForInputAudioChannel (c, + reversed, sequence, node, inputChan, @@ -1247,7 +1415,7 @@ class RenderSequenceBuilder audioBuffers.getReference (index).channel = { node.nodeID, outputChan }; } - auto midiBufferToUse = findBufferForInputMidiChannel (c, sequence, node, ourRenderingIndex); + auto midiBufferToUse = findBufferForInputMidiChannel (c, reversed, sequence, node, ourRenderingIndex); if (processor.producesMidi()) midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, midiChannelIndex }; @@ -1286,7 +1454,7 @@ class RenderSequenceBuilder return -1; } - void markAnyUnusedBuffersAsFree (const Connections& c, + void markAnyUnusedBuffersAsFree (const Connections::DestinationsForSources& c, Array& buffers, const int stepIndex) { @@ -1295,34 +1463,25 @@ class RenderSequenceBuilder b.setFree(); } - bool isBufferNeededLater (const Connections& c, - int stepIndexToSearchFrom, - int inputChannelOfIndexToIgnore, - NodeAndChannel output) const + bool isBufferNeededLater (const Connections::DestinationsForSources& c, + const int stepIndexToSearchFrom, + const int inputChannelOfIndexToIgnore, + const NodeAndChannel output) const { - while (stepIndexToSearchFrom < orderedNodes.size()) - { - auto* node = orderedNodes.getUnchecked (stepIndexToSearchFrom); - - if (output.isMIDI()) - { - if (inputChannelOfIndexToIgnore != midiChannelIndex - && c.isConnected ({ { output.nodeID, midiChannelIndex }, - { node->nodeID, midiChannelIndex } })) - return true; - } - else - { - for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) - if (i != inputChannelOfIndexToIgnore && c.isConnected ({ output, { node->nodeID, i } })) - return true; - } + if (orderedNodes.size() <= stepIndexToSearchFrom) + return false; - inputChannelOfIndexToIgnore = -1; - ++stepIndexToSearchFrom; + if (c.isSourceConnectedToDestinationNodeIgnoringChannel (output, + orderedNodes.getUnchecked (stepIndexToSearchFrom)->nodeID, + inputChannelOfIndexToIgnore)) + { + return true; } - return false; + return std::any_of (orderedNodes.begin() + stepIndexToSearchFrom + 1, orderedNodes.end(), [&] (const auto* node) + { + return c.isSourceConnectedToDestinationNodeIgnoringChannel (output, node->nodeID, -1); + }); } template @@ -1332,11 +1491,13 @@ class RenderSequenceBuilder audioBuffers.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros midiBuffers .add (AssignedBuffer::createReadOnlyEmpty()); + const auto reversed = c.getDestinationsForSources(); + for (int i = 0; i < orderedNodes.size(); ++i) { - createRenderingOpsForNode (c, sequence, *orderedNodes.getUnchecked (i), i); - markAnyUnusedBuffersAsFree (c, audioBuffers, i); - markAnyUnusedBuffersAsFree (c, midiBuffers, i); + createRenderingOpsForNode (c, reversed, sequence, *orderedNodes.getUnchecked (i), i); + markAnyUnusedBuffersAsFree (reversed, audioBuffers, i); + markAnyUnusedBuffersAsFree (reversed, midiBuffers, i); } sequence.numBuffersNeeded = audioBuffers.size(); @@ -1356,95 +1517,77 @@ class RenderSequence public: using AudioGraphIOProcessor = AudioProcessorGraph::AudioGraphIOProcessor; - RenderSequence (PrepareSettings s, const Nodes& n, const Connections& c) - : RenderSequence (s, - RenderSequenceBuilder::build> (n, c), - RenderSequenceBuilder::build> (n, c)) + RenderSequence (const PrepareSettings s, const Nodes& n, const Connections& c) + : RenderSequence (s, s.precision == AudioProcessor::ProcessingPrecision::singlePrecision + ? RenderSequenceBuilder::build (n, c) + : RenderSequenceBuilder::build (n, c)) { } - void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) + template + void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) { - renderSequenceF.perform (audio, midi, playHead); + if (auto* s = std::get_if> (&sequence.sequence)) + s->perform (audio, midi, playHead); + else + jassertfalse; // Not prepared for this audio format! } - void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) - { - renderSequenceD.perform (audio, midi, playHead); - } + int getLatencySamples() const { return sequence.latencySamples; } + PrepareSettings getSettings() const { return settings; } - void processIO (AudioGraphIOProcessor& io, AudioBuffer& audio, MidiBuffer& midi) +private: + template + static void visitRenderSequence (This& t, Callback&& callback) { - processIOBlock (io, renderSequenceF, audio, midi); + if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); + if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); + jassertfalse; } - void processIO (AudioGraphIOProcessor& io, AudioBuffer& audio, MidiBuffer& midi) + RenderSequence (const PrepareSettings s, SequenceAndLatency&& built) + : settings (s), sequence (std::move (built)) { - processIOBlock (io, renderSequenceD, audio, midi); + visitRenderSequence (*this, [&] (auto& seq) { seq.prepareBuffers (settings.blockSize); }); } - int getLatencySamples() const { return latencySamples; } - PrepareSettings getSettings() const { return settings; } - -private: - template - static void processIOBlock (AudioGraphIOProcessor& io, - SequenceType& sequence, - AudioBuffer& buffer, - MidiBuffer& midiMessages) - { - switch (io.getType()) - { - case AudioGraphIOProcessor::audioOutputNode: - { - auto&& currentAudioOutputBuffer = sequence.currentAudioOutputBuffer; - - for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) - currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); - - break; - } - - case AudioGraphIOProcessor::audioInputNode: - { - auto* currentInputBuffer = sequence.currentAudioInputBuffer; - - for (int i = jmin (currentInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) - buffer.copyFrom (i, 0, *currentInputBuffer, i, 0, buffer.getNumSamples()); + PrepareSettings settings; + SequenceAndLatency sequence; +}; - break; - } +//============================================================================== +/* Holds information about a particular graph configuration, without sharing ownership of any + graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see + whether two graph configurations match. +*/ +class RenderSequenceSignature +{ + auto tie() const { return std::tie (settings, connections, nodes); } - case AudioGraphIOProcessor::midiOutputNode: - sequence.currentMidiOutputBuffer.addEvents (midiMessages, 0, buffer.getNumSamples(), 0); - break; +public: + RenderSequenceSignature (const PrepareSettings s, const Nodes& n, const Connections& c) + : settings (s), connections (c), nodes (getNodeMap (n)) {} - case AudioGraphIOProcessor::midiInputNode: - midiMessages.addEvents (*sequence.currentMidiInputBuffer, 0, buffer.getNumSamples(), 0); - break; + bool operator== (const RenderSequenceSignature& other) const { return tie() == other.tie(); } + bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); } - default: - break; - } - } +private: + using NodeMap = std::map; - template - RenderSequence (PrepareSettings s, Float f, Double d) - : settings (s), - renderSequenceF (std::move (f.sequence)), - renderSequenceD (std::move (d.sequence)), - latencySamples (f.latencySamples) + static NodeMap getNodeMap (const Nodes& n) { - jassert (f.latencySamples == d.latencySamples); + const auto& nodeRefs = n.getNodes(); + NodeMap result; + + for (const auto& node : nodeRefs) + result.emplace (node->nodeID, node->getProcessor()->getBusesLayout()); - renderSequenceF.prepareBuffers (settings.blockSize); - renderSequenceD.prepareBuffers (settings.blockSize); + return result; } PrepareSettings settings; - GraphRenderSequence renderSequenceF; - GraphRenderSequence renderSequenceD; - int latencySamples = 0; + Connections connections; + NodeMap nodes; }; //============================================================================== @@ -1475,7 +1618,7 @@ class RenderSequenceExchange : private Timer isNew = true; } - /** Call from the audio thread only. */ + /* Call from the audio thread only. */ void updateAudioThreadState() { const SpinLock::ScopedTryLockType lock (mutex); @@ -1488,7 +1631,7 @@ class RenderSequenceExchange : private Timer } } - /** Call from the audio thread only. */ + /* Call from the audio thread only. */ RenderSequence* getAudioThreadState() const { return audioThreadState.get(); } private: @@ -1534,17 +1677,11 @@ bool AudioProcessorGraph::Connection::operator< (const Connection& other) const } //============================================================================== -class AudioProcessorGraph::Pimpl : public AsyncUpdater +class AudioProcessorGraph::Pimpl { public: explicit Pimpl (AudioProcessorGraph& o) : owner (&o) {} - ~Pimpl() override - { - cancelPendingUpdate(); - clear (UpdateKind::sync); - } - const auto& getNodes() const { return nodes.getNodes(); } void clear (UpdateKind updateKind) @@ -1554,6 +1691,7 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater nodes = Nodes{}; connections = Connections{}; + nodeStates.clear(); topologyChanged (updateKind); } @@ -1592,6 +1730,7 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater { connections.disconnectNode (nodeID); auto result = nodes.removeNode (nodeID); + nodeStates.removeNode (nodeID); topologyChanged (updateKind); return result; } @@ -1687,6 +1826,17 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater topologyChanged (UpdateKind::sync); } + void rebuild (UpdateKind updateKind) + { + if (updateKind == UpdateKind::none) + return; + + if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + updater.triggerAsyncUpdate(); + } + void reset() { for (auto* n : getNodes()) @@ -1743,36 +1893,41 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater void topologyChanged (UpdateKind updateKind) { owner->sendChangeMessage(); - - if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread()) - handleAsyncUpdate(); - else - triggerAsyncUpdate(); + rebuild (updateKind); } - void handleAsyncUpdate() override + void handleAsyncUpdate() { if (const auto newSettings = nodeStates.applySettings (nodes)) { for (const auto node : nodes.getNodes()) setParentGraph (node->getProcessor()); - auto sequence = std::make_unique (*newSettings, nodes, connections); - owner->setLatencySamples (sequence->getLatencySamples()); - renderSequenceExchange.set (std::move (sequence)); + const RenderSequenceSignature newSignature (*newSettings, nodes, connections); + + if (std::exchange (lastBuiltSequence, newSignature) != newSignature) + { + auto sequence = std::make_unique (*newSettings, nodes, connections); + owner->setLatencySamples (sequence->getLatencySamples()); + renderSequenceExchange.set (std::move (sequence)); + } } else { + lastBuiltSequence.reset(); renderSequenceExchange.set (nullptr); } } + AudioProcessorGraph* owner = nullptr; Nodes nodes; Connections connections; NodeStates nodeStates; RenderSequenceExchange renderSequenceExchange; NodeID lastNodeID; + std::optional lastBuiltSequence; + LockingAsyncUpdater updater { [this] { handleAsyncUpdate(); } }; }; //============================================================================== @@ -1799,6 +1954,7 @@ AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID x) const bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind) { return pimpl->disconnectNode (nodeID, updateKind); } void AudioProcessorGraph::releaseResources() { return pimpl->releaseResources(); } bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind) { return pimpl->removeIllegalConnections (updateKind); } +void AudioProcessorGraph::rebuild() { return pimpl->rebuild (UpdateKind::sync); } void AudioProcessorGraph::reset() { return pimpl->reset(); } bool AudioProcessorGraph::canConnect (const Connection& c) const { return pimpl->canConnect (c); } bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { return pimpl->isConnected (c); } @@ -1892,20 +2048,16 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessi return true; } -void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer&, MidiBuffer&) { - jassert (graph != nullptr); - - if (auto* state = graph->pimpl->getAudioThreadState()) - state->processIO (*this, buffer, midiMessages); + // The graph should never call this! + jassertfalse; } -void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer&, MidiBuffer&) { - jassert (graph != nullptr); - - if (auto* state = graph->pimpl->getAudioThreadState()) - state->processIO (*this, buffer, midiMessages); + // The graph should never call this! + jassertfalse; } double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const @@ -1943,15 +2095,15 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG { graph = newGraph; - if (graph != nullptr) - { - setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, - type == audioInputNode ? graph->getTotalNumInputChannels() : 0, - getSampleRate(), - getBlockSize()); + if (graph == nullptr) + return; - updateHostDisplay(); - } + setPlayConfigDetails (type == audioOutputNode ? newGraph->getTotalNumOutputChannels() : 0, + type == audioInputNode ? newGraph->getTotalNumInputChannels() : 0, + getSampleRate(), + getBlockSize()); + + updateHostDisplay(); } //============================================================================== @@ -2058,6 +2210,36 @@ class AudioProcessorGraphTests : public UnitTest expect (graph.isAnInputTo (*nodes[nodes.size() - 1], *node)); } } + + beginTest ("large render sequence can be built"); + { + AudioProcessorGraph graph; + + std::vector nodeIDs; + + constexpr auto numNodes = 1000; + constexpr auto numChannels = 100; + + for (auto i = 0; i < numNodes; ++i) + { + nodeIDs.push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getMultichannelProperties (numChannels), + MidiIn::yes, + MidiOut::yes))->nodeID); + } + + for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it) + for (auto channel = 0; channel < numChannels; ++channel) + expect (graph.addConnection ({ { it[0], channel }, { it[1], channel } })); + + const auto b = std::chrono::steady_clock::now(); + graph.prepareToPlay (44100.0, 512); + const auto e = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast (e - b).count(); + + // No test here, but older versions of the graph would take forever to complete building + // this graph, so we just want to make sure that we finish the test without timing out. + logMessage ("render sequence built in " + String (duration) + " ms"); + } } private: @@ -2102,10 +2284,16 @@ class AudioProcessorGraphTests : public UnitTest static BusesProperties getStereoProperties() { - return BusesProperties().withInput ("in", AudioChannelSet::stereo()) + return BusesProperties().withInput ("in", AudioChannelSet::stereo()) .withOutput ("out", AudioChannelSet::stereo()); } + static BusesProperties getMultichannelProperties (int numChannels) + { + return BusesProperties().withInput ("in", AudioChannelSet::discreteChannels (numChannels)) + .withOutput ("out", AudioChannelSet::discreteChannels (numChannels)); + } + private: MidiIn midiIn; MidiOut midiOut; diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 14b61e8..e03123c 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -127,7 +127,7 @@ class JUCE_API AudioProcessorGraph : public AudioProcessor, if (processor != nullptr) { if (auto* bypassParam = processor->getBypassParameter()) - return (bypassParam->getValue() != 0.0f); + return ! approximatelyEqual (bypassParam->getValue(), 0.0f); } return bypassed; @@ -210,8 +210,11 @@ class JUCE_API AudioProcessorGraph : public AudioProcessor, */ enum class UpdateKind { - sync, ///< Indicates that the graph should be rebuilt immediately after modification. - async ///< Indicates that the graph rebuild should be deferred. + sync, ///< Graph should be rebuilt immediately after modification. + async, ///< Graph rebuild should be delayed. If you make several changes to the graph + ///< inside the same call stack, these changes will be applied in one go. + none ///< Graph should not be rebuilt automatically. Use rebuild() to trigger a graph + ///< rebuild. }; //============================================================================== @@ -312,6 +315,14 @@ class JUCE_API AudioProcessorGraph : public AudioProcessor, */ bool removeIllegalConnections (UpdateKind = UpdateKind::sync); + /** Rebuilds the graph if necessary. + + This function will only ever rebuild the graph on the main thread. If this function is + called from another thread, the rebuild request will be dispatched asynchronously to the + main thread. + */ + void rebuild(); + //============================================================================== /** A special type of AudioProcessor that can live inside an AudioProcessorGraph in order to use the audio that comes into and out of the graph itself. diff --git a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index 7ddbc17..e0032ba 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -378,7 +378,7 @@ class SliderParameterComponent : public ParameterComponent { auto newVal = (float) slider.getValue(); - if (getParameter().getValue() != newVal) + if (! approximatelyEqual (getParameter().getValue(), newVal)) { if (! isDragging) getParameter().beginChangeGesture(); diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 4e2b16d..eadeef6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -64,7 +64,7 @@ class PluginListComponent::TableModel : public TableListBoxModel if (columnId == nameCol) text = list.getBlacklistedFiles() [row - list.getNumTypes()]; else if (columnId == descCol) - text = TRANS("Deactivated after failing to initialise correctly"); + text = TRANS ("Deactivated after failing to initialise correctly"); } else { @@ -155,11 +155,11 @@ PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, Kno TableHeaderComponent& header = table.getHeader(); - header.addColumn (TRANS("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards); - header.addColumn (TRANS("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable); - header.addColumn (TRANS("Category"), TableModel::categoryCol, 100, 100, 200); - header.addColumn (TRANS("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300); - header.addColumn (TRANS("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable); + header.addColumn (TRANS ("Name"), TableModel::nameCol, 200, 100, 700, TableHeaderComponent::defaultFlags | TableHeaderComponent::sortedForwards); + header.addColumn (TRANS ("Format"), TableModel::typeCol, 80, 80, 80, TableHeaderComponent::notResizable); + header.addColumn (TRANS ("Category"), TableModel::categoryCol, 100, 100, 200); + header.addColumn (TRANS ("Manufacturer"), TableModel::manufacturerCol, 200, 100, 300); + header.addColumn (TRANS ("Description"), TableModel::descCol, 300, 100, 500, TableHeaderComponent::notSortable); table.setHeaderHeight (22); table.setRowHeight (20); @@ -289,7 +289,7 @@ void PluginListComponent::removePluginItem (int index) PopupMenu PluginListComponent::createOptionsMenu() { PopupMenu menu; - menu.addItem (PopupMenu::Item (TRANS("Clear list")) + menu.addItem (PopupMenu::Item (TRANS ("Clear list")) .setAction ([this] { list.clear(); })); menu.addSeparator(); @@ -306,18 +306,18 @@ PopupMenu PluginListComponent::createOptionsMenu() menu.addSeparator(); - menu.addItem (PopupMenu::Item (TRANS("Remove selected plug-in from list")) + menu.addItem (PopupMenu::Item (TRANS ("Remove selected plug-in from list")) .setEnabled (table.getNumSelectedRows() > 0) .setAction ([this] { removeSelectedPlugins(); })); - menu.addItem (PopupMenu::Item (TRANS("Remove any plug-ins whose files no longer exist")) + menu.addItem (PopupMenu::Item (TRANS ("Remove any plug-ins whose files no longer exist")) .setAction ([this] { removeMissingPlugins(); })); menu.addSeparator(); auto selectedRow = table.getSelectedRow(); - menu.addItem (PopupMenu::Item (TRANS("Show folder containing selected plug-in")) + menu.addItem (PopupMenu::Item (TRANS ("Show folder containing selected plug-in")) .setEnabled (canShowFolderForPlugin (list, selectedRow)) .setAction ([this, selectedRow] { showFolderForPlugin (list, selectedRow); })); @@ -337,10 +337,10 @@ PopupMenu PluginListComponent::createMenuForRow (int rowNumber) if (rowNumber >= 0 && rowNumber < tableModel->getNumRows()) { - menu.addItem (PopupMenu::Item (TRANS("Remove plug-in from list")) + menu.addItem (PopupMenu::Item (TRANS ("Remove plug-in from list")) .setAction ([this, rowNumber] { removePluginItem (rowNumber); })); - menu.addItem (PopupMenu::Item (TRANS("Show folder containing plug-in")) + menu.addItem (PopupMenu::Item (TRANS ("Show folder containing plug-in")) .setEnabled (canShowFolderForPlugin (list, rowNumber)) .setAction ([this, rowNumber] { showFolderForPlugin (list, rowNumber); })); } @@ -391,7 +391,7 @@ class PluginListComponent::Scanner : private Timer formatToScan (format), filesOrIdentifiersToScan (filesOrIdentifiers), propertiesToUse (properties), - pathChooserWindow (TRANS("Select folders to scan..."), String(), MessageBoxIconType::NoIcon), + pathChooserWindow (TRANS ("Select folders to scan..."), String(), MessageBoxIconType::NoIcon), progressWindow (title, text, MessageBoxIconType::NoIcon), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) @@ -417,8 +417,8 @@ class PluginListComponent::Scanner : private Timer pathList.setPath (path); pathChooserWindow.addCustomComponent (&pathList); - pathChooserWindow.addButton (TRANS("Scan"), 1, KeyPress (KeyPress::returnKey)); - pathChooserWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + pathChooserWindow.addButton (TRANS ("Scan"), 1, KeyPress (KeyPress::returnKey)); + pathChooserWindow.addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); pathChooserWindow.enterModalState (true, ModalCallbackFunction::forComponent (startScanCallback, @@ -455,6 +455,7 @@ class PluginListComponent::Scanner : private Timer std::atomic finished { false }; std::unique_ptr pool; std::set initiallyBlacklistedFiles; + ScopedMessageBox messageBox; static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) { @@ -472,22 +473,27 @@ class PluginListComponent::Scanner : private Timer { for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) { - auto f = pathList.getPath()[i]; + auto f = pathList.getPath().getRawString (i); - if (isStupidPath (f)) + if (File::isAbsolutePath (f) && isStupidPath (File (f))) { - AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, - TRANS("Plugin Scanning"), - TRANS("If you choose to scan folders that contain non-plugin files, " - "then scanning may take a long time, and can cause crashes when " - "attempting to load unsuitable files.") - + newLine - + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") - .replace ("XYZ", f.getFullPathName()), - TRANS ("Scan"), - String(), - nullptr, - ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); + auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon, + TRANS ("Plugin Scanning"), + TRANS ("If you choose to scan folders that contain non-plugin files, " + "then scanning may take a long time, and can cause crashes when " + "attempting to load unsuitable files.") + + newLine + + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") + .replace ("XYZ", f), + TRANS ("Scan")); + messageBox = AlertWindow::showScopedAsync (options, [this] (int result) + { + if (result != 0) + startScan(); + else + finishedScan(); + }); + return; } } @@ -524,14 +530,6 @@ class PluginListComponent::Scanner : private Timer return false; } - static void warnAboutStupidPathsCallback (int result, Scanner* scanner) - { - if (result != 0) - scanner->startScan(); - else - scanner->finishedScan(); - } - void startScan() { pathChooserWindow.setVisible (false); @@ -549,13 +547,13 @@ class PluginListComponent::Scanner : private Timer propertiesToUse->saveIfNeeded(); } - progressWindow.addButton (TRANS("Cancel"), 0, KeyPress (KeyPress::escapeKey)); + progressWindow.addButton (TRANS ("Cancel"), 0, KeyPress (KeyPress::escapeKey)); progressWindow.addProgressBarComponent (progress); progressWindow.enterModalState(); if (numThreads > 0) { - pool.reset (new ThreadPool (numThreads)); + pool.reset (new ThreadPool (ThreadPoolOptions{}.withNumberOfThreads (numThreads))); for (int i = numThreads; --i >= 0;) pool->addJob (new ScanJob (*this), true); @@ -599,7 +597,7 @@ class PluginListComponent::Scanner : private Timer if (finished) finishedScan(); else - progressWindow.setMessage (TRANS("Testing") + ":\n\n" + pluginBeingScanned); + progressWindow.setMessage (TRANS ("Testing") + ":\n\n" + pluginBeingScanned); } bool doNextScan() @@ -639,8 +637,8 @@ void PluginListComponent::scanFor (AudioPluginFormat& format) void PluginListComponent::scanFor (AudioPluginFormat& format, const StringArray& filesOrIdentifiersToScan) { currentScanner.reset (new Scanner (*this, format, filesOrIdentifiersToScan, propertiesToUse, allowAsync, numThreads, - dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), - dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files..."))); + dialogTitle.isNotEmpty() ? dialogTitle : TRANS ("Scanning for plug-ins..."), + dialogText.isNotEmpty() ? dialogText : TRANS ("Searching for all possible plug-in files..."))); } bool PluginListComponent::isScanning() const noexcept @@ -672,9 +670,12 @@ void PluginListComponent::scanFinished (const StringArray& failedFiles, currentScanner.reset(); // mustn't delete this before using the failed files array if (! warnings.isEmpty()) - AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon, - TRANS("Scan complete"), - warnings.joinIntoString ("\n\n")); + { + auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::InfoIcon, + TRANS ("Scan complete"), + warnings.joinIntoString ("\n\n")); + messageBox = AlertWindow::showScopedAsync (options, nullptr); + } } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h index 7de6eaa..9daf248 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h +++ b/JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h @@ -130,6 +130,8 @@ class JUCE_API PluginListComponent : public Component, class Scanner; std::unique_ptr currentScanner; + ScopedMessageBox messageBox; + void scanFinished (const StringArray&, const std::vector&); void updateList(); void removeMissingPlugins(); diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp index 7bd5c93..55cd57a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.cpp @@ -114,6 +114,9 @@ class ARADocumentControllerSpecialisation::ARADocumentControllerImpl : public A ARA::PlugIn::ContentReader* doCreatePlaybackRegionContentReader (ARA::PlugIn::PlaybackRegion* playbackRegion, ARA::ARAContentType type, const ARA::ARAContentTimeRange* range) noexcept override; + void doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARATimeDuration* headTime, + ARA::ARATimeDuration* tailTime) noexcept override; //============================================================================== // ARAAudioSource analysis @@ -659,6 +662,13 @@ ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::ARADocumentCont return specialisation->doCreatePlaybackRegionContentReader (playbackRegion, type, range); } +void ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARATimeDuration* headTime, + ARA::ARATimeDuration* tailTime) noexcept +{ + specialisation->doGetPlaybackRegionHeadAndTailTime (playbackRegion, headTime, tailTime); +} + bool ARADocumentControllerSpecialisation::ARADocumentControllerImpl::doIsAudioSourceContentAnalysisIncomplete (const ARA::PlugIn::AudioSource* audioSource, ARA::ARAContentType type) noexcept { @@ -915,6 +925,14 @@ ARA::PlugIn::ContentReader* ARADocumentControllerSpecialisation::doCreatePlaybac return nullptr; } +void ARADocumentControllerSpecialisation::doGetPlaybackRegionHeadAndTailTime ([[maybe_unused]] const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARATimeDuration* headTime, + ARA::ARATimeDuration* tailTime) +{ + *headTime = 0.0; + *tailTime = 0.0; +} + bool ARADocumentControllerSpecialisation::doIsAudioSourceContentAnalysisIncomplete ([[maybe_unused]] const ARA::PlugIn::AudioSource* audioSource, [[maybe_unused]] ARA::ARAContentType type) { diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h index 4263573..c857c8e 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h @@ -282,6 +282,15 @@ class ARADocumentControllerSpecialisation : public ARADocument::Listener, ARA::ARAContentType type, const ARA::ARAContentTimeRange* range); + /** Override to implement getPlaybackRegionHeadAndTailTime(). + + This function is called within + ARA::PlugIn::DocumentControllerDelegate::doGetPlaybackRegionHeadAndTailTime. + */ + virtual void doGetPlaybackRegionHeadAndTailTime (const ARA::PlugIn::PlaybackRegion* playbackRegion, + ARA::ARATimeDuration* headTime, + ARA::ARATimeDuration* tailTime); + //============================================================================== // ARAAudioSource analysis diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp index e081699..3570ee6 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.cpp @@ -91,7 +91,7 @@ double ARARegionSequence::getCommonSampleRate() const const auto range = getPlaybackRegions(); const auto sampleRate = range.size() > 0 ? getSampleRate (range.front()) : 0.0; - if (std::any_of (range.begin(), range.end(), [&] (auto& x) { return getSampleRate (x) != sampleRate; })) + if (std::any_of (range.begin(), range.end(), [&] (auto& x) { return ! exactlyEqual (getSampleRate (x), sampleRate); })) return 0.0; return sampleRate; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h index c2cd072..f606ec5 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/ARA/juce_ARA_utils.h @@ -27,7 +27,8 @@ // Include ARA SDK headers JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wgnu-zero-variadic-macro-arguments", - "-Wunused-parameter") + "-Wunused-parameter", + "-Wfloat-equal") JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387) #include diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp new file mode 100644 index 0000000..61f37d6 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.cpp @@ -0,0 +1,299 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class AAXPluginId +{ +public: + static std::optional create (std::array letters) + { + std::array indices{}; + + for (size_t i = 0; i < letters.size(); ++i) + { + if (const auto index = findIndexOfChar (letters[i])) + indices[i] = *index; + else + return std::nullopt; + } + + return AAXPluginId { std::move (indices) }; + } + + std::optional withIncrementedLetter (size_t index, size_t increment) const + { + if (indices.size() <= index) + return std::nullopt; + + auto copy = *this; + copy.indices[index] += increment; + + if ((size_t) std::size (validChars) <= copy.indices[index]) + return std::nullopt; + + return copy; + } + + int32 getPluginId() const + { + int32 result = 0; + + for (size_t i = 0; i < indices.size(); ++i) + result |= static_cast (validChars[indices[i]]) << (8 * (indices.size() - 1 - i)); + + return result; + } + + static std::optional findIndexOfChar (char c) + { + const auto ptr = std::find (std::begin (validChars), std::end (validChars), c); + return ptr != std::end (validChars) ? std::make_optional (std::distance (std::begin (validChars), ptr)) + : std::nullopt; + } + +private: + static inline constexpr char validChars[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + explicit AAXPluginId (std::array indicesIn) + : indices (std::move (indicesIn)) + {} + + std::array indices; +}; + +static const AudioChannelSet channelSets[] { AudioChannelSet::disabled(), + AudioChannelSet::mono(), + AudioChannelSet::stereo(), + AudioChannelSet::createLCR(), + AudioChannelSet::createLCRS(), + AudioChannelSet::quadraphonic(), + AudioChannelSet::create5point0(), + AudioChannelSet::create5point1(), + AudioChannelSet::create6point0(), + AudioChannelSet::create6point1(), + AudioChannelSet::create7point0(), + AudioChannelSet::create7point1(), + AudioChannelSet::create7point0SDDS(), + AudioChannelSet::create7point1SDDS(), + AudioChannelSet::create7point0point2(), + AudioChannelSet::create7point1point2(), + AudioChannelSet::ambisonic (1), + AudioChannelSet::ambisonic (2), + AudioChannelSet::ambisonic (3), + AudioChannelSet::create5point0point2(), + AudioChannelSet::create5point1point2(), + AudioChannelSet::create5point0point4(), + AudioChannelSet::create5point1point4(), + AudioChannelSet::create7point0point4(), + AudioChannelSet::create7point1point4(), + AudioChannelSet::create7point0point6(), + AudioChannelSet::create7point1point6(), + AudioChannelSet::create9point0point4(), + AudioChannelSet::create9point1point4(), + AudioChannelSet::create9point0point6(), + AudioChannelSet::create9point1point6(), + AudioChannelSet::ambisonic (4), + AudioChannelSet::ambisonic (5), + AudioChannelSet::ambisonic (6), + AudioChannelSet::ambisonic (7) }; + +int32 AAXClientExtensions::getPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + bool idForAudioSuite) const +{ + auto pluginId = [&] + { + auto opt = idForAudioSuite ? AAXPluginId::create ({ 'j', 'y', 'a', 'a' }) + : AAXPluginId::create ({ 'j', 'c', 'a', 'a' }); + jassert (opt.has_value()); + return *opt; + }(); + + for (const auto& [channelSet, indexToModify] : { std::tuple (&mainInputLayout, (size_t) 2), + std::tuple (&mainOutputLayout, (size_t) 3) }) + { + const auto increment = (size_t) std::distance (std::begin (channelSets), + std::find (std::begin (channelSets), + std::end (channelSets), + *channelSet)); + + if (auto modifiedPluginIdOpt = pluginId.withIncrementedLetter (indexToModify, increment); + increment < (size_t) std::size (channelSets) && modifiedPluginIdOpt.has_value()) + { + pluginId = *modifiedPluginIdOpt; + } + else + { + jassertfalse; + } + } + + return pluginId.getPluginId(); +} + +String AAXClientExtensions::getPageFileName() const +{ + #ifdef JucePlugin_AAXPageTableFile + JUCE_COMPILER_WARNING ("JucePlugin_AAXPageTableFile is deprecated, instead implement AAXClientExtensions::getPageFileName()") + return JucePlugin_AAXPageTableFile; + #else + return {}; + #endif +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +static std::array toCharArray (int32 identifier) +{ + std::array result; + + for (size_t i = 0; i < result.size(); ++i) + result[i] = static_cast ((identifier >> (i * 8)) & 0xff); + + return result; +} + +static bool isValidAAXPluginId (int32 pluginId) +{ + const auto chars = toCharArray (pluginId); + + return std::all_of (std::begin (chars), + std::end (chars), + [] (const auto& c) + { + return AAXPluginId::findIndexOfChar (c).has_value(); + }); +} + +static int32 getPluginIDForMainBusConfigJuce705 (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + bool idForAudioSuite) +{ + int uniqueFormatId = 0; + + for (int dir = 0; dir < 2; ++dir) + { + const bool isInput = (dir == 0); + auto& set = (isInput ? mainInputLayout : mainOutputLayout); + int aaxFormatIndex = 0; + + const AudioChannelSet sets[] + { + AudioChannelSet::disabled(), + AudioChannelSet::mono(), + AudioChannelSet::stereo(), + AudioChannelSet::createLCR(), + AudioChannelSet::createLCRS(), + AudioChannelSet::quadraphonic(), + AudioChannelSet::create5point0(), + AudioChannelSet::create5point1(), + AudioChannelSet::create6point0(), + AudioChannelSet::create6point1(), + AudioChannelSet::create7point0(), + AudioChannelSet::create7point1(), + AudioChannelSet::create7point0SDDS(), + AudioChannelSet::create7point1SDDS(), + AudioChannelSet::create7point0point2(), + AudioChannelSet::create7point1point2(), + AudioChannelSet::ambisonic (1), + AudioChannelSet::ambisonic (2), + AudioChannelSet::ambisonic (3), + AudioChannelSet::create5point0point2(), + AudioChannelSet::create5point1point2(), + AudioChannelSet::create5point0point4(), + AudioChannelSet::create5point1point4(), + AudioChannelSet::create7point0point4(), + AudioChannelSet::create7point1point4(), + AudioChannelSet::create7point0point6(), + AudioChannelSet::create7point1point6(), + AudioChannelSet::create9point0point4(), + AudioChannelSet::create9point1point4(), + AudioChannelSet::create9point0point6(), + AudioChannelSet::create9point1point6(), + AudioChannelSet::ambisonic (4), + AudioChannelSet::ambisonic (5), + AudioChannelSet::ambisonic (6), + AudioChannelSet::ambisonic (7) + }; + + const auto index = (int) std::distance (std::begin (sets), std::find (std::begin (sets), std::end (sets), set)); + + if (index != (int) std::size (sets)) + aaxFormatIndex = index; + else + jassertfalse; + + uniqueFormatId = (uniqueFormatId << 8) | aaxFormatIndex; + } + + return (idForAudioSuite ? 0x6a796161 /* 'jyaa' */ : 0x6a636161 /* 'jcaa' */) + uniqueFormatId; +} + +class AAXClientExtensionsTests : public UnitTest +{ +public: + AAXClientExtensionsTests() + : UnitTest ("AAXClientExtensionsTests", UnitTestCategories::audioProcessors) + {} + + void runTest() override + { + AAXClientExtensions extensions; + + beginTest ("Previously valid PluginIds should be unchanged"); + { + for (const auto& input : channelSets) + for (const auto& output : channelSets) + for (const auto idForAudioSuite : { false, true }) + if (const auto oldId = getPluginIDForMainBusConfigJuce705 (input, output, idForAudioSuite); isValidAAXPluginId (oldId)) + expect (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite) == oldId); + } + + beginTest ("Valid, unique PluginIds should be generated for all configurations"); + { + std::set pluginIds; + + for (const auto& input : channelSets) + for (const auto& output : channelSets) + for (const auto idForAudioSuite : { false, true }) + pluginIds.insert (extensions.getPluginIDForMainBusConfig (input, output, idForAudioSuite)); + + for (auto identifier : pluginIds) + expect (isValidAAXPluginId (identifier)); + + expect (pluginIds.size() == square (std::size (channelSets)) * 2); + } + } +}; + +static AAXClientExtensionsTests aaxClientExtensionsTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.h new file mode 100644 index 0000000..ce83cc5 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AAXClientExtensions.h @@ -0,0 +1,83 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** + An interface to allow an AudioProcessor to implement extended AAX-specific functionality. + + To use this class, create an object that inherits from it, implement the methods, then return + a pointer to the object in your AudioProcessor `getAAXClientExtensions()` method. + + @see AudioProcessor, VST2ClientExtensions, VST3ClientExtensions + + @tags{Audio} +*/ +struct AAXClientExtensions +{ + virtual ~AAXClientExtensions() = default; + + /** AAX plug-ins need to report a unique "plug-in id" for every audio layout + configuration that your AudioProcessor supports on the main bus. Override this + function if you want your AudioProcessor to use a custom "plug-in id" (for example + to stay backward compatible with older versions of JUCE). + + The default implementation will compute a unique integer from the input and output + layout and add this value to the 4 character code 'jcaa' (for native AAX) or 'jyaa' + (for AudioSuite plug-ins). + */ + virtual int32 getPluginIDForMainBusConfig (const AudioChannelSet& mainInputLayout, + const AudioChannelSet& mainOutputLayout, + bool idForAudioSuite) const; + + /** Returns an optional filename (including extension) for a page file to be used. + + A page file allows an AAX plugin to specify how its parameters are displayed on + various control surfaces. For more information read the Page Table Guide in the + AAX SDK documentation. + + By default this file will be searched for in `*.aaxplugin/Contents/Resources`. + + @see getPageFileSearchPath + */ + virtual String getPageFileName() const; + + /** Optionally returns a search path for finding a page table file. + + This can be useful for specifying a location outside the plugin bundle so users can + make changes to a page table file without breaking any code signatures. + + If this function returns a default-constructed File, then a default location will be used. + The AAX SDK states this location will be `*.aaxplugin/Contents/Resources`. + + @note The returned path should be an absolute path to a directory. + + @see getPageFileName + */ + virtual File getPageFileSearchPath() const { return {}; } +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h index 0e47843..4f95b95 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterBool.h @@ -29,6 +29,8 @@ namespace juce /** Properties of an AudioParameterBool. @see AudioParameterBool(), RangedAudioParameterAttributes() + + @tags{Audio} */ class AudioParameterBoolAttributes : public RangedAudioParameterAttributes {}; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h index 1878d2b..0fdbacc 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterChoice.h @@ -29,6 +29,8 @@ namespace juce /** Properties of an AudioParameterChoice. @see AudioParameterChoice(), RangedAudioParameterAttributes() + + @tags{Audio} */ class AudioParameterChoiceAttributes : public RangedAudioParameterAttributes {}; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp index 78e2a72..d65d364 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.cpp @@ -44,7 +44,7 @@ AudioParameterFloat::AudioParameterFloat (const ParameterID& idToUse, { int numDecimalPlaces = 7; - if (range.interval != 0.0f) + if (! approximatelyEqual (range.interval, 0.0f)) { if (approximatelyEqual (std::abs (range.interval - std::floor (range.interval)), 0.0f)) return 0; @@ -95,7 +95,7 @@ void AudioParameterFloat::valueChanged (float) {} AudioParameterFloat& AudioParameterFloat::operator= (float newValue) { - if (value != newValue) + if (! approximatelyEqual ((float) value, newValue)) setValueNotifyingHost (convertTo0to1 (newValue)); return *this; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h index 64b54b6..3efb0c1 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterFloat.h @@ -29,6 +29,8 @@ namespace juce /** Properties of an AudioParameterFloat. @see AudioParameterFloat(), RangedAudioParameterAttributes() + + @tags{Audio} */ class AudioParameterFloatAttributes : public RangedAudioParameterAttributes {}; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h index 91708a0..6ef680e 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioParameterInt.h @@ -29,6 +29,8 @@ namespace juce /** Properties of an AudioParameterInt. @see AudioParameterInt(), RangedAudioParameterAttributes() + + @tags{Audio} */ class AudioParameterIntAttributes : public RangedAudioParameterAttributes {}; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h index 19df6bf..85e273a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorParameterWithID.h @@ -28,6 +28,8 @@ namespace juce /** Combines a parameter ID and a version hint. + + @tags{Audio} */ class ParameterID { @@ -62,6 +64,8 @@ class ParameterID /** An instance of this class may be passed to the constructor of an AudioProcessorParameterWithID to set optional characteristics of that parameter. + + @tags{Audio} */ class AudioProcessorParameterWithIDAttributes { diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index 798ccc9..a59634d 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -52,13 +52,11 @@ bool AudioProcessorValueTreeState::Parameter::isBoolean() const { return void AudioProcessorValueTreeState::Parameter::valueChanged (float newValue) { - if (lastValue == newValue) + if (approximatelyEqual ((float) lastValue, newValue)) return; lastValue = newValue; - - if (onValueChanged != nullptr) - onValueChanged(); + NullCheckedInvocation::invoke (onValueChanged); } //============================================================================== @@ -93,10 +91,8 @@ class AudioProcessorValueTreeState::ParameterAdapter : private AudioProcessorP void setDenormalisedValue (float value) { - if (value == unnormalisedValue) - return; - - setNormalisedValue (normalise (value)); + if (! approximatelyEqual (value, (float) unnormalisedValue)) + setNormalisedValue (normalise (value)); } float getDenormalisedValueForText (const String& text) const @@ -119,9 +115,9 @@ class AudioProcessorValueTreeState::ParameterAdapter : private AudioProcessorP if (! needsUpdate.compare_exchange_strong (needsUpdateTestValue, false)) return false; - if (auto valueProperty = tree.getPropertyPointer (key)) + if (auto* valueProperty = tree.getPropertyPointer (key)) { - if ((float) *valueProperty != unnormalisedValue) + if (! approximatelyEqual ((float) *valueProperty, unnormalisedValue.load())) { ScopedValueSetter svs (ignoreParameterChangedCallbacks, true); tree.setProperty (key, unnormalisedValue.load(), um); @@ -144,7 +140,7 @@ class AudioProcessorValueTreeState::ParameterAdapter : private AudioProcessorP { const auto newValue = denormalise (parameter.getValue()); - if (unnormalisedValue == newValue && ! listenersNeedCalling) + if (! listenersNeedCalling && approximatelyEqual ((float) unnormalisedValue, newValue)) return; unnormalisedValue = newValue; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h index 835103e..0550547 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.h @@ -32,6 +32,8 @@ namespace juce AudioParameterFloatAttributes. @see AudioParameterFloatAttributes, RangedAudioParameterAttributes + + @tags{Audio} */ class AudioProcessorValueTreeStateParameterAttributes { diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_NativeScaleFactorNotifier.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_NativeScaleFactorNotifier.h index 037db8d..cbff09d 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_NativeScaleFactorNotifier.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_NativeScaleFactorNotifier.h @@ -31,6 +31,8 @@ namespace juce This is used in the VST and VST3 wrappers to ensure that the editor's scale is kept in sync with the scale of its containing component. + + @tags{GUI} */ class NativeScaleFactorNotifier : private ComponentMovementWatcher, private ComponentPeer::ScaleFactorListener diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp index ebc10ae..e741d79 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp @@ -84,7 +84,7 @@ void ParameterAttachment::callIfParameterValueChanged (float newDenormalisedValu { const auto newValue = normalise (newDenormalisedValue); - if (parameter.getValue() != newValue) + if (! approximatelyEqual (parameter.getValue(), newValue)) callback (newValue); } diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.cpp index fec80b5..4345599 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.cpp @@ -119,6 +119,7 @@ const char* PluginHostType::getHostDescription() const noexcept case pluginval: return "pluginval"; case MergingPyramix: return "Pyramix"; case MuseReceptorGeneric: return "Muse Receptor"; + case Maschine: return "NI Maschine"; case Reaper: return "Reaper"; case Reason: return "Reason"; case Renoise: return "Renoise"; @@ -213,6 +214,7 @@ PluginHostType::HostType PluginHostType::getHostType() if (hostFilename.containsIgnoreCase ("OsxFL")) return FruityLoops; if (hostFilename.containsIgnoreCase ("pluginval")) return pluginval; if (hostFilename.containsIgnoreCase ("AudioPluginHost")) return JUCEPluginHost; + if (hostFilename.containsIgnoreCase ("Maschine")) return Maschine; if (hostFilename.containsIgnoreCase ("Vienna Ensemble Pro")) return ViennaEnsemblePro; if (hostFilename.containsIgnoreCase ("auvaltool")) return AUVal; if (hostFilename.containsIgnoreCase ("com.apple.audio.infohelper")) return AppleInfoHelper; @@ -280,6 +282,7 @@ PluginHostType::HostType PluginHostType::getHostType() if (hostFilename.containsIgnoreCase ("Wavelab")) return SteinbergWavelabGeneric; if (hostFilename.containsIgnoreCase ("TestHost")) return SteinbergTestHost; if (hostFilename.containsIgnoreCase ("rm-host")) return MuseReceptorGeneric; + if (hostFilename.containsIgnoreCase ("Maschine")) return Maschine; if (hostFilename.startsWith ("FL")) return FruityLoops; if (hostFilename.contains ("ilbridge.")) return FruityLoops; if (hostPath.containsIgnoreCase ("Studio One")) return StudioOne; diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.h index d1bdea6..5702045 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_PluginHostType.h @@ -78,6 +78,7 @@ class PluginHostType MagixSequoia, /**< Represents Magix Sequoia. */ MergingPyramix, /**< Represents Merging Pyramix. */ MuseReceptorGeneric, /**< Represents Muse Receptor. */ + Maschine, /**< Represents Native Instruments Maschine. */ pluginval, /**< Represents pluginval. */ Reaper, /**< Represents Cockos Reaper. */ Reason, /**< Represents Reason. */ @@ -203,6 +204,8 @@ class PluginHostType bool isWavelab() const noexcept { return isWavelabLegacy() || type == SteinbergWavelab7 || type == SteinbergWavelab8 || type == SteinbergWavelabGeneric; } /** Returns true if the host is Steinberg WaveLab 6 or below. */ bool isWavelabLegacy() const noexcept { return type == SteinbergWavelab5 || type == SteinbergWavelab6; } + /** Returns true if the host is Native Instruments Maschine. */ + bool isMaschine() const noexcept { return type == Maschine; } //============================================================================== /** Returns a human-readable description of the host. */ diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h index 4d4e9c4..6c2c0d3 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_RangedAudioParameter.h @@ -36,6 +36,8 @@ namespace juce i.e. the identifiers AudioParameterFloatAttributes and RangedAudioParameterAttributes should not be interchangable because we might need to add float-specific attributes in the future. Users should not refer directly to RangedAudioParameterAttributes. + + @tags{Audio} */ template class RangedAudioParameterAttributes diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.cpp b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp similarity index 64% rename from JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.cpp rename to JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp index b84f81f..8501d3d 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.cpp +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.cpp @@ -26,14 +26,14 @@ namespace juce { -pointer_sized_int VSTCallbackHandler::handleVstPluginCanDo ([[maybe_unused]] int32 index, - [[maybe_unused]] pointer_sized_int value, - [[maybe_unused]] void* ptr, - [[maybe_unused]] float opt) +pointer_sized_int VST2ClientExtensions::handleVstPluginCanDo ([[maybe_unused]] int32 index, + [[maybe_unused]] pointer_sized_int value, + [[maybe_unused]] void* ptr, + [[maybe_unused]] float opt) { return 0; } -void VSTCallbackHandler::handleVstHostCallbackAvailable ([[maybe_unused]] std::function&& callback) {} +void VST2ClientExtensions::handleVstHostCallbackAvailable ([[maybe_unused]] std::function&& callback) {} } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h similarity index 70% rename from JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h rename to JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h index 52f861e..98bbe0a 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VSTCallbackHandler.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST2ClientExtensions.h @@ -26,19 +26,19 @@ namespace juce { -/** An interface to allow an AudioProcessor to send and receive VST specific calls from - the host. +/** + An interface to allow an AudioProcessor to implement extended VST2-specific functionality. - To use this class, ensure that your AudioProcessor publicly inherits - from VSTCallbackHandler. + To use this class, create an object that inherits from it, implement the methods, then return + a pointer to the object in your AudioProcessor::getVST2ClientExtensions() method. - @see VST3ClientExtensions + @see AudioProcessor, AAXClientExtensions, VST3ClientExtensions @tags{Audio} */ -struct VSTCallbackHandler +struct VST2ClientExtensions { - virtual ~VSTCallbackHandler() = default; + virtual ~VST2ClientExtensions() = default; /** This is called by the VST plug-in wrapper when it receives unhandled plug-in "can do" calls from the host. @@ -56,13 +56,12 @@ struct VSTCallbackHandler void* ptr, float opt) = 0; - // Note: VS2013 prevents a "using" declaration here /** The host callback function type. */ - typedef pointer_sized_int (VstHostCallbackType) (int32 opcode, - int32 index, - pointer_sized_int value, - void* ptr, - float opt); + using VstHostCallbackType = pointer_sized_int (int32 opcode, + int32 index, + pointer_sized_int value, + void* ptr, + float opt); /** This is called once by the VST plug-in wrapper after its constructor. You can use the supplied function to query the VST host. @@ -70,4 +69,6 @@ struct VSTCallbackHandler virtual void handleVstHostCallbackAvailable (std::function&& callback); }; +using VSTCallbackHandler [[deprecated ("replace with VST2ClientExtensions")]] = VST2ClientExtensions; + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h index f86c74b..2e3345b 100644 --- a/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h +++ b/JuceLibraryCode/modules/juce_audio_processors/utilities/juce_VST3ClientExtensions.h @@ -37,13 +37,13 @@ namespace Steinberg namespace juce { -/** An interface to allow an AudioProcessor to implement extended VST3-specific - functionality. +/** + An interface to allow an AudioProcessor to implement extended VST3-specific functionality. - To use this class, ensure that your AudioProcessor publicly inherits - from VST3ClientExtensions. + To use this class, create an object that inherits from it, implement the methods, then return + a pointer to the object in your AudioProcessor::getVST3ClientExtensions() method. - @see VSTCallbackHandler + @see AudioProcessor, AAXClientExtensions, VST2ClientExtensions @tags{Audio} */ @@ -94,6 +94,17 @@ struct VST3ClientExtensions All other input buses will always be designated kAux. */ virtual bool getPluginHasMainInput() const { return true; } + + /** This function should return the UIDs of any compatible VST2 plug-ins. + + Each item in the vector should be a 32-character string consisting only + of the characters 0-9 and A-F. + + This information will be used to implement the IPluginCompatibility + interface. Hosts can use this interface to determine whether this VST3 + is capable of replacing a given VST2. + */ + virtual std::vector getCompatibleClasses() const { return {}; } }; } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp index b04784e..b10240f 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp @@ -401,9 +401,11 @@ class AudioDeviceSettingsPanel : public Component, } if (error.isNotEmpty()) - AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon, - TRANS("Error when trying to open audio device!"), - error); + messageBox = AlertWindow::showScopedAsync (MessageBoxOptions().withIconType (MessageBoxIconType::WarningIcon) + .withTitle (TRANS ("Error when trying to open audio device!")) + .withMessage (error) + .withButton (TRANS ("OK")), + nullptr); } bool showDeviceControlPanel() @@ -720,7 +722,7 @@ class AudioDeviceSettingsPanel : public Component, auto currentRate = currentDevice->getCurrentSampleRate(); - if (currentRate == 0) + if (exactlyEqual (currentRate, 0.0)) currentRate = 48000.0; for (auto bs : currentDevice->getAvailableBufferSizes()) @@ -966,6 +968,7 @@ class AudioDeviceSettingsPanel : public Component, private: std::unique_ptr inputChanList, outputChanList; + ScopedMessageBox messageBox; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceSettingsPanel) }; diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp index 997a835..e130def 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp @@ -501,8 +501,8 @@ class AudioThumbnail::CachedWindow if (numSamples == numSamplesCached && numChannelsCached == numChans - && startTime == cachedStart - && timePerPixel == cachedTimePerPixel + && approximatelyEqual (startTime, cachedStart) + && approximatelyEqual (timePerPixel, cachedTimePerPixel) && ! cacheNeedsRefilling) { return ! cacheNeedsRefilling; diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp index 98eb38b..b24c3e2 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_KeyboardComponentBase.cpp @@ -79,7 +79,7 @@ void KeyboardComponentBase::setKeyWidth (float widthInPixels) { jassert (widthInPixels > 0); - if (keyWidth != widthInPixels) // Prevent infinite recursion if the width is being computed in a 'resized()' callback + if (! approximatelyEqual (keyWidth, widthInPixels)) // Prevent infinite recursion if the width is being computed in a 'resized()' callback { keyWidth = widthInPixels; resized(); @@ -130,7 +130,7 @@ void KeyboardComponentBase::setLowestVisibleKeyFloat (float noteNumber) { noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber); - if (noteNumber != firstKey) + if (! approximatelyEqual (noteNumber, firstKey)) { bool hasMoved = (((int) firstKey) != (int) noteNumber); firstKey = noteNumber; @@ -151,7 +151,7 @@ void KeyboardComponentBase::setBlackNoteLengthProportion (float ratio) noexcept { jassert (ratio >= 0.0f && ratio <= 1.0f); - if (blackNoteLengthRatio != ratio) + if (! approximatelyEqual (blackNoteLengthRatio, ratio)) { blackNoteLengthRatio = ratio; resized(); @@ -168,7 +168,7 @@ void KeyboardComponentBase::setBlackNoteWidthProportion (float ratio) noexcept { jassert (ratio >= 0.0f && ratio <= 1.0f); - if (blackNoteWidthRatio != ratio) + if (! approximatelyEqual (blackNoteWidthRatio, ratio)) { blackNoteWidthRatio = ratio; resized(); @@ -452,7 +452,7 @@ void KeyboardComponentBase::resized() //============================================================================== void KeyboardComponentBase::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) { - auto amount = (orientation == horizontalKeyboard && wheel.deltaX != 0) + auto amount = (orientation == horizontalKeyboard && ! approximatelyEqual (wheel.deltaX, 0.0f)) ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY : -wheel.deltaY); diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp index 406f3e3..db54160 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.cpp @@ -59,7 +59,7 @@ struct MPEKeyboardComponent::MPENoteComponent : public Component g.drawEllipse (bounds.withSizeKeepingCentre (pressSize, pressSize), 1.0f); } - //========================================================================== + //============================================================================== MPEKeyboardComponent& owner; float radiusScale = 0.0f, noteOnVelocity = 0.0f, pressure = 0.5f; @@ -145,7 +145,7 @@ void MPEKeyboardComponent::colourChanged() repaint(); } -//========================================================================== +//============================================================================== MPEValue MPEKeyboardComponent::mousePositionToPitchbend (int initialNote, Point mousePos) { auto constrainedMousePos = [&] diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h index 22a28ff..5e58a93 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MPEKeyboardComponent.h @@ -100,7 +100,7 @@ class JUCE_API MPEKeyboardComponent : public KeyboardComponentBase, void colourChanged() override; private: - //========================================================================== + //============================================================================== struct MPENoteComponent; //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp index 1345b20..b30d2aa 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp @@ -32,11 +32,11 @@ MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& stateToUse, Ori { state.addListener (this); - // initialise with a default set of qwerty key-mappings.. - int note = 0; + // initialise with a default set of qwerty key-mappings. + const std::string_view keys { "awsedftgyhujkolp;" }; - for (char c : "awsedftgyhujkolp;") - setKeyPressForNote ({ c, 0, 0 }, note++); + for (const char& c : keys) + setKeyPressForNote ({c, 0, 0}, (int) std::distance (keys.data(), &c)); mouseOverNotes.insertMultiple (0, -1, 32); mouseDownNotes.insertMultiple (0, -1, 32); diff --git a/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.cpp b/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.cpp index bdbf765..b4a9aac 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.cpp @@ -67,38 +67,38 @@ #include "audio_cd/juce_AudioCDReader.cpp" #if JUCE_MAC - #include "native/juce_mac_BluetoothMidiDevicePairingDialogue.mm" + #include "native/juce_BluetoothMidiDevicePairingDialogue_mac.mm" #if JUCE_USE_CDREADER - #include "native/juce_mac_AudioCDReader.mm" + #include "native/juce_AudioCDReader_mac.mm" #endif #if JUCE_USE_CDBURNER - #include "native/juce_mac_AudioCDBurner.mm" + #include "native/juce_AudioCDBurner_mac.mm" #endif #elif JUCE_IOS - #include "native/juce_ios_BluetoothMidiDevicePairingDialogue.mm" + #include "native/juce_BluetoothMidiDevicePairingDialogue_ios.mm" #elif JUCE_ANDROID - #include "native/juce_android_BluetoothMidiDevicePairingDialogue.cpp" + #include "native/juce_BluetoothMidiDevicePairingDialogue_android.cpp" #elif JUCE_LINUX || JUCE_BSD #if JUCE_USE_CDREADER - #include "native/juce_linux_AudioCDReader.cpp" + #include "native/juce_AudioCDReader_linux.cpp" #endif - #include "native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp" + #include "native/juce_BluetoothMidiDevicePairingDialogue_linux.cpp" #elif JUCE_WINDOWS - #include "native/juce_win_BluetoothMidiDevicePairingDialogue.cpp" + #include "native/juce_BluetoothMidiDevicePairingDialogue_windows.cpp" #if JUCE_USE_CDREADER - #include "native/juce_win32_AudioCDReader.cpp" + #include "native/juce_AudioCDReader_windows.cpp" #endif #if JUCE_USE_CDBURNER - #include "native/juce_win32_AudioCDBurner.cpp" + #include "native/juce_AudioCDBurner_windows.cpp" #endif #endif diff --git a/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.h b/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.h index 77cd317..9be31f5 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.h +++ b/JuceLibraryCode/modules/juce_audio_utils/juce_audio_utils.h @@ -35,7 +35,7 @@ ID: juce_audio_utils vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE extra audio utility classes description: Classes for audio-related GUI and miscellaneous tasks. website: http://www.juce.com/juce diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm b/JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDBurner_mac.mm similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDBurner_mac.mm diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDBurner_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDBurner_windows.cpp diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_linux.cpp diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm b/JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_mac.mm similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_mac.mm diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_AudioCDReader_windows.cpp diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_android.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_android.cpp index 6d0798c..778711c 100644 --- a/JuceLibraryCode/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp +++ b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_android.cpp @@ -27,7 +27,7 @@ namespace juce { #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23) #undef JNI_CLASS_MEMBERS @@ -40,7 +40,7 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiS METHOD (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \ METHOD (startStopScan, "startStopScan", "(Z)V") -DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothManager", 23) +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothMidiManager", 23) #undef JNI_CLASS_MEMBERS //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_ios.mm similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_ios.mm diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_linux.cpp diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_mac.mm similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_mac.mm diff --git a/JuceLibraryCode/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp b/JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp rename to JuceLibraryCode/modules/juce_audio_utils/native/juce_BluetoothMidiDevicePairingDialogue_windows.cpp diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Array.h b/JuceLibraryCode/modules/juce_core/containers/juce_Array.h index fa3dfe3..002d362 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_Array.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Array.h @@ -386,7 +386,7 @@ class Array auto endPtr = values.end(); for (; e != endPtr; ++e) - if (elementToLookFor == *e) + if (exactlyEqual (elementToLookFor, *e)) return static_cast (e - values.begin()); return -1; @@ -404,7 +404,7 @@ class Array auto endPtr = values.end(); for (; e != endPtr; ++e) - if (elementToLookFor == *e) + if (exactlyEqual (elementToLookFor, *e)) return true; return false; diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.h b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.h index 186ad1d..06e85a1 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.h @@ -123,7 +123,7 @@ class ArrayBase : public TypeOfCriticalSectionToUse auto* e = begin(); for (auto& o : other) - if (! (*e++ == o)) + if (! exactlyEqual (*e++, o)) return false; return true; diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ListenerList.h b/JuceLibraryCode/modules/juce_core/containers/juce_ListenerList.h index 99ea14a..c10652c 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_ListenerList.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ListenerList.h @@ -325,7 +325,10 @@ class ListenerList WrappedIterator (const ListenerList& listToIterate, WrappedIterator*& listHeadIn) : it (listToIterate), listHead (listHeadIn), next (listHead) { + // GCC 12.2 with O1 and above gets confused here + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdangling-pointer") listHead = this; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } ~WrappedIterator() diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Optional.h b/JuceLibraryCode/modules/juce_core/containers/juce_Optional.h index ca38a9a..c6764c4 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_Optional.h +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Optional.h @@ -30,7 +30,9 @@ constexpr auto nullopt = std::nullopt; // link time code generation. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) +#ifndef DOXYGEN #define JUCE_OPTIONAL_OPERATORS X(==) X(!=) X(<) X(<=) X(>) X(>=) +#endif /** A simple optional type. @@ -161,6 +163,7 @@ Optional> makeOptional (Value&& v) return std::forward (v); } +#ifndef DOXYGEN #define X(op) \ template bool operator op (const Optional& lhs, const Optional& rhs) { return lhs.opt op rhs.opt; } \ template bool operator op (const Optional& lhs, Nullopt rhs) { return lhs.opt op rhs; } \ @@ -171,7 +174,7 @@ Optional> makeOptional (Value&& v) JUCE_OPTIONAL_OPERATORS #undef X - #undef JUCE_OPTIONAL_OPERATORS +#endif } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Span.h b/JuceLibraryCode/modules/juce_core/containers/juce_Span.h new file mode 100644 index 0000000..ec642bf --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Span.h @@ -0,0 +1,150 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +inline constexpr auto dynamicExtent = std::numeric_limits::max(); + +namespace detail +{ + //============================================================================== + template + constexpr auto hasToAddress = false; + + template + constexpr auto hasToAddress::to_address (std::declval()))>> = true; + + template + constexpr auto hasDataAndSize = false; + + template + constexpr auto hasDataAndSize())), + decltype (std::size (std::declval()))>> = true; + + template + struct NumBase + { + constexpr NumBase() = default; + + constexpr explicit NumBase (size_t) {} + + constexpr size_t size() const { return Extent; } + }; + + template <> + struct NumBase + { + constexpr NumBase() = default; + + constexpr explicit NumBase (size_t arg) + : num (arg) {} + + constexpr size_t size() const { return num; } + + size_t num{}; + }; + + template + constexpr T* toAddress (T* p) + { + return p; + } + + template + constexpr auto toAddress (const It& it) + { + if constexpr (detail::hasToAddress) + return std::pointer_traits::to_address (it); + else + return toAddress (it.operator->()); + } +} + +//============================================================================== +/** + A non-owning view over contiguous objects stored in an Array or vector + or other similar container. + + This is a bit like std::span from C++20, but with a more limited interface. + + @tags{Core} +*/ +template +class Span : private detail::NumBase // for empty-base optimisation +{ + using Base = detail::NumBase; + +public: + static constexpr auto extent = Extent; + + template = 0> + constexpr Span() {} + + template + constexpr Span (It it, size_t end) + : Base (end), ptr (detail::toAddress (it)) {} + + template , int> = 0> + constexpr Span (Range&& range) + : Base (std::size (range)), ptr (std::data (range)) {} + + constexpr Span (const Span&) = default; + + constexpr Span& operator= (const Span&) = default; + + using Base::size; + + constexpr Value* begin() const { return ptr; } + constexpr Value* end() const { return ptr + size(); } + + constexpr auto& front() const { return ptr[0]; } + constexpr auto& back() const { return ptr[size() - 1]; } + + constexpr auto& operator[] (size_t index) const { return ptr[index]; } + constexpr Value* data() const { return ptr; } + + constexpr bool empty() const { return size() == 0; } + +private: + Value* ptr = nullptr; +}; + +template +Span (T, End) -> Span()))>>; + +template +Span (T (&) [N]) -> Span; + +template +Span (std::array&) -> Span; + +template +Span (const std::array&) -> Span; + +template +Span (Range&& r) -> Span>; + + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp index 113ec1c..875a89a 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp @@ -201,7 +201,7 @@ struct var::VariantType static int64 doubleToInt64 (const ValueUnion& data) noexcept { return (int64) data.doubleValue; } static double doubleToDouble (const ValueUnion& data) noexcept { return data.doubleValue; } static String doubleToString (const ValueUnion& data) { return serialiseDouble (data.doubleValue); } - static bool doubleToBool (const ValueUnion& data) noexcept { return data.doubleValue != 0.0; } + static bool doubleToBool (const ValueUnion& data) noexcept { return ! exactlyEqual (data.doubleValue, 0.0); } static bool doubleEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { @@ -645,7 +645,7 @@ static int compare (const var& v1, const var& v2) return v1.toString().compare (v2.toString()); auto diff = static_cast (v1) - static_cast (v2); - return diff == 0 ? 0 : (diff < 0 ? -1 : 1); + return exactlyEqual (diff, 0.0) ? 0 : (diff < 0 ? -1 : 1); } bool operator== (const var& v1, const var& v2) { return v1.equals (v2); } diff --git a/JuceLibraryCode/modules/juce_core/files/juce_File.cpp b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp index 5d1fa06..db0890b 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_File.cpp +++ b/JuceLibraryCode/modules/juce_core/files/juce_File.cpp @@ -970,7 +970,7 @@ bool File::createSymbolicLink (const File& linkFileToCreate, linkFileToCreate.deleteFile(); } - #if JUCE_MAC || JUCE_LINUX + #if JUCE_MAC || JUCE_LINUX || JUCE_BSD // one common reason for getting an error here is that the file already exists if (symlink (nativePathOfTarget.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) { diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp index 91f4dc0..423f22a 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.cpp @@ -63,7 +63,12 @@ int FileSearchPath::getNumPaths() const File FileSearchPath::operator[] (int index) const { - return File (directories[index]); + return File (getRawString (index)); +} + +String FileSearchPath::getRawString (int index) const +{ + return directories[index]; } String FileSearchPath::toString() const @@ -110,21 +115,30 @@ void FileSearchPath::addPath (const FileSearchPath& other) void FileSearchPath::removeRedundantPaths() { - for (int i = directories.size(); --i >= 0;) + std::vector reduced; + + for (const auto& directory : directories) { - const File d1 (directories[i]); + const auto checkedIsChildOf = [&] (const auto& a, const auto& b) + { + return File::isAbsolutePath (a) && File::isAbsolutePath (b) && File (a).isAChildOf (b); + }; - for (int j = directories.size(); --j >= 0;) + const auto fContainsDirectory = [&] (const auto& f) { - const File d2 (directories[j]); + return f == directory || checkedIsChildOf (directory, f); + }; - if (i != j && (d1.isAChildOf (d2) || d1 == d2)) - { - directories.remove (i); - break; - } - } + if (std::find_if (reduced.begin(), reduced.end(), fContainsDirectory) != reduced.end()) + continue; + + const auto directoryContainsF = [&] (const auto& f) { return checkedIsChildOf (f, directory); }; + + reduced.erase (std::remove_if (reduced.begin(), reduced.end(), directoryContainsF), reduced.end()); + reduced.push_back (directory); } + + directories = StringArray (reduced.data(), (int) reduced.size()); } void FileSearchPath::removeNonExistentPaths() @@ -172,4 +186,54 @@ bool FileSearchPath::isFileInPath (const File& fileToCheck, return false; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class FileSearchPathTests : public UnitTest +{ +public: + FileSearchPathTests() : UnitTest ("FileSearchPath", UnitTestCategories::files) {} + + void runTest() override + { + beginTest ("removeRedundantPaths"); + { + #if JUCE_WINDOWS + const String prefix = "C:"; + #else + const String prefix = ""; + #endif + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c/e;" + prefix + "/a/b/c" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c;" + prefix + "/a/b/c/d;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { "%FOO%;" + prefix + "/a/b/c;%FOO%;" + prefix + "/a/b/c/d" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), "%FOO%;" + prefix + "/a/b/c"); + } + } + } +}; + +static FileSearchPathTests fileSearchPathTests; + +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h index 966439a..f4be32f 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h +++ b/JuceLibraryCode/modules/juce_core/files/juce_FileSearchPath.h @@ -71,10 +71,21 @@ class JUCE_API FileSearchPath /** Returns one of the folders in this search path. The file returned isn't guaranteed to actually be a valid directory. - @see getNumPaths + @see getNumPaths, getRawString */ File operator[] (int index) const; + /** Returns the unaltered text of the folder at the specified index. + + Unlike operator[], this function returns the exact text that was entered. It does not + attempt to convert the path into an absolute path. + + This may be useful if the directory string is expected to understand environment variables + or other placeholders that the File constructor doesn't necessarily understand. + @see operator[] + */ + String getRawString (int index) const; + /** Returns the search path as a semicolon-separated list of directories. */ String toString() const; diff --git a/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.cpp b/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.cpp index 94b4916..b784dd3 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.cpp +++ b/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.cpp @@ -206,6 +206,7 @@ class Table { "fdf", "application/vnd.fdf" }, { "fif", "application/fractals" }, { "fif", "image/fif" }, + { "flac", "audio/flac" }, { "fli", "video/fli" }, { "fli", "video/x-fli" }, { "flo", "image/florian" }, diff --git a/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.h b/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.h index 50836e9..0a17f85 100644 --- a/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.h +++ b/JuceLibraryCode/modules/juce_core/files/juce_common_MimeTypes.h @@ -28,18 +28,17 @@ namespace juce { +/** @internal */ struct MimeTypeTable { - -/* @internal */ +/** @internal */ static void registerCustomMimeTypeForFileExtension (const String& mimeType, const String& fileExtension); -/* @internal */ +/** @internal */ static StringArray getMimeTypesForFileExtension (const String& fileExtension); -/* @internal */ +/** @internal */ static StringArray getFileExtensionsForMimeType (const String& mimeType); - }; } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp index 195b545..dd578dc 100644 --- a/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp +++ b/JuceLibraryCode/modules/juce_core/javascript/juce_Javascript.cpp @@ -520,7 +520,7 @@ struct JavascriptEngine::RootObject : public DynamicObject { EqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::equals) {} var getWithUndefinedArg() const override { return true; } - var getWithDoubles (double a, double b) const override { return a == b; } + var getWithDoubles (double a, double b) const override { return exactlyEqual (a, b); } var getWithInts (int64 a, int64 b) const override { return a == b; } var getWithStrings (const String& a, const String& b) const override { return a == b; } var getWithArrayOrObject (const var& a, const var& b) const override { return a == b; } @@ -530,7 +530,7 @@ struct JavascriptEngine::RootObject : public DynamicObject { NotEqualsOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::notEquals) {} var getWithUndefinedArg() const override { return false; } - var getWithDoubles (double a, double b) const override { return a != b; } + var getWithDoubles (double a, double b) const override { return ! exactlyEqual (a, b); } var getWithInts (int64 a, int64 b) const override { return a != b; } var getWithStrings (const String& a, const String& b) const override { return a != b; } var getWithArrayOrObject (const var& a, const var& b) const override { return a != b; } @@ -593,14 +593,14 @@ struct JavascriptEngine::RootObject : public DynamicObject struct DivideOp : public BinaryOperator { DivideOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::divide) {} - var getWithDoubles (double a, double b) const override { return b != 0 ? a / b : std::numeric_limits::infinity(); } + var getWithDoubles (double a, double b) const override { return exactlyEqual (b, 0.0) ? std::numeric_limits::infinity() : a / b; } var getWithInts (int64 a, int64 b) const override { return b != 0 ? var ((double) a / (double) b) : var (std::numeric_limits::infinity()); } }; struct ModuloOp : public BinaryOperator { ModuloOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::modulo) {} - var getWithDoubles (double a, double b) const override { return b != 0 ? fmod (a, b) : std::numeric_limits::infinity(); } + var getWithDoubles (double a, double b) const override { return exactlyEqual (b, 0.0) ? std::numeric_limits::infinity() : fmod (a, b); } var getWithInts (int64 a, int64 b) const override { return b != 0 ? var (a % b) : var (std::numeric_limits::infinity()); } }; @@ -1711,6 +1711,7 @@ struct JavascriptEngine::RootObject : public DynamicObject setMethod ("exp", Math_exp); setMethod ("pow", Math_pow); setMethod ("sqr", Math_sqr); setMethod ("sqrt", Math_sqrt); setMethod ("ceil", Math_ceil); setMethod ("floor", Math_floor); + setMethod ("hypot", Math_hypot); setProperty ("PI", MathConstants::pi); setProperty ("E", MathConstants::euler); @@ -1749,6 +1750,7 @@ struct JavascriptEngine::RootObject : public DynamicObject static var Math_sqrt (Args a) { return std::sqrt (getDouble (a, 0)); } static var Math_ceil (Args a) { return std::ceil (getDouble (a, 0)); } static var Math_floor (Args a) { return std::floor (getDouble (a, 0)); } + static var Math_hypot (Args a) { return std::hypot (getDouble (a, 0), getDouble (a, 1)); } // We can't use the std namespace equivalents of these functions without breaking // compatibility with older versions of OS X. diff --git a/JuceLibraryCode/modules/juce_core/juce_core.cpp b/JuceLibraryCode/modules/juce_core/juce_core.cpp index fe6190e..03e50bb 100644 --- a/JuceLibraryCode/modules/juce_core/juce_core.cpp +++ b/JuceLibraryCode/modules/juce_core/juce_core.cpp @@ -41,7 +41,7 @@ #include #include -#if ! JUCE_ANDROID +#if ! (JUCE_ANDROID || JUCE_BSD) #include #include #endif @@ -186,67 +186,83 @@ #include "zip/juce_ZipFile.cpp" #include "files/juce_FileFilter.cpp" #include "files/juce_WildcardFileFilter.cpp" -#include "native/juce_native_ThreadPriorities.h" +#include "native/juce_ThreadPriorities_native.h" +#include "native/juce_PlatformTimerListener.h" //============================================================================== #if ! JUCE_WINDOWS - #include "native/juce_posix_SharedCode.h" - #include "native/juce_posix_NamedPipe.cpp" + #include "native/juce_SharedCode_posix.h" + #include "native/juce_NamedPipe_posix.cpp" #if ! JUCE_ANDROID || __ANDROID_API__ >= 24 - #include "native/juce_posix_IPAddress.h" + #include "native/juce_IPAddress_posix.h" #endif #endif //============================================================================== #if JUCE_MAC || JUCE_IOS - #include "native/juce_mac_Files.mm" - #include "native/juce_mac_Network.mm" - #include "native/juce_mac_Strings.mm" - #include "native/juce_intel_SharedCode.h" - #include "native/juce_mac_SystemStats.mm" - #include "native/juce_mac_Threads.mm" + #include "native/juce_Files_mac.mm" + #include "native/juce_Network_mac.mm" + #include "native/juce_Strings_mac.mm" + #include "native/juce_SharedCode_intel.h" + #include "native/juce_SystemStats_mac.mm" + #include "native/juce_Threads_mac.mm" + #include "native/juce_PlatformTimer_generic.cpp" //============================================================================== #elif JUCE_WINDOWS - #include "native/juce_win32_Files.cpp" - #include "native/juce_win32_Network.cpp" - #include "native/juce_win32_Registry.cpp" - #include "native/juce_win32_SystemStats.cpp" - #include "native/juce_win32_Threads.cpp" + #include "native/juce_Files_windows.cpp" + #include "native/juce_Network_windows.cpp" + #include "native/juce_Registry_windows.cpp" + #include "native/juce_SystemStats_windows.cpp" + #include "native/juce_Threads_windows.cpp" + #include "native/juce_PlatformTimer_windows.cpp" //============================================================================== -#elif JUCE_LINUX || JUCE_BSD - #include "native/juce_linux_CommonFile.cpp" - #include "native/juce_linux_Files.cpp" - #include "native/juce_linux_Network.cpp" +#elif JUCE_LINUX + #include "native/juce_CommonFile_linux.cpp" + #include "native/juce_Files_linux.cpp" + #include "native/juce_Network_linux.cpp" #if JUCE_USE_CURL - #include "native/juce_curl_Network.cpp" + #include "native/juce_Network_curl.cpp" #endif - #if JUCE_BSD - #include "native/juce_intel_SharedCode.h" + #include "native/juce_SystemStats_linux.cpp" + #include "native/juce_Threads_linux.cpp" + #include "native/juce_PlatformTimer_generic.cpp" + +//============================================================================== +#elif JUCE_BSD + #include "native/juce_CommonFile_linux.cpp" + #include "native/juce_Files_linux.cpp" + #include "native/juce_Network_linux.cpp" + #if JUCE_USE_CURL + #include "native/juce_Network_curl.cpp" #endif - #include "native/juce_linux_SystemStats.cpp" - #include "native/juce_linux_Threads.cpp" + #include "native/juce_SharedCode_intel.h" + #include "native/juce_SystemStats_linux.cpp" + #include "native/juce_Threads_linux.cpp" + #include "native/juce_PlatformTimer_generic.cpp" //============================================================================== #elif JUCE_ANDROID - #include "native/juce_linux_CommonFile.cpp" - #include "native/juce_android_JNIHelpers.cpp" - #include "native/juce_android_Files.cpp" - #include "native/juce_android_Misc.cpp" - #include "native/juce_android_Network.cpp" - #include "native/juce_android_SystemStats.cpp" - #include "native/juce_android_Threads.cpp" - #include "native/juce_android_RuntimePermissions.cpp" + #include "native/juce_CommonFile_linux.cpp" + #include "native/juce_JNIHelpers_android.cpp" + #include "native/juce_Files_android.cpp" + #include "native/juce_Misc_android.cpp" + #include "native/juce_Network_android.cpp" + #include "native/juce_SystemStats_android.cpp" + #include "native/juce_Threads_android.cpp" + #include "native/juce_RuntimePermissions_android.cpp" + #include "native/juce_PlatformTimer_generic.cpp" +//============================================================================== #elif JUCE_WASM - #include "native/juce_wasm_SystemStats.cpp" - + #include "native/juce_SystemStats_wasm.cpp" + #include "native/juce_PlatformTimer_generic.cpp" #endif #include "files/juce_common_MimeTypes.h" #include "files/juce_common_MimeTypes.cpp" -#include "native/juce_android_AndroidDocument.cpp" +#include "native/juce_AndroidDocument_android.cpp" #include "threads/juce_HighResolutionTimer.cpp" #include "threads/juce_WaitableEvent.cpp" #include "network/juce_URL.cpp" @@ -260,8 +276,9 @@ //============================================================================== #if JUCE_UNIT_TESTS #include "containers/juce_HashMap_test.cpp" - #include "containers/juce_Optional_test.cpp" + #include "maths/juce_MathsFunctions_test.cpp" + #include "misc/juce_EnumHelpers_test.cpp" #endif //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/juce_core.h b/JuceLibraryCode/modules/juce_core/juce_core.h index acde9f0..84a3294 100644 --- a/JuceLibraryCode/modules/juce_core/juce_core.h +++ b/JuceLibraryCode/modules/juce_core/juce_core.h @@ -32,7 +32,7 @@ ID: juce_core vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE core classes description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. website: http://www.juce.com/juce @@ -40,7 +40,7 @@ minimumCppStandard: 17 dependencies: - OSXFrameworks: Cocoa Foundation IOKit + OSXFrameworks: Cocoa Foundation IOKit Security iOSFrameworks: Foundation linuxLibs: rt dl pthread mingwLibs: uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm @@ -219,6 +219,7 @@ namespace juce extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noexcept; } +#include "misc/juce_EnumHelpers.h" #include "memory/juce_Memory.h" #include "maths/juce_MathsFunctions.h" #include "memory/juce_ByteOrder.h" @@ -276,6 +277,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "text/juce_LocalisedStrings.h" #include "text/juce_Base64.h" #include "misc/juce_Functional.h" +#include "containers/juce_Span.h" #include "misc/juce_Result.h" #include "misc/juce_Uuid.h" #include "misc/juce_ConsoleApplication.h" @@ -313,12 +315,12 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "misc/juce_WindowsRegistry.h" #include "threads/juce_ChildProcess.h" #include "threads/juce_DynamicLibrary.h" -#include "threads/juce_HighResolutionTimer.h" #include "threads/juce_InterProcessLock.h" #include "threads/juce_Process.h" #include "threads/juce_SpinLock.h" #include "threads/juce_WaitableEvent.h" #include "threads/juce_Thread.h" +#include "threads/juce_HighResolutionTimer.h" #include "threads/juce_ThreadLocalValue.h" #include "threads/juce_ThreadPool.h" #include "threads/juce_TimeSliceThread.h" @@ -347,16 +349,16 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "streams/juce_AndroidDocumentInputSource.h" #if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) - #include "native/juce_mac_ObjCHelpers.h" + #include "native/juce_ObjCHelpers_mac.h" #endif #if JUCE_CORE_INCLUDE_COM_SMART_PTR && JUCE_WINDOWS - #include "native/juce_win32_ComSmartPtr.h" + #include "native/juce_ComSmartPtr_windows.h" #endif #if JUCE_CORE_INCLUDE_JNI_HELPERS && JUCE_ANDROID #include - #include "native/juce_android_JNIHelpers.h" + #include "native/juce_JNIHelpers_android.h" #endif #if JUCE_UNIT_TESTS diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h index 85bf445..d9732e2 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h +++ b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions.h @@ -86,6 +86,250 @@ using uint32 = unsigned int; using ssize_t = pointer_sized_int; #endif +//============================================================================== +/** Handy function for avoiding unused variables warning. */ +template +void ignoreUnused (Types&&...) noexcept {} + +/** Handy function for getting the number of elements in a simple const C array. + E.g. + @code + static int myArray[] = { 1, 2, 3 }; + + int numElements = numElementsInArray (myArray) // returns 3 + @endcode +*/ +template +constexpr int numElementsInArray (Type (&)[N]) noexcept { return N; } + +//============================================================================== +// Some useful maths functions that aren't always present with all compilers and build settings. + +/** Using juce_hypot is easier than dealing with the different types of hypot function + that are provided by the various platforms and compilers. */ +template +Type juce_hypot (Type a, Type b) noexcept +{ + #if JUCE_MSVC + return static_cast (_hypot (a, b)); + #else + return static_cast (hypot (a, b)); + #endif +} + +#ifndef DOXYGEN +template <> +inline float juce_hypot (float a, float b) noexcept +{ + #if JUCE_MSVC + return _hypotf (a, b); + #else + return hypotf (a, b); + #endif +} +#endif + +//============================================================================== +/** Commonly used mathematical constants + + @tags{Core} +*/ +template +struct MathConstants +{ + /** A predefined value for Pi */ + static constexpr FloatType pi = static_cast (3.141592653589793238L); + + /** A predefined value for 2 * Pi */ + static constexpr FloatType twoPi = static_cast (2 * 3.141592653589793238L); + + /** A predefined value for Pi / 2 */ + static constexpr FloatType halfPi = static_cast (3.141592653589793238L / 2); + + /** A predefined value for Euler's number */ + static constexpr FloatType euler = static_cast (2.71828182845904523536L); + + /** A predefined value for sqrt(2) */ + static constexpr FloatType sqrt2 = static_cast (1.4142135623730950488L); +}; + +#ifndef DOXYGEN +/** A double-precision constant for pi. */ +[[deprecated ("This is deprecated in favour of MathConstants::pi.")]] +const constexpr double double_Pi = MathConstants::pi; + +/** A single-precision constant for pi. */ +[[deprecated ("This is deprecated in favour of MathConstants::pi.")]] +const constexpr float float_Pi = MathConstants::pi; +#endif + +/** Converts an angle in degrees to radians. */ +template +constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (MathConstants::pi / FloatType (180)); } + +/** Converts an angle in radians to degrees. */ +template +constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / MathConstants::pi); } + +//============================================================================== +/** The isfinite() method seems to vary between platforms, so this is a + platform-independent function for it. +*/ +template +bool juce_isfinite (NumericType value) noexcept +{ + if constexpr (std::numeric_limits::has_infinity + || std::numeric_limits::has_quiet_NaN + || std::numeric_limits::has_signaling_NaN) + { + return std::isfinite (value); + } + else + { + ignoreUnused (value); + return true; + } +} + +//============================================================================== +/** Equivalent to operator==, but suppresses float-equality warnings. + + This allows code to be explicit about float-equality checks that are known to have the correct + semantics. +*/ +template +constexpr bool exactlyEqual (Type a, Type b) +{ + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfloat-equal") + return a == b; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE +} + +/** A class encapsulating both relative and absolute tolerances for use in floating-point comparisons. + + @see approximatelyEqual, absoluteTolerance, relativeTolerance +*/ +template +class Tolerance +{ +public: + Tolerance() = default; + + /** Returns a copy of this Tolerance object with a new absolute tolerance. + + If you just need a Tolerance object with an absolute tolerance, it might be worth using the + absoluteTolerance() function. + + @see getAbsolute, absoluteTolerance + */ + [[nodiscard]] Tolerance withAbsolute (Type newAbsolute) + { + return withMember (*this, &Tolerance::absolute, std::abs (newAbsolute)); + } + + /** Returns a copy of this Tolerance object with a new relative tolerance. + + If you just need a Tolerance object with a relative tolerance, it might be worth using the + relativeTolerance() function. + + @see getRelative, relativeTolerance + */ + [[nodiscard]] Tolerance withRelative (Type newRelative) + { + return withMember (*this, &Tolerance::relative, std::abs (newRelative)); + } + + [[nodiscard]] Type getAbsolute() const { return absolute; } + [[nodiscard]] Type getRelative() const { return relative; } + +private: + Type absolute{}; + Type relative{}; +}; + +/** Returns a type deduced Tolerance object containing only an absolute tolerance. + + @see Tolerance::withAbsolute, approximatelyEqual + */ +template +static Tolerance absoluteTolerance (Type tolerance) +{ + return Tolerance{}.withAbsolute (tolerance); +} + +/** Returns a type deduced Tolerance object containing only a relative tolerance. + + @see Tolerance::withRelative, approximatelyEqual + */ +template +static Tolerance relativeTolerance (Type tolerance) +{ + return Tolerance{}.withRelative (tolerance); +} + + +/** Returns true if the two floating-point numbers are approximately equal. + + If either a or b are not finite, returns exactlyEqual (a, b). + + The default absolute tolerance is equal to the minimum normal value. This ensures + differences that are subnormal are always considered equal. It is highly recommend this + value is reviewed depending on the calculation being carried out. In general specifying an + absolute value is useful when considering values close to zero. For example you might + expect sin(pi) to return 0, but what it actually returns is close to the error of the value pi. + Therefore, in this example it might be better to set the absolute tolerance to sin(pi). + + The default relative tolerance is equal to the machine epsilon which is the difference between + 1.0 and the next floating-point value that can be represented by Type. In most cases this value + is probably reasonable. This value is multiplied by the largest absolute value of a and b so as + to scale relatively according to the input parameters. For example, specifying a relative value + of 0.05 will ensure values return equal if the difference between them is less than or equal to + 5% of the larger of the two absolute values. + + @param a The first number to compare. + @param b The second number to compare. + @param tolerance An object that represents both absolute and relative tolerances + when evaluating if a and b are equal. + + @see exactlyEqual +*/ +template , int> = 0> +constexpr bool approximatelyEqual (Type a, Type b, + Tolerance tolerance = Tolerance{} + .withAbsolute (std::numeric_limits::min()) + .withRelative (std::numeric_limits::epsilon())) +{ + if (! (juce_isfinite (a) && juce_isfinite (b))) + return exactlyEqual (a, b); + + const auto diff = std::abs (a - b); + + return diff <= tolerance.getAbsolute() + || diff <= tolerance.getRelative() * std::max (std::abs (a), std::abs (b)); +} + +/** Special case for non-floating-point types that returns true if both are exactly equal. */ +template , int> = 0> +constexpr bool approximatelyEqual (Type a, Type b) +{ + return a == b; +} + +//============================================================================== +/** Returns the next representable value by FloatType in the direction of the largest representable value. */ +template +FloatType nextFloatUp (FloatType value) noexcept +{ + return std::nextafter (value, std::numeric_limits::max()); +} + +/** Returns the next representable value by FloatType in the direction of the lowest representable value. */ +template +FloatType nextFloatDown (FloatType value) noexcept +{ + return std::nextafter (value, std::numeric_limits::lowest()); +} + //============================================================================== // Some indispensable min/max functions @@ -126,7 +370,7 @@ constexpr Type jmap (Type value0To1, Type targetRangeMin, Type targetRangeMax) template Type jmap (Type sourceValue, Type sourceRangeMin, Type sourceRangeMax, Type targetRangeMin, Type targetRangeMax) { - jassert (sourceRangeMax != sourceRangeMin); // mapping from a range of zero will produce NaN! + jassert (! approximatelyEqual (sourceRangeMax, sourceRangeMin)); // mapping from a range of zero will produce NaN! return targetRangeMin + ((targetRangeMax - targetRangeMin) * (sourceValue - sourceRangeMin)) / (sourceRangeMax - sourceRangeMin); } @@ -317,132 +561,6 @@ bool isWithin (Type a, Type b, Type tolerance) noexcept return std::abs (a - b) <= tolerance; } -/** Returns true if the two numbers are approximately equal. This is useful for floating-point - and double comparisons. -*/ -template -bool approximatelyEqual (Type a, Type b) noexcept -{ - return std::abs (a - b) <= (std::numeric_limits::epsilon() * std::max (a, b)) - || std::abs (a - b) < std::numeric_limits::min(); -} - -//============================================================================== -/** Handy function for avoiding unused variables warning. */ -template -void ignoreUnused (Types&&...) noexcept {} - -/** Handy function for getting the number of elements in a simple const C array. - E.g. - @code - static int myArray[] = { 1, 2, 3 }; - - int numElements = numElementsInArray (myArray) // returns 3 - @endcode -*/ -template -constexpr int numElementsInArray (Type (&)[N]) noexcept { return N; } - -//============================================================================== -// Some useful maths functions that aren't always present with all compilers and build settings. - -/** Using juce_hypot is easier than dealing with the different types of hypot function - that are provided by the various platforms and compilers. */ -template -Type juce_hypot (Type a, Type b) noexcept -{ - #if JUCE_MSVC - return static_cast (_hypot (a, b)); - #else - return static_cast (hypot (a, b)); - #endif -} - -#ifndef DOXYGEN -template <> -inline float juce_hypot (float a, float b) noexcept -{ - #if JUCE_MSVC - return _hypotf (a, b); - #else - return hypotf (a, b); - #endif -} -#endif - -//============================================================================== -/** Commonly used mathematical constants - - @tags{Core} -*/ -template -struct MathConstants -{ - /** A predefined value for Pi */ - static constexpr FloatType pi = static_cast (3.141592653589793238L); - - /** A predefined value for 2 * Pi */ - static constexpr FloatType twoPi = static_cast (2 * 3.141592653589793238L); - - /** A predefined value for Pi / 2 */ - static constexpr FloatType halfPi = static_cast (3.141592653589793238L / 2); - - /** A predefined value for Euler's number */ - static constexpr FloatType euler = static_cast (2.71828182845904523536L); - - /** A predefined value for sqrt(2) */ - static constexpr FloatType sqrt2 = static_cast (1.4142135623730950488L); -}; - -#ifndef DOXYGEN -/** A double-precision constant for pi. */ -[[deprecated ("This is deprecated in favour of MathConstants::pi.")]] -const constexpr double double_Pi = MathConstants::pi; - -/** A single-precision constant for pi. */ -[[deprecated ("This is deprecated in favour of MathConstants::pi.")]] -const constexpr float float_Pi = MathConstants::pi; -#endif - -/** Converts an angle in degrees to radians. */ -template -constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (MathConstants::pi / FloatType (180)); } - -/** Converts an angle in radians to degrees. */ -template -constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / MathConstants::pi); } - - -//============================================================================== -/** The isfinite() method seems to vary between platforms, so this is a - platform-independent function for it. -*/ -template -bool juce_isfinite (NumericType) noexcept -{ - return true; // Integer types are always finite -} - -template <> -inline bool juce_isfinite (float value) noexcept -{ - #if JUCE_WINDOWS && ! JUCE_MINGW - return _finite (value) != 0; - #else - return std::isfinite (value); - #endif -} - -template <> -inline bool juce_isfinite (double value) noexcept -{ - #if JUCE_WINDOWS && ! JUCE_MINGW - return _finite (value) != 0; - #else - return std::isfinite (value); - #endif -} - //============================================================================== #if JUCE_MSVC #pragma optimize ("t", off) diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions_test.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions_test.cpp new file mode 100644 index 0000000..4545044 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/maths/juce_MathsFunctions_test.cpp @@ -0,0 +1,543 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +template +String getTemplatedMathsFunctionUnitTestName (const String& functionName) +{ + const auto getTypeName = []() -> String + { + if constexpr (std::is_same_v) + return "int"; + + if constexpr (std::is_same_v) + return "float"; + + if constexpr (std::is_same_v) + return "double"; + + if constexpr (std::is_same_v) + return "long double"; + }; + + return functionName + "<" + getTypeName() + ">"; +} + +template +class ApproximatelyEqualTests final : public UnitTest +{ +public: + ApproximatelyEqualTests() + : UnitTest { getTemplatedMathsFunctionUnitTestName ("approximatelyEqual"), UnitTestCategories::maths } + {} + + void runTest() final + { + using limits = std::numeric_limits; + + constexpr auto zero = T{}; + constexpr auto one = T (1); + constexpr auto min = limits::min(); + constexpr auto max = limits::max(); + constexpr auto epsilon = limits::epsilon(); + constexpr auto oneThird = one / (T) 3; + + beginTest ("Equal values are always equal"); + { + expect (approximatelyEqual (zero, zero)); + expect (approximatelyEqual (zero, -zero)); + expect (approximatelyEqual (-zero, -zero)); + + expect (approximatelyEqual (min, min)); + expect (approximatelyEqual (-min, -min)); + + expect (approximatelyEqual (one, one)); + expect (approximatelyEqual (-one, -one)); + + expect (approximatelyEqual (max, max)); + expect (approximatelyEqual (-max, -max)); + + const Tolerance zeroTolerance{}; + + expect (approximatelyEqual (zero, zero, zeroTolerance)); + expect (approximatelyEqual (zero, -zero, zeroTolerance)); + expect (approximatelyEqual (-zero, -zero, zeroTolerance)); + + expect (approximatelyEqual (min, min, zeroTolerance)); + expect (approximatelyEqual (-min, -min, zeroTolerance)); + + expect (approximatelyEqual (one, one, zeroTolerance)); + expect (approximatelyEqual (-one, -one, zeroTolerance)); + + expect (approximatelyEqual (max, max, zeroTolerance)); + expect (approximatelyEqual (-max, -max, zeroTolerance)); + } + + beginTest ("Comparing subnormal values to zero, returns true"); + { + expect (! exactlyEqual (zero, nextFloatUp (zero))); + expect (approximatelyEqual (zero, nextFloatUp (zero))); + + expect (! exactlyEqual (zero, nextFloatDown (zero))); + expect (approximatelyEqual (zero, nextFloatDown (zero))); + + expect (! exactlyEqual (zero, nextFloatDown (min))); + expect (approximatelyEqual (zero, nextFloatDown (min))); + + expect (! exactlyEqual (zero, nextFloatUp (-min))); + expect (approximatelyEqual (zero, nextFloatUp (-min))); + } + + beginTest ("Comparing the minimum normal value to zero, returns true"); + { + expect (approximatelyEqual (zero, min)); + expect (approximatelyEqual (zero, -min)); + } + + beginTest ("Comparing normal values greater than the minimum to zero, returns true"); + { + expect (! approximatelyEqual (zero, one)); + expect (! approximatelyEqual (zero, epsilon)); + expect (! approximatelyEqual (zero, nextFloatUp (min))); + expect (! approximatelyEqual (zero, nextFloatDown (-min))); + } + + beginTest ("Values with large ranges can be compared"); + { + expect (! approximatelyEqual (zero, max)); + expect ( approximatelyEqual (zero, max, absoluteTolerance (max))); + expect ( approximatelyEqual (zero, max, relativeTolerance (one))); + expect (! approximatelyEqual (-one, max)); + expect (! approximatelyEqual (-max, max)); + } + + beginTest ("Larger values have a boundary that is a factor of the epsilon"); + { + for (auto exponent = 0; exponent < 127; ++exponent) + { + const auto value = std::pow ((T) 2, (T) exponent); + const auto boundaryValue = value * (one + epsilon); + + expect (juce_isfinite (value)); + expect (juce_isfinite (boundaryValue)); + + expect ( approximatelyEqual (value, boundaryValue)); + expect (! approximatelyEqual (value, nextFloatUp (boundaryValue))); + + expect ( approximatelyEqual (-value, -boundaryValue)); + expect (! approximatelyEqual (-value, nextFloatDown (-boundaryValue))); + } + } + + beginTest ("Tolerances scale with the values being compared"); + { + + expect (approximatelyEqual ((T) 100'000'000'000'000.01, + (T) 100'000'000'000'000.011)); + + expect (! approximatelyEqual ((T) 100.01, + (T) 100.011)); + + expect (! approximatelyEqual ((T) 123'000, (T) 121'000, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123'000, (T) 122'000, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123'000, (T) 123'000, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123'000, (T) 124'000, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 123'000, (T) 125'000, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual ((T) 123, (T) 121, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123, (T) 122, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123, (T) 123, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 123, (T) 124, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 123, (T) 125, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual ((T) 12.3, (T) 12.1, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 12.3, (T) 12.2, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 12.3, (T) 12.3, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 12.3, (T) 12.4, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 12.3, (T) 12.5, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual ((T) 1.23, (T) 1.21, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 1.23, (T) 1.22, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 1.23, (T) 1.23, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 1.23, (T) 1.24, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 1.23, (T) 1.25, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual ((T) 0.123, (T) 0.121, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.123, (T) 0.122, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.123, (T) 0.123, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.123, (T) 0.124, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 0.123, (T) 0.125, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual ((T) 0.000123, (T) 0.000121, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.000123, (T) 0.000122, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.000123, (T) 0.000123, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual ((T) 0.000123, (T) 0.000124, relativeTolerance ((T) 1e-2))); + expect (! approximatelyEqual ((T) 0.000123, (T) 0.000125, relativeTolerance ((T) 1e-2))); + } + + beginTest ("The square of the square root of 2 is approximately 2"); + { + constexpr auto two = (T) 2; + const auto sqrtOfTwo = std::sqrt (two); + + expect (approximatelyEqual (sqrtOfTwo * sqrtOfTwo, two)); + expect (approximatelyEqual (-sqrtOfTwo * sqrtOfTwo, -two)); + expect (approximatelyEqual (two / sqrtOfTwo, sqrtOfTwo)); + } + + if constexpr (limits::has_quiet_NaN) + { + beginTest ("Values are never equal to NaN"); + { + const auto nan = limits::quiet_NaN(); + + expect (! approximatelyEqual (nan, nan)); + + const auto expectNotEqualTo = [&](auto value) + { + expect (! approximatelyEqual (value, nan)); + expect (! approximatelyEqual (nan, value)); + }; + + expectNotEqualTo (zero); + expectNotEqualTo (-zero); + expectNotEqualTo (min); + expectNotEqualTo (-min); + expectNotEqualTo (one); + expectNotEqualTo (-one); + expectNotEqualTo (max); + expectNotEqualTo (-max); + } + } + + if constexpr (limits::has_infinity) + { + beginTest ("Only infinity is equal to infinity"); + { + const auto inf = limits::infinity(); + expect (approximatelyEqual (inf, inf)); + expect (approximatelyEqual (-inf, -inf)); + expect (! approximatelyEqual (inf, -inf)); + expect (! approximatelyEqual (-inf, inf)); + + const auto expectNotEqualTo = [&](auto value) + { + expect (! approximatelyEqual (value, inf)); + expect (! approximatelyEqual (value, -inf)); + expect (! approximatelyEqual (inf, value)); + expect (! approximatelyEqual (-inf, value)); + }; + + expectNotEqualTo (zero); + expectNotEqualTo (-zero); + expectNotEqualTo (min); + expectNotEqualTo (-min); + expectNotEqualTo (one); + expectNotEqualTo (-one); + expectNotEqualTo (max); + expectNotEqualTo (-max); + } + } + + beginTest ("Can set an absolute tolerance"); + { + constexpr std::array negativePowersOfTwo + { + (T) 0.5 /* 2^-1 */, + (T) 0.25 /* 2^-2 */, + (T) 0.125 /* 2^-3 */, + (T) 0.0625 /* 2^-4 */, + (T) 0.03125 /* 2^-5 */, + (T) 0.015625 /* 2^-6 */, + (T) 0.0078125 /* 2^-7 */ + }; + + const auto testTolerance = [&](auto tolerance) + { + const auto t = Tolerance{}.withAbsolute ((T) tolerance); + + const auto testValue= [&](auto value) + { + const auto boundary = value + tolerance; + + expect (approximatelyEqual (value, boundary, t)); + expect (! approximatelyEqual (value, nextFloatUp (boundary), t)); + + expect (approximatelyEqual (-value, -boundary, t)); + expect (! approximatelyEqual (-value, nextFloatDown (-boundary), t)); + }; + + testValue (zero); + testValue (min); + testValue (epsilon); + testValue (one); + + for (const auto value : negativePowersOfTwo) + testValue (value); + }; + + for (const auto toleranceValue : negativePowersOfTwo) + testTolerance (toleranceValue); + } + + beginTest ("Can set a relative tolerance"); + { + expect (! approximatelyEqual (oneThird, (T) 0.34, relativeTolerance ((T) 1e-2))); + expect ( approximatelyEqual (oneThird, (T) 0.334, relativeTolerance ((T) 1e-2))); + + expect (! approximatelyEqual (oneThird, (T) 0.334, relativeTolerance ((T) 1e-3))); + expect ( approximatelyEqual (oneThird, (T) 0.3334, relativeTolerance ((T) 1e-3))); + + expect (! approximatelyEqual (oneThird, (T) 0.3334, relativeTolerance ((T) 1e-4))); + expect ( approximatelyEqual (oneThird, (T) 0.33334, relativeTolerance ((T) 1e-4))); + + expect (! approximatelyEqual (oneThird, (T) 0.33334, relativeTolerance ((T) 1e-5))); + expect ( approximatelyEqual (oneThird, (T) 0.333334, relativeTolerance ((T) 1e-5))); + + expect (! approximatelyEqual (oneThird, (T) 0.333334, relativeTolerance ((T) 1e-6))); + expect ( approximatelyEqual (oneThird, (T) 0.3333334, relativeTolerance ((T) 1e-6))); + + expect (! approximatelyEqual (oneThird, (T) 0.3333334, relativeTolerance ((T) 1e-7))); + expect ( approximatelyEqual (oneThird, (T) 0.33333334, relativeTolerance ((T) 1e-7))); + + expect ( approximatelyEqual ((T) 1e6, (T) 1e6 + (T) 1, relativeTolerance ((T) 1e-6))); + expect (! approximatelyEqual ((T) 1e6, (T) 1e6 + (T) 1, relativeTolerance ((T) 1e-7))); + + expect ( approximatelyEqual ((T) -1e-6, (T) -1.0000009e-6, relativeTolerance ((T) 1e-6))); + expect (! approximatelyEqual ((T) -1e-6, (T) -1.0000009e-6, relativeTolerance ((T) 1e-7))); + + const auto a = (T) 1.234567; + const auto b = (T) 1.234568; + + for (auto exponent = 0; exponent < 39; ++exponent) + { + const auto m = std::pow ((T) 10, (T) exponent); + expect ( approximatelyEqual (a * m, b * m, relativeTolerance ((T) 1e-6))); + expect (! approximatelyEqual (a * m, b * m, relativeTolerance ((T) 1e-7))); + } + } + + beginTest ("A relative tolerance is always scaled by the maximum value"); + { + expect ( approximatelyEqual ((T) 9, (T) 10, absoluteTolerance ((T) 10.0 * (T) 0.1))); + expect (! approximatelyEqual ((T) 9, (T) 10, absoluteTolerance ((T) 9.0 * (T) 0.1))); + + expect (approximatelyEqual ((T) 9, (T) 10, relativeTolerance ((T) 0.1))); + expect (approximatelyEqual ((T) 10, (T) 9, relativeTolerance ((T) 0.1))); + } + + beginTest ("Documentation examples"); + { + constexpr auto pi = MathConstants::pi; + + expect (! approximatelyEqual (zero, std::sin (pi))); + expect ( approximatelyEqual (zero, std::sin (pi), absoluteTolerance (std::sin (pi)))); + + expect ( approximatelyEqual ((T) 100, (T) 95, relativeTolerance ((T) 0.05))); + expect (! approximatelyEqual ((T) 100, (T) 94, relativeTolerance ((T) 0.05))); + } + } +}; + +template<> +class ApproximatelyEqualTests final : public UnitTest +{ +public: + ApproximatelyEqualTests() + : UnitTest { getTemplatedMathsFunctionUnitTestName ("approximatelyEqual"), UnitTestCategories::maths } + {} + + void runTest() final + { + beginTest ("Identical integers are always equal"); + { + expect (approximatelyEqual ( 0, 0)); + expect (approximatelyEqual (-0, -0)); + + expect (approximatelyEqual ( 1, 1)); + expect (approximatelyEqual (-1, -1)); + + using limits = std::numeric_limits; + constexpr auto min = limits::min(); + constexpr auto max = limits::max(); + + expect (approximatelyEqual (min, min)); + expect (approximatelyEqual (max, max)); + } + + beginTest ("Non-identical integers are never equal"); + { + expect (! approximatelyEqual ( 0, 1)); + expect (! approximatelyEqual ( 0, -1)); + + expect (! approximatelyEqual ( 1, 2)); + expect (! approximatelyEqual (-1, -2)); + + using limits = std::numeric_limits; + constexpr auto min = limits::min(); + constexpr auto max = limits::max(); + + expect (! approximatelyEqual (min, min + 1)); + expect (! approximatelyEqual (max, max - 1)); + } + + beginTest ("Zero is equal regardless of the sign"); + { + expect (approximatelyEqual ( 0, -0)); + expect (approximatelyEqual (-0, 0)); + } + } +}; + +template +class IsFiniteTests final : public UnitTest +{ +public: + IsFiniteTests() + : UnitTest { getTemplatedMathsFunctionUnitTestName ("juce_isfinite"), UnitTestCategories::maths } + {} + + void runTest() final + { + using limits = std::numeric_limits; + + constexpr auto zero = T{}; + constexpr auto one = (T) 1; + constexpr auto max = limits::max(); + constexpr auto inf = limits::infinity(); + constexpr auto nan = limits::quiet_NaN(); + + beginTest ("Zero is finite"); + { + expect (juce_isfinite (zero)); + expect (juce_isfinite (-zero)); + } + + beginTest ("Subnormals are finite"); + { + expect (juce_isfinite (nextFloatUp (zero))); + expect (juce_isfinite (nextFloatDown (zero))); + } + + beginTest ("One is finite"); + { + expect (juce_isfinite (one)); + expect (juce_isfinite (-one)); + } + + beginTest ("Max is finite"); + { + expect (juce_isfinite (max)); + expect (juce_isfinite (-max)); + } + + beginTest ("Infinity is not finite"); + { + expect (! juce_isfinite (inf)); + expect (! juce_isfinite (-inf)); + } + + beginTest ("NaN is not finite"); + { + expect (! juce_isfinite (nan)); + expect (! juce_isfinite (-nan)); + expect (! juce_isfinite (std::sqrt ((T) -1))); + expect (! juce_isfinite (inf * zero)); + } + } +}; + +template +class NextFloatTests final : public UnitTest +{ +public: + NextFloatTests() + : UnitTest { getTemplatedMathsFunctionUnitTestName ("nextFloat"), UnitTestCategories::maths } + {} + + void runTest() final + { + using limits = std::numeric_limits; + + constexpr auto zero = T{}; + constexpr auto one = T (1); + constexpr auto min = limits::min(); + constexpr auto epsilon = limits::epsilon(); + + beginTest ("nextFloat from zero is subnormal"); + { + expect (juce_isfinite (nextFloatUp (zero))); + expect (! exactlyEqual (zero, nextFloatUp (zero))); + expect (! std::isnormal (nextFloatUp (zero))); + + expect (juce_isfinite (nextFloatDown (zero))); + expect (! exactlyEqual (zero, nextFloatDown (zero))); + expect (! std::isnormal (nextFloatDown (zero))); + } + + beginTest ("nextFloat from min, towards zero, is subnormal"); + { + expect (std::isnormal (min)); + expect (std::isnormal (-min)); + expect (! std::isnormal (nextFloatDown (min))); + expect (! std::isnormal (nextFloatUp (-min))); + } + + beginTest ("nextFloat from one matches epsilon"); + { + expect (! exactlyEqual (one, nextFloatUp (one))); + expect ( exactlyEqual (one + epsilon, nextFloatUp (one))); + + expect (! exactlyEqual (-one, nextFloatDown (-one))); + expect ( exactlyEqual (-one - epsilon, nextFloatDown (-one))); + } + } +}; + +template +struct MathsFloatingPointFunctionsTests +{ + IsFiniteTests isFiniteTests; + NextFloatTests nextFloatTests; + ApproximatelyEqualTests approximatelyEqualTests; +}; + +template<> +struct MathsFloatingPointFunctionsTests +{ + ApproximatelyEqualTests approximatelyEqualTests; +}; + +struct MathsFunctionsTests +{ + MathsFloatingPointFunctionsTests intFunctionTests; + MathsFloatingPointFunctionsTests floatFunctionTests; + MathsFloatingPointFunctionsTests doubleFunctionTests; + MathsFloatingPointFunctionsTests longDoubleFunctionTests; +}; + +static MathsFunctionsTests mathsFunctionsTests; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h b/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h index 7892fe4..4532bfd 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h +++ b/JuceLibraryCode/modules/juce_core/maths/juce_NormalisableRange.h @@ -128,7 +128,7 @@ class NormalisableRange auto proportion = clampTo0To1 ((v - start) / (end - start)); - if (skew == static_cast (1)) + if (exactlyEqual (skew, static_cast (1))) return proportion; if (! symmetricSkew) @@ -154,7 +154,7 @@ class NormalisableRange if (! symmetricSkew) { - if (skew != static_cast (1) && proportion > ValueType()) + if (! exactlyEqual (skew, static_cast (1)) && proportion > ValueType()) proportion = std::exp (std::log (proportion) / skew); return start + (end - start) * proportion; @@ -162,7 +162,7 @@ class NormalisableRange auto distanceFromMiddle = static_cast (2) * proportion - static_cast (1); - if (skew != static_cast (1) && distanceFromMiddle != static_cast (0)) + if (! exactlyEqual (skew, static_cast (1)) && ! exactlyEqual (distanceFromMiddle, static_cast (0))) distanceFromMiddle = std::exp (std::log (std::abs (distanceFromMiddle)) / skew) * (distanceFromMiddle < ValueType() ? static_cast (-1) : static_cast (1)); @@ -250,7 +250,7 @@ class NormalisableRange // If you hit this assertion then either your normalisation function is not working // correctly or your input is out of the expected bounds. - jassert (clampedValue == value); + jassert (exactlyEqual (clampedValue, value)); return clampedValue; } diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp b/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp index a4a0e74..5c26dd1 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Random.cpp @@ -103,7 +103,7 @@ float Random::nextFloat() noexcept { auto result = static_cast (static_cast (nextInt())) / (static_cast (std::numeric_limits::max()) + 1.0f); - return result == 1.0f ? 1.0f - std::numeric_limits::epsilon() : result; + return jmin (result, 1.0f - std::numeric_limits::epsilon()); } double Random::nextDouble() noexcept diff --git a/JuceLibraryCode/modules/juce_core/maths/juce_Range.h b/JuceLibraryCode/modules/juce_core/maths/juce_Range.h index 52d5869..f8a37b7 100644 --- a/JuceLibraryCode/modules/juce_core/maths/juce_Range.h +++ b/JuceLibraryCode/modules/juce_core/maths/juce_Range.h @@ -86,7 +86,7 @@ class Range constexpr inline ValueType getEnd() const noexcept { return end; } /** Returns true if the range has a length of zero. */ - constexpr inline bool isEmpty() const noexcept { return start == end; } + constexpr inline bool isEmpty() const noexcept { return exactlyEqual (start, end); } //============================================================================== /** Changes the start position of the range, leaving the end position unchanged. @@ -198,8 +198,13 @@ class Range return Range (start - amountToSubtract, end - amountToSubtract); } - constexpr bool operator== (Range other) const noexcept { return start == other.start && end == other.end; } - constexpr bool operator!= (Range other) const noexcept { return start != other.start || end != other.end; } + constexpr bool operator== (Range other) const noexcept + { + const auto tie = [] (const Range& r) { return std::tie (r.start, r.end); }; + return tie (*this) == tie (other); + } + + constexpr bool operator!= (Range other) const noexcept { return ! operator== (other); } //============================================================================== /** Returns true if the given position lies inside this range. diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_Reservoir.h b/JuceLibraryCode/modules/juce_core/memory/juce_Reservoir.h index 55e83cd..86ef8bc 100644 --- a/JuceLibraryCode/modules/juce_core/memory/juce_Reservoir.h +++ b/JuceLibraryCode/modules/juce_core/memory/juce_Reservoir.h @@ -25,6 +25,8 @@ namespace juce /** Helper functions for managing buffered readers. + + @tags{Audio} */ struct Reservoir { diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers.h b/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers.h new file mode 100644 index 0000000..5461f3d --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers.h @@ -0,0 +1,103 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +//============================================================================== +/** + Macro to enable bitwise operations for scoped enums (enum struct/class). + + To use this, add the line JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (MyEnum) + after your enum declaration at file scope level. + + e.g. @code + + enum class MyEnum + { + one = 1 << 0, + two = 1 << 1, + three = 1 << 2 + }; + + JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (MyEnum) + + MyEnum e = MyEnum::one | MyEnum::two; + + bool hasTwo = (e & MyEnum::two) != MyEnum{}; // true + bool hasTwo = hasBitValueSet (e, MyEnum::two); // true + + e = withBitValueCleared (e, MyEnum::two); + + bool hasTwo = hasBitValueSet (e, MyEnum::two); // false + + @endcode +*/ +#define JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS(EnumType) \ + static_assert (std::is_enum_v, \ + "JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS " \ + "should only be used with enum types"); \ + constexpr auto operator& (EnumType a, EnumType b) \ + { \ + using base_type = std::underlying_type::type; \ + return static_cast (base_type (a) & base_type (b)); \ + } \ + constexpr auto operator| (EnumType a, EnumType b) \ + { \ + using base_type = std::underlying_type::type; \ + return static_cast (base_type (a) | base_type (b)); \ + } \ + constexpr auto operator~ (EnumType a) \ + { \ + using base_type = std::underlying_type::type; \ + return static_cast (~base_type (a)); \ + } \ + constexpr auto& operator|= (EnumType& a, EnumType b) \ + { \ + a = (a | b); \ + return a; \ + } \ + constexpr auto& operator&= (EnumType& a, EnumType b) \ + { \ + a = (a & b); \ + return a; \ + } + + +namespace juce +{ + +template , int> = 0> +constexpr bool hasBitValueSet (EnumType enumValue, EnumType valueToLookFor) noexcept +{ + return (enumValue & valueToLookFor) != EnumType{}; +} + +template , int> = 0> +constexpr EnumType withBitValueSet (EnumType enumValue, EnumType valueToAdd) noexcept +{ + return enumValue | valueToAdd; +} + +template , int> = 0> +constexpr EnumType withBitValueCleared (EnumType enumValue, EnumType valueToRemove) noexcept +{ + return enumValue & ~valueToRemove; +} +} diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers_test.cpp b/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers_test.cpp new file mode 100644 index 0000000..50e4555 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/misc/juce_EnumHelpers_test.cpp @@ -0,0 +1,94 @@ +/* +============================================================================== + +This file is part of the JUCE library. +Copyright (c) 2022 - Raw Material Software Limited + +JUCE is an open source library subject to commercial or 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. + +JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER +EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE +DISCLAIMED. + +============================================================================== +*/ + +namespace juce +{ + +namespace detail +{ +enum class TestEnum +{ + one = 1 << 0, + four = 1 << 1, + other = 1 << 2 +}; + +JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (TestEnum) +} + +class EnumHelperTest : public UnitTest +{ +public: + EnumHelperTest() : UnitTest ("EnumHelpers", UnitTestCategories::containers) {} + + void runTest() override + { + using detail::TestEnum; + + TestEnum e = {}; + + beginTest ("Default initialised enum is 'none'"); + { + expect (e == TestEnum{}); + expect (! hasBitValueSet (e, TestEnum{})); + } + + beginTest ("withBitValueSet sets correct bit on empty enum"); + { + e = withBitValueSet (e, TestEnum::other); + expect (e == TestEnum::other); + expect (hasBitValueSet (e, TestEnum::other)); + } + + beginTest ("withBitValueSet sets correct bit on non-empty enum"); + { + e = withBitValueSet (e, TestEnum::one); + expect (hasBitValueSet (e, TestEnum::one)); + } + + beginTest ("withBitValueCleared clears correct bit"); + { + e = withBitValueCleared (e, TestEnum::one); + expect (e != TestEnum::one); + expect (hasBitValueSet (e, TestEnum::other)); + expect (! hasBitValueSet (e, TestEnum::one)); + } + + beginTest ("operators work as expected"); + { + e = {}; + + e = TestEnum::one; + expect ((e & TestEnum::one) != TestEnum{}); + e |= TestEnum::other; + expect ((e & TestEnum::other) != TestEnum{}); + + e &= ~TestEnum::one; + expect ((e & TestEnum::one) == TestEnum{}); + expect ((e & TestEnum::other) != TestEnum{}); + } + } +}; + +static EnumHelperTest enumHelperTest; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_Functional.h b/JuceLibraryCode/modules/juce_core/misc/juce_Functional.h index d9015c7..bd97660 100644 --- a/JuceLibraryCode/modules/juce_core/misc/juce_Functional.h +++ b/JuceLibraryCode/modules/juce_core/misc/juce_Functional.h @@ -80,7 +80,7 @@ using DisableIfSameOrDerived = std::enable_if_t -Object withMember (Object copy, Member OtherObject::* member, Other&& value) +[[nodiscard]] Object withMember (Object copy, Member OtherObject::* member, Other&& value) { copy.*member = std::forward (value); return copy; @@ -109,6 +109,8 @@ Object withMember (Object copy, Member OtherObject::* member, Other&& value) doWorkHavingEstablishedPreconditions(); } // ...or here! @endcode + + @tags{Core} */ template struct ScopeGuard : Fn { ~ScopeGuard() { Fn::operator()(); } }; template ScopeGuard (Fn) -> ScopeGuard; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_AndroidDocument.cpp b/JuceLibraryCode/modules/juce_core/native/juce_AndroidDocument_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_android_AndroidDocument.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_AndroidDocument_android.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h index feda7ed..42adee0 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -209,6 +209,8 @@ #include #include #include + #include + #include #include #include @@ -265,11 +267,13 @@ #include #include #include + #include + #include #include #include - // If you are getting include errors here, then you to re-build the Projucer - // and re-save your .jucer file. + // If you are getting include errors here, then you need to re-build + // the Projucer and re-save your .jucer file. #include #endif diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_CFHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_CFHelpers_mac.h similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_CFHelpers.h rename to JuceLibraryCode/modules/juce_core/native/juce_CFHelpers_mac.h diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h b/JuceLibraryCode/modules/juce_core/native/juce_ComSmartPtr_windows.h similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h rename to JuceLibraryCode/modules/juce_core/native/juce_ComSmartPtr_windows.h diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_CommonFile.cpp b/JuceLibraryCode/modules/juce_core/native/juce_CommonFile_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_linux_CommonFile.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_CommonFile_linux.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Files_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_android_Files.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Files_android.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Files_linux.cpp similarity index 98% rename from JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Files_linux.cpp index b8737c0..12216a4 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_Files.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_Files_linux.cpp @@ -20,10 +20,6 @@ ============================================================================== */ -#if JUCE_BSD -extern char** environ; -#endif - namespace juce { @@ -229,7 +225,7 @@ bool Process::openDocument (const String& fileName, const String& parameters) setsid(); // Child process - execve (argv[0], (char**) argv, environ); + execv (argv[0], (char**) argv); exit (0); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm b/JuceLibraryCode/modules/juce_core/native/juce_Files_mac.mm similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm rename to JuceLibraryCode/modules/juce_core/native/juce_Files_mac.mm diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Files_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Files_windows.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h b/JuceLibraryCode/modules/juce_core/native/juce_IPAddress_posix.h similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h rename to JuceLibraryCode/modules/juce_core/native/juce_IPAddress_posix.h diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.cpp b/JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.cpp similarity index 96% rename from JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.cpp index 0e92895..7540a33 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.cpp @@ -94,8 +94,8 @@ struct SystemJavaClassComparator if ((! isSysClassA) && (! isSysClassB)) { - return DefaultElementComparator::compareElements (first != nullptr ? first->byteCode != nullptr : false, - second != nullptr ? second->byteCode != nullptr : false); + return DefaultElementComparator::compareElements (first != nullptr && first->byteCode != nullptr, + second != nullptr && second->byteCode != nullptr); } return DefaultElementComparator::compareElements (isSystemClass (first), @@ -631,43 +631,17 @@ jobject FragmentOverlay::getNativeHandle() } //============================================================================== -class ActivityLauncher : public FragmentOverlay +void startAndroidActivityForResult (const LocalRef& intent, + int requestCode, + std::function)>&& callback) { -public: - ActivityLauncher (const LocalRef& intentToUse, - int requestCodeToUse, - std::function)> && callbackToUse) - : intent (intentToUse), requestCode (requestCodeToUse), callback (std::move (callbackToUse)) - {} - - void onStart() override - { - if (! std::exchange (activityHasStarted, true)) - getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult, - intent.get(), requestCode); - } - - void onActivityResult (int activityRequestCode, int resultCode, LocalRef data) override + auto* launcher = new ActivityLauncher (intent, requestCode); + launcher->callback = [launcher, c = std::move (callback)] (auto&&... args) { - if (callback) - callback (activityRequestCode, resultCode, std::move (data)); - - getEnv()->CallVoidMethod (getNativeHandle(), JuceFragmentOverlay.close); - delete this; - } - -private: - GlobalRef intent; - int requestCode; - std::function)> callback; - bool activityHasStarted = false; -}; - -void startAndroidActivityForResult (const LocalRef& intent, int requestCode, - std::function)> && callback) -{ - auto* activityLauncher = new ActivityLauncher (intent, requestCode, std::move (callback)); - activityLauncher->open(); + NullCheckedInvocation::invoke (c, args...); + delete launcher; + }; + launcher->open(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.h similarity index 96% rename from JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h rename to JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.h index 9d5cae1..a32a601 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_JNIHelpers_android.h @@ -262,7 +262,8 @@ template constexpr auto numBytes (const T (&) [N]) { retu METHOD (getApplicationInfo, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;") \ METHOD (checkCallingOrSelfPermission, "checkCallingOrSelfPermission", "(Ljava/lang/String;)I") \ METHOD (checkCallingOrSelfUriPermission, "checkCallingOrSelfUriPermission", "(Landroid/net/Uri;I)I") \ - METHOD (getCacheDir, "getCacheDir", "()Ljava/io/File;") + METHOD (getCacheDir, "getCacheDir", "()Ljava/io/File;") \ + METHOD (registerReceiver, "registerReceiver", "(Landroid/content/BroadcastReceiver;Landroid/content/IntentFilter;)Landroid/content/Intent;") \ DECLARE_JNI_CLASS (AndroidContext, "android/content/Context") #undef JNI_CLASS_MEMBERS @@ -418,6 +419,7 @@ DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread") METHOD (putExtraStrings, "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;") \ METHOD (putExtraParcelable, "putExtra", "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;") \ METHOD (putExtraBool, "putExtra", "(Ljava/lang/String;Z)Landroid/content/Intent;") \ + METHOD (putExtraInt, "putExtra", "(Ljava/lang/String;I)Landroid/content/Intent;") \ METHOD (putParcelableArrayListExtra, "putParcelableArrayListExtra", "(Ljava/lang/String;Ljava/util/ArrayList;)Landroid/content/Intent;") \ METHOD (setAction, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;") \ METHOD (setFlags, "setFlags", "(I)Landroid/content/Intent;") \ @@ -427,6 +429,12 @@ DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread") DECLARE_JNI_CLASS (AndroidIntent, "android/content/Intent") #undef JNI_CLASS_MEMBERS +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (createChooser, "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;Landroid/content/IntentSender;)Landroid/content/Intent;") \ + +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidIntent22, "android/content/Intent", 22) +#undef JNI_CLASS_MEMBERS + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (postRotate, "postRotate", "(FFF)Z") \ @@ -490,7 +498,8 @@ DECLARE_JNI_CLASS (AndroidPaint, "android/graphics/Paint") #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getActivity, "getActivity", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \ - METHOD (getIntentSender, "getIntentSender", "()Landroid/content/IntentSender;") + STATICMETHOD (getBroadcast, "getBroadcast", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \ + METHOD (getIntentSender, "getIntentSender", "()Landroid/content/IntentSender;") \ DECLARE_JNI_CLASS (AndroidPendingIntent, "android/app/PendingIntent") #undef JNI_CLASS_MEMBERS @@ -1026,10 +1035,37 @@ class FragmentOverlay //============================================================================== // Allows you to start an activity without requiring to have an activity -void startAndroidActivityForResult (const LocalRef& intent, int requestCode, - std::function)> && callback); +void startAndroidActivityForResult (const LocalRef& intent, + int requestCode, + std::function)>&& callback); -//============================================================================== +class ActivityLauncher : public FragmentOverlay +{ +public: + ActivityLauncher (const LocalRef& intentToUse, int requestCodeToUse) + : intent (intentToUse), requestCode (requestCodeToUse) + {} + + void onStart() override + { + if (! std::exchange (activityHasStarted, true)) + getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult, intent.get(), requestCode); + } + + void onActivityResult (int activityRequestCode, int resultCode, LocalRef data) override + { + NullCheckedInvocation::invoke (callback, activityRequestCode, resultCode, std::move (data)); + } + + std::function)> callback; + +private: + GlobalRef intent; + int requestCode; + bool activityHasStarted = false; +}; + + //============================================================================== bool androidHasSystemFeature (const String& property); String audioManagerGetProperty (const String& property); diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Misc.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Misc_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_android_Misc.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Misc_android.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp b/JuceLibraryCode/modules/juce_core/native/juce_NamedPipe_posix.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_NamedPipe_posix.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Network_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_android_Network.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Network_android.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_curl_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Network_curl.cpp similarity index 97% rename from JuceLibraryCode/modules/juce_core/native/juce_curl_Network.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Network_curl.cpp index 9fa4a5a..a8d0794 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_curl_Network.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_Network_curl.cpp @@ -89,7 +89,9 @@ struct CURLSymbols static DynamicLibrary libcurl; if (libcurl.getNativeHandle() == nullptr) - for (auto libName : { "libcurl.so", "libcurl.so.4", "libcurl.so.3" }) + for (auto libName : { "libcurl.so", + "libcurl.so.4", "libcurl.so.3", + "libcurl-gnutls.so.4", "libcurl-gnutls.so.3" }) if (libcurl.open (libName)) break; @@ -371,11 +373,18 @@ class WebInputStream::Pimpl if (symbols->curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &responseCode) == CURLE_OK) statusCode = static_cast (responseCode); - // get content length size + #if LIBCURL_VERSION_MAJOR < 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR < 55) double curlLength; if (symbols->curl_easy_getinfo (curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &curlLength) == CURLE_OK) + { + #else + curl_off_t curlLength; + if (symbols->curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &curlLength) == CURLE_OK) + { + #endif contentLength = static_cast (curlLength); - } + } + } return true; } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Network_linux.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Network_linux.cpp index ed9bac5..ce5a359 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_Network.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_Network_linux.cpp @@ -34,7 +34,7 @@ void MACAddress::findAllAddresses (Array& result) { if (i->ifa_addr->sa_family == AF_LINK) { - struct sockaddr_dl* sdl = (struct sockaddr_dl*) i->ifa_addr; + auto sdl = unalignedPointerCast (i->ifa_addr); MACAddress ma ((const uint8*) (sdl->sdl_data + sdl->sdl_nlen)); if (! ma.isNull()) diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm b/JuceLibraryCode/modules/juce_core/native/juce_Network_mac.mm similarity index 99% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm rename to JuceLibraryCode/modules/juce_core/native/juce_Network_mac.mm index a56fbb2..1a08940 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_Network_mac.mm @@ -686,7 +686,12 @@ static void didCompleteWithError (id self, SEL, NSURLConnection*, NSURLSessionTa activeSessions.set (uniqueIdentifier, this); auto nsUrl = [NSURL URLWithString: juceStringToNS (urlToUse.toString (true))]; + + jassert (nsUrl != nullptr); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion") NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL: nsUrl]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE if (options.usePost) [request setHTTPMethod: @"POST"]; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Network_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Network_windows.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_ObjCHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_ObjCHelpers_mac.h similarity index 92% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_ObjCHelpers.h rename to JuceLibraryCode/modules/juce_core/native/juce_ObjCHelpers_mac.h index c0c2209..22bee6c 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_ObjCHelpers.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_ObjCHelpers_mac.h @@ -20,7 +20,7 @@ ============================================================================== */ -#include "juce_mac_CFHelpers.h" +#include "juce_CFHelpers_mac.h" /* This file contains a few helper functions that are used internally but which need to be kept away from the public headers because they use obj-C symbols. @@ -504,29 +504,59 @@ auto createObjCBlockImpl (Class* object, Fn func, Signature) } } // namespace detail +/* Creates an Obj-C block automatically from a member function. */ template auto CreateObjCBlock (Class* object, MemberFunc fn) { return detail::createObjCBlockImpl (object, fn, detail::getSignature (fn)); } +/* Automatically copies and releases a block, a bit like a smart pointer for an Obj-C block. + + This is helpful to automatically manage the lifetime of blocks, e.g. if you need to keep a block + around to be used later. This is the case in the AudioUnit API, where the host may provide a + musicalContextBlock that can be called by the plugin during rendering. Copying blocks isn't + realtime-safe, so the plugin must cache the block before rendering. + + If you're just creating blocks to pass them directly to an Obj-C API, you probably won't need to + use this type. +*/ template class ObjCBlock { public: - ObjCBlock() { block = nullptr; } - template - ObjCBlock (C* _this, R (C::*fn)(P...)) : block (CreateObjCBlock (_this, fn)) {} - ObjCBlock (BlockType b) : block ([b copy]) {} - ObjCBlock& operator= (const BlockType& other) { if (block != nullptr) { [block release]; } block = [other copy]; return *this; } - bool operator== (const void* ptr) const { return ((const void*) block == ptr); } - bool operator!= (const void* ptr) const { return ((const void*) block != ptr); } - ~ObjCBlock() { if (block != nullptr) [block release]; } + ObjCBlock() = default; + + ObjCBlock (BlockType b) + : block ([b copy]) {} + + ObjCBlock (const ObjCBlock& other) + : block (other.block != nullptr ? [other.block copy] : nullptr) {} + + ObjCBlock& operator= (const BlockType& other) + { + ObjCBlock { other }.swap (*this); + return *this; + } + + ~ObjCBlock() noexcept + { + if (block != nullptr) + [block release]; + } + + bool operator== (BlockType ptr) const { return block == ptr; } + bool operator!= (BlockType ptr) const { return block != ptr; } operator BlockType() const { return block; } + void swap (ObjCBlock& other) noexcept + { + std::swap (other.block, block); + } + private: - BlockType block; + BlockType block = nullptr; }; //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimerListener.h b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimerListener.h new file mode 100644 index 0000000..21832a1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimerListener.h @@ -0,0 +1,32 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +struct PlatformTimerListener +{ + virtual ~PlatformTimerListener() = default; + virtual void onTimerExpired() = 0; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_generic.cpp b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_generic.cpp new file mode 100644 index 0000000..7a2ff45 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_generic.cpp @@ -0,0 +1,149 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class PlatformTimer final : private Thread +{ +public: + explicit PlatformTimer (PlatformTimerListener& ptl) + : Thread { "HighResolutionTimerThread" }, + listener { ptl } + { + startThread (Priority::highest); + } + + ~PlatformTimer() + { + stopThread (-1); + } + + void startTimer (int newIntervalMs) + { + jassert (newIntervalMs > 0); + jassert (timer == nullptr); + + { + std::scoped_lock lock { runCopyMutex }; + timer = std::make_shared (listener, newIntervalMs); + } + + notify(); + } + + void cancelTimer() + { + jassert (timer != nullptr); + + timer->cancel(); + + // Note the only race condition we need to protect against + // here is the copy in run(). + // + // Calls to startTimer(), cancelTimer(), and getIntervalMs() + // are already guaranteed to be both thread safe and well + // synchronised. + + std::scoped_lock lock { runCopyMutex }; + timer = nullptr; + } + + int getIntervalMs() const + { + return isThreadRunning() && timer != nullptr ? timer->getIntervalMs() : 0; + } + +private: + void run() final + { + const auto copyTimer = [&] + { + std::scoped_lock lock { runCopyMutex }; + return timer; + }; + + while (! threadShouldExit()) + { + if (auto t = copyTimer()) + t->run(); + + wait (-1); + } + } + + class Timer + { + public: + Timer (PlatformTimerListener& l, int i) + : listener { l }, intervalMs { i } {} + + int getIntervalMs() const + { + return intervalMs; + } + + void cancel() + { + stop.signal(); + } + + void run() + { + #if JUCE_MAC || JUCE_IOS + tryToUpgradeCurrentThreadToRealtime (Thread::RealtimeOptions{}.withPeriodMs (intervalMs)); + #endif + + const auto millisecondsUntil = [] (auto time) + { + return jmax (0.0, time - Time::getMillisecondCounterHiRes()); + }; + + while (! stop.wait (millisecondsUntil (nextEventTime))) + { + if (Time::getMillisecondCounterHiRes() >= nextEventTime) + { + listener.onTimerExpired(); + nextEventTime += intervalMs; + } + } + } + + private: + PlatformTimerListener& listener; + const int intervalMs; + double nextEventTime = Time::getMillisecondCounterHiRes() + intervalMs; + WaitableEvent stop { true }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Timer) + JUCE_DECLARE_NON_MOVEABLE (Timer) + }; + + PlatformTimerListener& listener; + mutable std::mutex runCopyMutex; + std::shared_ptr timer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTimer) + JUCE_DECLARE_NON_MOVEABLE (PlatformTimer) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_windows.cpp b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_windows.cpp new file mode 100644 index 0000000..f4cb516 --- /dev/null +++ b/JuceLibraryCode/modules/juce_core/native/juce_PlatformTimer_windows.cpp @@ -0,0 +1,68 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class PlatformTimer final +{ +public: + explicit PlatformTimer (PlatformTimerListener& ptl) + : listener { ptl } {} + + void startTimer (int newIntervalMs) + { + jassert (newIntervalMs > 0); + + const auto callback = [] (UINT, UINT, DWORD_PTR context, DWORD_PTR, DWORD_PTR) + { + reinterpret_cast (context)->onTimerExpired(); + }; + + timerId = timeSetEvent ((UINT) newIntervalMs, 1, callback, (DWORD_PTR) &listener, TIME_PERIODIC | TIME_CALLBACK_FUNCTION); + intervalMs = timerId != 0 ? newIntervalMs : 0; + } + + void cancelTimer() + { + jassert (timerId != 0); + + timeKillEvent (timerId); + timerId = 0; + intervalMs = 0; + } + + int getIntervalMs() const + { + return intervalMs; + } + +private: + PlatformTimerListener& listener; + UINT timerId { 0 }; + int intervalMs { 0 }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PlatformTimer) + JUCE_DECLARE_NON_MOVEABLE (PlatformTimer) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Registry.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Registry_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_Registry.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Registry_windows.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/JuceLibraryCode/modules/juce_core/native/juce_RuntimePermissions_android.cpp similarity index 97% rename from JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_RuntimePermissions_android.cpp index 45636eb..52584e1 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_RuntimePermissions_android.cpp @@ -43,7 +43,11 @@ static StringArray jucePermissionToAndroidPermissions (RuntimePermissions::Permi "android.permission.BLUETOOTH_CONNECT" }; } - case RuntimePermissions::writeExternalStorage: return { "android.permission.WRITE_EXTERNAL_STORAGE" }; + // WRITE_EXTERNAL_STORAGE has no effect on SDK 29+ + case RuntimePermissions::writeExternalStorage: + return getAndroidSDKVersion() < 29 ? StringArray { "android.permission.WRITE_EXTERNAL_STORAGE" } + : StringArray{}; + case RuntimePermissions::camera: return { "android.permission.CAMERA" }; case RuntimePermissions::readExternalStorage: diff --git a/JuceLibraryCode/modules/juce_core/native/juce_intel_SharedCode.h b/JuceLibraryCode/modules/juce_core/native/juce_SharedCode_intel.h similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_intel_SharedCode.h rename to JuceLibraryCode/modules/juce_core/native/juce_SharedCode_intel.h diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h b/JuceLibraryCode/modules/juce_core/native/juce_SharedCode_posix.h similarity index 98% rename from JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h rename to JuceLibraryCode/modules/juce_core/native/juce_SharedCode_posix.h index 2a739b6..ddddfb9 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_posix_SharedCode.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_SharedCode_posix.h @@ -854,7 +854,7 @@ class PosixThreadAttribute public: explicit PosixThreadAttribute (size_t stackSize) { - if (valid) + if (valid && stackSize != 0) pthread_attr_setstacksize (&attr, stackSize); } @@ -894,7 +894,7 @@ class PosixSchedulerPriority const auto min = jmax (0, sched_get_priority_min (SCHED_RR)); const auto max = jmax (1, sched_get_priority_max (SCHED_RR)); - return jmap (rt->priority, 0, 10, min, max); + return jmap (rt->getPriority(), 0, 10, min, max); } // We only use this helper if we're on an old macos/ios platform that might @@ -924,9 +924,9 @@ class PosixSchedulerPriority return 0; }(); - #if JUCE_MAC || JUCE_IOS + #if JUCE_MAC || JUCE_IOS || JUCE_BSD const auto scheduler = SCHED_OTHER; - #elif JUCE_LINUX || JUCE_BSD + #elif JUCE_LINUX const auto backgroundSched = prio == Thread::Priority::background ? SCHED_IDLE : SCHED_OTHER; const auto scheduler = isRealtime ? SCHED_RR : backgroundSched; @@ -959,11 +959,13 @@ class PosixSchedulerPriority int priority; }; -static void* makeThreadHandle (PosixThreadAttribute& attr, Thread* userData, void* (*threadEntryProc) (void*)) +static void* makeThreadHandle (PosixThreadAttribute& attr, void* userData, void* (*threadEntryProc) (void*)) { pthread_t handle = {}; - if (pthread_create (&handle, attr.get(), threadEntryProc, userData) != 0) + const auto status = pthread_create (&handle, attr.get(), threadEntryProc, userData); + + if (status != 0) return nullptr; pthread_detach (handle); @@ -1020,8 +1022,16 @@ void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask ([[maybe_unused]] uint32 CPU_ZERO (&affinity); for (int i = 0; i < 32; ++i) + { if ((affinityMask & (uint32) (1 << i)) != 0) + { + // GCC 12 on FreeBSD complains about CPU_SET irrespective of + // the type of the first argument + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wsign-conversion") CPU_SET ((size_t) i, &affinity); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + } #if (! JUCE_ANDROID) && ((! (JUCE_LINUX || JUCE_BSD)) || ((__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2004)) pthread_setaffinity_np (pthread_self(), sizeof (cpu_set_t), &affinity); diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm b/JuceLibraryCode/modules/juce_core/native/juce_Strings_mac.mm similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_Strings.mm rename to JuceLibraryCode/modules/juce_core/native/juce_Strings_mac.mm diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_android_SystemStats.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_SystemStats_android.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_linux.cpp similarity index 98% rename from JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_SystemStats_linux.cpp index 562a37b..74601fa 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_linux.cpp @@ -153,7 +153,7 @@ int SystemStats::getMemorySizeInMegabytes() int64 memory = 0; auto memorySize = sizeof (memory); auto result = sysctl (mib, numElementsInArray (mib), &memory, &memorySize, nullptr, 0); - return result == 0 ? (int) (memory / 1e6) : 0; + return result == 0 ? (int) (memory / (int64) 1e6) : 0; #else struct sysinfo sysi; @@ -204,7 +204,7 @@ String SystemStats::getUserLanguage() return {}; #else - return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); + return getLocaleValue (_NL_ADDRESS_LANG_AB); #endif } @@ -213,7 +213,7 @@ String SystemStats::getUserRegion() #if JUCE_BSD return {}; #else - return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); + return getLocaleValue (_NL_ADDRESS_COUNTRY_AB2); #endif } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_mac.mm similarity index 80% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm rename to JuceLibraryCode/modules/juce_core/native/juce_SystemStats_mac.mm index 0a5f5fd..eaa1478 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_mac.mm @@ -209,7 +209,7 @@ static String getOSXVersion() return String (reinterpret_cast (vendor), 12); #else - return {}; + return "Apple"; #endif } @@ -226,17 +226,25 @@ static String getOSXVersion() int SystemStats::getCpuSpeedInMegahertz() { + #ifdef JUCE_INTEL uint64 speedHz = 0; - size_t speedSize = sizeof (speedHz); + size_t optSize = sizeof (speedHz); int mib[] = { CTL_HW, HW_CPU_FREQ }; - sysctl (mib, 2, &speedHz, &speedSize, nullptr, 0); - - #if JUCE_BIG_ENDIAN - if (speedSize == 4) - speedHz >>= 32; - #endif + sysctl (mib, 2, &speedHz, &optSize, nullptr, 0); return (int) (speedHz / 1000000); + #else + size_t hz = 0; + size_t optSize = sizeof (hz); + sysctlbyname ("hw.tbfrequency", &hz, &optSize, nullptr, 0); + + struct clockinfo ci{}; + optSize = sizeof (ci); + int mib[] = { CTL_KERN, KERN_CLOCKRATE }; + sysctl (mib, 2, &ci, &optSize, nullptr, 0); + + return (int) (double (hz * uint64_t (ci.hz)) / 1000000.0); + #endif } //============================================================================== @@ -343,32 +351,63 @@ uint32 millisecondsSinceStartup() const noexcept String SystemStats::getUniqueDeviceID() { - static const auto deviceId = [] + #if JUCE_MAC + constexpr mach_port_t port = 0; + + const auto dict = IOServiceMatching ("IOPlatformExpertDevice"); + + if (const auto service = IOServiceGetMatchingService (port, dict); service != IO_OBJECT_NULL) { - ChildProcess proc; + const ScopeGuard scope { [&] { IOObjectRelease (service); } }; - if (proc.start ("ioreg -rd1 -c IOPlatformExpertDevice", ChildProcess::wantStdOut)) - { - constexpr const char key[] = "\"IOPlatformUUID\""; - constexpr const auto keyLen = (int) sizeof (key); + if (const CFUniquePtr uuidTypeRef { IORegistryEntryCreateCFProperty (service, CFSTR ("IOPlatformUUID"), kCFAllocatorDefault, 0) }) + if (CFGetTypeID (uuidTypeRef.get()) == CFStringGetTypeID()) + return String::fromCFString ((CFStringRef) uuidTypeRef.get()).removeCharacters ("-"); + } + #elif JUCE_IOS + JUCE_AUTORELEASEPOOL + { + if (UIDevice* device = [UIDevice currentDevice]) + if (NSUUID* uuid = [device identifierForVendor]) + return nsStringToJuce ([uuid UUIDString]); + } + #endif - auto output = proc.readAllProcessOutput(); - auto index = output.indexOf (key); + return ""; +} - if (index >= 0) - { - auto start = output.indexOf (index + keyLen, "\""); - auto end = output.indexOf (start + 1, "\""); - return output.substring (start + 1, end).replace("-", ""); - } - } +#if JUCE_MAC +bool SystemStats::isAppSandboxEnabled() +{ + static const auto result = [&] + { + SecCodeRef ref = nullptr; + + if (const auto err = SecCodeCopySelf (kSecCSDefaultFlags, &ref); err != noErr) + return false; + + const CFUniquePtr managedRef (ref); + CFDictionaryRef infoDict = nullptr; - return String(); + if (const auto err = SecCodeCopySigningInformation (managedRef.get(), kSecCSDynamicInformation, &infoDict); err != noErr) + return false; + + const CFUniquePtr managedInfoDict (infoDict); + const void* entitlementsDict = nullptr; + + if (! CFDictionaryGetValueIfPresent (managedInfoDict.get(), kSecCodeInfoEntitlementsDict, &entitlementsDict)) + return false; + + const void* flag = nullptr; + + if (! CFDictionaryGetValueIfPresent (static_cast (entitlementsDict), @"com.apple.security.app-sandbox", &flag)) + return false; + + return static_cast (CFBooleanGetValue (static_cast (flag))); }(); - // Please tell someone at JUCE if this occurs - jassert (deviceId.isNotEmpty()); - return deviceId; + return result; } +#endif } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/native/juce_wasm_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_wasm.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_wasm_SystemStats.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_SystemStats_wasm.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_windows.cpp similarity index 73% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_SystemStats_windows.cpp index bb4020b..0972e83 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_SystemStats_windows.cpp @@ -659,28 +659,221 @@ String SystemStats::getDisplayLanguage() return languagesBuffer.data(); } -String SystemStats::getUniqueDeviceID() +static constexpr DWORD generateProviderID (const char* string) { - #define PROVIDER(string) (DWORD) (string[0] << 24 | string[1] << 16 | string[2] << 8 | string[3]) + return (DWORD) string[0] << 0x18 + | (DWORD) string[1] << 0x10 + | (DWORD) string[2] << 0x08 + | (DWORD) string[3] << 0x00; +} - auto bufLen = GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), nullptr, 0); +static std::optional> readSMBIOSData() +{ + const auto sig = generateProviderID ("RSMB"); + const auto id = generateProviderID ("RSDT"); - if (bufLen > 0) + if (const auto bufLen = GetSystemFirmwareTable (sig, id, nullptr, 0); bufLen > 0) { - HeapBlock buffer { bufLen }; - GetSystemFirmwareTable (PROVIDER ("RSMB"), PROVIDER ("RSDT"), (void*) buffer.getData(), bufLen); + std::vector buffer; + + buffer.resize (bufLen); + + if (GetSystemFirmwareTable (sig, id, buffer.data(), bufLen) == buffer.size()) + return std::make_optional (std::move (buffer)); + } + + return {}; +} - return [&] +String getLegacyUniqueDeviceID() +{ + if (const auto dump = readSMBIOSData()) + { + uint64_t hash = 0; + const auto start = dump->data(); + const auto end = start + jmin (1024, (int) dump->size()); + + for (auto dataPtr = start; dataPtr != end; ++dataPtr) + hash = hash * (uint64_t) 101 + (uint8_t) *dataPtr; + + return String (hash); + } + + return {}; +} + +String SystemStats::getUniqueDeviceID() +{ + if (const auto smbiosBuffer = readSMBIOSData()) + { + #pragma pack (push, 1) + struct RawSMBIOSData + { + uint8_t unused[4]; + uint32_t length; + }; + + struct SMBIOSHeader + { + uint8_t id; + uint8_t length; + uint16_t handle; + }; + #pragma pack (pop) + + if (smbiosBuffer->size() < sizeof (RawSMBIOSData)) { - uint64_t hash = 0; - const auto start = buffer.getData(); - const auto end = start + jmin (1024, (int) bufLen); + // Malformed buffer; not enough room for RawSMBIOSData instance + jassertfalse; + return {}; + } + + String uuid; + const auto* asRawSMBIOSData = unalignedPointerCast (smbiosBuffer->data()); + + if (smbiosBuffer->size() < sizeof (RawSMBIOSData) + static_cast (asRawSMBIOSData->length)) + { + // Malformed buffer; declared length is longer than the buffer we were given + jassertfalse; + return {}; + } + + Span content (smbiosBuffer->data() + sizeof (RawSMBIOSData), asRawSMBIOSData->length); - for (auto dataPtr = start; dataPtr != end; ++dataPtr) - hash = hash * (uint64_t) 101 + *dataPtr; + while (! content.empty()) + { + if (content.size() < sizeof (SMBIOSHeader)) + { + // Malformed buffer; not enough room for header + jassertfalse; + break; + } + + const auto* header = unalignedPointerCast (content.data()); + + if (content.size() < header->length) + { + // Malformed buffer; declared length is longer than the buffer we were given + jassertfalse; + break; + } + + std::vector strings; + + // Each table comprises a struct and a varying number of null terminated + // strings. The string section is delimited by a pair of null terminators. + // Some fields in the header are indices into the string table. + + const auto endOfStringTable = [&header, &strings, &content] + { + const auto* dataTable = unalignedPointerCast (content.data()); + size_t stringOffset = header->length; + + while (stringOffset < content.size()) + { + const auto* str = dataTable + stringOffset; + const auto maxLength = content.size() - stringOffset; + const auto n = strnlen (str, maxLength); + + if (n == 0) + break; + + strings.emplace_back (str, n); + stringOffset += std::min (n + 1, maxLength); + } + + const auto lengthAfterHeader = jmax ((size_t) header->length + 2, stringOffset + 1); + return jmin (lengthAfterHeader, content.size()); + }(); + + const auto stringFromOffset = [&content, &strings] (size_t byteOffset) -> String + { + if (! isPositiveAndBelow (byteOffset, content.size())) + return std::string{}; + + const auto index = std::to_integer (content[byteOffset]); + + if (index <= 0 || strings.size() < index) + return std::string{}; + + const auto view = strings[index - 1]; + return std::string { view }; + }; + + enum + { + systemManufacturer = 0x04, + systemProductName = 0x05, + systemSerialNumber = 0x07, + systemUUID = 0x08, // 16byte UUID. Can be all 0xFF or all 0x00. Might be user changeable. + systemSKU = 0x19, + systemFamily = 0x1a, + + baseboardManufacturer = 0x04, + baseboardProduct = 0x05, + baseboardVersion = 0x06, + baseboardSerialNumber = 0x07, + baseboardAssetTag = 0x08, + + processorManufacturer = 0x07, + processorVersion = 0x10, + processorAssetTag = 0x21, + processorPartNumber = 0x22 + }; + + switch (header->id) + { + case 1: // System + { + uuid += stringFromOffset (systemManufacturer); + uuid += "\n"; + uuid += stringFromOffset (systemProductName); + uuid += "\n"; + + char hexBuf[(16 * 2) + 1]{}; + + if (systemUUID + 16 < content.size()) + { + const auto* src = content.data() + systemUUID; + + for (auto i = 0; i != 16; ++i) + snprintf (hexBuf + 2 * i, 3, "%02hhX", std::to_integer (src[i])); + } + + uuid += hexBuf; + uuid += "\n"; + break; + } + + case 2: // Baseboard + uuid += stringFromOffset (baseboardManufacturer); + uuid += "\n"; + uuid += stringFromOffset (baseboardProduct); + uuid += "\n"; + uuid += stringFromOffset (baseboardVersion); + uuid += "\n"; + uuid += stringFromOffset (baseboardSerialNumber); + uuid += "\n"; + uuid += stringFromOffset (baseboardAssetTag); + uuid += "\n"; + break; + + case 4: // Processor + uuid += stringFromOffset (processorManufacturer); + uuid += "\n"; + uuid += stringFromOffset (processorVersion); + uuid += "\n"; + uuid += stringFromOffset (processorAssetTag); + uuid += "\n"; + uuid += stringFromOffset (processorPartNumber); + uuid += "\n"; + break; + } + + content = Span (content.data() + endOfStringTable, content.size() - endOfStringTable); + } - return String (hash); - }(); + return String (uuid.hashCode64()); } // Please tell someone at JUCE if this occurs diff --git a/JuceLibraryCode/modules/juce_core/native/juce_native_ThreadPriorities.h b/JuceLibraryCode/modules/juce_core/native/juce_ThreadPriorities_native.h similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_native_ThreadPriorities.h rename to JuceLibraryCode/modules/juce_core/native/juce_ThreadPriorities_native.h diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Threads_android.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Threads_android.cpp index cad1d03..19a0507 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_Threads.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_Threads_android.cpp @@ -448,6 +448,4 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} - - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Threads_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_linux_Threads.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Threads_linux.cpp diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm b/JuceLibraryCode/modules/juce_core/native/juce_Threads_mac.mm similarity index 63% rename from JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm rename to JuceLibraryCode/modules/juce_core/native/juce_Threads_mac.mm index 2d4b05b..ed81ea2 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Threads.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_Threads_mac.mm @@ -64,28 +64,99 @@ static auto getJucePriority (qos_class_t qos) return Thread::Priority::normal; } +template +static std::optional firstOptionalWithValue (const std::initializer_list>& optionals) +{ + for (const auto& optional : optionals) + if (optional.has_value()) + return optional; + + return {}; +} + +static bool tryToUpgradeCurrentThreadToRealtime (const Thread::RealtimeOptions& options) +{ + const auto periodMs = options.getPeriodMs().value_or (0.0); + + const auto processingTimeMs = firstOptionalWithValue ( + { + options.getProcessingTimeMs(), + options.getMaximumProcessingTimeMs(), + options.getPeriodMs() + }).value_or (10.0); + + const auto maxProcessingTimeMs = options.getMaximumProcessingTimeMs() + .value_or (processingTimeMs); + + // The processing time can not exceed the maximum processing time! + jassert (maxProcessingTimeMs >= processingTimeMs); + + thread_time_constraint_policy_data_t policy; + policy.period = (uint32_t) Time::secondsToHighResolutionTicks (periodMs / 1'000.0); + policy.computation = (uint32_t) Time::secondsToHighResolutionTicks (processingTimeMs / 1'000.0); + policy.constraint = (uint32_t) Time::secondsToHighResolutionTicks (maxProcessingTimeMs / 1'000.0); + policy.preemptible = true; + + const auto result = thread_policy_set (pthread_mach_thread_np (pthread_self()), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t) &policy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT); + + if (result == KERN_SUCCESS) + return true; + + // testing has shown that passing a computation value > 50ms can + // lead to thread_policy_set returning an error indicating that an + // invalid argument was passed. If that happens this code tries to + // limit that value in the hope of resolving the issue. + + if (result == KERN_INVALID_ARGUMENT && options.getProcessingTimeMs() > 50.0) + return tryToUpgradeCurrentThreadToRealtime (options.withProcessingTimeMs (50.0)); + + return false; +} + bool Thread::createNativeThread (Priority priority) { - PosixThreadAttribute attr { threadStackSize }; + PosixThreadAttribute attribute { threadStackSize }; if (@available (macos 10.10, *)) - pthread_attr_set_qos_class_np (attr.get(), getNativeQOS (priority), 0); + pthread_attr_set_qos_class_np (attribute.get(), getNativeQOS (priority), 0); else - PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, priority).apply (attr); + PosixSchedulerPriority::getNativeSchedulerAndPriority (realtimeOptions, priority).apply (attribute); + + struct ThreadData + { + Thread& thread; + std::promise started{}; + }; + + ThreadData threadData { *this, {} }; - threadId = threadHandle = makeThreadHandle (attr, this, [] (void* userData) -> void* + threadId = threadHandle = makeThreadHandle (attribute, &threadData, [] (void* userData) -> void* { - auto* myself = static_cast (userData); + auto& data { *static_cast (userData) }; + auto& thread = data.thread; + + if (thread.isRealtime() + && ! tryToUpgradeCurrentThreadToRealtime (*thread.realtimeOptions)) + { + data.started.set_value (false); + return nullptr; + } + + data.started.set_value (true); JUCE_AUTORELEASEPOOL { - juce_threadEntryPoint (myself); + juce_threadEntryPoint (&thread); } return nullptr; }); - return threadId != nullptr; + return threadId != nullptr + && threadData.started.get_future().get(); } void Thread::killThread() @@ -122,30 +193,6 @@ static auto getJucePriority (qos_class_t qos) { jassert (Thread::getCurrentThreadId() == getThreadId()); - if (isRealtime()) - { - // macOS/iOS needs to know how much time you need! - jassert (realtimeOptions->workDurationMs > 0); - - mach_timebase_info_data_t timebase; - mach_timebase_info (&timebase); - - const auto periodMs = realtimeOptions->workDurationMs; - const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; - const auto periodTicks = (uint32_t) jmin ((double) std::numeric_limits::max(), periodMs * ticksPerMs); - - thread_time_constraint_policy_data_t policy; - policy.period = periodTicks; - policy.computation = jmin ((uint32_t) 50000, policy.period); - policy.constraint = policy.period; - policy.preemptible = true; - - return thread_policy_set (pthread_mach_thread_np (pthread_self()), - THREAD_TIME_CONSTRAINT_POLICY, - (thread_policy_t) &policy, - THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS; - } - if (@available (macOS 10.10, *)) return pthread_set_qos_class_self_np (getNativeQOS (priority), 0) == 0; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_Threads_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp rename to JuceLibraryCode/modules/juce_core/native/juce_Threads_windows.cpp diff --git a/JuceLibraryCode/modules/juce_core/network/juce_WebInputStream.cpp b/JuceLibraryCode/modules/juce_core/network/juce_WebInputStream.cpp index ec3b604..dac2036 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_WebInputStream.cpp +++ b/JuceLibraryCode/modules/juce_core/network/juce_WebInputStream.cpp @@ -61,11 +61,8 @@ StringPairArray WebInputStream::parseHttpHeaders (const String& headerData) StringPairArray headerPairs; auto headerLines = StringArray::fromLines (headerData); - // ignore the first line as this is the status line - for (int i = 1; i < headerLines.size(); ++i) + for (const auto& headersEntry : headerLines) { - const auto& headersEntry = headerLines[i]; - if (headersEntry.isNotEmpty()) { const auto key = headersEntry.upToFirstOccurrenceOf (": ", false, false); diff --git a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp index 0258657..45dee27 100644 --- a/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp +++ b/JuceLibraryCode/modules/juce_core/streams/juce_MemoryInputStream.cpp @@ -138,8 +138,8 @@ class MemoryStreamTests : public UnitTest expectEquals (mi.readString(), randomString); expect (mi.readInt64() == randomInt64); expect (mi.readInt64BigEndian() == randomInt64); - expect (mi.readDouble() == randomDouble); - expect (mi.readDoubleBigEndian() == randomDouble); + expectEquals (mi.readDouble(), randomDouble); + expectEquals (mi.readDoubleBigEndian(), randomDouble); const MemoryBlock data ("abcdefghijklmnopqrstuvwxyz", 26); MemoryInputStream stream (data, true); diff --git a/JuceLibraryCode/modules/juce_core/system/juce_CompilerWarnings.h b/JuceLibraryCode/modules/juce_core/system/juce_CompilerWarnings.h index c24f288..779fab7 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_CompilerWarnings.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_CompilerWarnings.h @@ -31,7 +31,8 @@ #define JUCE_NTH_ARG_(_00, _01, _02, _03, _04, _05, _06, _07, _08, _09, \ _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ - _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, N, ...)\ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \ + _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, N, ...)\ N #define JUCE_EACH_00_(FN) @@ -74,10 +75,30 @@ #define JUCE_EACH_37_(FN, X, ...) FN(X) JUCE_EACH_36_(FN, __VA_ARGS__) #define JUCE_EACH_38_(FN, X, ...) FN(X) JUCE_EACH_37_(FN, __VA_ARGS__) #define JUCE_EACH_39_(FN, X, ...) FN(X) JUCE_EACH_38_(FN, __VA_ARGS__) +#define JUCE_EACH_40_(FN, X, ...) FN(X) JUCE_EACH_39_(FN, __VA_ARGS__) +#define JUCE_EACH_41_(FN, X, ...) FN(X) JUCE_EACH_40_(FN, __VA_ARGS__) +#define JUCE_EACH_42_(FN, X, ...) FN(X) JUCE_EACH_41_(FN, __VA_ARGS__) +#define JUCE_EACH_43_(FN, X, ...) FN(X) JUCE_EACH_42_(FN, __VA_ARGS__) +#define JUCE_EACH_44_(FN, X, ...) FN(X) JUCE_EACH_43_(FN, __VA_ARGS__) +#define JUCE_EACH_45_(FN, X, ...) FN(X) JUCE_EACH_44_(FN, __VA_ARGS__) +#define JUCE_EACH_46_(FN, X, ...) FN(X) JUCE_EACH_45_(FN, __VA_ARGS__) +#define JUCE_EACH_47_(FN, X, ...) FN(X) JUCE_EACH_46_(FN, __VA_ARGS__) +#define JUCE_EACH_48_(FN, X, ...) FN(X) JUCE_EACH_47_(FN, __VA_ARGS__) +#define JUCE_EACH_49_(FN, X, ...) FN(X) JUCE_EACH_48_(FN, __VA_ARGS__) /** Apply the macro FN to each of the other arguments. */ #define JUCE_EACH(FN, ...) \ JUCE_NTH_ARG_(, __VA_ARGS__, \ + JUCE_EACH_49_, \ + JUCE_EACH_48_, \ + JUCE_EACH_47_, \ + JUCE_EACH_46_, \ + JUCE_EACH_45_, \ + JUCE_EACH_44_, \ + JUCE_EACH_43_, \ + JUCE_EACH_42_, \ + JUCE_EACH_41_, \ + JUCE_EACH_40_, \ JUCE_EACH_39_, \ JUCE_EACH_38_, \ JUCE_EACH_37_, \ diff --git a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h index 514c719..3c1da70 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h @@ -29,7 +29,7 @@ */ #define JUCE_MAJOR_VERSION 7 #define JUCE_MINOR_VERSION 0 -#define JUCE_BUILDNUMBER 5 +#define JUCE_BUILDNUMBER 7 /** Current JUCE version number. @@ -72,6 +72,7 @@ #include #include #include +#include #include //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp index 9cb5a54..319ab78 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.cpp @@ -60,24 +60,64 @@ String SystemStats::getJUCEVersion() StringArray SystemStats::getDeviceIdentifiers() { - StringArray ids; + for (const auto flag : { MachineIdFlags::fileSystemId, MachineIdFlags::macAddresses }) + if (auto ids = getMachineIdentifiers (flag); ! ids.isEmpty()) + return ids; - #if JUCE_WINDOWS - File f (File::getSpecialLocation (File::windowsSystemDirectory)); - #else - File f ("~"); - #endif - if (auto num = f.getFileIdentifier()) + jassertfalse; // Failed to create any IDs! + return {}; +} + +String getLegacyUniqueDeviceID(); + +StringArray SystemStats::getMachineIdentifiers (MachineIdFlags flags) +{ + auto macAddressProvider = [] (StringArray& arr) { - ids.add (String::toHexString ((int64) num)); - } - else + for (const auto& mac : MACAddress::getAllAddresses()) + arr.add (mac.toString()); + }; + + auto fileSystemProvider = [] (StringArray& arr) { - for (auto& address : MACAddress::getAllAddresses()) - ids.add (address.toString()); + #if JUCE_WINDOWS + File f (File::getSpecialLocation (File::windowsSystemDirectory)); + #else + File f ("~"); + #endif + if (auto num = f.getFileIdentifier()) + arr.add (String::toHexString ((int64) num)); + }; + + auto legacyIdProvider = [] ([[maybe_unused]] StringArray& arr) + { + #if JUCE_WINDOWS + arr.add (getLegacyUniqueDeviceID()); + #endif + }; + + auto uniqueIdProvider = [] (StringArray& arr) + { + arr.add (getUniqueDeviceID()); + }; + + struct Provider { MachineIdFlags flag; void (*func) (StringArray&); }; + static const Provider providers[] = + { + { MachineIdFlags::macAddresses, macAddressProvider }, + { MachineIdFlags::fileSystemId, fileSystemProvider }, + { MachineIdFlags::legacyUniqueId, legacyIdProvider }, + { MachineIdFlags::uniqueId, uniqueIdProvider } + }; + + StringArray ids; + + for (const auto& provider : providers) + { + if (hasBitValueSet (flags, provider.flag)) + provider.func (ids); } - jassert (! ids.isEmpty()); // Failed to create any IDs! return ids; } @@ -177,7 +217,7 @@ String SystemStats::getStackBacktrace() auto frames = backtrace (stack, numElementsInArray (stack)); char** frameStrings = backtrace_symbols (stack, frames); - for (int i = 0; i < frames; ++i) + for (auto i = (decltype (frames)) 0; i < frames; ++i) result << frameStrings[i] << newLine; ::free (frameStrings); @@ -230,13 +270,8 @@ void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) bool SystemStats::isRunningInAppExtensionSandbox() noexcept { #if JUCE_MAC || JUCE_IOS - static bool firstQuery = true; - static bool isRunningInAppSandbox = false; - - if (firstQuery) + static bool isRunningInAppSandbox = [&] { - firstQuery = false; - File bundle = File::getSpecialLocation (File::invokedExecutableFile).getParentDirectory(); #if JUCE_MAC @@ -244,8 +279,10 @@ bool SystemStats::isRunningInAppExtensionSandbox() noexcept #endif if (bundle.isDirectory()) - isRunningInAppSandbox = (bundle.getFileExtension() == ".appex"); - } + return bundle.getFileExtension() == ".appex"; + + return false; + }(); return isRunningInAppSandbox; #else diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h index 8404b54..5d082ad 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h @@ -153,10 +153,41 @@ class JUCE_API SystemStats final changes. This ID will be invalidated by changes to the motherboard and CPU on non-mobile - platforms, or resetting an Android device. + platforms, or performing a system restore on an Android device. + + There are some extra caveats on iOS: The returned ID is unique to the vendor part of + your 'Bundle Identifier' and is stable for all associated apps. The key is invalidated + once all associated apps are uninstalled. This function can return an empty string + under certain conditions, for example, If the device has not been unlocked since a + restart. */ static String getUniqueDeviceID(); + /** Kinds of identifier that are passed to getMachineIdentifiers(). */ + enum class MachineIdFlags + { + macAddresses = 1 << 0, ///< All Mac addresses of the machine. + fileSystemId = 1 << 1, ///< The filesystem id of the user's home directory (or system directory on Windows). + legacyUniqueId = 1 << 2, ///< Only implemented on Windows. A hash of the full smbios table, may be unstable on certain machines. + uniqueId = 1 << 3, ///< The most stable kind of machine identifier. A good default to use. + }; + + /** Returns a list of strings that can be used to uniquely identify a machine. + + To get multiple kinds of identifier at once, you can combine flags using + bitwise-or, e.g. `uniqueId | legacyUniqueId`. + + If a particular kind of identifier isn't available, it will be omitted from + the StringArray of results, so passing `uniqueId | legacyUniqueId` + may return 0, 1, or 2 results, depending on the platform and whether any + errors are encountered. + + If you've previously generated a machine ID and just want to check it against + all possible identifiers, you can enable all of the flags and check whether + the stored identifier matches any of the results. + */ + static StringArray getMachineIdentifiers (MachineIdFlags flags); + //============================================================================== // CPU and memory information.. @@ -228,7 +259,7 @@ class JUCE_API SystemStats final /** A function type for use in setApplicationCrashHandler(). When called, its void* argument will contain platform-specific data about the crash. */ - using CrashHandlerFunction = void(*)(void*); + using CrashHandlerFunction = void (*) (void*); /** Sets up a global callback function that will be called if the application executes some kind of illegal instruction. @@ -243,6 +274,10 @@ class JUCE_API SystemStats final */ static bool isRunningInAppExtensionSandbox() noexcept; + #if JUCE_MAC + static bool isAppSandboxEnabled(); + #endif + //============================================================================== #ifndef DOXYGEN [[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]] @@ -254,4 +289,6 @@ class JUCE_API SystemStats final JUCE_DECLARE_NON_COPYABLE (SystemStats) }; +JUCE_DECLARE_SCOPED_ENUM_BITWISE_OPERATORS (SystemStats::MachineIdFlags) + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h b/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h index 9ce3c62..0e7ee21 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_TargetPlatform.h @@ -62,7 +62,7 @@ #elif defined (JUCE_ANDROID) #undef JUCE_ANDROID #define JUCE_ANDROID 1 -#elif defined (__FreeBSD__) || (__OpenBSD__) +#elif defined (__FreeBSD__) || defined (__OpenBSD__) #define JUCE_BSD 1 #elif defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 diff --git a/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp index 4e589ce..3a021ee 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -134,7 +134,7 @@ double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept if (exponent == 0) return value; - if (value == 0.0) + if (exactlyEqual (value, 0.0)) return 0; const bool negative = (exponent < 0); diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp index e994394..a91df80 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp @@ -2266,7 +2266,7 @@ static String serialiseDouble (double input) int intInput = (int) input; - if ((double) intInput == input) + if (exactlyEqual ((double) intInput, input)) return { input, 1 }; auto numberOfDecimalPlaces = [absInput] @@ -2567,16 +2567,16 @@ class StringTests : public UnitTest beginTest ("Numeric conversions"); expect (String().getIntValue() == 0); - expect (String().getDoubleValue() == 0.0); - expect (String().getFloatValue() == 0.0f); + expectEquals (String().getDoubleValue(), 0.0); + expectEquals (String().getFloatValue(), 0.0f); expect (s.getIntValue() == 12345678); expect (s.getLargeIntValue() == (int64) 12345678); - expect (s.getDoubleValue() == 12345678.0); - expect (s.getFloatValue() == 12345678.0f); + expectEquals (s.getDoubleValue(), 12345678.0); + expectEquals (s.getFloatValue(), 12345678.0f); expect (String (-1234).getIntValue() == -1234); expect (String ((int64) -1234).getLargeIntValue() == -1234); - expect (String (-1234.56).getDoubleValue() == -1234.56); - expect (String (-1234.56f).getFloatValue() == -1234.56f); + expectEquals (String (-1234.56).getDoubleValue(), -1234.56); + expectEquals (String (-1234.56f).getFloatValue(), -1234.56f); expect (String (std::numeric_limits::max()).getIntValue() == std::numeric_limits::max()); expect (String (std::numeric_limits::min()).getIntValue() == std::numeric_limits::min()); expect (String (std::numeric_limits::max()).getLargeIntValue() == std::numeric_limits::max()); diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.h b/JuceLibraryCode/modules/juce_core/text/juce_String.h index 32a9b88..4148351 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_String.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.h @@ -1117,7 +1117,7 @@ class JUCE_API String final { jassert (numberOfSignificantFigures > 0); - if (number == 0) + if (exactlyEqual (number, DecimalType())) { if (numberOfSignificantFigures > 1) { diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp index ea7b9c0..cb1d842 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp +++ b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.cpp @@ -23,98 +23,411 @@ namespace juce { -class HighResolutionTimer::Pimpl : public Thread +//============================================================================== +class HighResolutionTimer::Impl : private PlatformTimerListener { - using steady_clock = std::chrono::steady_clock; - using milliseconds = std::chrono::milliseconds; - public: - explicit Pimpl (HighResolutionTimer& ownerRef) - : Thread ("HighResolutionTimerThread"), - owner (ownerRef) - { - } - - using Thread::isThreadRunning; + explicit Impl (HighResolutionTimer& o) + : owner { o } {} - void start (int periodMs) + void startTimer (int newIntervalMs) { + shouldCancelCallbacks.store (true); + + const auto shouldWaitForPendingCallbacks = [&] { - const std::scoped_lock lk { mutex }; - periodMillis = periodMs; - nextTickTime = steady_clock::now() + milliseconds (periodMillis); - } + const std::scoped_lock lock { timerMutex }; - waitEvent.notify_one(); + if (timer.getIntervalMs() > 0) + timer.cancelTimer(); - if (! isThreadRunning()) - startThread (Thread::Priority::high); - } + jassert (timer.getIntervalMs() == 0); - void stop() - { - { - const std::scoped_lock lk { mutex }; - periodMillis = 0; - } + if (newIntervalMs > 0) + timer.startTimer (jmax (0, newIntervalMs)); - waitEvent.notify_one(); + return callbackThreadId != std::this_thread::get_id() + && timer.getIntervalMs() <= 0; + }(); + + if (shouldWaitForPendingCallbacks) + std::scoped_lock lock { callbackMutex }; + } - if (Thread::getCurrentThreadId() != getThreadId()) - stopThread (-1); + int getIntervalMs() const + { + const std::scoped_lock lock { timerMutex }; + return timer.getIntervalMs(); } - int getPeriod() const + bool isTimerRunning() const { - return periodMillis; + return getIntervalMs() > 0; } private: - void run() override + void onTimerExpired() final { - for (;;) - { - { - std::unique_lock lk { mutex }; + callbackThreadId.store (std::this_thread::get_id()); - if (waitEvent.wait_until (lk, nextTickTime, [this] { return periodMillis == 0; })) - break; + { + std::scoped_lock lock { callbackMutex }; - nextTickTime = steady_clock::now() + milliseconds (periodMillis); + if (isTimerRunning()) + { + try + { + owner.hiResTimerCallback(); + } + catch (...) + { + // Exceptions thrown in a timer callback won't be + // propagated to the main thread, it's best to find + // a way to avoid them if possible + jassertfalse; + } } - - owner.hiResTimerCallback(); } + + callbackThreadId.store ({}); } HighResolutionTimer& owner; - std::atomic periodMillis { 0 }; - steady_clock::time_point nextTickTime; - std::mutex mutex; - std::condition_variable waitEvent; + mutable std::mutex timerMutex; + std::mutex callbackMutex; + std::atomic callbackThreadId{}; + std::atomic shouldCancelCallbacks { false }; + PlatformTimer timer { *this }; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Impl) + JUCE_DECLARE_NON_MOVEABLE (Impl) }; +//============================================================================== HighResolutionTimer::HighResolutionTimer() - : pimpl (new Pimpl (*this)) -{ -} + : impl (std::make_unique (*this)) {} HighResolutionTimer::~HighResolutionTimer() { + // You *must* call stopTimer from the derived class destructor to + // avoid data races on the timer's vtable + jassert (! isTimerRunning()); stopTimer(); } -void HighResolutionTimer::startTimer (int periodMs) +void HighResolutionTimer::startTimer (int newIntervalMs) { - pimpl->start (jmax (1, periodMs)); + impl->startTimer (newIntervalMs); } void HighResolutionTimer::stopTimer() { - pimpl->stop(); + impl->startTimer (0); } -bool HighResolutionTimer::isTimerRunning() const noexcept { return getTimerInterval() != 0; } -int HighResolutionTimer::getTimerInterval() const noexcept { return pimpl->getPeriod(); } +int HighResolutionTimer::getTimerInterval() const noexcept +{ + return impl->getIntervalMs(); +} + +bool HighResolutionTimer::isTimerRunning() const noexcept +{ + return impl->isTimerRunning(); +} + +//============================================================================== +#if JUCE_UNIT_TESTS + +class HighResolutionTimerTests : public UnitTest +{ +public: + HighResolutionTimerTests() + : UnitTest ("HighResolutionTimer", UnitTestCategories::threads) {} + + void runTest() override + { + constexpr int maximumTimeoutMs {30'000}; + + beginTest ("Start/stop a timer"); + { + WaitableEvent timerFiredOnce; + WaitableEvent timerFiredTwice; + + Timer timer {[&, callbackCount = 0] () mutable + { + switch (++callbackCount) + { + case 1: timerFiredOnce.signal(); return; + case 2: timerFiredTwice.signal(); return; + default: return; + } + }}; + + expect (! timer.isTimerRunning()); + expect (timer.getTimerInterval() == 0); + + timer.startTimer (1); + expect (timer.isTimerRunning()); + expect (timer.getTimerInterval() == 1); + expect (timerFiredOnce.wait (maximumTimeoutMs)); + expect (timerFiredTwice.wait (maximumTimeoutMs)); + + timer.stopTimer(); + expect (! timer.isTimerRunning()); + expect (timer.getTimerInterval() == 0); + } + + beginTest ("Stop a timer from the timer callback"); + { + WaitableEvent stoppedTimer; + + auto timerCallback = [&](Timer& timer) + { + expect (timer.isTimerRunning()); + timer.stopTimer(); + expect (! timer.isTimerRunning()); + stoppedTimer.signal(); + }; + + Timer timer {[&]{ timerCallback (timer); }}; + timer.startTimer (1); + expect (stoppedTimer.wait (maximumTimeoutMs)); + } + + beginTest ("Restart a timer from the timer callback"); + { + WaitableEvent restartTimer; + WaitableEvent timerRestarted; + WaitableEvent timerFiredAfterRestart; + + Timer timer {[&, callbackCount = 0] () mutable + { + switch (++callbackCount) + { + case 1: + expect (restartTimer.wait (maximumTimeoutMs)); + expect (timer.getTimerInterval() == 1); + + timer.startTimer (2); + expect (timer.getTimerInterval() == 2); + timerRestarted.signal(); + return; + + case 2: + expect (timer.getTimerInterval() == 2); + timerFiredAfterRestart.signal(); + return; + + default: + return; + } + }}; + + timer.startTimer (1); + expect (timer.getTimerInterval() == 1); + + restartTimer.signal(); + expect (timerRestarted.wait (maximumTimeoutMs)); + expect (timer.getTimerInterval() == 2); + expect (timerFiredAfterRestart.wait (maximumTimeoutMs)); + + timer.stopTimer(); + } + + beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish"); + { + WaitableEvent timerCallbackStarted; + WaitableEvent stoppingTimer; + std::atomic timerCallbackFinished { false }; + + Timer timer {[&, callbackCount = 0] () mutable + { + switch (++callbackCount) + { + case 1: + timerCallbackStarted.signal(); + expect (stoppingTimer.wait (maximumTimeoutMs)); + Thread::sleep (10); + timerCallbackFinished = true; + return; + + default: + return; + } + }}; + + timer.startTimer (1); + expect (timerCallbackStarted.wait (maximumTimeoutMs)); + + stoppingTimer.signal(); + timer.stopTimer(); + expect (timerCallbackFinished); + } + + beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish, even if the timer callback calls stopTimer first"); + { + WaitableEvent stoppedFromInsideTimerCallback; + WaitableEvent stoppingFromOutsideTimerCallback; + std::atomic timerCallbackFinished { false }; + + Timer timer {[&]() + { + timer.stopTimer(); + stoppedFromInsideTimerCallback.signal(); + expect (stoppingFromOutsideTimerCallback.wait (maximumTimeoutMs)); + Thread::sleep (10); + timerCallbackFinished = true; + + }}; + + timer.startTimer (1); + expect (stoppedFromInsideTimerCallback.wait (maximumTimeoutMs)); + + stoppingFromOutsideTimerCallback.signal(); + timer.stopTimer(); + expect (timerCallbackFinished); + } + + beginTest ("Adjusting a timer period from outside the timer callback doesn't cause data races"); + { + WaitableEvent timerCallbackStarted; + WaitableEvent timerRestarted; + WaitableEvent timerFiredAfterRestart; + std::atomic lastCallbackCount {0}; + + Timer timer {[&, callbackCount = 0] () mutable + { + switch (++callbackCount) + { + case 1: + expect (timer.getTimerInterval() == 1); + timerCallbackStarted.signal(); + Thread::sleep (10); + lastCallbackCount = 1; + return; + + case 2: + expect (timerRestarted.wait (maximumTimeoutMs)); + expect (timer.getTimerInterval() == 2); + lastCallbackCount = 2; + timerFiredAfterRestart.signal(); + return; + + default: + return; + } + }}; + + timer.startTimer (1); + expect (timerCallbackStarted.wait (maximumTimeoutMs)); + + timer.startTimer (2); + timerRestarted.signal(); + + expect (timerFiredAfterRestart.wait (maximumTimeoutMs)); + expect (lastCallbackCount == 2); + + timer.stopTimer(); + expect (lastCallbackCount == 2); + } + + beginTest ("A timer can be restarted externally, after being stopped internally"); + { + WaitableEvent timerStopped; + WaitableEvent timerFiredAfterRestart; + + Timer timer {[&, callbackCount = 0] () mutable + { + switch (++callbackCount) + { + case 1: + timer.stopTimer(); + timerStopped.signal(); + return; + + case 2: + timerFiredAfterRestart.signal(); + return; + + default: + return; + } + }}; + + expect (! timer.isTimerRunning()); + timer.startTimer (1); + expect (timer.isTimerRunning()); + + expect (timerStopped.wait (maximumTimeoutMs)); + expect (! timer.isTimerRunning()); + + timer.startTimer (1); + expect (timer.isTimerRunning()); + expect (timerFiredAfterRestart.wait (maximumTimeoutMs)); + } + + beginTest ("Calls to `startTimer` and `getTimerInterval` succeed while a callback is blocked"); + { + WaitableEvent timerBlocked; + WaitableEvent unblockTimer; + + Timer timer {[&] + { + timerBlocked.signal(); + unblockTimer.wait(); + timer.stopTimer(); + }}; + + timer.startTimer (1); + timerBlocked.wait(); + + expect (timer.getTimerInterval() == 1); + timer.startTimer (2); + expect (timer.getTimerInterval() == 2); + + unblockTimer.signal(); + timer.stopTimer(); + } + + beginTest ("Stress test"); + { + constexpr auto maxNumTimers { 100 }; + + std::vector> timers; + timers.reserve (maxNumTimers); + + for (int i = 0; i < maxNumTimers; ++i) + { + auto timer = std::make_unique ([]{}); + timer->startTimer (1); + + if (! timer->isTimerRunning()) + break; + + timers.push_back (std::move (timer)); + } + + expect (timers.size() >= 16); + } + } + + class Timer : public HighResolutionTimer + { + public: + explicit Timer (std::function fn) + : callback (std::move (fn)) {} + + ~Timer() override { stopTimer(); } + + void hiResTimerCallback() override { callback(); } + + private: + std::function callback; + }; +}; + +static HighResolutionTimerTests highResolutionTimerTests; + +#endif } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h index f28d0e3..00fc124 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h +++ b/JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h @@ -32,8 +32,8 @@ namespace juce You should only use this class in situations where you really need accuracy, because unlike the normal Timer class, which is very lightweight and cheap - to start/stop, the HighResolutionTimer will use far more resources, and - starting/stopping it may involve launching and killing threads. + the HighResolutionTimer will use far more resources and require thread + safety considerations. @see Timer @@ -57,20 +57,29 @@ class JUCE_API HighResolutionTimer This will be called on a dedicated timer thread, so make sure your implementation is thread-safe! + On some platforms the dedicated timer thread may be shared with + other HighResolutionTimer's so aim to complete any work in this + callback as fast as possible. + It's perfectly ok to call startTimer() or stopTimer() from within this - callback to change the subsequent intervals. + callback to change the subsequent intervals. However, if you call + stopTimer() in the callback it's still best practice to call stopTimer() + from the destructor in order to avoid data races. */ virtual void hiResTimerCallback() = 0; //============================================================================== /** Starts the timer and sets the length of interval required. - If the timer is already started, this will reset its counter, so the - time between calling this method and the next timer callback will not be - less than the interval length passed in. + If the timer has already started, this will reset the timer, so the + time between calling this method and the next timer callback + will not be less than the interval length passed in. + + In exceptional circumstances the dedicated timer thread may not start, + if this is a potential concern for your use case, you can call isTimerRunning() + to confirm if the timer actually started. - @param intervalInMilliseconds the interval to use (any values less than 1 will be - rounded up to 1) + @param intervalInMilliseconds the interval to use (a value of zero or less will stop the timer) */ void startTimer (int intervalInMilliseconds); @@ -79,6 +88,9 @@ class JUCE_API HighResolutionTimer This method may block while it waits for pending callbacks to complete. Once it returns, no more callbacks will be made. If it is called from the timer's own thread, it will cancel the timer after the current callback returns. + + To prevent data races it's normally best practice to call this in the derived classes + destructor, even if stopTimer() was called in the hiResTimerCallback(). */ void stopTimer(); @@ -93,8 +105,8 @@ class JUCE_API HighResolutionTimer int getTimerInterval() const noexcept; private: - class Pimpl; - std::unique_ptr pimpl; + class Impl; + std::unique_ptr impl; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HighResolutionTimer) }; diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp index aa0c94f..6285401 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp +++ b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.cpp @@ -166,7 +166,7 @@ bool Thread::startRealtimeThread (const RealtimeOptions& options) if (threadHandle == nullptr) { - realtimeOptions = makeOptional (options); + realtimeOptions = std::make_optional (options); if (startThreadInternal (Priority::normal)) return true; @@ -276,7 +276,7 @@ void Thread::removeListener (Listener* listener) bool Thread::isRealtime() const { - return realtimeOptions.hasValue(); + return realtimeOptions.has_value(); } void Thread::setAffinityMask (const uint32 newAffinityMask) @@ -285,7 +285,7 @@ void Thread::setAffinityMask (const uint32 newAffinityMask) } //============================================================================== -bool Thread::wait (const int timeOutMilliseconds) const +bool Thread::wait (double timeOutMilliseconds) const { return defaultEvent.wait (timeOutMilliseconds); } @@ -417,7 +417,7 @@ class AtomicTests : public UnitTest class AtomicTester { public: - AtomicTester() {} + AtomicTester() = default; static void testInteger (UnitTest& test) { @@ -460,17 +460,17 @@ class AtomicTests : public UnitTest /* These are some simple test cases to check the atomics - let me know if any of these assertions fail on your system! */ - test.expect (a.get() == (Type) 101); + test.expect (exactlyEqual (a.get(), (Type) 101)); test.expect (! a.compareAndSetBool ((Type) 300, (Type) 200)); - test.expect (a.get() == (Type) 101); + test.expect (exactlyEqual (a.get(), (Type) 101)); test.expect (a.compareAndSetBool ((Type) 200, a.get())); - test.expect (a.get() == (Type) 200); + test.expect (exactlyEqual (a.get(), (Type) 200)); - test.expect (a.exchange ((Type) 300) == (Type) 200); - test.expect (a.get() == (Type) 300); + test.expect (exactlyEqual (a.exchange ((Type) 300), (Type) 200)); + test.expect (exactlyEqual (a.get(), (Type) 300)); b = a; - test.expect (b.get() == a.get()); + test.expect (exactlyEqual (b.get(), a.get())); } }; }; diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h index c981f48..6e983e4 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h +++ b/JuceLibraryCode/modules/juce_core/threads/juce_Thread.h @@ -42,6 +42,9 @@ namespace juce class JUCE_API Thread { public: + //============================================================================== + static constexpr size_t osDefaultStackSize { 0 }; + //============================================================================== /** The different runtime priorities of non-realtime threads. @@ -72,14 +75,114 @@ class JUCE_API Thread */ struct RealtimeOptions { - /** Linux only: A value with a range of 0-10, where 10 is the highest priority. */ - int priority = 5; + /** A value with a range of 0-10, where 10 is the highest priority. + + Currently only used by Posix platforms. + + @see getPriority + */ + [[nodiscard]] RealtimeOptions withPriority (int newPriority) const + { + jassert (isPositiveAndNotGreaterThan (newPriority, 10)); + return withMember (*this, &RealtimeOptions::priority, juce::jlimit (0, 10, newPriority)); + } + + /** Specify the expected amount of processing time required each time the thread wakes up. + + Only used by macOS/iOS. + + @see getProcessingTimeMs, withMaximumProcessingTimeMs, withPeriodMs, withPeriodHz + */ + [[nodiscard]] RealtimeOptions withProcessingTimeMs (double newProcessingTimeMs) const + { + jassert (newProcessingTimeMs > 0.0); + return withMember (*this, &RealtimeOptions::processingTimeMs, newProcessingTimeMs); + } + + /** Specify the maximum amount of processing time required each time the thread wakes up. + + Only used by macOS/iOS. + + @see getMaximumProcessingTimeMs, withProcessingTimeMs, withPeriodMs, withPeriodHz + */ + [[nodiscard]] RealtimeOptions withMaximumProcessingTimeMs (double newMaximumProcessingTimeMs) const + { + jassert (newMaximumProcessingTimeMs > 0.0); + return withMember (*this, &RealtimeOptions::maximumProcessingTimeMs, newMaximumProcessingTimeMs); + } + + /** Specify the approximate amount of time between each thread wake up. + + Alternatively call withPeriodHz(). + + Only used by macOS/iOS. + + @see getPeriodMs, withPeriodHz, withProcessingTimeMs, withMaximumProcessingTimeMs, + */ + [[nodiscard]] RealtimeOptions withPeriodMs (double newPeriodMs) const + { + jassert (newPeriodMs > 0.0); + return withMember (*this, &RealtimeOptions::periodMs, newPeriodMs); + } - /** iOS/macOS only: A millisecond value representing the estimated time between each - 'Thread::run' call. Your thread may be penalised if you frequently - overrun this. + /** Specify the approximate frequency at which the thread will be woken up. + + Alternatively call withPeriodMs(). + + Only used by macOS/iOS. + + @see getPeriodHz, withPeriodMs, withProcessingTimeMs, withMaximumProcessingTimeMs, + */ + [[nodiscard]] RealtimeOptions withPeriodHz (double newPeriodHz) const + { + jassert (newPeriodHz > 0.0); + return withPeriodMs (1'000.0 / newPeriodHz); + } + + /** Returns a value with a range of 0-10, where 10 is the highest priority. + + @see withPriority + */ + [[nodiscard]] int getPriority() const + { + return priority; + } + + /** Returns the expected amount of processing time required each time the thread + wakes up. + + @see withProcessingTimeMs, getMaximumProcessingTimeMs, getPeriodMs */ - uint32_t workDurationMs = 0; + [[nodiscard]] std::optional getProcessingTimeMs() const + { + return processingTimeMs; + } + + /** Returns the maximum amount of processing time required each time the thread + wakes up. + + @see withMaximumProcessingTimeMs, getProcessingTimeMs, getPeriodMs + */ + [[nodiscard]] std::optional getMaximumProcessingTimeMs() const + { + return maximumProcessingTimeMs; + } + + /** Returns the approximate amount of time between each thread wake up, or + nullopt if there is no inherent periodicity. + + @see withPeriodMs, withPeriodHz, getProcessingTimeMs, getMaximumProcessingTimeMs + */ + [[nodiscard]] std::optional getPeriodMs() const + { + return periodMs; + } + + private: + int priority { 5 }; + std::optional processingTimeMs; + std::optional maximumProcessingTimeMs; + std::optional periodMs{}; }; //============================================================================== @@ -95,7 +198,7 @@ class JUCE_API Thread is zero then the default stack size of the OS will be used. */ - explicit Thread (const String& threadName, size_t threadStackSize = 0); + explicit Thread (const String& threadName, size_t threadStackSize = osDefaultStackSize); /** Destructor. @@ -337,7 +440,7 @@ class JUCE_API Thread @returns true if the event has been signalled, false if the timeout expires. */ - bool wait (int timeOutMilliseconds) const; + bool wait (double timeOutMilliseconds) const; /** Wakes up the thread. @@ -461,7 +564,7 @@ class JUCE_API Thread const String threadName; std::atomic threadHandle { nullptr }; std::atomic threadId { nullptr }; - Optional realtimeOptions = {}; + std::optional realtimeOptions = {}; CriticalSection startStopLock; WaitableEvent startSuspensionEvent, defaultEvent; size_t threadStackSize; diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp index c5fffc8..a75320a 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.cpp @@ -25,8 +25,9 @@ namespace juce struct ThreadPool::ThreadPoolThread : public Thread { - ThreadPoolThread (ThreadPool& p, size_t stackSize) - : Thread ("Pool", stackSize), pool (p) + ThreadPoolThread (ThreadPool& p, const Options& options) + : Thread { options.threadName, options.threadStackSizeBytes }, + pool { p } { } @@ -93,18 +94,24 @@ ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() } //============================================================================== -ThreadPool::ThreadPool (int numThreads, size_t threadStackSize, Thread::Priority priority) +ThreadPool::ThreadPool (const Options& options) { - jassert (numThreads > 0); // not much point having a pool without any threads! + // not much point having a pool without any threads! + jassert (options.numberOfThreads > 0); - for (int i = jmax (1, numThreads); --i >= 0;) - threads.add (new ThreadPoolThread (*this, threadStackSize)); + for (int i = jmax (1, options.numberOfThreads); --i >= 0;) + threads.add (new ThreadPoolThread (*this, options)); for (auto* t : threads) - t->startThread (priority); + t->startThread (options.desiredThreadPriority); } -ThreadPool::ThreadPool() : ThreadPool (SystemStats::getNumCpus(), 0, Thread::Priority::normal) +ThreadPool::ThreadPool (int numberOfThreads, + size_t threadStackSizeBytes, + Thread::Priority desiredThreadPriority) + : ThreadPool { Options{}.withNumberOfThreads (numberOfThreads) + .withThreadStackSizeBytes (threadStackSizeBytes) + .withDesiredThreadPriority (desiredThreadPriority) } { } @@ -162,13 +169,13 @@ void ThreadPool::addJob (std::function jobToRun) { struct LambdaJobWrapper : public ThreadPoolJob { - LambdaJobWrapper (std::function j) : ThreadPoolJob ("lambda"), job (j) {} + LambdaJobWrapper (std::function j) : ThreadPoolJob ("lambda"), job (std::move (j)) {} JobStatus runJob() override { job(); return ThreadPoolJob::jobHasFinished; } std::function job; }; - addJob (new LambdaJobWrapper (jobToRun), true); + addJob (new LambdaJobWrapper (std::move (jobToRun)), true); } int ThreadPool::getNumJobs() const noexcept diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h index db449b6..fa36803 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h +++ b/JuceLibraryCode/modules/juce_core/threads/juce_ThreadPool.h @@ -140,6 +140,51 @@ class JUCE_API ThreadPoolJob JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolJob) }; +//============================================================================== +/** + A set of threads that will run a list of jobs. + + When a ThreadPoolJob object is added to the ThreadPool's list, its runJob() method + will be called by the next pooled thread that becomes free. + + @see ThreadPoolJob, Thread + + @tags{Core} +*/ +struct ThreadPoolOptions +{ + /** The name to give each thread in the pool. */ + [[nodiscard]] ThreadPoolOptions withThreadName (String newThreadName) const + { + return withMember (*this, &ThreadPoolOptions::threadName, newThreadName); + } + + /** The number of threads to run. + These will be started when a pool is created, and run until the pool is destroyed. + */ + [[nodiscard]] ThreadPoolOptions withNumberOfThreads (int newNumberOfThreads) const + { + return withMember (*this, &ThreadPoolOptions::numberOfThreads, newNumberOfThreads); + } + + /** The size of the stack of each thread in the pool. */ + [[nodiscard]] ThreadPoolOptions withThreadStackSizeBytes (size_t newThreadStackSizeBytes) const + { + return withMember (*this, &ThreadPoolOptions::threadStackSizeBytes, newThreadStackSizeBytes); + } + + /** The desired priority of each thread in the pool. */ + [[nodiscard]] ThreadPoolOptions withDesiredThreadPriority (Thread::Priority newDesiredThreadPriority) const + { + return withMember (*this, &ThreadPoolOptions::desiredThreadPriority, newDesiredThreadPriority); + } + + String threadName { "Pool" }; + int numberOfThreads { SystemStats::getNumCpus() }; + size_t threadStackSizeBytes { Thread::osDefaultStackSize }; + Thread::Priority desiredThreadPriority { Thread::Priority::normal }; +}; + //============================================================================== /** @@ -155,26 +200,38 @@ class JUCE_API ThreadPoolJob class JUCE_API ThreadPool { public: + using Options = ThreadPoolOptions; + //============================================================================== - /** Creates a thread pool. + /** Creates a thread pool based on the provided options. + Once you've created a pool, you can give it some jobs by calling addJob(). + + @see ThreadPool::ThreadPoolOptions + */ + explicit ThreadPool (const Options& options); + + /** Creates a thread pool based using the default arguments provided by + ThreadPoolOptions. + Once you've created a pool, you can give it some jobs by calling addJob(). - @param numberOfThreads the number of threads to run. These will be started - immediately, and will run until the pool is deleted. - @param threadStackSize the size of the stack of each thread. If this value - is zero then the default stack size of the OS will - be used. - @param priority the desired priority of each thread in the pool. + @see ThreadPoolOptions */ - ThreadPool (int numberOfThreads, size_t threadStackSize = 0, Thread::Priority priority = Thread::Priority::normal); + ThreadPool() : ThreadPool { Options{} } {} - /** Creates a thread pool with one thread per CPU core. + /** Creates a thread pool. Once you've created a pool, you can give it some jobs by calling addJob(). - If you want to specify the number of threads, use the other constructor; this - one creates a pool which has one thread for each CPU core. - @see SystemStats::getNumCpus() + + @param numberOfThreads the number of threads to run. These will be started + immediately, and will run until the pool is deleted. + @param threadStackSizeBytes the size of the stack of each thread. If this value + is zero then the default stack size of the OS will + be used. + @param desiredThreadPriority the desired priority of each thread in the pool. */ - ThreadPool(); + ThreadPool (int numberOfThreads, + size_t threadStackSizeBytes = Thread::osDefaultStackSize, + Thread::Priority desiredThreadPriority = Thread::Priority::normal); /** Destructor. diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.cpp b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.cpp index 988726e..34cf538 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.cpp +++ b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.cpp @@ -28,19 +28,19 @@ WaitableEvent::WaitableEvent (bool manualReset) noexcept { } -bool WaitableEvent::wait (int timeOutMilliseconds) const +bool WaitableEvent::wait (double timeOutMilliseconds) const { std::unique_lock lock (mutex); if (! triggered) { - if (timeOutMilliseconds < 0) + if (timeOutMilliseconds < 0.0) { condition.wait (lock, [this] { return triggered == true; }); } else { - if (! condition.wait_for (lock, std::chrono::milliseconds (timeOutMilliseconds), + if (! condition.wait_for (lock, std::chrono::duration { timeOutMilliseconds }, [this] { return triggered == true; })) { return false; diff --git a/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h index 56dd901..859763f 100644 --- a/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h +++ b/JuceLibraryCode/modules/juce_core/threads/juce_WaitableEvent.h @@ -61,7 +61,7 @@ class JUCE_API WaitableEvent @returns true if the object has been signalled, false if the timeout expires first. @see signal, reset */ - bool wait (int timeOutMilliseconds = -1) const; + bool wait (double timeOutMilliseconds = -1.0) const; /** Wakes up any threads that are currently waiting on this object. diff --git a/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp index 53df6d4..c6bef0b 100644 --- a/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp +++ b/JuceLibraryCode/modules/juce_core/time/juce_RelativeTime.cpp @@ -54,8 +54,16 @@ RelativeTime RelativeTime::operator-= (double secs) noexcept { numSeconds JUCE_API RelativeTime JUCE_CALLTYPE operator+ (RelativeTime t1, RelativeTime t2) noexcept { return t1 += t2; } JUCE_API RelativeTime JUCE_CALLTYPE operator- (RelativeTime t1, RelativeTime t2) noexcept { return t1 -= t2; } -JUCE_API bool JUCE_CALLTYPE operator== (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() == t2.inSeconds(); } -JUCE_API bool JUCE_CALLTYPE operator!= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() != t2.inSeconds(); } +JUCE_API bool JUCE_CALLTYPE operator== (RelativeTime t1, RelativeTime t2) noexcept +{ + return exactlyEqual (t1.inSeconds(), t2.inSeconds()); +} + +JUCE_API bool JUCE_CALLTYPE operator!= (RelativeTime t1, RelativeTime t2) noexcept +{ + return ! (t1 == t2); +} + JUCE_API bool JUCE_CALLTYPE operator> (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() > t2.inSeconds(); } JUCE_API bool JUCE_CALLTYPE operator< (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() < t2.inSeconds(); } JUCE_API bool JUCE_CALLTYPE operator>= (RelativeTime t1, RelativeTime t2) noexcept { return t1.inSeconds() >= t2.inSeconds(); } diff --git a/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h index be54c73..0dd7fed 100644 --- a/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h +++ b/JuceLibraryCode/modules/juce_core/unit_tests/juce_UnitTest.h @@ -150,7 +150,7 @@ class JUCE_API UnitTest template void expectEquals (ValueType actual, ValueType expected, String failureMessage = String()) { - bool result = actual == expected; + bool result = exactlyEqual (actual, expected); expectResultAndPrint (actual, expected, result, "", failureMessage); } @@ -160,7 +160,7 @@ class JUCE_API UnitTest template void expectNotEquals (ValueType value, ValueType valueToCompareTo, String failureMessage = String()) { - bool result = value != valueToCompareTo; + bool result = ! exactlyEqual (value, valueToCompareTo); expectResultAndPrint (value, valueToCompareTo, result, "unequal to", failureMessage); } diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h index 9c53ed2..45de43f 100644 --- a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h +++ b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h @@ -35,7 +35,7 @@ ID: juce_data_structures vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE data model helper classes description: Classes for undo/redo management, and smart data structures. website: http://www.juce.com/juce diff --git a/JuceLibraryCode/modules/juce_data_structures/values/juce_CachedValue.h b/JuceLibraryCode/modules/juce_data_structures/values/juce_CachedValue.h index ce5dd92..b4617ea 100644 --- a/JuceLibraryCode/modules/juce_data_structures/values/juce_CachedValue.h +++ b/JuceLibraryCode/modules/juce_data_structures/values/juce_CachedValue.h @@ -116,13 +116,18 @@ class CachedValue : private ValueTree::Listener is equal to other. */ template - bool operator== (const OtherType& other) const { return cachedValue == other; } + bool operator== (const OtherType& other) const + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wfloat-equal") + return cachedValue == other; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } /** Returns true if the current value of the property (or the fallback value) is not equal to other. */ template - bool operator!= (const OtherType& other) const { return cachedValue != other; } + bool operator!= (const OtherType& other) const { return ! operator== (other); } //============================================================================== /** Returns the current property as a Value object. */ @@ -245,7 +250,7 @@ inline CachedValue& CachedValue::operator= (const Type& newValue) template inline void CachedValue::setValue (const Type& newValue, UndoManager* undoManagerToUse) { - if (cachedValue != newValue || isUsingDefault()) + if (! exactlyEqual (cachedValue, newValue) || isUsingDefault()) { cachedValue = newValue; targetTree.setProperty (targetProperty, VariantConverter::toVar (newValue), undoManagerToUse); diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.cpp b/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.cpp new file mode 100644 index 0000000..87c440b --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.cpp @@ -0,0 +1,133 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class LockingAsyncUpdater::Impl : public CallbackMessage +{ +public: + explicit Impl (std::function cb) + : callback (std::move (cb)) {} + + void clear() + { + const ScopedLock lock (mutex); + deliver = false; + callback = nullptr; + } + + void trigger() + { + { + const ScopedLock lock (mutex); + + if (deliver) + return; + + deliver = true; + } + + if (! post()) + cancel(); + } + + void cancel() + { + const ScopedLock lock (mutex); + deliver = false; + } + + bool isPending() + { + const ScopedLock lock (mutex); + return deliver; + } + + void messageCallback() override + { + const ScopedLock lock (mutex); + + if (std::exchange (deliver, false)) + NullCheckedInvocation::invoke (callback); + } + +private: + CriticalSection mutex; + std::function callback; + bool deliver = false; +}; + +//============================================================================== +LockingAsyncUpdater::LockingAsyncUpdater (std::function callbackToUse) + : impl (new Impl (std::move (callbackToUse))) {} + +LockingAsyncUpdater::LockingAsyncUpdater (LockingAsyncUpdater&& other) noexcept + : impl (std::exchange (other.impl, nullptr)) {} + +LockingAsyncUpdater& LockingAsyncUpdater::operator= (LockingAsyncUpdater&& other) noexcept +{ + LockingAsyncUpdater temp { std::move (other) }; + std::swap (temp.impl, impl); + return *this; +} + +LockingAsyncUpdater::~LockingAsyncUpdater() +{ + if (impl != nullptr) + impl->clear(); +} + +void LockingAsyncUpdater::triggerAsyncUpdate() +{ + if (impl != nullptr) + impl->trigger(); + else + jassertfalse; // moved-from! +} + +void LockingAsyncUpdater::cancelPendingUpdate() noexcept +{ + if (impl != nullptr) + impl->cancel(); + else + jassertfalse; // moved-from! +} + +void LockingAsyncUpdater::handleUpdateNowIfNeeded() +{ + if (impl != nullptr) + impl->messageCallback(); + else + jassertfalse; // moved-from! +} + +bool LockingAsyncUpdater::isUpdatePending() const noexcept +{ + if (impl != nullptr) + return impl->isPending(); + + jassertfalse; // moved-from! + return false; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.h b/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.h new file mode 100644 index 0000000..49cb91d --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/broadcasters/juce_LockingAsyncUpdater.h @@ -0,0 +1,113 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A bit like an AsyncUpdater, but guarantees that after cancelPendingUpdate() returns, + the async function will never be called until triggerAsyncUpdate() is called again. + This is an important guarantee for writing classes with async behaviour that can + still be destroyed safely from a background thread. + + Note that all of the member functions of this type have a chance of blocking, so + this class is unsuitable for broadcasting changes from a realtime thread. + + @tags{Events} +*/ +class JUCE_API LockingAsyncUpdater final +{ +public: + //============================================================================== + /** Creates a LockingAsyncUpdater object that will call the provided callback + on the main thread when triggered. + + Note that the LockingAsyncUpdater takes an internal mutex before calling + the provided callback. Therefore, in order to avoid deadlocks, you should + (ideally) ensure that no locks are taken inside the callbackToUse. If you + do need to take a lock inside the callback, make sure that you do not + hold the same lock while calling any of the LockingAsyncUpdater member + functions. + */ + explicit LockingAsyncUpdater (std::function callbackToUse); + + /** Move constructor. */ + LockingAsyncUpdater (LockingAsyncUpdater&& other) noexcept; + + /** Move assignment operator. */ + LockingAsyncUpdater& operator= (LockingAsyncUpdater&& other) noexcept; + + /** Destructor. + If there are any pending callbacks when the object is deleted, these are lost. + The async callback is guaranteed not to be called again once the destructor has + completed. + */ + ~LockingAsyncUpdater(); + + //============================================================================== + /** Causes the callback to be triggered at a later time. + + This method returns quickly, after which a callback to the + handleAsyncUpdate() method will be made by the impl thread as + soon as possible. + + If an update callback is already pending but hasn't started yet, calling + this method will have no effect. + + It's thread-safe to call this method from any thread, BUT beware of calling + it from a real-time (e.g. audio) thread, because it unconditionally locks + a mutex. This may block, e.g. if this is called from a background thread + while the async callback is in progress on the main thread. + */ + void triggerAsyncUpdate(); + + /** This will stop any pending updates from happening. + + If a callback is already in progress on another thread, this will block until + the callback has finished before returning. + */ + void cancelPendingUpdate() noexcept; + + /** If an update has been triggered and is pending, this will invoke it + synchronously. + + Use this as a kind of "flush" operation - if an update is pending, the + handleAsyncUpdate() method will be called immediately; if no update is + pending, then nothing will be done. + + Because this may invoke the callback, this method must only be called on + the main event thread. + */ + void handleUpdateNowIfNeeded(); + + /** Returns true if there's an update callback in the pipeline. */ + bool isUpdatePending() const noexcept; + +private: + class Impl; + ReferenceCountedObjectPtr impl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LockingAsyncUpdater) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_events/juce_events.cpp b/JuceLibraryCode/modules/juce_events/juce_events.cpp index ae71be3..3553cec 100644 --- a/JuceLibraryCode/modules/juce_events/juce_events.cpp +++ b/JuceLibraryCode/modules/juce_events/juce_events.cpp @@ -60,6 +60,7 @@ #include "messages/juce_MessageManager.cpp" #include "broadcasters/juce_ActionBroadcaster.cpp" #include "broadcasters/juce_AsyncUpdater.cpp" +#include "broadcasters/juce_LockingAsyncUpdater.cpp" #include "broadcasters/juce_ChangeBroadcaster.cpp" #include "timers/juce_MultiTimer.cpp" #include "timers/juce_Timer.cpp" @@ -72,25 +73,26 @@ //============================================================================== #if JUCE_MAC || JUCE_IOS - #include "native/juce_osx_MessageQueue.h" + #include "native/juce_MessageQueue_mac.h" #if JUCE_MAC - #include "native/juce_mac_MessageManager.mm" + #include "native/juce_MessageManager_mac.mm" #else - #include "native/juce_ios_MessageManager.mm" + #include "native/juce_MessageManager_ios.mm" #endif #elif JUCE_WINDOWS - #include "native/juce_win32_Messaging.cpp" + #include "native/juce_RunningInUnity.h" + #include "native/juce_Messaging_windows.cpp" #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER - #include "native/juce_win32_WinRTWrapper.cpp" + #include "native/juce_WinRTWrapper_windows.cpp" #endif #elif JUCE_LINUX || JUCE_BSD - #include "native/juce_linux_EventLoopInternal.h" - #include "native/juce_linux_Messaging.cpp" + #include "native/juce_EventLoopInternal_linux.h" + #include "native/juce_Messaging_linux.cpp" #elif JUCE_ANDROID - #include "native/juce_android_Messaging.cpp" + #include "native/juce_Messaging_android.cpp" #endif diff --git a/JuceLibraryCode/modules/juce_events/juce_events.h b/JuceLibraryCode/modules/juce_events/juce_events.h index fcaf743..93a897a 100644 --- a/JuceLibraryCode/modules/juce_events/juce_events.h +++ b/JuceLibraryCode/modules/juce_events/juce_events.h @@ -32,7 +32,7 @@ ID: juce_events vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE message and event handling classes description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. website: http://www.juce.com/juce @@ -82,6 +82,7 @@ #include "broadcasters/juce_ActionBroadcaster.h" #include "broadcasters/juce_ActionListener.h" #include "broadcasters/juce_AsyncUpdater.h" +#include "broadcasters/juce_LockingAsyncUpdater.h" #include "broadcasters/juce_ChangeListener.h" #include "broadcasters/juce_ChangeBroadcaster.h" #include "timers/juce_Timer.h" @@ -93,14 +94,14 @@ #include "native/juce_ScopedLowPowerModeDisabler.h" #if JUCE_LINUX || JUCE_BSD - #include "native/juce_linux_EventLoop.h" + #include "native/juce_EventLoop_linux.h" #endif #if JUCE_WINDOWS #if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW - #include "native/juce_win32_HiddenMessageWindow.h" + #include "native/juce_HiddenMessageWindow_windows.h" #endif #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER - #include "native/juce_win32_WinRTWrapper.h" + #include "native/juce_WinRTWrapper_windows.h" #endif #endif diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp index 38b2394..e08531a 100644 --- a/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp +++ b/JuceLibraryCode/modules/juce_events/messages/juce_ApplicationBase.cpp @@ -184,7 +184,7 @@ StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray() #endif #if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) - extern int juce_gtkWebkitMain (int argc, const char* argv[]); + extern "C" int juce_gtkWebkitMain (int argc, const char* const* argv); #endif #if JUCE_WINDOWS @@ -231,7 +231,7 @@ int JUCEApplicationBase::main (int argc, const char* argv[]) initialiseNSApplication(); #endif - #if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) + #if (JUCE_LINUX || JUCE_BSD) && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined (JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) if (argc >= 2 && String (argv[1]) == "--juce-gtkwebkitfork-child") return juce_gtkWebkitMain (argc, argv); #endif diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp index 8329fd3..b108efc 100644 --- a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.cpp @@ -81,8 +81,11 @@ bool MessageManager::MessageBase::post() //============================================================================== #if ! (JUCE_MAC || JUCE_IOS || JUCE_ANDROID) -// implemented in platform-specific code (juce_linux_Messaging.cpp and juce_win32_Messaging.cpp) +// implemented in platform-specific code (juce_Messaging_linux.cpp and juce_Messaging_windows.cpp) +namespace detail +{ bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); +} // namespace detail class MessageManager::QuitMessage : public MessageManager::MessageBase { @@ -106,7 +109,7 @@ void MessageManager::runDispatchLoop() { JUCE_TRY { - if (! dispatchNextMessageOnSystemQueue (false)) + if (! detail::dispatchNextMessageOnSystemQueue (false)) Thread::sleep (1); } JUCE_CATCH_EXCEPTION @@ -130,7 +133,7 @@ bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) { JUCE_TRY { - if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) + if (! detail::dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) Thread::sleep (1); } JUCE_CATCH_EXCEPTION @@ -281,25 +284,41 @@ bool MessageManager::existsAndIsCurrentThread() noexcept */ struct MessageManager::Lock::BlockingMessage : public MessageManager::MessageBase { - BlockingMessage (const MessageManager::Lock* parent) noexcept - : owner (parent) - {} + explicit BlockingMessage (const MessageManager::Lock* parent) noexcept + : owner (parent) {} void messageCallback() override { - { - ScopedLock lock (ownerCriticalSection); + std::unique_lock lock { mutex }; - if (auto* o = owner.get()) - o->messageCallback(); + if (owner != nullptr) + { + owner->abort(); + acquired = true; } - releaseEvent.wait(); + condvar.wait (lock, [&] { return owner == nullptr; }); } - CriticalSection ownerCriticalSection; - Atomic owner; - WaitableEvent releaseEvent; + void stopWaiting() + { + const ScopeGuard scope { [&] { condvar.notify_one(); } }; + const std::scoped_lock lock { mutex }; + owner = nullptr; + } + + bool wasAcquired() + { + const std::scoped_lock lock { mutex }; + return acquired; + } + +private: + std::mutex mutex; + std::condition_variable condvar; + + const MessageManager::Lock* owner = nullptr; + bool acquired = false; JUCE_DECLARE_NON_COPYABLE (BlockingMessage) }; @@ -320,9 +339,12 @@ bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept return false; } - if (! lockIsMandatory && (abortWait.get() != 0)) + if (! lockIsMandatory && [&] + { + const std::scoped_lock lock { mutex }; + return std::exchange (abortWait, false); + }()) { - abortWait.set (0); return false; } @@ -347,65 +369,54 @@ bool MessageManager::Lock::tryAcquire (bool lockIsMandatory) const noexcept return false; } - do + for (;;) { - while (abortWait.get() == 0) - lockedEvent.wait (-1); - - abortWait.set (0); + { + std::unique_lock lock { mutex }; + condvar.wait (lock, [&] { return std::exchange (abortWait, false); }); + } - if (lockGained.get() != 0) + if (blockingMessage->wasAcquired()) { mm->threadWithLock = Thread::getCurrentThreadId(); return true; } - } while (lockIsMandatory); + if (! lockIsMandatory) + break; + } // we didn't get the lock - blockingMessage->releaseEvent.signal(); - - { - ScopedLock lock (blockingMessage->ownerCriticalSection); - - lockGained.set (0); - blockingMessage->owner.set (nullptr); - } + blockingMessage->stopWaiting(); blockingMessage = nullptr; return false; } void MessageManager::Lock::exit() const noexcept { - if (lockGained.compareAndSetBool (false, true)) - { - auto* mm = MessageManager::instance; + if (blockingMessage == nullptr) + return; - jassert (mm == nullptr || mm->currentThreadHasLockedMessageManager()); - lockGained.set (0); + const ScopeGuard scope { [&] { blockingMessage = nullptr; } }; - if (mm != nullptr) - mm->threadWithLock = {}; + blockingMessage->stopWaiting(); - if (blockingMessage != nullptr) - { - blockingMessage->releaseEvent.signal(); - blockingMessage = nullptr; - } - } -} + if (! blockingMessage->wasAcquired()) + return; -void MessageManager::Lock::messageCallback() const -{ - lockGained.set (1); - abort(); + if (auto* mm = MessageManager::instance) + { + jassert (mm->currentThreadHasLockedMessageManager()); + mm->threadWithLock = {}; + } } void MessageManager::Lock::abort() const noexcept { - abortWait.set (1); - lockedEvent.signal(); + const ScopeGuard scope { [&] { condvar.notify_one(); } }; + const std::scoped_lock lock { mutex }; + abortWait = true; } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h index 739f1ba..f66a4a9 100644 --- a/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h +++ b/JuceLibraryCode/modules/juce_events/messages/juce_MessageManager.h @@ -298,12 +298,12 @@ class JUCE_API MessageManager final friend class ReferenceCountedObjectPtr; bool tryAcquire (bool) const noexcept; - void messageCallback() const; //============================================================================== mutable ReferenceCountedObjectPtr blockingMessage; - WaitableEvent lockedEvent; - mutable Atomic abortWait, lockGained; + mutable std::mutex mutex; + mutable std::condition_variable condvar; + mutable bool abortWait = false, acquired = false; }; //============================================================================== diff --git a/JuceLibraryCode/modules/juce_events/native/juce_linux_EventLoopInternal.h b/JuceLibraryCode/modules/juce_events/native/juce_EventLoopInternal_linux.h similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_linux_EventLoopInternal.h rename to JuceLibraryCode/modules/juce_events/native/juce_EventLoopInternal_linux.h diff --git a/JuceLibraryCode/modules/juce_events/native/juce_linux_EventLoop.h b/JuceLibraryCode/modules/juce_events/native/juce_EventLoop_linux.h similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_linux_EventLoop.h rename to JuceLibraryCode/modules/juce_events/native/juce_EventLoop_linux.h diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h b/JuceLibraryCode/modules/juce_events/native/juce_HiddenMessageWindow_windows.h similarity index 94% rename from JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h rename to JuceLibraryCode/modules/juce_events/native/juce_HiddenMessageWindow_windows.h index 8dbad81..95cdcf0 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_win32_HiddenMessageWindow.h +++ b/JuceLibraryCode/modules/juce_events/native/juce_HiddenMessageWindow_windows.h @@ -91,14 +91,13 @@ class JuceWindowIdentifier class DeviceChangeDetector : private Timer { public: - DeviceChangeDetector (const wchar_t* const name) - : messageWindow (name, (WNDPROC) deviceChangeEventCallback) + DeviceChangeDetector (const wchar_t* const name, std::function onChangeIn) + : messageWindow (name, (WNDPROC) deviceChangeEventCallback), + onChange (std::move (onChangeIn)) { SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this); } - virtual void systemDeviceChanged() = 0; - void triggerAsyncDeviceChangeCallback() { // We'll pause before sending a message, because on device removal, the OS hasn't always updated @@ -108,6 +107,7 @@ class DeviceChangeDetector : private Timer private: HiddenMessageWindow messageWindow; + std::function onChange; static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) @@ -127,7 +127,7 @@ class DeviceChangeDetector : private Timer void timerCallback() override { stopTimer(); - systemDeviceChanged(); + NullCheckedInvocation::invoke (onChange); } }; diff --git a/JuceLibraryCode/modules/juce_events/native/juce_ios_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_MessageManager_ios.mm similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_ios_MessageManager.mm rename to JuceLibraryCode/modules/juce_events/native/juce_MessageManager_ios.mm diff --git a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_MessageManager_mac.mm similarity index 58% rename from JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm rename to JuceLibraryCode/modules/juce_events/native/juce_MessageManager_mac.mm index e26151e..6f9599b 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/JuceLibraryCode/modules/juce_events/native/juce_MessageManager_mac.mm @@ -35,151 +35,188 @@ //============================================================================== struct AppDelegateClass : public ObjCClass { - AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") { - addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching); - addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); - addMethod (@selector (application:openFile:), application_openFile); - addMethod (@selector (application:openFiles:), application_openFiles); - addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive); - addMethod (@selector (applicationDidResignActive:), applicationDidResignActive); - addMethod (@selector (applicationWillUnhide:), applicationWillUnhide); + addMethod (@selector (applicationWillFinishLaunching:), [] (id self, SEL, NSNotification*) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self + andSelector: @selector (getUrl:withReplyEvent:) + forEventClass: kInternetEventClass + andEventID: kAEGetURL]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + }); - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent); - addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback); - addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan); - addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded); - addMethod (@selector (dummyMethod), dummyMethod); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + addMethod (@selector (applicationShouldTerminate:), [] (id /*self*/, SEL, NSApplication*) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + app->systemRequestedQuit(); - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - addIvar*> ("pushNotificationsDelegate"); + if (! MessageManager::getInstance()->hasStopMessageBeenSent()) + return NSTerminateCancel; + } - addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching); + return NSTerminateNow; + }); - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + addMethod (@selector (applicationWillTerminate:), [] (id /*self*/, SEL, NSNotification*) + { + JUCEApplicationBase::appWillTerminateByForce(); + }); - addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications); - addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications); - addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification); - #endif + addMethod (@selector (application:openFile:), [] (id /*self*/, SEL, NSApplication*, NSString* filename) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); + return YES; + } - registerClass(); - } + return NO; + }); + + addMethod (@selector (application:openFiles:), [] (id /*self*/, SEL, NSApplication*, NSArray* filenames) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + StringArray files; + + for (NSString* f in filenames) + files.add (quotedIfContainsSpaces (f)); + + if (files.size() > 0) + app->anotherInstanceStarted (files.joinIntoString (" ")); + } + }); + + addMethod (@selector (applicationDidBecomeActive:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); + addMethod (@selector (applicationDidResignActive:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); + addMethod (@selector (applicationWillUnhide:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); -private: - static void applicationWillFinishLaunching (id self, SEL, NSNotification*) - { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self - andSelector: @selector (getUrl:withReplyEvent:) - forEventClass: kInternetEventClass - andEventID: kAEGetURL]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } + addMethod (@selector (getUrl:withReplyEvent:), [] (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) + { + if (auto* app = JUCEApplicationBase::getInstance()) + app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); + }); - #if JUCE_PUSH_NOTIFICATIONS - static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) - { - if (notification.userInfo != nil) + addMethod (@selector (broadcastMessageCallback:), [] (id /*self*/, SEL, NSNotification* n) { - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a - // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type - NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE + NSDictionary* dict = (NSDictionary*) [n userInfo]; + auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); + MessageManager::getInstance()->deliverBroadcastMessage (messageString); + }); - if (userNotification != nil && userNotification.userInfo != nil) - didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); - } - } - #endif + addMethod (@selector (mainMenuTrackingBegan:), [] (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + menuTrackingChangedCallback (true); + }); - static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) - { - if (auto* app = JUCEApplicationBase::getInstance()) + addMethod (@selector (mainMenuTrackingEnded:), [] (id /*self*/, SEL, NSNotification*) { - app->systemRequestedQuit(); + if (menuTrackingChangedCallback != nullptr) + menuTrackingChangedCallback (false); + }); - if (! MessageManager::getInstance()->hasStopMessageBeenSent()) - return NSTerminateCancel; - } + // (used as a way of running a dummy thread) + addMethod (@selector (dummyMethod), [] (id /*self*/, SEL) {}); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE - return NSTerminateNow; - } + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + addIvar*> ("pushNotificationsDelegate"); - static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) - { - JUCEApplicationBase::appWillTerminateByForce(); - } + addMethod (@selector (applicationDidFinishLaunching:), [] (id self, SEL, NSNotification* notification) + { + if (notification.userInfo != nil) + { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a + // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type + NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion") + if (userNotification != nil && userNotification.userInfo != nil) + [self application: [NSApplication sharedApplication] didReceiveRemoteNotification: userNotification.userInfo]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + } + }); - static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) - { - if (auto* app = JUCEApplicationBase::getInstance()) + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (setPushNotificationsDelegate:), [] (id self, SEL, NSObject* delegate) { - app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); - return YES; - } + object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); + }); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE - return NO; - } + addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, NSApplication* application, NSData* deviceToken) + { + auto* delegate = getPushNotificationsDelegate (self); - static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) - { - if (auto* app = JUCEApplicationBase::getInstance()) + SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); + + if (delegate != nil && [delegate respondsToSelector: selector]) + { + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &deviceToken atIndex:3]; + + [invocation invoke]; + } + }); + + addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, NSApplication* application, NSError* error) { - StringArray files; + auto* delegate = getPushNotificationsDelegate (self); - for (NSString* f in filenames) - files.add (quotedIfContainsSpaces (f)); + SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); - if (files.size() > 0) - app->anotherInstanceStarted (files.joinIntoString (" ")); - } - } + if (delegate != nil && [delegate respondsToSelector: selector]) + { + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &error atIndex:3]; - static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } + [invocation invoke]; + } + }); - static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) - { - NSDictionary* dict = (NSDictionary*) [n userInfo]; - auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); - MessageManager::getInstance()->deliverBroadcastMessage (messageString); - } + addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, NSApplication* application, NSDictionary* userInfo) + { + auto* delegate = getPushNotificationsDelegate (self); - static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - menuTrackingChangedCallback (true); - } + SEL selector = @selector (application:didReceiveRemoteNotification:); - static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - menuTrackingChangedCallback (false); - } + if (delegate != nil && [delegate respondsToSelector: selector]) + { + NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; + [invocation setSelector: selector]; + [invocation setTarget: delegate]; + [invocation setArgument: &application atIndex:2]; + [invocation setArgument: &userInfo atIndex:3]; - static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) + [invocation invoke]; + } + }); + #endif + registerClass(); + } + +private: static void focusChanged() { if (appFocusChangeCallback != nullptr) (*appFocusChangeCallback)(); } - static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); - } - static String quotedIfContainsSpaces (NSString* file) { String s (nsStringToJuce (file)); @@ -191,71 +228,12 @@ static String quotedIfContainsSpaces (NSString* file) return s; } - #if JUCE_PUSH_NOTIFICATIONS //============================================================================== - static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) - { - object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); - } - + #if JUCE_PUSH_NOTIFICATIONS static NSObject* getPushNotificationsDelegate (id self) { return getIvar*> (self, "pushNotificationsDelegate"); } - - static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) - { - auto* delegate = getPushNotificationsDelegate (self); - - SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); - - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &deviceToken atIndex:3]; - - [invocation invoke]; - } - } - - static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) - { - auto* delegate = getPushNotificationsDelegate (self); - - SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); - - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &error atIndex:3]; - - [invocation invoke]; - } - } - - static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) - { - auto* delegate = getPushNotificationsDelegate (self); - - SEL selector = @selector (application:didReceiveRemoteNotification:); - - if (delegate != nil && [delegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: delegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &userInfo atIndex:3]; - - [invocation invoke]; - } - } #endif }; @@ -459,27 +437,6 @@ void initialiseNSApplication() userInfo: info]; } -// Special function used by some plugin classes to re-post carbon events -void repostCurrentNSEvent(); -void repostCurrentNSEvent() -{ - struct EventReposter : public CallbackMessage - { - EventReposter() : e ([[NSApp currentEvent] retain]) {} - ~EventReposter() override { [e release]; } - - void messageCallback() override - { - [NSApp postEvent: e atStart: YES]; - } - - NSEvent* e; - }; - - (new EventReposter())->post(); -} - - //============================================================================== #if JUCE_MAC struct MountedVolumeListChangeDetector::Pimpl diff --git a/JuceLibraryCode/modules/juce_events/native/juce_osx_MessageQueue.h b/JuceLibraryCode/modules/juce_events/native/juce_MessageQueue_mac.h similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_osx_MessageQueue.h rename to JuceLibraryCode/modules/juce_events/native/juce_MessageQueue_mac.h diff --git a/JuceLibraryCode/modules/juce_events/native/juce_android_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_Messaging_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_android_Messaging.cpp rename to JuceLibraryCode/modules/juce_events/native/juce_Messaging_android.cpp diff --git a/JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_Messaging_linux.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp rename to JuceLibraryCode/modules/juce_events/native/juce_Messaging_linux.cpp index 40e4ebc..6657577 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_linux_Messaging.cpp +++ b/JuceLibraryCode/modules/juce_events/native/juce_Messaging_linux.cpp @@ -327,6 +327,8 @@ void MessageManager::broadcastMessage (const String&) // TODO } +namespace detail +{ // this function expects that it will NEVER be called simultaneously for two concurrent threads bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) { @@ -349,6 +351,7 @@ bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) return true; } +} // namespace detail //============================================================================== void LinuxEventLoop::registerFdCallback (int fd, std::function readCallback, short eventMask) diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp b/JuceLibraryCode/modules/juce_events/native/juce_Messaging_windows.cpp similarity index 94% rename from JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp rename to JuceLibraryCode/modules/juce_events/native/juce_Messaging_windows.cpp index c6a3d80..4e3e53b 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/JuceLibraryCode/modules/juce_events/native/juce_Messaging_windows.cpp @@ -25,10 +25,6 @@ namespace juce extern HWND juce_messageWindowHandle; -#if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity - bool juce_isRunningInUnity(); -#endif - #if JUCE_MODULE_AVAILABLE_juce_gui_extra LRESULT juce_offerEventToActiveXControl (::MSG&); #endif @@ -94,13 +90,11 @@ class InternalMessageQueue if (! shouldTriggerMessageQueueDispatch) return; - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client && JucePlugin_Build_Unity - if (juce_isRunningInUnity()) + if (detail::RunningInUnity::state) { SendNotifyMessage (juce_messageWindowHandle, customMessageID, 0, 0); return; } - #endif PostMessage (juce_messageWindowHandle, customMessageID, 0, 0); } @@ -260,6 +254,9 @@ JUCE_IMPLEMENT_SINGLETON (InternalMessageQueue) const TCHAR InternalMessageQueue::messageWindowName[] = _T("JUCEWindow"); //============================================================================== +namespace detail +{ + bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) { if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) @@ -268,6 +265,8 @@ bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) return false; } +} // namespace detail + bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) { if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) @@ -299,25 +298,24 @@ void MessageManager::doPlatformSpecificShutdown() } //============================================================================== -struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector +struct MountedVolumeListChangeDetector::Pimpl { - Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d) + explicit Pimpl (MountedVolumeListChangeDetector& d) + : owner (d) { File::findFileSystemRoots (lastVolumeList); } - void systemDeviceChanged() override + void systemDeviceChanged() { Array newList; File::findFileSystemRoots (newList); - if (lastVolumeList != newList) - { - lastVolumeList = newList; + if (std::exchange (lastVolumeList, newList) != newList) owner.mountedVolumeListChanged(); - } } + DeviceChangeDetector detector { L"MountedVolumeList", [this] { systemDeviceChanged(); } }; MountedVolumeListChangeDetector& owner; Array lastVolumeList; }; diff --git a/JuceLibraryCode/modules/juce_events/native/juce_RunningInUnity.h b/JuceLibraryCode/modules/juce_events/native/juce_RunningInUnity.h new file mode 100644 index 0000000..41fc2cb --- /dev/null +++ b/JuceLibraryCode/modules/juce_events/native/juce_RunningInUnity.h @@ -0,0 +1,35 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or 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. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +namespace juce::detail +{ + +class RunningInUnity +{ +public: + /* @internal */ + static inline bool state = false; +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_WinRTWrapper.cpp b/JuceLibraryCode/modules/juce_events/native/juce_WinRTWrapper_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_win32_WinRTWrapper.cpp rename to JuceLibraryCode/modules/juce_events/native/juce_WinRTWrapper_windows.cpp diff --git a/JuceLibraryCode/modules/juce_events/native/juce_win32_WinRTWrapper.h b/JuceLibraryCode/modules/juce_events/native/juce_WinRTWrapper_windows.h similarity index 100% rename from JuceLibraryCode/modules/juce_events/native/juce_win32_WinRTWrapper.h rename to JuceLibraryCode/modules/juce_events/native/juce_WinRTWrapper_windows.h diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp index 4e369b3..2342e21 100644 --- a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.cpp @@ -44,7 +44,7 @@ namespace ColourHelpers float hue = 0.0f; - if (hi > 0 && ! approximatelyEqual (hi, lo)) + if (hi > 0 && ! exactlyEqual (hi, lo)) { auto invDiff = 1.0f / (float) (hi - lo); @@ -291,13 +291,18 @@ Colour::Colour (PixelAlpha alpha) noexcept } //============================================================================== -const PixelARGB Colour::getPixelARGB() const noexcept +PixelARGB Colour::getPixelARGB() const noexcept { PixelARGB p (argb); p.premultiply(); return p; } +PixelARGB Colour::getNonPremultipliedPixelARGB() const noexcept +{ + return argb; +} + uint32 Colour::getARGB() const noexcept { return argb.getInARGBMaskOrder(); diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h index c5cabe9..3ce9474 100644 --- a/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_Colour.h @@ -194,7 +194,11 @@ class JUCE_API Colour final /** Returns a premultiplied ARGB pixel object that represents this colour. */ - const PixelARGB getPixelARGB() const noexcept; + PixelARGB getPixelARGB() const noexcept; + + /** Returns an ARGB pixel object that represents this colour. + */ + PixelARGB getNonPremultipliedPixelARGB() const noexcept; /** Returns a 32-bit integer that represents this colour. diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp index ba42e53..1dbd024 100644 --- a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.cpp @@ -30,7 +30,7 @@ ColourGradient::ColourGradient() noexcept : isRadial (false) { #if JUCE_DEBUG point1.setX (987654.0f); - #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (point1.x != 987654.0f); + #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (! exactlyEqual (point1.x, 987654.0f)); #else #define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED #endif @@ -174,7 +174,7 @@ void ColourGradient::setColour (int index, Colour newColour) noexcept Colour ColourGradient::getColourAtPosition (double position) const noexcept { - jassert (colours.getReference(0).position == 0.0); // the first colour specified has to go at position 0 + jassert (approximatelyEqual (colours.getReference (0).position, 0.0)); // the first colour specified has to go at position 0 if (position <= 0 || colours.size() <= 1) return colours.getReference(0).colour; @@ -199,31 +199,30 @@ void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? jassert (colours.size() >= 2); jassert (numEntries > 0); - jassert (colours.getReference(0).position == 0.0); // The first colour specified has to go at position 0 + jassert (approximatelyEqual (colours.getReference(0).position, 0.0)); // The first colour specified has to go at position 0 - auto pix1 = colours.getReference (0).colour.getPixelARGB(); int index = 0; - for (int j = 1; j < colours.size(); ++j) + for (int j = 0; j < colours.size() - 1; ++j) { - auto& p = colours.getReference (j); - auto numToDo = roundToInt (p.position * (numEntries - 1)) - index; - auto pix2 = p.colour.getPixelARGB(); + const auto& o = colours.getReference (j + 0); + const auto& p = colours.getReference (j + 1); + const auto numToDo = roundToInt (p.position * (numEntries - 1)) - index; + const auto pix1 = o.colour.getNonPremultipliedPixelARGB(); + const auto pix2 = p.colour.getNonPremultipliedPixelARGB(); - for (int i = 0; i < numToDo; ++i) + for (auto i = 0; i < numToDo; ++i) { - jassert (index >= 0 && index < numEntries); + auto blended = pix1; + blended.tween (pix2, (uint32) ((i << 8) / numToDo)); + blended.premultiply(); - lookupTable[index] = pix1; - lookupTable[index].tween (pix2, (uint32) ((i << 8) / numToDo)); - ++index; + jassert (0 <= index && index < numEntries); + lookupTable[index++] = blended; } - - pix1 = pix2; } - while (index < numEntries) - lookupTable [index++] = pix1; + std::fill (lookupTable + index, lookupTable + numEntries, colours.getLast().colour.getPixelARGB()); } int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock& lookupTable) const @@ -259,12 +258,13 @@ bool ColourGradient::isInvisible() const noexcept bool ColourGradient::ColourPoint::operator== (ColourPoint other) const noexcept { - return position == other.position && colour == other.colour; + const auto tie = [] (const ColourPoint& p) { return std::tie (p.position, p.colour); }; + return tie (*this) == tie (other); } bool ColourGradient::ColourPoint::operator!= (ColourPoint other) const noexcept { - return position != other.position || colour != other.colour; + return ! operator== (other); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h index 553594c..a70096c 100644 --- a/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h +++ b/JuceLibraryCode/modules/juce_graphics/colour/juce_ColourGradient.h @@ -185,6 +185,17 @@ class JUCE_API ColourGradient final */ void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; + /** Creates a set of interpolated premultiplied ARGB values. + This will fill an array of a user-specified size with the gradient, interpolating to fit. + When calling this, the ColourGradient must have at least 2 colour stops specified. + */ + template + void createLookupTable (PixelARGB (&resultLookupTable)[NumEntries]) const noexcept + { + static_assert (NumEntries != 0); + createLookupTable (resultLookupTable, NumEntries); + } + /** Returns true if all colours are opaque. */ bool isOpaque() const noexcept; diff --git a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 21ac100..fb35101 100644 --- a/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/JuceLibraryCode/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -627,7 +627,7 @@ void Graphics::drawEllipse (Rectangle area, float lineThickness) const { Path p; - if (area.getWidth() == area.getHeight()) + if (approximatelyEqual (area.getWidth(), area.getHeight())) { // For a circle, we can avoid having to generate a stroke p.addEllipse (area.expanded (lineThickness * 0.5f)); @@ -781,7 +781,7 @@ void Graphics::drawDashedLine (Line line, const float* dashLengths, const Line segment (line.getStart() + (delta * lastAlpha).toFloat(), line.getStart() + (delta * jmin (1.0, alpha)).toFloat()); - if (lineThickness != 1.0f) + if (! approximatelyEqual (lineThickness, 1.0f)) drawLine (segment, lineThickness); else context.drawLine (segment); diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp index 0378348..c0807f0 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_CustomTypeface.cpp @@ -189,7 +189,7 @@ void CustomTypeface::addGlyph (juce_wchar character, const Path& path, float wid void CustomTypeface::addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept { - if (extraAmount != 0.0f) + if (! approximatelyEqual (extraAmount, 0.0f)) { if (auto* g = findGlyph (char1, true)) g->addKerningPair (char2, extraAmount); diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp index 06a48ee..f8fb0b1 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Font.cpp @@ -291,7 +291,7 @@ class Font::SharedFontInternal : public ReferenceCountedObject { const ScopedLock lock (mutex); - if (ascent == 0.0f) + if (approximatelyEqual (ascent, 0.0f)) ascent = getTypefacePtr (f)->getAscent(); return height * ascent; @@ -569,7 +569,7 @@ void Font::setHeight (float newHeight) { newHeight = FontValues::limitFontHeight (newHeight); - if (font->getHeight() != newHeight) + if (! approximatelyEqual (font->getHeight(), newHeight)) { dupeInternalIfShared(); font->setHeight (newHeight); @@ -581,7 +581,7 @@ void Font::setHeightWithoutChangingWidth (float newHeight) { newHeight = FontValues::limitFontHeight (newHeight); - if (font->getHeight() != newHeight) + if (! approximatelyEqual (font->getHeight(), newHeight)) { dupeInternalIfShared(); font->setHorizontalScale (font->getHorizontalScale() * (font->getHeight() / newHeight)); @@ -626,9 +626,9 @@ void Font::setSizeAndStyle (float newHeight, { newHeight = FontValues::limitFontHeight (newHeight); - if (font->getHeight() != newHeight - || font->getHorizontalScale() != newHorizontalScale - || font->getKerning() != newKerningAmount) + if (! approximatelyEqual (font->getHeight(), newHeight) + || ! approximatelyEqual (font->getHorizontalScale(), newHorizontalScale) + || ! approximatelyEqual (font->getKerning(), newKerningAmount)) { dupeInternalIfShared(); font->setHeight (newHeight); @@ -647,9 +647,9 @@ void Font::setSizeAndStyle (float newHeight, { newHeight = FontValues::limitFontHeight (newHeight); - if (font->getHeight() != newHeight - || font->getHorizontalScale() != newHorizontalScale - || font->getKerning() != newKerningAmount) + if (! approximatelyEqual (font->getHeight(), newHeight) + || ! approximatelyEqual (font->getHorizontalScale(), newHorizontalScale) + || ! approximatelyEqual (font->getKerning(), newKerningAmount)) { dupeInternalIfShared(); font->setHeight (newHeight); @@ -748,7 +748,7 @@ float Font::getStringWidthFloat (const String& text) const { auto w = getTypefacePtr()->getStringWidth (text); - if (font->getKerning() != 0.0f) + if (! approximatelyEqual (font->getKerning(), 0.0f)) w += font->getKerning() * (float) text.length(); return w * font->getHeight() * font->getHorizontalScale(); @@ -763,7 +763,7 @@ void Font::getGlyphPositions (const String& text, Array& glyphs, ArraygetHeight() * font->getHorizontalScale(); auto* x = xOffsets.getRawDataPointer(); - if (font->getKerning() != 0.0f) + if (! approximatelyEqual (font->getKerning(), 0.0f)) { for (int i = 0; i < num; ++i) x[i] = (x[i] + (float) i * font->getKerning()) * scale; diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp index 605d6a9..5ab14c5 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_GlyphArrangement.cpp @@ -312,7 +312,7 @@ void GlyphArrangement::addFittedText (const Font& f, const String& text, Justification layout, int maximumLines, float minimumHorizontalScale) { - if (minimumHorizontalScale == 0.0f) + if (approximatelyEqual (minimumHorizontalScale, 0.0f)) minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor(); // doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 @@ -363,7 +363,7 @@ void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float d { jassert (startIndex >= 0); - if (dx != 0.0f || dy != 0.0f) + if (! approximatelyEqual (dx, 0.0f) || ! approximatelyEqual (dy, 0.0f)) { if (num < 0 || startIndex + num > glyphs.size()) num = glyphs.size() - startIndex; @@ -491,7 +491,7 @@ void GlyphArrangement::justifyGlyphs (int startIndex, int num, { auto glyphY = glyphs.getReference (startIndex + i).getBaselineY(); - if (glyphY != baseY) + if (! approximatelyEqual (glyphY, baseY)) { spreadOutLine (startIndex + lineStart, i - lineStart, width); @@ -695,7 +695,7 @@ void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGl auto lineThickness = (pg.font.getDescent()) * 0.3f; auto nextX = pg.x + pg.w; - if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y) + if (i < glyphs.size() - 1 && approximatelyEqual (glyphs.getReference (i + 1).y, pg.y)) nextX = glyphs.getReference (i + 1).x; Path p; diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp index c66ab84..ee3e6f8 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_TextLayout.cpp @@ -294,7 +294,7 @@ void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& te maxWidth -= 10.0f; } - if (bestWidth != maxWidth) + if (! approximatelyEqual (bestWidth, maxWidth)) createLayout (text, bestWidth, maxHeight); } diff --git a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp index 8128e8e..97aad8e 100644 --- a/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp +++ b/JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -148,7 +148,7 @@ struct Typeface::HintingParams void applyVerticalHintingTransform (float fontSize, Path& path) { - if (cachedSize != fontSize) + if (! approximatelyEqual (cachedSize, fontSize)) { cachedSize = fontSize; cachedScale = Scaling (top, middle, bottom, fontSize); diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp index cd0ddba..39fdf34 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_AffineTransform.cpp @@ -35,12 +35,12 @@ AffineTransform::AffineTransform (float m00, float m01, float m02, bool AffineTransform::operator== (const AffineTransform& other) const noexcept { - return mat00 == other.mat00 - && mat01 == other.mat01 - && mat02 == other.mat02 - && mat10 == other.mat10 - && mat11 == other.mat11 - && mat12 == other.mat12; + const auto tie = [] (const AffineTransform& a) + { + return std::tie (a.mat00, a.mat01, a.mat02, a.mat10, a.mat11, a.mat12); + }; + + return tie (*this) == tie (other); } bool AffineTransform::operator!= (const AffineTransform& other) const noexcept @@ -51,12 +51,7 @@ bool AffineTransform::operator!= (const AffineTransform& other) const noexcept //============================================================================== bool AffineTransform::isIdentity() const noexcept { - return mat01 == 0.0f - && mat02 == 0.0f - && mat10 == 0.0f - && mat12 == 0.0f - && mat00 == 1.0f - && mat11 == 1.0f; + return operator== (AffineTransform()); } const AffineTransform AffineTransform::identity (1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f); @@ -207,7 +202,7 @@ AffineTransform AffineTransform::inverted() const noexcept bool AffineTransform::isSingularity() const noexcept { - return (mat00 * mat11 - mat10 * mat01) == 0.0f; + return exactlyEqual (mat00 * mat11 - mat10 * mat01, 0.0f); } AffineTransform AffineTransform::fromTargetPoints (float x00, float y00, @@ -229,10 +224,10 @@ AffineTransform AffineTransform::fromTargetPoints (float sx1, float sy1, float t bool AffineTransform::isOnlyTranslation() const noexcept { - return mat01 == 0.0f - && mat10 == 0.0f - && mat00 == 1.0f - && mat11 == 1.0f; + return exactlyEqual (mat01, 0.0f) + && exactlyEqual (mat10, 0.0f) + && exactlyEqual (mat00, 1.0f) + && exactlyEqual (mat11, 1.0f); } float AffineTransform::getDeterminant() const noexcept diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp index 47e627f..1529304 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_EdgeTable.cpp @@ -46,17 +46,22 @@ EdgeTable::EdgeTable (Rectangle area, const Path& path, const AffineTransfo t += lineStrideElements; } - auto leftLimit = scale * bounds.getX(); - auto topLimit = scale * bounds.getY(); - auto rightLimit = scale * bounds.getRight(); - auto heightLimit = scale * bounds.getHeight(); + auto leftLimit = scale * static_cast (bounds.getX()); + auto topLimit = scale * static_cast (bounds.getY()); + auto rightLimit = scale * static_cast (bounds.getRight()); + auto heightLimit = scale * static_cast (bounds.getHeight()); PathFlatteningIterator iter (path, transform); while (iter.next()) { - auto y1 = roundToInt (iter.y1 * 256.0f); - auto y2 = roundToInt (iter.y2 * 256.0f); + const auto scaleIterY = [] (auto y) + { + return static_cast (y * 256.0f + (y >= 0 ? 0.5f : -0.5f)); + }; + + auto y1 = scaleIterY (iter.y1); + auto y2 = scaleIterY (iter.y2); if (y1 != y2) { @@ -82,19 +87,15 @@ EdgeTable::EdgeTable (Rectangle area, const Path& path, const AffineTransfo { const double startX = 256.0f * iter.x1; const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); - auto stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); + auto stepSize = static_cast (jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier)))); do { auto step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); - auto x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); - - if (x < leftLimit) - x = leftLimit; - else if (x >= rightLimit) - x = rightLimit - 1; + auto x = static_cast (startX + multiplier * static_cast ((y1 + (step >> 1)) - startY)); + auto clampedX = static_cast (jlimit (leftLimit, rightLimit - 1, x)); - addEdgePoint (x, y1 / scale, direction * step); + addEdgePoint (clampedX, static_cast (y1 / scale), static_cast (direction * step)); y1 += step; } while (y1 < y2); diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h index a3703d1..031b770 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Line.h @@ -204,7 +204,7 @@ class Line Point getPointAlongLine (ValueType distanceFromStart) const noexcept { const auto length = getLength(); - return length == 0 ? start : start + (end - start) * (distanceFromStart / length); + return approximatelyEqual (length, (ValueType) 0) ? start : start + (end - start) * (distanceFromStart / length); } /** Returns a point which is a certain distance along and to the side of this line. @@ -391,32 +391,34 @@ class Line auto d2 = p4 - p3; auto divisor = d1.x * d2.y - d2.x * d1.y; - if (divisor == 0) + const auto zero = ValueType{}; + + if (approximatelyEqual (divisor, zero)) { if (! (d1.isOrigin() || d2.isOrigin())) { - if (d1.y == 0 && d2.y != 0) + if (approximatelyEqual (d1.y, zero) && ! approximatelyEqual (d2.y, zero)) { auto along = (p1.y - p3.y) / d2.y; intersection = p1.withX (p3.x + along * d2.x); return isZeroToOne (along); } - if (d2.y == 0 && d1.y != 0) + if (approximatelyEqual (d2.y, zero) && ! approximatelyEqual (d1.y, zero)) { auto along = (p3.y - p1.y) / d1.y; intersection = p3.withX (p1.x + along * d1.x); return isZeroToOne (along); } - if (d1.x == 0 && d2.x != 0) + if (approximatelyEqual (d1.x, zero) && ! approximatelyEqual (d2.x, zero)) { auto along = (p1.x - p3.x) / d2.x; intersection = p1.withY (p3.y + along * d2.y); return isZeroToOne (along); } - if (d2.x == 0 && d1.x != 0) + if (approximatelyEqual (d2.x, zero) && ! approximatelyEqual (d1.x, zero)) { auto along = (p3.x - p1.x) / d1.x; intersection = p3.withY (p1.y + along * d1.y); diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp index 45855ec..c8756eb 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.cpp @@ -28,7 +28,7 @@ namespace juce // tests that some coordinates aren't NaNs #define JUCE_CHECK_COORDS_ARE_VALID(x, y) \ - jassert (x == x && y == y); + jassert (! std::isnan (x) && ! std::isnan (y)); //============================================================================== namespace PathHelpers @@ -58,18 +58,13 @@ namespace PathHelpers } //============================================================================== -const float Path::lineMarker = 100001.0f; -const float Path::moveMarker = 100002.0f; -const float Path::quadMarker = 100003.0f; -const float Path::cubicMarker = 100004.0f; -const float Path::closeSubPathMarker = 100005.0f; const float Path::defaultToleranceForTesting = 1.0f; const float Path::defaultToleranceForMeasurement = 0.6f; static bool isMarker (float value, float marker) noexcept { - return value == marker; + return exactlyEqual (value, marker); } //============================================================================== @@ -726,10 +721,11 @@ void Path::addBubble (Rectangle bodyArea, void Path::addPath (const Path& other) { const auto* d = other.data.begin(); + const auto size = other.data.size(); - for (int i = 0; i < other.data.size();) + for (int i = 0; i < size;) { - auto type = d[i++]; + const auto type = d[i++]; if (isMarker (type, moveMarker)) { @@ -767,10 +763,11 @@ void Path::addPath (const Path& other, const AffineTransform& transformToApply) { const auto* d = other.data.begin(); + const auto size = other.data.size(); - for (int i = 0; i < other.data.size();) + for (int i = 0; i < size;) { - auto type = d[i++]; + const auto type = d[i++]; if (isMarker (type, closeSubPathMarker)) { diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h index 519cf65..8c4c1ad 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Path.h @@ -827,11 +827,11 @@ class JUCE_API Path final PathBounds bounds; bool useNonZeroWinding = true; - static const float lineMarker; - static const float moveMarker; - static const float quadMarker; - static const float cubicMarker; - static const float closeSubPathMarker; + static constexpr float lineMarker = 100001.0f; + static constexpr float moveMarker = 100002.0f; + static constexpr float quadMarker = 100003.0f; + static constexpr float cubicMarker = 100004.0f; + static constexpr float closeSubPathMarker = 100005.0f; JUCE_LEAK_DETECTOR (Path) }; diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp index 25ccea1..60397cf 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathIterator.cpp @@ -138,9 +138,9 @@ bool PathFlatteningIterator::next() closesSubPath = stackPos == stackBase.get() && source != path.data.end() - && *source == Path::closeSubPathMarker - && x2 == subPathCloseX - && y2 == subPathCloseY; + && isMarker (*source, Path::closeSubPathMarker) + && approximatelyEqual (x2, subPathCloseX) + && approximatelyEqual (y2, subPathCloseY); return true; } @@ -167,8 +167,8 @@ bool PathFlatteningIterator::next() auto errorY = m3y - y2; auto outsideTolerance = errorX * errorX + errorY * errorY > toleranceSquared; - auto canBeSubdivided = (m3x != m1x && m3x != m2x) - || (m3y != m1y && m3y != m2y); + auto canBeSubdivided = (! approximatelyEqual (m3x, m1x) && ! approximatelyEqual (m3x, m2x)) + || (! approximatelyEqual (m3y, m1y) && ! approximatelyEqual (m3y, m2y)); if (outsideTolerance && canBeSubdivided) { @@ -226,10 +226,10 @@ bool PathFlatteningIterator::next() auto outsideTolerance = error1X * error1X + error1Y * error1Y > toleranceSquared || error2X * error2X + error2Y * error2Y > toleranceSquared; - auto canBeSubdivided = (m4x != m1x && m4x != m2x) - || (m4y != m1y && m4y != m2y) - || (m5x != m3x && m5x != m2x) - || (m5y != m3y && m5y != m2y); + auto canBeSubdivided = (! approximatelyEqual (m4x, m1x) && ! approximatelyEqual (m4x, m2x)) + || (! approximatelyEqual (m4y, m1y) && ! approximatelyEqual (m4y, m2y)) + || (! approximatelyEqual (m5x, m3x) && ! approximatelyEqual (m5x, m2x)) + || (! approximatelyEqual (m5y, m3y) && ! approximatelyEqual (m5y, m2y)); if (outsideTolerance && canBeSubdivided) { @@ -266,7 +266,7 @@ bool PathFlatteningIterator::next() } else if (isMarker (type, Path::closeSubPathMarker)) { - if (x2 != subPathCloseX || y2 != subPathCloseY) + if (! approximatelyEqual (x2, subPathCloseX) || ! approximatelyEqual (y2, subPathCloseY)) { x1 = x2; y1 = y2; diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp index 5393e9e..5b06ce8 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_PathStrokeType.cpp @@ -57,9 +57,8 @@ PathStrokeType::~PathStrokeType() noexcept bool PathStrokeType::operator== (const PathStrokeType& other) const noexcept { - return thickness == other.thickness - && jointStyle == other.jointStyle - && endStyle == other.endStyle; + const auto tie = [] (const PathStrokeType& p) { return std::tie (p.thickness, p.jointStyle, p.endStyle); }; + return tie (*this) == tie (other); } bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept @@ -70,122 +69,128 @@ bool PathStrokeType::operator!= (const PathStrokeType& other) const noexcept //============================================================================== namespace PathStrokeHelpers { - static bool lineIntersection (const float x1, const float y1, - const float x2, const float y2, - const float x3, const float y3, - const float x4, const float y4, - float& intersectionX, - float& intersectionY, - float& distanceBeyondLine1EndSquared) noexcept + struct LineIntersection { - if (x2 != x3 || y2 != y3) + Point point; + float distanceBeyondLine1EndSquared; + bool intersects; + }; + + static LineIntersection lineIntersection (const float x1, const float y1, + const float x2, const float y2, + const float x3, const float y3, + const float x4, const float y4) + { + if (! approximatelyEqual (x2, x3) || ! approximatelyEqual (y2, y3)) { - auto dx1 = x2 - x1; - auto dy1 = y2 - y1; - auto dx2 = x4 - x3; - auto dy2 = y4 - y3; - auto divisor = dx1 * dy2 - dx2 * dy1; + const auto dx1 = x2 - x1; + const auto dy1 = y2 - y1; + const auto dx2 = x4 - x3; + const auto dy2 = y4 - y3; + const auto divisor = dx1 * dy2 - dx2 * dy1; - if (divisor == 0.0f) + if (approximatelyEqual (divisor, 0.0f)) { - if (! ((dx1 == 0.0f && dy1 == 0.0f) || (dx2 == 0.0f && dy2 == 0.0f))) + if (! ((approximatelyEqual (dx1, 0.0f) && approximatelyEqual (dy1, 0.0f)) + || (approximatelyEqual (dx2, 0.0f) && approximatelyEqual (dy2, 0.0f)))) { - if (dy1 == 0.0f && dy2 != 0.0f) + if (approximatelyEqual (dy1, 0.0f) && ! approximatelyEqual (dy2, 0.0f)) { - auto along = (y1 - y3) / dy2; - intersectionX = x3 + along * dx2; - intersectionY = y1; - - distanceBeyondLine1EndSquared = intersectionX - x2; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if ((x2 > x1) == (intersectionX < x2)) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - - return along >= 0 && along <= 1.0f; + const auto along = (y1 - y3) / dy2; + const auto intersectionX = x3 + along * dx2; + const auto intersectionY = y1; + + const auto distance = square (intersectionX - x2); + const auto distanceBeyondLine1EndSquared = (x2 > x1) == (intersectionX < x2) + ? -distance + : distance; + + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + along >= 0 && along <= 1.0f }; } - if (dy2 == 0.0f && dy1 != 0.0f) + if (approximatelyEqual (dy2, 0.0f) && ! approximatelyEqual (dy1, 0.0f)) { - auto along = (y3 - y1) / dy1; - intersectionX = x1 + along * dx1; - intersectionY = y3; + const auto along = (y3 - y1) / dy1; + const auto intersectionX = x1 + along * dx1; + const auto intersectionY = y3; - distanceBeyondLine1EndSquared = (along - 1.0f) * dx1; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if (along < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + const auto distance = square ((along - 1.0f) * dx1); + const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance; - return along >= 0 && along <= 1.0f; + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + along >= 0 && along <= 1.0f }; } - if (dx1 == 0.0f && dx2 != 0.0f) + if (approximatelyEqual (dx1, 0.0f) && ! approximatelyEqual (dx2, 0.0f)) { - auto along = (x1 - x3) / dx2; - intersectionX = x1; - intersectionY = y3 + along * dy2; - - distanceBeyondLine1EndSquared = intersectionY - y2; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - - if ((y2 > y1) == (intersectionY < y2)) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - - return along >= 0 && along <= 1.0f; + const auto along = (x1 - x3) / dx2; + const auto intersectionX = x1; + const auto intersectionY = y3 + along * dy2; + + const auto distance = square (intersectionY - y2); + const auto distanceBeyondLine1EndSquared = (y2 > y1) == (intersectionY < y2) + ? -distance + : distance; + + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + along >= 0 && along <= 1.0f }; } - if (dx2 == 0.0f && dx1 != 0.0f) + if (approximatelyEqual (dx2, 0.0f) && ! approximatelyEqual (dx1, 0.0f)) { - auto along = (x3 - x1) / dx1; - intersectionX = x3; - intersectionY = y1 + along * dy1; + const auto along = (x3 - x1) / dx1; + const auto intersectionX = x3; + const auto intersectionY = y1 + along * dy1; - distanceBeyondLine1EndSquared = (along - 1.0f) * dy1; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - if (along < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; + const auto distance = square ((along - 1.0f) * dy1); + const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance; - return along >= 0 && along <= 1.0f; + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + along >= 0 && along <= 1.0f }; } } - intersectionX = 0.5f * (x2 + x3); - intersectionY = 0.5f * (y2 + y3); + const auto intersectionX = 0.5f * (x2 + x3); + const auto intersectionY = 0.5f * (y2 + y3); + + const auto distanceBeyondLine1EndSquared = 0.0f; - distanceBeyondLine1EndSquared = 0.0f; - return false; + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + false }; } - auto along1 = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; + const auto along = ((y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor; - intersectionX = x1 + along1 * dx1; - intersectionY = y1 + along1 * dy1; + const auto intersectionX = x1 + along * dx1; + const auto intersectionY = y1 + along * dy1; - if (along1 >= 0 && along1 <= 1.0f) + if (along >= 0 && along <= 1.0f) { - auto along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor; + const auto along2 = ((y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor; if (along2 >= 0 && along2 <= 1.0f) { - distanceBeyondLine1EndSquared = 0.0f; - return true; + return { { intersectionX, intersectionY }, + 0.0f, + true }; } } - distanceBeyondLine1EndSquared = along1 - 1.0f; - distanceBeyondLine1EndSquared *= distanceBeyondLine1EndSquared; - distanceBeyondLine1EndSquared *= (dx1 * dx1 + dy1 * dy1); + const auto distance = square (along - 1.0f) * (dx1 * dx1 + dy1 * dy1); + const auto distanceBeyondLine1EndSquared = along < 1.0f ? -distance : distance; - if (along1 < 1.0f) - distanceBeyondLine1EndSquared = -distanceBeyondLine1EndSquared; - - return false; + return { { intersectionX, intersectionY }, + distanceBeyondLine1EndSquared, + false }; } - intersectionX = x2; - intersectionY = y2; - - distanceBeyondLine1EndSquared = 0.0f; - return true; + return { Point { x2, y2 }, 0.0f, true }; } static void addEdgeAndJoint (Path& destPath, @@ -198,31 +203,29 @@ namespace PathStrokeHelpers const float midX, const float midY) { if (style == PathStrokeType::beveled - || (x3 == x4 && y3 == y4) - || (x1 == x2 && y1 == y2)) + || (approximatelyEqual (x3, x4) && approximatelyEqual (y3, y4)) + || (approximatelyEqual (x1, x2) && approximatelyEqual (y1, y2))) { destPath.lineTo (x2, y2); destPath.lineTo (x3, y3); } else { - float jx, jy, distanceBeyondLine1EndSquared; + const auto intersection = lineIntersection (x1, y1, x2, y2, x3, y3, x4, y4); // if they intersect, use this point.. - if (lineIntersection (x1, y1, x2, y2, - x3, y3, x4, y4, - jx, jy, distanceBeyondLine1EndSquared)) + if (intersection.intersects) { - destPath.lineTo (jx, jy); + destPath.lineTo (intersection.point); } else { if (style == PathStrokeType::mitered) { - if (distanceBeyondLine1EndSquared < maxMiterExtensionSquared - && distanceBeyondLine1EndSquared > 0.0f) + if (0.0f < intersection.distanceBeyondLine1EndSquared + && intersection.distanceBeyondLine1EndSquared < maxMiterExtensionSquared) { - destPath.lineTo (jx, jy); + destPath.lineTo (intersection.point); } else { @@ -301,7 +304,7 @@ namespace PathStrokeHelpers auto dy = y2 - y1; auto len = juce_hypot (dx, dy); - if (len == 0.0f) + if (approximatelyEqual (len, 0.0f)) { offx1 = offx2 = x1; offy1 = offy2 = y1; @@ -610,7 +613,7 @@ namespace PathStrokeHelpers { auto len = std::sqrt (hypotSquared); - if (len == 0.0f) + if (approximatelyEqual (len, 0.0f)) { l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1; l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1; diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h index 7b43aa9..4a8c333 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Point.h @@ -54,14 +54,19 @@ class Point /** Copies this point from another one. */ Point& operator= (const Point&) = default; - constexpr inline bool operator== (Point other) const noexcept { return x == other.x && y == other.y; } - constexpr inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; } + constexpr inline bool operator== (Point other) const noexcept + { + const auto tie = [] (const Point& p) { return std::tie (p.x, p.y); }; + return tie (*this) == tie (other); + } + + constexpr inline bool operator!= (Point other) const noexcept { return ! operator== (other); } /** Returns true if the point is (0, 0). */ - constexpr bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); } + constexpr bool isOrigin() const noexcept { return operator== (Point()); } /** Returns true if the coordinates are finite values. */ - constexpr inline bool isFinite() const noexcept { return juce_isfinite(x) && juce_isfinite(y); } + constexpr inline bool isFinite() const noexcept { return juce_isfinite (x) && juce_isfinite (y); } /** Returns the point's x coordinate. */ constexpr inline ValueType getX() const noexcept { return x; } diff --git a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h index 6f1dc19..ceb6799 100644 --- a/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/JuceLibraryCode/modules/juce_graphics/geometry/juce_Rectangle.h @@ -613,10 +613,14 @@ class Rectangle //============================================================================== /** Returns true if the two rectangles are identical. */ - bool operator== (const Rectangle& other) const noexcept { return pos == other.pos && w == other.w && h == other.h; } + bool operator== (const Rectangle& other) const noexcept + { + const auto tie = [] (const Rectangle& r) { return std::tie (r.pos, r.w, r.h); }; + return tie (*this) == tie (other); + } /** Returns true if the two rectangles are not identical. */ - bool operator!= (const Rectangle& other) const noexcept { return pos != other.pos || w != other.w || h != other.h; } + bool operator!= (const Rectangle& other) const noexcept { return ! operator== (other); } /** Returns true if this coordinate is inside the rectangle. */ bool contains (ValueType xCoord, ValueType yCoord) const noexcept diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp index defad44..a9e3609 100644 --- a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_GIFLoader.cpp @@ -324,8 +324,8 @@ class GIFLoader if (finished) return -1; - buffer[0] = buffer [lastByteIndex - 2]; - buffer[1] = buffer [lastByteIndex - 1]; + buffer[0] = buffer [jmax (0, lastByteIndex - 2)]; + buffer[1] = buffer [jmax (0, lastByteIndex - 1)]; const int n = readDataBlock (buffer + 2); diff --git a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp index e089a54..deb8cf6 100644 --- a/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp +++ b/JuceLibraryCode/modules/juce_graphics/image_formats/juce_PNGLoader.cpp @@ -58,13 +58,14 @@ namespace pnglibNamespace using std::free; #endif - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wsign-conversion", + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcomma", + "-Wfloat-equal", "-Wimplicit-fallthrough", - "-Wtautological-constant-out-of-range-compare", - "-Wzero-as-null-pointer-constant", - "-Wcomma", "-Wmaybe-uninitialized", - "-Wnull-pointer-subtraction") + "-Wnull-pointer-subtraction", + "-Wsign-conversion", + "-Wtautological-constant-out-of-range-compare", + "-Wzero-as-null-pointer-constant") #undef check using std::abs; diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp index c8938e7..1ac7e06 100644 --- a/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ImageCache.cpp @@ -30,7 +30,12 @@ struct ImageCache::Pimpl : private Timer, private DeletedAtShutdown { Pimpl() = default; - ~Pimpl() override { clearSingletonInstance(); } + + ~Pimpl() override + { + stopTimer(); + clearSingletonInstance(); + } JUCE_DECLARE_SINGLETON (ImageCache::Pimpl, false) diff --git a/JuceLibraryCode/modules/juce_graphics/images/juce_ScaledImage.h b/JuceLibraryCode/modules/juce_graphics/images/juce_ScaledImage.h index 67481cd..d4eb41e 100644 --- a/JuceLibraryCode/modules/juce_graphics/images/juce_ScaledImage.h +++ b/JuceLibraryCode/modules/juce_graphics/images/juce_ScaledImage.h @@ -40,6 +40,8 @@ namespace juce The ScaledImage class is designed to store an image alongside a scale factor that informs a renderer how to convert between the image's pixels and points. + + @tags{GUI} */ class JUCE_API ScaledImage { diff --git a/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp b/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp index 1cd217d..1d79963 100644 --- a/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp +++ b/JuceLibraryCode/modules/juce_graphics/juce_graphics.cpp @@ -139,32 +139,32 @@ #endif #if JUCE_USE_FREETYPE - #include "native/juce_freetype_Fonts.cpp" + #include "native/juce_Fonts_freetype.cpp" #endif //============================================================================== #if JUCE_MAC || JUCE_IOS - #include "native/juce_mac_Fonts.mm" - #include "native/juce_mac_CoreGraphicsContext.mm" - #include "native/juce_mac_IconHelpers.cpp" + #include "native/juce_Fonts_mac.mm" + #include "native/juce_CoreGraphicsContext_mac.mm" + #include "native/juce_IconHelpers_mac.cpp" #elif JUCE_WINDOWS - #include "native/juce_win32_DirectWriteTypeface.cpp" - #include "native/juce_win32_DirectWriteTypeLayout.cpp" - #include "native/juce_win32_Fonts.cpp" - #include "native/juce_win32_IconHelpers.cpp" + #include "native/juce_DirectWriteTypeface_windows.cpp" + #include "native/juce_DirectWriteTypeLayout_windows.cpp" + #include "native/juce_Fonts_windows.cpp" + #include "native/juce_IconHelpers_windows.cpp" #if JUCE_DIRECT2D - #include "native/juce_win32_Direct2DGraphicsContext.cpp" + #include "native/juce_Direct2DGraphicsContext_windows.cpp" #endif #elif JUCE_LINUX || JUCE_BSD - #include "native/juce_linux_Fonts.cpp" - #include "native/juce_linux_IconHelpers.cpp" + #include "native/juce_Fonts_linux.cpp" + #include "native/juce_IconHelpers_linux.cpp" #elif JUCE_ANDROID - #include "native/juce_android_GraphicsContext.cpp" - #include "native/juce_android_Fonts.cpp" - #include "native/juce_android_IconHelpers.cpp" + #include "native/juce_GraphicsContext_android.cpp" + #include "native/juce_Fonts_android.cpp" + #include "native/juce_IconHelpers_android.cpp" #endif diff --git a/JuceLibraryCode/modules/juce_graphics/juce_graphics.h b/JuceLibraryCode/modules/juce_graphics/juce_graphics.h index a26ed49..62dc0e0 100644 --- a/JuceLibraryCode/modules/juce_graphics/juce_graphics.h +++ b/JuceLibraryCode/modules/juce_graphics/juce_graphics.h @@ -35,7 +35,7 @@ ID: juce_graphics vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE graphics classes description: Classes for 2D vector graphics, image loading/saving, font handling, etc. website: http://www.juce.com/juce @@ -150,10 +150,10 @@ namespace juce #include "effects/juce_GlowEffect.h" #if JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS && (JUCE_MAC || JUCE_IOS) - #include "native/juce_mac_CoreGraphicsHelpers.h" - #include "native/juce_mac_CoreGraphicsContext.h" + #include "native/juce_CoreGraphicsHelpers_mac.h" + #include "native/juce_CoreGraphicsContext_mac.h" #endif #if JUCE_DIRECT2D && JUCE_WINDOWS -#include "native/juce_win32_Direct2DGraphicsContext.h" +#include "native/juce_Direct2DGraphicsContext_windows.h" #endif diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h b/JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h similarity index 97% rename from JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h rename to JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h index 528c625..ccf962e 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.h +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h @@ -120,8 +120,7 @@ class CoreGraphicsContext : public LowLevelGraphicsContext detail::ContextPtr context; const CGFloat flipHeight; detail::ColorSpacePtr rgbColourSpace, greyColourSpace; - mutable Rectangle lastClipRect; - mutable bool lastClipRectIsValid = false; + mutable std::optional> lastClipRect; struct SavedState { @@ -142,8 +141,8 @@ class CoreGraphicsContext : public LowLevelGraphicsContext std::unique_ptr state; OwnedArray stateStack; + void setContextClipToPath (const Path&, const AffineTransform&); void drawGradient(); - void createPath (const Path&) const; void createPath (const Path&, const AffineTransform&) const; void flip() const; void applyTransform (const AffineTransform&) const; diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm similarity index 89% rename from JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm rename to JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm index 5ea3da8..d8e7f0c 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm @@ -221,8 +221,8 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) { CGContextTranslateCTM (context.get(), o.x, -o.y); - if (lastClipRectIsValid) - lastClipRect.translate (-o.x, -o.y); + if (lastClipRect.has_value()) + lastClipRect->translate (-o.x, -o.y); } void CoreGraphicsContext::addTransform (const AffineTransform& transform) @@ -231,7 +231,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) .followedBy (transform) .translated (0, (float) -flipHeight) .scaled (1.0f, -1.0f)); - lastClipRectIsValid = false; + lastClipRect.reset(); jassert (getPhysicalPixelScaleFactor() > 0.0f); } @@ -249,14 +249,14 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextClipToRect (context.get(), CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); - if (lastClipRectIsValid) + if (lastClipRect.has_value()) { // This is actually incorrect, because the actual clip region may be complex, and // clipping its bounds to a rect may not be right... But, removing this shortcut // doesn't actually fix anything because CoreGraphics also ignores complex regions // when calculating the resultant clip bounds, and makes the same mistake! - lastClipRect = lastClipRect.getIntersection (r); - return ! lastClipRect.isEmpty(); + lastClipRect = lastClipRect->getIntersection (r); + return ! lastClipRect->isEmpty(); } return ! isClipEmpty(); @@ -267,7 +267,6 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) if (clipRegion.isEmpty()) { CGContextClipToRect (context.get(), CGRectZero); - lastClipRectIsValid = true; lastClipRect = Rectangle(); return false; } @@ -280,7 +279,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); CGContextClipToRects (context.get(), rects, numRects); - lastClipRectIsValid = false; + lastClipRect.reset(); return true; } @@ -296,7 +295,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) clipToRectangleListWithoutTest (remaining); } -void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) +void CoreGraphicsContext::setContextClipToPath (const Path& path, const AffineTransform& transform) { createPath (path, transform); @@ -304,8 +303,12 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextClip (context.get()); else CGContextEOClip (context.get()); +} - lastClipRectIsValid = false; +void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform) +{ + setContextClipToPath (path, transform); + lastClipRect.reset(); } void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform) @@ -329,7 +332,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) applyTransform (t.inverted()); flip(); - lastClipRectIsValid = false; + lastClipRect.reset(); } } @@ -340,18 +343,17 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) Rectangle CoreGraphicsContext::getClipBounds() const { - if (! lastClipRectIsValid) + if (! lastClipRect.has_value()) { auto bounds = CGRectIntegral (CGContextGetClipBoundingBox (context.get())); - lastClipRectIsValid = true; - lastClipRect.setBounds (roundToInt (bounds.origin.x), - roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), - roundToInt (bounds.size.width), - roundToInt (bounds.size.height)); + lastClipRect = Rectangle (roundToInt (bounds.origin.x), + roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)), + roundToInt (bounds.size.width), + roundToInt (bounds.size.height)); } - return lastClipRect; + return *lastClipRect; } bool CoreGraphicsContext::isClipEmpty() const @@ -376,7 +378,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextSetTextMatrix (context.get(), state->textMatrix); stateStack.removeLast (1, false); - lastClipRectIsValid = false; + lastClipRect.reset(); } else { @@ -470,56 +472,45 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextSetBlendMode (context.get(), kCGBlendModeCopy); fillCGRect (cgRect, false); CGContextSetBlendMode (context.get(), kCGBlendModeNormal); + return; } - else + + if (state->fillType.isColour()) { - if (state->fillType.isColour()) - { - CGContextFillRect (context.get(), cgRect); - } - else - { - ScopedCGContextState scopedState (context.get()); + CGContextFillRect (context.get(), cgRect); + return; + } - CGContextClipToRect (context.get(), cgRect); + ScopedCGContextState scopedState (context.get()); + CGContextClipToRect (context.get(), cgRect); - if (state->fillType.isGradient()) - drawGradient(); - else - drawImage (state->fillType.image, state->fillType.transform, true); - } - } + if (state->fillType.isGradient()) + drawGradient(); + else + drawImage (state->fillType.image, state->fillType.transform, true); } void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform) { - ScopedCGContextState scopedState (context.get()); - if (state->fillType.isColour()) { - flip(); - applyTransform (transform); - createPath (path); + createPath (path, transform); if (path.isUsingNonZeroWinding()) CGContextFillPath (context.get()); else CGContextEOFillPath (context.get()); + + return; } - else - { - createPath (path, transform); - if (path.isUsingNonZeroWinding()) - CGContextClip (context.get()); - else - CGContextEOClip (context.get()); + ScopedCGContextState scopedState (context.get()); + setContextClipToPath (path, transform); - if (state->fillType.isGradient()) - drawGradient(); - else - drawImage (state->fillType.image, state->fillType.transform, true); - } + if (state->fillType.isGradient()) + drawGradient(); + else + drawImage (state->fillType.image, state->fillType.transform, true); } void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) @@ -549,15 +540,20 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextDrawTiledImage (context.get(), imageRect, image.get()); #else // There's a bug in CGContextDrawTiledImage that makes it incredibly slow - // if it's doing a transformation - it's quicker to just draw lots of images manually - if (&CGContextDrawTiledImage != nullptr && transform.isOnlyTranslation()) + // if it's doing a transformation - it's quicker to just draw lots of images manually, + // but we might not be able to draw the images ourselves if the clipping region is not + // finite + const auto doCustomTiling = [&] { - CGContextDrawTiledImage (context.get(), imageRect, image.get()); - } - else - { - // Fallback to manually doing a tiled fill - auto clip = CGRectIntegral (CGContextGetClipBoundingBox (context.get())); + if (transform.isOnlyTranslation()) + return false; + + const auto bound = CGContextGetClipBoundingBox (context.get()); + + if (CGRectIsNull (bound)) + return false; + + const auto clip = CGRectIntegral (bound); int x = 0, y = 0; while (x > clip.origin.x) x -= iw; @@ -573,7 +569,12 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) y += ih; } - } + + return true; + }; + + if (! doCustomTiling()) + CGContextDrawTiledImage (context.get(), imageRect, image.get()); #endif } else @@ -592,28 +593,25 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) void CoreGraphicsContext::fillRectList (const RectangleList& list) { - HeapBlock rects (list.getNumRectangles()); - - size_t num = 0; + std::vector rects; + rects.reserve ((size_t) list.getNumRectangles()); for (auto& r : list) - rects[num++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + rects.push_back (CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight())); if (state->fillType.isColour()) { - CGContextFillRects (context.get(), rects, num); + CGContextFillRects (context.get(), rects.data(), rects.size()); + return; } - else - { - ScopedCGContextState scopedState (context.get()); - CGContextClipToRects (context.get(), rects, num); + ScopedCGContextState scopedState (context.get()); + CGContextClipToRects (context.get(), rects.data(), rects.size()); - if (state->fillType.isGradient()) - drawGradient(); - else - drawImage (state->fillType.image, state->fillType.transform, true); - } + if (state->fillType.isGradient()) + drawGradient(); + else + drawImage (state->fillType.image, state->fillType.transform, true); } void CoreGraphicsContext::setFont (const Font& newFont) @@ -702,10 +700,8 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CoreGraphicsContext::SavedState::SavedState (const SavedState& other) : fillType (other.fillType), font (other.font), fontRef (other.fontRef), textMatrix (other.textMatrix), inverseTextMatrix (other.inverseTextMatrix), - gradient (other.gradient.get()) + gradient (other.gradient.get() != nullptr ? CGGradientRetain (other.gradient.get()) : nullptr) { - if (gradient != nullptr) - CGGradientRetain (gradient.get()); } CoreGraphicsContext::SavedState::~SavedState() = default; @@ -719,9 +715,9 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef colourSpace) { auto numColours = g.getNumColours(); - auto data = (CGFloat*) alloca ((size_t) numColours * 5 * sizeof (CGFloat)); - auto locations = data; - auto components = data + numColours; + std::vector data ((size_t) numColours * 5); + auto locations = data.data(); + auto components = locations + numColours; auto comps = components; for (int i = 0; i < numColours; ++i) @@ -734,8 +730,8 @@ static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef co locations[i] = (CGFloat) g.getColourPosition (i); // There's a bug (?) in the way the CG renderer works where it seems - // to go wrong if you have two colour stops both at position 0.. - jassert (i == 0 || locations[i] != 0); + // to go wrong if you have two colour stops both at position 0. + jassert (i == 0 || ! approximatelyEqual (locations[i], (CGFloat) 0.0)); } return CGGradientCreateWithColorComponents (colourSpace, components, locations, (size_t) numColours); @@ -763,24 +759,6 @@ static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef co kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); } -void CoreGraphicsContext::createPath (const Path& path) const -{ - CGContextBeginPath (context.get()); - - for (Path::Iterator i (path); i.next();) - { - switch (i.elementType) - { - case Path::Iterator::startNewSubPath: CGContextMoveToPoint (context.get(), i.x1, i.y1); break; - case Path::Iterator::lineTo: CGContextAddLineToPoint (context.get(), i.x1, i.y1); break; - case Path::Iterator::quadraticTo: CGContextAddQuadCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2); break; - case Path::Iterator::cubicTo: CGContextAddCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break; - case Path::Iterator::closePath: CGContextClosePath (context.get()); break; - default: jassertfalse; break; - } - } -} - void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const { CGContextBeginPath (context.get()); diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h b/JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_mac_CoreGraphicsHelpers.h rename to JuceLibraryCode/modules/juce_graphics/native/juce_CoreGraphicsHelpers_mac.h diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h b/JuceLibraryCode/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h rename to JuceLibraryCode/modules/juce_graphics/native/juce_Direct2DGraphicsContext_windows.h diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp similarity index 99% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp index c8ca091..bd42ced 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_DirectWriteTypeLayout_windows.cpp @@ -108,7 +108,7 @@ namespace DirectWriteTypeLayout if (! (baselineOriginY >= -1.0e10f && baselineOriginY <= 1.0e10f)) baselineOriginY = 0; // DirectWrite sometimes sends NaNs in this parameter - if (baselineOriginY != lastOriginY) + if (! approximatelyEqual (baselineOriginY, lastOriginY)) { lastOriginY = baselineOriginY; ++currentLine; diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_android_Fonts.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_android.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_freetype.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_freetype_Fonts.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_freetype.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_linux_Fonts.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_linux.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_mac.mm similarity index 99% rename from JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm rename to JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_mac.mm index 864e2dd..93c620d 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_mac.mm @@ -295,7 +295,7 @@ static AttributedStringAndFontMap createCFAttributedString (const AttributedStri auto extraKerning = attr.font.getExtraKerningFactor(); - if (extraKerning != 0) + if (! approximatelyEqual (extraKerning, 0.0f)) { extraKerning *= attr.font.getHeight(); diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_Fonts_windows.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_GraphicsContext.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_GraphicsContext_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_android_GraphicsContext.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_GraphicsContext_android.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_android_IconHelpers.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_android.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_android_IconHelpers.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_android.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_linux_IconHelpers.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_linux.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_linux_IconHelpers.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_linux.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_IconHelpers.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_mac.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_mac_IconHelpers.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_mac.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_win32_IconHelpers.cpp b/JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_windows.cpp similarity index 100% rename from JuceLibraryCode/modules/juce_graphics/native/juce_win32_IconHelpers.cpp rename to JuceLibraryCode/modules/juce_graphics/native/juce_IconHelpers_windows.cpp diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h b/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h index 644e743..6c367b1 100644 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -86,8 +86,10 @@ class TranslationOrTransform complexTransform = getTransformWith (t); isOnlyTranslated = false; - isRotated = (complexTransform.mat01 != 0.0f || complexTransform.mat10 != 0.0f - || complexTransform.mat00 < 0 || complexTransform.mat11 < 0); + isRotated = (! approximatelyEqual (complexTransform.mat01, 0.0f) + || ! approximatelyEqual (complexTransform.mat10, 0.0f) + || complexTransform.mat00 < 0 + || complexTransform.mat11 < 0); } float getPhysicalPixelScaleFactor() const noexcept diff --git a/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp index a5fe3af..386890c 100644 --- a/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp +++ b/JuceLibraryCode/modules/juce_graphics/placement/juce_RectanglePlacement.cpp @@ -39,7 +39,7 @@ bool RectanglePlacement::operator!= (const RectanglePlacement& other) const noex void RectanglePlacement::applyTo (double& x, double& y, double& w, double& h, const double dx, const double dy, const double dw, const double dh) const noexcept { - if (w == 0.0 || h == 0.0) + if (approximatelyEqual (w, 0.0) || approximatelyEqual (h, 0.0)) return; if ((flags & stretchToFit) != 0) diff --git a/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h b/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h index 31697f3..059ff41 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityEvent.h @@ -78,4 +78,4 @@ enum class AccessibilityEvent rowSelectionChanged }; -} +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h b/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h index a97c7aa..bb48463 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h +++ b/JuceLibraryCode/modules/juce_gui_basics/accessibility/enums/juce_AccessibilityRole.h @@ -68,4 +68,4 @@ enum class AccessibilityRole unspecified }; -} +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h b/JuceLibraryCode/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h index cc8c83d..6a5e3f9 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h +++ b/JuceLibraryCode/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h @@ -65,6 +65,7 @@ class JUCE_API AccessibilityTableInterface */ virtual const AccessibilityHandler* getHeaderHandler() const = 0; + /** A simple span of elements. */ struct Span { int begin, num; }; /** Given the handler of one of the cells in the table, returns the rows covered diff --git a/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp b/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp index 3f82da7..d168286 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp @@ -28,29 +28,62 @@ namespace juce AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr; -enum class InternalAccessibilityEvent -{ - elementCreated, - elementDestroyed, - elementMovedOrResized, - focusChanged, - windowOpened, - windowClosed -}; +class NativeChildHandler +{ +public: + static NativeChildHandler& getInstance() + { + static NativeChildHandler instance; + return instance; + } -void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent); + void* getNativeChild (Component& component) const + { + if (auto it = nativeChildForComponent.find (&component); + it != nativeChildForComponent.end()) + { + return it->second; + } -inline String getAccessibleApplicationOrPluginName() -{ - #if defined (JucePlugin_Name) - return JucePlugin_Name; - #else - if (auto* app = JUCEApplicationBase::getInstance()) - return app->getApplicationName(); + return nullptr; + } - return "JUCE Application"; - #endif -} + Component* getComponent (void* nativeChild) const + { + if (auto it = componentForNativeChild.find (nativeChild); + it != componentForNativeChild.end()) + { + return it->second; + } + + return nullptr; + } + + void setNativeChild (Component& component, void* nativeChild) + { + clearComponent (component); + + if (nativeChild != nullptr) + { + nativeChildForComponent[&component] = nativeChild; + componentForNativeChild[nativeChild] = &component; + } + } + +private: + NativeChildHandler() = default; + + void clearComponent (Component& component) + { + if (auto* nativeChild = getNativeChild (component)) + componentForNativeChild.erase (nativeChild); + + nativeChildForComponent.erase (&component); + } + + std::map componentForNativeChild; + std::map nativeChildForComponent; +}; AccessibilityHandler::AccessibilityHandler (Component& comp, AccessibilityRole accessibilityRole, @@ -68,7 +101,7 @@ AccessibilityHandler::AccessibilityHandler (Component& comp, AccessibilityHandler::~AccessibilityHandler() { giveAwayFocus(); - notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementDestroyed); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::elementDestroyed); } //============================================================================== @@ -320,13 +353,13 @@ void AccessibilityHandler::grabFocusInternal (bool canTryParent) void AccessibilityHandler::giveAwayFocusInternal() const { currentlyFocusedHandler = nullptr; - notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::focusChanged); } void AccessibilityHandler::takeFocus() { currentlyFocusedHandler = this; - notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::focusChanged); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::focusChanged); if ((component.isShowing() || component.isOnDesktop()) && component.getWantsKeyboardFocus() @@ -336,4 +369,35 @@ void AccessibilityHandler::takeFocus() } } +std::unique_ptr AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler) +{ + #if JUCE_NATIVE_ACCESSIBILITY_INCLUDED + return std::make_unique (handler); + #else + ignoreUnused (handler); + return nullptr; + #endif +} + +void* AccessibilityHandler::getNativeChildForComponent (Component& component) +{ + return NativeChildHandler::getInstance().getNativeChild (component); +} + +Component* AccessibilityHandler::getComponentForNativeChild (void* nativeChild) +{ + return NativeChildHandler::getInstance().getComponent (nativeChild); +} + +void AccessibilityHandler::setNativeChildForComponent (Component& component, void* nativeChild) +{ + NativeChildHandler::getInstance().setNativeChild (component, nativeChild); +} + +#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED + void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} + void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} + AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; } +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h b/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h index bdc0a3e..be993ef 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h +++ b/JuceLibraryCode/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.h @@ -291,6 +291,26 @@ class JUCE_API AccessibilityHandler AccessibilityNativeHandle* getNativeImplementation() const; /** @internal */ std::type_index getTypeIndex() const { return typeIndex; } + /** @internal */ + static void clearCurrentlyFocusedHandler() { currentlyFocusedHandler = nullptr; } + + /** @internal + + The following functions provide the means to associate JUCE Components with OS specific + types that provide their own accessibility mechanisms. This way accessibility navigation + can move from a JUCE Component to a native, embedded window and back. + + These functions assume that the concrete types behind the void* are + - Windows: HWND + - MacOS: NSView* + - iOS: UIView* + - Android: GlobalRef that points to an android.view.View + */ + static void* getNativeChildForComponent (Component& component); + /** @internal */ + static void setNativeChildForComponent (Component& component, void* nativeChild); + /** @internal */ + static Component* getComponentForNativeChild (void* nativeChild); private: //============================================================================== diff --git a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp index 26fd91b..4f0b8de 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.cpp @@ -83,16 +83,12 @@ bool JUCEApplication::perform (const InvocationInfo& info) } //============================================================================== -#if JUCE_MAC - extern void juce_initialiseMacMainMenu(); -#endif - bool JUCEApplication::initialiseApp() { if (JUCEApplicationBase::initialiseApp()) { #if JUCE_MAC - juce_initialiseMacMainMenu(); // (needs to get the app's name) + initialiseMacMainMenu(); // (needs to get the app's name) #endif return true; diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp index 00ab105..0db6710 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.cpp @@ -715,100 +715,9 @@ void Button::repeatTimerCallback() } } -//============================================================================== -class ButtonAccessibilityHandler : public AccessibilityHandler -{ -public: - explicit ButtonAccessibilityHandler (Button& buttonToWrap, AccessibilityRole roleIn) - : AccessibilityHandler (buttonToWrap, - isRadioButton (buttonToWrap) ? AccessibilityRole::radioButton : roleIn, - getAccessibilityActions (buttonToWrap), - getAccessibilityInterfaces (buttonToWrap)), - button (buttonToWrap) - { - } - - AccessibleState getCurrentState() const override - { - auto state = AccessibilityHandler::getCurrentState(); - - if (button.isToggleable()) - { - state = state.withCheckable(); - - if (button.getToggleState()) - state = state.withChecked(); - } - - return state; - } - - String getTitle() const override - { - auto title = AccessibilityHandler::getTitle(); - - if (title.isEmpty()) - return button.getButtonText(); - - return title; - } - - String getHelp() const override { return button.getTooltip(); } - -private: - class ButtonValueInterface : public AccessibilityTextValueInterface - { - public: - explicit ButtonValueInterface (Button& buttonToWrap) - : button (buttonToWrap) - { - } - - bool isReadOnly() const override { return true; } - String getCurrentValueAsString() const override { return button.getToggleState() ? "On" : "Off"; } - void setValueAsString (const String&) override {} - - private: - Button& button; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonValueInterface) - }; - - static bool isRadioButton (const Button& button) noexcept - { - return button.getRadioGroupId() != 0; - } - - static AccessibilityActions getAccessibilityActions (Button& button) - { - auto actions = AccessibilityActions().addAction (AccessibilityActionType::press, - [&button] { button.triggerClick(); }); - - if (button.isToggleable()) - actions = actions.addAction (AccessibilityActionType::toggle, - [&button] { button.setToggleState (! button.getToggleState(), sendNotification); }); - - return actions; - } - - static Interfaces getAccessibilityInterfaces (Button& button) - { - if (button.isToggleable()) - return { std::make_unique (button) }; - - return {}; - } - - Button& button; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler) -}; - std::unique_ptr Button::createAccessibilityHandler() { - return std::make_unique (*this, AccessibilityRole::button); + return std::make_unique (*this, AccessibilityRole::button); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h index 0fe1137..b078379 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_Button.h @@ -495,6 +495,8 @@ class JUCE_API Button : public Component, void focusLost (FocusChangeType) override; /** @internal */ void enablementChanged() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -522,7 +524,6 @@ class JUCE_API Button : public Component, bool triggerOnMouseDown = false; bool generateTooltip = false; - std::unique_ptr createAccessibilityHandler() override; void checkToggleableState (bool wasToggleable); void repeatTimerCallback(); diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp index b5223d2..4c83ccc 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.cpp @@ -123,7 +123,7 @@ void HyperlinkButton::paintButton (Graphics& g, std::unique_ptr HyperlinkButton::createAccessibilityHandler() { - return std::make_unique (*this, AccessibilityRole::hyperlink); + return std::make_unique (*this, AccessibilityRole::hyperlink); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h index bffa764..6166477 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h @@ -101,6 +101,9 @@ class JUCE_API HyperlinkButton : public Button /** Returns the type of justification, as set in setJustificationType(). */ Justification getJustificationType() const noexcept { return justification; } + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; + protected: //============================================================================== /** @internal */ @@ -111,8 +114,6 @@ class JUCE_API HyperlinkButton : public Button void paintButton (Graphics&, bool, bool) override; private: - std::unique_ptr createAccessibilityHandler() override; - //============================================================================== using Button::clicked; Font getFontToUse() const; diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp index 758bb76..e914554 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.cpp @@ -59,7 +59,7 @@ void ToggleButton::colourChanged() std::unique_ptr ToggleButton::createAccessibilityHandler() { - return std::make_unique (*this, AccessibilityRole::toggleButton); + return std::make_unique (*this, AccessibilityRole::toggleButton); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h index d2ad2bc..3376522 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h +++ b/JuceLibraryCode/modules/juce_gui_basics/buttons/juce_ToggleButton.h @@ -76,6 +76,9 @@ class JUCE_API ToggleButton : public Button tickDisabledColourId = 0x1006503 /**< The colour to use for the disabled tick mark and/or outline. */ }; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; + protected: //============================================================================== /** @internal */ @@ -84,8 +87,6 @@ class JUCE_API ToggleButton : public Button void colourChanged() override; private: - std::unique_ptr createAccessibilityHandler() override; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton) }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp index e65b264..33d0e06 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/commands/juce_ApplicationCommandManager.cpp @@ -270,7 +270,7 @@ ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget( // getting a bit desperate now: try all desktop comps.. for (int i = desktop.getNumComponents(); --i >= 0;) if (auto* component = desktop.getComponent (i)) - if (isForegroundOrEmbeddedProcess (component)) + if (detail::WindowingHelpers::isForegroundOrEmbeddedProcess (component)) if (auto* peer = component->getPeer()) if (auto* target = findTargetForComponent (peer->getLastFocusedSubcomponent())) return target; diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp index 05092df..9305822 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.cpp @@ -23,6 +23,11 @@ ============================================================================== */ +#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN \ + jassert ((MessageManager::getInstanceWithoutCreating() != nullptr \ + && MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()) \ + || getPeer() == nullptr); + namespace juce { @@ -158,343 +163,6 @@ class Component::MouseListenerList JUCE_DECLARE_NON_COPYABLE (MouseListenerList) }; -//============================================================================== -struct FocusRestorer -{ - FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {} - - ~FocusRestorer() - { - if (lastFocus != nullptr - && lastFocus->isShowing() - && ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) - lastFocus->grabKeyboardFocus(); - } - - WeakReference lastFocus; - - JUCE_DECLARE_NON_COPYABLE (FocusRestorer) -}; - -//============================================================================== -struct ScalingHelpers -{ - template - static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept - { - return scale != 1.0f ? pos / scale : pos; - } - - template - static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept - { - return scale != 1.0f ? pos * scale : pos; - } - - // For these, we need to avoid getSmallestIntegerContainer being used, which causes - // judder when moving windows - static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept - { - return scale != 1.0f ? Rectangle (roundToInt ((float) pos.getX() / scale), - roundToInt ((float) pos.getY() / scale), - roundToInt ((float) pos.getWidth() / scale), - roundToInt ((float) pos.getHeight() / scale)) : pos; - } - - static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept - { - return scale != 1.0f ? Rectangle (roundToInt ((float) pos.getX() * scale), - roundToInt ((float) pos.getY() * scale), - roundToInt ((float) pos.getWidth() * scale), - roundToInt ((float) pos.getHeight() * scale)) : pos; - } - - static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept - { - return scale != 1.0f ? Rectangle (pos.getX() / scale, - pos.getY() / scale, - pos.getWidth() / scale, - pos.getHeight() / scale) : pos; - } - - static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept - { - return scale != 1.0f ? Rectangle (pos.getX() * scale, - pos.getY() * scale, - pos.getWidth() * scale, - pos.getHeight() * scale) : pos; - } - - template - static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept - { - return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos); - } - - template - static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept - { - return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos); - } - - template - static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept - { - return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos); - } - - template - static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept - { - return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos); - } - - static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition(); } - static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition(); } - static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } - static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } - static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition(); } - static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition(); } - static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } - static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } - - static Point screenPosToLocalPos (Component& comp, Point pos) - { - if (auto* peer = comp.getPeer()) - { - pos = peer->globalToLocal (pos); - auto& peerComp = peer->getComponent(); - return comp.getLocalPoint (&peerComp, unscaledScreenPosToScaled (peerComp, pos)); - } - - return comp.getLocalPoint (nullptr, unscaledScreenPosToScaled (comp, pos)); - } -}; - -static const char colourPropertyPrefix[] = "jcclr_"; - -//============================================================================== -struct Component::ComponentHelpers -{ - #if JUCE_MODAL_LOOPS_PERMITTED - static void* runModalLoopCallback (void* userData) - { - return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); - } - #endif - - static Identifier getColourPropertyID (int colourID) - { - char buffer[32]; - auto* end = buffer + numElementsInArray (buffer) - 1; - auto* t = end; - *t = 0; - - for (auto v = (uint32) colourID;;) - { - *--t = "0123456789abcdef" [v & 15]; - v >>= 4; - - if (v == 0) - break; - } - - for (int i = (int) sizeof (colourPropertyPrefix) - 1; --i >= 0;) - *--t = colourPropertyPrefix[i]; - - return t; - } - - //============================================================================== - static bool hitTest (Component& comp, Point localPoint) - { - const auto intPoint = localPoint.roundToInt(); - return Rectangle { comp.getWidth(), comp.getHeight() }.contains (intPoint) - && comp.hitTest (intPoint.x, intPoint.y); - } - - // converts an unscaled position within a peer to the local position within that peer's component - template - static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept - { - if (comp.isTransformed()) - pos = pos.transformedBy (comp.getTransform().inverted()); - - return ScalingHelpers::unscaledScreenPosToScaled (comp, pos); - } - - // converts a position within a peer's component to the unscaled position within the peer - template - static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept - { - if (comp.isTransformed()) - pos = pos.transformedBy (comp.getTransform()); - - return ScalingHelpers::scaledScreenPosToUnscaled (comp, pos); - } - - template - static PointOrRect convertFromParentSpace (const Component& comp, const PointOrRect pointInParentSpace) - { - const auto transformed = comp.affineTransform != nullptr ? pointInParentSpace.transformedBy (comp.affineTransform->inverted()) - : pointInParentSpace; - - if (comp.isOnDesktop()) - { - if (auto* peer = comp.getPeer()) - return ScalingHelpers::unscaledScreenPosToScaled (comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (transformed))); - - jassertfalse; - return transformed; - } - - if (comp.getParentComponent() == nullptr) - return ScalingHelpers::subtractPosition (ScalingHelpers::unscaledScreenPosToScaled (comp, ScalingHelpers::scaledScreenPosToUnscaled (transformed)), comp); - - return ScalingHelpers::subtractPosition (transformed, comp); - } - - template - static PointOrRect convertToParentSpace (const Component& comp, const PointOrRect pointInLocalSpace) - { - const auto preTransform = [&] - { - if (comp.isOnDesktop()) - { - if (auto* peer = comp.getPeer()) - return ScalingHelpers::unscaledScreenPosToScaled (peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); - - jassertfalse; - return pointInLocalSpace; - } - - if (comp.getParentComponent() == nullptr) - return ScalingHelpers::unscaledScreenPosToScaled (ScalingHelpers::scaledScreenPosToUnscaled (comp, ScalingHelpers::addPosition (pointInLocalSpace, comp))); - - return ScalingHelpers::addPosition (pointInLocalSpace, comp); - }(); - - return comp.affineTransform != nullptr ? preTransform.transformedBy (*comp.affineTransform) - : preTransform; - } - - template - static PointOrRect convertFromDistantParentSpace (const Component* parent, const Component& target, PointOrRect coordInParent) - { - auto* directParent = target.getParentComponent(); - jassert (directParent != nullptr); - - if (directParent == parent) - return convertFromParentSpace (target, coordInParent); - - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) - return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent)); - JUCE_END_IGNORE_WARNINGS_MSVC - } - - template - static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) - { - while (source != nullptr) - { - if (source == target) - return p; - - JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) - - if (source->isParentOf (target)) - return convertFromDistantParentSpace (source, *target, p); - - JUCE_END_IGNORE_WARNINGS_MSVC - - p = convertToParentSpace (*source, p); - source = source->getParentComponent(); - } - - jassert (source == nullptr); - if (target == nullptr) - return p; - - auto* topLevelComp = target->getTopLevelComponent(); - - p = convertFromParentSpace (*topLevelComp, p); - - if (topLevelComp == target) - return p; - - return convertFromDistantParentSpace (topLevelComp, *target, p); - } - - static bool clipObscuredRegions (const Component& comp, Graphics& g, - const Rectangle clipRect, Point delta) - { - bool wasClipped = false; - - for (int i = comp.childComponentList.size(); --i >= 0;) - { - auto& child = *comp.childComponentList.getUnchecked(i); - - if (child.isVisible() && ! child.isTransformed()) - { - auto newClip = clipRect.getIntersection (child.boundsRelativeToParent); - - if (! newClip.isEmpty()) - { - if (child.isOpaque() && child.componentTransparency == 0) - { - g.excludeClipRegion (newClip + delta); - wasClipped = true; - } - else - { - auto childPos = child.getPosition(); - - if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta)) - wasClipped = true; - } - } - } - } - - return wasClipped; - } - - static Rectangle getParentOrMainMonitorBounds (const Component& comp) - { - if (auto* p = comp.getParentComponent()) - return p->getLocalBounds(); - - return Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; - } - - static void releaseAllCachedImageResources (Component& c) - { - if (auto* cached = c.getCachedComponentImage()) - cached->releaseResources(); - - for (auto* child : c.childComponentList) - releaseAllCachedImageResources (*child); - } - - //============================================================================== - static bool modalWouldBlockComponent (const Component& maybeBlocked, Component* modal) - { - return modal != nullptr - && modal != &maybeBlocked - && ! modal->isParentOf (&maybeBlocked) - && ! modal->canModalEventBeSentToComponent (&maybeBlocked); - } - - template - static void sendMouseEventToComponentsThatAreBlockedByModal (Component& modal, Function&& function) - { - for (auto& ms : Desktop::getInstance().getMouseSources()) - if (auto* c = ms.getComponentUnderMouse()) - if (modalWouldBlockComponent (*c, &modal)) - (c->*function) (ms, ScalingHelpers::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime()); - } -}; - //============================================================================== Component::Component() noexcept : componentFlags (0) @@ -574,7 +242,7 @@ void Component::setVisible (bool shouldBeVisible) if (! shouldBeVisible) { - ComponentHelpers::releaseAllCachedImageResources (*this); + detail::ComponentHelpers::releaseAllCachedImageResources (*this); if (hasKeyboardFocus (true)) { @@ -664,8 +332,8 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) jmax (1, getHeight())); #endif - const auto unscaledPosition = ScalingHelpers::scaledScreenPosToUnscaled (getScreenPosition()); - const auto topLeft = ScalingHelpers::unscaledScreenPosToScaled (*this, unscaledPosition); + const auto unscaledPosition = detail::ScalingHelpers::scaledScreenPosToUnscaled (getScreenPosition()); + const auto topLeft = detail::ScalingHelpers::unscaledScreenPosToScaled (*this, unscaledPosition); bool wasFullscreen = false; bool wasMinimised = false; @@ -747,7 +415,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) internalHierarchyChanged(); if (auto* handler = getAccessibilityHandler()) - notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowOpened); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*handler, detail::AccessibilityHelpers::Event::windowOpened); } } } @@ -761,9 +429,9 @@ void Component::removeFromDesktop() if (flags.hasHeavyweightPeerFlag) { if (auto* handler = getAccessibilityHandler()) - notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::windowClosed); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*handler, detail::AccessibilityHelpers::Event::windowClosed); - ComponentHelpers::releaseAllCachedImageResources (*this); + detail::ComponentHelpers::releaseAllCachedImageResources (*this); auto* peer = ComponentPeer::getPeerFor (this); jassert (peer != nullptr); @@ -1115,15 +783,15 @@ int Component::getScreenY() const { return getScreenPositi Point Component::getScreenPosition() const { return localPointToGlobal (Point()); } Rectangle Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } -Point Component::getLocalPoint (const Component* source, Point point) const { return ComponentHelpers::convertCoordinate (this, source, point); } -Point Component::getLocalPoint (const Component* source, Point point) const { return ComponentHelpers::convertCoordinate (this, source, point); } -Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return ComponentHelpers::convertCoordinate (this, source, area); } -Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return ComponentHelpers::convertCoordinate (this, source, area); } +Point Component::getLocalPoint (const Component* source, Point point) const { return detail::ComponentHelpers::convertCoordinate (this, source, point); } +Point Component::getLocalPoint (const Component* source, Point point) const { return detail::ComponentHelpers::convertCoordinate (this, source, point); } +Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return detail::ComponentHelpers::convertCoordinate (this, source, area); } +Rectangle Component::getLocalArea (const Component* source, Rectangle area) const { return detail::ComponentHelpers::convertCoordinate (this, source, area); } -Point Component::localPointToGlobal (Point point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } -Point Component::localPointToGlobal (Point point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } -Rectangle Component::localAreaToGlobal (Rectangle area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } -Rectangle Component::localAreaToGlobal (Rectangle area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } +Point Component::localPointToGlobal (Point point) const { return detail::ComponentHelpers::convertCoordinate (nullptr, this, point); } +Point Component::localPointToGlobal (Point point) const { return detail::ComponentHelpers::convertCoordinate (nullptr, this, point); } +Rectangle Component::localAreaToGlobal (Rectangle area) const { return detail::ComponentHelpers::convertCoordinate (nullptr, this, area); } +Rectangle Component::localAreaToGlobal (Rectangle area) const { return detail::ComponentHelpers::convertCoordinate (nullptr, this, area); } //============================================================================== void Component::setBounds (int x, int y, int w, int h) @@ -1238,7 +906,7 @@ void Component::sendMovedResizedMessages (bool wasMoved, bool wasResized) if ((wasMoved || wasResized) && ! checker.shouldBailOut()) if (auto* handler = getAccessibilityHandler()) - notifyAccessibilityEventInternal (*handler, InternalAccessibilityEvent::elementMovedOrResized); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*handler, detail::AccessibilityHelpers::Event::elementMovedOrResized); } void Component::setSize (int w, int h) { setBounds (getX(), getY(), w, h); } @@ -1271,7 +939,7 @@ void Component::setBoundsRelative (float x, float y, float w, float h) void Component::centreWithSize (int width, int height) { - auto parentArea = ComponentHelpers::getParentOrMainMonitorBounds (*this) + auto parentArea = detail::ComponentHelpers::getParentOrMainMonitorBounds (*this) .transformedBy (getTransform().inverted()); setBounds (parentArea.getCentreX() - width / 2, @@ -1281,7 +949,7 @@ void Component::centreWithSize (int width, int height) void Component::setBoundsInset (BorderSize borders) { - setBounds (borders.subtractedFrom (ComponentHelpers::getParentOrMainMonitorBounds (*this))); + setBounds (borders.subtractedFrom (detail::ComponentHelpers::getParentOrMainMonitorBounds (*this))); } void Component::setBoundsToFit (Rectangle targetArea, Justification justification, bool onlyReduceInSize) @@ -1391,7 +1059,7 @@ bool Component::hitTest (int x, int y) auto& child = *childComponentList.getUnchecked (i); if (child.isVisible() - && ComponentHelpers::hitTest (child, ComponentHelpers::convertFromParentSpace (child, Point (x, y).toFloat()))) + && detail::ComponentHelpers::hitTest (child, detail::ComponentHelpers::convertFromParentSpace (child, Point (x, y).toFloat()))) return true; } } @@ -1420,14 +1088,14 @@ bool Component::contains (Point point) bool Component::contains (Point point) { - if (ComponentHelpers::hitTest (*this, point)) + if (detail::ComponentHelpers::hitTest (*this, point)) { if (parentComponent != nullptr) - return parentComponent->contains (ComponentHelpers::convertToParentSpace (*this, point)); + return parentComponent->contains (detail::ComponentHelpers::convertToParentSpace (*this, point)); if (flags.hasHeavyweightPeerFlag) if (auto* peer = getPeer()) - return peer->contains (ComponentHelpers::localPositionToRawPeerPos (*this, point).roundToInt(), true); + return peer->contains (detail::ComponentHelpers::localPositionToRawPeerPos (*this, point).roundToInt(), true); } return false; @@ -1456,13 +1124,13 @@ Component* Component::getComponentAt (Point position) Component* Component::getComponentAt (Point position) { - if (flags.visibleFlag && ComponentHelpers::hitTest (*this, position)) + if (flags.visibleFlag && detail::ComponentHelpers::hitTest (*this, position)) { for (int i = childComponentList.size(); --i >= 0;) { auto* child = childComponentList.getUnchecked (i); - child = child->getComponentAt (ComponentHelpers::convertFromParentSpace (*child, position)); + child = child->getComponentAt (detail::ComponentHelpers::convertFromParentSpace (*child, position)); if (child != nullptr) return child; @@ -1579,7 +1247,7 @@ Component* Component::removeChildComponent (int index, bool sendParentEvents, bo childComponentList.remove (index); child->parentComponent = nullptr; - ComponentHelpers::releaseAllCachedImageResources (*child); + detail::ComponentHelpers::releaseAllCachedImageResources (*child); // (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) if (child->hasKeyboardFocus (true)) @@ -1732,7 +1400,7 @@ int Component::runModalLoop() { // use a callback so this can be called from non-gui threads return (int) (pointer_sized_int) MessageManager::getInstance() - ->callFunctionOnMessageThread (&ComponentHelpers::runModalLoopCallback, this); + ->callFunctionOnMessageThread (&detail::ComponentHelpers::runModalLoopCallback, this); } if (! isCurrentlyModal (false)) @@ -1758,7 +1426,7 @@ void Component::enterModalState (bool shouldTakeKeyboardFocus, // While this component is in modal state it may block other components from receiving // mouseExit events. To keep mouseEnter and mouseExit calls balanced on these components, // we must manually force the mouse to "leave" blocked components. - ComponentHelpers::sendMouseEventToComponentsThatAreBlockedByModal (*this, &Component::internalMouseExit); + detail::ComponentHelpers::sendMouseEventToComponentsThatAreBlockedByModal (*this, &Component::internalMouseExit); if (safeReference == nullptr) { @@ -1799,7 +1467,7 @@ void Component::exitModalState (int returnValue) // mouseEnter events. To keep mouseEnter and mouseExit calls balanced on these components, // we must manually force the mouse to "enter" blocked components. if (deletionChecker != nullptr) - ComponentHelpers::sendMouseEventToComponentsThatAreBlockedByModal (*deletionChecker, &Component::internalMouseEnter); + detail::ComponentHelpers::sendMouseEventToComponentsThatAreBlockedByModal (*deletionChecker, &Component::internalMouseEnter); } else { @@ -1822,7 +1490,7 @@ bool Component::isCurrentlyModal (bool onlyConsiderForemostModalComponent) const bool Component::isCurrentlyBlockedByAnotherModalComponent() const { - return ComponentHelpers::modalWouldBlockComponent (*this, getCurrentlyModalComponent()); + return detail::ComponentHelpers::modalWouldBlockComponent (*this, getCurrentlyModalComponent()); } int JUCE_CALLTYPE Component::getNumCurrentlyModalComponents() noexcept @@ -1923,7 +1591,7 @@ void Component::repaint (Rectangle area) void Component::repaintParent() { if (parentComponent != nullptr) - parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, getLocalBounds())); + parentComponent->internalRepaint (detail::ComponentHelpers::convertToParentSpace (*this, getLocalBounds())); } void Component::internalRepaint (Rectangle area) @@ -1965,7 +1633,7 @@ void Component::internalRepaintUnchecked (Rectangle area, bool isEntireComp else { if (parentComponent != nullptr) - parentComponent->internalRepaint (ComponentHelpers::convertToParentSpace (*this, area)); + parentComponent->internalRepaint (detail::ComponentHelpers::convertToParentSpace (*this, area)); } } } @@ -2006,7 +1674,7 @@ void Component::paintComponentAndChildren (Graphics& g) { Graphics::ScopedSaveState ss (g); - if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty())) + if (! (detail::ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty())) paint (g); } @@ -2209,7 +1877,7 @@ void Component::sendLookAndFeelChange() Colour Component::findColour (int colourID, bool inheritFromParent) const { - if (auto* v = properties.getVarPointer (ComponentHelpers::getColourPropertyID (colourID))) + if (auto* v = properties.getVarPointer (detail::ComponentHelpers::getColourPropertyID (colourID))) return Colour ((uint32) static_cast (*v)); if (inheritFromParent && parentComponent != nullptr @@ -2221,18 +1889,18 @@ Colour Component::findColour (int colourID, bool inheritFromParent) const bool Component::isColourSpecified (int colourID) const { - return properties.contains (ComponentHelpers::getColourPropertyID (colourID)); + return properties.contains (detail::ComponentHelpers::getColourPropertyID (colourID)); } void Component::removeColour (int colourID) { - if (properties.remove (ComponentHelpers::getColourPropertyID (colourID))) + if (properties.remove (detail::ComponentHelpers::getColourPropertyID (colourID))) colourChanged(); } void Component::setColour (int colourID, Colour colour) { - if (properties.set (ComponentHelpers::getColourPropertyID (colourID), (int) colour.getARGB())) + if (properties.set (detail::ComponentHelpers::getColourPropertyID (colourID), (int) colour.getARGB())) colourChanged(); } @@ -2244,7 +1912,7 @@ void Component::copyAllExplicitColoursTo (Component& target) const { auto name = properties.getName(i); - if (name.toString().startsWith (colourPropertyPrefix)) + if (name.toString().startsWith (detail::colourPropertyPrefix)) if (target.properties.set (name, properties [name])) changed = true; } @@ -2405,7 +2073,7 @@ void Component::internalMouseEnter (MouseInputSource source, Point relati repaint(); const auto me = makeMouseEvent (source, - PointerState().withPosition (relativePos), + detail::PointerState().withPosition (relativePos), source.getCurrentModifiers(), this, this, @@ -2442,7 +2110,7 @@ void Component::internalMouseExit (MouseInputSource source, Point relativ flags.cachedMouseInsideComponent = false; const auto me = makeMouseEvent (source, - PointerState().withPosition (relativePos), + detail::PointerState().withPosition (relativePos), source.getCurrentModifiers(), this, this, @@ -2462,7 +2130,9 @@ void Component::internalMouseExit (MouseInputSource source, Point relativ MouseListenerList::sendMouseEvent (checker, &MouseListener::mouseExit); } -void Component::internalMouseDown (MouseInputSource source, const PointerState& relativePointerState, Time time) +void Component::internalMouseDown (MouseInputSource source, + const detail::PointerState& relativePointerState, + Time time) { auto& desktop = Desktop::getInstance(); @@ -2512,7 +2182,7 @@ void Component::internalMouseDown (MouseInputSource source, const PointerState& if (! flags.dontFocusOnMouseClickFlag) { - grabKeyboardFocusInternal (focusChangedByMouseClick, true); + grabKeyboardFocusInternal (focusChangedByMouseClick, true, FocusChangeDirection::unknown); if (checker.shouldBailOut()) return; @@ -2531,7 +2201,10 @@ void Component::internalMouseDown (MouseInputSource source, const PointerState& MouseListenerList::sendMouseEvent (checker, &MouseListener::mouseDown); } -void Component::internalMouseUp (MouseInputSource source, const PointerState& relativePointerState, Time time, const ModifierKeys oldModifiers) +void Component::internalMouseUp (MouseInputSource source, + const detail::PointerState& relativePointerState, + Time time, + const ModifierKeys oldModifiers) { if (flags.mouseDownWasBlocked && isCurrentlyBlockedByAnotherModalComponent()) return; @@ -2579,7 +2252,7 @@ void Component::internalMouseUp (MouseInputSource source, const PointerState& re } } -void Component::internalMouseDrag (MouseInputSource source, const PointerState& relativePointerState, Time time) +void Component::internalMouseDrag (MouseInputSource source, const detail::PointerState& relativePointerState, Time time) { if (! isCurrentlyBlockedByAnotherModalComponent()) { @@ -2618,7 +2291,7 @@ void Component::internalMouseMove (MouseInputSource source, Point relativ else { const auto me = makeMouseEvent (source, - PointerState().withPosition (relativePos), + detail::PointerState().withPosition (relativePos), source.getCurrentModifiers(), this, this, @@ -2646,7 +2319,7 @@ void Component::internalMouseWheel (MouseInputSource source, Point relati auto& desktop = Desktop::getInstance(); const auto me = makeMouseEvent (source, - PointerState().withPosition (relativePos), + detail::PointerState().withPosition (relativePos), source.getCurrentModifiers(), this, this, @@ -2683,7 +2356,7 @@ void Component::internalMagnifyGesture (MouseInputSource source, Point re auto& desktop = Desktop::getInstance(); const auto me = makeMouseEvent (source, - PointerState().withPosition (relativePos), + detail::PointerState().withPosition (relativePos), source.getCurrentModifiers(), this, this, @@ -2762,17 +2435,20 @@ void Component::internalBroughtToFront() //============================================================================== void Component::focusGained (FocusChangeType) {} +void Component::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {} void Component::focusLost (FocusChangeType) {} void Component::focusOfChildComponentChanged (FocusChangeType) {} void Component::internalKeyboardFocusGain (FocusChangeType cause) { - internalKeyboardFocusGain (cause, WeakReference (this)); + internalKeyboardFocusGain (cause, WeakReference (this), FocusChangeDirection::unknown); } void Component::internalKeyboardFocusGain (FocusChangeType cause, - const WeakReference& safePointer) + const WeakReference& safePointer, + FocusChangeDirection direction) { + focusGainedWithDirection (cause, direction); focusGained (cause); if (safePointer == nullptr) @@ -2884,16 +2560,16 @@ Component* Component::findKeyboardFocusContainer() const return findContainer (this, &Component::isKeyboardFocusContainer); } -static const Identifier juce_explicitFocusOrderId ("_jexfo"); +static const Identifier explicitFocusOrderId ("_jexfo"); int Component::getExplicitFocusOrder() const { - return properties [juce_explicitFocusOrderId]; + return properties [explicitFocusOrderId]; } void Component::setExplicitFocusOrder (int newFocusOrderIndex) { - properties.set (juce_explicitFocusOrderId, newFocusOrderIndex); + properties.set (explicitFocusOrderId, newFocusOrderIndex); } std::unique_ptr Component::createFocusTraverser() @@ -2912,7 +2588,7 @@ std::unique_ptr Component::createKeyboardFocusTraverser() return parentComponent->createKeyboardFocusTraverser(); } -void Component::takeKeyboardFocus (FocusChangeType cause) +void Component::takeKeyboardFocus (FocusChangeType cause, FocusChangeDirection direction) { if (currentlyFocusedComponent == this) return; @@ -2941,11 +2617,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause) componentLosingFocus->internalKeyboardFocusLoss (cause); if (currentlyFocusedComponent == this) - internalKeyboardFocusGain (cause, safePointer); + internalKeyboardFocusGain (cause, safePointer, direction); } } -void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryParent) +void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryParent, FocusChangeDirection direction) { if (! isShowing()) return; @@ -2953,7 +2629,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar if (flags.wantsKeyboardFocusFlag && (isEnabled() || parentComponent == nullptr)) { - takeKeyboardFocus (cause); + takeKeyboardFocus (cause, direction); return; } @@ -2964,7 +2640,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar { if (auto* defaultComp = traverser->getDefaultComponent (this)) { - defaultComp->grabKeyboardFocusInternal (cause, false); + defaultComp->grabKeyboardFocusInternal (cause, false, direction); return; } } @@ -2972,7 +2648,7 @@ void Component::grabKeyboardFocusInternal (FocusChangeType cause, bool canTryPar // if no children want it and we're allowed to try our parent comp, // then pass up to parent, which will try our siblings. if (canTryParent && parentComponent != nullptr) - parentComponent->grabKeyboardFocusInternal (cause, true); + parentComponent->grabKeyboardFocusInternal (cause, true, direction); } void Component::grabKeyboardFocus() @@ -2981,7 +2657,7 @@ void Component::grabKeyboardFocus() // thread, you'll need to use a MessageManagerLock object to make sure it's thread-safe. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - grabKeyboardFocusInternal (focusChangedDirectly, true); + grabKeyboardFocusInternal (focusChangedDirectly, true, FocusChangeDirection::unknown); // A component can only be focused when it's actually on the screen! // If this fails then you're probably trying to grab the focus before you've @@ -3057,7 +2733,10 @@ void Component::moveKeyboardFocusToSibling (bool moveToNext) return; } - nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, true); + nextComp->grabKeyboardFocusInternal (focusChangedByTabKey, + true, + moveToNext ? FocusChangeDirection::forward + : FocusChangeDirection::backward); return; } } @@ -3300,7 +2979,7 @@ AccessibilityHandler* Component::getAccessibilityHandler() // created, the if() predicate above should evaluate to false on recursive calls, // terminating the recursion. if (accessibilityHandler != nullptr) - notifyAccessibilityEventInternal (*accessibilityHandler, InternalAccessibilityEvent::elementCreated); + detail::AccessibilityHelpers::notifyAccessibilityEvent (*accessibilityHandler, detail::AccessibilityHelpers::Event::elementCreated); else jassertfalse; // createAccessibilityHandler must return non-null } diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h index 392fa3a..3469a31 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_Component.h @@ -1168,14 +1168,14 @@ class JUCE_API Component : public MouseListener Calling this method will also invoke the sendLookAndFeelChange() method. - @see getLookAndFeel, lookAndFeelChanged + @see getLookAndFeel, lookAndFeelChanged, sendLookAndFeelChange */ void setLookAndFeel (LookAndFeel* newLookAndFeel); /** Called to let the component react to a change in the look-and-feel setting. - When the look-and-feel is changed for a component, this will be called in - all its child components, recursively. + When the look-and-feel is changed for a component, this method, repaint(), and + colourChanged() are called on the original component and all its children recursively. It can also be triggered manually by the sendLookAndFeelChange() method, in case an application uses a LookAndFeel class that might have changed internally. @@ -1184,10 +1184,8 @@ class JUCE_API Component : public MouseListener */ virtual void lookAndFeelChanged(); - /** Calls the lookAndFeelChanged() method in this component and all its children. - - This will recurse through the children and their children, calling lookAndFeelChanged() - on them all. + /** Calls the methods repaint(), lookAndFeelChanged(), and colourChanged() in this + component and all its children recursively. @see lookAndFeelChanged */ @@ -1893,11 +1891,28 @@ class JUCE_API Component : public MouseListener focusChangedDirectly /**< Means that the focus was changed by a call to grabKeyboardFocus(). */ }; + /** Enumeration used by the focusGainedWithDirection() method. */ + enum class FocusChangeDirection + { + unknown, + forward, + backward + }; + /** Called to indicate that this component has just acquired the keyboard focus. @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus */ virtual void focusGained (FocusChangeType cause); + /** Called to indicate that this component has just acquired the keyboard focus. + + This function is called every time focusGained() is called but it has an additional change + direction parameter. + + @see focusLost, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus + */ + virtual void focusGainedWithDirection (FocusChangeType cause, FocusChangeDirection direction); + /** Called to indicate that this component has just lost the keyboard focus. @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus */ @@ -2113,13 +2128,14 @@ class JUCE_API Component : public MouseListener The callback is an optional object which will receive a callback when the modal component loses its modal status, either by being hidden or when exitModalState() is called. If you pass an object in here, the system will take care of deleting it - later, after making the callback + later, after making the callback. If deleteWhenDismissed is true, then when it is dismissed, the component will be deleted and then the callback will be called. (This will safely handle the situation where the component is deleted before its exitModalState() method is called). - @see exitModalState, runModalLoop, ModalComponentManager::attachCallback + @see exitModalState, runModalLoop, ModalComponentManager::attachCallback, + ModalCallbackFunction */ void enterModalState (bool takeKeyboardFocus = true, ModalComponentManager::Callback* callback = nullptr, @@ -2236,6 +2252,8 @@ class JUCE_API Component : public MouseListener method, which your component can override if it needs to do something when colours are altered. + Note repaint() is not automatically called when a colour is changed. + For more details about colour IDs, see the comments for findColour(). @see findColour, isColourSpecified, colourChanged, LookAndFeel::findColour, LookAndFeel::setColour @@ -2257,8 +2275,11 @@ class JUCE_API Component : public MouseListener */ void copyAllExplicitColoursTo (Component& target) const; - /** This method is called when a colour is changed by the setColour() method. - @see setColour, findColour + /** This method is called when a colour is changed by the setColour() method, + or when the look-and-feel is changed by the setLookAndFeel() or + sendLookAndFeelChanged() methods. + + @see setColour, findColour, setLookAndFeel, sendLookAndFeelChanged */ virtual void colourChanged(); @@ -2479,6 +2500,9 @@ class JUCE_API Component : public MouseListener /** Returns the accessibility handler for this component, or nullptr if this component is not accessible. + To customise the accessibility handler for a component, override + createAccessibilityHandler(). + @see setAccessible */ AccessibilityHandler* getAccessibilityHandler(); @@ -2492,20 +2516,6 @@ class JUCE_API Component : public MouseListener void invalidateAccessibilityHandler(); //============================================================================== - #ifndef DOXYGEN - [[deprecated ("Use the setFocusContainerType that takes a more descriptive enum.")]] - void setFocusContainer (bool shouldBeFocusContainer) noexcept - { - setFocusContainerType (shouldBeFocusContainer ? FocusContainerType::keyboardFocusContainer - : FocusContainerType::none); - } - - [[deprecated ("Use the contains that takes a Point.")]] - void contains (int, int) = delete; - #endif - -private: - //============================================================================== /** Override this method to return a custom AccessibilityHandler for this component. The default implementation creates and returns a AccessibilityHandler object with an @@ -2519,13 +2529,34 @@ class JUCE_API Component : public MouseListener its Component, so it's safe to store and use a reference back to the Component inside the AccessibilityHandler if necessary. + This function should rarely be called directly. If you need to query a component's + accessibility handler, it's normally better to call getAccessibilityHandler(). + The exception to this rule is derived implementations of createAccessibilityHandler(), + which may find it useful to call the base class implementation, and then wrap or + modify the result. + @see getAccessibilityHandler */ virtual std::unique_ptr createAccessibilityHandler(); + //============================================================================== + #ifndef DOXYGEN + [[deprecated ("Use the setFocusContainerType that takes a more descriptive enum.")]] + void setFocusContainer (bool shouldBeFocusContainer) noexcept + { + setFocusContainerType (shouldBeFocusContainer ? FocusContainerType::keyboardFocusContainer + : FocusContainerType::none); + } + + [[deprecated ("Use the contains that takes a Point.")]] + void contains (int, int) = delete; + #endif + +private: + //============================================================================== friend class ComponentPeer; - friend class MouseInputSourceInternal; + friend class detail::MouseInputSourceImpl; #ifndef DOXYGEN static Component* currentlyFocusedComponent; @@ -2594,14 +2625,14 @@ class JUCE_API Component : public MouseListener //============================================================================== void internalMouseEnter (MouseInputSource, Point, Time); void internalMouseExit (MouseInputSource, Point, Time); - void internalMouseDown (MouseInputSource, const PointerState&, Time); - void internalMouseUp (MouseInputSource, const PointerState&, Time, const ModifierKeys oldModifiers); - void internalMouseDrag (MouseInputSource, const PointerState&, Time); + void internalMouseDown (MouseInputSource, const detail::PointerState&, Time); + void internalMouseUp (MouseInputSource, const detail::PointerState&, Time, const ModifierKeys oldModifiers); + void internalMouseDrag (MouseInputSource, const detail::PointerState&, Time); void internalMouseMove (MouseInputSource, Point, Time); void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); void internalMagnifyGesture (MouseInputSource, Point, Time, float); void internalBroughtToFront(); - void internalKeyboardFocusGain (FocusChangeType, const WeakReference&); + void internalKeyboardFocusGain (FocusChangeType, const WeakReference&, FocusChangeDirection); void internalKeyboardFocusGain (FocusChangeType); void internalKeyboardFocusLoss (FocusChangeType); void internalChildKeyboardFocusChange (FocusChangeType, const WeakReference&); @@ -2619,14 +2650,13 @@ class JUCE_API Component : public MouseListener void sendMovedResizedMessagesIfPending(); void repaintParent(); void sendFakeMouseMove() const; - void takeKeyboardFocus (FocusChangeType); - void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent); + void takeKeyboardFocus (FocusChangeType, FocusChangeDirection); + void grabKeyboardFocusInternal (FocusChangeType, bool canTryParent, FocusChangeDirection); void giveAwayKeyboardFocusInternal (bool sendFocusLossEvent); void sendEnablementChangeMessage(); void sendVisibilityChangeMessage(); - struct ComponentHelpers; - friend struct ComponentHelpers; + friend struct detail::ComponentHelpers; /* Components aren't allowed to have copy constructors, as this would mess up parent hierarchies. You might need to give your subclasses a private dummy constructor to avoid compiler warnings. diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_FocusTraverser.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_FocusTraverser.cpp index 9f8ea4f..1eb7c34 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_FocusTraverser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_FocusTraverser.cpp @@ -26,111 +26,25 @@ namespace juce { -namespace FocusHelpers -{ - static int getOrder (const Component* c) - { - auto order = c->getExplicitFocusOrder(); - return order > 0 ? order : std::numeric_limits::max(); - } - - template - static void findAllComponents (Component* parent, - std::vector& components, - FocusContainerFn isFocusContainer) - { - if (parent == nullptr || parent->getNumChildComponents() == 0) - return; - - std::vector localComponents; - - for (auto* c : parent->getChildren()) - if (c->isVisible() && c->isEnabled()) - localComponents.push_back (c); - - const auto compareComponents = [&] (const Component* a, const Component* b) - { - const auto getComponentOrderAttributes = [] (const Component* c) - { - return std::make_tuple (getOrder (c), - c->isAlwaysOnTop() ? 0 : 1, - c->getY(), - c->getX()); - }; - - return getComponentOrderAttributes (a) < getComponentOrderAttributes (b); - }; - - // This will sort so that they are ordered in terms of explicit focus, - // always on top, left-to-right, and then top-to-bottom. - std::stable_sort (localComponents.begin(), localComponents.end(), compareComponents); - - for (auto* c : localComponents) - { - components.push_back (c); - - if (! (c->*isFocusContainer)()) - findAllComponents (c, components, isFocusContainer); - } - } - - enum class NavigationDirection { forwards, backwards }; - - template - static Component* navigateFocus (Component* current, - Component* focusContainer, - NavigationDirection direction, - FocusContainerFn isFocusContainer) - { - if (focusContainer != nullptr) - { - std::vector components; - findAllComponents (focusContainer, components, isFocusContainer); - - const auto iter = std::find (components.cbegin(), components.cend(), current); - - if (iter == components.cend()) - return nullptr; - - switch (direction) - { - case NavigationDirection::forwards: - if (iter != std::prev (components.cend())) - return *std::next (iter); - - break; - - case NavigationDirection::backwards: - if (iter != components.cbegin()) - return *std::prev (iter); - - break; - } - } - - return nullptr; - } -} - //============================================================================== Component* FocusTraverser::getNextComponent (Component* current) { jassert (current != nullptr); - return FocusHelpers::navigateFocus (current, - current->findFocusContainer(), - FocusHelpers::NavigationDirection::forwards, - &Component::isFocusContainer); + return detail::FocusHelpers::navigateFocus (current, + current->findFocusContainer(), + detail::FocusHelpers::NavigationDirection::forwards, + &Component::isFocusContainer); } Component* FocusTraverser::getPreviousComponent (Component* current) { jassert (current != nullptr); - return FocusHelpers::navigateFocus (current, - current->findFocusContainer(), - FocusHelpers::NavigationDirection::backwards, - &Component::isFocusContainer); + return detail::FocusHelpers::navigateFocus (current, + current->findFocusContainer(), + detail::FocusHelpers::NavigationDirection::backwards, + &Component::isFocusContainer); } Component* FocusTraverser::getDefaultComponent (Component* parentComponent) @@ -138,9 +52,9 @@ Component* FocusTraverser::getDefaultComponent (Component* parentComponent) if (parentComponent != nullptr) { std::vector components; - FocusHelpers::findAllComponents (parentComponent, - components, - &Component::isFocusContainer); + detail::FocusHelpers::findAllComponents (parentComponent, + components, + &Component::isFocusContainer); if (! components.empty()) return components.front(); @@ -152,9 +66,9 @@ Component* FocusTraverser::getDefaultComponent (Component* parentComponent) std::vector FocusTraverser::getAllComponents (Component* parentComponent) { std::vector components; - FocusHelpers::findAllComponents (parentComponent, - components, - &Component::isFocusContainer); + detail::FocusHelpers::findAllComponents (parentComponent, + components, + &Component::isFocusContainer); return components; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp index bc0aeb7..0dde16f 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp @@ -269,7 +269,7 @@ int ModalComponentManager::runEventLoopForCurrentComponent() if (auto* currentlyModal = getModalComponent (0)) { - FocusRestorer focusRestorer; + detail::FocusRestorer focusRestorer; bool finished = false; attachCallback (currentlyModal, ModalCallbackFunction::create ([&] (int r) { returnValue = r; finished = true; })); diff --git a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.cpp b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.cpp index e4ee2c3..0fa2293 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.cpp @@ -27,7 +27,7 @@ namespace juce { Desktop::Desktop() - : mouseSources (new MouseInputSource::SourceList()), + : mouseSources (new detail::MouseInputSourceList()), masterScaleFactor ((float) getDefaultMasterScale()), nativeDarkModeChangeDetectorImpl (createNativeDarkModeChangeDetectorImpl()) { @@ -353,7 +353,7 @@ void Desktop::setGlobalScaleFactor (float newScaleFactor) noexcept { JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - if (masterScaleFactor != newScaleFactor) + if (! approximatelyEqual (masterScaleFactor, newScaleFactor)) { masterScaleFactor = newScaleFactor; displays->refresh(); diff --git a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.h b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.h index 79ba30d..fb12da3 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.h +++ b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Desktop.h @@ -410,12 +410,12 @@ class JUCE_API Desktop : private DeletedAtShutdown, friend class Component; friend class ComponentPeer; - friend class MouseInputSourceInternal; + friend class detail::MouseInputSourceImpl; friend class DeletedAtShutdown; - friend class TopLevelWindowManager; + friend class detail::TopLevelWindowManager; friend class Displays; - std::unique_ptr mouseSources; + std::unique_ptr mouseSources; ListenerList mouseListeners; ListenerList focusListeners; diff --git a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Displays.cpp b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Displays.cpp index 0e5cb35..d74721c 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Displays.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/desktop/juce_Displays.cpp @@ -26,15 +26,6 @@ namespace juce { -template -auto* getPrimaryDisplayImpl (This& t) -{ - JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - - const auto iter = std::find_if (t.displays.begin(), t.displays.end(), [] (auto& d) { return d.isMain; }); - return iter != t.displays.end() ? std::addressof (*iter) : nullptr; -} - Displays::Displays (Desktop& desktop) { init (desktop); @@ -171,7 +162,10 @@ Point Displays::logicalToPhysical (Point point, const Disp const Displays::Display* Displays::getPrimaryDisplay() const noexcept { - return getPrimaryDisplayImpl (*this); + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + + const auto iter = std::find_if (displays.begin(), displays.end(), [] (auto& d) { return d.isMain; }); + return iter != displays.end() ? iter : nullptr; } RectangleList Displays::getRectangleList (bool userAreasOnly) const @@ -266,10 +260,10 @@ static void processDisplay (DisplayNode* currentNode, Array& allNod Rectangle logicalArea (0.0, 0.0, logicalWidth, logicalHeight); - if (physicalArea.getRight() == physicalParentArea.getX()) logicalArea.setPosition ({ logicalParentArea.getX() - logicalWidth, physicalArea.getY() / parentScale }); // on left - else if (physicalArea.getX() == physicalParentArea.getRight()) logicalArea.setPosition ({ logicalParentArea.getRight(), physicalArea.getY() / parentScale }); // on right - else if (physicalArea.getBottom() == physicalParentArea.getY()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getY() - logicalHeight }); // on top - else if (physicalArea.getY() == physicalParentArea.getBottom()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getBottom() }); // on bottom + if (approximatelyEqual (physicalArea.getRight(), physicalParentArea.getX())) logicalArea.setPosition ({ logicalParentArea.getX() - logicalWidth, physicalArea.getY() / parentScale }); // on left + else if (approximatelyEqual (physicalArea.getX(), physicalParentArea.getRight())) logicalArea.setPosition ({ logicalParentArea.getRight(), physicalArea.getY() / parentScale }); // on right + else if (approximatelyEqual (physicalArea.getBottom(), physicalParentArea.getY())) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getY() - logicalHeight }); // on top + else if (approximatelyEqual (physicalArea.getY(), physicalParentArea.getBottom())) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getBottom() }); // on bottom else jassertfalse; currentNode->logicalArea = logicalArea; @@ -292,8 +286,8 @@ static void processDisplay (DisplayNode* currentNode, Array& allNod const auto otherPhysicalArea = node.display->totalArea.toDouble(); // If the displays are touching on any side - if (otherPhysicalArea.getX() == physicalArea.getRight() || otherPhysicalArea.getRight() == physicalArea.getX() - || otherPhysicalArea.getY() == physicalArea.getBottom() || otherPhysicalArea.getBottom() == physicalArea.getY()) + if (approximatelyEqual (otherPhysicalArea.getX(), physicalArea.getRight()) || approximatelyEqual (otherPhysicalArea.getRight(), physicalArea.getX()) + || approximatelyEqual (otherPhysicalArea.getY(), physicalArea.getBottom()) || approximatelyEqual (otherPhysicalArea.getBottom(), physicalArea.getY())) { node.parent = currentNode; children.add (&node); diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.cpp b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.cpp new file mode 100644 index 0000000..2779e3b --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.cpp @@ -0,0 +1,33 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED + void AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler&, Event) {} +#endif + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.h new file mode 100644 index 0000000..b0ea244 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AccessibilityHelpers.h @@ -0,0 +1,70 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct AccessibilityHelpers +{ + AccessibilityHelpers() = delete; + + enum class Event + { + elementCreated, + elementDestroyed, + elementMovedOrResized, + focusChanged, + windowOpened, + windowClosed + }; + + static void notifyAccessibilityEvent (const AccessibilityHandler&, Event); + + static String getApplicationOrPluginName() + { + #if defined (JucePlugin_Name) + return JucePlugin_Name; + #else + if (auto* app = JUCEApplicationBase::getInstance()) + return app->getApplicationName(); + + return "JUCE Application"; + #endif + } + + template + static const AccessibilityHandler* getEnclosingHandlerWithInterface (const AccessibilityHandler* handler, MemberFn fn) + { + if (handler == nullptr) + return nullptr; + + if ((handler->*fn)() != nullptr) + return handler; + + return getEnclosingHandlerWithInterface (handler->getParent(), fn); + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AlertWindowHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AlertWindowHelpers.h new file mode 100644 index 0000000..7d87824 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_AlertWindowHelpers.h @@ -0,0 +1,115 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct AlertWindowHelpers +{ + AlertWindowHelpers() = delete; + + static std::unique_ptr create (const MessageBoxOptions& opts) + { + class AlertWindowImpl : public detail::ScopedMessageBoxInterface + { + public: + explicit AlertWindowImpl (const MessageBoxOptions& opts) : options (opts) {} + + void runAsync (std::function recipient) override + { + if (auto* comp = setUpAlert()) + comp->enterModalState (true, ModalCallbackFunction::create (std::move (recipient)), true); + else + NullCheckedInvocation::invoke (recipient, 0); + } + + int runSync() override + { + #if JUCE_MODAL_LOOPS_PERMITTED + if (auto comp = rawToUniquePtr (setUpAlert())) + return comp->runModalLoop(); + #endif + + jassertfalse; + return 0; + } + + void close() override + { + if (alert != nullptr) + if (alert->isCurrentlyModal()) + alert->exitModalState(); + + alert = nullptr; + } + + private: + Component* setUpAlert() + { + auto* component = options.getAssociatedComponent(); + + auto& lf = component != nullptr ? component->getLookAndFeel() + : LookAndFeel::getDefaultLookAndFeel(); + + alert = lf.createAlertWindow (options.getTitle(), + options.getMessage(), + options.getButtonText (0), + options.getButtonText (1), + options.getButtonText (2), + options.getIconType(), + options.getNumButtons(), + component); + + if (alert == nullptr) + { + // You have to return an alert box! + jassertfalse; + return nullptr; + } + + if (auto* parent = options.getParentComponent()) + { + parent->addAndMakeVisible (alert); + + if (options.getAssociatedComponent() == nullptr) + alert->setCentrePosition (parent->getLocalBounds().getCentre()); + } + + alert->setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows()); + + return alert; + } + + const MessageBoxOptions options; + Component::SafePointer alert; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowImpl) + }; + + return std::make_unique (opts); + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ButtonAccessibilityHandler.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ButtonAccessibilityHandler.h new file mode 100644 index 0000000..dd9aa5e --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ButtonAccessibilityHandler.h @@ -0,0 +1,125 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +//============================================================================== +class ButtonAccessibilityHandler : public AccessibilityHandler +{ +public: + ButtonAccessibilityHandler (Button& buttonToWrap, AccessibilityRole roleIn) + : AccessibilityHandler (buttonToWrap, + isRadioButton (buttonToWrap) ? AccessibilityRole::radioButton : roleIn, + getAccessibilityActions (buttonToWrap), + getAccessibilityInterfaces (buttonToWrap)), + button (buttonToWrap) + {} + + + AccessibleState getCurrentState() const override + { + auto state = AccessibilityHandler::getCurrentState(); + + if (button.isToggleable()) + { + state = state.withCheckable(); + + if (button.getToggleState()) + state = state.withChecked(); + } + + return state; + } + + + String getTitle() const override + { + auto title = AccessibilityHandler::getTitle(); + + if (title.isEmpty()) + return button.getButtonText(); + + return title; + } + + String getHelp() const override + { + return button.getTooltip(); + } + + +private: + class ButtonValueInterface : public AccessibilityTextValueInterface + { + public: + explicit ButtonValueInterface (Button& buttonToWrap) + : button (buttonToWrap) + { + } + + bool isReadOnly() const override { return true; } + String getCurrentValueAsString() const override { return button.getToggleState() ? "On" : "Off"; } + void setValueAsString (const String&) override {} + + private: + Button& button; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonValueInterface) + }; + + static bool isRadioButton (const Button& button) noexcept + { + return button.getRadioGroupId() != 0; + } + + static AccessibilityActions getAccessibilityActions (Button& button) + { + auto actions = AccessibilityActions().addAction (AccessibilityActionType::press, + [&button] { button.triggerClick(); }); + + if (button.isToggleable()) + actions = actions.addAction (AccessibilityActionType::toggle, + [&button] { button.setToggleState (! button.getToggleState(), sendNotification); }); + + return actions; + } + + static Interfaces getAccessibilityInterfaces (Button& button) + { + if (button.isToggleable()) + return { std::make_unique (button) }; + + return {}; + } + + Button& button; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler) +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ComponentHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ComponentHelpers.h new file mode 100644 index 0000000..41ea70a --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ComponentHelpers.h @@ -0,0 +1,255 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +constexpr char colourPropertyPrefix[] = "jcclr_"; + +//============================================================================== +struct ComponentHelpers +{ + using SH = ScalingHelpers; + + #if JUCE_MODAL_LOOPS_PERMITTED + static void* runModalLoopCallback (void* userData) + { + return (void*) (pointer_sized_int) static_cast (userData)->runModalLoop(); + } + #endif + + static Identifier getColourPropertyID (int colourID) + { + char buffer[32]; + auto* end = buffer + numElementsInArray (buffer) - 1; + auto* t = end; + *t = 0; + + for (auto v = (uint32) colourID;;) + { + *--t = "0123456789abcdef" [v & 15]; + v >>= 4; + + if (v == 0) + break; + } + + for (int i = (int) sizeof (colourPropertyPrefix) - 1; --i >= 0;) + *--t = colourPropertyPrefix[i]; + + return t; + } + + //============================================================================== + static bool hitTest (Component& comp, Point localPoint) + { + const auto intPoint = localPoint.roundToInt(); + return Rectangle { comp.getWidth(), comp.getHeight() }.contains (intPoint) + && comp.hitTest (intPoint.x, intPoint.y); + } + + // converts an unscaled position within a peer to the local position within that peer's component + template + static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform().inverted()); + + return SH::unscaledScreenPosToScaled (comp, pos); + } + + // converts a position within a peer's component to the unscaled position within the peer + template + static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept + { + if (comp.isTransformed()) + pos = pos.transformedBy (comp.getTransform()); + + return SH::scaledScreenPosToUnscaled (comp, pos); + } + + template + static PointOrRect convertFromParentSpace (const Component& comp, const PointOrRect pointInParentSpace) + { + const auto transformed = comp.affineTransform != nullptr ? pointInParentSpace.transformedBy (comp.affineTransform->inverted()) + : pointInParentSpace; + + if (comp.isOnDesktop()) + { + if (auto* peer = comp.getPeer()) + return SH::unscaledScreenPosToScaled (comp, peer->globalToLocal (SH::scaledScreenPosToUnscaled (transformed))); + + jassertfalse; + return transformed; + } + + if (comp.getParentComponent() == nullptr) + return SH::subtractPosition (SH::unscaledScreenPosToScaled (comp, SH::scaledScreenPosToUnscaled (transformed)), comp); + + return SH::subtractPosition (transformed, comp); + } + + template + static PointOrRect convertToParentSpace (const Component& comp, const PointOrRect pointInLocalSpace) + { + const auto preTransform = [&] + { + if (comp.isOnDesktop()) + { + if (auto* peer = comp.getPeer()) + return SH::unscaledScreenPosToScaled (peer->localToGlobal (SH::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); + + jassertfalse; + return pointInLocalSpace; + } + + if (comp.getParentComponent() == nullptr) + return SH::unscaledScreenPosToScaled (SH::scaledScreenPosToUnscaled (comp, SH::addPosition (pointInLocalSpace, comp))); + + return SH::addPosition (pointInLocalSpace, comp); + }(); + + return comp.affineTransform != nullptr ? preTransform.transformedBy (*comp.affineTransform) + : preTransform; + } + + template + static PointOrRect convertFromDistantParentSpace (const Component* parent, const Component& target, PointOrRect coordInParent) + { + auto* directParent = target.getParentComponent(); + jassert (directParent != nullptr); + + if (directParent == parent) + return convertFromParentSpace (target, coordInParent); + + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) + return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent)); + JUCE_END_IGNORE_WARNINGS_MSVC + } + + template + static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p) + { + while (source != nullptr) + { + if (source == target) + return p; + + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011) + + if (source->isParentOf (target)) + return convertFromDistantParentSpace (source, *target, p); + + JUCE_END_IGNORE_WARNINGS_MSVC + + p = convertToParentSpace (*source, p); + source = source->getParentComponent(); + } + + jassert (source == nullptr); + if (target == nullptr) + return p; + + auto* topLevelComp = target->getTopLevelComponent(); + + p = convertFromParentSpace (*topLevelComp, p); + + if (topLevelComp == target) + return p; + + return convertFromDistantParentSpace (topLevelComp, *target, p); + } + + static bool clipObscuredRegions (const Component& comp, Graphics& g, + const Rectangle clipRect, Point delta) + { + bool wasClipped = false; + + for (int i = comp.childComponentList.size(); --i >= 0;) + { + auto& child = *comp.childComponentList.getUnchecked(i); + + if (child.isVisible() && ! child.isTransformed()) + { + auto newClip = clipRect.getIntersection (child.boundsRelativeToParent); + + if (! newClip.isEmpty()) + { + if (child.isOpaque() && child.componentTransparency == 0) + { + g.excludeClipRegion (newClip + delta); + wasClipped = true; + } + else + { + auto childPos = child.getPosition(); + + if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta)) + wasClipped = true; + } + } + } + } + + return wasClipped; + } + + static Rectangle getParentOrMainMonitorBounds (const Component& comp) + { + if (auto* p = comp.getParentComponent()) + return p->getLocalBounds(); + + return Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; + } + + static void releaseAllCachedImageResources (Component& c) + { + if (auto* cached = c.getCachedComponentImage()) + cached->releaseResources(); + + for (auto* child : c.childComponentList) + releaseAllCachedImageResources (*child); + } + + //============================================================================== + static bool modalWouldBlockComponent (const Component& maybeBlocked, Component* modal) + { + return modal != nullptr + && modal != &maybeBlocked + && ! modal->isParentOf (&maybeBlocked) + && ! modal->canModalEventBeSentToComponent (&maybeBlocked); + } + + template + static void sendMouseEventToComponentsThatAreBlockedByModal (Component& modal, Function&& function) + { + for (auto& ms : Desktop::getInstance().getMouseSources()) + if (auto* c = ms.getComponentUnderMouse()) + if (modalWouldBlockComponent (*c, &modal)) + (c->*function) (ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime()); + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_CustomMouseCursorInfo.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_CustomMouseCursorInfo.h new file mode 100644 index 0000000..9ec4463 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_CustomMouseCursorInfo.h @@ -0,0 +1,35 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct CustomMouseCursorInfo +{ + ScaledImage image; + Point hotspot; +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusHelpers.h new file mode 100644 index 0000000..d18c9b7 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusHelpers.h @@ -0,0 +1,117 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct FocusHelpers +{ + FocusHelpers() = delete; + + static int getOrder (const Component* c) + { + auto order = c->getExplicitFocusOrder(); + return order > 0 ? order : std::numeric_limits::max(); + } + + template + static void findAllComponents (Component* parent, + std::vector& components, + FocusContainerFn isFocusContainer) + { + if (parent == nullptr || parent->getNumChildComponents() == 0) + return; + + std::vector localComponents; + + for (auto* c : parent->getChildren()) + if (c->isVisible() && c->isEnabled()) + localComponents.push_back (c); + + const auto compareComponents = [&] (const Component* a, const Component* b) + { + const auto getComponentOrderAttributes = [] (const Component* c) + { + return std::make_tuple (getOrder (c), + c->isAlwaysOnTop() ? 0 : 1, + c->getY(), + c->getX()); + }; + + return getComponentOrderAttributes (a) < getComponentOrderAttributes (b); + }; + + // This will sort so that they are ordered in terms of explicit focus, + // always on top, left-to-right, and then top-to-bottom. + std::stable_sort (localComponents.begin(), localComponents.end(), compareComponents); + + for (auto* c : localComponents) + { + components.push_back (c); + + if (! (c->*isFocusContainer)()) + findAllComponents (c, components, isFocusContainer); + } + } + + enum class NavigationDirection { forwards, backwards }; + + template + static Component* navigateFocus (Component* current, + Component* focusContainer, + NavigationDirection direction, + FocusContainerFn isFocusContainer) + { + if (focusContainer != nullptr) + { + std::vector components; + findAllComponents (focusContainer, components, isFocusContainer); + + const auto iter = std::find (components.cbegin(), components.cend(), current); + + if (iter == components.cend()) + return nullptr; + + switch (direction) + { + case NavigationDirection::forwards: + if (iter != std::prev (components.cend())) + return *std::next (iter); + + break; + + case NavigationDirection::backwards: + if (iter != components.cbegin()) + return *std::prev (iter); + + break; + } + } + + return nullptr; + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusRestorer.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusRestorer.h new file mode 100644 index 0000000..d544351 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_FocusRestorer.h @@ -0,0 +1,46 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct FocusRestorer +{ + FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {} + + ~FocusRestorer() + { + if (lastFocus != nullptr + && lastFocus->isShowing() + && ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) + lastFocus->grabKeyboardFocus(); + } + + WeakReference lastFocus; + + JUCE_DECLARE_NON_COPYABLE (FocusRestorer) +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_LookAndFeelHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_LookAndFeelHelpers.h new file mode 100644 index 0000000..35ba8e3 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_LookAndFeelHelpers.h @@ -0,0 +1,62 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct LookAndFeelHelpers +{ + LookAndFeelHelpers() = delete; + + static Colour createBaseColour (Colour buttonColour, + bool hasKeyboardFocus, + bool shouldDrawButtonAsHighlighted, + bool shouldDrawButtonAsDown) noexcept + { + const float sat = hasKeyboardFocus ? 1.3f : 0.9f; + const Colour baseColour (buttonColour.withMultipliedSaturation (sat)); + + if (shouldDrawButtonAsDown) return baseColour.contrasting (0.2f); + if (shouldDrawButtonAsHighlighted) return baseColour.contrasting (0.1f); + + return baseColour; + } + + static TextLayout layoutTooltipText (const String& text, Colour colour) noexcept + { + const float tooltipFontSize = 13.0f; + const int maxToolTipWidth = 400; + + AttributedString s; + s.setJustification (Justification::centred); + s.append (text, Font (tooltipFontSize, Font::bold), colour); + + TextLayout tl; + tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth); + return tl; + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceImpl.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceImpl.h new file mode 100644 index 0000000..6aa9a19 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceImpl.h @@ -0,0 +1,588 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +class MouseInputSourceImpl : private AsyncUpdater +{ +public: + using SH = ScalingHelpers; + + MouseInputSourceImpl (int i, MouseInputSource::InputSourceType type) + : index (i), + inputType (type) + {} + + //============================================================================== + bool isDragging() const noexcept + { + return buttonState.isAnyMouseButtonDown(); + } + + Component* getComponentUnderMouse() const noexcept + { + return componentUnderMouse.get(); + } + + ModifierKeys getCurrentModifiers() const noexcept + { + return ModifierKeys::currentModifiers + .withoutMouseButtons() + .withFlags (buttonState.getRawFlags()); + } + + ComponentPeer* getPeer() noexcept + { + if (! ComponentPeer::isValidPeer (lastPeer)) + lastPeer = nullptr; + + return lastPeer; + } + + static Component* findComponentAt (Point screenPos, ComponentPeer* peer) + { + if (! ComponentPeer::isValidPeer (peer)) + return nullptr; + + auto relativePos = SH::unscaledScreenPosToScaled (peer->getComponent(), + peer->globalToLocal (screenPos)); + auto& comp = peer->getComponent(); + + // (the contains() call is needed to test for overlapping desktop windows) + if (comp.contains (relativePos)) + return comp.getComponentAt (relativePos); + + return nullptr; + } + + Point getScreenPosition() const noexcept + { + // This needs to return the live position if possible, but it mustn't update the lastScreenPos + // value, because that can cause continuity problems. + return SH::unscaledScreenPosToScaled (getRawScreenPosition()); + } + + Point getRawScreenPosition() const noexcept + { + return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition() + : lastPointerState.position); + } + + void setScreenPosition (Point p) + { + MouseInputSource::setRawMousePosition (SH::scaledScreenPosToUnscaled (p)); + } + + //============================================================================== + #if JUCE_DUMP_MOUSE_EVENTS + #define JUCE_MOUSE_EVENT_DBG(desc, screenPos) DBG ("Mouse " << desc << " #" << index \ + << ": " << SH::screenPosToLocalPos (comp, screenPos).toString() \ + << " - Comp: " << String::toHexString ((pointer_sized_int) &comp)); + #else + #define JUCE_MOUSE_EVENT_DBG(desc, screenPos) + #endif + + void sendMouseEnter (Component& comp, const detail::PointerState& pointerState, Time time) + { + JUCE_MOUSE_EVENT_DBG ("enter", pointerState.position) + comp.internalMouseEnter (MouseInputSource (this), + SH::screenPosToLocalPos (comp, pointerState.position), + time); + } + + void sendMouseExit (Component& comp, const detail::PointerState& pointerState, Time time) + { + JUCE_MOUSE_EVENT_DBG ("exit", pointerState.position) + comp.internalMouseExit (MouseInputSource (this), + SH::screenPosToLocalPos (comp, pointerState.position), + time); + } + + void sendMouseMove (Component& comp, const detail::PointerState& pointerState, Time time) + { + JUCE_MOUSE_EVENT_DBG ("move", pointerState.position) + comp.internalMouseMove (MouseInputSource (this), + SH::screenPosToLocalPos (comp, pointerState.position), + time); + } + + void sendMouseDown (Component& comp, const detail::PointerState& pointerState, Time time) + { + JUCE_MOUSE_EVENT_DBG ("down", pointerState.position) + comp.internalMouseDown (MouseInputSource (this), + pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)), + time); + } + + void sendMouseDrag (Component& comp, const detail::PointerState& pointerState, Time time) + { + JUCE_MOUSE_EVENT_DBG ("drag", pointerState.position) + comp.internalMouseDrag (MouseInputSource (this), + pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)), + time); + } + + void sendMouseUp (Component& comp, const detail::PointerState& pointerState, Time time, ModifierKeys oldMods) + { + JUCE_MOUSE_EVENT_DBG ("up", pointerState.position) + comp.internalMouseUp (MouseInputSource (this), + pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)), + time, + oldMods); + } + + void sendMouseWheel (Component& comp, Point screenPos, Time time, const MouseWheelDetails& wheel) + { + JUCE_MOUSE_EVENT_DBG ("wheel", screenPos) + comp.internalMouseWheel (MouseInputSource (this), + SH::screenPosToLocalPos (comp, screenPos), + time, + wheel); + } + + void sendMagnifyGesture (Component& comp, Point screenPos, Time time, float amount) + { + JUCE_MOUSE_EVENT_DBG ("magnify", screenPos) + comp.internalMagnifyGesture (MouseInputSource (this), + SH::screenPosToLocalPos (comp, screenPos), + time, + amount); + } + + #undef JUCE_MOUSE_EVENT_DBG + + //============================================================================== + // (returns true if the button change caused a modal event loop) + bool setButtons (const detail::PointerState& pointerState, Time time, ModifierKeys newButtonState) + { + if (buttonState == newButtonState) + return false; + + // (avoid sending a spurious mouse-drag when we receive a mouse-up) + if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown())) + setPointerState (pointerState, time, false); + + // (ignore secondary clicks when there's already a button down) + if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown()) + { + buttonState = newButtonState; + return false; + } + + auto lastCounter = mouseEventCounter; + + if (buttonState.isAnyMouseButtonDown()) + { + if (auto* current = getComponentUnderMouse()) + { + auto oldMods = getCurrentModifiers(); + buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop + + sendMouseUp (*current, pointerState.withPositionOffset (unboundedMouseOffset), time, oldMods); + + if (lastCounter != mouseEventCounter) + return true; // if a modal loop happened, then newButtonState is no longer valid. + } + + enableUnboundedMouseMovement (false, false); + } + + buttonState = newButtonState; + + if (buttonState.isAnyMouseButtonDown()) + { + Desktop::getInstance().incrementMouseClickCounter(); + + if (auto* current = getComponentUnderMouse()) + { + registerMouseDown (pointerState.position, time, *current, buttonState, + inputType == MouseInputSource::InputSourceType::touch); + sendMouseDown (*current, pointerState, time); + } + } + + return lastCounter != mouseEventCounter; + } + + void setComponentUnderMouse (Component* newComponent, const detail::PointerState& pointerState, Time time) + { + auto* current = getComponentUnderMouse(); + + if (newComponent != current) + { + WeakReference safeNewComp (newComponent); + auto originalButtonState = buttonState; + + if (current != nullptr) + { + WeakReference safeOldComp (current); + setButtons (pointerState, time, ModifierKeys()); + + if (auto oldComp = safeOldComp.get()) + { + componentUnderMouse = safeNewComp; + sendMouseExit (*oldComp, pointerState, time); + } + + buttonState = originalButtonState; + } + + componentUnderMouse = safeNewComp.get(); + current = safeNewComp.get(); + + if (current != nullptr) + sendMouseEnter (*current, pointerState, time); + + revealCursor (false); + setButtons (pointerState, time, originalButtonState); + } + } + + void setPeer (ComponentPeer& newPeer, const detail::PointerState& pointerState, Time time) + { + if (&newPeer != lastPeer && ( findComponentAt (pointerState.position, &newPeer) != nullptr + || findComponentAt (pointerState.position, lastPeer) == nullptr)) + { + setComponentUnderMouse (nullptr, pointerState, time); + lastPeer = &newPeer; + setComponentUnderMouse (findComponentAt (pointerState.position, getPeer()), pointerState, time); + } + } + + void setPointerState (const detail::PointerState& newPointerState, Time time, bool forceUpdate) + { + const auto& newScreenPos = newPointerState.position; + + if (! isDragging()) + setComponentUnderMouse (findComponentAt (newScreenPos, getPeer()), newPointerState, time); + + if ((newPointerState != lastPointerState) || forceUpdate) + { + cancelPendingUpdate(); + + lastPointerState = newPointerState; + + if (auto* current = getComponentUnderMouse()) + { + if (isDragging()) + { + registerMouseDrag (newScreenPos); + sendMouseDrag (*current, newPointerState.withPositionOffset (unboundedMouseOffset), time); + + if (isUnboundedMouseModeOn) + handleUnboundedDrag (*current); + } + else + { + sendMouseMove (*current, newPointerState, time); + } + } + + revealCursor (false); + } + } + + //============================================================================== + void handleEvent (ComponentPeer& newPeer, Point positionWithinPeer, Time time, + const ModifierKeys newMods, float newPressure, float newOrientation, PenDetails pen) + { + lastTime = time; + ++mouseEventCounter; + const auto pointerState = detail::PointerState().withPosition (newPeer.localToGlobal (positionWithinPeer)) + .withPressure (newPressure) + .withOrientation (newOrientation) + .withRotation (MouseInputSource::defaultRotation) + .withTiltX (pen.tiltX) + .withTiltY (pen.tiltY); + + if (isDragging() && newMods.isAnyMouseButtonDown()) + { + setPointerState (pointerState, time, false); + } + else + { + setPeer (newPeer, pointerState, time); + + if (auto* peer = getPeer()) + { + if (setButtons (pointerState, time, newMods)) + return; // some modal events have been dispatched, so the current event is now out-of-date + + peer = getPeer(); + + if (peer != nullptr) + setPointerState (pointerState, time, false); + } + } + } + + Component* getTargetForGesture (ComponentPeer& peer, Point positionWithinPeer, + Time time, Point& screenPos) + { + lastTime = time; + ++mouseEventCounter; + + screenPos = peer.localToGlobal (positionWithinPeer); + const auto pointerState = lastPointerState.withPosition (screenPos); + setPeer (peer, pointerState, time); + setPointerState (pointerState, time, false); + triggerFakeMove(); + + return getComponentUnderMouse(); + } + + void handleWheel (ComponentPeer& peer, Point positionWithinPeer, + Time time, const MouseWheelDetails& wheel) + { + Desktop::getInstance().incrementMouseWheelCounter(); + Point screenPos; + + // This will make sure that when the wheel spins in its inertial phase, any events + // continue to be sent to the last component that the mouse was over when it was being + // actively controlled by the user. This avoids confusion when scrolling through nested + // scrollable components. + if (lastNonInertialWheelTarget == nullptr || ! wheel.isInertial) + lastNonInertialWheelTarget = getTargetForGesture (peer, positionWithinPeer, time, screenPos); + else + screenPos = peer.localToGlobal (positionWithinPeer); + + if (auto target = lastNonInertialWheelTarget.get()) + sendMouseWheel (*target, screenPos, time, wheel); + } + + void handleMagnifyGesture (ComponentPeer& peer, Point positionWithinPeer, + Time time, const float scaleFactor) + { + Point screenPos; + + if (auto* current = getTargetForGesture (peer, positionWithinPeer, time, screenPos)) + sendMagnifyGesture (*current, screenPos, time, scaleFactor); + } + + //============================================================================== + Time getLastMouseDownTime() const noexcept + { + return mouseDowns[0].time; + } + + Point getLastMouseDownPosition() const noexcept + { + return SH::unscaledScreenPosToScaled (mouseDowns[0].position); + } + + int getNumberOfMultipleClicks() const noexcept + { + int numClicks = 1; + + if (! isLongPressOrDrag()) + { + for (int i = 1; i < numElementsInArray (mouseDowns); ++i) + { + if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2))) + ++numClicks; + else + break; + } + } + + return numClicks; + } + + bool isLongPressOrDrag() const noexcept + { + return movedSignificantly || + lastTime > (mouseDowns[0].time + RelativeTime::milliseconds (300)); + } + + bool hasMovedSignificantlySincePressed() const noexcept + { + return movedSignificantly; + } + + // Deprecated method + bool hasMouseMovedSignificantlySincePressed() const noexcept + { + return isLongPressOrDrag(); + } + + //============================================================================== + void triggerFakeMove() + { + triggerAsyncUpdate(); + } + + void handleAsyncUpdate() override + { + setPointerState (lastPointerState, + jmax (lastTime, Time::getCurrentTime()), true); + } + + //============================================================================== + void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen) + { + enable = enable && isDragging(); + isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen; + + if (enable != isUnboundedMouseModeOn) + { + if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin())) + { + // when released, return the mouse to within the component's bounds + if (auto* current = getComponentUnderMouse()) + setScreenPosition (current->getScreenBounds().toFloat() + .getConstrainedPoint (SH::unscaledScreenPosToScaled (lastPointerState.position))); + } + + isUnboundedMouseModeOn = enable; + unboundedMouseOffset = {}; + + revealCursor (true); + } + } + + void handleUnboundedDrag (Component& current) + { + auto componentScreenBounds = SH::scaledScreenPosToUnscaled (current.getParentMonitorArea() + .reduced (2, 2) + .toFloat()); + + if (! componentScreenBounds.contains (lastPointerState.position)) + { + auto componentCentre = current.getScreenBounds().toFloat().getCentre(); + unboundedMouseOffset += (lastPointerState.position - SH::scaledScreenPosToUnscaled (componentCentre)); + setScreenPosition (componentCentre); + } + else if (isCursorVisibleUntilOffscreen + && (! unboundedMouseOffset.isOrigin()) + && componentScreenBounds.contains (lastPointerState.position + unboundedMouseOffset)) + { + MouseInputSource::setRawMousePosition (lastPointerState.position + unboundedMouseOffset); + unboundedMouseOffset = {}; + } + } + + //============================================================================== + void showMouseCursor (MouseCursor cursor, bool forcedUpdate) + { + if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen)) + { + cursor = MouseCursor::NoCursor; + forcedUpdate = true; + } + + if (forcedUpdate || cursor.getHandle() != currentCursorHandle) + { + currentCursorHandle = cursor.getHandle(); + cursor.showInWindow (getPeer()); + } + } + + void hideCursor() + { + showMouseCursor (MouseCursor::NoCursor, true); + } + + void revealCursor (bool forcedUpdate) + { + MouseCursor mc (MouseCursor::NormalCursor); + + if (auto* current = getComponentUnderMouse()) + mc = current->getLookAndFeel().getMouseCursorFor (*current); + + showMouseCursor (mc, forcedUpdate); + } + + //============================================================================== + const int index; + const MouseInputSource::InputSourceType inputType; + Point unboundedMouseOffset; // NB: these are unscaled coords + detail::PointerState lastPointerState; + ModifierKeys buttonState; + + bool isUnboundedMouseModeOn = false, isCursorVisibleUntilOffscreen = false; + +private: + WeakReference componentUnderMouse, lastNonInertialWheelTarget; + ComponentPeer* lastPeer = nullptr; + + void* currentCursorHandle = nullptr; + int mouseEventCounter = 0; + + struct RecentMouseDown + { + RecentMouseDown() = default; + + Point position; + Time time; + ModifierKeys buttons; + uint32 peerID = 0; + bool isTouch = false; + + bool canBePartOfMultipleClickWith (const RecentMouseDown& other, int maxTimeBetweenMs) const noexcept + { + return time - other.time < RelativeTime::milliseconds (maxTimeBetweenMs) + && std::abs (position.x - other.position.x) < (float) getPositionToleranceForInputType() + && std::abs (position.y - other.position.y) < (float) getPositionToleranceForInputType() + && buttons == other.buttons + && peerID == other.peerID; + } + + int getPositionToleranceForInputType() const noexcept { return isTouch ? 25 : 8; } + }; + + RecentMouseDown mouseDowns[4]; + Time lastTime; + bool movedSignificantly = false; + + void registerMouseDown (Point screenPos, Time time, Component& component, + const ModifierKeys modifiers, bool isTouchSource) noexcept + { + for (int i = numElementsInArray (mouseDowns); --i > 0;) + mouseDowns[i] = mouseDowns[i - 1]; + + mouseDowns[0].position = screenPos; + mouseDowns[0].time = time; + mouseDowns[0].buttons = modifiers.withOnlyMouseButtons(); + mouseDowns[0].isTouch = isTouchSource; + + if (auto* peer = component.getPeer()) + mouseDowns[0].peerID = peer->getUniqueID(); + else + mouseDowns[0].peerID = 0; + + movedSignificantly = false; + lastNonInertialWheelTarget = nullptr; + } + + void registerMouseDrag (Point screenPos) noexcept + { + movedSignificantly = movedSignificantly || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInputSourceImpl) +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceList.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceList.h new file mode 100644 index 0000000..4bff875 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_MouseInputSourceList.h @@ -0,0 +1,154 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +class MouseInputSourceList : public Timer +{ +public: + MouseInputSourceList() + { + #if JUCE_ANDROID || JUCE_IOS + auto mainMouseInputType = MouseInputSource::InputSourceType::touch; + #else + auto mainMouseInputType = MouseInputSource::InputSourceType::mouse; + #endif + + addSource (0, mainMouseInputType); + } + + MouseInputSource* addSource (int index, MouseInputSource::InputSourceType type) + { + auto* s = new MouseInputSourceImpl (index, type); + sources.add (s); + sourceArray.add (MouseInputSource (s)); + + return &sourceArray.getReference (sourceArray.size() - 1); + } + + MouseInputSource* getMouseSource (int index) noexcept + { + return isPositiveAndBelow (index, sourceArray.size()) ? &sourceArray.getReference (index) + : nullptr; + } + + MouseInputSource* getOrCreateMouseInputSource (MouseInputSource::InputSourceType type, int touchIndex = 0) + { + if (type == MouseInputSource::InputSourceType::mouse + || type == MouseInputSource::InputSourceType::pen) + { + for (auto& m : sourceArray) + if (type == m.getType()) + return &m; + + addSource (0, type); + } + else if (type == MouseInputSource::InputSourceType::touch) + { + jassert (0 <= touchIndex && touchIndex < 100); // sanity-check on number of fingers + + for (auto& m : sourceArray) + if (type == m.getType() && touchIndex == m.getIndex()) + return &m; + + if (canUseTouch()) + return addSource (touchIndex, type); + } + + return nullptr; + } + + int getNumDraggingMouseSources() const noexcept + { + int num = 0; + + for (auto* s : sources) + if (s->isDragging()) + ++num; + + return num; + } + + MouseInputSource* getDraggingMouseSource (int index) noexcept + { + int num = 0; + + for (auto& s : sourceArray) + { + if (s.isDragging()) + { + if (index == num) + return &s; + + ++num; + } + } + + return nullptr; + } + + void beginDragAutoRepeat (int interval) + { + if (interval > 0) + { + if (getTimerInterval() != interval) + startTimer (interval); + } + else + { + stopTimer(); + } + } + + void timerCallback() override + { + bool anyDragging = false; + + for (auto* s : sources) + { + // NB: when doing auto-repeat, we need to force an update of the current position and button state, + // because on some OSes the queue can get overloaded with messages so that mouse-events don't get through.. + if (s->isDragging() && ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown()) + { + s->lastPointerState.position = s->getRawScreenPosition(); + s->triggerFakeMove(); + anyDragging = true; + } + } + + if (! anyDragging) + stopTimer(); + } + + OwnedArray sources; + Array sourceArray; + +private: + bool addSource(); + bool canUseTouch() const; +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_PointerState.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_PointerState.h similarity index 98% rename from JuceLibraryCode/modules/juce_gui_basics/mouse/juce_PointerState.h rename to JuceLibraryCode/modules/juce_gui_basics/detail/juce_PointerState.h index 9c5e1e6..868f25a 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/mouse/juce_PointerState.h +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_PointerState.h @@ -23,11 +23,9 @@ ============================================================================== */ -namespace juce +namespace juce::detail { -#ifndef DOXYGEN - class PointerState { auto tie() const noexcept @@ -103,7 +101,4 @@ inline auto makeMouseEvent (MouseInputSource source, mouseWasDragged); } - -#endif - -} // namespace juce +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScalingHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScalingHelpers.h new file mode 100644 index 0000000..8665c3b --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScalingHelpers.h @@ -0,0 +1,123 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct ScalingHelpers +{ + template + static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? pos / scale : pos; + } + + template + static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? pos * scale : pos; + } + + // For these, we need to avoid getSmallestIntegerContainer being used, which causes + // judder when moving windows + static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? Rectangle (roundToInt ((float) pos.getX() / scale), + roundToInt ((float) pos.getY() / scale), + roundToInt ((float) pos.getWidth() / scale), + roundToInt ((float) pos.getHeight() / scale)) : pos; + } + + static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? Rectangle (roundToInt ((float) pos.getX() * scale), + roundToInt ((float) pos.getY() * scale), + roundToInt ((float) pos.getWidth() * scale), + roundToInt ((float) pos.getHeight() * scale)) : pos; + } + + static Rectangle unscaledScreenPosToScaled (float scale, Rectangle pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? Rectangle (pos.getX() / scale, + pos.getY() / scale, + pos.getWidth() / scale, + pos.getHeight() / scale) : pos; + } + + static Rectangle scaledScreenPosToUnscaled (float scale, Rectangle pos) noexcept + { + return ! approximatelyEqual (scale, 1.0f) ? Rectangle (pos.getX() * scale, + pos.getY() * scale, + pos.getWidth() * scale, + pos.getHeight() * scale) : pos; + } + + template + static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos); + } + + template + static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept + { + return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos); + } + + template + static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept + { + return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos); + } + + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition(); } + static Point addPosition (Point p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Rectangle addPosition (Rectangle p, const Component& c) noexcept { return p + c.getPosition().toFloat(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition(); } + static Point subtractPosition (Point p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } + static Rectangle subtractPosition (Rectangle p, const Component& c) noexcept { return p - c.getPosition().toFloat(); } + + static Point screenPosToLocalPos (Component& comp, Point pos) + { + if (auto* peer = comp.getPeer()) + { + pos = peer->globalToLocal (pos); + auto& peerComp = peer->getComponent(); + return comp.getLocalPoint (&peerComp, unscaledScreenPosToScaled (peerComp, pos)); + } + + return comp.getLocalPoint (nullptr, unscaledScreenPosToScaled (comp, pos)); + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerImpl.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerImpl.h new file mode 100644 index 0000000..5315bf9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerImpl.h @@ -0,0 +1,98 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +class ConcreteScopedContentSharerImpl : public ScopedMessageBoxImpl, + private AsyncUpdater +{ +public: + static ScopedMessageBox show (std::unique_ptr&& native, + ContentSharer::Callback callback) + { + return ScopedMessageBox (runAsync (std::move (native), std::move (callback))); + } + + ~ConcreteScopedContentSharerImpl() override + { + cancelPendingUpdate(); + } + + void close() override + { + cancelPendingUpdate(); + nativeImplementation->close(); + self.reset(); + } + +private: + static std::shared_ptr runAsync (std::unique_ptr&& p, + ContentSharer::Callback&& c) + { + std::shared_ptr result (new ConcreteScopedContentSharerImpl (std::move (p), std::move (c))); + result->self = result; + result->triggerAsyncUpdate(); + return result; + } + + ConcreteScopedContentSharerImpl (std::unique_ptr&& p, + ContentSharer::Callback&& c) + : callback (std::move (c)), nativeImplementation (std::move (p)) {} + + void handleAsyncUpdate() override + { + nativeImplementation->runAsync ([weakRecipient = std::weak_ptr (self)] (bool result, const String& error) + { + const auto notifyRecipient = [result, error, weakRecipient] + { + if (const auto locked = weakRecipient.lock()) + { + NullCheckedInvocation::invoke (locked->callback, result, error); + locked->self.reset(); + } + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + notifyRecipient(); + else + MessageManager::callAsync (notifyRecipient); + }); + } + + ContentSharer::Callback callback; + std::unique_ptr nativeImplementation; + + /* The 'old' native message box API doesn't have a concept of content sharer owners. + Instead, content sharers have to clean up after themselves, once they're done displaying. + To allow this mode of usage, the implementation keeps an owning reference to itself, + which is cleared once the content sharer is closed or asked to quit. To display a content + sharer box without a scoped lifetime, just create a Pimpl instance without using + the ScopedContentSharer wrapper, and the Pimpl will destroy itself after it is dismissed. + */ + std::shared_ptr self; +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h new file mode 100644 index 0000000..f77ad89 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedContentSharerInterface.h @@ -0,0 +1,211 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +/* + Instances of this type can show and dismiss a content sharer. + + This is an interface rather than a concrete type so that platforms can pick an implementation at + runtime if necessary. +*/ +struct ScopedContentSharerInterface +{ + virtual ~ScopedContentSharerInterface() = default; + + /* Shows the content sharer. + + When the content sharer exits normally, it should send the result to the passed-in function. + The passed-in function is safe to call from any thread at any time. + */ + virtual void runAsync (ContentSharer::Callback callback) + { + jassertfalse; + NullCheckedInvocation::invoke (callback, false, "Content sharing not available on this platform!"); + } + + /* Forcefully closes the content sharer. + + This will be called when the content sharer handle has fallen out of scope. + If the content sharer has already been closed by the user, this shouldn't do anything. + */ + virtual void close() {} + + /* Implemented differently for each platform. */ + static std::unique_ptr shareFiles (const Array&, Component*); + static std::unique_ptr shareText (const String&, Component*); + + /* Implemented below. */ + static std::unique_ptr shareImages (const Array&, std::unique_ptr, Component*); + static std::unique_ptr shareData (MemoryBlock, Component*); +}; + +class TemporaryFilesDecorator : public ScopedContentSharerInterface, + private AsyncUpdater +{ +public: + explicit TemporaryFilesDecorator (Component* parentIn) + : parent (parentIn) {} + + void runAsync (ContentSharer::Callback cb) override + { + callback = std::move (cb); + + task = std::async (std::launch::async, [this] + { + std::tie (temporaryFiles, error) = prepareTemporaryFiles(); + triggerAsyncUpdate(); + }); + } + + void close() override + { + if (inner != nullptr) + inner->close(); + } + +private: + virtual std::tuple, String> prepareTemporaryFiles() const = 0; + + void handleAsyncUpdate() override + { + if (error.isNotEmpty()) + { + NullCheckedInvocation::invoke (callback, false, error); + return; + } + + inner = shareFiles (temporaryFiles, parent); + + if (inner == nullptr) + { + NullCheckedInvocation::invoke (callback, false, TRANS ("Failed to create file sharer")); + return; + } + + inner->runAsync (callback); + } + + Array temporaryFiles; + String error; + std::unique_ptr inner; + ContentSharer::Callback callback; + std::future task; + Component* parent = nullptr; +}; + +std::unique_ptr ScopedContentSharerInterface::shareImages (const Array& images, + std::unique_ptr format, + Component* parent) +{ + class Decorator : public TemporaryFilesDecorator + { + public: + Decorator (Array imagesIn, std::unique_ptr formatIn, Component* parentIn) + : TemporaryFilesDecorator (parentIn), images (std::move (imagesIn)), format (std::move (formatIn)) {} + + private: + std::tuple, String> prepareTemporaryFiles() const override + { + const auto extension = format->getFormatName().toLowerCase(); + + Array result; + + for (const auto& image : images) + { + File tempFile = File::createTempFile (extension); + + if (! tempFile.create().wasOk()) + return { Array{}, TRANS ("Failed to create temporary file") }; + + std::unique_ptr outputStream (tempFile.createOutputStream()); + + if (outputStream == nullptr) + return { Array{}, TRANS ("Failed to open temporary file for writing") }; + + if (format->writeImageToStream (image, *outputStream)) + result.add (URL (tempFile)); + } + + for (const auto& url : result) + jassertquiet (url.isLocalFile() && url.getLocalFile().existsAsFile()); + + return { std::move (result), String{} }; + } + + Array images; + std::unique_ptr format; + }; + + return std::make_unique (images, + format == nullptr ? std::make_unique() : std::move (format), + parent); +} + +std::unique_ptr ScopedContentSharerInterface::shareData (MemoryBlock mb, Component* parent) +{ + class Decorator : public TemporaryFilesDecorator + { + public: + Decorator (MemoryBlock mbIn, Component* parentIn) + : TemporaryFilesDecorator (parentIn), mb (std::move (mbIn)) {} + + private: + std::tuple, String> prepareTemporaryFiles() const override + { + File tempFile = File::createTempFile ("data"); + + if (! tempFile.create().wasOk()) + return { Array{}, TRANS ("Failed to create temporary file") }; + + std::unique_ptr outputStream (tempFile.createOutputStream()); + + if (outputStream == nullptr) + return { Array{}, TRANS ("Failed to open temporary file for writing") }; + + size_t pos = 0; + size_t totalSize = mb.getSize(); + + while (pos < totalSize) + { + size_t numToWrite = std::min ((size_t) 8192, totalSize - pos); + + if (! outputStream->write (mb.begin() + pos, numToWrite)) + return { Array{}, TRANS ("Failed to write to temporary file") }; + + pos += numToWrite; + } + + return { Array { URL (tempFile) }, String{} }; + } + + MemoryBlock mb; + }; + + return std::make_unique (std::move (mb), parent); +} + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h new file mode 100644 index 0000000..d638060 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxImpl.h @@ -0,0 +1,132 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +//============================================================================== +class ScopedMessageBoxImpl +{ +public: + virtual ~ScopedMessageBoxImpl() = default; + virtual void close() = 0; +}; + +//============================================================================== +class ConcreteScopedMessageBoxImpl : public ScopedMessageBoxImpl, + private AsyncUpdater +{ +public: + static ScopedMessageBox show (std::unique_ptr&& native, + std::function callback) + { + return ScopedMessageBox (runAsync (std::move (native), + rawToUniquePtr (ModalCallbackFunction::create (std::move (callback))))); + } + + static int showUnmanaged (std::unique_ptr&& native, + ModalComponentManager::Callback* cb) + { + #if JUCE_MODAL_LOOPS_PERMITTED + if (cb == nullptr) + return runSync (std::move (native)); + #endif + + runAsync (std::move (native), rawToUniquePtr (cb)); + + return 0; + } + + ~ConcreteScopedMessageBoxImpl() override + { + cancelPendingUpdate(); + } + + void close() override + { + cancelPendingUpdate(); + nativeImplementation->close(); + self.reset(); + } + +private: + static std::shared_ptr runAsync (std::unique_ptr&& p, + std::unique_ptr&& c) + { + std::shared_ptr result (new ConcreteScopedMessageBoxImpl (std::move (p), std::move (c))); + result->self = result; + result->triggerAsyncUpdate(); + return result; + } + + static int runSync (std::unique_ptr&& p) + { + auto local = std::move (p); + return local != nullptr ? local->runSync() : 0; + } + + explicit ConcreteScopedMessageBoxImpl (std::unique_ptr&& p) + : ConcreteScopedMessageBoxImpl (std::move (p), nullptr) {} + + ConcreteScopedMessageBoxImpl (std::unique_ptr&& p, + std::unique_ptr&& c) + : callback (std::move (c)), nativeImplementation (std::move (p)) {} + + void handleAsyncUpdate() override + { + nativeImplementation->runAsync ([weakRecipient = std::weak_ptr (self)] (int result) + { + const auto notifyRecipient = [result, weakRecipient] + { + if (const auto locked = weakRecipient.lock()) + { + if (auto* cb = locked->callback.get()) + cb->modalStateFinished (result); + + locked->self.reset(); + } + }; + + if (MessageManager::getInstance()->isThisTheMessageThread()) + notifyRecipient(); + else + MessageManager::callAsync (notifyRecipient); + }); + } + + std::unique_ptr callback; + std::unique_ptr nativeImplementation; + + /* The 'old' native message box API doesn't have a concept of message box owners. + Instead, message boxes have to clean up after themselves, once they're done displaying. + To allow this mode of usage, the implementation keeps an owning reference to itself, + which is cleared once the message box is closed or asked to quit. To display a native + message box without a scoped lifetime, just create a Pimpl instance without using + the ScopedMessageBox wrapper, and the Pimpl will destroy itself after it is dismissed. + */ + std::shared_ptr self; +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h new file mode 100644 index 0000000..a722f05 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ScopedMessageBoxInterface.h @@ -0,0 +1,60 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +/* + Instances of this type can show and dismiss a message box. + + This is an interface rather than a concrete type so that platforms can pick an implementation at + runtime if necessary. +*/ +struct ScopedMessageBoxInterface +{ + virtual ~ScopedMessageBoxInterface() = default; + + /* Shows the message box. + + When the message box exits normally, it should send the result to the passed-in function. + The passed-in function is safe to call from any thread at any time. + */ + virtual void runAsync (std::function) = 0; + + /* Shows the message box and blocks. */ + virtual int runSync() = 0; + + /* Forcefully closes the message box. + + This will be called when the message box handle has fallen out of scope. + If the message box has already been closed by the user, this shouldn't do anything. + */ + virtual void close() = 0; + + /* Implemented differently for each platform. */ + static std::unique_ptr create (const MessageBoxOptions& options); +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h new file mode 100644 index 0000000..c5359e0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ToolbarItemDragAndDropOverlayComponent.h @@ -0,0 +1,118 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +class ToolbarItemDragAndDropOverlayComponent : public Component +{ +public: + ToolbarItemDragAndDropOverlayComponent() + : isDragging (false) + { + setAlwaysOnTop (true); + setRepaintsOnMouseActivity (true); + setMouseCursor (MouseCursor::DraggingHandCursor); + } + + void paint (Graphics& g) override + { + if (ToolbarItemComponent* const tc = getToolbarItemComponent()) + { + if (isMouseOverOrDragging() + && tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) + { + g.setColour (findColour (Toolbar::editingModeOutlineColourId, true)); + g.drawRect (getLocalBounds(), jmin (2, (getWidth() - 1) / 2, + (getHeight() - 1) / 2)); + } + } + } + + void mouseDown (const MouseEvent& e) override + { + isDragging = false; + + if (ToolbarItemComponent* const tc = getToolbarItemComponent()) + { + tc->dragOffsetX = e.x; + tc->dragOffsetY = e.y; + } + } + + void mouseDrag (const MouseEvent& e) override + { + if (e.mouseWasDraggedSinceMouseDown() && ! isDragging) + { + isDragging = true; + + if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this)) + { + dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), ScaledImage(), true, nullptr, &e.source); + + if (ToolbarItemComponent* const tc = getToolbarItemComponent()) + { + tc->isBeingDragged = true; + + if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) + tc->setVisible (false); + } + } + } + } + + void mouseUp (const MouseEvent&) override + { + isDragging = false; + + if (ToolbarItemComponent* const tc = getToolbarItemComponent()) + { + tc->isBeingDragged = false; + + if (Toolbar* const tb = tc->getToolbar()) + tb->updateAllItemPositions (true); + else if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar) + delete tc; + } + } + + void parentSizeChanged() override + { + setBounds (0, 0, getParentWidth(), getParentHeight()); + } + +private: + //============================================================================== + bool isDragging; + + ToolbarItemComponent* getToolbarItemComponent() const noexcept + { + return dynamic_cast (getParentComponent()); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemDragAndDropOverlayComponent) +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h new file mode 100644 index 0000000..8aef293 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_TopLevelWindowManager.h @@ -0,0 +1,136 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +/** Keeps track of the active top level window. */ +class TopLevelWindowManager : private Timer, + private DeletedAtShutdown +{ +public: + TopLevelWindowManager() = default; + + ~TopLevelWindowManager() override + { + clearSingletonInstance(); + } + + JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager) + + static void checkCurrentlyFocusedTopLevelWindow() + { + if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating()) + wm->checkFocusAsync(); + } + + void checkFocusAsync() + { + startTimer (10); + } + + void checkFocus() + { + startTimer (jmin (1731, getTimerInterval() * 2)); + + auto* newActive = findCurrentlyActiveWindow(); + + if (newActive != currentActive) + { + currentActive = newActive; + + for (int i = windows.size(); --i >= 0;) + if (auto* tlw = windows[i]) + tlw->setWindowActive (isWindowActive (tlw)); + + Desktop::getInstance().triggerFocusCallback(); + } + } + + bool addWindow (TopLevelWindow* const w) + { + windows.add (w); + checkFocusAsync(); + + return isWindowActive (w); + } + + void removeWindow (TopLevelWindow* const w) + { + checkFocusAsync(); + + if (currentActive == w) + currentActive = nullptr; + + windows.removeFirstMatchingValue (w); + + if (windows.isEmpty()) + deleteInstance(); + } + + Array windows; + +private: + TopLevelWindow* currentActive = nullptr; + + void timerCallback() override + { + checkFocus(); + } + + bool isWindowActive (TopLevelWindow* const tlw) const + { + return (tlw == currentActive + || tlw->isParentOf (currentActive) + || tlw->hasKeyboardFocus (true)) + && tlw->isShowing(); + } + + TopLevelWindow* findCurrentlyActiveWindow() const + { + if (Process::isForegroundProcess()) + { + auto* focusedComp = Component::getCurrentlyFocusedComponent(); + auto* w = dynamic_cast (focusedComp); + + if (w == nullptr && focusedComp != nullptr) + w = focusedComp->findParentComponentOfClass(); + + if (w == nullptr) + w = currentActive; + + if (w != nullptr && w->isShowing()) + return w; + } + + return nullptr; + } + + JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager) +}; + +JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager) + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ViewportHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ViewportHelpers.h new file mode 100644 index 0000000..3db57f1 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_ViewportHelpers.h @@ -0,0 +1,49 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct ViewportHelpers +{ + ViewportHelpers() = delete; + + static bool wouldScrollOnEvent (const Viewport* vp, const MouseInputSource& src) + { + if (vp != nullptr) + { + switch (vp->getScrollOnDragMode()) + { + case Viewport::ScrollOnDragMode::all: return true; + case Viewport::ScrollOnDragMode::nonHover: return ! src.canHover(); + case Viewport::ScrollOnDragMode::never: return false; + } + } + + return false; + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/detail/juce_WindowingHelpers.h b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_WindowingHelpers.h new file mode 100644 index 0000000..a26af05 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/detail/juce_WindowingHelpers.h @@ -0,0 +1,52 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +struct WindowingHelpers +{ + WindowingHelpers() = delete; + + static Image createIconForFile (const File& file); + + #if JUCE_WINDOWS + static bool isEmbeddedInForegroundProcess (Component* c); + static bool isWindowOnCurrentVirtualDesktop (void*); + #else + static bool isEmbeddedInForegroundProcess (Component*) { return false; } + static bool isWindowOnCurrentVirtualDesktop (void*) { return true; } + #endif + + /* Returns true if this process is in the foreground, or if the viewComponent + is embedded into a window owned by the foreground process. + */ + static bool isForegroundOrEmbeddedProcess (Component* viewComponent) + { + return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent); + } +}; + +} // namespace juce::detail diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h index 8e067e7..9da0849 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableImage.h @@ -97,10 +97,11 @@ class JUCE_API DrawableImage : public Drawable Rectangle getDrawableBounds() const override; /** @internal */ Path getOutlineAsPath() const override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== - std::unique_ptr createAccessibilityHandler() override; bool setImageInternal (const Image&); //============================================================================== diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp index def3f54..0540aef 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.cpp @@ -108,7 +108,7 @@ void DrawableText::setBoundingBox (Parallelogram newBounds) void DrawableText::setFontHeight (float newHeight) { - if (fontHeight != newHeight) + if (! approximatelyEqual (fontHeight, newHeight)) { fontHeight = newHeight; refreshBounds(); @@ -117,7 +117,7 @@ void DrawableText::setFontHeight (float newHeight) void DrawableText::setFontHorizontalScale (float newScale) { - if (fontHScale != newScale) + if (! approximatelyEqual (fontHScale, newScale)) { fontHScale = newScale; refreshBounds(); diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h index bf793a1..3ddb2dc 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableText.h @@ -98,6 +98,8 @@ class JUCE_API DrawableText : public Drawable Path getOutlineAsPath() const override; /** @internal */ bool replaceColour (Colour originalColour, Colour replacementColour) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -108,7 +110,6 @@ class JUCE_API DrawableText : public Drawable Colour colour; Justification justification; - std::unique_ptr createAccessibilityHandler() override; void refreshBounds(); Rectangle getTextArea (float width, float height) const; AffineTransform getTextTransform (float width, float height) const; diff --git a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 4f1228d..fe749a5 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -189,8 +189,8 @@ class SVGState } else { - if (viewBoxW == 0.0f) newState.viewBoxW = newState.width; - if (viewBoxH == 0.0f) newState.viewBoxH = newState.height; + if (approximatelyEqual (viewBoxW, 0.0f)) newState.viewBoxW = newState.width; + if (approximatelyEqual (viewBoxH, 0.0f)) newState.viewBoxH = newState.height; } newState.parseSubElements (xml, *drawable); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp index 354eaa9..c54c7b3 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.cpp @@ -26,247 +26,49 @@ namespace juce { -#if JUCE_CONTENT_SHARING -//============================================================================== -class ContentSharer::PrepareImagesThread : private Thread +ScopedMessageBox ContentSharer::shareFilesScoped (const Array& files, + Callback callback, + Component* parent) { -public: - PrepareImagesThread (ContentSharer& cs, const Array& imagesToUse, - ImageFileFormat* imageFileFormatToUse) - : Thread ("ContentSharer::PrepareImagesThread"), - owner (cs), - images (imagesToUse), - imageFileFormat (imageFileFormatToUse == nullptr ? new PNGImageFormat() - : imageFileFormatToUse), - extension (imageFileFormat->getFormatName().toLowerCase()) - { - startThread(); - } - - ~PrepareImagesThread() override - { - signalThreadShouldExit(); - waitForThreadToExit (10000); - } - -private: - void run() override - { - for (const auto& image : images) - { - if (threadShouldExit()) - return; - - File tempFile = File::createTempFile (extension); - - if (! tempFile.create().wasOk()) - break; - - std::unique_ptr outputStream (tempFile.createOutputStream()); - - if (outputStream == nullptr) - break; - - if (imageFileFormat->writeImageToStream (image, *outputStream)) - owner.temporaryFiles.add (tempFile); - } - - finish(); - } - - void finish() - { - MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); }); - } - - ContentSharer& owner; - const Array images; - std::unique_ptr imageFileFormat; - String extension; -}; - -//============================================================================== -class ContentSharer::PrepareDataThread : private Thread -{ -public: - PrepareDataThread (ContentSharer& cs, const MemoryBlock& mb) - : Thread ("ContentSharer::PrepareDataThread"), - owner (cs), - data (mb) - { - startThread(); - } - - ~PrepareDataThread() override - { - signalThreadShouldExit(); - waitForThreadToExit (10000); - } - -private: - void run() override - { - File tempFile = File::createTempFile ("data"); - - if (tempFile.create().wasOk()) - { - if (auto outputStream = std::unique_ptr (tempFile.createOutputStream())) - { - size_t pos = 0; - size_t totalSize = data.getSize(); - - while (pos < totalSize) - { - if (threadShouldExit()) - return; - - size_t numToWrite = std::min ((size_t) 8192, totalSize - pos); - - outputStream->write (data.begin() + pos, numToWrite); - - pos += numToWrite; - } - - owner.temporaryFiles.add (tempFile); - } - } - - finish(); - } - - void finish() - { - MessageManager::callAsync ([this]() { owner.filesToSharePrepared(); }); - } - - ContentSharer& owner; - const MemoryBlock data; -}; -#endif - -//============================================================================== -JUCE_IMPLEMENT_SINGLETON (ContentSharer) - -ContentSharer::ContentSharer() {} -ContentSharer::~ContentSharer() { clearSingletonInstance(); } - -void ContentSharer::shareFiles ([[maybe_unused]] const Array& files, - std::function callbackToUse) -{ - #if JUCE_CONTENT_SHARING - startNewShare (callbackToUse); - pimpl->shareFiles (files); - #else - // Content sharing is not available on this platform! - jassertfalse; - - if (callbackToUse) - callbackToUse (false, "Content sharing is not available on this platform!"); - #endif -} - -#if JUCE_CONTENT_SHARING -void ContentSharer::startNewShare (std::function callbackToUse) -{ - // You should not start another sharing operation before the previous one is finished. - // Forcibly stopping a previous sharing operation is rarely a good idea! - jassert (pimpl == nullptr); - pimpl.reset(); - - prepareDataThread = nullptr; - prepareImagesThread = nullptr; - - deleteTemporaryFiles(); - - // You need to pass a valid callback. - jassert (callbackToUse); - callback = std::move (callbackToUse); - - pimpl.reset (createPimpl()); -} -#endif - -void ContentSharer::shareText ([[maybe_unused]] const String& text, - std::function callbackToUse) -{ - #if JUCE_CONTENT_SHARING - startNewShare (callbackToUse); - pimpl->shareText (text); - #else - // Content sharing is not available on this platform! - jassertfalse; - - if (callbackToUse) - callbackToUse (false, "Content sharing is not available on this platform!"); - #endif + auto impl = detail::ScopedContentSharerInterface::shareFiles (files, parent); + return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback)); } -void ContentSharer::shareImages ([[maybe_unused]] const Array& images, - std::function callbackToUse, - [[maybe_unused]] ImageFileFormat* imageFileFormatToUse) +ScopedMessageBox ContentSharer::shareTextScoped (const String& text, + Callback callback, + Component* parent) { - #if JUCE_CONTENT_SHARING - startNewShare (callbackToUse); - prepareImagesThread.reset (new PrepareImagesThread (*this, images, imageFileFormatToUse)); - #else - // Content sharing is not available on this platform! - jassertfalse; - - if (callbackToUse) - callbackToUse (false, "Content sharing is not available on this platform!"); - #endif + auto impl = detail::ScopedContentSharerInterface::shareText (text, parent); + return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback)); } -#if JUCE_CONTENT_SHARING -void ContentSharer::filesToSharePrepared() +ScopedMessageBox ContentSharer::shareImagesScoped (const Array& images, + std::unique_ptr format, + Callback callback, + Component* parent) { - Array urls; - - for (const auto& tempFile : temporaryFiles) - urls.add (URL (tempFile)); - - prepareImagesThread = nullptr; - prepareDataThread = nullptr; - - pimpl->shareFiles (urls); + auto impl = detail::ScopedContentSharerInterface::shareImages (images, std::move (format), parent); + return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback)); } -#endif -void ContentSharer::shareData ([[maybe_unused]] const MemoryBlock& mb, - std::function callbackToUse) +ScopedMessageBox ContentSharer::shareDataScoped (const MemoryBlock& mb, + Callback callback, + Component* parent) { - #if JUCE_CONTENT_SHARING - startNewShare (callbackToUse); - prepareDataThread.reset (new PrepareDataThread (*this, mb)); - #else - if (callbackToUse) - callbackToUse (false, "Content sharing not available on this platform!"); - #endif + auto impl = detail::ScopedContentSharerInterface::shareData (mb, parent); + return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback)); } -void ContentSharer::sharingFinished (bool succeeded, const String& errorDescription) +#if ! (JUCE_CONTENT_SHARING && (JUCE_IOS || JUCE_ANDROID)) +auto detail::ScopedContentSharerInterface::shareFiles (const Array&, Component*) -> std::unique_ptr { - deleteTemporaryFiles(); - - std::function cb; - std::swap (cb, callback); - - String error (errorDescription); - - #if JUCE_CONTENT_SHARING - pimpl.reset(); - #endif - - if (cb) - cb (succeeded, error); + return std::make_unique(); } -void ContentSharer::deleteTemporaryFiles() +auto detail::ScopedContentSharerInterface::shareText (const String&, Component*) -> std::unique_ptr { - for (auto& f : temporaryFiles) - f.deleteFile(); - - temporaryFiles.clear(); + return std::make_unique(); } +#endif } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h index df16e3d..a23cd0d 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h @@ -23,21 +23,30 @@ ============================================================================== */ -#pragma once - namespace juce { -/** A singleton class responsible for sharing content between apps and devices. +//============================================================================== +/** + Functions that allow sharing content between apps and devices. You can share text, images, files or an arbitrary data block. @tags{GUI} */ -class JUCE_API ContentSharer : public DeletedAtShutdown +class JUCE_API ContentSharer { public: - JUCE_DECLARE_SINGLETON (ContentSharer, false) + ContentSharer() = delete; + + /** A callback of this type is passed when starting a content sharing + session. + + When the session ends, the function will receive a flag indicating + whether the session was successful. In the case of failure, the + errorText argument may hold a string describing the problem. + */ + using Callback = std::function; /** Shares the given files. Each URL should be either a full file path or it should point to a resource within the application bundle. For @@ -50,9 +59,16 @@ class JUCE_API ContentSharer : public DeletedAtShutdown Sadly on Android the returned success flag may be wrong as there is no standard way the sharing targets report if the sharing operation succeeded. Also, the optional error message is always empty on Android. + + @param files the files to share + @param callback a callback that will be called on the main thread + when the sharing session ends + @param parent the component that should be used to host the + sharing view */ - void shareFiles (const Array& files, - std::function callback); + [[nodiscard]] static ScopedMessageBox shareFilesScoped (const Array& files, + Callback callback, + Component* parent = nullptr); /** Shares the given text. @@ -60,9 +76,16 @@ class JUCE_API ContentSharer : public DeletedAtShutdown Sadly on Android the returned success flag may be wrong as there is no standard way the sharing targets report if the sharing operation succeeded. Also, the optional error message is always empty on Android. + + @param text the text to share + @param callback a callback that will be called on the main thread + when the sharing session ends + @param parent the component that should be used to host the + sharing view */ - void shareText (const String& text, - std::function callback); + [[nodiscard]] static ScopedMessageBox shareTextScoped (const String& text, + Callback callback, + Component* parent = nullptr); /** A convenience function to share an image. This is useful when you have images loaded in memory. The images will be written to temporary files first, so if @@ -84,10 +107,20 @@ class JUCE_API ContentSharer : public DeletedAtShutdown Sadly on Android the returned success flag may be wrong as there is no standard way the sharing targets report if the sharing operation succeeded. Also, the optional error message is always empty on Android. + + @param images the images to share + @param format the file format to use when saving the images. + If no format is provided, a sensible default will + be used. + @param callback a callback that will be called on the main thread + when the sharing session ends + @param parent the component that should be used to host the + sharing view */ - void shareImages (const Array& images, - std::function callback, - ImageFileFormat* imageFileFormatToUse = nullptr); + [[nodiscard]] static ScopedMessageBox shareImagesScoped (const Array& images, + std::unique_ptr format, + Callback callback, + Component* parent = nullptr); /** A convenience function to share arbitrary data. The data will be written to a temporary file and then that file will be shared. If you have @@ -97,47 +130,16 @@ class JUCE_API ContentSharer : public DeletedAtShutdown Sadly on Android the returned success flag may be wrong as there is no standard way the sharing targets report if the sharing operation succeeded. Also, the optional error message is always empty on Android. - */ - void shareData (const MemoryBlock& mb, - std::function callback); - -private: - ContentSharer(); - ~ContentSharer(); - - Array temporaryFiles; - std::function callback; - - #if JUCE_CONTENT_SHARING - struct Pimpl - { - virtual ~Pimpl() {} - virtual void shareFiles (const Array& files) = 0; - virtual void shareText (const String& text) = 0; - }; - - std::unique_ptr pimpl; - Pimpl* createPimpl(); - - void startNewShare (std::function); - - class ContentSharerNativeImpl; - friend class ContentSharerNativeImpl; - - class PrepareImagesThread; - friend class PrepareImagesThread; - std::unique_ptr prepareImagesThread; - - class PrepareDataThread; - friend class PrepareDataThread; - std::unique_ptr prepareDataThread; - - void filesToSharePrepared(); - #endif - - void deleteTemporaryFiles(); - void sharingFinished (bool, const String&); + @param mb the data to share + @param callback a callback that will be called on the main thread + when the sharing session ends + @param parent the component that should be used to host the + sharing view + */ + [[nodiscard]] static ScopedMessageBox shareDataScoped (const MemoryBlock& mb, + Callback callback, + Component* parent = nullptr); }; } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index 766eaa7..5d0d609 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -371,7 +371,6 @@ void FileBrowserComponent::lookAndFeelChanged() filenameBox.applyColourToAllText (findColour (filenameBoxTextColourId)); resized(); - repaint(); } //============================================================================== @@ -605,7 +604,7 @@ void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPa void FileBrowserComponent::timerCallback() { - const auto isProcessActive = isForegroundOrEmbeddedProcess (this); + const auto isProcessActive = detail::WindowingHelpers::isForegroundOrEmbeddedProcess (this); if (wasProcessActive != isProcessActive) { diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h index 1fb9dfd..55e3965 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h @@ -252,6 +252,8 @@ class JUCE_API FileBrowserComponent : public Component, FilePreviewComponent* getPreviewComponent() const noexcept; /** @internal */ DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; protected: /** Returns a list of names and paths for the default places the user might want to look. @@ -283,7 +285,6 @@ class JUCE_API FileBrowserComponent : public Component, TimeSliceThread thread; bool wasProcessActive; - std::unique_ptr createAccessibilityHandler() override; void timerCallback() override; void sendListenerChangeMessage(); bool isFileOrDirSuitable (const File&) const; diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp index d45ebed..7bd67b9 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooser.cpp @@ -165,7 +165,7 @@ bool FileChooser::browseForDirectory() bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp) { - FocusRestorer focusRestorer; + detail::FocusRestorer focusRestorer; pimpl = createPimpl (flags, previewComp); pimpl->runModally(); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp index 82fb30a..1e2d994 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp @@ -115,7 +115,7 @@ FileChooserDialogBox::FileChooserDialogBox (const String& name, if (parentComp != nullptr) parentComp->addAndMakeVisible (this); else - setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); + setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows()); } FileChooserDialogBox::~FileChooserDialogBox() @@ -182,28 +182,26 @@ void FileChooserDialogBox::fileDoubleClicked (const File&) void FileChooserDialogBox::fileClicked (const File&, const MouseEvent&) {} void FileChooserDialogBox::browserRootChanged (const File&) {} -void FileChooserDialogBox::okToOverwriteFileCallback (int result, FileChooserDialogBox* box) -{ - if (result != 0 && box != nullptr) - box->exitModalState (1); -} - void FileChooserDialogBox::okButtonPressed() { if (warnAboutOverwritingExistingFiles && content->chooserComponent.isSaveMode() && content->chooserComponent.getSelectedFile(0).exists()) { - AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, - TRANS("File already exists"), - TRANS("There's already a file called: FLNM") - .replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName()) - + "\n\n" - + TRANS("Are you sure you want to overwrite it?"), - TRANS("Overwrite"), - TRANS("Cancel"), - this, - ModalCallbackFunction::forComponent (okToOverwriteFileCallback, this)); + auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon, + TRANS ("File already exists"), + TRANS ("There's already a file called: FLNM") + .replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName()) + + "\n\n" + + TRANS ("Are you sure you want to overwrite it?"), + TRANS ("Overwrite"), + TRANS ("Cancel"), + this); + messageBox = AlertWindow::showScopedAsync (options, [this] (int result) + { + if (result != 0) + exitModalState (1); + }); } else { @@ -251,9 +249,12 @@ void FileChooserDialogBox::createNewFolderConfirmed (const String& nameFromDialo auto parent = content->chooserComponent.getRoot(); if (! parent.getChildFile (name).createDirectory()) - AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon, - TRANS ("New Folder"), - TRANS ("Couldn't create the folder!")); + { + auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon, + TRANS ("New Folder"), + TRANS ("Couldn't create the folder!")); + messageBox = AlertWindow::showScopedAsync (options, nullptr); + } content->chooserComponent.refresh(); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h index 515d825..7006c17 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h @@ -158,9 +158,10 @@ class JUCE_API FileChooserDialogBox : public ResizableWindow, void createNewFolder(); void createNewFolderConfirmed (const String& name); - static void okToOverwriteFileCallback (int result, FileChooserDialogBox*); static void createNewFolderCallback (int result, FileChooserDialogBox*, Component::SafePointer); + ScopedMessageBox messageBox; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooserDialogBox) }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp index 09259fe..036967f 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp @@ -26,9 +26,6 @@ namespace juce { -Image juce_createIconForFile (const File& file); - - //============================================================================== FileListComponent::FileListComponent (DirectoryContentsList& listToShow) : ListBox ({}, this), @@ -103,6 +100,7 @@ void FileListComponent::changeListenerCallback (ChangeBroadcaster*) //============================================================================== class FileListComponent::ItemComponent : public Component, + public TooltipClient, private TimeSliceClient, private AsyncUpdater { @@ -193,6 +191,11 @@ class FileListComponent::ItemComponent : public Component, repaint(); } + String getTooltip() override + { + return owner.getTooltipForRow (index); + } + private: //============================================================================== FileListComponent& owner; @@ -217,7 +220,7 @@ class FileListComponent::ItemComponent : public Component, if (im.isNull() && ! onlyUpdateIfCached) { - im = juce_createIconForFile (file); + im = detail::WindowingHelpers::createIconForFile (file); if (im.isValid()) ImageCache::addImageToCache (im, hashCode); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 604d754..e545106 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -129,7 +129,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, f.setHorizontalScale (0.9f); g.setFont (f); - g.drawText (path[rowNumber].getFullPathName(), + g.drawText (path.getRawString (rowNumber), 4, 0, width - 6, height, Justification::centredLeft, true); } @@ -145,7 +145,7 @@ void FileSearchPathListComponent::deleteKeyPressed (int row) void FileSearchPathListComponent::returnKeyPressed (int row) { - chooser = std::make_unique (TRANS("Change folder..."), path[row], "*"); + chooser = std::make_unique (TRANS("Change folder..."), path.getRawString (row), "*"); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) @@ -258,7 +258,7 @@ void FileSearchPathListComponent::moveSelection (int delta) if (currentRow != newRow) { - auto f = path[currentRow]; + const auto f = File::createFileWithoutCheckingPath (path.getRawString (currentRow)); path.remove (currentRow); path.add (f, newRow); listBox.selectRow (newRow); diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp index 7a8cfb1..2a7bd5e 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp @@ -26,45 +26,83 @@ namespace juce { +template +int threeWayCompare (const T& a, const T& b) +{ + if (a < b) return -1; + if (b < a) return 1; + return 0; +} + +int threeWayCompare (const String& a, const String& b); +int threeWayCompare (const String& a, const String& b) +{ + return a.compare (b); +} + +struct ReverseCompareString +{ + String value; +}; + +int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b); +int threeWayCompare (const ReverseCompareString& a, const ReverseCompareString& b) +{ + return b.value.compare (a.value); +} + +template +constexpr int threeWayCompareImpl (const std::tuple& a, const std::tuple& b) +{ + if constexpr (position == sizeof... (Ts)) + { + ignoreUnused (a, b); + return 0; + } + else + { + const auto head = threeWayCompare (std::get (a), std::get (b)); + + if (head != 0) + return head; + + return threeWayCompareImpl (a, b); + } +} + +template +constexpr int threeWayCompare (const std::tuple& a, const std::tuple& b) +{ + return threeWayCompareImpl<0> (a, b); +} + //============================================================================== class FileListTreeItem : public TreeViewItem, private TimeSliceClient, - private AsyncUpdater, - private ChangeListener + private AsyncUpdater { public: FileListTreeItem (FileTreeComponent& treeComp, - DirectoryContentsList* parentContents, - int indexInContents, const File& f, TimeSliceThread& t) : file (f), owner (treeComp), - parentContentsList (parentContents), - indexInContentsList (indexInContents), - subContentsList (nullptr, false), thread (t) { - DirectoryContentsList::FileInfo fileInfo; + } - if (parentContents != nullptr - && parentContents->getFileInfo (indexInContents, fileInfo)) - { - fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize); - modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M"); - isDirectory = fileInfo.isDirectory; - } - else - { - isDirectory = true; - } + void update (const DirectoryContentsList::FileInfo& fileInfo) + { + fileSize = File::descriptionOfSizeInBytes (fileInfo.fileSize); + modTime = fileInfo.modificationTime.formatted ("%d %b '%y %H:%M"); + isDirectory = fileInfo.isDirectory; + repaintItem(); } ~FileListTreeItem() override { thread.removeTimeSliceClient (this); clearSubItems(); - removeSubContentsList(); } //============================================================================== @@ -76,88 +114,7 @@ class FileListTreeItem : public TreeViewItem, void itemOpennessChanged (bool isNowOpen) override { - if (isNowOpen) - { - clearSubItems(); - - isDirectory = file.isDirectory(); - - if (isDirectory) - { - if (subContentsList == nullptr && parentContentsList != nullptr) - { - auto l = new DirectoryContentsList (parentContentsList->getFilter(), thread); - - l->setDirectory (file, - parentContentsList->isFindingDirectories(), - parentContentsList->isFindingFiles()); - - setSubContentsList (l, true); - } - - changeListenerCallback (nullptr); - } - } - } - - void removeSubContentsList() - { - if (subContentsList != nullptr) - { - subContentsList->removeChangeListener (this); - subContentsList.reset(); - } - } - - void setSubContentsList (DirectoryContentsList* newList, const bool canDeleteList) - { - removeSubContentsList(); - - subContentsList = OptionalScopedPointer (newList, canDeleteList); - newList->addChangeListener (this); - } - - void selectFile (const File& target) - { - if (file == target) - { - setSelected (true, true); - return; - } - - if (subContentsList != nullptr && subContentsList->isStillLoading()) - { - pendingFileSelection.emplace (*this, target); - return; - } - - pendingFileSelection.reset(); - - if (! target.isAChildOf (file)) - return; - - setOpen (true); - - for (int i = 0; i < getNumSubItems(); ++i) - if (auto* f = dynamic_cast (getSubItem (i))) - f->selectFile (target); - } - - void changeListenerCallback (ChangeBroadcaster*) override - { - rebuildItemsFromContentList(); - } - - void rebuildItemsFromContentList() - { - clearSubItems(); - - if (isOpen() && subContentsList != nullptr) - { - for (int i = 0; i < subContentsList->getNumFiles(); ++i) - addSubItem (new FileListTreeItem (owner, subContentsList, i, - subContentsList->getFile(i), thread)); - } + NullCheckedInvocation::invoke (onOpennessChanged, file, isNowOpen); } void paintItem (Graphics& g, int width, int height) override @@ -176,7 +133,7 @@ class FileListTreeItem : public TreeViewItem, file, file.getFileName(), &icon, fileSize, modTime, isDirectory, isSelected(), - indexInContentsList, owner); + getIndexInParent(), owner); } String getAccessibilityName() override @@ -213,40 +170,11 @@ class FileListTreeItem : public TreeViewItem, } const File file; + std::function onOpennessChanged; private: - class PendingFileSelection : private Timer - { - public: - PendingFileSelection (FileListTreeItem& item, const File& f) - : owner (item), fileToSelect (f) - { - startTimer (10); - } - - ~PendingFileSelection() override - { - stopTimer(); - } - - private: - void timerCallback() override - { - // Take a copy of the file here, in case this PendingFileSelection - // object is destroyed during the call to selectFile. - owner.selectFile (File { fileToSelect }); - } - - FileListTreeItem& owner; - File fileToSelect; - }; - - Optional pendingFileSelection; FileTreeComponent& owner; - DirectoryContentsList* parentContentsList; - int indexInContentsList; - OptionalScopedPointer subContentsList; - bool isDirectory; + bool isDirectory = false; TimeSliceThread& thread; CriticalSection iconUpdate; Image icon; @@ -261,7 +189,7 @@ class FileListTreeItem : public TreeViewItem, if (im.isNull() && ! onlyUpdateIfCached) { - im = juce_createIconForFile (file); + im = detail::WindowingHelpers::createIconForFile (file); if (im.isValid()) ImageCache::addImageToCache (im, hashCode); @@ -282,11 +210,349 @@ class FileListTreeItem : public TreeViewItem, JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileListTreeItem) }; +class DirectoryScanner : private ChangeListener +{ +public: + struct Listener + { + virtual ~Listener() = default; + + virtual void rootChanged() = 0; + virtual void directoryChanged (const DirectoryContentsList&) = 0; + }; + + DirectoryScanner (DirectoryContentsList& rootIn, Listener& listenerIn) + : root (rootIn), listener (listenerIn) + { + root.addChangeListener (this); + } + + ~DirectoryScanner() override + { + root.removeChangeListener (this); + } + + void refresh() + { + root.refresh(); + } + + void open (const File& f) + { + auto& contentsList = [&]() -> auto& + { + if (auto it = contentsLists.find (f); it != contentsLists.end()) + return it->second; + + auto insertion = contentsLists.emplace (std::piecewise_construct, + std::forward_as_tuple (f), + std::forward_as_tuple (root.getFilter(), + root.getTimeSliceThread())); + return insertion.first->second; + }(); + + contentsList.addChangeListener (this); + contentsList.setDirectory (f, true, true); + contentsList.refresh(); + } + + void close (const File& f) + { + if (auto it = contentsLists.find (f); it != contentsLists.end()) + contentsLists.erase (it); + } + + File getRootDirectory() const + { + return root.getDirectory(); + } + + bool isStillLoading() const + { + return std::any_of (contentsLists.begin(), + contentsLists.end(), + [] (const auto& it) + { + return it.second.isStillLoading(); + }); + } + +private: + void changeListenerCallback (ChangeBroadcaster* source) override + { + auto* sourceList = static_cast (source); + + if (sourceList == &root) + { + if (std::exchange (lastDirectory, root.getDirectory()) != root.getDirectory()) + { + contentsLists.clear(); + listener.rootChanged(); + } + else + { + for (auto& contentsList : contentsLists) + contentsList.second.refresh(); + } + } + + listener.directoryChanged (*sourceList); + } + + DirectoryContentsList& root; + Listener& listener; + File lastDirectory; + std::map contentsLists; +}; + +struct FileEntry +{ + String path; + bool isDirectory; + + int compareWindows (const FileEntry& other) const + { + const auto toTuple = [] (const auto& x) { return std::tuple (! x.isDirectory, x.path.toLowerCase()); }; + return threeWayCompare (toTuple (*this), toTuple (other)); + } + + int compareLinux (const FileEntry& other) const + { + const auto toTuple = [] (const auto& x) { return std::tuple (x.path.toUpperCase(), ReverseCompareString { x.path }); }; + return threeWayCompare (toTuple (*this), toTuple (other)); + } + + int compareDefault (const FileEntry& other) const + { + return threeWayCompare (path.toLowerCase(), other.path.toLowerCase()); + } +}; + +class OSDependentFileComparisonRules +{ +public: + explicit OSDependentFileComparisonRules (SystemStats::OperatingSystemType systemTypeIn) + : systemType (systemTypeIn) + {} + + int compare (const FileEntry& first, const FileEntry& second) const + { + if ((systemType & SystemStats::OperatingSystemType::Windows) != 0) + return first.compareWindows (second); + + if ((systemType & SystemStats::OperatingSystemType::Linux) != 0) + return first.compareLinux (second); + + return first.compareDefault (second); + } + + bool operator() (const FileEntry& first, const FileEntry& second) const + { + return compare (first, second) < 0; + } + +private: + SystemStats::OperatingSystemType systemType; +}; + +class FileTreeComponent::Controller : private DirectoryScanner::Listener +{ +public: + explicit Controller (FileTreeComponent& ownerIn) + : owner (ownerIn), + scanner (owner.directoryContentsList, *this) + { + refresh(); + } + + ~Controller() override + { + owner.deleteRootItem(); + } + + void refresh() + { + scanner.refresh(); + } + + void selectFile (const File& target) + { + pendingFileSelection.emplace (target); + tryResolvePendingFileSelection(); + } + +private: + template + static void forEachItemRecursive (TreeViewItem* item, ItemCallback&& cb) + { + if (item == nullptr) + return; + + if (auto* fileListItem = dynamic_cast (item)) + cb (fileListItem); + + for (int i = 0; i < item->getNumSubItems(); ++i) + forEachItemRecursive (item->getSubItem (i), cb); + } + + //============================================================================== + void rootChanged() override + { + owner.deleteRootItem(); + treeItemForFile.clear(); + owner.setRootItem (createNewItem (scanner.getRootDirectory()).release()); + } + + void directoryChanged (const DirectoryContentsList& contentsList) override + { + auto* parentItem = [&]() -> FileListTreeItem* + { + if (auto it = treeItemForFile.find (contentsList.getDirectory()); it != treeItemForFile.end()) + return it->second; + + return nullptr; + }(); + + if (parentItem == nullptr) + { + jassertfalse; + return; + } + + for (int i = 0; i < contentsList.getNumFiles(); ++i) + { + auto file = contentsList.getFile (i); + + DirectoryContentsList::FileInfo fileInfo; + contentsList.getFileInfo (i, fileInfo); + + auto* item = [&] + { + if (auto it = treeItemForFile.find (file); it != treeItemForFile.end()) + return it->second; + + auto* newItem = createNewItem (file).release(); + parentItem->addSubItem (newItem); + return newItem; + }(); + + if (item->isOpen() && fileInfo.isDirectory) + scanner.open (item->file); + + item->update (fileInfo); + } + + if (contentsList.isStillLoading()) + return; + + std::set allFiles; + + for (int i = 0; i < contentsList.getNumFiles(); ++i) + allFiles.insert (contentsList.getFile (i)); + + for (int i = 0; i < parentItem->getNumSubItems();) + { + auto* fileItem = dynamic_cast (parentItem->getSubItem (i)); + + if (fileItem != nullptr && allFiles.count (fileItem->file) == 0) + { + forEachItemRecursive (parentItem->getSubItem (i), + [this] (auto* item) + { + scanner.close (item->file); + treeItemForFile.erase (item->file); + }); + + parentItem->removeSubItem (i); + } + else + { + ++i; + } + } + + struct Comparator + { + // The different OSes compare and order files in different ways. This function aims + // to match these different rules of comparison to mimic other FileBrowserComponent + // view modes where we don't need to order the results, and can just rely on the + // ordering of the list provided by the OS. + static int compareElements (TreeViewItem* first, TreeViewItem* second) + { + auto* item1 = dynamic_cast (first); + auto* item2 = dynamic_cast (second); + + if (item1 == nullptr || item2 == nullptr) + return 0; + + static const OSDependentFileComparisonRules comparisonRules { SystemStats::getOperatingSystemType() }; + + return comparisonRules.compare ({ item1->file.getFullPathName(), item1->file.isDirectory() }, + { item2->file.getFullPathName(), item2->file.isDirectory() }); + } + }; + + static Comparator comparator; + parentItem->sortSubItems (comparator); + tryResolvePendingFileSelection(); + } + + std::unique_ptr createNewItem (const File& file) + { + auto newItem = std::make_unique (owner, + file, + owner.directoryContentsList.getTimeSliceThread()); + + newItem->onOpennessChanged = [this, itemPtr = newItem.get()] (const auto& f, auto isOpen) + { + if (isOpen) + { + scanner.open (f); + } + else + { + forEachItemRecursive (itemPtr, + [this] (auto* item) + { + scanner.close (item->file); + }); + } + }; + + treeItemForFile[file] = newItem.get(); + return newItem; + } + + void tryResolvePendingFileSelection() + { + if (! pendingFileSelection.has_value()) + return; + + if (auto item = treeItemForFile.find (*pendingFileSelection); item != treeItemForFile.end()) + { + item->second->setSelected (true, true); + pendingFileSelection.reset(); + return; + } + + if (owner.directoryContentsList.isStillLoading() || scanner.isStillLoading()) + return; + + owner.clearSelectedItems(); + } + + FileTreeComponent& owner; + std::map treeItemForFile; + DirectoryScanner scanner; + std::optional pendingFileSelection; +}; + //============================================================================== FileTreeComponent::FileTreeComponent (DirectoryContentsList& listToShow) : DirectoryContentsDisplayComponent (listToShow), itemHeight (22) { + controller = std::make_unique (*this); setRootItemVisible (false); refresh(); } @@ -298,13 +564,7 @@ FileTreeComponent::~FileTreeComponent() void FileTreeComponent::refresh() { - deleteRootItem(); - - auto root = new FileListTreeItem (*this, nullptr, 0, directoryContentsList.getDirectory(), - directoryContentsList.getTimeSliceThread()); - - root->setSubContentsList (&directoryContentsList, false); - setRootItem (root); + controller->refresh(); } //============================================================================== @@ -333,8 +593,7 @@ void FileTreeComponent::setDragAndDropDescription (const String& description) void FileTreeComponent::setSelectedFile (const File& target) { - if (auto* t = dynamic_cast (getRootItem())) - t->selectFile (target); + controller->selectFile (target); } void FileTreeComponent::setItemHeight (int newHeight) @@ -348,4 +607,75 @@ void FileTreeComponent::setItemHeight (int newHeight) } } +#if JUCE_UNIT_TESTS + +class FileTreeComponentTests : public UnitTest +{ +public: + //============================================================================== + FileTreeComponentTests() : UnitTest ("FileTreeComponentTests", UnitTestCategories::gui) {} + + void runTest() override + { + const auto checkOrder = [] (const auto& orderedFiles, const std::vector& expected) + { + return std::equal (orderedFiles.begin(), orderedFiles.end(), + expected.begin(), expected.end(), + [] (const auto& entry, const auto& expectedPath) { return entry.path == expectedPath; }); + }; + + const auto doSort = [] (const auto platform, auto& range) + { + std::sort (range.begin(), range.end(), OSDependentFileComparisonRules { platform }); + }; + + beginTest ("Test Linux filename ordering"); + { + std::vector filesToOrder { { "_test", false }, + { "Atest", false }, + { "atest", false } }; + + doSort (SystemStats::OperatingSystemType::Linux, filesToOrder); + + expect (checkOrder (filesToOrder, { "atest", "Atest", "_test" })); + } + + beginTest ("Test Windows filename ordering"); + { + std::vector filesToOrder { { "cmake_install.cmake", false }, + { "CMakeFiles", true }, + { "JUCEConfig.cmake", false }, + { "tools", true }, + { "cmakefiles.cmake", false } }; + + doSort (SystemStats::OperatingSystemType::Windows, filesToOrder); + + expect (checkOrder (filesToOrder, { "CMakeFiles", + "tools", + "cmake_install.cmake", + "cmakefiles.cmake", + "JUCEConfig.cmake" })); + } + + beginTest ("Test MacOS filename ordering"); + { + std::vector filesToOrder { { "cmake_install.cmake", false }, + { "CMakeFiles", true }, + { "tools", true }, + { "JUCEConfig.cmake", false } }; + + doSort (SystemStats::OperatingSystemType::MacOSX, filesToOrder); + + expect (checkOrder (filesToOrder, { "cmake_install.cmake", + "CMakeFiles", + "JUCEConfig.cmake", + "tools" })); + } + } +}; + +static FileTreeComponentTests fileTreeComponentTests; + +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h index 17010a9..3f02a6e 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h @@ -99,6 +99,9 @@ class JUCE_API FileTreeComponent : public TreeView, String dragAndDropDescription; int itemHeight; + class Controller; + std::unique_ptr controller; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileTreeComponent) }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h index a6955bf..faa821f 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h @@ -52,13 +52,14 @@ class JUCE_API ImagePreviewComponent : public FilePreviewComponent, void paint (Graphics&) override; /** @internal */ void timerCallback() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: File fileToLoad; Image currentThumbnail; String currentDetails; - std::unique_ptr createAccessibilityHandler() override; void getThumbSize (int& w, int& h) const; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent) diff --git a/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.cpp b/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.cpp index fc057b7..d4a79c0 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.cpp @@ -107,70 +107,123 @@ #endif //============================================================================== -#define JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED_OR_OFFSCREEN \ - jassert ((MessageManager::getInstanceWithoutCreating() != nullptr \ - && MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()) \ - || getPeer() == nullptr); +#include "detail/juce_AccessibilityHelpers.h" +#include "detail/juce_ButtonAccessibilityHandler.h" +#include "detail/juce_ScalingHelpers.h" +#include "detail/juce_ComponentHelpers.h" +#include "detail/juce_FocusHelpers.h" +#include "detail/juce_FocusRestorer.h" +#include "detail/juce_ViewportHelpers.h" +#include "detail/juce_LookAndFeelHelpers.h" +#include "detail/juce_PointerState.h" +#include "detail/juce_CustomMouseCursorInfo.h" +#include "detail/juce_MouseInputSourceImpl.h" +#include "detail/juce_MouseInputSourceList.h" +#include "detail/juce_ToolbarItemDragAndDropOverlayComponent.h" +#include "detail/juce_ScopedMessageBoxInterface.h" +#include "detail/juce_ScopedMessageBoxImpl.h" +#include "detail/juce_ScopedContentSharerInterface.h" +#include "detail/juce_ScopedContentSharerImpl.h" +#include "detail/juce_WindowingHelpers.h" +#include "detail/juce_AlertWindowHelpers.h" +#include "detail/juce_TopLevelWindowManager.h" -namespace juce -{ - bool juce_areThereAnyAlwaysOnTopWindows(); +//============================================================================== +#if JUCE_IOS || JUCE_WINDOWS + #include "native/juce_MultiTouchMapper.h" +#endif + +#if JUCE_ANDROID || JUCE_WINDOWS || JUCE_IOS || JUCE_UNIT_TESTS + #include "native/accessibility/juce_AccessibilityTextHelpers.h" +#endif + +#if JUCE_MAC || JUCE_IOS + #include "native/accessibility/juce_AccessibilitySharedCode_mac.mm" + + #if JUCE_IOS + #include "native/juce_UIViewComponentPeer_ios.mm" + #include "native/accessibility/juce_Accessibility_ios.mm" + #include "native/juce_WindowUtils_ios.mm" + #include "native/juce_Windowing_ios.mm" + #include "native/juce_NativeMessageBox_ios.mm" + #include "native/juce_NativeModalWrapperComponent_ios.h" + #include "native/juce_FileChooser_ios.mm" + + #if JUCE_CONTENT_SHARING + #include "native/juce_ContentSharer_ios.cpp" + #endif + + #else + #include "native/accessibility/juce_Accessibility_mac.mm" + #include "native/juce_PerScreenDisplayLinks_mac.h" + #include "native/juce_NSViewComponentPeer_mac.mm" + #include "native/juce_WindowUtils_mac.mm" + #include "native/juce_Windowing_mac.mm" + #include "native/juce_NativeMessageBox_mac.mm" + #include "native/juce_MainMenu_mac.mm" + #include "native/juce_FileChooser_mac.mm" + #endif + + #include "native/juce_MouseCursor_mac.mm" + +#elif JUCE_WINDOWS + #include "native/accessibility/juce_ComInterfaces_windows.h" + #include "native/accessibility/juce_WindowsUIAWrapper_windows.h" + #include "native/accessibility/juce_AccessibilityElement_windows.h" + #include "native/accessibility/juce_UIAHelpers_windows.h" + #include "native/accessibility/juce_UIAProviders_windows.h" + #include "native/accessibility/juce_AccessibilityElement_windows.cpp" + #include "native/accessibility/juce_Accessibility_windows.cpp" + #include "native/juce_WindowsHooks_windows.h" + #include "native/juce_WindowUtils_windows.cpp" + #include "native/juce_Windowing_windows.cpp" + #include "native/juce_WindowsHooks_windows.cpp" + #include "native/juce_NativeMessageBox_windows.cpp" + #include "native/juce_DragAndDrop_windows.cpp" + #include "native/juce_FileChooser_windows.cpp" + +#elif JUCE_LINUX || JUCE_BSD + #include "native/juce_XSymbols_linux.cpp" + #include "native/juce_DragAndDrop_linux.cpp" - bool isEmbeddedInForegroundProcess (Component* c); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant") - #if ! JUCE_WINDOWS - bool isEmbeddedInForegroundProcess (Component*) { return false; } - #endif + #include "native/juce_ScopedWindowAssociation_linux.h" + #include "native/juce_WindowUtils_linux.cpp" + #include "native/juce_Windowing_linux.cpp" + #include "native/juce_NativeMessageBox_linux.cpp" + #include "native/juce_XWindowSystem_linux.cpp" - /* Returns true if this process is in the foreground, or if the viewComponent - is embedded into a window owned by the foreground process. - */ - static bool isForegroundOrEmbeddedProcess (Component* viewComponent) - { - return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent); - } + JUCE_END_IGNORE_WARNINGS_GCC_LIKE - bool isWindowOnCurrentVirtualDesktop (void*); + #include "native/juce_FileChooser_linux.cpp" - struct CustomMouseCursorInfo - { - ScaledImage image; - Point hotspot; - }; +#elif JUCE_ANDROID - template - static const AccessibilityHandler* getEnclosingHandlerWithInterface (const AccessibilityHandler* handler, MemberFn fn) - { - if (handler == nullptr) - return nullptr; + #include "juce_core/files/juce_common_MimeTypes.h" + #include "native/accessibility/juce_Accessibility_android.cpp" + #include "native/juce_WindowUtils_android.cpp" + #include "native/juce_Windowing_android.cpp" + #include "native/juce_NativeMessageBox_android.cpp" + #include "native/juce_FileChooser_android.cpp" - if ((handler->*fn)() != nullptr) - return handler; + #if JUCE_CONTENT_SHARING + #include "native/juce_ContentSharer_android.cpp" + #endif + +#endif - return getEnclosingHandlerWithInterface (handler->getParent(), fn); - } -} // namespace juce +//============================================================================== +// Depends on types defined in platform-specific windowing files +#include "mouse/juce_MouseCursor.cpp" -#include "mouse/juce_PointerState.h" +#if JUCE_UNIT_TESTS + #include "native/accessibility/juce_AccessibilityTextHelpers_test.cpp" +#endif +//============================================================================== #include "accessibility/juce_AccessibilityHandler.cpp" -#include "components/juce_Component.cpp" -#include "components/juce_ComponentListener.cpp" -#include "components/juce_FocusTraverser.cpp" -#include "mouse/juce_MouseInputSource.cpp" -#include "desktop/juce_Displays.cpp" -#include "desktop/juce_Desktop.cpp" -#include "components/juce_ModalComponentManager.cpp" -#include "mouse/juce_ComponentDragger.cpp" -#include "mouse/juce_DragAndDropContainer.cpp" -#include "mouse/juce_MouseEvent.cpp" -#include "mouse/juce_MouseInactivityDetector.cpp" -#include "mouse/juce_MouseListener.cpp" -#include "keyboard/juce_CaretComponent.cpp" -#include "keyboard/juce_KeyboardFocusTraverser.cpp" -#include "keyboard/juce_KeyListener.cpp" -#include "keyboard/juce_KeyPress.cpp" -#include "keyboard/juce_ModifierKeys.cpp" +#include "application/juce_Application.cpp" #include "buttons/juce_ArrowButton.cpp" #include "buttons/juce_Button.cpp" #include "buttons/juce_DrawableButton.cpp" @@ -180,6 +233,17 @@ namespace juce #include "buttons/juce_TextButton.cpp" #include "buttons/juce_ToggleButton.cpp" #include "buttons/juce_ToolbarButton.cpp" +#include "commands/juce_ApplicationCommandInfo.cpp" +#include "commands/juce_ApplicationCommandManager.cpp" +#include "commands/juce_ApplicationCommandTarget.cpp" +#include "commands/juce_KeyPressMappingSet.cpp" +#include "components/juce_Component.cpp" +#include "components/juce_ComponentListener.cpp" +#include "components/juce_FocusTraverser.cpp" +#include "components/juce_ModalComponentManager.cpp" +#include "desktop/juce_Desktop.cpp" +#include "desktop/juce_Displays.cpp" +#include "detail/juce_AccessibilityHelpers.cpp" #include "drawables/juce_Drawable.cpp" #include "drawables/juce_DrawableComposite.cpp" #include "drawables/juce_DrawableImage.cpp" @@ -188,22 +252,31 @@ namespace juce #include "drawables/juce_DrawableShape.cpp" #include "drawables/juce_DrawableText.cpp" #include "drawables/juce_SVGParser.cpp" +#include "filebrowser/juce_ContentSharer.cpp" #include "filebrowser/juce_DirectoryContentsDisplayComponent.cpp" #include "filebrowser/juce_DirectoryContentsList.cpp" #include "filebrowser/juce_FileBrowserComponent.cpp" #include "filebrowser/juce_FileChooser.cpp" #include "filebrowser/juce_FileChooserDialogBox.cpp" #include "filebrowser/juce_FileListComponent.cpp" -#include "filebrowser/juce_FilenameComponent.cpp" #include "filebrowser/juce_FileSearchPathListComponent.cpp" #include "filebrowser/juce_FileTreeComponent.cpp" +#include "filebrowser/juce_FilenameComponent.cpp" #include "filebrowser/juce_ImagePreviewComponent.cpp" -#include "filebrowser/juce_ContentSharer.cpp" +#include "keyboard/juce_CaretComponent.cpp" +#include "keyboard/juce_KeyListener.cpp" +#include "keyboard/juce_KeyPress.cpp" +#include "keyboard/juce_KeyboardFocusTraverser.cpp" +#include "keyboard/juce_ModifierKeys.cpp" #include "layout/juce_ComponentAnimator.cpp" #include "layout/juce_ComponentBoundsConstrainer.cpp" +#include "layout/juce_BorderedComponentBoundsConstrainer.cpp" #include "layout/juce_ComponentBuilder.cpp" #include "layout/juce_ComponentMovementWatcher.cpp" #include "layout/juce_ConcertinaPanel.cpp" +#include "layout/juce_FlexBox.cpp" +#include "layout/juce_Grid.cpp" +#include "layout/juce_GridItem.cpp" #include "layout/juce_GroupComponent.cpp" #include "layout/juce_MultiDocumentPanel.cpp" #include "layout/juce_ResizableBorderComponent.cpp" @@ -218,14 +291,26 @@ namespace juce #include "layout/juce_TabbedComponent.cpp" #include "layout/juce_Viewport.cpp" #include "lookandfeel/juce_LookAndFeel.cpp" -#include "lookandfeel/juce_LookAndFeel_V2.cpp" #include "lookandfeel/juce_LookAndFeel_V1.cpp" +#include "lookandfeel/juce_LookAndFeel_V2.cpp" #include "lookandfeel/juce_LookAndFeel_V3.cpp" #include "lookandfeel/juce_LookAndFeel_V4.cpp" -#include "menus/juce_MenuBarComponent.cpp" #include "menus/juce_BurgerMenuComponent.cpp" +#include "menus/juce_MenuBarComponent.cpp" #include "menus/juce_MenuBarModel.cpp" #include "menus/juce_PopupMenu.cpp" +#include "misc/juce_BubbleComponent.cpp" +#include "misc/juce_DropShadower.cpp" +#include "misc/juce_FocusOutline.cpp" +#include "misc/juce_JUCESplashScreen.cpp" +#include "mouse/juce_ComponentDragger.cpp" +#include "mouse/juce_DragAndDropContainer.cpp" +#include "mouse/juce_MouseEvent.cpp" +#include "mouse/juce_MouseInactivityDetector.cpp" +#include "mouse/juce_MouseInputSource.cpp" +#include "mouse/juce_MouseListener.cpp" +#include "native/accessibility/juce_Accessibility.cpp" +#include "native/juce_ScopedDPIAwarenessDisabler.cpp" #include "positioning/juce_MarkerList.cpp" #include "positioning/juce_RelativeCoordinate.cpp" #include "positioning/juce_RelativeCoordinatePositioner.cpp" @@ -236,11 +321,11 @@ namespace juce #include "properties/juce_BooleanPropertyComponent.cpp" #include "properties/juce_ButtonPropertyComponent.cpp" #include "properties/juce_ChoicePropertyComponent.cpp" +#include "properties/juce_MultiChoicePropertyComponent.cpp" #include "properties/juce_PropertyComponent.cpp" #include "properties/juce_PropertyPanel.cpp" #include "properties/juce_SliderPropertyComponent.cpp" #include "properties/juce_TextPropertyComponent.cpp" -#include "properties/juce_MultiChoicePropertyComponent.cpp" #include "widgets/juce_ComboBox.cpp" #include "widgets/juce_ImageComponent.cpp" #include "widgets/juce_Label.cpp" @@ -250,213 +335,20 @@ namespace juce #include "widgets/juce_TableHeaderComponent.cpp" #include "widgets/juce_TableListBox.cpp" #include "widgets/juce_TextEditor.cpp" -#include "widgets/juce_ToolbarItemComponent.cpp" #include "widgets/juce_Toolbar.cpp" +#include "widgets/juce_ToolbarItemComponent.cpp" #include "widgets/juce_ToolbarItemPalette.cpp" #include "widgets/juce_TreeView.cpp" +#include "windows/juce_NativeMessageBox.cpp" #include "windows/juce_AlertWindow.cpp" #include "windows/juce_CallOutBox.cpp" #include "windows/juce_ComponentPeer.cpp" #include "windows/juce_DialogWindow.cpp" #include "windows/juce_DocumentWindow.cpp" +#include "windows/juce_MessageBoxOptions.cpp" #include "windows/juce_ResizableWindow.cpp" +#include "windows/juce_ScopedMessageBox.cpp" #include "windows/juce_ThreadWithProgressWindow.cpp" #include "windows/juce_TooltipWindow.cpp" #include "windows/juce_TopLevelWindow.cpp" -#include "windows/juce_VBlankAttachement.cpp" -#include "commands/juce_ApplicationCommandInfo.cpp" -#include "commands/juce_ApplicationCommandManager.cpp" -#include "commands/juce_ApplicationCommandTarget.cpp" -#include "commands/juce_KeyPressMappingSet.cpp" -#include "application/juce_Application.cpp" -#include "misc/juce_BubbleComponent.cpp" -#include "misc/juce_DropShadower.cpp" -#include "misc/juce_FocusOutline.cpp" -#include "misc/juce_JUCESplashScreen.cpp" - -#include "layout/juce_FlexBox.cpp" -#include "layout/juce_GridItem.cpp" -#include "layout/juce_Grid.cpp" - -#if JUCE_IOS || JUCE_WINDOWS - #include "native/juce_MultiTouchMapper.h" -#endif - -#if JUCE_ANDROID || JUCE_WINDOWS || JUCE_IOS || JUCE_UNIT_TESTS - #include "native/accessibility/juce_AccessibilityTextHelpers.h" -#endif - -#if JUCE_MAC || JUCE_IOS - #include "native/accessibility/juce_mac_AccessibilitySharedCode.mm" - - #if JUCE_IOS - #include "native/juce_ios_UIViewComponentPeer.mm" - #include "native/accessibility/juce_ios_Accessibility.mm" - #include "native/juce_ios_Windowing.mm" - #include "native/juce_ios_FileChooser.mm" - - #if JUCE_CONTENT_SHARING - #include "native/juce_ios_ContentSharer.cpp" - #endif - - #else - #include "native/accessibility/juce_mac_Accessibility.mm" - #include "native/juce_mac_PerScreenDisplayLinks.h" - #include "native/juce_mac_NSViewComponentPeer.mm" - #include "native/juce_mac_Windowing.mm" - #include "native/juce_mac_MainMenu.mm" - #include "native/juce_mac_FileChooser.mm" - #endif - - #include "native/juce_mac_MouseCursor.mm" - -#elif JUCE_WINDOWS - #include "native/accessibility/juce_win32_ComInterfaces.h" - #include "native/accessibility/juce_win32_WindowsUIAWrapper.h" - #include "native/accessibility/juce_win32_AccessibilityElement.h" - #include "native/accessibility/juce_win32_UIAHelpers.h" - #include "native/accessibility/juce_win32_UIAProviders.h" - #include "native/accessibility/juce_win32_AccessibilityElement.cpp" - #include "native/accessibility/juce_win32_Accessibility.cpp" - #include "native/juce_win32_Windowing.cpp" - #include "native/juce_win32_DragAndDrop.cpp" - #include "native/juce_win32_FileChooser.cpp" - -#elif JUCE_LINUX || JUCE_BSD - #include "native/x11/juce_linux_X11_Symbols.cpp" - #include "native/x11/juce_linux_X11_DragAndDrop.cpp" - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant") - - #include "native/x11/juce_linux_ScopedWindowAssociation.h" - #include "native/juce_linux_Windowing.cpp" - #include "native/x11/juce_linux_XWindowSystem.cpp" - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - #include "native/juce_linux_FileChooser.cpp" - -#elif JUCE_ANDROID - -namespace juce -{ -static jobject makeAndroidRect (Rectangle r) -{ - return getEnv()->NewObject (AndroidRect, - AndroidRect.constructor, - r.getX(), - r.getY(), - r.getRight(), - r.getBottom()); -} - -static jobject makeAndroidPoint (Point p) -{ - return getEnv()->NewObject (AndroidPoint, - AndroidPoint.create, - p.getX(), - p.getY()); -} -} // namespace juce - - #include "juce_core/files/juce_common_MimeTypes.h" - #include "native/accessibility/juce_android_Accessibility.cpp" - #include "native/juce_android_Windowing.cpp" - #include "native/juce_android_FileChooser.cpp" - - #if JUCE_CONTENT_SHARING - #include "native/juce_android_ContentSharer.cpp" - #endif - -#endif - -namespace juce -{ - #if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED - class AccessibilityHandler::AccessibilityNativeImpl { public: AccessibilityNativeImpl (AccessibilityHandler&) {} }; - void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {} - void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {} - AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; } - void notifyAccessibilityEventInternal (const AccessibilityHandler&, InternalAccessibilityEvent) {} - std::unique_ptr AccessibilityHandler::createNativeImpl (AccessibilityHandler&) - { - return nullptr; - } - #else - std::unique_ptr AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler) - { - return std::make_unique (handler); - } - #endif -} - -//============================================================================== -#if JUCE_WINDOWS -namespace juce -{ - -JUCE_COMCLASS (JuceIVirtualDesktopManager, "a5cd92ff-29be-454c-8d04-d82879fb3f1b") : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE IsWindowOnCurrentVirtualDesktop( - __RPC__in HWND topLevelWindow, - __RPC__out BOOL * onCurrentDesktop) = 0; - - virtual HRESULT STDMETHODCALLTYPE GetWindowDesktopId( - __RPC__in HWND topLevelWindow, - __RPC__out GUID * desktopId) = 0; - - virtual HRESULT STDMETHODCALLTYPE MoveWindowToDesktop( - __RPC__in HWND topLevelWindow, - __RPC__in REFGUID desktopId) = 0; -}; - -JUCE_COMCLASS (JuceVirtualDesktopManager, "aa509086-5ca9-4c25-8f95-589d3c07b48a"); - -} // namespace juce - -#ifdef __CRT_UUID_DECL -__CRT_UUID_DECL (juce::JuceIVirtualDesktopManager, 0xa5cd92ff, 0x29be, 0x454c, 0x8d, 0x04, 0xd8, 0x28, 0x79, 0xfb, 0x3f, 0x1b) -__CRT_UUID_DECL (juce::JuceVirtualDesktopManager, 0xaa509086, 0x5ca9, 0x4c25, 0x8f, 0x95, 0x58, 0x9d, 0x3c, 0x07, 0xb4, 0x8a) -#endif - -bool juce::isWindowOnCurrentVirtualDesktop (void* x) -{ - if (x == nullptr) - return false; - - static auto* desktopManager = [] - { - JuceIVirtualDesktopManager* result = nullptr; - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") - - if (SUCCEEDED (CoCreateInstance (__uuidof (JuceVirtualDesktopManager), nullptr, CLSCTX_ALL, IID_PPV_ARGS (&result)))) - return result; - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - return static_cast (nullptr); - }(); - - BOOL current = false; - - if (auto* dm = desktopManager) - if (SUCCEEDED (dm->IsWindowOnCurrentVirtualDesktop (static_cast (x), ¤t))) - return current != false; - - return true; -} - -#else - bool juce::isWindowOnCurrentVirtualDesktop (void*) { return true; } - juce::ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); } - juce::ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {} -#endif - -// Depends on types defined in platform-specific windowing files -#include "mouse/juce_MouseCursor.cpp" - -#if JUCE_UNIT_TESTS -#include "native/accessibility/juce_AccessibilityTextHelpers_test.cpp" -#endif +#include "windows/juce_VBlankAttachment.cpp" diff --git a/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.h b/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.h index fd37c23..d9e3cf2 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.h +++ b/JuceLibraryCode/modules/juce_gui_basics/juce_gui_basics.h @@ -35,7 +35,7 @@ ID: juce_gui_basics vendor: juce - version: 7.0.5 + version: 7.0.7 name: JUCE GUI core classes description: Basic user-interface components and related classes. website: http://www.juce.com/juce @@ -127,7 +127,6 @@ namespace juce class Component; class LookAndFeel; class MouseInputSource; - class MouseInputSourceInternal; class ComponentPeer; class MouseEvent; struct MouseWheelDetails; @@ -161,16 +160,27 @@ namespace juce class Displays; class AccessibilityHandler; class KeyboardFocusTraverser; - class PointerState; class FlexBox; class Grid; class FocusOutline; - #if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX + #if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD Image createSnapshotOfNativeWindow (void* nativeWindowHandle); #endif -} + + namespace detail + { + struct ComponentHelpers; + class MouseInputSourceImpl; + class MouseInputSourceList; + class PointerState; + class ScopedMessageBoxImpl; + class ToolbarItemDragAndDropOverlayComponent; + class TopLevelWindowManager; + } // namespace detail + +} // namespace juce #include "mouse/juce_MouseCursor.h" #include "mouse/juce_MouseListener.h" @@ -189,6 +199,7 @@ namespace juce #include "desktop/juce_Desktop.h" #include "desktop/juce_Displays.h" #include "layout/juce_ComponentBoundsConstrainer.h" +#include "layout/juce_BorderedComponentBoundsConstrainer.h" #include "mouse/juce_ComponentDragger.h" #include "mouse/juce_DragAndDropTarget.h" #include "mouse/juce_DragAndDropContainer.h" @@ -269,6 +280,7 @@ namespace juce #include "widgets/juce_TreeView.h" #include "windows/juce_TopLevelWindow.h" #include "windows/juce_MessageBoxOptions.h" +#include "windows/juce_ScopedMessageBox.h" #include "windows/juce_AlertWindow.h" #include "windows/juce_CallOutBox.h" #include "windows/juce_ComponentPeer.h" @@ -278,7 +290,8 @@ namespace juce #include "windows/juce_NativeMessageBox.h" #include "windows/juce_ThreadWithProgressWindow.h" #include "windows/juce_TooltipWindow.h" -#include "windows/juce_VBlankAttachement.h" +#include "windows/juce_VBlankAttachment.h" +#include "windows/juce_WindowUtils.h" #include "layout/juce_MultiDocumentPanel.h" #include "layout/juce_SidePanel.h" #include "filebrowser/juce_FileBrowserListener.h" @@ -323,7 +336,9 @@ namespace juce #if JUCE_LINUX || JUCE_BSD #if JUCE_GUI_BASICS_INCLUDE_XHEADERS // If you're missing these headers, you need to install the libx11-dev package + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wvariadic-macros") #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #include #include #include @@ -363,13 +378,13 @@ namespace juce #undef SIZEOF #undef KeyPress - #include "native/x11/juce_linux_XWindowSystem.h" - #include "native/x11/juce_linux_X11_Symbols.h" + #include "native/juce_XWindowSystem_linux.h" + #include "native/juce_XSymbols_linux.h" #endif #endif #if JUCE_GUI_BASICS_INCLUDE_SCOPED_THREAD_DPI_AWARENESS_SETTER && JUCE_WINDOWS - #include "native/juce_win32_ScopedThreadDPIAwarenessSetter.h" + #include "native/juce_ScopedThreadDPIAwarenessSetter_windows.h" #endif #include "layout/juce_FlexItem.h" diff --git a/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp b/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp index 97f44e7..6c92c08 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/keyboard/juce_KeyboardFocusTraverser.cpp @@ -35,10 +35,10 @@ namespace KeyboardFocusTraverserHelpers } static Component* traverse (Component* current, Component* container, - FocusHelpers::NavigationDirection direction) + detail::FocusHelpers::NavigationDirection direction) { - if (auto* comp = FocusHelpers::navigateFocus (current, container, direction, - &Component::isKeyboardFocusContainer)) + if (auto* comp = detail::FocusHelpers::navigateFocus (current, container, direction, + &Component::isKeyboardFocusContainer)) { if (isKeyboardFocusable (comp, container)) return comp; @@ -53,13 +53,13 @@ namespace KeyboardFocusTraverserHelpers Component* KeyboardFocusTraverser::getNextComponent (Component* current) { return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(), - FocusHelpers::NavigationDirection::forwards); + detail::FocusHelpers::NavigationDirection::forwards); } Component* KeyboardFocusTraverser::getPreviousComponent (Component* current) { return KeyboardFocusTraverserHelpers::traverse (current, current->findKeyboardFocusContainer(), - FocusHelpers::NavigationDirection::backwards); + detail::FocusHelpers::NavigationDirection::backwards); } Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentComponent) @@ -74,9 +74,9 @@ Component* KeyboardFocusTraverser::getDefaultComponent (Component* parentCompone std::vector KeyboardFocusTraverser::getAllComponents (Component* parentComponent) { std::vector components; - FocusHelpers::findAllComponents (parentComponent, - components, - &Component::isKeyboardFocusContainer); + detail::FocusHelpers::findAllComponents (parentComponent, + components, + &Component::isKeyboardFocusContainer); auto removePredicate = [parentComponent] (const Component* comp) { diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPosition.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPosition.h index acdb915..b8cedb7 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPosition.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPosition.h @@ -179,7 +179,7 @@ class AnimatedPosition : private Timer { newPosition = range.clipValue (newPosition); - if (position != newPosition) + if (! approximatelyEqual (position, newPosition)) { position = newPosition; listeners.call ([this, newPosition] (Listener& l) { l.positionChanged (*this, newPosition); }); diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h index eed93d6..a6651d2 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_AnimatedPositionBehaviours.h @@ -89,7 +89,7 @@ namespace AnimatedPositionBehaviours */ bool isStopped (double /*position*/) const noexcept { - return velocity == 0.0; + return approximatelyEqual (velocity, 0.0); } private: diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.cpp new file mode 100644 index 0000000..e2f5265 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.cpp @@ -0,0 +1,77 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +void BorderedComponentBoundsConstrainer::checkBounds (Rectangle& bounds, + const Rectangle& previousBounds, + const Rectangle& limits, + bool isStretchingTop, + bool isStretchingLeft, + bool isStretchingBottom, + bool isStretchingRight) +{ + if (auto* decorated = getWrappedConstrainer()) + { + const auto border = getAdditionalBorder(); + const auto requestedBounds = bounds; + + border.subtractFrom (bounds); + decorated->checkBounds (bounds, + border.subtractedFrom (previousBounds), + limits, + isStretchingTop, + isStretchingLeft, + isStretchingBottom, + isStretchingRight); + border.addTo (bounds); + bounds = bounds.withPosition (requestedBounds.getPosition()); + + if (isStretchingTop && ! isStretchingBottom) + bounds = bounds.withBottomY (previousBounds.getBottom()); + + if (! isStretchingTop && isStretchingBottom) + bounds = bounds.withY (previousBounds.getY()); + + if (isStretchingLeft && ! isStretchingRight) + bounds = bounds.withRightX (previousBounds.getRight()); + + if (! isStretchingLeft && isStretchingRight) + bounds = bounds.withX (previousBounds.getX()); + } + else + { + ComponentBoundsConstrainer::checkBounds (bounds, + previousBounds, + limits, + isStretchingTop, + isStretchingLeft, + isStretchingBottom, + isStretchingRight); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.h new file mode 100644 index 0000000..0ed9889 --- /dev/null +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_BorderedComponentBoundsConstrainer.h @@ -0,0 +1,69 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2022 - Raw Material Software Limited + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 7 End-User License + Agreement and JUCE Privacy Policy. + + End User License Agreement: www.juce.com/juce-7-licence + Privacy Policy: www.juce.com/juce-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A ComponentBoundsConstrainer that can be used to add a constant border onto another + ComponentBoundsConstrainer. + + This is useful when trying to constrain the size of a resizable window or + other component that wraps a constrained component, such as a plugin + editor. + + @see ResizableCornerComponent, ResizableBorderComponent, ResizableWindow, + ComponentBoundsConstrainer + + @tags{GUI} +*/ +class JUCE_API BorderedComponentBoundsConstrainer : public ComponentBoundsConstrainer +{ +public: + /** Default constructor. */ + BorderedComponentBoundsConstrainer() = default; + + /** Returns a pointer to another constrainer that will be used as the + base for any resizing operations. + */ + virtual ComponentBoundsConstrainer* getWrappedConstrainer() const = 0; + + /** Returns the border that should be applied to the constrained bounds. */ + virtual BorderSize getAdditionalBorder() const = 0; + + /** @internal */ + void checkBounds (Rectangle& bounds, + const Rectangle& previousBounds, + const Rectangle& limits, + bool isStretchingTop, + bool isStretchingLeft, + bool isStretchingBottom, + bool isStretchingRight) override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BorderedComponentBoundsConstrainer) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp index d3f39c6..c05ebd1 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp @@ -49,7 +49,7 @@ class ComponentAnimator::AnimationTask destAlpha = finalAlpha; isMoving = (finalBounds != component->getBounds()); - isChangingAlpha = (finalAlpha != component->getAlpha()); + isChangingAlpha = ! approximatelyEqual (finalAlpha, component->getAlpha()); left = component->getX(); top = component->getY(); @@ -273,7 +273,7 @@ void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake) void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake) { - if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f)) + if (component != nullptr && ! (component->isVisible() && approximatelyEqual (component->getAlpha(), 1.0f))) { component->setAlpha (0.0f); component->setVisible (true); diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h index 30de3f0..fee213c 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h @@ -119,8 +119,10 @@ class JUCE_API ConcertinaPanel : public Component ConcertinaPanel&, Component&) = 0; }; -private: + /** @internal */ std::unique_ptr createAccessibilityHandler() override; + +private: void resized() override; class PanelHolder; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_FlexBox.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_FlexBox.cpp index e489df2..2d10f73 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_FlexBox.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_FlexBox.cpp @@ -84,8 +84,16 @@ struct FlexBoxLayoutCalculation ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; } - static bool isAuto (Coord value) noexcept { return value == FlexItem::autoValue; } - static bool isAssigned (Coord value) noexcept { return value != FlexItem::notAssigned; } + static bool isAuto (Coord value) noexcept + { + return exactlyEqual (value, static_cast (FlexItem::autoValue)); + } + + static bool isAssigned (Coord value) noexcept + { + return ! exactlyEqual (value, static_cast (FlexItem::notAssigned)); + } + static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; } //============================================================================== @@ -604,12 +612,12 @@ struct FlexBoxLayoutCalculation if (positiveFlexibility) { - if (totalFlexGrow != 0.0) + if (! approximatelyEqual (totalFlexGrow, 0.0)) changeUnit = difference / totalFlexGrow; } else { - if (totalFlexShrink != 0.0) + if (! approximatelyEqual (totalFlexShrink, 0.0)) changeUnit = difference / totalFlexShrink; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.cpp index e76cd98..7235f35 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -26,942 +26,1026 @@ namespace juce { -struct AllTracksIncludingImplicit +template +static Array operator+ (const Array& a, const Array& b) { - Array items; - int numImplicitLeading; // The number of implicit items before the explicit items -}; + auto copy = a; + copy.addArray (b); + return copy; +} -struct Tracks +struct Grid::Helpers { - AllTracksIncludingImplicit columns, rows; -}; -struct Grid::SizeCalculation -{ - static float getTotalAbsoluteSize (const Array& tracks, Px gapSize) noexcept + struct AllTracksIncludingImplicit { - float totalCellSize = 0.0f; - - for (const auto& trackInfo : tracks) - if (! trackInfo.isFractional() || trackInfo.isAuto()) - totalCellSize += trackInfo.getSize(); - - float totalGap = tracks.size() > 1 ? static_cast ((tracks.size() - 1) * gapSize.pixels) - : 0.0f; - - return totalCellSize + totalGap; - } + Array items; + int numImplicitLeading; // The number of implicit items before the explicit items + }; - static float getRelativeUnitSize (float size, float totalAbsolute, const Array& tracks) noexcept + struct Tracks { - const float totalRelative = jlimit (0.0f, size, size - totalAbsolute); - float factorsSum = 0.0f; - - for (const auto& trackInfo : tracks) - if (trackInfo.isFractional()) - factorsSum += trackInfo.getSize(); - - jassert (factorsSum != 0.0f); - return totalRelative / factorsSum; - } + AllTracksIncludingImplicit columns, rows; + }; - //============================================================================== - static float getTotalAbsoluteHeight (const Array& rowTracks, Px rowGap) + struct NoRounding { - return getTotalAbsoluteSize (rowTracks, rowGap); - } + template + T operator() (T t) const { return t; } + }; - static float getTotalAbsoluteWidth (const Array& columnTracks, Px columnGap) + struct StandardRounding { - return getTotalAbsoluteSize (columnTracks, columnGap); - } + template + T operator() (T t) const { return std::round (t); } + }; - static float getRelativeWidthUnit (float gridWidth, Px columnGap, const Array& columnTracks) + template + struct SizeCalculation { - return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGap), columnTracks); - } + float getTotalAbsoluteSize (const Array& tracks, Px gapSize) noexcept + { + float totalCellSize = 0.0f; - static float getRelativeHeightUnit (float gridHeight, Px rowGap, const Array& rowTracks) - { - return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGap), rowTracks); - } + for (const auto& trackInfo : tracks) + if (! trackInfo.isFractional() || trackInfo.isAuto()) + totalCellSize += roundingFunction (trackInfo.getSize()); - //============================================================================== - static bool hasAnyFractions (const Array& tracks) - { - return std::any_of (tracks.begin(), - tracks.end(), - [] (const auto& t) { return t.isFractional(); }); - } + float totalGap = tracks.size() > 1 ? (float) (tracks.size() - 1) * roundingFunction ((float) gapSize.pixels) + : 0.0f; - void computeSizes (float gridWidth, float gridHeight, - Px columnGapToUse, Px rowGapToUse, - const Tracks& tracks) - { - if (hasAnyFractions (tracks.columns.items)) - relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, tracks.columns.items); - else - remainingWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse); - - if (hasAnyFractions (tracks.rows.items)) - relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, tracks.rows.items); - else - remainingHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse); - } + return totalCellSize + totalGap; + } - float relativeWidthUnit = 0.0f; - float relativeHeightUnit = 0.0f; - float remainingWidth = 0.0f; - float remainingHeight = 0.0f; -}; + static float getRelativeUnitSize (float size, float totalAbsolute, const Array& tracks) noexcept + { + const float totalRelative = jlimit (0.0f, size, size - totalAbsolute); + float factorsSum = 0.0f; -//============================================================================== -struct Grid::PlacementHelpers -{ - enum { invalid = -999999 }; - static constexpr auto emptyAreaCharacter = "."; + for (const auto& trackInfo : tracks) + if (trackInfo.isFractional()) + factorsSum += trackInfo.getSize(); - //============================================================================== - struct LineRange { int start, end; }; - struct LineArea { LineRange column, row; }; - struct LineInfo { StringArray lineNames; }; + jassert (! approximatelyEqual (factorsSum, 0.0f)); + return totalRelative / factorsSum; + } - struct NamedArea - { - String name; - LineArea lines; - }; + //============================================================================== + float getTotalAbsoluteHeight (const Array& rowTracks, Px rowGapSize) + { + return getTotalAbsoluteSize (rowTracks, rowGapSize); + } - //============================================================================== - static Array getArrayOfLinesFromTracks (const Array& tracks) - { - // fill line info array - Array lines; + float getTotalAbsoluteWidth (const Array& columnTracks, Px columnGapSize) + { + return getTotalAbsoluteSize (columnTracks, columnGapSize); + } + + float getRelativeWidthUnit (float gridWidth, Px columnGapSize, const Array& columnTracks) + { + return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGapSize), columnTracks); + } + + float getRelativeHeightUnit (float gridHeight, Px rowGapSize, const Array& rowTracks) + { + return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGapSize), rowTracks); + } - for (int i = 1; i <= tracks.size(); ++i) + //============================================================================== + static bool hasAnyFractions (const Array& tracks) { - const auto& currentTrack = tracks.getReference (i - 1); + return std::any_of (tracks.begin(), + tracks.end(), + [] (const auto& t) { return t.isFractional(); }); + } - if (i == 1) // start line + void computeSizes (float gridWidth, float gridHeight, + Px columnGapToUse, Px rowGapToUse, + const Tracks& tracks) + { + if (hasAnyFractions (tracks.columns.items)) { - LineInfo li; - li.lineNames.add (currentTrack.getStartLineName()); - lines.add (li); + relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, tracks.columns.items); + fractionallyDividedWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse); } - - if (i > 1 && i <= tracks.size()) // two lines in between tracks + else { - const auto& prevTrack = tracks.getReference (i - 2); - - LineInfo li; - li.lineNames.add (prevTrack.getEndLineName()); - li.lineNames.add (currentTrack.getStartLineName()); - - lines.add (li); + remainingWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse); } - if (i == tracks.size()) // end line + if (hasAnyFractions (tracks.rows.items)) { - LineInfo li; - li.lineNames.add (currentTrack.getEndLineName()); - lines.add (li); + relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, tracks.rows.items); + fractionallyDividedHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse); } + else + { + remainingHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse); + } + + const auto calculateTrackBounds = [&] (auto& outBounds, + const auto& trackItems, + auto relativeUnit, + auto totalSizeForFractionalItems, + auto gap) + { + const auto lastFractionalIndex = [&] + { + for (int i = trackItems.size() - 1; 0 <= i; --i) + if (trackItems[i].isFractional()) + return i; + + return -1; + }(); + + float start = 0.0f; + float carriedError = 0.0f; + + for (int i = 0; i < trackItems.size(); ++i) + { + const auto& currentItem = trackItems[i]; + + const auto currentTrackSize = [&] + { + if (i == lastFractionalIndex) + return totalSizeForFractionalItems; + + const auto absoluteSize = currentItem.getAbsoluteSize (relativeUnit); + + if (! currentItem.isFractional()) + return roundingFunction (absoluteSize); + + const auto result = roundingFunction (absoluteSize - carriedError); + carriedError += result - absoluteSize; + return result; + }(); + + if (currentItem.isFractional()) + totalSizeForFractionalItems -= currentTrackSize; + + const auto end = start + currentTrackSize; + outBounds.emplace_back (start, end); + start = end + roundingFunction (static_cast (gap.pixels)); + } + }; + + calculateTrackBounds (columnTrackBounds, + tracks.columns.items, + relativeWidthUnit, + fractionallyDividedWidth, + columnGapToUse); + + calculateTrackBounds (rowTrackBounds, + tracks.rows.items, + relativeHeightUnit, + fractionallyDividedHeight, + rowGapToUse); } - jassert (lines.size() == tracks.size() + 1); + float relativeWidthUnit = 0.0f; + float relativeHeightUnit = 0.0f; + float fractionallyDividedWidth = 0.0f; + float fractionallyDividedHeight = 0.0f; + float remainingWidth = 0.0f; + float remainingHeight = 0.0f; - return lines; - } + std::vector> columnTrackBounds; + std::vector> rowTrackBounds; + RoundingFunction roundingFunction; + }; //============================================================================== - static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop, - const Array& tracks) + struct PlacementHelpers { - jassert (prop.hasAbsolute()); + enum { invalid = -999999 }; + static constexpr auto emptyAreaCharacter = "."; + + //============================================================================== + struct LineRange { int start, end; }; + struct LineArea { LineRange column, row; }; + struct LineInfo { StringArray lineNames; }; - const auto lines = getArrayOfLinesFromTracks (tracks); - int count = 0; + struct NamedArea + { + String name; + LineArea lines; + }; - for (int i = 0; i < lines.size(); i++) + //============================================================================== + static Array getArrayOfLinesFromTracks (const Array& tracks) { - for (const auto& name : lines.getReference (i).lineNames) + // fill line info array + Array lines; + + for (int i = 1; i <= tracks.size(); ++i) { - if (prop.getName() == name) + const auto& currentTrack = tracks.getReference (i - 1); + + if (i == 1) // start line { - ++count; - break; + LineInfo li; + li.lineNames.add (currentTrack.getStartLineName()); + lines.add (li); } - } - if (count == prop.getNumber()) - return i + 1; - } - - jassertfalse; - return count; - } + if (i > 1 && i <= tracks.size()) // two lines in between tracks + { + const auto& prevTrack = tracks.getReference (i - 2); - static int deduceAbsoluteLineNumber (GridItem::Property prop, - const Array& tracks) - { - jassert (prop.hasAbsolute()); + LineInfo li; + li.lineNames.add (prevTrack.getEndLineName()); + li.lineNames.add (currentTrack.getStartLineName()); - if (prop.hasName()) - return deduceAbsoluteLineNumberFromLineName (prop, tracks); + lines.add (li); + } - if (prop.getNumber() > 0) - return prop.getNumber(); + if (i == tracks.size()) // end line + { + LineInfo li; + li.lineNames.add (currentTrack.getEndLineName()); + lines.add (li); + } + } - if (prop.getNumber() < 0) - return tracks.size() + 2 + prop.getNumber(); + jassert (lines.size() == tracks.size() + 1); - // An integer value of 0 is invalid - jassertfalse; - return 1; - } + return lines; + } - static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber, - GridItem::Property propertyWithSpan, - const Array& tracks) - { - jassert (propertyWithSpan.hasSpan()); + //============================================================================== + static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop, + const Array& tracks) + { + jassert (prop.hasAbsolute()); - const auto lines = getArrayOfLinesFromTracks (tracks); - int count = 0; + const auto lines = getArrayOfLinesFromTracks (tracks); + int count = 0; - for (int i = startLineNumber; i < lines.size(); i++) - { - for (const auto& name : lines.getReference (i).lineNames) + for (int i = 0; i < lines.size(); i++) { - if (propertyWithSpan.getName() == name) + for (const auto& name : lines.getReference (i).lineNames) { - ++count; - break; + if (prop.getName() == name) + { + ++count; + break; + } } + + if (count == prop.getNumber()) + return i + 1; } - if (count == propertyWithSpan.getNumber()) - return i + 1; + jassertfalse; + return count; } - jassertfalse; - return count; - } + static int deduceAbsoluteLineNumber (GridItem::Property prop, + const Array& tracks) + { + jassert (prop.hasAbsolute()); - static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber, - GridItem::Property propertyWithSpan, - const Array& tracks) - { - jassert (propertyWithSpan.hasSpan()); + if (prop.hasName()) + return deduceAbsoluteLineNumberFromLineName (prop, tracks); - if (propertyWithSpan.hasName()) - return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks); + if (prop.getNumber() > 0) + return prop.getNumber(); - return startLineNumber + propertyWithSpan.getNumber(); - } + if (prop.getNumber() < 0) + return tracks.size() + 2 + prop.getNumber(); - //============================================================================== - static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const Array& tracks) - { - jassert (! (prop.start.hasAuto() && prop.end.hasAuto())); + // An integer value of 0 is invalid + jassertfalse; + return 1; + } - if (prop.start.hasAbsolute() && prop.end.hasAuto()) + static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber, + GridItem::Property propertyWithSpan, + const Array& tracks) { - prop.end = GridItem::Span (1); + jassert (propertyWithSpan.hasSpan()); + + const auto lines = getArrayOfLinesFromTracks (tracks); + int count = 0; + + for (int i = startLineNumber; i < lines.size(); i++) + { + for (const auto& name : lines.getReference (i).lineNames) + { + if (propertyWithSpan.getName() == name) + { + ++count; + break; + } + } + + if (count == propertyWithSpan.getNumber()) + return i + 1; + } + + jassertfalse; + return count; } - else if (prop.start.hasAuto() && prop.end.hasAbsolute()) + + static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber, + GridItem::Property propertyWithSpan, + const Array& tracks) { - prop.start = GridItem::Span (1); + jassert (propertyWithSpan.hasSpan()); + + if (propertyWithSpan.hasName()) + return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks); + + return startLineNumber + propertyWithSpan.getNumber(); } - auto s = [&]() -> LineRange + //============================================================================== + static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const Array& tracks) { - if (prop.start.hasAbsolute() && prop.end.hasAbsolute()) + jassert (! (prop.start.hasAuto() && prop.end.hasAuto())); + + if (prop.start.hasAbsolute() && prop.end.hasAuto()) { - return { deduceAbsoluteLineNumber (prop.start, tracks), - deduceAbsoluteLineNumber (prop.end, tracks) }; + prop.end = GridItem::Span (1); } - - if (prop.start.hasAbsolute() && prop.end.hasSpan()) + else if (prop.start.hasAuto() && prop.end.hasAbsolute()) { - const auto start = deduceAbsoluteLineNumber (prop.start, tracks); - return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.end, tracks) }; + prop.start = GridItem::Span (1); } - if (prop.start.hasSpan() && prop.end.hasAbsolute()) + auto s = [&]() -> LineRange { - const auto start = deduceAbsoluteLineNumber (prop.end, tracks); - return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.start, tracks) }; - } + if (prop.start.hasAbsolute() && prop.end.hasAbsolute()) + { + return { deduceAbsoluteLineNumber (prop.start, tracks), + deduceAbsoluteLineNumber (prop.end, tracks) }; + } - // Can't have an item with spans on both start and end. - jassertfalse; - return {}; - }(); + if (prop.start.hasAbsolute() && prop.end.hasSpan()) + { + const auto start = deduceAbsoluteLineNumber (prop.start, tracks); + return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.end, tracks) }; + } - // swap if start overtakes end - if (s.start > s.end) - std::swap (s.start, s.end); - else if (s.start == s.end) - s.end = s.start + 1; + if (prop.start.hasSpan() && prop.end.hasAbsolute()) + { + const auto start = deduceAbsoluteLineNumber (prop.end, tracks); + return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.start, tracks) }; + } - return s; - } + // Can't have an item with spans on both start and end. + jassertfalse; + return {}; + }(); - static LineArea deduceLineArea (const GridItem& item, - const Grid& grid, - const std::map& namedAreas) - { - if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty()) - { - // Must be a named area! - jassert (namedAreas.count (item.area) != 0); + // swap if start overtakes end + if (s.start > s.end) + std::swap (s.start, s.end); + else if (s.start == s.end) + s.end = s.start + 1; - return namedAreas.at (item.area); + return s; } - return { deduceLineRange (item.column, grid.templateColumns), - deduceLineRange (item.row, grid.templateRows) }; - } + static LineArea deduceLineArea (const GridItem& item, + const Grid& grid, + const std::map& namedAreas) + { + if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty()) + { + // Must be a named area! + jassert (namedAreas.count (item.area) != 0); - //============================================================================== - static Array parseAreasProperty (const StringArray& areasStrings) - { - Array strings; + return namedAreas.at (item.area); + } - for (const auto& areaString : areasStrings) - strings.add (StringArray::fromTokens (areaString, false)); + return { deduceLineRange (item.column, grid.templateColumns), + deduceLineRange (item.row, grid.templateRows) }; + } - if (strings.size() > 0) + //============================================================================== + static Array parseAreasProperty (const StringArray& areasStrings) { - for (auto s : strings) + Array strings; + + for (const auto& areaString : areasStrings) + strings.add (StringArray::fromTokens (areaString, false)); + + if (strings.size() > 0) { - jassert (s.size() == strings[0].size()); // all rows must have the same number of columns + for (auto s : strings) + { + jassert (s.size() == strings[0].size()); // all rows must have the same number of columns + } } - } - return strings; - } - - static NamedArea findArea (Array& stringsArrays) - { - NamedArea area; + return strings; + } - for (auto& stringArray : stringsArrays) + static NamedArea findArea (Array& stringsArrays) { - for (auto& string : stringArray) + NamedArea area; + + for (auto& stringArray : stringsArrays) { - // find anchor - if (area.name.isEmpty()) + for (auto& string : stringArray) { - if (string != emptyAreaCharacter) + // find anchor + if (area.name.isEmpty()) { - area.name = string; - area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed; - area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed; - - area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; - area.lines.column.end = stringArray.indexOf (string) + 2; - - // mark as visited - string = emptyAreaCharacter; + if (string != emptyAreaCharacter) + { + area.name = string; + area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed; + area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed; + + area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; + area.lines.column.end = stringArray.indexOf (string) + 2; + + // mark as visited + string = emptyAreaCharacter; + } } - } - else - { - if (string == area.name) + else { - area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; - area.lines.column.end = stringArray.indexOf (string) + 2; - - // mark as visited - string = emptyAreaCharacter; + if (string == area.name) + { + area.lines.row.end = stringsArrays.indexOf (stringArray) + 2; + area.lines.column.end = stringArray.indexOf (string) + 2; + + // mark as visited + string = emptyAreaCharacter; + } } } } + + return area; } - return area; - } + //============================================================================== + static std::map deduceNamedAreas (const StringArray& areasStrings) + { + auto stringsArrays = parseAreasProperty (areasStrings); - //============================================================================== - static std::map deduceNamedAreas (const StringArray& areasStrings) - { - auto stringsArrays = parseAreasProperty (areasStrings); + std::map areas; + + for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays)) + { + if (areas.count (area.name) == 0) + areas[area.name] = area.lines; + else + // Make sure your template-areas property only has one area with the same name and is well-formed + jassertfalse; + } - std::map areas; + return areas; + } - for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays)) + //============================================================================== + template + static Rectangle getCellBounds (int columnNumber, int rowNumber, + const Tracks& tracks, + const SizeCalculation& calculation) { - if (areas.count (area.name) == 0) - areas[area.name] = area.lines; - else - // Make sure your template-areas property only has one area with the same name and is well-formed - jassertfalse; + const auto correctedColumn = columnNumber - 1 + tracks.columns.numImplicitLeading; + const auto correctedRow = rowNumber - 1 + tracks.rows .numImplicitLeading; + + jassert (isPositiveAndBelow (correctedColumn, tracks.columns.items.size())); + jassert (isPositiveAndBelow (correctedRow, tracks.rows .items.size())); + + return + { + calculation.columnTrackBounds[(size_t) correctedColumn].getStart(), + calculation.rowTrackBounds[(size_t) correctedRow].getStart(), + calculation.columnTrackBounds[(size_t) correctedColumn].getEnd() - calculation.columnTrackBounds[(size_t) correctedColumn].getStart(), + calculation.rowTrackBounds[(size_t) correctedRow].getEnd() - calculation.rowTrackBounds[(size_t) correctedRow].getStart() + }; } - return areas; - } + template + static Rectangle alignCell (Rectangle area, + int columnNumber, int rowNumber, + int numberOfColumns, int numberOfRows, + const SizeCalculation& calculation, + AlignContent alignContent, + JustifyContent justifyContent) + { + if (alignContent == AlignContent::end) + area.setY (area.getY() + calculation.remainingHeight); - //============================================================================== - static float getCoord (int trackNumber, float relativeUnit, Px gap, const Array& tracks) - { - float c = 0; + if (justifyContent == JustifyContent::end) + area.setX (area.getX() + calculation.remainingWidth); - for (const auto* it = tracks.begin(); it != tracks.begin() + trackNumber; ++it) - c += it->getAbsoluteSize (relativeUnit) + static_cast (gap.pixels); + if (alignContent == AlignContent::center) + area.setY (area.getY() + calculation.remainingHeight / 2); - return c; - } + if (justifyContent == JustifyContent::center) + area.setX (area.getX() + calculation.remainingWidth / 2); - static Rectangle getCellBounds (int columnNumber, int rowNumber, - const Tracks& tracks, - SizeCalculation calculation, - Px columnGap, Px rowGap) - { - const auto correctedColumn = columnNumber - 1 + tracks.columns.numImplicitLeading; - const auto correctedRow = rowNumber - 1 + tracks.rows .numImplicitLeading; + if (alignContent == AlignContent::spaceBetween) + { + const auto shift = ((float) (rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1))); + area.setY (area.getY() + shift); + } - jassert (isPositiveAndBelow (correctedColumn, tracks.columns.items.size())); - jassert (isPositiveAndBelow (correctedRow, tracks.rows .items.size())); + if (justifyContent == JustifyContent::spaceBetween) + { + const auto shift = ((float) (columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1))); + area.setX (area.getX() + shift); + } - return { getCoord (correctedColumn, calculation.relativeWidthUnit, columnGap, tracks.columns.items), - getCoord (correctedRow, calculation.relativeHeightUnit, rowGap, tracks.rows .items), - tracks.columns.items.getReference (correctedColumn).getAbsoluteSize (calculation.relativeWidthUnit), - tracks.rows .items.getReference (correctedRow) .getAbsoluteSize (calculation.relativeHeightUnit) }; - } + if (alignContent == AlignContent::spaceEvenly) + { + const auto shift = ((float) rowNumber * (calculation.remainingHeight / float(numberOfRows + 1))); + area.setY (area.getY() + shift); + } - static Rectangle alignCell (Rectangle area, - int columnNumber, int rowNumber, - int numberOfColumns, int numberOfRows, - SizeCalculation calculation, - AlignContent alignContent, - JustifyContent justifyContent) - { - if (alignContent == AlignContent::end) - area.setY (area.getY() + calculation.remainingHeight); + if (justifyContent == JustifyContent::spaceEvenly) + { + const auto shift = ((float) columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1))); + area.setX (area.getX() + shift); + } - if (justifyContent == JustifyContent::end) - area.setX (area.getX() + calculation.remainingWidth); + if (alignContent == AlignContent::spaceAround) + { + const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows); + const auto sidesShift = inbetweenShift / 2; + auto shift = (float) (rowNumber - 1) * inbetweenShift + sidesShift; - if (alignContent == AlignContent::center) - area.setY (area.getY() + calculation.remainingHeight / 2); + area.setY (area.getY() + shift); + } - if (justifyContent == JustifyContent::center) - area.setX (area.getX() + calculation.remainingWidth / 2); + if (justifyContent == JustifyContent::spaceAround) + { + const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns); + const auto sidesShift = inbetweenShift / 2; + auto shift = (float) (columnNumber - 1) * inbetweenShift + sidesShift; - if (alignContent == AlignContent::spaceBetween) - { - const auto shift = ((float) (rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1))); - area.setY (area.getY() + shift); - } + area.setX (area.getX() + shift); + } - if (justifyContent == JustifyContent::spaceBetween) - { - const auto shift = ((float) (columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1))); - area.setX (area.getX() + shift); + return area; } - if (alignContent == AlignContent::spaceEvenly) + template + static Rectangle getAreaBounds (PlacementHelpers::LineRange columnRange, + PlacementHelpers::LineRange rowRange, + const Tracks& tracks, + const SizeCalculation& calculation, + AlignContent alignContent, + JustifyContent justifyContent) { - const auto shift = ((float) rowNumber * (calculation.remainingHeight / float(numberOfRows + 1))); - area.setY (area.getY() + shift); + const auto findAlignedCell = [&] (int column, int row) + { + const auto cell = getCellBounds (column, row, tracks, calculation); + return alignCell (cell, + column, + row, + tracks.columns.items.size(), + tracks.rows.items.size(), + calculation, + alignContent, + justifyContent); + }; + + const auto startCell = findAlignedCell (columnRange.start, rowRange.start); + const auto endCell = findAlignedCell (columnRange.end - 1, rowRange.end - 1); + + const auto horizontalRange = startCell.getHorizontalRange().getUnionWith (endCell.getHorizontalRange()); + const auto verticalRange = startCell.getVerticalRange() .getUnionWith (endCell.getVerticalRange()); + return { horizontalRange.getStart(), verticalRange.getStart(), + horizontalRange.getLength(), verticalRange.getLength() }; } + }; - if (justifyContent == JustifyContent::spaceEvenly) - { - const auto shift = ((float) columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1))); - area.setX (area.getX() + shift); - } + //============================================================================== + struct AutoPlacement + { + using ItemPlacementArray = Array>; - if (alignContent == AlignContent::spaceAround) + //============================================================================== + struct OccupancyPlane { - const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows); - const auto sidesShift = inbetweenShift / 2; - auto shift = (float) (rowNumber - 1) * inbetweenShift + sidesShift; + struct Cell { int column, row; }; - area.setY (area.getY() + shift); - } + OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColumnFirst) + : highestCrossDimension (isColumnFirst ? highestRowToUse : highestColumnToUse), + columnFirst (isColumnFirst) + {} - if (justifyContent == JustifyContent::spaceAround) - { - const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns); - const auto sidesShift = inbetweenShift / 2; - auto shift = (float) (columnNumber - 1) * inbetweenShift + sidesShift; + PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan) + { + for (int i = 0; i < columnSpan; i++) + for (int j = 0; j < rowSpan; j++) + setCell (cell.column + i, cell.row + j); - area.setX (area.getX() + shift); - } + return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } }; + } - return area; - } + PlacementHelpers::LineArea setCell (Cell start, Cell end) + { + return setCell (start, std::abs (end.column - start.column), + std::abs (end.row - start.row)); + } - static Rectangle getAreaBounds (PlacementHelpers::LineRange columnRange, - PlacementHelpers::LineRange rowRange, - const Tracks& tracks, - SizeCalculation calculation, - AlignContent alignContent, - JustifyContent justifyContent, - Px columnGap, Px rowGap) - { - const auto findAlignedCell = [&] (int column, int row) - { - const auto cell = getCellBounds (column, row, tracks, calculation, columnGap, rowGap); - return alignCell (cell, - column, - row, - tracks.columns.items.size(), - tracks.rows.items.size(), - calculation, - alignContent, - justifyContent); - }; + Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan) + { + while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan)) + referenceCell = advance (referenceCell); - const auto startCell = findAlignedCell (columnRange.start, rowRange.start); - const auto endCell = findAlignedCell (columnRange.end - 1, rowRange.end - 1); + return referenceCell; + } - const auto horizontalRange = startCell.getHorizontalRange().getUnionWith (endCell.getHorizontalRange()); - const auto verticalRange = startCell.getVerticalRange() .getUnionWith (endCell.getVerticalRange()); - return { horizontalRange.getStart(), verticalRange.getStart(), - horizontalRange.getLength(), verticalRange.getLength() }; - } -}; + Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber) + { + if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension) + highestCrossDimension = rowNumber + rowSpan; -template -static Array operator+ (const Array& a, const Array& b) -{ - auto copy = a; - copy.addArray (b); - return copy; -} + while (isOccupied (referenceCell, columnSpan, rowSpan) + || (referenceCell.row != rowNumber)) + referenceCell = advance (referenceCell); -//============================================================================== -struct Grid::AutoPlacement -{ - using ItemPlacementArray = Array>; + return referenceCell; + } - //============================================================================== - struct OccupancyPlane - { - struct Cell { int column, row; }; + Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber) + { + if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension) + highestCrossDimension = columnNumber + columnSpan; - OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColumnFirst) - : highestCrossDimension (isColumnFirst ? highestRowToUse : highestColumnToUse), - columnFirst (isColumnFirst) - {} + while (isOccupied (referenceCell, columnSpan, rowSpan) + || (referenceCell.column != columnNumber)) + referenceCell = advance (referenceCell); - PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan) - { - for (int i = 0; i < columnSpan; i++) - for (int j = 0; j < rowSpan; j++) - setCell (cell.column + i, cell.row + j); + return referenceCell; + } - return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } }; - } + void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan) + { + highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan })); + } - PlacementHelpers::LineArea setCell (Cell start, Cell end) - { - return setCell (start, std::abs (end.column - start.column), - std::abs (end.row - start.row)); - } + private: + struct SortableCell + { + int column, row; + bool columnFirst; - Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan) - { - while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan)) - referenceCell = advance (referenceCell); + bool operator< (const SortableCell& other) const + { + if (columnFirst) + { + if (row == other.row) + return column < other.column; - return referenceCell; - } + return row < other.row; + } - Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber) - { - if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension) - highestCrossDimension = rowNumber + rowSpan; + if (row == other.row) + return column < other.column; - while (isOccupied (referenceCell, columnSpan, rowSpan) - || (referenceCell.row != rowNumber)) - referenceCell = advance (referenceCell); + return row < other.row; + } + }; - return referenceCell; - } + void setCell (int column, int row) + { + occupiedCells.insert ({ column, row, columnFirst }); + } - Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber) - { - if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension) - highestCrossDimension = columnNumber + columnSpan; + bool isOccupied (Cell cell) const + { + return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0; + } - while (isOccupied (referenceCell, columnSpan, rowSpan) - || (referenceCell.column != columnNumber)) - referenceCell = advance (referenceCell); + bool isOccupied (Cell cell, int columnSpan, int rowSpan) const + { + for (int i = 0; i < columnSpan; i++) + for (int j = 0; j < rowSpan; j++) + if (isOccupied ({ cell.column + i, cell.row + j })) + return true; - return referenceCell; - } + return false; + } - void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan) - { - highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan })); - } + bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const + { + const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan }); + const auto highestIndexOfGrid = getHighestCrossDimension(); - private: - struct SortableCell - { - int column, row; - bool columnFirst; + return highestIndexOfGrid < highestIndexOfCell; + } - bool operator< (const SortableCell& other) const + int getHighestCrossDimension() const { - if (columnFirst) - { - if (row == other.row) - return column < other.column; + Cell cell { 1, 1 }; - return row < other.row; - } + if (occupiedCells.size() > 0) + cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row }; - if (row == other.row) - return column < other.column; + return std::max (getCrossDimension (cell), highestCrossDimension); + } + + Cell advance (Cell cell) const + { + if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension()) + return fromDimensions (getMainDimension (cell) + 1, 1); - return row < other.row; + return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1); } + + int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; } + int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; } + + Cell fromDimensions (int mainDimension, int crossDimension) const + { + if (columnFirst) + return { mainDimension, crossDimension }; + + return { crossDimension, mainDimension }; + } + + int highestCrossDimension; + bool columnFirst; + std::set occupiedCells; }; - void setCell (int column, int row) + //============================================================================== + static bool isFixed (GridItem::StartAndEndProperty prop) { - occupiedCells.insert ({ column, row, columnFirst }); + return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute(); } - bool isOccupied (Cell cell) const + static bool hasFullyFixedPlacement (const GridItem& item) { - return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0; - } + if (item.area.isNotEmpty()) + return true; - bool isOccupied (Cell cell, int columnSpan, int rowSpan) const - { - for (int i = 0; i < columnSpan; i++) - for (int j = 0; j < rowSpan; j++) - if (isOccupied ({ cell.column + i, cell.row + j })) - return true; + if (isFixed (item.column) && isFixed (item.row)) + return true; return false; } - bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const + static bool hasPartialFixedPlacement (const GridItem& item) { - const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan }); - const auto highestIndexOfGrid = getHighestCrossDimension(); + if (item.area.isNotEmpty()) + return false; + + if (isFixed (item.column) ^ isFixed (item.row)) + return true; - return highestIndexOfGrid < highestIndexOfCell; + return false; } - int getHighestCrossDimension() const + static bool hasAutoPlacement (const GridItem& item) { - Cell cell { 1, 1 }; - - if (occupiedCells.size() > 0) - cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row }; - - return std::max (getCrossDimension (cell), highestCrossDimension); + return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item); } - Cell advance (Cell cell) const + //============================================================================== + static bool hasDenseAutoFlow (AutoFlow autoFlow) { - if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension()) - return fromDimensions (getMainDimension (cell) + 1, 1); - - return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1); + return autoFlow == AutoFlow::columnDense + || autoFlow == AutoFlow::rowDense; } - int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; } - int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; } - - Cell fromDimensions (int mainDimension, int crossDimension) const + static bool isColumnAutoFlow (AutoFlow autoFlow) { - if (columnFirst) - return { mainDimension, crossDimension }; - - return { crossDimension, mainDimension }; + return autoFlow == AutoFlow::column + || autoFlow == AutoFlow::columnDense; } - int highestCrossDimension; - bool columnFirst; - std::set occupiedCells; - }; - - //============================================================================== - static bool isFixed (GridItem::StartAndEndProperty prop) - { - return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute(); - } - - static bool hasFullyFixedPlacement (const GridItem& item) - { - if (item.area.isNotEmpty()) - return true; + //============================================================================== + static int getSpanFromAuto (GridItem::StartAndEndProperty prop) + { + if (prop.end.hasSpan()) + return prop.end.getNumber(); - if (isFixed (item.column) && isFixed (item.row)) - return true; + if (prop.start.hasSpan()) + return prop.start.getNumber(); - return false; - } - - static bool hasPartialFixedPlacement (const GridItem& item) - { - if (item.area.isNotEmpty()) - return false; + return 1; + } - if (isFixed (item.column) ^ isFixed (item.row)) - return true; + //============================================================================== + ItemPlacementArray deduceAllItems (Grid& grid) const + { + const auto namedAreas = PlacementHelpers::deduceNamedAreas (grid.templateAreas); - return false; - } + OccupancyPlane plane (jmax (grid.templateColumns.size() + 1, 2), + jmax (grid.templateRows.size() + 1, 2), + isColumnAutoFlow (grid.autoFlow)); - static bool hasAutoPlacement (const GridItem& item) - { - return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item); - } + ItemPlacementArray itemPlacementArray; + Array sortedItems; - //============================================================================== - static bool hasDenseAutoFlow (AutoFlow autoFlow) - { - return autoFlow == AutoFlow::columnDense - || autoFlow == AutoFlow::rowDense; - } + for (auto& item : grid.items) + sortedItems.add (&item); - static bool isColumnAutoFlow (AutoFlow autoFlow) - { - return autoFlow == AutoFlow::column - || autoFlow == AutoFlow::columnDense; - } + std::stable_sort (sortedItems.begin(), sortedItems.end(), + [] (const GridItem* i1, const GridItem* i2) { return i1->order < i2->order; }); - //============================================================================== - static int getSpanFromAuto (GridItem::StartAndEndProperty prop) - { - if (prop.end.hasSpan()) - return prop.end.getNumber(); + // place fixed items first + for (auto* item : sortedItems) + { + if (hasFullyFixedPlacement (*item)) + { + const auto a = PlacementHelpers::deduceLineArea (*item, grid, namedAreas); + plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end }); + itemPlacementArray.add ({ item, a }); + } + } - if (prop.start.hasSpan()) - return prop.start.getNumber(); + OccupancyPlane::Cell lastInsertionCell = { 1, 1 }; - return 1; - } + for (auto* item : sortedItems) + { + if (hasPartialFixedPlacement (*item)) + { + if (isFixed (item->column)) + { + const auto p = PlacementHelpers::deduceLineRange (item->column, grid.templateColumns); + const auto columnSpan = std::abs (p.start - p.end); + const auto rowSpan = getSpanFromAuto (item->row); - //============================================================================== - ItemPlacementArray deduceAllItems (Grid& grid) const - { - const auto namedAreas = PlacementHelpers::deduceNamedAreas (grid.templateAreas); + const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 } + : lastInsertionCell; + const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start); + const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); + lastInsertionCell = nextAvailableCell; - OccupancyPlane plane (jmax (grid.templateColumns.size() + 1, 2), - jmax (grid.templateRows.size() + 1, 2), - isColumnAutoFlow (grid.autoFlow)); + itemPlacementArray.add ({ item, lineArea }); + } + else if (isFixed (item->row)) + { + const auto p = PlacementHelpers::deduceLineRange (item->row, grid.templateRows); + const auto columnSpan = getSpanFromAuto (item->column); + const auto rowSpan = std::abs (p.start - p.end); - ItemPlacementArray itemPlacementArray; - Array sortedItems; + const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start } + : lastInsertionCell; - for (auto& item : grid.items) - sortedItems.add (&item); + const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start); + const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); - std::stable_sort (sortedItems.begin(), sortedItems.end(), - [] (const GridItem* i1, const GridItem* i2) { return i1->order < i2->order; }); + lastInsertionCell = nextAvailableCell; - // place fixed items first - for (auto* item : sortedItems) - { - if (hasFullyFixedPlacement (*item)) - { - const auto a = PlacementHelpers::deduceLineArea (*item, grid, namedAreas); - plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end }); - itemPlacementArray.add ({ item, a }); + itemPlacementArray.add ({ item, lineArea }); + } + } } - } - OccupancyPlane::Cell lastInsertionCell = { 1, 1 }; + // https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3 + for (auto* item : sortedItems) + if (hasAutoPlacement (*item)) + plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row)); - for (auto* item : sortedItems) - { - if (hasPartialFixedPlacement (*item)) - { - if (isFixed (item->column)) - { - const auto p = PlacementHelpers::deduceLineRange (item->column, grid.templateColumns); - const auto columnSpan = std::abs (p.start - p.end); - const auto rowSpan = getSpanFromAuto (item->row); + lastInsertionCell = { 1, 1 }; - const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 } - : lastInsertionCell; - const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start); - const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); - lastInsertionCell = nextAvailableCell; - - itemPlacementArray.add ({ item, lineArea }); - } - else if (isFixed (item->row)) + for (auto* item : sortedItems) + { + if (hasAutoPlacement (*item)) { - const auto p = PlacementHelpers::deduceLineRange (item->row, grid.templateRows); const auto columnSpan = getSpanFromAuto (item->column); - const auto rowSpan = std::abs (p.start - p.end); - - const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start } - : lastInsertionCell; + const auto rowSpan = getSpanFromAuto (item->row); - const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start); + const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan); const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); - lastInsertionCell = nextAvailableCell; + if (! hasDenseAutoFlow (grid.autoFlow)) + lastInsertionCell = nextAvailableCell; - itemPlacementArray.add ({ item, lineArea }); + itemPlacementArray.add ({ item, lineArea }); } } - } - // https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3 - for (auto* item : sortedItems) - if (hasAutoPlacement (*item)) - plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row)); - - lastInsertionCell = { 1, 1 }; + return itemPlacementArray; + } - for (auto* item : sortedItems) + //============================================================================== + template + static PlacementHelpers::LineRange findFullLineRange (const ItemPlacementArray& items, Accessor&& accessor) { - if (hasAutoPlacement (*item)) - { - const auto columnSpan = getSpanFromAuto (item->column); - const auto rowSpan = getSpanFromAuto (item->row); + if (items.isEmpty()) + return { 1, 1 }; - const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan); - const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan); - - if (! hasDenseAutoFlow (grid.autoFlow)) - lastInsertionCell = nextAvailableCell; + const auto combine = [&accessor] (const auto& acc, const auto& item) + { + const auto newRange = accessor (item); + return PlacementHelpers::LineRange { std::min (acc.start, newRange.start), + std::max (acc.end, newRange.end) }; + }; - itemPlacementArray.add ({ item, lineArea }); - } + return std::accumulate (std::next (items.begin()), items.end(), accessor (*items.begin()), combine); } - return itemPlacementArray; - } + static PlacementHelpers::LineArea findFullLineArea (const ItemPlacementArray& items) + { + return { findFullLineRange (items, [] (const auto& item) { return item.second.column; }), + findFullLineRange (items, [] (const auto& item) { return item.second.row; }) }; + } - //============================================================================== - template - static PlacementHelpers::LineRange findFullLineRange (const ItemPlacementArray& items, Accessor&& accessor) - { - if (items.isEmpty()) - return { 1, 1 }; + template + static Array repeated (int repeats, const Item& item) + { + Array result; + result.insertMultiple (-1, item, repeats); + return result; + } - const auto combine = [&accessor] (const auto& acc, const auto& item) + static Tracks createImplicitTracks (const Grid& grid, const ItemPlacementArray& items) { - const auto newRange = accessor (item); - return PlacementHelpers::LineRange { std::min (acc.start, newRange.start), - std::max (acc.end, newRange.end) }; - }; + const auto fullArea = findFullLineArea (items); - return std::accumulate (std::next (items.begin()), items.end(), accessor (*items.begin()), combine); - } + const auto leadingColumns = std::max (0, 1 - fullArea.column.start); + const auto leadingRows = std::max (0, 1 - fullArea.row.start); - static PlacementHelpers::LineArea findFullLineArea (const ItemPlacementArray& items) - { - return { findFullLineRange (items, [] (const auto& item) { return item.second.column; }), - findFullLineRange (items, [] (const auto& item) { return item.second.row; }) }; - } + const auto trailingColumns = std::max (0, fullArea.column.end - grid.templateColumns.size() - 1); + const auto trailingRows = std::max (0, fullArea.row .end - grid.templateRows .size() - 1); - template - static Array repeated (int repeats, const Item& item) - { - Array result; - result.insertMultiple (-1, item, repeats); - return result; - } + return { { repeated (leadingColumns, grid.autoColumns) + grid.templateColumns + repeated (trailingColumns, grid.autoColumns), + leadingColumns }, + { repeated (leadingRows, grid.autoRows) + grid.templateRows + repeated (trailingRows, grid.autoRows), + leadingRows } }; + } - static Tracks createImplicitTracks (const Grid& grid, const ItemPlacementArray& items) - { - const auto fullArea = findFullLineArea (items); + //============================================================================== + static void applySizeForAutoTracks (Tracks& tracks, const ItemPlacementArray& placements) + { + const auto setSizes = [&placements] (auto& tracksInDirection, const auto& getItem, const auto& getItemSize) + { + auto& array = tracksInDirection.items; - const auto leadingColumns = std::max (0, 1 - fullArea.column.start); - const auto leadingRows = std::max (0, 1 - fullArea.row.start); + for (int index = 0; index < array.size(); ++index) + { + if (array.getReference (index).isAuto()) + { + const auto combiner = [&] (const auto acc, const auto& element) + { + const auto item = getItem (element.second); + const auto isNotSpan = std::abs (item.end - item.start) <= 1; + return isNotSpan && item.start == index + 1 - tracksInDirection.numImplicitLeading + ? std::max (acc, getItemSize (*element.first)) + : acc; + }; + + array.getReference (index).size = std::accumulate (placements.begin(), placements.end(), 0.0f, combiner); + } + } + }; - const auto trailingColumns = std::max (0, fullArea.column.end - grid.templateColumns.size() - 1); - const auto trailingRows = std::max (0, fullArea.row .end - grid.templateRows .size() - 1); + setSizes (tracks.rows, + [] (const auto& i) { return i.row; }, + [] (const auto& i) { return i.height + i.margin.top + i.margin.bottom; }); - return { { repeated (leadingColumns, grid.autoColumns) + grid.templateColumns + repeated (trailingColumns, grid.autoColumns), - leadingColumns }, - { repeated (leadingRows, grid.autoRows) + grid.templateRows + repeated (trailingRows, grid.autoRows), - leadingRows } }; - } + setSizes (tracks.columns, + [] (const auto& i) { return i.column; }, + [] (const auto& i) { return i.width + i.margin.left + i.margin.right; }); + } + }; //============================================================================== - static void applySizeForAutoTracks (Tracks& tracks, const ItemPlacementArray& placements) + struct BoxAlignment { - const auto setSizes = [&placements] (auto& tracksInDirection, const auto& getItem, const auto& getItemSize) + static Rectangle alignItem (const GridItem& item, const Grid& grid, Rectangle area) { - auto& array = tracksInDirection.items; + // if item align is auto, inherit value from grid + const auto alignType = item.alignSelf == GridItem::AlignSelf::autoValue + ? grid.alignItems + : static_cast (item.alignSelf); - for (int index = 0; index < array.size(); ++index) - { - if (array.getReference (index).isAuto()) - { - const auto combiner = [&] (const auto acc, const auto& element) - { - const auto item = getItem (element.second); - const auto isNotSpan = std::abs (item.end - item.start) <= 1; - return isNotSpan && item.start == index + 1 - tracksInDirection.numImplicitLeading - ? std::max (acc, getItemSize (*element.first)) - : acc; - }; - - array.getReference (index).size = std::accumulate (placements.begin(), placements.end(), 0.0f, combiner); - } - } - }; + const auto justifyType = item.justifySelf == GridItem::JustifySelf::autoValue + ? grid.justifyItems + : static_cast (item.justifySelf); - setSizes (tracks.rows, - [] (const auto& i) { return i.row; }, - [] (const auto& i) { return i.height + i.margin.top + i.margin.bottom; }); + // subtract margin from area + area = BorderSize (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right) + .subtractedFrom (area); - setSizes (tracks.columns, - [] (const auto& i) { return i.column; }, - [] (const auto& i) { return i.width + i.margin.left + i.margin.right; }); - } -}; + // align and justify + auto r = area; -//============================================================================== -struct Grid::BoxAlignment -{ - static Rectangle alignItem (const GridItem& item, - const Grid& grid, - Rectangle area) - { - // if item align is auto, inherit value from grid - const auto alignType = item.alignSelf == GridItem::AlignSelf::autoValue - ? grid.alignItems - : static_cast (item.alignSelf); - - const auto justifyType = item.justifySelf == GridItem::JustifySelf::autoValue - ? grid.justifyItems - : static_cast (item.justifySelf); - - // subtract margin from area - area = BorderSize (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right) - .subtractedFrom (area); - - // align and justify - auto r = area; - - if (item.width != (float) GridItem::notAssigned) r.setWidth (item.width); - if (item.height != (float) GridItem::notAssigned) r.setHeight (item.height); - if (item.maxWidth != (float) GridItem::notAssigned) r.setWidth (jmin (item.maxWidth, r.getWidth())); - if (item.minWidth > 0.0f) r.setWidth (jmax (item.minWidth, r.getWidth())); - if (item.maxHeight != (float) GridItem::notAssigned) r.setHeight (jmin (item.maxHeight, r.getHeight())); - if (item.minHeight > 0.0f) r.setHeight (jmax (item.minHeight, r.getHeight())); - - if (alignType == AlignItems::start && justifyType == JustifyItems::start) - return r; + if (! approximatelyEqual (item.width, (float) GridItem::notAssigned)) r.setWidth (item.width); + if (! approximatelyEqual (item.height, (float) GridItem::notAssigned)) r.setHeight (item.height); + if (! approximatelyEqual (item.maxWidth, (float) GridItem::notAssigned)) r.setWidth (jmin (item.maxWidth, r.getWidth())); + if (item.minWidth > 0.0f) r.setWidth (jmax (item.minWidth, r.getWidth())); + if (! approximatelyEqual (item.maxHeight, (float) GridItem::notAssigned)) r.setHeight (jmin (item.maxHeight, r.getHeight())); + if (item.minHeight > 0.0f) r.setHeight (jmax (item.minHeight, r.getHeight())); - if (alignType == AlignItems::end) r.setY (r.getY() + (area.getHeight() - r.getHeight())); - if (justifyType == JustifyItems::end) r.setX (r.getX() + (area.getWidth() - r.getWidth())); - if (alignType == AlignItems::center) r.setCentre (r.getCentreX(), area.getCentreY()); - if (justifyType == JustifyItems::center) r.setCentre (area.getCentreX(), r.getCentreY()); + if (alignType == AlignItems::start && justifyType == JustifyItems::start) + return r; + + if (alignType == AlignItems::end) r.setY (r.getY() + (area.getHeight() - r.getHeight())); + if (justifyType == JustifyItems::end) r.setX (r.getX() + (area.getWidth() - r.getWidth())); + if (alignType == AlignItems::center) r.setCentre (r.getCentreX(), area.getCentreY()); + if (justifyType == JustifyItems::center) r.setCentre (area.getCentreX(), r.getCentreY()); + + return r; + } + }; - return r; - } }; //============================================================================== @@ -1004,7 +1088,7 @@ Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Px sizeInPixels, c } Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept - : TrackInfo (startLineNameToUse, fractionOfFreeSpace) + : TrackInfo (startLineNameToUse, fractionOfFreeSpace) { endLineName = endLineNameToUse; } @@ -1017,37 +1101,57 @@ float Grid::TrackInfo::getAbsoluteSize (float relativeFractionalUnit) const //============================================================================== void Grid::performLayout (Rectangle targetArea) { - const auto itemsAndAreas = AutoPlacement().deduceAllItems (*this); + const auto itemsAndAreas = Helpers::AutoPlacement().deduceAllItems (*this); - auto implicitTracks = AutoPlacement::createImplicitTracks (*this, itemsAndAreas); + auto implicitTracks = Helpers::AutoPlacement::createImplicitTracks (*this, itemsAndAreas); - AutoPlacement::applySizeForAutoTracks (implicitTracks, itemsAndAreas); + Helpers::AutoPlacement::applySizeForAutoTracks (implicitTracks, itemsAndAreas); - SizeCalculation calculation; - calculation.computeSizes (targetArea.toFloat().getWidth(), - targetArea.toFloat().getHeight(), - columnGap, - rowGap, - implicitTracks); + Helpers::SizeCalculation calculation; + Helpers::SizeCalculation roundedCalculation; - for (auto& itemAndArea : itemsAndAreas) + const auto doComputeSizes = [&] (auto& sizeCalculation) { - const auto a = itemAndArea.second; - const auto areaBounds = PlacementHelpers::getAreaBounds (a.column, - a.row, - implicitTracks, - calculation, - alignContent, - justifyContent, - columnGap, - rowGap); + sizeCalculation.computeSizes (targetArea.toFloat().getWidth(), + targetArea.toFloat().getHeight(), + columnGap, + rowGap, + implicitTracks); + }; + doComputeSizes (calculation); + doComputeSizes (roundedCalculation); + + for (auto& itemAndArea : itemsAndAreas) + { auto* item = itemAndArea.first; - item->currentBounds = BoxAlignment::alignItem (*item, *this, areaBounds) - + targetArea.toFloat().getPosition(); + + const auto getBounds = [&] (const auto& sizeCalculation) + { + const auto a = itemAndArea.second; + + const auto areaBounds = Helpers::PlacementHelpers::getAreaBounds (a.column, + a.row, + implicitTracks, + sizeCalculation, + alignContent, + justifyContent); + + const auto rounded = [&] (auto rect) -> decltype (rect) + { + return { sizeCalculation.roundingFunction (rect.getX()), + sizeCalculation.roundingFunction (rect.getY()), + sizeCalculation.roundingFunction (rect.getWidth()), + sizeCalculation.roundingFunction (rect.getHeight()) }; + }; + + return rounded (Helpers::BoxAlignment::alignItem (*item, *this, areaBounds)); + }; + + item->currentBounds = getBounds (calculation) + targetArea.toFloat().getPosition(); if (auto* c = item->associatedComponent) - c->setBounds (item->currentBounds.getSmallestIntegerContainer()); + c->setBounds (getBounds (roundedCalculation).toNearestIntEdges() + targetArea.getPosition()); } } @@ -1355,6 +1459,177 @@ struct GridTests : public UnitTest expect (grid.items[1].currentBounds == Rect (420.0f, 70.0f, 60.0f, 70.0f)); expect (grid.items[2].currentBounds == Rect (200.0f, 330.0f, 200.0f, 70.0f)); } + + { + beginTest ("Items with specified sizes should translate to correctly rounded Component dimensions"); + + static constexpr int targetSize = 100; + + juce::Component component; + juce::GridItem item { component }; + item.alignSelf = juce::GridItem::AlignSelf::center; + item.justifySelf = juce::GridItem::JustifySelf::center; + item.width = (float) targetSize; + item.height = (float) targetSize; + + juce::Grid grid; + grid.templateColumns = { juce::Grid::Fr { 1 } }; + grid.templateRows = { juce::Grid::Fr { 1 } }; + grid.items = { item }; + + for (int totalSize = 100 - 20; totalSize < 100 + 20; ++totalSize) + { + Rectangle bounds { 0, 0, totalSize, totalSize }; + grid.performLayout (bounds); + + expectEquals (component.getWidth(), targetSize); + expectEquals (component.getHeight(), targetSize); + } + } + + { + beginTest ("Track sizes specified in Px should translate to correctly rounded Component dimensions"); + + static constexpr int targetSize = 100; + + juce::Component component; + juce::GridItem item { component }; + item.alignSelf = juce::GridItem::AlignSelf::center; + item.justifySelf = juce::GridItem::JustifySelf::center; + item.setArea (1, 3); + + juce::Grid grid; + grid.templateColumns = { juce::Grid::Fr { 1 }, + juce::Grid::Fr { 1 }, + juce::Grid::Px { targetSize }, + juce::Grid::Fr { 1 } }; + grid.templateRows = { juce::Grid::Fr { 1 } }; + grid.items = { item }; + + for (int totalSize = 100 - 20; totalSize < 100 + 20; ++totalSize) + { + Rectangle bounds { 0, 0, totalSize, totalSize }; + grid.performLayout (bounds); + + expectEquals (component.getWidth(), targetSize); + } + } + + { + beginTest ("Evaluate invariants on randomised Grid layouts"); + + struct Solution + { + Grid grid; + std::deque components; + int absoluteWidth; + Rectangle bounds; + }; + + auto createSolution = [this] (int numColumns, + float probabilityOfFractionalColumn, + Rectangle bounds) -> Solution + { + auto random = getRandom(); + + Grid grid; + grid.templateRows = { Grid::Fr { 1 } }; + + // Ensuring that the sum of absolute item widths never exceed total width + const auto widthOfAbsolute = (int) ((float) bounds.getWidth() / (float) (numColumns + 1)); + + for (int i = 0; i < numColumns; ++i) + { + if (random.nextFloat() < probabilityOfFractionalColumn) + grid.templateColumns.add (Grid::Fr { 1 }); + else + grid.templateColumns.add (Grid::Px { widthOfAbsolute }); + } + + std::deque itemComponents (static_cast (grid.templateColumns.size())); + + for (auto& c : itemComponents) + grid.items.add (GridItem { c }); + + grid.performLayout (bounds); + + return { std::move (grid), std::move (itemComponents), widthOfAbsolute, bounds }; + }; + + const auto getFractionalComponentWidths = [] (const Solution& solution) + { + std::vector result; + + for (int i = 0; i < solution.grid.templateColumns.size(); ++i) + if (solution.grid.templateColumns[i].isFractional()) + result.push_back (solution.components[(size_t) i].getWidth()); + + return result; + }; + + const auto getAbsoluteComponentWidths = [] (const Solution& solution) + { + std::vector result; + + for (int i = 0; i < solution.grid.templateColumns.size(); ++i) + if (! solution.grid.templateColumns[i].isFractional()) + result.push_back (solution.components[(size_t) i].getWidth()); + + return result; + }; + + const auto evaluateInvariants = [&] (const Solution& solution) + { + const auto fractionalWidths = getFractionalComponentWidths (solution); + + if (! fractionalWidths.empty()) + { + const auto [min, max] = std::minmax_element (fractionalWidths.begin(), + fractionalWidths.end()); + expectLessOrEqual (*max - *min, 1, "Fr { 1 } items are expected to share the " + "rounding errors equally and hence couldn't " + "deviate in size by more than 1 px"); + } + + const auto absoluteWidths = getAbsoluteComponentWidths (solution); + + for (const auto& w : absoluteWidths) + expectEquals (w, solution.absoluteWidth, "Sizes specified in absolute dimensions should " + "be preserved"); + + Rectangle unionOfComponentBounds; + + for (const auto& c : solution.components) + unionOfComponentBounds = unionOfComponentBounds.getUnion (c.getBoundsInParent()); + + if ((size_t) solution.grid.templateColumns.size() == absoluteWidths.size()) + expect (solution.bounds.contains (unionOfComponentBounds), "Non-oversized absolute Components " + "should never be placed outside the " + "provided bounds."); + else + expect (unionOfComponentBounds == solution.bounds, "With fractional items, positioned items " + "should cover the provided bounds exactly"); + }; + + const auto knownPreviousBad = createSolution (5, 1.0f, Rectangle { 0, 0, 600, 200 }.reduced (16)); + evaluateInvariants (knownPreviousBad); + + auto random = getRandom(); + + for (int i = 0; i < 1000; ++i) + { + const auto numColumns = random.nextInt (Range { 1, 26 }); + const auto probabilityOfFractionalColumn = random.nextFloat(); + const auto bounds = Rectangle { random.nextInt (Range { 0, 3 }), + random.nextInt (Range { 0, 3 }), + random.nextInt (Range { 300, 1200 }), + random.nextInt (Range { 100, 500 }) } + .reduced (random.nextInt (Range { 0, 16 })); + + const auto randomSolution = createSolution (numColumns, probabilityOfFractionalColumn, bounds); + evaluateInvariants (randomSolution); + } + } } }; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.h index ea87d4b..3cf6b5e 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_Grid.h @@ -156,9 +156,6 @@ class JUCE_API Grid final /** Creates an empty Grid container with default parameters. */ Grid() = default; - /** Destructor */ - ~Grid() noexcept = default; - //============================================================================== /** Specifies the alignment of content inside the items along the rows. */ JustifyItems justifyItems = JustifyItems::stretch; @@ -216,10 +213,7 @@ class JUCE_API Grid final private: //============================================================================== - struct SizeCalculation; - struct PlacementHelpers; - struct AutoPlacement; - struct BoxAlignment; + struct Helpers; }; constexpr Grid::Px operator"" _px (long double px) { return Grid::Px { px }; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_GroupComponent.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_GroupComponent.h index 9d9737e..bbd04a3 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_GroupComponent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_GroupComponent.h @@ -98,10 +98,10 @@ class JUCE_API GroupComponent : public Component void enablementChanged() override; /** @internal */ void colourChanged() override; - -private: + /** @internal */ std::unique_ptr createAccessibilityHandler() override; +private: String text; Justification justification; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.cpp index 6d024e3..ba36692 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.cpp @@ -125,6 +125,10 @@ void ResizableBorderComponent::mouseDown (const MouseEvent& e) originalBounds = component->getBounds(); + if (auto* peer = component->getPeer()) + if (&peer->getComponent() == component) + peer->startHostManagedResize (peer->globalToLocal (localPointToGlobal (e.getPosition())), mouseZone); + if (constrainer != nullptr) constrainer->resizeStart(); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h index 1ea3000..1694a08 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.h @@ -125,7 +125,7 @@ class JUCE_API ResizableBorderComponent : public Component /** Returns an appropriate mouse-cursor for this resize zone. */ MouseCursor getMouseCursor() const noexcept; - /** Returns true if dragging this zone will move the enire object without resizing it. */ + /** Returns true if dragging this zone will move the entire object without resizing it. */ bool isDraggingWholeObject() const noexcept { return zone == centre; } /** Returns true if dragging this zone will move the object's left edge. */ bool isDraggingLeftEdge() const noexcept { return (zone & left) != 0; } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.cpp index 4c37514..87b4816 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableCornerComponent.cpp @@ -45,7 +45,7 @@ void ResizableCornerComponent::paint (Graphics& g) isMouseButtonDown()); } -void ResizableCornerComponent::mouseDown (const MouseEvent&) +void ResizableCornerComponent::mouseDown (const MouseEvent& e) { if (component == nullptr) { @@ -55,6 +55,13 @@ void ResizableCornerComponent::mouseDown (const MouseEvent&) originalBounds = component->getBounds(); + using Zone = ResizableBorderComponent::Zone; + const Zone zone { Zone::bottom | Zone::right }; + + if (auto* peer = component->getPeer()) + if (&peer->getComponent() == component) + peer->startHostManagedResize (peer->globalToLocal (localPointToGlobal (e.getPosition())), zone); + if (constrainer != nullptr) constrainer->resizeStart(); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.cpp index 9d02261..d69372a 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableEdgeComponent.cpp @@ -52,7 +52,7 @@ void ResizableEdgeComponent::paint (Graphics& g) isMouseOver(), isMouseButtonDown()); } -void ResizableEdgeComponent::mouseDown (const MouseEvent&) +void ResizableEdgeComponent::mouseDown (const MouseEvent& e) { if (component == nullptr) { @@ -62,6 +62,25 @@ void ResizableEdgeComponent::mouseDown (const MouseEvent&) originalBounds = component->getBounds(); + using Zone = ResizableBorderComponent::Zone; + + const Zone zone { [&] + { + switch (edge) + { + case Edge::leftEdge: return Zone::left; + case Edge::rightEdge: return Zone::right; + case Edge::topEdge: return Zone::top; + case Edge::bottomEdge: return Zone::bottom; + } + + return Zone::centre; + }() }; + + if (auto* peer = component->getPeer()) + if (&peer->getComponent() == component) + peer->startHostManagedResize (peer->globalToLocal (localPointToGlobal (e.getPosition())), zone); + if (constrainer != nullptr) constrainer->resizeStart(); } diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ScrollBar.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ScrollBar.h index 8a82817..bb06dec 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ScrollBar.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_ScrollBar.h @@ -414,6 +414,8 @@ class JUCE_API ScrollBar : public Component, void parentHierarchyChanged() override; /** @internal */ void setVisible (bool) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -427,7 +429,6 @@ class JUCE_API ScrollBar : public Component, std::unique_ptr upButton, downButton; ListenerList listeners; - std::unique_ptr createAccessibilityHandler() override; void handleAsyncUpdate() override; void updateThumbPosition(); void timerCallback() override; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_SidePanel.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_SidePanel.h index 0f98b71..0c70f67 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_SidePanel.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_SidePanel.h @@ -195,6 +195,8 @@ class SidePanel : public Component, void mouseDrag (const MouseEvent&) override; /** @internal */ void mouseUp (const MouseEvent&) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -221,7 +223,6 @@ class SidePanel : public Component, bool shouldShowDismissButton = true; //============================================================================== - std::unique_ptr createAccessibilityHandler() override; void lookAndFeelChanged() override; void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; void changeListenerCallback (ChangeBroadcaster*) override; diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_StretchableLayoutManager.cpp b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_StretchableLayoutManager.cpp index f3e22b1..d7e5850 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_StretchableLayoutManager.cpp +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_StretchableLayoutManager.cpp @@ -336,7 +336,7 @@ int StretchableLayoutManager::sizeToRealSize (double size, int totalSpace) if (size < 0) size *= -totalSpace; - return roundToInt (size); + return roundToInt (jmax (1.0, size)); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h index 2354f45..6f71ddd 100644 --- a/JuceLibraryCode/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h +++ b/JuceLibraryCode/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h @@ -334,6 +334,8 @@ class JUCE_API TabbedButtonBar : public Component, void resized() override; /** @internal */ void lookAndFeelChanged() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; protected: //============================================================================== @@ -362,7 +364,6 @@ class JUCE_API TabbedButtonBar : public Component, std::unique_ptr behindFrontTab; std::unique_ptr