diff --git a/libs/juce-current/patches/01_audiodevicemanager-prefer-jack-over-alsa.patch b/libs/juce-current/patches/01_audiodevicemanager-prefer-jack-over-alsa.patch index 890e0918..a5090773 100644 --- a/libs/juce-current/patches/01_audiodevicemanager-prefer-jack-over-alsa.patch +++ b/libs/juce-current/patches/01_audiodevicemanager-prefer-jack-over-alsa.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp -index dc2ad8e3b..1f03bc047 100644 +index a19c7b05e..deabba6db 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp -@@ -184,8 +184,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& +@@ -185,8 +185,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Bela()); diff --git a/libs/juce-current/patches/03_fix-midi-message-constness.patch b/libs/juce-current/patches/03_fix-midi-message-constness.patch index 6d40720d..7d72928b 100644 --- a/libs/juce-current/patches/03_fix-midi-message-constness.patch +++ b/libs/juce-current/patches/03_fix-midi-message-constness.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.h b/modules/juce_audio_basics/midi/juce_MidiMessage.h -index 2d9e5adf0..30a609cec 100644 +index 22ac79483..5c761f659 100644 --- a/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/modules/juce_audio_basics/midi/juce_MidiMessage.h -@@ -945,7 +945,7 @@ private: +@@ -979,7 +979,7 @@ private: #endif inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } diff --git a/libs/juce-current/patches/04_vst2-interface.patch b/libs/juce-current/patches/04_vst2-interface.patch index 7ef90d2d..4ad2439b 100644 --- a/libs/juce-current/patches/04_vst2-interface.patch +++ b/libs/juce-current/patches/04_vst2-interface.patch @@ -1,5 +1,5 @@ diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -index f9b8bed8a..23484732b 100644 +index 58b34493b..8683dc728 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -78,14 +78,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) @@ -26,7 +26,7 @@ index f9b8bed8a..23484732b 100644 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 #include "../utility/juce_IncludeModuleHeaders.h" -@@ -289,7 +281,7 @@ private: +@@ -288,7 +280,7 @@ private: public: //============================================================================== @@ -35,7 +35,7 @@ index f9b8bed8a..23484732b 100644 : hostCallback (cb), processor (af) { -@@ -320,41 +312,41 @@ public: +@@ -319,41 +311,41 @@ public: juceParameters.update (*processor, false); memset (&vstEffect, 0, sizeof (vstEffect)); @@ -98,7 +98,7 @@ index f9b8bed8a..23484732b 100644 #endif activePlugins.add (this); -@@ -398,7 +390,7 @@ public: +@@ -397,7 +389,7 @@ public: } } @@ -107,7 +107,7 @@ index f9b8bed8a..23484732b 100644 template void internalProcessReplacing (FloatType** inputs, FloatType** outputs, -@@ -533,7 +525,7 @@ public: +@@ -532,7 +524,7 @@ public: // Send VST events to the host. if (hostCallback != nullptr) @@ -116,7 +116,7 @@ index f9b8bed8a..23484732b 100644 #elif JUCE_DEBUG /* This assertion is caused when you've added some events to the midiMessages array in your processBlock() method, which usually means -@@ -562,7 +554,7 @@ public: +@@ -561,7 +553,7 @@ public: internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); } @@ -125,7 +125,7 @@ index f9b8bed8a..23484732b 100644 { getWrapper (vstInterface)->processReplacing (inputs, outputs, sampleFrames); } -@@ -573,7 +565,7 @@ public: +@@ -572,7 +564,7 @@ public: internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); } @@ -134,7 +134,7 @@ index f9b8bed8a..23484732b 100644 { getWrapper (vstInterface)->processDoubleReplacing (inputs, outputs, sampleFrames); } -@@ -585,7 +577,7 @@ public: +@@ -584,7 +576,7 @@ public: { isProcessing = true; @@ -143,7 +143,7 @@ index f9b8bed8a..23484732b 100644 floatTempBuffers .channels.calloc (numInAndOutChannels); doubleTempBuffers.channels.calloc (numInAndOutChannels); -@@ -604,16 +596,16 @@ public: +@@ -603,16 +595,16 @@ public: midiEvents.ensureSize (2048); midiEvents.clear(); @@ -163,7 +163,7 @@ index f9b8bed8a..23484732b 100644 } if (getHostType().isAbletonLive() -@@ -627,7 +619,7 @@ public: +@@ -626,7 +618,7 @@ public: hostCmd.commandSize = sizeof (int); hostCmd.flags = AbletonLiveHostSpecific::KCantBeSuspended; @@ -172,7 +172,7 @@ index f9b8bed8a..23484732b 100644 } #if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect -@@ -654,28 +646,28 @@ public: +@@ -653,28 +645,28 @@ public: //============================================================================== bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info) override { @@ -212,7 +212,7 @@ index f9b8bed8a..23484732b 100644 } else { -@@ -683,34 +675,34 @@ public: +@@ -682,34 +674,34 @@ public: info.timeSigDenominator = 4; } @@ -269,7 +269,7 @@ index f9b8bed8a..23484732b 100644 } info.frameRate = rate; -@@ -722,14 +714,14 @@ public: +@@ -721,14 +713,14 @@ public: info.editOriginTime = 0; } @@ -290,7 +290,7 @@ index f9b8bed8a..23484732b 100644 } else { -@@ -749,7 +741,7 @@ public: +@@ -748,7 +740,7 @@ public: return 0.0f; } @@ -299,7 +299,7 @@ index f9b8bed8a..23484732b 100644 { return getWrapper (vstInterface)->getParameter (index); } -@@ -765,7 +757,7 @@ public: +@@ -764,7 +756,7 @@ public: } } @@ -308,7 +308,7 @@ index f9b8bed8a..23484732b 100644 { getWrapper (vstInterface)->setParameter (index, value); } -@@ -779,19 +771,19 @@ public: +@@ -778,19 +770,19 @@ public: } if (hostCallback != nullptr) @@ -331,24 +331,8 @@ index f9b8bed8a..23484732b 100644 } void parameterValueChanged (int, float newValue) override -@@ -804,7 +796,7 @@ public: - - void audioProcessorChanged (AudioProcessor*) override - { -- vstEffect.initialDelay = processor->getLatencySamples(); -+ vstEffect.latency = processor->getLatencySamples(); - triggerAsyncUpdate(); - } - -@@ -812,12 +804,12 @@ public: - { - if (hostCallback != nullptr) - { -- hostCallback (&vstEffect, Vst2::audioMasterUpdateDisplay, 0, 0, nullptr, 0); -- hostCallback (&vstEffect, Vst2::audioMasterIOChanged, 0, 0, nullptr, 0); -+ hostCallback (&vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); -+ hostCallback (&vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); - } +@@ -806,7 +798,7 @@ public: + hostChangeUpdater.update (details); } - bool getPinProperties (Vst2::VstPinProperties& properties, bool direction, int index) const @@ -356,7 +340,7 @@ index f9b8bed8a..23484732b 100644 { if (processor->isMidiEffect()) return false; -@@ -826,9 +818,9 @@ public: +@@ -815,9 +807,9 @@ public: // fill with default properties.flags = 0; @@ -369,7 +353,7 @@ index f9b8bed8a..23484732b 100644 if ((channelIdx = processor->getOffsetInBusBufferForAbsoluteChannelIndex (direction, index, busIdx)) >= 0) { -@@ -836,8 +828,8 @@ public: +@@ -825,8 +817,8 @@ public: auto& channelSet = bus.getCurrentLayout(); auto channelType = channelSet.getTypeOfChannel (channelIdx); @@ -380,7 +364,7 @@ index f9b8bed8a..23484732b 100644 String label = bus.getName(); #ifdef JucePlugin_PreferredChannelConfigurations -@@ -847,8 +839,8 @@ public: +@@ -836,8 +828,8 @@ public: label += " " + AudioChannelSet::getAbbreviatedChannelTypeName (channelType); #endif @@ -391,7 +375,7 @@ index f9b8bed8a..23484732b 100644 if (channelType == AudioChannelSet::left || channelType == AudioChannelSet::leftSurround -@@ -858,7 +850,7 @@ public: +@@ -847,7 +839,7 @@ public: || channelType == AudioChannelSet::topRearLeft || channelType == AudioChannelSet::leftSurroundRear || channelType == AudioChannelSet::wideLeft) @@ -400,7 +384,7 @@ index f9b8bed8a..23484732b 100644 return true; } -@@ -893,15 +885,15 @@ public: +@@ -882,15 +874,15 @@ public: void setHasEditorFlag (bool shouldSetHasEditor) { @@ -419,7 +403,7 @@ index f9b8bed8a..23484732b 100644 } void createEditorComp() -@@ -968,59 +960,59 @@ public: +@@ -957,59 +949,59 @@ public: switch (opCode) { @@ -527,16 +511,27 @@ index f9b8bed8a..23484732b 100644 { wrapper->dispatcher (opCode, args); delete wrapper; -@@ -1068,7 +1060,7 @@ public: - - void paint (Graphics&) override {} +@@ -1056,7 +1048,7 @@ public: + g.fillAll (Colours::black); + } - void getEditorBounds (Vst2::ERect& bounds) + void getEditorBounds (Vst2::VstEditorBounds& bounds) { - auto b = getSizeToContainChild(); - bounds = convertToHostBounds ({ 0, 0, (int16) b.getHeight(), (int16) b.getWidth() }); -@@ -1224,20 +1216,20 @@ public: + auto editorBounds = getSizeToContainChild(); + bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); +@@ -1183,8 +1175,8 @@ public: + auto rect = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); + + X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), +- static_cast (rect.right - rect.left), +- static_cast (rect.bottom - rect.top)); ++ static_cast (rect.rightmost - rect.leftmost), ++ static_cast (rect.lower - rect.upper)); + #else + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + #endif +@@ -1199,20 +1191,20 @@ public: void resizeHostWindow (int newWidth, int newHeight) { auto rect = convertToHostBounds ({ 0, 0, (int16) newHeight, (int16) newWidth }); @@ -554,16 +549,16 @@ index f9b8bed8a..23484732b 100644 if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive()) { - const ScopedValueSetter inSizeWindowSetter (isInSizeWindow, true); + const ScopedValueSetter resizingParentSetter (resizingParent, true); - sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::audioMasterSizeWindow, + sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::hostOpcodeWindowSize, newWidth, newHeight, nullptr, 0) != 0); } } -@@ -1359,17 +1351,17 @@ public: - #endif +@@ -1339,17 +1331,17 @@ public: + private: //============================================================================== - static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) + static Vst2::VstEditorBounds convertToHostBounds (const Vst2::VstEditorBounds& rect) @@ -584,10 +579,29 @@ index f9b8bed8a..23484732b 100644 } //============================================================================== -@@ -1398,12 +1390,12 @@ public: +@@ -1385,7 +1377,7 @@ private: + void update (const ChangeDetails& details) + { + if (details.latencyChanged) +- owner.vstEffect.initialDelay = owner.processor->getLatencySamples(); ++ owner.vstEffect.latency = owner.processor->getLatencySamples(); + + if (details.parameterInfoChanged || details.programChanged) + triggerAsyncUpdate(); +@@ -1396,20 +1388,20 @@ private: + { + if (auto* callback = owner.hostCallback) + { +- callback (&owner.vstEffect, Vst2::audioMasterUpdateDisplay, 0, 0, nullptr, 0); +- callback (&owner.vstEffect, Vst2::audioMasterIOChanged, 0, 0, nullptr, 0); ++ callback (&owner.vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); ++ callback (&owner.vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); + } + } + + JuceVSTWrapper& owner; + }; - //============================================================================== - private: - static JuceVSTWrapper* getWrapper (Vst2::AEffect* v) noexcept { return static_cast (v->object); } + static JuceVSTWrapper* getWrapper (Vst2::VstEffectInterface* v) noexcept { return static_cast (v->effectPointer); } @@ -599,7 +613,7 @@ index f9b8bed8a..23484732b 100644 } static int32 convertHexVersionToDecimal (const unsigned int hexVersion) -@@ -1471,8 +1463,8 @@ private: +@@ -1477,8 +1469,8 @@ private: tmpBuffers.release(); if (processor != nullptr) @@ -610,16 +624,16 @@ index f9b8bed8a..23484732b 100644 } void deleteTempChannels() -@@ -1632,7 +1624,7 @@ private: +@@ -1638,7 +1630,7 @@ private: if (editorComp != nullptr) { - editorComp->getEditorBounds (editorBounds); -- *((Vst2::ERect**) args.ptr) = &editorBounds; -+ *((Vst2::VstEditorBounds**) args.ptr) = &editorBounds; - return (pointer_sized_int) &editorBounds; + editorComp->getEditorBounds (editorRect); +- *((Vst2::ERect**) args.ptr) = &editorRect; ++ *((Vst2::VstEditorBounds**) args.ptr) = &editorRect; + return (pointer_sized_int) &editorRect; } -@@ -1722,7 +1714,7 @@ private: +@@ -1728,7 +1720,7 @@ private: pointer_sized_int handlePreAudioProcessingEvents (VstOpCodeArguments args) { #if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect @@ -628,7 +642,7 @@ index f9b8bed8a..23484732b 100644 return 1; #else ignoreUnused (args); -@@ -1773,12 +1765,12 @@ private: +@@ -1779,12 +1771,12 @@ private: pointer_sized_int handleGetInputPinProperties (VstOpCodeArguments args) { @@ -643,7 +657,7 @@ index f9b8bed8a..23484732b 100644 } pointer_sized_int handleGetPlugInCategory (VstOpCodeArguments) -@@ -1788,8 +1780,8 @@ private: +@@ -1794,8 +1786,8 @@ private: pointer_sized_int handleSetSpeakerConfiguration (VstOpCodeArguments args) { @@ -654,7 +668,7 @@ index f9b8bed8a..23484732b 100644 if (processor->isMidiEffect()) return 0; -@@ -1800,29 +1792,29 @@ private: +@@ -1806,29 +1798,29 @@ private: if (pluginInput != nullptr && pluginInput->type >= 0) { // inconsistent request? @@ -690,7 +704,7 @@ index f9b8bed8a..23484732b 100644 layouts.getChannelSet (false, 0) = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); #ifdef JucePlugin_PreferredChannelConfigurations -@@ -1870,7 +1862,7 @@ private: +@@ -1876,7 +1868,7 @@ private: && args.value == (int32) ByteOrder::bigEndianInt ("AeCs")) return handleSetContentScaleFactor (args.opt); @@ -699,7 +713,7 @@ index f9b8bed8a..23484732b 100644 return handleCockosGetParameterText (args.value, args.ptr, args.opt); if (auto callbackHandler = dynamic_cast (processor)) -@@ -1965,7 +1957,7 @@ private: +@@ -1971,7 +1963,7 @@ private: pointer_sized_int handleGetVstInterfaceVersion (VstOpCodeArguments) { @@ -708,7 +722,7 @@ index f9b8bed8a..23484732b 100644 } pointer_sized_int handleGetCurrentMidiProgram (VstOpCodeArguments) -@@ -1975,8 +1967,8 @@ private: +@@ -1981,8 +1973,8 @@ private: pointer_sized_int handleGetSpeakerConfiguration (VstOpCodeArguments args) { @@ -719,7 +733,7 @@ index f9b8bed8a..23484732b 100644 if (pluginHasSidechainsOrAuxs() || processor->isMidiEffect()) return false; -@@ -1984,10 +1976,10 @@ private: +@@ -1990,10 +1982,10 @@ private: auto inputLayout = processor->getChannelLayoutOfBus (true, 0); auto outputLayout = processor->getChannelLayoutOfBus (false, 0); @@ -733,7 +747,7 @@ index f9b8bed8a..23484732b 100644 *pluginInput = cachedInArrangement. getData(); *pluginOutput = cachedOutArrangement.getData(); -@@ -2009,7 +2001,7 @@ private: +@@ -2015,7 +2007,7 @@ private: { if (processor != nullptr) { @@ -742,7 +756,7 @@ index f9b8bed8a..23484732b 100644 && processor->supportsDoublePrecisionProcessing()) ? AudioProcessor::doublePrecision : AudioProcessor::singlePrecision); -@@ -2083,16 +2075,16 @@ private: +@@ -2089,16 +2081,16 @@ private: } //============================================================================== @@ -757,12 +771,12 @@ index f9b8bed8a..23484732b 100644 juce::MemoryBlock chunkMemory; uint32 chunkMemoryTime = 0; std::unique_ptr editorComp; -- Vst2::ERect editorBounds; -+ Vst2::VstEditorBounds editorBounds; +- Vst2::ERect editorRect; ++ Vst2::VstEditorBounds editorRect; MidiBuffer midiEvents; VSTMidiEventList outgoingEvents; -@@ -2117,7 +2109,7 @@ private: +@@ -2123,7 +2115,7 @@ private: VstTempBuffers doubleTempBuffers; int maxNumInChannels = 0, maxNumOutChannels = 0; @@ -771,7 +785,7 @@ index f9b8bed8a..23484732b 100644 ThreadLocalValue inParameterChangedCallback; -@@ -2129,7 +2121,7 @@ private: +@@ -2137,7 +2129,7 @@ private: //============================================================================== namespace { @@ -780,7 +794,7 @@ index f9b8bed8a..23484732b 100644 { JUCE_AUTORELEASEPOOL { -@@ -2137,7 +2129,7 @@ namespace +@@ -2145,7 +2137,7 @@ namespace try { @@ -789,7 +803,7 @@ index f9b8bed8a..23484732b 100644 { #if JUCE_LINUX MessageManagerLock mmLock; -@@ -2174,8 +2166,8 @@ namespace +@@ -2182,8 +2174,8 @@ namespace // Mac startup code.. #if JUCE_MAC @@ -800,7 +814,7 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; -@@ -2183,8 +2175,8 @@ namespace +@@ -2191,8 +2183,8 @@ namespace return pluginEntryPoint (audioMaster); } @@ -811,7 +825,7 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; -@@ -2196,8 +2188,8 @@ namespace +@@ -2204,8 +2196,8 @@ namespace // Linux startup code.. #elif JUCE_LINUX @@ -822,7 +836,7 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; -@@ -2205,8 +2197,8 @@ namespace +@@ -2213,8 +2205,8 @@ namespace return pluginEntryPoint (audioMaster); } @@ -833,7 +847,7 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; -@@ -2221,7 +2213,7 @@ namespace +@@ -2229,7 +2221,7 @@ namespace // Win32 startup code.. #else @@ -842,7 +856,7 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; -@@ -2229,7 +2221,7 @@ namespace +@@ -2237,7 +2229,7 @@ namespace } #if ! defined (JUCE_64BIT) && JUCE_MSVC // (can't compile this on win64, but it's not needed anyway with VST2.4) @@ -851,6 +865,71 @@ index f9b8bed8a..23484732b 100644 { PluginHostType::jucePlugInClientCurrentWrapperType = AudioProcessor::wrapperType_VST; +diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +index f0013bf3e..7909098c9 100644 +--- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp ++++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +@@ -61,10 +61,7 @@ + #define __cdecl + #endif + +- namespace Vst2 +- { +- #include "pluginterfaces/vst2.x/vstfxstore.h" +- } ++ #include + + #endif + +@@ -2047,16 +2044,16 @@ public: + + bool loadVST2CcnKBlock (const char* data, int size) + { +- auto* bank = reinterpret_cast (data); ++ auto* bank = reinterpret_cast (data); + +- jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); +- jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); +- jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); ++ jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->magic1)); ++ jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->magic2)); ++ jassert (htonl ((uint32) bank->version1) == 1 || htonl ((uint32) bank->version1) == 2); + jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); + +- setStateInformation (bank->content.data.chunk, +- jmin ((int) (size - (bank->content.data.chunk - data)), +- (int) htonl ((uint32) bank->content.data.size))); ++ setStateInformation (bank->chunk, ++ jmin ((int) (size - (bank->chunk - data)), ++ (int) htonl ((uint32) bank->chunkSize))); + return true; + } + +@@ -2252,16 +2249,16 @@ public: + return status; + + const int bankBlockSize = 160; +- Vst2::fxBank bank; ++ vst2FxBank bank; + + zerostruct (bank); +- bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); +- bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); +- bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); +- bank.version = (int32) htonl (2); +- bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); +- bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); +- bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); ++ bank.magic1 = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); ++ bank.size = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); ++ bank.magic2 = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); ++ bank.version1 = (int32) htonl (2); ++ bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); ++ bank.version2 = (int32) htonl (JucePlugin_VersionCode); ++ bank.chunkSize = (int32) htonl ((unsigned int) mem.getSize()); + + status = state->write (&bank, bankBlockSize); + diff --git a/modules/juce_audio_processors/format_types/juce_VSTCommon.h b/modules/juce_audio_processors/format_types/juce_VSTCommon.h index e3bd4edc4..b948abe2b 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTCommon.h @@ -1222,544 +1301,6 @@ index e3bd4edc4..b948abe2b 100644 default: break; } -diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h -new file mode 100644 -index 000000000..1b3eb7d08 ---- /dev/null -+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h -@@ -0,0 +1,532 @@ -+/* -+ ============================================================================== -+ -+ This file is part of the JUCE library. -+ Copyright (c) 2017 - ROLI Ltd. -+ -+ JUCE is an open source library subject to commercial or open-source -+ licensing. -+ -+ By using JUCE, you agree to the terms of both the JUCE 5 End-User License -+ Agreement and JUCE 5 Privacy Policy (both updated and effective as of the -+ 27th April 2017). -+ -+ End User License Agreement: www.juce.com/juce-5-licence -+ Privacy Policy: www.juce.com/juce-5-privacy-policy -+ -+ Or: You may also use this code under the terms of the GPL v3 (see -+ www.gnu.org/licenses). -+ -+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER -+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE -+ DISCLAIMED. -+ -+ ============================================================================== -+*/ -+ -+#ifndef JUCE_VSTINTERFACE_H_INCLUDED -+#define JUCE_VSTINTERFACE_H_INCLUDED -+ -+using namespace juce; -+ -+#if JUCE_MSVC -+ #define VSTINTERFACECALL __cdecl -+ #pragma pack(push) -+ #pragma pack(8) -+#elif JUCE_MAC || JUCE_IOS -+ #define VSTINTERFACECALL -+ #if JUCE_64BIT -+ #pragma options align=power -+ #else -+ #pragma options align=mac68k -+ #endif -+#else -+ #define VSTINTERFACECALL -+ #pragma pack(push, 8) -+#endif -+ -+const int32 juceVstInterfaceVersion = 2400; -+const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstEffectInterface -+{ -+ int32 interfaceIdentifier; -+ pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); -+ void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); -+ void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); -+ float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); -+ int32 numPrograms; -+ int32 numParameters; -+ int32 numInputChannels; -+ int32 numOutputChannels; -+ int32 flags; -+ pointer_sized_int hostSpace1; -+ pointer_sized_int hostSpace2; -+ int32 latency; -+ int32 deprecated1; -+ int32 deprecated2; -+ float deprecated3; -+ void* effectPointer; -+ void* userPointer; -+ int32 plugInIdentifier; -+ int32 plugInVersion; -+ void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); -+ void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); -+ char emptySpace[56]; -+}; -+ -+typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); -+ -+enum VstEffectInterfaceFlags -+{ -+ vstEffectFlagHasEditor = 1, -+ vstEffectFlagInplaceAudio = 16, -+ vstEffectFlagDataInChunks = 32, -+ vstEffectFlagIsSynth = 256, -+ vstEffectFlagInplaceDoubleAudio = 4096 -+}; -+ -+//============================================================================== -+enum VstHostToPlugInOpcodes -+{ -+ plugInOpcodeOpen, -+ plugInOpcodeClose, -+ plugInOpcodeSetCurrentProgram, -+ plugInOpcodeGetCurrentProgram, -+ plugInOpcodeSetCurrentProgramName, -+ plugInOpcodeGetCurrentProgramName, -+ plugInOpcodeGetParameterLabel, -+ plugInOpcodeGetParameterText, -+ plugInOpcodeGetParameterName, -+ plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, -+ plugInOpcodeSetBlockSize, -+ plugInOpcodeResumeSuspend, -+ plugInOpcodeGetEditorBounds, -+ plugInOpcodeOpenEditor, -+ plugInOpcodeCloseEditor, -+ plugInOpcodeDrawEditor, -+ plugInOpcodeGetMouse, -+ plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, -+ plugInOpcodeeffEditorTop, -+ plugInOpcodeSleepEditor, -+ plugInOpcodeIdentify, -+ plugInOpcodeGetData, -+ plugInOpcodeSetData, -+ plugInOpcodePreAudioProcessingEvents, -+ plugInOpcodeIsParameterAutomatable, -+ plugInOpcodeParameterValueForText, -+ plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, -+ plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, -+ plugInOpcodeConnectOutput, -+ plugInOpcodeGetInputPinProperties, -+ plugInOpcodeGetOutputPinProperties, -+ plugInOpcodeGetPlugInCategory, -+ plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, -+ plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, -+ plugInOpcodeGetPlugInName, -+ plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, -+ plugInOpcodeGetManufacturerProductName, -+ plugInOpcodeGetManufacturerVersion, -+ plugInOpcodeManufacturerSpecific, -+ plugInOpcodeCanPlugInDo, -+ plugInOpcodeGetTailSize, -+ plugInOpcodeIdle, -+ plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, -+ plugInOpcodeGetVstInterfaceVersion, -+ plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, -+ plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, -+ plugInOpcodeNextPlugInUniqueID, -+ plugInOpcodeStartProcess, -+ plugInOpcodeStopProcess, -+ plugInOpcodeSetNumberOfSamplesToProcess, -+ plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, -+ pluginOpcodeGetNumMidiInputChannels, -+ pluginOpcodeGetNumMidiOutputChannels, -+ plugInOpcodeMaximum = pluginOpcodeGetNumMidiOutputChannels -+}; -+ -+ -+enum VstPlugInToHostOpcodes -+{ -+ hostOpcodeParameterChanged, -+ hostOpcodeVstVersion, -+ hostOpcodeCurrentId, -+ hostOpcodeIdle, -+ hostOpcodePinConnected, -+ hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, -+ hostOpcodeGetTimingInfo, -+ hostOpcodePreAudioProcessingEvents, -+ hostOpcodeSetTime, -+ hostOpcodeTempoAt, -+ hostOpcodeGetNumberOfAutomatableParameters, -+ hostOpcodeGetParameterInterval, -+ hostOpcodeIOModified, -+ hostOpcodeNeedsIdle, -+ hostOpcodeWindowSize, -+ hostOpcodeGetSampleRate, -+ hostOpcodeGetBlockSize, -+ hostOpcodeGetInputLatency, -+ hostOpcodeGetOutputLatency, -+ hostOpcodeGetPreviousPlugIn, -+ hostOpcodeGetNextPlugIn, -+ hostOpcodeWillReplace, -+ hostOpcodeGetCurrentAudioProcessingLevel, -+ hostOpcodeGetAutomationState, -+ hostOpcodeOfflineStart, -+ hostOpcodeOfflineReadSource, -+ hostOpcodeOfflineWrite, -+ hostOpcodeOfflineGetCurrentPass, -+ hostOpcodeOfflineGetCurrentMetaPass, -+ hostOpcodeSetOutputSampleRate, -+ hostOpcodeGetOutputSpeakerConfiguration, -+ hostOpcodeGetManufacturerName, -+ hostOpcodeGetProductName, -+ hostOpcodeGetManufacturerVersion, -+ hostOpcodeManufacturerSpecific, -+ hostOpcodeSetIcon, -+ hostOpcodeCanHostDo, -+ hostOpcodeGetLanguage, -+ hostOpcodeOpenEditorWindow, -+ hostOpcodeCloseEditorWindow, -+ hostOpcodeGetDirectory, -+ hostOpcodeUpdateView, -+ hostOpcodeParameterChangeGestureBegin, -+ hostOpcodeParameterChangeGestureEnd, -+}; -+ -+//============================================================================== -+enum VstProcessingSampleType -+{ -+ vstProcessingSampleTypeFloat, -+ vstProcessingSampleTypeDouble -+}; -+ -+//============================================================================== -+// These names must be identical to the Steinberg SDK so JUCE users can set -+// exactly what they want. -+enum VstPlugInCategory -+{ -+ kPlugCategUnknown, -+ kPlugCategEffect, -+ kPlugCategSynth, -+ kPlugCategAnalysis, -+ kPlugCategMastering, -+ kPlugCategSpacializer, -+ kPlugCategRoomFx, -+ kPlugSurroundFx, -+ kPlugCategRestoration, -+ kPlugCategOfflineProcess, -+ kPlugCategShell, -+ kPlugCategGenerator -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstEditorBounds -+{ -+ int16 upper; -+ int16 leftmost; -+ int16 lower; -+ int16 rightmost; -+}; -+ -+//============================================================================== -+enum VstMaxStringLengths -+{ -+ vstMaxNameLength = 64, -+ vstMaxParameterOrPinLabelLength = 64, -+ vstMaxParameterOrPinShortLabelLength = 8, -+ vstMaxCategoryLength = 24, -+ vstMaxManufacturerStringLength = 64, -+ vstMaxPlugInNameStringLength = 64 -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstPinInfo -+{ -+ char text[vstMaxParameterOrPinLabelLength]; -+ int32 flags; -+ int32 configurationType; -+ char shortText[vstMaxParameterOrPinShortLabelLength]; -+ char unused[48]; -+}; -+ -+enum VstPinInfoFlags -+{ -+ vstPinInfoFlagIsActive = 1, -+ vstPinInfoFlagIsStereo = 2, -+ vstPinInfoFlagValid = 4 -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstEvent -+{ -+ int32 type; -+ int32 size; -+ int32 sampleOffset; -+ int32 flags; -+ char content[16]; -+}; -+ -+enum VstEventTypes -+{ -+ vstMidiEventType = 1, -+ vstSysExEventType = 6 -+}; -+ -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstEventBlock -+{ -+ int32 numberOfEvents; -+ pointer_sized_int future; -+ VstEvent* events[2]; -+}; -+ -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstMidiEvent -+{ -+ int32 type; -+ int32 size; -+ int32 sampleOffset; -+ int32 flags; -+ int32 noteSampleLength; -+ int32 noteSampleOffset; -+ char midiData[4]; -+ char tuning; -+ char noteVelocityOff; -+ char future1; -+ char future2; -+}; -+ -+enum VstMidiEventFlags -+{ -+ vstMidiEventIsRealtime = 1 -+}; -+ -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstSysExEvent -+{ -+ int32 type; -+ int32 size; -+ int32 offsetSamples; -+ int32 flags; -+ int32 sysExDumpSize; -+ pointer_sized_int future1; -+ char* sysExDump; -+ pointer_sized_int future2; -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstTimingInformation -+{ -+ double samplePosition; -+ double sampleRate; -+ double systemTimeNanoseconds; -+ double musicalPosition; -+ double tempoBPM; -+ double lastBarPosition; -+ double loopStartPosition; -+ double loopEndPosition; -+ int32 timeSignatureNumerator; -+ int32 timeSignatureDenominator; -+ int32 smpteOffset; -+ int32 smpteRate; -+ int32 samplesToNearestClock; -+ int32 flags; -+}; -+ -+enum VstTimingInformationFlags -+{ -+ vstTimingInfoFlagTransportChanged = 1, -+ vstTimingInfoFlagCurrentlyPlaying = 2, -+ vstTimingInfoFlagLoopActive = 4, -+ vstTimingInfoFlagCurrentlyRecording = 8, -+ vstTimingInfoFlagAutomationWriteModeActive = 64, -+ vstTimingInfoFlagAutomationReadModeActive = 128, -+ vstTimingInfoFlagNanosecondsValid = 256, -+ vstTimingInfoFlagMusicalPositionValid = 512, -+ vstTimingInfoFlagTempoValid = 1024, -+ vstTimingInfoFlagLastBarPositionValid = 2048, -+ vstTimingInfoFlagLoopPositionValid = 4096, -+ vstTimingInfoFlagTimeSignatureValid = 8192, -+ vstTimingInfoFlagSmpteValid = 16384, -+ vstTimingInfoFlagNearestClockValid = 32768 -+}; -+ -+//============================================================================== -+enum VstSmpteRates -+{ -+ vstSmpteRateFps24, -+ vstSmpteRateFps25, -+ vstSmpteRateFps2997, -+ vstSmpteRateFps30, -+ vstSmpteRateFps2997drop, -+ vstSmpteRateFps30drop, -+ -+ vstSmpteRate16mmFilm, -+ vstSmpteRate35mmFilm, -+ -+ vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, -+ vstSmpteRateFps249 , -+ vstSmpteRateFps599, -+ vstSmpteRateFps60 -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstIndividualSpeakerInfo -+{ -+ float azimuthalAngle; -+ float elevationAngle; -+ float radius; -+ float reserved; -+ char label[vstMaxNameLength]; -+ int32 type; -+ char unused[28]; -+}; -+ -+enum VstIndividualSpeakerType -+{ -+ vstIndividualSpeakerTypeUndefined = 0x7fffffff, -+ vstIndividualSpeakerTypeMono = 0, -+ vstIndividualSpeakerTypeLeft, -+ vstIndividualSpeakerTypeRight, -+ vstIndividualSpeakerTypeCentre, -+ vstIndividualSpeakerTypeLFE, -+ vstIndividualSpeakerTypeLeftSurround, -+ vstIndividualSpeakerTypeRightSurround, -+ vstIndividualSpeakerTypeLeftCentre, -+ vstIndividualSpeakerTypeRightCentre, -+ vstIndividualSpeakerTypeSurround, -+ vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, -+ vstIndividualSpeakerTypeLeftRearSurround, -+ vstIndividualSpeakerTypeRightRearSurround, -+ vstIndividualSpeakerTypeTopMiddle, -+ vstIndividualSpeakerTypeTopFrontLeft, -+ vstIndividualSpeakerTypeTopFrontCentre, -+ vstIndividualSpeakerTypeTopFrontRight, -+ vstIndividualSpeakerTypeTopRearLeft, -+ vstIndividualSpeakerTypeTopRearCentre, -+ vstIndividualSpeakerTypeTopRearRight, -+ vstIndividualSpeakerTypeLFE2 -+}; -+ -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct VstSpeakerConfiguration -+{ -+ int32 type; -+ int32 numberOfChannels; -+ VstIndividualSpeakerInfo speakers[8]; -+}; -+ -+enum VstSpeakerConfigurationType -+{ -+ vstSpeakerConfigTypeUser = -2, -+ vstSpeakerConfigTypeEmpty = -1, -+ vstSpeakerConfigTypeMono = 0, -+ vstSpeakerConfigTypeLR, -+ vstSpeakerConfigTypeLsRs, -+ vstSpeakerConfigTypeLcRc, -+ vstSpeakerConfigTypeSlSr, -+ vstSpeakerConfigTypeCLfe, -+ vstSpeakerConfigTypeLRC, -+ vstSpeakerConfigTypeLRS, -+ vstSpeakerConfigTypeLRCLfe, -+ vstSpeakerConfigTypeLRLfeS, -+ vstSpeakerConfigTypeLRCS, -+ vstSpeakerConfigTypeLRLsRs, -+ vstSpeakerConfigTypeLRCLfeS, -+ vstSpeakerConfigTypeLRLfeLsRs, -+ vstSpeakerConfigTypeLRCLsRs, -+ vstSpeakerConfigTypeLRCLfeLsRs, -+ vstSpeakerConfigTypeLRCLsRsCs, -+ vstSpeakerConfigTypeLRLsRsSlSr, -+ vstSpeakerConfigTypeLRCLfeLsRsCs, -+ vstSpeakerConfigTypeLRLfeLsRsSlSr, -+ vstSpeakerConfigTypeLRCLsRsLcRc, -+ vstSpeakerConfigTypeLRCLsRsSlSr, -+ vstSpeakerConfigTypeLRCLfeLsRsLcRc, -+ vstSpeakerConfigTypeLRCLfeLsRsSlSr, -+ vstSpeakerConfigTypeLRCLsRsLcRcCs, -+ vstSpeakerConfigTypeLRCLsRsCsSlSr, -+ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, -+ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, -+ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 -+}; -+ -+#if JUCE_BIG_ENDIAN -+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) -+#else -+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) -+#endif -+ -+enum PresonusExtensionConstants -+{ -+ presonusVendorID = JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), -+ presonusSetContentScaleFactor = JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's') -+}; -+ -+//============================================================================== -+/** Structure used for VSTs -+ -+ @tags{Audio} -+*/ -+struct fxBank -+{ -+ int32 magic1; -+ int32 size; -+ int32 magic2; -+ int32 version1; -+ int32 fxID; -+ int32 version2; -+ int32 elements; -+ int32 current; -+ char shouldBeZero[124]; -+ int32 chunkSize; -+ char chunk[1]; -+}; -+ -+#if JUCE_MSVC -+ #pragma pack(pop) -+#elif JUCE_MAC || JUCE_IOS -+ #pragma options align=reset -+#else -+ #pragma pack(pop) -+#endif -+ -+#endif // JUCE_VSTINTERFACE_H_INCLUDED diff --git a/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h b/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h index 49f0d3b3b..6e99d63d0 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h @@ -1906,7 +1447,7 @@ index 49f0d3b3b..6e99d63d0 100644 std::free (e); diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp -index 871a69bb1..adf058959 100644 +index 2716d8b70..620ba9874 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -37,7 +37,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant") @@ -1927,7 +1468,7 @@ index 871a69bb1..adf058959 100644 } #include "juce_VSTCommon.h" -@@ -76,7 +74,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) +@@ -77,7 +75,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) #endif #ifndef JUCE_VST_WRAPPER_INVOKE_MAIN @@ -1936,7 +1477,7 @@ index 871a69bb1..adf058959 100644 #endif //============================================================================== -@@ -216,8 +214,8 @@ namespace +@@ -217,8 +215,8 @@ namespace } //============================================================================== @@ -1947,7 +1488,7 @@ index 871a69bb1..adf058959 100644 //============================================================================== // Change this to disable logging of various VST activities -@@ -685,9 +683,9 @@ struct ModuleHandle : public ReferenceCountedObject +@@ -686,9 +684,9 @@ struct ModuleHandle : public ReferenceCountedObject module.close(); } @@ -1959,7 +1500,7 @@ index 871a69bb1..adf058959 100644 } #if JUCE_WINDOWS -@@ -811,9 +809,9 @@ struct ModuleHandle : public ReferenceCountedObject +@@ -812,9 +810,9 @@ struct ModuleHandle : public ReferenceCountedObject } } @@ -1971,7 +1512,7 @@ index 871a69bb1..adf058959 100644 } #endif -@@ -865,7 +863,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -866,7 +864,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { const ScopedLock sl (pluginInstance.lock); @@ -1980,7 +1521,7 @@ index 871a69bb1..adf058959 100644 } return 0.0f; -@@ -877,8 +875,8 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -878,8 +876,8 @@ struct VSTPluginInstance : public AudioPluginInstance, { const ScopedLock sl (pluginInstance.lock); @@ -1991,7 +1532,7 @@ index 871a69bb1..adf058959 100644 } } -@@ -911,7 +909,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -912,7 +910,7 @@ struct VSTPluginInstance : public AudioPluginInstance, if (valueType != nullptr || ! vstValueStrings.isEmpty()) return getText (getValue(), 1024); @@ -2000,7 +1541,7 @@ index 871a69bb1..adf058959 100644 } float getDefaultValue() const override -@@ -923,7 +921,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -924,7 +922,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (name.isEmpty()) return pluginInstance.getTextForOpcode (getParameterIndex(), @@ -2009,7 +1550,7 @@ index 871a69bb1..adf058959 100644 if (name.length() <= maximumStringLength) return name; -@@ -943,7 +941,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -944,7 +942,7 @@ struct VSTPluginInstance : public AudioPluginInstance, String getLabel() const override { return label.isEmpty() ? pluginInstance.getTextForOpcode (getParameterIndex(), @@ -2018,7 +1559,7 @@ index 871a69bb1..adf058959 100644 : label; } -@@ -985,7 +983,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -986,7 +984,7 @@ struct VSTPluginInstance : public AudioPluginInstance, const VSTXMLInfo::ValueType* const valueType; }; @@ -2027,7 +1568,7 @@ index 871a69bb1..adf058959 100644 double sampleRateToUse, int blockSizeToUse) : AudioPluginInstance (ioConfig), vstEffect (effect), -@@ -1008,13 +1006,13 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1009,13 +1007,13 @@ struct VSTPluginInstance : public AudioPluginInstance, { AudioProcessorParameterGroup newParameterTree; @@ -2043,7 +1584,7 @@ index 871a69bb1..adf058959 100644 bool isDiscrete = false; int numSteps = AudioProcessor::getDefaultNumParameterSteps(); bool isBoolSwitch = false; -@@ -1082,7 +1080,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1083,7 +1081,7 @@ struct VSTPluginInstance : public AudioPluginInstance, ~VSTPluginInstance() override { @@ -2052,7 +1593,7 @@ index 871a69bb1..adf058959 100644 { struct VSTDeleter : public CallbackMessage { -@@ -1115,7 +1113,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1116,7 +1114,7 @@ struct VSTPluginInstance : public AudioPluginInstance, void cleanup() { @@ -2061,7 +1602,7 @@ index 871a69bb1..adf058959 100644 { #if JUCE_MAC if (vstModule->resFileId != 0) -@@ -1140,16 +1138,16 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1141,16 +1139,16 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (auto* newEffect = constructEffect (newModule)) { @@ -2083,7 +1624,7 @@ index 871a69bb1..adf058959 100644 BusesProperties ioConfig = queryBusIO (newEffect); return new VSTPluginInstance (newModule, ioConfig, newEffect, initialSampleRate, blockSize); -@@ -1165,7 +1163,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1166,7 +1164,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { char buffer[512] = { 0 }; @@ -2092,7 +1633,7 @@ index 871a69bb1..adf058959 100644 desc.descriptiveName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); -@@ -1182,7 +1180,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1183,7 +1181,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { char buffer[512] = { 0 }; @@ -2101,7 +1642,7 @@ index 871a69bb1..adf058959 100644 desc.manufacturerName = String::createStringFromData (buffer, (int) sizeof (buffer)).trim(); } -@@ -1196,7 +1194,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1197,7 +1195,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (vstEffect != nullptr) { @@ -2110,7 +1651,7 @@ index 871a69bb1..adf058959 100644 initialise (initialSampleRate, initialBlockSize); return true; } -@@ -1222,25 +1220,25 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1223,25 +1221,25 @@ struct VSTPluginInstance : public AudioPluginInstance, setRateAndBufferSizeDetails (initialSampleRate, initialBlockSize); @@ -2143,7 +1684,7 @@ index 871a69bb1..adf058959 100644 if (getVstCategory() != Vst2::kPlugCategShell) // (workaround for Waves 5 plugins which crash during this call) updateStoredProgramNames(); -@@ -1251,7 +1249,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1252,7 +1250,7 @@ struct VSTPluginInstance : public AudioPluginInstance, usesCocoaNSView = ((unsigned int) pluginCanDo ("hasCockosViewAsConfig") & 0xffff0000ul) == 0xbeef0000ul; #endif @@ -2152,7 +1693,7 @@ index 871a69bb1..adf058959 100644 } void* getPlatformSpecificData() override { return vstEffect; } -@@ -1262,7 +1260,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1263,7 +1261,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { char buffer[512] = { 0 }; @@ -2161,7 +1702,7 @@ index 871a69bb1..adf058959 100644 { String productName = String::createStringFromData (buffer, (int) sizeof (buffer)); -@@ -1276,7 +1274,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1277,7 +1275,7 @@ struct VSTPluginInstance : public AudioPluginInstance, int getUID() const { @@ -2170,7 +1711,7 @@ index 871a69bb1..adf058959 100644 if (uid == 0) uid = vstModule->file.hashCode(); -@@ -1289,10 +1287,10 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1290,10 +1288,10 @@ struct VSTPluginInstance : public AudioPluginInstance, if (vstEffect == nullptr) return 0.0; @@ -2183,7 +1724,7 @@ index 871a69bb1..adf058959 100644 auto sampleRate = getSampleRate(); // remain backward compatible with old JUCE plug-ins: anything larger -@@ -1312,11 +1310,11 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1313,11 +1311,11 @@ struct VSTPluginInstance : public AudioPluginInstance, bool producesMidi() const override { return pluginCanDo ("sendVstMidiEvent") > 0; } bool supportsMPE() const override { return pluginCanDo ("MPE") > 0; } @@ -2198,7 +1739,7 @@ index 871a69bb1..adf058959 100644 //============================================================================== void prepareToPlay (double rate, int samplesPerBlockExpected) override -@@ -1331,17 +1329,17 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1332,17 +1330,17 @@ struct VSTPluginInstance : public AudioPluginInstance, SpeakerMappings::VstSpeakerConfigurationHolder inArr (getChannelLayoutOfBus (true, 0)); SpeakerMappings::VstSpeakerConfigurationHolder outArr (getChannelLayoutOfBus (false, 0)); @@ -2224,7 +1765,7 @@ index 871a69bb1..adf058959 100644 initialise (rate, samplesPerBlockExpected); -@@ -1356,18 +1354,18 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1357,18 +1355,18 @@ struct VSTPluginInstance : public AudioPluginInstance, incomingMidi.clear(); @@ -2249,7 +1790,7 @@ index 871a69bb1..adf058959 100644 tmpBufferFloat .setSize (maxChannels, samplesPerBlockExpected); tmpBufferDouble.setSize (maxChannels, samplesPerBlockExpected); -@@ -1375,7 +1373,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1376,7 +1374,7 @@ struct VSTPluginInstance : public AudioPluginInstance, channelBufferFloat .calloc (static_cast (maxChannels)); channelBufferDouble.calloc (static_cast (maxChannels)); @@ -2258,7 +1799,7 @@ index 871a69bb1..adf058959 100644 if (! isPowerOn) setPower (true); -@@ -1391,9 +1389,9 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1392,9 +1390,9 @@ struct VSTPluginInstance : public AudioPluginInstance, } } @@ -2270,7 +1811,7 @@ index 871a69bb1..adf058959 100644 } } -@@ -1401,7 +1399,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1402,7 +1400,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (initialised) { @@ -2279,7 +1820,7 @@ index 871a69bb1..adf058959 100644 setPower (false); } -@@ -1454,8 +1452,8 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1455,8 +1453,8 @@ struct VSTPluginInstance : public AudioPluginInstance, //============================================================================== bool supportsDoublePrecisionProcessing() const override { @@ -2290,7 +1831,7 @@ index 871a69bb1..adf058959 100644 } AudioProcessorParameter* getBypassParameter() const override { return vstSupportsBypass ? bypassParam.get() : nullptr; } -@@ -1473,15 +1471,15 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1474,15 +1472,15 @@ struct VSTPluginInstance : public AudioPluginInstance, if (numInputBuses > 1 || numOutputBuses > 1) return (layouts == getBusesLayout()); @@ -2309,7 +1850,7 @@ index 871a69bb1..adf058959 100644 #endif AudioProcessorEditor* createEditor() override; -@@ -1491,9 +1489,9 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1492,9 +1490,9 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (isValidChannel (index, true)) { @@ -2322,7 +1863,7 @@ index 871a69bb1..adf058959 100644 } return {}; -@@ -1504,9 +1502,9 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1505,9 +1503,9 @@ struct VSTPluginInstance : public AudioPluginInstance, if (! isValidChannel (index, true)) return false; @@ -2335,7 +1876,7 @@ index 871a69bb1..adf058959 100644 return true; } -@@ -1515,9 +1513,9 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1516,9 +1514,9 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (isValidChannel (index, false)) { @@ -2348,7 +1889,7 @@ index 871a69bb1..adf058959 100644 } return {}; -@@ -1528,9 +1526,9 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1529,9 +1527,9 @@ struct VSTPluginInstance : public AudioPluginInstance, if (! isValidChannel (index, false)) return false; @@ -2361,7 +1902,7 @@ index 871a69bb1..adf058959 100644 return true; } -@@ -1545,12 +1543,12 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1546,12 +1544,12 @@ struct VSTPluginInstance : public AudioPluginInstance, int getNumPrograms() override { return vstEffect != nullptr ? jmax (0, vstEffect->numPrograms) : 0; } // NB: some plugs return negative numbers from this function. @@ -2376,7 +1917,7 @@ index 871a69bb1..adf058959 100644 } const String getProgramName (int index) override -@@ -1564,7 +1562,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1565,7 +1563,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { char nm[264] = { 0 }; @@ -2385,7 +1926,7 @@ index 871a69bb1..adf058959 100644 return String::fromUTF8 (nm).trim(); } } -@@ -1577,7 +1575,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1578,7 +1576,7 @@ struct VSTPluginInstance : public AudioPluginInstance, if (index >= 0 && index == getCurrentProgram()) { if (getNumPrograms() > 0 && newName != getCurrentProgramName()) @@ -2394,7 +1935,7 @@ index 871a69bb1..adf058959 100644 } else { -@@ -1595,7 +1593,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1596,7 +1594,7 @@ struct VSTPluginInstance : public AudioPluginInstance, //============================================================================== void timerCallback() override { @@ -2403,7 +1944,7 @@ index 871a69bb1..adf058959 100644 stopTimer(); } -@@ -1609,7 +1607,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1610,7 +1608,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { switch (opcode) { @@ -2412,7 +1953,7 @@ index 871a69bb1..adf058959 100644 if (auto* param = getParameters()[index]) param->sendValueChangedMessageToListeners (opt); else -@@ -1617,23 +1615,23 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1618,23 +1616,23 @@ struct VSTPluginInstance : public AudioPluginInstance, break; @@ -2450,7 +1991,7 @@ index 871a69bb1..adf058959 100644 if (auto* param = getParameters()[index]) param->beginChangeGesture(); else -@@ -1641,7 +1639,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1642,7 +1640,7 @@ struct VSTPluginInstance : public AudioPluginInstance, break; @@ -2459,7 +2000,7 @@ index 871a69bb1..adf058959 100644 if (auto* param = getParameters()[index]) param->endChangeGesture(); else -@@ -1649,28 +1647,28 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1650,28 +1648,28 @@ struct VSTPluginInstance : public AudioPluginInstance, break; @@ -2508,7 +2049,7 @@ index 871a69bb1..adf058959 100644 break; default: -@@ -1685,19 +1683,19 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1686,19 +1684,19 @@ struct VSTPluginInstance : public AudioPluginInstance, { switch (opcode) { @@ -2539,7 +2080,7 @@ index 871a69bb1..adf058959 100644 default: DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); -@@ -1726,7 +1724,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1727,7 +1725,7 @@ struct VSTPluginInstance : public AudioPluginInstance, UseResFile (vstModule->resFileId); #endif @@ -2548,7 +2089,7 @@ index 871a69bb1..adf058959 100644 #if JUCE_MAC auto newResFile = CurResFile(); -@@ -1932,14 +1930,14 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1933,14 +1931,14 @@ struct VSTPluginInstance : public AudioPluginInstance, return true; } @@ -2565,7 +2106,7 @@ index 871a69bb1..adf058959 100644 if (data != nullptr && bytes <= (size_t) maxSizeMB * 1024 * 1024) { -@@ -1957,7 +1955,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1958,7 +1956,7 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (size > 0 && usesChunks()) { @@ -2574,7 +2115,7 @@ index 871a69bb1..adf058959 100644 if (! isPreset) updateStoredProgramNames(); -@@ -1968,7 +1966,7 @@ struct VSTPluginInstance : public AudioPluginInstance, +@@ -1969,7 +1967,7 @@ struct VSTPluginInstance : public AudioPluginInstance, return false; } @@ -2583,7 +2124,7 @@ index 871a69bb1..adf058959 100644 ModuleHandle::Ptr vstModule; std::unique_ptr extraFunctions; -@@ -1991,7 +1989,7 @@ private: +@@ -1992,7 +1990,7 @@ private: currentValue = (newValue != 0.0f); if (parent.vstSupportsBypass) @@ -2592,7 +2133,7 @@ index 871a69bb1..adf058959 100644 } float getValueForText (const String& text) const override -@@ -2038,7 +2036,7 @@ private: +@@ -2039,7 +2037,7 @@ private: CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; @@ -2601,7 +2142,7 @@ index 871a69bb1..adf058959 100644 AudioBuffer tmpBufferFloat; HeapBlock channelBufferFloat; -@@ -2075,7 +2073,7 @@ private: +@@ -2076,7 +2074,7 @@ private: if (auto* app = JUCEApplicationBase::getInstance()) hostName = app->getApplicationName(); @@ -2610,7 +2151,7 @@ index 871a69bb1..adf058959 100644 return 1; } -@@ -2096,7 +2094,7 @@ private: +@@ -2097,7 +2095,7 @@ private: #if JUCE_MAC if (getActiveEditor() != nullptr) @@ -2619,7 +2160,7 @@ index 871a69bb1..adf058959 100644 #endif Timer::callPendingTimersSynchronously(); -@@ -2131,9 +2129,9 @@ private: +@@ -2132,9 +2130,9 @@ private: } //============================================================================== @@ -2631,7 +2172,7 @@ index 871a69bb1..adf058959 100644 try { const IdleCallRecursionPreventer icrp; -@@ -2150,10 +2148,10 @@ private: +@@ -2151,10 +2149,10 @@ private: JUCE_VST_WRAPPER_INVOKE_MAIN } @@ -2645,7 +2186,7 @@ index 871a69bb1..adf058959 100644 _fpreset(); // some dodgy plugs mess around with this } -@@ -2168,11 +2166,11 @@ private: +@@ -2169,11 +2167,11 @@ private: return effect; } @@ -2659,7 +2200,7 @@ index 871a69bb1..adf058959 100644 return returnValue; // Workaround for old broken JUCE plug-ins which would return an invalid -@@ -2183,14 +2181,14 @@ private: +@@ -2184,14 +2182,14 @@ private: // plug-in is reporting. if (! pluginHasDefaultChannelLayouts (effect)) { @@ -2678,7 +2219,7 @@ index 871a69bb1..adf058959 100644 auto* inArr = inArrBlock.get(); auto* outArr = outArrBlock.get(); -@@ -2201,35 +2199,35 @@ private: +@@ -2202,35 +2200,35 @@ private: for (int dir = 0; dir < 2; ++dir) { const bool isInput = (dir == 0); @@ -2723,7 +2264,7 @@ index 871a69bb1..adf058959 100644 } // no buses? -@@ -2237,8 +2235,8 @@ private: +@@ -2238,8 +2236,8 @@ private: { String busName = (isInput ? "Input" : "Output"); @@ -2734,7 +2275,7 @@ index 871a69bb1..adf058959 100644 if (arr != nullptr) layout = SpeakerMappings::vstArrangementTypeToChannelSet (*arr); -@@ -2252,9 +2250,9 @@ private: +@@ -2253,9 +2251,9 @@ private: return returnValue; } @@ -2746,7 +2287,7 @@ index 871a69bb1..adf058959 100644 auto* inArr = inArrBlock.get(); auto* outArr = outArrBlock.get(); -@@ -2265,40 +2263,40 @@ private: +@@ -2266,40 +2264,40 @@ private: for (int dir = 0; dir < 2; ++dir) { const bool isInput = (dir == 0); @@ -2798,7 +2339,7 @@ index 871a69bb1..adf058959 100644 reinterpret_cast (&inArr), &outArr, 0.0f) != 0); } -@@ -2332,57 +2330,57 @@ private: +@@ -2333,57 +2331,57 @@ private: if (currentPlayHead->getCurrentPosition (position)) { @@ -2887,7 +2428,7 @@ index 871a69bb1..adf058959 100644 if (wantsMidiMessages) { -@@ -2393,13 +2391,13 @@ private: +@@ -2394,13 +2392,13 @@ private: midiEventsToSend.addEvent (metadata.data, metadata.numBytes, jlimit (0, numSamples - 1, metadata.samplePosition)); @@ -2903,7 +2444,7 @@ index 871a69bb1..adf058959 100644 auto channels = channelBuffer.get(); if (numChannels < maxChannels) -@@ -2438,35 +2436,35 @@ private: +@@ -2439,35 +2437,35 @@ private: //============================================================================== inline void invokeProcessFunction (AudioBuffer& buffer, int32 sampleFrames) { @@ -2948,7 +2489,7 @@ index 871a69bb1..adf058959 100644 vstHostTime.smpteOffset = (int32) (currentTime * 80.0 * frameRate + 0.5); } -@@ -2497,7 +2495,7 @@ private: +@@ -2498,7 +2496,7 @@ private: if (vstEffect == nullptr) return {}; @@ -2957,7 +2498,7 @@ index 871a69bb1..adf058959 100644 char nm[256] = { 0 }; dispatch (opcode, index, 0, nm, 0); return String::createStringFromData (nm, (int) sizeof (nm)).trim(); -@@ -2511,7 +2509,7 @@ private: +@@ -2512,7 +2510,7 @@ private: { { char nm[256] = { 0 }; @@ -2966,7 +2507,7 @@ index 871a69bb1..adf058959 100644 progName = String::createStringFromData (nm, (int) sizeof (nm)).trim(); } -@@ -2555,7 +2553,7 @@ private: +@@ -2556,7 +2554,7 @@ private: char nm[256] = { 0 }; // only do this if the plugin can't use indexed names.. @@ -2975,7 +2516,7 @@ index 871a69bb1..adf058959 100644 { auto oldProgram = getCurrentProgram(); MemoryBlock oldSettings; -@@ -2573,7 +2571,7 @@ private: +@@ -2574,7 +2572,7 @@ private: } } @@ -2984,7 +2525,7 @@ index 871a69bb1..adf058959 100644 { if (events != nullptr) { -@@ -2620,11 +2618,11 @@ private: +@@ -2621,11 +2619,11 @@ private: } //============================================================================== @@ -2998,7 +2539,7 @@ index 871a69bb1..adf058959 100644 String s; -@@ -2692,7 +2690,6 @@ private: +@@ -2693,7 +2691,6 @@ private: case Vst2::kPlugCategOfflineProcess: return "Offline Process"; case Vst2::kPlugCategShell: return "Shell"; case Vst2::kPlugCategUnknown: return "Unknown"; @@ -3006,7 +2547,7 @@ index 871a69bb1..adf058959 100644 default: break; } -@@ -2701,7 +2698,7 @@ private: +@@ -2702,7 +2699,7 @@ private: void setPower (const bool on) { @@ -3015,7 +2556,7 @@ index 871a69bb1..adf058959 100644 isPowerOn = on; } -@@ -2768,11 +2765,11 @@ public: +@@ -2769,11 +2766,11 @@ public: activeVSTWindows.add (this); @@ -3030,7 +2571,7 @@ index 871a69bb1..adf058959 100644 else setSize (1, 1); -@@ -2944,7 +2941,7 @@ public: +@@ -2943,7 +2940,7 @@ public: nativeScaleFactor = (float) newScaleFactor; if (pluginRespondsToDPIChanges) @@ -3039,7 +2580,7 @@ index 871a69bb1..adf058959 100644 (int) ByteOrder::bigEndianInt ("PreS"), (int) ByteOrder::bigEndianInt ("AeCs"), nullptr, nativeScaleFactor); -@@ -2973,7 +2970,7 @@ public: +@@ -2972,7 +2969,7 @@ public: if (! reentrantGuard) { reentrantGuard = true; @@ -3048,7 +2589,7 @@ index 871a69bb1..adf058959 100644 reentrantGuard = false; } -@@ -3005,7 +3002,7 @@ public: +@@ -3004,7 +3001,7 @@ public: activeVSTWindows.add (this); #if JUCE_MAC @@ -3057,7 +2598,7 @@ index 871a69bb1..adf058959 100644 #endif } -@@ -3035,24 +3032,24 @@ private: +@@ -3034,24 +3031,24 @@ private: isOpen = true; @@ -3090,7 +2631,7 @@ index 871a69bb1..adf058959 100644 if (w == 0 || h == 0) { -@@ -3083,14 +3080,14 @@ private: +@@ -3082,14 +3079,14 @@ private: if (auto* peer = getTopLevelComponent()->getPeer()) setScaleFactorAndDispatchMessage (peer->getPlatformScaleFactor()); @@ -3111,7 +2652,7 @@ index 871a69bb1..adf058959 100644 #if JUCE_WINDOWS originalWndProc = 0; -@@ -3120,8 +3117,8 @@ private: +@@ -3119,8 +3116,8 @@ private: if (rect != nullptr) { @@ -3122,7 +2663,7 @@ index 871a69bb1..adf058959 100644 if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && (! isWithin (w, rw, 2) || ! isWithin (h, rh, 2))) || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) -@@ -3156,8 +3153,8 @@ private: +@@ -3153,8 +3150,8 @@ private: if (rect != nullptr) { @@ -3133,7 +2674,7 @@ index 871a69bb1..adf058959 100644 if (w == 0 || h == 0) { -@@ -3206,7 +3203,7 @@ private: +@@ -3203,7 +3200,7 @@ private: JUCE_VST_LOG ("Closing VST UI: " + plugin.getName()); isOpen = false; @@ -3142,7 +2683,7 @@ index 871a69bb1..adf058959 100644 stopTimer(); #if JUCE_WINDOWS -@@ -3249,11 +3246,11 @@ private: +@@ -3246,11 +3243,11 @@ private: { if (! pluginRespondsToDPIChanges) { @@ -3158,7 +2699,7 @@ index 871a69bb1..adf058959 100644 if (! isWindowSizeCorrectForPlugin (w, h)) { -@@ -3332,17 +3329,17 @@ private: +@@ -3329,17 +3326,17 @@ private: if (owner.isOpen) { owner.isOpen = false; @@ -3182,7 +2723,7 @@ index 871a69bb1..adf058959 100644 return true; } -@@ -3352,7 +3349,7 @@ private: +@@ -3349,7 +3346,7 @@ private: { alreadyInside = true; getTopLevelComponent()->toFront (true); @@ -3191,7 +2732,7 @@ index 871a69bb1..adf058959 100644 alreadyInside = false; } else -@@ -3366,13 +3363,13 @@ private: +@@ -3363,13 +3360,13 @@ private: if (auto* peer = getPeer()) { auto pos = peer->globalToLocal (getScreenPosition()); @@ -3211,7 +2752,7 @@ index 871a69bb1..adf058959 100644 } } -@@ -3439,10 +3436,10 @@ AudioProcessorEditor* VSTPluginInstance::createEditor() +@@ -3436,10 +3433,10 @@ AudioProcessorEditor* VSTPluginInstance::createEditor() //============================================================================== // entry point for all callbacks from the plugin @@ -3224,7 +2765,7 @@ index 871a69bb1..adf058959 100644 return instance->handleCallback (opcode, index, value, ptr, opt); return VSTPluginInstance::handleGeneralCallback (opcode, index, value, ptr, opt); -@@ -3493,7 +3490,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result +@@ -3490,7 +3487,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result // Normal plugin... results.add (new PluginDescription (desc)); @@ -3233,7 +2774,7 @@ index 871a69bb1..adf058959 100644 } else { -@@ -3501,7 +3498,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result +@@ -3498,7 +3495,7 @@ void VSTPluginFormat::findAllTypesForFile (OwnedArray& result for (;;) { char shellEffectName [256] = { 0 }; @@ -3242,7 +2783,7 @@ index 871a69bb1..adf058959 100644 if (uid == 0) break; -@@ -3724,8 +3721,8 @@ void VSTPluginFormat::setExtraFunctions (AudioPluginInstance* plugin, ExtraFunct +@@ -3721,8 +3718,8 @@ void VSTPluginFormat::setExtraFunctions (AudioPluginInstance* plugin, ExtraFunct AudioPluginInstance* VSTPluginFormat::getPluginInstanceFromVstEffectInterface (void* aEffect) { diff --git a/libs/juce-current/patches/04_vst2-interface_file.patch b/libs/juce-current/patches/04_vst2-interface_file.patch new file mode 100644 index 00000000..33806dd3 --- /dev/null +++ b/libs/juce-current/patches/04_vst2-interface_file.patch @@ -0,0 +1,538 @@ +diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h +new file mode 100644 +index 000000000..58179be1a +--- /dev/null ++++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h +@@ -0,0 +1,532 @@ ++/* ++ ============================================================================== ++ ++ This file is part of the JUCE library. ++ Copyright (c) 2017 - ROLI Ltd. ++ ++ JUCE is an open source library subject to commercial or open-source ++ licensing. ++ ++ By using JUCE, you agree to the terms of both the JUCE 5 End-User License ++ Agreement and JUCE 5 Privacy Policy (both updated and effective as of the ++ 27th April 2017). ++ ++ End User License Agreement: www.juce.com/juce-5-licence ++ Privacy Policy: www.juce.com/juce-5-privacy-policy ++ ++ Or: You may also use this code under the terms of the GPL v3 (see ++ www.gnu.org/licenses). ++ ++ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER ++ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE ++ DISCLAIMED. ++ ++ ============================================================================== ++*/ ++ ++#ifndef JUCE_VSTINTERFACE_H_INCLUDED ++#define JUCE_VSTINTERFACE_H_INCLUDED ++ ++using namespace juce; ++ ++#if JUCE_MSVC ++ #define VSTINTERFACECALL __cdecl ++ #pragma pack(push) ++ #pragma pack(8) ++#elif JUCE_MAC || JUCE_IOS ++ #define VSTINTERFACECALL ++ #if JUCE_64BIT ++ #pragma options align=power ++ #else ++ #pragma options align=mac68k ++ #endif ++#else ++ #define VSTINTERFACECALL ++ #pragma pack(push, 8) ++#endif ++ ++const int32 juceVstInterfaceVersion = 2400; ++const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstEffectInterface ++{ ++ int32 interfaceIdentifier; ++ pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); ++ void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); ++ void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); ++ float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); ++ int32 numPrograms; ++ int32 numParameters; ++ int32 numInputChannels; ++ int32 numOutputChannels; ++ int32 flags; ++ pointer_sized_int hostSpace1; ++ pointer_sized_int hostSpace2; ++ int32 latency; ++ int32 deprecated1; ++ int32 deprecated2; ++ float deprecated3; ++ void* effectPointer; ++ void* userPointer; ++ int32 plugInIdentifier; ++ int32 plugInVersion; ++ void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); ++ void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); ++ char emptySpace[56]; ++}; ++ ++typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); ++ ++enum VstEffectInterfaceFlags ++{ ++ vstEffectFlagHasEditor = 1, ++ vstEffectFlagInplaceAudio = 16, ++ vstEffectFlagDataInChunks = 32, ++ vstEffectFlagIsSynth = 256, ++ vstEffectFlagInplaceDoubleAudio = 4096 ++}; ++ ++//============================================================================== ++enum VstHostToPlugInOpcodes ++{ ++ plugInOpcodeOpen, ++ plugInOpcodeClose, ++ plugInOpcodeSetCurrentProgram, ++ plugInOpcodeGetCurrentProgram, ++ plugInOpcodeSetCurrentProgramName, ++ plugInOpcodeGetCurrentProgramName, ++ plugInOpcodeGetParameterLabel, ++ plugInOpcodeGetParameterText, ++ plugInOpcodeGetParameterName, ++ plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, ++ plugInOpcodeSetBlockSize, ++ plugInOpcodeResumeSuspend, ++ plugInOpcodeGetEditorBounds, ++ plugInOpcodeOpenEditor, ++ plugInOpcodeCloseEditor, ++ plugInOpcodeDrawEditor, ++ plugInOpcodeGetMouse, ++ plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, ++ plugInOpcodeeffEditorTop, ++ plugInOpcodeSleepEditor, ++ plugInOpcodeIdentify, ++ plugInOpcodeGetData, ++ plugInOpcodeSetData, ++ plugInOpcodePreAudioProcessingEvents, ++ plugInOpcodeIsParameterAutomatable, ++ plugInOpcodeParameterValueForText, ++ plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, ++ plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, ++ plugInOpcodeConnectOutput, ++ plugInOpcodeGetInputPinProperties, ++ plugInOpcodeGetOutputPinProperties, ++ plugInOpcodeGetPlugInCategory, ++ plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, ++ plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, ++ plugInOpcodeGetPlugInName, ++ plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, ++ plugInOpcodeGetManufacturerProductName, ++ plugInOpcodeGetManufacturerVersion, ++ plugInOpcodeManufacturerSpecific, ++ plugInOpcodeCanPlugInDo, ++ plugInOpcodeGetTailSize, ++ plugInOpcodeIdle, ++ plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, ++ plugInOpcodeGetVstInterfaceVersion, ++ plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, ++ plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, ++ plugInOpcodeNextPlugInUniqueID, ++ plugInOpcodeStartProcess, ++ plugInOpcodeStopProcess, ++ plugInOpcodeSetNumberOfSamplesToProcess, ++ plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, ++ pluginOpcodeGetNumMidiInputChannels, ++ pluginOpcodeGetNumMidiOutputChannels, ++ plugInOpcodeMaximum = pluginOpcodeGetNumMidiOutputChannels ++}; ++ ++ ++enum VstPlugInToHostOpcodes ++{ ++ hostOpcodeParameterChanged, ++ hostOpcodeVstVersion, ++ hostOpcodeCurrentId, ++ hostOpcodeIdle, ++ hostOpcodePinConnected, ++ hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, ++ hostOpcodeGetTimingInfo, ++ hostOpcodePreAudioProcessingEvents, ++ hostOpcodeSetTime, ++ hostOpcodeTempoAt, ++ hostOpcodeGetNumberOfAutomatableParameters, ++ hostOpcodeGetParameterInterval, ++ hostOpcodeIOModified, ++ hostOpcodeNeedsIdle, ++ hostOpcodeWindowSize, ++ hostOpcodeGetSampleRate, ++ hostOpcodeGetBlockSize, ++ hostOpcodeGetInputLatency, ++ hostOpcodeGetOutputLatency, ++ hostOpcodeGetPreviousPlugIn, ++ hostOpcodeGetNextPlugIn, ++ hostOpcodeWillReplace, ++ hostOpcodeGetCurrentAudioProcessingLevel, ++ hostOpcodeGetAutomationState, ++ hostOpcodeOfflineStart, ++ hostOpcodeOfflineReadSource, ++ hostOpcodeOfflineWrite, ++ hostOpcodeOfflineGetCurrentPass, ++ hostOpcodeOfflineGetCurrentMetaPass, ++ hostOpcodeSetOutputSampleRate, ++ hostOpcodeGetOutputSpeakerConfiguration, ++ hostOpcodeGetManufacturerName, ++ hostOpcodeGetProductName, ++ hostOpcodeGetManufacturerVersion, ++ hostOpcodeManufacturerSpecific, ++ hostOpcodeSetIcon, ++ hostOpcodeCanHostDo, ++ hostOpcodeGetLanguage, ++ hostOpcodeOpenEditorWindow, ++ hostOpcodeCloseEditorWindow, ++ hostOpcodeGetDirectory, ++ hostOpcodeUpdateView, ++ hostOpcodeParameterChangeGestureBegin, ++ hostOpcodeParameterChangeGestureEnd, ++}; ++ ++//============================================================================== ++enum VstProcessingSampleType ++{ ++ vstProcessingSampleTypeFloat, ++ vstProcessingSampleTypeDouble ++}; ++ ++//============================================================================== ++// These names must be identical to the Steinberg SDK so JUCE users can set ++// exactly what they want. ++enum VstPlugInCategory ++{ ++ kPlugCategUnknown, ++ kPlugCategEffect, ++ kPlugCategSynth, ++ kPlugCategAnalysis, ++ kPlugCategMastering, ++ kPlugCategSpacializer, ++ kPlugCategRoomFx, ++ kPlugSurroundFx, ++ kPlugCategRestoration, ++ kPlugCategOfflineProcess, ++ kPlugCategShell, ++ kPlugCategGenerator ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstEditorBounds ++{ ++ int16 upper; ++ int16 leftmost; ++ int16 lower; ++ int16 rightmost; ++}; ++ ++//============================================================================== ++enum VstMaxStringLengths ++{ ++ vstMaxNameLength = 64, ++ vstMaxParameterOrPinLabelLength = 64, ++ vstMaxParameterOrPinShortLabelLength = 8, ++ vstMaxCategoryLength = 24, ++ vstMaxManufacturerStringLength = 64, ++ vstMaxPlugInNameStringLength = 64 ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstPinInfo ++{ ++ char text[vstMaxParameterOrPinLabelLength]; ++ int32 flags; ++ int32 configurationType; ++ char shortText[vstMaxParameterOrPinShortLabelLength]; ++ char unused[48]; ++}; ++ ++enum VstPinInfoFlags ++{ ++ vstPinInfoFlagIsActive = 1, ++ vstPinInfoFlagIsStereo = 2, ++ vstPinInfoFlagValid = 4 ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstEvent ++{ ++ int32 type; ++ int32 size; ++ int32 sampleOffset; ++ int32 flags; ++ char content[16]; ++}; ++ ++enum VstEventTypes ++{ ++ vstMidiEventType = 1, ++ vstSysExEventType = 6 ++}; ++ ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstEventBlock ++{ ++ int32 numberOfEvents; ++ pointer_sized_int future; ++ VstEvent* events[2]; ++}; ++ ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstMidiEvent ++{ ++ int32 type; ++ int32 size; ++ int32 sampleOffset; ++ int32 flags; ++ int32 noteSampleLength; ++ int32 noteSampleOffset; ++ char midiData[4]; ++ char tuning; ++ char noteVelocityOff; ++ char future1; ++ char future2; ++}; ++ ++enum VstMidiEventFlags ++{ ++ vstMidiEventIsRealtime = 1 ++}; ++ ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstSysExEvent ++{ ++ int32 type; ++ int32 size; ++ int32 offsetSamples; ++ int32 flags; ++ int32 sysExDumpSize; ++ pointer_sized_int future1; ++ char* sysExDump; ++ pointer_sized_int future2; ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstTimingInformation ++{ ++ double samplePosition; ++ double sampleRate; ++ double systemTimeNanoseconds; ++ double musicalPosition; ++ double tempoBPM; ++ double lastBarPosition; ++ double loopStartPosition; ++ double loopEndPosition; ++ int32 timeSignatureNumerator; ++ int32 timeSignatureDenominator; ++ int32 smpteOffset; ++ int32 smpteRate; ++ int32 samplesToNearestClock; ++ int32 flags; ++}; ++ ++enum VstTimingInformationFlags ++{ ++ vstTimingInfoFlagTransportChanged = 1, ++ vstTimingInfoFlagCurrentlyPlaying = 2, ++ vstTimingInfoFlagLoopActive = 4, ++ vstTimingInfoFlagCurrentlyRecording = 8, ++ vstTimingInfoFlagAutomationWriteModeActive = 64, ++ vstTimingInfoFlagAutomationReadModeActive = 128, ++ vstTimingInfoFlagNanosecondsValid = 256, ++ vstTimingInfoFlagMusicalPositionValid = 512, ++ vstTimingInfoFlagTempoValid = 1024, ++ vstTimingInfoFlagLastBarPositionValid = 2048, ++ vstTimingInfoFlagLoopPositionValid = 4096, ++ vstTimingInfoFlagTimeSignatureValid = 8192, ++ vstTimingInfoFlagSmpteValid = 16384, ++ vstTimingInfoFlagNearestClockValid = 32768 ++}; ++ ++//============================================================================== ++enum VstSmpteRates ++{ ++ vstSmpteRateFps24, ++ vstSmpteRateFps25, ++ vstSmpteRateFps2997, ++ vstSmpteRateFps30, ++ vstSmpteRateFps2997drop, ++ vstSmpteRateFps30drop, ++ ++ vstSmpteRate16mmFilm, ++ vstSmpteRate35mmFilm, ++ ++ vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, ++ vstSmpteRateFps249 , ++ vstSmpteRateFps599, ++ vstSmpteRateFps60 ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstIndividualSpeakerInfo ++{ ++ float azimuthalAngle; ++ float elevationAngle; ++ float radius; ++ float reserved; ++ char label[vstMaxNameLength]; ++ int32 type; ++ char unused[28]; ++}; ++ ++enum VstIndividualSpeakerType ++{ ++ vstIndividualSpeakerTypeUndefined = 0x7fffffff, ++ vstIndividualSpeakerTypeMono = 0, ++ vstIndividualSpeakerTypeLeft, ++ vstIndividualSpeakerTypeRight, ++ vstIndividualSpeakerTypeCentre, ++ vstIndividualSpeakerTypeLFE, ++ vstIndividualSpeakerTypeLeftSurround, ++ vstIndividualSpeakerTypeRightSurround, ++ vstIndividualSpeakerTypeLeftCentre, ++ vstIndividualSpeakerTypeRightCentre, ++ vstIndividualSpeakerTypeSurround, ++ vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, ++ vstIndividualSpeakerTypeLeftRearSurround, ++ vstIndividualSpeakerTypeRightRearSurround, ++ vstIndividualSpeakerTypeTopMiddle, ++ vstIndividualSpeakerTypeTopFrontLeft, ++ vstIndividualSpeakerTypeTopFrontCentre, ++ vstIndividualSpeakerTypeTopFrontRight, ++ vstIndividualSpeakerTypeTopRearLeft, ++ vstIndividualSpeakerTypeTopRearCentre, ++ vstIndividualSpeakerTypeTopRearRight, ++ vstIndividualSpeakerTypeLFE2 ++}; ++ ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct VstSpeakerConfiguration ++{ ++ int32 type; ++ int32 numberOfChannels; ++ VstIndividualSpeakerInfo speakers[8]; ++}; ++ ++enum VstSpeakerConfigurationType ++{ ++ vstSpeakerConfigTypeUser = -2, ++ vstSpeakerConfigTypeEmpty = -1, ++ vstSpeakerConfigTypeMono = 0, ++ vstSpeakerConfigTypeLR, ++ vstSpeakerConfigTypeLsRs, ++ vstSpeakerConfigTypeLcRc, ++ vstSpeakerConfigTypeSlSr, ++ vstSpeakerConfigTypeCLfe, ++ vstSpeakerConfigTypeLRC, ++ vstSpeakerConfigTypeLRS, ++ vstSpeakerConfigTypeLRCLfe, ++ vstSpeakerConfigTypeLRLfeS, ++ vstSpeakerConfigTypeLRCS, ++ vstSpeakerConfigTypeLRLsRs, ++ vstSpeakerConfigTypeLRCLfeS, ++ vstSpeakerConfigTypeLRLfeLsRs, ++ vstSpeakerConfigTypeLRCLsRs, ++ vstSpeakerConfigTypeLRCLfeLsRs, ++ vstSpeakerConfigTypeLRCLsRsCs, ++ vstSpeakerConfigTypeLRLsRsSlSr, ++ vstSpeakerConfigTypeLRCLfeLsRsCs, ++ vstSpeakerConfigTypeLRLfeLsRsSlSr, ++ vstSpeakerConfigTypeLRCLsRsLcRc, ++ vstSpeakerConfigTypeLRCLsRsSlSr, ++ vstSpeakerConfigTypeLRCLfeLsRsLcRc, ++ vstSpeakerConfigTypeLRCLfeLsRsSlSr, ++ vstSpeakerConfigTypeLRCLsRsLcRcCs, ++ vstSpeakerConfigTypeLRCLsRsCsSlSr, ++ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, ++ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, ++ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 ++}; ++ ++#if JUCE_BIG_ENDIAN ++ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) ++#else ++ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) ++#endif ++ ++enum PresonusExtensionConstants ++{ ++ presonusVendorID = JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), ++ presonusSetContentScaleFactor = JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's') ++}; ++ ++//============================================================================== ++/** Structure used for VSTs ++ ++ @tags{Audio} ++*/ ++struct vst2FxBank ++{ ++ int32 magic1; ++ int32 size; ++ int32 magic2; ++ int32 version1; ++ int32 fxID; ++ int32 version2; ++ int32 elements; ++ int32 current; ++ char shouldBeZero[124]; ++ int32 chunkSize; ++ char chunk[1]; ++}; ++ ++#if JUCE_MSVC ++ #pragma pack(pop) ++#elif JUCE_MAC || JUCE_IOS ++ #pragma options align=reset ++#else ++ #pragma pack(pop) ++#endif ++ ++#endif // JUCE_VSTINTERFACE_H_INCLUDED diff --git a/libs/juce-current/patches/04_vst2-interface_pt2.patch b/libs/juce-current/patches/04_vst2-interface_pt2.patch deleted file mode 100644 index e5a3657a..00000000 --- a/libs/juce-current/patches/04_vst2-interface_pt2.patch +++ /dev/null @@ -1,78 +0,0 @@ -diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp -index 3e63d3372..c09d99fc3 100644 ---- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp -+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp -@@ -61,10 +61,7 @@ - #define __cdecl - #endif - -- namespace Vst2 -- { -- #include "pluginterfaces/vst2.x/vstfxstore.h" -- } -+ #include - - #endif - -@@ -1967,16 +1964,16 @@ public: - - bool loadVST2CcnKBlock (const char* data, int size) - { -- auto* bank = reinterpret_cast (data); -+ auto* bank = reinterpret_cast (data); - -- jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); -- jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); -- jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); -+ jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->magic1)); -+ jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->magic2)); -+ jassert (htonl ((uint32) bank->version1) == 1 || htonl ((uint32) bank->version1) == 2); - jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); - -- setStateInformation (bank->content.data.chunk, -- jmin ((int) (size - (bank->content.data.chunk - data)), -- (int) htonl ((uint32) bank->content.data.size))); -+ setStateInformation (bank->chunk, -+ jmin ((int) (size - (bank->chunk - data)), -+ (int) htonl ((uint32) bank->chunkSize))); - return true; - } - -@@ -2172,16 +2169,16 @@ public: - return status; - - const int bankBlockSize = 160; -- Vst2::fxBank bank; -+ vst2FxBank bank; - - zerostruct (bank); -- bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); -- bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); -- bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); -- bank.version = (int32) htonl (2); -- bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); -- bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); -- bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); -+ bank.magic1 = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); -+ bank.size = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); -+ bank.magic2 = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); -+ bank.version1 = (int32) htonl (2); -+ bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); -+ bank.version2 = (int32) htonl (JucePlugin_VersionCode); -+ bank.chunkSize = (int32) htonl ((unsigned int) mem.getSize()); - - status = state->write (&bank, bankBlockSize); - -diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h -index 1b3eb7d08..58179be1a 100644 ---- a/modules/juce_audio_processors/format_types/juce_VSTInterface.h -+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h -@@ -506,7 +506,7 @@ enum PresonusExtensionConstants - - @tags{Audio} - */ --struct fxBank -+struct vst2FxBank - { - int32 magic1; - int32 size; diff --git a/libs/juce-current/patches/05_mingw-fixes.patch b/libs/juce-current/patches/05_mingw-fixes.patch index 6fb815ba..aca522b5 100644 --- a/libs/juce-current/patches/05_mingw-fixes.patch +++ b/libs/juce-current/patches/05_mingw-fixes.patch @@ -1,5 +1,5 @@ diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -index 23484732b..d2106179c 100644 +index 8683dc728..94f6bf569 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -61,7 +61,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) @@ -28,10 +28,10 @@ index 6bea84307..baaa59f06 100644 #if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) #error "You need to define the JucePlugin_LV2URI value!" diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp -index adf058959..c39d6f796 100644 +index 620ba9874..d9359b736 100644 --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp -@@ -61,9 +61,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) +@@ -62,9 +62,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) #ifndef WM_APPCOMMAND #define WM_APPCOMMAND 0x0319 #endif diff --git a/libs/juce-current/patches/06_old-compiler-compatibility.patch b/libs/juce-current/patches/06_old-compiler-compatibility.patch index 411f3c6d..119fae09 100644 --- a/libs/juce-current/patches/06_old-compiler-compatibility.patch +++ b/libs/juce-current/patches/06_old-compiler-compatibility.patch @@ -1,5 +1,5 @@ diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h -index 656ab3321..c40e854aa 100644 +index acd165ff0..bdc0bf58f 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -20,6 +20,11 @@ @@ -30,10 +30,10 @@ index ac5ce32da..f0cd938ae 100644 { kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, { kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm -index 892312098..411bf2097 100644 +index 9aa4a338e..7cad3e7c1 100644 --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm -@@ -58,6 +58,10 @@ +@@ -59,6 +59,10 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") namespace juce { @@ -44,7 +44,7 @@ index 892312098..411bf2097 100644 // Change this to disable logging of various activities #ifndef AU_LOGGING #define AU_LOGGING 1 -@@ -274,7 +278,7 @@ namespace AudioUnitFormatHelpers +@@ -276,7 +280,7 @@ namespace AudioUnitFormatHelpers NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; @@ -54,7 +54,7 @@ index 892312098..411bf2097 100644 desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h -index 02c3de12e..ae60fde15 100644 +index 39eac8211..f556f12b3 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1184,7 +1184,7 @@ public: @@ -67,7 +67,7 @@ index 02c3de12e..ae60fde15 100644 //============================================================================== /** Not for public use - this is called before deleting an editor component. */ diff --git a/modules/juce_core/native/juce_osx_ObjCHelpers.h b/modules/juce_core/native/juce_osx_ObjCHelpers.h -index 407d263d0..f2dc28e36 100644 +index a9a7c8eb7..aa068eaa0 100644 --- a/modules/juce_core/native/juce_osx_ObjCHelpers.h +++ b/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -71,7 +71,7 @@ inline NSArray* varArrayToNSArray (const var& varToParse); @@ -99,7 +99,7 @@ index 407d263d0..f2dc28e36 100644 return var (dynamicObject.get()); } diff --git a/modules/juce_core/system/juce_CompilerSupport.h b/modules/juce_core/system/juce_CompilerSupport.h -index e4d87ab97..ddeef94a5 100644 +index 4d96d7cb0..d76d92d49 100644 --- a/modules/juce_core/system/juce_CompilerSupport.h +++ b/modules/juce_core/system/juce_CompilerSupport.h @@ -92,7 +92,7 @@ @@ -112,7 +112,7 @@ index e4d87ab97..ddeef94a5 100644 #endif diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h -index 52db7c6d6..96ce314d8 100644 +index 43000cab6..0a1798ba2 100644 --- a/modules/juce_core/system/juce_PlatformDefs.h +++ b/modules/juce_core/system/juce_PlatformDefs.h @@ -99,11 +99,7 @@ namespace juce @@ -129,10 +129,10 @@ index 52db7c6d6..96ce314d8 100644 #if __GNUC__ >= 7 #define JUCE_FALLTHROUGH [[gnu::fallthrough]]; diff --git a/modules/juce_core/system/juce_TargetPlatform.h b/modules/juce_core/system/juce_TargetPlatform.h -index cf610da9a..9173cc34d 100644 +index 5b4d293bb..6b41688b2 100644 --- a/modules/juce_core/system/juce_TargetPlatform.h +++ b/modules/juce_core/system/juce_TargetPlatform.h -@@ -144,8 +144,8 @@ +@@ -145,8 +145,8 @@ #endif #if JUCE_MAC @@ -144,7 +144,7 @@ index cf610da9a..9173cc34d 100644 #error "Building for OSX 10.6 is no longer supported!" #endif diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm -index 37bf13094..60467ef61 100644 +index de6ffecfc..2a85d25f5 100644 --- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -26,6 +26,10 @@ @@ -159,10 +159,10 @@ index 37bf13094..60467ef61 100644 // This class has been renamed from CoreGraphicsImage to avoid a symbol // collision in Pro Tools 2019.12 and possibly 2020 depending on the Pro Tools diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm -index b6a918ce1..65b094343 100644 +index ff0a23a9f..88142091d 100644 --- a/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/modules/juce_graphics/native/juce_mac_Fonts.mm -@@ -362,20 +362,20 @@ namespace CoreTextTypeLayout +@@ -359,20 +359,20 @@ namespace CoreTextTypeLayout auto verticalJustification = text.getJustification().getOnlyVerticalFlags(); @@ -188,10 +188,10 @@ index b6a918ce1..65b094343 100644 auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm -index 7adb2a869..48ae2951b 100644 +index 26ca40630..bd5e69ab0 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm -@@ -1528,17 +1528,21 @@ private: +@@ -1583,17 +1583,21 @@ private: case NSEventTypeSystemDefined: case NSEventTypeApplicationDefined: case NSEventTypePeriodic: @@ -215,7 +215,7 @@ index 7adb2a869..48ae2951b 100644 #if JUCE_64BIT case NSEventTypeDirectTouch: diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm -index 5ffbe393c..ddb5aedc5 100644 +index 918f0b4a6..63e15d07b 100644 --- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -309,7 +309,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi @@ -228,10 +228,10 @@ index 5ffbe393c..ddb5aedc5 100644 auto eventPos = [event locationInWindow]; diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp -index cf8df3b72..6f8417340 100644 +index b8e6e0c7e..6ad01c68a 100644 --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp -@@ -3054,7 +3054,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, +@@ -3329,7 +3329,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XBut peer->toFront (true); peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, getLogicalMousePos (buttonPressEvent, peer->getPlatformScaleFactor()), ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, @@ -239,12 +239,12 @@ index cf8df3b72..6f8417340 100644 + MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent)); } - void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, const XButtonPressedEvent& buttonPressEvent) const + void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent) const diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h -index 3dacc095a..820987e94 100644 +index 691e5679b..ed4f3c199 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h -@@ -314,7 +314,7 @@ public: +@@ -321,7 +321,7 @@ public: //============================================================================== void handleMouseEvent (MouseInputSource::InputSourceType type, Point positionWithinPeer, ModifierKeys newMods, float pressure, @@ -254,10 +254,10 @@ index 3dacc095a..820987e94 100644 void handleMouseWheel (MouseInputSource::InputSourceType type, Point positionWithinPeer, int64 time, const MouseWheelDetails&, int touchIndex = 0); diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp -index d4ebed7fd..df48cceaf 100644 +index 9cf367411..d42555e13 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp -@@ -142,7 +142,9 @@ +@@ -136,7 +136,9 @@ #include "misc/juce_PushNotifications.cpp" #include "misc/juce_RecentlyOpenedFilesList.cpp" #include "misc/juce_SplashScreen.cpp" @@ -267,7 +267,7 @@ index d4ebed7fd..df48cceaf 100644 #include "misc/juce_LiveConstantEditor.cpp" #include "misc/juce_AnimatedAppComponent.cpp" -@@ -154,7 +156,9 @@ +@@ -146,7 +148,9 @@ #if JUCE_MAC #include "native/juce_mac_NSViewComponent.mm" #include "native/juce_mac_AppleRemote.mm" @@ -279,7 +279,7 @@ index d4ebed7fd..df48cceaf 100644 #if JUCE_IOS diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h -index 822c1885e..77e78c87a 100644 +index bcec491c9..062bde636 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -115,7 +115,9 @@ diff --git a/libs/juce-current/patches/09_proper-getExecutableFile.patch b/libs/juce-current/patches/09_proper-getExecutableFile.patch index f60aa353..25743f66 100644 --- a/libs/juce-current/patches/09_proper-getExecutableFile.patch +++ b/libs/juce-current/patches/09_proper-getExecutableFile.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h -index 982db865b..cfdd2d1cd 100644 +index 082a5697a..c79ca18ea 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h -@@ -590,12 +590,39 @@ File juce_getExecutableFile() +@@ -600,12 +600,39 @@ File juce_getExecutableFile() auto localSymbol = (void*) juce_getExecutableFile; dladdr (localSymbol, &exeInfo); diff --git a/libs/juce-current/patches/10_fix-fork-exec-usage.patch b/libs/juce-current/patches/10_fix-fork-exec-usage.patch index 1b38ab57..82c3f452 100644 --- a/libs/juce-current/patches/10_fix-fork-exec-usage.patch +++ b/libs/juce-current/patches/10_fix-fork-exec-usage.patch @@ -28,7 +28,7 @@ index d2a302e3e..3dc4602ce 100644 return cpid >= 0; diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm -index d26443079..616677e1f 100644 +index 1a4d07516..f385a089f 100644 --- a/modules/juce_core/native/juce_mac_Files.mm +++ b/modules/juce_core/native/juce_mac_Files.mm @@ -92,23 +92,22 @@ namespace MacFileHelpers @@ -65,10 +65,10 @@ index d26443079..616677e1f 100644 #endif } diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h -index cfdd2d1cd..332a64959 100644 +index c79ca18ea..ad46cf390 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h -@@ -1092,7 +1092,18 @@ public: +@@ -1104,7 +1104,18 @@ public: if (pipe (pipeHandles) == 0) { @@ -88,7 +88,7 @@ index cfdd2d1cd..332a64959 100644 if (result < 0) { -@@ -1101,6 +1112,7 @@ public: +@@ -1113,6 +1124,7 @@ public: } else if (result == 0) { @@ -96,7 +96,7 @@ index cfdd2d1cd..332a64959 100644 // we're the child process.. close (pipeHandles[0]); // close the read handle -@@ -1115,17 +1127,10 @@ public: +@@ -1127,17 +1139,10 @@ public: dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO); close (pipeHandles[1]); diff --git a/libs/juce-current/patches/11_childprocess-getPID.patch b/libs/juce-current/patches/11_childprocess-getPID.patch index f4b466e6..0f916b59 100644 --- a/libs/juce-current/patches/11_childprocess-getPID.patch +++ b/libs/juce-current/patches/11_childprocess-getPID.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h -index 332a64959..c6d95a93a 100644 +index ad46cf390..13724ba7c 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h -@@ -1227,6 +1227,11 @@ public: +@@ -1239,6 +1239,11 @@ public: return 0; } @@ -15,7 +15,7 @@ index 332a64959..c6d95a93a 100644 int pipeHandle = 0; int exitCode = -1; diff --git a/modules/juce_core/native/juce_win32_Threads.cpp b/modules/juce_core/native/juce_win32_Threads.cpp -index dbe0c6b5d..098416bd9 100644 +index 4a4148119..1c38ff2cf 100644 --- a/modules/juce_core/native/juce_win32_Threads.cpp +++ b/modules/juce_core/native/juce_win32_Threads.cpp @@ -477,6 +477,11 @@ public: diff --git a/libs/juce-current/patches/12_linux-filechooser-needs-modal-loops.patch b/libs/juce-current/patches/12_linux-filechooser-needs-modal-loops.patch index bcf52f0a..5782695a 100644 --- a/libs/juce-current/patches/12_linux-filechooser-needs-modal-loops.patch +++ b/libs/juce-current/patches/12_linux-filechooser-needs-modal-loops.patch @@ -1,5 +1,5 @@ diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp -index 97327c59e..0eca4dfb6 100644 +index b799ee2e4..803740c63 100644 --- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -26,6 +26,7 @@ @@ -10,7 +10,7 @@ index 97327c59e..0eca4dfb6 100644 static bool exeIsAvailable (String executable) { ChildProcess child; -@@ -241,10 +242,11 @@ private: +@@ -245,10 +246,11 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) }; @@ -23,7 +23,7 @@ index 97327c59e..0eca4dfb6 100644 return false; #else static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog"); -@@ -254,7 +256,11 @@ bool FileChooser::isPlatformDialogAvailable() +@@ -258,7 +260,11 @@ bool FileChooser::isPlatformDialogAvailable() FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { diff --git a/libs/juce-current/patches/14_fix-x11-temporary-windows.patch b/libs/juce-current/patches/14_fix-x11-temporary-windows.patch index 332abd57..976d46e6 100644 --- a/libs/juce-current/patches/14_fix-x11-temporary-windows.patch +++ b/libs/juce-current/patches/14_fix-x11-temporary-windows.patch @@ -1,16 +1,23 @@ diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp -index 6f8417340..5e8603833 100644 +index 6ad01c68a..490a3a792 100644 --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp -@@ -2561,8 +2561,9 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const +@@ -2804,10 +2804,14 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const - Atom netHints [2]; + if (atoms.windowType != None) + { +- auto hint = (styleFlags & ComponentPeer::windowIsTemporary) != 0 +- || ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) +- ? XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO") +- : XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); ++ Atom hint = None; ++ ++ if (styleFlags & ComponentPeer::windowIsTemporary) ++ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); ++ else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) ++ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); ++ else ++ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); -- if ((styleFlags & ComponentPeer::windowIsTemporary) != 0 -- || ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows())) -+ if (styleFlags & ComponentPeer::windowIsTemporary) -+ netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); -+ else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) - netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); - else - netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); + if (hint != None) + xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &hint, 1); diff --git a/libs/juce-current/patches/15_fix-linux-thread-prio.patch b/libs/juce-current/patches/15_fix-linux-thread-prio.patch index 359241fe..0d875f85 100644 --- a/libs/juce-current/patches/15_fix-linux-thread-prio.patch +++ b/libs/juce-current/patches/15_fix-linux-thread-prio.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h -index c6d95a93a..9c9053ac4 100644 +index 13724ba7c..f8892081c 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h -@@ -977,7 +977,11 @@ bool Thread::setThreadPriority (void* handle, int priority) +@@ -989,7 +989,11 @@ bool Thread::setThreadPriority (void* handle, int priority) if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) return false; diff --git a/libs/juce-current/patches/17_allow-vst2-without-audio.patch b/libs/juce-current/patches/17_allow-vst2-without-audio.patch index f05b79b4..0d7055a9 100644 --- a/libs/juce-current/patches/17_allow-vst2-without-audio.patch +++ b/libs/juce-current/patches/17_allow-vst2-without-audio.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -index c5e96d204..7e2122580 100644 +index 94f6bf569..ddbe6fd65 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -@@ -301,9 +301,6 @@ public: +@@ -294,9 +294,6 @@ public: // You must at least have some channels jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); diff --git a/libs/juce-current/patches/19_fix-vst2-linux-gui-init.patch b/libs/juce-current/patches/19_fix-vst2-linux-gui-init.patch index b1ec45bb..ff9334a7 100644 --- a/libs/juce-current/patches/19_fix-vst2-linux-gui-init.patch +++ b/libs/juce-current/patches/19_fix-vst2-linux-gui-init.patch @@ -1,15 +1,17 @@ diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -index cff9b7a88..3ae14372c 100644 +index ebc9325ca..cabbbcf13 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -@@ -209,9 +209,9 @@ struct SharedMessageThread : public Thread +@@ -203,11 +203,11 @@ struct SharedMessageThread : public Thread void run() override { initialiseJuce_GUI(); - initialised = true; MessageManager::getInstance()->setCurrentThreadAsMessageThread(); -+ initialised = true; XWindowSystem::getInstance(); ++ initialised = true; + while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) + {} diff --git a/libs/juce-current/patches/21_audioprocessor-no-gui.patch b/libs/juce-current/patches/21_audioprocessor-no-gui.patch index c02d9a01..d0bf3013 100644 --- a/libs/juce-current/patches/21_audioprocessor-no-gui.patch +++ b/libs/juce-current/patches/21_audioprocessor-no-gui.patch @@ -1,5 +1,5 @@ diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp -index ac194a19d..f494ec3a2 100644 +index 43ad88e1b..149154804 100644 --- a/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/modules/juce_audio_processors/juce_audio_processors.cpp @@ -47,7 +47,7 @@ @@ -11,7 +11,7 @@ index ac194a19d..f494ec3a2 100644 #include #include #include -@@ -141,9 +141,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wcast-align" +@@ -230,9 +230,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone #include "format_types/juce_LegacyAudioParameter.cpp" #include "processors/juce_AudioProcessor.cpp" #include "processors/juce_AudioPluginInstance.cpp" @@ -26,7 +26,7 @@ index ac194a19d..f494ec3a2 100644 #include "format_types/juce_LADSPAPluginFormat.cpp" #include "format_types/juce_VSTPluginFormat.cpp" diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp -index caa55b618..41c547b0c 100644 +index 040accf2e..df0b6c67b 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -51,12 +51,14 @@ AudioProcessor::AudioProcessor (const BusesProperties& ioConfig) @@ -44,7 +44,7 @@ index caa55b618..41c547b0c 100644 #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING // This will fail if you've called beginParameterChangeGesture() for one -@@ -804,6 +806,7 @@ void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChang +@@ -826,6 +828,7 @@ void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChang processorLayoutsChanged(); } @@ -52,7 +52,7 @@ index caa55b618..41c547b0c 100644 //============================================================================== void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept { -@@ -840,6 +843,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() +@@ -862,6 +865,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() return ed; } @@ -61,7 +61,7 @@ index caa55b618..41c547b0c 100644 //============================================================================== void AudioProcessor::getCurrentProgramStateInformation (juce::MemoryBlock& destData) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h -index dbad562ef..ab9e8a7d6 100644 +index f72a4ed3f..849b77d3c 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -926,6 +926,7 @@ public: @@ -103,10 +103,10 @@ index dbad562ef..ab9e8a7d6 100644 int blockSize = 0, latencySamples = 0; bool suspended = false; diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp -index fd8c35879..070f82554 100644 +index 17c5af30c..3d4a0d428 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp -@@ -1496,8 +1496,10 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const +@@ -1519,8 +1519,10 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } @@ -118,7 +118,7 @@ index fd8c35879..070f82554 100644 int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h -index a16e35df0..8d2221f1e 100644 +index a60e67d84..17a121893 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -355,8 +355,10 @@ public: diff --git a/libs/juce-current/patches/22_mingw-filechooser-no-vista.patch b/libs/juce-current/patches/22_mingw-filechooser-no-vista.patch index c21892ad..6849d29d 100644 --- a/libs/juce-current/patches/22_mingw-filechooser-no-vista.patch +++ b/libs/juce-current/patches/22_mingw-filechooser-no-vista.patch @@ -1,16 +1,16 @@ diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp -index 065d873e1..fb9cfc999 100644 +index 0c1138a2b..a5b42b8ef 100644 --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp -@@ -167,6 +167,7 @@ private: - Atomic nativeDialogRef; - Atomic shouldCancel; +@@ -172,6 +172,7 @@ private: + void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } + }; + #if JUCE_MSVC bool showDialog (IFileDialog& dialog, bool async) const { FILEOPENDIALOGOPTIONS flags = {}; -@@ -303,6 +304,7 @@ private: +@@ -327,6 +328,7 @@ private: return result; } @@ -18,13 +18,16 @@ index 065d873e1..fb9cfc999 100644 Array openDialogPreVista (bool async) { -@@ -412,8 +414,10 @@ private: +@@ -436,11 +438,13 @@ private: const Remover remover (*this); + #if JUCE_MSVC - if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) + if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista + && customComponent == nullptr) + { return openDialogVistaAndUp (async); + } + #endif return openDialogPreVista (async); diff --git a/libs/juce-current/patches/23_fix-juce-includes.patch b/libs/juce-current/patches/23_fix-juce-includes.patch index 5d578762..4ff9d260 100644 --- a/libs/juce-current/patches/23_fix-juce-includes.patch +++ b/libs/juce-current/patches/23_fix-juce-includes.patch @@ -1,8 +1,8 @@ diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -index dbf441fb5..63d931efd 100644 +index cabbbcf13..f722ab36c 100644 --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp -@@ -489,7 +489,7 @@ public: +@@ -488,7 +488,7 @@ public: { const int numChannels = jmax (numIn, numOut); diff --git a/libs/juce-current/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/libs/juce-current/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index 5a761a30..5a510994 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/libs/juce-current/source/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -353,8 +353,8 @@ namespace FloatVectorHelpers union signMask64 { double d; uint64 i; }; #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - template struct ModeType { using Mode = BasicOps32; }; - template<> struct ModeType<8> { using Mode = BasicOps64; }; + template struct ModeType { using Mode = BasicOps32; }; + template <> struct ModeType<8> { using Mode = BasicOps64; }; template struct MinMax diff --git a/libs/juce-current/source/modules/juce_audio_basics/juce_audio_basics.h b/libs/juce-current/source/modules/juce_audio_basics/juce_audio_basics.h index 85235438..8c207fa6 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/juce_audio_basics.h +++ b/libs/juce-current/source/modules/juce_audio_basics/juce_audio_basics.h @@ -32,7 +32,7 @@ ID: juce_audio_basics vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE audio and MIDI data classes description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp index 7d64b8fb..28f8f364 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp +++ b/libs/juce-current/source/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) @@ -117,7 +116,7 @@ void MidiBuffer::clear (int startSample, int numSamples) auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); - data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); + data.removeRange ((int) (start - data.begin()), (int) (end - start)); } void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) @@ -237,4 +236,73 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio return true; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct MidiBufferTest : public UnitTest +{ + MidiBufferTest() + : UnitTest ("MidiBuffer", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("Clear messages"); + { + const auto message = MidiMessage::noteOn (1, 64, 0.5f); + + const auto testBuffer = [&] + { + MidiBuffer buffer; + buffer.addEvent (message, 0); + buffer.addEvent (message, 10); + buffer.addEvent (message, 20); + buffer.addEvent (message, 30); + return buffer; + }(); + + { + auto buffer = testBuffer; + buffer.clear (10, 0); + expectEquals (buffer.getNumEvents(), 4); + } + + { + auto buffer = testBuffer; + buffer.clear (10, 1); + expectEquals (buffer.getNumEvents(), 3); + } + + { + auto buffer = testBuffer; + buffer.clear (10, 10); + expectEquals (buffer.getNumEvents(), 3); + } + + { + auto buffer = testBuffer; + buffer.clear (10, 20); + expectEquals (buffer.getNumEvents(), 2); + } + + { + auto buffer = testBuffer; + buffer.clear (10, 30); + expectEquals (buffer.getNumEvents(), 1); + } + + { + auto buffer = testBuffer; + buffer.clear (10, 300); + expectEquals (buffer.getNumEvents(), 1); + } + } + } +}; + +static MidiBufferTest midiBufferTest; + +#endif + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 51a88ab1..f1be5203 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/libs/juce-current/source/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.isValid()) + 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/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index cd4675d9..e1a21504 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -57,6 +57,31 @@ 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. + return {}; +} + int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept { numBytesUsed = 0; @@ -224,16 +249,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 +699,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 +708,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 +719,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 +1157,172 @@ 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()); + + expect (result.isValid()); + 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()); + + expect (! result.isValid()); + 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/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.h b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.h index 9c5af32e..5c761f65 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/libs/juce-current/source/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -858,11 +858,45 @@ public: //============================================================================== /** Reads a midi variable-length integer. - @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 + 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. */ - static int readVariableLengthVal (const uint8* data, - int& numBytesUsed) noexcept; + 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. + */ + struct VariableLengthValue + { + VariableLengthValue() = default; + + VariableLengthValue (int valueIn, int bytesUsedIn) + : value (valueIn), bytesUsed (bytesUsedIn) {} + + bool isValid() const noexcept { return bytesUsed > 0; } + + 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 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 and + `isValid()` will return false + */ + 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/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index aa155bcf..84df6f46 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index 26537025..3db823f5 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/libs/juce-current/source/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -376,6 +376,8 @@ private: 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/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_ADSR.h b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_ADSR.h index a9dc3138..a89866d0 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_ADSR.h +++ b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_ADSR.h @@ -199,7 +199,7 @@ public: @see getNextSample */ - template + template void applyEnvelopeToBuffer (AudioBuffer& buffer, int startSample, int numSamples) { jassert (startSample + numSamples <= buffer.getNumSamples()); diff --git a/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_Interpolators.cpp b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_Interpolators.cpp index b42e0c0b..6302f320 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_Interpolators.cpp +++ b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_Interpolators.cpp @@ -37,7 +37,7 @@ public: } private: - template + template void runInterplatorTests (const String& interpolatorName) { auto createGaussian = [] (std::vector& destination, float scale, float centreInSamples, float width) diff --git a/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp index 0fd5ce84..c1d2809c 100644 --- a/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp +++ b/libs/juce-current/source/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp @@ -29,7 +29,7 @@ struct LagrangeResampleHelper static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } }; -template<> +template <> struct LagrangeResampleHelper<0> { static forcedinline void calc (float&, float) noexcept {} diff --git a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 522a8949..32af4990 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -339,7 +339,7 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, midiDeviceInfosFromXml.clear(); enabledMidiInputs.clear(); - forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") + for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); auto isIdentifierAvailable = [] (const Array& available, const String& identifier) @@ -704,7 +704,7 @@ void AudioDeviceManager::restartLastAudioDevice() { // This method will only reload the last device that was running // before closeAudioDevice() was called - you need to actually open - // one first, with setAudioDevice(). + // one first, with setAudioDeviceSetup(). jassertfalse; return; } @@ -1146,12 +1146,19 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCall void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) { - for (auto& device : MidiInput::getAvailableDevices()) + if (name.isEmpty()) { - if (device.name == name) + removeMidiInputDeviceCallback ({}, callbackToRemove); + } + else + { + for (auto& device : MidiInput::getAvailableDevices()) { - removeMidiInputDeviceCallback (device.identifier, callbackToRemove); - return; + if (device.name == name) + { + removeMidiInputDeviceCallback (device.identifier, callbackToRemove); + return; + } } } } diff --git a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index ac490c0d..d5c6f03f 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -267,7 +267,7 @@ public: Note that this only reloads the last device that was running before closeAudioDevice() was called - it doesn't reload any kind of saved-state, - and can only be called after a device has been opened with SetAudioDevice(). + and can only be called after a device has been opened with setAudioDeviceSetup(). If a device is already open, this call will do nothing. */ diff --git a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index eca32bf9..683d038a 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/libs/juce-current/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -80,9 +80,8 @@ public: The scanForDevices() method must have been called to create this list. - @param wantInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. + @param wantInputNames for devices which have separate inputs and outputs + this determines which list of names is returned */ virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; diff --git a/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.cpp b/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.cpp index b3a315c9..7f7e461d 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.cpp @@ -47,6 +47,35 @@ #include "native/juce_MidiDataConcatenator.h" +#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 @@ -58,7 +87,7 @@ #undef Component #include "native/juce_mac_CoreAudio.cpp" - #include "native/juce_mac_CoreMidi.cpp" + #include "native/juce_mac_CoreMidi.mm" #elif JUCE_IOS #import @@ -70,7 +99,7 @@ #endif #include "native/juce_ios_Audio.cpp" - #include "native/juce_mac_CoreMidi.cpp" + #include "native/juce_mac_CoreMidi.mm" //============================================================================== #elif JUCE_WINDOWS diff --git a/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.h b/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.h index a61d3469..0cc0a449 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.h +++ b/libs/juce-current/source/modules/juce_audio_devices/juce_audio_devices.h @@ -32,7 +32,7 @@ ID: juce_audio_devices vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE audio and MIDI I/O device classes description: Classes to play and record from audio and MIDI I/O devices website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h new file mode 100644 index 00000000..49ce6642 --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPBytestreamInputHandler.h @@ -0,0 +1,134 @@ +/* + ============================================================================== + + 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) {} + + /** + Provides an `operator()` which can create an input handler for a given + MidiInput. + + All handler classes should have a similar Factory to facilitate + creation of handlers in generic contexts. + */ + 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) {} + + /** + Provides an `operator()` which can create an input handler for a given + MidiInput. + + All handler classes should have a similar Factory to facilitate + creation of handlers in generic contexts. + */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPConversion.h new file mode 100644 index 00000000..c85a33f1 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h new file mode 100644 index 00000000..d47927bd --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPConverters.h @@ -0,0 +1,165 @@ +/* + ============================================================================== + + 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 +{ + /** + Allows conversion from bytestream- or Universal MIDI Packet-formatted + messages to MIDI 1.0 messages in UMP format. + + @tags{Audio} + */ + 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)); + } + }; + + /** + Allows conversion from bytestream- or Universal MIDI Packet-formatted + messages to MIDI 2.0 messages in UMP format. + + @tags{Audio} + */ + 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; + }; + + /** + Allows conversion from bytestream- or Universal MIDI Packet-formatted + messages to UMP format. + + The packet protocol can be selected using the constructor parameter. + + @tags{Audio} + */ + 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{}; + }; + + /** + Allows conversion from bytestream- or Universal MIDI Packet-formatted + messages to bytestream format. + + @tags{Audio} + */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h new file mode 100644 index 00000000..a365b4fd --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPDispatcher.h @@ -0,0 +1,190 @@ +/* + ============================================================================== + + 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()), timeStamp); + currentPacketLen = 0; + } + }); + } + +private: + std::array nextPacket; + size_t currentPacketLen = 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h new file mode 100644 index 00000000..acfcc6c8 --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPFactory.h @@ -0,0 +1,534 @@ +/* + ============================================================================== + + 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 +{ + +/** + This struct holds functions that can be used to create different kinds + of Universal MIDI Packet. + + @tags{Audio} +*/ +struct Factory +{ + /** @internal */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPIterator.h new file mode 100644 index 00000000..a91b115d --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h new file mode 100644 index 00000000..5debeef1 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp new file mode 100644 index 00000000..9da72c34 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h new file mode 100644 index 00000000..61d566ff --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPProtocols.h new file mode 100644 index 00000000..a5b5ca9f --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPReceiver.h new file mode 100644 index 00000000..469efd3a --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.cpp new file mode 100644 index 00000000..bf4f790b --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h new file mode 100644 index 00000000..f5f898a7 --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPSysEx7.h @@ -0,0 +1,73 @@ +/* + ============================================================================== + + 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 +{ + +/** + This struct acts as a single-file namespace for Univeral MIDI Packet + functionality related to 7-bit SysEx. + + @tags{Audio} +*/ +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 + }; + + /** Holds the bytes from a single SysEx-7 packet. */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPTests.cpp new file mode 100644 index 00000000..0ac2cce0 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h new file mode 100644 index 00000000..b5471aa8 --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPU32InputHandler.h @@ -0,0 +1,145 @@ +/* + ============================================================================== + + 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) {} + + /** + Provides an `operator()` which can create an input handler for a given + MidiInput. + + All handler classes should have a similar Factory to facilitate + creation of handlers in generic contexts. + */ + 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) {} + + /** + Provides an `operator()` which can create an input handler for a given + MidiInput. + + All handler classes should have a similar Factory to facilitate + creation of handlers in generic contexts. + */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.cpp new file mode 100644 index 00000000..166c4bfa --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h new file mode 100644 index 00000000..9bac9e4b --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPUtils.h @@ -0,0 +1,113 @@ +/* + ============================================================================== + + 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); + + /** + Helper functions for setting/getting 4-bit ranges inside a 32-bit word. + */ + 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); + } + }; + + /** + Helper functions for setting/getting 8-bit ranges inside a 32-bit word. + */ + 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); + } + }; + + /** + Helper functions for setting/getting 16-bit ranges inside a 32-bit word. + */ + 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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPView.cpp new file mode 100644 index 00000000..c5f3db85 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPView.h new file mode 100644 index 00000000..504e024c --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPacket.h new file mode 100644 index 00000000..b58304d5 --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h b/libs/juce-current/source/modules/juce_audio_devices/midi_io/ump/juce_UMPackets.h new file mode 100644 index 00000000..2a0a1b8f --- /dev/null +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 646aae7c..feac8c3b 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -1663,11 +1663,16 @@ private: 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 @@ private: 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 @@ private: d->done = true; } else + { anyRemaining = true; + } } } @@ -1808,6 +1818,8 @@ private: numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; + isWaitingForInput = numInputChans > 0; + inputIndex = channelIndex; outputIndex = channelIndex + numInputChans; @@ -1892,6 +1904,8 @@ private: { if (numInputChannels > 0) { + isWaitingForInput = false; + int start1, size1, start2, size2; inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); @@ -1973,6 +1987,7 @@ private: 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; diff --git a/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp b/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp deleted file mode 100644 index 704a0574..00000000 --- a/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp +++ /dev/null @@ -1,732 +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; - } -} - -class MidiInput::Pimpl : public CoreMidiHelpers::MidiPortAndCallback -{ -public: - using MidiPortAndCallback::MidiPortAndCallback; -}; - -//============================================================================== -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(); - auto* ptr = mpc.get(); - midiInput->internal = std::move (mpc); - - const ScopedLock sl (callbackLock); - activeCallbacks.add (ptr); - - 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(); - auto* ptr = mpc.get(); - midiInput->internal = std::move (mpc); - - const ScopedLock sl (callbackLock); - activeCallbacks.add (ptr); - - 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() = 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 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 = 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 = 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 = 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) -{ - #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; - } - - internal->send (packetToSend); -} - -#undef CHECK_ERROR - -} // namespace juce diff --git a/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm b/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm new file mode 100644 index 00000000..49c8a60e --- /dev/null +++ b/libs/juce-current/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm @@ -0,0 +1,1325 @@ +/* + ============================================================================== + + 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; + + template + class ScopedMidiResource + { + public: + ScopedMidiResource() = default; + + explicit ScopedMidiResource (Resource r) : contents (r, {}) {} + + ~ScopedMidiResource() noexcept + { + auto ref = std::get<0> (contents); + + if (ref != 0) + std::get<1> (contents) (ref); + } + + ScopedMidiResource (const ScopedMidiResource& other) = delete; + ScopedMidiResource& operator= (const ScopedMidiResource& other) = delete; + + ScopedMidiResource (ScopedMidiResource&& other) noexcept { swap (other); } + + ScopedMidiResource& operator= (ScopedMidiResource&& other) noexcept + { + swap (other); + return *this; + } + + void swap (ScopedMidiResource& other) noexcept { std::swap (other.contents, contents); } + + Resource operator*() const noexcept { return std::get<0> (contents); } + + Resource release() noexcept + { + auto old = std::get<0> (contents); + std::get<0> (contents) = 0; + return old; + } + + private: + std::tuple contents { {}, {} }; + }; + + struct PortRefDestructor + { + void operator() (MIDIPortRef p) const noexcept { MIDIPortDispose (p); } + }; + + using ScopedPortRef = ScopedMidiResource; + + struct EndpointRefDestructor + { + void operator() (MIDIEndpointRef p) const noexcept { MIDIEndpointDispose (p); } + }; + + using ScopedEndpointRef = ScopedMidiResource; + + //============================================================================== + class MidiPortAndEndpoint + { + public: + MidiPortAndEndpoint (ScopedPortRef p, ScopedEndpointRef ep) noexcept + : port (std::move (p)), endpoint (std::move (ep)), sender (*endpoint) + {} + + ~MidiPortAndEndpoint() noexcept + { + // if port != 0, it means we didn't create the endpoint, so it's not safe to delete it + if (*port != 0) + endpoint.release(); + } + + 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: + ScopedPortRef port; + ScopedEndpointRef 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; + + ScopedPortRef scopedPort { port }; + + if (! CHECK_ERROR (MIDIPortConnectSource (*scopedPort, endpoint, nullptr))) + continue; + + input->internal->portAndEndpoint = std::make_unique (std::move (scopedPort), ScopedEndpointRef { 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); + ScopedEndpointRef scopedEndpoint { 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 (ScopedPortRef{}, std::move (scopedEndpoint)); + 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; + + ScopedPortRef scopedPort { port }; + + auto midiOutput = rawToUniquePtr (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); + midiOutput->internal = std::make_unique (std::move (scopedPort), ScopedEndpointRef { 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); + ScopedEndpointRef scopedEndpoint { 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 (*scopedEndpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) + return {}; + + auto midiOutput = rawToUniquePtr (new MidiOutput (deviceName, String (deviceIdentifier))); + midiOutput->internal = std::make_unique (ScopedPortRef{}, std::move (scopedEndpoint)); + + 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/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp index bc03b9e2..da55de31 100644 --- a/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -334,7 +334,7 @@ private: }; //============================================================================== - template + template struct Win32MidiDeviceQuery { static Array getAvailableDevices() @@ -782,9 +782,9 @@ private: public: virtual ~DeviceCallbackHandler() {}; - virtual HRESULT addDevice (IDeviceInformation*) = 0; - virtual HRESULT removeDevice (IDeviceInformationUpdate*) = 0; - virtual HRESULT updateDevice (IDeviceInformationUpdate*) = 0; + JUCE_COMCALL addDevice (IDeviceInformation*) = 0; + JUCE_COMCALL removeDevice (IDeviceInformationUpdate*) = 0; + JUCE_COMCALL updateDevice (IDeviceInformationUpdate*) = 0; bool attach (HSTRING deviceSelector, DeviceInformationKind infoKind) { @@ -834,7 +834,7 @@ private: } } - WinRTWrapper::ComPtr> iter; + ComSmartPtr> iter; auto hr = requestedProperties->QueryInterface (__uuidof (IIterable), (void**) iter.resetAndGetPointerAddress()); if (FAILED (hr)) @@ -891,7 +891,7 @@ private: watcher = nullptr; } - template + template IInspectable* getValueFromDeviceInfo (String key, InfoType* info) { __FIMapView_2_HSTRING_IInspectable* properties; @@ -924,7 +924,7 @@ private: String getGUIDFromInspectable (IInspectable& inspectable) { - WinRTWrapper::ComPtr> guidRef; + ComSmartPtr> guidRef; auto hr = inspectable.QueryInterface (__uuidof (IReference), (void**) guidRef.resetAndGetPointerAddress()); @@ -951,7 +951,7 @@ private: bool getBoolFromInspectable (IInspectable& inspectable) { - WinRTWrapper::ComPtr> boolRef; + ComSmartPtr> boolRef; auto hr = inspectable.QueryInterface (__uuidof (IReference), (void**) boolRef.resetAndGetPointerAddress()); @@ -978,7 +978,7 @@ private: struct DeviceEnumerationThread : public Thread { DeviceEnumerationThread (DeviceCallbackHandler& h, - WinRTWrapper::ComPtr& w, + ComSmartPtr& w, EventRegistrationToken& added, EventRegistrationToken& removed, EventRegistrationToken& updated) @@ -1012,12 +1012,12 @@ private: } DeviceCallbackHandler& handler; - WinRTWrapper::ComPtr& watcher; + ComSmartPtr& watcher; EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken; }; //============================================================================== - WinRTWrapper::ComPtr watcher; + ComSmartPtr watcher; EventRegistrationToken deviceAddedToken { 0 }, deviceRemovedToken { 0 }, @@ -1222,7 +1222,7 @@ private: template struct MidiIODeviceWatcher final : private DeviceCallbackHandler { - MidiIODeviceWatcher (WinRTWrapper::ComPtr& comFactory) + MidiIODeviceWatcher (ComSmartPtr& comFactory) : factory (comFactory) { } @@ -1407,7 +1407,7 @@ private: return {}; } - WinRTWrapper::ComPtr& factory; + ComSmartPtr& factory; Array connectedDevices; CriticalSection deviceChanges; @@ -1421,8 +1421,8 @@ private: struct OpenMidiPortThread : public Thread { OpenMidiPortThread (String threadName, String midiDeviceID, - WinRTWrapper::ComPtr& comFactory, - WinRTWrapper::ComPtr& comPort) + ComSmartPtr& comFactory, + ComSmartPtr& comPort) : Thread (threadName), deviceID (midiDeviceID), factory (comFactory), @@ -1438,7 +1438,7 @@ private: void run() override { WinRTWrapper::ScopedHString hDeviceId (deviceID); - WinRTWrapper::ComPtr> asyncOp; + ComSmartPtr> asyncOp; auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); if (FAILED (hr)) @@ -1466,8 +1466,8 @@ private: } const String deviceID; - WinRTWrapper::ComPtr& factory; - WinRTWrapper::ComPtr& port; + ComSmartPtr& factory; + ComSmartPtr& port; WaitableEvent portOpened { true }; }; @@ -1552,7 +1552,7 @@ private: BLEDeviceWatcher& bleDeviceWatcher; WinRTMIDIDeviceInfo deviceInfo; bool isBLEDevice = false; - WinRTWrapper::ComPtr midiPort; + ComSmartPtr midiPort; }; //============================================================================== @@ -1637,19 +1637,19 @@ private: if (! isStarted) return S_OK; - WinRTWrapper::ComPtr message; + ComSmartPtr message; auto hr = args->get_Message (message.resetAndGetPointerAddress()); if (FAILED (hr)) return hr; - WinRTWrapper::ComPtr buffer; + ComSmartPtr buffer; hr = message->get_RawData (buffer.resetAndGetPointerAddress()); if (FAILED (hr)) return hr; - WinRTWrapper::ComPtr bufferByteAccess; + ComSmartPtr bufferByteAccess; hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); if (FAILED (hr)) @@ -1775,15 +1775,15 @@ private: String getDeviceName() override { return deviceInfo.name; } //============================================================================== - WinRTWrapper::ComPtr buffer; - WinRTWrapper::ComPtr bufferByteAccess; + ComSmartPtr buffer; + ComSmartPtr bufferByteAccess; uint8_t* bufferData = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper); }; - WinRTWrapper::ComPtr midiInFactory; - WinRTWrapper::ComPtr midiOutFactory; + ComSmartPtr midiInFactory; + ComSmartPtr midiOutFactory; std::unique_ptr> inputDeviceWatcher; std::unique_ptr> outputDeviceWatcher; diff --git a/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index 38daf1cd..e86cfd08 100755 --- a/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/libs/juce-current/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -116,9 +116,6 @@ bool check (HRESULT hr) #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") #endif -#define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown -#define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE - enum EDataFlow { eRender = 0, @@ -337,10 +334,6 @@ JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD" JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0; }; -#undef JUCE_COMCALL -#undef JUCE_COMCLASS -#undef JUCE_IUNKNOWNCLASS - //============================================================================== namespace WasapiClasses { @@ -893,7 +886,7 @@ public: reservoirWritePos = 0; } - template + template void updateFormatWithType (SourceType*) noexcept { using NativeType = AudioData::Pointer; @@ -1053,7 +1046,7 @@ public: renderClient = nullptr; } - template + template void updateFormatWithType (DestType*) { using NativeType = AudioData::Pointer; @@ -1755,11 +1748,11 @@ private: ChangeNotificationClient (WASAPIAudioIODeviceType* d) : ComBaseClassHelper (0), device (d) {} - HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } + JUCE_COMRESULT OnDeviceAdded (LPCWSTR) { return notify(); } + JUCE_COMRESULT OnDeviceRemoved (LPCWSTR) { return notify(); } + JUCE_COMRESULT OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } + JUCE_COMRESULT OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } + JUCE_COMRESULT OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } private: WeakReference device; diff --git a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index b2167cf4..a389d009 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -67,25 +67,25 @@ namespace AiffFileHelpers Loop sustainLoop; Loop releaseLoop; - void copyTo (StringPairArray& values) const + void copyTo (std::map& values) const { - values.set ("MidiUnityNote", String (baseNote)); - values.set ("Detune", String (detune)); + values.emplace ("MidiUnityNote", String (baseNote)); + values.emplace ("Detune", String (detune)); - values.set ("LowNote", String (lowNote)); - values.set ("HighNote", String (highNote)); - values.set ("LowVelocity", String (lowVelocity)); - values.set ("HighVelocity", String (highVelocity)); + values.emplace ("LowNote", String (lowNote)); + values.emplace ("HighNote", String (highNote)); + values.emplace ("LowVelocity", String (lowVelocity)); + values.emplace ("HighVelocity", String (highVelocity)); - values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); + values.emplace ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); - values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more - values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); - values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); - values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); - values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); - values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); - values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); + values.emplace ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more + values.emplace ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); + values.emplace ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); + values.emplace ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); + values.emplace ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); + values.emplace ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); + values.emplace ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); } static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def) @@ -149,7 +149,7 @@ namespace AiffFileHelpers input.read (unknown, sizeof (unknown)); } - void addToMetadata (StringPairArray& metadata) const + void addToMetadata (std::map& metadata) const { const bool rootNoteSet = rootNote != 0; @@ -157,11 +157,11 @@ namespace AiffFileHelpers setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); if (rootNoteSet) - metadata.set (AiffAudioFormat::appleRootNote, String (rootNote)); + metadata.emplace (AiffAudioFormat::appleRootNote, String (rootNote)); - metadata.set (AiffAudioFormat::appleBeats, String (numBeats)); - metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen)); - metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum)); + metadata.emplace (AiffAudioFormat::appleBeats, String (numBeats)); + metadata.emplace (AiffAudioFormat::appleDenominator, String (timeSigDen)); + metadata.emplace (AiffAudioFormat::appleNumerator, String (timeSigNum)); const char* keyString = nullptr; @@ -175,12 +175,14 @@ namespace AiffFileHelpers } if (keyString != nullptr) - metadata.set (AiffAudioFormat::appleKey, keyString); + metadata.emplace (AiffAudioFormat::appleKey, keyString); } - void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const + void setBoolFlag (std::map& values, + const char* name, + bool shouldBeSet) const { - values.set (name, shouldBeSet ? "1" : "0"); + values.emplace (name, shouldBeSet ? "1" : "0"); } uint32 flags; @@ -388,6 +390,17 @@ public: { using namespace AiffFileHelpers; + std::map metadataValuesMap; + + for (int i = 0; i != metadataValues.size(); ++i) + { + metadataValuesMap.emplace (metadataValues.getAllKeys().getReference (i), + metadataValues.getAllValues().getReference (i)); + } + + // If this fails, there were duplicate keys in the metadata + jassert ((size_t) metadataValuesMap.size() == (size_t) metadataValues.size()); + if (input->readInt() == chunkName ("FORM")) { auto len = input->readIntBigEndian(); @@ -479,8 +492,8 @@ public: auto numCues = (uint16) input->readShortBigEndian(); // these two are always the same for AIFF-read files - metadataValues.set ("NumCuePoints", String (numCues)); - metadataValues.set ("NumCueLabels", String (numCues)); + metadataValuesMap.emplace ("NumCuePoints", String (numCues)); + metadataValuesMap.emplace ("NumCueLabels", String (numCues)); for (uint16 i = 0; i < numCues; ++i) { @@ -497,18 +510,18 @@ public: input->readByte(); auto prefixCue = "Cue" + String (i); - metadataValues.set (prefixCue + "Identifier", String (identifier)); - metadataValues.set (prefixCue + "Offset", String (offset)); + metadataValuesMap.emplace (prefixCue + "Identifier", String (identifier)); + metadataValuesMap.emplace (prefixCue + "Offset", String (offset)); auto prefixLabel = "CueLabel" + String (i); - metadataValues.set (prefixLabel + "Identifier", String (identifier)); - metadataValues.set (prefixLabel + "Text", textBlock.toString()); + metadataValuesMap.emplace (prefixLabel + "Identifier", String (identifier)); + metadataValuesMap.emplace (prefixLabel + "Text", textBlock.toString()); } } else if (type == chunkName ("COMT")) { auto numNotes = (uint16) input->readShortBigEndian(); - metadataValues.set ("NumCueNotes", String (numNotes)); + metadataValuesMap.emplace ("NumCueNotes", String (numNotes)); for (uint16 i = 0; i < numNotes; ++i) { @@ -520,9 +533,9 @@ public: input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); auto prefix = "CueNote" + String (i); - metadataValues.set (prefix + "TimeStamp", String (timestamp)); - metadataValues.set (prefix + "Identifier", String (identifier)); - metadataValues.set (prefix + "Text", textBlock.toString()); + metadataValuesMap.emplace (prefix + "TimeStamp", String (timestamp)); + metadataValuesMap.emplace (prefix + "Identifier", String (identifier)); + metadataValuesMap.emplace (prefix + "Text", textBlock.toString()); } } else if (type == chunkName ("INST")) @@ -530,16 +543,16 @@ public: HeapBlock inst; inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); input->read (inst, (int) length); - inst->copyTo (metadataValues); + inst->copyTo (metadataValuesMap); } else if (type == chunkName ("basc")) { - AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues); + AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValuesMap); } else if (type == chunkName ("cate")) { - metadataValues.set (AiffAudioFormat::appleTag, - AiffFileHelpers::CATEChunk::read (*input, length)); + metadataValuesMap.emplace (AiffAudioFormat::appleTag, + AiffFileHelpers::CATEChunk::read (*input, length)); } else if ((hasGotVer && hasGotData && hasGotType) || chunkEnd < input->getPosition() @@ -553,8 +566,10 @@ public: } } - if (metadataValues.size() > 0) - metadataValues.set ("MetaDataSource", "AIFF"); + if (metadataValuesMap.size() > 0) + metadataValuesMap.emplace ("MetaDataSource", "AIFF"); + + metadataValues.addMap (metadataValuesMap); } //============================================================================== diff --git a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp index fe680cba..a06087e8 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_FlacAudioFormat.cpp @@ -250,7 +250,7 @@ public: samplesInReservoir = 0; } else if (startSampleInFile < reservoirStart - || startSampleInFile > reservoirStart + jmax (samplesInReservoir, 511)) + || startSampleInFile > reservoirStart + jmax (samplesInReservoir, (int64) 511)) { // had some problems with flac crashing if the read pos is aligned more // accurately than this. Probably fixed in newer versions of the library, though. @@ -367,7 +367,7 @@ public: private: FlacNamespace::FLAC__StreamDecoder* decoder; AudioBuffer reservoir; - int reservoirStart = 0, samplesInReservoir = 0; + int64 reservoirStart = 0, samplesInReservoir = 0; bool ok = false, scanningForLength = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) diff --git a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp index 44bec28e..f355b644 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_MP3AudioFormat.cpp @@ -492,7 +492,9 @@ struct MP3Frame return frequencies[sampleRateIndex]; } - void decodeHeader (const uint32 header) + enum class ParseSuccessful { no, yes }; + + ParseSuccessful decodeHeader (const uint32 header) { jassert (((header >> 10) & 3) != 3); @@ -527,17 +529,18 @@ struct MP3Frame jassertfalse; // This means the file is using "free format". Apparently very few decoders // support this mode, and this one certainly doesn't handle it correctly! frameSize = 0; + return ParseSuccessful::no; } - else + + switch (layer) { - switch (layer) - { - case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; - case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; - case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; - default: break; - } + case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; + case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; + case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; + default: break; } + + return ParseSuccessful::yes; } int layer, frameSize, numChannels, single; @@ -1430,7 +1433,11 @@ struct MP3Stream lastFrameSize += nextFrameOffset; } - frame.decodeHeader ((uint32) stream.readIntBigEndian()); + const auto successful = frame.decodeHeader ((uint32) stream.readIntBigEndian()); + + if (successful == MP3Frame::ParseSuccessful::no) + return -1; + headerParsed = true; frameSize = frame.frameSize; isFreeFormat = (frameSize == 0); diff --git a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp index 5649ccd5..d2410edf 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_OggVorbisAudioFormat.cpp @@ -160,13 +160,13 @@ public: { while (numSamples > 0) { - auto numAvailable = (int) (reservoirStart + samplesInReservoir - startSampleInFile); + auto numAvailable = (reservoirStart + samplesInReservoir - startSampleInFile); if (startSampleInFile >= reservoirStart && numAvailable > 0) { // got a few samples overlapping, so use them before seeking.. - auto numToUse = jmin (numSamples, numAvailable); + auto numToUse = jmin ((int64) numSamples, numAvailable); for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) if (destSamples[i] != nullptr) @@ -175,8 +175,8 @@ public: (size_t) numToUse * sizeof (float)); startSampleInFile += numToUse; - numSamples -= numToUse; - startOffsetInDestBuffer += numToUse; + numSamples -= (int) numToUse; + startOffsetInDestBuffer += (int) numToUse; if (numSamples == 0) break; @@ -194,7 +194,7 @@ public: int bitStream = 0; int offset = 0; - int numToRead = samplesInReservoir; + int numToRead = (int) samplesInReservoir; while (numToRead > 0) { @@ -261,7 +261,7 @@ private: OggVorbisNamespace::OggVorbis_File ovFile; OggVorbisNamespace::ov_callbacks callbacks; AudioBuffer reservoir; - int reservoirStart = 0, samplesInReservoir = 0; + int64 reservoirStart = 0, samplesInReservoir = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) }; diff --git a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp index f25261c2..7642040b 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_formats/codecs/juce_WavAudioFormat.cpp @@ -993,10 +993,12 @@ public: input->skipNextBytes (2); bitsPerSample = (unsigned int) (int) input->readShort(); - if (bitsPerSample > 64) + if (bitsPerSample > 64 && (int) sampleRate != 0) { bytesPerFrame = bytesPerSec / (int) sampleRate; - bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; + + if (numChannels != 0) + bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; } else { diff --git a/libs/juce-current/source/modules/juce_audio_formats/juce_audio_formats.h b/libs/juce-current/source/modules/juce_audio_formats/juce_audio_formats.h index 655ded48..e5dac2fd 100644 --- a/libs/juce-current/source/modules/juce_audio_formats/juce_audio_formats.h +++ b/libs/juce-current/source/modules/juce_audio_formats/juce_audio_formats.h @@ -35,7 +35,7 @@ ID: juce_audio_formats vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE audio file format codecs description: Classes for reading and writing various audio file formats. website: http://www.juce.com/juce @@ -104,7 +104,7 @@ #define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 #endif -#if ! JUCE_MSVC +#if ! JUCE_WINDOWS #undef JUCE_USE_WINDOWS_MEDIA_FORMAT #define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 #endif diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp index 95a09132..c7cd80a6 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/AAX/juce_AAX_Wrapper.cpp @@ -1085,24 +1085,28 @@ namespace AAXClasses SetParameterNormalizedValue (paramID, (double) newValue); } - void audioProcessorChanged (AudioProcessor* processor) override + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override { ++mNumPlugInChanges; - auto numParameters = juceParameters.getNumParameters(); - - for (int i = 0; i < numParameters; ++i) + if (details.parameterInfoChanged) { - if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) + auto numParameters = juceParameters.getNumParameters(); + + for (int i = 0; i < numParameters; ++i) { - auto newName = juceParameters.getParamForIndex (i)->getName (31); + if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) + { + auto newName = juceParameters.getParamForIndex (i)->getName (31); - if (p->Name() != newName.toRawUTF8()) - p->SetName (AAX_CString (newName.toRawUTF8())); + if (p->Name() != newName.toRawUTF8()) + p->SetName (AAX_CString (newName.toRawUTF8())); + } } } - check (Controller()->SetSignalLatency (processor->getLatencySamples())); + if (details.latencyChanged) + check (Controller()->SetSignalLatency (processor->getLatencySamples())); } void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override @@ -1124,6 +1128,20 @@ namespace AAXClasses case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; + case AAX_eNotificationEvent_ASProcessingState: + { + if (data != nullptr && size == sizeof (AAX_EProcessingState)) + { + const auto state = *static_cast (data); + const auto nonRealtime = state == AAX_eProcessingState_Start + || state == AAX_eProcessingState_StartPass + || state == AAX_eProcessingState_BeginPassGroup; + pluginInstance->setNonRealtime (nonRealtime); + } + + break; + } + case AAX_eNotificationEvent_TrackNameChanged: if (data != nullptr) { diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm b/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm index f2277e51..1a687907 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AU_Wrapper.mm @@ -22,6 +22,8 @@ ============================================================================== */ + +#include #include #include "../utility/juce_CheckSettingMacros.h" @@ -45,7 +47,8 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshorten-64-to-32", "-Wzero-as-null-pointer-constant", "-Wnullable-to-nonnull-conversion", "-Wgnu-zero-variadic-macro-arguments", - "-Wformat-pedantic") + "-Wformat-pedantic", + "-Wdeprecated-anon-enum-enum-conversion") #include "../utility/juce_IncludeSystemHeaders.h" @@ -352,7 +355,7 @@ public: } } - err = (busNr == (requestedNumBus - 1) ? noErr : kAudioUnitErr_FormatNotSupported); + err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); } // was there an error? @@ -1163,16 +1166,24 @@ public: sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { - PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); + if (details.latencyChanged) + PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); + + if (details.parameterInfoChanged) + { + PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); + PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); + } - refreshCurrentPreset(); + PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); - PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + if (details.programChanged) + { + refreshCurrentPreset(); + PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); + } } //============================================================================== @@ -1900,7 +1911,7 @@ private: return (scope != kAudioUnitScope_Input && scope != kAudioUnitScope_Output) - ? kAudioUnitErr_InvalidScope : noErr; + ? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; } OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept @@ -2040,10 +2051,12 @@ private: addSupportedLayoutTags(); for (int i = 0; i < enabledInputs; ++i) - if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) return err; + if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) + return err; for (int i = 0; i < enabledOutputs; ++i) - if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) return err; + if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) + return err; return noErr; } @@ -2166,7 +2179,7 @@ private: #endif // add discrete layout tags - int n = bus->getMaxSupportedChannels(maxChannelsToProbeFor()); + int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); for (int ch = 0; ch < n; ++ch) { diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm b/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm index e274c729..8f3106ce 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/AU/juce_AUv3_Wrapper.mm @@ -73,6 +73,8 @@ #define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) +#include + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") using namespace juce; @@ -912,7 +914,7 @@ public: #endif //============================================================================== - void audioProcessorChanged (AudioProcessor* processor) override + void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails&) override { ignoreUnused (processor); @@ -1750,7 +1752,7 @@ public: { JUCE_ASSERT_MESSAGE_THREAD - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) JuceAudioUnitv3::removeEditor (getAudioProcessor()); } @@ -1795,22 +1797,25 @@ public: void viewDidLayoutSubviews() { - if (processorHolder != nullptr && [myself view] != nullptr) + if (auto holder = processorHolder.get()) { - if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + if ([myself view] != nullptr) { - if (processorHolder->viewConfiguration != nullptr) - editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); + if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) + { + if (holder->viewConfiguration != nullptr) + editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); - editor->setBounds (convertToRectInt ([[myself view] bounds])); + editor->setBounds (convertToRectInt ([[myself view] bounds])); - if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) - { - #if JUCE_IOS - [peerView setNeedsDisplay]; - #else - [peerView setNeedsDisplay: YES]; - #endif + if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) + { + #if JUCE_IOS + [peerView setNeedsDisplay]; + #else + [peerView setNeedsDisplay: YES]; + #endif + } } } } @@ -1818,21 +1823,21 @@ public: void didReceiveMemoryWarning() { - if (processorHolder != nullptr) - if (auto* processor = processorHolder->get()) + if (auto ptr = processorHolder.get()) + if (auto* processor = ptr->get()) processor->memoryWarningReceived(); } void viewDidAppear (bool) { - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (true); } void viewDidDisappear (bool) { - if (processorHolder != nullptr) + if (processorHolder.get() != nullptr) if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) editor->setVisible (false); } @@ -1846,65 +1851,72 @@ public: //============================================================================== AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) { - AUAudioUnit* retval = nil; + const auto holder = [&] + { + if (auto initialisedHolder = processorHolder.get()) + return initialisedHolder; - if (! MessageManager::getInstance()->isThisTheMessageThread()) + waitForExecutionOnMainThread ([this] { [myself view]; }); + return processorHolder.get(); + }(); + + if (holder == nullptr) + return nullptr; + + return (new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit(); + } + +private: + template + static void waitForExecutionOnMainThread (Callback&& callback) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) { - WaitableEvent creationEvent; + callback(); + return; + } - // AUv3 headers say that we may block this thread and that the message thread is guaranteed - // to be unblocked - struct AUCreator : public CallbackMessage - { - JuceAUViewController& owner; - AudioComponentDescription pDescr; - NSError** pError; - AUAudioUnit*& outAU; - WaitableEvent& e; - - AUCreator (JuceAUViewController& parent, const AudioComponentDescription& paramDescr, NSError** paramError, - AUAudioUnit*& outputAU, WaitableEvent& event) - : owner (parent), pDescr (paramDescr), pError (paramError), outAU (outputAU), e (event) - {} - - void messageCallback() override - { - outAU = owner.createAudioUnitOnMessageThread (pDescr, pError); - e.signal(); - } - }; + std::promise promise; + + MessageManager::callAsync ([&] + { + callback(); + promise.set_value(); + }); - (new AUCreator (*this, descr, error, retval, creationEvent))->post(); - creationEvent.wait (-1); + promise.get_future().get(); + } + + // There's a chance that createAudioUnit will be called from a background + // thread while the processorHolder is being updated on the main thread. + class LockedProcessorHolder + { + public: + AudioProcessorHolder::Ptr get() const + { + const ScopedLock lock (mutex); + return holder; } - else + + LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) { - retval = createAudioUnitOnMessageThread (descr, error); + const ScopedLock lock (mutex); + holder = other; + return *this; } - return [retval autorelease]; - } + private: + mutable CriticalSection mutex; + AudioProcessorHolder::Ptr holder; + }; -private: //============================================================================== AUViewController* myself; - AudioProcessorHolder::Ptr processorHolder = nullptr; + LockedProcessorHolder processorHolder; Rectangle preferredSize { 1, 1 }; //============================================================================== - AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) - { - JUCE_ASSERT_MESSAGE_THREAD - - [myself view]; // this will call [view load] and ensure that the AudioProcessor has been instantiated - - if (processorHolder == nullptr) - return nullptr; - - return (new JuceAudioUnitv3 (processorHolder, descr, 0, error))->getAudioUnit(); - } - - AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } + AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } }; //============================================================================== diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper.cpp index 018fc14d..638ce756 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper.cpp @@ -577,9 +577,9 @@ public: } } - void audioProcessorChanged (AudioProcessor*) + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) { - if (filter != nullptr && programsHost != nullptr) + if (details.programChanged && filter != nullptr && programsHost != nullptr) { if (filter->getNumPrograms() != lastProgramCount) { diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp index 7490bafb..ae304883 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp @@ -364,6 +364,7 @@ static const String makePresetsFile (AudioProcessor* const filter) // Header text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n"; text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; + text += "@prefix owl: .\n"; text += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n"; text += "@prefix rdf: .\n"; text += "@prefix rdfs: .\n"; @@ -385,6 +386,7 @@ static const String makePresetsFile (AudioProcessor* const filter) text += " rdfs:domain state:State ;\n"; text += " rdfs:range xsd:base64Binary .\n"; #endif + text += "\n"; #endif // Presets diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp index c7d78029..7836bf11 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/RTAS/juce_RTAS_Wrapper.cpp @@ -790,7 +790,7 @@ public: ReleaseControl (index + 2); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override { // xxx is there an RTAS equivalent? } diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h b/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h index aa2f471f..be2c9ecc 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_UnityPluginInterface.h @@ -131,7 +131,7 @@ struct UnityAudioEffectState UnityAudioAmbisonicData* ambisonicData; - template + template inline T* getEffectData() const { jassert (effectData != nullptr); diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp index f68674f4..c9c1831a 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/Unity/juce_Unity_Wrapper.cpp @@ -23,9 +23,10 @@ ============================================================================== */ +#include + #if JucePlugin_Build_Unity -#include #include "../utility/juce_IncludeModuleHeaders.h" #include diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 63d931ef..f722ab36 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -205,9 +205,9 @@ struct SharedMessageThread : public Thread initialiseJuce_GUI(); MessageManager::getInstance()->setCurrentThreadAsMessageThread(); - initialised = true; XWindowSystem::getInstance(); + initialised = true; while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) {} @@ -246,7 +246,6 @@ struct AbletonLiveHostSpecific class JuceVSTWrapper : public AudioProcessorListener, public AudioPlayHead, private Timer, - private AsyncUpdater, private AudioProcessorParameter::Listener { private: @@ -791,19 +790,9 @@ public: void parameterGestureChanged (int, bool) override {} - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { - vstEffect.latency = processor->getLatencySamples(); - triggerAsyncUpdate(); - } - - void handleAsyncUpdate() override - { - if (hostCallback != nullptr) - { - hostCallback (&vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); - hostCallback (&vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); - } + hostChangeUpdater.update (details); } bool getPinProperties (Vst2::VstPinInfo& properties, bool direction, int index) const @@ -1031,21 +1020,17 @@ public: : wrapper (w) { editor.setOpaque (true); - editor.setVisible (true); - setOpaque (true); - - setTopLeftPosition (editor.getPosition()); - editor.setTopLeftPosition (0, 0); - auto b = getLocalArea (&editor, editor.getLocalBounds()); - setSize (b.getWidth(), b.getHeight()); - addAndMakeVisible (editor); + auto editorBounds = getSizeToContainChild(); + setSize (editorBounds.getWidth(), editorBounds.getHeight()); + #if JUCE_WINDOWS if (! getHostType().isReceptor()) addMouseListener (this, true); #endif + setOpaque (true); ignoreUnused (fakeMouseGenerator); } @@ -1055,42 +1040,35 @@ public: // have been transferred to another parent which takes over ownership. } - void paint (Graphics&) override {} + void paint (Graphics& g) override + { + g.fillAll (Colours::black); + } void getEditorBounds (Vst2::VstEditorBounds& bounds) { - auto b = getSizeToContainChild(); - bounds = convertToHostBounds ({ 0, 0, (int16) b.getHeight(), (int16) b.getWidth() }); + auto editorBounds = getSizeToContainChild(); + bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); } void attachToHost (VstOpCodeArguments args) { - setOpaque (true); setVisible (false); - #if JUCE_WINDOWS + #if JUCE_WINDOWS || JUCE_LINUX addToDesktop (0, args.ptr); - hostWindow = (HWND) args.ptr; - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE + hostWindow = (HostWindowType) args.ptr; + + #if JUCE_LINUX + X11Symbols::getInstance()->xReparentWindow (display, + (Window) getWindowHandle(), + (HostWindowType) hostWindow, + 0, 0); + #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE checkHostWindowScaleFactor(); startTimer (500); #endif - #elif JUCE_LINUX - addToDesktop (0, args.ptr); - hostWindow = (Window) args.ptr; - X11Symbols::getInstance()->xReparentWindow (display, (Window) getWindowHandle(), hostWindow, 0, 0); - - if (auto* peer = getPeer()) - { - auto screenBounds = peer->localToGlobal (peer->getBounds()); - - auto scale = Desktop::getInstance().getDisplays().findDisplayForRect (screenBounds, false).scale - / Desktop::getInstance().getGlobalScaleFactor(); - - setContentScaleFactor ((float) scale); - } - #else + #elif JUCE_MAC hostWindow = attachComponentToWindowRefVST (this, args.ptr, wrapper.useNSView); #endif @@ -1101,15 +1079,10 @@ public: { #if JUCE_MAC if (hostWindow != nullptr) - { detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView); - hostWindow = nullptr; - } #endif - #if JUCE_LINUX hostWindow = {}; - #endif } void checkVisibility() @@ -1127,23 +1100,21 @@ public: void resized() override { - auto newBounds = getLocalBounds(); - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - if (! lastBounds.isEmpty() && isWithin (newBounds.toDouble().getAspectRatio(), lastBounds.toDouble().getAspectRatio(), 0.1)) - return; - - lastBounds = newBounds; - #endif - - if (auto* ed = getEditorComp()) + if (auto* pluginEditor = getEditorComp()) { - ed->setTopLeftPosition (0, 0); + if (! resizingParent) + { + auto newBounds = getLocalBounds(); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); + } - if (shouldResizeEditor) - ed->setBounds (ed->getLocalArea (this, newBounds)); + lastBounds = newBounds; + } - updateWindowSize (false); + updateWindowSize(); } #if JUCE_MAC && ! JUCE_64BIT @@ -1154,59 +1125,63 @@ public: void parentSizeChanged() override { - updateWindowSize (true); + updateWindowSize(); } void childBoundsChanged (Component*) override { - updateWindowSize (false); + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + updateWindowSize(); + lastBounds = newBounds; + } } juce::Rectangle getSizeToContainChild() { - if (auto* ed = getEditorComp()) - return getLocalArea (ed, ed->getLocalBounds()); + if (auto* pluginEditor = getEditorComp()) + return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); return {}; } - void updateWindowSize (bool resizeEditor) + void updateWindowSize() { - if (! isInSizeWindow) + if (! resizingParent + && getEditorComp() != nullptr + && hostWindow != HostWindowType{}) { - if (auto* ed = getEditorComp()) - { - ed->setTopLeftPosition (0, 0); - auto pos = getSizeToContainChild(); + auto editorBounds = getSizeToContainChild(); - #if JUCE_MAC - if (wrapper.useNSView) - setTopLeftPosition (0, getHeight() - pos.getHeight()); - #endif - - resizeHostWindow (pos.getWidth(), pos.getHeight()); - - #if ! JUCE_LINUX // setSize() on linux causes renoise and energyxt to fail. - if (! resizeEditor) // this is needed to prevent an infinite resizing loop due to coordinate rounding - shouldResizeEditor = false; + #if JUCE_MAC + if (wrapper.useNSView) + setTopLeftPosition (0, getHeight() - editorBounds.getHeight()); + #endif - setSize (pos.getWidth(), pos.getHeight()); + resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); - shouldResizeEditor = true; - #else - ignoreUnused (resizeEditor); + { + const ScopedValueSetter resizingParentSetter (resizingParent, true); - auto scale = Desktop::getInstance().getGlobalScaleFactor(); + #if JUCE_LINUX // setSize() on linux causes renoise and energyxt to fail. + auto rect = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), - static_cast (roundToInt ((float) pos.getWidth() * scale)), - static_cast (roundToInt ((float) pos.getHeight() * scale))); - #endif - - #if JUCE_MAC - resizeHostWindow (pos.getWidth(), pos.getHeight()); // (doing this a second time seems to be necessary in tracktion) + static_cast (rect.rightmost - rect.leftmost), + static_cast (rect.lower - rect.upper)); + #else + setSize (editorBounds.getWidth(), editorBounds.getHeight()); #endif } + + #if JUCE_MAC + resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); // (doing this a second time seems to be necessary in tracktion) + #endif } } @@ -1224,23 +1199,23 @@ public: if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive()) { - const ScopedValueSetter inSizeWindowSetter (isInSizeWindow, true); + const ScopedValueSetter resizingParentSetter (resizingParent, true); sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::hostOpcodeWindowSize, newWidth, newHeight, nullptr, 0) != 0); } } + // some hosts don't support the sizeWindow call, so do it manually.. if (! sizeWasSuccessful) { - // some hosts don't support the sizeWindow call, so do it manually.. + const ScopedValueSetter resizingParentSetter (resizingParent, true); + #if JUCE_MAC setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView); - #elif JUCE_LINUX // (Currently, all linux hosts support sizeWindow, so this should never need to happen) setSize (newWidth, newHeight); - #else int dw = 0; int dh = 0; @@ -1286,12 +1261,6 @@ public: SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); #endif } - - if (auto* peer = getPeer()) - { - peer->handleMovedOrResized(); - repaint(); - } } void setContentScaleFactor (float scale) @@ -1300,27 +1269,22 @@ public: { editorScaleFactor = scale; - if (auto* ed = getEditorComp()) - ed->setScaleFactor (editorScaleFactor); - - updateWindowSize (true); - } - } + if (auto* pluginEditor = getEditorComp()) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor() - { - auto hostWindowScale = (float) getScaleFactorForWindow (hostWindow); + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); - if (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, editorScaleFactor)) - wrapper.handleSetContentScaleFactor (hostWindowScale); - } + pluginEditor->setScaleFactor (editorScaleFactor); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); + } - void timerCallback() override - { - checkHostWindowScaleFactor(); + lastBounds = getSizeToContainChild(); + updateWindowSize(); + } + } } - #endif #if JUCE_WINDOWS void mouseDown (const MouseEvent&) override @@ -1336,6 +1300,21 @@ public: if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void checkHostWindowScaleFactor() + { + auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); + + if (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, editorScaleFactor)) + wrapper.handleSetContentScaleFactor (hostWindowScale); + } + + void timerCallback() override + { + checkHostWindowScaleFactor(); + } + #endif #endif #if JUCE_MAC @@ -1347,6 +1326,7 @@ public: } #endif + private: //============================================================================== static Vst2::VstEditorBounds convertToHostBounds (const Vst2::VstEditorBounds& rect) { @@ -1364,29 +1344,55 @@ public: //============================================================================== JuceVSTWrapper& wrapper; FakeMouseMoveGenerator fakeMouseGenerator; - bool isInSizeWindow = false; - bool shouldResizeEditor = true; + bool resizingChild = false, resizingParent = false; float editorScaleFactor = 1.0f; + juce::Rectangle lastBounds; - #if JUCE_MAC - void* hostWindow = nullptr; - #elif JUCE_LINUX + #if JUCE_LINUX + using HostWindowType = ::Window; ::Display* display = XWindowSystem::getInstance()->getDisplay(); - Window hostWindow = {}; #elif JUCE_WINDOWS - HWND hostWindow = {}; + using HostWindowType = HWND; WindowsHooks hooks; - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - juce::Rectangle lastBounds; - #endif + #else + using HostWindowType = void*; #endif + HostWindowType hostWindow = {}; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) }; //============================================================================== private: + struct HostChangeUpdater : private AsyncUpdater + { + explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} + ~HostChangeUpdater() override { cancelPendingUpdate(); } + + void update (const ChangeDetails& details) + { + if (details.latencyChanged) + owner.vstEffect.latency = owner.processor->getLatencySamples(); + + if (details.parameterInfoChanged || details.programChanged) + triggerAsyncUpdate(); + } + + private: + void handleAsyncUpdate() override + { + if (auto* callback = owner.hostCallback) + { + callback (&owner.vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); + callback (&owner.vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); + } + } + + JuceVSTWrapper& owner; + }; + static JuceVSTWrapper* getWrapper (Vst2::VstEffectInterface* v) noexcept { return static_cast (v->effectPointer); } bool isProcessLevelOffline() @@ -1620,9 +1626,9 @@ private: if (editorComp != nullptr) { - editorComp->getEditorBounds (editorBounds); - *((Vst2::VstEditorBounds**) args.ptr) = &editorBounds; - return (pointer_sized_int) &editorBounds; + editorComp->getEditorBounds (editorRect); + *((Vst2::VstEditorBounds**) args.ptr) = &editorRect; + return (pointer_sized_int) &editorRect; } return 0; @@ -2081,7 +2087,7 @@ private: juce::MemoryBlock chunkMemory; uint32 chunkMemoryTime = 0; std::unique_ptr editorComp; - Vst2::VstEditorBounds editorBounds; + Vst2::VstEditorBounds editorRect; MidiBuffer midiEvents; VSTMidiEventList outgoingEvents; @@ -2110,6 +2116,8 @@ private: ThreadLocalValue inParameterChangedCallback; + HostChangeUpdater hostChangeUpdater { *this }; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) }; diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index c09d99fc..7909098c 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -228,9 +228,26 @@ public: return getParamForVSTParamID (bypassParamID); } + AudioProcessorParameter* getProgramParameter() const noexcept + { + return getParamForVSTParamID (JuceAudioProcessor::paramPreset); + } + static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) { - return group == nullptr ? Vst::kRootUnitId : group->getID().hashCode(); + if (group == nullptr || group->getParent() == nullptr) + return Vst::kRootUnitId; + + // From the VST3 docs (also applicable to unit IDs!): + // Up to 2^31 parameters can be exported with id range [0, 2147483648] + // (the range [2147483649, 429496729] is reserved for host application). + auto unitID = group->getID().hashCode() & 0x7fffffff; + + // If you hit this assertion then your group ID is hashing to a value + // reserved by the VST3 SDK. Please use a different group ID. + jassert (unitID != Vst::kRootUnitId); + + return unitID; } int getNumParameters() const noexcept { return vstParamIDs.size(); } @@ -260,6 +277,21 @@ private: { parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); + #if JUCE_DEBUG + auto allGroups = parameterGroups; + allGroups.add (&audioProcessor->getParameterTree()); + std::unordered_set unitIDs; + + for (auto* group : allGroups) + { + auto insertResult = unitIDs.insert (getUnitID (group)); + + // If you hit this assertion then either a group ID is not unique or + // you are very unlucky and a hashed group ID is not unique + jassert (insertResult.second); + } + #endif + #if JUCE_FORCE_USE_LEGACY_PARAM_IDS const bool forceLegacyParamIDs = true; #else @@ -306,6 +338,20 @@ private: vstParamIDs.add (vstParamID); paramMap.set (static_cast (vstParamID), juceParam); } + + auto numPrograms = audioProcessor->getNumPrograms(); + + if (numPrograms > 1) + { + ownedProgramParameter = std::make_unique ("juceProgramParameter", "Program", + 0, numPrograms - 1, + audioProcessor->getCurrentProgram()); + + juceParameters.params.add (ownedProgramParameter.get()); + + vstParamIDs.add (JuceAudioProcessor::paramPreset); + paramMap.set (static_cast (JuceAudioProcessor::paramPreset), ownedProgramParameter.get()); + } } Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param) @@ -333,7 +379,7 @@ private: //============================================================================== LegacyAudioParametersWrapper juceParameters; HashMap paramMap; - std::unique_ptr ownedBypassParameter; + std::unique_ptr ownedBypassParameter, ownedProgramParameter; Array parameterGroups; JuceAudioProcessor() = delete; @@ -349,8 +395,7 @@ class JuceVST3EditController : public Vst::EditController, public Vst::IMidiMapping, public Vst::IUnitInfo, public Vst::ChannelContext::IInfoListener, - public AudioProcessorListener, - private AudioProcessorParameter::Listener + public AudioProcessorListener { public: JuceVST3EditController (Vst::IHostApplication* host) @@ -564,16 +609,20 @@ public: bool setNormalized (Vst::ParamValue v) override { - Vst::ParamValue program = v * info.stepCount; - - if (! isPositiveAndBelow ((int) program, owner.getNumPrograms())) - return false; + auto programValue = roundToInt (toPlain (v)); - if (valueNormalized != v) + if (isPositiveAndBelow (programValue, owner.getNumPrograms())) { - valueNormalized = v; - changed(); - return true; + if (programValue != owner.getCurrentProgram()) + owner.setCurrentProgram (programValue); + + if (valueNormalized != v) + { + valueNormalized = v; + changed(); + + return true; + } } return false; @@ -653,17 +702,21 @@ public: //============================================================================== tresult PLUGIN_API setComponentState (IBStream* stream) override { - // Cubase and Nuendo need to inform the host of the current parameter values if (auto* pluginInstance = getPluginInstance()) { for (auto vstParamId : audioProcessor->vstParamIDs) - setParamNormalized (vstParamId, audioProcessor->getParamForVSTParamID (vstParamId)->getValue()); + { + auto paramValue = [&] + { + if (vstParamId == JuceAudioProcessor::paramPreset) + return EditController::plainParamToNormalized (JuceAudioProcessor::paramPreset, + pluginInstance->getCurrentProgram()); - auto numPrograms = pluginInstance->getNumPrograms(); + return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); + }(); - if (numPrograms > 1) - setParamNormalized (JuceAudioProcessor::paramPreset, static_cast (pluginInstance->getCurrentProgram()) - / static_cast (numPrograms - 1)); + setParamNormalized (vstParamId, paramValue); + } } if (auto* handler = getComponentHandler()) @@ -882,7 +935,7 @@ public: } //============================================================================== - void paramChanged (Vst::ParamID vstParamId, float newValue) + void paramChanged (Vst::ParamID vstParamId, double newValue) { if (inParameterChangedCallback.get()) { @@ -891,8 +944,8 @@ public: } // NB: Cubase has problems if performEdit is called without setParamNormalized - EditController::setParamNormalized (vstParamId, (double) newValue); - performEdit (vstParamId, (double) newValue); + EditController::setParamNormalized (vstParamId, newValue); + performEdit (vstParamId, newValue); } //============================================================================== @@ -904,39 +957,43 @@ public: paramChanged (audioProcessor->getVSTParamIDForIndex (index), newValue); } - void audioProcessorChanged (AudioProcessor*) override + void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override { int32 flags = 0; - for (int32 i = 0; i < parameters.getParameterCount(); ++i) - if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) - if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) - flags |= Vst::kParamTitlesChanged; + if (details.parameterInfoChanged) + { + for (int32 i = 0; i < parameters.getParameterCount(); ++i) + if (auto* param = dynamic_cast (parameters.getParameterByIndex (i))) + if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) + flags |= Vst::kParamTitlesChanged; + } if (auto* pluginInstance = getPluginInstance()) { - auto newNumPrograms = pluginInstance->getNumPrograms(); - - if (newNumPrograms != lastNumPrograms) + if (details.programChanged && audioProcessor->getProgramParameter() != nullptr) { - if (newNumPrograms > 1) + auto currentProgram = pluginInstance->getCurrentProgram(); + auto paramValue = roundToInt (EditController::normalizedParamToPlain (JuceAudioProcessor::paramPreset, + EditController::getParamNormalized (JuceAudioProcessor::paramPreset))); + + if (currentProgram != paramValue) { - auto paramValue = static_cast (pluginInstance->getCurrentProgram()) - / static_cast (pluginInstance->getNumPrograms() - 1); + beginEdit (JuceAudioProcessor::paramPreset); + paramChanged (JuceAudioProcessor::paramPreset, + EditController::plainParamToNormalized (JuceAudioProcessor::paramPreset, currentProgram)); + endEdit (JuceAudioProcessor::paramPreset); - EditController::setParamNormalized (JuceAudioProcessor::paramPreset, paramValue); flags |= Vst::kParamValuesChanged; } - - lastNumPrograms = newNumPrograms; } - auto newLatencySamples = pluginInstance->getLatencySamples(); + auto latencySamples = pluginInstance->getLatencySamples(); - if (newLatencySamples != lastLatencySamples) + if (details.latencyChanged && latencySamples != lastLatencySamples) { flags |= Vst::kLatencyChanged; - lastLatencySamples = newLatencySamples; + lastLatencySamples = latencySamples; } } @@ -944,19 +1001,6 @@ public: componentHandler->restartComponent (flags); } - void parameterValueChanged (int, float newValue) override - { - // this can only come from the bypass parameter - paramChanged (audioProcessor->bypassParamID, newValue); - } - - void parameterGestureChanged (int, bool gestureIsStarting) override - { - // this can only come from the bypass parameter - if (gestureIsStarting) beginEdit (audioProcessor->bypassParamID); - else endEdit (audioProcessor->bypassParamID); - } - //============================================================================== AudioProcessor* getPluginInstance() const noexcept { @@ -971,7 +1015,7 @@ private: friend struct Param; //============================================================================== - ComSmartPtr audioProcessor; + VSTComSmartPtr audioProcessor; struct MidiController { @@ -983,11 +1027,42 @@ private: MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; + //============================================================================== + struct OwnedParameterListener : public AudioProcessorParameter::Listener + { + OwnedParameterListener (JuceVST3EditController& editController, + AudioProcessorParameter& juceParameter, + Vst::ParamID paramID) + : owner (editController), + vstParamID (paramID) + { + juceParameter.addListener (this); + } + + void parameterValueChanged (int, float newValue) override + { + owner.paramChanged (vstParamID, newValue); + } + + void parameterGestureChanged (int, bool gestureIsStarting) override + { + if (gestureIsStarting) + owner.beginEdit (vstParamID); + else + owner.endEdit (vstParamID); + } + + JuceVST3EditController& owner; + Vst::ParamID vstParamID; + }; + + std::vector> ownedParameterListeners; + //============================================================================== std::atomic vst3IsPlaying { false }, inSetupProcessing { false }; - int lastNumPrograms = 0, lastLatencySamples = 0; + int lastLatencySamples = 0; #if ! JUCE_MAC float lastScaleFactorReceived = 1.0f; @@ -999,10 +1074,11 @@ private: { pluginInstance->addListener (this); - // as the bypass is not part of the regular parameters - // we need to listen for it explicitly + // as the bypass is not part of the regular parameters we need to listen for it explicitly if (! audioProcessor->bypassIsRegularParameter) - audioProcessor->getBypassParameter()->addListener (this); + ownedParameterListeners.push_back (std::make_unique (*this, + *audioProcessor->getBypassParameter(), + audioProcessor->bypassParamID)); if (parameters.getParameterCount() <= 0) { @@ -1011,6 +1087,10 @@ private: for (int i = 0; i < n; ++i) { auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); + + if (vstParamID == JuceAudioProcessor::paramPreset) + continue; + auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); @@ -1019,8 +1099,14 @@ private: (vstParamID == audioProcessor->bypassParamID))); } - if (pluginInstance->getNumPrograms() > 1) + if (auto* programParam = audioProcessor->getProgramParameter()) + { + ownedParameterListeners.push_back (std::make_unique (*this, + *programParam, + JuceAudioProcessor::paramPreset)); + parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); + } } #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS @@ -1030,7 +1116,7 @@ private: initialiseMidiControllerMappings(); #endif - audioProcessorChanged (pluginInstance); + audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); } } @@ -1138,8 +1224,8 @@ private: createContentWrapperComponentIfNeeded(); #if JUCE_WINDOWS || JUCE_LINUX - component->addToDesktop (0, parent); component->setOpaque (true); + component->addToDesktop (0, (void*) systemWindow); component->setVisible (true); #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE @@ -1206,10 +1292,7 @@ private: if (component != nullptr) { - auto w = rect.getWidth(); - auto h = rect.getHeight(); - - component->setSize (w, h); + component->setSize (rect.getWidth(), rect.getHeight()); #if JUCE_MAC if (cubase10Workaround != nullptr) @@ -1233,6 +1316,11 @@ private: tresult PLUGIN_API getSize (ViewRect* size) override { + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + if (getHostType().isAbletonLive() && systemWindow == nullptr) + return kResultFalse; + #endif + if (size != nullptr && component != nullptr) { auto editorBounds = component->getSizeToContainChild(); @@ -1264,20 +1352,19 @@ private: { *rectToCheck = convertFromHostBounds (*rectToCheck); - auto transformScale = std::sqrt (std::abs (editor->getTransform().getDeterminant())); + auto editorBounds = editor->getLocalArea (component.get(), + Rectangle::leftTopRightBottom (rectToCheck->left, rectToCheck->top, + rectToCheck->right, rectToCheck->bottom).toFloat()); - auto minW = (double) ((float) constrainer->getMinimumWidth() * transformScale); - auto maxW = (double) ((float) constrainer->getMaximumWidth() * transformScale); - auto minH = (double) ((float) constrainer->getMinimumHeight() * transformScale); - auto maxH = (double) ((float) constrainer->getMaximumHeight() * transformScale); + auto minW = (float) constrainer->getMinimumWidth(); + auto maxW = (float) constrainer->getMaximumWidth(); + auto minH = (float) constrainer->getMinimumHeight(); + auto maxH = (float) constrainer->getMaximumHeight(); - auto width = (double) (rectToCheck->right - rectToCheck->left); - auto height = (double) (rectToCheck->bottom - rectToCheck->top); + auto width = jlimit (minW, maxW, editorBounds.getWidth()); + auto height = jlimit (minH, maxH, editorBounds.getHeight()); - width = jlimit (minW, maxW, width); - height = jlimit (minH, maxH, height); - - auto aspectRatio = constrainer->getFixedAspectRatio(); + auto aspectRatio = (float) constrainer->getFixedAspectRatio(); if (aspectRatio != 0.0) { @@ -1285,9 +1372,11 @@ private: if (getHostType().type == PluginHostType::SteinbergCubase9) { - if (editor->getWidth() == width && editor->getHeight() != height) + auto currentEditorBounds = editor->getBounds().toFloat(); + + if (currentEditorBounds.getWidth() == width && currentEditorBounds.getHeight() != height) adjustWidth = true; - else if (editor->getHeight() == height && editor->getWidth() != width) + else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) adjustWidth = false; } @@ -1313,8 +1402,11 @@ private: } } - rectToCheck->right = rectToCheck->left + roundToInt (width); - rectToCheck->bottom = rectToCheck->top + roundToInt (height); + auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)) + .getSmallestIntegerContainer(); + + rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); + rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); *rectToCheck = convertToHostBounds (*rectToCheck); } @@ -1349,16 +1441,7 @@ private: owner->lastScaleFactorReceived = editorScaleFactor; if (component != nullptr) - { - if (auto* editor = component->pluginEditor.get()) - { - editor->setScaleFactor (editorScaleFactor); - - component->resizeHostWindow(); - component->setTopLeftPosition (0, 0); - component->repaint(); - } - } + component->setEditorScaleFactor (editorScaleFactor); } return kResultTrue; @@ -1470,14 +1553,18 @@ private: if (resizingChild) return; - auto b = getSizeToContainChild(); + auto newBounds = getSizeToContainChild(); - if (lastBounds != b) + if (newBounds != lastBounds) { - lastBounds = b; - - const ScopedValueSetter resizingParentSetter (resizingParent, true); resizeHostWindow(); + + #if JUCE_LINUX + if (getHostType().isBitwigStudio()) + repaint(); + #endif + + lastBounds = newBounds; } } @@ -1489,33 +1576,12 @@ private: { auto newBounds = getLocalBounds(); - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - if (! lastBounds.isEmpty() && isWithin (newBounds.toDouble().getAspectRatio(), lastBounds.toDouble().getAspectRatio(), 0.1)) - return; - #endif - - lastBounds = newBounds; - - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - if (auto* constrainer = pluginEditor->getConstrainer()) { - auto aspectRatio = constrainer->getFixedAspectRatio(); - - if (aspectRatio != 0) - { - auto width = (double) lastBounds.getWidth(); - auto height = (double) lastBounds.getHeight(); - - if (width / height > aspectRatio) - setBounds ({ 0, 0, roundToInt (height * aspectRatio), lastBounds.getHeight() }); - else - setBounds ({ 0, 0, lastBounds.getWidth(), roundToInt (width / aspectRatio) }); - } + const ScopedValueSetter resizingChildSetter (resizingChild, true); + pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); } - pluginEditor->setTopLeftPosition (0, 0); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, getLocalBounds())); + lastBounds = newBounds; } } } @@ -1533,31 +1599,45 @@ private: { if (pluginEditor != nullptr) { - auto b = getSizeToContainChild(); - auto w = b.getWidth(); - auto h = b.getHeight(); - auto host = getHostType(); - - #if JUCE_WINDOWS - setSize (w, h); - #endif - if (owner.plugFrame != nullptr) { - auto newSize = convertToHostBounds ({ 0, 0, b.getWidth(), b.getHeight() }); + auto editorBounds = getSizeToContainChild(); + auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); { const ScopedValueSetter resizingParentSetter (resizingParent, true); owner.plugFrame->resizeView (&owner, &newSize); } + auto host = getHostType(); + #if JUCE_MAC if (host.isWavelab() || host.isReaper()) #else if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio()) #endif - setBounds (0, 0, w, h); + setBounds (editorBounds.withPosition (0, 0)); + } + } + } + + void setEditorScaleFactor (float scale) + { + if (pluginEditor != nullptr) + { + auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); + + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + pluginEditor->setScaleFactor (scale); + pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); } + + lastBounds = getSizeToContainChild(); + + resizeHostWindow(); + repaint(); } } @@ -1599,7 +1679,7 @@ private: //============================================================================== ScopedJuceInitialiser_GUI libraryInitialiser; - ComSmartPtr owner; + VSTComSmartPtr owner; AudioProcessor& pluginInstance; std::unique_ptr component; @@ -2555,20 +2635,11 @@ public: { auto vstParamID = paramQueue->getParameterId(); - if (vstParamID == JuceAudioProcessor::paramPreset) - { - auto numPrograms = pluginInstance->getNumPrograms(); - auto programValue = roundToInt (value * (jmax (0, numPrograms - 1))); - - if (numPrograms > 1 && isPositiveAndBelow (programValue, numPrograms) - && programValue != pluginInstance->getCurrentProgram()) - pluginInstance->setCurrentProgram (programValue); - } #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS - else if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) + if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value); - #endif else + #endif { auto floatValue = static_cast (value); @@ -2910,9 +2981,9 @@ private: std::atomic refCount { 1 }; AudioProcessor* pluginInstance; - ComSmartPtr host; - ComSmartPtr comPluginInstance; - ComSmartPtr juceVST3EditController; + VSTComSmartPtr host; + VSTComSmartPtr comPluginInstance; + VSTComSmartPtr juceVST3EditController; /** Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, @@ -3246,7 +3317,7 @@ private: //============================================================================== std::atomic refCount { 1 }; const PFactoryInfo factoryInfo; - ComSmartPtr host; + VSTComSmartPtr host; //============================================================================== struct ClassEntry @@ -3268,7 +3339,7 @@ private: std::vector> classes; //============================================================================== - template + template tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) { if (info != nullptr) diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client.h b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client.h index 752badd1..7e43e6a9 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client.h +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client.h @@ -35,7 +35,7 @@ ID: juce_audio_plugin_client vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE audio plugin wrapper classes description: Classes for building VST, VST3, AudioUnit, AAX and RTAS plugins. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm index c068f7b1..1b5be02f 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_AU_2.mm @@ -23,6 +23,8 @@ ============================================================================== */ +#include + #if JucePlugin_Build_AU #include @@ -44,7 +46,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wparentheses", "-Wnullable-to-nonnull-conversion", "-Wignored-qualifiers", "-Wfour-char-constants", - "-Wmissing-prototypes") + "-Wmissing-prototypes", + "-Wdeprecated-anon-enum-enum-conversion", + "-Wambiguous-reversed-operator") // From MacOS 10.13 and iOS 11 Apple has (sensibly!) stopped defining a whole // set of functions with rather generic names. However, we still need a couple diff --git a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp index fcfe4d85..98bac498 100644 --- a/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp +++ b/libs/juce-current/source/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp @@ -23,6 +23,8 @@ ============================================================================== */ +#include + #if JucePlugin_Build_Standalone #if ! JUCE_MODULE_AVAILABLE_juce_audio_utils diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h index 2dcbf39f..c311fdc0 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -353,11 +353,22 @@ struct AudioUnitHelpers auto defaultInputs = processor.getChannelCountOfBus (true, 0); auto defaultOutputs = processor.getChannelCountOfBus (false, 0); - SortedSet supportedChannels; + struct Channels + { + SInt16 ins, outs; + + std::pair makePair() const noexcept { return std::make_pair (ins, outs); } + + bool operator< (const Channels& other) const noexcept { return makePair() < other.makePair(); } + bool operator== (const Channels& other) const noexcept { return makePair() == other.makePair(); } + }; + + SortedSet supportedChannels; // add the current configuration if (defaultInputs != 0 || defaultOutputs != 0) - supportedChannels.add ((defaultInputs << 16) | defaultOutputs); + supportedChannels.add ({ static_cast (defaultInputs), + static_cast (defaultOutputs) }); for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) { @@ -375,19 +386,16 @@ struct AudioUnitHelpers if (! isNumberOfChannelsSupported (outBus, outChanNum, outLayout)) continue; - supportedChannels.add (((hasMainInputBus ? outLayout.getMainInputChannels() : 0) << 16) - | (hasMainOutputBus ? outLayout.getMainOutputChannels() : 0)); + supportedChannels.add ({ static_cast (hasMainInputBus ? outLayout.getMainInputChannels() : 0), + static_cast (hasMainOutputBus ? outLayout.getMainOutputChannels() : 0) }); } } auto hasInOutMismatch = false; - for (auto supported : supportedChannels) + for (const auto& supported : supportedChannels) { - auto numInputs = (supported >> 16) & 0xffff; - auto numOutputs = (supported >> 0) & 0xffff; - - if (numInputs != numOutputs) + if (supported.ins != supported.outs) { hasInOutMismatch = true; break; @@ -398,9 +406,10 @@ struct AudioUnitHelpers for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) { - auto channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); + Channels channelConfiguration { static_cast (inChanNum), + static_cast (hasInOutMismatch ? defaultOutputs : inChanNum) }; - if (! supportedChannels.contains (channelConfiguration)) + if (supportedChannels.contains (channelConfiguration)) { hasUnsupportedInput = true; break; @@ -409,25 +418,23 @@ struct AudioUnitHelpers for (auto outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) { - auto channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; + Channels channelConfiguration { static_cast (hasInOutMismatch ? defaultInputs : outChanNum), + static_cast (outChanNum) }; - if (! supportedChannels.contains (channelConfiguration)) + if (supportedChannels.contains (channelConfiguration)) { hasUnsupportedOutput = true; break; } } - for (auto supported : supportedChannels) + for (const auto& supported : supportedChannels) { - auto numInputs = (supported >> 16) & 0xffff; - auto numOutputs = (supported >> 0) & 0xffff; - AUChannelInfo info; // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html - info.inChannels = static_cast (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); - info.outChannels = static_cast (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); + info.inChannels = static_cast (hasMainInputBus ? (hasUnsupportedInput ? supported.ins : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); + info.outChannels = static_cast (hasMainOutputBus ? (hasUnsupportedOutput ? supported.outs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); if (info.inChannels == -2 && info.outChannels == -2) info.inChannels = -1; diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 613ef952..7cad3e7c 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -25,6 +25,8 @@ #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + #if JUCE_MAC #include #include @@ -247,7 +249,8 @@ namespace AudioUnitFormatHelpers if (Handle h = Get1IndResource (thngType, i)) { HLock (h); - const uint32* const types = (const uint32*) *h; + uint32 types[3]; + std::memcpy (types, *h, sizeof (types)); if (types[0] == kAudioUnitType_MusicDevice || types[0] == kAudioUnitType_MusicEffect @@ -759,7 +762,7 @@ public: zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) stream.mSampleRate = sampleRate; stream.mFormatID = kAudioFormatLinearPCM; - stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; + stream.mFormatFlags = (int) kAudioFormatFlagsNativeFloatPacked | (int) kAudioFormatFlagIsNonInterleaved | (int) kAudioFormatFlagsNativeEndian; stream.mFramesPerPacket = 1; stream.mBytesPerPacket = 4; stream.mBytesPerFrame = 4; @@ -1823,12 +1826,12 @@ private: default: if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) { - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); } else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) { sendAllParametersChangedEvents(); - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); } else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) { @@ -2912,4 +2915,6 @@ FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() } // namespace juce +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + #endif diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index 8ffe16b0..ae062599 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -374,7 +374,7 @@ public: destData.setSize ((size_t) numParameters * sizeof (float)); destData.fillWith (0); - auto* p = (float*) ((char*) destData.getData()); + auto* p = unalignedPointerCast (destData.getData()); for (int i = 0; i < numParameters; ++i) if (auto* param = getParameters()[i]) diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3Common.h old mode 100644 new mode 100755 index 8ee6db87..fb38a4f0 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -68,12 +68,14 @@ static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexc #endif //============================================================================== -inline juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (string); } -inline juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } +inline juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (juce::CharPointer_UTF8 ((juce::CharPointer_UTF8::CharType*) string)); } +inline juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } // NB: The casts are handled by a Steinberg::UString operator -inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast (string)); } -inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast (string)); } +inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast (string)); } +inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast (string)); } + +inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept { return reinterpret_cast (source.toUTF16().getAddress()); } inline void toString128 (Steinberg::Vst::String128 result, const char* source) { @@ -82,12 +84,7 @@ inline void toString128 (Steinberg::Vst::String128 result, const char* source) inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) { - Steinberg::UString (result, 128).fromAscii (source.toUTF8()); -} - -inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept -{ - return reinterpret_cast (source.toUTF16().getAddress()); + Steinberg::UString (result, 128).assign (toString (source)); } #if JUCE_WINDOWS @@ -355,24 +352,24 @@ static AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak //============================================================================== template -class ComSmartPtr +class VSTComSmartPtr { public: - ComSmartPtr() noexcept : source (nullptr) {} - ComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } - ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } - ~ComSmartPtr() { if (source != nullptr) source->release(); } + VSTComSmartPtr() noexcept : source (nullptr) {} + VSTComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } + VSTComSmartPtr (const VSTComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } + ~VSTComSmartPtr() { if (source != nullptr) source->release(); } operator ObjectType*() const noexcept { return source; } ObjectType* get() const noexcept { return source; } ObjectType& operator*() const noexcept { return *source; } ObjectType* operator->() const noexcept { return source; } - ComSmartPtr& operator= (const ComSmartPtr& other) { return operator= (other.source); } + VSTComSmartPtr& operator= (const VSTComSmartPtr& other) { return operator= (other.source); } - ComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) + VSTComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) { - ComSmartPtr p (newObjectToTakePossessionOf); + VSTComSmartPtr p (newObjectToTakePossessionOf); std::swap (p.source, source); return *this; } diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 0b6abb60..9161855f 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -31,10 +31,6 @@ namespace juce { -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - extern void setThreadDPIAwarenessForWindow (HWND); -#endif - using namespace Steinberg; //============================================================================== @@ -370,7 +366,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override; #if ! JUCE_MODAL_LOOPS_PERMITTED - static void menuFinished (int modalResult, ComSmartPtr menu) { menu->handleResult (modalResult); } + static void menuFinished (int modalResult, VSTComSmartPtr menu) { menu->handleResult (modalResult); } #endif private: @@ -382,7 +378,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 struct ItemAndTarget { Item item; - ComSmartPtr target; + VSTComSmartPtr target; }; Array items; @@ -446,7 +442,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid)) { - ComSmartPtr m (new Message (attributeList)); + VSTComSmartPtr m (new Message (attributeList)); messageQueue.add (m); m->addRef(); *obj = m; @@ -454,7 +450,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 } else if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid)) { - ComSmartPtr l (new AttributeList (this)); + VSTComSmartPtr l (new AttributeList (this)); l->addRef(); *obj = l; return kResultOk; @@ -541,14 +537,14 @@ private: var value; private: - ComSmartPtr attributeList; + VSTComSmartPtr attributeList; String messageId; Atomic refCount; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) }; - Array, CriticalSection> messageQueue; + Array, CriticalSection> messageQueue; //============================================================================== struct AttributeList : public Vst::IAttributeList @@ -664,7 +660,7 @@ private: } } - owner->messageQueue.add (ComSmartPtr (new Message (this, id, value))); + owner->messageQueue.add (VSTComSmartPtr (new Message (this, id, value))); } template @@ -687,7 +683,7 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) }; - ComSmartPtr attributeList; + VSTComSmartPtr attributeList; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) }; @@ -731,8 +727,8 @@ struct DescriptionFactory std::unique_ptr infoW; { - ComSmartPtr pf2; - ComSmartPtr pf3; + VSTComSmartPtr pf2; + VSTComSmartPtr pf3; if (pf2.loadFrom (factory)) { @@ -752,7 +748,7 @@ struct DescriptionFactory PluginDescription desc; { - ComSmartPtr component; + VSTComSmartPtr component; if (component.loadFrom (factory, info.cid)) { @@ -790,8 +786,8 @@ struct DescriptionFactory virtual Result performOnDescription (PluginDescription&) = 0; private: - ComSmartPtr vst3HostContext; - ComSmartPtr factory; + VSTComSmartPtr vst3HostContext; + VSTComSmartPtr factory; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) }; @@ -849,7 +845,7 @@ struct DLLHandle //============================================================================== /** The factory should begin with a refCount of 1, so don't increment the reference count - (ie: don't use a ComSmartPtr in here)! Its lifetime will be handled by this DLLHandle. + (ie: don't use a VSTComSmartPtr in here)! Its lifetime will be handled by this DLLHandle. */ IPluginFactory* JUCE_CALLTYPE getPluginFactory() { @@ -1102,8 +1098,8 @@ private: //============================================================================== bool open (const PluginDescription& description) { - ComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()) - .getPluginFactory()); + VSTComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()) + .getPluginFactory()); if (pluginFactory != nullptr) { @@ -1140,9 +1136,7 @@ private: //============================================================================== struct VST3PluginWindow : public AudioProcessorEditor, public ComponentMovementWatcher, - #if JUCE_WINDOWS || JUCE_LINUX public ComponentPeer::ScaleFactorListener, - #endif public IPlugFrame { VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) @@ -1155,26 +1149,20 @@ struct VST3PluginWindow : public AudioProcessorEditor, setVisible (true); warnOnFailure (view->setFrame (this)); - - #if ! JUCE_MAC view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); - #endif - resizeToFit(); } ~VST3PluginWindow() override { - #if ! JUCE_MAC if (scaleInterface != nullptr) scaleInterface->release(); - removeScaleFactorListeners(); + removeScaleFactorListener(); #if JUCE_LINUX embeddedComponent.removeClient(); #endif - #endif warnOnFailure (view->removed()); warnOnFailure (view->setFrame (nullptr)); @@ -1183,6 +1171,8 @@ struct VST3PluginWindow : public AudioProcessorEditor, #if JUCE_MAC embeddedComponent.setView (nullptr); + #elif JUCE_WINDOWS + embeddedComponent.setHWND (nullptr); #endif view = nullptr; @@ -1323,79 +1313,50 @@ struct VST3PluginWindow : public AudioProcessorEditor, //============================================================================== void componentPeerChanged() override { - #if ! JUCE_MAC - removeScaleFactorListeners(); + removeScaleFactorListener(); + currentPeer = getTopLevelComponent()->getPeer(); - if (auto* topPeer = getTopLevelComponent()->getPeer()) - topPeer->addScaleFactorListener (this); - #endif + if (currentPeer != nullptr) + { + currentPeer->addScaleFactorListener (this); + nativeScaleFactor = (float) currentPeer->getPlatformScaleFactor(); + } } void componentMovedOrResized (bool, bool wasResized) override { - if (recursiveResize) + if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) return; - auto* topComp = getTopLevelComponent(); + ViewRect rect; - if (topComp->getPeer() != nullptr) + if (view->canResize() == kResultTrue) { - #if JUCE_WINDOWS - auto pos = (topComp->getLocalPoint (this, Point()) * nativeScaleFactor).roundToInt(); - #endif - - const ScopedValueSetter recursiveResizeSetter (recursiveResize, true); + rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); + rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); - ViewRect rect; + view->checkSizeConstraint (&rect); - if (wasResized && view->canResize() == kResultTrue) { - rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); - rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); - - view->checkSizeConstraint (&rect); - - auto w = roundToInt ((float) rect.getWidth() / nativeScaleFactor); - auto h = roundToInt ((float) rect.getHeight() / nativeScaleFactor); - - setSize (w, h); + const ScopedValueSetter recursiveResizeSetter (recursiveResize, true); - #if JUCE_WINDOWS - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - setThreadDPIAwarenessForWindow (pluginHandle); - #endif - - SetWindowPos (pluginHandle, 0, - pos.x, pos.y, rect.getWidth(), rect.getHeight(), - isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); - #else - embeddedComponent.setBounds (getLocalBounds()); - #endif - - view->onSize (&rect); + setSize (roundToInt ((float) rect.getWidth() / nativeScaleFactor), + roundToInt ((float) rect.getHeight() / nativeScaleFactor)); } - else - { - warnOnFailure (view->getSize (&rect)); - #if JUCE_WINDOWS - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - setThreadDPIAwarenessForWindow (pluginHandle); - #endif - - SetWindowPos (pluginHandle, 0, - pos.x, pos.y, rect.getWidth(), rect.getHeight(), - isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); - #else - embeddedComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); - #endif - } + embeddedComponent.setBounds (getLocalBounds()); - // Some plugins don't update their cursor correctly when mousing out the window - Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + view->onSize (&rect); + } + else + { + warnOnFailure (view->getSize (&rect)); + resizeWithRect (embeddedComponent, rect, nativeScaleFactor); } - } + // Some plugins don't update their cursor correctly when mousing out the window + Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); + } using ComponentMovementWatcher::componentMovedOrResized; void componentVisibilityChanged() override @@ -1407,13 +1368,11 @@ struct VST3PluginWindow : public AudioProcessorEditor, componentMovedOrResized (true, true); } - using ComponentMovementWatcher::componentVisibilityChanged; - #if JUCE_WINDOWS || JUCE_LINUX void nativeScaleFactorChanged (double newScaleFactor) override { - if (pluginHandle == 0 || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) + if (pluginHandle == HandleFormat{} || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) return; nativeScaleFactor = (float) newScaleFactor; @@ -1421,7 +1380,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, if (scaleInterface != nullptr) scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); } - #endif void resizeToFit() { @@ -1458,73 +1416,48 @@ private: void attachPluginWindow() { - #if JUCE_MAC - if (pluginHandle == nil) - #else - if (pluginHandle == 0) - #endif + if (pluginHandle == HandleFormat{}) { - #if JUCE_WINDOWS - if (auto* topComp = getTopLevelComponent()) - { - peer.reset (embeddedComponent.createNewPeer (0, topComp->getWindowHandle())); - pluginHandle = (HandleFormat) peer->getNativeHandle(); - nativeScaleFactor = (float) peer->getPlatformScaleFactor(); - } - #else embeddedComponent.setBounds (getLocalBounds()); addAndMakeVisible (embeddedComponent); - #if JUCE_MAC - pluginHandle = (HandleFormat) embeddedComponent.getView(); - jassert (pluginHandle != nil); - #elif JUCE_LINUX - pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); - jassert (pluginHandle != 0); - #endif - #endif #if JUCE_MAC - if (pluginHandle != nil) - #else - if (pluginHandle != 0) + pluginHandle = (HandleFormat) embeddedComponent.getView(); + #elif JUCE_WINDOWS + pluginHandle = (HandleFormat) embeddedComponent.getHWND(); + #elif JUCE_LINUX + pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); #endif - warnOnFailure (view->attached ((void*) pluginHandle, defaultVST3WindowType)); - } - #if ! JUCE_MAC - if (auto* topPeer = getTopLevelComponent()->getPeer()) - { - nativeScaleFactor = 1.0f; // force update - nativeScaleFactorChanged ((float) topPeer->getPlatformScaleFactor()); + if (pluginHandle == HandleFormat{}) + { + jassertfalse; + return; + } + + warnOnFailure (view->attached ((void*) pluginHandle, defaultVST3WindowType)); + + if (scaleInterface != nullptr) + scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); } - #endif } - #if ! JUCE_MAC - void removeScaleFactorListeners() + void removeScaleFactorListener() { + if (currentPeer == nullptr) + return; + for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) - if (auto* p = ComponentPeer::getPeer (i)) - p->removeScaleFactorListener (this); + if (ComponentPeer::getPeer (i) == currentPeer) + currentPeer->removeScaleFactorListener (this); } - #endif //============================================================================== Atomic refCount { 1 }; - ComSmartPtr view; + VSTComSmartPtr view; #if JUCE_WINDOWS - struct ChildComponent : public Component - { - ChildComponent() {} - void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } - using Component::createNewPeer; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) - }; - - ChildComponent embeddedComponent; - std::unique_ptr peer; + HWNDComponentWithParent embeddedComponent; using HandleFormat = HWND; #elif JUCE_MAC AutoResizingNSViewComponentWithParent embeddedComponent; @@ -1538,14 +1471,11 @@ private: #endif HandleFormat pluginHandle = {}; - bool recursiveResize = false; + bool recursiveResize = false, hasDoneInitialResize = false; - #if ! JUCE_MAC + ComponentPeer* currentPeer = nullptr; Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; - #endif - float nativeScaleFactor = 1.0f; - bool hasDoneInitialResize = false; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) @@ -1569,7 +1499,7 @@ struct VST3ComponentHolder // transfers ownership to the plugin instance! AudioPluginInstance* createPluginInstance(); - bool fetchController (ComSmartPtr& editController) + bool fetchController (VSTComSmartPtr& editController) { if (! isComponentInitialised && ! initialise()) return false; @@ -1618,8 +1548,8 @@ struct VST3ComponentHolder ignoreUnused (success); jassert (success); - ComSmartPtr pf2; - ComSmartPtr pf3; + VSTComSmartPtr pf2; + VSTComSmartPtr pf3; std::unique_ptr info2; std::unique_ptr infoW; @@ -1683,7 +1613,7 @@ struct VST3ComponentHolder JUCE_ASSERT_MESSAGE_THREAD #endif - factory = ComSmartPtr (module->getPluginFactory()); + factory = VSTComSmartPtr (module->getPluginFactory()); int classIdx; if ((classIdx = getClassIndex (module->getName())) < 0) @@ -1734,9 +1664,9 @@ struct VST3ComponentHolder //============================================================================== VST3ModuleHandle::Ptr module; - ComSmartPtr factory; - ComSmartPtr host; - ComSmartPtr component; + VSTComSmartPtr factory; + VSTComSmartPtr host; + VSTComSmartPtr component; FUID cidOfComponent; bool isComponentInitialised = false; @@ -2292,7 +2222,7 @@ public: { if (trackInfoListener != nullptr) { - ComSmartPtr l (new TrackPropertiesAttributeList (properties)); + VSTComSmartPtr l (new TrackPropertiesAttributeList (properties)); trackInfoListener->setChannelContextInfos (l); } } @@ -2427,7 +2357,7 @@ public: if (getActiveEditor() != nullptr) return true; - ComSmartPtr view (tryCreatingView(), false); + VSTComSmartPtr view (tryCreatingView(), false); return view != nullptr; } @@ -2509,7 +2439,7 @@ public: bool setStateFromPresetFile (const MemoryBlock& rawData) { MemoryBlock rawDataCopy (rawData); - ComSmartPtr memoryStream = new Steinberg::MemoryStream (rawDataCopy.getData(), (int) rawDataCopy.getSize()); + VSTComSmartPtr memoryStream = new Steinberg::MemoryStream (rawDataCopy.getData(), (int) rawDataCopy.getSize()); if (memoryStream == nullptr || holder->component == nullptr) return false; @@ -2676,17 +2606,17 @@ private: std::unique_ptr infoW; // Rudimentary interfaces: - ComSmartPtr editController; - ComSmartPtr editController2; - ComSmartPtr midiMapping; - ComSmartPtr processor; - ComSmartPtr componentHandler; - ComSmartPtr componentHandler2; - ComSmartPtr unitInfo; - ComSmartPtr unitData; - ComSmartPtr programListData; - ComSmartPtr componentConnection, editControllerConnection; - ComSmartPtr trackInfoListener; + VSTComSmartPtr editController; + VSTComSmartPtr editController2; + VSTComSmartPtr midiMapping; + VSTComSmartPtr processor; + VSTComSmartPtr componentHandler; + VSTComSmartPtr componentHandler2; + VSTComSmartPtr unitInfo; + VSTComSmartPtr unitData; + VSTComSmartPtr programListData; + VSTComSmartPtr componentConnection, editControllerConnection; + VSTComSmartPtr trackInfoListener; /** The number of IO buses MUST match that of the plugin, even if there aren't enough channels to process, @@ -2701,7 +2631,7 @@ private: //============================================================================== template - static void appendStateFrom (XmlElement& head, ComSmartPtr& object, const String& identifier) + static void appendStateFrom (XmlElement& head, VSTComSmartPtr& object, const String& identifier) { if (object != nullptr) { @@ -2715,7 +2645,7 @@ private: } } - static ComSmartPtr createMemoryStreamForState (XmlElement& head, StringRef identifier) + static VSTComSmartPtr createMemoryStreamForState (XmlElement& head, StringRef identifier) { if (auto* state = head.getChildByName (identifier)) { @@ -2723,7 +2653,7 @@ private: if (mem.fromBase64Encoding (state->getAllSubText())) { - ComSmartPtr stream (new Steinberg::MemoryStream(), false); + VSTComSmartPtr stream (new Steinberg::MemoryStream(), false); stream->setSize ((TSize) mem.getSize()); mem.copyTo (stream->getData(), 0, mem.getSize()); return stream; @@ -2733,8 +2663,8 @@ private: return nullptr; } - ComSmartPtr inputParameterChanges, outputParameterChanges; - ComSmartPtr midiInputs, midiOutputs; + VSTComSmartPtr inputParameterChanges, outputParameterChanges; + VSTComSmartPtr midiInputs, midiOutputs; Vst::ProcessContext timingInfo; //< Only use this in processBlock()! bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; VST3Parameter* bypassParam = nullptr; @@ -2872,10 +2802,10 @@ private: setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); } - static AudioProcessor::BusesProperties getBusProperties (ComSmartPtr& component) + static AudioProcessor::BusesProperties getBusProperties (VSTComSmartPtr& component) { AudioProcessor::BusesProperties busProperties; - ComSmartPtr processor; + VSTComSmartPtr processor; processor.loadFrom (component.get()); for (int dirIdx = 0; dirIdx < 2; ++dirIdx) @@ -3179,7 +3109,9 @@ tresult VST3HostContext::restartComponent (Steinberg::int32 flags) if (plugin->processor != nullptr) plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); - plugin->updateHostDisplay(); + plugin->updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) + .withParameterInfoChanged (true)); + return kResultTrue; } @@ -3252,7 +3184,7 @@ tresult VST3HostContext::ContextMenu::popup (Steinberg::UCoord x, Steinberg::UCo // Unfortunately, Steinberg's docs explicitly say this should be modal.. handleResult (topLevelMenu->showMenu (options)); #else - topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr (this))); + topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, VSTComSmartPtr (this))); #endif return kResultOk; @@ -3314,12 +3246,12 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray& resul for every housed plugin. */ - ComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) - .getPluginFactory()); + VSTComSmartPtr pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) + .getPluginFactory()); if (pluginFactory != nullptr) { - ComSmartPtr host (new VST3HostContext()); + VSTComSmartPtr host (new VST3HostContext()); DescriptionLister lister (host, pluginFactory); lister.findDescriptionsAndPerform (File (fileOrIdentifier)); diff --git a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index c39d6f79..d9359b73 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -53,6 +53,7 @@ namespace Vst2 JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_MSVC +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) #include "juce_VSTMidiEventList.h" @@ -77,7 +78,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) //============================================================================== namespace juce { -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE +#if JUCE_WINDOWS extern void setThreadDPIAwarenessForWindow (HWND); #endif @@ -376,7 +377,7 @@ private: switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") })); - forEachXmlChildElement (xml, item) + for (auto* item : xml.getChildIterator()) { if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr); else if (item->hasTagName ("ValueType")) parseValueType (*item); @@ -430,7 +431,7 @@ private: int curEntry = 0; const int numEntries = item.getNumChildElements(); - forEachXmlChildElementWithTagName (item, entryXml, "Entry") + for (auto* entryXml : item.getChildWithTagNameIterator ("Entry")) { auto entry = new Entry(); entry->name = entryXml->getStringAttribute ("name"); @@ -459,7 +460,7 @@ private: templates.add (temp); temp->name = item.getStringAttribute ("name"); - forEachXmlChildElement (item, param) + for (auto* param : item.getChildIterator()) parseParam (*param, nullptr, temp); } @@ -508,7 +509,7 @@ private: } else { - forEachXmlChildElement (item, subItem) + for (auto* subItem : item.getChildIterator()) { if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); @@ -1596,8 +1597,8 @@ struct VSTPluginInstance : public AudioPluginInstance, void handleAsyncUpdate() override { - // indicates that something about the plugin has changed.. - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) + .withParameterInfoChanged (true)); } pointer_sized_int handleCallback (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) @@ -1763,9 +1764,9 @@ struct VSTPluginInstance : public AudioPluginInstance, { if (i != oldProg) { - auto prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); + auto prog = addBytesToPointer (set->programs, i * progLen); - if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) + if (getAddressDifference (prog, set) >= (int) dataSize) return false; if (fxbSwap (set->numPrograms) > 0) @@ -1779,9 +1780,9 @@ struct VSTPluginInstance : public AudioPluginInstance, if (fxbSwap (set->numPrograms) > 0) setCurrentProgram (oldProg); - auto prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); + auto prog = addBytesToPointer (set->programs, oldProg * progLen); - if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) + if (getAddressDifference (prog, set) >= (int) dataSize) return false; if (! restoreProgramSettings (prog)) @@ -1901,14 +1902,14 @@ struct VSTPluginInstance : public AudioPluginInstance, auto oldProgram = getCurrentProgram(); if (oldProgram >= 0) - setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); + setParamsInProgramBlock (addBytesToPointer (set->programs, oldProgram * progLen)); for (int i = 0; i < numPrograms; ++i) { if (i != oldProgram) { setCurrentProgram (i); - setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); + setParamsInProgramBlock (addBytesToPointer (set->programs, i * progLen)); } } @@ -2586,7 +2587,7 @@ private: getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); - auto p = (float*) (((char*) dest.getData()) + 64); + auto p = unalignedPointerCast (((char*) dest.getData()) + 64); for (int i = 0; i < numParameters; ++i) if (auto* param = getParameters()[i]) @@ -2597,7 +2598,7 @@ private: { changeProgramName (getCurrentProgram(), (const char*) m.getData()); - auto p = (float*) (((char*) m.getData()) + 64); + auto p = unalignedPointerCast (((char*) m.getData()) + 64); auto numParameters = getParameters().size(); for (int i = 0; i < numParameters; ++i) @@ -2858,9 +2859,7 @@ public: #if JUCE_WINDOWS if (pluginHWND != 0) { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE setThreadDPIAwarenessForWindow (pluginHWND); - #endif MoveWindow (pluginHWND, pos.getX(), pos.getY(), roundToInt (getWidth() * nativeScaleFactor), @@ -3123,9 +3122,7 @@ private: // very dodgy logic to decide which size is right. if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) { - #if JUCE_WIN_PER_MONITOR_DPI_AWARE setThreadDPIAwarenessForWindow (pluginHWND); - #endif SetWindowPos (pluginHWND, 0, 0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), @@ -3228,7 +3225,7 @@ private: bool willCauseRecursiveResize (int w, int h) { auto newScreenBounds = Rectangle (w, h).withPosition (getScreenPosition()); - return Desktop::getInstance().getDisplays().findDisplayForRect (newScreenBounds).scale != nativeScaleFactor; + return Desktop::getInstance().getDisplays().getDisplayForRect (newScreenBounds)->scale != nativeScaleFactor; } bool isWindowSizeCorrectForPlugin (int w, int h) @@ -3737,6 +3734,7 @@ void VSTPluginFormat::aboutToScanVSTShellPlugin (const PluginDescription&) {} } // namespace juce +JUCE_END_IGNORE_WARNINGS_GCC_LIKE JUCE_END_IGNORE_WARNINGS_MSVC #endif diff --git a/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.cpp b/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.cpp index f494ec3a..14915480 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -81,7 +81,97 @@ static bool arrayContainsPlugin (const OwnedArray& list, #endif -#if JUCE_MAC || JUCE_IOS +#if JUCE_WINDOWS + +//============================================================================== +class HWNDComponentWithParent : public HWNDComponent, + private Timer +{ +public: + HWNDComponentWithParent() + { + String className ("JUCE_"); + className << String::toHexString (Time::getHighResolutionTicks()); + + HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle(); + + WNDCLASSEX wc = {}; + wc.cbSize = sizeof (wc); + wc.lpfnWndProc = (WNDPROC) wndProc; + wc.cbWndExtra = 4; + wc.hInstance = moduleHandle; + wc.lpszClassName = className.toWideCharPointer(); + + atom = RegisterClassEx (&wc); + jassert (atom != 0); + + hwnd = CreateWindow (getClassNameFromAtom(), L"HWNDComponentWithParent", + 0, 0, 0, 0, 0, + nullptr, nullptr, moduleHandle, nullptr); + + jassert (hwnd != nullptr); + + setHWND (hwnd); + startTimer (30); + } + + ~HWNDComponentWithParent() override + { + if (IsWindow (hwnd)) + DestroyWindow (hwnd); + + UnregisterClass (getClassNameFromAtom(), nullptr); + } + +private: + //============================================================================== + static LRESULT CALLBACK wndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) + { + if (message == WM_SHOWWINDOW && wParam == TRUE) + return 0; + + return DefWindowProc (h, message, wParam, lParam); + } + + void timerCallback() override + { + if (HWND child = getChildHWND()) + { + stopTimer(); + + ShowWindow (child, SW_HIDE); + SetParent (child, NULL); + + auto windowFlags = GetWindowLongPtr (child, -16); + + windowFlags &= ~WS_CHILD; + windowFlags |= WS_POPUP; + + SetWindowLongPtr (child, -16, windowFlags); + + setHWND (child); + } + } + + LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) (pointer_sized_uint) atom; } + + HWND getChildHWND() const + { + if (HWND parent = (HWND) getHWND()) + return GetWindow (parent, GW_CHILD); + + return nullptr; + } + + //============================================================================== + ATOM atom; + HWND hwnd; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentWithParent) +}; + +#elif JUCE_MAC || JUCE_IOS #if JUCE_IOS #define JUCE_IOS_MAC_VIEW UIView @@ -130,12 +220,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone } } }; + #endif } // namespace juce -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wcast-align") - #include "format/juce_AudioPluginFormat.cpp" #include "format/juce_AudioPluginFormatManager.cpp" #include "format_types/juce_LegacyAudioParameter.cpp" diff --git a/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.h b/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.h index d01c3b29..deda7366 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/libs/juce-current/source/modules/juce_audio_processors/juce_audio_processors.h @@ -35,7 +35,7 @@ ID: juce_audio_processors vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE audio processor classes description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 9ea12040..df0b6c67 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -412,7 +412,7 @@ void AudioProcessor::setLatencySamples (int newLatency) if (latencySamples != newLatency) { latencySamples = newLatency; - updateHostDisplay(); + updateHostDisplay (AudioProcessorListener::ChangeDetails().withLatencyChanged (true)); } } @@ -423,11 +423,11 @@ AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noex return listeners[index]; } -void AudioProcessor::updateHostDisplay() +void AudioProcessor::updateHostDisplay (const AudioProcessorListener::ChangeDetails& details) { for (int i = listeners.size(); --i >= 0;) if (auto l = getListenerLocked (i)) - l->audioProcessorChanged (this); + l->audioProcessorChanged (this, details); } void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) @@ -445,6 +445,24 @@ void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) #endif } +void AudioProcessor::checkForDuplicateGroupIDs (const AudioProcessorParameterGroup& newGroup) +{ + ignoreUnused (newGroup); + + #if JUCE_DEBUG + auto groups = newGroup.getSubgroups (true); + groups.add (&newGroup); + + for (auto* group : groups) + { + auto insertResult = groupIDs.insert (group->getID()); + + // If you hit this assertion then a group ID is not unique + jassert (insertResult.second); + } + #endif +} + const Array& AudioProcessor::getParameters() const { return flatParameterList; } const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() const { return parameterTree; } @@ -463,6 +481,7 @@ void AudioProcessor::addParameter (AudioProcessorParameter* param) void AudioProcessor::addParameterGroup (std::unique_ptr group) { jassert (group != nullptr); + checkForDuplicateGroupIDs (*group); auto oldSize = flatParameterList.size(); flatParameterList.addArray (group->getParameters (true)); @@ -483,9 +502,12 @@ void AudioProcessor::setParameterTree (AudioProcessorParameterGroup&& newTree) { #if JUCE_DEBUG paramIDs.clear(); + groupIDs.clear(); #endif parameterTree = std::move (newTree); + checkForDuplicateGroupIDs (parameterTree); + flatParameterList = parameterTree.getParameters (true); for (int i = 0; i < flatParameterList.size(); ++i) diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index ab9e8a7d..849b77d3 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -993,7 +993,7 @@ public: It sends a hint to the host that something like the program, number of parameters, etc, has changed, and that it should update itself. */ - void updateHostDisplay(); + void updateHostDisplay (const AudioProcessorListener::ChangeDetails& details = {}); //============================================================================== /** Adds a parameter to the AudioProcessor. @@ -1499,10 +1499,11 @@ private: #endif bool textRecursionCheck = false; - std::unordered_set paramIDs; + std::unordered_set paramIDs, groupIDs; #endif void checkForDuplicateParamID (AudioProcessorParameter*); + void checkForDuplicateGroupIDs (const AudioProcessorParameterGroup&); AudioProcessorListener* getListenerLocked (int) const noexcept; void updateSpeakerFormatStrings(); diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 070f8255..3d4a0d42 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -1265,6 +1265,22 @@ void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamples { const ScopedLock sl (getCallbackLock()); setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock); + + const auto newPrepareSettings = [&] + { + PrepareSettings settings; + settings.precision = getProcessingPrecision(); + settings.sampleRate = sampleRate; + settings.blockSize = estimatedSamplesPerBlock; + settings.valid = true; + return settings; + }(); + + if (prepareSettings != newPrepareSettings) + { + unprepare(); + prepareSettings = newPrepareSettings; + } } clearRenderingSequence(); @@ -1277,16 +1293,23 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const return true; } -void AudioProcessorGraph::releaseResources() +void AudioProcessorGraph::unprepare() { - const ScopedLock sl (getCallbackLock()); - - cancelPendingUpdate(); + prepareSettings.valid = false; isPrepared = 0; for (auto* n : nodes) n->unprepare(); +} + +void AudioProcessorGraph::releaseResources() +{ + const ScopedLock sl (getCallbackLock()); + + cancelPendingUpdate(); + + unprepare(); if (renderSequenceFloat != nullptr) renderSequenceFloat->releaseBuffers(); diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 8d2221f1..17a12189 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -407,6 +407,24 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; private: + struct PrepareSettings + { + ProcessingPrecision precision = ProcessingPrecision::singlePrecision; + double sampleRate = 0.0; + int blockSize = 0; + bool valid = false; + + using Tied = std::tuple; + + Tied tie() const noexcept { return std::tie (precision, sampleRate, blockSize, valid); } + + bool operator== (const PrepareSettings& other) const noexcept { return tie() == other.tie(); } + bool operator!= (const PrepareSettings& other) const noexcept { return tie() != other.tie(); } + }; + //============================================================================== ReferenceCountedArray nodes; NodeID lastNodeID = {}; @@ -416,11 +434,14 @@ private: std::unique_ptr renderSequenceFloat; std::unique_ptr renderSequenceDouble; + PrepareSettings prepareSettings; + friend class AudioGraphIOProcessor; std::atomic isPrepared { false }; void topologyChanged(); + void unprepare(); void handleAsyncUpdate() override; void clearRenderingSequence(); void buildRenderingSequence(); diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h index 7e075b39..9a83d06c 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_AudioProcessorListener.h @@ -57,6 +57,28 @@ public: int parameterIndex, float newValue) = 0; + /** Provides details about aspects of an AudioProcessor which have changed. + */ + struct JUCE_API ChangeDetails + { + bool latencyChanged = false; + bool parameterInfoChanged = false; + bool programChanged = false; + + ChangeDetails withLatencyChanged (bool b) const noexcept { return with (&ChangeDetails::latencyChanged, b); } + ChangeDetails withParameterInfoChanged (bool b) const noexcept { return with (&ChangeDetails::parameterInfoChanged, b); } + ChangeDetails withProgramChanged (bool b) const noexcept { return with (&ChangeDetails::programChanged, b); } + + private: + template + ChangeDetails with (Member&& member, Value&& value) const noexcept + { + auto copy = *this; + copy.*member = std::forward (value); + return copy; + } + }; + /** Called to indicate that something else in the plugin has changed, like its program, number of parameters, etc. @@ -67,7 +89,7 @@ public: to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the message thread. */ - virtual void audioProcessorChanged (AudioProcessor* processor) = 0; + virtual void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) = 0; /** Indicates that a parameter change gesture has started. diff --git a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index 1d1c0df1..9018ad17 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -73,7 +73,7 @@ private: parameterValueHasChanged = 1; } - void audioProcessorChanged (AudioProcessor*) override {} + void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} //============================================================================== void timerCallback() override diff --git a/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index 1bfc7819..f27070b9 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -378,7 +378,7 @@ void KnownPluginList::recreateFromXml (const XmlElement& xml) if (xml.hasTagName ("KNOWNPLUGINS")) { - forEachXmlChildElement (xml, e) + for (auto* e : xml.getChildIterator()) { PluginDescription info; diff --git a/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 28e06a09..88c3f818 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -262,7 +262,7 @@ static bool canShowFolderForPlugin (KnownPluginList& list, int index) static void showFolderForPlugin (KnownPluginList& list, int index) { if (canShowFolderForPlugin (list, index)) - File (list.getTypes()[index].fileOrIdentifier).getParentDirectory().startAsProcess(); + File (list.getTypes()[index].fileOrIdentifier).revealToUser(); } void PluginListComponent::removeMissingPlugins() diff --git a/libs/juce-current/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp b/libs/juce-current/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp index b05ad8a0..2d4bfac2 100644 --- a/libs/juce-current/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp +++ b/libs/juce-current/source/modules/juce_audio_processors/utilities/juce_AudioProcessorValueTreeState.cpp @@ -162,7 +162,7 @@ private: return; unnormalisedValue = newValue; - listeners.call ([=] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); + listeners.call ([this] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); listenersNeedCalling = false; needsUpdate = true; } @@ -696,10 +696,10 @@ public: beginTest ("After construction, the value tree has the expected format"); { TestAudioProcessor proc ({ - std::make_unique ("", "", "", + std::make_unique ("A", "", "", std::make_unique ("a", "", false), std::make_unique ("b", "", NormalisableRange{}, 0.0f)), - std::make_unique ("", "", "", + std::make_unique ("B", "", "", std::make_unique ("c", "", 0, 1, 0), std::make_unique ("d", "", StringArray { "foo", "bar" }, 0)) }); diff --git a/libs/juce-current/source/modules/juce_audio_utils/juce_audio_utils.h b/libs/juce-current/source/modules/juce_audio_utils/juce_audio_utils.h index eb0f304b..dde97cb8 100644 --- a/libs/juce-current/source/modules/juce_audio_utils/juce_audio_utils.h +++ b/libs/juce-current/source/modules/juce_audio_utils/juce_audio_utils.h @@ -35,7 +35,7 @@ ID: juce_audio_utils vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE extra audio utility classes description: Classes for audio-related GUI and miscellaneous tasks. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm b/libs/juce-current/source/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm index 44e99251..cea8dcaf 100644 --- a/libs/juce-current/source/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm +++ b/libs/juce-current/source/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm @@ -30,7 +30,7 @@ namespace CDReaderHelpers { inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) { - forEachXmlChildElementWithTagName (xml, child, "key") + for (auto* child : xml.getChildWithTagNameIterator ("key")) if (child->getAllSubText().trim() == key) return child->getNextElement(); @@ -71,7 +71,7 @@ namespace CDReaderHelpers if (trackArray == nullptr) return "Couldn't find Track Array"; - forEachXmlChildElement (*trackArray, track) + for (auto* track : trackArray->getChildIterator()) { const int trackValue = getIntValueForKey (*track, "Start Block"); if (trackValue < 0) diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_AbstractFifo.h b/libs/juce-current/source/modules/juce_core/containers/juce_AbstractFifo.h index 392c7ac5..8e5c90db 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_AbstractFifo.h +++ b/libs/juce-current/source/modules/juce_core/containers/juce_AbstractFifo.h @@ -314,25 +314,25 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) }; -template<> +template <> inline void AbstractFifo::ScopedReadWrite::finish (AbstractFifo& f, int num) noexcept { f.finishedRead (num); } -template<> +template <> inline void AbstractFifo::ScopedReadWrite::finish (AbstractFifo& f, int num) noexcept { f.finishedWrite (num); } -template<> +template <> inline void AbstractFifo::ScopedReadWrite::prepare (AbstractFifo& f, int num) noexcept { f.prepareToRead (num, startIndex1, blockSize1, startIndex2, blockSize2); } -template<> +template <> inline void AbstractFifo::ScopedReadWrite::prepare (AbstractFifo& f, int num) noexcept { f.prepareToWrite (num, startIndex1, blockSize1, startIndex2, blockSize2); diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_Array.h b/libs/juce-current/source/modules/juce_core/containers/juce_Array.h index 85f833b1..64013f11 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_Array.h +++ b/libs/juce-current/source/modules/juce_core/containers/juce_Array.h @@ -110,16 +110,16 @@ public: /** Initalises an Array from a list of items. */ template - Array (const ElementType& firstNewElement, OtherElements... otherElements) + Array (const ElementType& firstNewElement, OtherElements&&... otherElements) { - values.add (firstNewElement, otherElements...); + values.add (firstNewElement, std::forward (otherElements)...); } /** Initalises an Array from a list of items. */ template - Array (ElementType&& firstNewElement, OtherElements... otherElements) + Array (ElementType&& firstNewElement, OtherElements&&... otherElements) { - values.add (std::move (firstNewElement), otherElements...); + values.add (std::move (firstNewElement), std::forward (otherElements)...); } template @@ -433,18 +433,18 @@ public: /** Appends multiple new elements at the end of the array. */ template - void add (const ElementType& firstNewElement, OtherElements... otherElements) + void add (const ElementType& firstNewElement, OtherElements&&... otherElements) { const ScopedLockType lock (getLock()); - values.add (firstNewElement, otherElements...); + values.add (firstNewElement, std::forward (otherElements)...); } /** Appends multiple new elements at the end of the array. */ template - void add (ElementType&& firstNewElement, OtherElements... otherElements) + void add (ElementType&& firstNewElement, OtherElements&&... otherElements) { const ScopedLockType lock (getLock()); - values.add (std::move (firstNewElement), otherElements...); + values.add (std::move (firstNewElement), std::forward (otherElements)...); } /** Inserts a new element into the array at a given position. diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.cpp b/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.cpp index 0ddd9b48..998a9078 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.cpp +++ b/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.cpp @@ -562,7 +562,7 @@ private: } } - template + template void checkEqual (const ArrayBase& a, const ArrayBase& b) { @@ -572,7 +572,7 @@ private: expect (a[i] == b[i]); } - template + template void checkEqual (ArrayBase& a, std::vector& b) { @@ -582,7 +582,7 @@ private: expect (a[i] == b[(size_t) i]); } - template + template void checkEqual (ArrayBase& a, ArrayBase& b, std::vector& c) diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.h b/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.h index 7d5c4d0b..af39b2ed 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.h +++ b/libs/juce-current/source/modules/juce_core/containers/juce_ArrayBase.h @@ -255,32 +255,24 @@ public: //============================================================================== void add (const ElementType& newElement) { - checkSourceIsNotAMember (&newElement); - ensureAllocatedSize (numUsed + 1); - addAssumingCapacityIsReady (newElement); + addImpl (newElement); } void add (ElementType&& newElement) { - checkSourceIsNotAMember (&newElement); - ensureAllocatedSize (numUsed + 1); - addAssumingCapacityIsReady (std::move (newElement)); + addImpl (std::move (newElement)); } template - void add (const ElementType& firstNewElement, OtherElements... otherElements) + void add (const ElementType& firstNewElement, OtherElements&&... otherElements) { - checkSourceIsNotAMember (&firstNewElement); - ensureAllocatedSize (numUsed + 1 + (int) sizeof... (otherElements)); - addAssumingCapacityIsReady (firstNewElement, otherElements...); + addImpl (firstNewElement, std::forward (otherElements)...); } template - void add (ElementType&& firstNewElement, OtherElements... otherElements) + void add (ElementType&& firstNewElement, OtherElements&&... otherElements) { - checkSourceIsNotAMember (&firstNewElement); - ensureAllocatedSize (numUsed + 1 + (int) sizeof... (otherElements)); - addAssumingCapacityIsReady (std::move (firstNewElement), otherElements...); + addImpl (std::move (firstNewElement), std::forward (otherElements)...); } //============================================================================== @@ -335,7 +327,7 @@ public: //============================================================================== void insert (int indexToInsertAt, ParameterType newElement, int numberOfTimesToInsertIt) { - checkSourceIsNotAMember (&newElement); + checkSourceIsNotAMember (newElement); auto* space = createInsertSpace (indexToInsertAt, numberOfTimesToInsertIt); for (int i = 0; i < numberOfTimesToInsertIt; ++i) @@ -561,21 +553,18 @@ private: } //============================================================================== - void addAssumingCapacityIsReady (const ElementType& element) { new (elements + numUsed++) ElementType (element); } - void addAssumingCapacityIsReady (ElementType&& element) { new (elements + numUsed++) ElementType (std::move (element)); } - - template - void addAssumingCapacityIsReady (const ElementType& firstNewElement, OtherElements... otherElements) + template + void addImpl (Elements&&... toAdd) { - addAssumingCapacityIsReady (firstNewElement); - addAssumingCapacityIsReady (otherElements...); + ignoreUnused (std::initializer_list { (((void) checkSourceIsNotAMember (toAdd)), 0)... }); + ensureAllocatedSize (numUsed + (int) sizeof... (toAdd)); + addAssumingCapacityIsReady (std::forward (toAdd)...); } - template - void addAssumingCapacityIsReady (ElementType&& firstNewElement, OtherElements... otherElements) + template + void addAssumingCapacityIsReady (Elements&&... toAdd) { - addAssumingCapacityIsReady (std::move (firstNewElement)); - addAssumingCapacityIsReady (otherElements...); + ignoreUnused (std::initializer_list { ((void) (new (elements + numUsed++) ElementType (std::forward (toAdd))), 0)... }); } //============================================================================== @@ -594,14 +583,14 @@ private: new (destination) ElementType (std::move (source)); } - void checkSourceIsNotAMember (const ElementType* element) + void checkSourceIsNotAMember (const ElementType& element) { // when you pass a reference to an existing element into a method like add() which // may need to reallocate the array to make more space, the incoming reference may // be deleted indirectly during the reallocation operation! To work around this, // make a local copy of the item you're trying to add (and maybe use std::move to // move it into the add() method to avoid any extra overhead) - jassert (element < begin() || element >= end()); + jassert (std::addressof (element) < begin() || end() <= std::addressof (element)); ignoreUnused (element); } diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_DynamicObject.h b/libs/juce-current/source/modules/juce_core/containers/juce_DynamicObject.h index d436906d..debc5d45 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_DynamicObject.h +++ b/libs/juce-current/source/modules/juce_core/containers/juce_DynamicObject.h @@ -87,7 +87,7 @@ public: This is basically the same as calling setProperty (methodName, (var::NativeFunction) myFunction), but helps to avoid accidentally invoking the wrong type of var constructor. It also makes - the code easier to read, + the code easier to read. */ void setMethod (Identifier methodName, var::NativeFunction function); diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_PropertySet.cpp b/libs/juce-current/source/modules/juce_core/containers/juce_PropertySet.cpp index 49128257..d00b63c8 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_PropertySet.cpp +++ b/libs/juce-current/source/modules/juce_core/containers/juce_PropertySet.cpp @@ -196,7 +196,7 @@ void PropertySet::restoreFromXml (const XmlElement& xml) const ScopedLock sl (lock); clear(); - forEachXmlChildElementWithTagName (xml, e, "VALUE") + for (auto* e : xml.getChildWithTagNameIterator ("VALUE")) { if (e->hasAttribute ("name") && e->hasAttribute ("val")) diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_Variant.cpp b/libs/juce-current/source/modules/juce_core/containers/juce_Variant.cpp index 6e92b955..dbb57b28 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_Variant.cpp +++ b/libs/juce-current/source/modules/juce_core/containers/juce_Variant.cpp @@ -37,215 +37,248 @@ enum VariantStreamMarkers }; //============================================================================== -class var::VariantType -{ -public: - VariantType() noexcept {} - virtual ~VariantType() noexcept {} - - virtual int toInt (const ValueUnion&) const noexcept { return 0; } - virtual int64 toInt64 (const ValueUnion&) const noexcept { return 0; } - virtual double toDouble (const ValueUnion&) const noexcept { return 0; } - virtual String toString (const ValueUnion&) const { return {}; } - virtual bool toBool (const ValueUnion&) const noexcept { return false; } - virtual ReferenceCountedObject* toObject (const ValueUnion&) const noexcept { return nullptr; } - virtual Array* toArray (const ValueUnion&) const noexcept { return nullptr; } - virtual MemoryBlock* toBinary (const ValueUnion&) const noexcept { return nullptr; } - virtual var clone (const var& original) const { return original; } - - virtual bool isVoid() const noexcept { return false; } - virtual bool isUndefined() const noexcept { return false; } - virtual bool isInt() const noexcept { return false; } - virtual bool isInt64() const noexcept { return false; } - virtual bool isBool() const noexcept { return false; } - virtual bool isDouble() const noexcept { return false; } - virtual bool isString() const noexcept { return false; } - virtual bool isObject() const noexcept { return false; } - virtual bool isArray() const noexcept { return false; } - virtual bool isBinary() const noexcept { return false; } - virtual bool isMethod() const noexcept { return false; } - virtual bool isComparable() const noexcept { return false; } - - virtual void cleanUp (ValueUnion&) const noexcept {} - virtual void createCopy (ValueUnion& dest, const ValueUnion& source) const { dest = source; } - virtual bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept = 0; - virtual void writeToStream (const ValueUnion& data, OutputStream& output) const = 0; -}; +struct var::VariantType +{ + struct VoidTag {}; + struct UndefinedTag {}; + struct IntTag {}; + struct Int64Tag {}; + struct DoubleTag {}; + struct BoolTag {}; + struct StringTag {}; + struct ObjectTag {}; + struct ArrayTag {}; + struct BinaryTag {}; + struct MethodTag {}; + + // members ===================================================================== + bool isVoid = false; + bool isUndefined = false; + bool isInt = false; + bool isInt64 = false; + bool isBool = false; + bool isDouble = false; + bool isString = false; + bool isObject = false; + bool isArray = false; + bool isBinary = false; + bool isMethod = false; + bool isComparable = false; + + int (*toInt) (const ValueUnion&) = defaultToInt; + int64 (*toInt64) (const ValueUnion&) = defaultToInt64; + double (*toDouble) (const ValueUnion&) = defaultToDouble; + String (*toString) (const ValueUnion&) = defaultToString; + bool (*toBool) (const ValueUnion&) = defaultToBool; + ReferenceCountedObject* (*toObject) (const ValueUnion&) = defaultToObject; + Array* (*toArray) (const ValueUnion&) = defaultToArray; + MemoryBlock* (*toBinary) (const ValueUnion&) = defaultToBinary; + var (*clone) (const var&) = defaultClone; + void (*cleanUp) (ValueUnion&) = defaultCleanUp; + void (*createCopy) (ValueUnion&, const ValueUnion&) = defaultCreateCopy; + + bool (*equals) (const ValueUnion&, const ValueUnion&, const VariantType&) = nullptr; + void (*writeToStream) (const ValueUnion&, OutputStream&) = nullptr; + + // defaults ==================================================================== + static int defaultToInt (const ValueUnion&) { return 0; } + static int64 defaultToInt64 (const ValueUnion&) { return 0; } + static double defaultToDouble (const ValueUnion&) { return 0; } + static String defaultToString (const ValueUnion&) { return {}; } + static bool defaultToBool (const ValueUnion&) { return false; } + static ReferenceCountedObject* defaultToObject (const ValueUnion&) { return nullptr; } + static Array* defaultToArray (const ValueUnion&) { return nullptr; } + static MemoryBlock* defaultToBinary (const ValueUnion&) { return nullptr; } + static var defaultClone (const var& other) { return other; } + static void defaultCleanUp (ValueUnion&) {} + static void defaultCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest = source; } + + // void ======================================================================== + static bool voidEquals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) noexcept + { + return otherType.isVoid || otherType.isUndefined; + } -//============================================================================== -class var::VariantType_Void : public var::VariantType -{ -public: - VariantType_Void() noexcept {} - static const VariantType_Void instance; + static void voidWriteToStream (const ValueUnion&, OutputStream& output) + { + output.writeCompressedInt (0); + } - bool isVoid() const noexcept override { return true; } - bool isComparable() const noexcept override { return true; } - bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } - void writeToStream (const ValueUnion&, OutputStream& output) const override { output.writeCompressedInt (0); } -}; + constexpr explicit VariantType (VoidTag) noexcept + : isVoid (true), + isComparable (true), + equals (voidEquals), + writeToStream (voidWriteToStream) {} -//============================================================================== -class var::VariantType_Undefined : public var::VariantType -{ -public: - VariantType_Undefined() noexcept {} - static const VariantType_Undefined instance; + // undefined =================================================================== + static String undefinedToString (const ValueUnion&) { return "undefined"; } - bool isUndefined() const noexcept override { return true; } - String toString (const ValueUnion&) const override { return "undefined"; } - bool equals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) const noexcept override { return otherType.isVoid() || otherType.isUndefined(); } + static bool undefinedEquals (const ValueUnion&, const ValueUnion&, const VariantType& otherType) noexcept + { + return otherType.isVoid || otherType.isUndefined; + } - void writeToStream (const ValueUnion&, OutputStream& output) const override + static void undefinedWriteToStream (const ValueUnion&, OutputStream& output) { output.writeCompressedInt (1); output.writeByte (varMarker_Undefined); } -}; -//============================================================================== -class var::VariantType_Int : public var::VariantType -{ -public: - VariantType_Int() noexcept {} - static const VariantType_Int instance; + constexpr explicit VariantType (UndefinedTag) noexcept + : isUndefined (true), + toString (undefinedToString), + equals (undefinedEquals), + writeToStream (undefinedWriteToStream) {} - int toInt (const ValueUnion& data) const noexcept override { return data.intValue; } - int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.intValue; } - double toDouble (const ValueUnion& data) const noexcept override { return (double) data.intValue; } - String toString (const ValueUnion& data) const override { return String (data.intValue); } - bool toBool (const ValueUnion& data) const noexcept override { return data.intValue != 0; } - bool isInt() const noexcept override { return true; } - bool isComparable() const noexcept override { return true; } + // int ========================================================================= + static int intToInt (const ValueUnion& data) noexcept { return data.intValue; } + static int64 intToInt64 (const ValueUnion& data) noexcept { return (int64) data.intValue; } + static double intToDouble (const ValueUnion& data) noexcept { return (double) data.intValue; } + static String intToString (const ValueUnion& data) { return String (data.intValue); } + static bool intToBool (const ValueUnion& data) noexcept { return data.intValue != 0; } - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + static bool intEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { - if (otherType.isDouble() || otherType.isInt64() || otherType.isString()) - return otherType.equals (otherData, data, *this); + if (otherType.isDouble || otherType.isInt64 || otherType.isString) + return otherType.equals (otherData, data, VariantType { IntTag{} }); return otherType.toInt (otherData) == data.intValue; } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void intWriteToStream (const ValueUnion& data, OutputStream& output) { output.writeCompressedInt (5); output.writeByte (varMarker_Int); output.writeInt (data.intValue); } -}; - -//============================================================================== -class var::VariantType_Int64 : public var::VariantType -{ -public: - VariantType_Int64() noexcept {} - static const VariantType_Int64 instance; - int toInt (const ValueUnion& data) const noexcept override { return (int) data.int64Value; } - int64 toInt64 (const ValueUnion& data) const noexcept override { return data.int64Value; } - double toDouble (const ValueUnion& data) const noexcept override { return (double) data.int64Value; } - String toString (const ValueUnion& data) const override { return String (data.int64Value); } - bool toBool (const ValueUnion& data) const noexcept override { return data.int64Value != 0; } - bool isInt64() const noexcept override { return true; } - bool isComparable() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + constexpr explicit VariantType (IntTag) noexcept + : isInt (true), + isComparable (true), + toInt (intToInt), + toInt64 (intToInt64), + toDouble (intToDouble), + toString (intToString), + toBool (intToBool), + equals (intEquals), + writeToStream (intWriteToStream) {} + + // int64 ======================================================================= + static int int64ToInt (const ValueUnion& data) noexcept { return (int) data.int64Value; } + static int64 int64ToInt64 (const ValueUnion& data) noexcept { return data.int64Value; } + static double int64ToDouble (const ValueUnion& data) noexcept { return (double) data.int64Value; } + static String int64ToString (const ValueUnion& data) { return String (data.int64Value); } + static bool int64ToBool (const ValueUnion& data) noexcept { return data.int64Value != 0; } + + static bool int64Equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { - if (otherType.isDouble() || otherType.isString()) - return otherType.equals (otherData, data, *this); + if (otherType.isDouble || otherType.isString) + return otherType.equals (otherData, data, VariantType { Int64Tag{} }); return otherType.toInt64 (otherData) == data.int64Value; } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void int64WriteToStream (const ValueUnion& data, OutputStream& output) { output.writeCompressedInt (9); output.writeByte (varMarker_Int64); output.writeInt64 (data.int64Value); } -}; - -//============================================================================== -class var::VariantType_Double : public var::VariantType -{ -public: - VariantType_Double() noexcept {} - static const VariantType_Double instance; - int toInt (const ValueUnion& data) const noexcept override { return (int) data.doubleValue; } - int64 toInt64 (const ValueUnion& data) const noexcept override { return (int64) data.doubleValue; } - double toDouble (const ValueUnion& data) const noexcept override { return data.doubleValue; } - String toString (const ValueUnion& data) const override { return serialiseDouble (data.doubleValue); } - bool toBool (const ValueUnion& data) const noexcept override { return data.doubleValue != 0.0; } - bool isDouble() const noexcept override { return true; } - bool isComparable() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + constexpr explicit VariantType (Int64Tag) noexcept + : isInt64 (true), + isComparable (true), + toInt (int64ToInt), + toInt64 (int64ToInt64), + toDouble (int64ToDouble), + toString (int64ToString), + toBool (int64ToBool), + equals (int64Equals), + writeToStream (int64WriteToStream) {} + + // double ====================================================================== + static int doubleToInt (const ValueUnion& data) noexcept { return (int) data.doubleValue; } + static int64 doubleToInt64 (const ValueUnion& data) noexcept { return (int64) data.doubleValue; } + static double doubleToDouble (const ValueUnion& data) noexcept { return data.doubleValue; } + static String doubleToString (const ValueUnion& data) { return serialiseDouble (data.doubleValue); } + static bool doubleToBool (const ValueUnion& data) noexcept { return data.doubleValue != 0.0; } + + static bool doubleEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { return std::abs (otherType.toDouble (otherData) - data.doubleValue) < std::numeric_limits::epsilon(); } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void doubleWriteToStream (const ValueUnion& data, OutputStream& output) { output.writeCompressedInt (9); output.writeByte (varMarker_Double); output.writeDouble (data.doubleValue); } -}; - -//============================================================================== -class var::VariantType_Bool : public var::VariantType -{ -public: - VariantType_Bool() noexcept {} - static const VariantType_Bool instance; - int toInt (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; } - int64 toInt64 (const ValueUnion& data) const noexcept override { return data.boolValue ? 1 : 0; } - double toDouble (const ValueUnion& data) const noexcept override { return data.boolValue ? 1.0 : 0.0; } - String toString (const ValueUnion& data) const override { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); } - bool toBool (const ValueUnion& data) const noexcept override { return data.boolValue; } - bool isBool() const noexcept override { return true; } - bool isComparable() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + constexpr explicit VariantType (DoubleTag) noexcept + : isDouble (true), + isComparable (true), + toInt (doubleToInt), + toInt64 (doubleToInt64), + toDouble (doubleToDouble), + toString (doubleToString), + toBool (doubleToBool), + equals (doubleEquals), + writeToStream (doubleWriteToStream) {} + + // bool ======================================================================== + static int boolToInt (const ValueUnion& data) noexcept { return data.boolValue ? 1 : 0; } + static int64 boolToInt64 (const ValueUnion& data) noexcept { return data.boolValue ? 1 : 0; } + static double boolToDouble (const ValueUnion& data) noexcept { return data.boolValue ? 1.0 : 0.0; } + static String boolToString (const ValueUnion& data) { return String::charToString (data.boolValue ? (juce_wchar) '1' : (juce_wchar) '0'); } + static bool boolToBool (const ValueUnion& data) noexcept { return data.boolValue; } + + static bool boolEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { return otherType.toBool (otherData) == data.boolValue; } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void boolWriteToStream (const ValueUnion& data, OutputStream& output) { output.writeCompressedInt (1); output.writeByte (data.boolValue ? (char) varMarker_BoolTrue : (char) varMarker_BoolFalse); } -}; -//============================================================================== -class var::VariantType_String : public var::VariantType -{ -public: - VariantType_String() noexcept {} - static const VariantType_String instance; - - void cleanUp (ValueUnion& data) const noexcept override { getString (data)-> ~String(); } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override { new (dest.stringValue) String (*getString (source)); } - - bool isString() const noexcept override { return true; } - int toInt (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue(); } - int64 toInt64 (const ValueUnion& data) const noexcept override { return getString (data)->getLargeIntValue(); } - double toDouble (const ValueUnion& data) const noexcept override { return getString (data)->getDoubleValue(); } - String toString (const ValueUnion& data) const override { return *getString (data); } - bool toBool (const ValueUnion& data) const noexcept override { return getString (data)->getIntValue() != 0 - || getString (data)->trim().equalsIgnoreCase ("true") - || getString (data)->trim().equalsIgnoreCase ("yes"); } - bool isComparable() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + constexpr explicit VariantType (BoolTag) noexcept + : isBool (true), + isComparable (true), + toInt (boolToInt), + toInt64 (boolToInt64), + toDouble (boolToDouble), + toString (boolToString), + toBool (boolToBool), + equals (boolEquals), + writeToStream (boolWriteToStream) {} + + // string ====================================================================== + static const String* getString (const ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } + static String* getString ( ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } + + static int stringToInt (const ValueUnion& data) noexcept { return getString (data)->getIntValue(); } + static int64 stringToInt64 (const ValueUnion& data) noexcept { return getString (data)->getLargeIntValue(); } + static double stringToDouble (const ValueUnion& data) noexcept { return getString (data)->getDoubleValue(); } + static String stringToString (const ValueUnion& data) { return *getString (data); } + static bool stringToBool (const ValueUnion& data) noexcept + { + return getString (data)->getIntValue() != 0 + || getString (data)->trim().equalsIgnoreCase ("true") + || getString (data)->trim().equalsIgnoreCase ("yes"); + } + + static void stringCleanUp (ValueUnion& data) noexcept { getString (data)-> ~String(); } + static void stringCreateCopy (ValueUnion& dest, const ValueUnion& source) { new (dest.stringValue) String (*getString (source)); } + + static bool stringEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { return otherType.toString (otherData) == *getString (data); } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void stringWriteToStream (const ValueUnion& data, OutputStream& output) { auto* s = getString (data); const size_t len = s->getNumBytesAsUTF8() + 1; @@ -256,65 +289,73 @@ public: output.write (temp, len); } -private: - static const String* getString (const ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } - static String* getString (ValueUnion& data) noexcept { return unalignedPointerCast (data.stringValue); } -}; + constexpr explicit VariantType (StringTag) noexcept + : isString (true), + isComparable (true), + toInt (stringToInt), + toInt64 (stringToInt64), + toDouble (stringToDouble), + toString (stringToString), + toBool (stringToBool), + cleanUp (stringCleanUp), + createCopy (stringCreateCopy), + equals (stringEquals), + writeToStream (stringWriteToStream) {} + + // object ====================================================================== + static String objectToString (const ValueUnion& data) + { + return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); + } -//============================================================================== -class var::VariantType_Object : public var::VariantType -{ -public: - VariantType_Object() noexcept {} - static const VariantType_Object instance; + static bool objectToBool (const ValueUnion& data) noexcept { return data.objectValue != nullptr; } + static ReferenceCountedObject* objectToObject (const ValueUnion& data) noexcept { return data.objectValue; } + + static var objectClone (const var& original) + { + if (auto* d = original.getDynamicObject()) + return d->clone().get(); - void cleanUp (ValueUnion& data) const noexcept override { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } + jassertfalse; // can only clone DynamicObjects! + return {}; + } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override + static void objectCleanUp (ValueUnion& data) noexcept { if (data.objectValue != nullptr) data.objectValue->decReferenceCount(); } + + static void objectCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest.objectValue = source.objectValue; if (dest.objectValue != nullptr) dest.objectValue->incReferenceCount(); } - String toString (const ValueUnion& data) const override { return "Object 0x" + String::toHexString ((int) (pointer_sized_int) data.objectValue); } - bool toBool (const ValueUnion& data) const noexcept override { return data.objectValue != nullptr; } - ReferenceCountedObject* toObject (const ValueUnion& data) const noexcept override { return data.objectValue; } - bool isObject() const noexcept override { return true; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + static bool objectEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { return otherType.toObject (otherData) == data.objectValue; } - var clone (const var& original) const override - { - if (auto* d = original.getDynamicObject()) - return d->clone().get(); - - jassertfalse; // can only clone DynamicObjects! - return {}; - } - - void writeToStream (const ValueUnion&, OutputStream& output) const override + static void objectWriteToStream (const ValueUnion&, OutputStream& output) { jassertfalse; // Can't write an object to a stream! output.writeCompressedInt (0); } -}; - -//============================================================================== -class var::VariantType_Array : public var::VariantType_Object -{ -public: - VariantType_Array() noexcept {} - static const VariantType_Array instance; - String toString (const ValueUnion&) const override { return "[Array]"; } - ReferenceCountedObject* toObject (const ValueUnion&) const noexcept override { return nullptr; } - bool isArray() const noexcept override { return true; } - - Array* toArray (const ValueUnion& data) const noexcept override + constexpr explicit VariantType (ObjectTag) noexcept + : isObject (true), + toString (objectToString), + toBool (objectToBool), + toObject (objectToObject), + clone (objectClone), + cleanUp (objectCleanUp), + createCopy (objectCreateCopy), + equals (objectEquals), + writeToStream (objectWriteToStream) {} + + // array ======================================================================= + static String arrayToString (const ValueUnion&) { return "[Array]"; } + static ReferenceCountedObject* arrayToObject (const ValueUnion&) noexcept { return nullptr; } + + static Array* arrayToArray (const ValueUnion& data) noexcept { if (auto* a = dynamic_cast (data.objectValue)) return &(a->array); @@ -322,18 +363,18 @@ public: return nullptr; } - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + static bool arrayEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { - auto* thisArray = toArray (data); + auto* thisArray = arrayToArray (data); auto* otherArray = otherType.toArray (otherData); return thisArray == otherArray || (thisArray != nullptr && otherArray != nullptr && *otherArray == *thisArray); } - var clone (const var& original) const override + static var arrayClone (const var& original) { Array arrayCopy; - if (auto* array = toArray (original.value)) + if (auto* array = arrayToArray (original.value)) { arrayCopy.ensureStorageAllocated (array->size()); @@ -344,9 +385,9 @@ public: return var (arrayCopy); } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void arrayWriteToStream (const ValueUnion& data, OutputStream& output) { - if (auto* array = toArray (data)) + if (auto* array = arrayToArray (data)) { MemoryOutputStream buffer (512); buffer.writeCompressedInt (array->size()); @@ -366,79 +407,106 @@ public: RefCountedArray (Array&& a) : array (std::move (a)) { incReferenceCount(); } Array array; }; -}; - -//============================================================================== -class var::VariantType_Binary : public var::VariantType -{ -public: - VariantType_Binary() noexcept {} - static const VariantType_Binary instance; - - void cleanUp (ValueUnion& data) const noexcept override { delete data.binaryValue; } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.binaryValue = new MemoryBlock (*source.binaryValue); } - - String toString (const ValueUnion& data) const override { return data.binaryValue->toBase64Encoding(); } - bool isBinary() const noexcept override { return true; } - MemoryBlock* toBinary (const ValueUnion& data) const noexcept override { return data.binaryValue; } - - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + constexpr explicit VariantType (ArrayTag) noexcept + : isObject (true), + isArray (true), + toString (arrayToString), + toBool (objectToBool), + toObject (arrayToObject), + toArray (arrayToArray), + clone (arrayClone), + cleanUp (objectCleanUp), + createCopy (objectCreateCopy), + equals (arrayEquals), + writeToStream (arrayWriteToStream) {} + + // binary ====================================================================== + static void binaryCleanUp (ValueUnion& data) noexcept { delete data.binaryValue; } + static void binaryCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest.binaryValue = new MemoryBlock (*source.binaryValue); } + + static String binaryToString (const ValueUnion& data) { return data.binaryValue->toBase64Encoding(); } + static MemoryBlock* binaryToBinary (const ValueUnion& data) noexcept { return data.binaryValue; } + + static bool binaryEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { const MemoryBlock* const otherBlock = otherType.toBinary (otherData); return otherBlock != nullptr && *otherBlock == *data.binaryValue; } - void writeToStream (const ValueUnion& data, OutputStream& output) const override + static void binaryWriteToStream (const ValueUnion& data, OutputStream& output) { output.writeCompressedInt (1 + (int) data.binaryValue->getSize()); output.writeByte (varMarker_Binary); output << *data.binaryValue; } -}; -//============================================================================== -class var::VariantType_Method : public var::VariantType -{ -public: - VariantType_Method() noexcept {} - static const VariantType_Method instance; + constexpr explicit VariantType (BinaryTag) noexcept + : isBinary (true), + toString (binaryToString), + toBinary (binaryToBinary), + cleanUp (binaryCleanUp), + createCopy (binaryCreateCopy), + equals (binaryEquals), + writeToStream (binaryWriteToStream) {} - void cleanUp (ValueUnion& data) const noexcept override { if (data.methodValue != nullptr ) delete data.methodValue; } - void createCopy (ValueUnion& dest, const ValueUnion& source) const override { dest.methodValue = new NativeFunction (*source.methodValue); } + // method ====================================================================== + static void methodCleanUp (ValueUnion& data) noexcept { if (data.methodValue != nullptr ) delete data.methodValue; } + static void methodCreateCopy (ValueUnion& dest, const ValueUnion& source) { dest.methodValue = new NativeFunction (*source.methodValue); } - String toString (const ValueUnion&) const override { return "Method"; } - bool toBool (const ValueUnion& data) const noexcept override { return data.methodValue != nullptr; } - bool isMethod() const noexcept override { return true; } + static String methodToString (const ValueUnion&) { return "Method"; } + static bool methodToBool (const ValueUnion& data) noexcept { return data.methodValue != nullptr; } - bool equals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) const noexcept override + static bool methodEquals (const ValueUnion& data, const ValueUnion& otherData, const VariantType& otherType) noexcept { - return otherType.isMethod() && otherData.methodValue == data.methodValue; + return otherType.isMethod && otherData.methodValue == data.methodValue; } - void writeToStream (const ValueUnion&, OutputStream& output) const override + static void methodWriteToStream (const ValueUnion&, OutputStream& output) { jassertfalse; // Can't write a method to a stream! output.writeCompressedInt (0); } + + constexpr explicit VariantType (MethodTag) noexcept + : isMethod (true), + toString (methodToString), + toBool (methodToBool), + cleanUp (methodCleanUp), + createCopy (methodCreateCopy), + equals (methodEquals), + writeToStream (methodWriteToStream) {} }; -//============================================================================== -const var::VariantType_Void var::VariantType_Void::instance; -const var::VariantType_Undefined var::VariantType_Undefined::instance; -const var::VariantType_Int var::VariantType_Int::instance; -const var::VariantType_Int64 var::VariantType_Int64::instance; -const var::VariantType_Bool var::VariantType_Bool::instance; -const var::VariantType_Double var::VariantType_Double::instance; -const var::VariantType_String var::VariantType_String::instance; -const var::VariantType_Object var::VariantType_Object::instance; -const var::VariantType_Array var::VariantType_Array::instance; -const var::VariantType_Binary var::VariantType_Binary::instance; -const var::VariantType_Method var::VariantType_Method::instance; +struct var::Instance +{ + static constexpr VariantType attributesVoid { VariantType::VoidTag{} }; + static constexpr VariantType attributesUndefined { VariantType::UndefinedTag{} }; + static constexpr VariantType attributesInt { VariantType::IntTag{} }; + static constexpr VariantType attributesInt64 { VariantType::Int64Tag{} }; + static constexpr VariantType attributesBool { VariantType::BoolTag{} }; + static constexpr VariantType attributesDouble { VariantType::DoubleTag{} }; + static constexpr VariantType attributesMethod { VariantType::MethodTag{} }; + static constexpr VariantType attributesArray { VariantType::ArrayTag{} }; + static constexpr VariantType attributesString { VariantType::StringTag{} }; + static constexpr VariantType attributesBinary { VariantType::BinaryTag{} }; + static constexpr VariantType attributesObject { VariantType::ObjectTag{} }; +}; +constexpr var::VariantType var::Instance::attributesVoid; +constexpr var::VariantType var::Instance::attributesUndefined; +constexpr var::VariantType var::Instance::attributesInt; +constexpr var::VariantType var::Instance::attributesInt64; +constexpr var::VariantType var::Instance::attributesBool; +constexpr var::VariantType var::Instance::attributesDouble; +constexpr var::VariantType var::Instance::attributesMethod; +constexpr var::VariantType var::Instance::attributesArray; +constexpr var::VariantType var::Instance::attributesString; +constexpr var::VariantType var::Instance::attributesBinary; +constexpr var::VariantType var::Instance::attributesObject; //============================================================================== -var::var() noexcept : type (&VariantType_Void::instance) {} +var::var() noexcept : type (&Instance::attributesVoid) {} var::var (const VariantType& t) noexcept : type (&t) {} var::~var() noexcept { type->cleanUp (value); } @@ -450,19 +518,19 @@ var::var (const var& valueToCopy) : type (valueToCopy.type) type->createCopy (value, valueToCopy.value); } -var::var (const int v) noexcept : type (&VariantType_Int::instance) { value.intValue = v; } -var::var (const int64 v) noexcept : type (&VariantType_Int64::instance) { value.int64Value = v; } -var::var (const bool v) noexcept : type (&VariantType_Bool::instance) { value.boolValue = v; } -var::var (const double v) noexcept : type (&VariantType_Double::instance) { value.doubleValue = v; } -var::var (NativeFunction m) noexcept : type (&VariantType_Method::instance) { value.methodValue = new NativeFunction (m); } -var::var (const Array& v) : type (&VariantType_Array::instance) { value.objectValue = new VariantType_Array::RefCountedArray(v); } -var::var (const String& v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } -var::var (const char* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } -var::var (const wchar_t* const v) : type (&VariantType_String::instance) { new (value.stringValue) String (v); } -var::var (const void* v, size_t sz) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v, sz); } -var::var (const MemoryBlock& v) : type (&VariantType_Binary::instance) { value.binaryValue = new MemoryBlock (v); } +var::var (const int v) noexcept : type (&Instance::attributesInt) { value.intValue = v; } +var::var (const int64 v) noexcept : type (&Instance::attributesInt64) { value.int64Value = v; } +var::var (const bool v) noexcept : type (&Instance::attributesBool) { value.boolValue = v; } +var::var (const double v) noexcept : type (&Instance::attributesDouble) { value.doubleValue = v; } +var::var (NativeFunction m) noexcept : type (&Instance::attributesMethod) { value.methodValue = new NativeFunction (m); } +var::var (const Array& v) : type (&Instance::attributesArray) { value.objectValue = new VariantType::RefCountedArray (v); } +var::var (const String& v) : type (&Instance::attributesString) { new (value.stringValue) String (v); } +var::var (const char* const v) : type (&Instance::attributesString) { new (value.stringValue) String (v); } +var::var (const wchar_t* const v) : type (&Instance::attributesString) { new (value.stringValue) String (v); } +var::var (const void* v, size_t sz) : type (&Instance::attributesBinary) { value.binaryValue = new MemoryBlock (v, sz); } +var::var (const MemoryBlock& v) : type (&Instance::attributesBinary) { value.binaryValue = new MemoryBlock (v); } -var::var (const StringArray& v) : type (&VariantType_Array::instance) +var::var (const StringArray& v) : type (&Instance::attributesArray) { Array strings; strings.ensureStorageAllocated (v.size()); @@ -470,10 +538,10 @@ var::var (const StringArray& v) : type (&VariantType_Array::instance) for (auto& i : v) strings.add (var (i)); - value.objectValue = new VariantType_Array::RefCountedArray (strings); + value.objectValue = new VariantType::RefCountedArray (strings); } -var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::instance) +var::var (ReferenceCountedObject* const object) : type (&Instance::attributesObject) { value.objectValue = object; @@ -481,20 +549,20 @@ var::var (ReferenceCountedObject* const object) : type (&VariantType_Object::in object->incReferenceCount(); } -var var::undefined() noexcept { return var (VariantType_Undefined::instance); } +var var::undefined() noexcept { return var (Instance::attributesUndefined); } //============================================================================== -bool var::isVoid() const noexcept { return type->isVoid(); } -bool var::isUndefined() const noexcept { return type->isUndefined(); } -bool var::isInt() const noexcept { return type->isInt(); } -bool var::isInt64() const noexcept { return type->isInt64(); } -bool var::isBool() const noexcept { return type->isBool(); } -bool var::isDouble() const noexcept { return type->isDouble(); } -bool var::isString() const noexcept { return type->isString(); } -bool var::isObject() const noexcept { return type->isObject(); } -bool var::isArray() const noexcept { return type->isArray(); } -bool var::isBinaryData() const noexcept { return type->isBinary(); } -bool var::isMethod() const noexcept { return type->isMethod(); } +bool var::isVoid() const noexcept { return type->isVoid; } +bool var::isUndefined() const noexcept { return type->isUndefined; } +bool var::isInt() const noexcept { return type->isInt; } +bool var::isInt64() const noexcept { return type->isInt64; } +bool var::isBool() const noexcept { return type->isBool; } +bool var::isDouble() const noexcept { return type->isDouble; } +bool var::isString() const noexcept { return type->isString; } +bool var::isObject() const noexcept { return type->isObject; } +bool var::isArray() const noexcept { return type->isArray; } +bool var::isBinaryData() const noexcept { return type->isBinary; } +bool var::isMethod() const noexcept { return type->isMethod; } var::operator int() const noexcept { return type->toInt (value); } var::operator int64() const noexcept { return type->toInt64 (value); } @@ -516,14 +584,14 @@ void var::swapWith (var& other) noexcept } var& var::operator= (const var& v) { type->cleanUp (value); type = v.type; type->createCopy (value, v.value); return *this; } -var& var::operator= (const int v) { type->cleanUp (value); type = &VariantType_Int::instance; value.intValue = v; return *this; } -var& var::operator= (const int64 v) { type->cleanUp (value); type = &VariantType_Int64::instance; value.int64Value = v; return *this; } -var& var::operator= (const bool v) { type->cleanUp (value); type = &VariantType_Bool::instance; value.boolValue = v; return *this; } -var& var::operator= (const double v) { type->cleanUp (value); type = &VariantType_Double::instance; value.doubleValue = v; return *this; } -var& var::operator= (const char* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } -var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } -var& var::operator= (const String& v) { type->cleanUp (value); type = &VariantType_String::instance; new (value.stringValue) String (v); return *this; } -var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &VariantType_Binary::instance; value.binaryValue = new MemoryBlock (v); return *this; } +var& var::operator= (const int v) { type->cleanUp (value); type = &Instance::attributesInt; value.intValue = v; return *this; } +var& var::operator= (const int64 v) { type->cleanUp (value); type = &Instance::attributesInt64; value.int64Value = v; return *this; } +var& var::operator= (const bool v) { type->cleanUp (value); type = &Instance::attributesBool; value.boolValue = v; return *this; } +var& var::operator= (const double v) { type->cleanUp (value); type = &Instance::attributesDouble; value.doubleValue = v; return *this; } +var& var::operator= (const char* const v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; } +var& var::operator= (const wchar_t* const v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; } +var& var::operator= (const String& v) { type->cleanUp (value); type = &Instance::attributesString; new (value.stringValue) String (v); return *this; } +var& var::operator= (const MemoryBlock& v) { type->cleanUp (value); type = &Instance::attributesBinary; value.binaryValue = new MemoryBlock (v); return *this; } var& var::operator= (const Array& v) { var v2 (v); swapWith (v2); return *this; } var& var::operator= (ReferenceCountedObject* v) { var v2 (v); swapWith (v2); return *this; } var& var::operator= (NativeFunction v) { var v2 (v); swapWith (v2); return *this; } @@ -532,7 +600,7 @@ var::var (var&& other) noexcept : type (other.type), value (other.value) { - other.type = &VariantType_Void::instance; + other.type = &Instance::attributesVoid; } var& var::operator= (var&& other) noexcept @@ -541,25 +609,25 @@ var& var::operator= (var&& other) noexcept return *this; } -var::var (String&& v) : type (&VariantType_String::instance) +var::var (String&& v) : type (&Instance::attributesString) { new (value.stringValue) String (std::move (v)); } -var::var (MemoryBlock&& v) : type (&VariantType_Binary::instance) +var::var (MemoryBlock&& v) : type (&Instance::attributesBinary) { value.binaryValue = new MemoryBlock (std::move (v)); } -var::var (Array&& v) : type (&VariantType_Array::instance) +var::var (Array&& v) : type (&Instance::attributesArray) { - value.objectValue = new VariantType_Array::RefCountedArray (std::move (v)); + value.objectValue = new VariantType::RefCountedArray (std::move (v)); } var& var::operator= (String&& v) { type->cleanUp (value); - type = &VariantType_String::instance; + type = &Instance::attributesString; new (value.stringValue) String (std::move (v)); return *this; } @@ -582,7 +650,7 @@ bool var::hasSameTypeAs (const var& other) const noexcept bool canCompare (const var& v1, const var& v2) { - return v1.type->isComparable() && v2.type->isComparable(); + return v1.type->isComparable && v2.type->isComparable; } static int compare (const var& v1, const var& v2) diff --git a/libs/juce-current/source/modules/juce_core/containers/juce_Variant.h b/libs/juce-current/source/modules/juce_core/containers/juce_Variant.h index e58f3386..e5a933fd 100644 --- a/libs/juce-current/source/modules/juce_core/containers/juce_Variant.h +++ b/libs/juce-current/source/modules/juce_core/containers/juce_Variant.h @@ -283,18 +283,8 @@ public: private: //============================================================================== - class VariantType; - class VariantType_Void; - class VariantType_Undefined; - class VariantType_Int; - class VariantType_Int64; - class VariantType_Double; - class VariantType_Bool; - class VariantType_String; - class VariantType_Object; - class VariantType_Array; - class VariantType_Binary; - class VariantType_Method; + struct VariantType; + struct Instance; union ValueUnion { diff --git a/libs/juce-current/source/modules/juce_core/javascript/juce_Javascript.cpp b/libs/juce-current/source/modules/juce_core/javascript/juce_Javascript.cpp index 9fe380c4..58729990 100644 --- a/libs/juce-current/source/modules/juce_core/javascript/juce_Javascript.cpp +++ b/libs/juce-current/source/modules/juce_core/javascript/juce_Javascript.cpp @@ -940,7 +940,7 @@ struct JavascriptEngine::RootObject : public DynamicObject { for (;;) { - p = p.findEndOfWhitespace(); + p.incrementToEndOfWhitespace(); if (*p == '/') { diff --git a/libs/juce-current/source/modules/juce_core/juce_core.cpp b/libs/juce-current/source/modules/juce_core/juce_core.cpp index 914cae68..84ded5f5 100644 --- a/libs/juce-current/source/modules/juce_core/juce_core.cpp +++ b/libs/juce-current/source/modules/juce_core/juce_core.cpp @@ -36,9 +36,10 @@ #include "juce_core.h" -#include #include #include +#include +#include #if ! JUCE_ANDROID #include @@ -71,6 +72,16 @@ #include #endif + #if JUCE_WASM + #include + #include + #include + #include + #include + #include + #include + #endif + #if JUCE_LINUX #include #include @@ -91,7 +102,7 @@ #include #include - #if ! JUCE_ANDROID + #if ! (JUCE_ANDROID || JUCE_WASM) #include #endif #endif @@ -99,7 +110,6 @@ #if JUCE_MAC || JUCE_IOS #include #include - #include #endif #if JUCE_ANDROID @@ -229,14 +239,20 @@ #include "native/juce_android_Threads.cpp" #include "native/juce_android_RuntimePermissions.cpp" +#elif JUCE_WASM + #include "native/juce_wasm_SystemStats.cpp" + #endif -#include "threads/juce_ChildProcess.cpp" #include "threads/juce_HighResolutionTimer.cpp" #include "threads/juce_WaitableEvent.cpp" #include "network/juce_URL.cpp" -#include "network/juce_WebInputStream.cpp" -#include "streams/juce_URLInputSource.cpp" + +#if ! JUCE_WASM + #include "threads/juce_ChildProcess.cpp" + #include "network/juce_WebInputStream.cpp" + #include "streams/juce_URLInputSource.cpp" +#endif //============================================================================== #if JUCE_UNIT_TESTS diff --git a/libs/juce-current/source/modules/juce_core/juce_core.h b/libs/juce-current/source/modules/juce_core/juce_core.h index d57a53e7..3612d622 100644 --- a/libs/juce-current/source/modules/juce_core/juce_core.h +++ b/libs/juce-current/source/modules/juce_core/juce_core.h @@ -32,14 +32,14 @@ ID: juce_core vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE core classes description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. website: http://www.juce.com/juce 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/libs/juce-current/source/modules/juce_core/logging/juce_Logger.cpp b/libs/juce-current/source/modules/juce_core/logging/juce_Logger.cpp index 555c954a..73f8c252 100644 --- a/libs/juce-current/source/modules/juce_core/logging/juce_Logger.cpp +++ b/libs/juce-current/source/modules/juce_core/logging/juce_Logger.cpp @@ -49,7 +49,7 @@ void Logger::writeToLog (const String& message) void JUCE_API JUCE_CALLTYPE logAssertion (const char* const filename, const int lineNum) noexcept { String m ("JUCE Assertion failure in "); - m << File::createFileWithoutCheckingPath (filename).getFileName() << ':' << lineNum; + m << File::createFileWithoutCheckingPath (CharPointer_UTF8 (filename)).getFileName() << ':' << lineNum; #if JUCE_LOG_ASSERTIONS Logger::writeToLog (m); diff --git a/libs/juce-current/source/modules/juce_core/maths/juce_Expression.cpp b/libs/juce-current/source/modules/juce_core/maths/juce_Expression.cpp index edbb0743..211860d8 100644 --- a/libs/juce-current/source/modules/juce_core/maths/juce_Expression.cpp +++ b/libs/juce-current/source/modules/juce_core/maths/juce_Expression.cpp @@ -699,7 +699,7 @@ struct Expression::Helpers bool readOperator (const char* ops, char* const opType = nullptr) noexcept { - text = text.findEndOfWhitespace(); + text.incrementToEndOfWhitespace(); while (*ops != 0) { @@ -719,7 +719,7 @@ struct Expression::Helpers bool readIdentifier (String& identifier) noexcept { - text = text.findEndOfWhitespace(); + text.incrementToEndOfWhitespace(); auto t = text; int numChars = 0; @@ -747,21 +747,21 @@ struct Expression::Helpers Term* readNumber() noexcept { - text = text.findEndOfWhitespace(); + text.incrementToEndOfWhitespace(); auto t = text; bool isResolutionTarget = (*t == '@'); if (isResolutionTarget) { ++t; - t = t.findEndOfWhitespace(); + t.incrementToEndOfWhitespace(); text = t; } if (*t == '-') { ++t; - t = t.findEndOfWhitespace(); + t.incrementToEndOfWhitespace(); } if (isDecimalDigit (*t) || (*t == '.' && isDecimalDigit (t[1]))) diff --git a/libs/juce-current/source/modules/juce_core/memory/juce_AllocationHooks.cpp b/libs/juce-current/source/modules/juce_core/memory/juce_AllocationHooks.cpp index 3c6cb6b3..b2d5ca24 100644 --- a/libs/juce-current/source/modules/juce_core/memory/juce_AllocationHooks.cpp +++ b/libs/juce-current/source/modules/juce_core/memory/juce_AllocationHooks.cpp @@ -94,7 +94,7 @@ UnitTestAllocationChecker::UnitTestAllocationChecker (UnitTest& test) UnitTestAllocationChecker::~UnitTestAllocationChecker() noexcept { getAllocationHooksForThread().removeListener (this); - unitTest.expectEquals (calls, (size_t) 0, "new or delete was incorrectly called while allocation checker was active"); + unitTest.expectEquals ((int) calls, 0, "new or delete was incorrectly called while allocation checker was active"); } void UnitTestAllocationChecker::newOrDeleteCalled() noexcept { ++calls; } diff --git a/libs/juce-current/source/modules/juce_core/memory/juce_HeapBlock.h b/libs/juce-current/source/modules/juce_core/memory/juce_HeapBlock.h index e935a521..9092529d 100644 --- a/libs/juce-current/source/modules/juce_core/memory/juce_HeapBlock.h +++ b/libs/juce-current/source/modules/juce_core/memory/juce_HeapBlock.h @@ -29,7 +29,7 @@ namespace HeapBlockHelper template struct ThrowOnFail { static void checkPointer (void*) {} }; - template<> + template <> struct ThrowOnFail { static void checkPointer (void* data) { if (data == nullptr) throw std::bad_alloc(); } }; } #endif diff --git a/libs/juce-current/source/modules/juce_core/memory/juce_Memory.h b/libs/juce-current/source/modules/juce_core/memory/juce_Memory.h index 03c0de3c..0f21a5a8 100644 --- a/libs/juce-current/source/modules/juce_core/memory/juce_Memory.h +++ b/libs/juce-current/source/modules/juce_core/memory/juce_Memory.h @@ -183,4 +183,18 @@ inline const Type* addBytesToPointer (const Type* basePointer, IntegerType bytes #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/libs/juce-current/source/modules/juce_core/misc/juce_ConsoleApplication.cpp b/libs/juce-current/source/modules/juce_core/misc/juce_ConsoleApplication.cpp index 57cfe629..389131a3 100644 --- a/libs/juce-current/source/modules/juce_core/misc/juce_ConsoleApplication.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_core/native/juce_BasicNativeHeaders.h b/libs/juce-current/source/modules/juce_core/native/juce_BasicNativeHeaders.h index c767726f..306bb536 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_core/native/juce_mac_Files.mm b/libs/juce-current/source/modules/juce_core/native/juce_mac_Files.mm index 4b673364..f385a089 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_mac_Files.mm +++ b/libs/juce-current/source/modules/juce_core/native/juce_mac_Files.mm @@ -200,7 +200,7 @@ File File::getSpecialLocation (const SpecialLocationType type) case invokedExecutableFile: if (juce_argv != nullptr && juce_argc > 0) - return File::getCurrentWorkingDirectory().getChildFile (String (juce_argv[0])); + return File::getCurrentWorkingDirectory().getChildFile (CharPointer_UTF8 (juce_argv[0])); // deliberate fall-through... JUCE_FALLTHROUGH diff --git a/libs/juce-current/source/modules/juce_core/native/juce_mac_SystemStats.mm b/libs/juce-current/source/modules/juce_core/native/juce_mac_SystemStats.mm index 9665260e..4bbf8bee 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/libs/juce-current/source/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 @@ -146,8 +157,8 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() return (OperatingSystemType) (minor + MacOSX_10_7 - 7); } - jassert (major == 11 && minor == 0); - return MacOSX_11_0; + jassert (major == 11); + return MacOS_11; #endif } diff --git a/libs/juce-current/source/modules/juce_core/native/juce_osx_ObjCHelpers.h b/libs/juce-current/source/modules/juce_core/native/juce_osx_ObjCHelpers.h index 2b5e31b6..aa068eaa 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_osx_ObjCHelpers.h +++ b/libs/juce-current/source/modules/juce_core/native/juce_osx_ObjCHelpers.h @@ -196,8 +196,16 @@ NSRect makeNSRect (const RectangleType& r) noexcept #if JUCE_INTEL template - struct NeedsStret { static constexpr auto value = sizeof (T) > 16; }; - 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> diff --git a/libs/juce-current/source/modules/juce_core/native/juce_posix_IPAddress.h b/libs/juce-current/source/modules/juce_core/native/juce_posix_IPAddress.h index 41c83cd3..79717461 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_posix_IPAddress.h +++ b/libs/juce-current/source/modules/juce_core/native/juce_posix_IPAddress.h @@ -25,6 +25,18 @@ namespace juce namespace { + struct InterfaceInfo + { + IPAddress interfaceAddress, broadcastAddress; + }; + + inline bool operator== (const InterfaceInfo& lhs, const InterfaceInfo& rhs) + { + return lhs.interfaceAddress == rhs.interfaceAddress + && lhs.broadcastAddress == rhs.broadcastAddress; + } + + #if ! JUCE_WASM static IPAddress makeAddress (const sockaddr_in6* addr_in) { if (addr_in == nullptr) @@ -54,17 +66,6 @@ namespace return IPAddress (ntohl (addr_in->sin_addr.s_addr)); } - struct InterfaceInfo - { - IPAddress interfaceAddress, broadcastAddress; - }; - - bool operator== (const InterfaceInfo& lhs, const InterfaceInfo& rhs) - { - return lhs.interfaceAddress == rhs.interfaceAddress - && lhs.broadcastAddress == rhs.broadcastAddress; - } - bool populateInterfaceInfo (struct ifaddrs* ifa, InterfaceInfo& interfaceInfo) { if (ifa->ifa_addr != nullptr) @@ -91,10 +92,15 @@ namespace return false; } + #endif Array getAllInterfaceInfo() { Array interfaces; + + #if JUCE_WASM + // TODO + #else struct ifaddrs* ifaddr = nullptr; if (getifaddrs (&ifaddr) != -1) @@ -109,6 +115,7 @@ namespace freeifaddrs (ifaddr); } + #endif return interfaces; } diff --git a/libs/juce-current/source/modules/juce_core/native/juce_posix_NamedPipe.cpp b/libs/juce-current/source/modules/juce_core/native/juce_posix_NamedPipe.cpp index 2321d45b..8b14f16d 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_posix_NamedPipe.cpp +++ b/libs/juce-current/source/modules/juce_core/native/juce_posix_NamedPipe.cpp @@ -23,6 +23,8 @@ namespace juce { +#if ! JUCE_WASM + class NamedPipe::Pimpl { public: @@ -154,7 +156,7 @@ private: 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 +183,20 @@ private: 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(); } @@ -235,4 +243,6 @@ int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOut return pimpl != nullptr ? pimpl->write (static_cast (sourceBuffer), numBytesToWrite, timeOutMilliseconds) : -1; } +#endif + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_core/native/juce_posix_SharedCode.h b/libs/juce-current/source/modules/juce_core/native/juce_posix_SharedCode.h index 9c9053ac..f8892081 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/libs/juce-current/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -130,16 +130,20 @@ bool File::setAsCurrentWorkingDirectory() const return chdir (getFullPathName().toUTF8()) == 0; } -#if JUCE_ANDROID - using juce_sigactionflags_type = unsigned long; -#else - using juce_sigactionflags_type = int; -#endif - //============================================================================== // The unix siginterrupt function is deprecated - this does the same job. int juce_siginterrupt (int sig, int flag) { + #if JUCE_WASM + ignoreUnused (sig, flag); + return 0; + #else + #if JUCE_ANDROID + using juce_sigactionflags_type = unsigned long; + #else + using juce_sigactionflags_type = int; + #endif + struct ::sigaction act; (void) ::sigaction (sig, nullptr, &act); @@ -149,6 +153,7 @@ int juce_siginterrupt (int sig, int flag) act.sa_flags |= static_cast (SA_RESTART); return ::sigaction (sig, &act, nullptr); + #endif } //============================================================================== @@ -168,6 +173,7 @@ namespace && JUCE_STAT (fileName.toUTF8(), &info) == 0; } + #if ! JUCE_WASM // if this file doesn't exist, find a parent of it that does.. bool juce_doStatFS (File f, struct statfs& result) { @@ -205,6 +211,7 @@ namespace if (isReadOnly != nullptr) *isReadOnly = access (path.toUTF8(), W_OK) != 0; } + #endif Result getResultForErrno() { @@ -329,6 +336,7 @@ void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 /*creationTime*/) const { + #if ! JUCE_WASM juce_statStruct info; if ((modificationTime != 0 || accessTime != 0) && juce_stat (fullPath, info)) @@ -360,6 +368,7 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 return utime (fullPath.toUTF8(), ×) == 0; #endif } + #endif return false; } @@ -533,6 +542,7 @@ String SystemStats::getEnvironmentVariable (const String& name, const String& de } //============================================================================== +#if ! JUCE_WASM void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive) { jassert (mode == readOnly || mode == readWrite); @@ -686,6 +696,8 @@ int File::getVolumeSerialNumber() const return 0; } +#endif + //============================================================================== #if ! JUCE_IOS void juce_runSystemCommand (const String&); @@ -1041,6 +1053,7 @@ void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (uint32 affinityMask) } //============================================================================== +#if ! JUCE_WASM bool DynamicLibrary::open (const String& name) { close(); @@ -1062,7 +1075,6 @@ void* DynamicLibrary::getFunction (const String& functionName) noexcept return handle != nullptr ? dlsym (handle, functionName.toUTF8()) : nullptr; } - //============================================================================== #if JUCE_LINUX || JUCE_ANDROID static String readPosixConfigFileValue (const char* file, const char* key) @@ -1160,7 +1172,7 @@ public: if (childPID == 0) return false; - int childState; + int childState = 0; auto pid = waitpid (childPID, &childState, WNOHANG); if (pid == 0) @@ -1262,220 +1274,130 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) return activeProcess != nullptr; } +#endif + //============================================================================== struct HighResolutionTimer::Pimpl { - Pimpl (HighResolutionTimer& t) : owner (t) - { - pthread_condattr_t attr; - pthread_condattr_init (&attr); - - #if JUCE_LINUX || (JUCE_ANDROID && defined(__ANDROID_API__) && __ANDROID_API__ >= 21) - pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); - #endif - - pthread_cond_init (&stopCond, &attr); - pthread_condattr_destroy (&attr); - pthread_mutex_init (&timerMutex, nullptr); - } + explicit Pimpl (HighResolutionTimer& t) + : owner (t) + {} ~Pimpl() { - jassert (! isRunning); + jassert (periodMs == 0); stop(); } void start (int newPeriod) { - if (periodMs != newPeriod) - { - if (thread != pthread_self()) - { - stop(); - - periodMs = newPeriod; - destroyThread = false; - isRunning = true; - - if (pthread_create (&thread, nullptr, timerThread, this) == 0) - setThreadToRealtime (thread, (uint64) newPeriod); - else - jassertfalse; - } - else - { - periodMs = newPeriod; - isRunning = true; - destroyThread = false; - } - } - } - - void stop() - { - isRunning = false; - - if (thread == pthread_t()) + if (periodMs == newPeriod) return; - if (thread == pthread_self()) + if (thread.get_id() == std::this_thread::get_id()) { - periodMs = 3600000; + periodMs = newPeriod; return; } - isRunning = false; - destroyThread = true; - - pthread_mutex_lock (&timerMutex); - pthread_cond_signal (&stopCond); - pthread_mutex_unlock (&timerMutex); - - pthread_join (thread, nullptr); - thread = {}; - } - - HighResolutionTimer& owner; - std::atomic periodMs { 0 }; - -private: - pthread_t thread = {}; - pthread_cond_t stopCond; - pthread_mutex_t timerMutex; - std::atomic destroyThread { false }, isRunning { false }; + stop(); - static void* timerThread (void* param) - { - #if ! JUCE_ANDROID - int dummy; - pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); - #endif + periodMs = newPeriod; - reinterpret_cast (param)->timerThread(); - return nullptr; - } + thread = std::thread ([this, newPeriod] + { + setThisThreadToRealtime ((uint64) newPeriod); - void timerThread() - { - auto lastPeriod = periodMs.load(); - Clock clock (lastPeriod); + auto lastPeriod = periodMs.load(); + Clock clock (lastPeriod); - pthread_mutex_lock (&timerMutex); + std::unique_lock unique_lock (timerMutex); - while (! destroyThread) - { - clock.next(); - while (! destroyThread && clock.wait (stopCond, timerMutex)); + while (periodMs != 0) + { + clock.next(); + while (periodMs != 0 && clock.wait (stopCond, unique_lock)); - if (destroyThread) - break; + if (periodMs == 0) + break; - if (isRunning) owner.hiResTimerCallback(); - auto newPeriod = periodMs.load(); + auto nextPeriod = periodMs.load(); - if (lastPeriod != newPeriod) - { - lastPeriod = newPeriod; - clock = Clock (lastPeriod); + if (lastPeriod != nextPeriod) + { + lastPeriod = nextPeriod; + clock = Clock (lastPeriod); + } } - } - periodMs = 0; - pthread_mutex_unlock (&timerMutex); - pthread_exit (nullptr); + periodMs = 0; + }); } - struct Clock + void stop() { - #if JUCE_MAC || JUCE_IOS - Clock (double millis) noexcept - { - (void) mach_timebase_info (&timebase); - delta = (((uint64_t) (millis * 1000000.0)) * timebase.denom) / timebase.numer; - time = mach_absolute_time(); - } - - bool wait (pthread_cond_t& cond, pthread_mutex_t& mutex) noexcept - { - struct timespec left; - - if (! hasExpired (left)) - return (pthread_cond_timedwait_relative_np (&cond, &mutex, &left) != ETIMEDOUT); - - return false; - } - - uint64_t time, delta; - mach_timebase_info_data_t timebase; - - bool hasExpired (struct timespec& time_left) noexcept - { - uint64_t now = mach_absolute_time(); + periodMs = 0; - if (now < time) - { - uint64_t left = time - now; - uint64_t nanos = (left * static_cast (timebase.numer)) / static_cast (timebase.denom); - time_left.tv_sec = static_cast<__darwin_time_t> (nanos / 1000000000ULL); - time_left.tv_nsec = static_cast (nanos - (static_cast (time_left.tv_sec) * 1000000000ULL)); + const auto thread_id = thread.get_id(); - return false; - } + if (thread_id == std::thread::id() || thread_id == std::this_thread::get_id()) + return; - return true; - } - #else - Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) { - struct timespec t; - clock_gettime (CLOCK_MONOTONIC, &t); - time = (uint64) (1000000000 * (int64) t.tv_sec + (int64) t.tv_nsec); + std::unique_lock unique_lock (timerMutex); + stopCond.notify_one(); } - bool wait (pthread_cond_t& cond, pthread_mutex_t& mutex) noexcept - { - struct timespec absExpire; + thread.join(); + } - if (! hasExpired (absExpire)) - return (pthread_cond_timedwait (&cond, &mutex, &absExpire) != ETIMEDOUT); + HighResolutionTimer& owner; + std::atomic periodMs { 0 }; - return false; - } +private: + std::thread thread; + std::condition_variable stopCond; + std::mutex timerMutex; - uint64 time, delta; + class Clock + { + public: + explicit Clock (std::chrono::steady_clock::rep millis) noexcept + : time (std::chrono::steady_clock::now()), + delta (std::chrono::milliseconds (millis)) + {} - bool hasExpired (struct timespec& expiryTime) noexcept + bool wait (std::condition_variable& cond, std::unique_lock& lock) noexcept { - struct timespec t; - clock_gettime (CLOCK_MONOTONIC, &t); - auto now = (uint64) (1000000000 * (int64) t.tv_sec + (int64) t.tv_nsec); - - if (now < time) - { - expiryTime.tv_sec = (time_t) (time / 1000000000); - expiryTime.tv_nsec = (long) (time % 1000000000); - - return false; - } - - return true; + return cond.wait_until (lock, time) != std::cv_status::timeout; } - #endif void next() noexcept { time += delta; } + + private: + std::chrono::time_point time; + std::chrono::steady_clock::duration delta; }; - static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) + static bool setThisThreadToRealtime (uint64 periodMs) { + const auto thread = pthread_self(); + #if JUCE_MAC || JUCE_IOS + mach_timebase_info_data_t timebase; + mach_timebase_info (&timebase); + + const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer; + const auto periodTicks = (uint32_t) (ticksPerMs * periodMs); + thread_time_constraint_policy_data_t policy; - policy.period = (uint32_t) (periodMs * 1000000); - policy.computation = 50000; + policy.period = periodTicks; + policy.computation = jmin ((uint32_t) 50000, policy.period); policy.constraint = policy.period; policy.preemptible = true; @@ -1489,7 +1411,6 @@ private: struct sched_param param; param.sched_priority = sched_get_priority_max (SCHED_RR); return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; - #endif } diff --git a/libs/juce-current/source/modules/juce_core/native/juce_wasm_SystemStats.cpp b/libs/juce-current/source/modules/juce_core/native/juce_wasm_SystemStats.cpp new file mode 100644 index 00000000..118f9d81 --- /dev/null +++ b/libs/juce-current/source/modules/juce_core/native/juce_wasm_SystemStats.cpp @@ -0,0 +1,87 @@ +/* + ============================================================================== + + 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 +{ + +void Logger::outputDebugString (const String& text) +{ + std::cerr << text << std::endl; +} + +//============================================================================== +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() { return WASM; } +String SystemStats::getOperatingSystemName() { return "WASM"; } +bool SystemStats::isOperatingSystem64Bit() { return true; } +String SystemStats::getDeviceDescription() { return "Web-browser"; } +String SystemStats::getDeviceManufacturer() { return {}; } +String SystemStats::getCpuVendor() { return {}; } +String SystemStats::getCpuModel() { return {}; } +int SystemStats::getCpuSpeedInMegahertz() { return 0; } +int SystemStats::getMemorySizeInMegabytes() { return 0; } +int SystemStats::getPageSize() { return 0; } +String SystemStats::getLogonName() { return {}; } +String SystemStats::getFullUserName() { return {}; } +String SystemStats::getComputerName() { return {}; } +String SystemStats::getUserLanguage() { return {}; } +String SystemStats::getUserRegion() { return {}; } +String SystemStats::getDisplayLanguage() { return {}; } + +//============================================================================== +void CPUInformation::initialise() noexcept +{ + numLogicalCPUs = 1; + numPhysicalCPUs = 1; +} + +//============================================================================== +uint32 juce_millisecondsSinceStartup() noexcept +{ + return static_cast (emscripten_get_now()); +} + +int64 Time::getHighResolutionTicks() noexcept +{ + return static_cast (emscripten_get_now() * 1000.0); +} + +int64 Time::getHighResolutionTicksPerSecond() noexcept +{ + return 1000000; // (microseconds) +} + +double Time::getMillisecondCounterHiRes() noexcept +{ + return emscripten_get_now(); +} + +bool Time::setSystemTimeToThisTime() const +{ + return false; +} + +JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept +{ + return false; +} + +} // namespace juce diff --git a/libs/juce-current/source/modules/juce_core/native/juce_win32_ComSmartPtr.h b/libs/juce-current/source/modules/juce_core/native/juce_win32_ComSmartPtr.h index c9232c2e..3c06d6d4 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_win32_ComSmartPtr.h +++ b/libs/juce-current/source/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -28,7 +28,7 @@ namespace juce #undef __uuidof #endif - template struct UUIDGetter { static CLSID get() { jassertfalse; return {}; } }; + template struct UUIDGetter { static CLSID get() { jassertfalse; return {}; } }; #define __uuidof(x) UUIDGetter::get() template <> @@ -38,7 +38,7 @@ namespace juce }; #define JUCE_DECLARE_UUID_GETTER(name, uuid) \ - template<> struct UUIDGetter { static CLSID get() { return uuidFromString (uuid); } }; + template <> struct UUIDGetter { static CLSID get() { return uuidFromString (uuid); } }; #define JUCE_COMCLASS(name, guid) \ struct name; \ @@ -50,6 +50,10 @@ namespace juce #define JUCE_COMCLASS(name, guid) struct __declspec (uuid (guid)) name #endif +#define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown +#define JUCE_COMRESULT HRESULT STDMETHODCALLTYPE +#define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE + inline GUID uuidFromString (const char* s) noexcept { uint32 ints[4] = {}; @@ -134,7 +138,7 @@ public: return this->QueryInterface (__uuidof (OtherComClass), destObject); } - template + template ComSmartPtr getInterface() const { ComSmartPtr destObject; @@ -154,18 +158,15 @@ private: }; //============================================================================== -#define JUCE_COMRESULT HRESULT __stdcall - -//============================================================================== -template -class ComBaseClassHelperBase : public ComClass +template +class ComBaseClassHelperBase : public First, public ComClasses... { public: ComBaseClassHelperBase (unsigned int initialRefCount) : refCount (initialRefCount) {} - virtual ~ComBaseClassHelperBase() {} + virtual ~ComBaseClassHelperBase() = default; - ULONG __stdcall AddRef() { return ++refCount; } - ULONG __stdcall Release() { auto r = --refCount; if (r == 0) delete this; return r; } + ULONG STDMETHODCALLTYPE AddRef() { return ++refCount; } + ULONG STDMETHODCALLTYPE Release() { auto r = --refCount; if (r == 0) delete this; return r; } protected: ULONG refCount; @@ -173,7 +174,7 @@ protected: JUCE_COMRESULT QueryInterface (REFIID refId, void** result) { if (refId == __uuidof (IUnknown)) - return castToType (result); + return castToType (result); *result = nullptr; return E_NOINTERFACE; @@ -182,7 +183,10 @@ protected: template JUCE_COMRESULT castToType (void** result) { - this->AddRef(); *result = dynamic_cast (this); return S_OK; + this->AddRef(); + *result = dynamic_cast (this); + + return S_OK; } }; @@ -190,19 +194,32 @@ protected: @tags{Core} */ -template -class ComBaseClassHelper : public ComBaseClassHelperBase +template +class ComBaseClassHelper : public ComBaseClassHelperBase { public: - ComBaseClassHelper (unsigned int initialRefCount = 1) : ComBaseClassHelperBase (initialRefCount) {} - ~ComBaseClassHelper() {} + ComBaseClassHelper (unsigned int initialRefCount = 1) : ComBaseClassHelperBase (initialRefCount) {} JUCE_COMRESULT QueryInterface (REFIID refId, void** result) { - if (refId == __uuidof (ComClass)) - return this->template castToType (result); + return queryInterfaceWithType (refId, result, Tag{}...); + } + +private: + JUCE_COMRESULT queryInterfaceWithType (REFIID refId, void** result) + { + return ComBaseClassHelperBase::QueryInterface (refId, result); + } + + template struct Tag {}; + + template + JUCE_COMRESULT queryInterfaceWithType (REFIID refId, void** result, Tag, Tag...) + { + if (refId == __uuidof (T)) + return this->template castToType (result); - return ComBaseClassHelperBase::QueryInterface (refId, result); + return queryInterfaceWithType (refId, result, Tag{}...); } }; diff --git a/libs/juce-current/source/modules/juce_core/native/juce_win32_Files.cpp b/libs/juce-current/source/modules/juce_core/native/juce_win32_Files.cpp index e0701dad..9547df8c 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_win32_Files.cpp +++ b/libs/juce-current/source/modules/juce_core/native/juce_win32_Files.cpp @@ -975,7 +975,7 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: const String filename; HANDLE pipeH, cancelEvent; - bool connected, ownsPipe, shouldStop; + bool connected, ownsPipe; + std::atomic shouldStop; CriticalSection createFileLock; private: @@ -1150,10 +1151,13 @@ private: 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 @@ private: 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/libs/juce-current/source/modules/juce_core/native/juce_win32_Threads.cpp b/libs/juce-current/source/modules/juce_core/native/juce_win32_Threads.cpp index 9f33a8ea..1c38ff2c 100644 --- a/libs/juce-current/source/modules/juce_core/native/juce_win32_Threads.cpp +++ b/libs/juce-current/source/modules/juce_core/native/juce_win32_Threads.cpp @@ -51,7 +51,7 @@ void CriticalSection::exit() const noexcept { LeaveCriticalSection ((CRI //============================================================================== void JUCE_API juce_threadEntryPoint (void*); -static unsigned int __stdcall threadEntryProc (void* userData) +static unsigned int STDMETHODCALLTYPE threadEntryProc (void* userData) { if (juce_messageWindowHandle != nullptr) AttachThreadInput (GetWindowThreadProcessId (juce_messageWindowHandle, nullptr), @@ -562,7 +562,7 @@ struct HighResolutionTimer::Pimpl private: unsigned int timerID; - static void __stdcall callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) + static void STDMETHODCALLTYPE callbackFunction (UINT, UINT, DWORD_PTR userInfo, DWORD_PTR, DWORD_PTR) { if (Pimpl* const timer = reinterpret_cast (userInfo)) if (timer->periodMs != 0) diff --git a/libs/juce-current/source/modules/juce_core/network/juce_NamedPipe.cpp b/libs/juce-current/source/modules/juce_core/network/juce_NamedPipe.cpp index c1c994b2..a4a26ecf 100644 --- a/libs/juce-current/source/modules/juce_core/network/juce_NamedPipe.cpp +++ b/libs/juce-current/source/modules/juce_core/network/juce_NamedPipe.cpp @@ -23,6 +23,8 @@ namespace juce { +#if ! JUCE_WASM + NamedPipe::NamedPipe() {} NamedPipe::~NamedPipe() @@ -41,6 +43,7 @@ bool NamedPipe::openExisting (const String& pipeName) bool NamedPipe::isOpen() const { + ScopedReadLock sl (lock); return pimpl != nullptr; } @@ -55,6 +58,7 @@ bool NamedPipe::createNewPipe (const String& pipeName, bool mustNotExist) String NamedPipe::getName() const { + ScopedReadLock sl (lock); return currentPipeName; } @@ -75,7 +79,7 @@ public: void runTest() override { - const String pipeName ("TestPipe"); + const auto pipeName = "TestPipe" + String ((intptr_t) Thread::getCurrentThreadId()); beginTest ("Pre test cleanup"); { @@ -208,11 +212,6 @@ private: pipe.openExisting (pipeName); } - ~NamedPipeThread() - { - stopThread (100); - } - NamedPipe pipe; const String& pipeName; WaitableEvent& workCompleted; @@ -229,6 +228,11 @@ private: sendData (sData) {} + ~SenderThread() override + { + stopThread (100); + } + void run() override { result = pipe.write (&sendData, sizeof (sendData), 2000); @@ -246,6 +250,11 @@ private: : NamedPipeThread ("NamePipeSender", pName, shouldCreatePipe, completed) {} + ~ReceiverThread() override + { + stopThread (100); + } + void run() override { result = pipe.read (&recvData, sizeof (recvData), 2000); @@ -258,6 +267,7 @@ private: static NamedPipeTests namedPipeTests; +#endif #endif } // namespace juce diff --git a/libs/juce-current/source/modules/juce_core/network/juce_Socket.cpp b/libs/juce-current/source/modules/juce_core/network/juce_Socket.cpp index 64168aa7..a43cb141 100644 --- a/libs/juce-current/source/modules/juce_core/network/juce_Socket.cpp +++ b/libs/juce-current/source/modules/juce_core/network/juce_Socket.cpp @@ -23,6 +23,8 @@ namespace juce { +#if ! JUCE_WASM + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4127 4389 4018) #ifndef AI_NUMERICSERV // (missing in older Mac SDKs) @@ -857,6 +859,7 @@ struct SocketTests : public UnitTest static SocketTests socketTests; +#endif #endif } // namespace juce diff --git a/libs/juce-current/source/modules/juce_core/system/juce_CompilerSupport.h b/libs/juce-current/source/modules/juce_core/system/juce_CompilerSupport.h index ddeef94a..d76d92d4 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_CompilerSupport.h +++ b/libs/juce-current/source/modules/juce_core/system/juce_CompilerSupport.h @@ -100,7 +100,7 @@ #if (! JUCE_MSVC) && (! JUCE_CXX14_IS_AVAILABLE) namespace std { - template + template unique_ptr make_unique (Args&&... args) { return unique_ptr (new T (std::forward (args)...)); diff --git a/libs/juce-current/source/modules/juce_core/system/juce_PlatformDefs.h b/libs/juce-current/source/modules/juce_core/system/juce_PlatformDefs.h index 9d26dacb..0a1798ba 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_PlatformDefs.h +++ b/libs/juce-current/source/modules/juce_core/system/juce_PlatformDefs.h @@ -59,15 +59,13 @@ namespace juce #endif //============================================================================== -#if JUCE_IOS || JUCE_LINUX +#if JUCE_IOS || (JUCE_MAC && JUCE_ARM) || JUCE_LINUX /** This will try to break into the debugger if the app is currently being debugged. If called by an app that's not being debugged, the behaviour isn't defined - it may crash or not, depending on the platform. @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) diff --git a/libs/juce-current/source/modules/juce_core/system/juce_StandardHeader.h b/libs/juce-current/source/modules/juce_core/system/juce_StandardHeader.h index 40c98e60..f83869ed 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_StandardHeader.h +++ b/libs/juce-current/source/modules/juce_core/system/juce_StandardHeader.h @@ -29,7 +29,7 @@ */ #define JUCE_MAJOR_VERSION 6 #define JUCE_MINOR_VERSION 0 -#define JUCE_BUILDNUMBER 4 +#define JUCE_BUILDNUMBER 7 /** Current JUCE version number. @@ -44,6 +44,7 @@ //============================================================================== #include +#include #include #include #include @@ -79,9 +80,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4514 4245 4100) #if JUCE_MAC || JUCE_IOS #include #include - #if JUCE_IOS - #include - #endif + #include #endif #if JUCE_LINUX @@ -110,7 +109,6 @@ JUCE_END_IGNORE_WARNINGS_MSVC #if JUCE_ANDROID #include - #include #include #endif diff --git a/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.cpp b/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.cpp index 84712716..813a23b2 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.cpp +++ b/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.cpp @@ -138,7 +138,7 @@ String SystemStats::getStackBacktrace() { String result; - #if JUCE_ANDROID || JUCE_MINGW + #if JUCE_ANDROID || JUCE_MINGW || JUCE_WASM jassertfalse; // sorry, not implemented yet! #elif JUCE_WINDOWS @@ -187,6 +187,8 @@ String SystemStats::getStackBacktrace() } //============================================================================== +#if ! JUCE_WASM + static SystemStats::CrashHandlerFunction globalCrashHandler = nullptr; #if JUCE_WINDOWS @@ -223,6 +225,8 @@ void SystemStats::setApplicationCrashHandler (CrashHandlerFunction handler) #endif } +#endif + bool SystemStats::isRunningInAppExtensionSandbox() noexcept { #if JUCE_MAC || JUCE_IOS diff --git a/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.h b/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.h index 3260af8e..d42a185c 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.h +++ b/libs/juce-current/source/modules/juce_core/system/juce_SystemStats.h @@ -51,6 +51,7 @@ public: Linux = 0x0400, Android = 0x0800, iOS = 0x1000, + WASM = 0x2000, MacOSX_10_7 = MacOSX | 7, MacOSX_10_8 = MacOSX | 8, @@ -61,7 +62,7 @@ public: MacOSX_10_13 = MacOSX | 13, MacOSX_10_14 = MacOSX | 14, MacOSX_10_15 = MacOSX | 15, - MacOSX_11_0 = MacOSX | 16, + MacOS_11 = MacOSX | 16, Win2000 = Windows | 1, WinXP = Windows | 2, diff --git a/libs/juce-current/source/modules/juce_core/system/juce_TargetPlatform.h b/libs/juce-current/source/modules/juce_core/system/juce_TargetPlatform.h index 7265da72..6b41688b 100644 --- a/libs/juce-current/source/modules/juce_core/system/juce_TargetPlatform.h +++ b/libs/juce-current/source/modules/juce_core/system/juce_TargetPlatform.h @@ -65,7 +65,7 @@ #elif defined (__FreeBSD__) || (__OpenBSD__) #define JUCE_BSD 1 #elif defined (LINUX) || defined (__linux__) - #define JUCE_LINUX 1 + #define JUCE_LINUX 1 #elif defined (__APPLE_CPP__) || defined (__APPLE_CC__) #define CF_EXCLUDE_CSTD_HEADERS 1 #include // (needed to find out what platform we're using) @@ -78,6 +78,8 @@ #else #define JUCE_MAC 1 #endif +#elif defined (__wasm__) + #define JUCE_WASM 1 #else #error "Unknown platform!" #endif diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_ASCII.h b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_ASCII.h index 597d6f2f..d84b19d5 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_ASCII.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_ASCII.h @@ -350,6 +350,9 @@ public: /** Returns the first non-whitespace character in the string. */ CharPointer_ASCII findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + /** Move this pointer to the first non-whitespace character in the string. */ + void incrementToEndOfWhitespace() noexcept { CharacterFunctions::incrementToEndOfWhitespace (*this); } + /** Returns true if the given unicode character can be represented in this encoding. */ static bool canRepresent (juce_wchar character) noexcept { diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF16.h b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF16.h index 62cfce54..5dcf6b86 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF16.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF16.h @@ -426,6 +426,9 @@ public: /** Returns the first non-whitespace character in the string. */ CharPointer_UTF16 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + /** Move this pointer to the first non-whitespace character in the string. */ + void incrementToEndOfWhitespace() noexcept { CharacterFunctions::incrementToEndOfWhitespace (*this); } + /** Returns true if the given unicode character can be represented in this encoding. */ static bool canRepresent (juce_wchar character) noexcept { diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF32.h b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF32.h index da66024b..4dcbbd86 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF32.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF32.h @@ -341,6 +341,9 @@ public: /** Returns the first non-whitespace character in the string. */ CharPointer_UTF32 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + /** Move this pointer to the first non-whitespace character in the string. */ + void incrementToEndOfWhitespace() noexcept { CharacterFunctions::incrementToEndOfWhitespace (*this); } + /** Returns true if the given unicode character can be represented in this encoding. */ static bool canRepresent (juce_wchar character) noexcept { diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF8.h b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF8.h index 5aa42c42..117d021b 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF8.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharPointer_UTF8.h @@ -483,6 +483,9 @@ public: /** Returns the first non-whitespace character in the string. */ CharPointer_UTF8 findEndOfWhitespace() const noexcept { return CharacterFunctions::findEndOfWhitespace (*this); } + /** Move this pointer to the first non-whitespace character in the string. */ + void incrementToEndOfWhitespace() noexcept { CharacterFunctions::incrementToEndOfWhitespace (*this); } + /** Returns true if the given unicode character can be represented in this encoding. */ static bool canRepresent (juce_wchar character) noexcept { diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.cpp b/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.cpp index 8341a8ca..a0e973e1 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.cpp +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -181,19 +181,209 @@ juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint #define QUOTE(x) #x #define STR(value) QUOTE(value) -#define ASYM_STRING_DOUBLE_PAIR(str, value) std::pair (STR(str), value) -#define STRING_DOUBLE_PAIR(value) ASYM_STRING_DOUBLE_PAIR(value, value) -#define STRING_DOUBLE_PAIR_COMBOS(value) \ - STRING_DOUBLE_PAIR(value), \ - STRING_DOUBLE_PAIR(-value), \ - ASYM_STRING_DOUBLE_PAIR(+value, value), \ - ASYM_STRING_DOUBLE_PAIR(000000 ## value, value), \ - ASYM_STRING_DOUBLE_PAIR(+000 ## value, value), \ - ASYM_STRING_DOUBLE_PAIR(-0 ## value, -value) +#define ASYM_CHARPTR_DOUBLE_PAIR(str, value) std::pair (STR(str), value) +#define CHARPTR_DOUBLE_PAIR(value) ASYM_CHARPTR_DOUBLE_PAIR(value, value) +#define CHARPTR_DOUBLE_PAIR_COMBOS(value) \ + CHARPTR_DOUBLE_PAIR(value), \ + CHARPTR_DOUBLE_PAIR(-value), \ + ASYM_CHARPTR_DOUBLE_PAIR(+value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(000000 ## value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(+000 ## value, value), \ + ASYM_CHARPTR_DOUBLE_PAIR(-0 ## value, -value) + +namespace characterFunctionsTests +{ + +template +MemoryBlock memoryBlockFromCharPtr (const typename CharPointerType::CharType* charPtr) +{ + using CharType = typename CharPointerType::CharType; + + MemoryBlock result; + CharPointerType source (charPtr); + + result.setSize (CharPointerType::getBytesRequiredFor (source) + sizeof (CharType)); + CharPointerType dest { (CharType*) result.getData() }; + dest.writeAll (source); + return result; +} + +template +MemoryBlock convert (const MemoryBlock& source, bool removeNullTerminator = false) +{ + using ToCharType = typename ToCharPointerType ::CharType; + using FromCharType = typename FromCharPointerType::CharType; + + FromCharPointerType sourcePtr { (FromCharType*) source.getData() }; + + std::vector sourceChars; + size_t requiredSize = 0; + juce_wchar c; + + while ((c = sourcePtr.getAndAdvance()) != '\0') + { + requiredSize += ToCharPointerType::getBytesRequiredFor (c); + sourceChars.push_back (c); + } + + if (! removeNullTerminator) + requiredSize += sizeof (ToCharType); + + MemoryBlock result; + result.setSize (requiredSize); + + ToCharPointerType dest { (ToCharType*) result.getData() }; + + for (auto wc : sourceChars) + dest.write (wc); + + if (! removeNullTerminator) + dest.writeNull(); + + return result; +} + +struct SeparatorStrings +{ + std::vector terminals, nulls; +}; + +template +SeparatorStrings getSeparators() +{ + jassertfalse; + return {}; +} + +template <> +SeparatorStrings getSeparators() +{ + SeparatorStrings result; + + const CharPointer_ASCII::CharType* terminalCharPtrs[] = { + "", "-", "+", "e", "e+", "E-", "f", " ", ",", ";", "<", "'", "\"", "_", "k", + " +", " -", " -e", "-In ", " +n", "n", " r" + }; + + for (auto ptr : terminalCharPtrs) + result.terminals.push_back (memoryBlockFromCharPtr (ptr)); + + const CharPointer_ASCII::CharType* nullCharPtrs[] = { "." }; + + result.nulls = result.terminals; + + for (auto ptr : nullCharPtrs) + result.nulls.push_back (memoryBlockFromCharPtr (ptr)); + + return result; +} + +template <> +SeparatorStrings getSeparators() +{ + auto result = getSeparators(); + + const CharPointer_UTF8::CharType* terminalCharPtrs[] = { + "\xe2\x82\xac", // € + "\xf0\x90\x90\xB7", // 𐐷 + "\xf0\x9f\x98\x83", // 😃 + "\xf0\x9f\x8f\x81\xF0\x9F\x9A\x97" // 🏁🚗 + }; + + for (auto ptr : terminalCharPtrs) + { + auto block = memoryBlockFromCharPtr (ptr); + + for (auto vec : { &result.terminals, &result.nulls }) + vec->push_back (block); + } + + return result; +} + +template +SeparatorStrings prefixWithAsciiSeparators (const std::vector>& terminalCharPtrs) +{ + auto asciiSeparators = getSeparators(); + + SeparatorStrings result; + + for (const auto& block : asciiSeparators.terminals) + result.terminals.push_back (convert (block)); + + for (const auto& block : asciiSeparators.nulls) + result.nulls.push_back (convert (block)); + for (auto& t : terminalCharPtrs) + { + const auto block = memoryBlockFromCharPtr ((typename CharPointerType::CharType*) t.data()); + + for (auto vec : { &result.terminals, &result.nulls }) + vec->push_back (block); + } + + return result; +} + +template <> +SeparatorStrings getSeparators() +{ + const std::vector> terminalCharPtrs { + { 0x0 }, + { 0x0076, 0x0 }, // v + { 0x20ac, 0x0 }, // € + { 0xd801, 0xdc37, 0x0 }, // 𐐷 + { 0x0065, 0xd83d, 0xde03, 0x0 }, // e😃 + { 0xd83c, 0xdfc1, 0xd83d, 0xde97, 0x0 } // 🏁🚗 + }; + + return prefixWithAsciiSeparators (terminalCharPtrs); +} + +template <> +SeparatorStrings getSeparators() +{ + const std::vector> terminalCharPtrs = { + { 0x00000076, 0x0 }, // v + { 0x000020aC, 0x0 }, // € + { 0x00010437, 0x0 }, // 𐐷 + { 0x00000065, 0x0001f603, 0x0 }, // e😃 + { 0x0001f3c1, 0x0001f697, 0x0 } // 🏁🚗 + }; + + return prefixWithAsciiSeparators (terminalCharPtrs); +} + +template +void withAllPrefixesAndSuffixes (const std::vector& prefixes, + const std::vector& suffixes, + const std::vector& testValues, + TestFunction&& test) +{ + for (const auto& prefix : prefixes) + { + for (const auto& testValue : testValues) + { + MemoryBlock testBlock = prefix; + testBlock.append (testValue.getData(), testValue.getSize()); + + for (const auto& suffix : suffixes) + { + MemoryBlock data = testBlock; + data.append (suffix.getData(), suffix.getSize()); + + test (data, suffix); + } + } + } +} + +template class CharacterFunctionsTests : public UnitTest { public: + using CharType = typename CharPointerType::CharType; + CharacterFunctionsTests() : UnitTest ("CharacterFunctions", UnitTestCategories::text) {} @@ -202,96 +392,159 @@ public: { beginTest ("readDoubleValue"); - static const std::pair testValues[] = + const std::pair trials[] = { // Integers - STRING_DOUBLE_PAIR_COMBOS (0), - STRING_DOUBLE_PAIR_COMBOS (3), - STRING_DOUBLE_PAIR_COMBOS (4931), - STRING_DOUBLE_PAIR_COMBOS (5000), - STRING_DOUBLE_PAIR_COMBOS (9862097), + CHARPTR_DOUBLE_PAIR_COMBOS (0), + CHARPTR_DOUBLE_PAIR_COMBOS (3), + CHARPTR_DOUBLE_PAIR_COMBOS (4931), + CHARPTR_DOUBLE_PAIR_COMBOS (5000), + CHARPTR_DOUBLE_PAIR_COMBOS (9862097), // Floating point numbers - STRING_DOUBLE_PAIR_COMBOS (7.000), - STRING_DOUBLE_PAIR_COMBOS (0.2), - STRING_DOUBLE_PAIR_COMBOS (.298630), - STRING_DOUBLE_PAIR_COMBOS (1.118), - STRING_DOUBLE_PAIR_COMBOS (0.9000), - STRING_DOUBLE_PAIR_COMBOS (0.0000001), - STRING_DOUBLE_PAIR_COMBOS (500.0000001), - STRING_DOUBLE_PAIR_COMBOS (9862098.2398604), + CHARPTR_DOUBLE_PAIR_COMBOS (0.), + CHARPTR_DOUBLE_PAIR_COMBOS (9.), + CHARPTR_DOUBLE_PAIR_COMBOS (7.000), + CHARPTR_DOUBLE_PAIR_COMBOS (0.2), + CHARPTR_DOUBLE_PAIR_COMBOS (.298630), + CHARPTR_DOUBLE_PAIR_COMBOS (1.118), + CHARPTR_DOUBLE_PAIR_COMBOS (0.9000), + CHARPTR_DOUBLE_PAIR_COMBOS (0.0000001), + CHARPTR_DOUBLE_PAIR_COMBOS (500.0000001), + CHARPTR_DOUBLE_PAIR_COMBOS (9862098.2398604), // Exponents - STRING_DOUBLE_PAIR_COMBOS (0e0), - STRING_DOUBLE_PAIR_COMBOS (0.e0), - STRING_DOUBLE_PAIR_COMBOS (0.00000e0), - STRING_DOUBLE_PAIR_COMBOS (.0e7), - STRING_DOUBLE_PAIR_COMBOS (0e-5), - STRING_DOUBLE_PAIR_COMBOS (2E0), - STRING_DOUBLE_PAIR_COMBOS (4.E0), - STRING_DOUBLE_PAIR_COMBOS (1.2000000E0), - STRING_DOUBLE_PAIR_COMBOS (1.2000000E6), - STRING_DOUBLE_PAIR_COMBOS (.398e3), - STRING_DOUBLE_PAIR_COMBOS (10e10), - STRING_DOUBLE_PAIR_COMBOS (1.4962e+2), - STRING_DOUBLE_PAIR_COMBOS (3198693.0973e4), - STRING_DOUBLE_PAIR_COMBOS (10973097.2087E-4), - STRING_DOUBLE_PAIR_COMBOS (1.3986e00006), - STRING_DOUBLE_PAIR_COMBOS (2087.3087e+00006), - STRING_DOUBLE_PAIR_COMBOS (6.0872e-00006), + CHARPTR_DOUBLE_PAIR_COMBOS (0e0), + CHARPTR_DOUBLE_PAIR_COMBOS (0.e0), + CHARPTR_DOUBLE_PAIR_COMBOS (0.00000e0), + CHARPTR_DOUBLE_PAIR_COMBOS (.0e7), + CHARPTR_DOUBLE_PAIR_COMBOS (0e-5), + CHARPTR_DOUBLE_PAIR_COMBOS (2E0), + CHARPTR_DOUBLE_PAIR_COMBOS (4.E0), + CHARPTR_DOUBLE_PAIR_COMBOS (1.2000000E0), + CHARPTR_DOUBLE_PAIR_COMBOS (1.2000000E6), + CHARPTR_DOUBLE_PAIR_COMBOS (.398e3), + CHARPTR_DOUBLE_PAIR_COMBOS (10e10), + CHARPTR_DOUBLE_PAIR_COMBOS (1.4962e+2), + CHARPTR_DOUBLE_PAIR_COMBOS (3198693.0973e4), + CHARPTR_DOUBLE_PAIR_COMBOS (10973097.2087E-4), + CHARPTR_DOUBLE_PAIR_COMBOS (1.3986e00006), + CHARPTR_DOUBLE_PAIR_COMBOS (2087.3087e+00006), + CHARPTR_DOUBLE_PAIR_COMBOS (6.0872e-00006), + + CHARPTR_DOUBLE_PAIR_COMBOS (1.7976931348623157e+308), + CHARPTR_DOUBLE_PAIR_COMBOS (2.2250738585072014e-308), // Too many sig figs. The parsing routine on MinGW gets the last // significant figure wrong. - STRING_DOUBLE_PAIR_COMBOS (17654321098765432.9), - STRING_DOUBLE_PAIR_COMBOS (183456789012345678.9), - STRING_DOUBLE_PAIR_COMBOS (1934567890123456789.9), - STRING_DOUBLE_PAIR_COMBOS (20345678901234567891.9), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e3), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e100), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-5), - STRING_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-40), - - STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890), - STRING_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890e-111) - - // Limits. DBL_MAX may not exist on Linux. - #if ! JUCE_LINUX - , STRING_DOUBLE_PAIR (DBL_MAX), - STRING_DOUBLE_PAIR (-DBL_MAX), - STRING_DOUBLE_PAIR (DBL_MIN) - #endif + CHARPTR_DOUBLE_PAIR_COMBOS (17654321098765432.9), + CHARPTR_DOUBLE_PAIR_COMBOS (183456789012345678.9), + CHARPTR_DOUBLE_PAIR_COMBOS (1934567890123456789.9), + CHARPTR_DOUBLE_PAIR_COMBOS (20345678901234567891.9), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e3), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752e100), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000000e-5), + CHARPTR_DOUBLE_PAIR_COMBOS (10000000000000000303786028427003666890752.000005e-40), + + CHARPTR_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890), + CHARPTR_DOUBLE_PAIR_COMBOS (1.23456789012345678901234567890e-111), }; - for (auto trial : testValues) + auto asciiToMemoryBlock = [] (const char* asciiPtr, bool removeNullTerminator) { - auto charPtr = trial.first.getCharPointer(); - expectEquals (CharacterFunctions::readDoubleValue (charPtr), trial.second); + auto block = memoryBlockFromCharPtr (asciiPtr); + return convert (block, removeNullTerminator); + }; + + const auto separators = getSeparators(); + + for (const auto& trial : trials) + { + for (const auto& terminal : separators.terminals) + { + MemoryBlock data { asciiToMemoryBlock (trial.first, true) }; + data.append (terminal.getData(), terminal.getSize()); + + CharPointerType charPtr { (CharType*) data.getData() }; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), trial.second); + expect (*charPtr == *(CharPointerType ((CharType*) terminal.getData()))); + } } + auto asciiToMemoryBlocks = [&] (const std::vector& asciiPtrs, bool removeNullTerminator) + { + std::vector result; + + for (auto* ptr : asciiPtrs) + result.push_back (asciiToMemoryBlock (ptr, removeNullTerminator)); + + return result; + }; + + std::vector prefixCharPtrs = { "" , "+", "-" }; + const auto prefixes = asciiToMemoryBlocks (prefixCharPtrs, true); + { - String nans[] = { "NaN", "-nan", "+NAN", "1.0E1024", "-1.0E-999", "1.23456789012345678901234567890e123456789"}; + std::vector nanCharPtrs = { "NaN", "nan", "NAN", "naN" }; + auto nans = asciiToMemoryBlocks (nanCharPtrs, true); - for (auto nan : nans) + withAllPrefixesAndSuffixes (prefixes, separators.terminals, nans, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) { - auto charPtr = nan.getCharPointer(); + CharPointerType charPtr { (CharType*) data.getData() }; expect (std::isnan (CharacterFunctions::readDoubleValue (charPtr))); - } + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); + } + + { + std::vector infCharPtrs = { "Inf", "inf", "INF", "InF", "1.0E1024", "1.23456789012345678901234567890e123456789" }; + auto infs = asciiToMemoryBlocks (infCharPtrs, true); + + withAllPrefixesAndSuffixes (prefixes, separators.terminals, infs, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) + { + CharPointerType charPtr { (CharType*) data.getData() }; + auto expected = charPtr[0] == '-' ? -std::numeric_limits::infinity() + : std::numeric_limits::infinity(); + expectEquals (CharacterFunctions::readDoubleValue (charPtr), expected); + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); } { - String infs[] = { "Inf", "-inf", "INF"}; + std::vector zeroCharPtrs = { "1.0E-400", "1.23456789012345678901234567890e-123456789" }; + auto zeros = asciiToMemoryBlocks (zeroCharPtrs, true); - for (auto inf : infs) + withAllPrefixesAndSuffixes (prefixes, separators.terminals, zeros, [this] (const MemoryBlock& data, + const MemoryBlock& suffix) { - auto charPtr = inf.getCharPointer(); - expect (std::isinf (CharacterFunctions::readDoubleValue (charPtr))); + CharPointerType charPtr { (CharType*) data.getData() }; + auto expected = charPtr[0] == '-' ? -0.0 : 0.0; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), expected); + expect (*charPtr == *(CharPointerType ((CharType*) suffix.getData()))); + }); + } + + { + for (const auto& n : separators.nulls) + { + MemoryBlock data { n.getData(), n.getSize() }; + CharPointerType charPtr { (CharType*) data.getData() }; + expectEquals (CharacterFunctions::readDoubleValue (charPtr), 0.0); + expect (charPtr == CharPointerType { (CharType*) data.getData() }.findEndOfWhitespace()); } } } }; -static CharacterFunctionsTests characterFunctionsTests; +static CharacterFunctionsTests characterFunctionsTestsAscii; +static CharacterFunctionsTests characterFunctionsTestsUtf8; +static CharacterFunctionsTests characterFunctionsTestsUtf16; +static CharacterFunctionsTests characterFunctionsTestsUtf32; + +} #endif diff --git a/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.h b/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.h index 76c9c5c7..cafe918e 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_CharacterFunctions.h @@ -146,25 +146,27 @@ public: template static double readDoubleValue (CharPointerType& text) noexcept { - #if JUCE_MINGW + constexpr auto inf = std::numeric_limits::infinity(); + bool isNegative = false; - #else + #if ! JUCE_MINGW constexpr const int maxSignificantDigits = 17 + 1; // An additional digit for rounding constexpr const int bufferSize = maxSignificantDigits + 7 + 1; // -.E-XXX and a trailing null-terminator char buffer[(size_t) bufferSize] = {}; - char* currentCharacter = &(buffer[0]); + char* writePtr = &(buffer[0]); #endif - text = text.findEndOfWhitespace(); + const auto endOfWhitspace = text.findEndOfWhitespace(); + text = endOfWhitspace; + auto c = *text; switch (c) { case '-': - #if JUCE_MINGW isNegative = true; - #else - *currentCharacter++ = '-'; + #if ! JUCE_MINGW + *writePtr++ = '-'; #endif JUCE_FALLTHROUGH case '+': @@ -178,15 +180,29 @@ public: { case 'n': case 'N': + { if ((text[1] == 'a' || text[1] == 'A') && (text[2] == 'n' || text[2] == 'N')) + { + text += 3; return std::numeric_limits::quiet_NaN(); - break; + } + + text = endOfWhitspace; + return 0.0; + } case 'i': case 'I': + { if ((text[1] == 'n' || text[1] == 'N') && (text[2] == 'f' || text[2] == 'F')) - return std::numeric_limits::infinity(); - break; + { + text += 3; + return isNegative ? -inf : inf; + } + + text = endOfWhitspace; + return 0.0; + } default: break; @@ -299,9 +315,8 @@ public: #else // ! JUCE_MINGW - int numSigFigs = 0; - bool decimalPointFound = false; - int extraExponent = 0; + int numSigFigs = 0, extraExponent = 0; + bool decimalPointFound = false, leadingZeros = false; for (;;) { @@ -323,16 +338,19 @@ public: } if (numSigFigs == 0 && digit == 0) + { + leadingZeros = true; continue; + } } - *currentCharacter++ = (char) ('0' + (char) digit); + *writePtr++ = (char) ('0' + (char) digit); numSigFigs++; } else if ((! decimalPointFound) && *text == '.') { ++text; - *currentCharacter++ = '.'; + *writePtr++ = '.'; decimalPointFound = true; } else @@ -341,7 +359,11 @@ public: } } - c = *text; + if ((! leadingZeros) && (numSigFigs == 0)) + { + text = endOfWhitspace; + return 0.0; + } auto writeExponentDigits = [] (int exponent, char* destination) { @@ -358,19 +380,28 @@ public: *destination++ = (char) ('0' + (char) exponent); }; - if ((c == 'e' || c == 'E') && numSigFigs > 0) + c = *text; + + if (c == 'e' || c == 'E') { - *currentCharacter++ = 'e'; + const auto startOfExponent = text; + *writePtr++ = 'e'; bool parsedExponentIsPositive = true; switch (*++text) { - case '-': parsedExponentIsPositive = false; JUCE_FALLTHROUGH - case '+': ++text; break; - default: break; + case '-': + parsedExponentIsPositive = false; + JUCE_FALLTHROUGH + case '+': + ++text; + break; + default: + break; } int exponent = 0; + const auto startOfExponentDigits = text; while (text.isDigit()) { @@ -380,22 +411,30 @@ public: exponent = (exponent * 10) + digit; } + if (text == startOfExponentDigits) + text = startOfExponent; + exponent = extraExponent + (parsedExponentIsPositive ? exponent : -exponent); if (exponent < 0) - *currentCharacter++ = '-'; - - exponent = std::abs (exponent); + { + if (exponent < std::numeric_limits::min_exponent10 - 1) + return isNegative ? -0.0 : 0.0; - if (exponent > std::numeric_limits::max_exponent10) - return std::numeric_limits::quiet_NaN(); + *writePtr++ = '-'; + exponent = -exponent; + } + else if (exponent > std::numeric_limits::max_exponent10 + 1) + { + return isNegative ? -inf : inf; + } - writeExponentDigits (exponent, currentCharacter); + writeExponentDigits (exponent, writePtr); } else if (extraExponent > 0) { - *currentCharacter++ = 'e'; - writeExponentDigits (extraExponent, currentCharacter); + *writePtr++ = 'e'; + writeExponentDigits (extraExponent, writePtr); } #if JUCE_WINDOWS @@ -755,6 +794,19 @@ public: return -1; } + /** Increments a pointer until it points to the first non-whitespace character + in a string. + + If the string contains only whitespace, the pointer will point to the + string's null terminator. + */ + template + static void incrementToEndOfWhitespace (Type& text) noexcept + { + while (text.isWhitespace()) + ++text; + } + /** Returns a pointer to the first non-whitespace character in a string. If the string contains only whitespace, this will return a pointer to its null terminator. @@ -762,9 +814,7 @@ public: template static Type findEndOfWhitespace (Type text) noexcept { - while (text.isWhitespace()) - ++text; - + incrementToEndOfWhitespace (text); return text; } diff --git a/libs/juce-current/source/modules/juce_core/text/juce_String.cpp b/libs/juce-current/source/modules/juce_core/text/juce_String.cpp index a685e6a6..2f1b2aee 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_String.cpp +++ b/libs/juce-current/source/modules/juce_core/text/juce_String.cpp @@ -2577,6 +2577,8 @@ public: expect (String (std::numeric_limits::max()).getLargeIntValue() == std::numeric_limits::max()); expect (String (std::numeric_limits::min()).getLargeIntValue() == std::numeric_limits::min()); expect (("xyz" + s).getTrailingIntValue() == s.getIntValue()); + expect (String ("xyz-5").getTrailingIntValue() == -5); + expect (String ("-12345").getTrailingIntValue() == -12345); expect (s.getHexValue32() == 0x12345678); expect (s.getHexValue64() == (int64) 0x12345678); expect (String::toHexString (0x1234abcd).equalsIgnoreCase ("1234abcd")); diff --git a/libs/juce-current/source/modules/juce_core/text/juce_String.h b/libs/juce-current/source/modules/juce_core/text/juce_String.h index b3eb7afe..9dc3ef96 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_String.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_String.h @@ -1049,7 +1049,8 @@ public: This will look for a value at the end of the string. e.g. for "321 xyz654" it will return 654; for "2 3 4" it'll return 4. - Negative numbers are not handled, so "xyz-5" returns 5. + If the string ends with a hyphen followed by numeric characters, the + return value will be negative. @see getIntValue */ diff --git a/libs/juce-current/source/modules/juce_core/text/juce_StringArray.cpp b/libs/juce-current/source/modules/juce_core/text/juce_StringArray.cpp index 8523dc96..5dc50eb9 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_StringArray.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_core/text/juce_StringArray.h b/libs/juce-current/source/modules/juce_core/text/juce_StringArray.h index 6f3670bd..4421d5cc 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_StringArray.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_StringArray.h @@ -49,7 +49,8 @@ public: /** Creates an array containing a list of strings. */ template - StringArray (StringRef firstValue, OtherElements... otherValues) : strings (firstValue, otherValues...) {} + StringArray (StringRef firstValue, OtherElements&&... otherValues) + : strings (firstValue, std::forward (otherValues)...) {} /** Creates an array containing a list of strings. */ StringArray (const std::initializer_list& strings); @@ -152,6 +153,12 @@ public: */ 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/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.cpp b/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.cpp index a906bf98..b1b12f92 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.h b/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.h index 77d486c9..16b9a99c 100644 --- a/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.h +++ b/libs/juce-current/source/modules/juce_core/text/juce_StringPairArray.h @@ -124,6 +124,10 @@ public: */ 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 @@ public: */ void minimiseStorageOverheads(); + //============================================================================== + /** Adds the contents of a map to this StringPairArray. */ + void addMap (const std::map& mapToAdd); private: //============================================================================== diff --git a/libs/juce-current/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp b/libs/juce-current/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp index e03f6e81..db4e3e8e 100644 --- a/libs/juce-current/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp +++ b/libs/juce-current/source/modules/juce_core/threads/juce_HighResolutionTimer.cpp @@ -23,7 +23,7 @@ namespace juce { -HighResolutionTimer::HighResolutionTimer() { pimpl.reset (new Pimpl (*this)); } +HighResolutionTimer::HighResolutionTimer() : pimpl (new Pimpl (*this)) {} HighResolutionTimer::~HighResolutionTimer() { stopTimer(); } void HighResolutionTimer::startTimer (int periodMs) { pimpl->start (jmax (1, periodMs)); } diff --git a/libs/juce-current/source/modules/juce_core/xml/juce_XmlDocument.cpp b/libs/juce-current/source/modules/juce_core/xml/juce_XmlDocument.cpp index eb472c3e..f3ae5435 100644 --- a/libs/juce-current/source/modules/juce_core/xml/juce_XmlDocument.cpp +++ b/libs/juce-current/source/modules/juce_core/xml/juce_XmlDocument.cpp @@ -291,7 +291,7 @@ void XmlDocument::skipNextWhiteSpace() { for (;;) { - input = input.findEndOfWhitespace(); + input.incrementToEndOfWhitespace(); if (input.isEmpty()) { @@ -684,7 +684,7 @@ void XmlDocument::readEntity (String& result) } else if (*input == '#') { - int charCode = 0; + int64_t charCode = 0; ++input; if (*input == 'x' || *input == 'X') @@ -712,15 +712,26 @@ void XmlDocument::readEntity (String& result) { int numChars = 0; - while (input[0] != ';') + for (;;) { + const auto firstChar = input[0]; + + if (firstChar == 0) + { + setLastError ("unexpected end of input", true); + return; + } + + if (firstChar == ';') + break; + if (++numChars > 12) { setLastError ("illegal escape sequence", true); break; } - charCode = charCode * 10 + ((int) input[0] - '0'); + charCode = charCode * 10 + ((int) firstChar - '0'); ++input; } diff --git a/libs/juce-current/source/modules/juce_core/xml/juce_XmlElement.h b/libs/juce-current/source/modules/juce_core/xml/juce_XmlElement.h index d15c7ac9..d9d1497f 100644 --- a/libs/juce-current/source/modules/juce_core/xml/juce_XmlElement.h +++ b/libs/juce-current/source/modules/juce_core/xml/juce_XmlElement.h @@ -23,61 +23,6 @@ namespace juce { -//============================================================================== -/** A handy macro to make it easy to iterate all the child elements in an XmlElement. - - The parentXmlElement should be a reference to the parent XML, and the childElementVariableName - will be the name of a pointer to each child element. - - E.g. @code - XmlElement* myParentXml = createSomeKindOfXmlDocument(); - - forEachXmlChildElement (*myParentXml, child) - { - if (child->hasTagName ("FOO")) - doSomethingWithXmlElement (child); - } - - @endcode - - @see forEachXmlChildElementWithTagName -*/ -#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ -\ - for (auto* childElementVariableName = (parentXmlElement).getFirstChildElement(); \ - childElementVariableName != nullptr; \ - childElementVariableName = childElementVariableName->getNextElement()) - -/** A macro that makes it easy to iterate all the child elements of an XmlElement - which have a specified tag. - - This does the same job as the forEachXmlChildElement macro, but only for those - elements that have a particular tag name. - - The parentXmlElement should be a reference to the parent XML, and the childElementVariableName - will be the name of a pointer to each child element. The requiredTagName is the - tag name to match. - - E.g. @code - XmlElement* myParentXml = createSomeKindOfXmlDocument(); - - forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") - { - // the child object is now guaranteed to be a element.. - doSomethingWithMYTAGElement (child); - } - - @endcode - - @see forEachXmlChildElement -*/ -#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ -\ - for (auto* childElementVariableName = (parentXmlElement).getChildByName (requiredTagName); \ - childElementVariableName != nullptr; \ - childElementVariableName = childElementVariableName->getNextElementWithTagName (requiredTagName)) - - //============================================================================== /** Used to build a tree of elements representing an XML document. @@ -695,6 +640,102 @@ public: /** Checks if a given string is a valid XML name */ static bool isValidXmlName (StringRef possibleName) noexcept; +private: + //============================================================================== + struct GetNextElement + { + XmlElement* getNext (const XmlElement& e) const { return e.getNextElement(); } + }; + + struct GetNextElementWithTagName + { + GetNextElementWithTagName() = default; + explicit GetNextElementWithTagName (String n) : name (std::move (n)) {} + XmlElement* getNext (const XmlElement& e) const { return e.getNextElementWithTagName (name); } + + String name; + }; + + //============================================================================== + template + class Iterator : private Traits + { + public: + using difference_type = ptrdiff_t; + using value_type = XmlElement*; + using pointer = const value_type*; + using reference = value_type; + using iterator_category = std::input_iterator_tag; + + Iterator() = default; + + template + Iterator (XmlElement* e, Args&&... args) + : Traits (std::forward (args)...), element (e) {} + + Iterator begin() const { return *this; } + Iterator end() const { return Iterator{}; } + + bool operator== (const Iterator& other) const { return element == other.element; } + bool operator!= (const Iterator& other) const { return ! operator== (other); } + + reference operator*() const { return element; } + pointer operator->() const { return &element; } + + Iterator& operator++() + { + element = Traits::getNext (*element); + return *this; + } + + Iterator operator++(int) + { + auto copy = *this; + ++(*this); + return copy; + } + + private: + value_type element = nullptr; + }; + +public: + //============================================================================== + /** Allows iterating the children of an XmlElement using range-for syntax. + + @code + void doSomethingWithXmlChildren (const XmlElement& myParentXml) + { + for (auto* element : myParentXml.getChildIterator()) + doSomethingWithXmlElement (element); + } + @endcode + */ + Iterator getChildIterator() const + { + return Iterator { getFirstChildElement() }; + } + + /** Allows iterating children of an XmlElement with a specific tag using range-for syntax. + + @code + void doSomethingWithXmlChildren (const XmlElement& myParentXml) + { + for (auto* element : myParentXml.getChildWithTagNameIterator ("MYTAG")) + doSomethingWithXmlElement (element); + } + @endcode + */ + Iterator getChildWithTagNameIterator (StringRef name) const + { + return Iterator { getChildByName (name), name }; + } + + /** This allows us to trigger a warning inside deprecated macros. */ + #ifndef DOXYGEN + JUCE_DEPRECATED_WITH_BODY (void macroBasedForLoop() const noexcept, {}) + #endif + //============================================================================== /** This has been deprecated in favour of the toString() method. */ JUCE_DEPRECATED (String createDocument (StringRef dtdToUse, @@ -717,8 +758,8 @@ public: StringRef encodingType = "UTF-8", int lineWrapLength = 60) const); - //============================================================================== private: + //============================================================================== struct XmlAttributeNode { XmlAttributeNode (const XmlAttributeNode&) noexcept; @@ -758,4 +799,56 @@ private: JUCE_LEAK_DETECTOR (XmlElement) }; +//============================================================================== +/** DEPRECATED: A handy macro to make it easy to iterate all the child elements in an XmlElement. + + New code should avoid this macro, and instead use getChildIterator directly. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElement (*myParentXml, child) + { + if (child->hasTagName ("FOO")) + doSomethingWithXmlElement (child); + } + + @endcode + + @see forEachXmlChildElementWithTagName +*/ +#define forEachXmlChildElement(parentXmlElement, childElementVariableName) \ + for (auto* (childElementVariableName) : ((parentXmlElement).macroBasedForLoop(), (parentXmlElement).getChildIterator())) + +/** DEPRECATED: A macro that makes it easy to iterate all the child elements of an XmlElement + which have a specified tag. + + New code should avoid this macro, and instead use getChildWithTagNameIterator directly. + + This does the same job as the forEachXmlChildElement macro, but only for those + elements that have a particular tag name. + + The parentXmlElement should be a reference to the parent XML, and the childElementVariableName + will be the name of a pointer to each child element. The requiredTagName is the + tag name to match. + + E.g. @code + XmlElement* myParentXml = createSomeKindOfXmlDocument(); + + forEachXmlChildElementWithTagName (*myParentXml, child, "MYTAG") + { + // the child object is now guaranteed to be a element.. + doSomethingWithMYTAGElement (child); + } + + @endcode + + @see forEachXmlChildElement +*/ +#define forEachXmlChildElementWithTagName(parentXmlElement, childElementVariableName, requiredTagName) \ + for (auto* (childElementVariableName) : ((parentXmlElement).macroBasedForLoop(), (parentXmlElement).getChildWithTagNameIterator ((requiredTagName)))) + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_cryptography/juce_cryptography.h b/libs/juce-current/source/modules/juce_cryptography/juce_cryptography.h index 94101d17..768b3cc3 100644 --- a/libs/juce-current/source/modules/juce_cryptography/juce_cryptography.h +++ b/libs/juce-current/source/modules/juce_cryptography/juce_cryptography.h @@ -35,7 +35,7 @@ ID: juce_cryptography vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE cryptography classes description: Classes for various basic cryptography functions, including RSA, Blowfish, MD5, SHA, etc. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/libs/juce-current/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp index 7b437cb7..bc1d1a11 100644 --- a/libs/juce-current/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp +++ b/libs/juce-current/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -187,7 +187,7 @@ bool PropertiesFile::loadAsXml() { if (auto doc = parseXMLIfTagMatches (file, PropertyFileConstants::fileTag)) { - forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) + for (auto* e : doc->getChildWithTagNameIterator (PropertyFileConstants::valueTag)) { auto name = e->getStringAttribute (PropertyFileConstants::nameAttribute); diff --git a/libs/juce-current/source/modules/juce_data_structures/juce_data_structures.h b/libs/juce-current/source/modules/juce_data_structures/juce_data_structures.h index d07fea85..996f5e7a 100644 --- a/libs/juce-current/source/modules/juce_data_structures/juce_data_structures.h +++ b/libs/juce-current/source/modules/juce_data_structures/juce_data_structures.h @@ -35,7 +35,7 @@ ID: juce_data_structures vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE data model helper classes description: Classes for undo/redo management, and smart data structures. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_data_structures/values/juce_ValueTree.cpp b/libs/juce-current/source/modules/juce_data_structures/values/juce_ValueTree.cpp index 088f74d0..704ddf81 100644 --- a/libs/juce-current/source/modules/juce_data_structures/values/juce_ValueTree.cpp +++ b/libs/juce-current/source/modules/juce_data_structures/values/juce_ValueTree.cpp @@ -1003,7 +1003,7 @@ ValueTree ValueTree::fromXml (const XmlElement& xml) ValueTree v (xml.getTagName()); v.object->properties.setFromXmlAttributes (xml); - forEachXmlChildElement (xml, e) + for (auto* e : xml.getChildIterator()) v.appendChild (fromXml (*e), nullptr); return v; diff --git a/libs/juce-current/source/modules/juce_dsp/filter_design/juce_FilterDesign.cpp b/libs/juce-current/source/modules/juce_dsp/filter_design/juce_FilterDesign.cpp index f1acc87a..c6a50ab0 100644 --- a/libs/juce-current/source/modules/juce_dsp/filter_design/juce_FilterDesign.cpp +++ b/libs/juce-current/source/modules/juce_dsp/filter_design/juce_FilterDesign.cpp @@ -270,19 +270,19 @@ typename FIR::Coefficients::Ptr for (int i = 0; i < hh.size(); ++i) c[i] = (float) hh[i]; - double NN; - - if (n % 2 == 0) - { - NN = 2.0 * result->getMagnitudeForFrequency (0.5, 1.0); - } - else + auto NN = [&] { + if (n % 2 == 0) + return 2.0 * result->getMagnitudeForFrequency (0.5, 1.0); + auto w01 = std::sqrt (kp * kp + (1 - kp * kp) * std::pow (std::cos (MathConstants::pi / (2.0 * n + 1.0)), 2.0)); - auto om01 = std::acos (-w01); - NN = -2.0 * result->getMagnitudeForFrequency (om01 / MathConstants::twoPi, 1.0); - } + if (std::abs (w01) > 1.0) + return 2.0 * result->getMagnitudeForFrequency (0.5, 1.0); + + auto om01 = std::acos (-w01); + return -2.0 * result->getMagnitudeForFrequency (om01 / MathConstants::twoPi, 1.0); + }(); for (int i = 0; i < hh.size(); ++i) c[i] = static_cast ((A * hn[i] + B * hnm[i]) / NN); diff --git a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.cpp b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.cpp index 42b6b6a4..6fb58f11 100644 --- a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.cpp +++ b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.cpp @@ -86,6 +86,12 @@ public: // This function is only safe to call from a single thread at a time. bool push (IncomingCommand& command) { return queue.push (command); } + void popAll() + { + const ScopedLock lock (popMutex); + queue.popAll ([] (IncomingCommand& command) { command(); command = nullptr; }); + } + using Thread::startThread; using Thread::stopThread; @@ -94,13 +100,23 @@ private: { while (! threadShouldExit()) { - if (queue.hasPendingMessages()) + const auto tryPop = [&] + { + const ScopedLock lock (popMutex); + + if (! queue.hasPendingMessages()) + return false; + queue.pop ([] (IncomingCommand& command) { command(); command = nullptr;}); - else + return true; + }; + + if (! tryPop()) sleep (10); } } + CriticalSection popMutex; Queue queue; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BackgroundMessageQueue) @@ -892,7 +908,6 @@ public: std::unique_ptr getEngine() { return factory.getEngine(); } private: - template void callLater (Fn&& fn) { @@ -1017,9 +1032,13 @@ public: void prepare (const ProcessSpec& spec) { + messageQueue->pimpl->popAll(); mixer.prepare (spec); engineQueue->prepare (spec); - currentEngine = engineQueue->getEngine(); + + if (auto newEngine = engineQueue->getEngine()) + currentEngine = std::move (newEngine); + previousEngine = nullptr; jassert (currentEngine != nullptr); } diff --git a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.h b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.h index 5f2436bb..2750f58c 100644 --- a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.h +++ b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution.h @@ -164,8 +164,16 @@ public: ~Convolution() noexcept; //============================================================================== - /** Must be called before loading any impulse response. This provides the - maximumBufferSize and the sample rate required for any resampling. + /** Must be called before first calling process. + + In general, calls to `loadImpulseResponse` load the impulse response (IR) + asynchronously. The IR will become active once it has been completely loaded + and processed, which may take some time. + + Calling process will ensure that the IR supplied to the most recent call to + `loadImpulseResponse` is fully initialised. This IR will then be active during + the next call to `process`. It is recommended to call `loadImpulseResponse` *before* + `process` if a specific IR must be active during the first process call. */ void prepare (const ProcessSpec&); @@ -253,7 +261,7 @@ public: Note: This is the latency of the convolution engine, not the latency associated with the current impulse response choice that has to be - considered separately (linear phase filters, for eaxmple). + considered separately (linear phase filters, for example). */ int getLatency() const; diff --git a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution_test.cpp b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution_test.cpp index d7ec4b81..af93b737 100644 --- a/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution_test.cpp +++ b/libs/juce-current/source/modules/juce_dsp/frequency/juce_Convolution_test.cpp @@ -89,6 +89,19 @@ class ConvolutionTest : public UnitTest expect (! std::isnan (block.getSample ((int) channel, (int) sample))); } + void checkAllChannelsNonZero (const AudioBlock& block) + { + for (size_t i = 0; i != block.getNumChannels(); ++i) + { + const auto* channel = block.getChannelPointer (i); + + expect (std::any_of (channel, channel + block.getNumSamples(), [] (float sample) + { + return sample != 0.0f; + })); + } + } + template void nonAllocatingExpectWithinAbsoluteError (const T& a, const T& b, const T& error) { @@ -168,16 +181,21 @@ class ConvolutionTest : public UnitTest } }; - const auto time = Time::getMillisecondCounter(); - - // Wait 10 seconds to load the impulse response - while (Time::getMillisecondCounter() - time < 10'000) + // If we load an IR while the convolution is already running, we'll need to wait + // for it to be loaded on a background thread + if (initSequence == InitSequence::prepareThenLoad) { - processBlocksWithDiracImpulse(); + const auto time = Time::getMillisecondCounter(); + + // Wait 10 seconds to load the impulse response + while (Time::getMillisecondCounter() - time < 10'000) + { + processBlocksWithDiracImpulse(); - // Check if the impulse response was loaded - if (block.getSample (0, 1) != 0.0f) - break; + // Check if the impulse response was loaded + if (block.getSample (0, 1) != 0.0f) + break; + } } // At this point, our convolution should be loaded and the current IR size should @@ -326,6 +344,45 @@ public: checkForNans (block); } + beginTest ("Convolutions can cope with a change in samplerate and blocksize"); + { + Convolution convolution; + + auto copy = impulseData; + convolution.loadImpulseResponse (std::move (copy), + 2000, + Convolution::Stereo::yes, + Convolution::Trim::no, + Convolution::Normalise::yes); + + const dsp::ProcessSpec specs[] = { { 96'000.0, 1024, 2 }, + { 48'000.0, 512, 2 }, + { 44'100.0, 256, 2 } }; + + for (const auto& thisSpec : specs) + { + convolution.prepare (thisSpec); + + expectWithinAbsoluteError ((double) convolution.getCurrentIRSize(), + thisSpec.sampleRate * 0.5, + 1.0); + + juce::AudioBuffer thisBuffer ((int) thisSpec.numChannels, + (int) thisSpec.maximumBlockSize); + AudioBlock thisBlock { thisBuffer }; + ProcessContextReplacing thisContext { thisBlock }; + + nTimes (100, [&] + { + addDiracImpulse (thisBlock); + convolution.process (thisContext); + + checkForNans (thisBlock); + checkAllChannelsNonZero (thisBlock); + }); + } + } + beginTest ("Short uniform convolutions work"); { const auto ramp = makeRamp (static_cast (spec.maximumBlockSize) / 2); diff --git a/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.cpp b/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.cpp index 3d0c608a..7c206340 100644 --- a/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.cpp +++ b/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.cpp @@ -950,7 +950,11 @@ FFT::FFT (int order) { } -FFT::~FFT() {} +FFT::FFT (FFT&&) noexcept = default; + +FFT& FFT::operator= (FFT&&) noexcept = default; + +FFT::~FFT() = default; void FFT::perform (const Complex* input, Complex* output, bool inverse) const noexcept { diff --git a/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.h b/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.h index 30ac0f81..f746e6fb 100644 --- a/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.h +++ b/libs/juce-current/source/modules/juce_dsp/frequency/juce_FFT.h @@ -50,6 +50,12 @@ public: */ FFT (int order); + /** Move constructor. */ + FFT (FFT&&) noexcept; + + /** Move assignment operator. */ + FFT& operator= (FFT&&) noexcept; + /** Destructor. */ ~FFT(); diff --git a/libs/juce-current/source/modules/juce_dsp/juce_dsp.cpp b/libs/juce-current/source/modules/juce_dsp/juce_dsp.cpp index 9e297f03..fdf1df31 100644 --- a/libs/juce-current/source/modules/juce_dsp/juce_dsp.cpp +++ b/libs/juce-current/source/modules/juce_dsp/juce_dsp.cpp @@ -54,8 +54,6 @@ #define JUCE_IPP_AVAILABLE 1 #endif -#include "containers/juce_FixedSizeFunction.h" - #include "processors/juce_FIRFilter.cpp" #include "processors/juce_IIRFilter.cpp" #include "processors/juce_FirstOrderTPTFilter.cpp" diff --git a/libs/juce-current/source/modules/juce_dsp/juce_dsp.h b/libs/juce-current/source/modules/juce_dsp/juce_dsp.h index bfcab0cf..15463ccc 100644 --- a/libs/juce-current/source/modules/juce_dsp/juce_dsp.h +++ b/libs/juce-current/source/modules/juce_dsp/juce_dsp.h @@ -35,7 +35,7 @@ ID: juce_dsp vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE DSP classes description: Classes for audio buffer manipulation, digital audio processing, filtering, oversampling, fast math functions etc. website: http://www.juce.com/juce @@ -101,10 +101,7 @@ #endif #endif -#include #include -#include -#include //============================================================================== @@ -246,6 +243,7 @@ namespace juce #include "maths/juce_LookupTable.h" #include "maths/juce_LogRampedValue.h" #include "containers/juce_AudioBlock.h" +#include "containers/juce_FixedSizeFunction.h" #include "processors/juce_ProcessContext.h" #include "processors/juce_ProcessorWrapper.h" #include "processors/juce_ProcessorChain.h" diff --git a/libs/juce-current/source/modules/juce_dsp/maths/juce_Matrix.h b/libs/juce-current/source/modules/juce_dsp/maths/juce_Matrix.h index 8d6f43ab..1c21f6fd 100644 --- a/libs/juce-current/source/modules/juce_dsp/maths/juce_Matrix.h +++ b/libs/juce-current/source/modules/juce_dsp/maths/juce_Matrix.h @@ -36,7 +36,7 @@ namespace dsp @tags{DSP} */ -template +template class Matrix { public: diff --git a/libs/juce-current/source/modules/juce_dsp/processors/juce_Panner.h b/libs/juce-current/source/modules/juce_dsp/processors/juce_Panner.h index ac6da7ab..3a524c2f 100644 --- a/libs/juce-current/source/modules/juce_dsp/processors/juce_Panner.h +++ b/libs/juce-current/source/modules/juce_dsp/processors/juce_Panner.h @@ -32,7 +32,7 @@ enum class PannerRule { linear, // regular 6 dB or linear panning rule, allows the panned sound to be // perceived as having a constant level when summed to mono - balanced, // both left and right are 1 when pan value is 0.5, with left decreasing + balanced, // both left and right are 1 when pan value is 0, with left decreasing // to 0 above this value and right decreasing to 0 below it sin3dB, // alternate version of the regular 3 dB panning rule with a sine curve sin4p5dB, // alternate version of the regular 4.5 dB panning rule with a sine curve diff --git a/libs/juce-current/source/modules/juce_dsp/processors/juce_ProcessorDuplicator.h b/libs/juce-current/source/modules/juce_dsp/processors/juce_ProcessorDuplicator.h index 6fb0f993..d15b151c 100644 --- a/libs/juce-current/source/modules/juce_dsp/processors/juce_ProcessorDuplicator.h +++ b/libs/juce-current/source/modules/juce_dsp/processors/juce_ProcessorDuplicator.h @@ -63,7 +63,7 @@ struct ProcessorDuplicator void reset() noexcept { for (auto* p : processors) p->reset(); } - template + template void process (const ProcessContext& context) noexcept { jassert ((int) context.getInputBlock().getNumChannels() <= processors.size()); diff --git a/libs/juce-current/source/modules/juce_dsp/widgets/juce_Bias.h b/libs/juce-current/source/modules/juce_dsp/widgets/juce_Bias.h index 77dee752..7df938da 100644 --- a/libs/juce-current/source/modules/juce_dsp/widgets/juce_Bias.h +++ b/libs/juce-current/source/modules/juce_dsp/widgets/juce_Bias.h @@ -96,7 +96,7 @@ public: //============================================================================== /** Processes the input and output buffers supplied in the processing context. */ - template + template void process (const ProcessContext& context) noexcept { auto&& inBlock = context.getInputBlock(); diff --git a/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp b/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp index a06167a0..aabd6314 100644 --- a/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.h b/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.h index 464be594..9f06adb2 100644 --- a/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnection.h +++ b/libs/juce-current/source/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 @@ public: */ 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 @@ public: private: //============================================================================== - CriticalSection pipeAndSocketLock; + ReadWriteLock pipeAndSocketLock; std::unique_ptr socket; std::unique_ptr pipe; bool callbackConnectionState = false; @@ -187,8 +200,9 @@ private: 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 @@ private: 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/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp b/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp index 6905529f..cf79303a 100644 --- a/libs/juce-current/source/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp +++ b/libs/juce-current/source/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/libs/juce-current/source/modules/juce_events/juce_events.cpp b/libs/juce-current/source/modules/juce_events/juce_events.cpp index 884db8d7..b2760a94 100644 --- a/libs/juce-current/source/modules/juce_events/juce_events.cpp +++ b/libs/juce-current/source/modules/juce_events/juce_events.cpp @@ -35,7 +35,7 @@ #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 -#if JUCE_USE_WINRT_MIDI || JUCE_USE_WIN_WEBVIEW2 +#if JUCE_USE_WINRT_MIDI #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 #endif @@ -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/libs/juce-current/source/modules/juce_events/juce_events.h b/libs/juce-current/source/modules/juce_events/juce_events.h index 2810b840..a62f530c 100644 --- a/libs/juce-current/source/modules/juce_events/juce_events.h +++ b/libs/juce-current/source/modules/juce_events/juce_events.h @@ -32,7 +32,7 @@ ID: juce_events vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE message and event handling classes description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_events/native/juce_mac_MessageManager.mm b/libs/juce-current/source/modules/juce_events/native/juce_mac_MessageManager.mm index 36352ab5..1cc0b9f8 100644 --- a/libs/juce-current/source/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/libs/juce-current/source/modules/juce_events/native/juce_mac_MessageManager.mm @@ -43,20 +43,24 @@ public: 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 @@ private: 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 @@ private: 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: 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 @@ -263,7 +276,7 @@ private: { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:"); + SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -281,7 +294,7 @@ private: { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:"); + SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -299,7 +312,7 @@ private: { auto* delegate = getPushNotificationsDelegate (self); - SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:"); + SEL selector = @selector (application:didReceiveRemoteNotification:); if (delegate != nil && [delegate respondsToSelector: selector]) { @@ -423,17 +436,16 @@ void initialiseNSApplication() } } -static AppDelegate* appDelegate = nullptr; +static std::unique_ptr appDelegate; void MessageManager::doPlatformSpecificInitialisation() { if (appDelegate == nil) - appDelegate = new AppDelegate(); + appDelegate.reset (new AppDelegate()); } void MessageManager::doPlatformSpecificShutdown() { - delete appDelegate; appDelegate = nullptr; } @@ -487,8 +499,10 @@ struct MountedVolumeListChangeDetector::Pimpl 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() @@ -506,7 +520,11 @@ private: 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/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.cpp b/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.cpp index 5d9bf8f7..b1a4abc3 100644 --- a/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.cpp +++ b/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.cpp @@ -23,6 +23,36 @@ namespace juce { +WinRTWrapper::WinRTWrapper() +{ + winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0"); + + if (winRTHandle == nullptr) + return; + + roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize"); + createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString"); + deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString"); + getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer"); + roActivateInstance = (RoActivateInstanceFuncPtr) ::GetProcAddress (winRTHandle, "RoActivateInstance"); + roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory"); + + if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr + || getHStringRawBuffer == nullptr || roActivateInstance == nullptr || roGetActivationFactory == nullptr) + return; + + HRESULT status = roInitialize (1); + initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L); +} + +WinRTWrapper::~WinRTWrapper() +{ + if (winRTHandle != nullptr) + ::FreeLibrary (winRTHandle); + + clearSingletonInstance(); +} + WinRTWrapper::ScopedHString::ScopedHString (String str) { if (WinRTWrapper::getInstance()->isInitialised()) @@ -37,14 +67,6 @@ WinRTWrapper::ScopedHString::~ScopedHString() WinRTWrapper::getInstance()->deleteHString (hstr); } -WinRTWrapper::~WinRTWrapper() -{ - if (winRTHandle != nullptr) - ::FreeLibrary (winRTHandle); - - clearSingletonInstance(); -} - String WinRTWrapper::hStringToString (HSTRING hstr) { if (isInitialised()) @@ -54,27 +76,6 @@ String WinRTWrapper::hStringToString (HSTRING hstr) return {}; } -WinRTWrapper::WinRTWrapper() -{ - winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0"); - - if (winRTHandle == nullptr) - return; - - roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize"); - createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString"); - deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString"); - getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer"); - roActivateInstance = (RoActivateInstanceFuncPtr) ::GetProcAddress (winRTHandle, "RoActivateInstance"); - roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory"); - - if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr - || getHStringRawBuffer == nullptr || roActivateInstance == nullptr || roGetActivationFactory == nullptr) - return; - - HRESULT status = roInitialize (1); - initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L); -} JUCE_IMPLEMENT_SINGLETON (WinRTWrapper) diff --git a/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.h b/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.h index 3133b391..a5b01d79 100644 --- a/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.h +++ b/libs/juce-current/source/modules/juce_events/native/juce_win32_WinRTWrapper.h @@ -26,78 +26,21 @@ namespace juce class WinRTWrapper : public DeletedAtShutdown { public: - class ScopedHString - { - public: - ScopedHString (String); - - ~ScopedHString(); - - HSTRING get() const noexcept { return hstr; } - - private: - HSTRING hstr = nullptr; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString) - }; - - template - class ComPtr - { - public: - ComPtr() noexcept {} - ComPtr (ComClass* obj) : p (obj) { if (p) p->AddRef(); } - ComPtr (const ComPtr& other) : p (other.p) { if (p) p->AddRef(); } - ~ComPtr() { release(); } - - operator ComClass*() const noexcept { return p; } - ComClass* get() const noexcept { return p; } - ComClass& operator*() const noexcept { return *p; } - ComClass* operator->() const noexcept { return p; } - - ComPtr& operator= (ComClass* const newP) - { - if (newP != nullptr) - newP->AddRef(); - - release(); - p = newP; - return *this; - } - - ComPtr& operator= (const ComPtr& newP) { return operator= (newP.p); } - - ComClass** resetAndGetPointerAddress() - { - release(); - p = nullptr; - return &p; - } - - private: - ComClass* p = nullptr; - - void release() { if (p != nullptr) p->Release(); } - - ComClass** operator&() noexcept; // private to avoid it being used accidentally - }; - - JUCE_DECLARE_SINGLETON (WinRTWrapper, true) - + //============================================================================== ~WinRTWrapper(); + bool isInitialised() const noexcept { return initialised; } - String hStringToString (HSTRING); - - bool isInitialised() const noexcept { return initialised; } + JUCE_DECLARE_SINGLETON (WinRTWrapper, true) + //============================================================================== template - ComPtr activateInstance (const wchar_t* runtimeClassID, REFCLSID classUUID) + ComSmartPtr activateInstance (const wchar_t* runtimeClassID, REFCLSID classUUID) { - ComPtr result; + ComSmartPtr result; if (isInitialised()) { - ComPtr inspectable; + ComSmartPtr inspectable; ScopedHString runtimeClass (runtimeClassID); auto hr = roActivateInstance (runtimeClass.get(), inspectable.resetAndGetPointerAddress()); @@ -109,9 +52,9 @@ public: } template - ComPtr getWRLFactory (const wchar_t* runtimeClassID) + ComSmartPtr getWRLFactory (const wchar_t* runtimeClassID) { - ComPtr comPtr; + ComSmartPtr comPtr; if (isInitialised()) { @@ -124,9 +67,27 @@ public: return comPtr; } + //============================================================================== + class ScopedHString + { + public: + ScopedHString (String); + ~ScopedHString(); + + HSTRING get() const noexcept { return hstr; } + + private: + HSTRING hstr = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString) + }; + + String hStringToString (HSTRING); + private: WinRTWrapper(); + //============================================================================== HMODULE winRTHandle = nullptr; bool initialised = false; @@ -143,6 +104,9 @@ private: WindowsGetStringRawBufferFuncPtr getHStringRawBuffer = nullptr; RoActivateInstanceFuncPtr roActivateInstance = nullptr; RoGetActivationFactoryFuncPtr roGetActivationFactory = nullptr; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTWrapper) }; } // namespace juce diff --git a/libs/juce-current/source/modules/juce_events/timers/juce_Timer.cpp b/libs/juce-current/source/modules/juce_events/timers/juce_Timer.cpp index 85e13986..e1c3459e 100644 --- a/libs/juce-current/source/modules/juce_events/timers/juce_Timer.cpp +++ b/libs/juce-current/source/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(); } diff --git a/libs/juce-current/source/modules/juce_graphics/colour/juce_Colour.cpp b/libs/juce-current/source/modules/juce_graphics/colour/juce_Colour.cpp index 007a2f36..25c748b8 100644 --- a/libs/juce-current/source/modules/juce_graphics/colour/juce_Colour.cpp +++ b/libs/juce-current/source/modules/juce_graphics/colour/juce_Colour.cpp @@ -84,7 +84,7 @@ namespace ColourHelpers if (lightness > 0.0f) hue = getHue (col); - saturation = (float) (hi - lo) / (1.0f - std::abs ((2.0f * lightness) - 1.0f)); + saturation = ((float) (hi - lo) / 255.0f) / (1.0f - std::abs ((2.0f * lightness) - 1.0f)); } } @@ -691,6 +691,8 @@ public: testColour (red.withMultipliedSaturationHSL (0.0f), 128, 128, 128); testColour (red.withMultipliedBrightness (0.5f), 128, 0, 0); testColour (red.withMultipliedLightness (2.0f), 255, 255, 255); + testColour (red.withMultipliedLightness (1.0f), 255, 0, 0); + testColour (red.withLightness (red.getLightness()), 255, 0, 0); } } }; diff --git a/libs/juce-current/source/modules/juce_graphics/colour/juce_PixelFormats.h b/libs/juce-current/source/modules/juce_graphics/colour/juce_PixelFormats.h index 2f7f7bf4..0563e8f6 100644 --- a/libs/juce-current/source/modules/juce_graphics/colour/juce_PixelFormats.h +++ b/libs/juce-current/source/modules/juce_graphics/colour/juce_PixelFormats.h @@ -59,10 +59,9 @@ class JUCE_API PixelARGB { public: /** Creates a pixel without defining its colour. */ - PixelARGB() = default; - ~PixelARGB() = default; + PixelARGB() noexcept = default; - PixelARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept + PixelARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept { components.b = b; components.g = g; @@ -72,7 +71,7 @@ public: //============================================================================== /** Returns a uint32 which represents the pixel in a platform dependent format. */ - forcedinline uint32 getNativeARGB() const noexcept { return internal; } + forcedinline uint32 getNativeARGB() const noexcept { return internal; } /** Returns a uint32 which will be in argb order as if constructed with the following mask operation ((alpha << 24) | (red << 16) | (green << 8) | blue). */ @@ -123,7 +122,7 @@ public: //============================================================================== /** Sets the pixel's colour from individual components. */ - void setARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept + void setARGB (uint8 a, uint8 r, uint8 g, uint8 b) noexcept { components.b = b; components.g = g; @@ -140,10 +139,10 @@ public: template forcedinline void blend (const Pixel& src) noexcept { - uint32 rb = src.getEvenBytes(); - uint32 ag = src.getOddBytes(); + auto rb = src.getEvenBytes(); + auto ag = src.getOddBytes(); - const uint32 alpha = 0x100 - (ag >> 16); + const auto alpha = 0x100 - (ag >> 16); rb += maskPixelComponents (getEvenBytes() * alpha); ag += maskPixelComponents (getOddBytes() * alpha); @@ -156,7 +155,7 @@ public: This takes into account the opacity of the pixel being overlaid, and blends it accordingly. */ - forcedinline void blend (const PixelRGB src) noexcept; + forcedinline void blend (PixelRGB src) noexcept; /** Blends another pixel onto this one, applying an extra multiplier to its opacity. @@ -167,10 +166,10 @@ public: template forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept { - uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); - uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); + auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); + auto ag = maskPixelComponents (extraAlpha * src.getOddBytes()); - const uint32 alpha = 0x100 - (ag >> 16); + const auto alpha = 0x100 - (ag >> 16); rb += maskPixelComponents (getEvenBytes() * alpha); ag += maskPixelComponents (getOddBytes() * alpha); @@ -182,13 +181,13 @@ public: between the two, as specified by the amount. */ template - forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + forcedinline void tween (const Pixel& src, uint32 amount) noexcept { - uint32 dEvenBytes = getEvenBytes(); + auto dEvenBytes = getEvenBytes(); dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); dEvenBytes &= 0x00ff00ff; - uint32 dOddBytes = getOddBytes(); + auto dOddBytes = getOddBytes(); dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); dOddBytes &= 0x00ff00ff; dOddBytes <<= 8; @@ -199,7 +198,7 @@ public: //============================================================================== /** Replaces the colour's alpha value with another one. */ - forcedinline void setAlpha (const uint8 newAlpha) noexcept + forcedinline void setAlpha (uint8 newAlpha) noexcept { components.a = newAlpha; } @@ -215,18 +214,23 @@ public: | (((((uint32) multiplier) * getEvenBytes()) >> 8) & 0x00ff00ff); } - forcedinline void multiplyAlpha (const float multiplier) noexcept + forcedinline void multiplyAlpha (float multiplier) noexcept { multiplyAlpha ((int) (multiplier * 255.0f)); } - inline PixelARGB getUnpremultiplied() const noexcept { PixelARGB p (internal); p.unpremultiply(); return p; } + inline PixelARGB getUnpremultiplied() const noexcept + { + PixelARGB p (internal); + p.unpremultiply(); + return p; + } /** Premultiplies the pixel's RGB values by its alpha. */ forcedinline void premultiply() noexcept { - const uint32 alpha = components.a; + const auto alpha = components.a; if (alpha < 0xff) { @@ -248,7 +252,7 @@ public: /** Unpremultiplies the pixel's RGB values. */ forcedinline void unpremultiply() noexcept { - const uint32 alpha = components.a; + const auto alpha = components.a; if (alpha < 0xff) { @@ -271,7 +275,7 @@ public: { if (components.a < 0xff && components.a > 0) { - const int newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a)); + const auto newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a)); components.r = components.g = components.b = (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8); @@ -301,7 +305,8 @@ public: private: //============================================================================== - PixelARGB (uint32 internalValue) noexcept : internal (internalValue) + PixelARGB (uint32 internalValue) noexcept + : internal (internalValue) { } @@ -349,8 +354,7 @@ class JUCE_API PixelRGB { public: /** Creates a pixel without defining its colour. */ - PixelRGB() = default; - ~PixelRGB() = default; + PixelRGB() noexcept = default; //============================================================================== /** Returns a uint32 which represents the pixel in a platform dependent format which is compatible @@ -407,7 +411,7 @@ public: return value of getOddBytes of the PixelARGB class. @see PixelARGB::getOddBytes */ - forcedinline uint32 getOddBytes() const noexcept { return (uint32)0xff0000 | g; } + forcedinline uint32 getOddBytes() const noexcept { return (uint32) 0xff0000 | g; } //============================================================================== forcedinline uint8 getAlpha() const noexcept { return 0xff; } @@ -431,7 +435,7 @@ public: } /** Sets the pixel's colour from individual components. */ - void setARGB (const uint8, const uint8 red, const uint8 green, const uint8 blue) noexcept + void setARGB (uint8, uint8 red, uint8 green, uint8 blue) noexcept { r = red; g = green; @@ -447,12 +451,12 @@ public: template forcedinline void blend (const Pixel& src) noexcept { - const uint32 alpha = (uint32) (0x100 - src.getAlpha()); + const auto alpha = (uint32) (0x100 - src.getAlpha()); // getEvenBytes returns 0x00rr00bb on non-android - uint32 rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha)); + const auto rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha)); // getOddBytes returns 0x00aa00gg on non-android - uint32 ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8)); + const auto ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8)); g = (uint8) (ag & 0xff); @@ -465,7 +469,7 @@ public: #endif } - forcedinline void blend (const PixelRGB src) noexcept + forcedinline void blend (PixelRGB src) noexcept { set (src); } @@ -478,10 +482,10 @@ public: template forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept { - uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); - uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); + auto ag = maskPixelComponents (extraAlpha * src.getOddBytes()); + auto rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); - const uint32 alpha = 0x100 - (ag >> 16); + const auto alpha = 0x100 - (ag >> 16); ag = clampPixelComponents (ag + (g * alpha >> 8)); rb = clampPixelComponents (rb + maskPixelComponents (getEvenBytes() * alpha)); @@ -501,12 +505,12 @@ public: between the two, as specified by the amount. */ template - forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + forcedinline void tween (const Pixel& src, uint32 amount) noexcept { - uint32 dEvenBytes = getEvenBytes(); + auto dEvenBytes = getEvenBytes(); dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); - uint32 dOddBytes = getOddBytes(); + auto dOddBytes = getOddBytes(); dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); g = (uint8) (dOddBytes & 0xff); // dOddBytes = 0x00aa00gg @@ -522,7 +526,7 @@ public: //============================================================================== /** This method is included for compatibility with the PixelARGB class. */ - forcedinline void setAlpha (const uint8) noexcept {} + forcedinline void setAlpha (uint8) noexcept {} /** Multiplies the colour's alpha value with another one. */ forcedinline void multiplyAlpha (int) noexcept {} @@ -551,7 +555,7 @@ public: private: //============================================================================== - PixelRGB (const uint32 internal) noexcept + PixelRGB (uint32 internal) noexcept { #if JUCE_ANDROID b = (uint8) (internal >> 16); @@ -577,7 +581,7 @@ private: #endif ; -forcedinline void PixelARGB::blend (const PixelRGB src) noexcept +forcedinline void PixelARGB::blend (PixelRGB src) noexcept { set (src); } @@ -596,8 +600,7 @@ class JUCE_API PixelAlpha { public: /** Creates a pixel without defining its colour. */ - PixelAlpha() = default; - ~PixelAlpha() = default; + PixelAlpha() noexcept = default; //============================================================================== /** Returns a uint32 which represents the pixel in a platform dependent format which is compatible @@ -646,7 +649,7 @@ public: } /** Sets the pixel's colour from individual components. */ - forcedinline void setARGB (const uint8 a_, const uint8 /*r*/, const uint8 /*g*/, const uint8 /*b*/) noexcept + forcedinline void setARGB (uint8 a_, uint8, uint8, uint8) noexcept { a = a_; } @@ -660,7 +663,7 @@ public: template forcedinline void blend (const Pixel& src) noexcept { - const int srcA = src.getAlpha(); + const auto srcA = src.getAlpha(); a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA); } @@ -673,7 +676,7 @@ public: forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept { ++extraAlpha; - const int srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8); + const auto srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8); a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha); } @@ -681,14 +684,14 @@ public: between the two, as specified by the amount. */ template - forcedinline void tween (const Pixel& src, const uint32 amount) noexcept + forcedinline void tween (const Pixel& src, uint32 amount) noexcept { a += ((src.getAlpha() - a) * amount) >> 8; } //============================================================================== /** Replaces the colour's alpha value with another one. */ - forcedinline void setAlpha (const uint8 newAlpha) noexcept + forcedinline void setAlpha (uint8 newAlpha) noexcept { a = newAlpha; } @@ -700,7 +703,7 @@ public: a = (uint8) ((a * multiplier) >> 8); } - forcedinline void multiplyAlpha (const float multiplier) noexcept + forcedinline void multiplyAlpha (float multiplier) noexcept { a = (uint8) (a * multiplier); } @@ -719,10 +722,8 @@ public: private: //============================================================================== - PixelAlpha (const uint32 internal) noexcept - { - a = (uint8) (internal >> 24); - } + PixelAlpha (uint32 internal) noexcept + : a ((uint8) (internal >> 24)) { } //============================================================================== uint8 a; diff --git a/libs/juce-current/source/modules/juce_graphics/geometry/juce_Path.cpp b/libs/juce-current/source/modules/juce_graphics/geometry/juce_Path.cpp index eb7d04ef..851e65c5 100644 --- a/libs/juce-current/source/modules/juce_graphics/geometry/juce_Path.cpp +++ b/libs/juce-current/source/modules/juce_graphics/geometry/juce_Path.cpp @@ -37,7 +37,7 @@ namespace PathHelpers static String nextToken (String::CharPointerType& t) { - t = t.findEndOfWhitespace(); + t.incrementToEndOfWhitespace(); auto start = t; size_t numChars = 0; diff --git a/libs/juce-current/source/modules/juce_graphics/image_formats/juce_PNGLoader.cpp b/libs/juce-current/source/modules/juce_graphics/image_formats/juce_PNGLoader.cpp index 79418fb8..210632bf 100644 --- a/libs/juce-current/source/modules/juce_graphics/image_formats/juce_PNGLoader.cpp +++ b/libs/juce-current/source/modules/juce_graphics/image_formats/juce_PNGLoader.cpp @@ -73,6 +73,10 @@ namespace pnglibNamespace #define PNG_ARM_NEON_SUPPORTED #endif + #ifndef Byte + using Byte = uint8_t; + #endif + #define PNG_16BIT_SUPPORTED #define PNG_ALIGNED_MEMORY_SUPPORTED #define PNG_BENIGN_ERRORS_SUPPORTED diff --git a/libs/juce-current/source/modules/juce_graphics/juce_graphics.h b/libs/juce-current/source/modules/juce_graphics/juce_graphics.h index de51543b..b2196bbf 100644 --- a/libs/juce-current/source/modules/juce_graphics/juce_graphics.h +++ b/libs/juce-current/source/modules/juce_graphics/juce_graphics.h @@ -35,7 +35,7 @@ ID: juce_graphics vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE graphics classes description: Classes for 2D vector graphics, image loading/saving, font handling, etc. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_linux_Fonts.cpp b/libs/juce-current/source/modules/juce_graphics/native/juce_linux_Fonts.cpp index a5cfd584..fbb000d2 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_linux_Fonts.cpp +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_linux_Fonts.cpp @@ -49,7 +49,7 @@ StringArray FTTypefaceList::getDefaultFontDirectories() { if (auto fontsInfo = findFontsConfFile()) { - forEachXmlChildElementWithTagName (*fontsInfo, e, "dir") + for (auto* e : fontsInfo->getChildWithTagNameIterator ("dir")) { auto fontPath = e->getAllSubText().trim(); diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/libs/juce-current/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm index f1fbba38..2a85d25f 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -175,9 +175,9 @@ private: static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) { #if JUCE_BIG_ENDIAN - return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; + return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Big) : kCGBitmapByteOrderDefault; #else - return format == Image::ARGB ? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; + return format == Image::ARGB ? ((uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Little) : kCGBitmapByteOrderDefault; #endif } @@ -941,8 +941,8 @@ CGContextRef juce_getImageContext (const Image& image) auto requiredSize = NSMakeSize (image.getWidth() / scaleFactor, image.getHeight() / scaleFactor); [im setSize: requiredSize]; - auto colourSpace = detail::ColorSpacePtr { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; - auto imageRef = detail::ImagePtr { juce_createCoreGraphicsImage (image, colourSpace.get(), true) }; + detail::ColorSpacePtr colourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; + detail::ImagePtr imageRef { juce_createCoreGraphicsImage (image, colourSpace.get(), true) }; NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: imageRef.get()]; [imageRep setSize: requiredSize]; diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_mac_Fonts.mm b/libs/juce-current/source/modules/juce_graphics/native/juce_mac_Fonts.mm index d3037689..88142091 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -207,9 +207,7 @@ namespace CoreTextTypeLayout //============================================================================== static CFAttributedStringRef createCFAttributedString (const AttributedString& text) { - #if JUCE_IOS - auto rgbColourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB); - #endif + const detail::ColorSpacePtr rgbColourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; auto attribString = CFAttributedStringCreateMutable (kCFAllocatorDefault, 0); auto cfText = text.getText().toCFString(); @@ -260,18 +258,11 @@ namespace CoreTextTypeLayout { auto col = attr.colour; - #if JUCE_IOS const CGFloat components[] = { col.getFloatRed(), col.getFloatGreen(), col.getFloatBlue(), col.getFloatAlpha() }; - auto colour = CGColorCreate (rgbColourSpace, components); - #else - auto colour = CGColorCreateGenericRGB (col.getFloatRed(), - col.getFloatGreen(), - col.getFloatBlue(), - col.getFloatAlpha()); - #endif + auto colour = CGColorCreate (rgbColourSpace.get(), components); CFAttributedStringSetAttribute (attribString, range, kCTForegroundColorAttributeName, colour); CGColorRelease (colour); @@ -296,9 +287,6 @@ namespace CoreTextTypeLayout CFAttributedStringSetAttribute (attribString, CFRangeMake (0, CFAttributedStringGetLength (attribString)), kCTParagraphStyleAttributeName, ctParagraphStyleRef); CFRelease (ctParagraphStyleRef); - #if JUCE_IOS - CGColorSpaceRelease (rgbColourSpace); - #endif return attribString; } diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp index d9120430..2e9875e3 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp @@ -440,7 +440,10 @@ bool TextLayout::createNativeLayout (const AttributedString& text) SharedResourcePointer factories; - if (factories->d2dFactory != nullptr && factories->systemFonts != nullptr) + if (factories->d2dFactory != nullptr + && factories->directWriteFactory != nullptr + && factories->systemFonts != nullptr + && factories->directWriteRenderTarget != nullptr) { DirectWriteTypeLayout::createLayout (*this, text, *factories->directWriteFactory, diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp index 4f1abee9..c9a38e26 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp @@ -146,7 +146,6 @@ public: { jassert (fontCollection != nullptr); - BOOL fontFound = false; uint32 fontIndex = 0; auto hr = fontCollection->FindFamilyName (font.getTypefaceName().toWideCharPointer(), &fontIndex, &fontFound); @@ -207,6 +206,7 @@ public: } bool loadedOk() const noexcept { return dwFontFace != nullptr; } + BOOL isFontFound() const noexcept { return fontFound; } float getAscent() const { return ascent; } float getDescent() const { return 1.0f - ascent; } @@ -279,12 +279,13 @@ private: float unitsToHeightScaleFactor = 1.0f, heightToPointsFactor = 1.0f, ascent = 0; int designUnitsPerEm = 0; AffineTransform pathTransform; + BOOL fontFound = false; struct PathGeometrySink : public ComBaseClassHelper { PathGeometrySink() : ComBaseClassHelper (0) {} - void __stdcall AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) noexcept override + void STDMETHODCALLTYPE AddBeziers (const D2D1_BEZIER_SEGMENT* beziers, UINT beziersCount) noexcept override { for (UINT i = 0; i < beziersCount; ++i) path.cubicTo (convertPoint (beziers[i].point1), @@ -292,29 +293,29 @@ private: convertPoint (beziers[i].point3)); } - void __stdcall AddLines (const D2D1_POINT_2F* points, UINT pointsCount) noexcept override + void STDMETHODCALLTYPE AddLines (const D2D1_POINT_2F* points, UINT pointsCount) noexcept override { for (UINT i = 0; i < pointsCount; ++i) path.lineTo (convertPoint (points[i])); } - void __stdcall BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) noexcept override + void STDMETHODCALLTYPE BeginFigure (D2D1_POINT_2F startPoint, D2D1_FIGURE_BEGIN) noexcept override { path.startNewSubPath (convertPoint (startPoint)); } - void __stdcall EndFigure (D2D1_FIGURE_END figureEnd) noexcept override + void STDMETHODCALLTYPE EndFigure (D2D1_FIGURE_END figureEnd) noexcept override { if (figureEnd == D2D1_FIGURE_END_CLOSED) path.closeSubPath(); } - void __stdcall SetFillMode (D2D1_FILL_MODE fillMode) noexcept override + void STDMETHODCALLTYPE SetFillMode (D2D1_FILL_MODE fillMode) noexcept override { path.setUsingNonZeroWinding (fillMode == D2D1_FILL_MODE_WINDING); } - void __stdcall SetSegmentFlags (D2D1_PATH_SEGMENT) noexcept override {} + void STDMETHODCALLTYPE SetSegmentFlags (D2D1_PATH_SEGMENT) noexcept override {} JUCE_COMRESULT Close() noexcept override { return S_OK; } Path path; diff --git a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_Fonts.cpp b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_Fonts.cpp index 3f95a456..0b35b6a6 100644 --- a/libs/juce-current/source/modules/juce_graphics/native/juce_win32_Fonts.cpp +++ b/libs/juce-current/source/modules/juce_graphics/native/juce_win32_Fonts.cpp @@ -612,7 +612,7 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { std::unique_ptr wtf (new WindowsDirectWriteTypeface (font, factories->systemFonts)); - if (wtf->loadedOk()) + if (wtf->loadedOk() && wtf->isFontFound()) return wtf.release(); } #endif diff --git a/libs/juce-current/source/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp b/libs/juce-current/source/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp index cebdfe80..61a10971 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/commands/juce_KeyPressMappingSet.cpp @@ -227,7 +227,7 @@ bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion) clearAllKeyPresses(); } - forEachXmlChildElement (xmlVersion, map) + for (auto* map : xmlVersion.getChildIterator()) { const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32(); diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp index a30981e7..c4404c63 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -452,7 +452,7 @@ struct Component::ComponentHelpers if (auto* p = comp.getParentComponent()) return p->getLocalBounds(); - return Desktop::getInstance().getDisplays().getMainDisplay().userArea; + return Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; } static void releaseAllCachedImageResources (Component& c) @@ -489,7 +489,7 @@ Component::~Component() if (parentComponent != nullptr) parentComponent->removeChildComponent (parentComponent->childComponentList.indexOf (this), true, false); - else if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) + else if (hasKeyboardFocus (true)) giveAwayFocus (currentlyFocusedComponent != this); if (flags.hasHeavyweightPeerFlag) @@ -546,11 +546,12 @@ void Component::setVisible (bool shouldBeVisible) { ComponentHelpers::releaseAllCachedImageResources (*this); - if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) + if (hasKeyboardFocus (true)) { if (parentComponent != nullptr) parentComponent->grabKeyboardFocus(); - else + + if (hasKeyboardFocus (true)) giveAwayFocus (true); } } @@ -1059,7 +1060,7 @@ int Component::getParentHeight() const noexcept Rectangle Component::getParentMonitorArea() const { - return Desktop::getInstance().getDisplays().findDisplayForRect (getScreenBounds()).userArea; + return Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds())->userArea; } int Component::getScreenX() const { return getScreenPosition().x; } @@ -1181,7 +1182,12 @@ void Component::sendMovedResizedMessages (bool wasMoved, bool wasResized) parentComponent->childBoundsChanged (this); if (! checker.shouldBailOut()) - componentListeners.callChecked (checker, [=] (ComponentListener& l) { l.componentMovedOrResized (*this, wasMoved, wasResized); }); + { + componentListeners.callChecked (checker, [this, wasMoved, wasResized] (ComponentListener& l) + { + l.componentMovedOrResized (*this, wasMoved, wasResized); + }); + } } void Component::setSize (int w, int h) { setBounds (getX(), getY(), w, h); } @@ -2589,6 +2595,9 @@ void Component::internalMagnifyGesture (MouseInputSource source, Point re void Component::sendFakeMouseMove() const { + if (flags.ignoresMouseClicksFlag && ! flags.allowChildMouseClicksFlag) + return; + auto mainMouse = Desktop::getInstance().getMainMouseSource(); if (! mainMouse.isDragging()) diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h index 5dbeb7a0..6b2b0072 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_Component.h @@ -1942,7 +1942,7 @@ public: virtual void handleCommandMessage (int commandId); //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Runs a component modally, waiting until the loop terminates. This method first makes the component visible, brings it to the front and diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp b/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp index ce106bba..d4d7266f 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp @@ -35,6 +35,12 @@ struct ModalComponentManager::ModalItem : public ComponentMovementWatcher jassert (comp != nullptr); } + ~ModalItem() override + { + if (autoDelete) + std::unique_ptr componentDeleter (component); + } + void componentMovedOrResized (bool, bool) override {} using ComponentMovementWatcher::componentMovedOrResized; diff --git a/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h b/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h index 996c5754..029ac225 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h +++ b/libs/juce-current/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h @@ -119,7 +119,7 @@ public: */ bool cancelAllModalComponents(); - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Runs the event loop until the currently topmost modal component is dismissed, and returns the exit code for that component. */ diff --git a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.cpp b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.cpp index dbadb8a6..047bf1b1 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.cpp @@ -331,4 +331,9 @@ void Desktop::setGlobalScaleFactor (float newScaleFactor) noexcept } } +bool Desktop::isHeadless() const noexcept +{ + return displays->displays.isEmpty(); +} + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.h b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.h index a63bc72b..895fd546 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.h +++ b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Desktop.h @@ -325,6 +325,10 @@ public: bool isOrientationEnabled (DisplayOrientation orientation) const noexcept; //============================================================================== + /** Returns the Displays object representing the connected displays. + + @see Displays + */ const Displays& getDisplays() const noexcept { return *displays; } //============================================================================== @@ -347,6 +351,10 @@ public: static bool isOSXDarkModeActive(); #endif + //============================================================================== + /** Returns true on a headless system where there are no connected displays. */ + bool isHeadless() const noexcept; + private: //============================================================================== static Desktop* instance; diff --git a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.cpp b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.cpp index 30416b50..995f161b 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.cpp @@ -36,10 +36,10 @@ void Displays::init (Desktop& desktop) findDisplays (desktop.getGlobalScaleFactor()); } -const Displays::Display& Displays::findDisplayForRect (Rectangle rect, bool isPhysical) const noexcept +const Displays::Display* Displays::getDisplayForRect (Rectangle rect, bool isPhysical) const noexcept { int maxArea = -1; - const Display* retVal = nullptr; + const Display* foundDisplay = nullptr; for (auto& display : displays) { @@ -54,17 +54,17 @@ const Displays::Display& Displays::findDisplayForRect (Rectangle rect, bool if (area >= maxArea) { maxArea = area; - retVal = &display; + foundDisplay = &display; } } - return *retVal; + return foundDisplay; } -const Displays::Display& Displays::findDisplayForPoint (Point point, bool isPhysical) const noexcept +const Displays::Display* Displays::getDisplayForPoint (Point point, bool isPhysical) const noexcept { auto minDistance = std::numeric_limits::max(); - const Display* retVal = nullptr; + const Display* foundDisplay = nullptr; for (auto& display : displays) { @@ -74,78 +74,101 @@ const Displays::Display& Displays::findDisplayForPoint (Point point, bool i displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical; if (displayArea.contains (point)) - return display; + return &display; auto distance = displayArea.getCentre().getDistanceFrom (point); + if (distance <= minDistance) { minDistance = distance; - retVal = &display; + foundDisplay = &display; } } - return *retVal; + return foundDisplay; } Rectangle Displays::physicalToLogical (Rectangle rect, const Display* useScaleFactorOfDisplay) const noexcept { - auto& display = useScaleFactorOfDisplay != nullptr ? *useScaleFactorOfDisplay - : findDisplayForRect (rect, true); + return physicalToLogical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt(); +} + +Rectangle Displays::physicalToLogical (Rectangle rect, const Display* useScaleFactorOfDisplay) const noexcept +{ + const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay + : getDisplayForRect (rect.toNearestInt(), true); + + if (display == nullptr) + return rect; auto globalScale = Desktop::getInstance().getGlobalScaleFactor(); - return ((rect.toFloat() - display.topLeftPhysical.toFloat()) / (display.scale / globalScale)).toNearestInt() + (display.totalArea.getTopLeft() * globalScale); + return ((rect - display->topLeftPhysical.toFloat()) / (display->scale / globalScale)) + + (display->totalArea.getTopLeft().toFloat() * globalScale); } Rectangle Displays::logicalToPhysical (Rectangle rect, const Display* useScaleFactorOfDisplay) const noexcept { - auto& display = useScaleFactorOfDisplay != nullptr ? *useScaleFactorOfDisplay - : findDisplayForRect (rect, false); + return logicalToPhysical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt(); +} + +Rectangle Displays::logicalToPhysical (Rectangle rect, const Display* useScaleFactorOfDisplay) const noexcept +{ + const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay + : getDisplayForRect (rect.toNearestInt(), false); + + if (display == nullptr) + return rect; auto globalScale = Desktop::getInstance().getGlobalScaleFactor(); - return ((rect.toFloat() - (display.totalArea.getTopLeft().toFloat() * globalScale)) * (display.scale / globalScale)).toNearestInt() + display.topLeftPhysical; + return ((rect.toFloat() - (display->totalArea.getTopLeft().toFloat() * globalScale)) * (display->scale / globalScale)) + + display->topLeftPhysical.toFloat(); } template Point Displays::physicalToLogical (Point point, const Display* useScaleFactorOfDisplay) const noexcept { - auto& display = useScaleFactorOfDisplay != nullptr ? *useScaleFactorOfDisplay - : findDisplayForPoint (point.roundToInt(), true); + const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay + : getDisplayForPoint (point.roundToInt(), true); + + if (display == nullptr) + return point; auto globalScale = Desktop::getInstance().getGlobalScaleFactor(); - Point logicalTopLeft (static_cast (display.totalArea.getX()), static_cast (display.totalArea.getY())); - Point physicalTopLeft (static_cast (display.topLeftPhysical.getX()), static_cast (display.topLeftPhysical.getY())); + Point logicalTopLeft (static_cast (display->totalArea.getX()), static_cast (display->totalArea.getY())); + Point physicalTopLeft (static_cast (display->topLeftPhysical.getX()), static_cast (display->topLeftPhysical.getY())); - return ((point - physicalTopLeft) / (display.scale / globalScale)) + (logicalTopLeft * globalScale); + return ((point - physicalTopLeft) / (display->scale / globalScale)) + (logicalTopLeft * globalScale); } template Point Displays::logicalToPhysical (Point point, const Display* useScaleFactorOfDisplay) const noexcept { - auto& display = useScaleFactorOfDisplay != nullptr ? *useScaleFactorOfDisplay - : findDisplayForPoint (point.roundToInt(), false); + const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay + : getDisplayForPoint (point.roundToInt(), false); + + if (display == nullptr) + return point; auto globalScale = Desktop::getInstance().getGlobalScaleFactor(); - Point logicalTopLeft (static_cast (display.totalArea.getX()), static_cast (display.totalArea.getY())); - Point physicalTopLeft (static_cast (display.topLeftPhysical.getX()), static_cast (display.topLeftPhysical.getY())); + Point logicalTopLeft (static_cast (display->totalArea.getX()), static_cast (display->totalArea.getY())); + Point physicalTopLeft (static_cast (display->topLeftPhysical.getX()), static_cast (display->topLeftPhysical.getY())); - return ((point - (logicalTopLeft * globalScale)) * (display.scale / globalScale)) + physicalTopLeft; + return ((point - (logicalTopLeft * globalScale)) * (display->scale / globalScale)) + physicalTopLeft; } -const Displays::Display& Displays::getMainDisplay() const noexcept +const Displays::Display* Displays::getPrimaryDisplay() const noexcept { JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED for (auto& d : displays) if (d.isMain) - return d; + return &d; - // no main display! - jassertfalse; - return displays.getReference (0); + return nullptr; } RectangleList Displays::getRectangleList (bool userAreasOnly) const @@ -193,33 +216,6 @@ bool operator== (const Displays::Display& d1, const Displays::Display& d2) noexc bool operator!= (const Displays::Display& d1, const Displays::Display& d2) noexcept; bool operator!= (const Displays::Display& d1, const Displays::Display& d2) noexcept { return ! (d1 == d2); } -// Deprecated method -const Displays::Display& Displays::getDisplayContaining (Point position) const noexcept -{ - JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED - const auto* best = &displays.getReference (0); - auto bestDistance = std::numeric_limits::max(); - - for (auto& d : displays) - { - if (d.totalArea.contains (position)) - { - best = &d; - break; - } - - auto distance = d.totalArea.getCentre().getDistanceFrom (position); - - if (distance < bestDistance) - { - bestDistance = distance; - best = &d; - } - } - - return *best; -} - //============================================================================== // These methods are used for converting the totalArea and userArea Rectangles in Display from physical to logical // pixels. We do this by constructing a graph of connected displays where the root node has position (0, 0); this can be @@ -385,4 +381,56 @@ void Displays::updateToLogical() template Point Displays::logicalToPhysical (Point, const Display*) const noexcept; #endif +//============================================================================== +// Deprecated methods +const Displays::Display& Displays::getDisplayContaining (Point position) const noexcept +{ + JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED + const auto* best = &displays.getReference (0); + auto bestDistance = std::numeric_limits::max(); + + for (auto& d : displays) + { + if (d.totalArea.contains (position)) + { + best = &d; + break; + } + + auto distance = d.totalArea.getCentre().getDistanceFrom (position); + + if (distance < bestDistance) + { + bestDistance = distance; + best = &d; + } + } + + return *best; +} + +const Displays::Display& Displays::findDisplayForRect (Rectangle rect, bool isPhysical) const noexcept +{ + if (auto* display = getDisplayForRect (rect, isPhysical)) + return *display; + + return emptyDisplay; +} + +const Displays::Display& Displays::findDisplayForPoint (Point point, bool isPhysical) const noexcept +{ + if (auto* display = getDisplayForPoint (point, isPhysical)) + return *display; + + return emptyDisplay; +} + +const Displays::Display& Displays::getMainDisplay() const noexcept +{ + if (auto* display = getPrimaryDisplay()) + return *display; + + return emptyDisplay; +} + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.h b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.h index 8e140cf3..cebb42ca 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.h +++ b/libs/juce-current/source/modules/juce_gui_basics/desktop/juce_Displays.h @@ -38,14 +38,16 @@ private: Displays (Desktop&); public: + //============================================================================== /** Represents a connected display device. */ - struct Display + struct JUCE_API Display { /** This will be true if this is the user's main display device. */ bool isMain; /** The total area of this display in logical pixels including any OS-dependent objects - like the taskbar, menu bar, etc. */ + like the taskbar, menu bar, etc. + */ Rectangle totalArea; /** The total area of this display in logical pixels which isn't covered by OS-dependent @@ -53,6 +55,14 @@ public: */ Rectangle userArea; + /** Represents the area of this display in logical pixels that is not functional for + displaying content. + + On mobile devices this may be the area covered by display cutouts and notches, where + you still want to draw a background but should not position important content. + */ + BorderSize safeAreaInsets; + /** The top-left of this display in physical coordinates. */ Point topLeftPhysical; @@ -74,44 +84,75 @@ public: double dpi; }; - /** Converts a Rectangle from physical to logical pixels. + //============================================================================== + /** Converts an integer Rectangle from physical to logical pixels. + + If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion + regardless of the display that the Rectangle to be converted is on. + */ + Rectangle physicalToLogical (Rectangle physicalRect, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + + /** Converts a floating-point Rectangle from physical to logical pixels. If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion regardless of the display that the Rectangle to be converted is on. */ - Rectangle physicalToLogical (Rectangle, const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + Rectangle physicalToLogical (Rectangle physicalRect, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; - /** Converts a Rectangle from logical to physical pixels. + /** Converts an integer Rectangle from logical to physical pixels. If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion regardless of the display that the Rectangle to be converted is on. */ - Rectangle logicalToPhysical (Rectangle, const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + Rectangle logicalToPhysical (Rectangle logicalRect, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; - /** Converts a Point from physical to logical pixels. */ + /** Converts a floating-point Rectangle from logical to physical pixels. + + If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion + regardless of the display that the Rectangle to be converted is on. + */ + Rectangle logicalToPhysical (Rectangle logicalRect, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + + /** Converts a Point from physical to logical pixels. + + If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion + regardless of the display that the Point to be converted is on. + */ template - Point physicalToLogical (Point, const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + Point physicalToLogical (Point physicalPoint, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + + /** Converts a Point from logical to physical pixels. - /** Converts a Point from logical to physical pixels. */ + If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion + regardless of the display that the Point to be converted is on. + */ template - Point logicalToPhysical (Point, const Display* useScaleFactorOfDisplay = nullptr) const noexcept; + Point logicalToPhysical (Point logicalPoint, + const Display* useScaleFactorOfDisplay = nullptr) const noexcept; /** Returns the Display object representing the display containing a given Rectangle (either - in logical or physical pixels). + in logical or physical pixels), or nullptr if there are no connected displays. If the Rectangle lies outside all the displays then the nearest one will be returned. */ - const Display& findDisplayForRect (Rectangle, bool isPhysical = false) const noexcept; + const Display* getDisplayForRect (Rectangle rect, bool isPhysical = false) const noexcept; /** Returns the Display object representing the display containing a given Point (either - in logical or physical pixels). + in logical or physical pixels), or nullptr if there are no connected displays. If the Point lies outside all the displays then the nearest one will be returned. */ - const Display& findDisplayForPoint (Point, bool isPhysical = false) const noexcept; + const Display* getDisplayForPoint (Point point, bool isPhysical = false) const noexcept; - /** Returns the Display object representing the display acting as the user's main screen. */ - const Display& getMainDisplay() const noexcept; + /** Returns the Display object representing the display acting as the user's main screen, or nullptr + if there are no connected displays. + */ + const Display* getPrimaryDisplay() const noexcept; /** Returns a RectangleList made up of all the displays in LOGICAL pixels. */ RectangleList getRectangleList (bool userAreasOnly) const; @@ -127,9 +168,15 @@ public: void refresh(); /** @internal */ ~Displays() = default; - // This method has been deprecated - use the findDisplayForPoint() or findDisplayForRect() methods instead + // This method has been deprecated - use the getDisplayForPoint() or getDisplayForRect() methods instead // as they can deal with converting between logical and physical pixels JUCE_DEPRECATED (const Display& getDisplayContaining (Point position) const noexcept); + + // These methods have been deprecated - use the methods which return a Display* instead as they will return + // nullptr on headless systems with no connected displays + JUCE_DEPRECATED (const Display& findDisplayForRect (Rectangle, bool isPhysical = false) const noexcept); + JUCE_DEPRECATED (const Display& findDisplayForPoint (Point, bool isPhysical = false) const noexcept); + JUCE_DEPRECATED (const Display& getMainDisplay() const noexcept); #endif private: @@ -139,6 +186,8 @@ private: void findDisplays (float masterScale); void updateToLogical(); + + Display emptyDisplay; }; } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/libs/juce-current/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index 867a5c39..caaacbfe 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -46,7 +46,7 @@ public: template bool applyOperationToChildWithID (const String& id, OperationType& op) const { - forEachXmlChildElement (*xml, e) + for (auto* e : xml->getChildIterator()) { XmlPath child (e, this); @@ -415,7 +415,7 @@ public: case 'z': path.closeSubPath(); last = last2 = subpathStart; - d = d.findEndOfWhitespace(); + d.incrementToEndOfWhitespace(); currentCommand = 'M'; break; @@ -462,7 +462,7 @@ private: //============================================================================== void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable, bool shouldParseClip = true) { - forEachXmlChildElement (*xml, e) + for (auto* e : xml->getChildIterator()) { const XmlPath child (xml.getChild (e)); @@ -755,7 +755,7 @@ private: dashLengths.add (value); - t = t.findEndOfWhitespace(); + t.incrementToEndOfWhitespace(); if (*t == ',') ++t; @@ -830,7 +830,7 @@ private: if (fillXml.xml != nullptr) { - forEachXmlChildElementWithTagName (*fillXml, e, "stop") + for (auto* e : fillXml->getChildWithTagNameIterator ("stop")) { auto col = parseColour (fillXml.getChild (e), "stop-color", Colours::black); @@ -1080,7 +1080,7 @@ private: auto dc = new DrawableComposite(); setCommonAttributes (*dc, xml); - forEachXmlChildElement (*xml, e) + for (auto* e : xml->getChildIterator()) { if (e->isTextElement()) { diff --git a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h index 48e8deb8..e1a50ee9 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h +++ b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooser.h @@ -125,6 +125,7 @@ public: ~FileChooser(); //============================================================================== + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Shows a dialog box to choose a file to open. This will display the dialog box modally, using an "open file" mode, so that @@ -180,6 +181,7 @@ public: browseForFileToOpen() for more info about the behaviour of this method. */ bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr); + #endif //============================================================================== /** Runs a dialog box for the given set of option flags. diff --git a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp index 5158a7df..b00ccc92 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp @@ -114,6 +114,8 @@ FileChooserDialogBox::FileChooserDialogBox (const String& name, if (parentComp != nullptr) parentComp->addAndMakeVisible (this); + else + setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); } FileChooserDialogBox::~FileChooserDialogBox() diff --git a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h index 5fbf6c88..160978e4 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h +++ b/libs/juce-current/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.h @@ -99,7 +99,7 @@ public: ~FileChooserDialogBox() override; //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Displays and runs the dialog box modally. This will show the box with the specified size, returning true if the user diff --git a/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.cpp b/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.cpp index a4d79860..45879fbf 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.cpp @@ -228,8 +228,6 @@ namespace juce #endif #if JUCE_MAC || JUCE_IOS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - #if JUCE_IOS #include "native/juce_ios_UIViewComponentPeer.mm" #include "native/juce_ios_Windowing.mm" @@ -246,8 +244,6 @@ namespace juce #include "native/juce_mac_FileChooser.mm" #endif - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #include "native/juce_mac_MouseCursor.mm" #elif JUCE_WINDOWS diff --git a/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.h b/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.h index a4873d04..85fc9c4a 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.h +++ b/libs/juce-current/source/modules/juce_gui_basics/juce_gui_basics.h @@ -35,7 +35,7 @@ ID: juce_gui_basics vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE GUI core classes description: Basic user-interface components and related classes. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp index 84b18388..06603058 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentAnimator.cpp @@ -163,7 +163,7 @@ public: else jassertfalse; // seem to be trying to animate a component that's not visible.. - auto scale = (float) Desktop::getInstance().getDisplays().findDisplayForRect (getScreenBounds()).scale + auto scale = (float) Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds())->scale * Component::getApproximateScaleFactorForComponent (&c); image = c.createComponentSnapshot (c.getLocalBounds(), false, scale); diff --git a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp index b2eb775e..9ea63b21 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ComponentBoundsConstrainer.cpp @@ -116,7 +116,7 @@ void ComponentBoundsConstrainer::setBoundsForComponent (Component* component, if (auto* peer = component->getPeer()) border = peer->getFrameSize(); - auto screenBounds = Desktop::getInstance().getDisplays().findDisplayForPoint (targetBounds.getCentre()).userArea; + auto screenBounds = Desktop::getInstance().getDisplays().getDisplayForPoint (targetBounds.getCentre())->userArea; limits = component->getLocalArea (nullptr, screenBounds) + component->getPosition(); } diff --git a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ScrollBar.cpp b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ScrollBar.cpp index ddf1374d..3d99248e 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ScrollBar.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_ScrollBar.cpp @@ -173,7 +173,7 @@ void ScrollBar::removeListener (Listener* listener) void ScrollBar::handleAsyncUpdate() { auto start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility) - listeners.call ([=] (Listener& l) { l.scrollBarMoved (this, start); }); + listeners.call ([this, start] (Listener& l) { l.scrollBarMoved (this, start); }); } //============================================================================== diff --git a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.cpp b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.cpp index c42fff17..54a143a0 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.cpp @@ -39,17 +39,25 @@ SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft, dismissButton.onClick = [this] { showOrHide (false); }; addAndMakeVisible (dismissButton); - Desktop::getInstance().addGlobalMouseListener (this); + auto& desktop = Desktop::getInstance(); + + desktop.addGlobalMouseListener (this); + desktop.getAnimator().addChangeListener (this); if (contentToDisplay != nullptr) setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded); setOpaque (false); + setVisible (false); + setAlwaysOnTop (true); } SidePanel::~SidePanel() { - Desktop::getInstance().removeGlobalMouseListener (this); + auto& desktop = Desktop::getInstance(); + + desktop.removeGlobalMouseListener (this); + desktop.getAnimator().removeChangeListener (this); if (parent != nullptr) parent->removeComponentListener (this); @@ -98,6 +106,9 @@ void SidePanel::showOrHide (bool show) Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent), 1.0f, 250, true, 1.0, 0.0); + if (isShowing && ! isVisible()) + setVisible (true); + if (onPanelShowHide != nullptr) onPanelShowHide (isShowing); } @@ -242,6 +253,12 @@ void SidePanel::componentMovedOrResized (Component& component, bool wasMoved, bo setBounds (calculateBoundsInParent (component)); } +void SidePanel::changeListenerCallback (ChangeBroadcaster*) +{ + if (isVisible() && ! isShowing && ! Desktop::getInstance().getAnimator().isAnimating (this)) + setVisible (false); +} + Rectangle SidePanel::calculateBoundsInParent (Component& parentComp) const { auto parentBounds = parentComp.getLocalBounds(); diff --git a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.h b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.h index b531be08..ba9703ea 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.h +++ b/libs/juce-current/source/modules/juce_gui_basics/layout/juce_SidePanel.h @@ -39,7 +39,8 @@ namespace juce @tags{GUI} */ class SidePanel : public Component, - private ComponentListener + private ComponentListener, + private ChangeListener { public: //============================================================================== @@ -144,16 +145,6 @@ public: /** Returns the text that is displayed in the title bar at the top of the SidePanel. */ String getTitleText() const noexcept { return titleLabel.getText(); } - //============================================================================== - void moved() override; - void resized() override; - void paint (Graphics& g) override; - - void parentHierarchyChanged() override; - - void mouseDrag (const MouseEvent&) override; - void mouseUp (const MouseEvent&) override; - //============================================================================== /** This abstract base class is implemented by LookAndFeel classes to provide SidePanel drawing functionality. @@ -191,6 +182,20 @@ public: /** You can assign a lambda to this callback object and it will be called when the panel is shown or hidden. */ std::function onPanelShowHide; + //============================================================================== + /** @internal */ + void moved() override; + /** @internal */ + void resized() override; + /** @internal */ + void paint (Graphics& g) override; + /** @internal */ + void parentHierarchyChanged() override; + /** @internal */ + void mouseDrag (const MouseEvent&) override; + /** @internal */ + void mouseUp (const MouseEvent&) override; + private: //============================================================================== Component* parent = nullptr; @@ -218,6 +223,7 @@ private: //============================================================================== void lookAndFeelChanged() override; void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; + void changeListenerCallback (ChangeBroadcaster*) override; Rectangle calculateBoundsInParent (Component&) const; void calculateAndRemoveShadowBounds (Rectangle& bounds); diff --git a/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index 4b53000b..7e7af1da 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -890,6 +890,20 @@ void LookAndFeel_V2::getIdealPopupMenuItemSize (const String& text, const bool i } } +void LookAndFeel_V2::getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const PopupMenu::Options&) +{ + getIdealPopupMenuItemSize (text, + isSeparator, + standardMenuItemHeight, + idealWidth, + idealHeight); +} + void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height) { auto background = findColour (PopupMenu::backgroundColourId); @@ -906,6 +920,14 @@ void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height #endif } +void LookAndFeel_V2::drawPopupMenuBackgroundWithOptions (Graphics& g, + int width, + int height, + const PopupMenu::Options&) +{ + drawPopupMenuBackground (g, width, height); +} + void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int height, bool isScrollUpArrow) { auto background = findColour (PopupMenu::backgroundColourId); @@ -931,6 +953,14 @@ void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int heigh g.fillPath (p); } +void LookAndFeel_V2::drawPopupMenuUpDownArrowWithOptions (Graphics& g, + int width, int height, + bool isScrollUpArrow, + const PopupMenu::Options&) +{ + drawPopupMenuUpDownArrow (g, width, height, isScrollUpArrow); +} + void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, @@ -1024,7 +1054,31 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, } } -void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle& area, const String& sectionName) +void LookAndFeel_V2::drawPopupMenuItemWithOptions (Graphics& g, const Rectangle& area, + bool isHighlighted, + const PopupMenu::Item& item, + const PopupMenu::Options&) +{ + const auto colour = item.colour != Colour() ? &item.colour : nullptr; + const auto hasSubMenu = item.subMenu != nullptr + && (item.itemID == 0 || item.subMenu->getNumItems() > 0); + + drawPopupMenuItem (g, + area, + item.isSeparator, + item.isEnabled, + isHighlighted, + item.isTicked, + hasSubMenu, + item.text, + item.shortcutKeyDescription, + item.image.get(), + colour); +} + +void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, + const Rectangle& area, + const String& sectionName) { g.setFont (getPopupMenuFont().boldened()); g.setColour (findColour (PopupMenu::headerTextColourId)); @@ -1034,6 +1088,13 @@ void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle& area, + const String& sectionName, + const PopupMenu::Options&) +{ + drawPopupMenuSectionHeader (g, area, sectionName); +} + //============================================================================== int LookAndFeel_V2::getMenuWindowFlags() { @@ -1098,6 +1159,20 @@ bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::O int LookAndFeel_V2::getPopupMenuBorderSize() { return 2; } +int LookAndFeel_V2::getPopupMenuBorderSizeWithOptions (const PopupMenu::Options&) +{ + return getPopupMenuBorderSize(); +} + +void LookAndFeel_V2::drawPopupMenuColumnSeparatorWithOptions (Graphics&, + const Rectangle&, + const PopupMenu::Options&) {} + +int LookAndFeel_V2::getPopupMenuColumnSeparatorWidthWithOptions (const PopupMenu::Options&) +{ + return 0; +} + //============================================================================== void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { diff --git a/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h b/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h index a6697d62..8d07e8ce 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h +++ b/libs/juce-current/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.h @@ -155,21 +155,47 @@ public: //============================================================================== void drawPopupMenuBackground (Graphics&, int width, int height) override; + void drawPopupMenuBackgroundWithOptions (Graphics&, + int width, + int height, + const PopupMenu::Options&) override; void drawPopupMenuItem (Graphics&, const Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const String& text, const String& shortcutKeyText, const Drawable* icon, const Colour* textColour) override; + void drawPopupMenuItemWithOptions (Graphics&, const Rectangle& area, + bool isHighlighted, + const PopupMenu::Item& item, + const PopupMenu::Options&) override; + void drawPopupMenuSectionHeader (Graphics&, const Rectangle& area, const String& sectionName) override; + void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle& area, + const String& sectionName, + const PopupMenu::Options&) override; + Font getPopupMenuFont() override; void drawPopupMenuUpDownArrow (Graphics&, int width, int height, bool isScrollUpArrow) override; + void drawPopupMenuUpDownArrowWithOptions (Graphics&, + int width, int height, + bool isScrollUpArrow, + const PopupMenu::Options&) override; + void getIdealPopupMenuItemSize (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight) override; + + void getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const PopupMenu::Options&) override; + int getMenuWindowFlags() override; void preparePopupMenuWindow (Component&) override; @@ -189,6 +215,14 @@ public: int getPopupMenuBorderSize() override; + int getPopupMenuBorderSizeWithOptions (const PopupMenu::Options&) override; + + void drawPopupMenuColumnSeparatorWithOptions (Graphics& g, + const Rectangle& bounds, + const PopupMenu::Options&) override; + + int getPopupMenuColumnSeparatorWidthWithOptions (const PopupMenu::Options&) override; + //============================================================================== void drawComboBox (Graphics&, int width, int height, bool isMouseButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, diff --git a/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index a71c6417..00735c1a 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -43,40 +43,51 @@ struct MenuWindow; static bool canBeTriggered (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; } static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; } -static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour() ? &item.colour : nullptr; } -static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); } //============================================================================== struct HeaderItemComponent : public PopupMenu::CustomComponent { - HeaderItemComponent (const String& name) : PopupMenu::CustomComponent (false) + HeaderItemComponent (const String& name, const Options& opts) + : CustomComponent (false), options (opts) { setName (name); } void paint (Graphics& g) override { - getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); + getLookAndFeel().drawPopupMenuSectionHeaderWithOptions (g, + getLocalBounds(), + getName(), + options); } void getIdealSize (int& idealWidth, int& idealHeight) override { - getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); + getLookAndFeel().getIdealPopupMenuItemSizeWithOptions (getName(), + false, + -1, + idealWidth, + idealHeight, + options); idealHeight += idealHeight / 2; idealWidth += idealWidth / 4; } + const Options& options; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent) }; //============================================================================== struct ItemComponent : public Component { - ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent) - : item (i), customComp (i.customComponent) + ItemComponent (const PopupMenu::Item& i, + const PopupMenu::Options& o, + MenuWindow& parent) + : item (i), options (o), customComp (i.customComponent) { if (item.isSectionHeader) - customComp = *new HeaderItemComponent (item.text); + customComp = *new HeaderItemComponent (item.text, options); if (customComp != nullptr) { @@ -90,7 +101,7 @@ struct ItemComponent : public Component int itemW = 80; int itemH = 16; - getIdealSize (itemW, itemH, standardItemHeight); + getIdealSize (itemW, itemH, options.getStandardItemHeight()); setSize (itemW, jlimit (1, 600, itemH)); addMouseListener (&parent, false); @@ -109,31 +120,29 @@ struct ItemComponent : public Component if (customComp != nullptr) customComp->getIdealSize (idealWidth, idealHeight); else - getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(), - item.isSeparator, - standardItemHeight, - idealWidth, idealHeight); + getLookAndFeel().getIdealPopupMenuItemSizeWithOptions (getTextForMeasurement(), + item.isSeparator, + standardItemHeight, + idealWidth, idealHeight, + options); } void paint (Graphics& g) override { if (customComp == nullptr) - getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(), - item.isSeparator, - item.isEnabled, - isHighlighted, - item.isTicked, - hasSubMenu (item), - item.text, - item.shortcutKeyDescription, - item.image.get(), - getColour (item)); + getLookAndFeel().drawPopupMenuItemWithOptions (g, getLocalBounds(), + isHighlighted, + item, + options); } void resized() override { if (auto* child = getChildComponent (0)) - child->setBounds (getLocalBounds().reduced (getLookAndFeel().getPopupMenuBorderSize(), 0)); + { + const auto border = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options); + child->setBounds (getLocalBounds().reduced (border, 0)); + } } void setHighlighted (bool shouldBeHighlighted) @@ -154,6 +163,7 @@ struct ItemComponent : public Component PopupMenu::Item item; private: + const PopupMenu::Options& options; // NB: we use a copy of the one from the item info in case we're using our own section comp ReferenceCountedObjectPtr customComp; bool isHighlighted = false; @@ -216,11 +226,25 @@ struct MenuWindow : public Component setLookAndFeel (parent != nullptr ? &(parent->getLookAndFeel()) : menu.lookAndFeel.get()); + auto& lf = getLookAndFeel(); parentComponent = lf.getParentComponentForMenuOptions (options); const_cast(options) = options.withParentComponent (parentComponent); + if (parentComponent != nullptr) + { + parentComponent->addChildComponent (this); + } + else + { + addToDesktop (ComponentPeer::windowIsTemporary + | ComponentPeer::windowIgnoresKeyPresses + | lf.getMenuWindowFlags()); + + Desktop::getInstance().addGlobalMouseListener (this); + } + if (parentComponent == nullptr && parentWindow == nullptr && lf.shouldPopupMenuScaleWithTargetComponent (options)) if (auto* targetComponent = options.getTargetComponent()) scaleFactor = Component::getApproximateScaleFactorForComponent (targetComponent); @@ -233,7 +257,7 @@ struct MenuWindow : public Component auto& item = menu.items.getReference (i); if (i + 1 < menu.items.size() || ! item.isSeparator) - items.add (new ItemComponent (item, options.getStandardItemHeight(), *this)); + items.add (new ItemComponent (item, options, *this)); } auto targetArea = options.getTargetScreenArea() / scaleFactor; @@ -253,19 +277,6 @@ struct MenuWindow : public Component resizeToBestWindowPos(); - if (parentComponent != nullptr) - { - parentComponent->addChildComponent (this); - } - else - { - addToDesktop (ComponentPeer::windowIsTemporary - | ComponentPeer::windowIgnoresKeyPresses - | lf.getMenuWindowFlags()); - - Desktop::getInstance().addGlobalMouseListener (this); - } - getActiveWindows().add (this); lf.preparePopupMenuWindow (*this); @@ -286,7 +297,26 @@ struct MenuWindow : public Component if (isOpaque()) g.fillAll (Colours::white); - getLookAndFeel().drawPopupMenuBackground (g, getWidth(), getHeight()); + auto& theme = getLookAndFeel(); + theme.drawPopupMenuBackgroundWithOptions (g, getWidth(), getHeight(), options); + + if (columnWidths.isEmpty()) + return; + + const auto separatorWidth = theme.getPopupMenuColumnSeparatorWidthWithOptions (options); + const auto border = theme.getPopupMenuBorderSizeWithOptions (options); + + auto currentX = 0; + + std::for_each (columnWidths.begin(), std::prev (columnWidths.end()), [&] (int width) + { + const Rectangle separator (currentX + width, + border, + separatorWidth, + getHeight() - border * 2); + theme.drawPopupMenuColumnSeparatorWithOptions (g, separator, options); + currentX += width + separatorWidth; + }); } void paintOverChildren (Graphics& g) override @@ -295,17 +325,27 @@ struct MenuWindow : public Component if (parentComponent != nullptr) lf.drawResizableFrame (g, getWidth(), getHeight(), - BorderSize (getLookAndFeel().getPopupMenuBorderSize())); + BorderSize (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options))); if (canScroll()) { if (isTopScrollZoneActive()) - lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, true); + { + lf.drawPopupMenuUpDownArrowWithOptions (g, + getWidth(), + PopupMenuSettings::scrollZone, + true, + options); + } if (isBottomScrollZoneActive()) { g.setOrigin (0, getHeight() - PopupMenuSettings::scrollZone); - lf.drawPopupMenuUpDownArrow (g, getWidth(), PopupMenuSettings::scrollZone, false); + lf.drawPopupMenuUpDownArrowWithOptions (g, + getWidth(), + PopupMenuSettings::scrollZone, + false, + options); } } } @@ -370,7 +410,7 @@ struct MenuWindow : public Component } else { - hide (nullptr, false); + hide (nullptr, true); } } } @@ -581,26 +621,22 @@ struct MenuWindow : public Component bool doesAnyJuceCompHaveFocus() { - bool anyFocused = Process::isForegroundProcess(); + if (! Process::isForegroundProcess()) + return false; - if (anyFocused && Component::getCurrentlyFocusedComponent() == nullptr) - { - // because no component at all may have focus, our test here will - // only be triggered when something has focus and then loses it. - anyFocused = ! hasAnyJuceCompHadFocus; + if (Component::getCurrentlyFocusedComponent() != nullptr) + return true; - for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + { + if (ComponentPeer::getPeer (i)->isFocused()) { - if (ComponentPeer::getPeer (i)->isFocused()) - { - anyFocused = true; - hasAnyJuceCompHadFocus = true; - break; - } + hasAnyJuceCompHadFocus = true; + return true; } } - return anyFocused; + return ! hasAnyJuceCompHadFocus; } //============================================================================== @@ -609,11 +645,11 @@ struct MenuWindow : public Component if (relativeTo != nullptr) targetPoint = relativeTo->localPointToGlobal (targetPoint); - auto parentArea = Desktop::getInstance().getDisplays().findDisplayForPoint (targetPoint * scaleFactor) + auto parentArea = Desktop::getInstance().getDisplays().getDisplayForPoint (targetPoint * scaleFactor) #if JUCE_MAC || JUCE_ANDROID - .userArea; + ->userArea; #else - .totalArea; // on windows, don't stop the menu overlapping the taskbar + ->totalArea; // on windows, don't stop the menu overlapping the taskbar #endif if (parentComponent == nullptr) @@ -621,7 +657,7 @@ struct MenuWindow : public Component return parentComponent->getLocalArea (nullptr, parentComponent->getScreenBounds() - .reduced (getLookAndFeel().getPopupMenuBorderSize()) + .reduced (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options)) .getIntersection (parentArea)); } @@ -690,7 +726,7 @@ struct MenuWindow : public Component x = tendTowardsRight ? jmin (parentArea.getRight() - widthToUse - 4, target.getRight()) : jmax (parentArea.getX() + 4, target.getX() - widthToUse); - if (getLookAndFeel().getPopupMenuBorderSize() == 0) // workaround for dismissing the window on mouse up when border size is 0 + if (getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) == 0) // workaround for dismissing the window on mouse up when border size is 0 x += tendTowardsRight ? 1 : -1; y = target.getCentreY() > parentArea.getCentreY() ? jmax (parentArea.getY(), target.getBottom() - heightToUse) @@ -708,6 +744,28 @@ struct MenuWindow : public Component } void layoutMenuItems (const int maxMenuW, const int maxMenuH, int& width, int& height) + { + // Ensure we don't try to add an empty column after the final item + if (auto* last = items.getLast()) + last->item.shouldBreakAfter = false; + + const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; }; + const auto numBreaks = static_cast (std::count_if (items.begin(), items.end(), isBreak)); + numColumns = numBreaks + 1; + + if (numBreaks == 0) + insertColumnBreaks (maxMenuW, maxMenuH); + + workOutManualSize (maxMenuW); + auto actualH = jmin (contentHeight, maxMenuH); + + needsToScroll = contentHeight > actualH; + + width = updateYPositions(); + height = actualH + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2; + } + + void insertColumnBreaks (const int maxMenuW, const int maxMenuH) { numColumns = options.getMinimumNumColumns(); contentHeight = 0; @@ -726,24 +784,74 @@ struct MenuWindow : public Component } if (totalW > maxMenuW / 2 - || contentHeight < maxMenuH - || numColumns >= maximumNumColumns) + || contentHeight < maxMenuH + || numColumns >= maximumNumColumns) break; ++numColumns; } - auto actualH = jmin (contentHeight, maxMenuH); + const auto itemsPerColumn = (items.size() + numColumns - 1) / numColumns; - needsToScroll = contentHeight > actualH; + for (auto i = 0;; i += itemsPerColumn) + { + const auto breakIndex = i + itemsPerColumn - 1; - width = updateYPositions(); - height = actualH + getLookAndFeel().getPopupMenuBorderSize() * 2; + if (breakIndex >= items.size()) + break; + + items[breakIndex]->item.shouldBreakAfter = true; + } + + if (! items.isEmpty()) + (*std::prev (items.end()))->item.shouldBreakAfter = false; + } + + int correctColumnWidths (const int maxMenuW) + { + auto totalW = std::accumulate (columnWidths.begin(), columnWidths.end(), 0); + const auto minWidth = jmin (maxMenuW, options.getMinimumWidth()); + + if (totalW < minWidth) + { + totalW = minWidth; + + for (auto& column : columnWidths) + column = totalW / numColumns; + } + + return totalW; + } + + void workOutManualSize (const int maxMenuW) + { + contentHeight = 0; + columnWidths.clear(); + + for (auto it = items.begin(), end = items.end(); it != end;) + { + const auto isBreak = [] (const ItemComponent* item) { return item->item.shouldBreakAfter; }; + const auto nextBreak = std::find_if (it, end, isBreak); + const auto columnEnd = nextBreak == end ? end : std::next (nextBreak); + + const auto getMaxWidth = [] (int acc, const ItemComponent* item) { return jmax (acc, item->getWidth()); }; + const auto colW = std::accumulate (it, columnEnd, options.getStandardItemHeight(), getMaxWidth); + const auto adjustedColW = jmin (maxMenuW / jmax (1, numColumns - 2), + colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2); + + const auto sumHeight = [] (int acc, const ItemComponent* item) { return acc + item->getHeight(); }; + const auto colH = std::accumulate (it, columnEnd, 0, sumHeight); + + contentHeight = jmax (contentHeight, colH); + columnWidths.add (adjustedColW); + it = columnEnd; + } + + correctColumnWidths (maxMenuW); } int workOutBestSize (const int maxMenuW) { - int totalW = 0; contentHeight = 0; int childNum = 0; @@ -760,27 +868,16 @@ struct MenuWindow : public Component colH += items.getUnchecked (childNum + i)->getHeight(); } - colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + getLookAndFeel().getPopupMenuBorderSize() * 2); + colW = jmin (maxMenuW / jmax (1, numColumns - 2), + colW + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) * 2); columnWidths.set (col, colW); - totalW += colW; contentHeight = jmax (contentHeight, colH); childNum += numChildren; } - // width must never be larger than the screen - auto minWidth = jmin (maxMenuW, options.getMinimumWidth()); - - if (totalW < minWidth) - { - totalW = minWidth; - - for (int col = 0; col < numColumns; ++col) - columnWidths.set (0, totalW / numColumns); - } - - return totalW; + return correctColumnWidths (maxMenuW); } void ensureItemIsVisible (const int itemID, int wantedY) @@ -854,11 +951,21 @@ struct MenuWindow : public Component { childYOffset += delta; - if (delta < 0) - childYOffset = jmax (childYOffset, 0); - else if (delta > 0) - childYOffset = jmin (childYOffset, - contentHeight - windowPos.getHeight() + getLookAndFeel().getPopupMenuBorderSize()); + childYOffset = [&] + { + if (delta < 0) + return jmax (childYOffset, 0); + + if (delta > 0) + { + const auto limit = contentHeight + - windowPos.getHeight() + + getLookAndFeel().getPopupMenuBorderSizeWithOptions (options); + return jmin (childYOffset, limit); + } + + return childYOffset; + }(); updateYPositions(); } @@ -873,29 +980,31 @@ struct MenuWindow : public Component int updateYPositions() { - int x = 0; - int childNum = 0; + const auto separatorWidth = getLookAndFeel().getPopupMenuColumnSeparatorWidthWithOptions (options); + const auto initialY = getLookAndFeel().getPopupMenuBorderSizeWithOptions (options) + - (childYOffset + (getY() - windowPos.getY())); - for (int col = 0; col < numColumns; ++col) - { - auto numChildren = jmin (items.size() - childNum, - (items.size() + numColumns - 1) / numColumns); + auto col = 0; + auto x = 0; + auto y = initialY; - auto colW = columnWidths[col]; - auto y = getLookAndFeel().getPopupMenuBorderSize() - (childYOffset + (getY() - windowPos.getY())); + for (const auto& item : items) + { + jassert (col < columnWidths.size()); + const auto columnWidth = columnWidths[col]; + item->setBounds (x, y, columnWidth, item->getHeight()); + y += item->getHeight(); - for (int i = 0; i < numChildren; ++i) + if (item->item.shouldBreakAfter) { - auto* c = items.getUnchecked (childNum + i); - c->setBounds (x, y, colW, c->getHeight()); - y += c->getHeight(); + col += 1; + x += columnWidth + separatorWidth; + y = initialY; } - - x += colW; - childNum += numChildren; } - return x; + return std::accumulate (columnWidths.begin(), columnWidths.end(), 0) + + (separatorWidth * (columnWidths.size() - 1)); } void setCurrentlyHighlightedChild (ItemComponent* child) @@ -1349,9 +1458,9 @@ PopupMenu::Item::Item (const Item& other) isEnabled (other.isEnabled), isTicked (other.isTicked), isSeparator (other.isSeparator), - isSectionHeader (other.isSectionHeader) -{ -} + isSectionHeader (other.isSectionHeader), + shouldBreakAfter (other.shouldBreakAfter) +{} PopupMenu::Item& PopupMenu::Item::operator= (const Item& other) { @@ -1369,6 +1478,7 @@ PopupMenu::Item& PopupMenu::Item::operator= (const Item& other) isTicked = other.isTicked; isSeparator = other.isSeparator; isSectionHeader = other.isSectionHeader; + shouldBreakAfter = other.shouldBreakAfter; return *this; } @@ -1629,6 +1739,12 @@ void PopupMenu::addSectionHeader (String title) addItem (std::move (i)); } +void PopupMenu::addColumnBreak() +{ + if (! items.isEmpty()) + std::prev (items.end())->shouldBreakAfter = true; +} + //============================================================================== PopupMenu::Options::Options() { @@ -1729,12 +1845,7 @@ Component* PopupMenu::createWindow (const Options& options, // This invokes any command manager commands and deletes the menu window when it is dismissed struct PopupMenuCompletionCallback : public ModalComponentManager::Callback { - PopupMenuCompletionCallback() - : prevFocused (Component::getCurrentlyFocusedComponent()), - prevTopLevel (prevFocused != nullptr ? prevFocused->getTopLevelComponent() : nullptr) - { - PopupMenuSettings::menuWasHiddenBecauseOfAppChange = false; - } + PopupMenuCompletionCallback() = default; void modalStateFinished (int result) override { @@ -1749,19 +1860,41 @@ struct PopupMenuCompletionCallback : public ModalComponentManager::Callback // (this would be the place to fade out the component, if that's what's required) component.reset(); - if (! PopupMenuSettings::menuWasHiddenBecauseOfAppChange) + if (PopupMenuSettings::menuWasHiddenBecauseOfAppChange) + return; + + auto* focusComponent = getComponentToPassFocusTo(); + + const auto focusedIsNotMinimised = [focusComponent] { - if (prevTopLevel != nullptr) - prevTopLevel->toFront (true); + if (focusComponent != nullptr) + if (auto* peer = focusComponent->getPeer()) + return ! peer->isMinimised(); - if (prevFocused != nullptr && prevFocused->isShowing()) - prevFocused->grabKeyboardFocus(); + return false; + }(); + + if (focusedIsNotMinimised) + { + if (auto* topLevel = focusComponent->getTopLevelComponent()) + topLevel->toFront (true); + + if (focusComponent->isShowing() && ! focusComponent->hasKeyboardFocus (true)) + focusComponent->grabKeyboardFocus(); } } + Component* getComponentToPassFocusTo() const + { + if (auto* current = Component::getCurrentlyFocusedComponent()) + return current; + + return prevFocused.get(); + } + ApplicationCommandManager* managerOfChosenCommand = nullptr; std::unique_ptr component; - WeakReference prevFocused, prevTopLevel; + WeakReference prevFocused { Component::getCurrentlyFocusedComponent() }; JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback) }; @@ -1777,6 +1910,8 @@ int PopupMenu::showWithOptionalCallback (const Options& options, { callback->component.reset (window); + PopupMenuSettings::menuWasHiddenBecauseOfAppChange = false; + window->setVisible (true); // (must be called before enterModalState on Windows to avoid DropShadower confusion) window->enterModalState (false, userCallbackDeleter.release()); ModalComponentManager::getInstance()->attachCallback (window, callback.release()); @@ -2020,4 +2155,23 @@ PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const return *(currentItem); } +void PopupMenu::LookAndFeelMethods::drawPopupMenuBackground (Graphics&, int, int) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuItem (Graphics&, const Rectangle&, + bool, bool, bool, + bool, bool, + const String&, + const String&, + const Drawable*, + const Colour*) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuSectionHeader (Graphics&, const Rectangle&, + const String&) {} + +void PopupMenu::LookAndFeelMethods::drawPopupMenuUpDownArrow (Graphics&, int, int, bool) {} + +void PopupMenu::LookAndFeelMethods::getIdealPopupMenuItemSize (const String&, bool, int, int&, int&) {} + +int PopupMenu::LookAndFeelMethods::getPopupMenuBorderSize() { return 0; } + } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.h b/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.h index ed12b5ee..495c62c3 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.h +++ b/libs/juce-current/source/modules/juce_gui_basics/menus/juce_PopupMenu.h @@ -179,6 +179,9 @@ public: /** True if this menu item is a section header. */ bool isSectionHeader = false; + /** True if this is the final item in the current column. */ + bool shouldBreakAfter = false; + /** Sets the isTicked flag (and returns a reference to this item to allow chaining). */ Item& setTicked (bool shouldBeTicked = true) & noexcept; /** Sets the isEnabled flag (and returns a reference to this item to allow chaining). */ @@ -410,6 +413,15 @@ public: */ void addSectionHeader (String title); + /** Adds a column break to the menu, to help break it up into sections. + Subsequent items will be placed in a new column, rather than being appended + to the current column. + + If a menu contains explicit column breaks, the menu will never add additional + breaks. + */ + void addColumnBreak(); + /** Returns the number of items that the menu currently contains. (This doesn't count separators). */ @@ -483,7 +495,7 @@ public: }; //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Displays the menu and waits for the user to pick something. This will display the menu modally, and return the ID of the item that the @@ -753,7 +765,13 @@ public: virtual ~LookAndFeelMethods() = default; /** Fills the background of a popup menu component. */ - virtual void drawPopupMenuBackground (Graphics&, int width, int height) = 0; + virtual void drawPopupMenuBackground (Graphics&, int width, int height); + + /** Fills the background of a popup menu component. */ + virtual void drawPopupMenuBackgroundWithOptions (Graphics&, + int width, + int height, + const Options&) = 0; /** Draws one of the items in a popup menu. */ virtual void drawPopupMenuItem (Graphics&, const Rectangle& area, @@ -762,24 +780,47 @@ public: const String& text, const String& shortcutKeyText, const Drawable* icon, - const Colour* textColour) = 0; + const Colour* textColour); + + /** Draws one of the items in a popup menu. */ + virtual void drawPopupMenuItemWithOptions (Graphics&, const Rectangle& area, + bool isHighlighted, + const Item& item, + const Options&) = 0; + + virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle&, + const String&); - virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle& area, - const String& sectionName) = 0; + virtual void drawPopupMenuSectionHeaderWithOptions (Graphics&, const Rectangle& area, + const String& sectionName, + const Options&) = 0; /** Returns the size and style of font to use in popup menus. */ virtual Font getPopupMenuFont() = 0; virtual void drawPopupMenuUpDownArrow (Graphics&, int width, int height, - bool isScrollUpArrow) = 0; + bool isScrollUpArrow); + + virtual void drawPopupMenuUpDownArrowWithOptions (Graphics&, + int width, int height, + bool isScrollUpArrow, + const Options&) = 0; /** Finds the best size for an item in a popup menu. */ virtual void getIdealPopupMenuItemSize (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, - int& idealHeight) = 0; + int& idealHeight); + + /** Finds the best size for an item in a popup menu. */ + virtual void getIdealPopupMenuItemSizeWithOptions (const String& text, + bool isSeparator, + int standardMenuItemHeight, + int& idealWidth, + int& idealHeight, + const Options&) = 0; virtual int getMenuWindowFlags() = 0; @@ -801,15 +842,30 @@ public: bool isMouseOverBar, MenuBarComponent&) = 0; - virtual Component* getParentComponentForMenuOptions (const PopupMenu::Options& options) = 0; + virtual Component* getParentComponentForMenuOptions (const Options& options) = 0; virtual void preparePopupMenuWindow (Component& newWindow) = 0; /** Return true if you want your popup menus to scale with the target component's AffineTransform - or scale factor */ - virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0; + or scale factor + */ + virtual bool shouldPopupMenuScaleWithTargetComponent (const Options& options) = 0; + + virtual int getPopupMenuBorderSize(); + + virtual int getPopupMenuBorderSizeWithOptions (const Options&) = 0; + + /** Implement this to draw some custom decoration between the columns of the popup menu. + + `getPopupMenuColumnSeparatorWidthWithOptions` must return a positive value in order + to display the separator. + */ + virtual void drawPopupMenuColumnSeparatorWithOptions (Graphics& g, + const Rectangle& bounds, + const Options&) = 0; - virtual int getPopupMenuBorderSize() = 0; + /** Return the amount of space that should be left between popup menu columns. */ + virtual int getPopupMenuColumnSeparatorWidthWithOptions (const Options&) = 0; }; private: diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index 0eca4dfb..803740c6 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -67,6 +67,7 @@ public: void runModally() override { + #if JUCE_MODAL_LOOPS_PERMITTED child.start (args, ChildProcess::wantStdOut); while (child.isRunning()) @@ -74,6 +75,9 @@ public: break; finish (false); + #else + jassertfalse; + #endif } void launch() override diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 6e6cfbed..ea597e33 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -31,15 +31,14 @@ static int numAlwaysOnTopPeers = 0; bool juce_areThereAnyAlwaysOnTopWindows() { return numAlwaysOnTopPeers > 0; } //============================================================================== -template class LinuxComponentPeer : public ComponentPeer { public: - LinuxComponentPeer (Component& comp, int windowStyleFlags, WindowHandleType parentToAddTo) + LinuxComponentPeer (Component& comp, int windowStyleFlags, ::Window parentToAddTo) : ComponentPeer (comp, windowStyleFlags), isAlwaysOnTop (comp.isAlwaysOnTop()) { - // it's dangerous to create a window on a thread other than the message thread.. + // it's dangerous to create a window on a thread other than the message thread. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED if (isAlwaysOnTop) @@ -57,7 +56,7 @@ public: ~LinuxComponentPeer() override { - // it's dangerous to delete a window on a thread other than the message thread.. + // it's dangerous to delete a window on a thread other than the message thread. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED repainter = nullptr; @@ -67,10 +66,15 @@ public: --numAlwaysOnTopPeers; } + ::Window getWindowHandle() const noexcept + { + return windowH; + } + //============================================================================== void* getNativeHandle() const override { - return (void*) windowH; + return reinterpret_cast (getWindowHandle()); } //============================================================================== @@ -170,8 +174,10 @@ public: if (fullScreen != shouldBeFullScreen) { + XWindowSystem::getInstance()->setMaximised (windowH, shouldBeFullScreen); + if (shouldBeFullScreen) - r = Desktop::getInstance().getDisplays().getMainDisplay().userArea; + r = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow); if (! r.isEmpty()) setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen); @@ -295,8 +301,8 @@ public: } //============================================================================== - WindowHandleType getParentWindow() { return parentWindow; } - void setParentWindow (WindowHandleType newParent) { parentWindow = newParent; } + ::Window getParentWindow() { return parentWindow; } + void setParentWindow (::Window newParent) { parentWindow = newParent; } //============================================================================== void updateWindowBounds() @@ -431,21 +437,25 @@ private: return; Point translation = (parentWindow != 0 ? getScreenPosition (isPhysical) : Point()); + const auto& desktop = Desktop::getInstance(); - auto newScaleFactor = Desktop::getInstance().getDisplays().findDisplayForRect (newBounds.translated (translation.x, translation.y), isPhysical).scale - / Desktop::getInstance().getGlobalScaleFactor(); - - if (! approximatelyEqual (newScaleFactor, currentScaleFactor)) + if (auto* display = desktop.getDisplays().getDisplayForRect (newBounds.translated (translation.x, translation.y), + isPhysical)) { - currentScaleFactor = newScaleFactor; - scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (currentScaleFactor); }); + auto newScaleFactor = display->scale / desktop.getGlobalScaleFactor(); + + if (! approximatelyEqual (newScaleFactor, currentScaleFactor)) + { + currentScaleFactor = newScaleFactor; + scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (currentScaleFactor); }); + } } } //============================================================================== std::unique_ptr repainter; - WindowHandleType windowH = {}, parentWindow = {}, keyProxy = {}; + ::Window windowH = {}, parentWindow = {}; Rectangle bounds; BorderSize windowBorder; bool fullScreen = false, isAlwaysOnTop = false; @@ -456,17 +466,16 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LinuxComponentPeer) }; -template -bool LinuxComponentPeer::isActiveApplication = false; +bool LinuxComponentPeer::isActiveApplication = false; //============================================================================== ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindowToAttachTo) { - return new LinuxComponentPeer<::Window> (*this, styleFlags, (::Window) nativeWindowToAttachTo); + return new LinuxComponentPeer (*this, styleFlags, (::Window) nativeWindowToAttachTo); } //============================================================================== -JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return LinuxComponentPeer<::Window>::isActiveApplication; } +JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return LinuxComponentPeer::isActiveApplication; } JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {} JUCE_API void JUCE_CALLTYPE Process::hide() {} @@ -475,15 +484,18 @@ JUCE_API void JUCE_CALLTYPE Process::hide() {} void Desktop::setKioskComponent (Component* comp, bool enableOrDisable, bool) { if (enableOrDisable) - comp->setBounds (getDisplays().findDisplayForRect (comp->getScreenBounds()).totalArea); + comp->setBounds (getDisplays().getDisplayForRect (comp->getScreenBounds())->totalArea); } void Displays::findDisplays (float masterScale) { - displays = XWindowSystem::getInstance()->findDisplays (masterScale); + if (XWindowSystem::getInstance()->getDisplay() != nullptr) + { + displays = XWindowSystem::getInstance()->findDisplays (masterScale); - if (! displays.isEmpty()) - updateToLogical(); + if (! displays.isEmpty()) + updateToLogical(); + } } bool Desktop::canUseSemiTransparentWindows() noexcept @@ -563,15 +575,14 @@ void MouseCursor::showInWindow (ComponentPeer* peer) const } //============================================================================== -template -static LinuxComponentPeer* getPeerForDragEvent (Component* sourceComp) +static LinuxComponentPeer* getPeerForDragEvent (Component* sourceComp) { if (sourceComp == nullptr) if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0)) sourceComp = draggingSource->getComponentUnderMouse(); if (sourceComp != nullptr) - if (auto* lp = dynamic_cast*> (sourceComp->getPeer())) + if (auto* lp = dynamic_cast (sourceComp->getPeer())) return lp; jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event! @@ -584,7 +595,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi if (files.isEmpty()) return false; - if (auto* peer = getPeerForDragEvent<::Window> (sourceComp)) + if (auto* peer = getPeerForDragEvent (sourceComp)) return XWindowSystem::getInstance()->externalDragFileInit (peer, files, canMoveFiles, std::move (callback)); // This method must be called in response to a component's mouseDown or mouseDrag event! @@ -598,7 +609,7 @@ bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Co if (text.isEmpty()) return false; - if (auto* peer = getPeerForDragEvent<::Window> (sourceComp)) + if (auto* peer = getPeerForDragEvent (sourceComp)) return XWindowSystem::getInstance()->externalDragTextInit (peer, text, std::move (callback)); // This method must be called in response to a component's mouseDown or mouseDrag event! @@ -680,13 +691,13 @@ Image juce_createIconForFile (const File&) void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy) { - if (auto* linuxPeer = dynamic_cast*> (peer)) + if (auto* linuxPeer = dynamic_cast (peer)) linuxPeer->addOpenGLRepaintListener (dummy); } void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy) { - if (auto* linuxPeer = dynamic_cast*> (peer)) + if (auto* linuxPeer = dynamic_cast (peer)) linuxPeer->removeOpenGLRepaintListener (dummy); } diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 002c86aa..af5d96af 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -61,15 +61,19 @@ public: selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0), selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0), isSave ((flags & FileBrowserComponent::saveMode) != 0), - selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0), - panel (isSave ? [[NSSavePanel alloc] init] : [[NSOpenPanel alloc] init]) + selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0) { setBounds (0, 0, 0, 0); setOpaque (true); - static DelegateClass cls; + static DelegateClass delegateClass; + static SafeSavePanel safeSavePanel; + static SafeOpenPanel safeOpenPanel; - delegate = [cls.createInstance() init]; + panel = isSave ? [safeSavePanel.createInstance() init] + : [safeOpenPanel.createInstance() init]; + + delegate = [delegateClass.createInstance() init]; object_setInstanceVariable (delegate, "cppObject", this); [panel setDelegate: delegate]; @@ -83,7 +87,7 @@ public: if (! isSave) { - NSOpenPanel* openPanel = (NSOpenPanel*) panel; + auto* openPanel = static_cast (panel); [openPanel setCanChooseDirectories: selectsDirectories]; [openPanel setCanChooseFiles: selectsFiles]; @@ -97,10 +101,16 @@ public: if (preview != nullptr) { nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())]; + [panel setAccessoryView: nsViewPreview]; + preview->addToDesktop (0, (void*) nsViewPreview); preview->setVisible (true); - [panel setAccessoryView: nsViewPreview]; + if (! isSave) + { + auto* openPanel = static_cast (panel); + [openPanel setAccessoryViewDisclosed: YES]; + } } if (isSave || selectsDirectories) @@ -125,6 +135,10 @@ public: ~Native() override { exitModalState (0); + + if (preview != nullptr) + preview->removeFromDesktop(); + removeFromDesktop(); if (panel != nil) @@ -161,27 +175,33 @@ public: enterModalState (true); [panel beginWithCompletionHandler:CreateObjCBlock (this, &Native::finished)]; + + if (preview != nullptr) + preview->toFront (true); } } void runModally() override { + #if JUCE_MODAL_LOOPS_PERMITTED + ensurePanelSafe(); + std::unique_ptr tempMenu; if (JUCEApplicationBase::isStandaloneApp()) - tempMenu.reset (new TemporaryMainMenuWithStandardCommands()); + tempMenu = std::make_unique (preview); jassert (panel != nil); auto result = [panel runModal]; finished (result); + #else + jassertfalse; + #endif } bool canModalEventBeSentToComponent (const Component* targetComponent) override { - if (targetComponent == nullptr) - return false; - - return targetComponent->findParentComponentOfClass() != nullptr; + return TemporaryMainMenuWithStandardCommands::checkModalEvent (preview, targetComponent); } private: @@ -218,7 +238,7 @@ private: } else { - auto* openPanel = (NSOpenPanel*) panel; + auto* openPanel = static_cast (panel); auto urls = [openPanel URLs]; for (unsigned int i = 0; i < [urls count]; ++i) @@ -244,26 +264,32 @@ private: void panelSelectionDidChange (id sender) { + jassert (sender == panel); + ignoreUnused (sender); + // NB: would need to extend FilePreviewComponent to handle the full list rather than just the first one if (preview != nullptr) - preview->selectedFileChanged (File (getSelectedPaths (sender)[0])); + preview->selectedFileChanged (File (getSelectedPaths()[0])); } - static StringArray getSelectedPaths (id sender) + StringArray getSelectedPaths() const { + if (panel == nullptr) + return {}; + StringArray paths; - if ([sender isKindOfClass: [NSOpenPanel class]]) + if (isSave) { - NSArray* urls = [(NSOpenPanel*) sender URLs]; + paths.add (nsStringToJuce ([[panel URL] path])); + } + else + { + auto* urls = [static_cast (panel) URLs]; for (NSUInteger i = 0; i < [urls count]; ++i) paths.add (nsStringToJuce ([[urls objectAtIndex: i] path])); } - else if ([sender isKindOfClass: [NSSavePanel class]]) - { - paths.add (nsStringToJuce ([[(NSSavePanel*) sender URL] path])); - } return paths; } @@ -280,10 +306,47 @@ private: StringArray filters; String startingDirectory, filename; + void ensurePanelSafe() + { + // If you hit this, something (probably the plugin host) has modified the panel, + // allowing the application to terminate while the panel's modal loop is running. + // This is a very bad idea! Quitting from within the panel's modal loop may cause + // your plugin/app destructor to run directly from within `runModally`, which will + // dispose all app resources while they're still in use. + // A safer alternative is to invoke the FileChooser with `launchAsync`, rather than + // using the modal launchers. + jassert ([panel preventsApplicationTerminationWhenModal]); + } + + static BOOL preventsApplicationTerminationWhenModal() { return YES; } + + template + struct SafeModalPanel : public ObjCClass + { + explicit SafeModalPanel (const char* name) : ObjCClass (name) + { + this->addMethod (@selector (preventsApplicationTerminationWhenModal), + preventsApplicationTerminationWhenModal, + "c@:"); + + this->registerClass(); + } + }; + + struct SafeSavePanel : SafeModalPanel + { + SafeSavePanel() : SafeModalPanel ("SaveSavePanel_") {} + }; + + struct SafeOpenPanel : SafeModalPanel + { + SafeOpenPanel() : SafeModalPanel ("SaveOpenPanel_") {} + }; + //============================================================================== - struct DelegateClass : ObjCClass + struct DelegateClass : public ObjCClass { - DelegateClass() : ObjCClass ("JUCEFileChooser_") + DelegateClass() : ObjCClass ("JUCEFileChooser_") { addIvar ("cppObject"); @@ -315,7 +378,7 @@ private: }; FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent* preview) + FilePreviewComponent* preview) { return new FileChooser::Native (owner, flags, preview); } diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm index 216bfe95..2389f7fa 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_MainMenu.mm @@ -38,7 +38,10 @@ struct JuceMainMenuBarHolder : private DeletedAtShutdown auto appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + [mainMenuBar setSubmenu: appMenu forItem: item]; [appMenu release]; @@ -282,9 +285,11 @@ public: } else { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") auto item = [[NSMenuItem alloc] initWithTitle: text action: @selector (menuItemInvoked:) keyEquivalent: nsEmptyString()]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE [item setTag: topLevelIndex]; [item setEnabled: i.isEnabled]; @@ -515,7 +520,10 @@ private: { addIvar ("owner"); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@"); addProtocol (@protocol (NSMenuDelegate)); @@ -550,8 +558,8 @@ JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr; class TemporaryMainMenuWithStandardCommands { public: - TemporaryMainMenuWithStandardCommands() - : oldMenu (MenuBarModel::getMacMainMenu()) + explicit TemporaryMainMenuWithStandardCommands (FilePreviewComponent* filePreviewComponent) + : oldMenu (MenuBarModel::getMacMainMenu()), dummyModalComponent (filePreviewComponent) { if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu()) oldAppleMenu = std::make_unique (*appleMenu); @@ -601,8 +609,17 @@ public: MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu.get(), oldRecentItems); } + static bool checkModalEvent (FilePreviewComponent* preview, const Component* targetComponent) + { + if (targetComponent == nullptr) + return false; + + return (targetComponent == preview + || targetComponent->findParentComponentOfClass() != nullptr); + } + private: - MenuBarModel* const oldMenu; + MenuBarModel* const oldMenu = nullptr; std::unique_ptr oldAppleMenu; String oldRecentItems; NSInteger editMenuIndex; @@ -615,8 +632,17 @@ private: // recursive when file dialogs are involved struct SilentDummyModalComp : public Component { - SilentDummyModalComp() {} + explicit SilentDummyModalComp (FilePreviewComponent* p) + : preview (p) {} + void inputAttemptWhenModal() override {} + + bool canModalEventBeSentToComponent (const Component* targetComponent) override + { + return checkModalEvent (preview, targetComponent); + } + + FilePreviewComponent* preview = nullptr; }; SilentDummyModalComp dummyModalComponent; diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 09f60928..bd5e69ab 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -100,7 +100,7 @@ public: notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver: view - selector: @selector (frameChanged:) + selector: frameChangedSelector name: NSViewFrameDidChangeNotification object: view]; @@ -171,12 +171,12 @@ public: #endif [notificationCenter addObserver: view - selector: @selector (frameChanged:) + selector: frameChangedSelector name: NSWindowDidMoveNotification object: window]; [notificationCenter addObserver: view - selector: @selector (frameChanged:) + selector: frameChangedSelector name: NSWindowDidMiniaturizeNotification object: window]; @@ -710,12 +710,24 @@ public: handleMagnifyGesture (MouseInputSource::InputSourceType::mouse, getMousePos (ev, view), getMouseTime (ev), 1.0f / invScale); } - void redirectCopy (NSObject*) { handleKeyPress (KeyPress ('c', ModifierKeys (ModifierKeys::commandModifier), 'c')); } - void redirectPaste (NSObject*) { handleKeyPress (KeyPress ('v', ModifierKeys (ModifierKeys::commandModifier), 'v')); } - void redirectCut (NSObject*) { handleKeyPress (KeyPress ('x', ModifierKeys (ModifierKeys::commandModifier), 'x')); } + void redirectCopy (NSObject*) { handleKeyPress (KeyPress ('c', ModifierKeys (ModifierKeys::commandModifier), 'c')); } + void redirectPaste (NSObject*) { handleKeyPress (KeyPress ('v', ModifierKeys (ModifierKeys::commandModifier), 'v')); } + void redirectCut (NSObject*) { handleKeyPress (KeyPress ('x', ModifierKeys (ModifierKeys::commandModifier), 'x')); } + void redirectSelectAll (NSObject*) { handleKeyPress (KeyPress ('a', ModifierKeys (ModifierKeys::commandModifier), 'a')); } void redirectWillMoveToWindow (NSWindow* newWindow) { + if (auto* currentWindow = [view window]) + { + [notificationCenter removeObserver: view + name: NSWindowDidMoveNotification + object: currentWindow]; + + [notificationCenter removeObserver: view + name: NSWindowWillMiniaturizeNotification + object: currentWindow]; + } + if (isSharedWindow && [view window] == window && newWindow == nullptr) { if (auto* comp = safeComponent.get()) @@ -851,6 +863,14 @@ public: if ([screen respondsToSelector: @selector (backingScaleFactor)]) displayScale = (float) screen.backingScaleFactor; + auto invalidateTransparentWindowShadow = [this] + { + // transparent NSWindows with a drop-shadow need to redraw their shadow when the content + // changes to avoid stale shadows being drawn behind the window + if (! isSharedWindow && ! [window isOpaque] && [window hasShadow]) + [window invalidateShadow]; + }; + #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS // This option invokes a separate paint call for each rectangle of the clip region. // It's a long story, but this is a basically a workaround for a CGContext not having @@ -872,12 +892,14 @@ public: CGContextRestoreGState (cg); } + invalidateTransparentWindowShadow(); return; } } #endif drawRect (cg, r, displayScale); + invalidateTransparentWindowShadow(); } void drawRect (CGContextRef cg, NSRect r, float displayScale) @@ -921,9 +943,8 @@ public: invokePaint (*context); } - CGColorSpaceRef colourSpace = CGColorSpaceCreateWithName (kCGColorSpaceSRGB); - CGImageRef image = juce_createCoreGraphicsImage (temp, colourSpace, false); - CGColorSpaceRelease (colourSpace); + detail::ColorSpacePtr colourSpace { CGColorSpaceCreateWithName (kCGColorSpaceSRGB) }; + CGImageRef image = juce_createCoreGraphicsImage (temp, colourSpace.get(), false); CGContextDrawImage (cg, CGRectMake (r.origin.x, r.origin.y, clipW, clipH), image); CGImageRelease (image); } @@ -1047,6 +1068,11 @@ public: grabFocus(); } + void resignKeyWindow() + { + viewFocusLoss(); + } + bool windowShouldClose() { if (! isValidPeer (this)) @@ -1074,6 +1100,25 @@ public: if (shouldSetVisible) getComponent().setVisible (true); } + + if (auto* currentWindow = [view window]) + { + [notificationCenter addObserver: view + selector: dismissModalsSelector + name: NSWindowDidMoveNotification + object: currentWindow]; + + [notificationCenter addObserver: view + selector: dismissModalsSelector + name: NSWindowWillMiniaturizeNotification + object: currentWindow]; + } + } + + void dismissModals() + { + if (hasNativeTitleBar() || isSharedWindow) + sendModalInputAttemptIfBlocked(); } void liveResizingStart() @@ -1392,7 +1437,7 @@ public: void grabFocus() override { - if (window != nil) + if (window != nil && [window canBecomeKeyWindow]) { [window makeKeyWindow]; [window makeFirstResponder: view]; @@ -1439,6 +1484,11 @@ public: static Array keysCurrentlyDown; static int insideToFrontCall; + static const SEL dismissModalsSelector; + static const SEL frameChangedSelector; + static const SEL asyncMouseDownSelector; + static const SEL asyncMouseUpSelector; + private: static NSView* createViewInstance(); static NSWindow* createWindowInstance(); @@ -1606,6 +1656,13 @@ private: int NSViewComponentPeer::insideToFrontCall = 0; +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") +const SEL NSViewComponentPeer::dismissModalsSelector = @selector (dismissModals); +const SEL NSViewComponentPeer::frameChangedSelector = @selector (frameChanged:); +const SEL NSViewComponentPeer::asyncMouseDownSelector = @selector (asyncMouseDown:); +const SEL NSViewComponentPeer::asyncMouseUpSelector = @selector (asyncMouseUp:); +JUCE_END_IGNORE_WARNINGS_GCC_LIKE + //============================================================================== struct JuceNSViewClass : public ObjCClass { @@ -1616,9 +1673,7 @@ struct JuceNSViewClass : public ObjCClass addMethod (@selector (isOpaque), isOpaque, "c@:"); addMethod (@selector (drawRect:), drawRect, "v@:", @encode (NSRect)); addMethod (@selector (mouseDown:), mouseDown, "v@:@"); - addMethod (@selector (asyncMouseDown:), asyncMouseDown, "v@:@"); addMethod (@selector (mouseUp:), mouseUp, "v@:@"); - addMethod (@selector (asyncMouseUp:), asyncMouseUp, "v@:@"); addMethod (@selector (mouseDragged:), mouseDragged, "v@:@"); addMethod (@selector (mouseMoved:), mouseMoved, "v@:@"); addMethod (@selector (mouseEntered:), mouseEntered, "v@:@"); @@ -1632,12 +1687,13 @@ struct JuceNSViewClass : public ObjCClass addMethod (@selector (scrollWheel:), scrollWheel, "v@:@"); addMethod (@selector (magnifyWithEvent:), magnify, "v@:@"); addMethod (@selector (acceptsFirstMouse:), acceptsFirstMouse, "c@:@"); - addMethod (@selector (frameChanged:), frameChanged, "v@:@"); addMethod (@selector (windowWillMiniaturize:), windowWillMiniaturize, "v@:@"); addMethod (@selector (windowDidDeminiaturize:), windowDidDeminiaturize, "v@:@"); - addMethod (@selector (wantsDefaultClipping:), wantsDefaultClipping, "c@:"); + addMethod (@selector (wantsDefaultClipping), wantsDefaultClipping, "c@:"); addMethod (@selector (worksWhenModal), worksWhenModal, "c@:"); + addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow, "v@:@"); addMethod (@selector (viewDidMoveToWindow), viewDidMoveToWindow, "v@:"); + addMethod (@selector (viewWillDraw), viewWillDraw, "v@:"); addMethod (@selector (keyDown:), keyDown, "v@:@"); addMethod (@selector (keyUp:), keyUp, "v@:@"); addMethod (@selector (insertText:), insertText, "v@:@"); @@ -1669,8 +1725,12 @@ struct JuceNSViewClass : public ObjCClass addMethod (@selector (paste:), paste, "v@:@"); addMethod (@selector (copy:), copy, "v@:@"); addMethod (@selector (cut:), cut, "v@:@"); + addMethod (@selector (selectAll:), selectAll, "v@:@"); - addMethod (@selector (viewWillMoveToWindow:), willMoveToWindow, "v@:@"); + addMethod (NSViewComponentPeer::dismissModalsSelector, dismissModals, "v@:"); + addMethod (NSViewComponentPeer::asyncMouseDownSelector, asyncMouseDown, "v@:@"); + addMethod (NSViewComponentPeer::asyncMouseUpSelector, asyncMouseUp, "v@:@"); + addMethod (NSViewComponentPeer::frameChangedSelector, frameChanged, "v@:@"); addProtocol (@protocol (NSTextInput)); @@ -1686,27 +1746,35 @@ private: static void mouseDown (id self, SEL s, NSEvent* ev) { if (JUCEApplicationBase::isStandaloneApp()) + { asyncMouseDown (self, s, ev); + } else + { // In some host situations, the host will stop modal loops from working // correctly if they're called from a mouse event, so we'll trigger // the event asynchronously.. - [self performSelectorOnMainThread: @selector (asyncMouseDown:) + [self performSelectorOnMainThread: NSViewComponentPeer::asyncMouseDownSelector withObject: ev waitUntilDone: NO]; + } } static void mouseUp (id self, SEL s, NSEvent* ev) { if (JUCEApplicationBase::isStandaloneApp()) + { asyncMouseUp (self, s, ev); + } else + { // In some host situations, the host will stop modal loops from working // correctly if they're called from a mouse event, so we'll trigger // the event asynchronously.. - [self performSelectorOnMainThread: @selector (asyncMouseUp:) + [self performSelectorOnMainThread: NSViewComponentPeer::asyncMouseUpSelector withObject: ev waitUntilDone: NO]; + } } static void asyncMouseDown (id self, SEL, NSEvent* ev) { if (auto* p = getOwner (self)) p->redirectMouseDown (ev); } @@ -1720,6 +1788,7 @@ private: static void copy (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectCopy (s); } static void paste (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectPaste (s); } static void cut (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectCut (s); } + static void selectAll (id self, SEL, NSObject* s) { if (auto* p = getOwner (self)) p->redirectSelectAll (s); } static void willMoveToWindow (id self, SEL, NSWindow* w) { if (auto* p = getOwner (self)) p->redirectWillMoveToWindow (w); } static BOOL acceptsFirstMouse (id, SEL, NSEvent*) { return YES; } @@ -1729,6 +1798,22 @@ private: static void drawRect (id self, SEL, NSRect r) { if (auto* p = getOwner (self)) p->drawRect (r); } static void frameChanged (id self, SEL, NSNotification*) { if (auto* p = getOwner (self)) p->redirectMovedOrResized(); } static void viewDidMoveToWindow (id self, SEL) { if (auto* p = getOwner (self)) p->viewMovedToWindow(); } + static void dismissModals (id self, SEL) { if (auto* p = getOwner (self)) p->dismissModals(); } + + static void viewWillDraw (id self, SEL) + { + // Without setting contentsFormat macOS Big Sur will always set the invalid area + // to be the entire frame. + #if defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 + if (NSFoundationVersionNumber > (double) NSFoundationVersionNumber10_11_Max) + { + CALayer* layer = ((NSView*) self).layer; + layer.contentsFormat = kCAContentsFormatRGBA8Uint; + } + #endif + + sendSuperclassMessage (self, @selector (viewWillDraw)); + } static void windowWillMiniaturize (id self, SEL, NSNotification*) { @@ -1988,13 +2073,13 @@ struct JuceNSWindowClass : public ObjCClass addMethod (@selector (canBecomeKeyWindow), canBecomeKeyWindow, "c@:"); addMethod (@selector (canBecomeMainWindow), canBecomeMainWindow, "c@:"); addMethod (@selector (becomeKeyWindow), becomeKeyWindow, "v@:"); + addMethod (@selector (resignKeyWindow), resignKeyWindow, "v@:"); addMethod (@selector (windowShouldClose:), windowShouldClose, "c@:@"); addMethod (@selector (constrainFrameRect:toScreen:), constrainFrameRect, @encode (NSRect), "@:", @encode (NSRect), "@"); addMethod (@selector (windowWillResize:toSize:), windowWillResize, @encode (NSSize), "@:@", @encode (NSSize)); addMethod (@selector (windowDidExitFullScreen:), windowDidExitFullScreen, "v@:@"); addMethod (@selector (windowWillEnterFullScreen:), windowWillEnterFullScreen, "v@:@"); addMethod (@selector (zoom:), zoom, "v@:@"); - addMethod (@selector (windowWillMove:), windowWillMove, "v@:@"); addMethod (@selector (windowWillStartLiveResize:), windowWillStartLiveResize, "v@:@"); addMethod (@selector (windowDidEndLiveResize:), windowDidEndLiveResize, "v@:@"); addMethod (@selector (window:shouldPopUpDocumentPathMenu:), shouldPopUpPathMenu, "B@:@", @encode (NSMenu*)); @@ -2050,6 +2135,14 @@ private: } } + static void resignKeyWindow (id self, SEL) + { + sendSuperclassMessage (self, @selector (resignKeyWindow)); + + if (auto* owner = getOwner (self)) + owner->resignKeyWindow(); + } + static BOOL windowShouldClose (id self, SEL, id /*window*/) { auto* owner = getOwner (self); @@ -2082,8 +2175,7 @@ private: frameRect = owner->constrainRect (frameRect); - if (owner->hasNativeTitleBar()) - owner->sendModalInputAttemptIfBlocked(); + owner->dismissModals(); return frameRect.size; } @@ -2117,13 +2209,6 @@ private: } } - static void windowWillMove (id self, SEL, NSNotification*) - { - if (auto* owner = getOwner (self)) - if (owner->hasNativeTitleBar()) - owner->sendModalInputAttemptIfBlocked(); - } - static void windowWillStartLiveResize (id self, SEL, NSNotification*) { if (auto* owner = getOwner (self)) @@ -2230,7 +2315,7 @@ void Desktop::setKioskComponent (Component* kioskComp, bool shouldBeEnabled, boo [NSApp setPresentationOptions: (allowMenusAndBars ? (NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar) : (NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar))]; - kioskComp->setBounds (getDisplays().findDisplayForRect (kioskComp->getScreenBounds()).totalArea); + kioskComp->setBounds (getDisplays().getDisplayForRect (kioskComp->getScreenBounds())->totalArea); peer->becomeKeyWindow(); } else diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm index bbe270cd..63e15d07 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_mac_Windowing.mm @@ -441,27 +441,31 @@ struct DisplaySettingsChangeCallback : private DeletedAtShutdown { DisplaySettingsChangeCallback() { - CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallBack, nullptr); + CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallback, nullptr); } ~DisplaySettingsChangeCallback() { - CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallBack, nullptr); + CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallback, nullptr); clearSingletonInstance(); } - static void displayReconfigurationCallBack (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*) + static void displayReconfigurationCallback (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void*) { - const_cast (Desktop::getInstance().getDisplays()).refresh(); + if (forceDisplayUpdate != nullptr) + forceDisplayUpdate(); } - JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback) + static std::function forceDisplayUpdate; + JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (DisplaySettingsChangeCallback) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback) }; JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback) +std::function DisplaySettingsChangeCallback::forceDisplayUpdate = nullptr; + static Rectangle convertDisplayRect (NSRect r, CGFloat mainScreenBottom) { r.origin.y = mainScreenBottom - (r.origin.y + r.size.height); @@ -494,7 +498,10 @@ void Displays::findDisplays (const float masterScale) { JUCE_AUTORELEASEPOOL { - DisplaySettingsChangeCallback::getInstance(); + auto& settingsChangeCallback = *DisplaySettingsChangeCallback::getInstance(); + + if (settingsChangeCallback.forceDisplayUpdate == nullptr) + settingsChangeCallback.forceDisplayUpdate = [this] { refresh(); }; CGFloat mainScreenBottom = 0; diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index fb9cfc99..a5b42b8e 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -46,7 +46,7 @@ public: const File& startingFile, const String& titleToUse, const String& filtersToUse) : Thread ("Native Win32 FileChooser"), - owner (parent), title (titleToUse), filtersString (filtersToUse), + owner (parent), title (titleToUse), filtersString (filtersToUse.replaceCharacter (',', ';')), selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0), isSave ((flags & FileBrowserComponent::saveMode) != 0), warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0), @@ -167,6 +167,11 @@ private: Atomic nativeDialogRef; Atomic shouldCancel; + struct FreeLPWSTR + { + void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } + }; + #if JUCE_MSVC bool showDialog (IFileDialog& dialog, bool async) const { @@ -194,7 +199,17 @@ private: PIDLIST_ABSOLUTE pidl = {}; if (FAILED (SHParseDisplayName (initialPath.toWideCharPointer(), nullptr, &pidl, SFGAO_FOLDER, nullptr))) - return false; + { + LPWSTR ptr = nullptr; + auto result = SHGetKnownFolderPath (FOLDERID_Desktop, 0, nullptr, &ptr); + std::unique_ptr desktopPath (ptr); + + if (FAILED (result)) + return false; + + if (FAILED (SHParseDisplayName (desktopPath.get(), nullptr, &pidl, SFGAO_FOLDER, nullptr))) + return false; + } const auto item = [&] { @@ -229,14 +244,13 @@ private: { const auto getUrl = [] (IShellItem& item) { - struct Free - { - void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } - }; - LPWSTR ptr = nullptr; - item.GetDisplayName (SIGDN_URL, &ptr); - return std::unique_ptr { ptr }; + + if (item.GetDisplayName (SIGDN_FILESYSPATH, &ptr) != S_OK) + return URL(); + + const auto path = std::unique_ptr { ptr }; + return URL (File (String (path.get()))); }; if (isSave) @@ -263,7 +277,12 @@ private: if (item == nullptr) return {}; - return { URL (String (getUrl (*item).get())) }; + const auto url = getUrl (*item); + + if (url.isEmpty()) + return {}; + + return { url }; } const auto dialog = [&] @@ -299,7 +318,12 @@ private: items->GetItemAt (i, scope.resetAndGetPointerAddress()); if (scope != nullptr) - result.add (String (getUrl (*scope).get())); + { + const auto url = getUrl (*scope); + + if (! url.isEmpty()) + result.add (url); + } } return result; @@ -415,8 +439,11 @@ private: const Remover remover (*this); #if JUCE_MSVC - if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) + if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista + && customComponent == nullptr) + { return openDialogVistaAndUp (async); + } #endif return openDialogPreVista (async); @@ -424,18 +451,33 @@ private: void run() override { + // We use a functor rather than a lambda here because + // we want to move ownership of the Ptr into the function + // object, and C++11 doesn't support general lambda capture + struct AsyncCallback + { + AsyncCallback (Ptr p, Array r) + : ptr (std::move (p)), + results (std::move (r)) {} + + void operator()() + { + ptr->results = std::move (results); + + if (ptr->owner != nullptr) + ptr->owner->exitModalState (ptr->results.size() > 0 ? 1 : 0); + } + + Ptr ptr; + Array results; + }; + // as long as the thread is running, don't delete this class Ptr safeThis (this); threadHasReference.signal(); auto r = openDialog (true); - MessageManager::callAsync ([safeThis, r] - { - safeThis->results = r; - - if (safeThis->owner != nullptr) - safeThis->owner->exitModalState (r.size() > 0 ? 1 : 0); - }); + MessageManager::callAsync (AsyncCallback (std::move (safeThis), std::move (r))); } static HashMap& getNativeDialogList() @@ -533,7 +575,7 @@ private: auto screenRectangle = Rectangle::leftTopRightBottom (dialogScreenRect.left, dialogScreenRect.top, dialogScreenRect.right, dialogScreenRect.bottom); - auto scale = Desktop::getInstance().getDisplays().findDisplayForRect (screenRectangle, true).scale; + auto scale = Desktop::getInstance().getDisplays().getDisplayForRect (screenRectangle, true)->scale; auto physicalComponentWidth = roundToInt (safeCustomComponent->getWidth() * scale); SetWindowPos (hdlg, nullptr, screenRectangle.getX(), screenRectangle.getY(), @@ -686,7 +728,7 @@ public: nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, fileChooser.title, fileChooser.filters)) { - auto mainMon = Desktop::getInstance().getDisplays().getMainDisplay().userArea; + auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; setBounds (mainMon.getX() + mainMon.getWidth() / 4, mainMon.getY() + mainMon.getHeight() / 4, @@ -720,12 +762,16 @@ public: void runModally() override { + #if JUCE_MODAL_LOOPS_PERMITTED enterModalState (true); nativeFileChooser->open (false); exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0); nativeFileChooser->cancel(); owner.finished (nativeFileChooser->results); + #else + jassertfalse; + #endif } bool canModalEventBeSentToComponent (const Component* targetComponent) override diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp old mode 100644 new mode 100755 index a3fd406b..43eeb665 --- a/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -362,7 +362,9 @@ static void setDPIAwareness() if (! JUCEApplicationBase::isStandaloneApp()) return; - HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll"); + const auto shcore = "SHCore.dll"; + LoadLibraryA (shcore); + const auto shcoreModule = GetModuleHandleA (shcore); if (shcoreModule != nullptr) { @@ -490,16 +492,17 @@ using SettingChangeCallbackFunc = void (*)(void); extern SettingChangeCallbackFunc settingChangeCallback; //============================================================================== -static Rectangle rectangleFromRECT (const RECT& r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; } -static RECT RECTFromRectangle (const Rectangle& r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } +static Rectangle rectangleFromRECT (RECT r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; } +static RECT RECTFromRectangle (Rectangle r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } -static Point pointFromPOINT (const POINT& p) noexcept { return { p.x, p.y }; } -static POINT POINTFromPoint (const Point& p) noexcept { return { p.x, p.y }; } +static Point pointFromPOINT (POINT p) noexcept { return { p.x, p.y }; } +static POINT POINTFromPoint (Point p) noexcept { return { p.x, p.y }; } //============================================================================== static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd); -static Rectangle convertPhysicalScreenRectangleToLogical (const Rectangle& r, HWND h) noexcept +template +static Rectangle convertPhysicalScreenRectangleToLogical (Rectangle r, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().physicalToLogical (r, getCurrentDisplayFromScaleFactor (h)); @@ -507,7 +510,8 @@ static Rectangle convertPhysicalScreenRectangleToLogical (const Rectangle convertLogicalScreenRectangleToPhysical (const Rectangle& r, HWND h) noexcept +template +static Rectangle convertLogicalScreenRectangleToPhysical (Rectangle r, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().logicalToPhysical (r, getCurrentDisplayFromScaleFactor (h)); @@ -515,7 +519,7 @@ static Rectangle convertLogicalScreenRectangleToPhysical (const Rectangle convertPhysicalScreenPointToLogical (const Point& p, HWND h) noexcept +static Point convertPhysicalScreenPointToLogical (Point p, HWND h) noexcept { if (isPerMonitorDPIAwareWindow (h)) return Desktop::getInstance().getDisplays().physicalToLogical (p, getCurrentDisplayFromScaleFactor (h)); @@ -545,44 +549,46 @@ JUCE_API double getScaleFactorForWindow (HWND h) return 1.0; } -#if JUCE_WIN_PER_MONITOR_DPI_AWARE - JUCE_API void setThreadDPIAwarenessForWindow (HWND nativeWindow) - { - // NB. Using local functions here because we need to call this method from the plug-in wrappers - // which don't load the DPI-awareness functions on startup - static SetThreadDPIAwarenessContextFunc localSetThreadDPIAwarenessContext = nullptr; - static GetWindowDPIAwarenessContextFunc localGetWindowDPIAwarenessContext = nullptr; - static GetThreadDPIAwarenessContextFunc localGetThreadDPIAwarenessContext = nullptr; - static GetAwarenessFromDpiAwarenessContextFunc localGetAwarenessFromDPIAwarenessContext = nullptr; - - static bool hasChecked = false; - static bool loadedOK = false; - - if (! hasChecked) - { - hasChecked = true; - - localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); - localGetWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); - localGetThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); - localGetAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); - - loadedOK = (localSetThreadDPIAwarenessContext != nullptr && localGetWindowDPIAwarenessContext != nullptr - && localGetThreadDPIAwarenessContext != nullptr && localGetAwarenessFromDPIAwarenessContext != nullptr); - } - - if (loadedOK) - { - auto dpiAwareWindow = localGetAwarenessFromDPIAwarenessContext (localGetWindowDPIAwarenessContext (nativeWindow)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; - auto dpiAwareThread = localGetAwarenessFromDPIAwarenessContext (localGetThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; - - if (dpiAwareWindow && ! dpiAwareThread) - localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); - else if (! dpiAwareWindow && dpiAwareThread) - localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); - } - } -#endif +JUCE_API void setThreadDPIAwarenessForWindow (HWND nativeWindow) +{ + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + // NB. Using local functions here because we need to call this method from the plug-in wrappers + // which don't load the DPI-awareness functions on startup + static SetThreadDPIAwarenessContextFunc localSetThreadDPIAwarenessContext = nullptr; + static GetWindowDPIAwarenessContextFunc localGetWindowDPIAwarenessContext = nullptr; + static GetThreadDPIAwarenessContextFunc localGetThreadDPIAwarenessContext = nullptr; + static GetAwarenessFromDpiAwarenessContextFunc localGetAwarenessFromDPIAwarenessContext = nullptr; + + static bool hasChecked = false; + static bool loadedOK = false; + + if (! hasChecked) + { + hasChecked = true; + + localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); + localGetWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); + localGetThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); + localGetAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); + + loadedOK = (localSetThreadDPIAwarenessContext != nullptr && localGetWindowDPIAwarenessContext != nullptr + && localGetThreadDPIAwarenessContext != nullptr && localGetAwarenessFromDPIAwarenessContext != nullptr); + } + + if (loadedOK) + { + auto dpiAwareWindow = localGetAwarenessFromDPIAwarenessContext (localGetWindowDPIAwarenessContext (nativeWindow)) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; + auto dpiAwareThread = localGetAwarenessFromDPIAwarenessContext (localGetThreadDPIAwarenessContext()) == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware; + + if (dpiAwareWindow && ! dpiAwareThread) + localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + else if (! dpiAwareWindow && dpiAwareThread) + localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); + } + #else + ignoreUnused (nativeWindow); + #endif +} //============================================================================== static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags, bool adjustTopLeft = false) @@ -599,15 +605,25 @@ static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags, bool adj SetWindowPos (hwnd, nullptr, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags); } -static RECT getWindowRect (HWND hwnd) +static RECT getWindowScreenRect (HWND hwnd) { #if JUCE_WIN_PER_MONITOR_DPI_AWARE setThreadDPIAwarenessForWindow (hwnd); #endif - RECT r; - GetWindowRect (hwnd, &r); - return r; + RECT rect; + GetWindowRect (hwnd, &rect); + return rect; +} + +static RECT getWindowClientRect (HWND hwnd) +{ + auto rect = getWindowScreenRect (hwnd); + + if (auto parentH = GetParent (hwnd)) + MapWindowPoints (HWND_DESKTOP, parentH, (LPPOINT) &rect, 2); + + return rect; } static void setWindowZOrder (HWND hwnd, HWND insertAfter) @@ -825,7 +841,7 @@ public: if (transparent) { - auto windowBounds = getWindowRect (hwnd); + auto windowBounds = getWindowScreenRect (hwnd); POINT p = { -x, -y }; POINT pos = { windowBounds.left, windowBounds.top }; @@ -876,7 +892,7 @@ Image createSnapshotOfNativeWindow (void* nativeWindowHandle) { auto hwnd = (HWND) nativeWindowHandle; - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); const int w = r.getWidth(); const int h = r.getHeight(); @@ -1048,11 +1064,11 @@ namespace IconConverters } //============================================================================== -JUCE_COMCLASS (ITipInvocation, "37c994e7-432b-4834-a2f7-dce1f13b834b") : public IUnknown +JUCE_IUNKNOWNCLASS (ITipInvocation, "37c994e7-432b-4834-a2f7-dce1f13b834b") { static CLSID getCLSID() noexcept { return { 0x4ce576fa, 0x83dc, 0x4f88, { 0x95, 0x1c, 0x9d, 0x07, 0x82, 0xb4, 0xe3, 0x76 } }; } - virtual HRESULT STDMETHODCALLTYPE Toggle (HWND) = 0; + JUCE_COMCALL Toggle (HWND) = 0; }; struct OnScreenKeyboard : public DeletedAtShutdown, @@ -1148,14 +1164,14 @@ typedef HSTRING_PRIVATE* HSTRING; struct IInspectable : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetIids (ULONG* ,IID**) = 0; - virtual HRESULT STDMETHODCALLTYPE GetRuntimeClassName (HSTRING*) = 0; - virtual HRESULT STDMETHODCALLTYPE GetTrustLevel (void*) = 0; + JUCE_COMCALL GetIids (ULONG* ,IID**) = 0; + JUCE_COMCALL GetRuntimeClassName (HSTRING*) = 0; + JUCE_COMCALL GetTrustLevel (void*) = 0; }; JUCE_COMCLASS (IUIViewSettingsInterop, "3694dbf9-8f68-44be-8ff5-195c98ede8a6") : public IInspectable { - virtual HRESULT STDMETHODCALLTYPE GetForWindow (HWND, REFIID, void**) = 0; + JUCE_COMCALL GetForWindow (HWND, REFIID, void**) = 0; }; JUCE_COMCLASS (IUIViewSettings, "c63657f6-8850-470d-88f8-455e16ea2c26") : public IInspectable @@ -1166,7 +1182,7 @@ JUCE_COMCLASS (IUIViewSettings, "c63657f6-8850-470d-88f8-455e16ea2c26") : publi Touch = 1 }; - virtual HRESULT STDMETHODCALLTYPE GetUserInteractionMode (UserInteractionMode*) = 0; + JUCE_COMCALL GetUserInteractionMode (UserInteractionMode*) = 0; }; @@ -1384,7 +1400,7 @@ public: { if (auto parentHwnd = GetParent (hwnd)) { - auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (parentHwnd)), hwnd); + auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (parentHwnd)), hwnd); newBounds.translate (parentRect.getX(), parentRect.getY()); } } @@ -1410,28 +1426,27 @@ public: Rectangle getBounds() const override { - auto bounds = getWindowRect (hwnd); - - if (auto parentH = GetParent (hwnd)) + auto bounds = [this] { - auto r = getWindowRect (parentH); - auto localBounds = Rectangle::leftTopRightBottom (bounds.left, bounds.top, - bounds.right, bounds.bottom).translated (-r.left, -r.top); + if (parentToAddTo == nullptr) + return convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); + + auto localBounds = rectangleFromRECT (getWindowClientRect (hwnd)); #if JUCE_WIN_PER_MONITOR_DPI_AWARE if (isPerMonitorDPIAwareWindow (hwnd)) - localBounds = (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt(); + return (localBounds.toDouble() / getPlatformScaleFactor()).toNearestInt(); #endif - return windowBorder.subtractedFrom (localBounds); - } + return localBounds; + }(); - return windowBorder.subtractedFrom (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (bounds), hwnd)); + return windowBorder.subtractedFrom (bounds); } Point getScreenPosition() const { - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); return { r.getX() + windowBorder.getLeft(), r.getY() + windowBorder.getTop() }; @@ -1534,7 +1549,7 @@ public: bool contains (Point localPos, bool trueIfInAChildWindow) const override { - auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd); + auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowScreenRect (hwnd)), hwnd); if (! r.withZeroOrigin().contains (localPos)) return false; @@ -1654,7 +1669,7 @@ public: // if the calling thread is DPI-aware but we are invalidating a non-DPI aware window RECT, we actually have to // divide the bounds by the scale factor as it will get multiplied for the virtualised paint callback... if (isPerMonitorDPIAwareThread() && ! isPerMonitorDPIAwareWindow (hwnd)) - scale = 1.0 / Desktop::getInstance().getDisplays().getMainDisplay().scale; + scale = 1.0 / Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; #endif auto scaled = area.toDouble() * scale; @@ -1795,31 +1810,11 @@ public: return peer.getComponent().getLocalPoint (nullptr, screenPos); } - template - void parseFileList (const CharType* names, const SIZE_T totalLen) - { - for (unsigned int i = 0;;) - { - unsigned int len = 0; - - while (i + len < totalLen && names[i + len] != 0) - ++len; - - if (len == 0) - break; - - dragInfo.files.add (String (names + i, len)); - i += len + 1; - } - } - struct DroppedData { DroppedData (IDataObject* dataObject, CLIPFORMAT type) { FORMATETC format = { type, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - STGMEDIUM resetMedium = { TYMED_HGLOBAL, { nullptr }, nullptr }; - medium = resetMedium; if (SUCCEEDED (error = dataObject->GetData (&format, &medium))) { @@ -1835,11 +1830,33 @@ public: } HRESULT error; - STGMEDIUM medium; + STGMEDIUM medium { TYMED_HGLOBAL, { nullptr }, nullptr }; void* data = {}; SIZE_T dataSize; }; + void parseFileList (HDROP dropFiles) + { + dragInfo.files.clearQuick(); + + std::vector nameBuffer; + + const auto numFiles = DragQueryFile (dropFiles, ~(UINT) 0, nullptr, 0); + + for (UINT i = 0; i < numFiles; ++i) + { + const auto bufferSize = DragQueryFile (dropFiles, i, nullptr, 0); + nameBuffer.clear(); + nameBuffer.resize (bufferSize + 1, 0); // + 1 for the null terminator + + const auto readCharacters = DragQueryFile (dropFiles, i, nameBuffer.data(), (UINT) nameBuffer.size()); + ignoreUnused (readCharacters); + jassert (readCharacters == bufferSize); + + dragInfo.files.add (String (nameBuffer.data())); + } + } + HRESULT updateFileList (IDataObject* const dataObject) { if (peerIsDeleted) @@ -1852,14 +1869,7 @@ public: if (SUCCEEDED (fileData.error)) { - auto dropFiles = static_cast (fileData.data); - const void* const names = addBytesToPointer (dropFiles, sizeof (DROPFILES)); - - if (dropFiles->fWide) - parseFileList (static_cast (names), fileData.dataSize); - else - parseFileList (static_cast (names), fileData.dataSize); - + parseFileList (static_cast (fileData.data)); return S_OK; } } @@ -2175,9 +2185,9 @@ private: auto bounds = component.getBounds(); if (bounds.isEmpty()) - scaleFactor = Desktop::getInstance().getDisplays().getMainDisplay().scale; + scaleFactor = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; else - scaleFactor = Desktop::getInstance().getDisplays().findDisplayForRect (bounds).scale; + scaleFactor = Desktop::getInstance().getDisplays().getDisplayForRect (bounds)->scale; scaleFactor /= Desktop::getInstance().getGlobalScaleFactor(); } @@ -2190,7 +2200,7 @@ private: // This is needed so that our plugin window gets notified of WM_SETTINGCHANGE messages // and can respond to display scale changes if (! JUCEApplication::isStandaloneApp()) - settingChangeCallback = forceDisplayUpdate; + settingChangeCallback = ComponentPeer::forceDisplayUpdate; // Calling this function here is (for some reason) necessary to make Windows // correctly enable the menu items that we specify in the wm_initmenu message. @@ -2308,17 +2318,9 @@ private: { auto& info = *(ChildWindowClippingInfo*) context; - auto parent = GetParent (hwnd); - - if (parent == info.peer->hwnd) + if (GetParent (hwnd) == info.peer->hwnd) { - auto r = getWindowRect (hwnd); - POINT pos = { r.left, r.top }; - ScreenToClient (GetParent (hwnd), &pos); - - Rectangle clip (pos.x, pos.y, - r.right - r.left, - r.bottom - r.top); + auto clip = rectangleFromRECT (getWindowClientRect (hwnd)); info.clip->subtract (clip - info.origin); @@ -2399,7 +2401,7 @@ private: // it's not possible to have a transparent window with a title bar at the moment! jassert (! hasTitleBar()); - auto r = getWindowRect (hwnd); + auto r = getWindowScreenRect (hwnd); x = y = 0; w = r.right - r.left; h = r.bottom - r.top; @@ -3198,17 +3200,19 @@ private: { if (isConstrainedNativeWindow()) { - auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd)); - auto current = getCurrentScaledBounds(); + const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r).toFloat(), hwnd); + auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt(); + + const auto original = getCurrentScaledBounds(); - constrainer->checkBounds (pos, current, + constrainer->checkBounds (pos, original, Desktop::getInstance().getDisplays().getTotalBounds (true), wParam == WMSZ_TOP || wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOPRIGHT, wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT || wParam == WMSZ_BOTTOMLEFT, wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT, wParam == WMSZ_RIGHT || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_BOTTOMRIGHT); - r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos), hwnd)); + r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()).toNearestInt(), hwnd)); } return TRUE; @@ -3222,22 +3226,29 @@ private: && (wp.x > -32000 && wp.y > -32000) && ! Component::isMouseButtonDownAnywhere()) { - auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, convertPhysicalScreenRectangleToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }), hwnd)); - auto current = getCurrentScaledBounds(); + const auto logicalBounds = convertPhysicalScreenRectangleToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }).toFloat(), hwnd); + auto pos = ScalingHelpers::unscaledScreenPosToScaled (component, logicalBounds).toNearestInt(); - constrainer->checkBounds (pos, current, + const auto original = getCurrentScaledBounds(); + + constrainer->checkBounds (pos, original, Desktop::getInstance().getDisplays().getTotalBounds (true), - pos.getY() != current.getY() && pos.getBottom() == current.getBottom(), - pos.getX() != current.getX() && pos.getRight() == current.getRight(), - pos.getY() == current.getY() && pos.getBottom() != current.getBottom(), - pos.getX() == current.getX() && pos.getRight() != current.getRight()); + pos.getY() != original.getY() && pos.getBottom() == original.getBottom(), + pos.getX() != original.getX() && pos.getRight() == original.getRight(), + pos.getY() == original.getY() && pos.getBottom() != original.getBottom(), + pos.getX() == original.getX() && pos.getRight() != original.getRight()); + + auto physicalBounds = convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos.toFloat()), hwnd); - pos = convertLogicalScreenRectangleToPhysical (ScalingHelpers::scaledScreenPosToUnscaled (component, pos), hwnd); + auto getNewPositionIfNotRoundingError = [] (int pos, float newPos) + { + return (std::abs ((float) pos - newPos) >= 1.0f) ? roundToInt (newPos) : pos; + }; - wp.x = pos.getX(); - wp.y = pos.getY(); - wp.cx = pos.getWidth(); - wp.cy = pos.getHeight(); + wp.x = getNewPositionIfNotRoundingError (wp.x, physicalBounds.getX()); + wp.y = getNewPositionIfNotRoundingError (wp.y, physicalBounds.getY()); + wp.cx = getNewPositionIfNotRoundingError (wp.cx, physicalBounds.getWidth()); + wp.cy = getNewPositionIfNotRoundingError (wp.cy, physicalBounds.getHeight()); } } @@ -3267,40 +3278,67 @@ private: return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly.. } + //============================================================================== + struct ChildWindowCallbackData + { + std::map windowRectsMap; + float scaleRatio; + }; + LRESULT handleDPIChanging (int newDPI, RECT newRect) { auto newScale = (double) newDPI / USER_DEFAULT_SCREEN_DPI; if (! approximatelyEqual (scaleFactor, newScale)) { - const ScopedValueSetter setter (isInDPIChange, true); - auto oldScale = scaleFactor; scaleFactor = newScale; - auto scaleRatio = scaleFactor / oldScale; - EnumChildWindows (hwnd, scaleChildHWNDCallback, (LPARAM) &scaleRatio); + { + const ScopedValueSetter setter (isInDPIChange, true); + setBounds (windowBorder.subtractedFrom (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (newRect), hwnd)), fullScreen); + } - setBounds (windowBorder.subtractedFrom (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (newRect), hwnd)), false); updateShadower(); InvalidateRect (hwnd, nullptr, FALSE); + + ChildWindowCallbackData callbackData; + callbackData.scaleRatio = (float) (scaleFactor / oldScale); + + EnumChildWindows (hwnd, getChildWindowRectCallback, (LPARAM) &callbackData); scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); }); + EnumChildWindows (hwnd, scaleChildWindowCallback, (LPARAM) &callbackData); } return 0; } - static BOOL CALLBACK scaleChildHWNDCallback (HWND hwnd, LPARAM context) + static BOOL CALLBACK getChildWindowRectCallback (HWND hwnd, LPARAM data) { - auto r = getWindowRect (hwnd); + auto& callbackData = *(reinterpret_cast (data)); - POINT p { r.left, r.top }; - ScreenToClient (GetParent (hwnd), &p); + callbackData.windowRectsMap[hwnd] = getWindowClientRect (hwnd); + return TRUE; + } + + static BOOL CALLBACK scaleChildWindowCallback (HWND hwnd, LPARAM data) + { + auto& callbackData = *(reinterpret_cast (data)); - auto ratio = *(double*) context; - SetWindowPos (hwnd, nullptr, roundToInt (p.x * ratio), roundToInt (p.y * ratio), - roundToInt ((r.right - r.left) * ratio), roundToInt ((r.bottom - r.top) * ratio), - SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); + auto originalBounds = rectangleFromRECT (callbackData.windowRectsMap[hwnd]); + auto scaledBounds = (originalBounds.toFloat() * callbackData.scaleRatio).toNearestInt(); + auto currentBounds = rectangleFromRECT (getWindowClientRect (hwnd)); + + if (scaledBounds != currentBounds) + { + SetWindowPos (hwnd, + nullptr, + scaledBounds.getX(), + scaledBounds.getY(), + scaledBounds.getWidth(), + scaledBounds.getHeight(), + SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); + } if (auto* peer = getOwnerOfWindow (hwnd)) peer->handleChildDPIChanging(); @@ -3310,15 +3348,14 @@ private: void handleChildDPIChanging() { - const ScopedValueSetter setter (isInDPIChange, true); - scaleFactor = getScaleFactorForWindow (parentToAddTo); + scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); }); updateShadower(); InvalidateRect (hwnd, nullptr, FALSE); - scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); }); } + //============================================================================== void handleAppActivation (const WPARAM wParam) { modifiersAtLastCallback = -1; @@ -3419,17 +3456,12 @@ private: if (fullScreen && ! isMinimised()) setWindowPos (hwnd, ScalingHelpers::scaledScreenPosToUnscaled (component, Desktop::getInstance().getDisplays() - .findDisplayForRect (component.getScreenBounds()).userArea), + .getDisplayForRect (component.getScreenBounds())->userArea), SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING); } - static void forceDisplayUpdate() - { - const_cast (Desktop::getInstance().getDisplays()).refresh(); - } - //============================================================================== - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client void setModifierKeyProvider (ModifierKeyProvider* provider) override { modProvider = provider; @@ -3441,7 +3473,6 @@ private: } #endif - //============================================================================== public: static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { @@ -3483,7 +3514,7 @@ private: // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the // physical screen position and then convert this to local logical coordinates auto localPos = getPOINTFromLParam (lParam); - auto r = getWindowRect (hwnd); + auto r = getWindowScreenRect (hwnd); return globalToLocal (Desktop::getInstance().getDisplays().physicalToLogical (pointFromPOINT ({ r.left + localPos.x + roundToInt (windowBorder.getLeft() * scaleFactor), r.top + localPos.y + roundToInt (windowBorder.getTop() * scaleFactor) })).toFloat()); @@ -3845,15 +3876,13 @@ private: bool sendInputAttemptWhenModalMessage() { - if (component.isCurrentlyBlockedByAnotherModalComponent()) - { - if (Component* const current = Component::getCurrentlyModalComponent()) - current->inputAttemptWhenModal(); + if (! component.isCurrentlyBlockedByAnotherModalComponent()) + return false; - return true; - } + if (auto* current = Component::getCurrentlyModalComponent()) + current->inputAttemptWhenModal(); - return false; + return true; } //============================================================================== @@ -4078,6 +4107,74 @@ private: stopTimer(); } + static bool isAncestor (HWND outer, HWND inner) + { + if (outer == nullptr || inner == nullptr) + return false; + + if (outer == inner) + return true; + + return isAncestor (outer, GetAncestor (inner, GA_PARENT)); + } + + void windowShouldDismissModals (HWND originator) + { + if (isAncestor (originator, hwnd)) + sendInputAttemptWhenModalMessage(); + } + + // Unfortunately SetWindowsHookEx only allows us to register a static function as a hook. + // To get around this, we keep a static list of listeners which are interested in + // top-level window events, and notify all of these listeners from the callback. + class TopLevelModalDismissBroadcaster + { + public: + TopLevelModalDismissBroadcaster() + : hook (SetWindowsHookEx (WH_CALLWNDPROC, + callWndProc, + (HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(), + GetCurrentThreadId())) + {} + + ~TopLevelModalDismissBroadcaster() noexcept + { + UnhookWindowsHookEx (hook); + } + + private: + static void processMessage (int nCode, const CWPSTRUCT* info) + { + if (nCode < 0 || info == nullptr) + return; + + constexpr UINT events[] { WM_MOVE, + WM_SIZE, + WM_NCPOINTERDOWN, + WM_NCLBUTTONDOWN, + WM_NCRBUTTONDOWN, + WM_NCMBUTTONDOWN }; + + if (std::find (std::begin (events), std::end (events), info->message) == std::end (events)) + return; + + // windowMayDismissModals could affect the number of active ComponentPeer instances + for (auto i = ComponentPeer::getNumPeers(); --i >= 0;) + if (i < ComponentPeer::getNumPeers()) + if (auto* hwndPeer = dynamic_cast (ComponentPeer::getPeer (i))) + hwndPeer->windowShouldDismissModals (info->hwnd); + } + + static LRESULT CALLBACK callWndProc (int nCode, WPARAM wParam, LPARAM lParam) + { + processMessage (nCode, reinterpret_cast (lParam)); + return CallNextHookEx ({}, nCode, wParam, lParam); + } + + HHOOK hook; + }; + + SharedResourcePointer modalDismissBroadcaster; IMEHandler imeHandler; //============================================================================== @@ -4455,7 +4552,7 @@ void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, tlw->setUsingNativeTitleBar (! enableOrDisable); if (enableOrDisable) - kioskModeComp->setBounds (getDisplays().findDisplayForRect (kioskModeComp->getScreenBounds()).totalArea); + kioskModeComp->setBounds (getDisplays().getDisplayForRect (kioskModeComp->getScreenBounds())->totalArea); } void Desktop::allowedOrientationsChanged() {} @@ -4487,7 +4584,7 @@ static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd) if (auto* peer = HWNDComponentPeer::getOwnerOfWindow (hwnd)) bounds = peer->getComponent().getTopLevelComponent()->getBounds(); else - bounds = Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (getWindowRect (hwnd))); + bounds = Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (getWindowScreenRect (hwnd))); const Displays::Display* retVal = nullptr; int maxArea = -1; @@ -4508,21 +4605,26 @@ static const Displays::Display* getCurrentDisplayFromScaleFactor (HWND hwnd) return retVal; } - return &Desktop::getInstance().getDisplays().getMainDisplay(); + return Desktop::getInstance().getDisplays().getPrimaryDisplay(); } //============================================================================== struct MonitorInfo { - MonitorInfo (bool main, RECT rect, double d) noexcept - : isMain (main), bounds (rect), dpi (d) {} + MonitorInfo (bool main, RECT totalArea, RECT workArea, double d) noexcept + : isMain (main), + totalAreaRect (totalArea), + workAreaRect (workArea), + dpi (d) + { + } bool isMain; - RECT bounds; + RECT totalAreaRect, workAreaRect; double dpi; }; -static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userInfo) +static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo) { MONITORINFO info = {}; info.cbSize = sizeof (info); @@ -4539,7 +4641,7 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userIn dpi = (dpiX + dpiY) / 2.0; } - ((Array*) userInfo)->add ({ isMain, *r, dpi }); + ((Array*) userInfo)->add ({ isMain, info.rcMonitor, info.rcWork, dpi }); return TRUE; } @@ -4553,7 +4655,10 @@ void Displays::findDisplays (float masterScale) auto globalDPI = getGlobalDPI(); if (monitors.size() == 0) - monitors.add ({ true, getWindowRect (GetDesktopWindow()), globalDPI }); + { + auto windowRect = getWindowScreenRect (GetDesktopWindow()); + monitors.add ({ true, windowRect, windowRect, globalDPI }); + } // make sure the first in the list is the main monitor for (int i = 1; i < monitors.size(); ++i) @@ -4577,30 +4682,24 @@ void Displays::findDisplays (float masterScale) d.scale = (d.dpi / USER_DEFAULT_SCREEN_DPI) * (masterScale / Desktop::getDefaultMasterScale()); } - d.userArea = d.totalArea = Rectangle::leftTopRightBottom (monitor.bounds.left, monitor.bounds.top, - monitor.bounds.right, monitor.bounds.bottom); - - if (d.isMain) - { - RECT workArea; - SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); - - d.userArea = d.userArea.getIntersection (Rectangle::leftTopRightBottom (workArea.left, workArea.top, - workArea.right, workArea.bottom)); - } + d.totalArea = rectangleFromRECT (monitor.totalAreaRect); + d.userArea = rectangleFromRECT (monitor.workAreaRect); displays.add (d); } #if JUCE_WIN_PER_MONITOR_DPI_AWARE - updateToLogical(); - #else - for (auto& d : displays) + if (isPerMonitorDPIAwareThread()) + updateToLogical(); + else + #endif { - d.totalArea /= masterScale; - d.userArea /= masterScale; + for (auto& d : displays) + { + d.totalArea /= masterScale; + d.userArea /= masterScale; + } } - #endif } //============================================================================== diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp index ff523ac4..d6508e09 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp @@ -287,8 +287,11 @@ public: if (XWindowSystemUtilities::Atoms::isMimeTypeFile (getDisplay(), dragAndDropCurrentMimeType)) { - for (int i = 0; i < lines.size(); ++i) - dragInfo.files.add (URL::removeEscapeChars (lines[i].replace ("file://", String(), true))); + for (const auto& line : lines) + { + const auto escaped = line.replace ("+", "%2B").replace ("file://", String(), true); + dragInfo.files.add (URL::removeEscapeChars (escaped)); + } dragInfo.files.trim(); dragInfo.files.removeEmptyStrings(); diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp index e00e1035..ad5b0e46 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp @@ -29,14 +29,14 @@ namespace juce namespace X11SymbolHelpers { -template +template struct SymbolBinding { FuncPtr& func; const char* name; }; -template +template SymbolBinding makeSymbolBinding (FuncPtr& func, const char* name) { return { func, name }; @@ -54,19 +54,19 @@ bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding) return false; } -template +template bool loadSymbols (DynamicLibrary& lib1, DynamicLibrary& lib2, SymbolBinding binding) { return loadSymbols (lib1, binding) || loadSymbols (lib2, binding); } -template +template bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding, Args... args) { return loadSymbols (lib, binding) && loadSymbols (lib, args...); } -template +template bool loadSymbols (DynamicLibrary& lib1, DynamicLibrary& lib2, SymbolBinding binding, Args... args) { return loadSymbols (lib1, lib2, binding) && loadSymbols (lib1, lib2, args...); @@ -181,6 +181,7 @@ bool X11Symbols::loadAllSymbols() makeSymbolBinding (xSetWMNormalHints, "XSetWMNormalHints"), makeSymbolBinding (xStringListToTextProperty, "XStringListToTextProperty"), makeSymbolBinding (xSync, "XSync"), + makeSymbolBinding (xSynchronize, "XSynchronize"), makeSymbolBinding (xTranslateCoordinates, "XTranslateCoordinates"), makeSymbolBinding (xrmUniqueQuark, "XrmUniqueQuark"), makeSymbolBinding (xUngrabPointer, "XUngrabPointer"), diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h index 8d9bb681..2ea5e6dc 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h +++ b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h @@ -29,10 +29,10 @@ namespace juce //============================================================================== namespace ReturnHelpers { - template + template Type returnDefaultConstructedAnyType() { return {}; } - template<> + template <> inline void returnDefaultConstructedAnyType() {} } @@ -453,6 +453,10 @@ public: (::Display*, Bool), void) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSynchronize, xSynchronize, + (::Display*, Bool), + int) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XTranslateCoordinates, xTranslateCoordinates, (::Display*, ::Window, ::Window, int, int, int*, int*, ::Window*), Bool) diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp index af659001..490a3a79 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp @@ -30,6 +30,10 @@ namespace juce #if JUCE_DEBUG && ! defined (JUCE_DEBUG_XERRORS) #define JUCE_DEBUG_XERRORS 1 + + #if ! defined (JUCE_DEBUG_XERRORS_SYNCHRONOUSLY) + #define JUCE_DEBUG_XERRORS_SYNCHRONOUSLY 1 + #endif #endif #if JUCE_MODULE_AVAILABLE_juce_gui_extra @@ -38,6 +42,40 @@ namespace juce #define JUCE_X11_SUPPORTS_XEMBED 0 #endif +namespace +{ + struct XFreeDeleter + { + void operator() (void* ptr) const + { + if (ptr != nullptr) + X11Symbols::getInstance()->xFree (ptr); + } + }; + + template + std::unique_ptr makeXFreePtr (Data* raw) { return std::unique_ptr (raw); } + + template + std::unique_ptr makeDeletedPtr (Data* raw, const Deleter& d) { return std::unique_ptr (raw, d); } + + template + struct XValueHolder + { + XValueHolder (XValueType&& xv, const std::function& cleanup) + : value (std::move (xv)), cleanupFunc (cleanup) + {} + + ~XValueHolder() + { + cleanupFunc (value); + } + + XValueType value; + std::function cleanupFunc; + }; +} + //============================================================================== XWindowSystemUtilities::ScopedXLock::ScopedXLock() { @@ -67,6 +105,7 @@ XWindowSystemUtilities::Atoms::Atoms (::Display* display) pid = getCreating (display, "_NET_WM_PID"); windowType = getIfExists (display, "_NET_WM_WINDOW_TYPE"); windowState = getIfExists (display, "_NET_WM_STATE"); + windowStateHidden = getIfExists (display, "_NET_WM_STATE_HIDDEN"); XdndAware = getCreating (display, "XdndAware"); XdndEnter = getCreating (display, "XdndEnter"); @@ -143,12 +182,6 @@ XWindowSystemUtilities::GetXProperty::~GetXProperty() } //============================================================================== -using WindowMessageReceiveCallback = void (*) (XEvent&); -using SelectionRequestCallback = void (*) (XSelectionRequestEvent&); - -static WindowMessageReceiveCallback dispatchWindowMessage = nullptr; -SelectionRequestCallback handleSelectionRequest = nullptr; - ::Window juce_messageWindowHandle; XContext windowHandleXContext; @@ -159,8 +192,11 @@ XContext windowHandleXContext; struct MotifWmHints { - unsigned long flags = 0, functions = 0, decorations = 0, status = 0; - long input_mode = 0; + unsigned long flags = 0; + unsigned long functions = 0; + unsigned long decorations = 0; + long input_mode = 0; + unsigned long status = 0; }; //=============================== X11 - Error Handling ========================= @@ -322,14 +358,14 @@ static void updateKeyModifiers (int status) noexcept { int keyMods = 0; - if ((status & ShiftMask) != 0) keyMods |= ModifierKeys::shiftModifier; - if ((status & ControlMask) != 0) keyMods |= ModifierKeys::ctrlModifier; + if ((status & ShiftMask) != 0) keyMods |= ModifierKeys::shiftModifier; + if ((status & ControlMask) != 0) keyMods |= ModifierKeys::ctrlModifier; if ((status & Keys::AltMask) != 0) keyMods |= ModifierKeys::altModifier; ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons().withFlags (keyMods); Keys::numLock = ((status & Keys::NumLockMask) != 0); - Keys::capsLock = ((status & LockMask) != 0); + Keys::capsLock = ((status & LockMask) != 0); } static bool updateKeyModifiersFromSym (KeySym sym, bool press) noexcept @@ -551,18 +587,16 @@ namespace Visuals desiredMask |= VisualBitsPerRGBMask; } - if (auto* xvinfos = X11Symbols::getInstance()->xGetVisualInfo (display, desiredMask, &desiredVisual, &numVisuals)) + if (auto xvinfos = makeXFreePtr (X11Symbols::getInstance()->xGetVisualInfo (display, desiredMask, &desiredVisual, &numVisuals))) { for (int i = 0; i < numVisuals; i++) { - if (xvinfos[i].depth == desiredDepth) + if (xvinfos.get()[i].depth == desiredDepth) { - visual = xvinfos[i].visual; + visual = xvinfos.get()[i].visual; break; } } - - X11Symbols::getInstance()->xFree (xvinfos); } return visual; @@ -588,9 +622,9 @@ namespace Visuals desiredVisual.depth = 32; desiredVisual.bits_per_rgb = 8; - if (auto xvinfos = X11Symbols::getInstance()->xGetVisualInfo (display, - VisualScreenMask | VisualDepthMask | VisualBitsPerRGBMask, - &desiredVisual, &numVisuals)) + if (auto xvinfos = makeXFreePtr (X11Symbols::getInstance()->xGetVisualInfo (display, + VisualScreenMask | VisualDepthMask | VisualBitsPerRGBMask, + &desiredVisual, &numVisuals))) { for (int i = 0; i < numVisuals; ++i) { @@ -605,8 +639,6 @@ namespace Visuals break; } } - - X11Symbols::getInstance()->xFree (xvinfos); } } } @@ -900,6 +932,158 @@ private: //=============================== X11 - Displays =============================== namespace DisplayHelpers { + template + void parseXSettings (const unsigned char* dataPtr, const size_t bytes, Callback&& callback) + { + struct Header + { + CARD8 byteOrder; + CARD8 padding[3]; + CARD32 serial; + CARD32 nSettings; + }; + + auto* data = dataPtr; + size_t byteNum = 0; + + const auto increment = [&] (size_t amount) + { + data += amount; + byteNum += amount; + }; + + const auto* header = reinterpret_cast (data); + increment (sizeof (Header)); + + const auto readCARD16 = [&]() -> CARD16 + { + if (byteNum + sizeof (CARD16) > bytes) + return {}; + + const auto value = header->byteOrder == MSBFirst ? ByteOrder::bigEndianShort (data) + : ByteOrder::littleEndianShort (data); + increment (sizeof (CARD16)); + return value; + }; + + const auto readCARD32 = [&]() -> CARD32 + { + if (byteNum + sizeof (CARD32) > bytes) + return {}; + + const auto value = header->byteOrder == MSBFirst ? ByteOrder::bigEndianInt (data) + : ByteOrder::littleEndianInt (data); + increment (sizeof (CARD32)); + return value; + }; + + const auto readString = [&] (size_t nameLen) -> std::string + { + const auto padded = (nameLen + 3) & (~(size_t) 3); + + if (byteNum + padded > bytes) + return {}; + + auto* ptr = reinterpret_cast (data); + const std::string result (ptr, ptr + nameLen); + increment (padded); + return result; + }; + + CARD16 setting = 0; + + while (byteNum < bytes && setting < header->nSettings) + { + const auto type = *reinterpret_cast (data); + increment (2); + + const auto name = readString (readCARD16()); + const auto serial = readCARD32(); + ignoreUnused (serial); + + enum { XSettingsTypeInteger, XSettingsTypeString, XSettingsTypeColor }; + + switch (type) + { + case XSettingsTypeInteger: + { + callback (name, (INT32) readCARD32()); + break; + } + + case XSettingsTypeString: + { + callback (name, readString (readCARD32())); + break; + } + + case XSettingsTypeColor: + { + // Order is important, these should be kept as separate statements! + const auto r = readCARD16(); + const auto g = readCARD16(); + const auto b = readCARD16(); + const auto a = readCARD16(); + callback (name, r, g, b, a); + break; + } + } + + setting += 1; + } + } + + double getScalingFactorFromXSettings() + { + if (auto* display = XWindowSystem::getInstance()->getDisplay()) + { + using namespace XWindowSystemUtilities; + + ScopedXLock xLock; + + const auto selectionWindow = X11Symbols::getInstance()->xGetSelectionOwner (display, + Atoms::getCreating (display, "_XSETTINGS_S0")); + + if (selectionWindow != None) + { + const auto xsettingsSettingsAtom = Atoms::getCreating (display, "_XSETTINGS_SETTINGS"); + + const GetXProperty prop { selectionWindow, + xsettingsSettingsAtom, + 0L, + std::numeric_limits::max(), + false, + xsettingsSettingsAtom }; + + if (prop.success + && prop.actualType == xsettingsSettingsAtom + && prop.actualFormat == 8) + { + struct ExtractRelevantSettings + { + void operator() (const std::string& name, INT32 value) + { + if (name == "Gdk/WindowScalingFactor") + scaleFactor = value; + } + + void operator() (const std::string&, const std::string&) {} + void operator() (const std::string&, CARD16, CARD16, CARD16, CARD16) {} + + INT32 scaleFactor = 0; + }; + + ExtractRelevantSettings callback; + parseXSettings (prop.data, prop.numItems, callback); + + return (double) callback.scaleFactor; + } + } + } + + return 0.0; + } + static double getDisplayDPI (::Display* display, int index) { auto widthMM = X11Symbols::getInstance()->xDisplayWidthMM (display, index); @@ -914,6 +1098,11 @@ namespace DisplayHelpers static double getDisplayScale (const String& name, double dpi) { + const auto scaleFactorFromXSettings = getScalingFactorFromXSettings(); + + if (scaleFactorFromXSettings > 0.0) + return scaleFactorFromXSettings; + if (name.isNotEmpty()) { // Ubuntu and derived distributions now save a per-display scale factor as a configuration @@ -988,13 +1177,8 @@ namespace DisplayHelpers { int numScreens; - if (auto* xinfo = X11Symbols::getInstance()->xineramaQueryScreens (display, &numScreens)) - { - Array infos (xinfo, numScreens); - X11Symbols::getInstance()->xFree (xinfo); - - return infos; - } + if (auto xinfo = makeXFreePtr (X11Symbols::getInstance()->xineramaQueryScreens (display, &numScreens))) + return { xinfo.get(), numScreens }; } return {}; @@ -1018,19 +1202,17 @@ namespace PixmapHelpers for (int x = 0; x < (int) width; ++x) colour[index++] = image.getPixelAt (x, y).getARGB(); - auto* ximage = X11Symbols::getInstance()->xCreateImage (display, (Visual*) CopyFromParent, 24, ZPixmap, - 0, reinterpret_cast (colour.getData()), - width, height, 32, 0); + auto ximage = makeXFreePtr (X11Symbols::getInstance()->xCreateImage (display, (Visual*) CopyFromParent, 24, ZPixmap, + 0, reinterpret_cast (colour.getData()), + width, height, 32, 0)); auto pixmap = X11Symbols::getInstance()->xCreatePixmap (display, X11Symbols::getInstance()->xDefaultRootWindow (display), width, height, 24); - auto gc = X11Symbols::getInstance()->xCreateGC (display, pixmap, 0, nullptr); - - X11Symbols::getInstance()->xPutImage (display, pixmap, gc, ximage, 0, 0, 0, 0, width, height); - X11Symbols::getInstance()->xFreeGC (display, gc); - X11Symbols::getInstance()->xFree (ximage); + XValueHolder gc (X11Symbols::getInstance()->xCreateGC (display, pixmap, 0, nullptr), + [&display] (GC& g) { X11Symbols::getInstance()->xFreeGC (display, g); }); + X11Symbols::getInstance()->xPutImage (display, pixmap, gc.value, ximage.get(), 0, 0, 0, 0, width, height); return pixmap; } @@ -1078,7 +1260,7 @@ namespace ClipboardHelpers if (prop.success) { - if (prop.actualType == XWindowSystem::getInstance()->getAtoms().utf8String&& prop.actualFormat == 8) + if (prop.actualType == XWindowSystem::getInstance()->getAtoms().utf8String && prop.actualFormat == 8) return String::fromUTF8 ((const char*) prop.data, (int) prop.numItems); if (prop.actualType == XA_STRING && prop.actualFormat == 8) @@ -1195,20 +1377,6 @@ namespace ClipboardHelpers } } -//============================================================================== -typedef void (*SelectionRequestCallback) (XSelectionRequestEvent&); -extern SelectionRequestCallback handleSelectionRequest; - -struct ClipboardCallbackInitialiser -{ - ClipboardCallbackInitialiser() - { - handleSelectionRequest = ClipboardHelpers::handleSelection; - } -}; - -static ClipboardCallbackInitialiser clipboardInitialiser; - //============================================================================== ComponentPeer* getPeerFor (::Window windowH) { @@ -1227,7 +1395,7 @@ ComponentPeer* getPeerFor (::Window windowH) } //============================================================================== -static std::unordered_map*, X11DragState> dragAndDropStateMap; +static std::unordered_map dragAndDropStateMap; XWindowSystem::XWindowSystem() { @@ -1287,11 +1455,11 @@ static int getAllEventsMask (bool ignoresMouseClicks) { return NoEventMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask - | ExposureMask | StructureNotifyMask | FocusChangeMask + | ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask | (ignoresMouseClicks ? 0 : (ButtonPressMask | ButtonReleaseMask)); } -::Window XWindowSystem::createWindow (::Window parentToAddTo, LinuxComponentPeer<::Window>* peer) const +::Window XWindowSystem::createWindow (::Window parentToAddTo, LinuxComponentPeer* peer) const { if (! xIsAvailable) { @@ -1338,26 +1506,24 @@ static int getAllEventsMask (bool ignoresMouseClicks) } // Set window manager hints - if (auto* wmHints = X11Symbols::getInstance()->xAllocWMHints()) + if (auto wmHints = makeXFreePtr (X11Symbols::getInstance()->xAllocWMHints())) { wmHints->flags = InputHint | StateHint; wmHints->input = True; wmHints->initial_state = NormalState; - X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); - X11Symbols::getInstance()->xFree (wmHints); + X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints.get()); } // Set class hint if (auto* app = JUCEApplicationBase::getInstance()) { - if (auto* classHint = X11Symbols::getInstance()->xAllocClassHint()) + if (auto classHint = makeXFreePtr (X11Symbols::getInstance()->xAllocClassHint())) { auto appName = app->getApplicationName(); classHint->res_name = (char*) appName.getCharPointer().getAddress(); classHint->res_class = (char*) appName.getCharPointer().getAddress(); - X11Symbols::getInstance()->xSetClassHint (display, windowH, classHint); - X11Symbols::getInstance()->xFree (classHint); + X11Symbols::getInstance()->xSetClassHint (display, windowH, classHint.get()); } } @@ -1393,7 +1559,7 @@ static int getAllEventsMask (bool ignoresMouseClicks) void XWindowSystem::destroyWindow (::Window windowH) { - auto* peer = dynamic_cast*> (getPeerFor (windowH)); + auto* peer = dynamic_cast (getPeerFor (windowH)); if (peer == nullptr) { @@ -1474,10 +1640,10 @@ void XWindowSystem::setIcon (::Window windowH, const Image& newIcon) const deleteIconPixmaps (windowH); - auto* wmHints = X11Symbols::getInstance()->xGetWMHints (display, windowH); + auto wmHints = makeXFreePtr (X11Symbols::getInstance()->xGetWMHints (display, windowH)); if (wmHints == nullptr) - wmHints = X11Symbols::getInstance()->xAllocWMHints(); + wmHints = makeXFreePtr (X11Symbols::getInstance()->xAllocWMHints()); if (wmHints != nullptr) { @@ -1485,8 +1651,7 @@ void XWindowSystem::setIcon (::Window windowH, const Image& newIcon) const wmHints->icon_pixmap = PixmapHelpers::createColourPixmapFromImage (display, newIcon); wmHints->icon_mask = PixmapHelpers::createMaskPixmapFromImage (display, newIcon); - X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); - X11Symbols::getInstance()->xFree (wmHints); + X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints.get()); } X11Symbols::getInstance()->xSync (display, False); @@ -1540,7 +1705,7 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle newBounds, bool XWindowSystemUtilities::ScopedXLock xLock; - if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints()) + if (auto hints = makeXFreePtr (X11Symbols::getInstance()->xAllocSizeHints())) { hints->flags = USSize | USPosition; hints->x = newBounds.getX(); @@ -1555,8 +1720,7 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle newBounds, bool hints->flags |= PMinSize | PMaxSize; } - X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints); - X11Symbols::getInstance()->xFree (hints); + X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints.get()); } auto windowBorder = peer->getFrameSize(); @@ -1687,6 +1851,25 @@ bool XWindowSystem::isMinimised (::Window windowH) const return false; } +void XWindowSystem::setMaximised (::Window windowH, bool shouldBeMaximised) const +{ + const auto root = X11Symbols::getInstance()->xRootWindow (display, X11Symbols::getInstance()->xDefaultScreen (display)); + + XEvent ev; + ev.xclient.window = windowH; + ev.xclient.type = ClientMessage; + ev.xclient.format = 32; + ev.xclient.message_type = XWindowSystemUtilities::Atoms::getCreating (display, "_NET_WM_STATE"); + ev.xclient.data.l[0] = shouldBeMaximised; + ev.xclient.data.l[1] = (long) XWindowSystemUtilities::Atoms::getCreating (display, "_NET_WM_STATE_MAXIMIZED_HORZ"); + ev.xclient.data.l[2] = (long) XWindowSystemUtilities::Atoms::getCreating (display, "_NET_WM_STATE_MAXIMIZED_VERT"); + ev.xclient.data.l[3] = 1; + ev.xclient.data.l[4] = 0; + + XWindowSystemUtilities::ScopedXLock xLock; + X11Symbols::getInstance()->xSendEvent (display, root, false, SubstructureRedirectMask | SubstructureNotifyMask, &ev); +} + void XWindowSystem::toFront (::Window windowH, bool) const { jassert (windowH != 0); @@ -1741,7 +1924,7 @@ bool XWindowSystem::isFocused (::Window windowH) const jassert (windowH != 0); #if JUCE_X11_SUPPORTS_XEMBED - if (auto w = (::Window) juce_getCurrentFocusWindow (dynamic_cast*> (getPeerFor (windowH)))) + if (auto w = (::Window) juce_getCurrentFocusWindow (dynamic_cast (getPeerFor (windowH)))) return w; #endif @@ -1944,7 +2127,8 @@ void* XWindowSystem::createCustomMouseCursorInfo (const Image& image, Point auto hotspotY = hotspot.y; #if JUCE_USE_XCURSOR - if (auto* xcImage = X11Symbols::getInstance()->xcursorImageCreate ((int) imageW, (int) imageH)) + if (auto xcImage = makeDeletedPtr (X11Symbols::getInstance()->xcursorImageCreate ((int) imageW, (int) imageH), + [] (XcursorImage* i) { X11Symbols::getInstance()->xcursorImageDestroy (i); })) { xcImage->xhot = (XcursorDim) hotspotX; xcImage->yhot = (XcursorDim) hotspotY; @@ -1954,8 +2138,7 @@ void* XWindowSystem::createCustomMouseCursorInfo (const Image& image, Point for (int x = 0; x < (int) imageW; ++x) *dest++ = image.getPixelAt (x, y).getARGB(); - auto* result = (void*) X11Symbols::getInstance()->xcursorImageLoadCursor (display, xcImage); - X11Symbols::getInstance()->xcursorImageDestroy (xcImage); + auto* result = (void*) X11Symbols::getInstance()->xcursorImageLoadCursor (display, xcImage.get()); if (result != nullptr) return result; @@ -2009,20 +2192,16 @@ void* XWindowSystem::createCustomMouseCursorInfo (const Image& image, Point } } - auto sourcePixmap = X11Symbols::getInstance()->xCreatePixmapFromBitmapData (display, root, sourcePlane.getData(), cursorW, cursorH, 0xffff, 0, 1); - auto maskPixmap = X11Symbols::getInstance()->xCreatePixmapFromBitmapData (display, root, maskPlane.getData(), cursorW, cursorH, 0xffff, 0, 1); + auto xFreePixmap = [this] (Pixmap& p) { X11Symbols::getInstance()->xFreePixmap (display, p); }; + XValueHolder sourcePixmap (X11Symbols::getInstance()->xCreatePixmapFromBitmapData (display, root, sourcePlane.getData(), cursorW, cursorH, 0xffff, 0, 1), xFreePixmap); + XValueHolder maskPixmap (X11Symbols::getInstance()->xCreatePixmapFromBitmapData (display, root, maskPlane.getData(), cursorW, cursorH, 0xffff, 0, 1), xFreePixmap); XColor white, black; black.red = black.green = black.blue = 0; white.red = white.green = white.blue = 0xffff; - auto* result = (void*) X11Symbols::getInstance()->xCreatePixmapCursor (display, sourcePixmap, maskPixmap, &white, &black, - (unsigned int) hotspotX, (unsigned int) hotspotY); - - X11Symbols::getInstance()->xFreePixmap (display, sourcePixmap); - X11Symbols::getInstance()->xFreePixmap (display, maskPixmap); - - return result; + return (void*) X11Symbols::getInstance()->xCreatePixmapCursor (display, sourcePixmap.value, maskPixmap.value, &white, &black, + (unsigned int) hotspotX, (unsigned int) hotspotY); } void XWindowSystem::deleteMouseCursor (void* cursorHandle) const @@ -2036,14 +2215,13 @@ void XWindowSystem::deleteMouseCursor (void* cursorHandle) const void* createDraggingHandCursor() { - static unsigned char dragHandData[] = { + constexpr unsigned char dragHandData[] = { 71,73,70,56,57,97,16,0,16,0,145,2,0,0,0,0,255,255,255,0,0,0,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0,16,0, 0,2,52,148,47,0,200,185,16,130,90,12,74,139,107,84,123,39,132,117,151,116,132,146,248,60,209,138,98,22,203, 114,34,236,37,52,77,217, 247,154,191,119,110,240,193,128,193,95,163,56,60,234,98,135,2,0,59 }; - size_t dragHandDataSize = 99; - return CustomMouseCursorInfo (ImageFileFormat::loadFrom (dragHandData, dragHandDataSize), { 8, 7 }).create(); + return CustomMouseCursorInfo (ImageFileFormat::loadFrom (dragHandData, (size_t) numElementsInArray (dragHandData)), { 8, 7 }).create(); } void* XWindowSystem::createStandardMouseCursor (MouseCursor::StandardCursorType type) const @@ -2078,15 +2256,14 @@ void* XWindowSystem::createStandardMouseCursor (MouseCursor::StandardCursorType case MouseCursor::CopyingCursor: { - static unsigned char copyCursorData[] = { + constexpr unsigned char copyCursorData[] = { 71,73,70,56,57,97,21,0,21,0,145,0,0,0,0,0,255,255,255,0,128,128,255,255,255,33,249,4,1,0,0,3,0,44,0,0,0,0, 21,0,21,0,0,2,72,4,134,169,171,16,199,98,11,79,90,71,161,93,56,111,78,133,218,215,137,31,82,154,100,200, 86,91,202,142,12,108,212,87,235,174,15,54,214,126,237,226,37,96,59,141,16,37,18,201,142,157,230,204,51,112, 252,114,147,74,83,5,50,68,147,208,217,16,71,149,252,124,5,0,59,0,0 }; - static constexpr int copyCursorSize = 119; - return CustomMouseCursorInfo (ImageFileFormat::loadFrom (copyCursorData, copyCursorSize), { 1, 3 }).create(); + return CustomMouseCursorInfo (ImageFileFormat::loadFrom (copyCursorData, (size_t) numElementsInArray (copyCursorData)), { 1, 3 }).create(); } case MouseCursor::NumStandardCursorTypes: @@ -2199,6 +2376,7 @@ Array XWindowSystem::findDisplays (float masterScale) const auto workAreaHints = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WORKAREA"); #if JUCE_USE_XRANDR + if (workAreaHints != None) { int major_opcode, first_event, first_error; @@ -2215,7 +2393,8 @@ Array XWindowSystem::findDisplays (float masterScale) const if (! hasWorkAreaData (prop)) continue; - if (auto* screens = X11Symbols::getInstance()->xRRGetScreenResources (display, rootWindow)) + if (auto screens = makeDeletedPtr (X11Symbols::getInstance()->xRRGetScreenResources (display, rootWindow), + [] (XRRScreenResources* srs) { X11Symbols::getInstance()->xRRFreeScreenResources (srs); })) { for (int j = 0; j < screens->noutput; ++j) { @@ -2226,11 +2405,13 @@ Array XWindowSystem::findDisplays (float masterScale) const if (! mainDisplay) mainDisplay = screens->outputs[j]; - if (auto* output = X11Symbols::getInstance()->xRRGetOutputInfo (display, screens, screens->outputs[j])) + if (auto output = makeDeletedPtr (X11Symbols::getInstance()->xRRGetOutputInfo (display, screens.get(), screens->outputs[j]), + [] (XRROutputInfo* oi) { X11Symbols::getInstance()->xRRFreeOutputInfo (oi); })) { if (output->crtc) { - if (auto* crtc = X11Symbols::getInstance()->xRRGetCrtcInfo (display, screens, output->crtc)) + if (auto crtc = makeDeletedPtr (X11Symbols::getInstance()->xRRGetCrtcInfo (display, screens.get(), output->crtc), + [] (XRRCrtcInfo* ci) { X11Symbols::getInstance()->xRRFreeCrtcInfo (ci); })) { Displays::Display d; d.totalArea = { crtc->x, crtc->y, (int) crtc->width, (int) crtc->height }; @@ -2251,17 +2432,11 @@ Array XWindowSystem::findDisplays (float masterScale) const displays.insert (0, d); else displays.add (d); - - X11Symbols::getInstance()->xRRFreeCrtcInfo (crtc); } } - - X11Symbols::getInstance()->xRRFreeOutputInfo (output); } } } - - X11Symbols::getInstance()->xRRFreeScreenResources (screens); } } @@ -2379,7 +2554,7 @@ void XWindowSystem::deleteKeyProxy (::Window keyProxy) const {} } -bool XWindowSystem::externalDragFileInit (LinuxComponentPeer<::Window>* peer, const StringArray& files, bool, std::function&& callback) const +bool XWindowSystem::externalDragFileInit (LinuxComponentPeer* peer, const StringArray& files, bool, std::function&& callback) const { auto& dragState = dragAndDropStateMap[peer]; @@ -2399,7 +2574,7 @@ bool XWindowSystem::externalDragFileInit (LinuxComponentPeer<::Window>* peer, co return dragState.externalDragInit ((::Window) peer->getNativeHandle(), false, uriList.joinIntoString ("\r\n"), std::move (callback)); } -bool XWindowSystem::externalDragTextInit (LinuxComponentPeer<::Window>* peer, const String& text, std::function&& callback) const +bool XWindowSystem::externalDragTextInit (LinuxComponentPeer* peer, const String& text, std::function&& callback) const { auto& dragState = dragAndDropStateMap[peer]; @@ -2453,29 +2628,24 @@ String XWindowSystem::getTextFromClipboard() const //============================================================================== bool XWindowSystem::isParentWindowOf (::Window windowH, ::Window possibleChild) const { - if (windowH != 0 && possibleChild != 0) - { - if (possibleChild == windowH) - return true; + if (windowH == 0 || possibleChild == 0) + return false; - Window* windowList = nullptr; - uint32 windowListSize = 0; - Window parent, root; + if (possibleChild == windowH) + return true; - XWindowSystemUtilities::ScopedXLock xLock; - if (X11Symbols::getInstance()->xQueryTree (display, possibleChild, &root, &parent, &windowList, &windowListSize) != 0) - { - if (windowList != nullptr) - X11Symbols::getInstance()->xFree (windowList); + Window* windowList = nullptr; + uint32 windowListSize = 0; + Window parent, root; - if (parent == root) - return false; + XWindowSystemUtilities::ScopedXLock xLock; + const auto result = X11Symbols::getInstance()->xQueryTree (display, possibleChild, &root, &parent, &windowList, &windowListSize); + const auto deleter = makeXFreePtr (windowList); - return isParentWindowOf (windowH, parent); - } - } + if (result == 0 || parent == root) + return false; - return false; + return isParentWindowOf (windowH, parent); } bool XWindowSystem::isFrontWindow (::Window windowH) const @@ -2484,28 +2654,24 @@ bool XWindowSystem::isFrontWindow (::Window windowH) const Window* windowList = nullptr; uint32 windowListSize = 0; - bool result = false; XWindowSystemUtilities::ScopedXLock xLock; Window parent; auto root = X11Symbols::getInstance()->xRootWindow (display, X11Symbols::getInstance()->xDefaultScreen (display)); - if (X11Symbols::getInstance()->xQueryTree (display, root, &root, &parent, &windowList, &windowListSize) != 0) + const auto queryResult = X11Symbols::getInstance()->xQueryTree (display, root, &root, &parent, &windowList, &windowListSize); + const auto deleter = makeXFreePtr (windowList); + + if (queryResult == 0) + return false; + + for (int i = (int) windowListSize; --i >= 0;) { - for (int i = (int) windowListSize; --i >= 0;) - { - if (auto* peer = dynamic_cast*> (getPeerFor (windowList[i]))) - { - result = (peer == dynamic_cast*> (getPeerFor (windowH))); - break; - } - } + if (auto* peer = dynamic_cast (getPeerFor (windowList[i]))) + return peer == dynamic_cast (getPeerFor (windowH)); } - if (windowList != nullptr) - X11Symbols::getInstance()->xFree (windowList); - - return result; + return false; } void XWindowSystem::xchangeProperty (::Window windowH, Atom property, Atom type, int format, const void* data, int numElements) const @@ -2519,7 +2685,7 @@ void XWindowSystem::removeWindowDecorations (::Window windowH) const { jassert (windowH != 0); - Atom hints = XWindowSystemUtilities::Atoms::getIfExists (display, "_MOTIF_WM_HINTS"); + auto hints = XWindowSystemUtilities::Atoms::getIfExists (display, "_MOTIF_WM_HINTS"); if (hints != None) { @@ -2562,14 +2728,25 @@ void XWindowSystem::removeWindowDecorations (::Window windowH) const } } +static void addAtomIfExists (bool condition, const char* key, ::Display* display, std::vector& atoms) +{ + if (condition) + { + auto atom = XWindowSystemUtilities::Atoms::getIfExists (display, key); + + if (atom != None) + atoms.push_back (atom); + } +} + void XWindowSystem::addWindowButtons (::Window windowH, int styleFlags) const { jassert (windowH != 0); XWindowSystemUtilities::ScopedXLock xLock; - Atom hints = XWindowSystemUtilities::Atoms::getIfExists (display, "_MOTIF_WM_HINTS"); + auto motifAtom = XWindowSystemUtilities::Atoms::getIfExists (display, "_MOTIF_WM_HINTS"); - if (hints != None) + if (motifAtom != None) { MotifWmHints motifHints; zerostruct (motifHints); @@ -2600,29 +2777,24 @@ void XWindowSystem::addWindowButtons (::Window windowH, int styleFlags) const motifHints.decorations |= 0x4; /* MWM_DECOR_RESIZEH */ } - xchangeProperty (windowH, hints, hints, 32, &motifHints, 5); + xchangeProperty (windowH, motifAtom, motifAtom, 32, &motifHints, 5); } - hints = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ALLOWED_ACTIONS"); + auto actionsAtom = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ALLOWED_ACTIONS"); - if (hints != None) + if (actionsAtom != None) { - Atom netHints [6]; - int num = 0; - - if ((styleFlags & ComponentPeer::windowIsResizable) != 0) - netHints [num++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ACTION_RESIZE"); + std::vector netHints; - if ((styleFlags & ComponentPeer::windowHasMaximiseButton) != 0) - netHints [num++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ACTION_FULLSCREEN"); - - if ((styleFlags & ComponentPeer::windowHasMinimiseButton) != 0) - netHints [num++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ACTION_MINIMIZE"); + addAtomIfExists ((styleFlags & ComponentPeer::windowIsResizable) != 0, "_NET_WM_ACTION_RESIZE", display, netHints); + addAtomIfExists ((styleFlags & ComponentPeer::windowHasMaximiseButton) != 0, "_NET_WM_ACTION_FULLSCREEN", display, netHints); + addAtomIfExists ((styleFlags & ComponentPeer::windowHasMinimiseButton) != 0, "_NET_WM_ACTION_MINIMIZE", display, netHints); + addAtomIfExists ((styleFlags & ComponentPeer::windowHasCloseButton) != 0, "_NET_WM_ACTION_CLOSE", display, netHints); - if ((styleFlags & ComponentPeer::windowHasCloseButton) != 0) - netHints [num++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_ACTION_CLOSE"); + auto numHints = (int) netHints.size(); - xchangeProperty (windowH, hints, XA_ATOM, 32, &netHints, num); + if (numHints > 0) + xchangeProperty (windowH, actionsAtom, XA_ATOM, 32, netHints.data(), numHints); } } @@ -2630,27 +2802,33 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const { jassert (windowH != 0); - Atom netHints [2]; + if (atoms.windowType != None) + { + Atom hint = None; - if (styleFlags & ComponentPeer::windowIsTemporary) - netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); - else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) - netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); - else - netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); + if (styleFlags & ComponentPeer::windowIsTemporary) + hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); + else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) + hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); + else + hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); - xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &netHints, 1); + if (hint != None) + xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &hint, 1); + } - int numHints = 0; + if (atoms.windowState != None) + { + std::vector netStateHints; - if ((styleFlags & ComponentPeer::windowAppearsOnTaskbar) == 0) - netHints [numHints++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_STATE_SKIP_TASKBAR"); + addAtomIfExists ((styleFlags & ComponentPeer::windowAppearsOnTaskbar) == 0, "_NET_WM_STATE_SKIP_TASKBAR", display, netStateHints); + addAtomIfExists (getPeerFor (windowH)->getComponent().isAlwaysOnTop(), "_NET_WM_STATE_ABOVE", display, netStateHints); - if (getPeerFor (windowH)->getComponent().isAlwaysOnTop()) - netHints [numHints++] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_STATE_ABOVE"); + auto numHints = (int) netStateHints.size(); - if (numHints > 0) - xchangeProperty (windowH, atoms.windowState, XA_ATOM, 32, &netHints, numHints); + if (numHints > 0) + xchangeProperty (windowH, atoms.windowState, XA_ATOM, 32, netStateHints.data(), numHints); + } } void XWindowSystem::initialisePointerMap() @@ -2683,7 +2861,7 @@ void XWindowSystem::deleteIconPixmaps (::Window windowH) const XWindowSystemUtilities::ScopedXLock xLock; - if (auto* wmHints = X11Symbols::getInstance()->xGetWMHints (display, windowH)) + if (auto wmHints = makeXFreePtr (X11Symbols::getInstance()->xGetWMHints (display, windowH))) { if ((wmHints->flags & IconPixmapHint) != 0) { @@ -2697,8 +2875,7 @@ void XWindowSystem::deleteIconPixmaps (::Window windowH) const X11Symbols::getInstance()->xFreePixmap (display, wmHints->icon_mask); } - X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); - X11Symbols::getInstance()->xFree (wmHints); + X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints.get()); } } @@ -2712,7 +2889,8 @@ void XWindowSystem::updateModifierMappings() const Keys::AltMask = 0; Keys::NumLockMask = 0; - if (auto* mapping = X11Symbols::getInstance()->xGetModifierMapping (display)) + if (auto mapping = makeDeletedPtr (X11Symbols::getInstance()->xGetModifierMapping (display), + [] (XModifierKeymap* mk) { X11Symbols::getInstance()->xFreeModifiermap (mk); })) { for (int modifierIdx = 0; modifierIdx < 8; ++modifierIdx) { @@ -2726,8 +2904,6 @@ void XWindowSystem::updateModifierMappings() const Keys::NumLockMask = 1 << modifierIdx; } } - - X11Symbols::getInstance()->xFreeModifiermap (mapping); } } @@ -2741,7 +2917,7 @@ long XWindowSystem::getUserTime (::Window windowH) const return 0; long result = 0; - memcpy (&result, prop.data, sizeof (long)); + std::memcpy (&result, prop.data, sizeof (long)); return result; } @@ -2804,16 +2980,21 @@ bool XWindowSystem::initialiseXDisplay() if (display == nullptr) return false; + #if JUCE_DEBUG_XERRORS_SYNCHRONOUSLY + X11Symbols::getInstance()->xSynchronize (display, True); + #endif + // Create a context to store user data associated with Windows we create windowHandleXContext = (XContext) X11Symbols::getInstance()->xrmUniqueQuark(); - // We're only interested in client messages for this window, which are always sent - XSetWindowAttributes swa; - swa.event_mask = NoEventMask; - // Create our message window (this will never be mapped) auto screen = X11Symbols::getInstance()->xDefaultScreen (display); auto root = X11Symbols::getInstance()->xRootWindow (display, screen); + X11Symbols::getInstance()->xSelectInput (display, root, SubstructureNotifyMask); + + // We're only interested in client messages for this window, which are always sent + XSetWindowAttributes swa; + swa.event_mask = NoEventMask; juce_messageWindowHandle = X11Symbols::getInstance()->xCreateWindow (display, root, 0, 0, 1, 1, 0, 0, InputOnly, X11Symbols::getInstance()->xDefaultVisual (display, screen), @@ -2856,15 +3037,13 @@ bool XWindowSystem::initialiseXDisplay() X11Symbols::getInstance()->xNextEvent (display, &evt); } - if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle - && handleSelectionRequest != nullptr) + if (evt.type == SelectionRequest && evt.xany.window == juce_messageWindowHandle) { - handleSelectionRequest (evt.xselectionrequest); + ClipboardHelpers::handleSelection (evt.xselectionrequest); } - else if (evt.xany.window != juce_messageWindowHandle - && dispatchWindowMessage != nullptr) + else if (evt.xany.window != juce_messageWindowHandle) { - dispatchWindowMessage (evt); + windowMessageReceive (evt); } } while (display != nullptr); @@ -2928,7 +3107,7 @@ static int64 getEventTime (const EventType& t) return getEventTime (t.time); } -void XWindowSystem::handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEvent& event) const +void XWindowSystem::handleWindowMessage (LinuxComponentPeer* peer, XEvent& event) const { switch (event.xany.type) { @@ -2950,6 +3129,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEv case GravityNotify: handleGravityNotify (peer); break; case SelectionClear: dragAndDropStateMap[peer].handleExternalSelectionClear(); break; case SelectionRequest: dragAndDropStateMap[peer].handleExternalSelectionRequest (event); break; + case PropertyNotify: propertyNotifyEvent (peer, event.xproperty); break; case CirculateNotify: case CreateNotify: @@ -2975,7 +3155,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEv } } -void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer<::Window>* peer, XKeyEvent& keyEvent) const +void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer* peer, XKeyEvent& keyEvent) const { auto oldMods = ModifierKeys::currentModifiers; @@ -3095,7 +3275,7 @@ void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer<::Window>* peer, XKe peer->handleKeyPress (keyCode, unicodeChar); } -void XWindowSystem::handleKeyReleaseEvent (LinuxComponentPeer<::Window>* peer, const XKeyEvent& keyEvent) const +void XWindowSystem::handleKeyReleaseEvent (LinuxComponentPeer* peer, const XKeyEvent& keyEvent) const { auto isKeyReleasePartOfAutoRepeat = [&]() -> bool { @@ -3134,7 +3314,7 @@ void XWindowSystem::handleKeyReleaseEvent (LinuxComponentPeer<::Window>* peer, c } } -void XWindowSystem::handleWheelEvent (LinuxComponentPeer<::Window>* peer, const XButtonPressedEvent& buttonPressEvent, float amount) const +void XWindowSystem::handleWheelEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent, float amount) const { MouseWheelDetails wheel; wheel.deltaX = 0.0f; @@ -3147,7 +3327,7 @@ void XWindowSystem::handleWheelEvent (LinuxComponentPeer<::Window>* peer, const getEventTime (buttonPressEvent), wheel); } -void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, const XButtonPressedEvent& buttonPressEvent, int buttonModifierFlag) const +void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent, int buttonModifierFlag) const { ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (buttonModifierFlag); peer->toFront (true); @@ -3156,7 +3336,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent)); } -void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, const XButtonPressedEvent& buttonPressEvent) const +void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent) const { updateKeyModifiers ((int) buttonPressEvent.state); @@ -3176,7 +3356,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, } } -void XWindowSystem::handleButtonReleaseEvent (LinuxComponentPeer<::Window>* peer, const XButtonReleasedEvent& buttonRelEvent) const +void XWindowSystem::handleButtonReleaseEvent (LinuxComponentPeer* peer, const XButtonReleasedEvent& buttonRelEvent) const { updateKeyModifiers ((int) buttonRelEvent.state); @@ -3205,7 +3385,7 @@ void XWindowSystem::handleButtonReleaseEvent (LinuxComponentPeer<::Window>* peer ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, getEventTime (buttonRelEvent)); } -void XWindowSystem::handleMotionNotifyEvent (LinuxComponentPeer<::Window>* peer, const XPointerMovedEvent& movedEvent) const +void XWindowSystem::handleMotionNotifyEvent (LinuxComponentPeer* peer, const XPointerMovedEvent& movedEvent) const { updateKeyModifiers ((int) movedEvent.state); @@ -3219,7 +3399,7 @@ void XWindowSystem::handleMotionNotifyEvent (LinuxComponentPeer<::Window>* peer, MouseInputSource::invalidOrientation, getEventTime (movedEvent)); } -void XWindowSystem::handleEnterNotifyEvent (LinuxComponentPeer<::Window>* peer, const XEnterWindowEvent& enterEvent) const +void XWindowSystem::handleEnterNotifyEvent (LinuxComponentPeer* peer, const XEnterWindowEvent& enterEvent) const { if (peer->getParentWindow() != 0) peer->updateWindowBounds(); @@ -3233,7 +3413,7 @@ void XWindowSystem::handleEnterNotifyEvent (LinuxComponentPeer<::Window>* peer, } } -void XWindowSystem::handleLeaveNotifyEvent (LinuxComponentPeer<::Window>* peer, const XLeaveWindowEvent& leaveEvent) const +void XWindowSystem::handleLeaveNotifyEvent (LinuxComponentPeer* peer, const XLeaveWindowEvent& leaveEvent) const { // Suppress the normal leave if we've got a pointer grab, or if // it's a bogus one caused by clicking a mouse button when running @@ -3248,7 +3428,7 @@ void XWindowSystem::handleLeaveNotifyEvent (LinuxComponentPeer<::Window>* peer, } } -void XWindowSystem::handleFocusInEvent (LinuxComponentPeer<::Window>* peer) const +void XWindowSystem::handleFocusInEvent (LinuxComponentPeer* peer) const { peer->isActiveApplication = true; @@ -3259,7 +3439,7 @@ void XWindowSystem::handleFocusInEvent (LinuxComponentPeer<::Window>* peer) cons } } -void XWindowSystem::handleFocusOutEvent (LinuxComponentPeer<::Window>* peer) const +void XWindowSystem::handleFocusOutEvent (LinuxComponentPeer* peer) const { if (! isFocused ((::Window) peer->getNativeHandle()) && peer->focused) { @@ -3270,7 +3450,7 @@ void XWindowSystem::handleFocusOutEvent (LinuxComponentPeer<::Window>* peer) con } } -void XWindowSystem::handleExposeEvent (LinuxComponentPeer<::Window>* peer, XExposeEvent& exposeEvent) const +void XWindowSystem::handleExposeEvent (LinuxComponentPeer* peer, XExposeEvent& exposeEvent) const { // Batch together all pending expose events XEvent nextEvent; @@ -3312,19 +3492,22 @@ void XWindowSystem::handleExposeEvent (LinuxComponentPeer<::Window>* peer, XExpo } } -void XWindowSystem::handleConfigureNotifyEvent (LinuxComponentPeer<::Window>* peer, XConfigureEvent& confEvent) const +void XWindowSystem::dismissBlockingModals (LinuxComponentPeer* peer) const +{ + if (peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()) + if (auto* currentModalComp = Component::getCurrentlyModalComponent()) + currentModalComp->inputAttemptWhenModal(); +} + +void XWindowSystem::handleConfigureNotifyEvent (LinuxComponentPeer* peer, XConfigureEvent& confEvent) const { peer->updateWindowBounds(); peer->updateBorderSize(); peer->handleMovedOrResized(); // if the native title bar is dragged, need to tell any active menus, etc. - if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) != 0 - && peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()) - { - if (auto* currentModalComp = Component::getCurrentlyModalComponent()) - currentModalComp->inputAttemptWhenModal(); - } + if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) != 0) + dismissBlockingModals (peer); auto windowH = (::Window) peer->getNativeHandle(); @@ -3332,13 +3515,44 @@ void XWindowSystem::handleConfigureNotifyEvent (LinuxComponentPeer<::Window>* pe peer->handleBroughtToFront(); } -void XWindowSystem::handleGravityNotify (LinuxComponentPeer<::Window>* peer) const +void XWindowSystem::handleGravityNotify (LinuxComponentPeer* peer) const { peer->updateWindowBounds(); peer->updateBorderSize(); peer->handleMovedOrResized(); } +void XWindowSystem::propertyNotifyEvent (LinuxComponentPeer* peer, const XPropertyEvent& event) const +{ + const auto isStateChangeEvent = [&] + { + if (event.atom != atoms.state) + return false; + + return isMinimised (event.window); + }; + + const auto isHidden = [&] + { + if (event.atom != atoms.windowState) + return false; + + XWindowSystemUtilities::ScopedXLock xLock; + XWindowSystemUtilities::GetXProperty prop (event.window, atoms.windowState, 0, 128, false, XA_ATOM); + + if (! (prop.success && prop.actualFormat == 32 && prop.actualType == XA_ATOM)) + return false; + + const auto data = reinterpret_cast (prop.data); + const auto end = data + prop.numItems; + + return std::find (data, end, atoms.windowStateHidden) != end; + }; + + if (isStateChangeEvent() || isHidden()) + dismissBlockingModals (peer); +} + void XWindowSystem::handleMappingNotify (XMappingEvent& mappingEvent) const { if (mappingEvent.request != MappingPointer) @@ -3350,7 +3564,7 @@ void XWindowSystem::handleMappingNotify (XMappingEvent& mappingEvent) const } } -void XWindowSystem::handleClientMessageEvent (LinuxComponentPeer<::Window>* peer, XClientMessageEvent& clientMsg, XEvent& event) const +void XWindowSystem::handleClientMessageEvent (LinuxComponentPeer* peer, XClientMessageEvent& clientMsg, XEvent& event) const { if (clientMsg.message_type == atoms.protocols && clientMsg.format == 32) { @@ -3422,7 +3636,7 @@ void XWindowSystem::handleClientMessageEvent (LinuxComponentPeer<::Window>* peer } } -void XWindowSystem::handleXEmbedMessage (LinuxComponentPeer<::Window>* peer, XClientMessageEvent& clientMsg) const +void XWindowSystem::handleXEmbedMessage (LinuxComponentPeer* peer, XClientMessageEvent& clientMsg) const { switch (clientMsg.data.l[1]) { @@ -3444,39 +3658,49 @@ void XWindowSystem::handleXEmbedMessage (LinuxComponentPeer<::Window>* peer, XCl } //============================================================================== -namespace WindowingHelpers +void XWindowSystem::dismissBlockingModals (LinuxComponentPeer* peer, const XConfigureEvent& configure) const { - static void windowMessageReceive (XEvent& event) + if (peer == nullptr) + return; + + const auto peerHandle = peer->getWindowHandle(); + + if (configure.window != peerHandle && isParentWindowOf (configure.window, peerHandle)) + dismissBlockingModals (peer); +} + +void XWindowSystem::windowMessageReceive (XEvent& event) +{ + if (event.xany.window != None) { - if (event.xany.window != None) + #if JUCE_X11_SUPPORTS_XEMBED + if (! juce_handleXEmbedEvent (nullptr, &event)) + #endif { - #if JUCE_X11_SUPPORTS_XEMBED - if (! juce_handleXEmbedEvent (nullptr, &event)) - #endif + if (auto* peer = dynamic_cast (getPeerFor (event.xany.window))) { - if (auto* peer = dynamic_cast*> (getPeerFor (event.xany.window))) - XWindowSystem::getInstance()->handleWindowMessage (peer, event); + XWindowSystem::getInstance()->handleWindowMessage (peer, event); + return; } - } - else if (event.xany.type == KeymapNotify) - { - auto& keymapEvent = (const XKeymapEvent&) event.xkeymap; - memcpy (Keys::keyStates, keymapEvent.key_vector, 32); + + if (event.type != ConfigureNotify) + return; + + const auto* instance = XWindowSystem::getInstance(); + + for (auto i = ComponentPeer::getNumPeers(); --i >= 0;) + instance->dismissBlockingModals (dynamic_cast (ComponentPeer::getPeer (i)), + event.xconfigure); } } -} - -struct WindowingCallbackInitialiser -{ - WindowingCallbackInitialiser() + else if (event.xany.type == KeymapNotify) { - dispatchWindowMessage = WindowingHelpers::windowMessageReceive; + auto& keymapEvent = (const XKeymapEvent&) event.xkeymap; + memcpy (Keys::keyStates, keymapEvent.key_vector, 32); } -}; - -static WindowingCallbackInitialiser windowingInitialiser; - +} +//============================================================================== JUCE_IMPLEMENT_SINGLETON (XWindowSystem) } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h index 7d20959e..3f804a2f 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h +++ b/libs/juce-current/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h @@ -85,7 +85,7 @@ namespace XWindowSystemUtilities static constexpr unsigned long DndVersion = 3; - Atom protocols, protocolList[3], changeState, state, userTime, activeWin, pid, windowType, windowState, + Atom protocols, protocolList[3], changeState, state, userTime, activeWin, pid, windowType, windowState, windowStateHidden, XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus, XdndDrop, XdndFinished, XdndSelection, XdndTypeList, XdndActionList, XdndActionDescription, XdndActionCopy, XdndActionPrivate, XembedMsgType, XembedInfo, allowedActions[5], allowedMimeTypes[4], utf8String, clipboard, targets; @@ -93,69 +93,70 @@ namespace XWindowSystemUtilities } //============================================================================== -template class LinuxComponentPeer; class XWindowSystem : public DeletedAtShutdown { public: //============================================================================== - ::Window createWindow (::Window parentWindow, LinuxComponentPeer<::Window>* peer) const; - void destroyWindow (::Window windowH); + ::Window createWindow (::Window parentWindow, LinuxComponentPeer*) const; + void destroyWindow (::Window); - void setTitle (::Window windowH, const String& title) const; - void setIcon (::Window windowH, const Image& newIcon) const; - void setVisible (::Window windowH, bool shouldBeVisible) const; - void setBounds (::Window windowH, Rectangle newBounds, bool fullScreen) const; + void setTitle (::Window, const String&) const; + void setIcon (::Window , const Image&) const; + void setVisible (::Window, bool shouldBeVisible) const; + void setBounds (::Window, Rectangle, bool fullScreen) const; - BorderSize getBorderSize (::Window windowH) const; - Rectangle getWindowBounds (::Window windowH, ::Window parentWindow); + BorderSize getBorderSize (::Window) const; + Rectangle getWindowBounds (::Window, ::Window parentWindow); Point getParentScreenPosition() const; - bool contains (::Window windowH, Point localPos) const; + bool contains (::Window, Point localPos) const; - void setMinimised (::Window windowH, bool shouldBeMinimised) const; - bool isMinimised (::Window windowH) const; + void setMinimised (::Window, bool shouldBeMinimised) const; + bool isMinimised (::Window) const; - void toFront (::Window windowH, bool makeActive) const; - void toBehind (::Window windowH, ::Window otherWindow) const; + void setMaximised (::Window, bool shouldBeMinimised) const; - bool isFocused (::Window windowH) const; - bool grabFocus (::Window windowH) const; + void toFront (::Window, bool makeActive) const; + void toBehind (::Window, ::Window otherWindow) const; + + bool isFocused (::Window) const; + bool grabFocus (::Window) const; bool canUseSemiTransparentWindows() const; bool canUseARGBImages() const; - int getNumPaintsPendingForWindow (::Window windowH); - void processPendingPaintsForWindow (::Window windowH); - void addPendingPaintForWindow (::Window windowH); - void removePendingPaintForWindow (::Window windowH); + int getNumPaintsPendingForWindow (::Window); + void processPendingPaintsForWindow (::Window); + void addPendingPaintForWindow (::Window); + void removePendingPaintForWindow (::Window); Image createImage (bool isSemiTransparentWindow, int width, int height, bool argb) const; - void blitToWindow (::Window windowH, Image image, Rectangle destinationRect, Rectangle totalRect) const; + void blitToWindow (::Window, Image, Rectangle destinationRect, Rectangle totalRect) const; void setScreenSaverEnabled (bool enabled) const; Point getCurrentMousePosition() const; void setMousePosition (Point pos) const; - void* createCustomMouseCursorInfo (const Image& image, Point hotspot) const; + void* createCustomMouseCursorInfo (const Image&, Point hotspot) const; void deleteMouseCursor (void* cursorHandle) const; - void* createStandardMouseCursor (MouseCursor::StandardCursorType type) const; - void showCursor (::Window windowH, void* cursorHandle) const; + void* createStandardMouseCursor (MouseCursor::StandardCursorType) const; + void showCursor (::Window, void* cursorHandle) const; bool isKeyCurrentlyDown (int keyCode) const; ModifierKeys getNativeRealtimeModifiers() const; Array findDisplays (float masterScale) const; - ::Window createKeyProxy (::Window windowH) const; - void deleteKeyProxy (::Window keyProxy) const; + ::Window createKeyProxy (::Window) const; + void deleteKeyProxy (::Window) const; - bool externalDragFileInit (LinuxComponentPeer<::Window>* peer, const StringArray& files, bool canMove, std::function&& callback) const; - bool externalDragTextInit (LinuxComponentPeer<::Window>* peer, const String& text, std::function&& callback) const; + bool externalDragFileInit (LinuxComponentPeer*, const StringArray& files, bool canMove, std::function&& callback) const; + bool externalDragTextInit (LinuxComponentPeer*, const String& text, std::function&& callback) const; - void copyTextToClipboard (const String& clipText); + void copyTextToClipboard (const String&); String getTextFromClipboard() const; String getLocalClipboardContent() const { return localClipboardContent; } @@ -164,7 +165,8 @@ public: XWindowSystemUtilities::Atoms& getAtoms() { return atoms; } //============================================================================== - void handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEvent& event) const; + void handleWindowMessage (LinuxComponentPeer*, XEvent&) const; + bool isParentWindowOf (::Window, ::Window possibleChild) const; //============================================================================== JUCE_DECLARE_SINGLETON (XWindowSystem, false) @@ -182,9 +184,9 @@ private: struct DisplayVisuals { - explicit DisplayVisuals (::Display* d); + explicit DisplayVisuals (::Display*); - VisualAndDepth getBestVisualForWindow (bool isSemiTransparent) const; + VisualAndDepth getBestVisualForWindow (bool) const; bool isValid() const noexcept; Visual* visual16Bit = nullptr; @@ -196,42 +198,47 @@ private: void destroyXDisplay(); //============================================================================== - ::Window getFocusWindow (::Window windowH) const; + ::Window getFocusWindow (::Window) const; - bool isParentWindowOf (::Window windowH, ::Window possibleChild) const; - bool isFrontWindow (::Window windowH) const; + bool isFrontWindow (::Window) const; //============================================================================== - void xchangeProperty (::Window windowH, Atom property, Atom type, int format, const void* data, int numElements) const; + void xchangeProperty (::Window, Atom, Atom, int, const void*, int) const; - void removeWindowDecorations (::Window windowH) const; - void addWindowButtons (::Window windowH, int styleFlags) const; - void setWindowType (::Window windowH, int styleFlags) const; + void removeWindowDecorations (::Window) const; + void addWindowButtons (::Window, int) const; + void setWindowType (::Window, int) const; void initialisePointerMap(); - void deleteIconPixmaps (::Window windowH) const; + void deleteIconPixmaps (::Window) const; void updateModifierMappings() const; - long getUserTime (::Window windowH) const; + long getUserTime (::Window) const; //============================================================================== - void handleKeyPressEvent (LinuxComponentPeer<::Window>*, XKeyEvent&) const; - void handleKeyReleaseEvent (LinuxComponentPeer<::Window>*, const XKeyEvent&) const; - void handleWheelEvent (LinuxComponentPeer<::Window>*, const XButtonPressedEvent&, float) const; - void handleButtonPressEvent (LinuxComponentPeer<::Window>*, const XButtonPressedEvent&, int) const; - void handleButtonPressEvent (LinuxComponentPeer<::Window>*, const XButtonPressedEvent&) const; - void handleButtonReleaseEvent (LinuxComponentPeer<::Window>*, const XButtonReleasedEvent&) const; - void handleMotionNotifyEvent (LinuxComponentPeer<::Window>*, const XPointerMovedEvent&) const; - void handleEnterNotifyEvent (LinuxComponentPeer<::Window>*, const XEnterWindowEvent&) const; - void handleLeaveNotifyEvent (LinuxComponentPeer<::Window>*, const XLeaveWindowEvent&) const; - void handleFocusInEvent (LinuxComponentPeer<::Window>*) const; - void handleFocusOutEvent (LinuxComponentPeer<::Window>*) const; - void handleExposeEvent (LinuxComponentPeer<::Window>*, XExposeEvent&) const; - void handleConfigureNotifyEvent (LinuxComponentPeer<::Window>*, XConfigureEvent&) const; - void handleGravityNotify (LinuxComponentPeer<::Window>*) const; + void handleKeyPressEvent (LinuxComponentPeer*, XKeyEvent&) const; + void handleKeyReleaseEvent (LinuxComponentPeer*, const XKeyEvent&) const; + void handleWheelEvent (LinuxComponentPeer*, const XButtonPressedEvent&, float) const; + void handleButtonPressEvent (LinuxComponentPeer*, const XButtonPressedEvent&, int) const; + void handleButtonPressEvent (LinuxComponentPeer*, const XButtonPressedEvent&) const; + void handleButtonReleaseEvent (LinuxComponentPeer*, const XButtonReleasedEvent&) const; + void handleMotionNotifyEvent (LinuxComponentPeer*, const XPointerMovedEvent&) const; + void handleEnterNotifyEvent (LinuxComponentPeer*, const XEnterWindowEvent&) const; + void handleLeaveNotifyEvent (LinuxComponentPeer*, const XLeaveWindowEvent&) const; + void handleFocusInEvent (LinuxComponentPeer*) const; + void handleFocusOutEvent (LinuxComponentPeer*) const; + void handleExposeEvent (LinuxComponentPeer*, XExposeEvent&) const; + void handleConfigureNotifyEvent (LinuxComponentPeer*, XConfigureEvent&) const; + void handleGravityNotify (LinuxComponentPeer*) const; + void propertyNotifyEvent (LinuxComponentPeer*, const XPropertyEvent&) const; void handleMappingNotify (XMappingEvent&) const; - void handleClientMessageEvent (LinuxComponentPeer<::Window>*, XClientMessageEvent&, XEvent&) const; - void handleXEmbedMessage (LinuxComponentPeer<::Window>*, XClientMessageEvent&) const; + void handleClientMessageEvent (LinuxComponentPeer*, XClientMessageEvent&, XEvent&) const; + void handleXEmbedMessage (LinuxComponentPeer*, XClientMessageEvent&) const; + + void dismissBlockingModals (LinuxComponentPeer*) const; + void dismissBlockingModals (LinuxComponentPeer*, const XConfigureEvent&) const; + + static void windowMessageReceive (XEvent&); //============================================================================== bool xIsAvailable = false; diff --git a/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativePoint.cpp b/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativePoint.cpp index 1fa8f4f7..90f99473 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativePoint.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativePoint.cpp @@ -30,7 +30,7 @@ namespace RelativePointHelpers { inline void skipComma (String::CharPointerType& s) { - s = s.findEndOfWhitespace(); + s.incrementToEndOfWhitespace(); if (*s == ',') ++s; diff --git a/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativeRectangle.cpp b/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativeRectangle.cpp index ac482ed6..9012bafe 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativeRectangle.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/positioning/juce_RelativeRectangle.cpp @@ -30,7 +30,7 @@ namespace RelativeRectangleHelpers { inline void skipComma (String::CharPointerType& s) { - s = s.findEndOfWhitespace(); + s.incrementToEndOfWhitespace(); if (*s == ',') ++s; diff --git a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp index 6187f2eb..17c1e84a 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp @@ -154,10 +154,8 @@ ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, const Array& correspondingValues) : ChoicePropertyComponent (name, choiceList, correspondingValues) { - createComboBox(); - - comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSource (valueToControl, - correspondingValues))); + refreshChoices(); + initialiseComboBox (Value (new RemapperValueSource (valueToControl, correspondingValues))); } ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault& valueToControl, @@ -168,18 +166,15 @@ ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault& valueToContr { valueWithDefault = &valueToControl; - createComboBoxWithDefault (choiceList [correspondingValues.indexOf (valueWithDefault->getDefault())]); + auto getDefaultString = [this, correspondingValues] { return choices [correspondingValues.indexOf (valueWithDefault->getDefault())]; }; - comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSourceWithDefault (valueWithDefault, - correspondingValues))); + refreshChoices (getDefaultString()); + initialiseComboBox (Value (new RemapperValueSourceWithDefault (valueWithDefault, correspondingValues))); - valueWithDefault->onDefaultChange = [this, choiceList, correspondingValues] + valueWithDefault->onDefaultChange = [this, getDefaultString] { auto selectedId = comboBox.getSelectedId(); - - comboBox.clear(); - createComboBoxWithDefault (choiceList [correspondingValues.indexOf (valueWithDefault->getDefault())]); - + refreshChoices (getDefaultString()); comboBox.setSelectedId (selectedId); }; } @@ -191,18 +186,15 @@ ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault& valueToContr { valueWithDefault = &valueToControl; - createComboBoxWithDefault (valueWithDefault->getDefault() ? "Enabled" : "Disabled"); + auto getDefaultString = [this] { return valueWithDefault->getDefault() ? "Enabled" : "Disabled"; }; - comboBox.getSelectedIdAsValue().referTo (Value (new RemapperValueSourceWithDefault (valueWithDefault, - { true, false }))); + refreshChoices (getDefaultString()); + initialiseComboBox (Value (new RemapperValueSourceWithDefault (valueWithDefault, { true, false }))); - valueWithDefault->onDefaultChange = [this] + valueWithDefault->onDefaultChange = [this, getDefaultString] { auto selectedId = comboBox.getSelectedId(); - - comboBox.clear(); - createComboBoxWithDefault (valueWithDefault->getDefault() ? "Enabled" : "Disabled"); - + refreshChoices (getDefaultString()); comboBox.setSelectedId (selectedId); }; } @@ -214,26 +206,21 @@ ChoicePropertyComponent::~ChoicePropertyComponent() } //============================================================================== -void ChoicePropertyComponent::createComboBox() +void ChoicePropertyComponent::initialiseComboBox (const Value& v) { - addAndMakeVisible (comboBox); - - for (auto choice : choices) + if (v != Value()) { - if (choice.isNotEmpty()) - comboBox.addItem (choice, choices.indexOf (choice) + 1); - else - comboBox.addSeparator(); + comboBox.setSelectedId (v.getValue(), dontSendNotification); + comboBox.getSelectedIdAsValue().referTo (v); } comboBox.setEditableText (false); + addAndMakeVisible (comboBox); } -void ChoicePropertyComponent::createComboBoxWithDefault (const String& defaultString) +void ChoicePropertyComponent::refreshChoices() { - addAndMakeVisible (comboBox); - - comboBox.addItem ("Default" + (defaultString.isNotEmpty() ? " (" + defaultString + ")" : ""), -1); + comboBox.clear(); for (auto choice : choices) { @@ -242,10 +229,15 @@ void ChoicePropertyComponent::createComboBoxWithDefault (const String& defaultSt else comboBox.addSeparator(); } +} - comboBox.setEditableText (false); +void ChoicePropertyComponent::refreshChoices (const String& defaultString) +{ + refreshChoices(); + comboBox.addItem ("Default" + (defaultString.isNotEmpty() ? " (" + defaultString + ")" : ""), -1); } +//============================================================================== void ChoicePropertyComponent::setIndex (const int /*newIndex*/) { jassertfalse; // you need to override this method in your subclass! @@ -269,7 +261,8 @@ void ChoicePropertyComponent::refresh() { if (! comboBox.isVisible()) { - createComboBox(); + refreshChoices(); + initialiseComboBox ({}); comboBox.onChange = [this] { changeIndex(); }; } diff --git a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h index 93cb8bbf..4b1ee39e 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h +++ b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h @@ -143,8 +143,9 @@ private: class RemapperValueSourceWithDefault; //============================================================================== - void createComboBox(); - void createComboBoxWithDefault (const String&); + void initialiseComboBox (const Value&); + void refreshChoices(); + void refreshChoices (const String&); void changeIndex(); diff --git a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp index aee1c82e..b9a3aff2 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.cpp @@ -30,9 +30,11 @@ struct PropertyPanel::SectionComponent : public Component { SectionComponent (const String& sectionTitle, const Array& newProperties, - bool sectionIsOpen) + bool sectionIsOpen, + int extraPadding) : Component (sectionTitle), - isOpen (sectionIsOpen) + isOpen (sectionIsOpen), + padding (extraPadding) { lookAndFeelChanged(); @@ -63,7 +65,7 @@ struct PropertyPanel::SectionComponent : public Component for (auto* propertyComponent : propertyComps) { propertyComponent->setBounds (1, y, getWidth() - 2, propertyComponent->getPreferredHeight()); - y = propertyComponent->getBottom(); + y = propertyComponent->getBottom() + padding; } } @@ -78,10 +80,16 @@ struct PropertyPanel::SectionComponent : public Component { auto y = titleHeight; - if (isOpen) + auto numComponents = propertyComps.size(); + + if (numComponents > 0 && isOpen) + { for (auto* propertyComponent : propertyComps) y += propertyComponent->getPreferredHeight(); + y += (numComponents - 1) * padding; + } + return y; } @@ -122,6 +130,7 @@ struct PropertyPanel::SectionComponent : public Component OwnedArray propertyComps; int titleHeight; bool isOpen; + int padding; JUCE_DECLARE_NON_COPYABLE (SectionComponent) }; @@ -241,26 +250,32 @@ int PropertyPanel::getTotalContentHeight() const return propertyHolderComponent->getHeight(); } -void PropertyPanel::addProperties (const Array& newProperties) +void PropertyPanel::addProperties (const Array& newProperties, + int extraPaddingBetweenComponents) { if (isEmpty()) repaint(); - propertyHolderComponent->insertSection (-1, new SectionComponent (String(), newProperties, true)); + propertyHolderComponent->insertSection (-1, new SectionComponent ({}, newProperties, true, extraPaddingBetweenComponents)); updatePropHolderLayout(); } void PropertyPanel::addSection (const String& sectionTitle, const Array& newProperties, bool shouldBeOpen, - int indexToInsertAt) + int indexToInsertAt, + int extraPaddingBetweenComponents) { jassert (sectionTitle.isNotEmpty()); if (isEmpty()) repaint(); - propertyHolderComponent->insertSection (indexToInsertAt, new SectionComponent (sectionTitle, newProperties, shouldBeOpen)); + propertyHolderComponent->insertSection (indexToInsertAt, new SectionComponent (sectionTitle, + newProperties, + shouldBeOpen, + extraPaddingBetweenComponents)); + updatePropHolderLayout(); } @@ -352,7 +367,7 @@ void PropertyPanel::restoreOpennessState (const XmlElement& xml) { auto sections = getSectionNames(); - forEachXmlChildElementWithTagName (xml, e, "SECTION") + for (auto* e : xml.getChildWithTagNameIterator ("SECTION")) { setSectionOpen (sections.indexOf (e->getStringAttribute ("name")), e->getBoolAttribute ("open")); diff --git a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h index 94bafef8..b1272839 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h +++ b/libs/juce-current/source/modules/juce_gui_basics/properties/juce_PropertyPanel.h @@ -65,7 +65,8 @@ public: These properties are added without them being inside a named section. If you want them to be kept together in a collapsible section, use addSection() instead. */ - void addProperties (const Array& newPropertyComponents); + void addProperties (const Array& newPropertyComponents, + int extraPaddingBetweenComponents = 0); /** Adds a set of properties to the panel. @@ -81,7 +82,8 @@ public: void addSection (const String& sectionTitle, const Array& newPropertyComponents, bool shouldSectionInitiallyBeOpen = true, - int indexToInsertAt = -1); + int indexToInsertAt = -1, + int extraPaddingBetweenComponents = 0); /** Calls the refresh() method of all PropertyComponents in the panel */ void refreshAll() const; diff --git a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_ComboBox.h b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_ComboBox.h index 905da938..f6d67cb7 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_ComboBox.h +++ b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_ComboBox.h @@ -272,7 +272,10 @@ public: /** Returns the PopupMenu object associated with the ComboBox. Can be useful for adding sub-menus to the ComboBox standard PopupMenu */ - PopupMenu* getRootMenu() { return ¤tMenu; } + PopupMenu* getRootMenu() noexcept { return ¤tMenu; } + + /** Returns the PopupMenu object associated with the ComboBox. */ + const PopupMenu* getRootMenu() const noexcept { return ¤tMenu; } //============================================================================== /** diff --git a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp index 0309219f..1ef18057 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TableHeaderComponent.cpp @@ -439,7 +439,7 @@ void TableHeaderComponent::restoreFromString (const String& storedVersion) { int index = 0; - forEachXmlChildElement (*storedXML, col) + for (auto* col : storedXML->getChildIterator()) { auto tabId = col->getIntAttribute ("id"); diff --git a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index 0b21f75c..233c2330 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -278,8 +278,8 @@ struct TextEditor::Iterator Iterator (const TextEditor& ed) : sections (ed.sections), justification (ed.justification), - bottomRight (ed.getMaximumWidth(), ed.getMaximumHeight()), - wordWrapWidth (ed.getWordWrapWidth()), + bottomRight ((float) ed.getMaximumTextWidth(), (float) ed.getMaximumTextHeight()), + wordWrapWidth ((float) ed.getWordWrapWidth()), passwordCharacter (ed.passwordCharacter), lineSpacing (ed.lineSpacing), underlineWhitespace (ed.underlineWhitespace) @@ -669,6 +669,18 @@ struct TextEditor::Iterator return bottom * 0.5f; } + int getTotalTextHeight() + { + while (next()) {} + + auto height = lineY + lineHeight + getYOffset(); + + if (atom != nullptr && atom->isNewLine()) + height += lineHeight; + + return roundToInt (height); + } + //============================================================================== int indexInText = 0; float lineY = 0, lineHeight = 0, maxDescent = 0; @@ -866,7 +878,7 @@ struct TextEditor::TextEditorViewport : public Viewport private: TextEditor& owner; - float lastWordWrapWidth = 0; + int lastWordWrapWidth = 0; bool reentrant = false; JUCE_DECLARE_NON_COPYABLE (TextEditorViewport) @@ -1328,7 +1340,10 @@ void TextEditor::moveCaret (int newCaretPos) if (newCaretPos != getCaretPosition()) { caretPosition = newCaretPos; - textHolder->restartTimer(); + + if (hasKeyboardFocus (false)) + textHolder->restartTimer(); + scrollToMakeSureCursorIsVisible(); updateCaretPosition(); } @@ -1401,63 +1416,34 @@ Rectangle TextEditor::getCaretRectangleFloat() const // Extra space for the cursor at the right-hand-edge constexpr int rightEdgeSpace = 2; -float TextEditor::getWordWrapWidth() const +int TextEditor::getWordWrapWidth() const { - return wordWrap ? getMaximumWidth() - : std::numeric_limits::max(); + return wordWrap ? getMaximumTextWidth() + : std::numeric_limits::max(); } -float TextEditor::getMaximumWidth() const +int TextEditor::getMaximumTextWidth() const { - return (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1)); + return viewport->getMaximumVisibleWidth() - leftIndent + rightEdgeSpace; } -float TextEditor::getMaximumHeight() const +int TextEditor::getMaximumTextHeight() const { - return (float) (viewport->getMaximumVisibleHeight() - topIndent); + return viewport->getMaximumVisibleHeight() - topIndent; } void TextEditor::checkLayout() { if (getWordWrapWidth() > 0) { - auto maxWidth = getMaximumWidth(); - Iterator i (*this); - - while (i.next()) - maxWidth = jmax (maxWidth, i.atomRight); - - auto textRight = roundToInt (maxWidth); - auto textBottom = roundToInt (i.lineY + i.lineHeight + i.getYOffset()); - - if (i.atom != nullptr && i.atom->isNewLine()) - textBottom += (int) i.lineHeight; - - updateTextHolderSize (textRight, textBottom); - updateScrollbarVisibility (textRight, textBottom); - } -} - -void TextEditor::updateTextHolderSize (int textRight, int textBottom) -{ - auto w = leftIndent + jmax (roundToInt (getMaximumWidth()), textRight); - auto h = topIndent + textBottom; - - textHolder->setSize (w + rightEdgeSpace, h + 1); -} - -void TextEditor::updateScrollbarVisibility (int textRight, int textBottom) -{ - if (scrollbarVisible && multiline) - { - auto horizontalVisible = (leftIndent + textRight) > (viewport->getMaximumVisibleWidth() - viewport->getScrollBarThickness()); - auto verticalVisible = (topIndent + textBottom) > (viewport->getMaximumVisibleHeight() + 1); + auto textBottom = Iterator (*this).getTotalTextHeight() + topIndent; + auto textRight = getMaximumTextWidth() + leftIndent + rightEdgeSpace; - viewport->setScrollBarsShown (verticalVisible, horizontalVisible); - } - else - { - viewport->setScrollBarsShown (false, false); + textHolder->setSize (textRight, textBottom); + viewport->setScrollBarsShown (scrollbarVisible + && multiline + && (textBottom > viewport->getMaximumVisibleHeight()), + false); } } @@ -1730,10 +1716,13 @@ void TextEditor::paintOverChildren (Graphics& g) g.setColour (colourForTextWhenEmpty); g.setFont (getFont()); - g.drawText (textToShowWhenEmpty, - leftIndent, topIndent, - viewport->getWidth() - leftIndent, getHeight() - topIndent, - justification, true); + Rectangle textBounds (leftIndent, + topIndent, + viewport->getWidth() - leftIndent, + getHeight() - topIndent); + + if (! textBounds.isEmpty()) + g.drawText (textToShowWhenEmpty, textBounds, justification, true); } getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); diff --git a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.h b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.h index 1ce56c66..dfbe49ba 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -793,11 +793,9 @@ private: bool moveCaretWithTransaction (int newPos, bool selecting); void drawContent (Graphics&); void checkLayout(); - void updateTextHolderSize (int, int); - void updateScrollbarVisibility (int, int); - float getWordWrapWidth() const; - float getMaximumWidth() const; - float getMaximumHeight() const; + int getWordWrapWidth() const; + int getMaximumTextWidth() const; + int getMaximumTextHeight() const; void timerCallbackInt(); void checkFocus(); void repaintText (Range); diff --git a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp index b6d4d1cc..d4391222 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -646,7 +646,7 @@ void TreeView::restoreOpennessState (const XmlElement& newState, const bool rest { clearSelectedItems(); - forEachXmlChildElementWithTagName (newState, e, "SELECTED") + for (auto* e : newState.getChildWithTagNameIterator ("SELECTED")) if (auto* item = rootItem->findItemFromIdentifierString (e->getStringAttribute ("id"))) item->setSelected (true, false); } @@ -1834,7 +1834,7 @@ void TreeViewItem::restoreOpennessState (const XmlElement& e) Array items; items.addArray (subItems); - forEachXmlChildElement (e, n) + for (auto* n : e.getChildIterator()) { auto id = n->getStringAttribute ("id"); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_AlertWindow.h b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_AlertWindow.h index 1b234c43..d73eb07b 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_AlertWindow.h +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_AlertWindow.h @@ -228,7 +228,7 @@ public: //============================================================================== // easy-to-use message box functions: - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Shows a dialog box that just has a message and a single button to get rid of it. The box is shown modally, and the method will block until the user has clicked the @@ -394,7 +394,7 @@ public: it'll show a box with just an ok button @returns true if the ok button was pressed, false if they pressed cancel. */ - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN static bool JUCE_CALLTYPE showNativeDialogBox (const String& title, const String& bodyText, bool isOkCancel); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_CallOutBox.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_CallOutBox.cpp index 9cbd6035..bab4c696 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_CallOutBox.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_CallOutBox.cpp @@ -40,7 +40,7 @@ CallOutBox::CallOutBox (Component& c, Rectangle area, Component* const pare else { setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); - updatePosition (area, Desktop::getInstance().getDisplays().findDisplayForRect (area).userArea); + updatePosition (area, Desktop::getInstance().getDisplays().getDisplayForRect (area)->userArea); addToDesktop (ComponentPeer::windowIsTemporary); startTimer (100); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index 90ad1b05..8d7febd4 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -584,5 +584,10 @@ ModifierKeys ComponentPeer::getCurrentModifiersRealtime() noexcept return ModifierKeys::currentModifiers; } +//============================================================================== +void ComponentPeer::forceDisplayUpdate() +{ + Desktop::getInstance().displays->refresh(); +} } // namespace juce diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h index b5d4577e..ed4f3c19 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -70,7 +70,8 @@ public: performAnyPendingRepaintsNow() method is called. */ windowIgnoresKeyPresses = (1 << 10), /**< Tells the window not to catch any keypresses. This can be used for things like plugin windows, to stop them interfering - with the host's shortcut keys */ + with the host's shortcut keys. This will prevent the window from + gaining keyboard focus. */ windowIsSemiTransparent = (1 << 30) /**< Not intended for public use - makes a window transparent. */ }; @@ -414,6 +415,8 @@ public: protected: //============================================================================== + static void forceDisplayUpdate(); + Component& component; const int styleFlags; Rectangle lastNonFullscreenBounds; @@ -423,12 +426,14 @@ protected: private: //============================================================================== + Component* getTargetForKeyPress(); + WeakReference lastFocusedComponent, dragAndDropTargetComponent; Component* lastDragAndDropCompUnderMouse = nullptr; const uint32 uniqueID; bool isWindowMinimised = false; - Component* getTargetForKeyPress(); + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentPeer) }; diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp index 92bf09e1..b7510ac1 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_DialogWindow.cpp @@ -117,7 +117,7 @@ DialogWindow* DialogWindow::LaunchOptions::launchAsync() return d; } -#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN +#if JUCE_MODAL_LOOPS_PERMITTED int DialogWindow::LaunchOptions::runModal() { return launchAsync()->runModalLoop(); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_NativeMessageBox.h b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_NativeMessageBox.h index 70b097a5..5a5e86d7 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_NativeMessageBox.h +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_NativeMessageBox.h @@ -47,7 +47,7 @@ public: alert window should be associated with. Depending on the look and feel, this might be used for positioning of the alert window. */ - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN static void JUCE_CALLTYPE showMessageBox (AlertWindow::AlertIconType iconType, const String& title, const String& message, diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp index d1d20d6c..42bd2d9d 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ResizableWindow.cpp @@ -541,7 +541,7 @@ bool ResizableWindow::restoreWindowStateFromString (const String& s) if (onScreenArea.getWidth() * onScreenArea.getHeight() < 32 * 32) { - auto screen = desktop.getDisplays().findDisplayForRect (newPos).userArea; + auto screen = desktop.getDisplays().getDisplayForRect (newPos)->userArea; newPos.setSize (jmin (newPos.getWidth(), screen.getWidth()), jmin (newPos.getHeight(), screen.getHeight())); diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h index 91664e0c..8c892d17 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_ThreadWithProgressWindow.h @@ -113,7 +113,7 @@ public: ~ThreadWithProgressWindow() override; //============================================================================== - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** Starts the thread and waits for it to finish. This will start the thread, make the dialog box appear, and wait until either diff --git a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp index f0623784..c893c8d9 100644 --- a/libs/juce-current/source/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp +++ b/libs/juce-current/source/modules/juce_gui_basics/windows/juce_TooltipWindow.cpp @@ -91,7 +91,7 @@ void TooltipWindow::displayTip (Point screenPos, const String& tip) } else { - updatePosition (tip, screenPos, Desktop::getInstance().getDisplays().findDisplayForPoint (screenPos).userArea); + updatePosition (tip, screenPos, Desktop::getInstance().getDisplays().getDisplayForPoint (screenPos)->userArea); addToDesktop (ComponentPeer::windowHasDropShadow | ComponentPeer::windowIsTemporary diff --git a/libs/juce-current/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index 0ac45323..08f4546b 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -348,9 +348,6 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& doc, CodeTokeniser* cons setMouseCursor (MouseCursor::IBeamCursor); setWantsKeyboardFocus (true); - lookAndFeelChanged(); - addAndMakeVisible (caret.get()); - addAndMakeVisible (verticalScrollBar); verticalScrollBar.setSingleStepSize (1.0); @@ -369,6 +366,8 @@ CodeEditorComponent::CodeEditorComponent (CodeDocument& doc, CodeTokeniser* cons verticalScrollBar.addListener (pimpl.get()); horizontalScrollBar.addListener (pimpl.get()); document.addListener (pimpl.get()); + + lookAndFeelChanged(); } CodeEditorComponent::~CodeEditorComponent() @@ -405,7 +404,10 @@ void CodeEditorComponent::setTemporaryUnderlining (const Array>&) Rectangle CodeEditorComponent::getCaretRectangle() { - return getLocalArea (caret.get(), caret->getLocalBounds()); + if (caret != nullptr) + return getLocalArea (caret.get(), caret->getLocalBounds()); + + return {}; } void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown) @@ -582,7 +584,8 @@ void CodeEditorComponent::retokenise (int startIndex, int endIndex) //============================================================================== void CodeEditorComponent::updateCaretPosition() { - caret->setCaretPosition (getCharacterBounds (getCaretPos())); + if (caret != nullptr) + caret->setCaretPosition (getCharacterBounds (getCaretPos())); } void CodeEditorComponent::moveCaretTo (const CodeDocument::Position& newPos, const bool highlighting) @@ -1318,6 +1321,7 @@ bool CodeEditorComponent::perform (const InvocationInfo& info) void CodeEditorComponent::lookAndFeelChanged() { caret.reset (getLookAndFeel().createCaretComponent (this)); + addAndMakeVisible (*caret); } bool CodeEditorComponent::performCommand (const CommandID commandID) diff --git a/libs/juce-current/source/modules/juce_gui_extra/documents/juce_FileBasedDocument.h b/libs/juce-current/source/modules/juce_gui_extra/documents/juce_FileBasedDocument.h index 8e929c76..6515131a 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/documents/juce_FileBasedDocument.h +++ b/libs/juce-current/source/modules/juce_gui_extra/documents/juce_FileBasedDocument.h @@ -277,7 +277,7 @@ protected: */ virtual void setLastDocumentOpened (const File& file) = 0; - #if JUCE_MODAL_LOOPS_PERMITTED + #if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN /** This is called by saveAsInteractive() to allow you to optionally customise the filename that the user is presented with in the save dialog. The defaultFile parameter is an initial suggestion based on what the class knows diff --git a/libs/juce-current/source/modules/juce_gui_extra/embedding/juce_HWNDComponent.h b/libs/juce-current/source/modules/juce_gui_extra/embedding/juce_HWNDComponent.h index 34761652..dc7c4492 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/embedding/juce_HWNDComponent.h +++ b/libs/juce-current/source/modules/juce_gui_extra/embedding/juce_HWNDComponent.h @@ -71,6 +71,9 @@ public: /** Resizes this component to fit the HWND that it contains. */ void resizeToFit(); + /** @internal */ + void paint (Graphics&) override; + private: class Pimpl; std::unique_ptr pimpl; diff --git a/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.cpp b/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.cpp index df48ccea..d42555e1 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.cpp @@ -40,10 +40,6 @@ #define JUCE_GRAPHICS_INCLUDE_COREGRAPHICS_HELPERS 1 #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 -#if JUCE_USE_WIN_WEBVIEW2 - #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 -#endif - #ifndef JUCE_PUSH_NOTIFICATIONS #define JUCE_PUSH_NOTIFICATIONS 0 #endif @@ -97,18 +93,16 @@ #include #include - #pragma warning (push) - #pragma warning (disable: 4265) + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265) #include #include - #pragma warning (pop) + JUCE_END_IGNORE_WARNINGS_MSVC #include "WebView2.h" - #pragma warning (push) - #pragma warning (disable: 4458) + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4458) #include "WebView2EnvironmentOptions.h" - #pragma warning (pop) + JUCE_END_IGNORE_WARNINGS_MSVC #endif #endif @@ -151,8 +145,6 @@ //============================================================================== #if JUCE_MAC || JUCE_IOS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - #if JUCE_MAC #include "native/juce_mac_NSViewComponent.mm" #include "native/juce_mac_AppleRemote.mm" @@ -169,8 +161,6 @@ #include "native/juce_mac_WebBrowserComponent.mm" #endif - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - //============================================================================== #elif JUCE_WINDOWS #include "native/juce_win32_ActiveXComponent.cpp" diff --git a/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.h b/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.h index b7bd9a98..062bde63 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.h +++ b/libs/juce-current/source/modules/juce_gui_extra/juce_gui_extra.h @@ -35,7 +35,7 @@ ID: juce_gui_extra vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE extended GUI classes description: Miscellaneous GUI classes for specialised tasks. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp index ce6ea14f..4b422fac 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.cpp @@ -213,7 +213,7 @@ void LivePropertyEditorBase::findOriginalValueInCode() } p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1); - p = p.findEndOfWhitespace(); + p.incrementToEndOfWhitespace(); if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty()) { diff --git a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h index a49bc01c..85d9ad10 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h +++ b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_LiveConstantEditor.h @@ -137,18 +137,18 @@ namespace LiveConstantEditor Component* createBoolSlider (LivePropertyEditorBase&); template struct CustomEditor { static Component* create (LivePropertyEditorBase&) { return nullptr; } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } }; - template<> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createBoolSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } }; + template <> struct CustomEditor { static Component* create (LivePropertyEditorBase& e) { return createBoolSlider (e); } }; template struct LivePropertyEditor : public LivePropertyEditorBase diff --git a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_SplashScreen.cpp b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_SplashScreen.cpp index 04d3fbe1..9169cfb1 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/misc/juce_SplashScreen.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/misc/juce_SplashScreen.cpp @@ -57,7 +57,7 @@ void SplashScreen::makeVisible (int w, int h, bool useDropShadow, bool fullscree clickCountToDelete = Desktop::getInstance().getMouseButtonClickCounter(); creationTime = Time::getCurrentTime(); - const Rectangle screenSize = Desktop::getInstance().getDisplays().getMainDisplay().userArea; + const Rectangle screenSize = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; const int width = (fullscreen ? screenSize.getWidth() : w); const int height = (fullscreen ? screenSize.getHeight() : h); diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp index 931a80da..8f80566a 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp @@ -52,7 +52,7 @@ public: auto pos = topComp->getLocalPoint (&owner, Point()); Rectangle r (pos.x, pos.y, owner.getWidth(), owner.getHeight()); - r *= Desktop::getInstance().getDisplays().getMainDisplay().scale; + r *= Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(), r.getRight(), r.getBottom()); diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp index eee4c210..7ad40ee3 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp @@ -127,14 +127,14 @@ private: clearSingletonInstance(); } - template + template struct SymbolBinding { FuncPtr& func; const char* name; }; - template + template SymbolBinding makeSymbolBinding (FuncPtr& func, const char* name) { return { func, name }; @@ -152,7 +152,7 @@ private: return false; } - template + template bool loadSymbols (DynamicLibrary& lib, SymbolBinding binding, Args... args) { return loadSymbols (lib, binding) && loadSymbols (lib, args...); diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp index 2d973167..3cc7a8e3 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_linux_XEmbedComponent.cpp @@ -438,7 +438,7 @@ private: auto& displays = Desktop::getInstance().getDisplays(); auto* peer = owner.getPeer(); const double scale = (peer != nullptr ? peer->getPlatformScaleFactor() - : displays.getMainDisplay().scale); + : displays.getPrimaryDisplay()->scale); Point topLeftInPeer = (peer != nullptr ? peer->getComponent().getLocalPoint (&owner, Point (0, 0)) diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm index a4d997a8..6326283b 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_NSViewComponent.mm @@ -42,10 +42,12 @@ struct NSViewResizeWatcher callback = [cls.createInstance() init]; ViewFrameChangeCallbackClass::setTarget (callback, this); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [[NSNotificationCenter defaultCenter] addObserver: callback selector: @selector (frameChanged:) name: NSViewFrameDidChangeNotification object: view]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } void detachViewWatcher() @@ -69,7 +71,11 @@ private: ViewFrameChangeCallbackClass() : ObjCClass ("JUCE_NSViewCallback_") { addIvar ("target"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (frameChanged:), frameChanged, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + registerClass(); } diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp index 17880e77..f72d9378 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_PushNotifications.cpp @@ -272,10 +272,10 @@ struct PushNotificationsDelegate id appDelegate = [[NSApplication sharedApplication] delegate]; - SEL selector = NSSelectorFromString (@"setPushNotificationsDelegate:"); - - if ([appDelegate respondsToSelector: selector]) - [appDelegate performSelector: selector withObject: delegate.get()]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegate:)]) + [appDelegate performSelector: @selector (setPushNotificationsDelegate:) withObject: delegate.get()]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE [NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate.get(); } diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp index b92b3b37..f40d1668 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp @@ -223,10 +223,12 @@ struct ViewBasedStatusItem : public StatusItemContainer SystemTrayViewClass::frameChanged (view.get(), SEL(), nullptr); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [[NSNotificationCenter defaultCenter] addObserver: view.get() selector: @selector (frameChanged:) name: NSWindowDidMoveNotification object: nil]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } ~ViewBasedStatusItem() override @@ -312,7 +314,10 @@ struct ViewBasedStatusItem : public StatusItemContainer addMethod (@selector (mouseDown:), handleEventDown, "v@:@"); addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@"); addMethod (@selector (drawRect:), drawRect, "v@:@"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (frameChanged:), frameChanged, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE registerClass(); } diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 13d21c9a..3ba0a42f 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -26,10 +26,14 @@ namespace juce { -#if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) \ +#if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) #define JUCE_USE_WKWEBVIEW 1 + #if (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11) + #define WKWEBVIEW_WEBVIEWDIDCLOSE_SUPPORTED 1 + #endif + #if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) #define WKWEBVIEW_OPENPANEL_SUPPORTED 1 #endif @@ -41,9 +45,9 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr NSString* urlString = juceStringToNS (url); #if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9) - urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLQueryAllowedCharacterSet]]; #else - urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + urlString = [urlString stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]; #endif if (NSURL* nsURL = [NSURL URLWithString: urlString]) @@ -78,6 +82,63 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr return nullptr; } +#if JUCE_MAC + +#if JUCE_USE_WKWEBVIEW + using WebViewBase = ObjCClass; +#else + using WebViewBase = ObjCClass; +#endif + +struct WebViewKeyEquivalentResponder : public WebViewBase +{ + WebViewKeyEquivalentResponder() + : WebViewBase ("WebViewKeyEquivalentResponder_") + { + addIvar ("owner"); + addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); + registerClass(); + } + +private: + static WebViewKeyEquivalentResponder* getOwner (id self) + { + return getIvar (self, "owner"); + } + + static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event) + { + NSResponder* first = [[self window] firstResponder]; + + #if (defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) + constexpr auto mask = NSEventModifierFlagDeviceIndependentFlagsMask; + constexpr auto key = NSEventModifierFlagCommand; + #else + constexpr auto mask = NSDeviceIndependentModifierFlagsMask; + constexpr auto key = NSCommandKeyMask; + #endif + + if (([event modifierFlags] & mask) == key) + { + auto sendAction = [&] (SEL actionSelector) -> BOOL + { + return [NSApp sendAction: actionSelector + to: first + from: self]; + }; + + if ([[event charactersIgnoringModifiers] isEqualToString: @"x"]) return sendAction (@selector (cut:)); + if ([[event charactersIgnoringModifiers] isEqualToString: @"c"]) return sendAction (@selector (copy:)); + if ([[event charactersIgnoringModifiers] isEqualToString: @"v"]) return sendAction (@selector (paste:)); + if ([[event charactersIgnoringModifiers] isEqualToString: @"a"]) return sendAction (@selector (selectAll:)); + } + + return sendSuperclassMessage (self, selector, event); + } +}; + +#endif + #if JUCE_USE_WKWEBVIEW struct WebViewDelegateClass : public ObjCClass @@ -91,13 +152,15 @@ struct WebViewDelegateClass : public ObjCClass addMethod (@selector (webView:didFailNavigation:withError:), didFailNavigation, "v@:@@@"); addMethod (@selector (webView:didFailProvisionalNavigation:withError:), didFailProvisionalNavigation, "v@:@@@"); - addMethod (@selector (webView:webViewDidClose:), webViewDidClose, "v@:@"); + #if WKWEBVIEW_WEBVIEWDIDCLOSE_SUPPORTED + addMethod (@selector (webViewDidClose:), webViewDidClose, "v@:@"); + #endif + addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction: windowFeatures:), createWebView, "@@:@@@@"); #if WKWEBVIEW_OPENPANEL_SUPPORTED - addMethod (@selector (webView:runOpenPanelWithParameters: - initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@"); + addMethod (@selector (webView:runOpenPanelWithParameters:initiatedByFrame:completionHandler:), runOpenPanel, "v@:@@@@"); #endif registerClass(); @@ -144,10 +207,12 @@ private: displayError (getOwner (self), error); } + #if WKWEBVIEW_WEBVIEWDIDCLOSE_SUPPORTED static void webViewDidClose (id self, SEL, WKWebView*) { getOwner (self)->windowCloseRequest(); } + #endif static WKWebView* createWebView (id self, SEL, WKWebView*, WKWebViewConfiguration*, WKNavigationAction* navigationAction, WKWindowFeatures*) @@ -211,12 +276,17 @@ public: #if JUCE_MAC auto frame = NSMakeRect (0, 0, 100.0f, 100.0f); + + static WebViewKeyEquivalentResponder webviewClass; + webView = (WKWebView*) webviewClass.createInstance(); + + webView = [webView initWithFrame: frame + configuration: config]; #else auto frame = CGRectMake (0, 0, 100.0f, 100.0f); - #endif - webView = [[WKWebView alloc] initWithFrame: frame configuration: config]; + #endif static WebViewDelegateClass cls; webViewDelegate = [cls.createInstance() init]; @@ -242,12 +312,24 @@ public: const StringArray* headers, const MemoryBlock* postData) { - stop(); + auto trimmed = url.trimStart(); - if (url.trimStart().startsWithIgnoreCase ("javascript:")) + if (trimmed.startsWithIgnoreCase ("javascript:")) { [webView evaluateJavaScript: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false)) completionHandler: nil]; + + return; + } + + stop(); + + if (trimmed.startsWithIgnoreCase ("file:")) + { + auto file = URL (url).getLocalFile(); + + if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) + [webView loadFileURL: nsUrl allowingReadAccessToURL: nsUrl]; } else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData)) { @@ -270,37 +352,6 @@ private: #if JUCE_MAC -struct WebViewKeyEquivalentResponder : public ObjCClass -{ - WebViewKeyEquivalentResponder() : ObjCClass ("WebViewKeyEquivalentResponder_") - { - addIvar ("owner"); - addMethod (@selector (performKeyEquivalent:), performKeyEquivalent, @encode (BOOL), "@:@"); - registerClass(); - } - -private: - static WebViewKeyEquivalentResponder* getOwner (id self) - { - return getIvar (self, "owner"); - } - - static BOOL performKeyEquivalent (id self, SEL selector, NSEvent* event) - { - NSResponder* first = [[self window] firstResponder]; - - if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) == NSCommandKeyMask) - { - if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) return [NSApp sendAction:@selector(cut:) to:first from:self]; - if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) return [NSApp sendAction:@selector(copy:) to:first from:self]; - if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) return [NSApp sendAction:@selector(paste:) to:first from:self]; - if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) return [NSApp sendAction:@selector(selectAll:) to:first from:self]; - } - - return sendSuperclassMessage (self, selector, event); - } -}; - struct DownloadClickDetectorClass : public ObjCClass { DownloadClickDetectorClass() : ObjCClass ("JUCEWebClickDetector_") @@ -492,14 +543,32 @@ public: const StringArray* headers, const MemoryBlock* postData) { - stop(); - if (url.trimStart().startsWithIgnoreCase ("javascript:")) { - [webView stringByEvaluatingJavaScriptFromString: - juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))]; + [webView stringByEvaluatingJavaScriptFromString: juceStringToNS (url.fromFirstOccurrenceOf (":", false, false))]; + return; } - else if (NSMutableURLRequest* request = getRequestForURL (url, headers, postData)) + + stop(); + + auto getRequest = [&]() -> NSMutableURLRequest* + { + if (url.trimStart().startsWithIgnoreCase ("file:")) + { + auto file = URL (url).getLocalFile(); + + if (NSURL* nsUrl = [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]) + return [NSMutableURLRequest requestWithURL: nsUrl + cachePolicy: NSURLRequestUseProtocolCachePolicy + timeoutInterval: 30.0]; + + return nullptr; + } + + return getRequestForURL (url, headers, postData); + }; + + if (NSMutableURLRequest* request = getRequest()) { #if JUCE_MAC [[webView mainFrame] loadRequest: request]; @@ -526,10 +595,12 @@ public: void mouseMove (const MouseEvent&) { + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") // WebKit doesn't capture mouse-moves itself, so it seems the only way to make // them work is to push them via this non-public method.. if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)]) [webView performSelector: @selector (_updateMouseoverWithFakeEvent)]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } private: diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp index c4d6ef77..905e1378 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_HWNDComponent.cpp @@ -26,6 +26,8 @@ namespace juce { +void setThreadDPIAwarenessForWindow (HWND); + class HWNDComponent::Pimpl : public ComponentMovementWatcher { public: @@ -44,27 +46,25 @@ public: DestroyWindow (hwnd); } - using ComponentMovementWatcher::componentMovedOrResized; - void componentMovedOrResized (bool wasMoved, bool wasResized) override { - auto* topComponent = owner.getTopLevelComponent(); - - if (auto* peer = owner.getPeer()) + if (auto* peer = owner.getTopLevelComponent()->getPeer()) { - auto pos = topComponent->getLocalPoint (&owner, Point()); + auto area = (peer->getAreaCoveredBy (owner).toFloat() * peer->getPlatformScaleFactor()).getSmallestIntegerContainer(); - auto scaled = (Rectangle (pos.x, pos.y, owner.getWidth(), owner.getHeight()).toDouble() - * peer->getPlatformScaleFactor()).getSmallestIntegerContainer(); + setThreadDPIAwarenessForWindow (hwnd); - DWORD windowFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER; - if (! wasMoved) windowFlags |= SWP_NOMOVE; - if (! wasResized) windowFlags |= SWP_NOSIZE; + UINT flagsToSend = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER; - SetWindowPos (hwnd, nullptr, scaled.getX(), scaled.getY(), scaled.getWidth(), scaled.getHeight(), windowFlags); + if (! wasMoved) flagsToSend |= SWP_NOMOVE; + if (! wasResized) flagsToSend |= SWP_NOSIZE; + + SetWindowPos (hwnd, nullptr, area.getX(), area.getY(), area.getWidth(), area.getHeight(), flagsToSend); } } + using ComponentMovementWatcher::componentMovedOrResized; + void componentPeerChanged() override { auto* peer = owner.getPeer(); @@ -85,13 +85,13 @@ public: InvalidateRect (hwnd, nullptr, 0); } - using ComponentMovementWatcher::componentVisibilityChanged; - void componentVisibilityChanged() override { componentPeerChanged(); } + using ComponentMovementWatcher::componentVisibilityChanged; + void componentBroughtToFront (Component& comp) override { ComponentMovementWatcher::componentBroughtToFront (comp); @@ -101,11 +101,13 @@ public: { if (auto* peer = owner.getPeer()) { + setThreadDPIAwarenessForWindow (hwnd); + RECT r; GetWindowRect (hwnd, &r); + Rectangle windowRectangle (r.right - r.left, r.bottom - r.top); - return (Rectangle::leftTopRightBottom (r.left, r.top, r.right, r.bottom).toDouble() - / peer->getPlatformScaleFactor()).getSmallestIntegerContainer(); + return (windowRectangle.toFloat() / peer->getPlatformScaleFactor()).toNearestInt(); } return {}; @@ -120,7 +122,8 @@ private: { auto windowFlags = GetWindowLongPtr (hwnd, -16); - windowFlags &= ~(WS_POPUP | WS_CHILD); + windowFlags &= ~WS_POPUP; + windowFlags |= WS_CHILD; SetWindowLongPtr (hwnd, -16, windowFlags); SetParent (hwnd, (HWND) currentPeer->getNativeHandle()); @@ -131,6 +134,7 @@ private: void removeFromParent() { + ShowWindow (hwnd, SW_HIDE); SetParent (hwnd, NULL); } @@ -141,12 +145,11 @@ private: }; //============================================================================== -HWNDComponent::HWNDComponent() -{ -} - +HWNDComponent::HWNDComponent() {} HWNDComponent::~HWNDComponent() {} +void HWNDComponent::paint (Graphics&) {} + void HWNDComponent::setHWND (void* hwnd) { if (hwnd != getHWND()) diff --git a/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp b/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp index 630cac0b..285e7bce 100644 --- a/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp +++ b/libs/juce-current/source/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp @@ -335,9 +335,6 @@ public: : ComponentMovementWatcher (&o), owner (o) { - if (! WinRTWrapper::getInstance()->isInitialised()) - throw std::runtime_error ("Failed to initialise the WinRT wrapper"); - if (! createWebViewEnvironment (dllLocation, userDataFolder)) throw std::runtime_error ("Failed to create the CoreWebView2Environemnt"); @@ -439,7 +436,7 @@ public: private: //============================================================================== - template + template static String getUriStringFromArgs (ArgType* args) { if (args != nullptr) @@ -531,10 +528,10 @@ private: if (urlRequest.url.isEmpty()) return S_OK; - WinRTWrapper::ComPtr request; + ComSmartPtr request; args->get_Request (request.resetAndGetPointerAddress()); - auto uriString = getUriStringFromArgs (request.get()); + auto uriString = getUriStringFromArgs (request); if (uriString == urlRequest.url || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url)) @@ -545,14 +542,14 @@ private: { method = "POST"; - WinRTWrapper::ComPtr content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(), + ComSmartPtr content (SHCreateMemStream ((BYTE*) urlRequest.postData.getData(), (UINT) urlRequest.postData.getSize())); - request->put_Content (content.get()); + request->put_Content (content); } if (! urlRequest.headers.isEmpty()) { - WinRTWrapper::ComPtr headers; + ComSmartPtr headers; request->get_Headers (headers.resetAndGetPointerAddress()); for (auto& header : urlRequest.headers) @@ -675,7 +672,7 @@ private: void closeWebView() { - if (webViewController.get() != nullptr) + if (webViewController != nullptr) { webViewController->Close(); webViewController = nullptr; @@ -711,9 +708,9 @@ private: HMODULE webView2LoaderHandle = nullptr; - WinRTWrapper::ComPtr webViewEnvironment; - WinRTWrapper::ComPtr webViewController; - WinRTWrapper::ComPtr webView; + ComSmartPtr webViewEnvironment; + ComSmartPtr webViewController; + ComSmartPtr webView; EventRegistrationToken navigationStartingToken { 0 }, newWindowRequestedToken { 0 }, diff --git a/libs/juce-current/source/modules/juce_opengl/juce_opengl.cpp b/libs/juce-current/source/modules/juce_opengl/juce_opengl.cpp index 78beeee9..76754ae0 100644 --- a/libs/juce-current/source/modules/juce_opengl/juce_opengl.cpp +++ b/libs/juce-current/source/modules/juce_opengl/juce_opengl.cpp @@ -263,16 +263,12 @@ private: //============================================================================== #if JUCE_MAC || JUCE_IOS - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - #if JUCE_MAC #include "native/juce_OpenGL_osx.h" #else #include "native/juce_OpenGL_ios.h" #endif - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #elif JUCE_WINDOWS #include "native/juce_OpenGL_win32.h" diff --git a/libs/juce-current/source/modules/juce_opengl/juce_opengl.h b/libs/juce-current/source/modules/juce_opengl/juce_opengl.h index 1114994a..f7bc806f 100644 --- a/libs/juce-current/source/modules/juce_opengl/juce_opengl.h +++ b/libs/juce-current/source/modules/juce_opengl/juce_opengl.h @@ -35,7 +35,7 @@ ID: juce_opengl vendor: juce - version: 6.0.4 + version: 6.0.7 name: JUCE OpenGL classes description: Classes for rendering OpenGL in a JUCE window. website: http://www.juce.com/juce diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_android.h b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_android.h index 63a7d14a..91e44111 100644 --- a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_android.h +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_android.h @@ -231,7 +231,7 @@ public: auto env = getEnv(); lastBounds = bounds; - auto r = bounds * Desktop::getInstance().getDisplays().getMainDisplay().scale; + auto r = bounds * Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; env->CallVoidMethod (surfaceView.get(), JuceOpenGLViewSurface.layout, (jint) r.getX(), (jint) r.getY(), (jint) r.getRight(), (jint) r.getBottom()); diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_ios.h b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_ios.h index ee8e5ac4..ae58579b 100644 --- a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_ios.h +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_ios.h @@ -154,9 +154,13 @@ public: if (openGLversion >= openGL3_2) { - glBlitFramebuffer (0, 0, lastBounds.getWidth(), lastBounds.getHeight(), - 0, 0, lastBounds.getWidth(), lastBounds.getHeight(), - GL_COLOR_BUFFER_BIT, GL_NEAREST); + auto w = roundToInt (lastBounds.getWidth() * glLayer.contentsScale); + auto h = roundToInt (lastBounds.getHeight() * glLayer.contentsScale); + + glBlitFramebuffer (0, 0, w, h, + 0, 0, w, h, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); } else { @@ -180,7 +184,7 @@ public: void updateWindowPosition (Rectangle bounds) { view.frame = convertToCGRect (bounds); - glLayer.contentsScale = (CGFloat) (Desktop::getInstance().getDisplays().getMainDisplay().scale + glLayer.contentsScale = (CGFloat) (Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale / component.getDesktopScaleFactor()); if (lastBounds != bounds) diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_linux_X11.h b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_linux_X11.h index c3301846..ef7eb852 100644 --- a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_linux_X11.h +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_linux_X11.h @@ -135,7 +135,7 @@ public: XWindowSystemUtilities::ScopedXLock xLock; X11Symbols::getInstance()->xUnmapWindow (display, embeddedWindow); X11Symbols::getInstance()->xDestroyWindow (display, embeddedWindow); - X11Symbols::getInstance()->xSync (display, True); + X11Symbols::getInstance()->xSync (display, False); } } diff --git a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_osx.h b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_osx.h index 3a7686bf..e6378a99 100644 --- a/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_osx.h +++ b/libs/juce-current/source/modules/juce_opengl/native/juce_OpenGL_osx.h @@ -49,10 +49,12 @@ public: if ([view respondsToSelector: @selector (setWantsBestResolutionOpenGLSurface:)]) [view setWantsBestResolutionOpenGLSurface: YES]; + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [[NSNotificationCenter defaultCenter] addObserver: view selector: @selector (_surfaceNeedsUpdate:) name: NSViewGlobalFrameDidChangeNotification object: view]; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE renderContext = [[[NSOpenGLContext alloc] initWithFormat: format shareContext: (NSOpenGLContext*) contextToShare] autorelease]; diff --git a/libs/juce-current/source/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/libs/juce-current/source/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index 59910469..0cc2a611 100644 --- a/libs/juce-current/source/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/libs/juce-current/source/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -286,7 +286,7 @@ public: if (auto* peer = component.getPeer()) { auto localBounds = component.getLocalBounds(); - auto displayScale = Desktop::getInstance().getDisplays().findDisplayForRect (component.getTopLevelComponent()->getScreenBounds()).scale; + auto displayScale = Desktop::getInstance().getDisplays().getDisplayForRect (component.getTopLevelComponent()->getScreenBounds())->scale; auto newArea = peer->getComponent().getLocalArea (&component, localBounds).withZeroOrigin() * displayScale; diff --git a/libs/juce-legacy/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp b/libs/juce-legacy/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp index dba25089..0b8be28e 100644 --- a/libs/juce-legacy/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp +++ b/libs/juce-legacy/source/modules/juce_audio_plugin_client/LV2/juce_LV2_Wrapper_Exporter.cpp @@ -364,6 +364,7 @@ static const String makePresetsFile (AudioProcessor* const filter) // Header text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n"; text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; + text += "@prefix owl: .\n"; text += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n"; text += "@prefix rdf: .\n"; text += "@prefix rdfs: .\n"; @@ -385,6 +386,7 @@ static const String makePresetsFile (AudioProcessor* const filter) text += " rdfs:domain state:State ;\n"; text += " rdfs:range xsd:base64Binary .\n"; #endif + text += "\n"; #endif // Presets