From aded817b77284cc9ee2bd6a7585a7e6aa1718b09 Mon Sep 17 00:00:00 2001 From: Geert Bevin Date: Sat, 2 Jan 2021 12:06:33 -0500 Subject: [PATCH] Updated to JUCE 6.0.5. Now has a Universal build target for macOS. --- .../MacOSX/sendmidi.xcodeproj/project.pbxproj | 33 +- .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../sendmidi_ConsoleApp.vcxproj | 36 +- .../sendmidi_ConsoleApp.vcxproj.filters | 69 + JuceLibraryCode/AppConfig.h | 6 +- .../audio_play_head/juce_AudioPlayHead.h | 33 +- .../buffers/juce_AudioDataConverters.cpp | 48 +- .../buffers/juce_AudioSampleBuffer.h | 14 +- .../juce_audio_basics/juce_audio_basics.h | 2 +- .../midi/juce_MidiBuffer.cpp | 5 +- .../juce_audio_basics/midi/juce_MidiFile.cpp | 512 +++++-- .../midi/juce_MidiKeyboardState.h | 2 +- .../midi/juce_MidiMessage.cpp | 215 ++- .../juce_audio_basics/midi/juce_MidiMessage.h | 40 +- .../mpe/juce_MPEInstrument.cpp | 38 +- .../mpe/juce_MPEInstrument.h | 2 + .../audio_io/juce_AudioDeviceManager.cpp | 9 +- .../audio_io/juce_AudioIODeviceType.cpp | 101 +- .../audio_io/juce_AudioIODeviceType.h | 7 +- .../juce_audio_devices/juce_audio_devices.cpp | 151 +- .../juce_audio_devices/juce_audio_devices.h | 29 +- .../midi_io/juce_MidiDevices.h | 13 +- .../ump/juce_UMPBytestreamInputHandler.h | 120 ++ .../midi_io/ump/juce_UMPConversion.h | 326 +++++ .../midi_io/ump/juce_UMPConverters.h | 139 ++ .../midi_io/ump/juce_UMPDispatcher.h | 192 +++ .../midi_io/ump/juce_UMPFactory.h | 527 +++++++ .../midi_io/ump/juce_UMPIterator.h | 126 ++ .../ump/juce_UMPMidi1ToBytestreamTranslator.h | 213 +++ .../juce_UMPMidi1ToMidi2DefaultTranslator.cpp | 195 +++ .../juce_UMPMidi1ToMidi2DefaultTranslator.h | 187 +++ .../midi_io/ump/juce_UMPProtocols.h | 44 + .../midi_io/ump/juce_UMPReceiver.h | 40 + .../midi_io/ump/juce_UMPSysEx7.cpp | 53 + .../midi_io/ump/juce_UMPSysEx7.h | 66 + .../midi_io/ump/juce_UMPTests.cpp | 1020 +++++++++++++ .../midi_io/ump/juce_UMPU32InputHandler.h | 131 ++ .../midi_io/ump/juce_UMPUtils.cpp | 59 + .../midi_io/ump/juce_UMPUtils.h | 104 ++ .../midi_io/ump/juce_UMPView.cpp | 35 + .../midi_io/ump/juce_UMPView.h | 88 ++ .../midi_io/ump/juce_UMPacket.h | 187 +++ .../midi_io/ump/juce_UMPackets.h | 92 ++ .../native/juce_android_Audio.cpp | 15 - .../native/juce_android_Midi.cpp | 41 +- .../native/juce_android_Oboe.cpp | 25 +- .../native/juce_android_OpenSL.cpp | 5 - .../native/juce_ios_Audio.cpp | 6 - .../native/juce_linux_ALSA.cpp | 5 - .../native/juce_linux_Bela.cpp | 55 +- .../native/juce_linux_JackAudio.cpp | 368 ++--- .../native/juce_linux_Midi.cpp | 46 +- .../native/juce_mac_CoreAudio.cpp | 35 +- .../native/juce_mac_CoreMidi.cpp | 723 ---------- .../native/juce_mac_CoreMidi.mm | 1269 +++++++++++++++++ .../native/juce_win32_ASIO.cpp | 21 +- .../native/juce_win32_DirectSound.cpp | 6 - .../native/juce_win32_Midi.cpp | 78 +- .../native/juce_win32_WASAPI.cpp | 449 ++++-- .../native/oboe/src/common/Trace.h | 2 +- .../juce_core/containers/juce_ArrayBase.cpp | 8 +- .../juce_core/containers/juce_Variant.cpp | 4 +- JuceLibraryCode/modules/juce_core/juce_core.h | 4 +- .../modules/juce_core/memory/juce_Memory.h | 68 +- .../misc/juce_ConsoleApplication.cpp | 4 +- .../native/juce_BasicNativeHeaders.h | 2 +- .../juce_android_RuntimePermissions.cpp | 20 +- .../native/juce_linux_SystemStats.cpp | 4 +- .../juce_core/native/juce_mac_Files.mm | 15 +- .../juce_core/native/juce_mac_SystemStats.mm | 35 +- .../juce_core/native/juce_osx_ObjCHelpers.h | 75 +- .../juce_core/native/juce_posix_IPAddress.h | 8 +- .../juce_core/native/juce_posix_NamedPipe.cpp | 18 +- .../juce_core/native/juce_win32_ComSmartPtr.h | 11 + .../juce_core/native/juce_win32_Files.cpp | 43 +- .../native/juce_win32_SystemStats.cpp | 3 - .../juce_core/native/juce_win32_Threads.cpp | 2 +- .../juce_core/network/juce_NamedPipe.cpp | 19 +- .../juce_core/system/juce_PlatformDefs.h | 4 +- .../juce_core/system/juce_StandardHeader.h | 4 +- .../juce_core/system/juce_SystemStats.h | 5 +- .../modules/juce_core/text/juce_String.cpp | 12 +- .../juce_core/text/juce_StringArray.cpp | 5 + .../modules/juce_core/text/juce_StringArray.h | 6 + .../juce_core/text/juce_StringPairArray.cpp | 142 ++ .../juce_core/text/juce_StringPairArray.h | 7 + .../juce_data_structures.h | 2 +- .../juce_InterprocessConnection.cpp | 149 +- .../juce_InterprocessConnection.h | 28 +- .../juce_InterprocessConnectionServer.cpp | 2 +- .../modules/juce_events/juce_events.cpp | 4 - .../modules/juce_events/juce_events.h | 2 +- .../native/juce_mac_MessageManager.mm | 33 +- .../modules/juce_events/timers/juce_Timer.cpp | 8 + 94 files changed, 7554 insertions(+), 1668 deletions(-) delete mode 100644 Builds/MacOSX/sendmidi.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h create mode 100644 JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h delete mode 100644 JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp create mode 100644 JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm mode change 100644 => 100755 JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp diff --git a/Builds/MacOSX/sendmidi.xcodeproj/project.pbxproj b/Builds/MacOSX/sendmidi.xcodeproj/project.pbxproj index e09ed00..4a31a20 100644 --- a/Builds/MacOSX/sendmidi.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/sendmidi.xcodeproj/project.pbxproj @@ -29,6 +29,10 @@ isa = PBXBuildFile; fileRef = BF812B73A42627ED8AD203EA; }; + 8C315CE0BB161E8F64351690 = { + isa = PBXBuildFile; + fileRef = 07349D8481B1ED49EE53EB64; + }; 2B50A8D000C0D831B3F9702E = { isa = PBXBuildFile; fileRef = D2BC5390A6F33C796C125CA6; @@ -61,6 +65,13 @@ isa = PBXBuildFile; fileRef = F0AC19E3023CA8E85D590A1B; }; + 07349D8481B1ED49EE53EB64 = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = Foundation.framework; + path = System/Library/Frameworks/Foundation.framework; + sourceTree = SDKROOT; + }; 08ACADE6BD3415FD33BA447B = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; @@ -266,6 +277,7 @@ 8C841A693EA83A57B62FDF16, 39BBB61E6B2796DB8C72B46D, BF812B73A42627ED8AD203EA, + 07349D8481B1ED49EE53EB64, D2BC5390A6F33C796C125CA6, ); name = Frameworks; @@ -321,15 +333,20 @@ ); GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ( - "../../JuceLibraryCode", - "../../JuceLibraryCode/modules", + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", "$(inherited)", ); INSTALL_PATH = "/usr/bin"; MACOSX_DEPLOYMENT_TARGET = 10.7; + MTL_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", + ); PRODUCT_BUNDLE_IDENTIFIER = com.uwyn.sendmidi; PRODUCT_NAME = "sendmidi"; USE_HEADERMAP = NO; + VALID_ARCHS = "i386 x86_64 arm64 arm64e"; }; name = Debug; }; @@ -362,16 +379,21 @@ ); GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ( - "../../JuceLibraryCode", - "../../JuceLibraryCode/modules", + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", "$(inherited)", ); INSTALL_PATH = "/usr/bin"; LLVM_LTO = YES; MACOSX_DEPLOYMENT_TARGET = 10.7; + MTL_HEADER_SEARCH_PATHS = ( + "$(SRCROOT)/../../JuceLibraryCode", + "$(SRCROOT)/../../JuceLibraryCode/modules", + ); PRODUCT_BUNDLE_IDENTIFIER = com.uwyn.sendmidi; PRODUCT_NAME = "sendmidi"; USE_HEADERMAP = NO; + VALID_ARCHS = "i386 x86_64 arm64 arm64e"; }; name = Release; }; @@ -520,6 +542,7 @@ 8E8410A6B527CB8A75830C3C, 5DC8BD2E0E589697B3A66001, 1CD0216F853135DDC28315F0, + 8C315CE0BB161E8F64351690, 2B50A8D000C0D831B3F9702E, ); runOnlyForDeploymentPostprocessing = 0; @@ -542,7 +565,7 @@ 6323F87A9BE44E1C4257AD90 = { isa = PBXProject; buildConfigurationList = 9B7F79ECBA6BB3FD7D9F4A1B; - attributes = { LastUpgradeCheck = 1100; ORGANIZATIONNAME = "Uwyn"; }; + attributes = { LastUpgradeCheck = 1230; ORGANIZATIONNAME = "Uwyn"; }; compatibilityVersion = "Xcode 3.2"; hasScannedForEncodings = 0; mainGroup = CA1CE00FEEC53FB42E8CDD06; diff --git a/Builds/MacOSX/sendmidi.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Builds/MacOSX/sendmidi.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 949b678..0000000 --- a/Builds/MacOSX/sendmidi.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - BuildSystemType - Original - - diff --git a/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj b/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj index 2b56396..72d518a 100644 --- a/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj +++ b/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj @@ -67,7 +67,7 @@ _CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=1.0.15;JUCE_APP_VERSION_HEX=0x1000f;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreadedDebugDLL true - + NotUsing $(IntDir)\ $(IntDir)\ $(IntDir)\ @@ -109,7 +109,7 @@ _CRT_SECURE_NO_WARNINGS;_CONSOLE;WIN32;_WINDOWS;NDEBUG;JUCER_VS2015_78A5022=1;JUCE_APP_VERSION=1.0.15;JUCE_APP_VERSION_HEX=0x1000f;JucePlugin_Build_VST=0;JucePlugin_Build_VST3=0;JucePlugin_Build_AU=0;JucePlugin_Build_AUv3=0;JucePlugin_Build_RTAS=0;JucePlugin_Build_AAX=0;JucePlugin_Build_Standalone=0;JucePlugin_Build_Unity=0;%(PreprocessorDefinitions) MultiThreadedDLL true - + NotUsing $(IntDir)\ $(IntDir)\ $(IntDir)\ @@ -132,6 +132,7 @@ true true true + UseLinkTimeCodeGeneration true @@ -252,6 +253,21 @@ true + + true + + + true + + + true + + + true + + + true + true @@ -901,6 +917,22 @@ + + + + + + + + + + + + + + + + diff --git a/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj.filters b/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj.filters index 7dfef32..302557d 100644 --- a/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj.filters +++ b/Builds/VisualStudio2015/sendmidi_ConsoleApp.vcxproj.filters @@ -38,6 +38,9 @@ {BF23FC10-1D57-2A9B-706F-6DD8A7B593D4} + + {386862D5-4DCC-A4B3-5642-60A201E303EF} + {092EFC17-7C95-7E04-0ACA-0D61A462EE81} @@ -295,6 +298,21 @@ JUCE Modules\juce_audio_devices\audio_io + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + JUCE Modules\juce_audio_devices\midi_io @@ -481,6 +499,9 @@ JUCE Modules\juce_audio_devices\native + + JUCE Modules\juce_audio_devices\native + JUCE Modules\juce_audio_devices\native @@ -1077,6 +1098,54 @@ JUCE Modules\juce_audio_devices\audio_io + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + + + JUCE Modules\juce_audio_devices\midi_io\ump + JUCE Modules\juce_audio_devices\midi_io diff --git a/JuceLibraryCode/AppConfig.h b/JuceLibraryCode/AppConfig.h index 796e7b6..c082b68 100644 --- a/JuceLibraryCode/AppConfig.h +++ b/JuceLibraryCode/AppConfig.h @@ -41,7 +41,7 @@ #define JUCE_USE_DARK_SPLASH_SCREEN 1 -#define JUCE_PROJUCER_VERSION 0x60001 +#define JUCE_PROJUCER_VERSION 0x60005 //============================================================================== #define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 @@ -67,10 +67,6 @@ //#define JUCE_WASAPI 1 #endif -#ifndef JUCE_WASAPI_EXCLUSIVE - //#define JUCE_WASAPI_EXCLUSIVE 0 -#endif - #ifndef JUCE_DIRECTSOUND //#define JUCE_DIRECTSOUND 1 #endif diff --git a/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h b/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h index 13d6082..448aeb4 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -122,10 +122,35 @@ class JUCE_API AudioPlayHead bool isLooping; //============================================================================== - bool operator== (const CurrentPositionInfo& other) const noexcept; - bool operator!= (const CurrentPositionInfo& other) const noexcept; - - void resetToDefault(); + bool operator== (const CurrentPositionInfo& other) const noexcept + { + return timeInSamples == other.timeInSamples + && ppqPosition == other.ppqPosition + && editOriginTime == other.editOriginTime + && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart + && frameRate == other.frameRate + && isPlaying == other.isPlaying + && isRecording == other.isRecording + && bpm == other.bpm + && timeSigNumerator == other.timeSigNumerator + && timeSigDenominator == other.timeSigDenominator + && ppqLoopStart == other.ppqLoopStart + && ppqLoopEnd == other.ppqLoopEnd + && isLooping == other.isLooping; + } + + bool operator!= (const CurrentPositionInfo& other) const noexcept + { + return ! operator== (other); + } + + void resetToDefault() + { + zerostruct (*this); + timeSigNumerator = 4; + timeSigDenominator = 4; + bpm = 120; + } }; //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp index dfc4e1c..82dc3c2 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp @@ -32,7 +32,7 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest { for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -43,7 +43,7 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } @@ -57,7 +57,7 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest { for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -68,7 +68,7 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } @@ -132,7 +132,7 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest { for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -143,7 +143,7 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } @@ -157,7 +157,7 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest { for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); intData += destBytesPerSample; } } @@ -168,7 +168,7 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest for (int i = numSamples; --i >= 0;) { intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + *unalignedPointerCast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); } } } @@ -181,10 +181,10 @@ void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* de for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (d) = source[i]; + *unalignedPointerCast (d) = source[i]; #if JUCE_BIG_ENDIAN - *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); + *unalignedPointerCast (d) = ByteOrder::swap (*unalignedPointerCast (d)); #endif d += destBytesPerSample; @@ -199,10 +199,10 @@ void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* de for (int i = 0; i < numSamples; ++i) { - *reinterpret_cast (d) = source[i]; + *unalignedPointerCast (d) = source[i]; #if JUCE_LITTLE_ENDIAN - *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); + *unalignedPointerCast (d) = ByteOrder::swap (*unalignedPointerCast (d)); #endif d += destBytesPerSample; @@ -219,7 +219,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest { for (int i = 0; i < numSamples; ++i) { - dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast (intData)); intData += srcBytesPerSample; } } @@ -230,7 +230,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast (intData)); } } } @@ -244,7 +244,7 @@ void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest { for (int i = 0; i < numSamples; ++i) { - dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast (intData)); intData += srcBytesPerSample; } } @@ -255,7 +255,7 @@ void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast (intData)); } } } @@ -319,7 +319,7 @@ void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest { for (int i = 0; i < numSamples; ++i) { - dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast (intData)); intData += srcBytesPerSample; } } @@ -330,7 +330,7 @@ void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast (intData)); } } } @@ -344,7 +344,7 @@ void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest { for (int i = 0; i < numSamples; ++i) { - dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast (intData)); intData += srcBytesPerSample; } } @@ -355,7 +355,7 @@ void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest for (int i = numSamples; --i >= 0;) { intData -= srcBytesPerSample; - dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast (intData)); } } } @@ -366,10 +366,10 @@ void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* de for (int i = 0; i < numSamples; ++i) { - dest[i] = *reinterpret_cast (s); + dest[i] = *unalignedPointerCast (s); #if JUCE_BIG_ENDIAN - auto d = reinterpret_cast (dest + i); + auto d = unalignedPointerCast (dest + i); *d = ByteOrder::swap (*d); #endif @@ -383,10 +383,10 @@ void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* de for (int i = 0; i < numSamples; ++i) { - dest[i] = *reinterpret_cast (s); + dest[i] = *unalignedPointerCast (s); #if JUCE_LITTLE_ENDIAN - auto d = reinterpret_cast (dest + i); + auto d = unalignedPointerCast (dest + i); *d = ByteOrder::swap (*d); #endif diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 9b501e6..de9a8e1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -404,8 +404,8 @@ class AudioBuffer auto numSamplesToCopy = (size_t) jmin (newNumSamples, size); - auto newChannels = reinterpret_cast (newData.get()); - auto newChan = reinterpret_cast (newData + channelListSize); + auto newChannels = unalignedPointerCast (newData.get()); + auto newChan = unalignedPointerCast (newData + channelListSize); for (int j = 0; j < newNumChannels; ++j) { @@ -437,10 +437,10 @@ class AudioBuffer { allocatedBytes = newTotalBytes; allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); - channels = reinterpret_cast (allocatedData.get()); + channels = unalignedPointerCast (allocatedData.get()); } - auto* chan = reinterpret_cast (allocatedData + channelListSize); + auto* chan = unalignedPointerCast (allocatedData + channelListSize); for (int i = 0; i < newNumChannels; ++i) { @@ -1137,8 +1137,8 @@ class AudioBuffer allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; allocatedData.malloc (allocatedBytes); - channels = reinterpret_cast (allocatedData.get()); - auto chan = reinterpret_cast (allocatedData + channelListSize); + channels = unalignedPointerCast (allocatedData.get()); + auto chan = unalignedPointerCast (allocatedData + channelListSize); for (int i = 0; i < numChannels; ++i) { @@ -1162,7 +1162,7 @@ class AudioBuffer else { allocatedData.malloc (numChannels + 1, sizeof (Type*)); - channels = reinterpret_cast (allocatedData.get()); + channels = unalignedPointerCast (allocatedData.get()); } for (int i = 0; i < numChannels; ++i) diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h index 8ca25ea..a70d421 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: 6.0.1 + version: 6.0.5 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_MidiBuffer.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp index 6100741..c0abc83 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp @@ -60,9 +60,8 @@ namespace MidiBufferHelpers if (maxBytes == 1) return 1; - int n; - auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); - return jmin (maxBytes, n + 2 + bytesLeft); + const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1); + return jmin (maxBytes, var.value + 2 + var.bytesUsed); } if (byte >= 0x80) diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 30c75b9..aeb576f 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -46,23 +46,77 @@ namespace MidiFileHelpers } } - static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept + template + struct Optional { - auto ch = ByteOrder::bigEndianInt (data); - data += 4; + Optional() = default; - if (ch != ByteOrder::bigEndianInt ("MThd")) + Optional (const Value& v) + : value (v), valid (true) {} + + Value value = Value(); + bool valid = false; + }; + + template + struct ReadTrait; + + template <> + struct ReadTrait { static constexpr auto read = ByteOrder::bigEndianInt; }; + + template <> + struct ReadTrait { static constexpr auto read = ByteOrder::bigEndianShort; }; + + template + Optional tryRead (const uint8*& data, size_t& remaining) + { + using Trait = ReadTrait; + constexpr auto size = sizeof (Integral); + + if (remaining < size) + return {}; + + const Optional result { Trait::read (data) }; + + data += size; + remaining -= size; + + return result; + } + + struct HeaderDetails + { + size_t bytesRead = 0; + short timeFormat = 0; + short fileType = 0; + short numberOfTracks = 0; + }; + + static Optional parseMidiHeader (const uint8* const initialData, + const size_t maxSize) + { + auto* data = initialData; + auto remaining = maxSize; + + auto ch = tryRead (data, remaining); + + if (! ch.valid) + return {}; + + if (ch.value != ByteOrder::bigEndianInt ("MThd")) { - bool ok = false; + auto ok = false; - if (ch == ByteOrder::bigEndianInt ("RIFF")) + if (ch.value == ByteOrder::bigEndianInt ("RIFF")) { for (int i = 0; i < 8; ++i) { - ch = ByteOrder::bigEndianInt (data); - data += 4; + ch = tryRead (data, remaining); + + if (! ch.valid) + return {}; - if (ch == ByteOrder::bigEndianInt ("MThd")) + if (ch.value == ByteOrder::bigEndianInt ("MThd")) { ok = true; break; @@ -71,21 +125,37 @@ namespace MidiFileHelpers } if (! ok) - return false; + return {}; } - auto bytesRemaining = ByteOrder::bigEndianInt (data); - data += 4; - fileType = (short) ByteOrder::bigEndianShort (data); - data += 2; - numberOfTracks = (short) ByteOrder::bigEndianShort (data); - data += 2; - timeFormat = (short) ByteOrder::bigEndianShort (data); - data += 2; - bytesRemaining -= 6; - data += bytesRemaining; - - return true; + const auto bytesRemaining = tryRead (data, remaining); + + if (! bytesRemaining.valid || bytesRemaining.value > remaining) + return {}; + + const auto optFileType = tryRead (data, remaining); + + if (! optFileType.valid || 2 < optFileType.value) + return {}; + + const auto optNumTracks = tryRead (data, remaining); + + if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1)) + return {}; + + const auto optTimeFormat = tryRead (data, remaining); + + if (! optTimeFormat.valid) + return {}; + + HeaderDetails result; + + result.fileType = (short) optFileType.value; + result.timeFormat = (short) optTimeFormat.value; + result.numberOfTracks = (short) optNumTracks.value; + result.bytesRead = maxSize - remaining; + + return { result }; } static double convertTicksToSeconds (double time, @@ -149,6 +219,47 @@ namespace MidiFileHelpers } } } + + static MidiMessageSequence readTrack (const uint8* data, int size) + { + double time = 0; + uint8 lastStatusByte = 0; + + MidiMessageSequence result; + + while (size > 0) + { + const auto delay = MidiMessage::readVariableLengthValue (data, (int) size); + + if (delay.bytesUsed == 0) + break; + + data += delay.bytesUsed; + size -= delay.bytesUsed; + time += delay.value; + + if (size <= 0) + break; + + int messSize = 0; + const MidiMessage mm (data, size, messSize, lastStatusByte, time); + + if (messSize <= 0) + break; + + size -= messSize; + data += messSize; + + result.addEvent (mm); + + auto firstByte = *(mm.getRawData()); + + if ((firstByte & 0xf0) != 0xf0) + lastStatusByte = firstByte; + } + + return result; + } } //============================================================================== @@ -253,78 +364,56 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) const int maxSensibleMidiFileSize = 200 * 1024 * 1024; // (put a sanity-check on the file size, as midi files are generally small) - if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) - { - auto size = data.getSize(); - auto d = static_cast (data.getData()); - short fileType, expectedTracks; - - if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) - { - size -= (size_t) (d - static_cast (data.getData())); - int track = 0; - - for (;;) - { - auto chunkType = (int) ByteOrder::bigEndianInt (d); - d += 4; - auto chunkSize = (int) ByteOrder::bigEndianInt (d); - d += 4; + if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) + return false; - if (chunkSize <= 0 || (size_t) chunkSize > size) - break; + auto size = data.getSize(); + auto d = static_cast (data.getData()); - if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) - readNextTrack (d, chunkSize, createMatchingNoteOffs); + const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); - if (++track >= expectedTracks) - break; + if (! optHeader.valid) + return false; - size -= (size_t) chunkSize + 8; - d += chunkSize; - } + const auto header = optHeader.value; + timeFormat = header.timeFormat; - return true; - } - } + d += header.bytesRead; + size -= (size_t) header.bytesRead; - return false; -} + for (int track = 0; track < header.numberOfTracks; ++track) + { + const auto optChunkType = MidiFileHelpers::tryRead (d, size); -void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) -{ - double time = 0; - uint8 lastStatusByte = 0; + if (! optChunkType.valid) + return false; - MidiMessageSequence result; + const auto optChunkSize = MidiFileHelpers::tryRead (d, size); - while (size > 0) - { - int bytesUsed; - auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed); - data += bytesUsed; - size -= bytesUsed; - time += delay; + if (! optChunkSize.valid) + return false; - int messSize = 0; - const MidiMessage mm (data, size, messSize, lastStatusByte, time); + const auto chunkSize = optChunkSize.value; - if (messSize <= 0) - break; + if (size < chunkSize) + return false; - size -= messSize; - data += messSize; + if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk")) + readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); - result.addEvent (mm); + size -= chunkSize; + d += chunkSize; + } - auto firstByte = *(mm.getRawData()); + return size == 0; +} - if ((firstByte & 0xf0) != 0xf0) - lastStatusByte = firstByte; - } +void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) +{ + auto sequence = MidiFileHelpers::readTrack (data, size); // sort so that we put all the note-offs before note-ons that have the same time - std::stable_sort (result.list.begin(), result.list.end(), + std::stable_sort (sequence.list.begin(), sequence.list.end(), [] (const MidiMessageSequence::MidiEventHolder* a, const MidiMessageSequence::MidiEventHolder* b) { @@ -337,10 +426,10 @@ void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNo return a->message.isNoteOff() && b->message.isNoteOn(); }); - addTrack (result); - if (createMatchingNoteOffs) - tracks.getLast()->updateMatchedPairs(); + sequence.updateMatchedPairs(); + + addTrack (sequence); } //============================================================================== @@ -443,4 +532,267 @@ bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) return true; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct MidiFileTest : public UnitTest +{ + MidiFileTest() + : UnitTest ("MidiFile", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("ReadTrack respects running status"); + { + const auto sequence = parseSequence ([] (OutputStream& os) + { + MidiFileHelpers::writeVariableLengthInt (os, 100); + writeBytes (os, { 0x90, 0x40, 0x40 }); + MidiFileHelpers::writeVariableLengthInt (os, 200); + writeBytes (os, { 0x40, 0x40 }); + MidiFileHelpers::writeVariableLengthInt (os, 300); + writeBytes (os, { 0xff, 0x2f, 0x00 }); + }); + + expectEquals (sequence.getNumEvents(), 3); + expect (sequence.getEventPointer (0)->message.isNoteOn()); + expect (sequence.getEventPointer (1)->message.isNoteOn()); + expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent()); + } + + beginTest ("ReadTrack returns available messages if input is truncated"); + { + { + const auto sequence = parseSequence ([] (OutputStream& os) + { + // Incomplete delta time + writeBytes (os, { 0xff }); + }); + + expectEquals (sequence.getNumEvents(), 0); + } + + { + const auto sequence = parseSequence ([] (OutputStream& os) + { + // Complete delta with no following event + MidiFileHelpers::writeVariableLengthInt (os, 0xffff); + }); + + expectEquals (sequence.getNumEvents(), 0); + } + + { + const auto sequence = parseSequence ([] (OutputStream& os) + { + // Complete delta with malformed following event + MidiFileHelpers::writeVariableLengthInt (os, 0xffff); + writeBytes (os, { 0x90, 0x40 }); + }); + + expectEquals (sequence.getNumEvents(), 1); + expect (sequence.getEventPointer (0)->message.isNoteOff()); + expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40); + expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00); + } + } + + beginTest ("Header parsing works"); + { + { + // No data + const auto header = parseHeader ([] (OutputStream&) {}); + expect (! header.valid); + } + + { + // Invalid initial byte + const auto header = parseHeader ([] (OutputStream& os) + { + writeBytes (os, { 0xff }); + }); + + expect (! header.valid); + } + + { + // Type block, but no header data + const auto header = parseHeader ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd' }); + }); + + expect (! header.valid); + } + + { + // We (ll-formed header, but track type is 0 and channels != 1 + const auto header = parseHeader ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); + }); + + expect (! header.valid); + } + + { + // Well-formed header, but track type is 5 + const auto header = parseHeader ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); + }); + + expect (! header.valid); + } + + { + // Well-formed header + const auto header = parseHeader ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); + }); + + expect (header.valid); + + expectEquals (header.value.fileType, (short) 1); + expectEquals (header.value.numberOfTracks, (short) 16); + expectEquals (header.value.timeFormat, (short) 1); + expectEquals ((int) header.value.bytesRead, 14); + } + } + + beginTest ("Read from stream"); + { + { + // Empty input + const auto file = parseFile ([] (OutputStream&) {}); + expect (! file.valid); + } + + { + // Malformed header + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd' }); + }); + + expect (! file.valid); + } + + { + // Header, no channels + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); + }); + + expect (file.valid); + expectEquals (file.value.getNumTracks(), 0); + } + + { + // Header, one malformed channel + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); + writeBytes (os, { 'M', 'T', 'r', '?' }); + }); + + expect (! file.valid); + } + + { + // Header, one channel with malformed message + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); + writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); + }); + + expect (file.valid); + expectEquals (file.value.getNumTracks(), 1); + expectEquals (file.value.getTrack (0)->getNumEvents(), 0); + } + + { + // Header, one channel with incorrect length message + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); + writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); + }); + + expect (! file.valid); + } + + { + // Header, one channel, all well-formed + const auto file = parseFile ([] (OutputStream& os) + { + writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); + writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 }); + + MidiFileHelpers::writeVariableLengthInt (os, 0x0f); + writeBytes (os, { 0x80, 0x00, 0x00 }); + }); + + expect (file.valid); + expectEquals (file.value.getNumTracks(), 1); + + auto& track = *file.value.getTrack (0); + expectEquals (track.getNumEvents(), 1); + expect (track.getEventPointer (0)->message.isNoteOff()); + expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); + } + } + } + + template + static MidiMessageSequence parseSequence (Fn&& fn) + { + MemoryOutputStream os; + fn (os); + + return MidiFileHelpers::readTrack (reinterpret_cast (os.getData()), + (int) os.getDataSize()); + } + + template + static MidiFileHelpers::Optional parseHeader (Fn&& fn) + { + MemoryOutputStream os; + fn (os); + + return MidiFileHelpers::parseMidiHeader (reinterpret_cast (os.getData()), + os.getDataSize()); + } + + template + static MidiFileHelpers::Optional parseFile (Fn&& fn) + { + MemoryOutputStream os; + fn (os); + + MemoryInputStream is (os.getData(), os.getDataSize(), false); + MidiFile mf; + + if (mf.readFrom (is)) + return mf; + + return {}; + } + + static void writeBytes (OutputStream& os, const std::vector& bytes) + { + for (const auto& byte : bytes) + os.writeByte ((char) byte); + } +}; + +static MidiFileTest midiFileTests; + +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h index 64665a7..f0954b5 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h @@ -136,7 +136,7 @@ class JUCE_API MidiKeyboardState //============================================================================== /** Receives events from a MidiKeyboardState object. */ - class Listener + class JUCE_API Listener { public: //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index 5b78b6c..5a154ca 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -57,6 +57,32 @@ uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, } //============================================================================== +MidiMessage::VariableLengthValue MidiMessage::readVariableLengthValue (const uint8* data, int maxBytesToUse) noexcept +{ + uint32 v = 0; + + // The largest allowable variable-length value is 0x0f'ff'ff'ff which is + // represented by the 4-byte stream 0xff 0xff 0xff 0x7f. + // Longer bytestreams risk overflowing a 32-bit signed int. + const auto limit = jmin (maxBytesToUse, 4); + + for (int numBytesUsed = 0; numBytesUsed < limit; ++numBytesUsed) + { + const auto i = data[numBytesUsed]; + v = (v << 7) + (i & 0x7f); + + if (! (i & 0x80)) + return { (int) v, numBytesUsed + 1 }; + } + + // If this is hit, the input was malformed. Either there were not enough + // bytes of input to construct a full value, or no terminating byte was + // found. This implementation only supports variable-length values of up + // to four bytes. + jassertfalse; + return {}; +} + int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept { numBytesUsed = 0; @@ -224,16 +250,8 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const } else if (byte == 0xff) { - if (sz == 1) - { - size = 1; - } - else - { - int n; - const int bytesLeft = readVariableLengthVal (src + 1, n); - size = jmin (sz + 1, n + 2 + bytesLeft); - } + const auto bytesLeft = readVariableLengthValue (src + 1, sz - 1); + size = jmin (sz + 1, bytesLeft.bytesUsed + 2 + bytesLeft.value); auto dest = allocateSpace (size); *dest = (uint8) byte; @@ -682,7 +700,7 @@ bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0x int MidiMessage::getMetaEventType() const noexcept { auto data = getRawData(); - return *data != 0xff ? -1 : data[1]; + return (size < 2 || *data != 0xff) ? -1 : data[1]; } int MidiMessage::getMetaEventLength() const noexcept @@ -691,8 +709,8 @@ int MidiMessage::getMetaEventLength() const noexcept if (*data == 0xff) { - int n; - return jmin (size - 2, readVariableLengthVal (data + 2, n)); + const auto var = readVariableLengthValue (data + 2, size - 2); + return jmax (0, jmin (size - 2 - var.bytesUsed, var.value)); } return 0; @@ -702,10 +720,9 @@ const uint8* MidiMessage::getMetaEventData() const noexcept { jassert (isMetaEvent()); - int n; auto d = getRawData() + 2; - readVariableLengthVal (d, n); - return d + n; + const auto var = readVariableLengthValue (d, size - 2); + return d + var.bytesUsed; } bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } @@ -1141,4 +1158,170 @@ const char* MidiMessage::getControllerName (const int n) return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct MidiMessageTest : public UnitTest +{ + MidiMessageTest() + : UnitTest ("MidiMessage", UnitTestCategories::midi) + {} + + void runTest() override + { + using std::begin; + using std::end; + + beginTest ("ReadVariableLengthValue should return valid, backward-compatible results"); + { + const std::vector inputs[] + { + { 0x00 }, + { 0x40 }, + { 0x7f }, + { 0x81, 0x00 }, + { 0xc0, 0x00 }, + { 0xff, 0x7f }, + { 0x81, 0x80, 0x00 }, + { 0xc0, 0x80, 0x00 }, + { 0xff, 0xff, 0x7f }, + { 0x81, 0x80, 0x80, 0x00 }, + { 0xc0, 0x80, 0x80, 0x00 }, + { 0xff, 0xff, 0xff, 0x7f } + }; + + const int outputs[] + { + 0x00, + 0x40, + 0x7f, + 0x80, + 0x2000, + 0x3fff, + 0x4000, + 0x100000, + 0x1fffff, + 0x200000, + 0x8000000, + 0xfffffff, + }; + + expectEquals (std::distance (begin (inputs), end (inputs)), + std::distance (begin (outputs), end (outputs))); + + size_t index = 0; + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) + + for (const auto& input : inputs) + { + auto copy = input; + + while (copy.size() < 16) + copy.push_back (0); + + const auto result = MidiMessage::readVariableLengthValue (copy.data(), + (int) copy.size()); + + expectEquals (result.value, outputs[index]); + expectEquals (result.bytesUsed, (int) inputs[index].size()); + + int legacyNumUsed = 0; + const auto legacyResult = MidiMessage::readVariableLengthVal (copy.data(), + legacyNumUsed); + + expectEquals (result.value, legacyResult); + expectEquals (result.bytesUsed, legacyNumUsed); + + ++index; + } + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + JUCE_END_IGNORE_WARNINGS_MSVC + } + + beginTest ("ReadVariableLengthVal should return 0 if input is truncated"); + { + for (size_t i = 0; i != 16; ++i) + { + std::vector input; + input.resize (i, 0xFF); + + const auto result = MidiMessage::readVariableLengthValue (input.data(), + (int) input.size()); + + expectEquals (result.value, 0); + expectEquals (result.bytesUsed, 0); + } + } + + const std::vector metaEvents[] + { + // Format is 0xff, followed by a 'kind' byte, followed by a variable-length + // 'data-length' value, followed by that many data bytes + { 0xff, 0x00, 0x02, 0x00, 0x00 }, // Sequence number + { 0xff, 0x01, 0x00 }, // Text event + { 0xff, 0x02, 0x00 }, // Copyright notice + { 0xff, 0x03, 0x00 }, // Track name + { 0xff, 0x04, 0x00 }, // Instrument name + { 0xff, 0x05, 0x00 }, // Lyric + { 0xff, 0x06, 0x00 }, // Marker + { 0xff, 0x07, 0x00 }, // Cue point + { 0xff, 0x20, 0x01, 0x00 }, // Channel prefix + { 0xff, 0x2f, 0x00 }, // End of track + { 0xff, 0x51, 0x03, 0x01, 0x02, 0x03 }, // Set tempo + { 0xff, 0x54, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05 }, // SMPTE offset + { 0xff, 0x58, 0x04, 0x01, 0x02, 0x03, 0x04 }, // Time signature + { 0xff, 0x59, 0x02, 0x01, 0x02 }, // Key signature + { 0xff, 0x7f, 0x00 }, // Sequencer-specific + }; + + beginTest ("MidiMessage data constructor works for well-formed meta-events"); + { + const auto status = (uint8) 0x90; + + for (const auto& input : metaEvents) + { + int bytesUsed = 0; + const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status); + + expect (msg.isMetaEvent()); + expectEquals (msg.getMetaEventLength(), (int) input.size() - 3); + expectEquals (msg.getMetaEventType(), (int) input[1]); + } + } + + beginTest ("MidiMessage data constructor works for malformed meta-events"); + { + const auto status = (uint8) 0x90; + + const auto runTest = [&] (const std::vector& input) + { + int bytesUsed = 0; + const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status); + + expect (msg.isMetaEvent()); + expectEquals (msg.getMetaEventLength(), jmax (0, (int) input.size() - 3)); + expectEquals (msg.getMetaEventType(), input.size() >= 2 ? input[1] : -1); + }; + + runTest ({ 0xff }); + + for (const auto& input : metaEvents) + { + auto copy = input; + copy[2] = 0x40; // Set the size of the message to more bytes than are present + + runTest (copy); + } + } + } +}; + +static MidiMessageTest midiMessageTests; + +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h index 87b2efb..7ab3d0d 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -401,7 +401,7 @@ class JUCE_API MidiMessage /** Returns true if the message is an aftertouch event. For aftertouch events, use the getNoteNumber() method to find out the key - that it applies to, and getAftertouchValue() to find out the amount. Use + that it applies to, and getAfterTouchValue() to find out the amount. Use getChannel() to find out the channel. @see getAftertouchValue, getNoteNumber @@ -858,11 +858,43 @@ class JUCE_API MidiMessage //============================================================================== /** Reads a midi variable-length integer. + This signature has been deprecated in favour of the safer + readVariableLengthValue. + + The `data` argument indicates the data to read the number from, + and `numBytesUsed` is used as an out-parameter to indicate the number + of bytes that were read. + */ + JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data, + int& numBytesUsed) noexcept); + + /** Holds information about a variable-length value which was parsed + from a stream of bytes. + + A valid value requires that `bytesUsed` is greater than 0. + If `bytesUsed <= 0` this object should be considered invalid. + */ + struct VariableLengthValue + { + VariableLengthValue() = default; + + VariableLengthValue (int valueIn, int bytesUsedIn) + : value (valueIn), bytesUsed (bytesUsedIn) {} + + int value = 0; + int bytesUsed = 0; + }; + + /** Reads a midi variable-length integer, with protection against buffer overflow. + @param data the data to read the number from - @param numBytesUsed on return, this will be set to the number of bytes that were read + @param maxBytesToUse the number of bytes in the region following `data` + @returns a struct containing the parsed value, and the number + of bytes that were read. If parsing fails, both the + `value` and `bytesUsed` fields will be set to 0. */ - static int readVariableLengthVal (const uint8* data, - int& numBytesUsed) noexcept; + static VariableLengthValue readVariableLengthValue (const uint8* data, + int maxBytesToUse) noexcept; /** Based on the first byte of a short midi message, this uses a lookup table to return the message length (either 1, 2, or 3 bytes). diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index 796007e..2d1a2bd 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp @@ -27,30 +27,33 @@ namespace { const uint8 noLSBValueReceived = 0xff; const Range allChannels { 1, 17 }; + + template + void mpeInstrumentFill (Range& range, const Value& value) + { + std::fill (std::begin (range), std::end (range), value); + } } //============================================================================== MPEInstrument::MPEInstrument() noexcept { - std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived); - std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived); - std::fill_n (isMemberChannelSustained, 16, false); + mpeInstrumentFill (lastPressureLowerBitReceivedOnChannel, noLSBValueReceived); + mpeInstrumentFill (lastTimbreLowerBitReceivedOnChannel, noLSBValueReceived); + mpeInstrumentFill (isMemberChannelSustained, false); pitchbendDimension.value = &MPENote::pitchbend; pressureDimension.value = &MPENote::pressure; timbreDimension.value = &MPENote::timbre; - // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) - std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); + resetLastReceivedValues(); legacyMode.isEnabled = false; legacyMode.pitchbendRange = 2; legacyMode.channelRange = allChannels; } -MPEInstrument::~MPEInstrument() -{ -} +MPEInstrument::~MPEInstrument() = default; //============================================================================== MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept @@ -58,6 +61,23 @@ MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept return zoneLayout; } +void MPEInstrument::resetLastReceivedValues() +{ + struct Defaults + { + MPEDimension& dimension; + MPEValue defaultValue; + }; + + // The default value for pressure is 0, for all other dimensions it is centre + for (const auto& pair : { Defaults { pressureDimension, MPEValue::minValue() }, + Defaults { pitchbendDimension, MPEValue::centreValue() }, + Defaults { timbreDimension, MPEValue::centreValue() } }) + { + mpeInstrumentFill (pair.dimension.lastValueReceivedOnChannel, pair.defaultValue); + } +} + void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) { releaseAllNotes(); @@ -65,6 +85,8 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) const ScopedLock sl (lock); legacyMode.isEnabled = false; zoneLayout = newLayout; + + resetLastReceivedValues(); } //============================================================================== diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index 6572754..6853f29 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -376,6 +376,8 @@ class JUCE_API MPEInstrument LegacyMode legacyMode; MPEDimension pitchbendDimension, pressureDimension, timbreDimension; + void resetLastReceivedValues(); + void updateDimension (int midiChannel, MPEDimension&, MPEValue); void updateDimensionMaster (bool, MPEDimension&, MPEValue); void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); 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 e914d16..a9ab37e 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -177,8 +177,9 @@ static void addIfNotNull (OwnedArray& list, AudioIODeviceType void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& list) { - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (false)); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (true)); + addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::shared)); + addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::exclusive)); + addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::sharedLowLatency)); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); @@ -544,6 +545,8 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup else if (currentAudioDevice != nullptr) return {}; + stopDevice(); + if (getCurrentDeviceTypeObject() == nullptr || (newSetup.inputDeviceName.isEmpty() && newSetup.outputDeviceName.isEmpty())) { @@ -555,8 +558,6 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup return {}; } - stopDevice(); - String error; if (currentSetup.inputDeviceName != newSetup.inputDeviceName diff --git a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp index 249620e..0657b62 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp @@ -42,48 +42,105 @@ void AudioIODeviceType::callDeviceChangeListeners() } //============================================================================== -#if ! JUCE_MAC -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } +#if JUCE_MAC + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return new CoreAudioClasses::CoreAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } #endif -#if ! JUCE_IOS -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } +#if JUCE_IOS + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return new iOSAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } #endif -#if ! (JUCE_WINDOWS && JUCE_WASAPI) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; } +#if JUCE_WINDOWS && JUCE_WASAPI + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode) + { + auto windowsVersion = SystemStats::getOperatingSystemType(); + + if (windowsVersion < SystemStats::WinVista + || (WasapiClasses::isLowLatencyMode (deviceMode) && windowsVersion < SystemStats::Windows10)) + return nullptr; + + return new WasapiClasses::WASAPIAudioIODeviceType (deviceMode); + } + + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode) + { + return createAudioIODeviceType_WASAPI (exclusiveMode ? WASAPIDeviceMode::exclusive + : WASAPIDeviceMode::shared); + } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode) { return nullptr; } + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; } #endif -#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } +#if JUCE_WINDOWS && JUCE_DIRECTSOUND + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return new DSoundAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } #endif -#if ! (JUCE_WINDOWS && JUCE_ASIO) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } +#if JUCE_WINDOWS && JUCE_ASIO + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return new ASIOAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } #endif -#if ! (JUCE_LINUX && JUCE_ALSA) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } +#if JUCE_LINUX && JUCE_ALSA + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return createAudioIODeviceType_ALSA_PCMDevices(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } #endif -#if ! (JUCE_LINUX && JUCE_JACK) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } +#if JUCE_LINUX && JUCE_JACK + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return new JackAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } #endif -#if ! (JUCE_LINUX && JUCE_BELA) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; } +#if JUCE_LINUX && JUCE_BELA + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return new BelaAudioIODeviceType(); } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; } #endif -#if ! JUCE_ANDROID -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } +#if JUCE_ANDROID + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() + { + #if JUCE_USE_ANDROID_OBOE + if (isOboeAvailable()) + return nullptr; + #endif + + #if JUCE_USE_ANDROID_OPENSLES + if (isOpenSLAvailable()) + return nullptr; + #endif + + return new AndroidAudioIODeviceType(); + } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } #endif -#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } +#if JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() + { + return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; + } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } #endif -#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OBOE) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; } +#if JUCE_ANDROID && JUCE_USE_ANDROID_OBOE + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() + { + return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr; + } +#else + AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; } #endif } // namespace juce 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 1ece550..37829a8 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -149,8 +149,8 @@ class JUCE_API AudioIODeviceType static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); /** Creates an iOS device type if it's available on this platform, or returns null. */ static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); - /** Creates a WASAPI device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode); + /** Creates a WASAPI device type in the specified mode if it's available on this platform, or returns null. */ + static AudioIODeviceType* createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode); /** Creates a DirectSound device type if it's available on this platform, or returns null. */ static AudioIODeviceType* createAudioIODeviceType_DirectSound(); /** Creates an ASIO device type if it's available on this platform, or returns null. */ @@ -168,6 +168,9 @@ class JUCE_API AudioIODeviceType /** Creates a Bela device type if it's available on this platform, or returns null. */ static AudioIODeviceType* createAudioIODeviceType_Bela(); + /** This method has been deprecated. You should call the method which takes a WASAPIDeviceMode instead. */ + JUCE_DEPRECATED (static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode)); + protected: explicit AudioIODeviceType (const String& typeName); diff --git a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp index 3dcfba3..d24d382 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/juce_audio_devices.cpp @@ -45,6 +45,39 @@ #include "juce_audio_devices.h" +#include "native/juce_MidiDataConcatenator.h" + +#include + +#include "midi_io/ump/juce_UMPProtocols.h" +#include "midi_io/ump/juce_UMPUtils.h" +#include "midi_io/ump/juce_UMPacket.h" +#include "midi_io/ump/juce_UMPSysEx7.h" +#include "midi_io/ump/juce_UMPView.h" +#include "midi_io/ump/juce_UMPIterator.h" +#include "midi_io/ump/juce_UMPackets.h" +#include "midi_io/ump/juce_UMPFactory.h" +#include "midi_io/ump/juce_UMPConversion.h" +#include "midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h" +#include "midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h" +#include "midi_io/ump/juce_UMPConverters.h" +#include "midi_io/ump/juce_UMPDispatcher.h" +#include "midi_io/ump/juce_UMPReceiver.h" +#include "midi_io/ump/juce_UMPBytestreamInputHandler.h" +#include "midi_io/ump/juce_UMPU32InputHandler.h" + +#include "midi_io/ump/juce_UMPUtils.cpp" +#include "midi_io/ump/juce_UMPView.cpp" +#include "midi_io/ump/juce_UMPSysEx7.cpp" +#include "midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" + +#include "midi_io/ump/juce_UMPTests.cpp" + +namespace juce +{ +namespace ump = universal_midi_packets; +} + //============================================================================== #if JUCE_MAC #define Point CarbonDummyPointName @@ -55,6 +88,9 @@ #undef Point #undef Component + #include "native/juce_mac_CoreAudio.cpp" + #include "native/juce_mac_CoreMidi.mm" + #elif JUCE_IOS #import #import @@ -64,13 +100,21 @@ #import #endif + #include "native/juce_ios_Audio.cpp" + #include "native/juce_mac_CoreMidi.mm" + //============================================================================== #elif JUCE_WINDOWS #if JUCE_WASAPI #include + #include "native/juce_win32_WASAPI.cpp" #endif - #if JUCE_USE_WINRT_MIDI && JUCE_MSVC + #if JUCE_DIRECTSOUND + #include "native/juce_win32_DirectSound.cpp" + #endif + + #if JUCE_USE_WINRT_MIDI && (JUCE_MSVC || JUCE_CLANG) /* If you cannot find any of the header files below then you are probably attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you need to install version 10.0.14393.0 of the Windows Standalone SDK and you may @@ -93,6 +137,8 @@ JUCE_END_IGNORE_WARNINGS_MSVC #endif + #include "native/juce_win32_Midi.cpp" + #if JUCE_ASIO /* This is very frustrating - we only need to use a handful of definitions from a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy @@ -114,6 +160,7 @@ needed - so to simplify things, you could just copy these into your JUCE directory). */ #include + #include "native/juce_win32_ASIO.cpp" #endif //============================================================================== @@ -128,6 +175,7 @@ just set the JUCE_ALSA flag to 0. */ #include + #include "native/juce_linux_ALSA.cpp" #endif #if JUCE_JACK @@ -140,6 +188,7 @@ JUCE with low latency audio support, just set the JUCE_JACK flag to 0. */ #include + #include "native/juce_linux_JackAudio.cpp" #endif #if JUCE_BELA @@ -149,89 +198,18 @@ */ #include #include + #include "native/juce_linux_Bela.cpp" #endif #undef SIZEOF -//============================================================================== -#elif JUCE_ANDROID - - #if JUCE_USE_ANDROID_OPENSLES - #include - #include - #include - #endif - - #if JUCE_USE_ANDROID_OBOE - #if JUCE_USE_ANDROID_OPENSLES - #error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES" - #endif - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", - "-Wzero-as-null-pointer-constant", - "-Winconsistent-missing-destructor-override", - "-Wshadow-field-in-constructor", - "-Wshadow-field") - #include - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #endif - -#endif - -#include "audio_io/juce_AudioDeviceManager.cpp" -#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" -#include "native/juce_MidiDataConcatenator.h" - -//============================================================================== -#if JUCE_MAC - #include "native/juce_mac_CoreAudio.cpp" - #include "native/juce_mac_CoreMidi.cpp" - -//============================================================================== -#elif JUCE_IOS - #include "native/juce_ios_Audio.cpp" - #include "native/juce_mac_CoreMidi.cpp" - -//============================================================================== -#elif JUCE_WINDOWS - - #if JUCE_WASAPI - #include "native/juce_win32_WASAPI.cpp" - #endif - - #if JUCE_DIRECTSOUND - #include "native/juce_win32_DirectSound.cpp" - #endif - - #include "native/juce_win32_Midi.cpp" - - #if JUCE_ASIO - #include "native/juce_win32_ASIO.cpp" - #endif - -//============================================================================== -#elif JUCE_LINUX - #if JUCE_ALSA - #include "native/juce_linux_ALSA.cpp" - #endif - - #if JUCE_JACK - #include "native/juce_linux_JackAudio.cpp" - #endif - - #if JUCE_BELA - #include "native/juce_linux_Bela.cpp" - #else + #if ! JUCE_BELA #include "native/juce_linux_Midi.cpp" #endif //============================================================================== #elif JUCE_ANDROID + #include "native/juce_android_Audio.cpp" #include "native/juce_android_Midi.cpp" @@ -239,10 +217,25 @@ #include "native/juce_android_HighPerformanceAudioHelpers.h" #if JUCE_USE_ANDROID_OPENSLES + #include + #include + #include #include "native/juce_android_OpenSL.cpp" #endif #if JUCE_USE_ANDROID_OBOE + #if JUCE_USE_ANDROID_OPENSLES + #error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES" + #endif + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", + "-Wzero-as-null-pointer-constant", + "-Winconsistent-missing-destructor-override", + "-Wshadow-field-in-constructor", + "-Wshadow-field") + #include + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #include "native/juce_android_Oboe.cpp" #endif #endif @@ -259,3 +252,11 @@ namespace juce bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } } #endif + +#include "audio_io/juce_AudioDeviceManager.cpp" +#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 d0ee77a..e976dbc 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: 6.0.1 + version: 6.0.5 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 @@ -88,21 +88,12 @@ #endif /** Config: JUCE_WASAPI - Enables WASAPI audio devices (Windows Vista and above). See also the - JUCE_WASAPI_EXCLUSIVE flag. + Enables WASAPI audio devices (Windows Vista and above). */ #ifndef JUCE_WASAPI #define JUCE_WASAPI 1 #endif -/** Config: JUCE_WASAPI_EXCLUSIVE - Enables WASAPI audio devices in exclusive mode (Windows Vista and above). -*/ -#ifndef JUCE_WASAPI_EXCLUSIVE - #define JUCE_WASAPI_EXCLUSIVE 0 -#endif - - /** Config: JUCE_DIRECTSOUND Enables DirectSound audio (MS Windows only). */ @@ -174,6 +165,22 @@ //============================================================================== #include "midi_io/juce_MidiDevices.h" #include "midi_io/juce_MidiMessageCollector.h" + +namespace juce +{ + /** Available modes for the WASAPI audio device. + + Pass one of these to the AudioIODeviceType::createAudioIODeviceType_WASAPI() + method to create a WASAPI AudioIODeviceType object in this mode. + */ + enum class WASAPIDeviceMode + { + shared, + exclusive, + sharedLowLatency + }; +} + #include "audio_io/juce_AudioIODevice.h" #include "audio_io/juce_AudioIODeviceType.h" #include "audio_io/juce_SystemAudioVolume.h" 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 4da13af..4d555a8 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/juce_MidiDevices.h @@ -164,12 +164,16 @@ class JUCE_API MidiInput final /** Deprecated. */ static std::unique_ptr openDevice (int, MidiInputCallback*); + /** @internal */ + class Pimpl; + private: //============================================================================== explicit MidiInput (const String&, const String&); MidiDeviceInfo deviceInfo; - void* internal = nullptr; + + std::unique_ptr internal; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) }; @@ -350,6 +354,9 @@ class JUCE_API MidiOutput final : private Thread /** Deprecated. */ static std::unique_ptr openDevice (int); + /** @internal */ + class Pimpl; + private: //============================================================================== struct PendingMessage @@ -368,7 +375,9 @@ class JUCE_API MidiOutput final : private Thread void run() override; MidiDeviceInfo deviceInfo; - void* internal = nullptr; + + std::unique_ptr internal; + CriticalSection lock; PendingMessage* firstMessage = nullptr; diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h new file mode 100644 index 0000000..4cddbfe --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h @@ -0,0 +1,120 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + A base class for classes which convert bytestream midi to other formats. +*/ +struct BytestreamInputHandler +{ + virtual ~BytestreamInputHandler() noexcept = default; + + virtual void reset() = 0; + virtual void pushMidiData (const void* data, int bytes, double time) = 0; +}; + +/** + Parses a continuous bytestream and emits complete MidiMessages whenever a full + message is received. +*/ +struct BytestreamToBytestreamHandler : public BytestreamInputHandler +{ + BytestreamToBytestreamHandler (MidiInput& i, MidiInputCallback& c) + : input (i), callback (c), concatenator (2048) {} + + class Factory + { + public: + explicit Factory (MidiInputCallback* c) + : callback (c) {} + + std::unique_ptr operator() (MidiInput& i) const + { + if (callback != nullptr) + return std::make_unique (i, *callback); + + jassertfalse; + return {}; + } + + private: + MidiInputCallback* callback = nullptr; + }; + + void reset() override { concatenator.reset(); } + + void pushMidiData (const void* data, int bytes, double time) override + { + concatenator.pushMidiData (data, bytes, time, &input, callback); + } + + MidiInput& input; + MidiInputCallback& callback; + MidiDataConcatenator concatenator; +}; + +/** + Parses a continuous MIDI 1.0 bytestream, and emits full messages in the requested + UMP format. +*/ +struct BytestreamToUMPHandler : public BytestreamInputHandler +{ + BytestreamToUMPHandler (PacketProtocol protocol, Receiver& c) + : recipient (c), dispatcher (protocol, 2048) {} + + class Factory + { + public: + Factory (PacketProtocol p, Receiver& c) + : protocol (p), callback (c) {} + + std::unique_ptr operator() (MidiInput&) const + { + return std::make_unique (protocol, callback); + } + + private: + PacketProtocol protocol; + Receiver& callback; + }; + + void reset() override { dispatcher.reset(); } + + void pushMidiData (const void* data, int bytes, double time) override + { + const auto* ptr = static_cast (data); + dispatcher.dispatch (ptr, ptr + bytes, time, [&] (const View& v) + { + recipient.packetReceived (v, time); + }); + } + + Receiver& recipient; + BytestreamToUMPDispatcher dispatcher; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h new file mode 100644 index 0000000..8b2ef45 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h @@ -0,0 +1,326 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Functions to assist conversion of UMP messages to/from other formats, + especially older 'bytestream' formatted MidiMessages. + + @tags{Audio} +*/ +struct Conversion +{ + /** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets. + + `callback` is a function which accepts a single View argument. + */ + template + static void toMidi1 (const MidiMessage& m, PacketCallbackFunction&& callback) + { + const auto* data = m.getRawData(); + const auto firstByte = data[0]; + const auto size = m.getRawDataSize(); + + if (firstByte != 0xf0) + { + const auto mask = [size]() -> uint32_t + { + switch (size) + { + case 0: return 0xff000000; + case 1: return 0xffff0000; + case 2: return 0xffffff00; + case 3: return 0xffffffff; + } + + return 0x00000000; + }(); + + const auto extraByte = (uint8_t) ((((firstByte & 0xf0) == 0xf0) ? 0x1 : 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 numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes); + auto* dataOffset = m.getSysExData(); + + if (numMessages <= 1) + { + const auto packet = Factory::makeSysExIn1Packet (0, (uint8_t) numSysExBytes, dataOffset); + callback (View (packet.data())); + return; + } + + constexpr auto byteIncrement = 6; + + for (auto i = numSysExBytes; i > 0; i -= byteIncrement, dataOffset += byteIncrement) + { + const auto func = [&] + { + if (i == numSysExBytes) + return Factory::makeSysExStart; + + if (i <= byteIncrement) + return Factory::makeSysExEnd; + + return Factory::makeSysExContinue; + }(); + + const auto bytesNow = std::min (byteIncrement, i); + const auto packet = func (0, (uint8_t) bytesNow, dataOffset); + callback (View (packet.data())); + } + } + + /** 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) + { + const auto shifted = (uint8_t) (word7Bit << 0x1); + const auto repeat = (uint8_t) (word7Bit & 0x3f); + const auto mask = (uint8_t) (word7Bit <= 0x40 ? 0x0 : 0xff); + return (uint8_t) (shifted | ((repeat >> 5) & mask)); + } + + /** Widens a 7-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ + static uint16_t scaleTo16 (uint8_t word7Bit) + { + const auto shifted = (uint16_t) (word7Bit << 0x9); + const auto repeat = (uint16_t) (word7Bit & 0x3f); + const auto mask = (uint16_t) (word7Bit <= 0x40 ? 0x0 : 0xffff); + return (uint16_t) (shifted | (((repeat << 3) | (repeat >> 3)) & mask)); + } + + /** Widens a 14-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ + static uint16_t scaleTo16 (uint16_t word14Bit) + { + const auto shifted = (uint16_t) (word14Bit << 0x2); + const auto repeat = (uint16_t) (word14Bit & 0x1fff); + const auto mask = (uint16_t) (word14Bit <= 0x2000 ? 0x0 : 0xffff); + return (uint16_t) (shifted | ((repeat >> 11) & mask)); + } + + /** Widens a 7-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ + static uint32_t scaleTo32 (uint8_t word7Bit) + { + const auto shifted = (uint32_t) (word7Bit << 0x19); + const auto repeat = (uint32_t) (word7Bit & 0x3f); + const auto mask = (uint32_t) (word7Bit <= 0x40 ? 0x0 : 0xffffffff); + return (uint32_t) (shifted | (((repeat << 19) + | (repeat << 13) + | (repeat << 7) + | (repeat << 1) + | (repeat >> 5)) & mask)); + } + + /** Widens a 14-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ + static uint32_t scaleTo32 (uint16_t word14Bit) + { + const auto shifted = (uint32_t) (word14Bit << 0x12); + const auto repeat = (uint32_t) (word14Bit & 0x1fff); + const auto mask = (uint32_t) (word14Bit <= 0x2000 ? 0x0 : 0xffffffff); + return (uint32_t) (shifted | (((repeat << 5) | (repeat >> 8)) & mask)); + } + + /** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ + static uint8_t scaleTo7 (uint8_t word8Bit) { return (uint8_t) (word8Bit >> 1); } + + /** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ + static uint8_t scaleTo7 (uint16_t word16Bit) { return (uint8_t) (word16Bit >> 9); } + + /** Narrows a 32-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ + static uint8_t scaleTo7 (uint32_t word32Bit) { return (uint8_t) (word32Bit >> 25); } + + /** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ + static uint16_t scaleTo14 (uint16_t word16Bit) { return (uint16_t) (word16Bit >> 2); } + + /** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ + static uint16_t scaleTo14 (uint32_t word32Bit) { return (uint16_t) (word32Bit >> 18); } + + /** Converts UMP messages which may include MIDI 2.0 channel voice messages into + equivalent MIDI 1.0 messages (still in UMP format). + + `callback` is a function that accepts a single View argument and will be + called with each converted packet. + + Note that not all MIDI 2.0 messages have MIDI 1.0 equivalents, so such + messages will be ignored. + */ + template + static void midi2ToMidi1DefaultTranslation (const View& v, Callback&& callback) + { + const auto firstWord = v[0]; + + if (Utils::getMessageType (firstWord) != 0x4) + { + callback (v); + return; + } + + const auto status = Utils::getStatus (firstWord); + const auto typeAndGroup = (uint8_t) ((0x2 << 0x4) | Utils::getGroup (firstWord)); + + switch (status) + { + case 0x8: // note off + case 0x9: // note on + 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]); + + // 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 shouldIgnore = status == 0xb && [&] + { + switch (byte2) + { + case 0: + case 6: + case 32: + case 38: + case 98: + case 99: + case 100: + case 101: + return true; + } + + return false; + }(); + + if (shouldIgnore) + return; + + const PacketX1 packet { Utils::bytesToWord (typeAndGroup, + statusAndChannel, + byte2, + correctedByte) }; + callback (View (packet.data())); + return; + } + + case 0xd: // channel pressure + { + const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); + const auto byte2 = scaleTo7 (v[1]); + + const PacketX1 packet { Utils::bytesToWord (typeAndGroup, + statusAndChannel, + byte2, + 0) }; + callback (View (packet.data())); + return; + } + + 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 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)) }, + }; + + for (const auto& packet : packets) + callback (View (packet.data())); + + return; + } + + case 0xc: // program change / bank select + { + if (firstWord & 1) + { + const auto statusAndChannel = (uint8_t) ((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)) }, + }; + + for (const auto& packet : packets) + callback (View (packet.data())); + } + + const auto statusAndChannel = (uint8_t) ((0xc << 0x4) | Utils::getChannel (firstWord)); + const PacketX1 packet { Utils::bytesToWord (typeAndGroup, + statusAndChannel, + (uint8_t) ((v[1] >> 0x18) & 0x7f), + 0) }; + callback (View (packet.data())); + return; + } + + case 0xe: // pitch bend + { + const auto data = scaleTo14 (v[1]); + const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); + const PacketX1 packet { Utils::bytesToWord (typeAndGroup, + statusAndChannel, + (uint8_t) (data & 0x7f), + (uint8_t) ((data >> 7) & 0x7f)) }; + callback (View (packet.data())); + return; + } + + default: // other message types do not translate + return; + } + } +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h new file mode 100644 index 0000000..6c439b0 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h @@ -0,0 +1,139 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + struct ToUMP1Converter + { + template + void convert (const MidiMessage& m, Fn&& fn) + { + Conversion::toMidi1 (m, std::forward (fn)); + } + + template + void convert (const View& v, Fn&& fn) + { + Conversion::midi2ToMidi1DefaultTranslation (v, std::forward (fn)); + } + }; + + struct ToUMP2Converter + { + template + void convert (const MidiMessage& m, Fn&& fn) + { + Conversion::toMidi1 (m, [&] (const View& v) + { + translator.dispatch (v, fn); + }); + } + + template + void convert (const View& v, Fn&& fn) + { + translator.dispatch (v, std::forward (fn)); + } + + void reset() + { + translator.reset(); + } + + Midi1ToMidi2DefaultTranslator translator; + }; + + class GenericUMPConverter + { + public: + explicit GenericUMPConverter (PacketProtocol m) + : mode (m) {} + + void reset() + { + std::get<1> (converters).reset(); + } + + template + void convert (const MidiMessage& m, Fn&& fn) + { + switch (mode) + { + 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)); + } + } + + 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)); + } + } + + template + void convert (Iterator begin, Iterator end, Fn&& fn) + { + std::for_each (begin, end, [&] (const View& v) + { + convert (v, fn); + }); + } + + PacketProtocol getProtocol() const noexcept { return mode; } + + private: + std::tuple converters; + const PacketProtocol mode{}; + }; + + struct ToBytestreamConverter + { + explicit ToBytestreamConverter (int storageSize) + : translator (storageSize) {} + + template + void convert (const MidiMessage& m, Fn&& fn) + { + fn (m); + } + + template + void convert (const View& v, double time, Fn&& fn) + { + Conversion::midi2ToMidi1DefaultTranslation (v, [&] (const View& midi1) + { + translator.dispatch (midi1, time, fn); + }); + } + + void reset() { translator.reset(); } + + Midi1ToBytestreamTranslator translator; + }; +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h new file mode 100644 index 0000000..909622d --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h @@ -0,0 +1,192 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Parses a raw stream of uint32_t, and calls a user-provided callback every time + a full Universal MIDI Packet is encountered. + + @tags{Audio} +*/ +class Dispatcher +{ +public: + /** Clears the dispatcher. */ + void reset() { currentPacketLen = 0; } + + /** Calls `callback` with a View of each packet encountered in the range delimited + by `begin` and `end`. + + If the range ends part-way through a packet, the next call to `dispatch` will + continue from that point in the packet (unless `reset` is called first). + */ + template + void dispatch (const uint32_t* begin, + const uint32_t* end, + double timeStamp, + PacketCallbackFunction&& callback) + { + std::for_each (begin, end, [&] (uint32_t word) + { + nextPacket[currentPacketLen++] = word; + + if (currentPacketLen == Utils::getNumWordsForMessageType (nextPacket.front())) + { + callback (View (nextPacket.data()), time); + currentPacketLen = 0; + time = timeStamp; + } + }); + } + +private: + std::array nextPacket; + size_t currentPacketLen = 0; + double time = 0.0; +}; + +//============================================================================== +/** + Parses a stream of bytes representing a sequence of bytestream-encoded MIDI 1.0 messages, + converting the messages to UMP format and passing the packets to a user-provided callback + as they become ready. + + @tags{Audio} +*/ +class BytestreamToUMPDispatcher +{ +public: + /** Initialises the dispatcher. + + Channel messages will be converted to the requested protocol format `pp`. + `storageSize` bytes will be allocated to store incomplete messages. + */ + explicit BytestreamToUMPDispatcher (PacketProtocol pp, int storageSize) + : concatenator (storageSize), + converter (pp) + {} + + void reset() + { + concatenator.reset(); + converter.reset(); + } + + /** Calls `callback` with a View of each converted packet as it becomes ready. + + @param begin the first byte in a range of bytes representing bytestream-encoded MIDI messages. + @param end one-past the last byte in a range of bytes representing bytestream-encoded MIDI messages. + @param timestamp a timestamp to apply to the created packets. + @param callback a callback which will be passed a View pointing to each new packet as it becomes ready. + */ + template + void dispatch (const uint8_t* begin, + const uint8_t* end, + double timestamp, + PacketCallbackFunction&& callback) + { + using CallbackPtr = decltype (std::addressof (callback)); + + struct Callback + { + Callback (BytestreamToUMPDispatcher& d, CallbackPtr c) + : dispatch (d), callbackPtr (c) {} + + void handleIncomingMidiMessage (void*, const MidiMessage& msg) const + { + Conversion::toMidi1 (msg, [&] (const View& view) + { + dispatch.converter.convert (view, *callbackPtr); + }); + } + + void handlePartialSysexMessage (void*, const uint8_t*, int, double) const {} + + BytestreamToUMPDispatcher& dispatch; + CallbackPtr callbackPtr = nullptr; + }; + + Callback inputCallback { *this, &callback }; + concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback); + } + +private: + MidiDataConcatenator concatenator; + GenericUMPConverter converter; +}; + +//============================================================================== +/** + Parses a stream of 32-bit words representing a sequence of UMP-encoded MIDI messages, + converting the messages to MIDI 1.0 bytestream format and passing them to a user-provided + callback as they become ready. + + @tags{Audio} +*/ +class ToBytestreamDispatcher +{ +public: + /** Initialises the dispatcher. + + `storageSize` bytes will be allocated to store incomplete messages. + */ + explicit ToBytestreamDispatcher (int storageSize) + : converter (storageSize) {} + + /** Clears the dispatcher. */ + void reset() + { + dispatcher.reset(); + converter.reset(); + } + + /** Calls `callback` with converted bytestream-formatted MidiMessage whenever + a new message becomes available. + + @param begin the first word in a stream of words representing UMP-encoded MIDI packets. + @param end one-past the last word in a stream of words representing UMP-encoded MIDI packets. + @param timestamp a timestamp to apply to converted messages. + @param callback a callback which will be passed a MidiMessage each time a new message becomes ready. + */ + template + void dispatch (const uint32_t* begin, + const uint32_t* end, + double timestamp, + BytestreamMessageCallback&& callback) + { + dispatcher.dispatch (begin, end, timestamp, [&] (const View& view, double time) + { + converter.convert (view, time, callback); + }); + } + +private: + Dispatcher dispatcher; + ToBytestreamConverter converter; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h new file mode 100644 index 0000000..ae36270 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h @@ -0,0 +1,527 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +struct Factory +{ + struct Detail + { + static PacketX1 makeSystem() { return PacketX1{}.withMessageType (1); } + static PacketX1 makeV1() { return PacketX1{}.withMessageType (2); } + static PacketX2 makeV2() { return PacketX2{}.withMessageType (4); } + + static PacketX2 makeSysEx (uint8_t group, + uint8_t status, + uint8_t numBytes, + const uint8_t* data) + { + jassert (numBytes <= 6); + + std::array bytes{{}}; + bytes[0] = (0x3 << 0x4) | group; + bytes[1] = (uint8_t) (status << 0x4) | numBytes; + + std::memcpy (bytes.data() + 2, data, numBytes); + + std::array words; + + size_t index = 0; + + for (auto& word : words) + word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); + + return PacketX2 { words }; + } + + static PacketX4 makeSysEx8 (uint8_t group, + uint8_t status, + uint8_t numBytes, + uint8_t dataStart, + const uint8_t* data) + { + jassert (numBytes <= 16 - dataStart); + + std::array bytes{{}}; + bytes[0] = (0x5 << 0x4) | group; + bytes[1] = (uint8_t) (status << 0x4) | numBytes; + + std::memcpy (bytes.data() + dataStart, data, numBytes); + + std::array words; + + size_t index = 0; + + for (auto& word : words) + word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); + + return PacketX4 { words }; + } + }; + + static PacketX1 makeNoop (uint8_t group) + { + return PacketX1{}.withGroup (group); + } + + static PacketX1 makeJRClock (uint8_t group, uint16_t time) + { + return PacketX1 { time }.withStatus (1).withGroup (group); + } + + static PacketX1 makeJRTimestamp (uint8_t group, uint16_t time) + { + return PacketX1 { time }.withStatus (2).withGroup (group); + } + + static PacketX1 makeTimeCode (uint8_t group, uint8_t code) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xf1) + .withU8<2> (code & 0x7f); + } + + static PacketX1 makeSongPositionPointer (uint8_t group, uint16_t pos) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xf2) + .withU8<2> (pos & 0x7f) + .withU8<3> ((pos >> 7) & 0x7f); + } + + static PacketX1 makeSongSelect (uint8_t group, uint8_t song) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xf3) + .withU8<2> (song & 0x7f); + } + + static PacketX1 makeTuneRequest (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xf6); + } + + static PacketX1 makeTimingClock (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xf8); + } + + static PacketX1 makeStart (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xfa); + } + + static PacketX1 makeContinue (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xfb); + } + + static PacketX1 makeStop (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xfc); + } + + static PacketX1 makeActiveSensing (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xfe); + } + + static PacketX1 makeReset (uint8_t group) + { + return Detail::makeSystem().withGroup (group) + .withU8<1> (0xff); + } + + static PacketX1 makeNoteOffV1 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t velocity) + { + return Detail::makeV1().withGroup (group) + .withStatus (0x8) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> (velocity & 0x7f); + } + + static PacketX1 makeNoteOnV1 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t velocity) + { + return Detail::makeV1().withGroup (group) + .withStatus (0x9) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> (velocity & 0x7f); + } + + static PacketX1 makePolyPressureV1 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t pressure) + { + return Detail::makeV1().withGroup (group) + .withStatus (0xa) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> (pressure & 0x7f); + } + + static PacketX1 makeControlChangeV1 (uint8_t group, + uint8_t channel, + uint8_t controller, + uint8_t value) + { + return Detail::makeV1().withGroup (group) + .withStatus (0xb) + .withChannel (channel) + .withU8<2> (controller & 0x7f) + .withU8<3> (value & 0x7f); + } + + static PacketX1 makeProgramChangeV1 (uint8_t group, + uint8_t channel, + uint8_t program) + { + return Detail::makeV1().withGroup (group) + .withStatus (0xc) + .withChannel (channel) + .withU8<2> (program & 0x7f); + } + + static PacketX1 makeChannelPressureV1 (uint8_t group, + uint8_t channel, + uint8_t pressure) + { + return Detail::makeV1().withGroup (group) + .withStatus (0xd) + .withChannel (channel) + .withU8<2> (pressure & 0x7f); + } + + static PacketX1 makePitchBend (uint8_t group, + uint8_t channel, + uint16_t pitchbend) + { + return Detail::makeV1().withGroup (group) + .withStatus (0xe) + .withChannel (channel) + .withU8<2> (pitchbend & 0x7f) + .withU8<3> ((pitchbend >> 7) & 0x7f); + } + + static PacketX2 makeSysExIn1Packet (uint8_t group, + uint8_t numBytes, + const uint8_t* data) + { + return Detail::makeSysEx (group, 0x0, numBytes, data); + } + + static PacketX2 makeSysExStart (uint8_t group, + uint8_t numBytes, + const uint8_t* data) + { + return Detail::makeSysEx (group, 0x1, numBytes, data); + } + + static PacketX2 makeSysExContinue (uint8_t group, + uint8_t numBytes, + const uint8_t* data) + { + return Detail::makeSysEx (group, 0x2, numBytes, data); + } + + static PacketX2 makeSysExEnd (uint8_t group, + uint8_t numBytes, + const uint8_t* data) + { + return Detail::makeSysEx (group, 0x3, numBytes, data); + } + + static PacketX2 makeRegisteredPerNoteControllerV2 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t controller, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x0) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> (controller & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeAssignablePerNoteControllerV2 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t controller, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x1) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> (controller & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeRegisteredControllerV2 (uint8_t group, + uint8_t channel, + uint8_t bank, + uint8_t index, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x2) + .withChannel (channel) + .withU8<2> (bank & 0x7f) + .withU8<3> (index & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeAssignableControllerV2 (uint8_t group, + uint8_t channel, + uint8_t bank, + uint8_t index, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x3) + .withChannel (channel) + .withU8<2> (bank & 0x7f) + .withU8<3> (index & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeRelativeRegisteredControllerV2 (uint8_t group, + uint8_t channel, + uint8_t bank, + uint8_t index, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x4) + .withChannel (channel) + .withU8<2> (bank & 0x7f) + .withU8<3> (index & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeRelativeAssignableControllerV2 (uint8_t group, + uint8_t channel, + uint8_t bank, + uint8_t index, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x5) + .withChannel (channel) + .withU8<2> (bank & 0x7f) + .withU8<3> (index & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makePerNotePitchBendV2 (uint8_t group, + uint8_t channel, + uint8_t note, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x6) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU32<1> (data); + } + + enum class NoteAttributeKind : uint8_t + { + none = 0x00, + manufacturer = 0x01, + profile = 0x02, + pitch7_9 = 0x03 + }; + + static PacketX2 makeNoteOffV2 (uint8_t group, + uint8_t channel, + uint8_t note, + NoteAttributeKind attribute, + uint16_t velocity, + uint16_t attributeValue) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x8) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> ((uint8_t) attribute) + .withU16<2> (velocity) + .withU16<3> (attributeValue); + } + + static PacketX2 makeNoteOnV2 (uint8_t group, + uint8_t channel, + uint8_t note, + NoteAttributeKind attribute, + uint16_t velocity, + uint16_t attributeValue) + { + return Detail::makeV2().withGroup (group) + .withStatus (0x9) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU8<3> ((uint8_t) attribute) + .withU16<2> (velocity) + .withU16<3> (attributeValue); + } + + static PacketX2 makePolyPressureV2 (uint8_t group, + uint8_t channel, + uint8_t note, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xa) + .withChannel (channel) + .withU8<2> (note & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeControlChangeV2 (uint8_t group, + uint8_t channel, + uint8_t controller, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xb) + .withChannel (channel) + .withU8<2> (controller & 0x7f) + .withU32<1> (data); + } + + static PacketX2 makeProgramChangeV2 (uint8_t group, + uint8_t channel, + uint8_t optionFlags, + uint8_t program, + uint8_t bankMsb, + uint8_t bankLsb) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xc) + .withChannel (channel) + .withU8<3> (optionFlags) + .withU8<4> (program) + .withU8<6> (bankMsb) + .withU8<7> (bankLsb); + } + + static PacketX2 makeChannelPressureV2 (uint8_t group, + uint8_t channel, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xd) + .withChannel (channel) + .withU32<1> (data); + } + + static PacketX2 makePitchBendV2 (uint8_t group, + uint8_t channel, + uint32_t data) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xe) + .withChannel (channel) + .withU32<1> (data); + } + + static PacketX2 makePerNoteManagementV2 (uint8_t group, + uint8_t channel, + uint8_t note, + uint8_t optionFlags) + { + return Detail::makeV2().withGroup (group) + .withStatus (0xf) + .withChannel (channel) + .withU8<2> (note) + .withU8<3> (optionFlags); + } + + + static PacketX4 makeSysEx8in1Packet (uint8_t group, + uint8_t numBytes, + uint8_t streamId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x0, numBytes, 3, data).withU8<2> (streamId); + } + + static PacketX4 makeSysEx8Start (uint8_t group, + uint8_t numBytes, + uint8_t streamId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x1, numBytes, 3, data).withU8<2> (streamId); + } + + static PacketX4 makeSysEx8Continue (uint8_t group, + uint8_t numBytes, + uint8_t streamId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x2, numBytes, 3, data).withU8<2> (streamId); + } + + static PacketX4 makeSysEx8End (uint8_t group, + uint8_t numBytes, + uint8_t streamId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x3, numBytes, 3, data).withU8<2> (streamId); + } + + static PacketX4 makeMixedDataSetHeader (uint8_t group, + uint8_t dataSetId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x8, 14, 2, data).withChannel (dataSetId); + } + + static PacketX4 makeDataSetPayload (uint8_t group, + uint8_t dataSetId, + const uint8_t* data) + { + return Detail::makeSysEx8 (group, 0x9, 14, 2, data).withChannel (dataSetId); + } +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h new file mode 100644 index 0000000..c092021 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h @@ -0,0 +1,126 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Enables iteration over a collection of Universal MIDI Packets stored as + a contiguous range of 32-bit words. + + This iterator is used by Packets to allow access to the messages + that it contains. + + @tags{Audio} +*/ +class Iterator +{ +public: + /** Creates an invalid (singular) iterator. */ + Iterator() noexcept = default; + + /** Creates an iterator pointing at `ptr`. */ + explicit Iterator (const uint32_t* ptr, size_t bytes) noexcept + : view (ptr) + #if JUCE_DEBUG + , bytesRemaining (bytes) + #endif + { + ignoreUnused (bytes); + } + + using difference_type = std::iterator_traits::difference_type; + using value_type = View; + using reference = const View&; + using pointer = const View*; + using iterator_category = std::input_iterator_tag; + + /** Moves this iterator to the next packet in the range. */ + Iterator& operator++() noexcept + { + const auto increment = view.size(); + + #if JUCE_DEBUG + // If you hit this, the memory region contained a truncated or otherwise + // malformed Universal MIDI Packet. + // The Iterator can only be used on regions containing complete packets! + jassert (increment <= bytesRemaining); + bytesRemaining -= increment; + #endif + + view = View (view.data() + increment); + return *this; + } + + /** Moves this iterator to the next packet in the range, + returning the value of the iterator before it was + incremented. + */ + Iterator operator++ (int) noexcept + { + auto copy = *this; + ++(*this); + return copy; + } + + /** Returns true if this iterator points to the same address + as another iterator. + */ + bool operator== (const Iterator& other) const noexcept + { + return view == other.view; + } + + /** Returns false if this iterator points to the same address + as another iterator. + */ + bool operator!= (const Iterator& other) const noexcept + { + return ! operator== (other); + } + + /** Returns a reference to a View of the packet currently + pointed-to by this iterator. + + The View can be queried for its size and content. + */ + reference operator*() noexcept { return view; } + + /** Returns a pointer to a View of the packet currently + pointed-to by this iterator. + + The View can be queried for its size and content. + */ + pointer operator->() noexcept { return &view; } + +private: + View view; + + #if JUCE_DEBUG + size_t bytesRemaining = 0; + #endif +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h new file mode 100644 index 0000000..11c127c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h @@ -0,0 +1,213 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using + the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages. + + @tags{Audio} +*/ +class Midi1ToBytestreamTranslator +{ +public: + /** Ensures that there is room in the internal buffer for a sysex message of at least + `initialBufferSize` bytes. + */ + explicit Midi1ToBytestreamTranslator (int initialBufferSize) + { + pendingSysExData.reserve (size_t (initialBufferSize)); + } + + /** Clears the concatenator. */ + void reset() + { + pendingSysExData.clear(); + pendingSysExTime = 0.0; + } + + /** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to + an equivalent MidiMessage. Accumulates SysEx packets into a single + MidiMessage, as appropriate. + + @param packet a packet which is using the MIDI 1.0 Protocol. + @param time the timestamp to be applied to these messages. + @param callback a callback which will be called with each converted MidiMessage. + */ + template + void dispatch (const View& packet, double time, MessageCallback&& callback) + { + const auto firstWord = *packet.data(); + + if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord)) + pendingSysExData.clear(); + + switch (packet.size()) + { + case 1: + { + // Utility messages don't translate to bytestream format + if (Utils::getMessageType (firstWord) != 0x00) + callback (fromUmp (PacketX1 { firstWord }, time)); + + break; + } + + case 2: + { + if (Utils::getMessageType (firstWord) == 0x3) + processSysEx (PacketX2 { packet[0], packet[1] }, time, callback); + + break; + } + + case 3: // no 3-word packets in the current spec + case 4: // no 4-word packets translate to bytestream format + default: + break; + } + } + + /** Converts from a Universal MIDI Packet to MIDI 1 bytestream format. + + This is only capable of converting a single Universal MIDI Packet to + an equivalent bytestream MIDI message. This function cannot understand + multi-packet messages, like SysEx7 messages. + + To convert multi-packet messages, use `Midi1ToBytestreamTranslator` + to convert from a UMP MIDI 1.0 stream, or `ToBytestreamDispatcher` + to convert from both MIDI 2.0 and MIDI 1.0. + */ + static MidiMessage fromUmp (const PacketX1& m, double time = 0) + { + const auto word = m.front(); + jassert (Utils::getNumWordsForMessageType (word) == 1); + + const std::array bytes { { uint8_t ((word >> 0x10) & 0xff), + uint8_t ((word >> 0x08) & 0xff), + uint8_t ((word >> 0x00) & 0xff) } }; + const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front()); + return MidiMessage (bytes.data(), numBytes, time); + } + +private: + template + void processSysEx (const PacketX2& packet, + double time, + MessageCallback&& callback) + { + switch (getSysEx7Kind (packet[0])) + { + case SysEx7::Kind::complete: + startSysExMessage (time); + pushBytes (packet); + terminateSysExMessage (callback); + break; + + case SysEx7::Kind::begin: + startSysExMessage (time); + pushBytes (packet); + break; + + case SysEx7::Kind::continuation: + if (pendingSysExData.empty()) + break; + + pushBytes (packet); + break; + + case SysEx7::Kind::end: + if (pendingSysExData.empty()) + break; + + pushBytes (packet); + terminateSysExMessage (callback); + break; + } + } + + void pushBytes (const PacketX2& packet) + { + const auto bytes = SysEx7::getDataBytes (packet); + pendingSysExData.insert (pendingSysExData.end(), + bytes.data.begin(), + bytes.data.begin() + bytes.size); + } + + void startSysExMessage (double time) + { + pendingSysExTime = time; + pendingSysExData.push_back (0xf0); + } + + template + void terminateSysExMessage (MessageCallback&& callback) + { + pendingSysExData.push_back (0xf7); + callback (MidiMessage (pendingSysExData.data(), + int (pendingSysExData.size()), + pendingSysExTime)); + pendingSysExData.clear(); + } + + static bool shouldPacketTerminateSysExEarly (uint32_t firstWord) + { + return ! (isSysExContinuation (firstWord) + || isSystemRealTime (firstWord) + || isJROrNOP (firstWord)); + } + + static SysEx7::Kind getSysEx7Kind (uint32_t word) + { + return SysEx7::Kind ((word >> 0x14) & 0xf); + } + + static bool isJROrNOP (uint32_t word) + { + return Utils::getMessageType (word) == 0x0; + } + + static bool isSysExContinuation (uint32_t word) + { + if (Utils::getMessageType (word) != 0x3) + return false; + + const auto kind = getSysEx7Kind (word); + return kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end; + } + + static bool isSystemRealTime (uint32_t word) + { + return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8; + } + + std::vector pendingSysExData; + + double pendingSysExTime = 0.0; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp new file mode 100644 index 0000000..13aa58a --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp @@ -0,0 +1,195 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 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)) + : helpers.byte0; + + return PacketX2 + { + Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, 0), + (uint32_t) (Conversion::scaleTo16 (velocity) << 0x10) + }; +} + +PacketX2 Midi1ToMidi2DefaultTranslator::processPolyPressure (const HelperValues helpers) +{ + return PacketX2 + { + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, 0), + Conversion::scaleTo32 (helpers.byte2) + }; +} + +bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues helpers, + PacketX2& packet) +{ + const auto statusAndChannel = helpers.byte0; + const auto cc = helpers.byte1; + + const auto shouldAccumulate = [&] + { + switch (cc) + { + case 6: + case 38: + case 98: + case 99: + case 100: + case 101: + return true; + } + + return false; + }(); + + const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); + const auto channel = (uint8_t) (statusAndChannel & 0xf); + const auto byte = helpers.byte2; + + if (shouldAccumulate) + { + auto& accumulator = groupAccumulators[group][channel]; + + if (accumulator.addByte (cc, byte)) + { + const auto& bytes = accumulator.getBytes(); + const auto bank = bytes[0]; + const auto index = bytes[1]; + const auto msb = bytes[2]; + const auto lsb = bytes[3]; + + const auto value = (uint16_t) (((msb & 0x7f) << 7) | (lsb & 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), + Conversion::scaleTo32 (value) + }; + return true; + } + + return false; + } + + if (cc == 0) + { + groupBanks[group][channel].setMsb (byte); + return false; + } + + if (cc == 32) + { + groupBanks[group][channel].setLsb (byte); + return false; + } + + packet = PacketX2 + { + Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, cc, 0), + Conversion::scaleTo32 (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 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) + }; +} + +PacketX2 Midi1ToMidi2DefaultTranslator::processChannelPressure (const HelperValues helpers) +{ + return PacketX2 + { + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), + Conversion::scaleTo32 (helpers.byte1) + }; +} + +PacketX2 Midi1ToMidi2DefaultTranslator::processPitchBend (const HelperValues helpers) +{ + const auto lsb = helpers.byte1; + const auto msb = helpers.byte2; + const auto value = (uint16_t) (msb << 7 | lsb); + + return PacketX2 + { + Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), + Conversion::scaleTo32 (value) + }; +} + +bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, uint8_t byte) +{ + const auto isStart = cc == 99 || cc == 101; + + if (isStart) + { + kind = cc == 99 ? PnKind::nrpn : PnKind::rpn; + index = 0; + } + + bytes[index] = byte; + + const auto shouldContinue = [&] + { + switch (index) + { + case 0: return isStart; + case 1: return kind == PnKind::nrpn ? cc == 98 : cc == 100; + case 2: return cc == 6; + case 3: return cc == 38; + } + + return false; + }(); + + index = shouldContinue ? index + 1 : 0; + + if (index != bytes.size()) + return false; + + index = 0; + return true; +} + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h new file mode 100644 index 0000000..9d993aa --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h @@ -0,0 +1,187 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Translates a series of MIDI 1 Universal MIDI Packets to corresponding MIDI 2 + packets. + + @tags{Audio} +*/ +class Midi1ToMidi2DefaultTranslator +{ +public: + Midi1ToMidi2DefaultTranslator() = default; + + /** Converts MIDI 1 Universal MIDI Packets to corresponding MIDI 2 packets, + calling `callback` with each converted packet. + + In some cases (such as RPN/NRPN messages) multiple MIDI 1 packets will + convert to a single MIDI 2 packet. In these cases, the translator will + accumulate the full message internally, and send a single callback with + the completed message, once all the individual MIDI 1 packets have been + processed. + */ + template + void dispatch (const View& v, PacketCallback&& callback) + { + const auto firstWord = v[0]; + const auto messageType = Utils::getMessageType (firstWord); + + if (messageType != 0x2) + { + callback (v); + return; + } + + 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), + }; + + switch (Utils::getStatus (firstWord)) + { + case 0x8: + case 0x9: + { + const auto packet = processNoteOnOrOff (helperValues); + callback (View (packet.data())); + return; + } + + case 0xa: + { + const auto packet = processPolyPressure (helperValues); + callback (View (packet.data())); + return; + } + + case 0xb: + { + PacketX2 packet; + + if (processControlChange (helperValues, packet)) + callback (View (packet.data())); + + return; + } + + case 0xc: + { + const auto packet = processProgramChange (helperValues); + callback (View (packet.data())); + return; + } + + case 0xd: + { + const auto packet = processChannelPressure (helperValues); + callback (View (packet.data())); + return; + } + + case 0xe: + { + const auto packet = processPitchBend (helperValues); + callback (View (packet.data())); + return; + } + } + } + + void reset() + { + groupAccumulators = {}; + groupBanks = {}; + } + +private: + enum class PnKind { nrpn, rpn }; + + struct HelperValues + { + uint8_t typeAndGroup; + uint8_t byte0; + uint8_t byte1; + uint8_t byte2; + }; + + static PacketX2 processNoteOnOrOff (const HelperValues helpers); + static PacketX2 processPolyPressure (const HelperValues helpers); + + bool processControlChange (const HelperValues helpers, PacketX2& packet); + + PacketX2 processProgramChange (const HelperValues helpers) const; + + static PacketX2 processChannelPressure (const HelperValues helpers); + static PacketX2 processPitchBend (const HelperValues helpers); + + class PnAccumulator + { + public: + bool addByte (uint8_t cc, uint8_t byte); + + const std::array& getBytes() const noexcept { return bytes; } + PnKind getKind() const noexcept { return kind; } + + private: + std::array bytes; + uint8_t index = 0; + PnKind kind = PnKind::nrpn; + }; + + class Bank + { + public: + bool isValid() const noexcept { return ! (msb & 0x80); } + + uint8_t getMsb() const noexcept { return msb & 0x7f; } + uint8_t getLsb() const noexcept { return lsb & 0x7f; } + + void setMsb (uint8_t i) noexcept { msb = i & 0x7f; } + void setLsb (uint8_t i) noexcept { msb &= 0x7f; lsb = i & 0x7f; } + + private: + // We use the top bit to indicate whether this bank is valid. + // After reading the spec, it's not clear how we should determine whether + // there are valid values, so we'll just assume that the bank is valid + // once either the lsb or msb have been written. + uint8_t msb = 0x80; + uint8_t lsb = 0x00; + }; + + using ChannelAccumulators = std::array; + std::array groupAccumulators; + + using ChannelBanks = std::array; + std::array groupBanks; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h new file mode 100644 index 0000000..f465166 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h @@ -0,0 +1,44 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets. */ +enum class PacketProtocol +{ + MIDI_1_0, + MIDI_2_0, +}; + +/** All kinds of MIDI protocol understood by JUCE. */ +enum class MidiProtocol +{ + bytestream, + UMP_MIDI_1_0, + UMP_MIDI_2_0, +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h new file mode 100644 index 0000000..408ab6c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h @@ -0,0 +1,40 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + A base class for classes which receive Universal MIDI Packets from an input. +*/ +struct Receiver +{ + virtual ~Receiver() noexcept = default; + + /** This will be called each time a new packet is ready for processing. */ + virtual void packetReceived (const View& packet, double time) = 0; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp new file mode 100644 index 0000000..43e6c22 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp @@ -0,0 +1,53 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +uint32_t SysEx7::getNumPacketsRequiredForDataSize (uint32_t size) +{ + constexpr auto denom = 6; + return (size / denom) + ((size % denom) != 0); +} + +SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) +{ + const auto numBytes = Utils::getChannel (packet[0]); + constexpr uint8_t maxBytes = 6; + jassert (numBytes <= maxBytes); + + return + { + { packet.getU8<2>(), + packet.getU8<3>(), + packet.getU8<4>(), + packet.getU8<5>(), + packet.getU8<6>(), + packet.getU8<7>() }, + jmin (numBytes, maxBytes) + }; +} + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h new file mode 100644 index 0000000..d330e4d --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h @@ -0,0 +1,66 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +struct SysEx7 +{ + /** Returns the number of 64-bit packets required to hold a series of + SysEx bytes. + + The number passed to this function should exclude the leading/trailing + SysEx bytes used in an old midi bytestream, as these are not required + when using Universal MIDI Packets. + */ + static uint32_t getNumPacketsRequiredForDataSize (uint32_t); + + /** The different kinds of UMP SysEx-7 message. */ + enum class Kind : uint8_t + { + /** The whole message fits in a single 2-word packet. */ + complete = 0, + + /** The packet begins a SysEx message that will continue in subsequent packets. */ + begin = 1, + + /** The packet is a continuation of an ongoing SysEx message. */ + continuation = 2, + + /** The packet terminates an ongoing SysEx message. */ + end = 3 + }; + + struct PacketBytes + { + std::array data; + uint8_t size; + }; + + /** Extracts the data bytes from a 64-bit data message. */ + static PacketBytes getDataBytes (const PacketX2& packet); +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp new file mode 100644 index 0000000..97c9779 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp @@ -0,0 +1,1020 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +#if JUCE_UNIT_TESTS + +constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast (i); } +constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast (i); } +constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast (i); } +constexpr uint64_t operator""_u64 (unsigned long long int i) { return static_cast (i); } + +class UniversalMidiPacketTests : public UnitTest +{ +public: + UniversalMidiPacketTests() + : UnitTest ("Universal MIDI Packet", UnitTestCategories::midi) + { + } + + void runTest() override + { + auto random = getRandom(); + + beginTest ("Short bytestream midi messages can be round-tripped through the UMP converter"); + { + Midi1ToBytestreamTranslator translator (0); + + forEachNonSysExTestMessage (random, [&] (const MidiMessage& m) + { + Packets packets; + Conversion::toMidi1 (m, packets); + 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)); + + translator.dispatch (View {packets.data() }, + 0, + [&] (const MidiMessage& roundTripped) + { + expect (equal (m, roundTripped)); + }); + }); + } + + beginTest ("Bytestream SysEx converts to universal packets"); + { + { + // Zero length message + Packets packets; + Conversion::toMidi1 (createRandomSysEx (random, 0), packets); + expect (packets.size() == 2); + + expect (packets.data()[0] == 0x30000000); + expect (packets.data()[1] == 0x00000000); + } + + { + const auto message = createRandomSysEx (random, 1); + Packets packets; + Conversion::toMidi1 (message, packets); + expect (packets.size() == 2); + + const auto* sysEx = message.getSysExData(); + expect (packets.data()[0] == Utils::bytesToWord (0x30, 0x01, sysEx[0], 0)); + expect (packets.data()[1] == 0x00000000); + } + + { + const auto message = createRandomSysEx (random, 6); + Packets packets; + Conversion::toMidi1 (message, packets); + 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])); + } + + { + const auto message = createRandomSysEx (random, 12); + Packets packets; + Conversion::toMidi1 (message, packets); + 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])); + } + + { + const auto message = createRandomSysEx (random, 13); + Packets packets; + Conversion::toMidi1 (message, packets); + 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()[5] == 0x00000000); + } + } + + ToBytestreamDispatcher converter (0); + Packets packets; + + const auto checkRoundTrip = [&] (const MidiBuffer& expected) + { + for (const auto meta : expected) + Conversion::toMidi1 (meta.getMessage(), packets); + + MidiBuffer output; + converter.dispatch (packets.data(), + packets.data() + packets.size(), + 0, + [&] (const MidiMessage& roundTripped) + { + output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + }); + packets.clear(); + + expect (equal (expected, output)); + }; + + beginTest ("Long SysEx bytestream midi messages can be round-tripped through the UMP converter"); + { + for (auto length : { 0, 1, 2, 3, 4, 5, 6, 7, 13, 20, 100, 1000 }) + { + MidiBuffer expected; + expected.addEvent (createRandomSysEx (random, size_t (length)), 0); + checkRoundTrip (expected); + } + } + + beginTest ("UMP SysEx7 messages interspersed with utility messages convert to bytestream"); + { + const auto sysEx = createRandomSysEx (random, 100); + Packets originalPackets; + Conversion::toMidi1 (sysEx, originalPackets); + + Packets modifiedPackets; + + const auto addRandomUtilityUMP = [&] + { + const auto newPacket = createRandomUtilityUMP (random); + modifiedPackets.add (View (newPacket.data())); + }; + + for (const auto& packet : originalPackets) + { + addRandomUtilityUMP(); + modifiedPackets.add (packet); + addRandomUtilityUMP(); + } + + MidiBuffer output; + converter.dispatch (modifiedPackets.data(), + modifiedPackets.data() + modifiedPackets.size(), + 0, + [&] (const MidiMessage& roundTripped) + { + output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + }); + + // All Utility messages should have been ignored + expect (output.getNumEvents() == 1); + + for (const auto meta : output) + expect (equal (meta.getMessage(), sysEx)); + } + + beginTest ("UMP SysEx7 messages interspersed with System Realtime messages convert to bytestream"); + { + const auto sysEx = createRandomSysEx (random, 200); + Packets originalPackets; + Conversion::toMidi1 (sysEx, originalPackets); + + Packets modifiedPackets; + MidiBuffer realtimeMessages; + + const auto addRandomRealtimeUMP = [&] + { + const auto newPacket = createRandomRealtimeUMP (random); + modifiedPackets.add (View (newPacket.data())); + realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0); + }; + + for (const auto& packet : originalPackets) + { + addRandomRealtimeUMP(); + modifiedPackets.add (packet); + addRandomRealtimeUMP(); + } + + MidiBuffer output; + converter.dispatch (modifiedPackets.data(), + modifiedPackets.data() + modifiedPackets.size(), + 0, + [&] (const MidiMessage& roundTripped) + { + output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + }); + + const auto numOutputs = output.getNumEvents(); + const auto numInputs = realtimeMessages.getNumEvents(); + expect (numOutputs == numInputs + 1); + + if (numOutputs == numInputs + 1) + { + const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, + const MidiMessageMetadata& b) + { + return equal (a.getMessage(), b.getMessage()); + }; + + auto it = output.begin(); + + for (const auto meta : realtimeMessages) + { + if (! isMetadataEquivalent (*it, meta)) + { + expect (equal ((*it).getMessage(), sysEx)); + ++it; + } + + expect (isMetadataEquivalent (*it, meta)); + ++it; + } + } + } + + 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); + + Packets modifiedPackets; + MidiBuffer realtimeMessages; + + const auto addRandomRealtimeUMP = [&] + { + const auto newPacket = createRandomRealtimeUMP (random); + modifiedPackets.add (View (newPacket.data())); + realtimeMessages.addEvent (Midi1ToBytestreamTranslator::fromUmp (newPacket), 0); + }; + + const auto addRandomUtilityUMP = [&] + { + const auto newPacket = createRandomUtilityUMP (random); + modifiedPackets.add (View (newPacket.data())); + }; + + for (const auto& packet : originalPackets) + { + addRandomRealtimeUMP(); + addRandomUtilityUMP(); + modifiedPackets.add (packet); + addRandomRealtimeUMP(); + addRandomUtilityUMP(); + } + + MidiBuffer output; + converter.dispatch (modifiedPackets.data(), + modifiedPackets.data() + modifiedPackets.size(), + 0, + [&] (const MidiMessage& roundTripped) + { + output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + }); + + const auto numOutputs = output.getNumEvents(); + const auto numInputs = realtimeMessages.getNumEvents(); + expect (numOutputs == numInputs + 1); + + if (numOutputs == numInputs + 1) + { + const auto isMetadataEquivalent = [] (const MidiMessageMetadata& a, const MidiMessageMetadata& b) + { + return equal (a.getMessage(), b.getMessage()); + }; + + auto it = output.begin(); + + for (const auto meta : realtimeMessages) + { + if (! isMetadataEquivalent (*it, meta)) + { + expect (equal ((*it).getMessage(), sysEx)); + ++it; + } + + expect (isMetadataEquivalent (*it, meta)); + ++it; + } + } + } + + beginTest ("SysEx messages are terminated by non-Utility, non-Realtime messages"); + { + const auto noteOn = [&] + { + MidiBuffer b; + b.addEvent (MidiMessage::noteOn (1, uint8_t (64), uint8_t (64)), 0); + return b; + }(); + + const auto noteOnPackets = [&] + { + Packets p; + + for (const auto meta : noteOn) + Conversion::toMidi1 (meta.getMessage(), p); + + return p; + }(); + + const auto sysEx = createRandomSysEx (random, 300); + + const auto originalPackets = [&] + { + Packets p; + Conversion::toMidi1 (sysEx, p); + return p; + }(); + + const auto modifiedPackets = [&] + { + Packets p; + + const auto insertionPoint = std::next (originalPackets.begin(), 10); + std::for_each (originalPackets.begin(), + insertionPoint, + [&] (const View& view) { p.add (view); }); + + for (const auto& view : noteOnPackets) + p.add (view); + + std::for_each (insertionPoint, + originalPackets.end(), + [&] (const View& view) { p.add (view); }); + + return p; + }(); + + // modifiedPackets now contains some SysEx packets interrupted by a MIDI 1 noteOn + + MidiBuffer output; + + const auto pushToOutput = [&] (const Packets& p) + { + converter.dispatch (p.data(), + p.data() + p.size(), + 0, + [&] (const MidiMessage& roundTripped) + { + output.addEvent (roundTripped, int (roundTripped.getTimeStamp())); + }); + }; + + pushToOutput (modifiedPackets); + + // Interrupted sysEx shouldn't be present + expect (equal (output, noteOn)); + + const auto newSysEx = createRandomSysEx (random, 300); + Packets newSysExPackets; + Conversion::toMidi1 (newSysEx, newSysExPackets); + + // If we push another midi event without interrupting it, + // it should get through without being modified, + // and it shouldn't be affected by the previous (interrupted) sysex. + + output.clear(); + pushToOutput (newSysExPackets); + + expect (output.getNumEvents() == 1); + + for (const auto meta : output) + expect (equal (meta.getMessage(), newSysEx)); + } + + beginTest ("Widening conversions work"); + { + // This is similar to the 'slow' example code from the MIDI 2.0 spec + const auto baselineScale = [] (uint32_t srcVal, uint32_t srcBits, uint32_t dstBits) + { + const auto scaleBits = (uint32_t) (dstBits - srcBits); + + auto bitShiftedValue = (uint32_t) (srcVal << scaleBits); + + const auto srcCenter = (uint32_t) (1 << (srcBits - 1)); + + if (srcVal <= srcCenter) + return bitShiftedValue; + + const auto repeatBits = (uint32_t) (srcBits - 1); + const auto repeatMask = (uint32_t) ((1 << repeatBits) - 1); + + auto repeatValue = (uint32_t) (srcVal & repeatMask); + + if (scaleBits > repeatBits) + repeatValue <<= scaleBits - repeatBits; + else + repeatValue >>= repeatBits - scaleBits; + + while (repeatValue != 0) + { + bitShiftedValue |= repeatValue; + repeatValue >>= repeatBits; + } + + return bitShiftedValue; + }; + + const auto baselineScale7To8 = [&] (uint8_t in) + { + return baselineScale (in, 7, 8); + }; + + const auto baselineScale7To16 = [&] (uint8_t in) + { + return baselineScale (in, 7, 16); + }; + + const auto baselineScale14To16 = [&] (uint16_t in) + { + return baselineScale (in, 14, 16); + }; + + const auto baselineScale7To32 = [&] (uint8_t in) + { + return baselineScale (in, 7, 32); + }; + + const auto baselineScale14To32 = [&] (uint16_t in) + { + return baselineScale (in, 14, 32); + }; + + for (auto i = 0; i != 100; ++i) + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals ((int64_t) Conversion::scaleTo8 (rand), + (int64_t) baselineScale7To8 (rand)); + } + + expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x00), (int64_t) 0x0000); + expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x0a), (int64_t) 0x1400); + expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x40), (int64_t) 0x8000); + expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x57), (int64_t) 0xaeba); + expectEquals ((int64_t) Conversion::scaleTo16 ((uint8_t) 0x7f), (int64_t) 0xffff); + + for (auto i = 0; i != 100; ++i) + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals ((int64_t) Conversion::scaleTo16 (rand), + (int64_t) baselineScale7To16 (rand)); + } + + for (auto i = 0; i != 100; ++i) + { + const auto rand = (uint16_t) random.nextInt (0x4000); + expectEquals ((int64_t) Conversion::scaleTo16 (rand), + (int64_t) baselineScale14To16 (rand)); + } + + for (auto i = 0; i != 100; ++i) + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals ((int64_t) Conversion::scaleTo32 (rand), + (int64_t) baselineScale7To32 (rand)); + } + + expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x0000), (int64_t) 0x00000000); + expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x2000), (int64_t) 0x80000000); + expectEquals ((int64_t) Conversion::scaleTo32 ((uint16_t) 0x3fff), (int64_t) 0xffffffff); + + for (auto i = 0; i != 100; ++i) + { + const auto rand = (uint16_t) random.nextInt (0x4000); + expectEquals ((int64_t) Conversion::scaleTo32 (rand), + (int64_t) baselineScale14To32 (rand)); + } + } + + beginTest ("Round-trip widening/narrowing conversions work"); + { + for (auto i = 0; i != 100; ++i) + { + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals (Conversion::scaleTo7 (Conversion::scaleTo8 (rand)), rand); + } + + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals (Conversion::scaleTo7 (Conversion::scaleTo16 (rand)), rand); + } + + { + const auto rand = (uint8_t) random.nextInt (0x80); + expectEquals (Conversion::scaleTo7 (Conversion::scaleTo32 (rand)), rand); + } + + { + const auto rand = (uint16_t) random.nextInt (0x4000); + expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo16 (rand)), (uint64_t) rand); + } + + { + const auto rand = (uint16_t) random.nextInt (0x4000); + expectEquals ((uint64_t) Conversion::scaleTo14 (Conversion::scaleTo32 (rand)), (uint64_t) rand); + } + } + } + + beginTest ("MIDI 2 -> 1 note on conversions"); + { + { + Packets midi2; + midi2.add (PacketX2 { 0x41946410, 0x12345678 }); + + Packets midi1; + midi1.add (PacketX1 { 0x21946409 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + { + // If the velocity is close to 0, the output velocity should still be 1 + Packets midi2; + midi2.add (PacketX2 { 0x4295327f, 0x00345678 }); + + Packets midi1; + midi1.add (PacketX1 { 0x22953201 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + } + + beginTest ("MIDI 2 -> 1 note off conversion"); + { + Packets midi2; + midi2.add (PacketX2 { 0x448b0520, 0xfedcba98 }); + + Packets midi1; + midi1.add (PacketX1 { 0x248b057f }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + beginTest ("MIDI 2 -> 1 poly pressure conversion"); + { + Packets midi2; + midi2.add (PacketX2 { 0x49af0520, 0x80dcba98 }); + + Packets midi1; + midi1.add (PacketX1 { 0x29af0540 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + beginTest ("MIDI 2 -> 1 control change conversion"); + { + Packets midi2; + midi2.add (PacketX2 { 0x49b00520, 0x80dcba98 }); + + Packets midi1; + midi1.add (PacketX1 { 0x29b00540 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + beginTest ("MIDI 2 -> 1 channel pressure conversion"); + { + Packets midi2; + midi2.add (PacketX2 { 0x40d20520, 0x80dcba98 }); + + Packets midi1; + midi1.add (PacketX1 { 0x20d24000 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + beginTest ("MIDI 2 -> 1 nrpn rpn conversion"); + { + { + Packets midi2; + midi2.add (PacketX2 { 0x44240123, 0x456789ab }); + + Packets midi1; + midi1.add (PacketX1 { 0x24b46501 }); + midi1.add (PacketX1 { 0x24b46423 }); + midi1.add (PacketX1 { 0x24b40622 }); + midi1.add (PacketX1 { 0x24b42659 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + { + Packets midi2; + midi2.add (PacketX2 { 0x48347f7f, 0xffffffff }); + + Packets midi1; + midi1.add (PacketX1 { 0x28b4637f }); + midi1.add (PacketX1 { 0x28b4627f }); + midi1.add (PacketX1 { 0x28b4067f }); + midi1.add (PacketX1 { 0x28b4267f }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + } + + beginTest ("MIDI 2 -> 1 program change and bank select conversion"); + { + { + // If the bank valid bit is 0, just emit a program change + Packets midi2; + midi2.add (PacketX2 { 0x4cc10000, 0x70004020 }); + + Packets midi1; + midi1.add (PacketX1 { 0x2cc17000 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + { + // If the bank valid bit is 1, emit bank select control changes and a program change + Packets midi2; + midi2.add (PacketX2 { 0x4bc20001, 0x70004020 }); + + Packets midi1; + midi1.add (PacketX1 { 0x2bb20040 }); + midi1.add (PacketX1 { 0x2bb22020 }); + midi1.add (PacketX1 { 0x2bc27000 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + } + + beginTest ("MIDI 2 -> 1 pitch bend conversion"); + { + Packets midi2; + midi2.add (PacketX2 { 0x4eee0000, 0x12340000 }); + + Packets midi1; + midi1.add (PacketX1 { 0x2eee0d09 }); + + checkMidi2ToMidi1Conversion (midi2, midi1); + } + + beginTest ("MIDI 2 -> 1 messages which don't convert"); + { + const uint8_t opcodes[] { 0x0, 0x1, 0x4, 0x5, 0x6, 0xf }; + + for (const auto opcode : opcodes) + { + Packets midi2; + midi2.add (PacketX2 { Utils::bytesToWord (0x40, (uint8_t) (opcode << 0x4), 0, 0), 0x0 }); + checkMidi2ToMidi1Conversion (midi2, {}); + } + } + + beginTest ("MIDI 2 -> 1 messages which are passed through"); + { + const uint8_t typecodesX1[] { 0x0, 0x1, 0x2 }; + + for (const auto typecode : typecodesX1) + { + Packets p; + p.add (PacketX1 { (uint32_t) (typecode << 0x1c | (random.nextInt64() & 0xffffff)) }); + + checkMidi2ToMidi1Conversion (p, p); + } + + { + Packets p; + p.add (PacketX2 { (uint32_t) (0x3 << 0x1c | (random.nextInt64() & 0xffffff)), + (uint32_t) (random.nextInt64() & 0xffffffff) }); + + checkMidi2ToMidi1Conversion (p, p); + } + + { + Packets p; + p.add (PacketX4 { (uint32_t) (0x5 << 0x1c | (random.nextInt64() & 0xffffff)), + (uint32_t) (random.nextInt64() & 0xffffffff), + (uint32_t) (random.nextInt64() & 0xffffffff), + (uint32_t) (random.nextInt64() & 0xffffffff) }); + + checkMidi2ToMidi1Conversion (p, p); + } + } + + beginTest ("MIDI 2 -> 1 control changes which should be ignored"); + { + const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 }; + + for (const auto cc : CCs) + { + Packets midi2; + midi2.add (PacketX2 { (uint32_t) (0x40b00000 | (cc << 0x8)), 0x00000000 }); + + checkMidi2ToMidi1Conversion (midi2, {}); + } + } + + beginTest ("MIDI 1 -> 2 note on conversions"); + { + { + Packets midi1; + midi1.add (PacketX1 { 0x20904040 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40904000, static_cast (Conversion::scaleTo16 (0x40_u8)) << 0x10 }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + // If velocity is 0, convert to a note-off + { + Packets midi1; + midi1.add (PacketX1 { 0x23935100 }); + + Packets midi2; + midi2.add (PacketX2 { 0x43835100, 0x0 }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + } + + beginTest ("MIDI 1 -> 2 note off conversions"); + { + Packets midi1; + midi1.add (PacketX1 { 0x21831020 }); + + Packets midi2; + midi2.add (PacketX2 { 0x41831000, static_cast (Conversion::scaleTo16 (0x20_u8)) << 0x10 }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + beginTest ("MIDI 1 -> 2 poly pressure conversions"); + { + Packets midi1; + midi1.add (PacketX1 { 0x20af7330 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40af7300, Conversion::scaleTo32 (0x30_u8) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + beginTest ("individual MIDI 1 -> 2 control changes which should be ignored"); + { + const uint8_t CCs[] { 6, 38, 98, 99, 100, 101, 0, 32 }; + + for (const auto cc : CCs) + { + Packets midi1; + midi1.add (PacketX1 { Utils::bytesToWord (0x20, 0xb0, cc, 0x00) }); + + checkMidi1ToMidi2Conversion (midi1, {}); + } + } + + beginTest ("MIDI 1 -> 2 control change conversions"); + { + // normal control change + { + Packets midi1; + midi1.add (PacketX1 { 0x29b1017f }); + + Packets midi2; + midi2.add (PacketX2 { 0x49b10100, Conversion::scaleTo32 (0x7f_u8) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + // nrpn + { + Packets midi1; + midi1.add (PacketX1 { 0x20b06301 }); + midi1.add (PacketX1 { 0x20b06223 }); + midi1.add (PacketX1 { 0x20b00645 }); + midi1.add (PacketX1 { 0x20b02667 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40300123, Conversion::scaleTo32 (static_cast ((0x45 << 7) | 0x67)) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + // rpn + { + Packets midi1; + midi1.add (PacketX1 { 0x20b06543 }); + midi1.add (PacketX1 { 0x20b06421 }); + midi1.add (PacketX1 { 0x20b00601 }); + midi1.add (PacketX1 { 0x20b02623 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40204321, Conversion::scaleTo32 (static_cast ((0x01 << 7) | 0x23)) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + } + + beginTest ("MIDI 1 -> MIDI 2 program change and bank select"); + { + Packets midi1; + // program change with bank + midi1.add (PacketX1 { 0x2bb20030 }); + midi1.add (PacketX1 { 0x2bb22010 }); + midi1.add (PacketX1 { 0x2bc24000 }); + // program change without bank (different group and channel) + midi1.add (PacketX1 { 0x20c01000 }); + + Packets midi2; + midi2.add (PacketX2 { 0x4bc20001, 0x40003010 }); + midi2.add (PacketX2 { 0x40c00000, 0x10000000 }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + beginTest ("MIDI 1 -> MIDI 2 channel pressure conversions"); + { + Packets midi1; + midi1.add (PacketX1 { 0x20df3000 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40df0000, Conversion::scaleTo32 (0x30_u8) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + + beginTest ("MIDI 1 -> MIDI 2 pitch bend conversions"); + { + Packets midi1; + midi1.add (PacketX1 { 0x20e74567 }); + + Packets midi2; + midi2.add (PacketX2 { 0x40e70000, Conversion::scaleTo32 (static_cast ((0x67 << 7) | 0x45)) }); + + checkMidi1ToMidi2Conversion (midi1, midi2); + } + } + +private: + static Packets convertMidi2ToMidi1 (const Packets& midi2) + { + Packets r; + + for (const auto& packet : midi2) + Conversion::midi2ToMidi1DefaultTranslation (packet, [&r] (const View& v) { r.add (v); }); + + return r; + } + + static Packets convertMidi1ToMidi2 (const Packets& midi1) + { + Packets r; + Midi1ToMidi2DefaultTranslator translator; + + for (const auto& packet : midi1) + translator.dispatch (packet, [&r] (const View& v) { r.add (v); }); + + return r; + } + + void checkBytestreamConversion (const Packets& actual, const Packets& expected) + { + expectEquals ((int) actual.size(), (int) expected.size()); + + if (actual.size() != expected.size()) + return; + + auto actualPtr = actual.data(); + + std::for_each (expected.data(), + expected.data() + expected.size(), + [&] (const uint32_t word) { expectEquals ((uint64_t) *actualPtr++, (uint64_t) word); }); + } + + void checkMidi2ToMidi1Conversion (const Packets& midi2, const Packets& expected) + { + checkBytestreamConversion (convertMidi2ToMidi1 (midi2), expected); + } + + void checkMidi1ToMidi2Conversion (const Packets& midi1, const Packets& expected) + { + checkBytestreamConversion (convertMidi1ToMidi2 (midi1), expected); + } + + MidiMessage createRandomSysEx (Random& random, size_t sysExBytes) + { + std::vector data; + data.reserve (sysExBytes); + + for (size_t i = 0; i != sysExBytes; ++i) + data.push_back (uint8_t (random.nextInt (0x80))); + + return MidiMessage::createSysExMessage (data.data(), int (data.size())); + } + + PacketX1 createRandomUtilityUMP (Random& random) + { + 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))) }; + } + + PacketX1 createRandomRealtimeUMP (Random& random) + { + const auto status = [&] + { + 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; + } + + jassertfalse; + return 0x00; + }(); + + return PacketX1 { Utils::bytesToWord (0x10, uint8_t (status), 0x00, 0x00) }; + } + + template + void forEachNonSysExTestMessage (Random& random, Fn&& fn) + { + for (uint8_t firstByte = 0x80; firstByte != 0x00; ++firstByte) + { + if (firstByte == 0xf0 || firstByte == 0xf7) + continue; // sysEx is tested separately + + const auto length = MidiMessage::getMessageLengthFromFirstByte (firstByte); + const auto getDataByte = [&] { return uint8_t (random.nextInt (256) & 0x7f); }; + + const auto message = [&] + { + switch (length) + { + case 1: return MidiMessage (firstByte); + case 2: return MidiMessage (firstByte, getDataByte()); + case 3: return MidiMessage (firstByte, getDataByte(), getDataByte()); + } + + return MidiMessage(); + }(); + + fn (message); + } + } + + #if JUCE_WINDOWS + #define JUCE_CHECKED_ITERATOR(msg, size) \ + stdext::checked_array_iterator::type> ((msg), (size)) + #else + #define JUCE_CHECKED_ITERATOR(msg, size) (msg) + #endif + + static bool equal (const MidiMessage& a, const MidiMessage& b) noexcept + { + return a.getRawDataSize() == b.getRawDataSize() + && std::equal (a.getRawData(), a.getRawData() + a.getRawDataSize(), + JUCE_CHECKED_ITERATOR (b.getRawData(), b.getRawDataSize())); + } + + #undef JUCE_CHECKED_ITERATOR + + static bool equal (const MidiBuffer& a, const MidiBuffer& b) noexcept + { + return a.data == b.data; + } +}; + +static UniversalMidiPacketTests universalMidiPacketTests; + +#endif + +} +} 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 new file mode 100644 index 0000000..1806fc9 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h @@ -0,0 +1,131 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + A base class for classes which convert Universal MIDI Packets to other + formats. +*/ +struct U32InputHandler +{ + virtual ~U32InputHandler() noexcept = default; + + virtual void reset() = 0; + virtual void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) = 0; +}; + +/** + Parses a continuous stream of U32 words and emits complete MidiMessages whenever a full + message is received. +*/ +struct U32ToBytestreamHandler : public U32InputHandler +{ + U32ToBytestreamHandler (MidiInput& i, MidiInputCallback& c) + : input (i), callback (c), dispatcher (2048) {} + + class Factory + { + public: + explicit Factory (MidiInputCallback* c) + : callback (c) {} + + std::unique_ptr operator() (MidiInput& i) const + { + if (callback != nullptr) + return std::make_unique (i, *callback); + + jassertfalse; + return {}; + } + + private: + MidiInputCallback* callback = nullptr; + }; + + void reset() override { dispatcher.reset(); } + + void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override + { + dispatcher.dispatch (begin, end, time, [this] (const MidiMessage& m) + { + callback.handleIncomingMidiMessage (&input, m); + }); + } + + MidiInput& input; + MidiInputCallback& callback; + ToBytestreamDispatcher dispatcher; +}; + +/** + Parses a continuous stream of U32 words and emits full messages in the requested + UMP format. +*/ +struct U32ToUMPHandler : public U32InputHandler +{ + U32ToUMPHandler (PacketProtocol protocol, Receiver& c) + : recipient (c), converter (protocol) {} + + class Factory + { + public: + Factory (PacketProtocol p, Receiver& c) + : protocol (p), callback (c) {} + + std::unique_ptr operator() (MidiInput&) const + { + return std::make_unique (protocol, callback); + } + + private: + PacketProtocol protocol; + Receiver& callback; + }; + + void reset() override + { + dispatcher.reset(); + converter.reset(); + } + + void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override + { + dispatcher.dispatch (begin, end, time, [this] (const View& view, double thisTime) + { + converter.convert (view, [&] (const View& converted) + { + recipient.packetReceived (converted, thisTime); + }); + }); + } + + Receiver& recipient; + Dispatcher dispatcher; + GenericUMPConverter converter; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp new file mode 100644 index 0000000..6ed97ba --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp @@ -0,0 +1,59 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +uint32_t Utils::getNumWordsForMessageType (uint32_t mt) +{ + switch (Utils::getMessageType (mt)) + { + case 0x0: + case 0x1: + case 0x2: + case 0x6: + case 0x7: + return 1; + case 0x3: + case 0x4: + case 0x8: + case 0x9: + case 0xa: + return 2; + case 0xb: + case 0xc: + return 3; + case 0x5: + case 0xd: + case 0xe: + case 0xf: + return 4; + } + + jassertfalse; + return 1; +} + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h new file mode 100644 index 0000000..a8c2a08 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h @@ -0,0 +1,104 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Helpful types and functions for interacting with Universal MIDI Packets. + + @tags{Audio} +*/ +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) + { + return uint32_t (a << 0x18 | b << 0x10 | c << 0x08 | d << 0x00); + } + + /** Returns the expected number of 32-bit words in a Universal MIDI Packet, given + the first word of the packet. + + The result will be between 1 and 4 inclusive. + A result of 1 means that the word is itself a complete packet. + */ + static uint32_t getNumWordsForMessageType (uint32_t); + + template + struct U4 + { + static constexpr uint32_t shift = (uint32_t) 0x1c - Index * 4; + + static constexpr uint32_t set (uint32_t word, uint8_t value) + { + return (word & ~((uint32_t) 0xf << shift)) | (uint32_t) ((value & 0xf) << shift); + } + + static constexpr uint8_t get (uint32_t word) + { + return (uint8_t) ((word >> shift) & 0xf); + } + }; + + template + struct U8 + { + static constexpr uint32_t shift = (uint32_t) 0x18 - Index * 8; + + static constexpr uint32_t set (uint32_t word, uint8_t value) + { + return (word & ~((uint32_t) 0xff << shift)) | (uint32_t) (value << shift); + } + + static constexpr uint8_t get (uint32_t word) + { + return (uint8_t) ((word >> shift) & 0xff); + } + }; + + template + struct U16 + { + static constexpr uint32_t shift = (uint32_t) 0x10 - Index * 16; + + static constexpr uint32_t set (uint32_t word, uint16_t value) + { + return (word & ~((uint32_t) 0xffff << shift)) | (uint32_t) (value << shift); + } + + static constexpr uint16_t get (uint32_t word) + { + return (uint16_t) ((word >> shift) & 0xffff); + } + }; + + static constexpr uint8_t getMessageType (uint32_t w) noexcept { return U4<0>::get (w); } + static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); } + static constexpr uint8_t getStatus (uint32_t w) noexcept { return U4<2>::get (w); } + static constexpr uint8_t getChannel (uint32_t w) noexcept { return U4<3>::get (w); } +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp new file mode 100644 index 0000000..a34e15c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp @@ -0,0 +1,35 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +uint32_t View::size() const noexcept +{ + jassert (ptr != nullptr); + return Utils::getNumWordsForMessageType (*ptr); +} + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h new file mode 100644 index 0000000..4e451dc --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h @@ -0,0 +1,88 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Points to a single Universal MIDI Packet. + + The packet must be well-formed for member functions to work correctly. + + Specifically, the constructor argument must be the beginning of a region of + uint32_t that contains at least `getNumWordsForMessageType(*ddata)` items, + where `data` is the constructor argument. + + NOTE: Instances of this class do not own the memory that they point to! + If you need to store a packet pointed-to by a View for later use, copy + the view contents to a Packets collection, or use the Utils::PacketX types. + + @tags{Audio} +*/ +class View +{ +public: + /** Create an invalid view. */ + View() noexcept = default; + + /** Create a view of the packet starting at address `d`. */ + explicit View (const uint32_t* data) noexcept : ptr (data) {} + + /** Get a pointer to the first word in the Universal MIDI Packet currently + pointed-to by this view. + */ + const uint32_t* data() const noexcept { return ptr; } + + /** Get the number of 32-words (between 1 and 4 inclusive) in the Universal + MIDI Packet currently pointed-to by this view. + */ + uint32_t size() const noexcept; + + /** Get a specific word from this packet. + + Passing an `index` that is greater than or equal to the result of `size` + will cause undefined behaviour. + */ + const uint32_t& operator[] (size_t index) const noexcept { return ptr[index]; } + + /** Get an iterator pointing to the first word in the packet. */ + const uint32_t* begin() const noexcept { return ptr; } + const uint32_t* cbegin() const noexcept { return ptr; } + + /** Get an iterator pointing one-past the last word in the packet. */ + const uint32_t* end() const noexcept { return ptr + size(); } + const uint32_t* cend() const noexcept { return ptr + size(); } + + /** Return true if this view is pointing to the same address as another view. */ + bool operator== (const View& other) const noexcept { return ptr == other.ptr; } + + /** Return false if this view is pointing to the same address as another view. */ + bool operator!= (const View& other) const noexcept { return ! operator== (other); } + +private: + const uint32_t* ptr = nullptr; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h new file mode 100644 index 0000000..4baa482 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h @@ -0,0 +1,187 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Holds a single Universal MIDI Packet. +*/ +template +class Packet +{ +public: + Packet() = default; + + template ::type = 0> + Packet (uint32_t a) + : contents { { a } } + { + jassert (Utils::getNumWordsForMessageType (a) == 1); + } + + template ::type = 0> + Packet (uint32_t a, uint32_t b) + : contents { { a, b } } + { + jassert (Utils::getNumWordsForMessageType (a) == 2); + } + + template ::type = 0> + Packet (uint32_t a, uint32_t b, uint32_t c) + : contents { { a, b, c } } + { + jassert (Utils::getNumWordsForMessageType (a) == 3); + } + + template ::type = 0> + Packet (uint32_t a, uint32_t b, uint32_t c, uint32_t d) + : contents { { a, b, c, d } } + { + jassert (Utils::getNumWordsForMessageType (a) == 4); + } + + template ::type = 0> + explicit Packet (const std::array& fullPacket) + : contents (fullPacket) + { + jassert (Utils::getNumWordsForMessageType (fullPacket.front()) == numWords); + } + + Packet withMessageType (uint8_t type) const noexcept + { + return withU4<0> (type); + } + + Packet withGroup (uint8_t group) const noexcept + { + return withU4<1> (group); + } + + Packet withStatus (uint8_t status) const noexcept + { + return withU4<2> (status); + } + + Packet withChannel (uint8_t channel) const noexcept + { + return withU4<3> (channel); + } + + uint8_t getMessageType() const noexcept { return getU4<0>(); } + + uint8_t getGroup() const noexcept { return getU4<1>(); } + + uint8_t getStatus() const noexcept { return getU4<2>(); } + + uint8_t getChannel() const noexcept { return getU4<3>(); } + + template + Packet withU4 (uint8_t value) const noexcept + { + constexpr auto word = index / 8; + auto copy = *this; + std::get (copy.contents) = Utils::U4::set (copy.template getU32(), value); + return copy; + } + + template + Packet withU8 (uint8_t value) const noexcept + { + constexpr auto word = index / 4; + auto copy = *this; + std::get (copy.contents) = Utils::U8::set (copy.template getU32(), value); + return copy; + } + + template + Packet withU16 (uint16_t value) const noexcept + { + constexpr auto word = index / 2; + auto copy = *this; + std::get (copy.contents) = Utils::U16::set (copy.template getU32(), value); + return copy; + } + + template + Packet withU32 (uint32_t value) const noexcept + { + auto copy = *this; + std::get (copy.contents) = value; + return copy; + } + + template + uint8_t getU4() const noexcept + { + return Utils::U4::get (this->template getU32()); + } + + template + uint8_t getU8() const noexcept + { + return Utils::U8::get (this->template getU32()); + } + + template + uint16_t getU16() const noexcept + { + return Utils::U16::get (this->template getU32()); + } + + template + uint32_t getU32() const noexcept + { + return std::get (contents); + } + + //============================================================================== + using Contents = std::array; + + using const_iterator = typename Contents::const_iterator; + + const_iterator begin() const noexcept { return contents.begin(); } + const_iterator cbegin() const noexcept { return contents.begin(); } + + const_iterator end() const noexcept { return contents.end(); } + const_iterator cend() const noexcept { return contents.end(); } + + const uint32_t* data() const noexcept { return contents.data(); } + + const uint32_t& front() const noexcept { return contents.front(); } + const uint32_t& back() const noexcept { return contents.back(); } + + const uint32_t& operator[] (size_t index) const noexcept { return contents[index]; } + +private: + Contents contents { {} }; +}; + +using PacketX1 = Packet<1>; +using PacketX2 = Packet<2>; +using PacketX3 = Packet<3>; +using PacketX4 = Packet<4>; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h new file mode 100644 index 0000000..33e2dc2 --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h @@ -0,0 +1,92 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 universal_midi_packets +{ + +/** + Holds a collection of Universal MIDI Packets. + + Unlike MidiBuffer, this collection does not store any additional information + (e.g. timestamps) alongside the raw messages. + + If timestamps are required, these can be added to the container in UMP format, + as Jitter Reduction Utility messages. + + @tags{Audio} +*/ +class Packets +{ +public: + /** Adds a single packet to the collection. + + The View must be valid for this to work. If the view + points to a malformed message, or if the view points to a region + too short for the contained message, this call will result in + undefined behaviour. + */ + void add (const View& v) { storage.insert (storage.end(), v.cbegin(), v.cend()); } + + void add (const PacketX1& p) { addImpl (p); } + void add (const PacketX2& p) { addImpl (p); } + void add (const PacketX3& p) { addImpl (p); } + void add (const PacketX4& p) { addImpl (p); } + + /** Pre-allocates space for at least `numWords` 32-bit words in this collection. */ + void reserve (size_t numWords) { storage.reserve (numWords); } + + /** Removes all previously-added packets from this collection. */ + void clear() { storage.clear(); } + + /** Gets an iterator pointing to the first packet in this collection. */ + Iterator cbegin() const noexcept { return Iterator (data(), size()); } + Iterator begin() const noexcept { return cbegin(); } + + /** Gets an iterator pointing one-past the last packet in this collection. */ + Iterator cend() const noexcept { return Iterator (data() + size(), 0); } + Iterator end() const noexcept { return cend(); } + + /** Gets a pointer to the contents of the collection as a range of raw 32-bit words. */ + const uint32_t* data() const noexcept { return storage.data(); } + + /** Returns the number of uint32_t words in storage. + + Note that this is likely to be larger than the number of packets + currently being stored, as some packets span multiple words. + */ + size_t size() const noexcept { return storage.size(); } + +private: + template + void addImpl (const Packet& p) + { + jassert (Utils::getNumWordsForMessageType (p[0]) == numWords); + add (View (p.data())); + } + + std::vector storage; +}; + +} +} diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp index 53267e9..88b2989 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -478,19 +478,4 @@ class AndroidAudioIODeviceType : public AudioIODeviceType extern bool isOboeAvailable(); extern bool isOpenSLAvailable(); -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() -{ - #if JUCE_USE_ANDROID_OBOE - if (isOboeAvailable()) - return nullptr; - #endif - - #if JUCE_USE_ANDROID_OPENSLES - if (isOpenSLAvailable()) - return nullptr; - #endif - - return new AndroidAudioIODeviceType(); -} - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp index 163a1d0..4424b72 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -350,17 +350,17 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$Juc #undef JNI_CLASS_MEMBERS //============================================================================== -class AndroidMidiInput +class MidiInput::Pimpl { public: - AndroidMidiInput (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) + 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))) { } - ~AndroidMidiInput() + ~Pimpl() { if (jobject d = javaMidiDevice.get()) { @@ -416,7 +416,7 @@ class AndroidMidiInput static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, jint offset, jint len, jlong timestamp) { - auto* myself = reinterpret_cast (host); + auto* myself = reinterpret_cast (host); myself->handleMidi (byteArray, offset, len, timestamp); } @@ -429,15 +429,15 @@ class AndroidMidiInput }; //============================================================================== -class AndroidMidiOutput +class MidiOutput::Pimpl { public: - AndroidMidiOutput (const LocalRef& midiDevice) + Pimpl (const LocalRef& midiDevice) : javaMidiDevice (midiDevice) { } - ~AndroidMidiOutput() + ~Pimpl() { if (jobject d = javaMidiDevice.get()) { @@ -468,7 +468,7 @@ class AndroidMidiOutput //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" ) + 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 @@ -509,11 +509,11 @@ class AndroidMidiDeviceManager return {}; } - AndroidMidiInput* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) + MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) { if (auto dm = deviceManager.get()) { - std::unique_ptr androidMidiInput (new AndroidMidiInput (juceMidiInput, deviceID, callback, dm)); + auto androidMidiInput = std::make_unique (juceMidiInput, deviceID, callback, dm); if (androidMidiInput->isOpen()) return androidMidiInput.release(); @@ -522,11 +522,11 @@ class AndroidMidiDeviceManager return nullptr; } - AndroidMidiOutput* openMidiOutputPortWithID (int deviceID) + MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) { if (auto dm = deviceManager.get()) if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) - return new AndroidMidiOutput (LocalRef(javaMidiPort)); + return new MidiOutput::Pimpl (LocalRef(javaMidiPort)); return nullptr; } @@ -564,7 +564,7 @@ std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) { - midiInput->internal = port; + midiInput->internal.reset (port); midiInput->setName (port->getName()); return midiInput; @@ -601,20 +601,17 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) { } -MidiInput::~MidiInput() -{ - delete reinterpret_cast (internal); -} +MidiInput::~MidiInput() = default; void MidiInput::start() { - if (auto* mi = reinterpret_cast (internal)) + if (auto* mi = internal.get()) mi->start(); } void MidiInput::stop() { - if (auto* mi = reinterpret_cast (internal)) + if (auto* mi = internal.get()) mi->stop(); } @@ -646,7 +643,7 @@ std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifi if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) { std::unique_ptr midiOutput (new MidiOutput ({}, deviceIdentifier)); - midiOutput->internal = port; + midiOutput->internal.reset (port); midiOutput->setName (port->getName()); return midiOutput; @@ -681,13 +678,11 @@ std::unique_ptr MidiOutput::openDevice (int index) MidiOutput::~MidiOutput() { stopBackgroundThread(); - - delete reinterpret_cast (internal); } void MidiOutput::sendMessageNow (const MidiMessage& message) { - if (auto* androidMidi = reinterpret_cast(internal)) + if (auto* androidMidi = internal.get()) { auto* env = getEnv(); auto messageSize = message.getRawDataSize(); diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp index 8789419..233174e 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_Oboe.cpp @@ -383,6 +383,15 @@ class OboeAudioIODevice : public AudioIODevice { auto bufferSizeHint = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); + // providing a callback is required on some devices to get a FAST track, so we pass an + // empty one to the temp stream to get the best available buffer size + struct DummyCallback : public oboe::AudioStreamCallback + { + oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override { return oboe::DataCallbackResult::Stop; } + }; + + DummyCallback callback; + // NB: Exclusive mode could be rejected if a device is already opened in that mode, so to get // reliable results, only use this function when a device is closed. // We initially try to open a stream with a buffer size returned from @@ -395,7 +404,7 @@ class OboeAudioIODevice : public AudioIODevice getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), bufferSizeHint, - nullptr); + &callback); if (auto* nativeStream = tempStream.getNativeStream()) return nativeStream->getFramesPerBurst(); @@ -498,7 +507,7 @@ class OboeAudioIODevice : public AudioIODevice + "\nFramesPerCallback = " + String (stream->getFramesPerCallback()) + "\nBytesPerFrame = " + String (stream->getBytesPerFrame()) + "\nBytesPerSample = " + String (stream->getBytesPerSample()) - + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency) + + "\nPerformanceMode = " + getOboeString (stream->getPerformanceMode()) + "\ngetDeviceId = " + String (stream->getDeviceId())); } } @@ -591,7 +600,7 @@ class OboeAudioIODevice : public AudioIODevice + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?")) + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?")) + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?")) - + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)); + + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?"))); } void close() @@ -878,7 +887,7 @@ class OboeAudioIODevice : public AudioIODevice + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?")) + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?")) + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?")) - + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency) + + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?")) + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))); } @@ -1261,6 +1270,7 @@ class OboeAudioIODeviceType : public AudioIODeviceType case 22: return "USB headset"; case 23: return "hearing aid"; case 24: return "built-in speaker safe"; + case 25: return {}; default: jassertfalse; return {}; // type not supported yet, needs to be added! } } @@ -1310,15 +1320,8 @@ class OboeAudioIODeviceType : public AudioIODeviceType const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe"; - -//============================================================================== bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); } -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() -{ - return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr; -} - //============================================================================== class OboeRealtimeThread : private oboe::AudioStreamCallback { diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index 04f7c79..928eaf5 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -1120,11 +1120,6 @@ const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL"; //============================================================================== bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() -{ - return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; -} - //============================================================================== class SLRealtimeThread { diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 674bc44..d55619d 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -1431,12 +1431,6 @@ void iOSAudioIODeviceType::handleAsyncUpdate() callDeviceChangeListeners(); } -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() -{ - return new iOSAudioIODeviceType(); -} - //============================================================================== AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp index 18a1238..07d1cb8 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_ALSA.cpp @@ -1299,9 +1299,4 @@ AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices() return new ALSAAudioIODeviceType (false, "ALSA"); } -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() -{ - return createAudioIODeviceType_ALSA_PCMDevices(); -} - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp index 49112d4..270ba8e 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Bela.cpp @@ -24,12 +24,12 @@ namespace juce { //============================================================================== -class BelaMidiInput +class MidiInput::Pimpl { public: - static Array midiInputs; + static Array midiInputs; - BelaMidiInput (const String& port, MidiInput* input, MidiInputCallback* callback) + Pimpl (const String& port, MidiInput* input, MidiInputCallback* callback) : midiInput (input), midiPort (port), midiCallback (callback) { jassert (midiCallback != nullptr); @@ -38,7 +38,7 @@ class BelaMidiInput buffer.resize (32); } - ~BelaMidiInput() + ~Pimpl() { stop(); midiInputs.removeAllInstancesOf (this); @@ -76,7 +76,7 @@ class BelaMidiInput } if (receivedBytes > 0) - pushMidiData (receivedBytes); + pushMidiData ((int) receivedBytes); } static Array getDevices (bool input) @@ -141,7 +141,7 @@ class BelaMidiInput snd_rawmidi_info_t* info; snd_rawmidi_info_alloca (&info); - snd_rawmidi_info_set_device (info, device); + snd_rawmidi_info_set_device (info, (unsigned int) device); snd_rawmidi_info_set_stream (info, input ? SND_RAWMIDI_STREAM_INPUT : SND_RAWMIDI_STREAM_OUTPUT); @@ -173,10 +173,10 @@ class BelaMidiInput Midi midi; MidiDataConcatenator concatenator { 512 }; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaMidiInput) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; -Array BelaMidiInput::midiInputs; +Array MidiInput::Pimpl::midiInputs; //============================================================================== @@ -366,7 +366,7 @@ class BelaAudioIODevice : public AudioIODevice String getLastError() override { return lastError; } //============================================================================== - int getCurrentBufferSizeSamples() override { return actualBufferSize; } + int getCurrentBufferSizeSamples() override { return (int) actualBufferSize; } double getCurrentSampleRate() override { return 44100.0; } int getCurrentBitDepth() override { return 16; } BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; } @@ -384,8 +384,8 @@ class BelaAudioIODevice : public AudioIODevice bool setup (BelaContext& context) { actualBufferSize = context.audioFrames; - actualNumberOfInputs = context.audioInChannels + context.analogInChannels; - actualNumberOfOutputs = context.audioOutChannels + context.analogOutChannels; + actualNumberOfInputs = (int) (context.audioInChannels + context.analogInChannels); + actualNumberOfOutputs = (int) (context.audioOutChannels + context.analogOutChannels); isBelaOpen = true; firstCallback = true; @@ -405,7 +405,7 @@ class BelaAudioIODevice : public AudioIODevice ScopedLock lock (callbackLock); // Check for and process and midi - for (auto midiInput : BelaMidiInput::midiInputs) + for (auto midiInput : MidiInput::Pimpl::midiInputs) midiInput->poll(); if (callback != nullptr) @@ -413,27 +413,29 @@ class BelaAudioIODevice : public AudioIODevice jassert (context.audioFrames <= actualBufferSize); jassert ((context.flags & BELA_FLAG_INTERLEAVED) == 0); + using Frames = decltype (context.audioFrames); + // Setup channelInBuffers for (int ch = 0; ch < actualNumberOfInputs; ++ch) { if (ch < analogChannelStart) - channelInBuffer[ch] = &context.audioIn[ch * context.audioFrames]; + channelInBuffer[ch] = &context.audioIn[(Frames) ch * context.audioFrames]; else - channelInBuffer[ch] = &context.analogIn[(ch - analogChannelStart) * context.analogFrames]; + channelInBuffer[ch] = &context.analogIn[(Frames) (ch - analogChannelStart) * context.analogFrames]; } // Setup channelOutBuffers for (int ch = 0; ch < actualNumberOfOutputs; ++ch) { if (ch < analogChannelStart) - channelOutBuffer[ch] = &context.audioOut[ch * context.audioFrames]; + channelOutBuffer[ch] = &context.audioOut[(Frames) ch * context.audioFrames]; else - channelOutBuffer[ch] = &context.analogOut[(ch - analogChannelStart) * context.audioFrames]; + channelOutBuffer[ch] = &context.analogOut[(Frames) (ch - analogChannelStart) * context.audioFrames]; } callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, channelOutBuffer.getData(), actualNumberOfOutputs, - context.audioFrames); + (int) context.audioFrames); } } @@ -521,25 +523,19 @@ struct BelaAudioIODeviceType : public AudioIODeviceType JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType) }; -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() -{ - return new BelaAudioIODeviceType(); -} - //============================================================================== MidiInput::MidiInput (const String& deviceName, const String& deviceID) : deviceInfo (deviceName, deviceID) { } -MidiInput::~MidiInput() { delete static_cast (internal); } -void MidiInput::start() { static_cast (internal)->start(); } -void MidiInput::stop() { static_cast (internal)->stop(); } +MidiInput::~MidiInput() = default; +void MidiInput::start() { internal->start(); } +void MidiInput::stop() { internal->stop(); } Array MidiInput::getAvailableDevices() { - return BelaMidiInput::getDevices (true); + return Pimpl::getDevices (true); } MidiDeviceInfo MidiInput::getDefaultDevice() @@ -553,7 +549,7 @@ std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier return {}; std::unique_ptr midiInput (new MidiInput (deviceIdentifier, deviceIdentifier)); - midiInput->internal = new BelaMidiInput (deviceIdentifier, midiInput.get(), callback); + midiInput->internal = std::make_unique (deviceIdentifier, midiInput.get(), callback); return midiInput; } @@ -587,7 +583,8 @@ std::unique_ptr MidiInput::openDevice (int index, MidiInputCallback* //============================================================================== // TODO: Add Bela MidiOutput support -MidiOutput::~MidiOutput() {} +class MidiOutput::Pimpl {}; +MidiOutput::~MidiOutput() = default; void MidiOutput::sendMessageNow (const MidiMessage&) {} Array MidiOutput::getAvailableDevices() { return {}; } MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp index 3ab41a4..21786d0 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp @@ -36,9 +36,11 @@ static void* juce_loadJackFunction (const char* const name) #define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ return_type fn_name argument_types \ { \ + using ReturnType = return_type; \ typedef return_type (*fn_type) argument_types; \ static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ - return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ + jassert (fn != nullptr); \ + return (fn != nullptr) ? ((*fn) arguments) : ReturnType(); \ } #define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ @@ -46,30 +48,35 @@ static void* juce_loadJackFunction (const char* const name) { \ typedef void (*fn_type) argument_types; \ static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ + jassert (fn != nullptr); \ if (fn != nullptr) (*fn) arguments; \ } //============================================================================== -JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)); -JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); -JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); -JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); -JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); -JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); -JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); -JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); -JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); -JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); -JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); -JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); -JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); -JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); -JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); -JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg)); +JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)) +JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)) +JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)) +JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)) +JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)) +JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)) +JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)) +JUCE_DECL_VOID_JACK_FUNCTION (jack_on_info_shutdown, (jack_client_t* client, JackInfoShutdownCallback function, void* arg), (client, function, arg)) +JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)) +JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)) +JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)) +JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)) +JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)) +JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)) +JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)) +JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)) +JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)) +JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)) +JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)) +JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)) +JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg)) +JUCE_DECL_JACK_FUNCTION (int, jack_port_flags, (const jack_port_t* port), (port)) +JUCE_DECL_JACK_FUNCTION (jack_port_t*, jack_port_by_name, (jack_client_t* client, const char* name), (client, name)) +JUCE_DECL_VOID_JACK_FUNCTION (jack_free, (void* ptr), (ptr)) #if JUCE_DEBUG #define JACK_LOGGING_ENABLED 1 @@ -115,56 +122,56 @@ namespace struct JackPortIterator { JackPortIterator (jack_client_t* const client, const bool forInput) - : ports (nullptr), index (-1) { if (client != nullptr) - ports = juce::jack_get_ports (client, nullptr, nullptr, - forInput ? JackPortIsOutput : JackPortIsInput); - // (NB: This looks like it's the wrong way round, but it is correct!) - } - - ~JackPortIterator() - { - ::free (ports); + ports.reset (juce::jack_get_ports (client, nullptr, nullptr, + forInput ? JackPortIsInput : JackPortIsOutput)); } bool next() { - if (ports == nullptr || ports [index + 1] == nullptr) + if (ports == nullptr || ports.get()[index + 1] == nullptr) return false; - name = CharPointer_UTF8 (ports[++index]); - clientName = name.upToFirstOccurrenceOf (":", false, false); + name = CharPointer_UTF8 (ports.get()[++index]); return true; } - const char** ports; - int index; + String getClientName() const + { + return name.upToFirstOccurrenceOf (":", false, false); + } + + String getChannelName() const + { + return name.fromFirstOccurrenceOf (":", false, false); + } + + struct Free + { + void operator() (const char** ptr) const noexcept { juce::jack_free (ptr); } + }; + + std::unique_ptr ports; + int index = -1; String name; - String clientName; }; -class JackAudioIODeviceType; -static Array activeDeviceTypes; - //============================================================================== class JackAudioIODevice : public AudioIODevice { public: - JackAudioIODevice (const String& deviceName, - const String& inId, - const String& outId) - : AudioIODevice (deviceName, "JACK"), - inputId (inId), - outputId (outId), - deviceIsOpen (false), - callback (nullptr), - totalNumberOfInputChannels (0), - totalNumberOfOutputChannels (0) - { - jassert (deviceName.isNotEmpty()); - - jack_status_t status; + JackAudioIODevice (const String& inName, + const String& outName, + std::function notifyIn) + : AudioIODevice (outName.isEmpty() ? inName : outName, "JACK"), + inputName (inName), + outputName (outName), + notifyChannelsChanged (std::move (notifyIn)) + { + jassert (outName.isNotEmpty() || inName.isNotEmpty()); + + jack_status_t status = {}; client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); if (client == nullptr) @@ -179,10 +186,10 @@ class JackAudioIODevice : public AudioIODevice const StringArray inputChannels (getInputChannelNames()); for (int i = 0; i < inputChannels.size(); ++i) { - String inputName; - inputName << "in_" << ++totalNumberOfInputChannels; + String inputChannelName; + inputChannelName << "in_" << ++totalNumberOfInputChannels; - inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(), + inputPorts.add (juce::jack_port_register (client, inputChannelName.toUTF8(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); } @@ -190,10 +197,10 @@ class JackAudioIODevice : public AudioIODevice const StringArray outputChannels (getOutputChannelNames()); for (int i = 0; i < outputChannels.size(); ++i) { - String outputName; - outputName << "out_" << ++totalNumberOfOutputChannels; + String outputChannelName; + outputChannelName << "out_" << ++totalNumberOfOutputChannels; - outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(), + outputPorts.add (juce::jack_port_register (client, outputChannelName.toUTF8(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); } @@ -202,7 +209,7 @@ class JackAudioIODevice : public AudioIODevice } } - ~JackAudioIODevice() + ~JackAudioIODevice() override { close(); if (client != nullptr) @@ -212,19 +219,19 @@ class JackAudioIODevice : public AudioIODevice } } - StringArray getChannelNames (bool forInput) const + StringArray getChannelNames (const String& clientName, bool forInput) const { StringArray names; for (JackPortIterator i (client, forInput); i.next();) - if (i.clientName == getName()) - names.add (i.name.fromFirstOccurrenceOf (":", false, false)); + if (i.getClientName() == clientName) + names.add (i.getChannelName()); return names; } - StringArray getOutputChannelNames() override { return getChannelNames (false); } - StringArray getInputChannelNames() override { return getChannelNames (true); } + StringArray getOutputChannelNames() override { return getChannelNames (outputName, true); } + StringArray getInputChannelNames() override { return getChannelNames (inputName, false); } Array getAvailableSampleRates() override { @@ -241,15 +248,29 @@ class JackAudioIODevice : public AudioIODevice Array sizes; if (client != nullptr) - sizes.add (juce::jack_get_buffer_size (client)); + sizes.add (static_cast (juce::jack_get_buffer_size (client))); return sizes; } int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } - int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } - double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } + int getCurrentBufferSizeSamples() override { return client != nullptr ? static_cast (juce::jack_get_buffer_size (client)) : 0; } + double getCurrentSampleRate() override { return client != nullptr ? static_cast (juce::jack_get_sample_rate (client)) : 0; } + + template + void forEachClientChannel (const String& clientName, bool isInput, Fn&& fn) + { + auto index = 0; + for (JackPortIterator i (client, isInput); i.next();) + { + if (i.getClientName() != clientName) + continue; + + fn (i.ports.get()[i.index], index); + index += 1; + } + } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double /* sampleRate */, int /* bufferSizeSamples */) override @@ -263,38 +284,55 @@ class JackAudioIODevice : public AudioIODevice lastError.clear(); close(); - xruns = 0; + xruns.store (0, std::memory_order_relaxed); juce::jack_set_process_callback (client, processCallback, this); juce::jack_set_port_connect_callback (client, portConnectCallback, this); juce::jack_on_shutdown (client, shutdownCallback, this); + juce::jack_on_info_shutdown (client, infoShutdownCallback, this); juce::jack_set_xrun_callback (client, xrunCallback, this); juce::jack_activate (client); deviceIsOpen = true; if (! inputChannels.isZero()) { - for (JackPortIterator i (client, true); i.next();) + forEachClientChannel (inputName, false, [&] (const char* portName, int index) { - if (inputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index])); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } + if (! inputChannels[index]) + return; + + jassert (index < inputPorts.size()); + + const auto* source = portName; + const auto* inputPort = inputPorts[index]; + + jassert (juce::jack_port_flags (juce::jack_port_by_name (client, source)) & JackPortIsOutput); + jassert (juce::jack_port_flags (inputPort) & JackPortIsInput); + + auto error = juce::jack_connect (client, source, juce::jack_port_name (inputPort)); + if (error != 0) + JUCE_JACK_LOG ("Cannot connect input port " + String (index) + " (" + portName + "), error " + String (error)); + }); } if (! outputChannels.isZero()) { - for (JackPortIterator i (client, false); i.next();) + forEachClientChannel (outputName, true, [&] (const char* portName, int index) { - if (outputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } + if (! outputChannels[index]) + return; + + jassert (index < outputPorts.size()); + + const auto* outputPort = outputPorts[index]; + const auto* destination = portName; + + jassert (juce::jack_port_flags (outputPort) & JackPortIsOutput); + jassert (juce::jack_port_flags (juce::jack_port_by_name (client, destination)) & JackPortIsInput); + + auto error = juce::jack_connect (client, juce::jack_port_name (outputPort), destination); + if (error != 0) + JUCE_JACK_LOG ("Cannot connect output port " + String (index) + " (" + portName + "), error " + String (error)); + }); } updateActivePorts(); @@ -308,12 +346,15 @@ class JackAudioIODevice : public AudioIODevice if (client != nullptr) { - juce::jack_deactivate (client); + const auto result = juce::jack_deactivate (client); + jassert (result == 0); + ignoreUnused (result); juce::jack_set_xrun_callback (client, xrunCallback, nullptr); juce::jack_set_process_callback (client, processCallback, nullptr); juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); juce::jack_on_shutdown (client, shutdownCallback, nullptr); + juce::jack_on_info_shutdown (client, infoShutdownCallback, nullptr); } deviceIsOpen = false; @@ -347,7 +388,7 @@ class JackAudioIODevice : public AudioIODevice bool isPlaying() override { return callback != nullptr; } int getCurrentBitDepth() override { return 32; } String getLastError() override { return lastError; } - int getXRunCount() const noexcept override { return xruns; } + int getXRunCount() const noexcept override { return xruns.load (std::memory_order_relaxed); } BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } BigInteger getActiveInputChannels() const override { return activeInputChannels; } @@ -357,7 +398,7 @@ class JackAudioIODevice : public AudioIODevice int latency = 0; for (int i = 0; i < outputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); + latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, outputPorts[i])); return latency; } @@ -367,14 +408,36 @@ class JackAudioIODevice : public AudioIODevice int latency = 0; for (int i = 0; i < inputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); + latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, inputPorts[i])); return latency; } - String inputId, outputId; + String inputName, outputName; private: + //============================================================================== + class MainThreadDispatcher : private AsyncUpdater + { + public: + explicit MainThreadDispatcher (JackAudioIODevice& device) : ref (device) {} + ~MainThreadDispatcher() override { cancelPendingUpdate(); } + + void updateActivePorts() + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + } + + private: + void handleAsyncUpdate() override { ref.updateActivePorts(); } + + JackAudioIODevice& ref; + }; + + //============================================================================== void process (const int numSamples) { int numActiveInChans = 0, numActiveOutChans = 0; @@ -382,17 +445,17 @@ class JackAudioIODevice : public AudioIODevice for (int i = 0; i < totalNumberOfInputChannels; ++i) { if (activeInputChannels[i]) - if (jack_default_audio_sample_t* in - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples)) - inChans [numActiveInChans++] = (float*) in; + if (auto* in = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (inputPorts.getUnchecked (i), + static_cast (numSamples))) + inChans[numActiveInChans++] = (float*) in; } for (int i = 0; i < totalNumberOfOutputChannels; ++i) { if (activeOutputChannels[i]) - if (jack_default_audio_sample_t* out - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples)) - outChans [numActiveOutChans++] = (float*) out; + if (auto* out = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (outputPorts.getUnchecked (i), + static_cast (numSamples))) + outChans[numActiveOutChans++] = (float*) out; } const ScopedLock sl (callbackLock); @@ -406,14 +469,14 @@ class JackAudioIODevice : public AudioIODevice else { for (int i = 0; i < numActiveOutChans; ++i) - zeromem (outChans[i], sizeof (float) * numSamples); + zeromem (outChans[i], static_cast (numSamples) * sizeof (float)); } } static int processCallback (jack_nframes_t nframes, void* callbackArgument) { if (callbackArgument != nullptr) - ((JackAudioIODevice*) callbackArgument)->process (nframes); + ((JackAudioIODevice*) callbackArgument)->process (static_cast (nframes)); return 0; } @@ -431,11 +494,11 @@ class JackAudioIODevice : public AudioIODevice BigInteger newOutputChannels, newInputChannels; for (int i = 0; i < outputPorts.size(); ++i) - if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i))) + if (juce::jack_port_connected (outputPorts.getUnchecked (i))) newOutputChannels.setBit (i); for (int i = 0; i < inputPorts.size(); ++i) - if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i))) + if (juce::jack_port_connected (inputPorts.getUnchecked (i))) newInputChannels.setBit (i); if (newOutputChannels != activeOutputChannels @@ -451,14 +514,15 @@ class JackAudioIODevice : public AudioIODevice if (oldCallback != nullptr) start (oldCallback); - sendDeviceChangedCallback(); + if (notifyChannelsChanged != nullptr) + notifyChannelsChanged(); } } static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) { if (JackAudioIODevice* device = static_cast (arg)) - device->updateActivePorts(); + device->mainThreadDispatcher.updateActivePorts(); } static void threadInitCallback (void* /* callbackArgument */) @@ -477,81 +541,76 @@ class JackAudioIODevice : public AudioIODevice } } + static void infoShutdownCallback (jack_status_t code, const char* reason, void* arg) + { + jassert (code == 0); + ignoreUnused (code); + + JUCE_JACK_LOG ("Shutting down with message:"); + JUCE_JACK_LOG (reason); + ignoreUnused (reason); + + shutdownCallback (arg); + } + static void errorCallback (const char* msg) { JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg)); + ignoreUnused (msg); } - static void sendDeviceChangedCallback(); - - bool deviceIsOpen; - jack_client_t* client; + bool deviceIsOpen = false; + jack_client_t* client = nullptr; String lastError; - AudioIODeviceCallback* callback; + AudioIODeviceCallback* callback = nullptr; CriticalSection callbackLock; HeapBlock inChans, outChans; - int totalNumberOfInputChannels; - int totalNumberOfOutputChannels; - Array inputPorts, outputPorts; + int totalNumberOfInputChannels = 0; + int totalNumberOfOutputChannels = 0; + Array inputPorts, outputPorts; BigInteger activeInputChannels, activeOutputChannels; - int xruns; -}; + std::atomic xruns { 0 }; + std::function notifyChannelsChanged; + MainThreadDispatcher mainThreadDispatcher { *this }; +}; //============================================================================== +class JackAudioIODeviceType; + class JackAudioIODeviceType : public AudioIODeviceType { public: JackAudioIODeviceType() - : AudioIODeviceType ("JACK"), - hasScanned (false) - { - activeDeviceTypes.add (this); - } - - ~JackAudioIODeviceType() - { - activeDeviceTypes.removeFirstMatchingValue (this); - } + : AudioIODeviceType ("JACK") + {} void scanForDevices() { hasScanned = true; inputNames.clear(); - inputIds.clear(); outputNames.clear(); - outputIds.clear(); if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); if (juce_libjackHandle == nullptr) return; - jack_status_t status; + jack_status_t status = {}; // open a dummy client - if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) + if (auto* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) { // scan for output devices for (JackPortIterator i (client, false); i.next();) - { - if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName)) - { - inputNames.add (i.clientName); - inputIds.add (i.ports [i.index]); - } - } + if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.getClientName())) + inputNames.add (i.getClientName()); // scan for input devices for (JackPortIterator i (client, true); i.next();) - { - if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName)) - { - outputNames.add (i.clientName); - outputIds.add (i.ports [i.index]); - } - } + if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.getClientName())) + outputNames.add (i.getClientName()); juce::jack_client_close (client); } @@ -580,8 +639,8 @@ class JackAudioIODeviceType : public AudioIODeviceType jassert (hasScanned); // need to call scanForDevices() before doing this if (JackAudioIODevice* d = dynamic_cast (device)) - return asInput ? inputIds.indexOf (d->inputId) - : outputIds.indexOf (d->outputId); + return asInput ? inputNames.indexOf (d->inputName) + : outputNames.indexOf (d->outputName); return -1; } @@ -595,34 +654,17 @@ class JackAudioIODeviceType : public AudioIODeviceType const int outputIndex = outputNames.indexOf (outputDeviceName); if (inputIndex >= 0 || outputIndex >= 0) - return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName - : inputDeviceName, - inputIds [inputIndex], - outputIds [outputIndex]); + return new JackAudioIODevice (inputDeviceName, outputDeviceName, + [this] { callDeviceChangeListeners(); }); return nullptr; } - void portConnectionChange() { callDeviceChangeListeners(); } - private: - StringArray inputNames, outputNames, inputIds, outputIds; - bool hasScanned; + StringArray inputNames, outputNames; + bool hasScanned = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) }; -void JackAudioIODevice::sendDeviceChangedCallback() -{ - for (int i = activeDeviceTypes.size(); --i >= 0;) - if (JackAudioIODeviceType* d = activeDeviceTypes[i]) - d->portConnectionChange(); -} - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() -{ - return new JackAudioIODeviceType(); -} - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp index d648a8e..c9706fa 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp @@ -25,10 +25,6 @@ namespace juce #if JUCE_ALSA -//============================================================================== -namespace -{ - //============================================================================== class AlsaClient : public ReferenceCountedObject { @@ -453,9 +449,23 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, return port; } -} // namespace +struct AlsaPortPtr +{ + explicit AlsaPortPtr (AlsaClient::Port* p) + : ptr (p) {} + + ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); } + + AlsaClient::Port* ptr = nullptr; +}; //============================================================================== +class MidiInput::Pimpl : public AlsaPortPtr +{ +public: + using AlsaPortPtr::AlsaPortPtr; +}; + Array MidiInput::getAvailableDevices() { Array devices; @@ -485,7 +495,7 @@ std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier std::unique_ptr midiInput (new MidiInput (port->portName, deviceIdentifier)); port->setupInput (midiInput.get(), callback); - midiInput->internal = port; + midiInput->internal = std::make_unique (port); return midiInput; } @@ -501,7 +511,7 @@ std::unique_ptr MidiInput::createNewDevice (const String& deviceName, std::unique_ptr midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); port->setupInput (midiInput.get(), callback); - midiInput->internal = port; + midiInput->internal = std::make_unique (port); return midiInput; } @@ -536,20 +546,25 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) MidiInput::~MidiInput() { stop(); - AlsaClient::getInstance()->deletePort (static_cast (internal)); } void MidiInput::start() { - static_cast (internal)->enableCallback (true); + internal->ptr->enableCallback (true); } void MidiInput::stop() { - static_cast (internal)->enableCallback (false); + internal->ptr->enableCallback (false); } //============================================================================== +class MidiOutput::Pimpl : public AlsaPortPtr +{ +public: + using AlsaPortPtr::AlsaPortPtr; +}; + Array MidiOutput::getAvailableDevices() { Array devices; @@ -577,7 +592,7 @@ std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifi std::unique_ptr midiOutput (new MidiOutput (port->portName, deviceIdentifier)); port->setupOutput(); - midiOutput->internal = port; + midiOutput->internal = std::make_unique (port); return midiOutput; } @@ -593,7 +608,7 @@ std::unique_ptr MidiOutput::createNewDevice (const String& deviceNam std::unique_ptr midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); port->setupOutput(); - midiOutput->internal = port; + midiOutput->internal = std::make_unique (port); return midiOutput; } @@ -623,17 +638,18 @@ std::unique_ptr MidiOutput::openDevice (int index) MidiOutput::~MidiOutput() { stopBackgroundThread(); - AlsaClient::getInstance()->deletePort (static_cast (internal)); } void MidiOutput::sendMessageNow (const MidiMessage& message) { - static_cast (internal)->sendMessageNow (message); + internal->ptr->sendMessageNow (message); } //============================================================================== #else +class MidiInput::Pimpl {}; + // (These are just stub functions if ALSA is unavailable...) MidiInput::MidiInput (const String& deviceName, const String& deviceID) : deviceInfo (deviceName, deviceID) @@ -651,6 +667,8 @@ StringArray MidiInput::getDevices() int MidiInput::getDefaultDeviceIndex() { return 0;} std::unique_ptr MidiInput::openDevice (int, MidiInputCallback*) { return {}; } +class MidiOutput::Pimpl {}; + MidiOutput::~MidiOutput() {} void MidiOutput::sendMessageNow (const MidiMessage&) {} Array MidiOutput::getAvailableDevices() { return {}; } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 058dc51..654c7c6 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -1663,11 +1663,16 @@ class AudioIODeviceCombiner : public AudioIODevice, void readInput (AudioBuffer& buffer, const int numSamples, const int blockSizeMs) { for (auto* d : devices) - d->done = (d->numInputChans == 0); + d->done = (d->numInputChans == 0 || d->isWaitingForInput); - for (int tries = 5;;) + float totalWaitTimeMs = blockSizeMs * 5.0f; + constexpr int numReadAttempts = 6; + auto sumPower2s = [] (int maxPower) { return (1 << (maxPower + 1)) - 1; }; + float waitTime = totalWaitTimeMs / (float) sumPower2s (numReadAttempts - 2); + + for (int numReadAttemptsRemaining = numReadAttempts;;) { - bool anyRemaining = false; + bool anySamplesRemaining = false; for (auto* d : devices) { @@ -1679,17 +1684,20 @@ class AudioIODeviceCombiner : public AudioIODevice, d->done = true; } else - anyRemaining = true; + { + anySamplesRemaining = true; + } } } - if (! anyRemaining) + if (! anySamplesRemaining) return; - if (--tries == 0) + if (--numReadAttemptsRemaining == 0) break; - wait (blockSizeMs); + wait (jmax (1, roundToInt (waitTime))); + waitTime *= 2.0f; } for (auto* d : devices) @@ -1717,7 +1725,9 @@ class AudioIODeviceCombiner : public AudioIODevice, d->done = true; } else + { anyRemaining = true; + } } } @@ -1808,6 +1818,8 @@ class AudioIODeviceCombiner : public AudioIODevice, numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; + isWaitingForInput = numInputChans > 0; + inputIndex = channelIndex; outputIndex = channelIndex + numInputChans; @@ -1892,6 +1904,8 @@ class AudioIODeviceCombiner : public AudioIODevice, { if (numInputChannels > 0) { + isWaitingForInput = false; + int start1, size1, start2, size2; inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); @@ -1973,6 +1987,7 @@ class AudioIODeviceCombiner : public AudioIODevice, std::unique_ptr device; int inputIndex = 0, numInputChans = 0, outputIndex = 0, numOutputChans = 0; bool useInputs = false, useOutputs = false; + std::atomic isWaitingForInput { false }; AbstractFifo inputFifo { 32 }, outputFifo { 32 }; bool done = false; @@ -2234,12 +2249,6 @@ class CoreAudioIODeviceType : public AudioIODeviceType, }; -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() -{ - return new CoreAudioClasses::CoreAudioIODeviceType(); -} - #undef JUCE_COREAUDIOLOG } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp deleted file mode 100644 index a39d9c3..0000000 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp +++ /dev/null @@ -1,723 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2020 - 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 -{ - -#ifndef JUCE_LOG_COREMIDI_ERRORS - #define JUCE_LOG_COREMIDI_ERRORS 1 -#endif - -namespace CoreMidiHelpers -{ - //============================================================================== - static bool checkError (OSStatus err, int lineNum) - { - if (err == noErr) - return true; - - #if JUCE_LOG_COREMIDI_ERRORS - Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); - #endif - - ignoreUnused (lineNum); - return false; - } - - #undef CHECK_ERROR - #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) - - static MidiDeviceInfo getMidiObjectInfo (MIDIObjectRef entity) - { - MidiDeviceInfo info; - - { - ScopedCFString str; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString))) - info.name = String::fromCFString (str.cfString); - } - - SInt32 objectID = 0; - - if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID))) - { - info.identifier = String (objectID); - } - else - { - ScopedCFString str; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyUniqueID, &str.cfString))) - info.identifier = String::fromCFString (str.cfString); - } - - return info; - } - - static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal) - { - // NB: don't attempt to use nullptr for refs - it fails in some types of build. - MIDIEntityRef entity = 0; - MIDIEndpointGetEntity (endpoint, &entity); - - // probably virtual - if (entity == 0) - return getMidiObjectInfo (endpoint); - - auto result = getMidiObjectInfo (endpoint); - - // endpoint is empty - try the entity - if (result == MidiDeviceInfo()) - result = getMidiObjectInfo (entity); - - // now consider the device - MIDIDeviceRef device = 0; - MIDIEntityGetDevice (entity, &device); - - if (device != 0) - { - auto info = getMidiObjectInfo (device); - - if (info != MidiDeviceInfo()) - { - // if an external device has only one entity, throw away - // the endpoint name and just use the device name - if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) - { - result = info; - } - else if (! result.name.startsWithIgnoreCase (info.name)) - { - // prepend the device name and identifier to the entity's - result.name = (info.name + " " + result.name).trimEnd(); - result.identifier = info.identifier + " " + result.identifier; - } - } - } - - return result; - } - - static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint) - { - MidiDeviceInfo result; - - // Does the endpoint have connections? - CFDataRef connections = nullptr; - int numConnections = 0; - - MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); - - if (connections != nullptr) - { - numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); - - if (numConnections > 0) - { - auto* pid = reinterpret_cast (CFDataGetBytePtr (connections)); - - for (int i = 0; i < numConnections; ++i, ++pid) - { - auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); - MIDIObjectRef connObject; - MIDIObjectType connObjectType; - auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType); - - if (err == noErr) - { - MidiDeviceInfo deviceInfo; - - if (connObjectType == kMIDIObjectType_ExternalSource - || connObjectType == kMIDIObjectType_ExternalDestination) - { - // Connected to an external device's endpoint (10.3 and later). - deviceInfo = getEndpointInfo (static_cast (connObject), true); - } - else - { - // Connected to an external device (10.2) (or something else, catch-all) - deviceInfo = getMidiObjectInfo (connObject); - } - - if (deviceInfo != MidiDeviceInfo()) - { - if (result.name.isNotEmpty()) result.name += ", "; - if (result.identifier.isNotEmpty()) result.identifier += ", "; - - result.name += deviceInfo.name; - result.identifier += deviceInfo.identifier; - } - } - } - } - - CFRelease (connections); - } - - // Here, either the endpoint had no connections, or we failed to obtain names for them. - if (result == MidiDeviceInfo()) - return getEndpointInfo (endpoint, false); - - return result; - } - - static int createUniqueIDForMidiPort (String deviceName, bool isInput) - { - String uniqueID; - - #ifdef JucePlugin_CFBundleIdentifier - uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); - #else - auto appBundle = File::getSpecialLocation (File::currentApplicationFile); - ScopedCFString appBundlePath (appBundle.getFullPathName()); - - if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundlePath.cfString, kCFURLPOSIXPathStyle, true)) - { - auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL); - CFRelease (bundleURL); - - if (bundleRef != nullptr) - { - if (auto bundleId = CFBundleGetIdentifier (bundleRef)) - uniqueID = String::fromCFString (bundleId); - - CFRelease (bundleRef); - } - } - #endif - - if (uniqueID.isEmpty()) - uniqueID = String (Random::getSystemRandom().nextInt (1024)); - - uniqueID += "." + deviceName + (isInput ? ".input" : ".output"); - return uniqueID.hashCode(); - } - - static void enableSimulatorMidiSession() - { - #if TARGET_OS_SIMULATOR - static bool hasEnabledNetworkSession = false; - - if (! hasEnabledNetworkSession) - { - MIDINetworkSession* session = [MIDINetworkSession defaultSession]; - session.enabled = YES; - session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; - - hasEnabledNetworkSession = true; - } - #endif - } - - static void globalSystemChangeCallback (const MIDINotification*, void*) - { - // TODO.. Should pass-on this notification.. - } - - static String getGlobalMidiClientName() - { - if (auto* app = JUCEApplicationBase::getInstance()) - return app->getApplicationName(); - - return "JUCE"; - } - - static MIDIClientRef getGlobalMidiClient() - { - static MIDIClientRef globalMidiClient = 0; - - if (globalMidiClient == 0) - { - // Since OSX 10.6, the MIDIClientCreate function will only work - // correctly when called from the message thread! - JUCE_ASSERT_MESSAGE_THREAD - - enableSimulatorMidiSession(); - - ScopedCFString name (getGlobalMidiClientName()); - CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); - } - - return globalMidiClient; - } - - static Array findDevices (bool forInput) - { - // It seems that OSX can be a bit picky about the thread that's first used to - // search for devices. It's safest to use the message thread for calling this. - JUCE_ASSERT_MESSAGE_THREAD - - if (getGlobalMidiClient() == 0) - { - jassertfalse; - return {}; - } - - enableSimulatorMidiSession(); - - Array devices; - auto numDevices = (forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); - - for (ItemCount i = 0; i < numDevices; ++i) - { - MidiDeviceInfo deviceInfo; - - if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) - deviceInfo = getConnectedEndpointInfo (dest); - - if (deviceInfo == MidiDeviceInfo()) - deviceInfo.name = deviceInfo.identifier = ""; - - devices.add (deviceInfo); - } - - return devices; - } - - //============================================================================== - class MidiPortAndEndpoint - { - public: - MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept - : port (p), endpoint (ep) - { - } - - ~MidiPortAndEndpoint() noexcept - { - if (port != 0) - MIDIPortDispose (port); - - // if port == nullptr, it means we created the endpoint, so it's safe to delete it - if (port == 0 && endpoint != 0) - MIDIEndpointDispose (endpoint); - } - - void send (const MIDIPacketList* packets) noexcept - { - if (port != 0) - MIDISend (port, endpoint, packets); - else - MIDIReceived (endpoint, packets); - } - - MIDIPortRef port; - MIDIEndpointRef endpoint; - }; - - //============================================================================== - struct MidiPortAndCallback; - CriticalSection callbackLock; - Array activeCallbacks; - - struct MidiPortAndCallback - { - MidiPortAndCallback (MidiInputCallback& cb) : callback (cb) {} - - ~MidiPortAndCallback() - { - active = false; - - { - const ScopedLock sl (callbackLock); - activeCallbacks.removeFirstMatchingValue (this); - } - - if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) - CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endpoint)); - } - - void handlePackets (const MIDIPacketList* pktlist) - { - auto time = Time::getMillisecondCounterHiRes() * 0.001; - - const ScopedLock sl (callbackLock); - - if (activeCallbacks.contains (this) && active) - { - auto* packet = &pktlist->packet[0]; - - for (unsigned int i = 0; i < pktlist->numPackets; ++i) - { - auto len = readUnalignedlength)> (&(packet->length)); - concatenator.pushMidiData (packet->data, (int) len, time, input, callback); - - packet = MIDIPacketNext (packet); - } - } - } - - MidiInput* input = nullptr; - std::unique_ptr portAndEndpoint; - std::atomic active { false }; - - private: - MidiInputCallback& callback; - MidiDataConcatenator concatenator { 2048 }; - }; - - static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) - { - static_cast (readProcRefCon)->handlePackets (pktlist); - } - - static Array getEndpoints (bool isInput) - { - Array endpoints; - auto numDevices = (isInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); - - for (ItemCount i = 0; i < numDevices; ++i) - endpoints.add (isInput ? MIDIGetSource (i) : MIDIGetDestination (i)); - - return endpoints; - } -} - -//============================================================================== -Array MidiInput::getAvailableDevices() -{ - return CoreMidiHelpers::findDevices (true); -} - -MidiDeviceInfo MidiInput::getDefaultDevice() -{ - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) -{ - if (deviceIdentifier.isEmpty()) - return nullptr; - - using namespace CoreMidiHelpers; - - if (auto client = getGlobalMidiClient()) - { - for (auto& endpoint : getEndpoints (true)) - { - auto endpointInfo = getConnectedEndpointInfo (endpoint); - - if (deviceIdentifier == endpointInfo.identifier) - { - ScopedCFString cfName; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) - { - MIDIPortRef port; - auto mpc = std::make_unique (*callback); - - if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port))) - { - if (CHECK_ERROR (MIDIPortConnectSource (port, endpoint, nullptr))) - { - mpc->portAndEndpoint = std::make_unique (port, endpoint); - - std::unique_ptr midiInput (new MidiInput (endpointInfo.name, endpointInfo.identifier)); - - mpc->input = midiInput.get(); - midiInput->internal = mpc.get(); - - const ScopedLock sl (callbackLock); - activeCallbacks.add (mpc.release()); - - return midiInput; - } - else - { - CHECK_ERROR (MIDIPortDispose (port)); - } - } - } - } - } - } - - return {}; -} - -std::unique_ptr MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) -{ - using namespace CoreMidiHelpers; - jassert (callback != nullptr); - - if (auto client = getGlobalMidiClient()) - { - auto mpc = std::make_unique (*callback); - mpc->active = false; - - MIDIEndpointRef endpoint; - ScopedCFString name (deviceName); - - auto err = MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endpoint); - - #if JUCE_IOS - if (err == kMIDINotPermitted) - { - // If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" - // setting in the iOS exporter for your app - this is required if you want to create a MIDI device! - jassertfalse; - return nullptr; - } - #endif - - if (CHECK_ERROR (err)) - { - auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); - - if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) - { - mpc->portAndEndpoint = std::make_unique ((UInt32) 0, endpoint); - - std::unique_ptr midiInput (new MidiInput (deviceName, String (deviceIdentifier))); - - mpc->input = midiInput.get(); - midiInput->internal = mpc.get(); - - const ScopedLock sl (callbackLock); - activeCallbacks.add (mpc.release()); - - return midiInput; - } - } - } - - return {}; -} - -StringArray MidiInput::getDevices() -{ - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiInput::getDefaultDeviceIndex() -{ - return 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() -{ - delete static_cast (internal); -} - -void MidiInput::start() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = true; -} - -void MidiInput::stop() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = false; -} - -//============================================================================== -Array MidiOutput::getAvailableDevices() -{ - return CoreMidiHelpers::findDevices (false); -} - -MidiDeviceInfo MidiOutput::getDefaultDevice() -{ - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) -{ - if (deviceIdentifier.isEmpty()) - return nullptr; - - using namespace CoreMidiHelpers; - - if (auto client = getGlobalMidiClient()) - { - for (auto& endpoint : getEndpoints (false)) - { - auto endpointInfo = getConnectedEndpointInfo (endpoint); - - if (deviceIdentifier == endpointInfo.identifier) - { - ScopedCFString cfName; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) - { - MIDIPortRef port; - - if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) - { - std::unique_ptr midiOutput (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); - midiOutput->internal = new MidiPortAndEndpoint (port, endpoint); - - return midiOutput; - } - } - } - } - } - - return {}; -} - -std::unique_ptr MidiOutput::createNewDevice (const String& deviceName) -{ - using namespace CoreMidiHelpers; - - if (auto client = getGlobalMidiClient()) - { - MIDIEndpointRef endpoint; - - ScopedCFString name (deviceName); - - auto err = MIDISourceCreate (client, name.cfString, &endpoint); - - #if JUCE_IOS - if (err == kMIDINotPermitted) - { - // If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" - // setting in the iOS exporter for your app - this is required if you want to create a MIDI device! - jassertfalse; - return nullptr; - } - #endif - - if (CHECK_ERROR (err)) - { - auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, false); - - if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) - { - std::unique_ptr midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); - midiOutput->internal = new MidiPortAndEndpoint (0, endpoint); - - return midiOutput; - } - } - } - - return {}; -} - -StringArray MidiOutput::getDevices() -{ - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return 0; -} - -std::unique_ptr MidiOutput::openDevice (int index) -{ - return openDevice (getAvailableDevices()[index].identifier); -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); - - delete static_cast (internal); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - #if JUCE_IOS - const MIDITimeStamp timeStamp = mach_absolute_time(); - #else - const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); - #endif - - HeapBlock allocatedPackets; - MIDIPacketList stackPacket; - auto* packetToSend = &stackPacket; - auto dataSize = (size_t) message.getRawDataSize(); - - if (message.isSysEx()) - { - const int maxPacketSize = 256; - int pos = 0, bytesLeft = (int) dataSize; - const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; - allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); - packetToSend = allocatedPackets; - packetToSend->numPackets = (UInt32) numPackets; - - auto* p = packetToSend->packet; - - for (int i = 0; i < numPackets; ++i) - { - p->timeStamp = timeStamp; - p->length = (UInt16) jmin (maxPacketSize, bytesLeft); - memcpy (p->data, message.getRawData() + pos, p->length); - pos += p->length; - bytesLeft -= p->length; - p = MIDIPacketNext (p); - } - } - else if (dataSize < 65536) // max packet size - { - auto stackCapacity = sizeof (stackPacket.packet->data); - - if (dataSize > stackCapacity) - { - allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); - packetToSend = allocatedPackets; - } - - packetToSend->numPackets = 1; - auto& p = *(packetToSend->packet); - p.timeStamp = timeStamp; - p.length = (UInt16) dataSize; - memcpy (p.data, message.getRawData(), dataSize); - } - else - { - jassertfalse; // packet too large to send! - return; - } - - static_cast (internal)->send (packetToSend); -} - -#undef CHECK_ERROR - -} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm new file mode 100644 index 0000000..0bd963c --- /dev/null +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm @@ -0,0 +1,1269 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - 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 +{ + +#ifndef JUCE_LOG_COREMIDI_ERRORS + #define JUCE_LOG_COREMIDI_ERRORS 1 +#endif + +namespace CoreMidiHelpers +{ + static bool checkError (OSStatus err, int lineNum) + { + if (err == noErr) + return true; + + #if JUCE_LOG_COREMIDI_ERRORS + Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); + #endif + + ignoreUnused (lineNum); + return false; + } + + #undef CHECK_ERROR + #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) + + enum class ImplementationStrategy + { + onlyNew, + both, + onlyOld + }; + + #if (defined (MAC_OS_VERSION_11_0) || defined (__IPHONE_14_0)) + #if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_11_0 || __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_14_0) + #define JUCE_HAS_NEW_COREMIDI_API 1 + #define JUCE_HAS_OLD_COREMIDI_API 0 + constexpr auto implementationStrategy = ImplementationStrategy::onlyNew; + #else + #define JUCE_HAS_NEW_COREMIDI_API 1 + #define JUCE_HAS_OLD_COREMIDI_API 1 + constexpr auto implementationStrategy = ImplementationStrategy::both; + #endif + #else + #define JUCE_HAS_NEW_COREMIDI_API 0 + #define JUCE_HAS_OLD_COREMIDI_API 1 + constexpr auto implementationStrategy = ImplementationStrategy::onlyOld; + #endif + + struct SenderBase + { + virtual ~SenderBase() noexcept = default; + + virtual void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) = 0; + virtual void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Iterator e) = 0; + + virtual ump::MidiProtocol getProtocol() const noexcept = 0; + }; + + template + struct Sender; + + #if JUCE_HAS_NEW_COREMIDI_API + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") + + template <> + struct Sender : public SenderBase + { + explicit Sender (MIDIEndpointRef ep) + : umpConverter (getProtocolForEndpoint (ep)) + {} + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) override + { + newSendImpl (port, endpoint, m); + } + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Iterator e) override + { + 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; + + static ump::PacketProtocol getProtocolForEndpoint (MIDIEndpointRef ep) noexcept + { + SInt32 protocol = 0; + CHECK_ERROR (MIDIObjectGetIntegerProperty (ep, kMIDIPropertyProtocolID, &protocol)); + + return protocol == kMIDIProtocol_2_0 ? ump::PacketProtocol::MIDI_2_0 + : ump::PacketProtocol::MIDI_1_0; + } + + 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 + const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); + #endif + + MIDIEventList stackList = {}; + MIDIEventPacket* end = nullptr; + + const auto init = [&] + { + end = MIDIEventListInit (&stackList, + umpConverter.getProtocol() == ump::PacketProtocol::MIDI_2_0 ? kMIDIProtocol_2_0 + : kMIDIProtocol_1_0); + }; + + const auto send = [&] + { + CHECK_ERROR (port != 0 ? MIDISendEventList (port, endpoint, &stackList) + : MIDIReceivedEventList (endpoint, &stackList)); + }; + + const auto add = [&] (const ump::View& view) + { + static_assert (sizeof (uint32_t) == sizeof (UInt32) + && alignof (uint32_t) == alignof (UInt32), + "If this fails, the cast below will be broken too!"); + end = MIDIEventListAdd (&stackList, + sizeof (MIDIEventList::packet), + end, + timeStamp, + view.size(), + reinterpret_cast (view.data())); + }; + + init(); + + umpConverter.convert (params..., [&] (const ump::View& view) + { + add (view); + + if (end != nullptr) + return; + + send(); + init(); + add (view); + }); + + send(); + } + }; + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #endif + + #if JUCE_HAS_OLD_COREMIDI_API + template <> + struct Sender : public SenderBase + { + explicit Sender (MIDIEndpointRef) {} + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) override + { + oldSendImpl (port, endpoint, m); + } + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Iterator e) override + { + std::for_each (b, e, [&] (const ump::View& v) + { + bytestreamConverter.convert (v, 0.0, [&] (const MidiMessage& 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) + { + #if JUCE_IOS + const MIDITimeStamp timeStamp = mach_absolute_time(); + #else + const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); + #endif + + HeapBlock allocatedPackets; + MIDIPacketList stackPacket; + auto* packetToSend = &stackPacket; + auto dataSize = (size_t) message.getRawDataSize(); + + if (message.isSysEx()) + { + const int maxPacketSize = 256; + int pos = 0, bytesLeft = (int) dataSize; + const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; + allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); + packetToSend = allocatedPackets; + packetToSend->numPackets = (UInt32) numPackets; + + auto* p = packetToSend->packet; + + for (int i = 0; i < numPackets; ++i) + { + p->timeStamp = timeStamp; + p->length = (UInt16) jmin (maxPacketSize, bytesLeft); + memcpy (p->data, message.getRawData() + pos, p->length); + pos += p->length; + bytesLeft -= p->length; + p = MIDIPacketNext (p); + } + } + else if (dataSize < 65536) // max packet size + { + auto stackCapacity = sizeof (stackPacket.packet->data); + + if (dataSize > stackCapacity) + { + allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); + packetToSend = allocatedPackets; + } + + packetToSend->numPackets = 1; + auto& p = *(packetToSend->packet); + p.timeStamp = timeStamp; + p.length = (UInt16) dataSize; + memcpy (p.data, message.getRawData(), dataSize); + } + else + { + jassertfalse; // packet too large to send! + return; + } + + if (port != 0) + MIDISend (port, endpoint, packetToSend); + else + MIDIReceived (endpoint, packetToSend); + } + }; + #endif + + #if JUCE_HAS_NEW_COREMIDI_API && JUCE_HAS_OLD_COREMIDI_API + template <> + struct Sender + { + explicit Sender (MIDIEndpointRef ep) + : sender (makeImpl (ep)) + {} + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, const MidiMessage& m) + { + sender->send (port, endpoint, m); + } + + void send (MIDIPortRef port, MIDIEndpointRef endpoint, ump::Iterator b, ump::Iterator e) + { + sender->send (port, endpoint, b, e); + } + + ump::MidiProtocol getProtocol() const noexcept + { + return sender->getProtocol(); + } + + private: + static std::unique_ptr makeImpl (MIDIEndpointRef ep) + { + if (@available (macOS 11, iOS 14, *)) + return std::make_unique> (ep); + + return std::make_unique> (ep); + } + + std::unique_ptr sender; + }; + #endif + + using SenderToUse = Sender; + + //============================================================================== + class MidiPortAndEndpoint + { + public: + MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept + : port (p), endpoint (ep), sender (ep) + {} + + ~MidiPortAndEndpoint() noexcept + { + if (port != 0) + MIDIPortDispose (port); + + // if port == nullptr, it means we created the endpoint, so it's safe to delete it + if (port == 0 && endpoint != 0) + MIDIEndpointDispose (endpoint); + } + + void send (const MidiMessage& m) + { + sender.send (port, endpoint, m); + } + + void send (ump::Iterator b, ump::Iterator e) + { + sender.send (port, endpoint, b, 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: + MIDIPortRef port; + MIDIEndpointRef endpoint; + + SenderToUse sender; + }; + + static MidiDeviceInfo getMidiObjectInfo (MIDIObjectRef entity) + { + MidiDeviceInfo info; + + { + ScopedCFString str; + + if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString))) + info.name = String::fromCFString (str.cfString); + } + + SInt32 objectID = 0; + + if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID))) + { + info.identifier = String (objectID); + } + else + { + ScopedCFString str; + + if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyUniqueID, &str.cfString))) + info.identifier = String::fromCFString (str.cfString); + } + + return info; + } + + static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal) + { + // NB: don't attempt to use nullptr for refs - it fails in some types of build. + MIDIEntityRef entity = 0; + MIDIEndpointGetEntity (endpoint, &entity); + + // probably virtual + if (entity == 0) + return getMidiObjectInfo (endpoint); + + auto result = getMidiObjectInfo (endpoint); + + // endpoint is empty - try the entity + if (result == MidiDeviceInfo()) + result = getMidiObjectInfo (entity); + + // now consider the device + MIDIDeviceRef device = 0; + MIDIEntityGetDevice (entity, &device); + + if (device != 0) + { + auto info = getMidiObjectInfo (device); + + if (info != MidiDeviceInfo()) + { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) + { + result = info; + } + else if (! result.name.startsWithIgnoreCase (info.name)) + { + // prepend the device name and identifier to the entity's + result.name = (info.name + " " + result.name).trimEnd(); + result.identifier = info.identifier + " " + result.identifier; + } + } + } + + return result; + } + + static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint) + { + MidiDeviceInfo result; + + // Does the endpoint have connections? + CFDataRef connections = nullptr; + int numConnections = 0; + + MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); + + if (connections != nullptr) + { + numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); + + if (numConnections > 0) + { + auto* pid = reinterpret_cast (CFDataGetBytePtr (connections)); + + for (int i = 0; i < numConnections; ++i, ++pid) + { + auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType); + + if (err == noErr) + { + MidiDeviceInfo deviceInfo; + + if (connObjectType == kMIDIObjectType_ExternalSource + || connObjectType == kMIDIObjectType_ExternalDestination) + { + // Connected to an external device's endpoint (10.3 and later). + deviceInfo = getEndpointInfo (static_cast (connObject), true); + } + else + { + // Connected to an external device (10.2) (or something else, catch-all) + deviceInfo = getMidiObjectInfo (connObject); + } + + if (deviceInfo != MidiDeviceInfo()) + { + if (result.name.isNotEmpty()) result.name += ", "; + if (result.identifier.isNotEmpty()) result.identifier += ", "; + + result.name += deviceInfo.name; + result.identifier += deviceInfo.identifier; + } + } + } + } + + CFRelease (connections); + } + + // Here, either the endpoint had no connections, or we failed to obtain names for them. + if (result == MidiDeviceInfo()) + return getEndpointInfo (endpoint, false); + + return result; + } + + static int createUniqueIDForMidiPort (String deviceName, bool isInput) + { + String uniqueID; + + #ifdef JucePlugin_CFBundleIdentifier + uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); + #else + auto appBundle = File::getSpecialLocation (File::currentApplicationFile); + ScopedCFString appBundlePath (appBundle.getFullPathName()); + + if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, + appBundlePath.cfString, + kCFURLPOSIXPathStyle, + true)) + { + auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL); + CFRelease (bundleURL); + + if (bundleRef != nullptr) + { + if (auto bundleId = CFBundleGetIdentifier (bundleRef)) + uniqueID = String::fromCFString (bundleId); + + CFRelease (bundleRef); + } + } + #endif + + if (uniqueID.isEmpty()) + uniqueID = String (Random::getSystemRandom().nextInt (1024)); + + uniqueID += "." + deviceName + (isInput ? ".input" : ".output"); + return uniqueID.hashCode(); + } + + static void enableSimulatorMidiSession() + { + #if TARGET_OS_SIMULATOR + static bool hasEnabledNetworkSession = false; + + if (! hasEnabledNetworkSession) + { + MIDINetworkSession* session = [MIDINetworkSession defaultSession]; + session.enabled = YES; + session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; + + hasEnabledNetworkSession = true; + } + #endif + } + + static void globalSystemChangeCallback (const MIDINotification*, void*) + { + // TODO.. Should pass-on this notification.. + } + + static String getGlobalMidiClientName() + { + if (auto* app = JUCEApplicationBase::getInstance()) + return app->getApplicationName(); + + return "JUCE"; + } + + static MIDIClientRef getGlobalMidiClient() + { + static MIDIClientRef globalMidiClient = 0; + + if (globalMidiClient == 0) + { + // Since OSX 10.6, the MIDIClientCreate function will only work + // correctly when called from the message thread! + JUCE_ASSERT_MESSAGE_THREAD + + enableSimulatorMidiSession(); + + ScopedCFString name (getGlobalMidiClientName()); + CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); + } + + return globalMidiClient; + } + + static Array findDevices (bool forInput) + { + // It seems that OSX can be a bit picky about the thread that's first used to + // search for devices. It's safest to use the message thread for calling this. + JUCE_ASSERT_MESSAGE_THREAD + + if (getGlobalMidiClient() == 0) + { + jassertfalse; + return {}; + } + + enableSimulatorMidiSession(); + + Array devices; + auto numDevices = (forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); + + for (ItemCount i = 0; i < numDevices; ++i) + { + MidiDeviceInfo deviceInfo; + + if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) + deviceInfo = getConnectedEndpointInfo (dest); + + if (deviceInfo == MidiDeviceInfo()) + deviceInfo.name = deviceInfo.identifier = ""; + + devices.add (deviceInfo); + } + + return devices; + } + + //============================================================================== + template + struct Receiver; + + #if JUCE_HAS_NEW_COREMIDI_API + template <> + struct Receiver + { + Receiver (ump::PacketProtocol protocol, ump::Receiver& receiver) + : u32InputHandler (std::make_unique (protocol, receiver)) + {} + + Receiver (MidiInput& input, MidiInputCallback& callback) + : u32InputHandler (std::make_unique (input, callback)) + {} + + void dispatch (const MIDIEventList& list, double time) const + { + auto* packet = &list.packet[0]; + + for (uint32_t i = 0; i < list.numPackets; ++i) + { + static_assert (sizeof (uint32_t) == sizeof (UInt32) + && alignof (uint32_t) == alignof (UInt32), + "If this fails, the cast below will be broken too!"); + u32InputHandler->pushMidiData (reinterpret_cast (packet->words), + reinterpret_cast (packet->words + packet->wordCount), + time); + + packet = MIDIEventPacketNext (packet); + } + } + + private: + std::unique_ptr u32InputHandler; + }; + #endif + + #if JUCE_HAS_OLD_COREMIDI_API + template <> + struct Receiver + { + Receiver (ump::PacketProtocol protocol, ump::Receiver& receiver) + : bytestreamInputHandler (std::make_unique (protocol, receiver)) + {} + + Receiver (MidiInput& input, MidiInputCallback& callback) + : bytestreamInputHandler (std::make_unique (input, callback)) + {} + + void dispatch (const MIDIPacketList& list, double time) const + { + auto* packet = &list.packet[0]; + + for (unsigned int i = 0; i < list.numPackets; ++i) + { + auto len = readUnalignedlength)> (&(packet->length)); + bytestreamInputHandler->pushMidiData (packet->data, len, time); + + packet = MIDIPacketNext (packet); + } + } + + private: + std::unique_ptr bytestreamInputHandler; + }; + #endif + + #if JUCE_HAS_NEW_COREMIDI_API && JUCE_HAS_OLD_COREMIDI_API + template <> + struct Receiver + { + Receiver (ump::PacketProtocol protocol, ump::Receiver& receiver) + : newReceiver (protocol, receiver), oldReceiver (protocol, receiver) + {} + + Receiver (MidiInput& input, MidiInputCallback& callback) + : newReceiver (input, callback), oldReceiver (input, callback) + {} + + void dispatch (const MIDIEventList& list, double time) const + { + newReceiver.dispatch (list, time); + } + + void dispatch (const MIDIPacketList& list, double time) const + { + oldReceiver.dispatch (list, time); + } + + private: + Receiver newReceiver; + Receiver oldReceiver; + }; + #endif + + using ReceiverToUse = Receiver; + + class MidiPortAndCallback; + CriticalSection callbackLock; + Array activeCallbacks; + + class MidiPortAndCallback + { + public: + MidiPortAndCallback (MidiInput& inputIn, ReceiverToUse receiverIn) + : input (&inputIn), receiver (std::move (receiverIn)) + {} + + ~MidiPortAndCallback() + { + active = false; + + { + const ScopedLock sl (callbackLock); + activeCallbacks.removeFirstMatchingValue (this); + } + + if (portAndEndpoint != nullptr && portAndEndpoint->canStop()) + portAndEndpoint->stop(); + } + + template + void handlePackets (const EventList& list) + { + const auto time = Time::getMillisecondCounterHiRes() * 0.001; + + const ScopedLock sl (callbackLock); + + if (activeCallbacks.contains (this) && active) + receiver.dispatch (list, time); + } + + MidiInput* input = nullptr; + std::atomic active { false }; + + ReceiverToUse receiver; + + std::unique_ptr portAndEndpoint; + + private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiPortAndCallback) + }; + + //============================================================================== + static Array getEndpoints (bool isInput) + { + Array endpoints; + auto numDevices = (isInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); + + for (ItemCount i = 0; i < numDevices; ++i) + endpoints.add (isInput ? MIDIGetSource (i) : MIDIGetDestination (i)); + + return endpoints; + } + + struct CreatorFunctionPointers + { + OSStatus (*createInputPort) (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef portName, + void* refCon, + MIDIPortRef* outPort); + + OSStatus (*createDestination) (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + void* refCon, + MIDIEndpointRef* outDest); + + OSStatus (*createSource) (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + MIDIEndpointRef* outSrc); + }; + + template + struct CreatorFunctions; + + #if JUCE_HAS_NEW_COREMIDI_API + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability-new") + + template <> + struct CreatorFunctions + { + static OSStatus createInputPort (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef portName, + void* refCon, + MIDIPortRef* outPort) + { + return MIDIInputPortCreateWithProtocol (client, + portName, + convertToPacketProtocol (protocol), + outPort, + ^void (const MIDIEventList* l, void* src) + { + newMidiInputProc (l, refCon, src); + }); + } + + static OSStatus createDestination (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + void* refCon, + MIDIEndpointRef* outDest) + { + return MIDIDestinationCreateWithProtocol (client, + name, + convertToPacketProtocol (protocol), + outDest, + ^void (const MIDIEventList* l, void* src) + { + newMidiInputProc (l, refCon, src); + }); + } + + static OSStatus createSource (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + MIDIEndpointRef* outSrc) + { + return MIDISourceCreateWithProtocol (client, + name, + convertToPacketProtocol (protocol), + outSrc); + } + + static constexpr CreatorFunctionPointers getCreatorFunctionPointers() + { + return { createInputPort, createDestination, createSource }; + } + + private: + static constexpr MIDIProtocolID convertToPacketProtocol (ump::PacketProtocol p) + { + return p == ump::PacketProtocol::MIDI_2_0 ? kMIDIProtocol_2_0 + : kMIDIProtocol_1_0; + } + + static void newMidiInputProc (const MIDIEventList* list, void* readProcRefCon, void*) + { + static_cast (readProcRefCon)->handlePackets (*list); + } + }; + + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #endif + + #if JUCE_HAS_OLD_COREMIDI_API + template <> + struct CreatorFunctions + { + static OSStatus createInputPort (ump::PacketProtocol, + MIDIClientRef client, + CFStringRef portName, + void* refCon, + MIDIPortRef* outPort) + { + return MIDIInputPortCreate (client, portName, oldMidiInputProc, refCon, outPort); + } + + static OSStatus createDestination (ump::PacketProtocol, + MIDIClientRef client, + CFStringRef name, + void* refCon, + MIDIEndpointRef* outDest) + { + return MIDIDestinationCreate (client, name, oldMidiInputProc, refCon, outDest); + } + + static OSStatus createSource (ump::PacketProtocol, + MIDIClientRef client, + CFStringRef name, + MIDIEndpointRef* outSrc) + { + return MIDISourceCreate (client, name, outSrc); + } + + static constexpr CreatorFunctionPointers getCreatorFunctionPointers() + { + return { createInputPort, createDestination, createSource }; + } + + private: + static void oldMidiInputProc (const MIDIPacketList* list, void* readProcRefCon, void*) + { + static_cast (readProcRefCon)->handlePackets (*list); + } + }; + #endif + + #if JUCE_HAS_NEW_COREMIDI_API && JUCE_HAS_OLD_COREMIDI_API + template <> + struct CreatorFunctions + { + static OSStatus createInputPort (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef portName, + void* refCon, + MIDIPortRef* outPort) + { + return getCreatorFunctionPointers().createInputPort (protocol, client, portName, refCon, outPort); + } + + static OSStatus createDestination (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + void* refCon, + MIDIEndpointRef* outDest) + { + return getCreatorFunctionPointers().createDestination (protocol, client, name, refCon, outDest); + } + + static OSStatus createSource (ump::PacketProtocol protocol, + MIDIClientRef client, + CFStringRef name, + MIDIEndpointRef* outSrc) + { + return getCreatorFunctionPointers().createSource (protocol, client, name, outSrc); + } + + private: + static CreatorFunctionPointers getCreatorFunctionPointers() + { + if (@available (macOS 11, iOS 14, *)) + return CreatorFunctions::getCreatorFunctionPointers(); + + return CreatorFunctions::getCreatorFunctionPointers(); + } + }; + #endif + + using CreatorFunctionsToUse = CreatorFunctions; +} + +//============================================================================== +class MidiInput::Pimpl : public CoreMidiHelpers::MidiPortAndCallback +{ +public: + using MidiPortAndCallback::MidiPortAndCallback; + + static std::unique_ptr makePimpl (MidiInput& midiInput, + ump::PacketProtocol packetProtocol, + ump::Receiver& umpReceiver) + { + return std::make_unique (midiInput, CoreMidiHelpers::ReceiverToUse (packetProtocol, umpReceiver)); + } + + static std::unique_ptr makePimpl (MidiInput& midiInput, + MidiInputCallback* midiInputCallback) + { + if (midiInputCallback == nullptr) + return {}; + + return std::make_unique (midiInput, CoreMidiHelpers::ReceiverToUse (midiInput, *midiInputCallback)); + } + + template + static std::unique_ptr makeInput (const String& name, + const String& identifier, + Args&&... args) + { + using namespace CoreMidiHelpers; + + if (auto midiInput = rawToUniquePtr (new MidiInput (name, identifier))) + { + if ((midiInput->internal = makePimpl (*midiInput, std::forward (args)...))) + { + const ScopedLock sl (callbackLock); + activeCallbacks.add (midiInput->internal.get()); + + return midiInput; + } + } + + return {}; + } + + template + static std::unique_ptr openDevice (ump::PacketProtocol protocol, + const String& deviceIdentifier, + Args&&... args) + { + using namespace CoreMidiHelpers; + + if (deviceIdentifier.isEmpty()) + return {}; + + if (auto client = getGlobalMidiClient()) + { + for (auto& endpoint : getEndpoints (true)) + { + auto endpointInfo = getConnectedEndpointInfo (endpoint); + + if (deviceIdentifier != endpointInfo.identifier) + continue; + + ScopedCFString cfName; + + if (! CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) + continue; + + if (auto input = makeInput (endpointInfo.name, endpointInfo.identifier, std::forward (args)...)) + { + MIDIPortRef port; + + if (! CHECK_ERROR (CreatorFunctionsToUse::createInputPort (protocol, client, cfName.cfString, input->internal.get(), &port))) + continue; + + if (! CHECK_ERROR (MIDIPortConnectSource (port, endpoint, nullptr))) + { + CHECK_ERROR (MIDIPortDispose (port)); + continue; + } + + input->internal->portAndEndpoint = std::make_unique (port, endpoint); + return input; + } + } + } + + return {}; + } + + template + static std::unique_ptr createDevice (ump::PacketProtocol protocol, + const String& deviceName, + Args&&... args) + { + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) + { + auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); + + if (auto input = makeInput (deviceName, String (deviceIdentifier), std::forward (args)...)) + { + MIDIEndpointRef endpoint; + ScopedCFString name (deviceName); + + auto err = CreatorFunctionsToUse::createDestination (protocol, client, name.cfString, input->internal.get(), &endpoint); + + #if JUCE_IOS + if (err == kMIDINotPermitted) + { + // If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" + // setting in the iOS exporter for your app - this is required if you want to create a MIDI device! + jassertfalse; + return {}; + } + #endif + + if (! CHECK_ERROR (err)) + return {}; + + if (! CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) + return {}; + + input->internal->portAndEndpoint = std::make_unique ((MIDIPortRef) 0, endpoint); + return input; + } + } + + return {}; + } +}; + +//============================================================================== +Array MidiInput::getAvailableDevices() +{ + return CoreMidiHelpers::findDevices (true); +} + +MidiDeviceInfo MidiInput::getDefaultDevice() +{ + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) +{ + if (callback == nullptr) + return nullptr; + + return Pimpl::openDevice (ump::PacketProtocol::MIDI_1_0, + deviceIdentifier, + callback); +} + +std::unique_ptr MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) +{ + return Pimpl::createDevice (ump::PacketProtocol::MIDI_1_0, + deviceName, + callback); +} + +StringArray MidiInput::getDevices() +{ + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiInput::getDefaultDeviceIndex() +{ + return 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() +{ + const ScopedLock sl (CoreMidiHelpers::callbackLock); + internal->active = true; +} + +void MidiInput::stop() +{ + const ScopedLock sl (CoreMidiHelpers::callbackLock); + internal->active = false; +} + +//============================================================================== +class MidiOutput::Pimpl : public CoreMidiHelpers::MidiPortAndEndpoint +{ +public: + using MidiPortAndEndpoint::MidiPortAndEndpoint; +}; + +Array MidiOutput::getAvailableDevices() +{ + return CoreMidiHelpers::findDevices (false); +} + +MidiDeviceInfo MidiOutput::getDefaultDevice() +{ + return getAvailableDevices().getFirst(); +} + +std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) +{ + if (deviceIdentifier.isEmpty()) + return {}; + + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) + { + for (auto& endpoint : getEndpoints (false)) + { + auto endpointInfo = getConnectedEndpointInfo (endpoint); + + if (deviceIdentifier != endpointInfo.identifier) + continue; + + ScopedCFString cfName; + + if (! CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) + continue; + + MIDIPortRef port; + + if (! CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) + continue; + + auto midiOutput = rawToUniquePtr (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); + midiOutput->internal = std::make_unique (port, endpoint); + + return midiOutput; + } + } + + return {}; +} + +std::unique_ptr MidiOutput::createNewDevice (const String& deviceName) +{ + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) + { + MIDIEndpointRef endpoint; + + ScopedCFString name (deviceName); + + auto err = CreatorFunctionsToUse::createSource (ump::PacketProtocol::MIDI_1_0, client, name.cfString, &endpoint); + + #if JUCE_IOS + if (err == kMIDINotPermitted) + { + // If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" + // setting in the iOS exporter for your app - this is required if you want to create a MIDI device! + jassertfalse; + return {}; + } + #endif + + if (! CHECK_ERROR (err)) + return {}; + + auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, false); + + if (! CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) + return {}; + + auto midiOutput = rawToUniquePtr (new MidiOutput (deviceName, String (deviceIdentifier))); + midiOutput->internal = std::make_unique ((UInt32) 0, endpoint); + + return midiOutput; + } + + return {}; +} + +StringArray MidiOutput::getDevices() +{ + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return 0; +} + +std::unique_ptr MidiOutput::openDevice (int index) +{ + return openDevice (getAvailableDevices()[index].identifier); +} + +MidiOutput::~MidiOutput() +{ + stopBackgroundThread(); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + internal->send (message); +} + +#undef CHECK_ERROR + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp index 466bf1f..03df0d4 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_ASIO.cpp @@ -504,7 +504,7 @@ class ASIOAudioIODevice : public AudioIODevice, { inBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * n); - ASIOChannelInfo channelInfo = { 0 }; + ASIOChannelInfo channelInfo = {}; channelInfo.channel = i; channelInfo.isInput = 1; asioObject->getChannelInfo (&channelInfo); @@ -526,7 +526,7 @@ class ASIOAudioIODevice : public AudioIODevice, { outBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * (numActiveInputChans + n)); - ASIOChannelInfo channelInfo = { 0 }; + ASIOChannelInfo channelInfo = {}; channelInfo.channel = i; channelInfo.isInput = 0; asioObject->getChannelInfo (&channelInfo); @@ -673,10 +673,10 @@ class ASIOAudioIODevice : public AudioIODevice, lastCallback->audioDeviceStopped(); } - String getLastError() { return error; } - bool hasControlPanel() const { return true; } + String getLastError() override { return error; } + bool hasControlPanel() const override { return true; } - bool showControlPanel() + bool showControlPanel() override { JUCE_ASIO_LOG ("showing control panel"); @@ -767,7 +767,7 @@ class ASIOAudioIODevice : public AudioIODevice, bool deviceIsOpen = false, isStarted = false, buffersCreated = false; std::atomic calledback { false }; - bool littleEndian = false, postOutput = true, needToReset = false; + bool postOutput = true, needToReset = false; bool insideControlPanelModalLoop = false; bool shouldUsePreferredSize = false; int xruns = 0; @@ -785,7 +785,7 @@ class ASIOAudioIODevice : public AudioIODevice, String getChannelName (int index, bool isInput) const { - ASIOChannelInfo channelInfo = { 0 }; + ASIOChannelInfo channelInfo = {}; channelInfo.channel = index; channelInfo.isInput = isInput ? 1 : 0; asioObject->getChannelInfo (&channelInfo); @@ -1065,7 +1065,7 @@ class ASIOAudioIODevice : public AudioIODevice, for (int i = 0; i < totalNumOutputChans; ++i) { - ASIOChannelInfo channelInfo = { 0 }; + ASIOChannelInfo channelInfo = {}; channelInfo.channel = i; channelInfo.isInput = 0; asioObject->getChannelInfo (&channelInfo); @@ -1640,9 +1640,4 @@ void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type) type->sendDeviceChangeToListeners(); } -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() -{ - return new ASIOAudioIODeviceType(); -} - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp index 9498067..38300ae 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp @@ -1282,10 +1282,4 @@ class DSoundAudioIODeviceType : public AudioIODeviceType, JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) }; -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() -{ - return new DSoundAudioIODeviceType(); -} - } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp index 21cc58d..91379b5 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -29,37 +29,39 @@ namespace juce { -struct MidiServiceType +class MidiInput::Pimpl { - struct InputWrapper - { - virtual ~InputWrapper() {} +public: + virtual ~Pimpl() noexcept = default; - virtual String getDeviceIdentifier() = 0; - virtual String getDeviceName() = 0; + virtual String getDeviceIdentifier() = 0; + virtual String getDeviceName() = 0; - virtual void start() = 0; - virtual void stop() = 0; - }; + virtual void start() = 0; + virtual void stop() = 0; +}; - struct OutputWrapper - { - virtual ~OutputWrapper() {} +class MidiOutput::Pimpl +{ +public: + virtual ~Pimpl() noexcept = default; - virtual String getDeviceIdentifier() = 0; - virtual String getDeviceName() = 0; + virtual String getDeviceIdentifier() = 0; + virtual String getDeviceName() = 0; - virtual void sendMessageNow (const MidiMessage&) = 0; - }; + virtual void sendMessageNow (const MidiMessage&) = 0; +}; - MidiServiceType() {} - virtual ~MidiServiceType() {} +struct MidiServiceType +{ + MidiServiceType() = default; + virtual ~MidiServiceType() noexcept = default; virtual Array getAvailableDevices (bool) = 0; virtual MidiDeviceInfo getDefaultDevice (bool) = 0; - virtual InputWrapper* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; - virtual OutputWrapper* createOutputWrapper (const String&) = 0; + virtual MidiInput::Pimpl* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; + virtual MidiOutput::Pimpl* createOutputWrapper (const String&) = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) }; @@ -82,12 +84,12 @@ struct Win32MidiService : public MidiServiceType, : Win32OutputWrapper::getDefaultDevice(); } - InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override + MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override { return new Win32InputWrapper (*this, input, deviceIdentifier, callback); } - OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override + MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override { return new Win32OutputWrapper (*this, deviceIdentifier); } @@ -384,7 +386,7 @@ struct Win32MidiService : public MidiServiceType, } }; - struct Win32InputWrapper : public InputWrapper, + struct Win32InputWrapper : public MidiInput::Pimpl, public Win32MidiDeviceQuery { Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c) @@ -508,7 +510,7 @@ struct Win32MidiService : public MidiServiceType, }; //============================================================================== - struct Win32OutputWrapper : public OutputWrapper, + struct Win32OutputWrapper : public MidiOutput::Pimpl, public Win32MidiDeviceQuery { Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) @@ -763,12 +765,12 @@ struct WinRTMidiService : public MidiServiceType : outputDeviceWatcher->getDefaultDevice(); } - InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override + MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override { return new WinRTInputWrapper (*this, input, deviceIdentifier, callback); } - OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override + MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override { return new WinRTOutputWrapper (*this, deviceIdentifier); } @@ -1554,7 +1556,7 @@ struct WinRTMidiService : public MidiServiceType }; //============================================================================== - struct WinRTInputWrapper final : public InputWrapper, + struct WinRTInputWrapper final : public MidiInput::Pimpl, private WinRTIOWrapper { @@ -1708,7 +1710,7 @@ struct WinRTMidiService : public MidiServiceType }; //============================================================================== - struct WinRTOutputWrapper final : public OutputWrapper, + struct WinRTOutputWrapper final : public MidiOutput::Pimpl, private WinRTIOWrapper { WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) @@ -1865,7 +1867,7 @@ std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier return {}; std::unique_ptr in (new MidiInput ({}, deviceIdentifier)); - std::unique_ptr wrapper; + std::unique_ptr wrapper; try { @@ -1877,7 +1879,7 @@ std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier } in->setName (wrapper->getDeviceName()); - in->internal = wrapper.release(); + in->internal = std::move (wrapper); return in; } @@ -1907,13 +1909,10 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) { } -MidiInput::~MidiInput() -{ - delete static_cast (internal); -} +MidiInput::~MidiInput() = default; -void MidiInput::start() { static_cast (internal)->start(); } -void MidiInput::stop() { static_cast (internal)->stop(); } +void MidiInput::start() { internal->start(); } +void MidiInput::stop() { internal->stop(); } //============================================================================== Array MidiOutput::getAvailableDevices() @@ -1931,7 +1930,7 @@ std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifi if (deviceIdentifier.isEmpty()) return {}; - std::unique_ptr wrapper; + std::unique_ptr wrapper; try { @@ -1945,7 +1944,7 @@ std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifi std::unique_ptr out; out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); - out->internal = wrapper.release(); + out->internal = std::move (wrapper); return out; } @@ -1973,12 +1972,11 @@ std::unique_ptr MidiOutput::openDevice (int index) MidiOutput::~MidiOutput() { stopBackgroundThread(); - delete static_cast (internal); } void MidiOutput::sendMessageNow (const MidiMessage& message) { - static_cast (internal)->sendMessageNow (message); + internal->sendMessageNow (message); } } // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp old mode 100644 new mode 100755 index 64d2004..333d295 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -208,6 +208,29 @@ enum AUDCLNT_SHAREMODE AUDCLNT_SHAREMODE_EXCLUSIVE }; +enum AUDIO_STREAM_CATEGORY +{ + AudioCategory_Other = 0, + AudioCategory_ForegroundOnlyMedia, + AudioCategory_BackgroundCapableMedia, + AudioCategory_Communications, + AudioCategory_Alerts, + AudioCategory_SoundEffects, + AudioCategory_GameEffects, + AudioCategory_GameMedia, + AudioCategory_GameChat, + AudioCategory_Speech, + AudioCategory_Movie, + AudioCategory_Media +}; + +struct AudioClientProperties +{ + UINT32 cbSize; + BOOL bIsOffload; + AUDIO_STREAM_CATEGORY eCategory; +}; + JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") { JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0; @@ -224,6 +247,20 @@ JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") JUCE_COMCALL GetService (REFIID, void**) = 0; }; +JUCE_COMCLASS (IAudioClient2, "726778CD-F60A-4eda-82DE-E47610CD78AA") : public IAudioClient +{ + JUCE_COMCALL IsOffloadCapable (AUDIO_STREAM_CATEGORY, BOOL*) = 0; + JUCE_COMCALL SetClientProperties (const AudioClientProperties*) = 0; + JUCE_COMCALL GetBufferSizeLimits (const WAVEFORMATEX*, BOOL, REFERENCE_TIME*, REFERENCE_TIME*) = 0; +}; + +JUCE_COMCLASS (IAudioClient3, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") : public IAudioClient2 +{ + JUCE_COMCALL GetSharedModeEnginePeriod (const WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32*) = 0; + JUCE_COMCALL GetCurrentSharedModeEnginePeriod (WAVEFORMATEX**, UINT32*) = 0; + JUCE_COMCALL InitializeSharedAudioStream (DWORD, UINT32, const WAVEFORMATEX*, LPCGUID) = 0; +}; + JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317") { JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0; @@ -322,87 +359,76 @@ String getDeviceID (IMMDevice* device) return s; } -EDataFlow getDataFlow (const ComSmartPtr& device) +static EDataFlow getDataFlow (const ComSmartPtr& device) { EDataFlow flow = eRender; - ComSmartPtr endPoint; - if (check (device.QueryInterface (endPoint))) - (void) check (endPoint->GetDataFlow (&flow)); + if (auto endpoint = device.getInterface()) + (void) check (endpoint->GetDataFlow (&flow)); return flow; } -int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept +static int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept { return roundToInt (sampleRate * ((double) t) * 0.0000001); } -REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept +static REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept { return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); } -void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept +static void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept { memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE) : sizeof (WAVEFORMATEX)); } +static bool isExclusiveMode (WASAPIDeviceMode deviceMode) noexcept +{ + return deviceMode == WASAPIDeviceMode::exclusive; +} + +static bool isLowLatencyMode (WASAPIDeviceMode deviceMode) noexcept +{ + return deviceMode == WASAPIDeviceMode::sharedLowLatency; +} + +static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept +{ + return deviceMode == WASAPIDeviceMode::shared; +} + //============================================================================== class WASAPIDeviceBase { public: - WASAPIDeviceBase (const ComSmartPtr& d, bool exclusiveMode) - : device (d), useExclusiveMode (exclusiveMode) + WASAPIDeviceBase (const ComSmartPtr& d, WASAPIDeviceMode mode) + : device (d), + deviceMode (mode) { clientEvent = CreateEvent (nullptr, false, false, nullptr); ComSmartPtr tempClient (createClient()); + if (tempClient == nullptr) return; - REFERENCE_TIME defaultPeriod, minPeriod; - if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod))) - return; + WAVEFORMATEXTENSIBLE format; - WAVEFORMATEX* mixFormat = nullptr; - if (! check (tempClient->GetMixFormat (&mixFormat))) + if (! getClientMixFormat (tempClient, format)) return; - WAVEFORMATEXTENSIBLE format; - copyWavFormat (format, mixFormat); - CoTaskMemFree (mixFormat); - actualNumChannels = numChannels = format.Format.nChannels; defaultSampleRate = format.Format.nSamplesPerSec; - minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); - defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); - mixFormatChannelMask = format.dwChannelMask; - rates.addUsingDefaultSort (defaultSampleRate); + mixFormatChannelMask = format.dwChannelMask; - if (useExclusiveMode - && findSupportedFormat (tempClient, defaultSampleRate, format.dwChannelMask, format)) - { - // Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format) - } - - for (auto rate : { 8000, 11025, 16000, 22050, 32000, - 44100, 48000, 88200, 96000, 176400, - 192000, 352800, 384000, 705600, 768000 }) - { - if (rates.contains (rate)) - continue; - - format.Format.nSamplesPerSec = (DWORD) rate; - format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8); + if (isExclusiveMode (deviceMode)) + findSupportedFormat (tempClient, defaultSampleRate, mixFormatChannelMask, format); - if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE - : AUDCLNT_SHAREMODE_SHARED, - (WAVEFORMATEX*) &format, 0))) - if (! rates.contains (rate)) - rates.addUsingDefaultSort (rate); - } + querySupportedBufferSizes (format, tempClient); + querySupportedSampleRates (format, tempClient); } virtual ~WASAPIDeviceBase() @@ -487,11 +513,14 @@ class WASAPIDeviceBase //============================================================================== ComSmartPtr device; ComSmartPtr client; + + WASAPIDeviceMode deviceMode; + double sampleRate = 0, defaultSampleRate = 0; int numChannels = 0, actualNumChannels = 0; int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0; + int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0; DWORD mixFormatChannelMask = 0; - const bool useExclusiveMode; Array rates; HANDLE clientEvent = {}; BigInteger channels; @@ -582,6 +611,84 @@ class WASAPIDeviceBase return newClient; } + static bool getClientMixFormat (ComSmartPtr& client, WAVEFORMATEXTENSIBLE& format) + { + WAVEFORMATEX* mixFormat = nullptr; + + if (! check (client->GetMixFormat (&mixFormat))) + return false; + + copyWavFormat (format, mixFormat); + CoTaskMemFree (mixFormat); + + return true; + } + + //============================================================================== + void querySupportedBufferSizes (WAVEFORMATEXTENSIBLE format, ComSmartPtr& audioClient) + { + if (isLowLatencyMode (deviceMode)) + { + if (auto audioClient3 = audioClient.getInterface()) + { + UINT32 defaultPeriod = 0, fundamentalPeriod = 0, minPeriod = 0, maxPeriod = 0; + + if (check (audioClient3->GetSharedModeEnginePeriod ((WAVEFORMATEX*) &format, + &defaultPeriod, + &fundamentalPeriod, + &minPeriod, + &maxPeriod))) + { + minBufferSize = minPeriod; + defaultBufferSize = defaultPeriod; + lowLatencyMaxBufferSize = maxPeriod; + lowLatencyBufferSizeMultiple = fundamentalPeriod; + } + } + } + else + { + REFERENCE_TIME defaultPeriod, minPeriod; + + if (! check (audioClient->GetDevicePeriod (&defaultPeriod, &minPeriod))) + return; + + minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); + defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); + } + } + + void querySupportedSampleRates (WAVEFORMATEXTENSIBLE format, ComSmartPtr& audioClient) + { + for (auto rate : { 8000, 11025, 16000, 22050, 32000, + 44100, 48000, 88200, 96000, 176400, + 192000, 352800, 384000, 705600, 768000 }) + { + if (rates.contains (rate)) + continue; + + format.Format.nSamplesPerSec = (DWORD) rate; + format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8); + + WAVEFORMATEX* nearestFormat = nullptr; + + if (SUCCEEDED (audioClient->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE + : AUDCLNT_SHAREMODE_SHARED, + (WAVEFORMATEX*) &format, + isExclusiveMode (deviceMode) ? nullptr + : &nearestFormat))) + { + if (nearestFormat != nullptr) + rate = nearestFormat->nSamplesPerSec; + + if (! rates.contains (rate)) + rates.addUsingDefaultSort (rate); + } + + CoTaskMemFree (nearestFormat); + } + } + struct AudioSampleFormat { bool useFloat; @@ -613,22 +720,35 @@ class WASAPIDeviceBase format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; format.dwChannelMask = newMixFormatChannelMask; - WAVEFORMATEXTENSIBLE* nearestFormat = nullptr; + WAVEFORMATEX* nearestFormat = nullptr; - HRESULT hr = clientToUse->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE - : AUDCLNT_SHAREMODE_SHARED, + HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE + : AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*) &format, - useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat); + isExclusiveMode (deviceMode) ? nullptr + : &nearestFormat); logFailure (hr); - if (hr == S_FALSE && format.Format.nSamplesPerSec == nearestFormat->Format.nSamplesPerSec) + auto supportsSRC = supportsSampleRateConversion (deviceMode); + + if (hr == S_FALSE + && nearestFormat != nullptr + && (format.Format.nSamplesPerSec == nearestFormat->nSamplesPerSec + || supportsSRC)) { - copyWavFormat (format, (const WAVEFORMATEX*) nearestFormat); + copyWavFormat (format, nearestFormat); + + if (supportsSRC) + { + format.Format.nSamplesPerSec = (DWORD) newSampleRate; + format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign); + } + hr = S_OK; } CoTaskMemFree (nearestFormat); - return check (hr); + return hr == S_OK; } bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate, @@ -652,50 +772,88 @@ class WASAPIDeviceBase return false; } - bool tryInitialisingWithBufferSize (int bufferSizeSamples) + DWORD getStreamFlags() { - WAVEFORMATEXTENSIBLE format; + DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/ - if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format)) + if (supportsSampleRateConversion (deviceMode)) + streamFlags |= (0x80000000 /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/ + | 0x8000000); /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/ + + return streamFlags; + } + + bool initialiseLowLatencyClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format) + { + if (auto audioClient3 = client.getInterface()) + return check (audioClient3->InitializeSharedAudioStream (getStreamFlags(), + bufferSizeSamples, + (WAVEFORMATEX*) &format, + nullptr)); + + return false; + } + + bool initialiseStandardClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format) + { + REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; + + check (client->GetDevicePeriod (&defaultPeriod, &minPeriod)); + + if (isExclusiveMode (deviceMode) && bufferSizeSamples > 0) + defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec)); + + for (;;) { - REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; + GUID session; + auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE + : AUDCLNT_SHAREMODE_SHARED, + getStreamFlags(), + defaultPeriod, + isExclusiveMode (deviceMode) ? defaultPeriod : 0, + (WAVEFORMATEX*) &format, + &session); + + if (check (hr)) + return true; - check (client->GetDevicePeriod (&defaultPeriod, &minPeriod)); + // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks) + if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED + break; - if (useExclusiveMode && bufferSizeSamples > 0) - defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec)); + UINT32 numFrames = 0; + if (! check (client->GetBufferSize (&numFrames))) + break; - for (;;) - { - GUID session; - HRESULT hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED, - 0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/, - defaultPeriod, useExclusiveMode ? defaultPeriod : 0, (WAVEFORMATEX*) &format, &session); + // Recreate client + client = nullptr; + client = createClient(); - if (check (hr)) - { - actualNumChannels = format.Format.nChannels; - const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - bytesPerSample = format.Format.wBitsPerSample / 8; - bytesPerFrame = format.Format.nBlockAlign; + defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec); + } - updateFormat (isFloat); - return true; - } + return false; + } - // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks) - if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED - break; + bool tryInitialisingWithBufferSize (int bufferSizeSamples) + { + WAVEFORMATEXTENSIBLE format; - UINT32 numFrames = 0; - if (! check (client->GetBufferSize (&numFrames))) - break; + if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format)) + { + auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format) + : initialiseStandardClient (bufferSizeSamples, format); + + if (isInitialised) + { + actualNumChannels = format.Format.nChannels; + const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + bytesPerSample = format.Format.wBitsPerSample / 8; + bytesPerFrame = format.Format.nBlockAlign; - // Recreate client - client = nullptr; - client = createClient(); + updateFormat (isFloat); - defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec); + return true; } } @@ -709,8 +867,8 @@ class WASAPIDeviceBase class WASAPIInputDevice : public WASAPIDeviceBase { public: - WASAPIInputDevice (const ComSmartPtr& d, bool exclusiveMode) - : WASAPIDeviceBase (d, exclusiveMode) + WASAPIInputDevice (const ComSmartPtr& d, WASAPIDeviceMode mode) + : WASAPIDeviceBase (d, mode) { } @@ -872,8 +1030,8 @@ class WASAPIInputDevice : public WASAPIDeviceBase class WASAPIOutputDevice : public WASAPIDeviceBase { public: - WASAPIOutputDevice (const ComSmartPtr& d, bool exclusiveMode) - : WASAPIDeviceBase (d, exclusiveMode) + WASAPIOutputDevice (const ComSmartPtr& d, WASAPIDeviceMode mode) + : WASAPIDeviceBase (d, mode) { } @@ -931,7 +1089,7 @@ class WASAPIOutputDevice : public WASAPIDeviceBase if (numChannels <= 0) return 0; - if (! useExclusiveMode) + if (! isExclusiveMode (deviceMode)) { UINT32 padding = 0; @@ -953,7 +1111,7 @@ class WASAPIOutputDevice : public WASAPIDeviceBase while (bufferSize > 0) { // This is needed in order not to drop any input data if the output device endpoint buffer was full - if ((! useExclusiveMode) && inputDevice != nullptr + if ((! isExclusiveMode (deviceMode)) && inputDevice != nullptr && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) inputDevice->handleDeviceBuffer(); @@ -968,7 +1126,7 @@ class WASAPIOutputDevice : public WASAPIDeviceBase break; } - if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) + if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) break; uint8* outputData = nullptr; @@ -1002,12 +1160,12 @@ class WASAPIAudioIODevice : public AudioIODevice, const String& typeName, const String& outputDeviceID, const String& inputDeviceID, - bool exclusiveMode) + WASAPIDeviceMode mode) : AudioIODevice (deviceName, typeName), Thread ("JUCE WASAPI"), outputDeviceId (outputDeviceID), inputDeviceId (inputDeviceID), - useExclusiveMode (exclusiveMode) + deviceMode (mode) { } @@ -1026,21 +1184,48 @@ class WASAPIAudioIODevice : public AudioIODevice, { jassert (inputDevice != nullptr || outputDevice != nullptr); + sampleRates.clear(); + if (inputDevice != nullptr && outputDevice != nullptr) { defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate); - minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize); + minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize); defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); - sampleRates = inputDevice->rates; - sampleRates.removeValuesNotIn (outputDevice->rates); + + if (isLowLatencyMode (deviceMode)) + { + lowLatencyMaxBufferSize = jmin (inputDevice->lowLatencyMaxBufferSize, outputDevice->lowLatencyMaxBufferSize); + lowLatencyBufferSizeMultiple = jmax (inputDevice->lowLatencyBufferSizeMultiple, outputDevice->lowLatencyBufferSizeMultiple); + } + + sampleRates.addArray (inputDevice->rates); + + if (supportsSampleRateConversion (deviceMode)) + { + for (auto r : outputDevice->rates) + if (! sampleRates.contains (r)) + sampleRates.addUsingDefaultSort (r); + } + else + { + sampleRates.removeValuesNotIn (outputDevice->rates); + } } else { - WASAPIDeviceBase* d = inputDevice != nullptr ? static_cast (inputDevice.get()) - : static_cast (outputDevice.get()); + auto* d = inputDevice != nullptr ? static_cast (inputDevice.get()) + : static_cast (outputDevice.get()); + defaultSampleRate = d->defaultSampleRate; minBufferSize = d->minBufferSize; defaultBufferSize = d->defaultBufferSize; + + if (isLowLatencyMode (deviceMode)) + { + lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize; + lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple; + } + sampleRates = d->rates; } @@ -1050,13 +1235,28 @@ class WASAPIAudioIODevice : public AudioIODevice, if (minBufferSize != defaultBufferSize) bufferSizes.addUsingDefaultSort (minBufferSize); - int n = 64; - for (int i = 0; i < 40; ++i) + if (isLowLatencyMode (deviceMode)) { - if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n)) - bufferSizes.addUsingDefaultSort (n); + auto size = minBufferSize; + + while (size < lowLatencyMaxBufferSize) + { + size += lowLatencyBufferSizeMultiple; - n += (n < 512) ? 32 : (n < 1024 ? 64 : 128); + if (! bufferSizes.contains (size)) + bufferSizes.addUsingDefaultSort (size); + } + } + else + { + int n = 64; + for (int i = 0; i < 40; ++i) + { + if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n)) + bufferSizes.addUsingDefaultSort (n); + + n += (n < 512) ? 32 : (n < 1024 ? 64 : 128); + } } return true; @@ -1131,7 +1331,7 @@ class WASAPIAudioIODevice : public AudioIODevice, return lastError; } - if (useExclusiveMode) + if (isExclusiveMode (deviceMode)) { // This is to make sure that the callback uses actualBufferSize in case of exclusive mode if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize) @@ -1297,7 +1497,7 @@ class WASAPIAudioIODevice : public AudioIODevice, } else { - if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) + if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) inputDevice->handleDeviceBuffer(); } @@ -1347,9 +1547,10 @@ class WASAPIAudioIODevice : public AudioIODevice, // Device stats... std::unique_ptr inputDevice; std::unique_ptr outputDevice; - const bool useExclusiveMode; + WASAPIDeviceMode deviceMode; double defaultSampleRate = 0; int minBufferSize = 0, defaultBufferSize = 0; + int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0; int latencyIn = 0, latencyOut = 0; Array sampleRates; Array bufferSizes; @@ -1399,9 +1600,9 @@ class WASAPIAudioIODevice : public AudioIODevice, auto flow = getDataFlow (device); if (deviceId == inputDeviceId && flow == eCapture) - inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode)); + inputDevice.reset (new WASAPIInputDevice (device, deviceMode)); else if (deviceId == outputDeviceId && flow == eRender) - outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode)); + outputDevice.reset (new WASAPIOutputDevice (device, deviceMode)); } return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) @@ -1458,10 +1659,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, private DeviceChangeDetector { public: - WASAPIAudioIODeviceType (bool exclusive) - : AudioIODeviceType (exclusive ? "Windows Audio (Exclusive Mode)" : "Windows Audio"), + WASAPIAudioIODeviceType (WASAPIDeviceMode mode) + : AudioIODeviceType (getDeviceTypename (mode)), DeviceChangeDetector (L"Windows Audio"), - exclusiveMode (exclusive) + deviceMode (mode) { } @@ -1529,7 +1730,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, getTypeName(), outputDeviceIds [outputIndex], inputDeviceIds [inputIndex], - exclusiveMode)); + deviceMode)); if (! device->initialise()) device = nullptr; @@ -1543,7 +1744,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, StringArray inputDeviceNames, inputDeviceIds; private: - const bool exclusiveMode; + WASAPIDeviceMode deviceMode; bool hasScanned = false; ComSmartPtr enumerator; @@ -1698,6 +1899,17 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, callDeviceChangeListeners(); } + //============================================================================== + static String getDeviceTypename (WASAPIDeviceMode mode) + { + if (mode == WASAPIDeviceMode::shared) return "Windows Audio"; + if (mode == WASAPIDeviceMode::sharedLowLatency) return "Windows Audio (Low Latency Mode)"; + if (mode == WASAPIDeviceMode::exclusive) return "Windows Audio (Exclusive Mode)"; + + jassertfalse; + return {}; + } + //============================================================================== JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType) @@ -1756,19 +1968,6 @@ struct MMDeviceMasterVolume } -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode) -{ - #if ! JUCE_WASAPI_EXCLUSIVE - if (exclusiveMode) - return nullptr; - #endif - - return SystemStats::getOperatingSystemType() >= SystemStats::WinVista - ? new WasapiClasses::WASAPIAudioIODeviceType (exclusiveMode) - : nullptr; -} - //============================================================================== #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); } diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/oboe/src/common/Trace.h b/JuceLibraryCode/modules/juce_audio_devices/native/oboe/src/common/Trace.h index c7965f9..dad6c00 100644 --- a/JuceLibraryCode/modules/juce_audio_devices/native/oboe/src/common/Trace.h +++ b/JuceLibraryCode/modules/juce_audio_devices/native/oboe/src/common/Trace.h @@ -28,4 +28,4 @@ class Trace { static bool mIsTracingSupported; }; -#endif //OBOE_TRACE_H \ No newline at end of file +#endif //OBOE_TRACE_H diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.cpp index 6f19a0b..727b10b 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.cpp +++ b/JuceLibraryCode/modules/juce_core/containers/juce_ArrayBase.cpp @@ -87,14 +87,14 @@ namespace ArrayBaseTestsHelpers }; } -bool operator== (const ArrayBaseTestsHelpers::TriviallyCopyableType& tct, - const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct) +static bool operator== (const ArrayBaseTestsHelpers::TriviallyCopyableType& tct, + const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct) { return tct.getValue() == ntct.getValue(); } -bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, - const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) +static bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, + const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) { return tct == ntct; } diff --git a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp index 129540f..cc971e7 100644 --- a/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp +++ b/JuceLibraryCode/modules/juce_core/containers/juce_Variant.cpp @@ -257,8 +257,8 @@ class var::VariantType_String : public var::VariantType } private: - static const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } - static String* getString (ValueUnion& data) noexcept { return reinterpret_cast (data.stringValue); } + static const String* getString (const ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } + static String* getString (ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } }; //============================================================================== diff --git a/JuceLibraryCode/modules/juce_core/juce_core.h b/JuceLibraryCode/modules/juce_core/juce_core.h index ed6ca99..5f59a20 100644 --- a/JuceLibraryCode/modules/juce_core/juce_core.h +++ b/JuceLibraryCode/modules/juce_core/juce_core.h @@ -32,14 +32,14 @@ ID: juce_core vendor: juce - version: 6.0.1 + version: 6.0.5 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 license: ISC dependencies: - OSXFrameworks: Cocoa IOKit + OSXFrameworks: Cocoa Foundation IOKit iOSFrameworks: Foundation linuxLibs: rt dl pthread mingwLibs: uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm diff --git a/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h b/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h index 3b22f62..3d40119 100644 --- a/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h +++ b/JuceLibraryCode/modules/juce_core/memory/juce_Memory.h @@ -39,13 +39,6 @@ inline void zerostruct (Type& structure) noexcept { memset ((v template inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } -/** A handy function which adds a number of bytes to any type of pointer and returns the result. - This can be useful to avoid casting pointers to a char* and back when you want to move them by - a specific number of bytes, -*/ -template -inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept { return reinterpret_cast (const_cast (reinterpret_cast (basePointer)) + bytes); } - /** A handy function to round up a pointer to the nearest multiple of a given number of bytes. alignmentBytes must be a power of two. */ template @@ -83,6 +76,53 @@ inline void writeUnaligned (void* dstPtr, Type value) noexcept memcpy (dstPtr, &value, sizeof (Type)); } +//============================================================================== +/** Casts a pointer to another type via `void*`, which suppresses the cast-align + warning which sometimes arises when casting pointers to types with different + alignment. + You should only use this when you know for a fact that the input pointer points + to a region that has suitable alignment for `Type`, e.g. regions returned from + malloc/calloc that should be suitable for any non-over-aligned type. +*/ +template ::value, int>::type = 0> +inline Type unalignedPointerCast (void* ptr) noexcept +{ + return reinterpret_cast (ptr); +} + +/** Casts a pointer to another type via `void*`, which suppresses the cast-align + warning which sometimes arises when casting pointers to types with different + alignment. + You should only use this when you know for a fact that the input pointer points + to a region that has suitable alignment for `Type`, e.g. regions returned from + malloc/calloc that should be suitable for any non-over-aligned type. +*/ +template ::value, int>::type = 0> +inline Type unalignedPointerCast (const void* ptr) noexcept +{ + return reinterpret_cast (ptr); +} + +/** A handy function which adds a number of bytes to any type of pointer and returns the result. + This can be useful to avoid casting pointers to a char* and back when you want to move them by + a specific number of bytes, +*/ +template +inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept +{ + return unalignedPointerCast (reinterpret_cast (basePointer) + bytes); +} + +/** A handy function which adds a number of bytes to any type of pointer and returns the result. + This can be useful to avoid casting pointers to a char* and back when you want to move them by + a specific number of bytes, +*/ +template +inline const Type* addBytesToPointer (const Type* basePointer, IntegerType bytes) noexcept +{ + return unalignedPointerCast (reinterpret_cast (basePointer) + bytes); +} + //============================================================================== #if JUCE_MAC || JUCE_IOS || DOXYGEN @@ -143,4 +183,18 @@ inline void writeUnaligned (void* dstPtr, Type value) noexcept #define juce_UseDebuggingNewOperator #endif + /** Converts an owning raw pointer into a unique_ptr, deriving the + type of the unique_ptr automatically. + + This should only be used with pointers to single objects. + Do NOT pass a pointer to an array to this function, as the + destructor of the unique_ptr will incorrectly call `delete` + instead of `delete[]` on the pointer. + */ + template + std::unique_ptr rawToUniquePtr (T* ptr) + { + return std::unique_ptr (ptr); + } + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/misc/juce_ConsoleApplication.cpp b/JuceLibraryCode/modules/juce_core/misc/juce_ConsoleApplication.cpp index febe9b8..c212e33 100644 --- a/JuceLibraryCode/modules/juce_core/misc/juce_ConsoleApplication.cpp +++ b/JuceLibraryCode/modules/juce_core/misc/juce_ConsoleApplication.cpp @@ -141,7 +141,7 @@ ArgumentList::ArgumentList (String exeName, StringArray args) args.removeEmptyStrings(); for (auto& a : args) - arguments.add ({ a }); + arguments.add ({ a.unquoted() }); } ArgumentList::ArgumentList (int argc, char* argv[]) @@ -168,7 +168,7 @@ int ArgumentList::indexOfOption (StringRef option) const jassert (option == String (option).trim()); // passing non-trimmed strings will always fail to find a match! for (int i = 0; i < arguments.size(); ++i) - if (arguments.getReference(i) == option) + if (arguments.getReference (i) == option) return i; return -1; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h index b919d10..f489d56 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -132,7 +132,7 @@ #define STRICT 1 #define WIN32_LEAN_AND_MEAN 1 #if JUCE_MINGW - #define _WIN32_WINNT 0x0501 + #define _WIN32_WINNT 0x0600 #else #define _WIN32_WINNT 0x0602 #endif diff --git a/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp index 8aec477..51521de 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_android_RuntimePermissions.cpp @@ -28,11 +28,11 @@ static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionI { switch (permission) { - case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO"; - case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_COARSE_LOCATION"; - case RuntimePermissions::readExternalStorage: return "android.permission.READ_EXTERNAL_STORAGE"; - case RuntimePermissions::writeExternalStorage: return "android.permission.WRITE_EXTERNAL_STORAGE"; - case RuntimePermissions::camera: return "android.permission.CAMERA"; + case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO"; + case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_FINE_LOCATION"; + case RuntimePermissions::readExternalStorage: return "android.permission.READ_EXTERNAL_STORAGE"; + case RuntimePermissions::writeExternalStorage: return "android.permission.WRITE_EXTERNAL_STORAGE"; + case RuntimePermissions::camera: return "android.permission.CAMERA"; } // invalid permission @@ -42,11 +42,11 @@ static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionI static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission) { - if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio; - else if (permission == "android.permission.ACCESS_COARSE_LOCATION") return RuntimePermissions::bluetoothMidi; - else if (permission == "android.permission.READ_EXTERNAL_STORAGE") return RuntimePermissions::readExternalStorage; - else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE") return RuntimePermissions::writeExternalStorage; - else if (permission == "android.permission.CAMERA") return RuntimePermissions::camera; + if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio; + else if (permission == "android.permission.ACCESS_FINE_LOCATION") return RuntimePermissions::bluetoothMidi; + else if (permission == "android.permission.READ_EXTERNAL_STORAGE") return RuntimePermissions::readExternalStorage; + else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE") return RuntimePermissions::writeExternalStorage; + else if (permission == "android.permission.CAMERA") return RuntimePermissions::camera; return static_cast (-1); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp index 5860c58..9599ec3 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -215,8 +215,8 @@ double Time::getMillisecondCounterHiRes() noexcept bool Time::setSystemTimeToThisTime() const { timeval t; - t.tv_sec = millisSinceEpoch / 1000; - t.tv_usec = (millisSinceEpoch - t.tv_sec * 1000) * 1000; + t.tv_sec = decltype (timeval::tv_sec) (millisSinceEpoch / 1000); + t.tv_usec = decltype (timeval::tv_usec) ((millisSinceEpoch - t.tv_sec * 1000) * 1000); return settimeofday (&t, nullptr) == 0; } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm index 965dfda..91f483d 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm @@ -425,13 +425,23 @@ bool next (String& filenameFound, StringArray params; params.addTokens (parameters, true); - NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; - NSMutableArray* paramArray = [[NSMutableArray new] autorelease]; for (int i = 0; i < params.size(); ++i) [paramArray addObject: juceStringToNS (params[i])]; + #if (defined MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 + auto config = [NSWorkspaceOpenConfiguration configuration]; + [config setCreatesNewApplicationInstance: YES]; + config.arguments = paramArray; + + [workspace openApplicationAtURL: filenameAsURL + configuration: config + completionHandler: nil]; + return true; + #else + NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; + [dict setObject: paramArray forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")]; @@ -439,6 +449,7 @@ bool next (String& filenameFound, options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance configuration: dict error: nil]; + #endif } if (file.exists()) diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm index 750be11..4776905 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_SystemStats.mm @@ -121,10 +121,21 @@ static String getOSXVersion() { JUCE_AUTORELEASEPOOL { - NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: - nsStringLiteral ("/System/Library/CoreServices/SystemVersion.plist")]; + const String systemVersionPlist ("/System/Library/CoreServices/SystemVersion.plist"); - return nsStringToJuce ([dict objectForKey: nsStringLiteral ("ProductVersion")]); + #if (defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) + NSError* error = nullptr; + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL: createNSURLFromFile (systemVersionPlist) + error: &error]; + #else + NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (systemVersionPlist)]; + #endif + + if (dict != nullptr) + return nsStringToJuce ([dict objectForKey: nsStringLiteral ("ProductVersion")]); + + jassertfalse; + return {}; } } #endif @@ -137,11 +148,17 @@ static String getOSXVersion() StringArray parts; parts.addTokens (getOSXVersion(), ".", StringRef()); - jassert (parts[0].getIntValue() == 10); - const int major = parts[1].getIntValue(); - jassert (major > 2); + const auto major = parts[0].getIntValue(); + const auto minor = parts[1].getIntValue(); - return (OperatingSystemType) (major + MacOSX_10_4 - 4); + if (major == 10) + { + jassert (minor > 2); + return (OperatingSystemType) (minor + MacOSX_10_7 - 7); + } + + jassert (major == 11); + return MacOS_11; #endif } @@ -199,10 +216,8 @@ static String getOSXVersion() { #if JUCE_IOS return false; - #elif JUCE_64BIT - return true; #else - return getOperatingSystemType() >= MacOSX_10_6; + return true; #endif } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h b/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h index b651e11..b17a1cb 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -192,28 +192,40 @@ NSRect makeNSRect (const RectangleType& r) noexcept static_cast (r.getHeight())); } #endif -#if JUCE_MAC || JUCE_IOS -// This is necessary as on iOS builds, some arguments may be passed on registers -// depending on the argument type. The re-cast objc_msgSendSuper to a function -// take the same arguments as the target method. -template -ReturnValue ObjCMsgSendSuper (struct objc_super* s, SEL sel, Params... params) -{ - using SuperFn = ReturnValue (*)(struct objc_super*, SEL, Params...); - SuperFn fn = reinterpret_cast (objc_msgSendSuper); - return fn (s, sel, params...); -} +#if JUCE_INTEL + template + struct NeedsStret + { + #if JUCE_32BIT + static constexpr auto value = sizeof (T) > 8; + #else + static constexpr auto value = sizeof (T) > 16; + #endif + }; + + template<> + struct NeedsStret { static constexpr auto value = false; }; + + template ::value> + struct MetaSuperFn { static constexpr auto value = objc_msgSendSuper_stret; }; + + template + struct MetaSuperFn { static constexpr auto value = objc_msgSendSuper; }; +#else + template + struct MetaSuperFn { static constexpr auto value = objc_msgSendSuper; }; +#endif -// These hacks are a workaround for newer Xcode builds which by default prevent calls to these objc functions.. -typedef id (*MsgSendSuperFn) (struct objc_super*, SEL, ...); -inline MsgSendSuperFn getMsgSendSuperFn() noexcept { return (MsgSendSuperFn) (void*) objc_msgSendSuper; } +template +static ReturnType ObjCMsgSendSuper (id self, SEL sel, Params... params) +{ + using SuperFn = ReturnType (*) (struct objc_super*, SEL, Params...); + const auto fn = reinterpret_cast (MetaSuperFn::value); -#if ! JUCE_IOS -typedef double (*MsgSendFPRetFn) (id, SEL op, ...); -inline MsgSendFPRetFn getMsgSendFPRetFn() noexcept { return (MsgSendFPRetFn) (void*) objc_msgSend_fpret; } -#endif -#endif + objc_super s = { self, [SuperType class] }; + return fn (&s, sel, params...); +} //============================================================================== struct NSObjectDeleter @@ -235,7 +247,10 @@ struct ObjCClass ~ObjCClass() { - objc_disposeClassPair (cls); + auto kvoSubclassName = String ("NSKVONotifying_") + class_getName (cls); + + if (objc_getClass (kvoSubclassName.toUTF8()) == nullptr) + objc_disposeClassPair (cls); } void registerClass() @@ -286,13 +301,11 @@ struct ObjCClass jassert (b); ignoreUnused (b); } - #if JUCE_MAC || JUCE_IOS - static id sendSuperclassMessage (id self, SEL selector) + template + static ReturnType sendSuperclassMessage (id self, SEL sel, Params... params) { - objc_super s = { self, [SuperclassType class] }; - return getMsgSendSuperFn() (&s, selector); + return ObjCMsgSendSuper (self, sel, params...); } - #endif template static Type getIvar (id self, const char* name) @@ -329,18 +342,14 @@ struct ObjCLifetimeManagedClass : public ObjCClass addMethod (@selector (dealloc), dealloc, "v@:"); - registerClass(); } static id initWithJuceObject (id _self, SEL, JuceClass* obj) { - NSObject* self = _self; - - objc_super s = { self, [NSObject class] }; - self = ObjCMsgSendSuper (&s, @selector(init)); - + NSObject* self = sendSuperclassMessage (_self, @selector (init)); object_setInstanceVariable (self, "cppObject", obj); + return self; } @@ -352,11 +361,9 @@ struct ObjCLifetimeManagedClass : public ObjCClass object_setInstanceVariable (_self, "cppObject", nullptr); } - objc_super s = { _self, [NSObject class] }; - ObjCMsgSendSuper (&s, @selector(dealloc)); + sendSuperclassMessage (_self, @selector (dealloc)); } - static ObjCLifetimeManagedClass objCLifetimeManagedClass; }; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h b/JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h index f87bf35..822d83c 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_posix_IPAddress.h @@ -71,8 +71,8 @@ namespace { if (ifa->ifa_addr->sa_family == AF_INET) { - auto interfaceAddressInfo = reinterpret_cast (ifa->ifa_addr); - auto broadcastAddressInfo = reinterpret_cast (ifa->ifa_dstaddr); + auto interfaceAddressInfo = unalignedPointerCast (ifa->ifa_addr); + auto broadcastAddressInfo = unalignedPointerCast (ifa->ifa_dstaddr); if (interfaceAddressInfo->sin_addr.s_addr != INADDR_NONE) { @@ -83,8 +83,8 @@ namespace } else if (ifa->ifa_addr->sa_family == AF_INET6) { - interfaceInfo.interfaceAddress = makeAddress (reinterpret_cast (ifa->ifa_addr)); - interfaceInfo.broadcastAddress = makeAddress (reinterpret_cast (ifa->ifa_dstaddr)); + interfaceInfo.interfaceAddress = makeAddress (unalignedPointerCast (ifa->ifa_addr)); + interfaceInfo.broadcastAddress = makeAddress (unalignedPointerCast (ifa->ifa_dstaddr)); return true; } } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp b/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp index 98e6132..0a3ad85 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_posix_NamedPipe.cpp @@ -154,7 +154,7 @@ class NamedPipe::Pimpl bool openPipe (bool isInput, uint32 timeoutEnd) { auto& pipe = isInput ? pipeIn : pipeOut; - int flags = isInput ? O_RDWR | O_NONBLOCK : O_WRONLY; + int flags = (isInput ? O_RDWR : O_WRONLY) | O_NONBLOCK; const String& pipeName = isInput ? (createdPipe ? pipeInName : pipeOutName) : (createdPipe ? pipeOutName : pipeInName); @@ -181,14 +181,20 @@ class NamedPipe::Pimpl void NamedPipe::close() { - if (pimpl != nullptr) { - pimpl->stopReadOperation = true; + ScopedReadLock sl (lock); - char buffer[1] = { 0 }; - ssize_t done = ::write (pimpl->pipeIn, buffer, 1); - ignoreUnused (done); + if (pimpl != nullptr) + { + pimpl->stopReadOperation = true; + char buffer[1] = { 0 }; + ssize_t done = ::write (pimpl->pipeIn, buffer, 1); + ignoreUnused (done); + } + } + + { ScopedWriteLock sl (lock); pimpl.reset(); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h b/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h index 55bee8f..e571e9f 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -134,6 +134,17 @@ class ComSmartPtr return this->QueryInterface (__uuidof (OtherComClass), destObject); } + template + ComSmartPtr getInterface() const + { + ComSmartPtr destObject; + + if (QueryInterface (destObject) == S_OK) + return destObject; + + return nullptr; + } + private: ComClass* p = nullptr; diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp index 62b609c..002e2a0 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Files.cpp @@ -975,7 +975,7 @@ class NamedPipe::Pimpl Pimpl (const String& pipeName, const bool createPipe, bool mustNotExist) : filename ("\\\\.\\pipe\\" + File::createLegalFileName (pipeName)), pipeH (INVALID_HANDLE_VALUE), - cancelEvent (CreateEvent (nullptr, FALSE, FALSE, nullptr)), + cancelEvent (CreateEvent (nullptr, TRUE, FALSE, nullptr)), connected (false), ownsPipe (createPipe), shouldStop (false) { if (createPipe) @@ -1071,14 +1071,12 @@ class NamedPipe::Pimpl return 0; OverlappedEvent over; - unsigned long numRead; + unsigned long numRead = 0; if (ReadFile (pipeH, destBuffer, (DWORD) maxBytesToRead, &numRead, &over.over)) return (int) numRead; - const DWORD lastError = GetLastError(); - - if (lastError == ERROR_IO_PENDING) + if (GetLastError() == ERROR_IO_PENDING) { if (! waitForIO (over, timeOutMilliseconds)) return -1; @@ -1087,7 +1085,9 @@ class NamedPipe::Pimpl return (int) numRead; } - if (ownsPipe && (GetLastError() == ERROR_BROKEN_PIPE || GetLastError() == ERROR_PIPE_NOT_CONNECTED)) + const auto lastError = GetLastError(); + + if (ownsPipe && (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_PIPE_NOT_CONNECTED)) disconnectPipe(); else break; @@ -1127,7 +1127,8 @@ class NamedPipe::Pimpl const String filename; HANDLE pipeH, cancelEvent; - bool connected, ownsPipe, shouldStop; + bool connected, ownsPipe; + std::atomic shouldStop; CriticalSection createFileLock; private: @@ -1150,10 +1151,13 @@ class NamedPipe::Pimpl bool waitForIO (OverlappedEvent& over, int timeOutMilliseconds) { if (shouldStop) + { + CancelIo (pipeH); return false; + } HANDLE handles[] = { over.over.hEvent, cancelEvent }; - DWORD waitResult = WaitForMultipleObjects (2, handles, FALSE, + DWORD waitResult = WaitForMultipleObjects (numElementsInArray (handles), handles, FALSE, timeOutMilliseconds >= 0 ? (DWORD) timeOutMilliseconds : INFINITE); @@ -1169,11 +1173,17 @@ class NamedPipe::Pimpl void NamedPipe::close() { - if (pimpl != nullptr) { - pimpl->shouldStop = true; - SetEvent (pimpl->cancelEvent); + ScopedReadLock sl (lock); + if (pimpl != nullptr) + { + pimpl->shouldStop = true; + SetEvent (pimpl->cancelEvent); + } + } + + { ScopedWriteLock sl (lock); pimpl.reset(); } @@ -1181,22 +1191,19 @@ void NamedPipe::close() bool NamedPipe::openInternal (const String& pipeName, const bool createPipe, bool mustNotExist) { - pimpl.reset (new Pimpl (pipeName, createPipe, mustNotExist)); + auto newPimpl = std::make_unique (pipeName, createPipe, mustNotExist); if (createPipe) { - if (pimpl->pipeH == INVALID_HANDLE_VALUE) - { - pimpl.reset(); + if (newPimpl->pipeH == INVALID_HANDLE_VALUE) return false; - } } - else if (! pimpl->connect (200)) + else if (! newPimpl->connect (200)) { - pimpl.reset(); return false; } + pimpl = std::move (newPimpl); return true; } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp index 5e10133..13bf475 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -283,9 +283,6 @@ String SystemStats::getOperatingSystemName() case Android: JUCE_FALLTHROUGH case iOS: JUCE_FALLTHROUGH - case MacOSX_10_4: JUCE_FALLTHROUGH - case MacOSX_10_5: JUCE_FALLTHROUGH - case MacOSX_10_6: JUCE_FALLTHROUGH case MacOSX_10_7: JUCE_FALLTHROUGH case MacOSX_10_8: JUCE_FALLTHROUGH case MacOSX_10_9: JUCE_FALLTHROUGH diff --git a/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp b/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp index 75bcee7..f0136ea 100644 --- a/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp +++ b/JuceLibraryCode/modules/juce_core/native/juce_win32_Threads.cpp @@ -448,7 +448,7 @@ class ChildProcess::ActiveProcess if (! isRunning()) break; - Thread::yield(); + Thread::sleep (1); } else { diff --git a/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp index 98ea28e..740ae6e 100644 --- a/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp +++ b/JuceLibraryCode/modules/juce_core/network/juce_NamedPipe.cpp @@ -41,6 +41,7 @@ bool NamedPipe::openExisting (const String& pipeName) bool NamedPipe::isOpen() const { + ScopedReadLock sl (lock); return pimpl != nullptr; } @@ -55,6 +56,7 @@ bool NamedPipe::createNewPipe (const String& pipeName, bool mustNotExist) String NamedPipe::getName() const { + ScopedReadLock sl (lock); return currentPipeName; } @@ -75,7 +77,7 @@ class NamedPipeTests : public UnitTest void runTest() override { - const String pipeName ("TestPipe"); + const auto pipeName = "TestPipe" + String ((intptr_t) Thread::getCurrentThreadId()); beginTest ("Pre test cleanup"); { @@ -208,11 +210,6 @@ class NamedPipeTests : public UnitTest pipe.openExisting (pipeName); } - ~NamedPipeThread() - { - stopThread (100); - } - NamedPipe pipe; const String& pipeName; WaitableEvent& workCompleted; @@ -229,6 +226,11 @@ class NamedPipeTests : public UnitTest sendData (sData) {} + ~SenderThread() override + { + stopThread (100); + } + void run() override { result = pipe.write (&sendData, sizeof (sendData), 2000); @@ -246,6 +248,11 @@ class NamedPipeTests : public UnitTest : NamedPipeThread ("NamePipeSender", pName, shouldCreatePipe, completed) {} + ~ReceiverThread() override + { + stopThread (100); + } + void run() override { result = pipe.read (&recvData, sizeof (recvData), 2000); diff --git a/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h b/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h index ffa74f8..59b2df4 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_PlatformDefs.h @@ -66,12 +66,14 @@ namespace juce @see jassert() */ #define JUCE_BREAK_IN_DEBUGGER { ::kill (0, SIGTRAP); } +#elif JUCE_MAC && JUCE_CLANG && JUCE_ARM + #define JUCE_BREAK_IN_DEBUGGER { __builtin_debugtrap(); } #elif JUCE_MSVC #ifndef __INTEL_COMPILER #pragma intrinsic (__debugbreak) #endif #define JUCE_BREAK_IN_DEBUGGER { __debugbreak(); } -#elif JUCE_GCC || JUCE_MAC +#elif JUCE_INTEL && (JUCE_GCC || JUCE_MAC) #if JUCE_NO_INLINE_ASM #define JUCE_BREAK_IN_DEBUGGER { } #else diff --git a/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h b/JuceLibraryCode/modules/juce_core/system/juce_StandardHeader.h index 6602828..d2711bc 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 6 #define JUCE_MINOR_VERSION 0 -#define JUCE_BUILDNUMBER 1 +#define JUCE_BUILDNUMBER 5 /** Current JUCE version number. @@ -44,6 +44,7 @@ //============================================================================== #include +#include #include #include #include @@ -110,7 +111,6 @@ JUCE_END_IGNORE_WARNINGS_MSVC #if JUCE_ANDROID #include - #include #include #endif diff --git a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h index 1ca327a..68c91d1 100644 --- a/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h +++ b/JuceLibraryCode/modules/juce_core/system/juce_SystemStats.h @@ -52,9 +52,6 @@ class JUCE_API SystemStats final Android = 0x0800, iOS = 0x1000, - MacOSX_10_4 = MacOSX | 4, - MacOSX_10_5 = MacOSX | 5, - MacOSX_10_6 = MacOSX | 6, MacOSX_10_7 = MacOSX | 7, MacOSX_10_8 = MacOSX | 8, MacOSX_10_9 = MacOSX | 9, @@ -63,6 +60,8 @@ class JUCE_API SystemStats final MacOSX_10_12 = MacOSX | 12, MacOSX_10_13 = MacOSX | 13, MacOSX_10_14 = MacOSX | 14, + MacOSX_10_15 = MacOSX | 15, + MacOS_11 = MacOSX | 16, Win2000 = Windows | 1, WinXP = Windows | 2, diff --git a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp index e383bad..f7f9066 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_String.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_String.cpp @@ -68,7 +68,7 @@ class StringHolder static CharPointerType createUninitialisedBytes (size_t numBytes) { numBytes = (numBytes + 3) & ~(size_t) 3; - auto s = reinterpret_cast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); + auto s = unalignedPointerCast (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); s->refCount.value = 0; s->allocatedNumBytes = numBytes; return CharPointerType (s->text); @@ -210,7 +210,7 @@ class StringHolder static StringHolder* bufferFromText (const CharPointerType text) noexcept { // (Can't use offsetof() here because of warnings about this not being a POD) - return reinterpret_cast (reinterpret_cast (text.getAddress()) + return unalignedPointerCast (reinterpret_cast (text.getAddress()) - (reinterpret_cast (reinterpret_cast (128)->text) - 128)); } @@ -1991,7 +1991,7 @@ String String::createStringFromData (const void* const unknownData, int size) StringCreationHelper builder ((size_t) numChars); - auto src = reinterpret_cast (data + 2); + auto src = unalignedPointerCast (data + 2); if (CharPointer_UTF16::isByteOrderMarkBigEndian (data)) { @@ -2061,19 +2061,19 @@ struct StringEncodingConverter template <> struct StringEncodingConverter { - static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 (reinterpret_cast (source.getCharPointer().getAddress())); } + static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 (unalignedPointerCast (source.getCharPointer().getAddress())); } }; template <> struct StringEncodingConverter { - static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 (reinterpret_cast (source.getCharPointer().getAddress())); } + static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 (unalignedPointerCast (source.getCharPointer().getAddress())); } }; template <> struct StringEncodingConverter { - static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 (reinterpret_cast (source.getCharPointer().getAddress())); } + static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 (unalignedPointerCast (source.getCharPointer().getAddress())); } }; CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter::convert (*this); } diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp index 7c8785a..54372e4 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.cpp @@ -132,6 +132,11 @@ String& StringArray::getReference (int index) noexcept return strings.getReference (index); } +const String& StringArray::getReference (int index) const noexcept +{ + return strings.getReference (index); +} + void StringArray::add (String newString) { // NB: the local temp copy is to avoid a dangling pointer if the diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h index 2487433..0b2cf66 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringArray.h @@ -152,6 +152,12 @@ class JUCE_API StringArray */ String& getReference (int index) noexcept; + /** Returns a reference to one of the strings in the array. + This lets you modify a string in-place in the array, but you must be sure that + the index is in-range. + */ + const String& getReference (int index) const noexcept; + /** Returns a pointer to the first String in the array. This method is provided for compatibility with standard C++ iteration mechanisms. */ diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp index 4290ee2..1a0ba55 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.cpp @@ -145,6 +145,11 @@ void StringPairArray::setIgnoresCase (bool shouldIgnoreCase) ignoreCase = shouldIgnoreCase; } +bool StringPairArray::getIgnoresCase() const noexcept +{ + return ignoreCase; +} + String StringPairArray::getDescription() const { String s; @@ -166,4 +171,141 @@ void StringPairArray::minimiseStorageOverheads() values.minimiseStorageOverheads(); } +void StringPairArray::addMap (const std::map& toAdd) +{ + // If we just called `set` for each item in `toAdd`, that would + // perform badly when adding to large StringPairArrays, as `set` + // has to loop through the whole container looking for matching keys. + // Instead, we use a temporary map to give us better lookup performance. + std::map contents; + + const auto normaliseKey = [this] (const String& key) + { + return ignoreCase ? key.toLowerCase() : key; + }; + + for (auto i = 0; i != size(); ++i) + contents.emplace (normaliseKey (getAllKeys().getReference (i)), i); + + for (const auto& pair : toAdd) + { + const auto key = normaliseKey (pair.first); + const auto it = contents.find (key); + + if (it != contents.cend()) + { + values.getReference (it->second) = pair.second; + } + else + { + contents.emplace (key, static_cast (contents.size())); + keys.add (pair.first); + values.add (pair.second); + } + } +} + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +static String operator""_S (const char* chars, size_t) +{ + return String { chars }; +} + +class StringPairArrayTests : public UnitTest +{ +public: + StringPairArrayTests() + : UnitTest ("StringPairArray", UnitTestCategories::text) + {} + + void runTest() override + { + beginTest ("addMap respects case sensitivity of StringPairArray"); + { + StringPairArray insensitive { true }; + insensitive.addMap ({ { "duplicate", "a" }, + { "Duplicate", "b" } }); + + expect (insensitive.size() == 1); + expectEquals (insensitive["DUPLICATE"], "a"_S); + + StringPairArray sensitive { false }; + sensitive.addMap ({ { "duplicate", "a"_S }, + { "Duplicate", "b"_S } }); + + expect (sensitive.size() == 2); + expectEquals (sensitive["duplicate"], "a"_S); + expectEquals (sensitive["Duplicate"], "b"_S); + expectEquals (sensitive["DUPLICATE"], ""_S); + } + + beginTest ("addMap overwrites existing pairs"); + { + StringPairArray insensitive { true }; + insensitive.set ("key", "value"); + insensitive.addMap ({ { "KEY", "VALUE" } }); + + expect (insensitive.size() == 1); + expectEquals (insensitive.getAllKeys()[0], "key"_S); + expectEquals (insensitive.getAllValues()[0], "VALUE"_S); + + StringPairArray sensitive { false }; + sensitive.set ("key", "value"); + sensitive.addMap ({ { "KEY", "VALUE" }, + { "key", "another value" } }); + + expect (sensitive.size() == 2); + expect (sensitive.getAllKeys() == StringArray { "key", "KEY" }); + expect (sensitive.getAllValues() == StringArray { "another value", "VALUE" }); + } + + beginTest ("addMap doesn't change the order of existing keys"); + { + StringPairArray array; + array.set ("a", "a"); + array.set ("z", "z"); + array.set ("b", "b"); + array.set ("y", "y"); + array.set ("c", "c"); + + array.addMap ({ { "B", "B" }, + { "0", "0" }, + { "Z", "Z" } }); + + expect (array.getAllKeys() == StringArray { "a", "z", "b", "y", "c", "0" }); + expect (array.getAllValues() == StringArray { "a", "Z", "B", "y", "c", "0" }); + } + + beginTest ("addMap has equivalent behaviour to addArray"); + { + StringPairArray initial; + initial.set ("aaa", "aaa"); + initial.set ("zzz", "zzz"); + initial.set ("bbb", "bbb"); + + auto withAddMap = initial; + withAddMap.addMap ({ { "ZZZ", "ZZZ" }, + { "ddd", "ddd" } }); + + auto withAddArray = initial; + withAddArray.addArray ([] + { + StringPairArray toAdd; + toAdd.set ("ZZZ", "ZZZ"); + toAdd.set ("ddd", "ddd"); + return toAdd; + }()); + + expect (withAddMap == withAddArray); + } + } +}; + +static StringPairArrayTests stringPairArrayTests; + +#endif + } // namespace juce diff --git a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h index e99db6b..90c71f2 100644 --- a/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h +++ b/JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h @@ -124,6 +124,10 @@ class JUCE_API StringPairArray */ void setIgnoresCase (bool shouldIgnoreCase); + /** Indicates whether a case-insensitive search is used when looking up a key string. + */ + bool getIgnoresCase() const noexcept; + //============================================================================== /** Returns a descriptive string containing the items. This is handy for dumping the contents of an array. @@ -139,6 +143,9 @@ class JUCE_API StringPairArray */ void minimiseStorageOverheads(); + //============================================================================== + /** Adds the contents of a map to this StringPairArray. */ + void addMap (const std::map& mapToAdd); private: //============================================================================== diff --git a/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h b/JuceLibraryCode/modules/juce_data_structures/juce_data_structures.h index 4266b34..ace0ee1 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: 6.0.1 + version: 6.0.5 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_events/interprocess/juce_InterprocessConnection.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp index b92f0ff..eb3989b 100644 --- a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.cpp @@ -32,19 +32,64 @@ struct InterprocessConnection::ConnectionThread : public Thread JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread) }; +class SafeActionImpl +{ +public: + explicit SafeActionImpl (InterprocessConnection& p) + : ref (p) {} + + template + void ifSafe (Fn&& fn) + { + const ScopedLock lock (mutex); + + if (safe) + fn (ref); + } + + void setSafe (bool s) + { + const ScopedLock lock (mutex); + safe = s; + } + + bool isSafe() + { + const ScopedLock lock (mutex); + return safe; + } + +private: + CriticalSection mutex; + InterprocessConnection& ref; + bool safe = false; +}; + +class InterprocessConnection::SafeAction : public SafeActionImpl +{ + using SafeActionImpl::SafeActionImpl; +}; + //============================================================================== InterprocessConnection::InterprocessConnection (bool callbacksOnMessageThread, uint32 magicMessageHeaderNumber) : useMessageThread (callbacksOnMessageThread), - magicMessageHeader (magicMessageHeaderNumber) + magicMessageHeader (magicMessageHeaderNumber), + safeAction (std::make_shared (*this)) { thread.reset (new ConnectionThread (*this)); } InterprocessConnection::~InterprocessConnection() { + // You *must* call `disconnect` in the destructor of your derived class to ensure + // that any pending messages are not delivered. If the messages were delivered after + // destroying the derived class, we'd end up calling the pure virtual implementations + // of `messageReceived`, `connectionMade` and `connectionLost` which is definitely + // not a good idea! + jassert (! safeAction->isSafe()); + callbackConnectionState = false; - disconnect(); - masterReference.clear(); + disconnect (4000, Notify::no); thread.reset(); } @@ -54,18 +99,15 @@ bool InterprocessConnection::connectToSocket (const String& hostName, { disconnect(); - const ScopedLock sl (pipeAndSocketLock); - socket.reset (new StreamingSocket()); + auto s = std::make_unique(); - if (socket->connect (hostName, portNumber, timeOutMillisecs)) + if (s->connect (hostName, portNumber, timeOutMillisecs)) { - threadIsRunning = true; - connectionMadeInt(); - thread->startThread(); + const ScopedWriteLock sl (pipeAndSocketLock); + initialiseWithSocket (std::move (s)); return true; } - socket.reset(); return false; } @@ -73,13 +115,13 @@ bool InterprocessConnection::connectToPipe (const String& pipeName, int timeoutM { disconnect(); - std::unique_ptr newPipe (new NamedPipe()); + auto newPipe = std::make_unique(); if (newPipe->openExisting (pipeName)) { - const ScopedLock sl (pipeAndSocketLock); + const ScopedWriteLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = timeoutMs; - initialiseWithPipe (newPipe.release()); + initialiseWithPipe (std::move (newPipe)); return true; } @@ -90,44 +132,49 @@ bool InterprocessConnection::createPipe (const String& pipeName, int timeoutMs, { disconnect(); - std::unique_ptr newPipe (new NamedPipe()); + auto newPipe = std::make_unique(); if (newPipe->createNewPipe (pipeName, mustNotExist)) { - const ScopedLock sl (pipeAndSocketLock); + const ScopedWriteLock sl (pipeAndSocketLock); pipeReceiveMessageTimeout = timeoutMs; - initialiseWithPipe (newPipe.release()); + initialiseWithPipe (std::move (newPipe)); return true; } return false; } -void InterprocessConnection::disconnect() +void InterprocessConnection::disconnect (int timeoutMs, Notify notify) { thread->signalThreadShouldExit(); { - const ScopedLock sl (pipeAndSocketLock); + const ScopedReadLock sl (pipeAndSocketLock); if (socket != nullptr) socket->close(); if (pipe != nullptr) pipe->close(); } - thread->stopThread (4000); + thread->stopThread (timeoutMs); deletePipeAndSocket(); - connectionLostInt(); + + if (notify == Notify::yes) + connectionLostInt(); + + callbackConnectionState = false; + safeAction->setSafe (false); } void InterprocessConnection::deletePipeAndSocket() { - const ScopedLock sl (pipeAndSocketLock); + const ScopedWriteLock sl (pipeAndSocketLock); socket.reset(); pipe.reset(); } bool InterprocessConnection::isConnected() const { - const ScopedLock sl (pipeAndSocketLock); + const ScopedReadLock sl (pipeAndSocketLock); return ((socket != nullptr && socket->isConnected()) || (pipe != nullptr && pipe->isOpen())) @@ -137,7 +184,7 @@ bool InterprocessConnection::isConnected() const String InterprocessConnection::getConnectedHostName() const { { - const ScopedLock sl (pipeAndSocketLock); + const ScopedReadLock sl (pipeAndSocketLock); if (pipe == nullptr && socket == nullptr) return {}; @@ -164,7 +211,7 @@ bool InterprocessConnection::sendMessage (const MemoryBlock& message) int InterprocessConnection::writeData (void* data, int dataSize) { - const ScopedLock sl (pipeAndSocketLock); + const ScopedReadLock sl (pipeAndSocketLock); if (socket != nullptr) return socket->write (data, dataSize); @@ -176,45 +223,47 @@ int InterprocessConnection::writeData (void* data, int dataSize) } //============================================================================== -void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) +void InterprocessConnection::initialise() { - jassert (socket == nullptr && pipe == nullptr); - socket.reset (newSocket); - + safeAction->setSafe (true); threadIsRunning = true; connectionMadeInt(); thread->startThread(); } -void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) +void InterprocessConnection::initialiseWithSocket (std::unique_ptr newSocket) { jassert (socket == nullptr && pipe == nullptr); - pipe.reset (newPipe); + socket = std::move (newSocket); + initialise(); +} - threadIsRunning = true; - connectionMadeInt(); - thread->startThread(); +void InterprocessConnection::initialiseWithPipe (std::unique_ptr newPipe) +{ + jassert (socket == nullptr && pipe == nullptr); + pipe = std::move (newPipe); + initialise(); } //============================================================================== struct ConnectionStateMessage : public MessageManager::MessageBase { - ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept - : owner (ipc), connectionMade (connected) + ConnectionStateMessage (std::shared_ptr ipc, bool connected) noexcept + : safeAction (ipc), connectionMade (connected) {} void messageCallback() override { - if (auto* ipc = owner.get()) + safeAction->ifSafe ([this] (InterprocessConnection& owner) { if (connectionMade) - ipc->connectionMade(); + owner.connectionMade(); else - ipc->connectionLost(); - } + owner.connectionLost(); + }); } - WeakReference owner; + std::shared_ptr safeAction; bool connectionMade; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage) @@ -227,7 +276,7 @@ void InterprocessConnection::connectionMadeInt() callbackConnectionState = true; if (useMessageThread) - (new ConnectionStateMessage (this, true))->post(); + (new ConnectionStateMessage (safeAction, true))->post(); else connectionMade(); } @@ -240,7 +289,7 @@ void InterprocessConnection::connectionLostInt() callbackConnectionState = false; if (useMessageThread) - (new ConnectionStateMessage (this, false))->post(); + (new ConnectionStateMessage (safeAction, false))->post(); else connectionLost(); } @@ -248,17 +297,19 @@ void InterprocessConnection::connectionLostInt() struct DataDeliveryMessage : public Message { - DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d) - : owner (ipc), data (d) + DataDeliveryMessage (std::shared_ptr ipc, const MemoryBlock& d) + : safeAction (ipc), data (d) {} void messageCallback() override { - if (auto* ipc = owner.get()) - ipc->messageReceived (data); + safeAction->ifSafe ([this] (InterprocessConnection& owner) + { + owner.messageReceived (data); + }); } - WeakReference owner; + std::shared_ptr safeAction; MemoryBlock data; }; @@ -267,7 +318,7 @@ void InterprocessConnection::deliverDataInt (const MemoryBlock& data) jassert (callbackConnectionState); if (useMessageThread) - (new DataDeliveryMessage (this, data))->post(); + (new DataDeliveryMessage (safeAction, data))->post(); else messageReceived (data); } @@ -275,6 +326,8 @@ void InterprocessConnection::deliverDataInt (const MemoryBlock& data) //============================================================================== int InterprocessConnection::readData (void* data, int num) { + const ScopedReadLock sl (pipeAndSocketLock); + if (socket != nullptr) return socket->read (data, num, true); diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h index 975139c..863562b 100644 --- a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnection.h @@ -43,6 +43,9 @@ class MemoryBlock; To act as a socket server and create connections for one or more client, see the InterprocessConnectionServer class. + IMPORTANT NOTE: Your derived Connection class *must* call `disconnect` in its destructor + in order to cancel any pending messages before the class is destroyed. + @see InterprocessConnectionServer, Socket, NamedPipe @tags{Events} @@ -117,8 +120,18 @@ class JUCE_API InterprocessConnection */ bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs, bool mustNotExist = false); - /** Disconnects and closes any currently-open sockets or pipes. */ - void disconnect(); + /** Whether the disconnect call should trigger callbacks. */ + enum class Notify { no, yes }; + + /** Disconnects and closes any currently-open sockets or pipes. + + Derived classes *must* call this in their destructors in order to avoid undefined + behaviour. + + @param timeoutMs the time in ms to wait before killing the thread by force + @param notify whether or not to call `connectionLost` + */ + void disconnect (int timeoutMs = -1, Notify notify = Notify::yes); /** True if a socket or pipe is currently active. */ bool isConnected() const; @@ -178,7 +191,7 @@ class JUCE_API InterprocessConnection private: //============================================================================== - CriticalSection pipeAndSocketLock; + ReadWriteLock pipeAndSocketLock; std::unique_ptr socket; std::unique_ptr pipe; bool callbackConnectionState = false; @@ -187,8 +200,9 @@ class JUCE_API InterprocessConnection int pipeReceiveMessageTimeout = -1; friend class InterprocessConnectionServer; - void initialiseWithSocket (StreamingSocket*); - void initialiseWithPipe (NamedPipe*); + void initialise(); + void initialiseWithSocket (std::unique_ptr); + void initialiseWithPipe (std::unique_ptr); void deletePipeAndSocket(); void connectionMadeInt(); void connectionLostInt(); @@ -200,10 +214,12 @@ class JUCE_API InterprocessConnection std::unique_ptr thread; std::atomic threadIsRunning { false }; + class SafeAction; + std::shared_ptr safeAction; + void runThread(); int writeData (void*, int); - JUCE_DECLARE_WEAK_REFERENCEABLE (InterprocessConnection) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) }; diff --git a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp index ce3cfcd..2ff1000 100644 --- a/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp +++ b/JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp @@ -73,7 +73,7 @@ void InterprocessConnectionServer::run() if (clientSocket != nullptr) if (auto* newConnection = createConnectionObject()) - newConnection->initialiseWithSocket (clientSocket.release()); + newConnection->initialiseWithSocket (std::move (clientSocket)); } } diff --git a/JuceLibraryCode/modules/juce_events/juce_events.cpp b/JuceLibraryCode/modules/juce_events/juce_events.cpp index afba0b9..a1aaa39 100644 --- a/JuceLibraryCode/modules/juce_events/juce_events.cpp +++ b/JuceLibraryCode/modules/juce_events/juce_events.cpp @@ -73,16 +73,12 @@ #include "native/juce_osx_MessageQueue.h" - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - #if JUCE_MAC #include "native/juce_mac_MessageManager.mm" #else #include "native/juce_ios_MessageManager.mm" #endif - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #elif JUCE_WINDOWS #include "native/juce_win32_Messaging.cpp" #if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER diff --git a/JuceLibraryCode/modules/juce_events/juce_events.h b/JuceLibraryCode/modules/juce_events/juce_events.h index 34701be..00e8984 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: 6.0.1 + version: 6.0.5 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 diff --git a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm index 7d45219..84a4eca 100644 --- a/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/JuceLibraryCode/modules/juce_events/native/juce_mac_MessageManager.mm @@ -43,20 +43,24 @@ NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) name: NSMenuDidBeginTrackingNotification object: nil]; [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) name: NSMenuDidEndTrackingNotification object: nil]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE if (JUCEApplicationBase::isStandaloneApp()) { [NSApp setDelegate: delegate]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate selector: @selector (broadcastMessageCallback:) name: getBroadcastEventName() object: nil suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } else { @@ -103,7 +107,6 @@ AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") { addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); - addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); @@ -111,17 +114,25 @@ addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); addMethod (@selector (dummyMethod), dummyMethod, "v@:"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE #if JUCE_PUSH_NOTIFICATIONS //============================================================================== addIvar*> ("pushNotificationsDelegate"); addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); @@ -133,10 +144,12 @@ 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 } #if JUCE_PUSH_NOTIFICATIONS @@ -144,7 +157,11 @@ static void applicationDidFinishLaunching (id self, SEL, NSNotification* notific { if (notification.userInfo != nil) { - NSUserNotification* userNotification = [notification.userInfo objectForKey: nsStringLiteral ("NSApplicationLaunchUserNotificationKey")]; + 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 if (userNotification != nil && userNotification.userInfo != nil) didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); @@ -259,7 +276,7 @@ static void registeredForRemoteNotifications (id self, SEL, NSApplication* appli { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:"); + SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -277,7 +294,7 @@ static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:"); + SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -295,7 +312,7 @@ static void didReceiveRemoteNotification (id self, SEL, NSApplication* applicati { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:"); + SEL selector = @selector (application:didReceiveRemoteNotification:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -483,8 +500,10 @@ void messageCallback() override NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil]; [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } ~Pimpl() @@ -502,7 +521,11 @@ void messageCallback() override ObserverClass() : ObjCClass ("JUCEDriveObserver_") { addIvar ("owner"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (changed:), changed, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + addProtocol (@protocol (NSTextInput)); registerClass(); } diff --git a/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp index 2300c82..4a8ef2e 100644 --- a/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp +++ b/JuceLibraryCode/modules/juce_events/timers/juce_Timer.cpp @@ -317,6 +317,14 @@ Timer::Timer (const Timer&) noexcept {} Timer::~Timer() { + // If you're destroying a timer on a background thread, make sure the timer has + // been stopped before execution reaches this point. A simple way to achieve this + // is to add a call to `stopTimer()` to the destructor of your class which inherits + // from Timer. + jassert (! isTimerRunning() + || MessageManager::getInstanceWithoutCreating() == nullptr + || MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); + stopTimer(); }