From 65305b1afe2a3d34dd49a09ce6928b6df922e36d Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 10 Jan 2023 14:10:52 +0000 Subject: [PATCH 001/347] Projucer: Build VST3 bundles from the MSVC exporters --- BREAKING-CHANGES.txt | 26 ++++ .../jucer_ProjectExport_CodeBlocks.h | 4 +- .../ProjectSaving/jucer_ProjectExport_MSVC.h | 126 ++++++++++++------ 3 files changed, 115 insertions(+), 41 deletions(-) diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 973f3e074681..d2f7c04e75df 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -1,6 +1,32 @@ JUCE breaking changes ===================== +develop +======= + +Change +------ +Projucer-generated MSVC projects now build VST3s as bundles, rather than as +single DLL files. + +Possible Issues +--------------- +Build workflows that expect the VST3 to be a single DLL may break. + +Workaround +---------- +Any post-build scripts that expect to copy or move the built VST3 should be +updated so that the entire bundle directory is copied/moved. The DLL itself +can still be located and extracted from within the generated bundle if +necessary. + +Rationale +--------- +Distributing VST3s as single files was deprecated in VST3 v3.6.10. JUCE's CMake +scripts already produce VST3s as bundles, so this change increases consistency +between the two build systems. + + Version 7.0.3 ============= diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h index 8d1c04b31ff6..553bafd6dd2c 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_CodeBlocks.h @@ -225,9 +225,9 @@ class CodeBlocksProjectExporter : public ProjectExporter if (archFlag.startsWith (prefix)) return archFlag.substring (prefix.length()); - else if (archFlag == "-m64") + if (archFlag == "-m64") return "x86_64"; - else if (archFlag == "-m32") + if (archFlag == "-m32") return "i386"; jassertfalse; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index c7b85ea20987..96fb439b7c7d 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -1105,7 +1105,7 @@ class MSVCProjectExporterBase : public ProjectExporter .toWindowsStyle()); } - String getConfigTargetPath (const BuildConfiguration& config) const + String getConfigTargetPath (const MSVCBuildConfiguration& config) const { const auto result = getSolutionTargetPath (config) + "\\" + getName(); @@ -1147,7 +1147,6 @@ class MSVCProjectExporterBase : public ProjectExporter if (fileType == pluginBundle) { - if (type == VST3PlugIn) return ".vst3"; if (type == AAXPlugIn) return ".aaxdll"; return ".dll"; @@ -1209,27 +1208,47 @@ class MSVCProjectExporterBase : public ProjectExporter String getExtraPostBuildSteps (const MSVCBuildConfiguration& config) const { + const auto copyBuildOutputIntoBundle = [&] (const StringArray& segments) + { + return "copy /Y " + + getOutputFilePath (config).quoted() + + " " + + getOwner().getOutDirFile (config, segments.joinIntoString ("\\")).quoted(); + }; + + const auto copyBundleToInstallDirectory = [&] (const StringArray& segments, const String& directory) + { + const auto copyStep = "\r\nxcopy /E /H /K /R /Y /I " + + getOwner().getOutDirFile (config, segments[0]).quoted() + + " " + + (directory + "\\" + segments[0] + "\\").quoted(); + + return config.isPluginBinaryCopyStepEnabled() ? copyStep : ""; + }; + if (type == AAXPlugIn) { - build_tools::RelativePath aaxSDK (owner.getAAXPathString(), build_tools::RelativePath::projectFolder); - build_tools::RelativePath aaxLibsFolder = aaxSDK.getChildFile ("Libs"); - build_tools::RelativePath bundleScript = aaxSDK.getChildFile ("Utilities").getChildFile ("CreatePackage.bat"); - build_tools::RelativePath iconFilePath = getAAXIconFile(); + const build_tools::RelativePath aaxSDK (owner.getAAXPathString(), build_tools::RelativePath::projectFolder); + const build_tools::RelativePath aaxLibsFolder = aaxSDK.getChildFile ("Libs"); + const build_tools::RelativePath bundleScript = aaxSDK.getChildFile ("Utilities").getChildFile ("CreatePackage.bat"); + const build_tools::RelativePath iconFilePath = getAAXIconFile(); - auto outputFilename = config.getOutputFilename (".aaxplugin", true, type); - auto bundleDir = getOwner().getOutDirFile (config, outputFilename); - auto bundleContents = bundleDir + "\\Contents"; - auto archDir = bundleContents + String ("\\") + config.getArchitectureString(); - auto executablePath = archDir + String ("\\") + outputFilename; + const auto segments = getAaxBundleStructure (config); - auto pkgScript = String ("copy /Y ") + getOutputFilePath (config).quoted() + String (" ") + executablePath.quoted() + String ("\r\ncall ") - + createRebasedPath (bundleScript) + String (" ") + archDir.quoted() + String (" ") + createRebasedPath (iconFilePath); + const auto pkgScript = copyBuildOutputIntoBundle (segments); - if (config.isPluginBinaryCopyStepEnabled()) - return pkgScript + "\r\n" + "xcopy " + bundleDir.quoted() + " " - + String (config.getAAXBinaryLocationString() + "\\" + outputFilename + "\\").quoted() + " /E /H /K /R /Y"; + const auto archDir = StringArray (segments.strings.data(), segments.size() - 1).joinIntoString ("\\"); + const auto rebasedArchDir = getOwner().getOutDirFile (config, archDir); + const auto fixScript = "\r\ncall " + + createRebasedPath (bundleScript) + + " " + + rebasedArchDir.quoted() + + String (" ") + + createRebasedPath (iconFilePath); - return pkgScript; + const auto copyScript = copyBundleToInstallDirectory (segments, config.getAAXBinaryLocationString()); + + return pkgScript + fixScript + copyScript; } if (type == UnityPlugIn) @@ -1266,44 +1285,53 @@ class MSVCProjectExporterBase : public ProjectExporter + "\\" + writerTarget->getBinaryNameWithSuffix (config); - const auto copyScript = [&]() -> String - { - if (! config.isPluginBinaryCopyStepEnabled()) - return ""; - - return "xcopy /E /H /I /K /R /Y \"$(OutDir)\" \"" + config.getLV2BinaryLocationString() - + '\\' + config.getTargetBinaryNameString() + ".lv2\"\r\n"; - }(); + const auto copyStep = "xcopy /E /H /I /K /R /Y \"$(OutDir)\" \"" + + config.getLV2BinaryLocationString() + + '\\' + + config.getTargetBinaryNameString() + + ".lv2\"\r\n"; - return writer.quoted() + " \"$(OutDir)$(TargetFileName)\"\r\n" + copyScript; + return writer.quoted() + + " \"$(OutDir)$(TargetFileName)\"\r\n" + + (config.isPluginBinaryCopyStepEnabled() ? copyStep : ""); } - if (config.isPluginBinaryCopyStepEnabled()) + if (type == VST3PlugIn) { - auto copyScript = String ("copy /Y \"$(OutDir)$(TargetFileName)\"") + String (" \"$COPYDIR$\\$(TargetFileName)\""); + const auto segments = getVst3BundleStructure (config); + const auto pkgScript = copyBuildOutputIntoBundle (segments); + const auto copyScript = copyBundleToInstallDirectory (segments, config.getVST3BinaryLocationString()); - if (type == VSTPlugIn) return copyScript.replace ("$COPYDIR$", config.getVSTBinaryLocationString()); - if (type == VST3PlugIn) return copyScript.replace ("$COPYDIR$", config.getVST3BinaryLocationString()); + return pkgScript + copyScript; } + if (type == VSTPlugIn && config.isPluginBinaryCopyStepEnabled()) + return "copy /Y \"$(OutDir)$(TargetFileName)\" \"" + config.getVSTBinaryLocationString() + "\\$(TargetFileName)\""; + return {}; } String getExtraPreBuildSteps (const MSVCBuildConfiguration& config) const { - if (type == AAXPlugIn) + const auto createBundleStructure = [&] (const StringArray& segments) { + auto directory = getOwner().getOutDirFile (config, ""); String script; - auto bundleDir = getOwner().getOutDirFile (config, config.getOutputFilename (".aaxplugin", false, type)); - auto bundleContents = bundleDir + "\\Contents"; - auto archDir = bundleContents + String ("\\") + config.getArchitectureString(); - - for (auto& folder : StringArray { bundleDir, bundleContents, archDir }) - script += String ("if not exist \"") + folder + String ("\" mkdir \"") + folder + String ("\"\r\n"); + std::for_each (segments.begin(), std::prev (segments.end()), [&] (const auto& s) + { + directory += (directory.isEmpty() ? "" : "\\") + s; + script += "if not exist \"" + directory + "\" mkdir \"" + directory + "\"\r\n"; + }); return script; - } + }; + + if (type == AAXPlugIn) + return createBundleStructure (getAaxBundleStructure (config)); + + if (type == VST3PlugIn) + return createBundleStructure (getVst3BundleStructure (config)); return {}; } @@ -1343,7 +1371,7 @@ class MSVCProjectExporterBase : public ProjectExporter return getOwner().getOutDirFile (config, getBinaryNameWithSuffix (config)); } - StringArray getLibrarySearchPaths (const BuildConfiguration& config) const + StringArray getLibrarySearchPaths (const MSVCBuildConfiguration& config) const { auto librarySearchPaths = config.getLibrarySearchPaths(); @@ -1410,6 +1438,26 @@ class MSVCProjectExporterBase : public ProjectExporter } protected: + StringArray getAaxBundleStructure (const MSVCBuildConfiguration& config) const + { + const auto dllName = config.getOutputFilename (".aaxplugin", false, type); + return { dllName, "Contents", config.getArchitectureString(), dllName }; + } + + StringArray getVst3BundleStructure (const MSVCBuildConfiguration& config) const + { + static const std::map suffixes + { + { "Win32", "x86" }, + { "x64", "x86_64" }, + }; + + const auto iter = suffixes.find (config.getArchitectureString()); + + const auto dllName = config.getOutputFilename (".vst3", false, type); + return { dllName, "Contents", iter != suffixes.cend() ? iter->second + "-win" : "win", dllName }; + } + const MSVCProjectExporterBase& owner; String projectGuid; }; From 6bd31bab35aed4b24946107222da93b31385f32a Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 11 Jan 2023 16:55:17 +0000 Subject: [PATCH 002/347] VST3 Client: Allow mismatched channel counts in safe cases --- .../format_types/juce_VST3Common.h | 40 ++++++++++++++----- .../format_types/juce_VST3PluginFormat.cpp | 1 - 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index cf7f2a81437c..0090f3854aa5 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -613,7 +613,9 @@ static int countValidBuses (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) })); } -template +enum class Direction { input, output }; + +template static bool validateLayouts (Iterator first, Iterator last, const std::vector& map) { if ((size_t) std::distance (first, last) > map.size()) @@ -623,12 +625,24 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector{}, *it); - const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); + auto& bus = *it; + auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); + const auto expectedChannels = static_cast (mapIterator->size()); + const auto actualChannels = static_cast (bus.numChannels); + const auto limit = jmin (expectedChannels, actualChannels); + const auto anyChannelIsNull = std::any_of (busPtr, busPtr + limit, [] (auto* ptr) { return ptr == nullptr; }); + constexpr auto isInput = direction == Direction::input; + + const auto channelCountIsUsable = isInput ? expectedChannels <= actualChannels + : actualChannels <= expectedChannels; // Null channels are allowed if the bus is inactive - if (mapIterator->isHostActive() && (anyChannelIsNull || (int) mapIterator->size() != it->numChannels)) + if (mapIterator->isHostActive() && (anyChannelIsNull || ! channelCountIsUsable)) return false; + + // If this is hit, the destination bus has fewer channels than the source bus. + // As a result, some channels will 'go missing', and channel layouts may be invalid. + jassert (actualChannels == expectedChannels); } // If the host didn't provide the full complement of buses, it must be because the other @@ -669,7 +683,7 @@ class ClientBufferMapperData // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here const auto vstInputs = countValidBuses (data.inputs, data.numInputs); - if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) + if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) return getBlankBuffer (usedChannels, (int) data.numSamples); setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); @@ -702,7 +716,12 @@ class ClientBufferMapperData if (mapping.isHostActive() && busIndex < vstInputs) { - auto** busPtr = getAudioBusPointer (detail::Tag{}, data.inputs[busIndex]); + auto& bus = data.inputs[busIndex]; + + // Every JUCE channel must have a VST3 channel counterpart + jassert (mapping.size() <= static_cast (bus.numChannels)); + + auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) { @@ -921,7 +940,7 @@ class ClientRemappedBuffer // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here const auto vstOutputs = (size_t) countValidBuses (data.outputs, data.numOutputs); - if (validateLayouts (data.outputs, data.outputs + vstOutputs, *outputMap)) + if (validateLayouts (data.outputs, data.outputs + vstOutputs, *outputMap)) copyToHostOutputBuses (vstOutputs); else clearHostOutputBuses (vstOutputs); @@ -940,9 +959,12 @@ class ClientRemappedBuffer { auto& bus = data.outputs[i]; + // Every VST3 channel must have a JUCE channel counterpart + jassert (static_cast (bus.numChannels) <= mapping.size()); + if (mapping.isClientActive()) { - for (size_t j = 0; j < mapping.size(); ++j) + for (size_t j = 0; j < static_cast (bus.numChannels); ++j) { auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; const auto juceChannel = juceBusOffset + (size_t) mapping.getJuceChannelForVst3Channel ((int) j); @@ -951,7 +973,7 @@ class ClientRemappedBuffer } else { - for (size_t j = 0; j < mapping.size(); ++j) + for (size_t j = 0; j < static_cast (bus.numChannels); ++j) { auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; FloatVectorOperations::clear (hostChannel, (size_t) data.numSamples); diff --git a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 7865d50bdad1..4426ad13c191 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -94,7 +94,6 @@ static int warnOnFailureIfImplemented (int result) noexcept #define warnOnFailureIfImplemented(x) x #endif -enum class Direction { input, output }; enum class MediaKind { audio, event }; static Vst::MediaType toVstType (MediaKind x) { return x == MediaKind::audio ? Vst::kAudio : Vst::kEvent; } From 4b222427f9437358a82a25d40b9b135511e69462 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 11 Jan 2023 20:09:50 +0000 Subject: [PATCH 003/347] VST3: Avoid requesting channel layouts that cannot be represented as SpeakerArrangements --- .../VST3/juce_VST3_Wrapper.cpp | 49 +++++++-- .../format_types/juce_VST3Common.h | 72 +++++++----- .../format_types/juce_VST3PluginFormat.cpp | 103 ++++++++++-------- .../processors/juce_AudioProcessor.h | 14 ++- 4 files changed, 154 insertions(+), 84 deletions(-) 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 da79dcc63ff8..d809ae37e6d2 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -3051,7 +3051,9 @@ class JuceVST3Component : public Vst::IComponent, info.mediaType = Vst::kAudio; info.direction = dir; info.channelCount = bus->getLastEnabledLayout().size(); - jassert (info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (getVst3SpeakerArrangement (bus->getLastEnabledLayout()))); + + [[maybe_unused]] const auto lastEnabledVst3Layout = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); + jassert (lastEnabledVst3Layout.has_value() && info.channelCount == Steinberg::Vst::SpeakerArr::getChannelCount (*lastEnabledVst3Layout)); toString128 (info.name, bus->getName()); info.busType = [&] @@ -3288,19 +3290,42 @@ class JuceVST3Component : public Vst::IComponent, // see the following documentation to understand the correct way to react to this callback // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 - const auto requestedLayout = [&] + const auto toLayoutsArray = [] (auto begin, auto end) -> std::optional> { - auto result = pluginInstance->getBusesLayout(); + Array result; + + for (auto it = begin; it != end; ++it) + { + const auto set = getChannelSetForSpeakerArrangement (*it); + + if (! set.has_value()) + return {}; + + result.add (*set); + } - for (int i = 0; i < numIns; ++i) - result.getChannelSet (true, i) = getChannelSetForSpeakerArrangement (inputs[i]); + return result; + }; + + const auto optionalRequestedLayout = [&]() -> std::optional + { + const auto ins = toLayoutsArray (inputs, inputs + numIns); + const auto outs = toLayoutsArray (outputs, outputs + numOuts); - for (int i = 0; i < numOuts; ++i) - result.getChannelSet (false, i) = getChannelSetForSpeakerArrangement (outputs[i]); + if (! ins.has_value() || ! outs.has_value()) + return {}; + AudioProcessor::BusesLayout result; + result.inputBuses = *ins; + result.outputBuses = *outs; return result; }(); + if (! optionalRequestedLayout.has_value()) + return kResultFalse; + + const auto& requestedLayout = *optionalRequestedLayout; + #ifdef JucePlugin_PreferredChannelConfigurations short configs[][2] = { JucePlugin_PreferredChannelConfigurations }; if (! AudioProcessor::containsLayout (requestedLayout, configs)) @@ -3339,8 +3364,14 @@ class JuceVST3Component : public Vst::IComponent, { if (auto* bus = pluginInstance->getBus (dir == Vst::kInput, index)) { - arr = getVst3SpeakerArrangement (bus->getLastEnabledLayout()); - return kResultTrue; + if (const auto arrangement = getVst3SpeakerArrangement (bus->getLastEnabledLayout())) + { + arr = *arrangement; + return kResultTrue; + } + + // There's a bus here, but we can't represent its layout in terms of VST3 speakers! + jassertfalse; } return kResultFalse; diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index 0090f3854aa5..eb204b34f5ba 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -197,7 +197,7 @@ static inline Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg return arrangement; } -static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept +static std::optional getSpeakerType (const AudioChannelSet& set, AudioChannelSet::ChannelType type) noexcept { switch (type) { @@ -280,11 +280,10 @@ static Steinberg::Vst::Speaker getSpeakerType (const AudioChannelSet& set, Audio break; } - auto channelIndex = static_cast (type) - (static_cast (AudioChannelSet::discreteChannel0) + 6ull); - return (1ull << (channelIndex + 33ull /* last speaker in vst layout + 1 */)); + return {}; } -static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept +static std::optional getChannelType (Steinberg::Vst::SpeakerArrangement arr, Steinberg::Vst::Speaker type) noexcept { switch (type) { @@ -340,12 +339,7 @@ static AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::SpeakerArran case Steinberg::Vst::kSpeakerBrr: return AudioChannelSet::bottomRearRight; } - auto channelType = BigInteger (static_cast (type)).findNextSetBit (0); - - // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (channelType >= 33); - - return static_cast (static_cast (AudioChannelSet::discreteChannel0) + 6 + (channelType - 33)); + return {}; } namespace detail @@ -423,7 +417,7 @@ inline bool isLayoutTableValid() }); } -static Array getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) +static std::optional> getSpeakerOrder (Steinberg::Vst::SpeakerArrangement arr) { using namespace Steinberg::Vst; using namespace Steinberg::Vst::SpeakerArr; @@ -445,12 +439,16 @@ static Array getSpeakerOrder (Steinberg::Vst::Spea result.ensureStorageAllocated (channels); for (auto i = 0; i < channels; ++i) - result.add (getChannelType (arr, getSpeaker (arr, i))); + if (const auto t = getChannelType (arr, getSpeaker (arr, i))) + result.add (*t); - return result; + if (getChannelCount (arr) == result.size()) + return result; + + return {}; } -static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept +static std::optional getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept { using namespace Steinberg::Vst::SpeakerArr; @@ -470,21 +468,25 @@ static Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const Audio Steinberg::Vst::SpeakerArrangement result = 0; for (const auto& type : channels.getChannelTypes()) - result |= getSpeakerType (channels, type); + if (const auto t = getSpeakerType (channels, type)) + result |= *t; + + if (getChannelCount (result) == channels.size()) + return result; - return result; + return {}; } -inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept +inline std::optional getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept { using namespace Steinberg::Vst::SpeakerArr; - const auto result = AudioChannelSet::channelSetWithChannels (getSpeakerOrder (arr)); + if (const auto order = getSpeakerOrder (arr)) + return AudioChannelSet::channelSetWithChannels (*order); // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassert (result.size() == getChannelCount (arr)); - - return result; + jassertfalse; + return {}; } //============================================================================== @@ -522,7 +524,21 @@ struct ChannelMapping */ static std::vector makeChannelIndices (const AudioChannelSet& juceArrangement) { - const auto order = getSpeakerOrder (getVst3SpeakerArrangement (juceArrangement)); + const auto order = [&] + { + const auto fallback = juceArrangement.getChannelTypes(); + const auto vst3Arrangement = getVst3SpeakerArrangement (juceArrangement); + + if (! vst3Arrangement.has_value()) + return fallback; + + const auto reordered = getSpeakerOrder (*vst3Arrangement); + + if (! reordered.has_value() || AudioChannelSet::channelSetWithChannels (*reordered) != juceArrangement) + return fallback; + + return *reordered; + }(); std::vector result; @@ -627,14 +643,14 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vector{}, bus); - const auto expectedChannels = static_cast (mapIterator->size()); - const auto actualChannels = static_cast (bus.numChannels); - const auto limit = jmin (expectedChannels, actualChannels); + const auto expectedJuceChannels = (int) mapIterator->size(); + const auto actualVstChannels = (int) bus.numChannels; + const auto limit = jmin (expectedJuceChannels, actualVstChannels); const auto anyChannelIsNull = std::any_of (busPtr, busPtr + limit, [] (auto* ptr) { return ptr == nullptr; }); constexpr auto isInput = direction == Direction::input; - const auto channelCountIsUsable = isInput ? expectedChannels <= actualChannels - : actualChannels <= expectedChannels; + const auto channelCountIsUsable = isInput ? expectedJuceChannels <= actualVstChannels + : actualVstChannels <= expectedJuceChannels; // Null channels are allowed if the bus is inactive if (mapIterator->isHostActive() && (anyChannelIsNull || ! channelCountIsUsable)) @@ -642,7 +658,7 @@ static bool validateLayouts (Iterator first, Iterator last, const std::vectorgetName() : String(); } - void repopulateArrangements (Array& inputArrangements, Array& outputArrangements) const + std::vector getActualArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector result; - auto numInputAudioBuses = getBusCount (true); - auto numOutputAudioBuses = getBusCount (false); + const auto numBuses = getBusCount (isInput); - for (int i = 0; i < numInputAudioBuses; ++i) - inputArrangements.add (getArrangementForBus (processor, true, i)); + for (auto i = 0; i < numBuses; ++i) + result.push_back (getArrangementForBus (processor, isInput, i)); - for (int i = 0; i < numOutputAudioBuses; ++i) - outputArrangements.add (getArrangementForBus (processor, false, i)); + return result; } - void processorLayoutsToArrangements (Array& inputArrangements, Array& outputArrangements) + std::optional> busLayoutsToArrangements (bool isInput) const { - inputArrangements.clearQuick(); - outputArrangements.clearQuick(); + std::vector result; - auto numInputBuses = getBusCount (true); - auto numOutputBuses = getBusCount (false); + const auto numBuses = getBusCount (isInput); - for (int i = 0; i < numInputBuses; ++i) - inputArrangements.add (getVst3SpeakerArrangement (getBus (true, i)->getLastEnabledLayout())); + for (auto i = 0; i < numBuses; ++i) + { + if (const auto arr = getVst3SpeakerArrangement (getBus (isInput, i)->getLastEnabledLayout())) + result.push_back (*arr); + else + return {}; + } - for (int i = 0; i < numOutputBuses; ++i) - outputArrangements.add (getVst3SpeakerArrangement (getBus (false, i)->getLastEnabledLayout())); + return result; } void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override @@ -2520,21 +2519,21 @@ class VST3PluginInstance final : public AudioPluginInstance holder->initialise(); - Array inputArrangements, outputArrangements; - processorLayoutsToArrangements (inputArrangements, outputArrangements); + auto inArrangements = busLayoutsToArrangements (true) .value_or (std::vector{}); + auto outArrangements = busLayoutsToArrangements (false).value_or (std::vector{}); // Some plug-ins will crash if you pass a nullptr to setBusArrangements! SpeakerArrangement nullArrangement = {}; - auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer(); - auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer(); + auto* inData = inArrangements .empty() ? &nullArrangement : inArrangements .data(); + auto* outData = outArrangements.empty() ? &nullArrangement : outArrangements.data(); - warnOnFailure (processor->setBusArrangements (inputArrangementData, inputArrangements.size(), - outputArrangementData, outputArrangements.size())); + warnOnFailure (processor->setBusArrangements (inData, static_cast (inArrangements .size()), + outData, static_cast (outArrangements.size()))); - Array actualInArr, actualOutArr; - repopulateArrangements (actualInArr, actualOutArr); + const auto inArrActual = getActualArrangements (true); + const auto outArrActual = getActualArrangements (false); - jassert (actualInArr == inputArrangements && actualOutArr == outputArrangements); + jassert (inArrActual == inArrangements && outArrActual == outArrangements); // Needed for having the same sample rate in processBlock(); some plugins need this! setRateAndBufferSizeDetails (newSampleRate, estimatedSamplesPerBlock); @@ -2736,34 +2735,49 @@ class VST3PluginInstance final : public AudioPluginInstance } } - Array inputArrangements, outputArrangements; - - for (int i = 0; i < layouts.inputBuses.size(); ++i) + const auto getPotentialArrangements = [&] (bool isInput) -> std::optional> { - const auto& requested = layouts.getChannelSet (true, i); - inputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)); - } + std::vector result; + + for (int i = 0; i < layouts.getBuses (isInput).size(); ++i) + { + const auto& requested = layouts.getChannelSet (isInput, i); + + if (const auto arr = getVst3SpeakerArrangement (requested.isDisabled() ? getBus (true, i)->getLastEnabledLayout() : requested)) + result.push_back (*arr); + else + return {}; + } - for (int i = 0; i < layouts.outputBuses.size(); ++i) + return result; + }; + + auto inArrangements = getPotentialArrangements (true); + auto outArrangements = getPotentialArrangements (false); + + if (! inArrangements.has_value() || ! outArrangements.has_value()) { - const auto& requested = layouts.getChannelSet (false, i); - outputArrangements.add (getVst3SpeakerArrangement (requested.isDisabled() ? getBus (false, i)->getLastEnabledLayout() : requested)); + // This bus layout can't be represented as a VST3 speaker arrangement + return false; } + auto& inputArrangements = *inArrangements; + auto& outputArrangements = *outArrangements; + // Some plug-ins will crash if you pass a nullptr to setBusArrangements! Vst::SpeakerArrangement nullArrangement = {}; - auto* inputArrangementData = inputArrangements.isEmpty() ? &nullArrangement : inputArrangements.getRawDataPointer(); - auto* outputArrangementData = outputArrangements.isEmpty() ? &nullArrangement : outputArrangements.getRawDataPointer(); + auto* inputArrangementData = inputArrangements .empty() ? &nullArrangement : inputArrangements .data(); + auto* outputArrangementData = outputArrangements.empty() ? &nullArrangement : outputArrangements.data(); - if (processor->setBusArrangements (inputArrangementData, inputArrangements.size(), - outputArrangementData, outputArrangements.size()) != kResultTrue) + if (processor->setBusArrangements (inputArrangementData, static_cast (inputArrangements .size()), + outputArrangementData, static_cast (outputArrangements.size())) != kResultTrue) return false; // check if the layout matches the request - Array actualIn, actualOut; - repopulateArrangements (actualIn, actualOut); + const auto inArrActual = getActualArrangements (true); + const auto outArrActual = getActualArrangements (false); - return (actualIn == inputArrangements && actualOut == outputArrangements); + return (inArrActual == inputArrangements && outArrActual == outputArrangements); } bool canApplyBusesLayout (const BusesLayout& layouts) const override @@ -3385,7 +3399,8 @@ class VST3PluginInstance final : public AudioPluginInstance Vst::SpeakerArrangement arr; if (processor != nullptr && processor->getBusArrangement (dir, i, arr) == kResultOk) - layout = getChannelSetForSpeakerArrangement (arr); + if (const auto set = getChannelSetForSpeakerArrangement (arr)) + layout = *set; busProperties.addBus (isInput, toString (info.name), layout, (info.flags & Vst::BusInfo::kDefaultActive) != 0); diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h index b0acefa92734..27accb8a6f92 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -309,29 +309,37 @@ class JUCE_API AudioProcessor */ struct BusesLayout { + private: + template + static auto& getBuses (This& t, bool isInput) { return isInput ? t.inputBuses : t.outputBuses; } + + public: /** An array containing the list of input buses that this processor supports. */ Array inputBuses; /** An array containing the list of output buses that this processor supports. */ Array outputBuses; + auto& getBuses (bool isInput) const { return getBuses (*this, isInput); } + auto& getBuses (bool isInput) { return getBuses (*this, isInput); } + /** Get the number of channels of a particular bus */ int getNumChannels (bool isInput, int busIndex) const noexcept { - auto& bus = (isInput ? inputBuses : outputBuses); + auto& bus = getBuses (isInput); return isPositiveAndBelow (busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; } /** Get the channel set of a particular bus */ AudioChannelSet& getChannelSet (bool isInput, int busIndex) noexcept { - return (isInput ? inputBuses : outputBuses).getReference (busIndex); + return getBuses (isInput).getReference (busIndex); } /** Get the channel set of a particular bus */ AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept { - return (isInput ? inputBuses : outputBuses)[busIndex]; + return getBuses (isInput)[busIndex]; } /** Get the input channel layout on the main bus. */ From cf297c75c6bf8ed3968b5766469ac7ddc54d42e6 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 12 Jan 2023 12:34:34 +0000 Subject: [PATCH 004/347] PopupMenu: Fix issue where PopupMenu would sometimes use the default rather than the parent look and feel Previously, for the following snippet, the menu's LnF was incorrectly being forced to the default LnF. The correct behaviour is to display the menu using LnF v4. The menu doesn't have an explicit LnF set, so it should use the LnF of its parent component. LookAndFeel::setDefaultLookAndFeel (&lookAndFeel_V1); setLookAndFeel (&lookAndFeel_V4); PopupMenu().showMenuAsync (PopupMenu::Options{}.withParentComponent (this)); --- modules/juce_gui_basics/menus/juce_PopupMenu.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index 8dd135733a6c..f168cd8c5e35 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -333,7 +333,7 @@ struct MenuWindow : public Component float parentScaleFactor = 1.0f) : Component ("menu"), parent (parentWindow), - options (opts.withParentComponent (findLookAndFeel (menu, parentWindow)->getParentComponentForMenuOptions (opts))), + options (opts.withParentComponent (findNonNullLookAndFeel (menu, parentWindow).getParentComponentForMenuOptions (opts))), managerOfChosenCommand (manager), componentAttachedTo (options.getTargetComponent()), dismissOnMouseUp (shouldDismissOnMouseUp), @@ -1296,13 +1296,16 @@ struct MenuWindow : public Component LookAndFeel* findLookAndFeel (const PopupMenu& menu, MenuWindow* parentWindow) const { - if (parentWindow != nullptr) - return &(parentWindow->getLookAndFeel()); + return parentWindow != nullptr ? &(parentWindow->getLookAndFeel()) + : menu.lookAndFeel.get(); + } - if (auto* lnf = menu.lookAndFeel.get()) - return lnf; + LookAndFeel& findNonNullLookAndFeel (const PopupMenu& menu, MenuWindow* parentWindow) const + { + if (auto* result = findLookAndFeel (menu, parentWindow)) + return *result; - return &getLookAndFeel(); + return getLookAndFeel(); } //============================================================================== From 0f83e90f0d5e384b52b29869421bdb9217ec01d1 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 12 Jan 2023 13:52:18 +0000 Subject: [PATCH 005/347] FileChooser: Fix opening native dialogs in sandboxed macOS apps --- .../native/juce_mac_FileChooser.mm | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm index 9f6bc9dcdd69..1bd8cd530920 100644 --- a/modules/juce_gui_basics/native/juce_mac_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_mac_FileChooser.mm @@ -66,12 +66,20 @@ setBounds (0, 0, 0, 0); setOpaque (true); - static DelegateClass delegateClass; - static SafeSavePanel safeSavePanel; - static SafeOpenPanel safeOpenPanel; + panel = [&] + { + if (SystemStats::isAppSandboxEnabled()) + return isSave ? [[NSSavePanel alloc] init] + : [[NSOpenPanel alloc] init]; + + static SafeSavePanel safeSavePanel; + static SafeOpenPanel safeOpenPanel; - panel = isSave ? [safeSavePanel.createInstance() init] - : [safeOpenPanel.createInstance() init]; + return isSave ? [safeSavePanel.createInstance() init] + : [safeOpenPanel.createInstance() init]; + }(); + + static DelegateClass delegateClass; delegate = [delegateClass.createInstance() init]; object_setInstanceVariable (delegate, "cppObject", this); @@ -350,12 +358,12 @@ explicit SafeModalPanel (const char* name) : ObjCClass (name) struct SafeSavePanel : SafeModalPanel { - SafeSavePanel() : SafeModalPanel ("SaveSavePanel_") {} + SafeSavePanel() : SafeModalPanel ("SafeSavePanel_") {} }; struct SafeOpenPanel : SafeModalPanel { - SafeOpenPanel() : SafeModalPanel ("SaveOpenPanel_") {} + SafeOpenPanel() : SafeModalPanel ("SafeOpenPanel_") {} }; //============================================================================== From 5401ad6427d49f24ac2523e435a03b752fdd6074 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 12 Jan 2023 17:52:56 +0000 Subject: [PATCH 006/347] FileChooser: Allow directory selection on iOS --- .../native/juce_ios_FileChooser.mm | 63 +++++++++++-------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm index 47b5690eddbf..cd97759d4672 100644 --- a/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ b/modules/juce_gui_basics/native/juce_ios_FileChooser.mm @@ -194,8 +194,10 @@ void pickerWasCancelled() { delegate.reset ([[FileChooserDelegateClass alloc] initWithOwner: this]); - String firstFileExtension; - auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); + const auto validExtensions = getValidExtensionsForWildcards (owner.filters); + const auto utTypeArray = (flags & FileBrowserComponent::canSelectDirectories) != 0 + ? @[@"public.folder"] + : createNSArrayFromStringArray (getUTTypesForExtensions (validExtensions)); if ((flags & FileBrowserComponent::saveMode) != 0) { @@ -207,8 +209,10 @@ void pickerWasCancelled() if (! currentFileOrDirectory.existsAsFile()) { - auto filename = getFilename (currentFileOrDirectory, firstFileExtension); - auto tmpDirectory = File::createTempFile ("JUCE-filepath"); + const auto extension = validExtensions.isEmpty() ? String() + : validExtensions.getReference (0); + const auto filename = getFilename (currentFileOrDirectory, extension); + const auto tmpDirectory = File::createTempFile ("JUCE-filepath"); if (tmpDirectory.createDirectory().wasOk()) { @@ -294,36 +298,45 @@ void passResultsToInitiator (Array urls) } //============================================================================== - static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension) + static StringArray getValidExtensionsForWildcards (const String& filterWildcards) { - auto filters = StringArray::fromTokens (filterWildcards, ";", ""); - StringArray result; + const auto filters = StringArray::fromTokens (filterWildcards, ";", ""); + + if (filters.contains ("*") || filters.isEmpty()) + return {}; - firstExtension = {}; + StringArray result; - if (! filters.contains ("*") && filters.size() > 0) + for (const auto& filter : filters) { - for (auto filter : filters) - { - if (filter.isEmpty()) - continue; + if (filter.isEmpty()) + continue; - // iOS only supports file extension wild cards - jassert (filter.upToLastOccurrenceOf (".", true, false) == "*."); + // iOS only supports file extension wild cards + jassert (filter.upToLastOccurrenceOf (".", true, false) == "*."); - auto fileExtension = filter.fromLastOccurrenceOf (".", false, false); - CFUniquePtr fileExtensionCF (fileExtension.toCFString()); + result.add (filter.fromLastOccurrenceOf (".", false, false)); + } - if (firstExtension.isEmpty()) - firstExtension = fileExtension; + return result; + } - if (auto tag = CFUniquePtr (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr))) - result.add (String::fromCFString (tag.get())); - } - } - else + static StringArray getUTTypesForExtensions (const StringArray& extensions) + { + if (extensions.isEmpty()) + return { "public.data" }; + + StringArray result; + + for (const auto& extension : extensions) { - result.add ("public.data"); + if (extension.isEmpty()) + continue; + + CFUniquePtr fileExtensionCF (extension.toCFString()); + + if (const auto tag = CFUniquePtr (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr))) + result.add (String::fromCFString (tag.get())); } return result; From 238fbfca94a563714530c05479a7fad1ff10a426 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 16 Jan 2023 12:14:02 +0000 Subject: [PATCH 007/347] AudioIODeviceType: Fix typo --- modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index aa08cece415b..d4cc17397bef 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -116,7 +116,7 @@ class JUCE_API AudioIODeviceType /** A class for receiving events when audio devices are inserted or removed. - You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object + You can register an AudioIODeviceType::Listener with an AudioIODeviceType object using the AudioIODeviceType::addListener() method, and it will be called when devices of that type are added or removed. From 49a954d47335c654ca9fa9f520b772fbd3bb2210 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 16 Jan 2023 14:03:04 +0000 Subject: [PATCH 008/347] WASAPI: Only send change broadcast when devices are updated --- .../native/juce_win32_DirectSound.cpp | 31 +++--- .../native/juce_win32_WASAPI.cpp | 95 +++++++++---------- .../native/juce_win32_HiddenMessageWindow.h | 10 +- .../native/juce_win32_Messaging.cpp | 13 ++- 4 files changed, 71 insertions(+), 78 deletions(-) diff --git a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp index 017bee61e893..28a102ac417d 100644 --- a/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ b/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp @@ -1035,8 +1035,14 @@ class DSoundAudioIODevice : public AudioIODevice, }; //============================================================================== -struct DSoundDeviceList +class DSoundDeviceList { + auto tie() const + { + return std::tie (outputDeviceNames, inputDeviceNames, outputGuids, inputGuids); + } + +public: StringArray outputDeviceNames, inputDeviceNames; Array outputGuids, inputGuids; @@ -1054,13 +1060,8 @@ struct DSoundDeviceList } } - bool operator!= (const DSoundDeviceList& other) const noexcept - { - return outputDeviceNames != other.outputDeviceNames - || inputDeviceNames != other.inputDeviceNames - || outputGuids != other.outputGuids - || inputGuids != other.inputGuids; - } + bool operator== (const DSoundDeviceList& other) const noexcept { return tie() == other.tie(); } + bool operator!= (const DSoundDeviceList& other) const noexcept { return tie() != other.tie(); } private: static BOOL enumProc (LPGUID lpGUID, String desc, StringArray& names, Array& guids) @@ -1213,13 +1214,11 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, } //============================================================================== -class DSoundAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector +class DSoundAudioIODeviceType : public AudioIODeviceType { public: DSoundAudioIODeviceType() - : AudioIODeviceType ("DirectSound"), - DeviceChangeDetector (L"DirectSound") + : AudioIODeviceType ("DirectSound") { initialiseDSoundFunctions(); } @@ -1274,19 +1273,17 @@ class DSoundAudioIODeviceType : public AudioIODeviceType, } private: + DeviceChangeDetector detector { L"DirectSound", [this] { systemDeviceChanged(); } }; DSoundDeviceList deviceList; bool hasScanned = false; - void systemDeviceChanged() override + void systemDeviceChanged() { DSoundDeviceList newList; newList.scan(); - if (newList != deviceList) - { - deviceList = newList; + if (std::exchange (deviceList, newList) != newList) callDeviceChangeListeners(); - } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) diff --git a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp index b516cdbba9b0..92122988b2b7 100644 --- a/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ b/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp @@ -1694,13 +1694,11 @@ class WASAPIAudioIODevice : public AudioIODevice, //============================================================================== -class WASAPIAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector +class WASAPIAudioIODeviceType : public AudioIODeviceType { public: - WASAPIAudioIODeviceType (WASAPIDeviceMode mode) + explicit WASAPIAudioIODeviceType (WASAPIDeviceMode mode) : AudioIODeviceType (getDeviceTypename (mode)), - DeviceChangeDetector (L"Windows Audio"), deviceMode (mode) { } @@ -1715,22 +1713,15 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, void scanForDevices() override { hasScanned = true; - - outputDeviceNames.clear(); - inputDeviceNames.clear(); - outputDeviceIds.clear(); - inputDeviceIds.clear(); - - scan (outputDeviceNames, inputDeviceNames, - outputDeviceIds, inputDeviceIds); + devices = scan(); } StringArray getDeviceNames (bool wantInputNames) const override { jassert (hasScanned); // need to call scanForDevices() before doing this - return wantInputNames ? inputDeviceNames - : outputDeviceNames; + return wantInputNames ? devices.inputDeviceNames + : devices.outputDeviceNames; } int getDefaultDeviceIndex (bool /*forInput*/) const override @@ -1744,8 +1735,8 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, jassert (hasScanned); // need to call scanForDevices() before doing this if (auto d = dynamic_cast (device)) - return asInput ? inputDeviceIds.indexOf (d->inputDeviceId) - : outputDeviceIds.indexOf (d->outputDeviceId); + return asInput ? devices.inputDeviceIds .indexOf (d->inputDeviceId) + : devices.outputDeviceIds.indexOf (d->outputDeviceId); return -1; } @@ -1759,16 +1750,16 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, std::unique_ptr device; - auto outputIndex = outputDeviceNames.indexOf (outputDeviceName); - auto inputIndex = inputDeviceNames.indexOf (inputDeviceName); + auto outputIndex = devices.outputDeviceNames.indexOf (outputDeviceName); + auto inputIndex = devices.inputDeviceNames .indexOf (inputDeviceName); if (outputIndex >= 0 || inputIndex >= 0) { device.reset (new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName, getTypeName(), - outputDeviceIds [outputIndex], - inputDeviceIds [inputIndex], + devices.outputDeviceIds[outputIndex], + devices.inputDeviceIds [inputIndex], deviceMode)); if (! device->initialise()) @@ -1779,10 +1770,24 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, } //============================================================================== - StringArray outputDeviceNames, outputDeviceIds; - StringArray inputDeviceNames, inputDeviceIds; + struct Devices + { + StringArray outputDeviceNames, outputDeviceIds; + StringArray inputDeviceNames, inputDeviceIds; + + auto tie() const + { + return std::tie (outputDeviceNames, outputDeviceIds, inputDeviceNames, inputDeviceIds); + } + + bool operator== (const Devices& other) const { return tie() == other.tie(); } + bool operator!= (const Devices& other) const { return tie() != other.tie(); } + }; + + Devices devices; private: + DeviceChangeDetector deviceChangeDetector { L"Windows Audio", [this] { systemDeviceChanged(); } }; WASAPIDeviceMode deviceMode; bool hasScanned = false; ComSmartPtr enumerator; @@ -1791,7 +1796,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, class ChangeNotificationClient : public ComBaseClassHelper { public: - ChangeNotificationClient (WASAPIAudioIODeviceType* d) + explicit ChangeNotificationClient (WASAPIAudioIODeviceType* d) : ComBaseClassHelper (0), device (d) {} JUCE_COMRESULT OnDeviceAdded (LPCWSTR) { return notify(); } @@ -1806,7 +1811,7 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, HRESULT notify() { if (device != nullptr) - device->triggerAsyncDeviceChangeCallback(); + device->deviceChangeDetector.triggerAsyncDeviceChangeCallback(); return S_OK; } @@ -1840,15 +1845,12 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, } //============================================================================== - void scan (StringArray& outDeviceNames, - StringArray& inDeviceNames, - StringArray& outDeviceIds, - StringArray& inDeviceIds) + Devices scan() { if (enumerator == nullptr) { if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - return; + return {}; notifyClient = new ChangeNotificationClient (this); enumerator->RegisterEndpointNotificationCallback (notifyClient); @@ -1862,7 +1864,9 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) && check (deviceCollection->GetCount (&numDevices)))) - return; + return {}; + + Devices result; for (UINT32 i = 0; i < numDevices; ++i) { @@ -1902,40 +1906,33 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, if (flow == eRender) { const int index = (deviceId == defaultRenderer) ? 0 : -1; - outDeviceIds.insert (index, deviceId); - outDeviceNames.insert (index, name); + result.outputDeviceIds.insert (index, deviceId); + result.outputDeviceNames.insert (index, name); } else if (flow == eCapture) { const int index = (deviceId == defaultCapture) ? 0 : -1; - inDeviceIds.insert (index, deviceId); - inDeviceNames.insert (index, name); + result.inputDeviceIds.insert (index, deviceId); + result.inputDeviceNames.insert (index, name); } } - inDeviceNames.appendNumbersToDuplicates (false, false); - outDeviceNames.appendNumbersToDuplicates (false, false); + result.inputDeviceNames .appendNumbersToDuplicates (false, false); + result.outputDeviceNames.appendNumbersToDuplicates (false, false); + + return result; } //============================================================================== - void systemDeviceChanged() override + void systemDeviceChanged() { - StringArray newOutNames, newInNames, newOutIds, newInIds; - scan (newOutNames, newInNames, newOutIds, newInIds); + const auto newDevices = scan(); - if (newOutNames != outputDeviceNames - || newInNames != inputDeviceNames - || newOutIds != outputDeviceIds - || newInIds != inputDeviceIds) + if (std::exchange (devices, newDevices) != newDevices) { hasScanned = true; - outputDeviceNames = newOutNames; - inputDeviceNames = newInNames; - outputDeviceIds = newOutIds; - inputDeviceIds = newInIds; + callDeviceChangeListeners(); } - - callDeviceChangeListeners(); } //============================================================================== diff --git a/modules/juce_events/native/juce_win32_HiddenMessageWindow.h b/modules/juce_events/native/juce_win32_HiddenMessageWindow.h index e199569c27a0..e64271d16832 100644 --- a/modules/juce_events/native/juce_win32_HiddenMessageWindow.h +++ b/modules/juce_events/native/juce_win32_HiddenMessageWindow.h @@ -91,14 +91,13 @@ class JuceWindowIdentifier class DeviceChangeDetector : private Timer { public: - DeviceChangeDetector (const wchar_t* const name) - : messageWindow (name, (WNDPROC) deviceChangeEventCallback) + DeviceChangeDetector (const wchar_t* const name, std::function onChangeIn) + : messageWindow (name, (WNDPROC) deviceChangeEventCallback), + onChange (std::move (onChangeIn)) { SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this); } - virtual void systemDeviceChanged() = 0; - void triggerAsyncDeviceChangeCallback() { // We'll pause before sending a message, because on device removal, the OS hasn't always updated @@ -108,6 +107,7 @@ class DeviceChangeDetector : private Timer private: HiddenMessageWindow messageWindow; + std::function onChange; static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) @@ -127,7 +127,7 @@ class DeviceChangeDetector : private Timer void timerCallback() override { stopTimer(); - systemDeviceChanged(); + NullCheckedInvocation::invoke (onChange); } }; diff --git a/modules/juce_events/native/juce_win32_Messaging.cpp b/modules/juce_events/native/juce_win32_Messaging.cpp index 0e91c2159628..b970ebb13446 100644 --- a/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/modules/juce_events/native/juce_win32_Messaging.cpp @@ -299,25 +299,24 @@ void MessageManager::doPlatformSpecificShutdown() } //============================================================================== -struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector +struct MountedVolumeListChangeDetector::Pimpl { - Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d) + explicit Pimpl (MountedVolumeListChangeDetector& d) + : owner (d) { File::findFileSystemRoots (lastVolumeList); } - void systemDeviceChanged() override + void systemDeviceChanged() { Array newList; File::findFileSystemRoots (newList); - if (lastVolumeList != newList) - { - lastVolumeList = newList; + if (std::exchange (lastVolumeList, newList) != newList) owner.mountedVolumeListChanged(); - } } + DeviceChangeDetector detector { L"MountedVolumeList", [this] { systemDeviceChanged(); } }; MountedVolumeListChangeDetector& owner; Array lastVolumeList; }; From 26a872ba9f8375125499b97e8b46726da24457be Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 16 Jan 2023 15:27:38 +0000 Subject: [PATCH 009/347] AudioDeviceManager: Send changeNotification when MIDI devices change This patch also updates the MidiDemo to automatically refresh the device lists when the set of available devices changes. --- examples/Audio/MidiDemo.h | 48 +- .../audio_io/juce_AudioDeviceManager.cpp | 85 +- .../audio_io/juce_AudioDeviceManager.h | 6 + .../juce_audio_devices/juce_audio_devices.cpp | 2 +- .../midi_io/juce_MidiDevices.cpp | 75 ++ .../midi_io/juce_MidiDevices.h | 84 +- .../app/com/rmsl/juce/JuceMidiSupport.java | 57 +- .../native/juce_android_Midi.cpp | 1130 ++++++++++++----- .../native/juce_linux_Midi.cpp | 202 +-- .../native/juce_mac_CoreMidi.mm | 21 +- .../native/juce_win32_Midi.cpp | 12 +- ...oid_BluetoothMidiDevicePairingDialogue.cpp | 4 +- 12 files changed, 1227 insertions(+), 499 deletions(-) diff --git a/examples/Audio/MidiDemo.h b/examples/Audio/MidiDemo.h index 35e9db0e1548..6287818ada5f 100644 --- a/examples/Audio/MidiDemo.h +++ b/examples/Audio/MidiDemo.h @@ -52,19 +52,27 @@ //============================================================================== struct MidiDeviceListEntry : ReferenceCountedObject { - MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {} + explicit MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {} MidiDeviceInfo deviceInfo; std::unique_ptr inDevice; std::unique_ptr outDevice; using Ptr = ReferenceCountedObjectPtr; + + void stopAndReset() + { + if (inDevice != nullptr) + inDevice->stop(); + + inDevice .reset(); + outDevice.reset(); + } }; //============================================================================== class MidiDemo : public Component, - private Timer, private MidiKeyboardState::Listener, private MidiInputCallback, private AsyncUpdater @@ -113,12 +121,11 @@ class MidiDemo : public Component, setSize (732, 520); - startTimer (500); + updateDeviceLists(); } ~MidiDemo() override { - stopTimer(); midiInputs .clear(); midiOutputs.clear(); keyboardState.removeListener (this); @@ -128,12 +135,6 @@ class MidiDemo : public Component, } //============================================================================== - void timerCallback() override - { - updateDeviceList (true); - updateDeviceList (false); - } - void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override { MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); @@ -211,17 +212,8 @@ class MidiDemo : public Component, void closeDevice (bool isInput, int index) { - if (isInput) - { - jassert (midiInputs[index]->inDevice.get() != nullptr); - midiInputs[index]->inDevice->stop(); - midiInputs[index]->inDevice.reset(); - } - else - { - jassert (midiOutputs[index]->outDevice.get() != nullptr); - midiOutputs[index]->outDevice.reset(); - } + auto& list = isInput ? midiInputs : midiOutputs; + list[index]->stopAndReset(); } int getNumMidiInputs() const noexcept @@ -423,7 +415,6 @@ class MidiDemo : public Component, if (hasDeviceListChanged (availableDevices, isInputDeviceList)) { - ReferenceCountedArray& midiDevices = isInputDeviceList ? midiInputs : midiOutputs; @@ -463,6 +454,12 @@ class MidiDemo : public Component, addAndMakeVisible (label); } + void updateDeviceLists() + { + for (const auto isInput : { true, false }) + updateDeviceList (isInput); + } + //============================================================================== Label midiInputLabel { "Midi Input Label", "MIDI Input:" }; Label midiOutputLabel { "Midi Output Label", "MIDI Output:" }; @@ -473,12 +470,17 @@ class MidiDemo : public Component, TextEditor midiMonitor { "MIDI Monitor" }; TextButton pairButton { "MIDI Bluetooth devices..." }; - std::unique_ptr midiInputSelector, midiOutputSelector; ReferenceCountedArray midiInputs, midiOutputs; + std::unique_ptr midiInputSelector, midiOutputSelector; CriticalSection midiMonitorLock; Array incomingMessages; + MidiDeviceListConnection connection = MidiDeviceListConnection::make ([this] + { + updateDeviceLists(); + }); + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiDemo) }; diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index e2645ef018d6..4a1a9aed5e63 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -220,6 +220,12 @@ void AudioDeviceManager::audioDeviceListChanged() sendChangeMessage(); } +void AudioDeviceManager::midiDeviceListChanged() +{ + openLastRequestedMidiDevices (midiDeviceInfosFromXml, defaultMidiOutputDeviceInfo); + sendChangeMessage(); +} + //============================================================================== static void addIfNotNull (OwnedArray& list, AudioIODeviceType* const device) { @@ -430,65 +436,62 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, if (error.isNotEmpty() && selectDefaultDeviceOnFailure) error = initialise (numInputChansNeeded, numOutputChansNeeded, nullptr, false, preferredDefaultDeviceName); - midiDeviceInfosFromXml.clear(); enabledMidiInputs.clear(); - for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) - midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); - - auto isIdentifierAvailable = [] (const Array& available, const String& identifier) + const auto midiInputs = [&] { - for (auto& device : available) - if (device.identifier == identifier) - return true; + Array result; - return false; - }; + for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) + result.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); - auto getUpdatedIdentifierForName = [&] (const Array& available, const String& name) -> String - { - for (auto& device : available) - if (device.name == name) - return device.identifier; + return result; + }(); - return {}; - }; + const MidiDeviceInfo defaultOutputDeviceInfo (xml.getStringAttribute ("defaultMidiOutput"), + xml.getStringAttribute ("defaultMidiOutputDevice")); - auto inputs = MidiInput::getAvailableDevices(); + openLastRequestedMidiDevices (midiInputs, defaultOutputDeviceInfo); - for (auto& info : midiDeviceInfosFromXml) + return error; +} + +void AudioDeviceManager::openLastRequestedMidiDevices (const Array& desiredInputs, const MidiDeviceInfo& defaultOutput) +{ + const auto openDeviceIfAvailable = [&] (const Array& devices, + const MidiDeviceInfo& deviceToOpen, + auto&& doOpen) { - if (isIdentifierAvailable (inputs, info.identifier)) + const auto iterWithMatchingIdentifier = std::find_if (devices.begin(), devices.end(), [&] (const auto& x) { - setMidiInputDeviceEnabled (info.identifier, true); + return x.identifier == deviceToOpen.identifier; + }); + + if (iterWithMatchingIdentifier != devices.end()) + { + doOpen (deviceToOpen.identifier); + return; } - else + + const auto iterWithMatchingName = std::find_if (devices.begin(), devices.end(), [&] (const auto& x) { - auto identifier = getUpdatedIdentifierForName (inputs, info.name); + return x.name == deviceToOpen.name; + }); - if (identifier.isNotEmpty()) - setMidiInputDeviceEnabled (identifier, true); - } - } + if (iterWithMatchingName != devices.end()) + doOpen (iterWithMatchingName->identifier); + }; - MidiDeviceInfo defaultOutputDeviceInfo (xml.getStringAttribute ("defaultMidiOutput"), - xml.getStringAttribute ("defaultMidiOutputDevice")); + midiDeviceInfosFromXml = desiredInputs; - auto outputs = MidiOutput::getAvailableDevices(); + const auto inputs = MidiInput::getAvailableDevices(); - if (isIdentifierAvailable (outputs, defaultOutputDeviceInfo.identifier)) - { - setDefaultMidiOutputDevice (defaultOutputDeviceInfo.identifier); - } - else - { - auto identifier = getUpdatedIdentifierForName (outputs, defaultOutputDeviceInfo.name); + for (const auto& info : midiDeviceInfosFromXml) + openDeviceIfAvailable (inputs, info, [&] (const auto identifier) { setMidiInputDeviceEnabled (identifier, true); }); - if (identifier.isNotEmpty()) - setDefaultMidiOutputDevice (identifier); - } + const auto outputs = MidiOutput::getAvailableDevices(); - return error; + openDeviceIfAvailable (outputs, defaultOutput, [&] (const auto identifier) { setDefaultMidiOutputDevice (identifier); }); } String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index c655254ecff9..b3fd6e819330 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -499,6 +499,10 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster std::unique_ptr lastExplicitSettings; mutable bool listNeedsScanning = true; AudioBuffer tempBuffer; + MidiDeviceListConnection midiDeviceListConnection = MidiDeviceListConnection::make ([this] + { + midiDeviceListChanged(); + }); struct MidiCallbackInfo { @@ -537,6 +541,7 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster void audioDeviceErrorInt (const String&); void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); void audioDeviceListChanged(); + void midiDeviceListChanged(); String restartDevice (int blockSizeToUse, double sampleRateToUse, const BigInteger& ins, const BigInteger& outs); @@ -554,6 +559,7 @@ class JUCE_API AudioDeviceManager : public ChangeBroadcaster String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*); String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure, const String& preferredDefaultDeviceName, const AudioDeviceSetup*); + void openLastRequestedMidiDevices (const Array&, const MidiDeviceInfo&); AudioIODeviceType* findType (const String& inputName, const String& outputName); AudioIODeviceType* findType (const String& typeName); diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index 5449282bbc35..5a467889d803 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -46,6 +46,7 @@ #include "juce_audio_devices.h" #include "audio_io/juce_SampleRateHelpers.cpp" +#include "midi_io/juce_MidiDevices.cpp" //============================================================================== #if JUCE_MAC || JUCE_IOS @@ -249,6 +250,5 @@ namespace juce #include "audio_io/juce_AudioIODevice.cpp" #include "audio_io/juce_AudioIODeviceType.cpp" #include "midi_io/juce_MidiMessageCollector.cpp" -#include "midi_io/juce_MidiDevices.cpp" #include "sources/juce_AudioSourcePlayer.cpp" #include "sources/juce_AudioTransportSource.cpp" diff --git a/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp index b2baf8c1a1eb..e96a9d63c0f4 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp @@ -23,6 +23,81 @@ namespace juce { +class MidiDeviceListConnectionBroadcaster : private AsyncUpdater +{ +public: + ~MidiDeviceListConnectionBroadcaster() override + { + cancelPendingUpdate(); + } + + MidiDeviceListConnection::Key add (std::function callback) + { + JUCE_ASSERT_MESSAGE_THREAD + return callbacks.emplace (key++, std::move (callback)).first->first; + } + + void remove (const MidiDeviceListConnection::Key k) + { + JUCE_ASSERT_MESSAGE_THREAD + callbacks.erase (k); + } + + void notify() + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + cancelPendingUpdate(); + + const State newState; + + if (std::exchange (lastNotifiedState, newState) != newState) + for (auto it = callbacks.begin(); it != callbacks.end();) + NullCheckedInvocation::invoke ((it++)->second); + } + else + { + triggerAsyncUpdate(); + } + } + + static auto& get() + { + static MidiDeviceListConnectionBroadcaster result; + return result; + } + +private: + MidiDeviceListConnectionBroadcaster() = default; + + class State + { + Array ins = MidiInput::getAvailableDevices(), outs = MidiOutput::getAvailableDevices(); + auto tie() const { return std::tie (ins, outs); } + + public: + bool operator== (const State& other) const { return tie() == other.tie(); } + bool operator!= (const State& other) const { return tie() != other.tie(); } + }; + + void handleAsyncUpdate() override + { + notify(); + } + + std::map> callbacks; + State lastNotifiedState; + MidiDeviceListConnection::Key key = 0; +}; + +//============================================================================== +MidiDeviceListConnection::~MidiDeviceListConnection() noexcept +{ + if (broadcaster != nullptr) + broadcaster->remove (key); +} + +//============================================================================== void MidiInputCallback::handlePartialSysexMessage ([[maybe_unused]] MidiInput* source, [[maybe_unused]] const uint8* messageData, [[maybe_unused]] int numBytesSoFar, diff --git a/modules/juce_audio_devices/midi_io/juce_MidiDevices.h b/modules/juce_audio_devices/midi_io/juce_MidiDevices.h index 5dab3f9258de..e2b416d03136 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiDevices.h +++ b/modules/juce_audio_devices/midi_io/juce_MidiDevices.h @@ -22,6 +22,85 @@ namespace juce { + +class MidiDeviceListConnectionBroadcaster; + +/** + To find out when the available MIDI devices change, call MidiDeviceListConnection::make(), + passing a lambda that will be called on each configuration change. + + To stop the lambda receiving callbacks, destroy the MidiDeviceListConnection instance returned + from make(), or call reset() on it. + + @code + // Start listening for configuration changes + auto connection = MidiDeviceListConnection::make ([] + { + // This will print a message when devices are connected/disconnected + DBG ("MIDI devices changed"); + }); + + // Stop listening + connection.reset(); + @endcode +*/ +class MidiDeviceListConnection +{ +public: + using Key = uint64_t; + + /** Constructs an inactive connection. + */ + MidiDeviceListConnection() = default; + + MidiDeviceListConnection (const MidiDeviceListConnection&) = delete; + MidiDeviceListConnection (MidiDeviceListConnection&& other) noexcept + : broadcaster (std::exchange (other.broadcaster, nullptr)), + key (std::exchange (other.key, Key{})) + { + } + + MidiDeviceListConnection& operator= (const MidiDeviceListConnection&) = delete; + MidiDeviceListConnection& operator= (MidiDeviceListConnection&& other) noexcept + { + MidiDeviceListConnection (std::move (other)).swap (*this); + return *this; + } + + ~MidiDeviceListConnection() noexcept; + + /** Clears this connection. + + If this object had an active connection, that connection will be deactivated, and the + corresponding callback will be removed from the MidiDeviceListConnectionBroadcaster. + */ + void reset() noexcept + { + MidiDeviceListConnection().swap (*this); + } + + /** Registers a function to be called whenever the midi device list changes. + + The callback will only be active for as long as the return MidiDeviceListConnection remains + alive. To stop receiving device change notifications, destroy the Connection object, e.g. + by allowing it to fall out of scope. + */ + static MidiDeviceListConnection make (std::function); + +private: + MidiDeviceListConnection (MidiDeviceListConnectionBroadcaster* b, const Key k) + : broadcaster (b), key (k) {} + + void swap (MidiDeviceListConnection& other) noexcept + { + std::swap (other.broadcaster, broadcaster); + std::swap (other.key, key); + } + + MidiDeviceListConnectionBroadcaster* broadcaster = nullptr; + Key key = {}; +}; + //============================================================================== /** This struct contains information about a MIDI input or output device. @@ -61,8 +140,9 @@ struct MidiDeviceInfo String identifier; //============================================================================== - bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; } - bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); } + auto tie() const { return std::tie (name, identifier); } + bool operator== (const MidiDeviceInfo& other) const noexcept { return tie() == other.tie(); } + bool operator!= (const MidiDeviceInfo& other) const noexcept { return tie() != other.tie(); } }; class MidiInputCallback; diff --git a/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java b/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java index 0fd83c65cf73..337119b080c9 100644 --- a/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java +++ b/modules/juce_audio_devices/native/java/app/com/rmsl/juce/JuceMidiSupport.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; @@ -43,6 +44,7 @@ import android.bluetooth.BluetoothDevice; import android.media.midi.MidiOutputPort; import android.media.midi.MidiReceiver; +import android.os.Build; import android.os.ParcelUuid; import android.util.Log; import android.util.Pair; @@ -56,6 +58,7 @@ import java.util.List; import static android.content.Context.MIDI_SERVICE; +import static android.content.Context.BLUETOOTH_SERVICE; public class JuceMidiSupport { @@ -77,10 +80,18 @@ public interface JuceMidiPort String getName (); } + static BluetoothAdapter getDefaultBluetoothAdapter (Context ctx) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) + return BluetoothAdapter.getDefaultAdapter(); + + return ((BluetoothManager) ctx.getSystemService (BLUETOOTH_SERVICE)).getAdapter(); + } + //============================================================================== - public static class BluetoothManager extends ScanCallback + public static class BluetoothMidiManager extends ScanCallback { - BluetoothManager (Context contextToUse) + BluetoothMidiManager (Context contextToUse) { appContext = contextToUse; } @@ -92,7 +103,7 @@ public String[] getMidiBluetoothAddresses () public String getHumanReadableStringForBluetoothAddress (String address) { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + BluetoothDevice btDevice = getDefaultBluetoothAdapter (appContext).getRemoteDevice (address); return btDevice.getName (); } @@ -103,11 +114,11 @@ public int getBluetoothDeviceStatus (String address) public void startStopScan (boolean shouldStart) { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter (); + BluetoothAdapter bluetoothAdapter = getDefaultBluetoothAdapter (appContext); if (bluetoothAdapter == null) { - Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter"); + Log.d ("JUCE", "BluetoothMidiManager error: could not get default Bluetooth adapter"); return; } @@ -115,7 +126,7 @@ public void startStopScan (boolean shouldStart) if (bluetoothLeScanner == null) { - Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner"); + Log.d ("JUCE", "BluetoothMidiManager error: could not get Bluetooth LE scanner"); return; } @@ -140,7 +151,7 @@ public void startStopScan (boolean shouldStart) public boolean pairBluetoothMidiDevice (String address) { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + BluetoothDevice btDevice = getDefaultBluetoothAdapter (appContext).getRemoteDevice (address); if (btDevice == null) { @@ -543,12 +554,8 @@ public MidiDeviceManager (Context contextToUse) return; } - openPorts = new HashMap> (); - midiDevices = new ArrayList> (); - openTasks = new HashMap (); - btDevicesPairing = new HashMap (); - MidiDeviceInfo[] foundDevices = manager.getDevices (); + for (MidiDeviceInfo info : foundDevices) onDeviceAdded (info); @@ -810,6 +817,7 @@ public void removePort (MidiPortPath path) openPorts.remove (path); } + @Override public void onDeviceAdded (MidiDeviceInfo info) { // only add standard midi devices @@ -819,6 +827,7 @@ public void onDeviceAdded (MidiDeviceInfo info) manager.openDevice (info, this, null); } + @Override public void onDeviceRemoved (MidiDeviceInfo info) { synchronized (MidiDeviceManager.class) @@ -856,8 +865,11 @@ public void onDeviceRemoved (MidiDeviceInfo info) midiDevices.remove (devicePair); } } + + handleDevicesChanged(); } + @Override public void onDeviceStatusChanged (MidiDeviceStatus status) { } @@ -933,6 +945,7 @@ public void onDeviceOpenedDelayed (MidiDevice theDevice) BluetoothGatt gatt = openTasks.get (deviceID).getGatt (); openTasks.remove (deviceID); midiDevices.add (new Pair (theDevice, gatt)); + handleDevicesChanged(); } } else { @@ -973,7 +986,6 @@ public String getPortName (MidiPortPath path) { for (MidiDeviceInfo info : deviceInfos) { - int localIndex = 0; if (info.getId () == path.deviceId) { for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) @@ -1048,11 +1060,11 @@ private Pair getMidiDevicePairForId (int deviceId) } private MidiManager manager; - private HashMap btDevicesPairing; - private HashMap openTasks; - private ArrayList> midiDevices; + private HashMap btDevicesPairing = new HashMap(); + private HashMap openTasks = new HashMap(); + private ArrayList> midiDevices = new ArrayList>(); private MidiDeviceInfo[] deviceInfos; - private HashMap> openPorts; + private HashMap> openPorts = new HashMap>(); private Context appContext = null; } @@ -1070,9 +1082,9 @@ public static MidiDeviceManager getAndroidMidiDeviceManager (Context context) return midiDeviceManager; } - public static BluetoothManager getAndroidBluetoothManager (Context context) + public static BluetoothMidiManager getAndroidBluetoothManager (Context context) { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter (); + BluetoothAdapter adapter = getDefaultBluetoothAdapter (context); if (adapter == null) return null; @@ -1083,12 +1095,15 @@ public static BluetoothManager getAndroidBluetoothManager (Context context) synchronized (JuceMidiSupport.class) { if (bluetoothManager == null) - bluetoothManager = new BluetoothManager (context); + bluetoothManager = new BluetoothMidiManager (context); } return bluetoothManager; } + // To be called when devices become (un)available + private native static void handleDevicesChanged(); + private static MidiDeviceManager midiDeviceManager = null; - private static BluetoothManager bluetoothManager = null; + private static BluetoothMidiManager bluetoothManager = null; } diff --git a/modules/juce_audio_devices/native/juce_android_Midi.cpp b/modules/juce_audio_devices/native/juce_android_Midi.cpp index 08825325c8e2..2b4f417668de 100644 --- a/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -26,309 +26,759 @@ namespace juce //============================================================================== // This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23 // See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 javaMidiByteCode[] = -{31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220, -69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45, -201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81, -80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204, -156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248, -199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3, -250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200, -6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66, -224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205, -68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101, -192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188, -3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151, -1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191, -1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121, -224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239, -129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53, -224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224, -23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86, -224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4, -136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68, -21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60, -96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24, -184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64, -12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224, -108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216, -11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33, -252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132, -37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128, -29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127, -252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4, -210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216, -88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251, -208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62, -230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151, -159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190, -88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29, -109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163, -40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253, -51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110, -83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164, -197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167, -22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224, -216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117, -192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116, -189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199, -213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164, -159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105, -59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235, -217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221, -64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252, -94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124, -181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91, -243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89, -180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78, -199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79, -168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94, -247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232, -109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116, -187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158, -21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179, -82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159, -77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204, -127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209, -252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207, -117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7, -239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108, -203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251, -13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67, -92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168, -241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183, -49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55, -168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223, -13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247, -253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98, -237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252, -74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101, -230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78, -172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55, -170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81, -197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219, -5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95, -25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56, -227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203, -90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192, -127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114, -105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254, -27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220, -174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71, -137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66, -75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116, -221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105, -7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189, -10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158, -205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5, -196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253, -230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125, -196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68, -154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179, -252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30, -44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74, -79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90, -68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7, -41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161, -247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250, -105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8, -255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93, -66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13, -218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52, -151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9, -255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145, -154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4, -229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157, -220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200, -197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11, -164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218, -106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235, -122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21, -73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219, -210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66, -146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80, -43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211, -249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113, -244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240, -166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160, -208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39, -245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38, -158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253, -158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192, -208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246, -177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235, -81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111, -122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101, -44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3, -34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7, -114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176, -144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207, -231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236, -183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120, -152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96, -94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130, -243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129, -117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245, -172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122, -162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174, -240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245, -61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117, -234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179, -86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160, -171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238, -63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223, -90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238, -25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34, -104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196, -164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186, -169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118, -187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101, -11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47, -206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110, -157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91, -150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245, -125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62, -109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46, -153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185, -81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24, -83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121, -38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217, -95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134, -137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150, -129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73, -144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173, -205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43, -223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215, -241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186, -90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101, -19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153, -30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250, -215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27, -163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130, -249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34, -107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153, -167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127, -103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196, -38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138, -79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199, -199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163, -121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208, -151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163, -163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110, -231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221, -22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251, -233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70, -205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191, -173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251, -226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216, -165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116, -28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194, -76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181, -188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119, -77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190, -192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173, -88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168, -214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29, -200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176, -116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106, -254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219, -157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202, -19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105, -167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77, -146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76, -185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228, -29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198, -167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26, -74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80, -26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229, -198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249, -51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121, -246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199, -70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70, -102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200, -188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193, -216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201, -93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242, -252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175, -243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109, -27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140, -146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176, -68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213, -58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38, -226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184, -186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217, -34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38, -144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115, -200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38, -101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112, -214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28, -217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42, -76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223, -42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126, -208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227, -213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164, -147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205, -194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213, -163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83, -199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250, -77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74, -201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154, -244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127, -11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146, -29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35, -143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33, -46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163, -29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160, -245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102, -188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240, -91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253, -30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105, -137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122, -186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244, -155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137, -5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233, -249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228, -39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49, -177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227, -13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179, -189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94, -238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196, -0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23, -107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161, -223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57, -174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108, -143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240, -229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232, -105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198, -190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56, -124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72, -172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25, -117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118, -88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198, -127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184, -32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148, -46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179, -39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85, -105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48, -220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123, -137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182, -235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64, -244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110, -26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181, -74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15, -102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195, -186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132, -107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157, -74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12, -255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248, -248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0}; - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") - -DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode) -#undef JNI_CLASS_MEMBERS +constexpr unsigned char javaMidiByteCode[] +{ + 0x1f, 0x8b, 0x08, 0x08, 0xa3, 0xf2, 0xc6, 0x63, 0x00, 0x03, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0x95, 0x7c, + 0x09, 0x7c, 0xdb, 0x47, 0x95, 0xff, 0x9b, 0x9f, 0x7e, 0xb2, 0x6c, 0xf9, + 0x92, 0x64, 0x27, 0x76, 0x1c, 0xc7, 0x96, 0x8f, 0xc4, 0xf7, 0x15, 0xc7, + 0xa9, 0x13, 0x3b, 0xa9, 0xef, 0xc4, 0xb1, 0x73, 0xd4, 0x56, 0xd2, 0x36, + 0x2e, 0xdb, 0x2a, 0xb6, 0x12, 0xab, 0xb1, 0x25, 0x45, 0x92, 0xd3, 0x04, + 0x4a, 0x9b, 0x1e, 0x6c, 0x12, 0x08, 0x10, 0x4a, 0x9b, 0x96, 0x12, 0xd8, + 0xd2, 0x1b, 0x28, 0xd0, 0x76, 0x39, 0xca, 0x6e, 0x77, 0x29, 0x6c, 0x97, + 0x2d, 0xf7, 0xd5, 0x85, 0x70, 0x2d, 0xa5, 0x84, 0xa5, 0x94, 0x02, 0x61, + 0xf7, 0xbf, 0x6c, 0xe8, 0xbf, 0x90, 0xfd, 0xbe, 0x99, 0xf9, 0x49, 0x3f, + 0x3b, 0x6e, 0x53, 0x92, 0xcf, 0x57, 0x6f, 0xe6, 0xcd, 0x9b, 0x99, 0x37, + 0x33, 0x6f, 0xde, 0xbc, 0xf9, 0xfd, 0x24, 0x4f, 0x85, 0x0e, 0xb9, 0x5b, + 0xdb, 0x3b, 0xe8, 0xae, 0x83, 0xd9, 0xd5, 0xe5, 0x6f, 0x37, 0xff, 0xb8, + 0x6a, 0xcf, 0xe9, 0xa7, 0xdf, 0xb2, 0xa7, 0xbd, 0xf5, 0xf0, 0x6b, 0xab, + 0x1b, 0x4f, 0x9c, 0xba, 0xff, 0x6c, 0xe9, 0x30, 0x51, 0x8c, 0x88, 0x0e, + 0xed, 0x5a, 0xe3, 0x23, 0xfd, 0x6f, 0x74, 0x33, 0xd1, 0x32, 0xa1, 0xf8, + 0x9b, 0x80, 0xe7, 0x4d, 0xa2, 0xab, 0x40, 0xcf, 0x39, 0x89, 0x2a, 0x40, + 0x3d, 0x6e, 0xa2, 0xbf, 0x07, 0x2d, 0xce, 0x26, 0xca, 0x02, 0x7d, 0xdc, + 0x83, 0x3a, 0x97, 0x11, 0x7d, 0xdb, 0x4b, 0x74, 0xa6, 0x89, 0xe8, 0x47, + 0xc0, 0x4f, 0x81, 0x17, 0x81, 0x5f, 0x02, 0xd9, 0xcd, 0x44, 0x4b, 0x80, + 0x15, 0x40, 0x05, 0x50, 0x03, 0x34, 0x01, 0xfd, 0xc0, 0x6d, 0xc0, 0x73, + 0xc0, 0x59, 0xe0, 0x57, 0xc0, 0x6f, 0x80, 0x73, 0x80, 0xb7, 0x85, 0xa8, + 0x13, 0xe8, 0x02, 0x7a, 0x81, 0x00, 0x30, 0x0d, 0x1c, 0x04, 0x4e, 0x00, + 0xef, 0x05, 0xde, 0x0f, 0xdc, 0x03, 0x7c, 0x08, 0x78, 0x00, 0x78, 0x04, + 0x78, 0x0c, 0x78, 0x12, 0x38, 0x03, 0xfc, 0x19, 0xf0, 0xb7, 0x12, 0x8d, + 0x00, 0x37, 0x02, 0x1f, 0x05, 0xbe, 0x05, 0xbc, 0x0a, 0x94, 0xb7, 0x11, + 0x6d, 0x01, 0x66, 0x81, 0x93, 0xc0, 0x3f, 0x01, 0x2f, 0x02, 0x7f, 0x06, + 0xca, 0x56, 0x63, 0x3c, 0xc0, 0x01, 0xe0, 0x1e, 0xe0, 0x69, 0xe0, 0xb7, + 0xc0, 0xaa, 0x76, 0xa2, 0xcd, 0xc0, 0x2c, 0x70, 0x12, 0x78, 0x0c, 0xf8, + 0x22, 0xf0, 0x03, 0xe0, 0x1c, 0x60, 0xae, 0xc1, 0xbc, 0x01, 0xcd, 0xc0, + 0x14, 0x70, 0x37, 0xf0, 0x35, 0xe0, 0x4f, 0xc0, 0xa6, 0x0e, 0xa2, 0x77, + 0x03, 0xcf, 0x00, 0xbf, 0x03, 0xf2, 0xd7, 0x12, 0xb5, 0x03, 0xe3, 0xc0, + 0x41, 0xe0, 0x2e, 0xe0, 0x09, 0xe0, 0x39, 0xe0, 0xc7, 0xc0, 0x39, 0xc0, + 0xc4, 0x9c, 0x16, 0x03, 0x2d, 0xc0, 0x36, 0x60, 0x16, 0xb8, 0x1d, 0xf8, + 0x20, 0xf0, 0x29, 0xe0, 0x59, 0xe0, 0x87, 0xc0, 0xef, 0x00, 0x47, 0x27, + 0xfa, 0x06, 0x1a, 0x80, 0x0d, 0xc0, 0x36, 0x20, 0x08, 0x1c, 0x02, 0xde, + 0x05, 0x7c, 0x13, 0x78, 0x11, 0x38, 0x07, 0xfc, 0x09, 0xf0, 0xaf, 0x23, + 0xaa, 0x02, 0x6a, 0x80, 0x46, 0x60, 0x35, 0xb0, 0x1e, 0xd8, 0x08, 0xf4, + 0x03, 0xc3, 0xc0, 0x0e, 0x60, 0x27, 0xb0, 0x1b, 0xb8, 0x0e, 0xd8, 0x0b, + 0xcc, 0x02, 0x49, 0xe0, 0x10, 0xf0, 0x36, 0xe0, 0x66, 0xe0, 0x36, 0xe0, + 0x28, 0xf0, 0x71, 0xe0, 0x47, 0x00, 0xad, 0xc7, 0x7a, 0x03, 0xf5, 0xc0, + 0x5a, 0xa0, 0x1f, 0xd8, 0x0c, 0xec, 0x04, 0x82, 0x40, 0x18, 0x48, 0x02, + 0x27, 0x81, 0xfb, 0x81, 0xa7, 0x80, 0xaf, 0x02, 0x3f, 0x01, 0xce, 0x02, + 0xbf, 0x01, 0xfe, 0x1b, 0x78, 0x15, 0x10, 0x5d, 0x44, 0x6e, 0xa0, 0x08, + 0xa8, 0x00, 0x9a, 0x81, 0x36, 0xa0, 0x0b, 0xd8, 0x0c, 0x04, 0x80, 0x6b, + 0x80, 0x13, 0xc0, 0x29, 0xe0, 0xf3, 0xc0, 0x8f, 0x80, 0x9f, 0x03, 0xbf, + 0x05, 0xfe, 0x0b, 0xf8, 0x0b, 0xb7, 0xd1, 0x8d, 0x36, 0x80, 0x76, 0x60, + 0x07, 0xb0, 0x0f, 0xb8, 0x11, 0x38, 0x05, 0x7c, 0x02, 0xf8, 0x1c, 0xf0, + 0x55, 0xe0, 0x3f, 0x81, 0x3f, 0x00, 0x62, 0x03, 0x51, 0x2e, 0xb0, 0x14, + 0xe8, 0x05, 0x36, 0x03, 0xdb, 0x81, 0xab, 0x80, 0x1b, 0x81, 0x87, 0x80, + 0x6f, 0x01, 0x2f, 0x03, 0xc6, 0x46, 0xd8, 0x38, 0x50, 0x0c, 0xd4, 0x00, + 0xeb, 0x80, 0x7e, 0x60, 0x0c, 0x98, 0x04, 0xe6, 0x80, 0x9b, 0x80, 0x3b, + 0x81, 0x07, 0x80, 0xc7, 0x80, 0xcf, 0x02, 0xff, 0x02, 0x7c, 0x1d, 0xf8, + 0x77, 0xe0, 0x45, 0xe0, 0x15, 0xe0, 0x8f, 0xc0, 0x5f, 0x00, 0xd7, 0xe5, + 0xe8, 0x1f, 0x58, 0x06, 0x34, 0x02, 0xeb, 0x80, 0xcb, 0x81, 0x41, 0xe0, + 0x6a, 0x20, 0x0e, 0x1c, 0x03, 0xfe, 0x0e, 0x78, 0x0a, 0xf8, 0x2a, 0xf0, + 0x53, 0xe0, 0x15, 0xe0, 0x35, 0xa0, 0xa0, 0x87, 0xa8, 0x1a, 0xe8, 0x02, + 0x06, 0x80, 0x31, 0x60, 0x0a, 0xb8, 0x09, 0x38, 0x01, 0xdc, 0x0b, 0xdc, + 0x0f, 0x3c, 0x05, 0x7c, 0x03, 0xf8, 0x21, 0xf0, 0x12, 0x20, 0x7a, 0x89, + 0x96, 0x03, 0x2d, 0xc0, 0xe5, 0xc0, 0x16, 0x60, 0x27, 0x70, 0x0d, 0x70, + 0x00, 0x78, 0x37, 0xf0, 0x30, 0xf0, 0x04, 0xf0, 0x8f, 0xc0, 0x33, 0xc0, + 0x19, 0xe0, 0x05, 0xe0, 0x15, 0xc0, 0xd5, 0x47, 0x94, 0x03, 0x2c, 0x01, + 0xca, 0x81, 0x1a, 0xa0, 0x09, 0x18, 0x06, 0xc6, 0x81, 0x29, 0xe0, 0x00, + 0x70, 0x0b, 0x70, 0x14, 0x78, 0x0f, 0xf0, 0x01, 0xe0, 0x51, 0xe0, 0x93, + 0xc0, 0x53, 0xc0, 0x33, 0xc0, 0x57, 0x80, 0xef, 0x01, 0x2f, 0x03, 0xce, + 0x7e, 0xb4, 0x05, 0xac, 0x02, 0x5a, 0x80, 0x3c, 0x98, 0x58, 0x01, 0x29, + 0x5f, 0x54, 0x09, 0x54, 0x01, 0xd5, 0xc0, 0x4a, 0x60, 0x15, 0x50, 0x03, + 0xd4, 0x02, 0x75, 0x40, 0x3d, 0xd0, 0x00, 0x34, 0x02, 0x70, 0x45, 0x04, + 0xd7, 0x42, 0x70, 0x0b, 0x04, 0x17, 0x40, 0xd8, 0xea, 0x84, 0x6d, 0x4d, + 0xd8, 0xba, 0x84, 0x2d, 0x4a, 0xd8, 0x96, 0x84, 0x6d, 0x47, 0xd8, 0x4e, + 0x84, 0x2d, 0x42, 0xda, 0x94, 0x09, 0xe6, 0x43, 0x30, 0x09, 0xc2, 0x52, + 0x13, 0x96, 0x86, 0x30, 0xbd, 0x84, 0xa1, 0x12, 0x54, 0x23, 0xa8, 0x43, + 0x03, 0xc0, 0x20, 0x30, 0xa4, 0xfd, 0x25, 0xdc, 0x27, 0xc1, 0xad, 0xd2, + 0x16, 0x60, 0x84, 0xfd, 0x29, 0xb0, 0x15, 0xd8, 0x06, 0x6c, 0x07, 0x76, + 0x00, 0x57, 0x00, 0x63, 0xc0, 0x38, 0x10, 0x00, 0x76, 0x02, 0x57, 0x02, + 0x57, 0x03, 0xbb, 0x81, 0x6b, 0x80, 0xbf, 0x01, 0xae, 0x05, 0x82, 0xc0, + 0x1e, 0x60, 0x12, 0xb8, 0x1e, 0x78, 0x2b, 0xf0, 0x76, 0xe0, 0x26, 0xe0, + 0x66, 0xe0, 0x08, 0x70, 0x0b, 0x70, 0x2b, 0xa9, 0xb9, 0xb1, 0xfe, 0xe5, + 0x6b, 0xba, 0x03, 0x83, 0xf7, 0xe8, 0xf4, 0x55, 0x48, 0x2f, 0x03, 0x35, + 0x74, 0xbe, 0x4c, 0xa7, 0xaf, 0xd3, 0x7c, 0x87, 0x8d, 0xef, 0xd0, 0x75, + 0x99, 0x6f, 0x6a, 0xbe, 0x5f, 0xa7, 0xa7, 0x35, 0x3f, 0xcb, 0x26, 0x8f, + 0xe3, 0x80, 0x92, 0x9a, 0x9f, 0xa3, 0xf9, 0x25, 0x5a, 0xa7, 0x1b, 0x35, + 0xdf, 0xd2, 0x89, 0xd3, 0x1e, 0x5b, 0xba, 0xc0, 0x26, 0xbf, 0x44, 0xcb, + 0x97, 0xe8, 0x32, 0xab, 0x6e, 0xa9, 0xad, 0xaf, 0x32, 0xad, 0x5b, 0x89, + 0xd6, 0x89, 0x65, 0xca, 0x74, 0x7a, 0x87, 0x4e, 0x57, 0xe8, 0x71, 0xb1, + 0x4c, 0xb5, 0x96, 0x29, 0xd5, 0xe9, 0xdb, 0x91, 0x5e, 0xa1, 0xd3, 0x27, + 0xb4, 0xfc, 0x2a, 0x5b, 0xdd, 0x1a, 0x5d, 0x77, 0x39, 0x29, 0x5b, 0xba, + 0x47, 0xeb, 0xd0, 0x6a, 0xd3, 0xb3, 0xcd, 0xa6, 0xdb, 0x6a, 0x9b, 0x6e, + 0x9c, 0xbe, 0x0f, 0xfc, 0x72, 0x9d, 0x7e, 0xb4, 0x31, 0xcd, 0xb7, 0xe6, + 0xb3, 0xdd, 0xd6, 0x4e, 0xbb, 0x4d, 0x7f, 0x4e, 0x3f, 0x6e, 0x4b, 0x5b, + 0x63, 0x5c, 0x6b, 0xeb, 0x6b, 0xbd, 0xad, 0x2f, 0xb6, 0xcd, 0xa7, 0x34, + 0xbf, 0x5b, 0xf3, 0xd9, 0x46, 0x2e, 0xd7, 0xe9, 0xfd, 0x3a, 0xcd, 0x75, + 0x67, 0x74, 0xfa, 0x19, 0xa4, 0x67, 0x75, 0xfa, 0x39, 0xa4, 0x23, 0x3a, + 0xfd, 0x3c, 0xd2, 0x51, 0x9d, 0x3e, 0x8b, 0xf4, 0x41, 0x9d, 0x7e, 0xa5, + 0x51, 0xc5, 0x02, 0x9c, 0x3e, 0x8f, 0xf4, 0x01, 0xab, 0x7d, 0x6c, 0xa8, + 0xa4, 0x4e, 0xe7, 0x20, 0x3d, 0xa7, 0xd3, 0x85, 0xb6, 0xb4, 0xbf, 0x29, + 0xdd, 0x66, 0xa3, 0x2d, 0x7d, 0x9d, 0xad, 0xaf, 0x35, 0x36, 0x7e, 0x77, + 0x53, 0xba, 0xdf, 0x01, 0x1b, 0x7f, 0x87, 0x2d, 0x7d, 0x95, 0xad, 0xdf, + 0xeb, 0x6c, 0xfc, 0x69, 0x5b, 0xdd, 0x18, 0xd2, 0x37, 0x58, 0x63, 0xb7, + 0xc9, 0x1f, 0x43, 0xfa, 0x90, 0x4e, 0x9f, 0xb4, 0xd5, 0x7d, 0xdc, 0xa6, + 0x0f, 0xaf, 0x9d, 0x25, 0xff, 0xa8, 0x8d, 0xbf, 0xc3, 0x96, 0x3e, 0x6d, + 0xeb, 0xeb, 0x41, 0xa4, 0x13, 0x56, 0x3b, 0x48, 0x1f, 0xd6, 0xe9, 0xa7, + 0x9a, 0xd2, 0x73, 0xf5, 0x0c, 0xd2, 0x71, 0x9d, 0xfe, 0x7a, 0x93, 0xda, + 0xc3, 0x3d, 0x7a, 0x8d, 0xde, 0xa6, 0xd3, 0xbc, 0x46, 0x37, 0xea, 0xf4, + 0x59, 0x5b, 0xfa, 0x3e, 0x5b, 0xda, 0xb2, 0x9f, 0x7e, 0x5d, 0x97, 0xd3, + 0x03, 0x36, 0x7b, 0x18, 0xb4, 0xd9, 0xc3, 0x90, 0xe6, 0x97, 0xe8, 0xf4, + 0x8d, 0xda, 0x9e, 0xa7, 0x79, 0x5d, 0x60, 0x8d, 0x1f, 0xd1, 0xd4, 0x21, + 0x78, 0xaf, 0x78, 0xe8, 0x28, 0x31, 0x6d, 0xa7, 0x77, 0x4a, 0xba, 0x8e, + 0x4e, 0x48, 0x9a, 0x45, 0x42, 0xb0, 0x9f, 0x5d, 0x46, 0x7f, 0x4b, 0x4c, + 0x7b, 0xe8, 0xab, 0x92, 0x0a, 0xfa, 0x96, 0xa4, 0x35, 0xf4, 0x3f, 0x92, + 0xd6, 0xd2, 0xab, 0xc4, 0xbe, 0x78, 0x89, 0x94, 0xab, 0xd2, 0xfc, 0x2a, + 0xcd, 0x5f, 0xa9, 0xf3, 0x4c, 0x3d, 0x82, 0xf7, 0x9a, 0x49, 0xef, 0x25, + 0xa6, 0x5e, 0xfa, 0x9e, 0xa4, 0xaa, 0x7c, 0x95, 0x2e, 0xaf, 0xd1, 0xfa, + 0xd4, 0xc0, 0x13, 0xbf, 0x47, 0xd2, 0x01, 0xba, 0x57, 0xd2, 0x62, 0xfa, + 0x8e, 0xa4, 0x6b, 0xe9, 0xdf, 0x75, 0xf9, 0x7f, 0x6b, 0xfa, 0xff, 0x48, + 0xed, 0xd5, 0x0f, 0x48, 0xda, 0x43, 0x5f, 0xd7, 0xf9, 0x3f, 0x11, 0x9f, + 0x05, 0x15, 0xf4, 0x2e, 0x49, 0x6b, 0xe8, 0x79, 0x62, 0x9f, 0x97, 0x45, + 0x8f, 0x49, 0xea, 0xa0, 0x4f, 0x4a, 0x9a, 0x41, 0xff, 0x42, 0xec, 0xf3, + 0x32, 0xe9, 0x2e, 0x49, 0xab, 0xe8, 0x21, 0x4d, 0xff, 0x89, 0xd8, 0xe7, + 0x35, 0xd0, 0xfb, 0x35, 0xfd, 0xa0, 0xa4, 0x4e, 0x7a, 0x5c, 0xd2, 0xed, + 0x74, 0x01, 0xd4, 0x09, 0x7e, 0x06, 0x68, 0x26, 0x6a, 0x3c, 0x48, 0xec, + 0x17, 0x87, 0x29, 0x47, 0x30, 0xbd, 0x8c, 0xf2, 0x41, 0xdd, 0xba, 0x3c, + 0x3b, 0x45, 0xb3, 0xe9, 0xa4, 0xa4, 0x6e, 0xca, 0x42, 0x79, 0xae, 0x6e, + 0x2f, 0x4f, 0x97, 0xe7, 0x81, 0x73, 0x52, 0xd2, 0x1c, 0x72, 0x09, 0x45, + 0x33, 0x05, 0xfb, 0xcc, 0x3c, 0xfa, 0x28, 0x31, 0xad, 0xa4, 0x67, 0x41, + 0xbd, 0x5a, 0x2f, 0x2f, 0x3c, 0xeb, 0xe7, 0x24, 0xf5, 0xd0, 0xef, 0x24, + 0xf5, 0xd2, 0x7f, 0x81, 0xfa, 0xb4, 0xfe, 0x1c, 0xdc, 0x7f, 0x41, 0xd3, + 0x7f, 0x25, 0xe5, 0x6f, 0x3f, 0x2b, 0xe9, 0x38, 0x7d, 0x51, 0x52, 0x1f, + 0x7d, 0x49, 0xf3, 0xb9, 0x7c, 0x89, 0x6e, 0x77, 0x09, 0x4e, 0x2f, 0x13, + 0xfd, 0x2e, 0xd5, 0x7a, 0x15, 0xe3, 0xb4, 0x7a, 0x52, 0xd2, 0x36, 0x7a, + 0x45, 0xd2, 0x2e, 0xfa, 0xad, 0xa4, 0x1b, 0xe9, 0x35, 0x49, 0x37, 0xd0, + 0x12, 0xc1, 0xf6, 0xa7, 0xea, 0x2f, 0x83, 0xc5, 0xdf, 0xa9, 0xe9, 0x07, + 0xa5, 0x2d, 0xaa, 0x76, 0x4a, 0xa1, 0xff, 0x03, 0xd2, 0x66, 0x0b, 0xe8, + 0x7e, 0x62, 0x5f, 0x69, 0xd0, 0x3d, 0xd2, 0x1e, 0x87, 0x64, 0x79, 0x05, + 0xd6, 0x53, 0x51, 0x41, 0x1f, 0x97, 0x74, 0x15, 0x7d, 0x5a, 0xd2, 0x5d, + 0xf4, 0x8f, 0x92, 0x6e, 0xa6, 0x33, 0x92, 0x36, 0xd2, 0x4b, 0x92, 0x36, + 0xd1, 0xaf, 0x25, 0x1d, 0xa3, 0xf3, 0x92, 0x8e, 0x50, 0xae, 0xb4, 0xeb, + 0x4d, 0x54, 0x28, 0xed, 0xb7, 0x57, 0xb6, 0x57, 0xa9, 0xf5, 0x62, 0xfa, + 0x61, 0x49, 0xd5, 0xfc, 0x54, 0x22, 0x2a, 0xf8, 0x37, 0x49, 0xb7, 0xd1, + 0x37, 0x74, 0xf9, 0x59, 0x49, 0xb7, 0xd2, 0xcb, 0x92, 0x8e, 0x52, 0x86, + 0x50, 0xfc, 0x6c, 0x4d, 0xf3, 0x04, 0xdb, 0x77, 0x8f, 0x6c, 0xb7, 0x4a, + 0xb7, 0x5b, 0xa5, 0xdb, 0xad, 0xd2, 0xed, 0x56, 0xe9, 0xf6, 0xaa, 0x74, + 0xfd, 0x2a, 0x5d, 0xbf, 0x4a, 0xd7, 0xaf, 0xd6, 0xf5, 0xaa, 0xb5, 0x7c, + 0xb5, 0x96, 0xaf, 0xd6, 0xf2, 0xd5, 0x5a, 0xbe, 0x5a, 0xcb, 0xaf, 0x44, + 0xd4, 0x91, 0x21, 0xf7, 0xd1, 0x1a, 0xfa, 0xa1, 0xa4, 0x1d, 0xf4, 0x23, + 0x4d, 0x7f, 0x2c, 0x69, 0x3b, 0xfd, 0x44, 0xd2, 0xb5, 0xf4, 0x53, 0x4d, + 0xff, 0x43, 0xf3, 0x7f, 0xa9, 0xe9, 0x7f, 0x4a, 0xba, 0x9a, 0x7e, 0xa5, + 0xe9, 0x6f, 0xe4, 0xbe, 0xeb, 0x97, 0xed, 0xae, 0x42, 0xff, 0xef, 0x93, + 0xb4, 0x8a, 0x3e, 0x26, 0xa9, 0x4b, 0xde, 0xf5, 0xf8, 0x6c, 0xfc, 0x8c, + 0xa4, 0x88, 0xa6, 0x84, 0xda, 0x6f, 0x19, 0x72, 0xdf, 0xa9, 0xf1, 0xd6, + 0xc0, 0x52, 0xfe, 0x4e, 0xd2, 0x12, 0xba, 0x4f, 0xd2, 0x1a, 0x7a, 0x58, + 0x52, 0xb5, 0x7e, 0x35, 0xb0, 0x9b, 0x27, 0x24, 0xbd, 0x92, 0x9e, 0x92, + 0x74, 0x17, 0x7d, 0x5e, 0xd3, 0x7f, 0x90, 0xb4, 0x90, 0x9e, 0x96, 0x74, + 0x25, 0xfd, 0xb3, 0xa4, 0xa5, 0xf4, 0x8c, 0xa4, 0xeb, 0xe9, 0x67, 0x92, + 0xae, 0xa3, 0x17, 0x34, 0xfd, 0xb9, 0xe6, 0xbf, 0x28, 0x69, 0x37, 0xfd, + 0x42, 0xfb, 0x85, 0xdf, 0x4b, 0x5a, 0x44, 0xe7, 0x24, 0x5d, 0x46, 0x7f, + 0x90, 0x74, 0x07, 0xfd, 0x51, 0xd2, 0x56, 0xfa, 0x5f, 0xed, 0x47, 0xfe, + 0x22, 0xe9, 0x26, 0x2a, 0x10, 0xec, 0x1f, 0x9a, 0xe5, 0x38, 0x6a, 0x11, + 0x91, 0x9d, 0xd6, 0xfe, 0xe2, 0x2b, 0xd2, 0x4f, 0x34, 0x60, 0x47, 0x2a, + 0x9a, 0xa1, 0xe9, 0x6d, 0x92, 0x2e, 0xa5, 0x47, 0x24, 0x5d, 0x4e, 0x8f, + 0x4a, 0x6a, 0xd2, 0xa7, 0x74, 0xf9, 0x97, 0x89, 0x63, 0x82, 0x46, 0x29, + 0xdf, 0xa6, 0xdb, 0x69, 0xc3, 0xca, 0xf9, 0x04, 0xd3, 0x32, 0x2a, 0x12, + 0x1c, 0x03, 0xa8, 0xf6, 0x56, 0xeb, 0x79, 0x5b, 0x8d, 0x28, 0xe4, 0x13, + 0xc4, 0x67, 0xbd, 0xea, 0xbf, 0x1d, 0xf3, 0xff, 0x35, 0xe2, 0x58, 0x74, + 0x50, 0xca, 0x75, 0x60, 0x67, 0xf0, 0x3e, 0x59, 0xab, 0xeb, 0xad, 0x85, + 0xdc, 0x3b, 0x74, 0xfe, 0x0e, 0x9d, 0x3f, 0x25, 0x69, 0x2d, 0x7d, 0x53, + 0xe7, 0xff, 0x4c, 0x2a, 0x5e, 0x70, 0x0b, 0xa6, 0x3b, 0xc9, 0x2b, 0x38, + 0xa6, 0xad, 0xa3, 0x63, 0xc4, 0x71, 0xad, 0x6a, 0xa7, 0x53, 0xd7, 0xef, + 0x84, 0xfc, 0xdd, 0x92, 0xfa, 0x65, 0x3f, 0x9d, 0x88, 0x98, 0xbf, 0x2d, + 0x69, 0x05, 0xfd, 0x7f, 0xcd, 0xe7, 0xf6, 0xd6, 0xe9, 0x7a, 0xeb, 0x74, + 0xff, 0xeb, 0x74, 0x3f, 0xeb, 0x74, 0x3f, 0xeb, 0x74, 0x3f, 0xeb, 0xa1, + 0xff, 0x73, 0xc4, 0xb4, 0x9c, 0x7e, 0x40, 0x1c, 0x9f, 0x28, 0xbd, 0xba, + 0x35, 0xdd, 0xa0, 0xdb, 0xd9, 0x80, 0xe8, 0xd8, 0x10, 0x1c, 0x4f, 0xab, + 0xfc, 0x46, 0x6d, 0x77, 0x1c, 0xb3, 0x09, 0x6e, 0x93, 0xd4, 0xbf, 0x62, + 0xe0, 0x1a, 0x04, 0xd5, 0x5f, 0xc2, 0x21, 0x77, 0x6a, 0x48, 0xc5, 0x6b, + 0x22, 0x23, 0x1d, 0x6f, 0x71, 0xf9, 0x8d, 0x28, 0x3f, 0xaf, 0x0f, 0xc1, + 0x95, 0x9a, 0xef, 0xb4, 0x95, 0x9f, 0x40, 0x79, 0xe1, 0xa0, 0xca, 0xaf, + 0xd2, 0xfc, 0x8d, 0xb6, 0xf2, 0xd3, 0x28, 0x1f, 0xd1, 0xe5, 0x35, 0xba, + 0xff, 0xa5, 0x40, 0x4f, 0xa3, 0x2a, 0x7f, 0x0c, 0xe5, 0x71, 0x5d, 0x5e, + 0xab, 0xeb, 0xd9, 0xfb, 0x7f, 0x0d, 0xe5, 0x2f, 0xeb, 0xf2, 0x3a, 0x5d, + 0xdf, 0x5e, 0x9e, 0x83, 0x0b, 0x81, 0xa9, 0x0f, 0xe3, 0x4a, 0x5d, 0xce, + 0x67, 0xf2, 0x66, 0xdd, 0x7e, 0x29, 0xca, 0x9b, 0x75, 0x79, 0x95, 0xad, + 0xbe, 0x55, 0xbe, 0x06, 0xe5, 0xd7, 0xeb, 0x72, 0x07, 0xfc, 0x24, 0xc7, + 0xeb, 0xf7, 0xd5, 0xa9, 0x58, 0x37, 0xe0, 0x71, 0xd0, 0x01, 0xcf, 0x71, + 0xee, 0x85, 0x22, 0x7e, 0x03, 0xbc, 0x1c, 0x63, 0x9d, 0x91, 0x4d, 0x25, + 0x46, 0x21, 0xfa, 0xf8, 0x10, 0x1d, 0xf0, 0xaf, 0x86, 0xbc, 0xcf, 0xc8, + 0x37, 0x94, 0xe4, 0x31, 0x2d, 0xf9, 0x76, 0x48, 0xba, 0xc1, 0xb5, 0xda, + 0xfb, 0x58, 0x9d, 0x8a, 0xdf, 0xe7, 0x4b, 0xcd, 0x6a, 0xa9, 0x74, 0xbf, + 0x9f, 0xaa, 0x53, 0x31, 0xfc, 0x62, 0xfd, 0x46, 0x3c, 0x4e, 0xc8, 0xe4, + 0x18, 0x5e, 0x29, 0x2f, 0xa4, 0xfc, 0x67, 0x2c, 0x79, 0xbf, 0x93, 0x22, + 0x9e, 0x8f, 0x4a, 0x6b, 0xa9, 0xf2, 0x0e, 0xa1, 0xce, 0xc7, 0x70, 0x52, + 0xe5, 0xc0, 0x4a, 0x86, 0x70, 0x52, 0xa9, 0xf6, 0x79, 0xdc, 0x9f, 0xaf, + 0xe3, 0xb3, 0x10, 0xf1, 0x9b, 0x27, 0x0f, 0xf9, 0x4a, 0xd8, 0x55, 0xcc, + 0xc3, 0x7e, 0x64, 0x02, 0x6d, 0x4f, 0xf8, 0x1c, 0xf2, 0x2e, 0x61, 0xca, + 0x53, 0x9b, 0xe8, 0xcb, 0x75, 0x6a, 0x9d, 0xe2, 0x9e, 0x4f, 0x20, 0x9f, + 0xe3, 0x88, 0x7b, 0x3e, 0x0e, 0xea, 0x86, 0xcd, 0xe6, 0x82, 0xf7, 0x18, + 0xf3, 0xd0, 0x7e, 0x2e, 0xf9, 0xbc, 0x91, 0xd6, 0xf5, 0x38, 0xdf, 0x6a, + 0xcf, 0xe5, 0xe9, 0x51, 0xa8, 0x7f, 0xdc, 0x5f, 0x9e, 0xb4, 0x15, 0x87, + 0xe4, 0x7e, 0xab, 0x4e, 0xed, 0x1b, 0x9f, 0x67, 0xb5, 0xc3, 0x49, 0x3e, + 0x7f, 0xbb, 0xc3, 0x0b, 0x9d, 0x7d, 0xe8, 0x2f, 0x07, 0x6d, 0x66, 0x53, + 0xa0, 0x82, 0xc7, 0x60, 0xea, 0x31, 0x3f, 0x82, 0xfa, 0xbe, 0x9e, 0x76, + 0x47, 0x39, 0x95, 0x38, 0x78, 0xae, 0xc3, 0x72, 0xae, 0x1d, 0x56, 0x0d, + 0x47, 0xa7, 0xc3, 0x47, 0x81, 0x6a, 0x55, 0xc3, 0x21, 0x6b, 0x3c, 0x0a, + 0xbe, 0xce, 0x39, 0x22, 0xfe, 0x0d, 0x58, 0xe3, 0x3c, 0xc4, 0x0b, 0x86, + 0xbc, 0x4f, 0xfd, 0xb4, 0x4e, 0xdd, 0xff, 0x02, 0xd7, 0xa5, 0xe7, 0xb5, + 0x44, 0x14, 0x62, 0xfc, 0x99, 0x54, 0xe2, 0xca, 0x91, 0x6d, 0x1f, 0x80, + 0x7c, 0x44, 0x5e, 0x9a, 0x72, 0x34, 0x3f, 0x3b, 0xc5, 0xef, 0x74, 0x5d, + 0x46, 0x95, 0xc8, 0xc7, 0x3c, 0xf9, 0xf0, 0xd8, 0x25, 0xc2, 0x44, 0x2b, + 0x6d, 0xd0, 0x30, 0x47, 0x44, 0xfc, 0x5e, 0xf8, 0xc4, 0x4a, 0x91, 0x87, + 0xb2, 0x02, 0xd6, 0xd9, 0x17, 0xf1, 0x2f, 0xc1, 0x3e, 0xcb, 0x71, 0xf8, + 0xcc, 0x88, 0x7f, 0x29, 0xfc, 0x3e, 0x52, 0x2b, 0x39, 0x35, 0x4e, 0x55, + 0x95, 0x7d, 0xe8, 0xc1, 0x83, 0x16, 0x72, 0x5c, 0x5b, 0x5d, 0x86, 0x79, + 0xc0, 0xf3, 0x61, 0x5e, 0x51, 0x57, 0xc4, 0x53, 0xa8, 0xda, 0xea, 0xc9, + 0xa1, 0x58, 0xb0, 0x16, 0xf5, 0x72, 0xe9, 0x3a, 0x39, 0x77, 0x96, 0x5d, + 0xfc, 0xbe, 0x4e, 0xed, 0xd9, 0xf9, 0xf6, 0x73, 0x04, 0x76, 0x91, 0xa7, + 0x57, 0x56, 0xfd, 0x33, 0xe4, 0xfc, 0xe6, 0xa7, 0xec, 0xe3, 0x7f, 0xea, + 0xd4, 0x1d, 0x32, 0xe0, 0x77, 0x63, 0x7e, 0xb3, 0x51, 0x27, 0x21, 0xed, + 0x82, 0x6d, 0x22, 0x03, 0xff, 0xb9, 0xe6, 0xab, 0xda, 0x86, 0x62, 0x1e, + 0xbe, 0xb9, 0x4f, 0x08, 0x37, 0x4d, 0x18, 0x2e, 0x9a, 0x70, 0x64, 0xd3, + 0x6e, 0x33, 0x0b, 0xde, 0xf5, 0x1a, 0x91, 0xa9, 0x75, 0x11, 0xb2, 0xaf, + 0xcc, 0x7a, 0x15, 0x0b, 0x07, 0xfc, 0x2e, 0x69, 0x0b, 0x11, 0x0f, 0xdf, + 0xfe, 0x6b, 0x33, 0x4b, 0x70, 0xc6, 0x94, 0x08, 0xaf, 0x9c, 0x33, 0x8f, + 0xec, 0xb1, 0x13, 0x23, 0x55, 0xbd, 0xde, 0x0c, 0x8e, 0x8f, 0x26, 0xc0, + 0x9b, 0x40, 0xad, 0x3c, 0xb9, 0x26, 0xdc, 0x9e, 0x10, 0x2d, 0x08, 0xbb, + 0x84, 0xd4, 0xa3, 0x00, 0xed, 0xba, 0x40, 0x23, 0x1e, 0x8e, 0xe2, 0xa3, + 0x9e, 0x93, 0xda, 0x7e, 0x84, 0x6d, 0x7c, 0x96, 0x4d, 0xe5, 0x41, 0xf7, + 0x4c, 0xd0, 0x65, 0xf5, 0xca, 0x9e, 0x37, 0x64, 0xe7, 0xd0, 0xf8, 0x2d, + 0x59, 0xe4, 0x3a, 0xe2, 0x7a, 0xbf, 0x78, 0x50, 0x7c, 0xc6, 0xfc, 0xf2, + 0xc1, 0xcc, 0x3e, 0x2d, 0x6b, 0xa6, 0x6e, 0xd7, 0xe9, 0xfa, 0xd6, 0xfc, + 0x54, 0xd6, 0xab, 0x98, 0x4c, 0x69, 0xeb, 0xd1, 0x63, 0xca, 0xa1, 0x9d, + 0x15, 0x99, 0xb4, 0x0e, 0xe3, 0x8e, 0xe0, 0x82, 0xe5, 0xc7, 0x8c, 0x5e, + 0xe3, 0xcf, 0x9c, 0x57, 0xaf, 0xee, 0x0d, 0xea, 0x75, 0xca, 0x7a, 0xcd, + 0x5c, 0x8f, 0xac, 0x7a, 0x17, 0xad, 0x11, 0xa5, 0xd7, 0xa8, 0xb5, 0xde, + 0x5a, 0xa3, 0x5c, 0xcc, 0x56, 0xde, 0xbc, 0x35, 0x62, 0xdd, 0xb9, 0x66, + 0x47, 0xbd, 0x5a, 0xff, 0x98, 0xe7, 0xbd, 0x72, 0x8d, 0x72, 0xb1, 0x46, + 0x39, 0x58, 0xa3, 0x3c, 0xb4, 0x6e, 0xad, 0x4b, 0x4f, 0x6a, 0x5d, 0x72, + 0xf4, 0xba, 0x54, 0x2f, 0xba, 0x2e, 0xb9, 0x7a, 0x5d, 0xf2, 0x6c, 0xeb, + 0x82, 0xf6, 0x50, 0x6b, 0xf1, 0x75, 0x19, 0x49, 0xad, 0xcb, 0x96, 0x79, + 0xeb, 0xe2, 0xd4, 0xda, 0x5d, 0x51, 0xaf, 0x9e, 0x35, 0x04, 0x3c, 0xba, + 0xdf, 0x9e, 0x95, 0xe4, 0xef, 0x47, 0xbf, 0xec, 0x23, 0x1d, 0x4e, 0xc1, + 0xfd, 0xae, 0x5e, 0xa4, 0xed, 0x85, 0x6b, 0xb2, 0x18, 0xcf, 0x21, 0x6b, + 0x60, 0xc4, 0xf5, 0x24, 0xd7, 0x7b, 0x42, 0x78, 0x30, 0x07, 0x3c, 0x13, + 0x13, 0x46, 0xbe, 0x1c, 0xbb, 0xe3, 0xa2, 0x3a, 0x8b, 0xad, 0xf7, 0xeb, + 0xf1, 0x78, 0xfe, 0xa7, 0xea, 0x49, 0xce, 0xb5, 0xaf, 0xb2, 0xbd, 0x2a, + 0x5f, 0xee, 0xdf, 0x2c, 0xec, 0xdf, 0x00, 0xda, 0x8f, 0x78, 0x32, 0xf5, + 0x1e, 0xbf, 0x09, 0xb3, 0xf4, 0xd7, 0xb6, 0xfd, 0x66, 0x78, 0x8b, 0xe9, + 0xbf, 0x18, 0xcf, 0xb2, 0x9f, 0xd9, 0x7a, 0xf5, 0xdc, 0x28, 0xe0, 0xf1, + 0x4a, 0x9d, 0x9d, 0x72, 0xd6, 0x1c, 0x14, 0xaf, 0x57, 0x67, 0x6f, 0x29, + 0x3e, 0x57, 0x60, 0xcf, 0x4e, 0xf4, 0x16, 0x60, 0xa5, 0x9e, 0x44, 0x8f, + 0x6e, 0x23, 0xd0, 0xef, 0xa5, 0x4e, 0x07, 0xfc, 0x85, 0xc7, 0x05, 0x49, + 0xce, 0x45, 0x3c, 0x19, 0x48, 0x4d, 0xf4, 0x7a, 0x91, 0xf3, 0xa1, 0xcc, + 0x05, 0xce, 0x32, 0xc9, 0xf1, 0xc1, 0x82, 0xf2, 0x8d, 0x5c, 0x51, 0x46, + 0x35, 0x82, 0xb5, 0x5d, 0x8a, 0xd6, 0x4b, 0xb5, 0x4e, 0xfc, 0xbc, 0x25, + 0x43, 0xda, 0x5a, 0xd5, 0xcd, 0x2d, 0xe5, 0x55, 0x52, 0x27, 0xd6, 0xea, + 0xd6, 0x7a, 0xeb, 0x6c, 0xf3, 0xa1, 0x1d, 0x3e, 0x79, 0xb9, 0x2f, 0x3f, + 0xb1, 0xc5, 0xe6, 0x6b, 0x1d, 0xd9, 0x8b, 0x1d, 0xad, 0x57, 0xcf, 0x08, + 0x95, 0x8e, 0x01, 0xf4, 0xd5, 0x29, 0x2a, 0x64, 0x0d, 0x39, 0xc7, 0x9e, + 0x2a, 0x50, 0xb7, 0xe1, 0x6b, 0x6f, 0x5f, 0xbd, 0x14, 0xdc, 0x4a, 0xc9, + 0x2d, 0x31, 0xde, 0x0d, 0x2b, 0x6d, 0x65, 0x7f, 0x2a, 0xca, 0x85, 0x43, + 0xed, 0x2d, 0x8f, 0x29, 0xcb, 0xca, 0x10, 0x11, 0x95, 0xc9, 0x93, 0xde, + 0x4b, 0x4a, 0x63, 0x35, 0x37, 0xd5, 0x28, 0x6d, 0xd0, 0xf6, 0x24, 0x10, + 0x6f, 0x39, 0xa5, 0x06, 0x44, 0x77, 0xd4, 0xab, 0xe7, 0x66, 0x13, 0xf0, + 0xfb, 0x31, 0xcf, 0x13, 0xd2, 0x86, 0x0a, 0xb0, 0x87, 0x60, 0xff, 0xa6, + 0x57, 0xda, 0xa7, 0xda, 0x47, 0x1f, 0xd6, 0x7b, 0x5b, 0xeb, 0xd9, 0x56, + 0x80, 0x5e, 0xb3, 0x28, 0xb0, 0x1a, 0xfa, 0x1a, 0x38, 0x6f, 0xfc, 0x37, + 0x40, 0x9b, 0x32, 0xbe, 0x41, 0xda, 0xfa, 0xcc, 0x97, 0x7d, 0xb1, 0x67, + 0x57, 0xbd, 0x3e, 0x5c, 0xaf, 0x9e, 0x2f, 0x95, 0x22, 0xe2, 0x41, 0x1b, + 0xc1, 0x65, 0xd0, 0xfa, 0x34, 0x8f, 0x0f, 0xa7, 0xf3, 0x00, 0xe2, 0x85, + 0xc0, 0x24, 0x73, 0xee, 0xb5, 0x9f, 0xab, 0x66, 0xa7, 0xb9, 0xd4, 0x3a, + 0x57, 0xcd, 0x72, 0xd3, 0x4f, 0x67, 0x9c, 0x86, 0x08, 0x0c, 0x08, 0x2a, + 0x37, 0xbd, 0x72, 0x96, 0x70, 0xea, 0x99, 0x5b, 0x4d, 0x61, 0x88, 0xb1, + 0xda, 0x5f, 0xf1, 0xd8, 0xb9, 0xff, 0x5a, 0xa3, 0x46, 0xd4, 0x5e, 0x50, + 0x3a, 0x34, 0xc9, 0x9e, 0x9b, 0xa1, 0x83, 0x3a, 0x33, 0x9e, 0xac, 0x57, + 0xcf, 0xac, 0x02, 0x3b, 0x54, 0x5f, 0x3c, 0x7a, 0xee, 0x8b, 0x63, 0x84, + 0x4e, 0xd1, 0x2a, 0xfb, 0x22, 0x39, 0xaf, 0x7e, 0x0a, 0x20, 0x88, 0x2e, + 0x37, 0x54, 0x3f, 0x86, 0x8c, 0x35, 0x78, 0x7d, 0xdd, 0x0e, 0xdf, 0x9a, + 0xf6, 0xfe, 0x5f, 0x5d, 0xe0, 0xd5, 0x60, 0x6e, 0x89, 0x43, 0xad, 0xc6, + 0x6a, 0xa4, 0xcb, 0x11, 0xdb, 0x74, 0x1a, 0x3f, 0xbc, 0xc0, 0xab, 0x21, + 0x6b, 0xf8, 0xef, 0xa0, 0x3d, 0x58, 0xbf, 0x4e, 0xe3, 0x5b, 0x17, 0xbc, + 0xc2, 0x97, 0xe1, 0xcd, 0xc8, 0xd0, 0x16, 0xf2, 0xcf, 0xa9, 0x75, 0x97, + 0x73, 0xd1, 0xab, 0xb4, 0xe1, 0x15, 0x64, 0x6d, 0x84, 0xac, 0x53, 0x2c, + 0xb5, 0x11, 0xb2, 0x5d, 0x68, 0x53, 0x05, 0x6d, 0x1c, 0x4a, 0x1b, 0x79, + 0x9a, 0xc3, 0x9a, 0x30, 0x43, 0x8e, 0xf6, 0xf1, 0x97, 0x2e, 0x60, 0xe4, + 0x46, 0x19, 0xf9, 0x9c, 0x5e, 0x67, 0xae, 0x53, 0x8e, 0xdf, 0x69, 0x8d, + 0xbf, 0x46, 0xf6, 0x56, 0x4b, 0x19, 0xf2, 0xcc, 0x17, 0xf4, 0xd5, 0x7a, + 0xf5, 0xbc, 0x86, 0x35, 0x74, 0x4a, 0x9b, 0x7c, 0xbf, 0x1c, 0xbb, 0xcf, + 0xe8, 0x84, 0x25, 0xe5, 0x1b, 0xa5, 0x02, 0xfa, 0x08, 0x8e, 0xc2, 0x4c, + 0x19, 0xd5, 0x98, 0x54, 0x26, 0x10, 0xc7, 0xf5, 0x2c, 0xb5, 0x6a, 0x18, + 0x3e, 0x47, 0xa4, 0xf5, 0x41, 0xf2, 0x3b, 0x02, 0x3d, 0x4b, 0xc0, 0x2b, + 0x92, 0x33, 0x58, 0x69, 0xac, 0xa4, 0x58, 0xeb, 0x95, 0xd4, 0x67, 0xfa, + 0x0a, 0x22, 0x3d, 0x0e, 0x72, 0x56, 0xe5, 0x48, 0xcf, 0x1f, 0xe8, 0x4b, + 0xd5, 0x73, 0x72, 0xad, 0x31, 0xe2, 0x73, 0xfc, 0x26, 0xdc, 0xcb, 0xd0, + 0xaa, 0x03, 0xba, 0x8a, 0x1a, 0xa7, 0x3a, 0x67, 0x3b, 0xa4, 0x9e, 0x7d, + 0xd2, 0x7f, 0x72, 0x1c, 0xfd, 0xe3, 0x7a, 0x92, 0x31, 0xba, 0xcf, 0x13, + 0xdb, 0x71, 0x23, 0x55, 0xf4, 0x73, 0x84, 0xc7, 0x91, 0x1e, 0x97, 0xbd, + 0xa8, 0xfd, 0x1e, 0xc7, 0x61, 0x3e, 0x23, 0x36, 0x76, 0x23, 0xf5, 0x7b, + 0x72, 0x4c, 0xaf, 0xc9, 0xcf, 0x33, 0x78, 0x2f, 0xbe, 0x84, 0xf2, 0xb7, + 0xa4, 0xe6, 0xb6, 0x33, 0xc7, 0x44, 0x34, 0x56, 0x6b, 0xf8, 0x2a, 0x22, + 0xad, 0x49, 0x2a, 0xce, 0xca, 0xc9, 0xf2, 0x89, 0xce, 0xac, 0x11, 0x0a, + 0xdc, 0x5a, 0x82, 0x75, 0xfb, 0x08, 0x6e, 0x24, 0x3c, 0xd7, 0x18, 0x27, + 0xac, 0x67, 0xec, 0x8e, 0x52, 0x8a, 0xf9, 0xe3, 0x98, 0xaf, 0x1c, 0xf0, + 0xd6, 0x51, 0xa0, 0x52, 0x59, 0x40, 0xa7, 0xd1, 0x8e, 0x76, 0x0a, 0x69, + 0xec, 0x74, 0x19, 0xea, 0x94, 0xd2, 0x97, 0x60, 0x75, 0x9d, 0x66, 0x09, + 0x55, 0xe6, 0x54, 0x92, 0xeb, 0x4b, 0xae, 0xdb, 0x5c, 0x77, 0x99, 0x0f, + 0x1f, 0xcc, 0xd8, 0x08, 0xfd, 0x6a, 0x3d, 0x63, 0xf7, 0xb2, 0xcc, 0x72, + 0xba, 0x9f, 0x72, 0xdc, 0x9d, 0xee, 0x4c, 0xc8, 0x54, 0x51, 0xac, 0x67, + 0x98, 0x4e, 0xdc, 0x5b, 0x6b, 0xb8, 0x4a, 0x3b, 0x73, 0xf2, 0x28, 0xf0, + 0x4e, 0xd4, 0xcc, 0xee, 0x40, 0x4f, 0x77, 0xe2, 0xde, 0xc5, 0xf3, 0x72, + 0x6f, 0x36, 0x7a, 0xcf, 0x91, 0xf6, 0x9b, 0x85, 0xfd, 0x93, 0xa5, 0xc6, + 0x17, 0x90, 0x73, 0xb2, 0x9b, 0x9c, 0xda, 0x76, 0x5f, 0xab, 0x57, 0xef, + 0x15, 0x02, 0xad, 0xcb, 0x31, 0xab, 0x0f, 0x68, 0xdb, 0xfd, 0xe4, 0x02, + 0x1b, 0x5e, 0x6e, 0xb3, 0xe1, 0x36, 0xec, 0x50, 0xc8, 0xfa, 0xef, 0x87, + 0xa7, 0xe6, 0xfc, 0x2a, 0xd4, 0x9b, 0x92, 0x16, 0xc5, 0x96, 0xd9, 0x27, + 0x47, 0x7e, 0x96, 0x2d, 0x13, 0xb3, 0x97, 0x8e, 0x49, 0x33, 0x1a, 0xd4, + 0x73, 0xea, 0x88, 0x7f, 0x86, 0xad, 0xda, 0xb1, 0x60, 0x4f, 0x62, 0x65, + 0xf3, 0x6d, 0xb1, 0x6e, 0x2d, 0xda, 0xbc, 0x4d, 0xf6, 0xdd, 0xde, 0x7b, + 0xee, 0x82, 0x97, 0x7c, 0x0e, 0xaf, 0x83, 0xb4, 0xce, 0xd8, 0xfd, 0x0d, + 0x4a, 0x67, 0xb5, 0x16, 0x6c, 0x6d, 0xb0, 0x2b, 0xb1, 0x0e, 0xbb, 0x34, + 0xe6, 0xbf, 0x9e, 0xe3, 0x40, 0x68, 0x8c, 0xb8, 0xb7, 0x10, 0x1e, 0xd6, + 0x0c, 0xb4, 0x2d, 0x95, 0x2b, 0xd2, 0xcb, 0xf6, 0x58, 0xd9, 0x29, 0x4c, + 0xe9, 0x77, 0x63, 0xfe, 0xfd, 0x2c, 0x87, 0xf9, 0x56, 0x79, 0xd8, 0xb9, + 0x99, 0x6f, 0xe6, 0x9a, 0x98, 0x27, 0x73, 0xbe, 0x8d, 0xe7, 0xcb, 0x3e, + 0x0d, 0x2a, 0x6b, 0x50, 0xef, 0x33, 0x76, 0xbe, 0xb0, 0x42, 0x5a, 0xb0, + 0xcf, 0x83, 0xd5, 0xf7, 0x2b, 0xcb, 0x0e, 0x7c, 0xb7, 0x88, 0x2a, 0xaa, + 0x7c, 0xa6, 0xcf, 0xd9, 0xd1, 0xd1, 0x47, 0x43, 0x19, 0x86, 0x93, 0xf7, + 0x51, 0x06, 0xb9, 0x5d, 0x63, 0x2f, 0x95, 0x52, 0xfb, 0xd1, 0x0e, 0xe8, + 0x58, 0x81, 0x7c, 0x8e, 0xab, 0xe2, 0x90, 0x2f, 0xab, 0xe3, 0x78, 0x13, + 0x0d, 0xb9, 0x5d, 0x59, 0x11, 0x4f, 0x39, 0xc6, 0xed, 0xce, 0x6e, 0xcf, + 0xae, 0x44, 0x79, 0x99, 0x4c, 0x8f, 0xbd, 0x52, 0x46, 0xed, 0x5f, 0x83, + 0x0d, 0x79, 0x56, 0x20, 0x9f, 0x93, 0xc7, 0x63, 0xcb, 0x93, 0xb3, 0x93, + 0x27, 0x3d, 0x35, 0xda, 0xc8, 0x2b, 0xa1, 0xef, 0x4b, 0xdf, 0x90, 0x87, + 0x74, 0x79, 0x5e, 0x3b, 0xac, 0xde, 0x9b, 0x77, 0x26, 0x2b, 0x4b, 0xd4, + 0x3e, 0x7f, 0xc6, 0xe9, 0x14, 0xb5, 0x5f, 0x28, 0x13, 0x25, 0x38, 0xc8, + 0xbc, 0x79, 0xb9, 0x79, 0x65, 0x02, 0xbb, 0x36, 0xaf, 0xf6, 0x02, 0x9f, + 0xe2, 0x9b, 0x31, 0x9a, 0x5d, 0x29, 0x5f, 0xbd, 0x53, 0xcf, 0xa5, 0x41, + 0xad, 0x0d, 0xea, 0x3d, 0x8f, 0x9a, 0xcb, 0x98, 0x27, 0xc2, 0x2b, 0x20, + 0x26, 0xda, 0x8a, 0x30, 0x2f, 0x95, 0xb8, 0x45, 0xc4, 0x3c, 0xa7, 0xf4, + 0xaa, 0xf1, 0x7c, 0xf1, 0xaa, 0xb1, 0x77, 0xe4, 0x55, 0x33, 0x6d, 0x96, + 0x61, 0x4a, 0x4b, 0xa8, 0x95, 0x11, 0x55, 0x81, 0xb4, 0x84, 0xbb, 0x11, + 0x95, 0xf1, 0x1a, 0xb2, 0x27, 0x3e, 0xe0, 0x79, 0xb7, 0x3e, 0xdd, 0xef, + 0x86, 0xe7, 0xae, 0x7d, 0x91, 0x3d, 0x34, 0xd6, 0xd6, 0xac, 0x1a, 0xe0, + 0xfb, 0xd3, 0x87, 0xa8, 0x47, 0xfa, 0x61, 0xdc, 0x9f, 0xe4, 0x0a, 0x40, + 0x67, 0xb3, 0xf6, 0x02, 0xaf, 0x01, 0xfb, 0xf8, 0x81, 0x94, 0xce, 0xfd, + 0x72, 0xed, 0xd9, 0x96, 0xfa, 0x1b, 0x52, 0xfb, 0xd8, 0x1f, 0xe5, 0x96, + 0x71, 0x2e, 0x59, 0x65, 0x9b, 0xad, 0x32, 0x7f, 0xba, 0x2c, 0x5f, 0xdf, + 0x4d, 0xb6, 0x36, 0xa8, 0x77, 0x0e, 0xe9, 0x71, 0x05, 0xbe, 0x83, 0x95, + 0xf3, 0xf9, 0x70, 0x83, 0xe8, 0xa8, 0x6e, 0xa1, 0x21, 0xa7, 0x30, 0x79, + 0xa5, 0xe0, 0x59, 0x32, 0x2a, 0xf6, 0xf9, 0x32, 0x3b, 0x0e, 0x55, 0xd2, + 0x50, 0x56, 0x46, 0x26, 0xaf, 0x54, 0x16, 0xb9, 0xdd, 0xed, 0x2f, 0xe3, + 0xfe, 0xe1, 0xae, 0x95, 0x67, 0xae, 0x13, 0xab, 0xe5, 0x2b, 0x6d, 0xff, + 0xa6, 0x03, 0xfa, 0xfa, 0x72, 0x78, 0xfd, 0x20, 0x91, 0x15, 0xeb, 0xb9, + 0x85, 0xfe, 0xe1, 0x34, 0x8f, 0xf3, 0x3e, 0x3a, 0x93, 0x99, 0x29, 0x6a, + 0x7f, 0x76, 0xc6, 0x34, 0x45, 0xed, 0x77, 0xbd, 0x72, 0xbe, 0x79, 0x4c, + 0x57, 0x37, 0xa8, 0xf7, 0x68, 0x31, 0xcf, 0x2a, 0xe4, 0x2b, 0xb1, 0xb2, + 0x31, 0x0f, 0x3f, 0x6f, 0x9a, 0x68, 0x5d, 0x3a, 0x2f, 0xb7, 0xdc, 0xa6, + 0xe5, 0x44, 0xeb, 0xb2, 0x79, 0x65, 0x25, 0x34, 0xb1, 0x66, 0x09, 0xce, + 0xe3, 0xef, 0x62, 0xf6, 0x8a, 0xe5, 0x7a, 0x94, 0x9b, 0xc5, 0xe0, 0x15, + 0xd3, 0x3a, 0xd3, 0x4d, 0x25, 0x26, 0xdf, 0xe2, 0x42, 0x32, 0x0a, 0x35, + 0xa5, 0xdd, 0xd4, 0x48, 0x99, 0x8a, 0x1e, 0x9f, 0xe8, 0xc0, 0x9d, 0x63, + 0xc8, 0x30, 0xb1, 0x12, 0x73, 0xb8, 0xa1, 0x9f, 0x11, 0x42, 0xd4, 0xfe, + 0x6f, 0x00, 0xf5, 0x10, 0x4f, 0x20, 0x38, 0x5e, 0x03, 0x69, 0x2b, 0x86, + 0x88, 0x41, 0x4f, 0x7e, 0x9e, 0x12, 0xe8, 0x29, 0x46, 0x2f, 0x0d, 0x58, + 0x23, 0xeb, 0x54, 0x61, 0xef, 0x7b, 0x1f, 0xa4, 0x02, 0xbd, 0xca, 0x63, + 0xa8, 0xf3, 0xe5, 0x93, 0x0b, 0xce, 0x99, 0x42, 0xdb, 0x39, 0x03, 0x8f, + 0xd1, 0xaf, 0x3c, 0x46, 0x95, 0xcc, 0xb3, 0xc7, 0x98, 0x64, 0xbf, 0xf6, + 0x6b, 0x8e, 0x9f, 0x96, 0xc3, 0xd2, 0x02, 0x3d, 0x25, 0xaf, 0xe7, 0x7d, + 0xd0, 0x56, 0x89, 0xe5, 0x7d, 0x50, 0xb7, 0x16, 0x6d, 0x95, 0xd8, 0xda, + 0xe2, 0xbd, 0x75, 0x97, 0x3c, 0x17, 0xcb, 0x8d, 0x6a, 0xc8, 0x5d, 0xc1, + 0xed, 0xfe, 0x62, 0xa2, 0xb7, 0x04, 0x6d, 0x2e, 0x76, 0x1a, 0x57, 0x2e, + 0x38, 0x8d, 0x0d, 0xf8, 0x61, 0x8f, 0xa4, 0xe5, 0x86, 0x29, 0x23, 0x37, + 0xc3, 0x96, 0xe3, 0x13, 0x2e, 0xe0, 0x85, 0xbf, 0x16, 0x5e, 0x19, 0xb9, + 0xc1, 0x23, 0xfc, 0x58, 0xb5, 0xcb, 0xcf, 0x78, 0x39, 0x42, 0x37, 0x75, + 0x7c, 0x62, 0xc5, 0x07, 0xec, 0x63, 0x1b, 0x20, 0xf7, 0xfe, 0x94, 0xdd, + 0xde, 0x21, 0x4c, 0x6d, 0x7f, 0x27, 0x1b, 0xd4, 0xb9, 0xc4, 0x67, 0x3e, + 0xfb, 0x3c, 0x5f, 0x5b, 0xbb, 0x87, 0x9f, 0x04, 0x04, 0xb0, 0x8a, 0x3e, + 0xdc, 0x49, 0xeb, 0xa8, 0xd5, 0xc7, 0x31, 0x68, 0x96, 0x3c, 0x93, 0x1c, + 0x74, 0x37, 0xe4, 0x8f, 0xd9, 0xfc, 0x5c, 0x09, 0x5a, 0xe7, 0xf8, 0xcd, + 0xaf, 0xe2, 0x37, 0x58, 0x6d, 0x60, 0x6e, 0x39, 0x6e, 0xa9, 0xef, 0xe1, + 0xf1, 0x67, 0xb0, 0x97, 0x0b, 0x91, 0xdb, 0xb9, 0xce, 0x39, 0x43, 0x3a, + 0xc6, 0x73, 0xfa, 0xd6, 0xb6, 0x87, 0x36, 0xc1, 0x13, 0xa6, 0xa2, 0x3c, + 0xa7, 0x8a, 0x2b, 0xae, 0x48, 0x45, 0x79, 0x9d, 0x56, 0x94, 0xe7, 0x08, + 0xcc, 0x29, 0x4f, 0xd9, 0x81, 0x7a, 0x9d, 0x4e, 0x44, 0xac, 0x07, 0x96, + 0xca, 0x79, 0x6e, 0x93, 0xb2, 0x26, 0xa9, 0xf2, 0x87, 0x50, 0xee, 0x2a, + 0xa8, 0x2d, 0xb5, 0x62, 0x43, 0xf6, 0xb8, 0x45, 0xd2, 0xe3, 0x62, 0x1c, + 0x5e, 0xcc, 0x1b, 0x24, 0x55, 0xec, 0xc8, 0xbb, 0x97, 0x67, 0x50, 0xc8, + 0x99, 0xcb, 0x94, 0x91, 0x5c, 0x25, 0x4e, 0x8b, 0xd8, 0xf8, 0x1e, 0x3a, + 0x70, 0x34, 0x70, 0x28, 0xa5, 0xb9, 0xc1, 0xa7, 0x55, 0xad, 0xa8, 0xcc, + 0xdc, 0x00, 0x0b, 0xff, 0x14, 0x24, 0x3b, 0x4d, 0x27, 0x15, 0x18, 0xdf, + 0x76, 0xd5, 0x3a, 0x0a, 0x8c, 0x29, 0xba, 0xa5, 0x32, 0xd2, 0xf3, 0x38, + 0x15, 0xaf, 0x96, 0x91, 0x60, 0x26, 0x3c, 0x74, 0x26, 0xef, 0xa3, 0x41, + 0xb4, 0xcb, 0xb7, 0xa4, 0x65, 0x98, 0xd9, 0x49, 0x1d, 0x8b, 0x1a, 0xf4, + 0x0e, 0x71, 0x73, 0xcb, 0xe4, 0x3b, 0x84, 0x8c, 0x9f, 0x0d, 0x19, 0x4b, + 0x3f, 0xd9, 0xa0, 0xde, 0x63, 0xa5, 0xe7, 0xd0, 0x99, 0x9e, 0x43, 0x11, + 0xe8, 0x53, 0x5a, 0x08, 0xf9, 0x24, 0xe3, 0x23, 0x38, 0x99, 0xd8, 0xde, + 0x60, 0xaf, 0x7d, 0x58, 0x63, 0xff, 0x07, 0x10, 0x5b, 0xb8, 0x8d, 0x75, + 0xc6, 0x95, 0x34, 0x5f, 0xee, 0x7e, 0x1d, 0x97, 0xb1, 0x1d, 0x87, 0xe4, + 0x08, 0xd8, 0xbe, 0x75, 0xb9, 0xe0, 0x39, 0x2a, 0x90, 0xd1, 0x57, 0xa5, + 0xc3, 0x8f, 0x08, 0xa6, 0x9d, 0xc6, 0xa5, 0x3f, 0x6c, 0xa3, 0x03, 0xf2, + 0x8d, 0x52, 0x6d, 0x4b, 0x5a, 0x8b, 0xd7, 0x8b, 0xbd, 0x97, 0xeb, 0x55, + 0xb1, 0xcf, 0x72, 0xe1, 0x9b, 0x98, 0x65, 0xa7, 0x8a, 0x97, 0x11, 0xaf, + 0x71, 0x0c, 0xc4, 0xf3, 0x14, 0x41, 0xc9, 0x01, 0x7d, 0xaf, 0xe0, 0x77, + 0x81, 0x2a, 0xf6, 0x3e, 0x78, 0x73, 0x4b, 0xf2, 0x20, 0xee, 0x12, 0xea, + 0x0c, 0x78, 0xae, 0x41, 0xbd, 0x9b, 0xb7, 0xe6, 0xc8, 0xcf, 0x67, 0x91, + 0xe0, 0x98, 0x65, 0x99, 0xec, 0x73, 0x34, 0x15, 0xb5, 0x5a, 0x3b, 0xc3, + 0x57, 0xe0, 0x2b, 0x6c, 0x5f, 0xd3, 0x8e, 0x53, 0x33, 0x70, 0x97, 0xda, + 0xb9, 0x1c, 0x8d, 0xf1, 0xce, 0x55, 0xf4, 0xe3, 0xec, 0x1f, 0x33, 0x3a, + 0x33, 0x7e, 0x7f, 0x81, 0x77, 0x1b, 0xfb, 0xd3, 0xf2, 0x8c, 0x5a, 0x1a, + 0xdb, 0x57, 0x6a, 0xb5, 0x9e, 0xd9, 0x7e, 0xf4, 0x77, 0x17, 0x02, 0x77, + 0xaa, 0x9d, 0x3c, 0x25, 0x3d, 0x98, 0xda, 0xc9, 0xca, 0x9b, 0xa9, 0x9d, + 0x6c, 0x52, 0xed, 0x37, 0x3a, 0x8d, 0x4c, 0xbd, 0x2b, 0xf5, 0x6e, 0xbc, + 0x97, 0x57, 0xe6, 0x83, 0xf0, 0x70, 0x65, 0x7a, 0x3e, 0x31, 0x5e, 0xb7, + 0xdc, 0x7f, 0x6e, 0x2b, 0x3e, 0xdd, 0x89, 0x31, 0xbe, 0x25, 0xb5, 0xff, + 0xae, 0x59, 0xf4, 0x59, 0x99, 0xb5, 0x27, 0xff, 0xa3, 0x41, 0xdd, 0xb9, + 0xad, 0x3d, 0x58, 0xab, 0xf7, 0xa0, 0xf5, 0x3c, 0xe8, 0xac, 0x3e, 0x4f, + 0x02, 0x9e, 0x12, 0x69, 0xfd, 0x7e, 0xe9, 0x23, 0x0d, 0x69, 0x5d, 0x2f, + 0x37, 0xa8, 0xef, 0x36, 0xe8, 0x08, 0x04, 0x1e, 0x9a, 0x77, 0xb3, 0xf6, + 0x93, 0xb6, 0xb8, 0xa3, 0x53, 0x14, 0xe9, 0xfc, 0xfd, 0xc8, 0xab, 0xdd, + 0xb4, 0x70, 0xf5, 0x54, 0xf9, 0x43, 0x28, 0xd7, 0xb1, 0x09, 0x6a, 0xf1, + 0x2d, 0x6f, 0xaf, 0xdc, 0xeb, 0xec, 0x2f, 0x59, 0xc6, 0xb6, 0x67, 0x20, + 0x5b, 0x61, 0xc5, 0x3b, 0x88, 0x63, 0x8a, 0x69, 0xc1, 0x0d, 0x06, 0x76, + 0x78, 0x08, 0x76, 0xc8, 0x56, 0x61, 0xf2, 0xb3, 0x09, 0x3e, 0x15, 0xd0, + 0x22, 0x9f, 0x0a, 0x85, 0xca, 0x4a, 0x64, 0xb4, 0xc3, 0xb1, 0xc1, 0x00, + 0xda, 0xde, 0xa6, 0x6d, 0x84, 0xbf, 0xcb, 0x91, 0x2d, 0x6d, 0x64, 0xe2, + 0xe6, 0x96, 0xb1, 0x09, 0x39, 0x56, 0x79, 0x23, 0x6e, 0x54, 0xcf, 0xa4, + 0x7d, 0x38, 0x63, 0x10, 0xd3, 0xa3, 0xdd, 0xf7, 0xc9, 0x48, 0x81, 0x29, + 0x7a, 0x33, 0x56, 0x57, 0x30, 0xb7, 0xdc, 0xc4, 0x2a, 0xb7, 0x95, 0xd1, + 0x58, 0x1f, 0xe2, 0x98, 0x8a, 0x7c, 0xda, 0xd9, 0xb6, 0x82, 0x76, 0xf6, + 0xad, 0x40, 0x3a, 0x0b, 0xfc, 0x52, 0x1a, 0x1b, 0x40, 0x3c, 0xd4, 0xeb, + 0x90, 0x4f, 0xce, 0xac, 0xfb, 0x2a, 0x7c, 0x66, 0xa3, 0xf5, 0x1d, 0x98, + 0x36, 0x1a, 0xeb, 0x2d, 0xc5, 0xde, 0x3f, 0x42, 0xdf, 0xf5, 0x1e, 0xa1, + 0xb1, 0xfe, 0x32, 0xea, 0xa8, 0xc2, 0x09, 0xeb, 0x28, 0x70, 0xdc, 0x4c, + 0x8f, 0xb7, 0xc5, 0xfc, 0xef, 0x42, 0xfc, 0x10, 0xf1, 0x9c, 0x90, 0x37, + 0xde, 0x9d, 0xbd, 0x88, 0xc4, 0x30, 0x93, 0xbe, 0x3f, 0xe0, 0x04, 0xf6, + 0xde, 0x81, 0xa5, 0xcd, 0xa7, 0xf4, 0x73, 0x1c, 0x7e, 0x54, 0x96, 0x7e, + 0x8e, 0x73, 0xb5, 0x28, 0xa5, 0x6b, 0x8c, 0x15, 0x74, 0xb5, 0xa3, 0x2c, + 0xf5, 0x1c, 0x87, 0x68, 0xb9, 0x7e, 0x2e, 0x7d, 0x1d, 0x50, 0x80, 0xd3, + 0xa0, 0x03, 0xeb, 0x71, 0x40, 0xd6, 0xe6, 0x9b, 0x41, 0x09, 0x1d, 0x97, + 0xe7, 0xab, 0x3a, 0x5b, 0x78, 0xb5, 0xb2, 0xf5, 0x53, 0x61, 0x87, 0x3e, + 0x23, 0x2b, 0x1b, 0xd5, 0x73, 0x7c, 0x7e, 0xce, 0x89, 0x12, 0xd8, 0xcf, + 0x3a, 0xb4, 0xed, 0x15, 0xe9, 0x67, 0x9b, 0x9c, 0x2b, 0xa5, 0x3a, 0xd8, + 0xc3, 0x1e, 0x51, 0x4e, 0xeb, 0x70, 0xbb, 0xad, 0x14, 0xbc, 0x6a, 0x6b, + 0x30, 0xde, 0x30, 0x38, 0x65, 0xb4, 0xc7, 0x28, 0xc7, 0x3d, 0x2b, 0x17, + 0x11, 0x68, 0x8d, 0xe1, 0x93, 0xb5, 0x84, 0x7e, 0xb6, 0xce, 0xff, 0x2b, + 0xc8, 0xea, 0xab, 0x45, 0x7f, 0x0f, 0xc3, 0x3a, 0xf5, 0x0d, 0xd9, 0x3e, + 0x46, 0x0f, 0x7d, 0xac, 0x1e, 0xfc, 0xba, 0x87, 0x1a, 0xf4, 0x30, 0x2d, + 0x7b, 0xf0, 0xcb, 0x1e, 0xfc, 0xa9, 0x1e, 0xd4, 0x33, 0x3c, 0xee, 0x63, + 0x45, 0xaa, 0x0f, 0xeb, 0xdb, 0x28, 0xf3, 0xf7, 0x87, 0xf5, 0x4c, 0xaa, + 0xab, 0x51, 0x7d, 0x4f, 0x46, 0xcd, 0x63, 0xfa, 0x5b, 0x39, 0x16, 0xff, + 0x80, 0x7c, 0xcb, 0x9e, 0x47, 0xf7, 0x0a, 0x41, 0x79, 0xc2, 0xe3, 0x7b, + 0x0b, 0xc5, 0x90, 0xa0, 0xeb, 0xf1, 0xf1, 0x10, 0x8e, 0xa3, 0x3c, 0xba, + 0x16, 0x29, 0xe3, 0x1c, 0x75, 0x19, 0xef, 0xbb, 0xf9, 0x06, 0x7a, 0x4e, + 0x4a, 0x79, 0xf3, 0xe9, 0xef, 0x05, 0x74, 0x99, 0xde, 0x2f, 0x3c, 0x05, + 0xdd, 0x33, 0xa7, 0x68, 0x0e, 0xdc, 0x2f, 0x6f, 0x7b, 0x67, 0xe4, 0xd0, + 0xe1, 0xf0, 0xee, 0xdd, 0x13, 0x0f, 0x0d, 0x6f, 0xa7, 0x47, 0x58, 0x90, + 0x3e, 0x6a, 0xe0, 0xe3, 0x8c, 0x7c, 0x3e, 0x42, 0x79, 0xdd, 0x4d, 0x4d, + 0x4d, 0xdd, 0x74, 0x3b, 0x58, 0xdb, 0x8e, 0x5f, 0x3e, 0x72, 0x7d, 0x13, + 0xfd, 0x02, 0x22, 0xdd, 0x74, 0xaf, 0x61, 0x2a, 0xf3, 0x3c, 0x87, 0xec, + 0xb3, 0x23, 0xf4, 0xaa, 0x26, 0x5c, 0xf5, 0xb3, 0x86, 0x43, 0xd7, 0xa4, + 0x5f, 0xa6, 0xeb, 0x7d, 0xc1, 0xe0, 0x7a, 0xdf, 0x57, 0x65, 0xdb, 0x8e, + 0xd3, 0xc7, 0x1d, 0xd0, 0x25, 0x0f, 0x6d, 0x7f, 0xda, 0xc1, 0xac, 0x2d, + 0xdd, 0xf4, 0x2e, 0x3e, 0xd5, 0x7f, 0x8c, 0xcd, 0x3c, 0xda, 0x34, 0x3a, + 0xda, 0x3d, 0xd1, 0xb2, 0xad, 0xa7, 0xa5, 0x9b, 0xde, 0xcb, 0xcc, 0x57, + 0xc1, 0xdc, 0x30, 0x3a, 0x72, 0xf8, 0xc5, 0xfd, 0xf4, 0x5d, 0x87, 0xd2, + 0x6b, 0x37, 0x5a, 0xbf, 0x0b, 0x65, 0x1b, 0x8e, 0x76, 0xd3, 0x7b, 0x32, + 0x41, 0xef, 0x11, 0x85, 0xf9, 0xcf, 0x76, 0xd3, 0x5d, 0x19, 0x3c, 0x56, + 0x9f, 0xe7, 0xe8, 0xe1, 0x9f, 0xd3, 0xe9, 0x4c, 0xa4, 0x37, 0x08, 0x5f, + 0xfe, 0xcf, 0x9b, 0xe8, 0x2b, 0x4e, 0xa4, 0x47, 0x8e, 0x6f, 0x08, 0xb7, + 0x3c, 0x24, 0x7c, 0xbe, 0x47, 0x36, 0xd0, 0xc7, 0x9c, 0x06, 0xdf, 0x8d, + 0x3e, 0x05, 0x7e, 0x17, 0x9d, 0x37, 0xa5, 0x5a, 0xc7, 0x6f, 0x3c, 0xda, + 0xfc, 0xce, 0x77, 0x1f, 0x7f, 0xe4, 0xf8, 0xbd, 0x0f, 0xb7, 0xd1, 0x43, + 0xb2, 0xa5, 0xa5, 0x9e, 0xa3, 0xf4, 0x8e, 0x4c, 0x56, 0x15, 0xed, 0xd3, + 0x67, 0xb8, 0x95, 0x0d, 0xd7, 0x1f, 0x6a, 0x3e, 0xd5, 0x1c, 0x6e, 0xa6, + 0x1f, 0xb8, 0x90, 0x3b, 0xbc, 0xe1, 0xee, 0x63, 0x0f, 0xef, 0x1f, 0x19, + 0x1d, 0x09, 0x3f, 0x7b, 0x82, 0x7e, 0x69, 0x72, 0x79, 0xb8, 0x7c, 0x42, + 0x14, 0x7a, 0x0e, 0xfd, 0x6b, 0xd8, 0x98, 0xfb, 0x5b, 0xfa, 0x99, 0x89, + 0x5e, 0x7e, 0xc4, 0x1f, 0xe7, 0x59, 0x7c, 0xe2, 0xd4, 0x43, 0xe1, 0x4f, + 0x1f, 0xa4, 0x07, 0x58, 0xd0, 0xb8, 0xdd, 0xec, 0x3a, 0x74, 0xe8, 0xb0, + 0xf1, 0xa7, 0xb7, 0xdd, 0x30, 0x72, 0x76, 0xdb, 0x68, 0xf8, 0x7a, 0xfa, + 0x1c, 0x46, 0x34, 0xb1, 0x61, 0x42, 0x2c, 0xcd, 0xff, 0x5e, 0x53, 0x39, + 0x3e, 0x31, 0x90, 0x66, 0x60, 0xf4, 0xd0, 0x5b, 0x8f, 0x4f, 0x18, 0x33, + 0x57, 0xd3, 0x53, 0xac, 0xd3, 0x71, 0x3a, 0xc7, 0x64, 0xc3, 0xc8, 0xcc, + 0xb3, 0xc7, 0x7f, 0x31, 0x72, 0xec, 0xe8, 0xdb, 0x1f, 0x99, 0xe9, 0xbe, + 0xbc, 0xa7, 0x6d, 0xf7, 0xdd, 0xc2, 0xe3, 0xa1, 0x8f, 0x72, 0x27, 0x1b, + 0x8e, 0x3d, 0x7b, 0xec, 0xc2, 0xa9, 0x87, 0xb6, 0x8f, 0xfc, 0xa2, 0x1b, + 0x65, 0x7d, 0x3d, 0xf4, 0x45, 0x59, 0xe1, 0xd1, 0x91, 0x0f, 0x61, 0x3c, + 0xc7, 0x44, 0x61, 0x51, 0xf7, 0xc6, 0x89, 0xf2, 0x2b, 0xe9, 0x2c, 0x8f, + 0x86, 0x9e, 0xe0, 0x32, 0xfa, 0x35, 0xa7, 0xdf, 0xba, 0xe1, 0xd8, 0x23, + 0xdd, 0x1b, 0xaf, 0xd9, 0x3f, 0xd2, 0x7d, 0xcf, 0xfe, 0x91, 0xb7, 0x1d, + 0xdf, 0x48, 0xa7, 0x1d, 0x38, 0xef, 0x3f, 0xdb, 0x44, 0x77, 0x60, 0x01, + 0x44, 0x41, 0x3e, 0xbd, 0xc3, 0x91, 0x5a, 0xda, 0x9d, 0x90, 0x0f, 0x1f, + 0xa6, 0x2f, 0xf0, 0x1c, 0xbf, 0xeb, 0xad, 0x1b, 0x46, 0x0e, 0x95, 0x6f, + 0xa6, 0x27, 0x90, 0x69, 0x3c, 0xae, 0xd2, 0x97, 0xb3, 0xd9, 0xb2, 0x2d, + 0x17, 0x69, 0xfb, 0xae, 0xd6, 0x94, 0x9f, 0x1b, 0x19, 0x92, 0xb6, 0x2b, + 0x9f, 0x41, 0xea, 0xfc, 0xf0, 0x20, 0xda, 0x75, 0x6a, 0x99, 0x32, 0x4d, + 0x89, 0xac, 0xef, 0x9a, 0x09, 0xfd, 0xd4, 0x50, 0xc8, 0xef, 0xb1, 0x58, + 0x27, 0x8b, 0xf5, 0xae, 0xcb, 0x90, 0x74, 0xb5, 0xcc, 0xd7, 0x6a, 0xfe, + 0x60, 0x4a, 0x2e, 0x57, 0xd7, 0xed, 0x91, 0x9f, 0xaa, 0x8f, 0xde, 0x54, + 0x7d, 0xa1, 0xfb, 0x57, 0x28, 0x92, 0x7b, 0xd4, 0x20, 0xd2, 0xcf, 0x73, + 0xd3, 0xdf, 0x5d, 0x53, 0xbc, 0x0c, 0xcd, 0xcb, 0x90, 0x3c, 0x95, 0x76, + 0xa5, 0xda, 0x72, 0x6b, 0xea, 0xd5, 0xb4, 0x40, 0xcb, 0x14, 0xe8, 0x76, + 0x99, 0x57, 0x48, 0xe9, 0x77, 0x84, 0x86, 0xa4, 0x2b, 0xc8, 0x7a, 0xa7, + 0xa7, 0xee, 0xc0, 0x4b, 0xe4, 0x18, 0x4c, 0x52, 0xef, 0xa1, 0x6b, 0xb5, + 0xdf, 0xe4, 0x77, 0xe2, 0xa6, 0xa6, 0x5e, 0xed, 0x6f, 0xbb, 0x74, 0x9d, + 0x2e, 0x9c, 0x02, 0x6e, 0xdd, 0xde, 0x46, 0xed, 0x43, 0x7a, 0x74, 0x59, + 0xaf, 0xd6, 0xdf, 0x4c, 0xa5, 0xe5, 0xa8, 0x6b, 0xc9, 0x51, 0x3b, 0x5c, + 0x47, 0xa2, 0x8e, 0xaa, 0x5b, 0xdb, 0xfb, 0x3a, 0x5b, 0x07, 0x3b, 0x7a, + 0x9b, 0x06, 0x07, 0x06, 0x3b, 0x9b, 0xd6, 0xf4, 0xb5, 0xb7, 0x37, 0xf5, + 0x5e, 0xd6, 0xd1, 0xd6, 0xb4, 0xb6, 0x7f, 0xb0, 0x7d, 0xcd, 0x60, 0xff, + 0x9a, 0xfe, 0xcb, 0x5a, 0x5b, 0x29, 0xb3, 0x7b, 0x72, 0x26, 0x1c, 0x09, + 0x27, 0x37, 0x52, 0x46, 0xb7, 0xa2, 0xc6, 0xc6, 0x2e, 0x32, 0x37, 0x76, + 0xd5, 0xed, 0xe2, 0x4f, 0xa4, 0x0b, 0xfb, 0x66, 0xe6, 0x42, 0xc9, 0x68, + 0x34, 0x39, 0xbd, 0x35, 0x3c, 0x15, 0xde, 0x1a, 0x8c, 0x04, 0xf7, 0x85, + 0xe2, 0xb4, 0x71, 0x31, 0xae, 0x3f, 0x14, 0x8f, 0x47, 0xe3, 0xeb, 0xfd, + 0x93, 0xd1, 0xb9, 0x99, 0x29, 0x7f, 0x24, 0x9a, 0xf4, 0xef, 0x0b, 0x25, + 0xfd, 0x29, 0x49, 0xff, 0xe8, 0xa0, 0x3f, 0x31, 0x19, 0x8c, 0x44, 0x50, + 0xbf, 0xff, 0xcd, 0xd7, 0x9f, 0x0a, 0xed, 0x0d, 0xce, 0xcd, 0xd8, 0xdb, + 0x09, 0x4e, 0x05, 0x63, 0x49, 0x34, 0x52, 0x32, 0x30, 0x37, 0x3b, 0x7b, + 0x38, 0xc5, 0xdf, 0x14, 0x4c, 0x26, 0xfb, 0x83, 0x33, 0x33, 0x7b, 0x82, + 0x93, 0xfb, 0x49, 0x0c, 0x93, 0x31, 0x3c, 0x4a, 0x8e, 0xe1, 0xd1, 0x51, + 0x2a, 0x1f, 0xde, 0xee, 0x1f, 0x3c, 0x34, 0x19, 0x8a, 0x25, 0xc3, 0xd1, + 0x88, 0xff, 0x86, 0xe9, 0xf0, 0x4c, 0xc8, 0x3f, 0x39, 0x13, 0x4d, 0x84, + 0x23, 0xfb, 0xfc, 0xb1, 0x68, 0x3c, 0x49, 0x2b, 0x87, 0xb7, 0xbf, 0x5e, + 0xf9, 0x2c, 0xd4, 0x83, 0x0a, 0x07, 0xc3, 0x93, 0x21, 0x12, 0x5b, 0xc8, + 0xdc, 0xb2, 0xb3, 0x7f, 0x90, 0xbc, 0x5b, 0xe6, 0x26, 0x43, 0xac, 0xf8, + 0x70, 0x24, 0x36, 0x97, 0xdc, 0xc1, 0x4d, 0xf8, 0x2c, 0xd6, 0xf6, 0xb9, + 0xa4, 0xc5, 0xcb, 0xb1, 0x78, 0x32, 0x57, 0x68, 0xe5, 0xc6, 0xe7, 0x62, + 0xdc, 0x6b, 0xf3, 0xf5, 0xc1, 0x83, 0x41, 0x42, 0x40, 0x68, 0x8c, 0x0e, + 0x93, 0x63, 0x74, 0x58, 0x7e, 0xa0, 0x07, 0x7c, 0xec, 0x06, 0x0f, 0xba, + 0x8f, 0xe2, 0xc3, 0x1c, 0x1d, 0xdd, 0x3d, 0x4a, 0xd5, 0xa3, 0xc1, 0xc8, + 0x54, 0x3c, 0x1a, 0x9e, 0x6a, 0xd9, 0x63, 0x8d, 0xb6, 0x25, 0x35, 0xee, + 0x5e, 0x35, 0x1d, 0x5d, 0x54, 0xf5, 0x46, 0x52, 0x03, 0x72, 0x0c, 0x5d, + 0x54, 0xf1, 0x46, 0x42, 0x3c, 0x85, 0x5d, 0x54, 0x77, 0x29, 0x11, 0x6b, + 0x96, 0xbb, 0xa8, 0xe5, 0x92, 0xa2, 0xd3, 0xc1, 0x78, 0x70, 0x12, 0xea, + 0x85, 0x13, 0xc9, 0xf0, 0x64, 0x17, 0x35, 0x5c, 0xaa, 0xc2, 0x40, 0x28, + 0x31, 0x19, 0x0f, 0xc7, 0x92, 0x51, 0x0c, 0xe8, 0x0d, 0x87, 0xad, 0x8d, + 0x66, 0x71, 0x75, 0x67, 0x42, 0x69, 0xc1, 0xd1, 0xd0, 0xb8, 0xb2, 0xba, + 0xc5, 0x67, 0x08, 0xa2, 0x5c, 0x9e, 0x1e, 0xd3, 0xeb, 0xb4, 0xc7, 0x42, + 0x43, 0xe1, 0x19, 0x0c, 0xa5, 0xba, 0x6f, 0x2e, 0x3c, 0x33, 0xc5, 0xed, + 0x2d, 0x36, 0x99, 0xf3, 0x44, 0xdf, 0x50, 0x64, 0x2c, 0x94, 0x80, 0x61, + 0x2f, 0x3e, 0x27, 0x5a, 0x64, 0x3c, 0x94, 0x4c, 0xc2, 0x0c, 0x13, 0xe9, + 0x2e, 0xdf, 0x60, 0x08, 0x96, 0x70, 0x17, 0x2d, 0x4b, 0x09, 0x4d, 0x46, + 0x23, 0xc9, 0x50, 0x24, 0xd9, 0xd2, 0xcf, 0xf4, 0x10, 0x3a, 0x2b, 0x4f, + 0x15, 0xcd, 0x86, 0xa6, 0xc2, 0xc1, 0x16, 0x36, 0xf0, 0x16, 0x36, 0x4b, + 0xcb, 0x40, 0x1a, 0xdf, 0x58, 0x60, 0x38, 0xb2, 0x37, 0x5a, 0xcd, 0x06, + 0xcd, 0x09, 0xbb, 0x3a, 0xaf, 0x2b, 0xdd, 0x45, 0x2b, 0xdf, 0x58, 0x68, + 0x3c, 0x19, 0x4c, 0xce, 0x41, 0xeb, 0xca, 0xd7, 0x13, 0x4b, 0x6d, 0x33, + 0xbb, 0xc1, 0x2d, 0x90, 0xd1, 0xe6, 0x50, 0xad, 0x9a, 0x4c, 0xaf, 0xe6, + 0x65, 0x97, 0xaa, 0xb0, 0x3d, 0xa2, 0xaa, 0x6c, 0x8f, 0x85, 0x22, 0xa1, + 0xa9, 0x51, 0xd8, 0x69, 0x48, 0xda, 0x8a, 0xff, 0x12, 0x15, 0xdf, 0x60, + 0xec, 0x69, 0x1f, 0x60, 0x5f, 0xff, 0x05, 0x42, 0x63, 0xa1, 0xc9, 0x50, + 0xf8, 0x20, 0xb7, 0x53, 0x92, 0x12, 0x89, 0x26, 0x5a, 0xe4, 0x42, 0x57, + 0xef, 0x1a, 0x1c, 0x1b, 0x1f, 0xde, 0xbe, 0xad, 0x8b, 0x0a, 0xe6, 0x97, + 0x45, 0xa6, 0x66, 0xb0, 0x44, 0x85, 0x76, 0xe6, 0xe6, 0x20, 0x33, 0xd1, + 0x4c, 0x91, 0x9d, 0xbb, 0x23, 0x18, 0x9f, 0x0c, 0xcd, 0xec, 0x9c, 0x0b, + 0x4f, 0x75, 0x91, 0x2f, 0x55, 0x30, 0x97, 0x0c, 0xcf, 0xb4, 0x8c, 0x46, + 0xf7, 0xd9, 0xdb, 0x95, 0xbc, 0x1d, 0xc1, 0x70, 0x7c, 0x51, 0x66, 0x37, + 0xad, 0x19, 0x9d, 0x8c, 0xce, 0xb6, 0xc4, 0x67, 0x13, 0x33, 0x2d, 0xd7, + 0xc3, 0x83, 0xb5, 0x2c, 0x70, 0x63, 0xd5, 0x8b, 0x79, 0xf2, 0x2e, 0x6a, + 0xbb, 0x44, 0xad, 0x8b, 0x3c, 0x68, 0x17, 0xad, 0x7e, 0x93, 0x55, 0xec, + 0xb3, 0xdb, 0xf8, 0x26, 0xeb, 0x28, 0xe9, 0xd1, 0x4b, 0x48, 0xa7, 0x4d, + 0x32, 0x65, 0x4d, 0xaf, 0x7b, 0xc2, 0x74, 0xd1, 0xc0, 0x5f, 0xdd, 0x5a, + 0x9a, 0xc3, 0xc6, 0x16, 0x08, 0x26, 0xf6, 0x5f, 0x7a, 0xa2, 0x2e, 0x6a, + 0xe5, 0xd2, 0x83, 0xb6, 0x06, 0xbc, 0x23, 0x98, 0x9c, 0xe6, 0x0d, 0xff, + 0x86, 0xd2, 0xbc, 0xed, 0xa6, 0x82, 0x33, 0x07, 0xc3, 0xfb, 0x5b, 0xe0, + 0x24, 0xa3, 0xd8, 0x8a, 0x38, 0x04, 0x5b, 0x06, 0x23, 0xfa, 0x00, 0xec, + 0x9f, 0x09, 0x26, 0xb0, 0x35, 0xcb, 0x16, 0x91, 0x19, 0x66, 0x9f, 0xaa, + 0xcb, 0x2b, 0x16, 0x29, 0xdf, 0x1a, 0x9a, 0xdd, 0xa3, 0x05, 0x42, 0x10, + 0x59, 0xb1, 0x88, 0xc8, 0x78, 0x78, 0x5f, 0x04, 0x7b, 0x3f, 0x1e, 0xe2, + 0x4d, 0x70, 0x71, 0x71, 0x60, 0x3a, 0x1e, 0xbd, 0x01, 0x55, 0x97, 0x8c, + 0xf2, 0x59, 0xd9, 0x12, 0x8e, 0xb6, 0xd8, 0x0e, 0xea, 0x2e, 0xf2, 0x2a, + 0xf6, 0x4c, 0x30, 0xb2, 0xaf, 0x45, 0xeb, 0x51, 0x60, 0x63, 0x0d, 0xc3, + 0xe3, 0xc9, 0xf9, 0xf2, 0xd9, 0x98, 0xdb, 0xf7, 0x5c, 0x1f, 0x9a, 0x4c, + 0xce, 0xe7, 0x8d, 0x27, 0xe3, 0x18, 0x69, 0xaa, 0x1b, 0xc9, 0x93, 0x5d, + 0x07, 0xf7, 0xf0, 0x6e, 0x5b, 0x61, 0x63, 0xc7, 0x43, 0x7b, 0x5b, 0xae, + 0x0c, 0x05, 0xf7, 0x8f, 0x85, 0xf6, 0x86, 0xe2, 0xa1, 0xc8, 0xe4, 0xa5, + 0x8a, 0xbb, 0xad, 0x46, 0xe5, 0x86, 0xea, 0x8d, 0xc7, 0x83, 0x87, 0xd9, + 0xc3, 0x74, 0x2d, 0xce, 0xee, 0xb6, 0xd4, 0x4a, 0xb3, 0xd3, 0x63, 0x92, + 0xbc, 0xcd, 0xc1, 0x04, 0x0e, 0xbe, 0xd8, 0xa2, 0xcc, 0xee, 0x8b, 0x98, + 0x38, 0x10, 0x2e, 0x96, 0x04, 0xb3, 0x1b, 0x1e, 0x24, 0xcd, 0x1c, 0xc6, + 0x39, 0x15, 0x94, 0xe7, 0xad, 0xc7, 0xc6, 0x55, 0x6a, 0x2e, 0xe4, 0x74, + 0x53, 0xbe, 0x8d, 0x23, 0xdb, 0xf7, 0xda, 0x18, 0x81, 0xf0, 0x2c, 0x4f, + 0xf8, 0x92, 0x85, 0x2c, 0x65, 0xea, 0xde, 0x8b, 0x6c, 0x99, 0x7a, 0x2f, + 0x62, 0x2d, 0x1e, 0x04, 0xda, 0xa3, 0xc4, 0xc4, 0x61, 0xb8, 0xe8, 0x59, + 0x7f, 0x22, 0x14, 0x97, 0x51, 0x99, 0xef, 0xe2, 0x5d, 0x45, 0x39, 0xf6, + 0x2d, 0x40, 0xae, 0xf1, 0x81, 0x91, 0x6b, 0x87, 0xb7, 0x05, 0x68, 0xa5, + 0xfd, 0x94, 0x6c, 0xee, 0xef, 0x1d, 0x1d, 0xed, 0xeb, 0xed, 0x1f, 0xb9, + 0x36, 0x70, 0xf5, 0x8e, 0xc1, 0x6b, 0xb7, 0xf6, 0x06, 0xfa, 0x37, 0x5f, + 0x3b, 0xba, 0x7d, 0x3c, 0x40, 0x62, 0x17, 0x19, 0xbb, 0x10, 0x8e, 0xed, + 0x42, 0x00, 0x69, 0xee, 0x1a, 0xde, 0x3d, 0x4c, 0x19, 0xbb, 0xb6, 0x20, + 0x40, 0xdb, 0x02, 0x36, 0xc2, 0xb2, 0x5d, 0x88, 0xd7, 0xcc, 0x5d, 0x1c, + 0xb0, 0x39, 0x77, 0x49, 0x2e, 0x38, 0xf2, 0x83, 0xa5, 0x47, 0x55, 0x21, + 0xd2, 0x4e, 0xfe, 0xdc, 0xa2, 0x08, 0x82, 0xbc, 0x5d, 0xbb, 0x49, 0x20, + 0xae, 0x43, 0x63, 0x06, 0x02, 0x3a, 0x63, 0xa2, 0x8f, 0x2a, 0x27, 0x2e, + 0x1d, 0x3d, 0x34, 0x4d, 0xfc, 0x55, 0xa7, 0x71, 0xf5, 0x9b, 0x10, 0x87, + 0x45, 0x4c, 0x2c, 0xb2, 0x21, 0xe6, 0x31, 0xad, 0x1d, 0xe1, 0x0e, 0x4e, + 0x4e, 0x86, 0x12, 0x89, 0xea, 0x56, 0x5c, 0x15, 0xb2, 0x55, 0x7a, 0x68, + 0x26, 0xb8, 0x2f, 0x41, 0x8e, 0xe0, 0xd4, 0x14, 0x38, 0xaa, 0x2f, 0x19, + 0xde, 0xba, 0x83, 0xb1, 0x98, 0x0e, 0x32, 0x28, 0x23, 0x98, 0x60, 0x63, + 0xa1, 0xac, 0xd4, 0xb8, 0xa8, 0x24, 0x95, 0x1c, 0x1d, 0x94, 0xde, 0x47, + 0xad, 0xde, 0xce, 0x9d, 0xc3, 0x03, 0xe4, 0xd9, 0xb3, 0x20, 0xa6, 0xa3, + 0xc2, 0x3d, 0xf6, 0x43, 0x45, 0xe9, 0x9e, 0xb0, 0xc9, 0x5d, 0xab, 0x23, + 0x72, 0xcf, 0x9e, 0xa4, 0x2e, 0xe4, 0xb3, 0x0a, 0x2a, 0x53, 0xc6, 0x9e, + 0x24, 0x7b, 0x69, 0x72, 0xee, 0xe1, 0x73, 0x94, 0x32, 0x30, 0x97, 0x38, + 0x07, 0xc9, 0x39, 0x39, 0x13, 0x0a, 0xc6, 0x99, 0x44, 0x13, 0x21, 0x72, + 0x21, 0x2a, 0x8a, 0x60, 0xd4, 0x94, 0xad, 0x13, 0xb2, 0x4a, 0x26, 0xc7, + 0x4a, 0xc1, 0x70, 0x24, 0x21, 0xd9, 0x32, 0x35, 0x12, 0x3a, 0x4c, 0x62, + 0x8a, 0x32, 0x55, 0x77, 0xc3, 0x18, 0xf2, 0x54, 0x6a, 0x1e, 0x13, 0xe4, + 0x9e, 0x0a, 0x27, 0xac, 0x96, 0x32, 0x42, 0x07, 0xe6, 0x82, 0x33, 0x09, + 0x6a, 0xde, 0x1b, 0xc4, 0xf5, 0x61, 0xca, 0x9f, 0x8c, 0xfa, 0x27, 0xe3, + 0xa1, 0x60, 0x32, 0xe4, 0xdf, 0x33, 0x37, 0xa3, 0xef, 0x2d, 0xaa, 0xae, + 0x7f, 0x6f, 0x3c, 0x3a, 0x8b, 0x3b, 0xcc, 0x54, 0x1c, 0xb3, 0x49, 0x99, + 0x7b, 0xc3, 0x91, 0xe0, 0x4c, 0xf8, 0xad, 0x21, 0xaa, 0x40, 0x6a, 0x2a, + 0x3d, 0xdc, 0xa1, 0x68, 0xdc, 0x16, 0xe1, 0x2b, 0xe1, 0x72, 0x16, 0xb1, + 0x8c, 0x7c, 0x31, 0x01, 0xe7, 0xde, 0x70, 0x1c, 0xf3, 0xee, 0xe6, 0x2e, + 0xd4, 0x1a, 0x92, 0x03, 0xdb, 0x87, 0xdc, 0xf8, 0xd0, 0xd7, 0x04, 0x9d, + 0x56, 0xf2, 0xd9, 0x9c, 0x9e, 0x99, 0xe1, 0x05, 0x4c, 0x50, 0x09, 0x67, + 0xd4, 0x8a, 0x2e, 0x0c, 0xb2, 0x69, 0x79, 0xba, 0xec, 0xe2, 0x4d, 0xbc, + 0x84, 0x0b, 0x63, 0xb1, 0x99, 0xf0, 0xa4, 0x74, 0xdb, 0x96, 0x15, 0x14, + 0x80, 0x7d, 0x91, 0x86, 0xc5, 0x76, 0xa6, 0x3d, 0xfc, 0x93, 0xad, 0x5c, + 0x1c, 0xb3, 0x53, 0x26, 0xd8, 0xd2, 0xb9, 0x93, 0x17, 0xa9, 0x01, 0x75, + 0x19, 0xb4, 0xc6, 0x52, 0x92, 0x66, 0x2d, 0xbc, 0x0e, 0x51, 0x96, 0x2c, + 0x93, 0x66, 0x92, 0x97, 0x4a, 0xea, 0x85, 0x4b, 0xe5, 0x13, 0xe4, 0x42, + 0x5a, 0x2e, 0x7f, 0x1d, 0x12, 0x9b, 0xe7, 0x66, 0x39, 0x2c, 0xc7, 0x05, + 0x13, 0x9e, 0x5f, 0x4d, 0xe0, 0xa2, 0xd3, 0x0c, 0x51, 0xd8, 0xad, 0x24, + 0x53, 0xb2, 0x05, 0x6e, 0x97, 0x2a, 0x91, 0xe0, 0xd3, 0xf5, 0xa2, 0x89, + 0xda, 0x16, 0x9c, 0x65, 0xe6, 0xf0, 0x40, 0x82, 0x6a, 0x2e, 0x96, 0x91, + 0x21, 0xd0, 0x45, 0x82, 0xb5, 0x17, 0x0b, 0xaa, 0xc0, 0xe7, 0x22, 0xc9, + 0x65, 0x90, 0xe4, 0xe2, 0x85, 0x6a, 0x62, 0x70, 0x4b, 0x75, 0x91, 0xaa, + 0xc3, 0x7b, 0x04, 0xc3, 0xd1, 0x2a, 0x73, 0x0b, 0xd2, 0x00, 0x78, 0xf5, + 0x65, 0x26, 0xd7, 0xca, 0xcc, 0xf1, 0xc1, 0x4d, 0x1e, 0x9d, 0x65, 0x1f, + 0xca, 0xd5, 0x06, 0xe4, 0x5a, 0x28, 0x5b, 0x91, 0xa2, 0xf1, 0x68, 0x2c, + 0x14, 0x4f, 0x86, 0xd1, 0x4f, 0x3e, 0xb2, 0x63, 0xa1, 0xd9, 0x68, 0x32, + 0xa4, 0x67, 0x9c, 0xeb, 0x8e, 0x4b, 0x3f, 0xad, 0x37, 0xba, 0xec, 0x32, + 0x70, 0x38, 0x16, 0xa2, 0xc2, 0x69, 0x19, 0xb0, 0xea, 0xf9, 0xc7, 0xfd, + 0x30, 0xb2, 0x2f, 0x34, 0x45, 0xb9, 0x8a, 0xab, 0x83, 0x62, 0x72, 0x4d, + 0x07, 0x13, 0xdb, 0xd8, 0x88, 0x32, 0x91, 0x98, 0xee, 0x8f, 0x4e, 0x41, + 0xd5, 0x70, 0xa2, 0x5f, 0x6d, 0x36, 0x88, 0xbb, 0xc2, 0x89, 0xc1, 0xd9, + 0x58, 0xf2, 0x30, 0x27, 0xe4, 0xfc, 0x71, 0x71, 0xfa, 0x36, 0x9e, 0x19, + 0xd6, 0x67, 0x1a, 0x65, 0x72, 0xcc, 0xb3, 0x39, 0x8a, 0x4d, 0x91, 0xb1, + 0x3f, 0x74, 0x18, 0xbe, 0x9f, 0x5c, 0xb3, 0xda, 0x6c, 0x4d, 0x76, 0x8e, + 0xe4, 0x9e, 0x4d, 0xcd, 0x0f, 0x79, 0x67, 0x2f, 0xb2, 0xed, 0xec, 0x59, + 0x9b, 0x03, 0x32, 0x23, 0x3c, 0x4d, 0x66, 0x84, 0x15, 0xf3, 0x45, 0x23, + 0x7d, 0xc1, 0xe4, 0xe4, 0x74, 0xfa, 0x22, 0x97, 0xa0, 0x22, 0x18, 0xff, + 0xbc, 0xfb, 0xae, 0x35, 0xba, 0xc2, 0x85, 0x05, 0x6c, 0x63, 0xb4, 0x64, + 0x21, 0xf7, 0xca, 0x38, 0xf4, 0x96, 0xad, 0xa8, 0x81, 0x62, 0x33, 0xf1, + 0xe6, 0x08, 0xa9, 0x66, 0xc8, 0x13, 0x8d, 0xa4, 0x6f, 0xc7, 0xb2, 0x05, + 0xaf, 0x9d, 0xa3, 0x6a, 0xe7, 0x46, 0xf5, 0x7d, 0x06, 0x56, 0x80, 0x9e, + 0xf3, 0xa2, 0xf3, 0xae, 0x37, 0xdc, 0xa7, 0x3d, 0x3f, 0x10, 0x9a, 0x09, + 0x1e, 0x06, 0x3b, 0xdf, 0x62, 0xf3, 0x2a, 0x1e, 0xb4, 0xcb, 0xa9, 0xdd, + 0x69, 0x0d, 0xc4, 0x15, 0x8d, 0x0c, 0xcd, 0xcc, 0x25, 0xa6, 0x29, 0x27, + 0x1a, 0xd9, 0x9a, 0x9c, 0xb3, 0xd8, 0xd0, 0x8c, 0xf5, 0x51, 0x26, 0x30, + 0x96, 0x48, 0x84, 0xa9, 0x98, 0x39, 0x33, 0x61, 0xde, 0x47, 0x52, 0xaf, + 0xfe, 0xe8, 0x6c, 0x0c, 0x7e, 0x10, 0xb2, 0xa8, 0x29, 0x4f, 0x38, 0xe9, + 0x27, 0xad, 0x9c, 0x9a, 0x41, 0xca, 0x40, 0x2e, 0x14, 0x91, 0xf3, 0xa5, + 0xed, 0x26, 0x31, 0xc0, 0x3e, 0x16, 0xd7, 0x24, 0xc8, 0x16, 0xc0, 0xde, + 0x22, 0x0b, 0x9c, 0x07, 0xb9, 0x99, 0xa9, 0xd3, 0xb9, 0x9c, 0x4e, 0x5b, + 0x41, 0x11, 0x67, 0xe7, 0x5d, 0x32, 0xae, 0x0c, 0x27, 0xa7, 0x61, 0xc7, + 0xc5, 0x56, 0x41, 0xfa, 0x2a, 0xa1, 0x4b, 0x7c, 0x56, 0x89, 0x8d, 0x97, + 0xc7, 0x3c, 0xdb, 0x63, 0x9d, 0x2c, 0xce, 0xab, 0x6d, 0x20, 0x93, 0xec, + 0x8c, 0xe1, 0x13, 0xa2, 0x37, 0xb0, 0xbb, 0x2a, 0x88, 0x61, 0x93, 0x2d, + 0xd4, 0xb1, 0x64, 0x11, 0xe6, 0x78, 0x32, 0x14, 0x0b, 0xdc, 0x10, 0xa5, + 0xa2, 0x79, 0x65, 0xe9, 0xcd, 0x4a, 0x59, 0x31, 0x79, 0xac, 0x4f, 0x85, + 0x0e, 0x51, 0x66, 0xcc, 0x0a, 0x65, 0x1c, 0x6c, 0xea, 0x4b, 0xe3, 0xa1, + 0x7d, 0x7c, 0x4d, 0x8d, 0xcf, 0xbf, 0xeb, 0x52, 0x46, 0x5c, 0x2e, 0x1e, + 0xb9, 0x15, 0x95, 0xca, 0x2e, 0x8b, 0xe3, 0x60, 0x0a, 0x25, 0x92, 0x69, + 0x8b, 0xda, 0x11, 0x0f, 0x47, 0xb1, 0x22, 0x87, 0xc9, 0x11, 0x9f, 0x8b, + 0x90, 0xcb, 0x7a, 0x22, 0x97, 0x99, 0x98, 0x9c, 0x0e, 0x4d, 0xe1, 0xac, + 0xa2, 0x8c, 0x44, 0x08, 0xa7, 0xda, 0x14, 0x99, 0x09, 0x5e, 0x8a, 0x12, + 0xfe, 0x54, 0x8f, 0xc1, 0xa6, 0x83, 0x53, 0xfe, 0xe1, 0xed, 0xfe, 0x90, + 0x15, 0x81, 0xa3, 0x4e, 0x48, 0x1d, 0x59, 0x94, 0x9f, 0x08, 0xa5, 0x6e, + 0x46, 0x72, 0x97, 0x67, 0x83, 0xc1, 0x0b, 0xbb, 0x95, 0xb7, 0x6e, 0x1e, + 0x67, 0xf4, 0x81, 0x8f, 0x1b, 0x29, 0x5a, 0xe6, 0x03, 0xcf, 0x99, 0x48, + 0x06, 0x79, 0x3a, 0x25, 0x61, 0x59, 0xca, 0x55, 0xc9, 0x64, 0x34, 0x26, + 0xb3, 0x66, 0x02, 0x29, 0x74, 0x62, 0xe5, 0x33, 0x92, 0xd3, 0x61, 0xc4, + 0x22, 0xe4, 0x4a, 0x46, 0x65, 0x6c, 0x4c, 0x99, 0xc9, 0xa8, 0x3e, 0xe5, + 0x96, 0xcc, 0x45, 0x16, 0x9b, 0xf8, 0x65, 0x0b, 0xd8, 0xb6, 0xe9, 0x2d, + 0x9e, 0x8b, 0xbc, 0xce, 0x34, 0x3a, 0x0f, 0x06, 0x21, 0x4f, 0x2e, 0x49, + 0xb6, 0xef, 0xa5, 0xf8, 0x4d, 0x37, 0x0d, 0x74, 0xbe, 0xad, 0x92, 0xcb, + 0x30, 0xdc, 0xca, 0xf5, 0x95, 0x58, 0x92, 0xca, 0xc6, 0x4a, 0xdc, 0xaa, + 0x62, 0xe1, 0x19, 0x79, 0xe2, 0x35, 0xcd, 0x62, 0x94, 0x28, 0x88, 0x87, + 0x10, 0x65, 0x24, 0x42, 0x28, 0x84, 0xd7, 0x6a, 0xc2, 0x7c, 0x4e, 0xee, + 0x4f, 0xcc, 0xcd, 0x26, 0x2a, 0xd7, 0xef, 0x45, 0x68, 0x10, 0x6a, 0xac, + 0x9c, 0x0d, 0x47, 0x9a, 0x82, 0xb1, 0x70, 0xe5, 0xfa, 0xb6, 0xb5, 0x8d, + 0x95, 0xb0, 0xe9, 0x04, 0xea, 0xa2, 0x5a, 0x7b, 0x73, 0x7b, 0xf3, 0xea, + 0xd6, 0x26, 0x84, 0x07, 0x0d, 0xc1, 0x68, 0x22, 0xd6, 0x51, 0xf9, 0x76, + 0x32, 0x9a, 0xc4, 0x29, 0xa3, 0xd4, 0x2c, 0x12, 0x45, 0xd7, 0x15, 0x35, + 0x16, 0x65, 0xaa, 0xac, 0xa3, 0xe8, 0x2d, 0x45, 0xe3, 0x45, 0x2e, 0xa3, + 0x1e, 0x99, 0xe2, 0x3a, 0xa3, 0xc1, 0xb8, 0x5d, 0x98, 0x59, 0xdf, 0x17, + 0x45, 0x6e, 0x99, 0xac, 0xce, 0xca, 0x40, 0xba, 0x48, 0x95, 0xd6, 0xa8, + 0x52, 0x03, 0x9c, 0xdc, 0x74, 0x72, 0xaf, 0x6a, 0xc7, 0x85, 0x66, 0xc7, + 0x8b, 0x1c, 0x45, 0x3d, 0x45, 0xad, 0x45, 0x55, 0x68, 0x4f, 0x32, 0x9d, + 0x45, 0x46, 0x8a, 0xd1, 0xcc, 0x0c, 0x51, 0xbc, 0xc6, 0xd2, 0xc2, 0x28, + 0xba, 0xaa, 0x68, 0xb3, 0x25, 0x68, 0x16, 0x4d, 0xa0, 0x76, 0x95, 0x3d, + 0x7b, 0x45, 0xd1, 0x50, 0xba, 0x99, 0xab, 0x74, 0x33, 0x59, 0x16, 0x63, + 0x02, 0x75, 0x77, 0x15, 0x0d, 0x80, 0x91, 0xd2, 0x78, 0xca, 0x68, 0xe4, + 0x32, 0xa3, 0x78, 0x55, 0xf1, 0x4a, 0xc5, 0x75, 0x83, 0xbb, 0x4f, 0x71, + 0x33, 0x8a, 0x6b, 0x8a, 0x2b, 0x8a, 0x6b, 0x8b, 0xab, 0x8a, 0x2b, 0x8b, + 0xab, 0xd3, 0x75, 0x96, 0xa4, 0x93, 0x4b, 0x85, 0x43, 0x64, 0x99, 0x25, + 0x0e, 0xc3, 0x30, 0x84, 0xb1, 0xe6, 0xc8, 0x11, 0xf3, 0xd1, 0x95, 0x1d, + 0xe2, 0xbe, 0x6a, 0x21, 0x9e, 0x03, 0xce, 0x03, 0xcf, 0xaf, 0x14, 0xe2, + 0xe4, 0x2a, 0x21, 0x1e, 0x04, 0xbe, 0x5d, 0x25, 0xc4, 0x7d, 0x35, 0x42, + 0xbc, 0xc0, 0x8f, 0xdf, 0x33, 0xae, 0xb8, 0x25, 0x93, 0xc4, 0x22, 0x20, + 0x43, 0xb8, 0xf3, 0x0d, 0x71, 0xd2, 0x7f, 0xe5, 0x2d, 0x47, 0xcc, 0x17, + 0x9a, 0xaf, 0x12, 0x47, 0x5a, 0x84, 0xb8, 0x13, 0x78, 0x14, 0x78, 0x1a, + 0x38, 0x07, 0xdc, 0xde, 0x2a, 0xc4, 0x3d, 0xc0, 0x63, 0xc0, 0x33, 0xc0, + 0xf3, 0xad, 0x64, 0x0a, 0xa7, 0x17, 0x4a, 0x08, 0xae, 0xba, 0x07, 0x55, + 0x1f, 0x6c, 0x9f, 0x14, 0x47, 0xda, 0xd0, 0xf3, 0x6a, 0x21, 0x5e, 0x82, + 0xc8, 0x6b, 0x48, 0x7f, 0xbd, 0x9d, 0x5c, 0x2e, 0xdf, 0x12, 0x25, 0xa6, + 0xff, 0x4f, 0x43, 0xf6, 0x99, 0x5e, 0xc3, 0xb8, 0xb3, 0x43, 0x18, 0xdf, + 0x5e, 0xef, 0x30, 0x4e, 0xae, 0x01, 0xdd, 0xe8, 0x30, 0x4e, 0xaf, 0x75, + 0x1b, 0xe7, 0x3b, 0xc3, 0xe6, 0x4b, 0x7d, 0x0e, 0xf1, 0x74, 0x0f, 0xba, + 0xec, 0x76, 0x88, 0xdb, 0x41, 0xef, 0xeb, 0x31, 0xc4, 0xf9, 0x6e, 0x21, + 0x9e, 0xea, 0x82, 0x3a, 0x83, 0x42, 0xbc, 0x02, 0x3c, 0xbe, 0x19, 0x2a, + 0x6c, 0x41, 0x7e, 0x14, 0xe8, 0x14, 0xe2, 0x4c, 0xa7, 0x21, 0xee, 0xbc, + 0x0c, 0x23, 0x46, 0xfe, 0x1c, 0x70, 0x62, 0x2b, 0x39, 0x04, 0x16, 0x92, + 0xff, 0xdd, 0x22, 0xd0, 0xe1, 0xa3, 0x3b, 0x6e, 0xc5, 0xfc, 0x6c, 0x43, + 0xad, 0xed, 0x06, 0xb9, 0xa8, 0xdc, 0x2d, 0xdc, 0xb7, 0x8b, 0x63, 0x47, + 0xcc, 0x9f, 0x8c, 0x71, 0xe9, 0xb9, 0x31, 0x71, 0xcc, 0x7f, 0x6c, 0x5c, + 0x64, 0x9d, 0xb8, 0x42, 0x64, 0x9d, 0xbf, 0x42, 0x64, 0x7e, 0x7d, 0x87, + 0x78, 0xa7, 0x41, 0xe4, 0x34, 0xb2, 0xac, 0x6f, 0x08, 0x0b, 0xda, 0x08, + 0xc1, 0xa7, 0xeb, 0xcc, 0x5b, 0x0d, 0xba, 0x5c, 0xbc, 0x52, 0x27, 0xcc, + 0x67, 0xeb, 0x31, 0x25, 0x75, 0x86, 0xb8, 0xbd, 0x4e, 0x88, 0xb3, 0x48, + 0x9f, 0x07, 0x1e, 0x6c, 0x80, 0x9e, 0xc0, 0xb9, 0x06, 0xf4, 0xef, 0xca, + 0x91, 0xf5, 0x86, 0x79, 0x5a, 0x1b, 0xb7, 0x88, 0xdb, 0x9b, 0x84, 0xf9, + 0x93, 0x26, 0xe8, 0xdb, 0x88, 0xa9, 0x05, 0x5e, 0x43, 0xfa, 0xa9, 0x66, + 0xcc, 0x51, 0xb3, 0x28, 0xb2, 0xde, 0xc9, 0xf0, 0x9b, 0x8c, 0x07, 0xfa, + 0x15, 0x7d, 0xb2, 0x5f, 0xbd, 0xff, 0xf8, 0x02, 0xe8, 0x97, 0x6d, 0xe9, + 0xef, 0xe8, 0xf4, 0x4f, 0x41, 0xcf, 0xda, 0xd2, 0xbf, 0xd7, 0xf5, 0x5e, + 0xd5, 0x34, 0x7f, 0x40, 0x51, 0xbf, 0xa6, 0x75, 0x9a, 0x76, 0x6a, 0xba, + 0x49, 0xd3, 0x5d, 0x9a, 0xee, 0x1b, 0x50, 0xef, 0x57, 0xb8, 0x8f, 0x43, + 0x48, 0xdf, 0x36, 0x90, 0xee, 0xf3, 0x3d, 0x5a, 0xe6, 0x03, 0x36, 0xde, + 0x63, 0xb6, 0xf4, 0xe7, 0x90, 0x7e, 0x7c, 0x28, 0xfd, 0xfe, 0xc9, 0x7a, + 0x2f, 0xf5, 0xe8, 0x90, 0xfa, 0xdb, 0x11, 0xf7, 0x81, 0x3e, 0x6d, 0xfd, + 0x00, 0x5b, 0xff, 0x7b, 0x6e, 0x41, 0xfe, 0xf9, 0x05, 0x79, 0xff, 0x26, + 0xf5, 0x8e, 0xc7, 0x9a, 0x1b, 0x7e, 0xbf, 0xc4, 0x7f, 0x50, 0x42, 0xfe, + 0xad, 0x84, 0x4d, 0xea, 0xfd, 0x52, 0xf1, 0x26, 0xf5, 0x1b, 0xee, 0x4c, + 0xd0, 0x30, 0xe8, 0xb9, 0x21, 0xf5, 0x7b, 0xff, 0x17, 0x86, 0xd4, 0xef, + 0xfb, 0xcf, 0x0f, 0xa9, 0xdf, 0x99, 0xbf, 0x04, 0xda, 0xba, 0x69, 0x7e, + 0xfb, 0xdd, 0x0b, 0xf2, 0x03, 0x9b, 0xd2, 0x6b, 0xc0, 0xff, 0x7a, 0x74, + 0x7b, 0x3b, 0x16, 0xf0, 0x47, 0x34, 0xdf, 0x4b, 0xf3, 0xf9, 0x4c, 0xad, + 0xbf, 0x13, 0x64, 0x50, 0xfa, 0x6f, 0x05, 0xf1, 0x9c, 0x5a, 0x7f, 0x2f, + 0x88, 0xc7, 0xc2, 0xdf, 0x51, 0xe0, 0x9f, 0x44, 0xf0, 0xfc, 0x58, 0x7f, + 0x37, 0x88, 0xdf, 0xbe, 0x59, 0x7f, 0x3b, 0x48, 0xf8, 0xd5, 0xdf, 0xc5, + 0xe0, 0xbf, 0x1f, 0xe4, 0xf0, 0xab, 0xef, 0x84, 0xf3, 0xef, 0xdb, 0x84, + 0x47, 0x7d, 0x5f, 0x95, 0x7f, 0x87, 0x67, 0xf8, 0x55, 0x5f, 0xfc, 0xf7, + 0x85, 0x4c, 0xbf, 0x9a, 0x1b, 0xb6, 0x1b, 0xfe, 0x11, 0x1c, 0xb7, 0xc3, + 0xbf, 0x0f, 0x74, 0xfa, 0x95, 0x4e, 0xfc, 0x3b, 0x41, 0x87, 0x47, 0xbd, + 0xa7, 0x3b, 0x8d, 0x74, 0x86, 0x96, 0xe1, 0xdf, 0x11, 0xf2, 0xcb, 0x4a, + 0x96, 0xe1, 0xbf, 0x71, 0xf4, 0x7f, 0x27, 0xec, 0x58, 0xd2, 0x1c, 0x49, + 0x00, 0x00 +}; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ @@ -354,9 +804,13 @@ class MidiInput::Pimpl { public: Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) - : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), - javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, - (jint) deviceID, (jlong) this))) + : juceMidiInput (midiInput), + callback (midiInputCallback), + midiConcatenator (2048), + javaMidiDevice (LocalRef (getEnv()->CallObjectMethod (deviceManager, + MidiDeviceManager.openMidiInputPortWithID, + (jint) deviceID, + (jlong) this))) { } @@ -396,31 +850,23 @@ class MidiInput::Pimpl return {}; } - void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) + static void handleReceive (JNIEnv* env, Pimpl& myself, jbyteArray byteArray, jint offset, jint len, jlong timestamp) { - auto* env = getEnv(); - jassert (byteArray != nullptr); auto* data = env->GetByteArrayElements (byteArray, nullptr); - HeapBlock buffer (static_cast (len)); - std::memcpy (buffer.get(), data + offset, static_cast (len)); + std::vector buffer (static_cast (len)); + std::memcpy (buffer.data(), data + offset, static_cast (len)); - midiConcatenator.pushMidiData (buffer.get(), - len, static_cast (timestamp) * 1.0e-9, - juceMidiInput, *callback); + myself.midiConcatenator.pushMidiData (buffer.data(), + len, + static_cast (timestamp) * 1.0e-9, + myself.juceMidiInput, + *myself.callback); env->ReleaseByteArrayElements (byteArray, data, 0); } - static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, - jint offset, jint len, jlong timestamp) - { - auto* myself = reinterpret_cast (host); - - myself->handleMidi (byteArray, offset, len, timestamp); - } - private: MidiInput* juceMidiInput; MidiInputCallback* callback; @@ -468,7 +914,7 @@ class MidiOutput::Pimpl //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" ) + CALLBACK (generatedCallback<&MidiInput::Pimpl::handleReceive>, "handleReceive", "(J[BIIJ)V" ) DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) #undef JNI_CLASS_MEMBERS @@ -477,12 +923,7 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSuppor class AndroidMidiDeviceManager { public: - AndroidMidiDeviceManager() - : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, - JuceMidiSupport.getAndroidMidiDeviceManager, - getAppContext().get()))) - { - } + AndroidMidiDeviceManager() = default; Array getDevices (bool input) { @@ -517,6 +958,9 @@ class AndroidMidiDeviceManager if (androidMidiInput->isOpen()) return androidMidiInput.release(); + + // Perhaps the device is already open + jassertfalse; } return nullptr; @@ -525,14 +969,34 @@ class AndroidMidiDeviceManager MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) { if (auto dm = deviceManager.get()) + { if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) return new MidiOutput::Pimpl (LocalRef(javaMidiPort)); + // Perhaps the port is already open + jassertfalse; + } + return nullptr; } private: - GlobalRef deviceManager; + static void handleDevicesChanged (JNIEnv*, jclass) + { + MidiDeviceListConnectionBroadcaster::get().notify(); + } + + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + CALLBACK (handleDevicesChanged, "handleDevicesChanged", "()V" ) \ + STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode) + #undef JNI_CLASS_MEMBERS + + GlobalRef deviceManager { LocalRef (getEnv()->CallStaticObjectMethod (JuceMidiSupport, + JuceMidiSupport.getAndroidMidiDeviceManager, + getAppContext().get())) }; }; //============================================================================== @@ -698,4 +1162,10 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) } } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function callback) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (callback)) }; +} + } // namespace juce diff --git a/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/modules/juce_audio_devices/native/juce_linux_Midi.cpp index 3f87c27d0cd1..6b6f1ede5ee9 100644 --- a/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_linux_Midi.cpp @@ -29,35 +29,20 @@ namespace juce class AlsaClient : public ReferenceCountedObject { public: - AlsaClient() - { - jassert (instance == nullptr); - - snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); - - if (handle != nullptr) - { - snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); - snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); - clientId = snd_seq_client_id (handle); - - // It's good idea to pre-allocate a good number of elements - ports.ensureStorageAllocated (32); - } - } - ~AlsaClient() { + inputThread.reset(); + jassert (instance != nullptr); instance = nullptr; jassert (activeCallbacks.get() == 0); - if (inputThread) - inputThread->stopThread (3000); - if (handle != nullptr) + { + snd_seq_delete_simple_port (handle, announcementsIn); snd_seq_close (handle); + } } static String getAlsaMidiName() @@ -123,15 +108,7 @@ class AlsaClient : public ReferenceCountedObject void enableCallback (bool enable) { - const auto oldValue = callbackEnabled.exchange (enable); - - if (oldValue != enable) - { - if (enable) - client.registerCallback(); - else - client.unregisterCallback(); - } + callbackEnabled = enable; } bool sendMessageNow (const MidiMessage& message) @@ -238,23 +215,6 @@ class AlsaClient : public ReferenceCountedObject return instance; } - void registerCallback() - { - if (inputThread == nullptr) - inputThread.reset (new MidiInputThread (*this)); - - if (++activeCallbacks == 1) - inputThread->startThread(); - } - - void unregisterCallback() - { - jassert (activeCallbacks.get() > 0); - - if (--activeCallbacks == 0 && inputThread->isThreadRunning()) - inputThread->signalThreadShouldExit(); - } - void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) { const ScopedLock sl (callbackLock); @@ -294,8 +254,34 @@ class AlsaClient : public ReferenceCountedObject } private: + AlsaClient() + { + jassert (instance == nullptr); + + snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); + + if (handle != nullptr) + { + snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); + snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8()); + clientId = snd_seq_client_id (handle); + + // It's good idea to pre-allocate a good number of elements + ports.ensureStorageAllocated (32); + + announcementsIn = snd_seq_create_simple_port (handle, + TRANS ("announcements").toRawUTF8(), + SND_SEQ_PORT_CAP_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_connect_from (handle, announcementsIn, SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE); + + inputThread.emplace (*this); + } + } + snd_seq_t* handle = nullptr; int clientId = 0; + int announcementsIn = 0; OwnedArray ports; Atomic activeCallbacks; CriticalSection callbackLock; @@ -303,17 +289,52 @@ class AlsaClient : public ReferenceCountedObject static AlsaClient* instance; //============================================================================== - class MidiInputThread : public Thread + class SequencerThread { public: - MidiInputThread (AlsaClient& c) - : Thread ("JUCE MIDI Input"), client (c) + explicit SequencerThread (AlsaClient& c) + : client (c) + { + } + + ~SequencerThread() noexcept { - jassert (client.get() != nullptr); + shouldStop = true; + thread.join(); } - void run() override + private: + // If we directly call MidiDeviceListConnectionBroadcaster::get() from the background thread, + // there's a possibility that we'll deadlock in the following scenario: + // - The main thread calls MidiDeviceListConnectionBroadcaster::get() for the first time + // (e.g. to register a listener). The static MidiDeviceListConnectionBroadcaster singleton + // begins construction. During the constructor, an AlsaClient is created to iterate midi + // ins/outs. + // - The AlsaClient starts a new SequencerThread. If connections are updated, the + // SequencerThread may call MidiDeviceListConnectionBroadcaster::get().notify() + // while the MidiDeviceListConnectionBroadcaster singleton is still being created. + // - The SequencerThread blocks until the MidiDeviceListConnectionBroadcaster has been + // created on the main thread, but the MidiDeviceListConnectionBroadcaster's constructor + // can't complete until the AlsaClient's destructor has run, which in turn requires the + // SequencerThread to join. + class UpdateNotifier : private AsyncUpdater { + public: + ~UpdateNotifier() override { cancelPendingUpdate(); } + using AsyncUpdater::triggerAsyncUpdate; + + private: + void handleAsyncUpdate() override { MidiDeviceListConnectionBroadcaster::get().notify(); } + }; + + AlsaClient& client; + MidiDataConcatenator concatenator { 2048 }; + std::atomic shouldStop { false }; + UpdateNotifier notifier; + std::thread thread { [this] + { + Thread::setCurrentThreadName ("JUCE MIDI Input"); + auto seqHandle = client.get(); const int maxEventSize = 16 * 1024; @@ -321,17 +342,20 @@ class AlsaClient : public ReferenceCountedObject if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) { - auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); - HeapBlock pfd (numPfds); - snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); + const ScopeGuard freeMidiEvent { [&] { snd_midi_event_free (midiParser); } }; - HeapBlock buffer (maxEventSize); + const auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); + std::vector pfd (static_cast (numPfds)); + snd_seq_poll_descriptors (seqHandle, pfd.data(), (unsigned int) numPfds, POLLIN); - while (! threadShouldExit()) + std::vector buffer (maxEventSize); + + while (! shouldStop) { - if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call + // This timeout shouldn't be too long, so that the program can exit in a timely manner + if (poll (pfd.data(), (nfds_t) numPfds, 100) > 0) { - if (threadShouldExit()) + if (shouldStop) break; do @@ -340,33 +364,51 @@ class AlsaClient : public ReferenceCountedObject if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) { + const ScopeGuard freeInputEvent { [&] { snd_seq_free_event (inputEvent); } }; + + constexpr int systemEvents[] + { + SND_SEQ_EVENT_CLIENT_CHANGE, + SND_SEQ_EVENT_CLIENT_START, + SND_SEQ_EVENT_CLIENT_EXIT, + SND_SEQ_EVENT_PORT_CHANGE, + SND_SEQ_EVENT_PORT_START, + SND_SEQ_EVENT_PORT_EXIT, + SND_SEQ_EVENT_PORT_SUBSCRIBED, + SND_SEQ_EVENT_PORT_UNSUBSCRIBED, + }; + + const auto foundEvent = std::find (std::begin (systemEvents), + std::end (systemEvents), + inputEvent->type); + + if (foundEvent != std::end (systemEvents)) + { + notifier.triggerAsyncUpdate(); + continue; + } + // xxx what about SYSEXes that are too big for the buffer? - auto numBytes = snd_midi_event_decode (midiParser, buffer, - maxEventSize, inputEvent); + const auto numBytes = snd_midi_event_decode (midiParser, + buffer.data(), + maxEventSize, + inputEvent); snd_midi_event_reset_decode (midiParser); - concatenator.pushMidiData (buffer, (int) numBytes, + concatenator.pushMidiData (buffer.data(), (int) numBytes, Time::getMillisecondCounter() * 0.001, inputEvent, client); - - snd_seq_free_event (inputEvent); } } while (snd_seq_event_input_pending (seqHandle, 0) > 0); } } - - snd_midi_event_free (midiParser); } - } - - private: - AlsaClient& client; - MidiDataConcatenator concatenator { 2048 }; + } }; }; - std::unique_ptr inputThread; + std::optional inputThread; }; AlsaClient* AlsaClient::instance = nullptr; @@ -659,6 +701,18 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) internal->ptr->sendMessageNow (message); } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + // We capture the AlsaClient instance here to ensure that it remains alive for at least as long + // as the MidiDeviceListConnection. This is necessary because system change messages will only + // be processed when the AlsaClient's SequencerThread is running. + return { &broadcaster, broadcaster.add ([fn = std::move (cb), client = AlsaClient::getInstance()] + { + NullCheckedInvocation::invoke (fn); + }) }; +} + //============================================================================== #else @@ -693,6 +747,12 @@ StringArray MidiOutput::getDevices() int MidiOutput::getDefaultDeviceIndex() { return 0;} std::unique_ptr MidiOutput::openDevice (int) { return {}; } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + #endif } // namespace juce diff --git a/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm b/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm index fd61986e7a97..ed40d6719dd5 100644 --- a/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm +++ b/modules/juce_audio_devices/native/juce_mac_CoreMidi.mm @@ -579,9 +579,10 @@ static void enableSimulatorMidiSession() #endif } - static void globalSystemChangeCallback (const MIDINotification*, void*) + static void globalSystemChangeCallback (const MIDINotification* notification, void*) { - // TODO.. Should pass-on this notification.. + if (notification != nullptr && notification->messageID == kMIDIMsgSetupChanged) + MidiDeviceListConnectionBroadcaster::get().notify(); } static String getGlobalMidiClientName() @@ -594,9 +595,7 @@ static String getGlobalMidiClientName() static MIDIClientRef getGlobalMidiClient() { - static MIDIClientRef globalMidiClient = 0; - - if (globalMidiClient == 0) + static const auto globalMidiClient = [&] { // Since OSX 10.6, the MIDIClientCreate function will only work // correctly when called from the message thread! @@ -605,8 +604,10 @@ static MIDIClientRef getGlobalMidiClient() enableSimulatorMidiSession(); CFUniquePtr name (getGlobalMidiClientName().toCFString()); - CHECK_ERROR (MIDIClientCreate (name.get(), &globalSystemChangeCallback, nullptr, &globalMidiClient)); - } + MIDIClientRef result{}; + CHECK_ERROR (MIDIClientCreate (name.get(), globalSystemChangeCallback, nullptr, &result)); + return result; + }(); return globalMidiClient; } @@ -1300,6 +1301,12 @@ static CreatorFunctionPointers getCreatorFunctionPointers() internal->send (message); } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + #undef CHECK_ERROR } // namespace juce diff --git a/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/modules/juce_audio_devices/native/juce_win32_Midi.cpp index c2b13f70983a..05ee23d6c176 100644 --- a/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -103,7 +103,7 @@ struct MidiServiceType struct Win32MidiService : public MidiServiceType, private Timer { - Win32MidiService() {} + Win32MidiService() = default; Array getAvailableDevices (bool isInput) override { @@ -1871,6 +1871,10 @@ struct MidiService : public DeletedAtShutdown private: std::unique_ptr internal; + DeviceChangeDetector detector { L"JuceMidiDeviceDetector_", [] + { + MidiDeviceListConnectionBroadcaster::get().notify(); + } }; }; JUCE_IMPLEMENT_SINGLETON (MidiService) @@ -2013,4 +2017,10 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) internal->sendMessageNow (message); } +MidiDeviceListConnection MidiDeviceListConnection::make (std::function cb) +{ + auto& broadcaster = MidiDeviceListConnectionBroadcaster::get(); + return { &broadcaster, broadcaster.add (std::move (cb)) }; +} + } // namespace juce diff --git a/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp b/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp index 4cbea0a07e8c..6673568a3c4c 100644 --- a/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp +++ b/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp @@ -27,7 +27,7 @@ namespace juce { #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothMidiManager;") DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23) #undef JNI_CLASS_MEMBERS @@ -40,7 +40,7 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiS METHOD (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \ METHOD (startStopScan, "startStopScan", "(Z)V") -DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothManager", 23) +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothMidiManager", 23) #undef JNI_CLASS_MEMBERS //============================================================================== From a9a95fe69f492dcf1766fa2134e7909154cce072 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 12 Jan 2023 13:37:02 +0000 Subject: [PATCH 010/347] SystemStats: Add helper function to detect app sandbox --- examples/Utilities/SystemInfoDemo.h | 57 +++++++++++-------- modules/juce_core/juce_core.h | 2 +- .../juce_core/native/juce_mac_SystemStats.mm | 34 +++++++++++ modules/juce_core/system/juce_SystemStats.h | 6 +- 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/examples/Utilities/SystemInfoDemo.h b/examples/Utilities/SystemInfoDemo.h index f4bcddbe8feb..ab70171656a2 100644 --- a/examples/Utilities/SystemInfoDemo.h +++ b/examples/Utilities/SystemInfoDemo.h @@ -120,6 +120,11 @@ static String getDisplayInfo() return displayDesc; } +static String boolString (bool b) +{ + return b ? "yes" : "no"; +} + static String getAllSystemInfo() { String systemInfo; @@ -150,30 +155,36 @@ static String getAllSystemInfo() << "CPU vendor: " << SystemStats::getCpuVendor() << newLine << "CPU model: " << SystemStats::getCpuModel() << newLine << "CPU speed: " << SystemStats::getCpuSpeedInMegahertz() << " MHz" << newLine - << "CPU has MMX: " << (SystemStats::hasMMX() ? "yes" : "no") << newLine - << "CPU has FMA3: " << (SystemStats::hasFMA3() ? "yes" : "no") << newLine - << "CPU has FMA4: " << (SystemStats::hasFMA4() ? "yes" : "no") << newLine - << "CPU has SSE: " << (SystemStats::hasSSE() ? "yes" : "no") << newLine - << "CPU has SSE2: " << (SystemStats::hasSSE2() ? "yes" : "no") << newLine - << "CPU has SSE3: " << (SystemStats::hasSSE3() ? "yes" : "no") << newLine - << "CPU has SSSE3: " << (SystemStats::hasSSSE3() ? "yes" : "no") << newLine - << "CPU has SSE4.1: " << (SystemStats::hasSSE41() ? "yes" : "no") << newLine - << "CPU has SSE4.2: " << (SystemStats::hasSSE42() ? "yes" : "no") << newLine - << "CPU has 3DNOW: " << (SystemStats::has3DNow() ? "yes" : "no") << newLine - << "CPU has AVX: " << (SystemStats::hasAVX() ? "yes" : "no") << newLine - << "CPU has AVX2: " << (SystemStats::hasAVX2() ? "yes" : "no") << newLine - << "CPU has AVX512F: " << (SystemStats::hasAVX512F() ? "yes" : "no") << newLine - << "CPU has AVX512BW: " << (SystemStats::hasAVX512BW() ? "yes" : "no") << newLine - << "CPU has AVX512CD: " << (SystemStats::hasAVX512CD() ? "yes" : "no") << newLine - << "CPU has AVX512DQ: " << (SystemStats::hasAVX512DQ() ? "yes" : "no") << newLine - << "CPU has AVX512ER: " << (SystemStats::hasAVX512ER() ? "yes" : "no") << newLine - << "CPU has AVX512IFMA: " << (SystemStats::hasAVX512IFMA() ? "yes" : "no") << newLine - << "CPU has AVX512PF: " << (SystemStats::hasAVX512PF() ? "yes" : "no") << newLine - << "CPU has AVX512VBMI: " << (SystemStats::hasAVX512VBMI() ? "yes" : "no") << newLine - << "CPU has AVX512VL: " << (SystemStats::hasAVX512VL() ? "yes" : "no") << newLine - << "CPU has AVX512VPOPCNTDQ: " << (SystemStats::hasAVX512VPOPCNTDQ() ? "yes" : "no") << newLine - << "CPU has Neon: " << (SystemStats::hasNeon() ? "yes" : "no") << newLine + << "CPU has MMX: " << boolString (SystemStats::hasMMX() ) << newLine + << "CPU has FMA3: " << boolString (SystemStats::hasFMA3() ) << newLine + << "CPU has FMA4: " << boolString (SystemStats::hasFMA4() ) << newLine + << "CPU has SSE: " << boolString (SystemStats::hasSSE() ) << newLine + << "CPU has SSE2: " << boolString (SystemStats::hasSSE2() ) << newLine + << "CPU has SSE3: " << boolString (SystemStats::hasSSE3() ) << newLine + << "CPU has SSSE3: " << boolString (SystemStats::hasSSSE3() ) << newLine + << "CPU has SSE4.1: " << boolString (SystemStats::hasSSE41() ) << newLine + << "CPU has SSE4.2: " << boolString (SystemStats::hasSSE42() ) << newLine + << "CPU has 3DNOW: " << boolString (SystemStats::has3DNow() ) << newLine + << "CPU has AVX: " << boolString (SystemStats::hasAVX() ) << newLine + << "CPU has AVX2: " << boolString (SystemStats::hasAVX2() ) << newLine + << "CPU has AVX512F: " << boolString (SystemStats::hasAVX512F() ) << newLine + << "CPU has AVX512BW: " << boolString (SystemStats::hasAVX512BW() ) << newLine + << "CPU has AVX512CD: " << boolString (SystemStats::hasAVX512CD() ) << newLine + << "CPU has AVX512DQ: " << boolString (SystemStats::hasAVX512DQ() ) << newLine + << "CPU has AVX512ER: " << boolString (SystemStats::hasAVX512ER() ) << newLine + << "CPU has AVX512IFMA: " << boolString (SystemStats::hasAVX512IFMA() ) << newLine + << "CPU has AVX512PF: " << boolString (SystemStats::hasAVX512PF() ) << newLine + << "CPU has AVX512VBMI: " << boolString (SystemStats::hasAVX512VBMI() ) << newLine + << "CPU has AVX512VL: " << boolString (SystemStats::hasAVX512VL() ) << newLine + << "CPU has AVX512VPOPCNTDQ: " << boolString (SystemStats::hasAVX512VPOPCNTDQ() ) << newLine + << "CPU has Neon: " << boolString (SystemStats::hasNeon() ) << newLine + << newLine; + + #if JUCE_MAC + systemInfo + << "Application sandbox enabled: " << boolString (SystemStats::isAppSandboxEnabled()) << newLine << newLine; + #endif systemInfo << "Current working directory: " << File::getCurrentWorkingDirectory().getFullPathName() << newLine diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h index 4ce05eb81316..8490eaec1c0c 100644 --- a/modules/juce_core/juce_core.h +++ b/modules/juce_core/juce_core.h @@ -40,7 +40,7 @@ minimumCppStandard: 17 dependencies: - OSXFrameworks: Cocoa Foundation IOKit + OSXFrameworks: Cocoa Foundation IOKit Security iOSFrameworks: Foundation linuxLibs: rt dl pthread mingwLibs: uuid wsock32 wininet version ole32 ws2_32 oleaut32 imm32 comdlg32 shlwapi rpcrt4 winmm diff --git a/modules/juce_core/native/juce_mac_SystemStats.mm b/modules/juce_core/native/juce_mac_SystemStats.mm index 2b6939e0d1f8..cd0b4a5c344e 100644 --- a/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/modules/juce_core/native/juce_mac_SystemStats.mm @@ -371,4 +371,38 @@ uint32 millisecondsSinceStartup() const noexcept return deviceId; } +#if JUCE_MAC +bool SystemStats::isAppSandboxEnabled() +{ + static const auto result = [&] + { + SecCodeRef ref = nullptr; + + if (const auto err = SecCodeCopySelf (kSecCSDefaultFlags, &ref); err != noErr) + return false; + + const CFUniquePtr managedRef (ref); + CFDictionaryRef infoDict = nullptr; + + if (const auto err = SecCodeCopySigningInformation (managedRef.get(), kSecCSDynamicInformation, &infoDict); err != noErr) + return false; + + const CFUniquePtr managedInfoDict (infoDict); + const void* entitlementsDict = nullptr; + + if (! CFDictionaryGetValueIfPresent (managedInfoDict.get(), kSecCodeInfoEntitlementsDict, &entitlementsDict)) + return false; + + const void* flag = nullptr; + + if (! CFDictionaryGetValueIfPresent (static_cast (entitlementsDict), @"com.apple.security.app-sandbox", &flag)) + return false; + + return static_cast (CFBooleanGetValue (static_cast (flag))); + }(); + + return result; +} +#endif + } // namespace juce diff --git a/modules/juce_core/system/juce_SystemStats.h b/modules/juce_core/system/juce_SystemStats.h index a80cc541de72..abe0955e7e01 100644 --- a/modules/juce_core/system/juce_SystemStats.h +++ b/modules/juce_core/system/juce_SystemStats.h @@ -228,7 +228,7 @@ class JUCE_API SystemStats final /** A function type for use in setApplicationCrashHandler(). When called, its void* argument will contain platform-specific data about the crash. */ - using CrashHandlerFunction = void(*)(void*); + using CrashHandlerFunction = void (*) (void*); /** Sets up a global callback function that will be called if the application executes some kind of illegal instruction. @@ -243,6 +243,10 @@ class JUCE_API SystemStats final */ static bool isRunningInAppExtensionSandbox() noexcept; + #if JUCE_MAC + static bool isAppSandboxEnabled(); + #endif + //============================================================================== #ifndef DOXYGEN [[deprecated ("This method was spelt wrong! Please change your code to use getCpuSpeedInMegahertz instead.")]] From 73bd3f865dd13b1b61e674ff237e9970eba4a3d3 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 17 Jan 2023 18:43:17 +0000 Subject: [PATCH 011/347] Projucer: Strongly assert that bluetooth will not be used for fine location access --- .../Source/ProjectSaving/jucer_ProjectExport_Android.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index a030a95940fd..93c1cead5d93 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -1740,9 +1740,14 @@ class AndroidProjectExporter : public ProjectExporter if (permission == "android.permission.READ_EXTERNAL_STORAGE") usesPermission->setAttribute ("android:maxSdkVersion", "32"); + if (permission == "android.permission.BLUETOOTH_SCAN") + usesPermission->setAttribute ("android:usesPermissionFlags", "neverForLocation"); + // These permissions are obsoleted by new more fine-grained permissions in API level 31 if (permission == "android.permission.BLUETOOTH" - || permission == "android.permission.BLUETOOTH_ADMIN") + || permission == "android.permission.BLUETOOTH_ADMIN" + || permission == "android.permission.ACCESS_FINE_LOCATION" + || permission == "android.permission.ACCESS_COARSE_LOCATION") { usesPermission->setAttribute ("android:maxSdkVersion", "30"); } From 33673eac175cc8bdb107b73b567fb8e504fc8819 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 17 Jan 2023 19:22:17 +0000 Subject: [PATCH 012/347] Resave all projects --- .../Builds/Android/app/src/main/AndroidManifest.xml | 6 +++--- .../Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj | 4 ++++ .../MacOSX/AudioPerformanceTest.xcodeproj/project.pbxproj | 4 ++++ .../Builds/Android/app/src/main/AndroidManifest.xml | 6 +++--- .../Builds/MacOSX/AudioPluginHost.xcodeproj/project.pbxproj | 4 ++++ .../Builds/MacOSX/BinaryBuilder.xcodeproj/project.pbxproj | 4 ++++ .../MacOSX/NetworkGraphicsDemo.xcodeproj/project.pbxproj | 4 ++++ .../Builds/MacOSX/Projucer.xcodeproj/project.pbxproj | 4 ++++ .../Builds/MacOSX/UnitTestRunner.xcodeproj/project.pbxproj | 4 ++++ 9 files changed, 34 insertions(+), 6 deletions(-) diff --git a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml index 7500ac43f5d3..b15ef7000842 100644 --- a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml +++ b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml @@ -8,13 +8,13 @@ - - + + - + diff --git a/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj b/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj index f69455f9aca0..7535458bff56 100644 --- a/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj +++ b/examples/DemoRunner/Builds/MacOSX/DemoRunner.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0140787C0118A95E37DE90B4 /* include_juce_video.mm */ = {isa = PBXBuildFile; fileRef = 9144821E003E15E4042B57DB; }; 028383D0577D0236899D8CA5 /* OpenGL.framework */ = {isa = PBXBuildFile; fileRef = 40BD06D4AB0D2C73E936A2F1; }; + 05E2CC77AE99E8A2E3A18777 /* Security.framework */ = {isa = PBXBuildFile; fileRef = 87D5F938A115568F9CF3BE5A; }; 1351A13E78F38741C6075600 /* CoreAudio.framework */ = {isa = PBXBuildFile; fileRef = 4F0A137A4115946A346180E6; }; 163B0CF2DD0990A63DF1D5A6 /* AudioToolbox.framework */ = {isa = PBXBuildFile; fileRef = 470C3E4553B513FFEF752779; }; 1BA301E39E29966719B710A1 /* GUI */ = {isa = PBXBuildFile; fileRef = 9EBAEBBD9093CB005D1692F2; }; @@ -104,6 +105,7 @@ 71A91516AFD980FEE694C0E1 /* IOKit.framework */ /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 7A5AAE9EE573FC6105CC4AAC /* SettingsContent.h */ /* SettingsContent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SettingsContent.h; path = ../../Source/UI/SettingsContent.h; sourceTree = SOURCE_ROOT; }; 7B3243C92248D379A0489AA4 /* Utilities */ /* Utilities */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Utilities; path = ../../../Utilities; sourceTree = ""; }; + 87D5F938A115568F9CF3BE5A /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 8CE533D611CD0984AD028D73 /* juce_graphics */ /* juce_graphics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_graphics; path = ../../../../modules/juce_graphics; sourceTree = SOURCE_ROOT; }; 8D44097417573B38729A0179 /* include_juce_audio_processors_ara.cpp */ /* include_juce_audio_processors_ara.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_processors_ara.cpp; path = ../../JuceLibraryCode/include_juce_audio_processors_ara.cpp; sourceTree = SOURCE_ROOT; }; 903CD4126C779884797EF915 /* juce_core */ /* juce_core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_core; path = ../../../../modules/juce_core; sourceTree = SOURCE_ROOT; }; @@ -158,6 +160,7 @@ 8584640341100008744861A5, 028383D0577D0236899D8CA5, B1981F62F6A91FD2F579A198, + 05E2CC77AE99E8A2E3A18777, 89AD16514B1F4133FFEA1DF9, ); runOnlyForDeploymentPostprocessing = 0; @@ -182,6 +185,7 @@ 71A91516AFD980FEE694C0E1, 40BD06D4AB0D2C73E936A2F1, 23CD1A3F9067C3A0ECE7BB67, + 87D5F938A115568F9CF3BE5A, 96D99A08027CA35D6A4E5CFD, ); name = Frameworks; diff --git a/extras/AudioPerformanceTest/Builds/MacOSX/AudioPerformanceTest.xcodeproj/project.pbxproj b/extras/AudioPerformanceTest/Builds/MacOSX/AudioPerformanceTest.xcodeproj/project.pbxproj index 133817549367..6f534c4e4538 100644 --- a/extras/AudioPerformanceTest/Builds/MacOSX/AudioPerformanceTest.xcodeproj/project.pbxproj +++ b/extras/AudioPerformanceTest/Builds/MacOSX/AudioPerformanceTest.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ D2CECF93178A1738DA02CA4A /* include_juce_data_structures.mm */ = {isa = PBXBuildFile; fileRef = EDD11E2CC0B18196ADA0C87B; }; DA21A6E7A18555DCFC63B07C /* RecentFilesMenuTemplate.nib */ = {isa = PBXBuildFile; fileRef = FAAB4EAE4A57B642D3B9EC23; }; E1282ABB96DD2E7FA7F63559 /* App */ = {isa = PBXBuildFile; fileRef = 614F2084407B35D62101F69F; }; + ED96AB7BB45155C78DABD3B9 /* Security.framework */ = {isa = PBXBuildFile; fileRef = 5307BB2EB4B0D26BB39EA248; }; F8099BB77DC0D01DCCC6AFB9 /* QuartzCore.framework */ = {isa = PBXBuildFile; fileRef = 0A58FDDF6FB9253F51939A52; }; FFAF94080FF4A9995B33151E /* include_juce_core.mm */ = {isa = PBXBuildFile; fileRef = 24425FFB0BCC7E54CADAA013; }; /* End PBXBuildFile section */ @@ -54,6 +55,7 @@ 43775DC3D9F7917846EA5327 /* IOKit.framework */ /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 453777CEB7099A5D61901D13 /* Cocoa.framework */ /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 50FEDCEF881CC99174035167 /* juce_gui_basics */ /* juce_gui_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_basics; path = ../../../../modules/juce_gui_basics; sourceTree = SOURCE_ROOT; }; + 5307BB2EB4B0D26BB39EA248 /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 614F2084407B35D62101F69F /* App */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AudioPerformanceTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 77AA9722BAADD4108205501A /* juce_data_structures */ /* juce_data_structures */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_data_structures; path = ../../../../modules/juce_data_structures; sourceTree = SOURCE_ROOT; }; 7E951216B6138C76653B1460 /* include_juce_graphics.mm */ /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; }; @@ -95,6 +97,7 @@ CC782AABFA20787BABBCED90, 9031C69145EE085B60904363, F8099BB77DC0D01DCCC6AFB9, + ED96AB7BB45155C78DABD3B9, 537E779F6008999191B2920A, ); runOnlyForDeploymentPostprocessing = 0; @@ -115,6 +118,7 @@ E1BB9D521BF6C055F5B88628, 43775DC3D9F7917846EA5327, 0A58FDDF6FB9253F51939A52, + 5307BB2EB4B0D26BB39EA248, 3058871156B921B9E5946C4F, ); name = Frameworks; diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml index ed0fcf1b0cd0..7d32b0dc2836 100644 --- a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml @@ -8,13 +8,13 @@ - - + + - + diff --git a/extras/AudioPluginHost/Builds/MacOSX/AudioPluginHost.xcodeproj/project.pbxproj b/extras/AudioPluginHost/Builds/MacOSX/AudioPluginHost.xcodeproj/project.pbxproj index 0fb33e31d613..def9c603ba09 100644 --- a/extras/AudioPluginHost/Builds/MacOSX/AudioPluginHost.xcodeproj/project.pbxproj +++ b/extras/AudioPluginHost/Builds/MacOSX/AudioPluginHost.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ B288A89F96704F142ED8E939 /* AudioUnit.framework */ = {isa = PBXBuildFile; fileRef = 5ACC21AA45BBF48C3C64D56D; }; BBA1733CF8B064A5FD0B4CF4 /* OpenGL.framework */ = {isa = PBXBuildFile; fileRef = D313CF37B25D7FD313C4F336; }; C38D14DC58F1941DD5E4BF60 /* include_juce_gui_extra.mm */ = {isa = PBXBuildFile; fileRef = 2BE6C2DFD6EBB9A89109AEB5; }; + C4AD86AA4B49877C7E02A4C0 /* Security.framework */ = {isa = PBXBuildFile; fileRef = C1981AC950A91C9050CE8358; }; CAC10E4345428CAEE6F0DA1B /* include_juce_audio_processors_ara.cpp */ = {isa = PBXBuildFile; fileRef = A43CE79CB190C2D69E17E1E3; }; CAF0DE157C8F7D9F168AA3B6 /* include_juce_audio_processors.mm */ = {isa = PBXBuildFile; fileRef = 5FBD6C402617272052BB4D81; }; D92C7BF86C9CCF6B4D14F809 /* RecentFilesMenuTemplate.nib */ = {isa = PBXBuildFile; fileRef = 7DA35787B5F6F7440D667CC8; }; @@ -113,6 +114,7 @@ B8774D8AD307D798831C0DF7 /* DiscRecording.framework */ /* DiscRecording.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiscRecording.framework; path = System/Library/Frameworks/DiscRecording.framework; sourceTree = SDKROOT; }; B8E24A5CEE6B7055537725CF /* include_juce_cryptography.mm */ /* include_juce_cryptography.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_cryptography.mm; path = ../../JuceLibraryCode/include_juce_cryptography.mm; sourceTree = SOURCE_ROOT; }; B95B9D6774059DBB19F2B4E2 /* InternalPlugins.h */ /* InternalPlugins.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InternalPlugins.h; path = ../../Source/Plugins/InternalPlugins.h; sourceTree = SOURCE_ROOT; }; + C1981AC950A91C9050CE8358 /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; C37B2E77AAB6C9E13729BF99 /* IOConfigurationWindow.cpp */ /* IOConfigurationWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = IOConfigurationWindow.cpp; path = ../../Source/Plugins/IOConfigurationWindow.cpp; sourceTree = SOURCE_ROOT; }; CA726B9AA0EC87B58D005C8D /* ARAPlugin.h */ /* ARAPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARAPlugin.h; path = ../../Source/Plugins/ARAPlugin.h; sourceTree = SOURCE_ROOT; }; D313CF37B25D7FD313C4F336 /* OpenGL.framework */ /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; @@ -142,6 +144,7 @@ 68FBFDA1FE637B3EDA09A592, BBA1733CF8B064A5FD0B4CF4, A02C9F4C4B840C27B6CAFEBD, + C4AD86AA4B49877C7E02A4C0, 4DB15177DDC357F4503F88CF, ); runOnlyForDeploymentPostprocessing = 0; @@ -283,6 +286,7 @@ 4DF6E6E41E10965AD169143B, D313CF37B25D7FD313C4F336, 89309C0C5F3269BD06BE7F27, + C1981AC950A91C9050CE8358, B457EE687507BF1DEEA7581F, ); name = Frameworks; diff --git a/extras/BinaryBuilder/Builds/MacOSX/BinaryBuilder.xcodeproj/project.pbxproj b/extras/BinaryBuilder/Builds/MacOSX/BinaryBuilder.xcodeproj/project.pbxproj index 032956a7a890..5af691d3518a 100644 --- a/extras/BinaryBuilder/Builds/MacOSX/BinaryBuilder.xcodeproj/project.pbxproj +++ b/extras/BinaryBuilder/Builds/MacOSX/BinaryBuilder.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 3C6FF7689E2FD827A48E2303 /* Main.cpp */ = {isa = PBXBuildFile; fileRef = 50B7C64414A3E778021F5EC4; }; 4B72EFB0E9D74CA7227F6CAB /* Cocoa.framework */ = {isa = PBXBuildFile; fileRef = 57DDB771ED96A256F190ADF8; }; 542006E949BB022F198DF0F2 /* RecentFilesMenuTemplate.nib */ = {isa = PBXBuildFile; fileRef = 1A71A586C0F50B6B328D877B; }; + 76329EF5627C14DD1C621F6E /* Security.framework */ = {isa = PBXBuildFile; fileRef = F9E4EBBA2682611A3978340C; }; 9E4D85A3D54739A0FA80A446 /* include_juce_core.mm */ = {isa = PBXBuildFile; fileRef = D186E2D509765FAE0758F17D; }; C17566E1AACD033B3DD74E9F /* Foundation.framework */ = {isa = PBXBuildFile; fileRef = 6AA23C81496E022290EB2A0C; }; /* End PBXBuildFile section */ @@ -26,6 +27,7 @@ D186E2D509765FAE0758F17D /* include_juce_core.mm */ /* include_juce_core.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_core.mm; path = ../../JuceLibraryCode/include_juce_core.mm; sourceTree = SOURCE_ROOT; }; D6C3594C8BEC94040AF108FE /* JuceHeader.h */ /* JuceHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JuceHeader.h; path = ../../JuceLibraryCode/JuceHeader.h; sourceTree = SOURCE_ROOT; }; F769CD634476C91F4C9D0596 /* IOKit.framework */ /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + F9E4EBBA2682611A3978340C /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -36,6 +38,7 @@ 4B72EFB0E9D74CA7227F6CAB, C17566E1AACD033B3DD74E9F, 0D53D8B0AEE37C02C147344B, + 76329EF5627C14DD1C621F6E, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -110,6 +113,7 @@ 57DDB771ED96A256F190ADF8, 6AA23C81496E022290EB2A0C, F769CD634476C91F4C9D0596, + F9E4EBBA2682611A3978340C, ); name = Frameworks; sourceTree = ""; diff --git a/extras/NetworkGraphicsDemo/Builds/MacOSX/NetworkGraphicsDemo.xcodeproj/project.pbxproj b/extras/NetworkGraphicsDemo/Builds/MacOSX/NetworkGraphicsDemo.xcodeproj/project.pbxproj index 4f363192ff3d..f123991e4f09 100644 --- a/extras/NetworkGraphicsDemo/Builds/MacOSX/NetworkGraphicsDemo.xcodeproj/project.pbxproj +++ b/extras/NetworkGraphicsDemo/Builds/MacOSX/NetworkGraphicsDemo.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 77745BF98931B91341FE17F6 /* IOKit.framework */ = {isa = PBXBuildFile; fileRef = F3292E3563DB7ABB076DB400; }; 80B9F7ED2009922C693B7DD4 /* DiscRecording.framework */ = {isa = PBXBuildFile; fileRef = CB82A14817C3E2ABBBBC3864; }; 80EE2C27B466BAFD83881D3F /* Accelerate.framework */ = {isa = PBXBuildFile; fileRef = 2E13A899F4E3C99054A3656F; }; + 88DB5C5128E42AD59AD944D9 /* Security.framework */ = {isa = PBXBuildFile; fileRef = 9D1E94FAB66D1FB2531A3F9F; }; 8ECB0767EE340DD83869E37D /* WebKit.framework */ = {isa = PBXBuildFile; fileRef = EC794872987FEA2E129C589A; }; 95C7D26CC68839FC6BF90AC3 /* include_juce_audio_processors_ara.cpp */ = {isa = PBXBuildFile; fileRef = 52EF9BE720EFF47106DB0351; }; 987CBD5330E76B404F0D966C /* Main.cpp */ = {isa = PBXBuildFile; fileRef = 77C0AC21C1028911123844FC; }; @@ -75,6 +76,7 @@ 9982F39121710EFFD5FEEAEF /* MasterComponent.h */ /* MasterComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MasterComponent.h; path = ../../Source/MasterComponent.h; sourceTree = SOURCE_ROOT; }; 9C67BD1915C7FD5747C2BA8F /* juce_audio_formats */ /* juce_audio_formats */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_formats; path = ../../../../modules/juce_audio_formats; sourceTree = SOURCE_ROOT; }; 9C689AFBF364CB167C422D29 /* juce_gui_extra */ /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = ../../../../modules/juce_gui_extra; sourceTree = SOURCE_ROOT; }; + 9D1E94FAB66D1FB2531A3F9F /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 9E8129263CD42C6029FC2CAD /* AudioToolbox.framework */ /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; A505E1DABB2ED630EFBA96DB /* juce_audio_processors */ /* juce_audio_processors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_processors; path = ../../../../modules/juce_audio_processors; sourceTree = SOURCE_ROOT; }; A7FF2B353C8568B5A7A80117 /* include_juce_graphics.mm */ /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; }; @@ -116,6 +118,7 @@ 77745BF98931B91341FE17F6, B323E5E5FBD5663B21A8E623, EC14DA30C090DDC62084DB4C, + 88DB5C5128E42AD59AD944D9, 8ECB0767EE340DD83869E37D, ); runOnlyForDeploymentPostprocessing = 0; @@ -159,6 +162,7 @@ F3292E3563DB7ABB076DB400, 996E743A20FC78671766BF59, 935CA85EF98714D3A17AE737, + 9D1E94FAB66D1FB2531A3F9F, EC794872987FEA2E129C589A, ); name = Frameworks; diff --git a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj index 68b0ed3a9c71..fe6398e49f65 100644 --- a/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj +++ b/extras/Projucer/Builds/MacOSX/Projucer.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 4E6DC4778D583C48C3CCD4DC /* jucer_ResourceFile.cpp */ = {isa = PBXBuildFile; fileRef = E13A54A6D3A1895EACE53E51; }; 4FAAB649E846BA2764C02ACE /* jucer_PaintElementImage.cpp */ = {isa = PBXBuildFile; fileRef = 72ED72174F9DBD0ABD8AFCED; }; 537ABF1DB09DDBD1542A2B0C /* jucer_ComponentLayoutEditor.cpp */ = {isa = PBXBuildFile; fileRef = EF25A29A2194FC107B40F65F; }; + 54C391DB9AC4AFABD2EFFD7E /* Security.framework */ = {isa = PBXBuildFile; fileRef = 4E410AEB132B44674291105A; }; 5DD883699B85E4C492CAD065 /* include_juce_core.mm */ = {isa = PBXBuildFile; fileRef = DB9C8E35DF815B803CB4A9CF; }; 638C7247B6DBA67EFE46E124 /* jucer_PIPGenerator.cpp */ = {isa = PBXBuildFile; fileRef = 191330B20DAC08B890656EA0; }; 63D97E01F2C4C91037CB65BD /* jucer_TestComponent.cpp */ = {isa = PBXBuildFile; fileRef = 24EB4C2412821B8019D6F754; }; @@ -167,6 +168,7 @@ 4AE72953E3B3DF06D3B9BA86 /* jucer_PaintElementPath.cpp */ /* jucer_PaintElementPath.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = jucer_PaintElementPath.cpp; path = ../../Source/ComponentEditor/PaintElements/jucer_PaintElementPath.cpp; sourceTree = SOURCE_ROOT; }; 4D5F0CA8D1273144681A1D48 /* jucer_JucerTreeViewBase.cpp */ /* jucer_JucerTreeViewBase.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = jucer_JucerTreeViewBase.cpp; path = ../../Source/Utility/UI/jucer_JucerTreeViewBase.cpp; sourceTree = SOURCE_ROOT; }; 4D698BF12BCD6B0896BCDF17 /* jucer_AutoUpdater.h */ /* jucer_AutoUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_AutoUpdater.h; path = ../../Source/Application/jucer_AutoUpdater.h; sourceTree = SOURCE_ROOT; }; + 4E410AEB132B44674291105A /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 4E671236FDBD5AD4699740C6 /* jucer_JucerCommandIDs.h */ /* jucer_JucerCommandIDs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_JucerCommandIDs.h; path = ../../Source/ComponentEditor/UI/jucer_JucerCommandIDs.h; sourceTree = SOURCE_ROOT; }; 4ECF029E3A69BF42FED1503D /* jucer_PIPAudioProcessorTemplate.h */ /* jucer_PIPAudioProcessorTemplate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = jucer_PIPAudioProcessorTemplate.h; path = ../../Source/BinaryData/Templates/jucer_PIPAudioProcessorTemplate.h; sourceTree = SOURCE_ROOT; }; 4F687965FBE86EAFDB3ACFEC /* BinaryData.h */ /* BinaryData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BinaryData.h; path = ../../JuceLibraryCode/BinaryData.h; sourceTree = SOURCE_ROOT; }; @@ -401,6 +403,7 @@ CDEF9FF2D119476D707305DF, 96EC6315E1B3F1A109F84BAF, 11D42F7EC6E6539D79A7F4B1, + 54C391DB9AC4AFABD2EFFD7E, B980464FA2761CCD64B1FAD6, ); runOnlyForDeploymentPostprocessing = 0; @@ -452,6 +455,7 @@ 431D30038CBF67F80E8B3A13, 9F01BA9942D038EA8B5289A8, E5D6C36496F5BC84D7213BE8, + 4E410AEB132B44674291105A, CF6C8BD0DA3D8CD4E99EBADA, ); name = Frameworks; diff --git a/extras/UnitTestRunner/Builds/MacOSX/UnitTestRunner.xcodeproj/project.pbxproj b/extras/UnitTestRunner/Builds/MacOSX/UnitTestRunner.xcodeproj/project.pbxproj index ba35702c1115..93d323747c9c 100644 --- a/extras/UnitTestRunner/Builds/MacOSX/UnitTestRunner.xcodeproj/project.pbxproj +++ b/extras/UnitTestRunner/Builds/MacOSX/UnitTestRunner.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 1B09834E81EAF5BCB87FAAF4 /* include_juce_product_unlocking.mm */ = {isa = PBXBuildFile; fileRef = B96EC82EC3D2813B50386198; }; 1D06F1A254F84A7AE3E90DF2 /* include_juce_opengl.mm */ = {isa = PBXBuildFile; fileRef = 1CA82C74AEC08421812BDCAC; }; 263250D6F359CE403B0566FF /* IOKit.framework */ = {isa = PBXBuildFile; fileRef = 8C449538B266A891147103D6; }; + 2AD7D2E1785FCABA09AE3764 /* Security.framework */ = {isa = PBXBuildFile; fileRef = 5CF6DD6C5477309A1E9AB644; }; 32010EA67EEFE024B9CE1CB5 /* DiscRecording.framework */ = {isa = PBXBuildFile; fileRef = B4202EE1243A8FCA29996D05; }; 33D24B475EA928745A87EDDB /* include_juce_audio_devices.mm */ = {isa = PBXBuildFile; fileRef = 00CDB93410EA5AECBA5ADA95; }; 3822F598DA7044E5DB7633A9 /* include_juce_audio_utils.mm */ = {isa = PBXBuildFile; fileRef = 846E187EC2E797B982861CA4; }; @@ -74,6 +75,7 @@ 4CA19EC18C2BC536B3636842 /* include_juce_dsp.mm */ /* include_juce_dsp.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_dsp.mm; path = ../../JuceLibraryCode/include_juce_dsp.mm; sourceTree = SOURCE_ROOT; }; 583EA0E5C4B75A629AEF1157 /* include_juce_gui_basics.mm */ /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; }; 5C7BDD8DF72F2FC2D44D757A /* RecentFilesMenuTemplate.nib */ /* RecentFilesMenuTemplate.nib */ = {isa = PBXFileReference; lastKnownFileType = file.nib; name = RecentFilesMenuTemplate.nib; path = RecentFilesMenuTemplate.nib; sourceTree = SOURCE_ROOT; }; + 5CF6DD6C5477309A1E9AB644 /* Security.framework */ /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 748F996DD2778AD1442AECA6 /* juce_product_unlocking */ /* juce_product_unlocking */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_product_unlocking; path = ../../../../modules/juce_product_unlocking; sourceTree = SOURCE_ROOT; }; 7898C73DCA6FA9D9CF669D32 /* AudioToolbox.framework */ /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 7C4E4601FFB642386AD27B07 /* juce_events */ /* juce_events */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_events; path = ../../../../modules/juce_events; sourceTree = SOURCE_ROOT; }; @@ -126,6 +128,7 @@ 263250D6F359CE403B0566FF, 17A09B4AF453B148CD7349F4, 1A038A2954FB9A4F208BE3F2, + 2AD7D2E1785FCABA09AE3764, 4BC57B0D2215621D90C8881C, ); runOnlyForDeploymentPostprocessing = 0; @@ -150,6 +153,7 @@ 8C449538B266A891147103D6, FCB76958E12B2D7F8277CD59, F260758DB97CF0F5C85218C1, + 5CF6DD6C5477309A1E9AB644, D2EBC6292AE5AFC46EB10DAC, ); name = Frameworks; From 070a6b35e93ca15deed8b059dfa0d491488f0519 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 18 Jan 2023 14:38:48 +0000 Subject: [PATCH 013/347] CMake: Avoid passing generator platform to jucaide build When building for arm64 with a VS generator on a x86_64 host, CMAKE_CROSSCOMPILING is not set, and copying the generator platform can result in a juceaide binary that doesn't run on the host system. The removed code no longer seems to be necessary when configuring with newer Clion versions. --- extras/Build/juceaide/CMakeLists.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/extras/Build/juceaide/CMakeLists.txt b/extras/Build/juceaide/CMakeLists.txt index 422d8d093eb8..549bd1c8d6bf 100644 --- a/extras/Build/juceaide/CMakeLists.txt +++ b/extras/Build/juceaide/CMakeLists.txt @@ -97,14 +97,6 @@ else() if(DEFINED ENV{PATH_ORIG}) set(ENV{PATH} "$ENV{PATH_ORIG}") endif() - else() - # When building with clang-cl in Clion on Windows for an x64 target, the ABI detection phase - # of the inner build can fail unless we pass through these flags too - set(extra_configure_flags - "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}" - "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}" - "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}" - "-DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}") endif() message(STATUS "Configuring juceaide") @@ -118,7 +110,6 @@ else() "-DCMAKE_BUILD_TYPE=Debug" "-DJUCE_BUILD_HELPER_TOOLS=ON" "-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}" - ${extra_configure_flags} WORKING_DIRECTORY "${JUCE_SOURCE_DIR}" OUTPUT_VARIABLE command_output ERROR_VARIABLE command_output From 273275cf07add0d4ace6d29c15d25eae29b71794 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 18 Jan 2023 18:47:04 +0000 Subject: [PATCH 014/347] Android: Allow recording demo to write files on recent Android versions --- examples/Assets/DemoUtilities.h | 17 +++++-- examples/Audio/AudioRecordingDemo.h | 49 +++++++------------ .../juce_android_RuntimePermissions.cpp | 6 ++- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/examples/Assets/DemoUtilities.h b/examples/Assets/DemoUtilities.h index ffdfc3631c93..b2d38fffb610 100644 --- a/examples/Assets/DemoUtilities.h +++ b/examples/Assets/DemoUtilities.h @@ -244,10 +244,8 @@ struct SlowerBouncingNumber : public BouncingNumber inline std::unique_ptr makeInputSource (const URL& url) { - #if JUCE_ANDROID - if (auto doc = AndroidDocument::fromDocument (url)) + if (const auto doc = AndroidDocument::fromDocument (url)) return std::make_unique (doc); - #endif #if ! JUCE_IOS if (url.isLocalFile()) @@ -257,4 +255,17 @@ inline std::unique_ptr makeInputSource (const URL& url) return std::make_unique (url); } +inline std::unique_ptr makeOutputStream (const URL& url) +{ + if (const auto doc = AndroidDocument::fromDocument (url)) + return doc.createOutputStream(); + + #if ! JUCE_IOS + if (url.isLocalFile()) + return url.getLocalFile().createOutputStream(); + #endif + + return url.createOutputStream(); +} + #endif // PIP_DEMO_UTILITIES_INCLUDED diff --git a/examples/Audio/AudioRecordingDemo.h b/examples/Audio/AudioRecordingDemo.h index 38bf15b7d7b8..8084383caa9e 100644 --- a/examples/Audio/AudioRecordingDemo.h +++ b/examples/Audio/AudioRecordingDemo.h @@ -305,17 +305,14 @@ class AudioRecordingDemo : public Component LiveScrollingAudioDisplay liveAudioScroller; RecordingThumbnail recordingThumbnail; - AudioRecorder recorder { recordingThumbnail.getAudioThumbnail() }; - - Label explanationLabel { {}, "This page demonstrates how to record a wave file from the live audio input..\n\n" - #if (JUCE_ANDROID || JUCE_IOS) - "After you are done with your recording you can share with other apps." - #else - "Pressing record will start recording a file in your \"Documents\" folder." - #endif - }; + AudioRecorder recorder { recordingThumbnail.getAudioThumbnail() }; + + Label explanationLabel { {}, + "This page demonstrates how to record a wave file from the live audio input.\n\n" + "After you are done with your recording you can choose where to save it." }; TextButton recordButton { "Record" }; File lastRecording; + FileChooser chooser { "Output file...", File::getCurrentWorkingDirectory().getChildFile ("recording.wav"), "*.wav" }; void startRecording() { @@ -350,28 +347,18 @@ class AudioRecordingDemo : public Component { recorder.stop(); - #if JUCE_CONTENT_SHARING - SafePointer safeThis (this); - File fileToShare = lastRecording; - - ContentSharer::getInstance()->shareFiles (Array ({URL (fileToShare)}), - [safeThis, fileToShare] (bool success, const String& error) - { - if (fileToShare.existsAsFile()) - fileToShare.deleteFile(); - - if (! success && error.isNotEmpty()) - NativeMessageBox::showAsync (MessageBoxOptions() - .withIconType (MessageBoxIconType::WarningIcon) - .withTitle ("Sharing Error") - .withMessage (error), - nullptr); - }); - #endif - - lastRecording = File(); - recordButton.setButtonText ("Record"); - recordingThumbnail.setDisplayFullThumbnail (true); + chooser.launchAsync ( FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting, + [this] (const FileChooser& c) + { + if (FileInputStream inputStream (lastRecording); inputStream.openedOk()) + if (const auto outputStream = makeOutputStream (c.getURLResult())) + outputStream->writeFromInputStream (inputStream, -1); + + recordButton.setButtonText ("Record"); + recordingThumbnail.setDisplayFullThumbnail (true); + }); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioRecordingDemo) diff --git a/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/modules/juce_core/native/juce_android_RuntimePermissions.cpp index 285b92c1e166..45a30233e775 100644 --- a/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ b/modules/juce_core/native/juce_android_RuntimePermissions.cpp @@ -43,7 +43,11 @@ static StringArray jucePermissionToAndroidPermissions (RuntimePermissions::Permi "android.permission.BLUETOOTH_CONNECT" }; } - case RuntimePermissions::writeExternalStorage: return { "android.permission.WRITE_EXTERNAL_STORAGE" }; + // WRITE_EXTERNAL_STORAGE has no effect on SDK 29+ + case RuntimePermissions::writeExternalStorage: + return getAndroidSDKVersion() < 29 ? StringArray { "android.permission.WRITE_EXTERNAL_STORAGE" } + : StringArray{}; + case RuntimePermissions::camera: return { "android.permission.CAMERA" }; case RuntimePermissions::readExternalStorage: From c56102f50a88fdb3031bad8cf7806dd8c33436e5 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 18 Jan 2023 20:21:55 +0000 Subject: [PATCH 015/347] AudioFormatReaderSource: Avoid reading past the end of the wrapped AudioFormatReader --- .../format/juce_AudioFormatReaderSource.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp b/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp index 7c47f86a9caf..96f2d5ba4de6 100644 --- a/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp +++ b/modules/juce_audio_formats/format/juce_AudioFormatReaderSource.cpp @@ -81,8 +81,14 @@ void AudioFormatReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& i } else { - reader->read (info.buffer, info.startSample, - info.numSamples, start, true, true); + const auto samplesToRead = jlimit (int64{}, + (int64) info.numSamples, + reader->lengthInSamples - start); + + reader->read (info.buffer, info.startSample, (int) samplesToRead, start, true, true); + info.buffer->clear ((int) (info.startSample + samplesToRead), + (int) (info.numSamples - samplesToRead)); + nextPlayPos += info.numSamples; } } From eaa6dfc3ee31de52fa5d85a9f78d535ddf6a87ea Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 19 Jan 2023 13:14:04 +0000 Subject: [PATCH 016/347] AudioProcessorGraph: Allow triggering manual rebuild, and avoid rebuilding if nothing has changed --- .../processors/juce_AudioProcessorGraph.cpp | 67 ++++++++++++++++--- .../processors/juce_AudioProcessorGraph.h | 15 ++++- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 5e615433abcd..58913a171361 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -351,14 +351,14 @@ class NodeStates /* Called from prepareToPlay and releaseResources with the PrepareSettings that should be used next time the graph is rebuilt. */ - void setState (Optional newSettings) + void setState (std::optional newSettings) { const std::lock_guard lock (mutex); next = newSettings; } /* Call from the audio thread only. */ - Optional getLastRequestedSettings() const { return next; } + std::optional getLastRequestedSettings() const { return next; } /* Call from the main thread only! @@ -375,7 +375,7 @@ class NodeStates Returns the settings that were applied to the nodes. */ - Optional applySettings (const Nodes& n) + std::optional applySettings (const Nodes& n) { const auto settingsChanged = [this] { @@ -411,7 +411,7 @@ class NodeStates preparedNodes.clear(); } - if (current.hasValue()) + if (current.has_value()) { for (const auto& node : n.getNodes()) { @@ -433,7 +433,7 @@ class NodeStates private: std::mutex mutex; std::set preparedNodes; - Optional current, next; + std::optional current, next; }; //============================================================================== @@ -1447,6 +1447,40 @@ class RenderSequence int latencySamples = 0; }; +//============================================================================== +/** Holds information about a particular graph configuration, without sharing ownership of any + graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see + whether two graph configurations match. +*/ +class RenderSequenceSignature +{ + auto tie() const { return std::tie (settings, connections, nodes); } + +public: + RenderSequenceSignature (const PrepareSettings s, const Nodes& n, const Connections& c) + : settings (s), connections (c), nodes (getNodeIDs (n)) {} + + bool operator== (const RenderSequenceSignature& other) const { return tie() == other.tie(); } + bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); } + +private: + static std::vector getNodeIDs (const Nodes& n) + { + const auto& nodeRefs = n.getNodes(); + std::vector result; + result.reserve ((size_t) nodeRefs.size()); + + for (const auto& node : nodeRefs) + result.push_back (node->nodeID); + + return result; + } + + PrepareSettings settings; + Connections connections; + std::vector nodes; +}; + //============================================================================== /* Facilitates wait-free render-sequence updates. @@ -1687,6 +1721,14 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater topologyChanged (UpdateKind::sync); } + void rebuild() + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + } + void reset() { for (auto* n : getNodes()) @@ -1757,22 +1799,30 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater for (const auto node : nodes.getNodes()) setParentGraph (node->getProcessor()); - auto sequence = std::make_unique (*newSettings, nodes, connections); - owner->setLatencySamples (sequence->getLatencySamples()); - renderSequenceExchange.set (std::move (sequence)); + const RenderSequenceSignature newSignature (*newSettings, nodes, connections); + + if (std::exchange (lastBuiltSequence, newSignature) != newSignature) + { + auto sequence = std::make_unique (*newSettings, nodes, connections); + owner->setLatencySamples (sequence->getLatencySamples()); + renderSequenceExchange.set (std::move (sequence)); + } } else { + lastBuiltSequence.reset(); renderSequenceExchange.set (nullptr); } } + AudioProcessorGraph* owner = nullptr; Nodes nodes; Connections connections; NodeStates nodeStates; RenderSequenceExchange renderSequenceExchange; NodeID lastNodeID; + std::optional lastBuiltSequence; }; //============================================================================== @@ -1799,6 +1849,7 @@ AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID x) const bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind) { return pimpl->disconnectNode (nodeID, updateKind); } void AudioProcessorGraph::releaseResources() { return pimpl->releaseResources(); } bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind) { return pimpl->removeIllegalConnections (updateKind); } +void AudioProcessorGraph::rebuild() { return pimpl->rebuild(); } void AudioProcessorGraph::reset() { return pimpl->reset(); } bool AudioProcessorGraph::canConnect (const Connection& c) const { return pimpl->canConnect (c); } bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { return pimpl->isConnected (c); } diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 8190d754d4d0..2234d2c5ae95 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -210,8 +210,11 @@ class JUCE_API AudioProcessorGraph : public AudioProcessor, */ enum class UpdateKind { - sync, ///< Indicates that the graph should be rebuilt immediately after modification. - async ///< Indicates that the graph rebuild should be deferred. + sync, ///< Graph should be rebuilt immediately after modification. + async, ///< Graph rebuild should be delayed. If you make several changes to the graph + ///< inside the same call stack, these changes will be applied in one go. + none ///< Graph should not be rebuilt automatically. Use rebuild() to trigger a graph + ///< rebuild. }; //============================================================================== @@ -312,6 +315,14 @@ class JUCE_API AudioProcessorGraph : public AudioProcessor, */ bool removeIllegalConnections (UpdateKind = UpdateKind::sync); + /** Rebuilds the graph if necessary. + + This function will only ever rebuild the graph on the main thread. If this function is + called from another thread, the rebuild request will be dispatched asynchronously to the + main thread. + */ + void rebuild(); + //============================================================================== /** A special type of AudioProcessor that can live inside an AudioProcessorGraph in order to use the audio that comes into and out of the graph itself. From 4211a2a0fd221f10f4b309729ac041ee7e4f2212 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 19 Jan 2023 16:06:29 +0000 Subject: [PATCH 017/347] AudioProcessorGraph: Only prepare a render sequence for the current processing precision --- .../processors/juce_AudioProcessorGraph.cpp | 89 +++++++++---------- .../juce_core/system/juce_StandardHeader.h | 1 + 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 58913a171361..3e1828a40a61 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -835,6 +835,16 @@ struct GraphRenderSequence std::vector> renderOps; }; +//============================================================================== +struct SequenceAndLatency +{ + using RenderSequenceVariant = std::variant, + GraphRenderSequence>; + + RenderSequenceVariant sequence; + int latencySamples = 0; +}; + //============================================================================== class RenderSequenceBuilder { @@ -846,19 +856,12 @@ class RenderSequenceBuilder static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex; - template - static auto build (const Nodes& n, const Connections& c) + template + static SequenceAndLatency build (const Nodes& n, const Connections& c) { - RenderSequence sequence; + GraphRenderSequence sequence; const RenderSequenceBuilder builder (n, c, sequence); - - struct SequenceAndLatency - { - RenderSequence sequence; - int latencySamples = 0; - }; - - return SequenceAndLatency { std::move (sequence), builder.totalLatency }; + return { std::move (sequence), builder.totalLatency }; } private: @@ -1307,8 +1310,7 @@ class RenderSequenceBuilder if (output.isMIDI()) { if (inputChannelOfIndexToIgnore != midiChannelIndex - && c.isConnected ({ { output.nodeID, midiChannelIndex }, - { node->nodeID, midiChannelIndex } })) + && c.isConnected ({ output, { node->nodeID, midiChannelIndex } })) return true; } else @@ -1356,37 +1358,49 @@ class RenderSequence public: using AudioGraphIOProcessor = AudioProcessorGraph::AudioGraphIOProcessor; - RenderSequence (PrepareSettings s, const Nodes& n, const Connections& c) - : RenderSequence (s, - RenderSequenceBuilder::build> (n, c), - RenderSequenceBuilder::build> (n, c)) + RenderSequence (const PrepareSettings s, const Nodes& n, const Connections& c) + : RenderSequence (s, s.precision == AudioProcessor::ProcessingPrecision::singlePrecision + ? RenderSequenceBuilder::build (n, c) + : RenderSequenceBuilder::build (n, c)) { } - void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) + template + void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) { - renderSequenceF.perform (audio, midi, playHead); + if (auto* s = std::get_if> (&sequence.sequence)) + s->perform (audio, midi, playHead); + else + jassertfalse; // Not prepared for this audio format! } - void process (AudioBuffer& audio, MidiBuffer& midi, AudioPlayHead* playHead) + template + void processIO (AudioGraphIOProcessor& io, AudioBuffer& audio, MidiBuffer& midi) { - renderSequenceD.perform (audio, midi, playHead); + if (auto* s = std::get_if> (&sequence.sequence)) + processIOBlock (io, *s, audio, midi); + else + jassertfalse; // Not prepared for this audio format! } - void processIO (AudioGraphIOProcessor& io, AudioBuffer& audio, MidiBuffer& midi) + int getLatencySamples() const { return sequence.latencySamples; } + PrepareSettings getSettings() const { return settings; } + +private: + template + static decltype (auto) visitRenderSequence (This& t, Callback&& callback) { - processIOBlock (io, renderSequenceF, audio, midi); + if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); + if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); + jassertfalse; } - void processIO (AudioGraphIOProcessor& io, AudioBuffer& audio, MidiBuffer& midi) + RenderSequence (const PrepareSettings s, SequenceAndLatency&& built) + : settings (s), sequence (std::move (built)) { - processIOBlock (io, renderSequenceD, audio, midi); + visitRenderSequence (*this, [&] (auto& seq) { seq.prepareBuffers (settings.blockSize); }); } - int getLatencySamples() const { return latencySamples; } - PrepareSettings getSettings() const { return settings; } - -private: template static void processIOBlock (AudioGraphIOProcessor& io, SequenceType& sequence, @@ -1428,23 +1442,8 @@ class RenderSequence } } - template - RenderSequence (PrepareSettings s, Float f, Double d) - : settings (s), - renderSequenceF (std::move (f.sequence)), - renderSequenceD (std::move (d.sequence)), - latencySamples (f.latencySamples) - { - jassert (f.latencySamples == d.latencySamples); - - renderSequenceF.prepareBuffers (settings.blockSize); - renderSequenceD.prepareBuffers (settings.blockSize); - } - PrepareSettings settings; - GraphRenderSequence renderSequenceF; - GraphRenderSequence renderSequenceD; - int latencySamples = 0; + SequenceAndLatency sequence; }; //============================================================================== diff --git a/modules/juce_core/system/juce_StandardHeader.h b/modules/juce_core/system/juce_StandardHeader.h index 7a0714349caa..79170755c3ad 100644 --- a/modules/juce_core/system/juce_StandardHeader.h +++ b/modules/juce_core/system/juce_StandardHeader.h @@ -72,6 +72,7 @@ #include #include #include +#include #include //============================================================================== From cd6939c073dc07090d24a9e646fec925dcae2768 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 19 Jan 2023 18:37:18 +0000 Subject: [PATCH 018/347] AudioProcessorGraph: Improve lookup speed in isBufferNeedLater --- .../processors/juce_AudioProcessorGraph.cpp | 142 ++++++++++++------ 1 file changed, 100 insertions(+), 42 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 3e1828a40a61..f1f6ab745d7c 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -139,6 +139,15 @@ class Connections using Connection = AudioProcessorGraph::Connection; using NodeAndChannel = AudioProcessorGraph::NodeAndChannel; +private: + static auto equalRange (const std::set& pins, const NodeID node) + { + return std::equal_range (pins.cbegin(), pins.cend(), node, ImplicitNode::compare); + } + + using Map = std::map>; + +public: static constexpr auto midiChannelIndex = AudioProcessorGraph::midiChannelIndex; bool addConnection (const Nodes& n, const Connection& c) @@ -179,7 +188,7 @@ class Connections for (auto& pair : sourcesForDestination) { - const auto range = std::equal_range (pair.second.cbegin(), pair.second.cend(), n, ImplicitNode::compare); + const auto range = equalRange (pair.second, n); result |= range.first != range.second; pair.second.erase (range.first, range.second); } @@ -231,8 +240,8 @@ class Connections return std::any_of (matchingDestinations.first, matchingDestinations.second, [srcID] (const auto& pair) { - const auto iter = std::lower_bound (pair.second.cbegin(), pair.second.cend(), srcID, ImplicitNode::compare); - return iter != pair.second.cend() && iter->nodeID == srcID; + const auto [begin, end] = equalRange (pair.second, srcID); + return begin != end; }); } @@ -276,9 +285,44 @@ class Connections bool operator== (const Connections& other) const { return sourcesForDestination == other.sourcesForDestination; } bool operator!= (const Connections& other) const { return sourcesForDestination != other.sourcesForDestination; } -private: - using Map = std::map>; + class DestinationsForSources + { + public: + explicit DestinationsForSources (Map m) : map (std::move (m)) {} + + bool isSourceConnectedToDestinationNodeIgnoringChannel (const NodeAndChannel& source, NodeID dest, int channel) const + { + if (const auto destIter = map.find (source); destIter != map.cend()) + { + const auto [begin, end] = equalRange (destIter->second, dest); + return std::any_of (begin, end, [&] (const NodeAndChannel& nodeAndChannel) + { + return nodeAndChannel != NodeAndChannel { dest, channel }; + }); + } + + return false; + } + + private: + Map map; + }; + + /* Reverses the graph, to allow fast lookup by source. + This is expensive, don't call this more than necessary! + */ + auto getDestinationsForSources() const + { + Map destinationsForSources; + + for (const auto& [destination, sources] : sourcesForDestination) + for (const auto& source : sources) + destinationsForSources[source].insert (destination); + return DestinationsForSources (std::move (destinationsForSources)); + } + +private: struct SearchState { std::set visited; @@ -430,6 +474,20 @@ class NodeStates return current; } + /* Call from the main thread to indicate that a node has been removed from the graph. + */ + void removeNode (const NodeID n) + { + preparedNodes.erase (n); + } + + /* Call from the main thread to indicate that all nodes have been removed from the graph. + */ + void clear() + { + preparedNodes.clear(); + } + private: std::mutex mutex; std::set preparedNodes; @@ -964,6 +1022,7 @@ class RenderSequenceBuilder //============================================================================== template int findBufferForInputAudioChannel (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, const int inputChan, @@ -1001,7 +1060,7 @@ class RenderSequenceBuilder jassert (bufIndex >= 0); } - if (inputChan < numOuts && isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (inputChan < numOuts && isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { // can't mess up this channel because it's needed later by another node, // so we need to use a copy of it.. @@ -1028,7 +1087,7 @@ class RenderSequenceBuilder { auto sourceBufIndex = getBufferContaining (src); - if (sourceBufIndex >= 0 && ! isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (sourceBufIndex >= 0 && ! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { // we've found one of our input chans that can be re-used.. reusableInputIndex = i; @@ -1082,7 +1141,7 @@ class RenderSequenceBuilder if (nodeDelay < maxLatency) { - if (! isBufferNeededLater (c, ourRenderingIndex, inputChan, src)) + if (! isBufferNeededLater (reversed, ourRenderingIndex, inputChan, src)) { sequence.addDelayChannelOp (srcIndex, maxLatency - nodeDelay); } @@ -1108,6 +1167,7 @@ class RenderSequenceBuilder template int findBufferForInputMidiChannel (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, int ourRenderingIndex) @@ -1134,7 +1194,7 @@ class RenderSequenceBuilder if (midiBufferToUse >= 0) { - if (isBufferNeededLater (c, ourRenderingIndex, midiChannelIndex, src)) + if (isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src)) { // can't mess up this channel because it's needed later by another node, so we // need to use a copy of it.. @@ -1163,7 +1223,7 @@ class RenderSequenceBuilder auto sourceBufIndex = getBufferContaining (src); if (sourceBufIndex >= 0 - && ! isBufferNeededLater (c, ourRenderingIndex, midiChannelIndex, src)) + && ! isBufferNeededLater (reversed, ourRenderingIndex, midiChannelIndex, src)) { // we've found one of our input buffers that can be re-used.. reusableInputIndex = i; @@ -1212,6 +1272,7 @@ class RenderSequenceBuilder template void createRenderingOpsForNode (const Connections& c, + const Connections::DestinationsForSources& reversed, RenderSequence& sequence, Node& node, const int ourRenderingIndex) @@ -1228,6 +1289,7 @@ class RenderSequenceBuilder { // get a list of all the inputs to this node auto index = findBufferForInputAudioChannel (c, + reversed, sequence, node, inputChan, @@ -1250,7 +1312,7 @@ class RenderSequenceBuilder audioBuffers.getReference (index).channel = { node.nodeID, outputChan }; } - auto midiBufferToUse = findBufferForInputMidiChannel (c, sequence, node, ourRenderingIndex); + auto midiBufferToUse = findBufferForInputMidiChannel (c, reversed, sequence, node, ourRenderingIndex); if (processor.producesMidi()) midiBuffers.getReference (midiBufferToUse).channel = { node.nodeID, midiChannelIndex }; @@ -1289,7 +1351,7 @@ class RenderSequenceBuilder return -1; } - void markAnyUnusedBuffersAsFree (const Connections& c, + void markAnyUnusedBuffersAsFree (const Connections::DestinationsForSources& c, Array& buffers, const int stepIndex) { @@ -1298,33 +1360,25 @@ class RenderSequenceBuilder b.setFree(); } - bool isBufferNeededLater (const Connections& c, - int stepIndexToSearchFrom, - int inputChannelOfIndexToIgnore, - NodeAndChannel output) const + bool isBufferNeededLater (const Connections::DestinationsForSources& c, + const int stepIndexToSearchFrom, + const int inputChannelOfIndexToIgnore, + const NodeAndChannel output) const { - while (stepIndexToSearchFrom < orderedNodes.size()) - { - auto* node = orderedNodes.getUnchecked (stepIndexToSearchFrom); - - if (output.isMIDI()) - { - if (inputChannelOfIndexToIgnore != midiChannelIndex - && c.isConnected ({ output, { node->nodeID, midiChannelIndex } })) - return true; - } - else - { - for (int i = 0; i < node->getProcessor()->getTotalNumInputChannels(); ++i) - if (i != inputChannelOfIndexToIgnore && c.isConnected ({ output, { node->nodeID, i } })) - return true; - } + if (orderedNodes.size() <= stepIndexToSearchFrom) + return false; - inputChannelOfIndexToIgnore = -1; - ++stepIndexToSearchFrom; + if (c.isSourceConnectedToDestinationNodeIgnoringChannel (output, + orderedNodes.getUnchecked (stepIndexToSearchFrom)->nodeID, + inputChannelOfIndexToIgnore)) + { + return true; } - return false; + return std::any_of (orderedNodes.begin() + stepIndexToSearchFrom + 1, orderedNodes.end(), [&] (const auto* node) + { + return c.isSourceConnectedToDestinationNodeIgnoringChannel (output, node->nodeID, -1); + }); } template @@ -1334,11 +1388,13 @@ class RenderSequenceBuilder audioBuffers.add (AssignedBuffer::createReadOnlyEmpty()); // first buffer is read-only zeros midiBuffers .add (AssignedBuffer::createReadOnlyEmpty()); + const auto reversed = c.getDestinationsForSources(); + for (int i = 0; i < orderedNodes.size(); ++i) { - createRenderingOpsForNode (c, sequence, *orderedNodes.getUnchecked (i), i); - markAnyUnusedBuffersAsFree (c, audioBuffers, i); - markAnyUnusedBuffersAsFree (c, midiBuffers, i); + createRenderingOpsForNode (c, reversed, sequence, *orderedNodes.getUnchecked (i), i); + markAnyUnusedBuffersAsFree (reversed, audioBuffers, i); + markAnyUnusedBuffersAsFree (reversed, midiBuffers, i); } sequence.numBuffersNeeded = audioBuffers.size(); @@ -1388,7 +1444,7 @@ class RenderSequence private: template - static decltype (auto) visitRenderSequence (This& t, Callback&& callback) + static void visitRenderSequence (This& t, Callback&& callback) { if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); if (auto* sequence = std::get_if> (&t.sequence.sequence)) return callback (*sequence); @@ -1447,7 +1503,7 @@ class RenderSequence }; //============================================================================== -/** Holds information about a particular graph configuration, without sharing ownership of any +/* Holds information about a particular graph configuration, without sharing ownership of any graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see whether two graph configurations match. */ @@ -1508,7 +1564,7 @@ class RenderSequenceExchange : private Timer isNew = true; } - /** Call from the audio thread only. */ + /* Call from the audio thread only. */ void updateAudioThreadState() { const SpinLock::ScopedTryLockType lock (mutex); @@ -1521,7 +1577,7 @@ class RenderSequenceExchange : private Timer } } - /** Call from the audio thread only. */ + /* Call from the audio thread only. */ RenderSequence* getAudioThreadState() const { return audioThreadState.get(); } private: @@ -1587,6 +1643,7 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater nodes = Nodes{}; connections = Connections{}; + nodeStates.clear(); topologyChanged (updateKind); } @@ -1625,6 +1682,7 @@ class AudioProcessorGraph::Pimpl : public AsyncUpdater { connections.disconnectNode (nodeID); auto result = nodes.removeNode (nodeID); + nodeStates.removeNode (nodeID); topologyChanged (updateKind); return result; } From 621e5e3de44ae1c5aa0100411dd3b16b20f70672 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 25 Jan 2023 17:09:46 +0000 Subject: [PATCH 019/347] AudioProcessorGraph: Add basic benchmark test --- .../processors/juce_AudioProcessorGraph.cpp | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index f1f6ab745d7c..8c20df3722e9 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -2166,6 +2166,36 @@ class AudioProcessorGraphTests : public UnitTest expect (graph.isAnInputTo (*nodes[nodes.size() - 1], *node)); } } + + beginTest ("large render sequence can be built"); + { + AudioProcessorGraph graph; + + std::vector nodeIDs; + + constexpr auto numNodes = 1000; + constexpr auto numChannels = 100; + + for (auto i = 0; i < numNodes; ++i) + { + nodeIDs.push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getMultichannelProperties (numChannels), + MidiIn::yes, + MidiOut::yes))->nodeID); + } + + for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it) + for (auto channel = 0; channel < numChannels; ++channel) + expect (graph.addConnection ({ { it[0], channel }, { it[1], channel } })); + + const auto b = std::chrono::steady_clock::now(); + graph.prepareToPlay (44100.0, 512); + const auto e = std::chrono::steady_clock::now(); + const auto duration = std::chrono::duration_cast (e - b).count(); + + // No test here, but older versions of the graph would take forever to complete building + // this graph, so we just want to make sure that we finish the test without timing out. + logMessage ("render sequence built in " + String (duration) + " ms"); + } } private: @@ -2210,10 +2240,16 @@ class AudioProcessorGraphTests : public UnitTest static BusesProperties getStereoProperties() { - return BusesProperties().withInput ("in", AudioChannelSet::stereo()) + return BusesProperties().withInput ("in", AudioChannelSet::stereo()) .withOutput ("out", AudioChannelSet::stereo()); } + static BusesProperties getMultichannelProperties (int numChannels) + { + return BusesProperties().withInput ("in", AudioChannelSet::discreteChannels (numChannels)) + .withOutput ("out", AudioChannelSet::discreteChannels (numChannels)); + } + private: MidiIn midiIn; MidiOut midiOut; From db90a31813f50b2d8bb65f12e80e95176f55805b Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 19 Jan 2023 20:00:22 +0000 Subject: [PATCH 020/347] TreeView: Fix getAllVisibleItems() when only the final row of the tree fits in the viewport Fixes #1118 --- modules/juce_gui_basics/widgets/juce_TreeView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/modules/juce_gui_basics/widgets/juce_TreeView.cpp index 75d261787b14..51c80dd9551d 100644 --- a/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -647,7 +647,7 @@ class TreeView::ContentComponent : public Component, auto* i = owner.rootItemVisible ? owner.rootItem : owner.rootItem->subItems.getFirst(); - while (i != nullptr && i->y < visibleTop) + while (i != nullptr && i->y + i->getItemHeight() < visibleTop) i = getNextVisibleItem (i, true); return i; From 10baaa420be1cb525cd04da0ccbad80b3b8b9ea1 Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 25 Jan 2023 13:14:31 +0000 Subject: [PATCH 021/347] CMake: Warn if bundle ID contains spaces --- docs/CMake API.md | 2 +- extras/Build/CMake/JUCEUtils.cmake | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CMake API.md b/docs/CMake API.md index 059c73ab7db7..dbf16b56d6d0 100644 --- a/docs/CMake API.md +++ b/docs/CMake API.md @@ -266,7 +266,7 @@ attributes directly to these creation functions, rather than adding them later. `BUNDLE_ID` - An identifier string in the form "com.yourcompany.productname" which should uniquely identify this target. Mainly used for macOS builds. If not specified, a default will be generated using - the target's `COMPANY_NAME` and `PRODUCT_NAME`. + the target's `COMPANY_NAME` and the name of the CMake target. `MICROPHONE_PERMISSION_ENABLED` - May be either TRUE or FALSE. Adds the appropriate entries to an app's Info.plist. diff --git a/extras/Build/CMake/JUCEUtils.cmake b/extras/Build/CMake/JUCEUtils.cmake index 90f59fbf9077..b86e0b454795 100644 --- a/extras/Build/CMake/JUCEUtils.cmake +++ b/extras/Build/CMake/JUCEUtils.cmake @@ -1297,6 +1297,12 @@ function(_juce_set_fallback_properties target) get_target_property(real_company_name ${target} JUCE_COMPANY_NAME) _juce_set_property_if_not_set(${target} BUNDLE_ID "com.${real_company_name}.${target}") + get_target_property(applied_bundle_id ${target} JUCE_BUNDLE_ID) + + if("${applied_bundle_id}" MATCHES ".* .*") + message(WARNING "Target ${target} has JUCE_BUNDLE_ID '${applied_bundle_id}', which contains spaces. Use the BUNDLE_ID option to specify a valid ID.") + endif() + _juce_set_property_if_not_set(${target} VERSION ${PROJECT_VERSION}) get_target_property(final_version ${target} JUCE_VERSION) From f7a28a6d624553f72d2c20811215f2937fc075e1 Mon Sep 17 00:00:00 2001 From: "martin@ncore.li" Date: Thu, 26 Jan 2023 15:35:39 +0100 Subject: [PATCH 022/347] VST3: Fix bad value returned from setBusArrangements() (false == kResultTrue) when in/out bus counts are mismatching --- modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d809ae37e6d2..7e0d400e4a12 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -3285,7 +3285,7 @@ class JuceVST3Component : public Vst::IComponent, auto numOutputBuses = pluginInstance->getBusCount (false); if (numIns > numInputBuses || numOuts > numOutputBuses) - return false; + return kResultFalse; // see the following documentation to understand the correct way to react to this callback // https://steinbergmedia.github.io/vst3_doc/vstinterfaces/classSteinberg_1_1Vst_1_1IAudioProcessor.html#ad3bc7bac3fd3b194122669be2a1ecc42 From 39ba1c838307d66f0c99598d2b006b16fecc587c Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 26 Jan 2023 17:45:32 +0000 Subject: [PATCH 023/347] VST3: Remove unhelpful assertion Some hosts (e.g. REAPER) will attempt to instantiate plug-ins with 64 active channels, and JUCE can't represent this layout. In this case, failing to convert between VST3/JUCE layouts is not a logical error, so asserting is not appropriate. --- modules/juce_audio_processors/format_types/juce_VST3Common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index eb204b34f5ba..fae1f06a465e 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -485,7 +485,6 @@ inline std::optional getChannelSetForSpeakerArrangement (Steinb return AudioChannelSet::channelSetWithChannels (*order); // VST3 <-> JUCE layout conversion error: report this bug to the JUCE forum - jassertfalse; return {}; } From b9cc72b167443333cd549a54c0b8a4b682c7b068 Mon Sep 17 00:00:00 2001 From: Oli James Date: Thu, 26 Jan 2023 21:48:05 +0000 Subject: [PATCH 024/347] LinuxWebComponent: Fix high CPU usage --- .../native/juce_linux_X11_WebBrowserComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp index 6acaada9c391..3e0f88168166 100644 --- a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp @@ -829,7 +829,7 @@ class WebBrowserComponent::Pimpl : private Thread, int result = 0; while (result == 0 || (result < 0 && errno == EINTR)) - result = poll (&pfds.front(), static_cast (pfds.size()), 0); + result = poll (&pfds.front(), static_cast (pfds.size()), 10); if (result < 0) break; From ff534baf5f5d83e69cb7e5f4b8acfcbcbfdaaec6 Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 27 Jan 2023 22:29:08 +0000 Subject: [PATCH 025/347] GenericAudioProcessorEditor: Fix issue where the ChoiceParameterComponent would re-set the parameter value in response to a parameter value change --- .../processors/juce_GenericAudioProcessorEditor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index d153c3d28912..d67be5b857a1 100644 --- a/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -293,7 +293,7 @@ class ChoiceParameterComponent : public ParameterComponent index = roundToInt (getParameter().getValue() * (float) (parameterValues.size() - 1)); } - box.setSelectedItemIndex (index); + box.setSelectedItemIndex (index, dontSendNotification); } void boxChanged() From a28597c3c9c87ee95ab4a16bc68b58887d961ecf Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Tue, 31 Jan 2023 12:53:50 +0000 Subject: [PATCH 026/347] Fix GCC 12 compiler warnings --- modules/juce_box2d/box2d/Common/b2Math.h | 2 +- modules/juce_core/containers/juce_ListenerList.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/juce_box2d/box2d/Common/b2Math.h b/modules/juce_box2d/box2d/Common/b2Math.h index e42428e6ef5c..f8415fe7591a 100644 --- a/modules/juce_box2d/box2d/Common/b2Math.h +++ b/modules/juce_box2d/box2d/Common/b2Math.h @@ -143,7 +143,7 @@ struct b2Vec2 return b2Vec2(-y, x); } - float32 x, y; + float32 x{}, y{}; }; /// A 2D column vector with 3 elements. diff --git a/modules/juce_core/containers/juce_ListenerList.h b/modules/juce_core/containers/juce_ListenerList.h index 595e37793dde..5f657a0757de 100644 --- a/modules/juce_core/containers/juce_ListenerList.h +++ b/modules/juce_core/containers/juce_ListenerList.h @@ -325,7 +325,10 @@ class ListenerList WrappedIterator (const ListenerList& listToIterate, WrappedIterator*& listHeadIn) : it (listToIterate), listHead (listHeadIn), next (listHead) { + // GCC 12.2 with O1 and above gets confused here + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdangling-pointer") listHead = this; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE } ~WrappedIterator() From 543b001e9cfe85f981d1266877a10bbe7d806a1e Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 31 Jan 2023 13:00:14 +0000 Subject: [PATCH 027/347] FileSearchPath: Allow working with paths that are not necessarily absolute This allows paths that are prefixed with environment variables to behave as expected. This is useful when scanning the default LV2 locations in the AudioPluginHost on Windows. --- .../scanning/juce_PluginListComponent.cpp | 6 +- .../juce_core/files/juce_FileSearchPath.cpp | 86 ++++++++++++++++--- modules/juce_core/files/juce_FileSearchPath.h | 13 ++- .../juce_FileSearchPathListComponent.cpp | 6 +- 4 files changed, 93 insertions(+), 18 deletions(-) diff --git a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp index 6af4db764055..008c74753b19 100644 --- a/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp +++ b/modules/juce_audio_processors/scanning/juce_PluginListComponent.cpp @@ -472,9 +472,9 @@ class PluginListComponent::Scanner : private Timer { for (int i = 0; i < pathList.getPath().getNumPaths(); ++i) { - auto f = pathList.getPath()[i]; + auto f = pathList.getPath().getRawString (i); - if (isStupidPath (f)) + if (File::isAbsolutePath (f) && isStupidPath (File (f))) { AlertWindow::showOkCancelBox (MessageBoxIconType::WarningIcon, TRANS("Plugin Scanning"), @@ -483,7 +483,7 @@ class PluginListComponent::Scanner : private Timer "attempting to load unsuitable files.") + newLine + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") - .replace ("XYZ", f.getFullPathName()), + .replace ("XYZ", f), TRANS ("Scan"), String(), nullptr, diff --git a/modules/juce_core/files/juce_FileSearchPath.cpp b/modules/juce_core/files/juce_FileSearchPath.cpp index 6b4ad3981bfc..9b786ac05e8e 100644 --- a/modules/juce_core/files/juce_FileSearchPath.cpp +++ b/modules/juce_core/files/juce_FileSearchPath.cpp @@ -63,7 +63,12 @@ int FileSearchPath::getNumPaths() const File FileSearchPath::operator[] (int index) const { - return File (directories[index]); + return File (getRawString (index)); +} + +String FileSearchPath::getRawString (int index) const +{ + return directories[index]; } String FileSearchPath::toString() const @@ -110,21 +115,30 @@ void FileSearchPath::addPath (const FileSearchPath& other) void FileSearchPath::removeRedundantPaths() { - for (int i = directories.size(); --i >= 0;) + std::vector reduced; + + for (const auto& directory : directories) { - const File d1 (directories[i]); + const auto checkedIsChildOf = [&] (const auto& a, const auto& b) + { + return File::isAbsolutePath (a) && File::isAbsolutePath (b) && File (a).isAChildOf (b); + }; - for (int j = directories.size(); --j >= 0;) + const auto fContainsDirectory = [&] (const auto& f) { - const File d2 (directories[j]); + return f == directory || checkedIsChildOf (directory, f); + }; - if (i != j && (d1.isAChildOf (d2) || d1 == d2)) - { - directories.remove (i); - break; - } - } + if (std::find_if (reduced.begin(), reduced.end(), fContainsDirectory) != reduced.end()) + continue; + + const auto directoryContainsF = [&] (const auto& f) { return checkedIsChildOf (f, directory); }; + + reduced.erase (std::remove_if (reduced.begin(), reduced.end(), directoryContainsF), reduced.end()); + reduced.push_back (directory); } + + directories = StringArray (reduced.data(), (int) reduced.size()); } void FileSearchPath::removeNonExistentPaths() @@ -172,4 +186,54 @@ bool FileSearchPath::isFileInPath (const File& fileToCheck, return false; } +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class FileSearchPathTests : public UnitTest +{ +public: + FileSearchPathTests() : UnitTest ("FileSearchPath", UnitTestCategories::files) {} + + void runTest() override + { + beginTest ("removeRedundantPaths"); + { + #if JUCE_WINDOWS + const String prefix = "C:"; + #else + const String prefix = ""; + #endif + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c/e;" + prefix + "/a/b/c" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c;" + prefix + "/a/b/c/d;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { prefix + "/a/b/c/d;" + prefix + "/a/b/c;" + prefix + "/a/b/c/e" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), prefix + "/a/b/c"); + } + + { + FileSearchPath fsp { "%FOO%;" + prefix + "/a/b/c;%FOO%;" + prefix + "/a/b/c/d" }; + fsp.removeRedundantPaths(); + expectEquals (fsp.toString(), "%FOO%;" + prefix + "/a/b/c"); + } + } + } +}; + +static FileSearchPathTests fileSearchPathTests; + +#endif + } // namespace juce diff --git a/modules/juce_core/files/juce_FileSearchPath.h b/modules/juce_core/files/juce_FileSearchPath.h index 8b48162ba515..d789cf413312 100644 --- a/modules/juce_core/files/juce_FileSearchPath.h +++ b/modules/juce_core/files/juce_FileSearchPath.h @@ -71,10 +71,21 @@ class JUCE_API FileSearchPath /** Returns one of the folders in this search path. The file returned isn't guaranteed to actually be a valid directory. - @see getNumPaths + @see getNumPaths, getRawString */ File operator[] (int index) const; + /** Returns the unaltered text of the folder at the specified index. + + Unlike operator[], this function returns the exact text that was entered. It does not + attempt to convert the path into an absolute path. + + This may be useful if the directory string is expected to understand environment variables + or other placeholders that the File constructor doesn't necessarily understand. + @see operator[] + */ + String getRawString (int index) const; + /** Returns the search path as a semicolon-separated list of directories. */ String toString() const; diff --git a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp index 65ceb66a3947..1799e7382b76 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileSearchPathListComponent.cpp @@ -129,7 +129,7 @@ void FileSearchPathListComponent::paintListBoxItem (int rowNumber, Graphics& g, f.setHorizontalScale (0.9f); g.setFont (f); - g.drawText (path[rowNumber].getFullPathName(), + g.drawText (path.getRawString (rowNumber), 4, 0, width - 6, height, Justification::centredLeft, true); } @@ -145,7 +145,7 @@ void FileSearchPathListComponent::deleteKeyPressed (int row) void FileSearchPathListComponent::returnKeyPressed (int row) { - chooser = std::make_unique (TRANS("Change folder..."), path[row], "*"); + chooser = std::make_unique (TRANS("Change folder..."), path.getRawString (row), "*"); auto chooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories; chooser->launchAsync (chooserFlags, [this, row] (const FileChooser& fc) @@ -258,7 +258,7 @@ void FileSearchPathListComponent::moveSelection (int delta) if (currentRow != newRow) { - auto f = path[currentRow]; + const auto f = File::createFileWithoutCheckingPath (path.getRawString (currentRow)); path.remove (currentRow); path.add (f, newRow); listBox.selectRow (newRow); From f7d01e9a5ebd91295eed91b6bdf30b0c4d657348 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 31 Jan 2023 13:15:22 +0000 Subject: [PATCH 028/347] CodeEditorComponent: Remove unnecessary assertion --- modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index dd4345297d54..5ca12315a458 100644 --- a/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -508,7 +508,7 @@ bool CodeEditorComponent::isTextInputActive() const void CodeEditorComponent::setTemporaryUnderlining (const Array>&) { - jassertfalse; // TODO Windows IME not yet supported for this comp.. + // TODO IME composition ranges not yet supported for this component } void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown) From 13d27987f49914be1ca7672c4da0818872eddfb0 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 2 Feb 2023 14:35:58 +0000 Subject: [PATCH 029/347] VST3 Client: Enforce that process and setActive are not called simultaneously in FL Studio Opening some JUCE VST3s in FL Studio's patcher can cause crashes because of data races on objects accessed from setActive() and process(). According to the VST3 specification, these functions should never be called simultaneously, so I think this is a bug in FL Studio. --- .../VST3/juce_VST3_Wrapper.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 7e0d400e4a12..7a1746fe97dd 100644 --- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -2551,6 +2551,8 @@ class JuceVST3Component : public Vst::IComponent, //============================================================================== tresult PLUGIN_API setActive (TBool state) override { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + const auto willBeActive = (state != 0); active = false; @@ -3140,6 +3142,8 @@ class JuceVST3Component : public Vst::IComponent, Steinberg::int32 index, TBool state) override { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + // The host is misbehaving! The plugin must be deactivated before setting new arrangements. jassert (! active); @@ -3274,6 +3278,8 @@ class JuceVST3Component : public Vst::IComponent, tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + if (active) { // The host is misbehaving! The plugin must be deactivated before setting new arrangements. @@ -3501,6 +3507,8 @@ class JuceVST3Component : public Vst::IComponent, tresult PLUGIN_API process (Vst::ProcessData& data) override { + const FLStudioDIYSpecificationEnforcementLock lock (flStudioDIYSpecificationEnforcementMutex); + if (pluginInstance == nullptr) return kResultFalse; @@ -3573,6 +3581,24 @@ class JuceVST3Component : public Vst::IComponent, } private: + /* FL's Patcher implements the VST3 specification incorrectly, calls process() before/during + setActive(). + */ + class [[nodiscard]] FLStudioDIYSpecificationEnforcementLock + { + public: + explicit FLStudioDIYSpecificationEnforcementLock (CriticalSection& mutex) + { + static const auto lockRequired = PluginHostType().isFruityLoops(); + + if (lockRequired) + lock.emplace (mutex); + } + + private: + std::optional lock; + }; + InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) { const auto result = testForMultiple (*this, @@ -3790,6 +3816,7 @@ class JuceVST3Component : public Vst::IComponent, #endif static const char* kJucePrivateDataIdentifier; + CriticalSection flStudioDIYSpecificationEnforcementMutex; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3Component) }; From 454bc7c63a3a1364314185f776786d4ec2e8e5e9 Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 2 Feb 2023 19:06:39 +0000 Subject: [PATCH 030/347] CoreGraphicsContext: Avoid hanging when drawing transformed tiled images CGContextGetClipBoundingBox sometimes returns a 'null' rect. When it does, the following logic will get stuck in the while loop drawing individual tiles over and over again. --- .../native/juce_mac_CoreGraphicsContext.mm | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm index 417d16c7f46c..74bd072825b2 100644 --- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -549,15 +549,20 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) CGContextDrawTiledImage (context.get(), imageRect, image.get()); #else // There's a bug in CGContextDrawTiledImage that makes it incredibly slow - // if it's doing a transformation - it's quicker to just draw lots of images manually - if (&CGContextDrawTiledImage != nullptr && transform.isOnlyTranslation()) + // if it's doing a transformation - it's quicker to just draw lots of images manually, + // but we might not be able to draw the images ourselves if the clipping region is not + // finite + const auto doCustomTiling = [&] { - CGContextDrawTiledImage (context.get(), imageRect, image.get()); - } - else - { - // Fallback to manually doing a tiled fill - auto clip = CGRectIntegral (CGContextGetClipBoundingBox (context.get())); + if (transform.isOnlyTranslation()) + return false; + + const auto bound = CGContextGetClipBoundingBox (context.get()); + + if (CGRectIsNull (bound)) + return false; + + const auto clip = CGRectIntegral (bound); int x = 0, y = 0; while (x > clip.origin.x) x -= iw; @@ -573,7 +578,12 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) y += ih; } - } + + return true; + }; + + if (! doCustomTiling()) + CGContextDrawTiledImage (context.get(), imageRect, image.get()); #endif } else From 043182faa66908472f98237cde1bd6f9c5a1a92c Mon Sep 17 00:00:00 2001 From: attila Date: Mon, 9 Jan 2023 17:57:32 +0100 Subject: [PATCH 031/347] Linux: Avoid logging BadShmSeg error when SHM capability is not detected --- modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6103b7449736..2c80ce63f9ce 100644 --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp @@ -638,8 +638,8 @@ enum if (X11Symbols::getInstance()->xShmAttach (display, &segmentInfo) != 0) { - X11Symbols::getInstance()->xSync (display, False); X11Symbols::getInstance()->xShmDetach (display, &segmentInfo); + X11Symbols::getInstance()->xSync (display, False); isAvailable = true; } From 0f402bb81f681bde2ad2569d2b3583d032a20022 Mon Sep 17 00:00:00 2001 From: attila Date: Wed, 11 Jan 2023 17:12:55 +0100 Subject: [PATCH 032/347] ARAPluginDemo: Fix thread race when PlaybackRegion is modified during playback --- examples/Plugins/ARAPluginDemo.h | 56 +++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/examples/Plugins/ARAPluginDemo.h b/examples/Plugins/ARAPluginDemo.h index 17befd7bed2c..524da91848a3 100644 --- a/examples/Plugins/ARAPluginDemo.h +++ b/examples/Plugins/ARAPluginDemo.h @@ -296,11 +296,18 @@ class PossiblyBufferedReader std::unique_ptr reader; }; +struct ProcessingLockInterface +{ + virtual ~ProcessingLockInterface() = default; + virtual ScopedTryReadLock getProcessingLock() = 0; +}; + //============================================================================== class PlaybackRenderer : public ARAPlaybackRenderer { public: - using ARAPlaybackRenderer::ARAPlaybackRenderer; + PlaybackRenderer (ARA::PlugIn::DocumentController* dc, ProcessingLockInterface& lockInterfaceIn) + : ARAPlaybackRenderer (dc), lockInterface (lockInterfaceIn) {} void prepareToPlay (double sampleRateIn, int maximumSamplesPerBlockIn, @@ -351,6 +358,11 @@ class PlaybackRenderer : public ARAPlaybackRenderer AudioProcessor::Realtime realtime, const AudioPlayHead::PositionInfo& positionInfo) noexcept override { + const auto lock = lockInterface.getProcessingLock(); + + if (! lock.isLocked()) + return true; + const auto numSamples = buffer.getNumSamples(); jassert (numSamples <= maximumSamplesPerBlock); jassert (numChannels == buffer.getNumChannels()); @@ -458,8 +470,7 @@ class PlaybackRenderer : public ARAPlaybackRenderer private: //============================================================================== - // We're subclassing here only to provide a proper default c'tor for our shared resource - + ProcessingLockInterface& lockInterface; SharedResourcePointer sharedTimesliceThread; std::map audioSourceReaders; bool useBufferedAudioSourceReader = true; @@ -473,8 +484,12 @@ class EditorRenderer : public ARAEditorRenderer, private ARARegionSequence::Listener { public: - EditorRenderer (ARA::PlugIn::DocumentController* documentController, const PreviewState* previewStateIn) - : ARAEditorRenderer (documentController), previewState (previewStateIn), previewBuffer() + EditorRenderer (ARA::PlugIn::DocumentController* documentController, + const PreviewState* previewStateIn, + ProcessingLockInterface& lockInterfaceIn) + : ARAEditorRenderer (documentController), + lockInterface (lockInterfaceIn), + previewState (previewStateIn) { jassert (previewState != nullptr); } @@ -549,6 +564,11 @@ class EditorRenderer : public ARAEditorRenderer, { ignoreUnused (realtime); + const auto lock = lockInterface.getProcessingLock(); + + if (! lock.isLocked()) + return true; + return asyncConfigCallback.withLock ([&] (bool locked) { if (! locked) @@ -661,6 +681,7 @@ class EditorRenderer : public ARAEditorRenderer, }); } + ProcessingLockInterface& lockInterface; const PreviewState* previewState = nullptr; AsyncConfigurationCallback asyncConfigCallback { [this] { configure(); } }; double lastPreviewTime = 0.0; @@ -678,7 +699,8 @@ class EditorRenderer : public ARAEditorRenderer, }; //============================================================================== -class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControllerSpecialisation +class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControllerSpecialisation, + private ProcessingLockInterface { public: using ARADocumentControllerSpecialisation::ARADocumentControllerSpecialisation; @@ -686,6 +708,16 @@ class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControl PreviewState previewState; protected: + void willBeginEditing (ARADocument*) override + { + processBlockLock.enterWrite(); + } + + void didEndEditing (ARADocument*) override + { + processBlockLock.exitWrite(); + } + ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, ARA::ARAAudioModificationHostRef hostRef, const ARAAudioModification* optionalModificationToClone) noexcept override @@ -697,12 +729,12 @@ class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControl ARAPlaybackRenderer* doCreatePlaybackRenderer() noexcept override { - return new PlaybackRenderer (getDocumentController()); + return new PlaybackRenderer (getDocumentController(), *this); } EditorRenderer* doCreateEditorRenderer() noexcept override { - return new EditorRenderer (getDocumentController(), &previewState); + return new EditorRenderer (getDocumentController(), &previewState, *this); } bool doRestoreObjectsFromStream (ARAInputStream& input, @@ -779,6 +811,14 @@ class ARADemoPluginDocumentControllerSpecialisation : public ARADocumentControl return true; } + +private: + ScopedTryReadLock getProcessingLock() override + { + return ScopedTryReadLock { processBlockLock }; + } + + ReadWriteLock processBlockLock; }; struct PlayHeadState From 6c720bf3f1bbf33af774ef790158e57a2675cf43 Mon Sep 17 00:00:00 2001 From: attila Date: Thu, 12 Jan 2023 19:08:11 +0100 Subject: [PATCH 033/347] Android: Add simulated mouse exit event to the mouse up callback Without this event Components such as Button would remain in a hover state indefinitely after being clicked on a touch screen. The simulated event matches the behaviour of the other platforms. --- .../juce_gui_basics/native/juce_android_Windowing.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 1fadc42a53e0..b5edc55a02ee 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -1489,6 +1489,15 @@ class AndroidComponentPeer : public ComponentPeer, time, {}, index); + + handleMouseEvent (MouseInputSource::InputSourceType::touch, + MouseInputSource::offscreenMousePos, + ModifierKeys::currentModifiers.withoutMouseButtons(), + MouseInputSource::defaultPressure, + MouseInputSource::defaultOrientation, + time, + {}, + index); } void handleAccessibilityHoverCallback (int command, Point sysPos, int64) From a662e5584bbdb9f16f0ffe6da62371af0718b56b Mon Sep 17 00:00:00 2001 From: attila Date: Wed, 18 Jan 2023 16:43:39 +0100 Subject: [PATCH 034/347] PopupMenu: Add explicit focus order to menu items Without this VoiceOver will iterate over menu items left to right first, and iteration order will be affected by whether the PopupMenu gets broken up into multiple columns due to not enough screen space. --- modules/juce_gui_basics/menus/juce_PopupMenu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index f168cd8c5e35..046d889fa2ea 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -391,6 +391,7 @@ struct MenuWindow : public Component if (i + 1 < menu.items.size() || ! item.isSeparator) { auto* child = items.add (new ItemComponent (item, options, *this)); + child->setExplicitFocusOrder (1 + i); if (initialSelectedId != 0 && item.itemID == initialSelectedId) setCurrentlyHighlightedChild (child); From 0033e521793bde2e1d9150f067d3613a4bc28eb5 Mon Sep 17 00:00:00 2001 From: reuk Date: Sun, 5 Feb 2023 12:43:07 +0000 Subject: [PATCH 035/347] VBlankAttachment: Make isEmpty const --- modules/juce_gui_basics/windows/juce_VBlankAttachement.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/juce_gui_basics/windows/juce_VBlankAttachement.h b/modules/juce_gui_basics/windows/juce_VBlankAttachement.h index 2eb9daa935c1..19514eb2e7ac 100644 --- a/modules/juce_gui_basics/windows/juce_VBlankAttachement.h +++ b/modules/juce_gui_basics/windows/juce_VBlankAttachement.h @@ -34,7 +34,7 @@ class JUCE_API VBlankAttachment final : public ComponentPeer::VBlankListener, { public: /** Default constructor for creating an empty object. */ - VBlankAttachment() {} + VBlankAttachment() = default; /** Constructor. Creates an attachment that will call the passed in function at every vertical blank event of the display that the passed in Component is currently visible on. @@ -49,7 +49,7 @@ class JUCE_API VBlankAttachment final : public ComponentPeer::VBlankListener, ~VBlankAttachment() override; /** Returns true for a default constructed object. */ - bool isEmpty() { return owner == nullptr; } + bool isEmpty() const { return owner == nullptr; } //============================================================================== void onVBlank() override; From 0889588ae400d4a64ed6eab3cbddb6aa2a2c3c7c Mon Sep 17 00:00:00 2001 From: reuk Date: Sun, 5 Feb 2023 19:29:36 +0000 Subject: [PATCH 036/347] VST3: Avoid copy in channel mapper --- modules/juce_audio_processors/format_types/juce_VST3Common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/juce_audio_processors/format_types/juce_VST3Common.h b/modules/juce_audio_processors/format_types/juce_VST3Common.h index fae1f06a465e..18c89a744ccd 100644 --- a/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -701,7 +701,7 @@ class ClientBufferMapperData if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) return getBlankBuffer (usedChannels, (int) data.numSamples); - setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); + setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); setUpOutputChannels (scratchBuffer, outputMap, channels); const auto channelPtr = channels.empty() ? scratchBuffer.getArrayOfWritePointers() @@ -719,7 +719,7 @@ class ClientBufferMapperData { for (size_t busIndex = 0; busIndex < map.size(); ++busIndex) { - const auto mapping = map[busIndex]; + const auto& mapping = map[busIndex]; if (! mapping.isClientActive()) continue; From 793df5dd3d4368bc121c960b89b922e2ccae4fe2 Mon Sep 17 00:00:00 2001 From: reuk Date: Sun, 5 Feb 2023 19:32:32 +0000 Subject: [PATCH 037/347] iOS Windowing: Avoid private selector warnings --- modules/juce_gui_basics/native/juce_ios_Windowing.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 6fffc44e701f..b8287238ca6c 100644 --- a/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -852,8 +852,8 @@ Image juce_createIconForFile (const File&) void Displays::findDisplays (float masterScale) { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - static const auto keyboardShownSelector = @selector (keyboardShown:); - static const auto keyboardHiddenSelector = @selector (keyboardHidden:); + static const auto keyboardShownSelector = @selector (juceKeyboardShown:); + static const auto keyboardHiddenSelector = @selector (juceKeyboardHidden:); JUCE_END_IGNORE_WARNINGS_GCC_LIKE class OnScreenKeyboardChangeDetectorImpl From 321cab0c15f5c398fff2d805c261611918c34291 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 6 Feb 2023 12:08:23 +0000 Subject: [PATCH 038/347] MIDILogger: Update bus layouts to support loading in Cakewalk --- examples/Plugins/MidiLoggerPluginDemo.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/Plugins/MidiLoggerPluginDemo.h b/examples/Plugins/MidiLoggerPluginDemo.h index 7602d14549d8..104ef89f4187 100644 --- a/examples/Plugins/MidiLoggerPluginDemo.h +++ b/examples/Plugins/MidiLoggerPluginDemo.h @@ -337,9 +337,11 @@ class MidiLoggerPluginDemoProcessor : public AudioProcessor, static BusesProperties getBusesLayout() { - // Live doesn't like to load midi-only plugins, so we add an audio output there. - return PluginHostType().isAbletonLive() ? BusesProperties().withOutput ("out", AudioChannelSet::stereo()) - : BusesProperties(); + // Live and Cakewalk don't like to load midi-only plugins, so we add an audio output there. + const PluginHostType host; + return host.isAbletonLive() || host.isSonar() + ? BusesProperties().withOutput ("out", AudioChannelSet::stereo()) + : BusesProperties(); } ValueTree state { "state" }; From f5aa881b6f2d7b13fd758b98bec660b592ee3021 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 6 Feb 2023 16:29:33 +0000 Subject: [PATCH 039/347] FileChooser: Improve modal behaviour in plugins on Windows The previous method for finding the dialog's owner was ineffective, and it was still possible for other windows to obscure the file picker in many cases. Using GetActiveWindow seems to produce the expected behaviour. --- .../native/juce_win32_FileChooser.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp index 316526059228..ac6f1d97c456 100644 --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp @@ -166,7 +166,7 @@ class Win32NativeFileChooser : private Thread void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } }; - bool showDialog (IFileDialog& dialog, bool async) + bool showDialog (IFileDialog& dialog) { FILEOPENDIALOGOPTIONS flags = {}; @@ -285,7 +285,7 @@ class Win32NativeFileChooser : private Thread Events events { *this }; ScopedAdvise scope { dialog, events }; - return dialog.Show (async ? nullptr : static_cast (owner->getWindowHandle())) == S_OK; + return dialog.Show (GetActiveWindow()) == S_OK; }(); ScopedLock lock (deletingDialog); @@ -295,7 +295,7 @@ class Win32NativeFileChooser : private Thread } //============================================================================== - Array openDialogVistaAndUp (bool async) + Array openDialogVistaAndUp() { const auto getUrl = [] (IShellItem& item) { @@ -320,7 +320,7 @@ class Win32NativeFileChooser : private Thread if (dialog == nullptr) return {}; - showDialog (*dialog, async); + showDialog (*dialog); const auto item = [&] { @@ -350,7 +350,7 @@ class Win32NativeFileChooser : private Thread if (dialog == nullptr) return {}; - showDialog (*dialog, async); + showDialog (*dialog); const auto items = [&] { @@ -391,7 +391,7 @@ class Win32NativeFileChooser : private Thread if (selectsDirectories) { BROWSEINFO bi = {}; - bi.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle()); + bi.hwndOwner = GetActiveWindow(); bi.pszDisplayName = files; bi.lpszTitle = title.toWideCharPointer(); bi.lParam = (LPARAM) this; @@ -441,7 +441,7 @@ class Win32NativeFileChooser : private Thread startingFile.getFullPathName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR)); } - of.hwndOwner = (HWND) (async ? nullptr : owner->getWindowHandle()); + of.hwndOwner = GetActiveWindow(); of.lpstrFilter = filters.getData(); of.nFilterIndex = 1; of.lpstrFile = files; @@ -502,7 +502,7 @@ class Win32NativeFileChooser : private Thread if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista && customComponent == nullptr) { - return openDialogVistaAndUp (async); + return openDialogVistaAndUp(); } return openDialogPreVista (async); From 408f6030e6dd2a54193d9ee0953ed4c7455bea22 Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 6 Feb 2023 19:36:28 +0000 Subject: [PATCH 040/347] NSViewComponentPeer: Attempt to avoid reentrant calls to makeKeyWindow AUv2 plugins on Arm that are hosted out-of-process (e.g. in Logic 10.7) can sometimes crash due to endlessly recursing through becomeKeyWindow. This tends to happen when displaying a secondary window in a plugin, e.g. an AlertWindow, then clicking on a secondary app, then clicking back on the AlertWindow. To avoid this case, we check that the peer isn't already key before calling makeKeyWindow. Unfortunately, we can't use isKeyWindow to avoid the recursion because this may not return true until after becomeKeyWindow has returned. --- .../native/juce_mac_NSViewComponentPeer.mm | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 95321245ca99..d5f25718d9bf 100644 --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -558,7 +558,7 @@ void toFront (bool makeActiveWindow) override { ++insideToFrontCall; - if (makeActiveWindow) + if (makeActiveWindow && ! inBecomeKeyWindow) [window makeKeyAndOrderFront: nil]; else [window orderFront: nil]; @@ -1541,7 +1541,9 @@ void grabFocus() override { if (window != nil) { - [window makeKeyWindow]; + if (! inBecomeKeyWindow) + [window makeKeyWindow]; + [window makeFirstResponder: view]; viewFocusGain(); @@ -1622,7 +1624,7 @@ bool sendEventToInputContextOrComponent (NSEvent* ev) bool isFirstLiveResize = false, viewCannotHandleEvent = false; bool isStretchingTop = false, isStretchingLeft = false, isStretchingBottom = false, isStretchingRight = false; bool windowRepresentsFile = false; - bool isAlwaysOnTop = false, wasAlwaysOnTop = false; + bool isAlwaysOnTop = false, wasAlwaysOnTop = false, inBecomeKeyWindow = false; String stringBeingComposed; int startOfMarkedTextInTextInputTarget = 0; @@ -2453,6 +2455,10 @@ static NSDragOperation draggingUpdated (id self, SEL, id sender) if (auto* owner = getOwner (self)) { + jassert (! owner->inBecomeKeyWindow); + + const ScopedValueSetter scope { owner->inBecomeKeyWindow, true }; + if (owner->canBecomeKeyWindow()) { owner->becomeKeyWindow(); From 9cfbccca8ecd8ccd44d895a8de9c68d3df8df57a Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 6 Feb 2023 20:08:59 +0000 Subject: [PATCH 041/347] Accessibility: Make createAccessibilityHandler public to allow calls from derived classes --- .../DemoRunner/Source/UI/SettingsContent.h | 2 +- modules/juce_gui_basics/buttons/juce_Button.h | 3 +- .../buttons/juce_HyperlinkButton.h | 5 ++- .../buttons/juce_ToggleButton.h | 5 ++- .../components/juce_Component.h | 38 ++++++++++++------- .../drawables/juce_DrawableImage.h | 3 +- .../drawables/juce_DrawableText.h | 3 +- .../filebrowser/juce_FileBrowserComponent.h | 3 +- .../filebrowser/juce_ImagePreviewComponent.h | 3 +- .../layout/juce_ConcertinaPanel.h | 4 +- .../layout/juce_GroupComponent.h | 4 +- .../juce_gui_basics/layout/juce_ScrollBar.h | 3 +- .../juce_gui_basics/layout/juce_SidePanel.h | 3 +- .../layout/juce_TabbedButtonBar.h | 3 +- .../layout/juce_TabbedComponent.h | 3 +- .../juce_gui_basics/layout/juce_Viewport.h | 1 + .../menus/juce_BurgerMenuComponent.h | 3 +- .../menus/juce_MenuBarComponent.h | 3 +- .../misc/juce_JUCESplashScreen.h | 4 +- .../juce_gui_basics/widgets/juce_ComboBox.h | 3 +- .../widgets/juce_ImageComponent.h | 4 +- modules/juce_gui_basics/widgets/juce_Label.h | 4 +- .../juce_gui_basics/widgets/juce_ListBox.h | 3 +- .../widgets/juce_ProgressBar.h | 4 +- modules/juce_gui_basics/widgets/juce_Slider.h | 3 +- .../widgets/juce_TableHeaderComponent.h | 3 +- .../widgets/juce_TableListBox.h | 3 +- .../juce_gui_basics/widgets/juce_TextEditor.h | 3 +- .../juce_gui_basics/widgets/juce_Toolbar.h | 3 +- .../widgets/juce_ToolbarItemComponent.h | 4 +- .../widgets/juce_ToolbarItemPalette.h | 3 +- .../juce_gui_basics/widgets/juce_TreeView.h | 3 +- .../windows/juce_AlertWindow.h | 4 +- .../juce_gui_basics/windows/juce_CallOutBox.h | 3 +- .../windows/juce_DialogWindow.h | 5 ++- .../windows/juce_TooltipWindow.h | 3 +- .../windows/juce_TopLevelWindow.h | 3 +- .../code_editor/juce_CodeEditorComponent.h | 3 +- 38 files changed, 104 insertions(+), 56 deletions(-) diff --git a/examples/DemoRunner/Source/UI/SettingsContent.h b/examples/DemoRunner/Source/UI/SettingsContent.h index 9c3c7133015f..a90d5b2b57d3 100644 --- a/examples/DemoRunner/Source/UI/SettingsContent.h +++ b/examples/DemoRunner/Source/UI/SettingsContent.h @@ -236,12 +236,12 @@ class SettingsContent : public Component audioSettings.setBounds (bounds); } - private: std::unique_ptr createAccessibilityHandler() override { return createIgnoredAccessibilityHandler (*this); } + private: GraphicsSettingsGroup graphicsSettings; AudioSettingsGroup audioSettings; }; diff --git a/modules/juce_gui_basics/buttons/juce_Button.h b/modules/juce_gui_basics/buttons/juce_Button.h index ce849023b972..e9878fb09f0f 100644 --- a/modules/juce_gui_basics/buttons/juce_Button.h +++ b/modules/juce_gui_basics/buttons/juce_Button.h @@ -495,6 +495,8 @@ class JUCE_API Button : public Component, void focusLost (FocusChangeType) override; /** @internal */ void enablementChanged() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -522,7 +524,6 @@ class JUCE_API Button : public Component, bool triggerOnMouseDown = false; bool generateTooltip = false; - std::unique_ptr createAccessibilityHandler() override; void checkToggleableState (bool wasToggleable); void repeatTimerCallback(); diff --git a/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h b/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h index 553464fa7a4f..de346033b9a4 100644 --- a/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h +++ b/modules/juce_gui_basics/buttons/juce_HyperlinkButton.h @@ -101,6 +101,9 @@ class JUCE_API HyperlinkButton : public Button /** Returns the type of justification, as set in setJustificationType(). */ Justification getJustificationType() const noexcept { return justification; } + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; + protected: //============================================================================== /** @internal */ @@ -111,8 +114,6 @@ class JUCE_API HyperlinkButton : public Button void paintButton (Graphics&, bool, bool) override; private: - std::unique_ptr createAccessibilityHandler() override; - //============================================================================== using Button::clicked; Font getFontToUse() const; diff --git a/modules/juce_gui_basics/buttons/juce_ToggleButton.h b/modules/juce_gui_basics/buttons/juce_ToggleButton.h index d01488c7ce7c..df5ffcc3a1a4 100644 --- a/modules/juce_gui_basics/buttons/juce_ToggleButton.h +++ b/modules/juce_gui_basics/buttons/juce_ToggleButton.h @@ -76,6 +76,9 @@ class JUCE_API ToggleButton : public Button tickDisabledColourId = 0x1006503 /**< The colour to use for the disabled tick mark and/or outline. */ }; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; + protected: //============================================================================== /** @internal */ @@ -84,8 +87,6 @@ class JUCE_API ToggleButton : public Button void colourChanged() override; private: - std::unique_ptr createAccessibilityHandler() override; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton) }; diff --git a/modules/juce_gui_basics/components/juce_Component.h b/modules/juce_gui_basics/components/juce_Component.h index 9f7304969532..da3a4a166637 100644 --- a/modules/juce_gui_basics/components/juce_Component.h +++ b/modules/juce_gui_basics/components/juce_Component.h @@ -2479,6 +2479,9 @@ class JUCE_API Component : public MouseListener /** Returns the accessibility handler for this component, or nullptr if this component is not accessible. + To customise the accessibility handler for a component, override + createAccessibilityHandler(). + @see setAccessible */ AccessibilityHandler* getAccessibilityHandler(); @@ -2492,20 +2495,6 @@ class JUCE_API Component : public MouseListener void invalidateAccessibilityHandler(); //============================================================================== - #ifndef DOXYGEN - [[deprecated ("Use the setFocusContainerType that takes a more descriptive enum.")]] - void setFocusContainer (bool shouldBeFocusContainer) noexcept - { - setFocusContainerType (shouldBeFocusContainer ? FocusContainerType::keyboardFocusContainer - : FocusContainerType::none); - } - - [[deprecated ("Use the contains that takes a Point.")]] - void contains (int, int) = delete; - #endif - -private: - //============================================================================== /** Override this method to return a custom AccessibilityHandler for this component. The default implementation creates and returns a AccessibilityHandler object with an @@ -2519,10 +2508,31 @@ class JUCE_API Component : public MouseListener its Component, so it's safe to store and use a reference back to the Component inside the AccessibilityHandler if necessary. + This function should rarely be called directly. If you need to query a component's + accessibility handler, it's normally better to call getAccessibilityHandler(). + The exception to this rule is derived implementations of createAccessibilityHandler(), + which may find it useful to call the base class implementation, and then wrap or + modify the result. + @see getAccessibilityHandler */ virtual std::unique_ptr createAccessibilityHandler(); + //============================================================================== + #ifndef DOXYGEN + [[deprecated ("Use the setFocusContainerType that takes a more descriptive enum.")]] + void setFocusContainer (bool shouldBeFocusContainer) noexcept + { + setFocusContainerType (shouldBeFocusContainer ? FocusContainerType::keyboardFocusContainer + : FocusContainerType::none); + } + + [[deprecated ("Use the contains that takes a Point.")]] + void contains (int, int) = delete; + #endif + +private: + //============================================================================== friend class ComponentPeer; friend class MouseInputSourceInternal; diff --git a/modules/juce_gui_basics/drawables/juce_DrawableImage.h b/modules/juce_gui_basics/drawables/juce_DrawableImage.h index 6e1bb162a8e1..f56a2d4df5d9 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableImage.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableImage.h @@ -97,10 +97,11 @@ class JUCE_API DrawableImage : public Drawable Rectangle getDrawableBounds() const override; /** @internal */ Path getOutlineAsPath() const override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== - std::unique_ptr createAccessibilityHandler() override; bool setImageInternal (const Image&); //============================================================================== diff --git a/modules/juce_gui_basics/drawables/juce_DrawableText.h b/modules/juce_gui_basics/drawables/juce_DrawableText.h index 76ba450d4c78..61c0680c9bfe 100644 --- a/modules/juce_gui_basics/drawables/juce_DrawableText.h +++ b/modules/juce_gui_basics/drawables/juce_DrawableText.h @@ -98,6 +98,8 @@ class JUCE_API DrawableText : public Drawable Path getOutlineAsPath() const override; /** @internal */ bool replaceColour (Colour originalColour, Colour replacementColour) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -108,7 +110,6 @@ class JUCE_API DrawableText : public Drawable Colour colour; Justification justification; - std::unique_ptr createAccessibilityHandler() override; void refreshBounds(); Rectangle getTextArea (float width, float height) const; AffineTransform getTextTransform (float width, float height) const; diff --git a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h index e70857bc08c1..dc4409a75003 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h @@ -252,6 +252,8 @@ class JUCE_API FileBrowserComponent : public Component, FilePreviewComponent* getPreviewComponent() const noexcept; /** @internal */ DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; protected: /** Returns a list of names and paths for the default places the user might want to look. @@ -283,7 +285,6 @@ class JUCE_API FileBrowserComponent : public Component, TimeSliceThread thread; bool wasProcessActive; - std::unique_ptr createAccessibilityHandler() override; void timerCallback() override; void sendListenerChangeMessage(); bool isFileOrDirSuitable (const File&) const; diff --git a/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h b/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h index 2c354a73ebb3..874fb54f130c 100644 --- a/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_ImagePreviewComponent.h @@ -52,13 +52,14 @@ class JUCE_API ImagePreviewComponent : public FilePreviewComponent, void paint (Graphics&) override; /** @internal */ void timerCallback() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: File fileToLoad; Image currentThumbnail; String currentDetails; - std::unique_ptr createAccessibilityHandler() override; void getThumbSize (int& w, int& h) const; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePreviewComponent) diff --git a/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h b/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h index db959f34d095..d6aa4a9fc9f3 100644 --- a/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h +++ b/modules/juce_gui_basics/layout/juce_ConcertinaPanel.h @@ -119,8 +119,10 @@ class JUCE_API ConcertinaPanel : public Component ConcertinaPanel&, Component&) = 0; }; -private: + /** @internal */ std::unique_ptr createAccessibilityHandler() override; + +private: void resized() override; class PanelHolder; diff --git a/modules/juce_gui_basics/layout/juce_GroupComponent.h b/modules/juce_gui_basics/layout/juce_GroupComponent.h index e45b15f9204e..31b343776797 100644 --- a/modules/juce_gui_basics/layout/juce_GroupComponent.h +++ b/modules/juce_gui_basics/layout/juce_GroupComponent.h @@ -98,10 +98,10 @@ class JUCE_API GroupComponent : public Component void enablementChanged() override; /** @internal */ void colourChanged() override; - -private: + /** @internal */ std::unique_ptr createAccessibilityHandler() override; +private: String text; Justification justification; diff --git a/modules/juce_gui_basics/layout/juce_ScrollBar.h b/modules/juce_gui_basics/layout/juce_ScrollBar.h index a6d94185a795..1c151ff5a5e0 100644 --- a/modules/juce_gui_basics/layout/juce_ScrollBar.h +++ b/modules/juce_gui_basics/layout/juce_ScrollBar.h @@ -414,6 +414,8 @@ class JUCE_API ScrollBar : public Component, void parentHierarchyChanged() override; /** @internal */ void setVisible (bool) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -427,7 +429,6 @@ class JUCE_API ScrollBar : public Component, std::unique_ptr upButton, downButton; ListenerList listeners; - std::unique_ptr createAccessibilityHandler() override; void handleAsyncUpdate() override; void updateThumbPosition(); void timerCallback() override; diff --git a/modules/juce_gui_basics/layout/juce_SidePanel.h b/modules/juce_gui_basics/layout/juce_SidePanel.h index 59395f10dd1f..a425bb9d3959 100644 --- a/modules/juce_gui_basics/layout/juce_SidePanel.h +++ b/modules/juce_gui_basics/layout/juce_SidePanel.h @@ -195,6 +195,8 @@ class SidePanel : public Component, void mouseDrag (const MouseEvent&) override; /** @internal */ void mouseUp (const MouseEvent&) override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; private: //============================================================================== @@ -221,7 +223,6 @@ class SidePanel : public Component, bool shouldShowDismissButton = true; //============================================================================== - std::unique_ptr createAccessibilityHandler() override; void lookAndFeelChanged() override; void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; void changeListenerCallback (ChangeBroadcaster*) override; diff --git a/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h b/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h index c4ab1a434953..5d52af15c908 100644 --- a/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h +++ b/modules/juce_gui_basics/layout/juce_TabbedButtonBar.h @@ -334,6 +334,8 @@ class JUCE_API TabbedButtonBar : public Component, void resized() override; /** @internal */ void lookAndFeelChanged() override; + /** @internal */ + std::unique_ptr createAccessibilityHandler() override; protected: //============================================================================== @@ -362,7 +364,6 @@ class JUCE_API TabbedButtonBar : public Component, std::unique_ptr behindFrontTab; std::unique_ptr