@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
index dc2ad8e3b..1f03bc047 100644 | |||
index a19c7b05e..deabba6db 100644 | |||
--- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
+++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | |||
@@ -184,8 +184,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& | |||
@@ -185,8 +185,8 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Bela()); | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_audio_basics/midi/juce_MidiMessage.h b/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
index 2d9e5adf0..30a609cec 100644 | |||
index 22ac79483..5c761f659 100644 | |||
--- a/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
+++ b/modules/juce_audio_basics/midi/juce_MidiMessage.h | |||
@@ -945,7 +945,7 @@ private: | |||
@@ -979,7 +979,7 @@ private: | |||
#endif | |||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||
@@ -0,0 +1,538 @@ | |||
diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
new file mode 100644 | |||
index 000000000..58179be1a | |||
--- /dev/null | |||
+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
@@ -0,0 +1,532 @@ | |||
+/* | |||
+ ============================================================================== | |||
+ | |||
+ This file is part of the JUCE library. | |||
+ Copyright (c) 2017 - ROLI Ltd. | |||
+ | |||
+ JUCE is an open source library subject to commercial or open-source | |||
+ licensing. | |||
+ | |||
+ By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
+ Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
+ 27th April 2017). | |||
+ | |||
+ End User License Agreement: www.juce.com/juce-5-licence | |||
+ Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
+ | |||
+ Or: You may also use this code under the terms of the GPL v3 (see | |||
+ www.gnu.org/licenses). | |||
+ | |||
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
+ DISCLAIMED. | |||
+ | |||
+ ============================================================================== | |||
+*/ | |||
+ | |||
+#ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||
+#define JUCE_VSTINTERFACE_H_INCLUDED | |||
+ | |||
+using namespace juce; | |||
+ | |||
+#if JUCE_MSVC | |||
+ #define VSTINTERFACECALL __cdecl | |||
+ #pragma pack(push) | |||
+ #pragma pack(8) | |||
+#elif JUCE_MAC || JUCE_IOS | |||
+ #define VSTINTERFACECALL | |||
+ #if JUCE_64BIT | |||
+ #pragma options align=power | |||
+ #else | |||
+ #pragma options align=mac68k | |||
+ #endif | |||
+#else | |||
+ #define VSTINTERFACECALL | |||
+ #pragma pack(push, 8) | |||
+#endif | |||
+ | |||
+const int32 juceVstInterfaceVersion = 2400; | |||
+const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEffectInterface | |||
+{ | |||
+ int32 interfaceIdentifier; | |||
+ pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
+ void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
+ void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); | |||
+ float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); | |||
+ int32 numPrograms; | |||
+ int32 numParameters; | |||
+ int32 numInputChannels; | |||
+ int32 numOutputChannels; | |||
+ int32 flags; | |||
+ pointer_sized_int hostSpace1; | |||
+ pointer_sized_int hostSpace2; | |||
+ int32 latency; | |||
+ int32 deprecated1; | |||
+ int32 deprecated2; | |||
+ float deprecated3; | |||
+ void* effectPointer; | |||
+ void* userPointer; | |||
+ int32 plugInIdentifier; | |||
+ int32 plugInVersion; | |||
+ void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
+ void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); | |||
+ char emptySpace[56]; | |||
+}; | |||
+ | |||
+typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
+ | |||
+enum VstEffectInterfaceFlags | |||
+{ | |||
+ vstEffectFlagHasEditor = 1, | |||
+ vstEffectFlagInplaceAudio = 16, | |||
+ vstEffectFlagDataInChunks = 32, | |||
+ vstEffectFlagIsSynth = 256, | |||
+ vstEffectFlagInplaceDoubleAudio = 4096 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstHostToPlugInOpcodes | |||
+{ | |||
+ plugInOpcodeOpen, | |||
+ plugInOpcodeClose, | |||
+ plugInOpcodeSetCurrentProgram, | |||
+ plugInOpcodeGetCurrentProgram, | |||
+ plugInOpcodeSetCurrentProgramName, | |||
+ plugInOpcodeGetCurrentProgramName, | |||
+ plugInOpcodeGetParameterLabel, | |||
+ plugInOpcodeGetParameterText, | |||
+ plugInOpcodeGetParameterName, | |||
+ plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, | |||
+ plugInOpcodeSetBlockSize, | |||
+ plugInOpcodeResumeSuspend, | |||
+ plugInOpcodeGetEditorBounds, | |||
+ plugInOpcodeOpenEditor, | |||
+ plugInOpcodeCloseEditor, | |||
+ plugInOpcodeDrawEditor, | |||
+ plugInOpcodeGetMouse, | |||
+ plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, | |||
+ plugInOpcodeeffEditorTop, | |||
+ plugInOpcodeSleepEditor, | |||
+ plugInOpcodeIdentify, | |||
+ plugInOpcodeGetData, | |||
+ plugInOpcodeSetData, | |||
+ plugInOpcodePreAudioProcessingEvents, | |||
+ plugInOpcodeIsParameterAutomatable, | |||
+ plugInOpcodeParameterValueForText, | |||
+ plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, | |||
+ plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, | |||
+ plugInOpcodeConnectOutput, | |||
+ plugInOpcodeGetInputPinProperties, | |||
+ plugInOpcodeGetOutputPinProperties, | |||
+ plugInOpcodeGetPlugInCategory, | |||
+ plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, | |||
+ plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, | |||
+ plugInOpcodeGetPlugInName, | |||
+ plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, | |||
+ plugInOpcodeGetManufacturerProductName, | |||
+ plugInOpcodeGetManufacturerVersion, | |||
+ plugInOpcodeManufacturerSpecific, | |||
+ plugInOpcodeCanPlugInDo, | |||
+ plugInOpcodeGetTailSize, | |||
+ plugInOpcodeIdle, | |||
+ plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, | |||
+ plugInOpcodeGetVstInterfaceVersion, | |||
+ plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, | |||
+ plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, | |||
+ plugInOpcodeNextPlugInUniqueID, | |||
+ plugInOpcodeStartProcess, | |||
+ plugInOpcodeStopProcess, | |||
+ plugInOpcodeSetNumberOfSamplesToProcess, | |||
+ plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, | |||
+ pluginOpcodeGetNumMidiInputChannels, | |||
+ pluginOpcodeGetNumMidiOutputChannels, | |||
+ plugInOpcodeMaximum = pluginOpcodeGetNumMidiOutputChannels | |||
+}; | |||
+ | |||
+ | |||
+enum VstPlugInToHostOpcodes | |||
+{ | |||
+ hostOpcodeParameterChanged, | |||
+ hostOpcodeVstVersion, | |||
+ hostOpcodeCurrentId, | |||
+ hostOpcodeIdle, | |||
+ hostOpcodePinConnected, | |||
+ hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, | |||
+ hostOpcodeGetTimingInfo, | |||
+ hostOpcodePreAudioProcessingEvents, | |||
+ hostOpcodeSetTime, | |||
+ hostOpcodeTempoAt, | |||
+ hostOpcodeGetNumberOfAutomatableParameters, | |||
+ hostOpcodeGetParameterInterval, | |||
+ hostOpcodeIOModified, | |||
+ hostOpcodeNeedsIdle, | |||
+ hostOpcodeWindowSize, | |||
+ hostOpcodeGetSampleRate, | |||
+ hostOpcodeGetBlockSize, | |||
+ hostOpcodeGetInputLatency, | |||
+ hostOpcodeGetOutputLatency, | |||
+ hostOpcodeGetPreviousPlugIn, | |||
+ hostOpcodeGetNextPlugIn, | |||
+ hostOpcodeWillReplace, | |||
+ hostOpcodeGetCurrentAudioProcessingLevel, | |||
+ hostOpcodeGetAutomationState, | |||
+ hostOpcodeOfflineStart, | |||
+ hostOpcodeOfflineReadSource, | |||
+ hostOpcodeOfflineWrite, | |||
+ hostOpcodeOfflineGetCurrentPass, | |||
+ hostOpcodeOfflineGetCurrentMetaPass, | |||
+ hostOpcodeSetOutputSampleRate, | |||
+ hostOpcodeGetOutputSpeakerConfiguration, | |||
+ hostOpcodeGetManufacturerName, | |||
+ hostOpcodeGetProductName, | |||
+ hostOpcodeGetManufacturerVersion, | |||
+ hostOpcodeManufacturerSpecific, | |||
+ hostOpcodeSetIcon, | |||
+ hostOpcodeCanHostDo, | |||
+ hostOpcodeGetLanguage, | |||
+ hostOpcodeOpenEditorWindow, | |||
+ hostOpcodeCloseEditorWindow, | |||
+ hostOpcodeGetDirectory, | |||
+ hostOpcodeUpdateView, | |||
+ hostOpcodeParameterChangeGestureBegin, | |||
+ hostOpcodeParameterChangeGestureEnd, | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstProcessingSampleType | |||
+{ | |||
+ vstProcessingSampleTypeFloat, | |||
+ vstProcessingSampleTypeDouble | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+// These names must be identical to the Steinberg SDK so JUCE users can set | |||
+// exactly what they want. | |||
+enum VstPlugInCategory | |||
+{ | |||
+ kPlugCategUnknown, | |||
+ kPlugCategEffect, | |||
+ kPlugCategSynth, | |||
+ kPlugCategAnalysis, | |||
+ kPlugCategMastering, | |||
+ kPlugCategSpacializer, | |||
+ kPlugCategRoomFx, | |||
+ kPlugSurroundFx, | |||
+ kPlugCategRestoration, | |||
+ kPlugCategOfflineProcess, | |||
+ kPlugCategShell, | |||
+ kPlugCategGenerator | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEditorBounds | |||
+{ | |||
+ int16 upper; | |||
+ int16 leftmost; | |||
+ int16 lower; | |||
+ int16 rightmost; | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstMaxStringLengths | |||
+{ | |||
+ vstMaxNameLength = 64, | |||
+ vstMaxParameterOrPinLabelLength = 64, | |||
+ vstMaxParameterOrPinShortLabelLength = 8, | |||
+ vstMaxCategoryLength = 24, | |||
+ vstMaxManufacturerStringLength = 64, | |||
+ vstMaxPlugInNameStringLength = 64 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstPinInfo | |||
+{ | |||
+ char text[vstMaxParameterOrPinLabelLength]; | |||
+ int32 flags; | |||
+ int32 configurationType; | |||
+ char shortText[vstMaxParameterOrPinShortLabelLength]; | |||
+ char unused[48]; | |||
+}; | |||
+ | |||
+enum VstPinInfoFlags | |||
+{ | |||
+ vstPinInfoFlagIsActive = 1, | |||
+ vstPinInfoFlagIsStereo = 2, | |||
+ vstPinInfoFlagValid = 4 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 sampleOffset; | |||
+ int32 flags; | |||
+ char content[16]; | |||
+}; | |||
+ | |||
+enum VstEventTypes | |||
+{ | |||
+ vstMidiEventType = 1, | |||
+ vstSysExEventType = 6 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstEventBlock | |||
+{ | |||
+ int32 numberOfEvents; | |||
+ pointer_sized_int future; | |||
+ VstEvent* events[2]; | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstMidiEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 sampleOffset; | |||
+ int32 flags; | |||
+ int32 noteSampleLength; | |||
+ int32 noteSampleOffset; | |||
+ char midiData[4]; | |||
+ char tuning; | |||
+ char noteVelocityOff; | |||
+ char future1; | |||
+ char future2; | |||
+}; | |||
+ | |||
+enum VstMidiEventFlags | |||
+{ | |||
+ vstMidiEventIsRealtime = 1 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstSysExEvent | |||
+{ | |||
+ int32 type; | |||
+ int32 size; | |||
+ int32 offsetSamples; | |||
+ int32 flags; | |||
+ int32 sysExDumpSize; | |||
+ pointer_sized_int future1; | |||
+ char* sysExDump; | |||
+ pointer_sized_int future2; | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstTimingInformation | |||
+{ | |||
+ double samplePosition; | |||
+ double sampleRate; | |||
+ double systemTimeNanoseconds; | |||
+ double musicalPosition; | |||
+ double tempoBPM; | |||
+ double lastBarPosition; | |||
+ double loopStartPosition; | |||
+ double loopEndPosition; | |||
+ int32 timeSignatureNumerator; | |||
+ int32 timeSignatureDenominator; | |||
+ int32 smpteOffset; | |||
+ int32 smpteRate; | |||
+ int32 samplesToNearestClock; | |||
+ int32 flags; | |||
+}; | |||
+ | |||
+enum VstTimingInformationFlags | |||
+{ | |||
+ vstTimingInfoFlagTransportChanged = 1, | |||
+ vstTimingInfoFlagCurrentlyPlaying = 2, | |||
+ vstTimingInfoFlagLoopActive = 4, | |||
+ vstTimingInfoFlagCurrentlyRecording = 8, | |||
+ vstTimingInfoFlagAutomationWriteModeActive = 64, | |||
+ vstTimingInfoFlagAutomationReadModeActive = 128, | |||
+ vstTimingInfoFlagNanosecondsValid = 256, | |||
+ vstTimingInfoFlagMusicalPositionValid = 512, | |||
+ vstTimingInfoFlagTempoValid = 1024, | |||
+ vstTimingInfoFlagLastBarPositionValid = 2048, | |||
+ vstTimingInfoFlagLoopPositionValid = 4096, | |||
+ vstTimingInfoFlagTimeSignatureValid = 8192, | |||
+ vstTimingInfoFlagSmpteValid = 16384, | |||
+ vstTimingInfoFlagNearestClockValid = 32768 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+enum VstSmpteRates | |||
+{ | |||
+ vstSmpteRateFps24, | |||
+ vstSmpteRateFps25, | |||
+ vstSmpteRateFps2997, | |||
+ vstSmpteRateFps30, | |||
+ vstSmpteRateFps2997drop, | |||
+ vstSmpteRateFps30drop, | |||
+ | |||
+ vstSmpteRate16mmFilm, | |||
+ vstSmpteRate35mmFilm, | |||
+ | |||
+ vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, | |||
+ vstSmpteRateFps249 , | |||
+ vstSmpteRateFps599, | |||
+ vstSmpteRateFps60 | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstIndividualSpeakerInfo | |||
+{ | |||
+ float azimuthalAngle; | |||
+ float elevationAngle; | |||
+ float radius; | |||
+ float reserved; | |||
+ char label[vstMaxNameLength]; | |||
+ int32 type; | |||
+ char unused[28]; | |||
+}; | |||
+ | |||
+enum VstIndividualSpeakerType | |||
+{ | |||
+ vstIndividualSpeakerTypeUndefined = 0x7fffffff, | |||
+ vstIndividualSpeakerTypeMono = 0, | |||
+ vstIndividualSpeakerTypeLeft, | |||
+ vstIndividualSpeakerTypeRight, | |||
+ vstIndividualSpeakerTypeCentre, | |||
+ vstIndividualSpeakerTypeLFE, | |||
+ vstIndividualSpeakerTypeLeftSurround, | |||
+ vstIndividualSpeakerTypeRightSurround, | |||
+ vstIndividualSpeakerTypeLeftCentre, | |||
+ vstIndividualSpeakerTypeRightCentre, | |||
+ vstIndividualSpeakerTypeSurround, | |||
+ vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, | |||
+ vstIndividualSpeakerTypeLeftRearSurround, | |||
+ vstIndividualSpeakerTypeRightRearSurround, | |||
+ vstIndividualSpeakerTypeTopMiddle, | |||
+ vstIndividualSpeakerTypeTopFrontLeft, | |||
+ vstIndividualSpeakerTypeTopFrontCentre, | |||
+ vstIndividualSpeakerTypeTopFrontRight, | |||
+ vstIndividualSpeakerTypeTopRearLeft, | |||
+ vstIndividualSpeakerTypeTopRearCentre, | |||
+ vstIndividualSpeakerTypeTopRearRight, | |||
+ vstIndividualSpeakerTypeLFE2 | |||
+}; | |||
+ | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct VstSpeakerConfiguration | |||
+{ | |||
+ int32 type; | |||
+ int32 numberOfChannels; | |||
+ VstIndividualSpeakerInfo speakers[8]; | |||
+}; | |||
+ | |||
+enum VstSpeakerConfigurationType | |||
+{ | |||
+ vstSpeakerConfigTypeUser = -2, | |||
+ vstSpeakerConfigTypeEmpty = -1, | |||
+ vstSpeakerConfigTypeMono = 0, | |||
+ vstSpeakerConfigTypeLR, | |||
+ vstSpeakerConfigTypeLsRs, | |||
+ vstSpeakerConfigTypeLcRc, | |||
+ vstSpeakerConfigTypeSlSr, | |||
+ vstSpeakerConfigTypeCLfe, | |||
+ vstSpeakerConfigTypeLRC, | |||
+ vstSpeakerConfigTypeLRS, | |||
+ vstSpeakerConfigTypeLRCLfe, | |||
+ vstSpeakerConfigTypeLRLfeS, | |||
+ vstSpeakerConfigTypeLRCS, | |||
+ vstSpeakerConfigTypeLRLsRs, | |||
+ vstSpeakerConfigTypeLRCLfeS, | |||
+ vstSpeakerConfigTypeLRLfeLsRs, | |||
+ vstSpeakerConfigTypeLRCLsRs, | |||
+ vstSpeakerConfigTypeLRCLfeLsRs, | |||
+ vstSpeakerConfigTypeLRCLsRsCs, | |||
+ vstSpeakerConfigTypeLRLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsCs, | |||
+ vstSpeakerConfigTypeLRLfeLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLsRsLcRc, | |||
+ vstSpeakerConfigTypeLRCLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsLcRc, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsSlSr, | |||
+ vstSpeakerConfigTypeLRCLsRsLcRcCs, | |||
+ vstSpeakerConfigTypeLRCLsRsCsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, | |||
+ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 | |||
+}; | |||
+ | |||
+#if JUCE_BIG_ENDIAN | |||
+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) | |||
+#else | |||
+ #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) | |||
+#endif | |||
+ | |||
+enum PresonusExtensionConstants | |||
+{ | |||
+ presonusVendorID = JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), | |||
+ presonusSetContentScaleFactor = JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's') | |||
+}; | |||
+ | |||
+//============================================================================== | |||
+/** Structure used for VSTs | |||
+ | |||
+ @tags{Audio} | |||
+*/ | |||
+struct vst2FxBank | |||
+{ | |||
+ int32 magic1; | |||
+ int32 size; | |||
+ int32 magic2; | |||
+ int32 version1; | |||
+ int32 fxID; | |||
+ int32 version2; | |||
+ int32 elements; | |||
+ int32 current; | |||
+ char shouldBeZero[124]; | |||
+ int32 chunkSize; | |||
+ char chunk[1]; | |||
+}; | |||
+ | |||
+#if JUCE_MSVC | |||
+ #pragma pack(pop) | |||
+#elif JUCE_MAC || JUCE_IOS | |||
+ #pragma options align=reset | |||
+#else | |||
+ #pragma pack(pop) | |||
+#endif | |||
+ | |||
+#endif // JUCE_VSTINTERFACE_H_INCLUDED |
@@ -1,78 +0,0 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
index 3e63d3372..c09d99fc3 100644 | |||
--- a/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp | |||
@@ -61,10 +61,7 @@ | |||
#define __cdecl | |||
#endif | |||
- namespace Vst2 | |||
- { | |||
- #include "pluginterfaces/vst2.x/vstfxstore.h" | |||
- } | |||
+ #include <juce_audio_processors/format_types/juce_VSTInterface.h> | |||
#endif | |||
@@ -1967,16 +1964,16 @@ public: | |||
bool loadVST2CcnKBlock (const char* data, int size) | |||
{ | |||
- auto* bank = reinterpret_cast<const Vst2::fxBank*> (data); | |||
+ auto* bank = reinterpret_cast<const vst2FxBank*> (data); | |||
- jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->chunkMagic)); | |||
- jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->fxMagic)); | |||
- jassert (htonl ((uint32) bank->version) == 1 || htonl ((uint32) bank->version) == 2); | |||
+ jassert (ByteOrder::bigEndianInt ("CcnK") == htonl ((uint32) bank->magic1)); | |||
+ jassert (ByteOrder::bigEndianInt ("FBCh") == htonl ((uint32) bank->magic2)); | |||
+ jassert (htonl ((uint32) bank->version1) == 1 || htonl ((uint32) bank->version1) == 2); | |||
jassert (JucePlugin_VSTUniqueID == htonl ((uint32) bank->fxID)); | |||
- setStateInformation (bank->content.data.chunk, | |||
- jmin ((int) (size - (bank->content.data.chunk - data)), | |||
- (int) htonl ((uint32) bank->content.data.size))); | |||
+ setStateInformation (bank->chunk, | |||
+ jmin ((int) (size - (bank->chunk - data)), | |||
+ (int) htonl ((uint32) bank->chunkSize))); | |||
return true; | |||
} | |||
@@ -2172,16 +2169,16 @@ public: | |||
return status; | |||
const int bankBlockSize = 160; | |||
- Vst2::fxBank bank; | |||
+ vst2FxBank bank; | |||
zerostruct (bank); | |||
- bank.chunkMagic = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); | |||
- bank.byteSize = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); | |||
- bank.fxMagic = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); | |||
- bank.version = (int32) htonl (2); | |||
- bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); | |||
- bank.fxVersion = (int32) htonl (JucePlugin_VersionCode); | |||
- bank.content.data.size = (int32) htonl ((unsigned int) mem.getSize()); | |||
+ bank.magic1 = (int32) htonl (ByteOrder::bigEndianInt ("CcnK")); | |||
+ bank.size = (int32) htonl (bankBlockSize - 8 + (unsigned int) mem.getSize()); | |||
+ bank.magic2 = (int32) htonl (ByteOrder::bigEndianInt ("FBCh")); | |||
+ bank.version1 = (int32) htonl (2); | |||
+ bank.fxID = (int32) htonl (JucePlugin_VSTUniqueID); | |||
+ bank.version2 = (int32) htonl (JucePlugin_VersionCode); | |||
+ bank.chunkSize = (int32) htonl ((unsigned int) mem.getSize()); | |||
status = state->write (&bank, bankBlockSize); | |||
diff --git a/modules/juce_audio_processors/format_types/juce_VSTInterface.h b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
index 1b3eb7d08..58179be1a 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
+++ b/modules/juce_audio_processors/format_types/juce_VSTInterface.h | |||
@@ -506,7 +506,7 @@ enum PresonusExtensionConstants | |||
@tags{Audio} | |||
*/ | |||
-struct fxBank | |||
+struct vst2FxBank | |||
{ | |||
int32 magic1; | |||
int32 size; |
@@ -1,5 +1,5 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index 23484732b..d2106179c 100644 | |||
index 8683dc728..94f6bf569 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -61,7 +61,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996 4100) | |||
@@ -28,10 +28,10 @@ index 6bea84307..baaa59f06 100644 | |||
#if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) | |||
#error "You need to define the JucePlugin_LV2URI value!" | |||
diff --git a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
index adf058959..c39d6f796 100644 | |||
index 620ba9874..d9359b736 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
+++ b/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | |||
@@ -61,9 +61,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
@@ -62,9 +62,6 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
#ifndef WM_APPCOMMAND | |||
#define WM_APPCOMMAND 0x0319 | |||
#endif | |||
@@ -1,5 +1,5 @@ | |||
diff --git a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
index 656ab3321..c40e854aa 100644 | |||
index acd165ff0..bdc0bf58f 100644 | |||
--- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
+++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | |||
@@ -20,6 +20,11 @@ | |||
@@ -30,10 +30,10 @@ index ac5ce32da..f0cd938ae 100644 | |||
{ kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, | |||
{ kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, | |||
diff --git a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
index 892312098..411bf2097 100644 | |||
index 9aa4a338e..7cad3e7c1 100644 | |||
--- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
+++ b/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | |||
@@ -58,6 +58,10 @@ | |||
@@ -59,6 +59,10 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
namespace juce | |||
{ | |||
@@ -44,7 +44,7 @@ index 892312098..411bf2097 100644 | |||
// Change this to disable logging of various activities | |||
#ifndef AU_LOGGING | |||
#define AU_LOGGING 1 | |||
@@ -274,7 +278,7 @@ namespace AudioUnitFormatHelpers | |||
@@ -276,7 +280,7 @@ namespace AudioUnitFormatHelpers | |||
NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; | |||
NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; | |||
@@ -54,7 +54,7 @@ index 892312098..411bf2097 100644 | |||
desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); | |||
desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
index 02c3de12e..ae60fde15 100644 | |||
index 39eac8211..f556f12b3 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
@@ -1184,7 +1184,7 @@ public: | |||
@@ -67,7 +67,7 @@ index 02c3de12e..ae60fde15 100644 | |||
//============================================================================== | |||
/** Not for public use - this is called before deleting an editor component. */ | |||
diff --git a/modules/juce_core/native/juce_osx_ObjCHelpers.h b/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
index 407d263d0..f2dc28e36 100644 | |||
index a9a7c8eb7..aa068eaa0 100644 | |||
--- a/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
+++ b/modules/juce_core/native/juce_osx_ObjCHelpers.h | |||
@@ -71,7 +71,7 @@ inline NSArray* varArrayToNSArray (const var& varToParse); | |||
@@ -99,7 +99,7 @@ index 407d263d0..f2dc28e36 100644 | |||
return var (dynamicObject.get()); | |||
} | |||
diff --git a/modules/juce_core/system/juce_CompilerSupport.h b/modules/juce_core/system/juce_CompilerSupport.h | |||
index e4d87ab97..ddeef94a5 100644 | |||
index 4d96d7cb0..d76d92d49 100644 | |||
--- a/modules/juce_core/system/juce_CompilerSupport.h | |||
+++ b/modules/juce_core/system/juce_CompilerSupport.h | |||
@@ -92,7 +92,7 @@ | |||
@@ -112,7 +112,7 @@ index e4d87ab97..ddeef94a5 100644 | |||
#endif | |||
diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h | |||
index 52db7c6d6..96ce314d8 100644 | |||
index 43000cab6..0a1798ba2 100644 | |||
--- a/modules/juce_core/system/juce_PlatformDefs.h | |||
+++ b/modules/juce_core/system/juce_PlatformDefs.h | |||
@@ -99,11 +99,7 @@ namespace juce | |||
@@ -129,10 +129,10 @@ index 52db7c6d6..96ce314d8 100644 | |||
#if __GNUC__ >= 7 | |||
#define JUCE_FALLTHROUGH [[gnu::fallthrough]]; | |||
diff --git a/modules/juce_core/system/juce_TargetPlatform.h b/modules/juce_core/system/juce_TargetPlatform.h | |||
index cf610da9a..9173cc34d 100644 | |||
index 5b4d293bb..6b41688b2 100644 | |||
--- a/modules/juce_core/system/juce_TargetPlatform.h | |||
+++ b/modules/juce_core/system/juce_TargetPlatform.h | |||
@@ -144,8 +144,8 @@ | |||
@@ -145,8 +145,8 @@ | |||
#endif | |||
#if JUCE_MAC | |||
@@ -144,7 +144,7 @@ index cf610da9a..9173cc34d 100644 | |||
#error "Building for OSX 10.6 is no longer supported!" | |||
#endif | |||
diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
index 37bf13094..60467ef61 100644 | |||
index de6ffecfc..2a85d25f5 100644 | |||
--- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
+++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | |||
@@ -26,6 +26,10 @@ | |||
@@ -159,10 +159,10 @@ index 37bf13094..60467ef61 100644 | |||
// This class has been renamed from CoreGraphicsImage to avoid a symbol | |||
// collision in Pro Tools 2019.12 and possibly 2020 depending on the Pro Tools | |||
diff --git a/modules/juce_graphics/native/juce_mac_Fonts.mm b/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
index b6a918ce1..65b094343 100644 | |||
index ff0a23a9f..88142091d 100644 | |||
--- a/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
+++ b/modules/juce_graphics/native/juce_mac_Fonts.mm | |||
@@ -362,20 +362,20 @@ namespace CoreTextTypeLayout | |||
@@ -359,20 +359,20 @@ namespace CoreTextTypeLayout | |||
auto verticalJustification = text.getJustification().getOnlyVerticalFlags(); | |||
@@ -188,10 +188,10 @@ index b6a918ce1..65b094343 100644 | |||
auto frame = createCTFrame (framesetter, CGRectMake ((CGFloat) ctFrameArea.getX(), flipHeight - (CGFloat) ctFrameArea.getBottom(), | |||
diff --git a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
index 7adb2a869..48ae2951b 100644 | |||
index 26ca40630..bd5e69ab0 100644 | |||
--- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
+++ b/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | |||
@@ -1528,17 +1528,21 @@ private: | |||
@@ -1583,17 +1583,21 @@ private: | |||
case NSEventTypeSystemDefined: | |||
case NSEventTypeApplicationDefined: | |||
case NSEventTypePeriodic: | |||
@@ -215,7 +215,7 @@ index 7adb2a869..48ae2951b 100644 | |||
#if JUCE_64BIT | |||
case NSEventTypeDirectTouch: | |||
diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
index 5ffbe393c..ddb5aedc5 100644 | |||
index 918f0b4a6..63e15d07b 100644 | |||
--- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
+++ b/modules/juce_gui_basics/native/juce_mac_Windowing.mm | |||
@@ -309,7 +309,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi | |||
@@ -228,10 +228,10 @@ index 5ffbe393c..ddb5aedc5 100644 | |||
auto eventPos = [event locationInWindow]; | |||
diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
index cf8df3b72..6f8417340 100644 | |||
index b8e6e0c7e..6ad01c68a 100644 | |||
--- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
+++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
@@ -3054,7 +3054,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, | |||
@@ -3329,7 +3329,7 @@ void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XBut | |||
peer->toFront (true); | |||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, getLogicalMousePos (buttonPressEvent, peer->getPlatformScaleFactor()), | |||
ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, | |||
@@ -239,12 +239,12 @@ index cf8df3b72..6f8417340 100644 | |||
+ MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent)); | |||
} | |||
void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer<::Window>* peer, const XButtonPressedEvent& buttonPressEvent) const | |||
void XWindowSystem::handleButtonPressEvent (LinuxComponentPeer* peer, const XButtonPressedEvent& buttonPressEvent) const | |||
diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
index 3dacc095a..820987e94 100644 | |||
index 691e5679b..ed4f3c199 100644 | |||
--- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
+++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h | |||
@@ -314,7 +314,7 @@ public: | |||
@@ -321,7 +321,7 @@ public: | |||
//============================================================================== | |||
void handleMouseEvent (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, ModifierKeys newMods, float pressure, | |||
@@ -254,10 +254,10 @@ index 3dacc095a..820987e94 100644 | |||
void handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, | |||
int64 time, const MouseWheelDetails&, int touchIndex = 0); | |||
diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp | |||
index d4ebed7fd..df48cceaf 100644 | |||
index 9cf367411..d42555e13 100644 | |||
--- a/modules/juce_gui_extra/juce_gui_extra.cpp | |||
+++ b/modules/juce_gui_extra/juce_gui_extra.cpp | |||
@@ -142,7 +142,9 @@ | |||
@@ -136,7 +136,9 @@ | |||
#include "misc/juce_PushNotifications.cpp" | |||
#include "misc/juce_RecentlyOpenedFilesList.cpp" | |||
#include "misc/juce_SplashScreen.cpp" | |||
@@ -267,7 +267,7 @@ index d4ebed7fd..df48cceaf 100644 | |||
#include "misc/juce_LiveConstantEditor.cpp" | |||
#include "misc/juce_AnimatedAppComponent.cpp" | |||
@@ -154,7 +156,9 @@ | |||
@@ -146,7 +148,9 @@ | |||
#if JUCE_MAC | |||
#include "native/juce_mac_NSViewComponent.mm" | |||
#include "native/juce_mac_AppleRemote.mm" | |||
@@ -279,7 +279,7 @@ index d4ebed7fd..df48cceaf 100644 | |||
#if JUCE_IOS | |||
diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h | |||
index 822c1885e..77e78c87a 100644 | |||
index bcec491c9..062bde636 100644 | |||
--- a/modules/juce_gui_extra/juce_gui_extra.h | |||
+++ b/modules/juce_gui_extra/juce_gui_extra.h | |||
@@ -115,7 +115,9 @@ | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index 982db865b..cfdd2d1cd 100644 | |||
index 082a5697a..c79ca18ea 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -590,12 +590,39 @@ File juce_getExecutableFile() | |||
@@ -600,12 +600,39 @@ File juce_getExecutableFile() | |||
auto localSymbol = (void*) juce_getExecutableFile; | |||
dladdr (localSymbol, &exeInfo); | |||
@@ -28,7 +28,7 @@ index d2a302e3e..3dc4602ce 100644 | |||
return cpid >= 0; | |||
diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm | |||
index d26443079..616677e1f 100644 | |||
index 1a4d07516..f385a089f 100644 | |||
--- a/modules/juce_core/native/juce_mac_Files.mm | |||
+++ b/modules/juce_core/native/juce_mac_Files.mm | |||
@@ -92,23 +92,22 @@ namespace MacFileHelpers | |||
@@ -65,10 +65,10 @@ index d26443079..616677e1f 100644 | |||
#endif | |||
} | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index cfdd2d1cd..332a64959 100644 | |||
index c79ca18ea..ad46cf390 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -1092,7 +1092,18 @@ public: | |||
@@ -1104,7 +1104,18 @@ public: | |||
if (pipe (pipeHandles) == 0) | |||
{ | |||
@@ -88,7 +88,7 @@ index cfdd2d1cd..332a64959 100644 | |||
if (result < 0) | |||
{ | |||
@@ -1101,6 +1112,7 @@ public: | |||
@@ -1113,6 +1124,7 @@ public: | |||
} | |||
else if (result == 0) | |||
{ | |||
@@ -96,7 +96,7 @@ index cfdd2d1cd..332a64959 100644 | |||
// we're the child process.. | |||
close (pipeHandles[0]); // close the read handle | |||
@@ -1115,17 +1127,10 @@ public: | |||
@@ -1127,17 +1139,10 @@ public: | |||
dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO); | |||
close (pipeHandles[1]); | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index 332a64959..c6d95a93a 100644 | |||
index ad46cf390..13724ba7c 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -1227,6 +1227,11 @@ public: | |||
@@ -1239,6 +1239,11 @@ public: | |||
return 0; | |||
} | |||
@@ -15,7 +15,7 @@ index 332a64959..c6d95a93a 100644 | |||
int pipeHandle = 0; | |||
int exitCode = -1; | |||
diff --git a/modules/juce_core/native/juce_win32_Threads.cpp b/modules/juce_core/native/juce_win32_Threads.cpp | |||
index dbe0c6b5d..098416bd9 100644 | |||
index 4a4148119..1c38ff2cf 100644 | |||
--- a/modules/juce_core/native/juce_win32_Threads.cpp | |||
+++ b/modules/juce_core/native/juce_win32_Threads.cpp | |||
@@ -477,6 +477,11 @@ public: | |||
@@ -1,5 +1,5 @@ | |||
diff --git a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
index 97327c59e..0eca4dfb6 100644 | |||
index b799ee2e4..803740c63 100644 | |||
--- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | |||
@@ -26,6 +26,7 @@ | |||
@@ -10,7 +10,7 @@ index 97327c59e..0eca4dfb6 100644 | |||
static bool exeIsAvailable (String executable) | |||
{ | |||
ChildProcess child; | |||
@@ -241,10 +242,11 @@ private: | |||
@@ -245,10 +246,11 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | |||
}; | |||
@@ -23,7 +23,7 @@ index 97327c59e..0eca4dfb6 100644 | |||
return false; | |||
#else | |||
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog"); | |||
@@ -254,7 +256,11 @@ bool FileChooser::isPlatformDialogAvailable() | |||
@@ -258,7 +260,11 @@ bool FileChooser::isPlatformDialogAvailable() | |||
FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) | |||
{ | |||
@@ -1,16 +1,23 @@ | |||
diff --git a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
index 6f8417340..5e8603833 100644 | |||
index 6ad01c68a..490a3a792 100644 | |||
--- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
+++ b/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | |||
@@ -2561,8 +2561,9 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const | |||
@@ -2804,10 +2804,14 @@ void XWindowSystem::setWindowType (::Window windowH, int styleFlags) const | |||
Atom netHints [2]; | |||
if (atoms.windowType != None) | |||
{ | |||
- auto hint = (styleFlags & ComponentPeer::windowIsTemporary) != 0 | |||
- || ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) | |||
- ? XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO") | |||
- : XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); | |||
+ Atom hint = None; | |||
+ | |||
+ if (styleFlags & ComponentPeer::windowIsTemporary) | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); | |||
+ else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); | |||
+ else | |||
+ hint = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); | |||
- if ((styleFlags & ComponentPeer::windowIsTemporary) != 0 | |||
- || ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows())) | |||
+ if (styleFlags & ComponentPeer::windowIsTemporary) | |||
+ netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_TOOLTIP"); | |||
+ else if ((styleFlags & ComponentPeer::windowHasDropShadow) == 0 && Desktop::canUseSemiTransparentWindows()) | |||
netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_COMBO"); | |||
else | |||
netHints [0] = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WM_WINDOW_TYPE_NORMAL"); | |||
if (hint != None) | |||
xchangeProperty (windowH, atoms.windowType, XA_ATOM, 32, &hint, 1); |
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | |||
index c6d95a93a..9c9053ac4 100644 | |||
index 13724ba7c..f8892081c 100644 | |||
--- a/modules/juce_core/native/juce_posix_SharedCode.h | |||
+++ b/modules/juce_core/native/juce_posix_SharedCode.h | |||
@@ -977,7 +977,11 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||
@@ -989,7 +989,11 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||
if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) | |||
return false; | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index c5e96d204..7e2122580 100644 | |||
index 94f6bf569..ddbe6fd65 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -301,9 +301,6 @@ public: | |||
@@ -294,9 +294,6 @@ public: | |||
// You must at least have some channels | |||
jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); | |||
@@ -1,15 +1,17 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index cff9b7a88..3ae14372c 100644 | |||
index ebc9325ca..cabbbcf13 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -209,9 +209,9 @@ struct SharedMessageThread : public Thread | |||
@@ -203,11 +203,11 @@ struct SharedMessageThread : public Thread | |||
void run() override | |||
{ | |||
initialiseJuce_GUI(); | |||
- initialised = true; | |||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
+ initialised = true; | |||
XWindowSystem::getInstance(); | |||
+ initialised = true; | |||
while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) | |||
{} |
@@ -1,5 +1,5 @@ | |||
diff --git a/modules/juce_audio_processors/juce_audio_processors.cpp b/modules/juce_audio_processors/juce_audio_processors.cpp | |||
index ac194a19d..f494ec3a2 100644 | |||
index 43ad88e1b..149154804 100644 | |||
--- a/modules/juce_audio_processors/juce_audio_processors.cpp | |||
+++ b/modules/juce_audio_processors/juce_audio_processors.cpp | |||
@@ -47,7 +47,7 @@ | |||
@@ -11,7 +11,7 @@ index ac194a19d..f494ec3a2 100644 | |||
#include <X11/Xlib.h> | |||
#include <X11/Xutil.h> | |||
#include <sys/utsname.h> | |||
@@ -141,9 +141,11 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wcast-align" | |||
@@ -230,9 +230,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
#include "format_types/juce_LegacyAudioParameter.cpp" | |||
#include "processors/juce_AudioProcessor.cpp" | |||
#include "processors/juce_AudioPluginInstance.cpp" | |||
@@ -26,7 +26,7 @@ index ac194a19d..f494ec3a2 100644 | |||
#include "format_types/juce_LADSPAPluginFormat.cpp" | |||
#include "format_types/juce_VSTPluginFormat.cpp" | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
index caa55b618..41c547b0c 100644 | |||
index 040accf2e..df0b6c67b 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | |||
@@ -51,12 +51,14 @@ AudioProcessor::AudioProcessor (const BusesProperties& ioConfig) | |||
@@ -44,7 +44,7 @@ index caa55b618..41c547b0c 100644 | |||
#if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | |||
// This will fail if you've called beginParameterChangeGesture() for one | |||
@@ -804,6 +806,7 @@ void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChang | |||
@@ -826,6 +828,7 @@ void AudioProcessor::audioIOChanged (bool busNumberChanged, bool channelNumChang | |||
processorLayoutsChanged(); | |||
} | |||
@@ -52,7 +52,7 @@ index caa55b618..41c547b0c 100644 | |||
//============================================================================== | |||
void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept | |||
{ | |||
@@ -840,6 +843,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||
@@ -862,6 +865,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||
return ed; | |||
} | |||
@@ -61,7 +61,7 @@ index caa55b618..41c547b0c 100644 | |||
//============================================================================== | |||
void AudioProcessor::getCurrentProgramStateInformation (juce::MemoryBlock& destData) | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
index dbad562ef..ab9e8a7d6 100644 | |||
index f72a4ed3f..849b77d3c 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | |||
@@ -926,6 +926,7 @@ public: | |||
@@ -103,10 +103,10 @@ index dbad562ef..ab9e8a7d6 100644 | |||
int blockSize = 0, latencySamples = 0; | |||
bool suspended = false; | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
index fd8c35879..070f82554 100644 | |||
index 17c5af30c..3d4a0d428 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | |||
@@ -1496,8 +1496,10 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const | |||
@@ -1519,8 +1519,10 @@ bool AudioProcessorGraph::AudioGraphIOProcessor::producesMidi() const | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } | |||
@@ -118,7 +118,7 @@ index fd8c35879..070f82554 100644 | |||
int AudioProcessorGraph::AudioGraphIOProcessor::getNumPrograms() { return 0; } | |||
int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { return 0; } | |||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
index a16e35df0..8d2221f1e 100644 | |||
index a60e67d84..17a121893 100644 | |||
--- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | |||
@@ -355,8 +355,10 @@ public: | |||
@@ -1,16 +1,16 @@ | |||
diff --git a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
index 065d873e1..fb9cfc999 100644 | |||
index 0c1138a2b..a5b42b8ef 100644 | |||
--- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
+++ b/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | |||
@@ -167,6 +167,7 @@ private: | |||
Atomic<HWND> nativeDialogRef; | |||
Atomic<int> shouldCancel; | |||
@@ -172,6 +172,7 @@ private: | |||
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } | |||
}; | |||
+ #if JUCE_MSVC | |||
bool showDialog (IFileDialog& dialog, bool async) const | |||
{ | |||
FILEOPENDIALOGOPTIONS flags = {}; | |||
@@ -303,6 +304,7 @@ private: | |||
@@ -327,6 +328,7 @@ private: | |||
return result; | |||
} | |||
@@ -18,13 +18,16 @@ index 065d873e1..fb9cfc999 100644 | |||
Array<URL> openDialogPreVista (bool async) | |||
{ | |||
@@ -412,8 +414,10 @@ private: | |||
@@ -436,11 +438,13 @@ private: | |||
const Remover remover (*this); | |||
+ #if JUCE_MSVC | |||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) | |||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista | |||
&& customComponent == nullptr) | |||
{ | |||
return openDialogVistaAndUp (async); | |||
} | |||
+ #endif | |||
return openDialogPreVista (async); | |||
@@ -1,8 +1,8 @@ | |||
diff --git a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
index dbf441fb5..63d931efd 100644 | |||
index cabbbcf13..f722ab36c 100644 | |||
--- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
+++ b/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | |||
@@ -489,7 +489,7 @@ public: | |||
@@ -488,7 +488,7 @@ public: | |||
{ | |||
const int numChannels = jmax (numIn, numOut); | |||
@@ -353,8 +353,8 @@ namespace FloatVectorHelpers | |||
union signMask64 { double d; uint64 i; }; | |||
#if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
template<int typeSize> struct ModeType { using Mode = BasicOps32; }; | |||
template<> struct ModeType<8> { using Mode = BasicOps64; }; | |||
template <int typeSize> struct ModeType { using Mode = BasicOps32; }; | |||
template <> struct ModeType<8> { using Mode = BasicOps64; }; | |||
template <typename Mode> | |||
struct MinMax | |||
@@ -32,7 +32,7 @@ | |||
ID: juce_audio_basics | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE audio and MIDI data classes | |||
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||
website: http://www.juce.com/juce | |||
@@ -60,9 +60,8 @@ namespace MidiBufferHelpers | |||
if (maxBytes == 1) | |||
return 1; | |||
int n; | |||
auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); | |||
return jmin (maxBytes, n + 2 + bytesLeft); | |||
const auto var = MidiMessage::readVariableLengthValue (data + 1, maxBytes - 1); | |||
return jmin (maxBytes, var.value + 2 + var.bytesUsed); | |||
} | |||
if (byte >= 0x80) | |||
@@ -117,7 +116,7 @@ void MidiBuffer::clear (int startSample, int numSamples) | |||
auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | |||
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); | |||
data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); | |||
data.removeRange ((int) (start - data.begin()), (int) (end - start)); | |||
} | |||
void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | |||
@@ -237,4 +236,73 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio | |||
return true; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiBufferTest : public UnitTest | |||
{ | |||
MidiBufferTest() | |||
: UnitTest ("MidiBuffer", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("Clear messages"); | |||
{ | |||
const auto message = MidiMessage::noteOn (1, 64, 0.5f); | |||
const auto testBuffer = [&] | |||
{ | |||
MidiBuffer buffer; | |||
buffer.addEvent (message, 0); | |||
buffer.addEvent (message, 10); | |||
buffer.addEvent (message, 20); | |||
buffer.addEvent (message, 30); | |||
return buffer; | |||
}(); | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 0); | |||
expectEquals (buffer.getNumEvents(), 4); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 1); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 10); | |||
expectEquals (buffer.getNumEvents(), 3); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 20); | |||
expectEquals (buffer.getNumEvents(), 2); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 30); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
{ | |||
auto buffer = testBuffer; | |||
buffer.clear (10, 300); | |||
expectEquals (buffer.getNumEvents(), 1); | |||
} | |||
} | |||
} | |||
}; | |||
static MidiBufferTest midiBufferTest; | |||
#endif | |||
} // namespace juce |
@@ -46,23 +46,77 @@ namespace MidiFileHelpers | |||
} | |||
} | |||
static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept | |||
template <typename Value> | |||
struct Optional | |||
{ | |||
auto ch = ByteOrder::bigEndianInt (data); | |||
data += 4; | |||
Optional() = default; | |||
if (ch != ByteOrder::bigEndianInt ("MThd")) | |||
Optional (const Value& v) | |||
: value (v), valid (true) {} | |||
Value value = Value(); | |||
bool valid = false; | |||
}; | |||
template <typename Integral> | |||
struct ReadTrait; | |||
template <> | |||
struct ReadTrait<uint32> { static constexpr auto read = ByteOrder::bigEndianInt; }; | |||
template <> | |||
struct ReadTrait<uint16> { static constexpr auto read = ByteOrder::bigEndianShort; }; | |||
template <typename Integral> | |||
Optional<Integral> tryRead (const uint8*& data, size_t& remaining) | |||
{ | |||
using Trait = ReadTrait<Integral>; | |||
constexpr auto size = sizeof (Integral); | |||
if (remaining < size) | |||
return {}; | |||
const Optional<Integral> result { Trait::read (data) }; | |||
data += size; | |||
remaining -= size; | |||
return result; | |||
} | |||
struct HeaderDetails | |||
{ | |||
size_t bytesRead = 0; | |||
short timeFormat = 0; | |||
short fileType = 0; | |||
short numberOfTracks = 0; | |||
}; | |||
static Optional<HeaderDetails> parseMidiHeader (const uint8* const initialData, | |||
const size_t maxSize) | |||
{ | |||
auto* data = initialData; | |||
auto remaining = maxSize; | |||
auto ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
return {}; | |||
if (ch.value != ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
bool ok = false; | |||
auto ok = false; | |||
if (ch == ByteOrder::bigEndianInt ("RIFF")) | |||
if (ch.value == ByteOrder::bigEndianInt ("RIFF")) | |||
{ | |||
for (int i = 0; i < 8; ++i) | |||
{ | |||
ch = ByteOrder::bigEndianInt (data); | |||
data += 4; | |||
ch = tryRead<uint32> (data, remaining); | |||
if (! ch.valid) | |||
return {}; | |||
if (ch == ByteOrder::bigEndianInt ("MThd")) | |||
if (ch.value == ByteOrder::bigEndianInt ("MThd")) | |||
{ | |||
ok = true; | |||
break; | |||
@@ -71,21 +125,37 @@ namespace MidiFileHelpers | |||
} | |||
if (! ok) | |||
return false; | |||
return {}; | |||
} | |||
auto bytesRemaining = ByteOrder::bigEndianInt (data); | |||
data += 4; | |||
fileType = (short) ByteOrder::bigEndianShort (data); | |||
data += 2; | |||
numberOfTracks = (short) ByteOrder::bigEndianShort (data); | |||
data += 2; | |||
timeFormat = (short) ByteOrder::bigEndianShort (data); | |||
data += 2; | |||
bytesRemaining -= 6; | |||
data += bytesRemaining; | |||
return true; | |||
const auto bytesRemaining = tryRead<uint32> (data, remaining); | |||
if (! bytesRemaining.valid || bytesRemaining.value > remaining) | |||
return {}; | |||
const auto optFileType = tryRead<uint16> (data, remaining); | |||
if (! optFileType.valid || 2 < optFileType.value) | |||
return {}; | |||
const auto optNumTracks = tryRead<uint16> (data, remaining); | |||
if (! optNumTracks.valid || (optFileType.value == 0 && optNumTracks.value != 1)) | |||
return {}; | |||
const auto optTimeFormat = tryRead<uint16> (data, remaining); | |||
if (! optTimeFormat.valid) | |||
return {}; | |||
HeaderDetails result; | |||
result.fileType = (short) optFileType.value; | |||
result.timeFormat = (short) optTimeFormat.value; | |||
result.numberOfTracks = (short) optNumTracks.value; | |||
result.bytesRead = maxSize - remaining; | |||
return { result }; | |||
} | |||
static double convertTicksToSeconds (double time, | |||
@@ -149,6 +219,47 @@ namespace MidiFileHelpers | |||
} | |||
} | |||
} | |||
static MidiMessageSequence readTrack (const uint8* data, int size) | |||
{ | |||
double time = 0; | |||
uint8 lastStatusByte = 0; | |||
MidiMessageSequence result; | |||
while (size > 0) | |||
{ | |||
const auto delay = MidiMessage::readVariableLengthValue (data, (int) size); | |||
if (! delay.isValid()) | |||
break; | |||
data += delay.bytesUsed; | |||
size -= delay.bytesUsed; | |||
time += delay.value; | |||
if (size <= 0) | |||
break; | |||
int messSize = 0; | |||
const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||
if (messSize <= 0) | |||
break; | |||
size -= messSize; | |||
data += messSize; | |||
result.addEvent (mm); | |||
auto firstByte = *(mm.getRawData()); | |||
if ((firstByte & 0xf0) != 0xf0) | |||
lastStatusByte = firstByte; | |||
} | |||
return result; | |||
} | |||
} | |||
//============================================================================== | |||
@@ -253,78 +364,56 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) | |||
const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||
// (put a sanity-check on the file size, as midi files are generally small) | |||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
{ | |||
auto size = data.getSize(); | |||
auto d = static_cast<const uint8*> (data.getData()); | |||
short fileType, expectedTracks; | |||
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) | |||
{ | |||
size -= (size_t) (d - static_cast<const uint8*> (data.getData())); | |||
int track = 0; | |||
for (;;) | |||
{ | |||
auto chunkType = (int) ByteOrder::bigEndianInt (d); | |||
d += 4; | |||
auto chunkSize = (int) ByteOrder::bigEndianInt (d); | |||
d += 4; | |||
if (! sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
return false; | |||
if (chunkSize <= 0 || (size_t) chunkSize > size) | |||
break; | |||
auto size = data.getSize(); | |||
auto d = static_cast<const uint8*> (data.getData()); | |||
if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) | |||
readNextTrack (d, chunkSize, createMatchingNoteOffs); | |||
const auto optHeader = MidiFileHelpers::parseMidiHeader (d, size); | |||
if (++track >= expectedTracks) | |||
break; | |||
if (! optHeader.valid) | |||
return false; | |||
size -= (size_t) chunkSize + 8; | |||
d += chunkSize; | |||
} | |||
const auto header = optHeader.value; | |||
timeFormat = header.timeFormat; | |||
return true; | |||
} | |||
} | |||
d += header.bytesRead; | |||
size -= (size_t) header.bytesRead; | |||
return false; | |||
} | |||
for (int track = 0; track < header.numberOfTracks; ++track) | |||
{ | |||
const auto optChunkType = MidiFileHelpers::tryRead<uint32> (d, size); | |||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||
{ | |||
double time = 0; | |||
uint8 lastStatusByte = 0; | |||
if (! optChunkType.valid) | |||
return false; | |||
MidiMessageSequence result; | |||
const auto optChunkSize = MidiFileHelpers::tryRead<uint32> (d, size); | |||
while (size > 0) | |||
{ | |||
int bytesUsed; | |||
auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed); | |||
data += bytesUsed; | |||
size -= bytesUsed; | |||
time += delay; | |||
if (! optChunkSize.valid) | |||
return false; | |||
int messSize = 0; | |||
const MidiMessage mm (data, size, messSize, lastStatusByte, time); | |||
const auto chunkSize = optChunkSize.value; | |||
if (messSize <= 0) | |||
break; | |||
if (size < chunkSize) | |||
return false; | |||
size -= messSize; | |||
data += messSize; | |||
if (optChunkType.value == ByteOrder::bigEndianInt ("MTrk")) | |||
readNextTrack (d, (int) chunkSize, createMatchingNoteOffs); | |||
result.addEvent (mm); | |||
size -= chunkSize; | |||
d += chunkSize; | |||
} | |||
auto firstByte = *(mm.getRawData()); | |||
return size == 0; | |||
} | |||
if ((firstByte & 0xf0) != 0xf0) | |||
lastStatusByte = firstByte; | |||
} | |||
void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) | |||
{ | |||
auto sequence = MidiFileHelpers::readTrack (data, size); | |||
// sort so that we put all the note-offs before note-ons that have the same time | |||
std::stable_sort (result.list.begin(), result.list.end(), | |||
std::stable_sort (sequence.list.begin(), sequence.list.end(), | |||
[] (const MidiMessageSequence::MidiEventHolder* a, | |||
const MidiMessageSequence::MidiEventHolder* b) | |||
{ | |||
@@ -337,10 +426,10 @@ void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNo | |||
return a->message.isNoteOff() && b->message.isNoteOn(); | |||
}); | |||
addTrack (result); | |||
if (createMatchingNoteOffs) | |||
tracks.getLast()->updateMatchedPairs(); | |||
sequence.updateMatchedPairs(); | |||
addTrack (sequence); | |||
} | |||
//============================================================================== | |||
@@ -443,4 +532,267 @@ bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) | |||
return true; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiFileTest : public UnitTest | |||
{ | |||
MidiFileTest() | |||
: UnitTest ("MidiFile", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
beginTest ("ReadTrack respects running status"); | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
MidiFileHelpers::writeVariableLengthInt (os, 100); | |||
writeBytes (os, { 0x90, 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 200); | |||
writeBytes (os, { 0x40, 0x40 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 300); | |||
writeBytes (os, { 0xff, 0x2f, 0x00 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 3); | |||
expect (sequence.getEventPointer (0)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (1)->message.isNoteOn()); | |||
expect (sequence.getEventPointer (2)->message.isEndOfTrackMetaEvent()); | |||
} | |||
beginTest ("ReadTrack returns available messages if input is truncated"); | |||
{ | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Incomplete delta time | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with no following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 0); | |||
} | |||
{ | |||
const auto sequence = parseSequence ([] (OutputStream& os) | |||
{ | |||
// Complete delta with malformed following event | |||
MidiFileHelpers::writeVariableLengthInt (os, 0xffff); | |||
writeBytes (os, { 0x90, 0x40 }); | |||
}); | |||
expectEquals (sequence.getNumEvents(), 1); | |||
expect (sequence.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (sequence.getEventPointer (0)->message.getNoteNumber(), 0x40); | |||
expectEquals (sequence.getEventPointer (0)->message.getVelocity(), (uint8) 0x00); | |||
} | |||
} | |||
beginTest ("Header parsing works"); | |||
{ | |||
{ | |||
// No data | |||
const auto header = parseHeader ([] (OutputStream&) {}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Invalid initial byte | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 0xff }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Type block, but no header data | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// We (ll-formed header, but track type is 0 and channels != 1 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 0, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Well-formed header, but track type is 5 | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 5, 0, 16, 0, 1 }); | |||
}); | |||
expect (! header.valid); | |||
} | |||
{ | |||
// Well-formed header | |||
const auto header = parseHeader ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 16, 0, 1 }); | |||
}); | |||
expect (header.valid); | |||
expectEquals (header.value.fileType, (short) 1); | |||
expectEquals (header.value.numberOfTracks, (short) 16); | |||
expectEquals (header.value.timeFormat, (short) 1); | |||
expectEquals ((int) header.value.bytesRead, 14); | |||
} | |||
} | |||
beginTest ("Read from stream"); | |||
{ | |||
{ | |||
// Empty input | |||
const auto file = parseFile ([] (OutputStream&) {}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Malformed header | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd' }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, no channels | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 0, 0, 1 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 0); | |||
} | |||
{ | |||
// Header, one malformed channel | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', '?' }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, one channel with malformed message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 1, 0xff }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
expectEquals (file.value.getTrack (0)->getNumEvents(), 0); | |||
} | |||
{ | |||
// Header, one channel with incorrect length message | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0x0f, 0, 0, 0, 0xff }); | |||
}); | |||
expect (! file.valid); | |||
} | |||
{ | |||
// Header, one channel, all well-formed | |||
const auto file = parseFile ([] (OutputStream& os) | |||
{ | |||
writeBytes (os, { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1, 0, 1, 0, 1 }); | |||
writeBytes (os, { 'M', 'T', 'r', 'k', 0, 0, 0, 4 }); | |||
MidiFileHelpers::writeVariableLengthInt (os, 0x0f); | |||
writeBytes (os, { 0x80, 0x00, 0x00 }); | |||
}); | |||
expect (file.valid); | |||
expectEquals (file.value.getNumTracks(), 1); | |||
auto& track = *file.value.getTrack (0); | |||
expectEquals (track.getNumEvents(), 1); | |||
expect (track.getEventPointer (0)->message.isNoteOff()); | |||
expectEquals (track.getEventPointer (0)->message.getTimeStamp(), (double) 0x0f); | |||
} | |||
} | |||
} | |||
template <typename Fn> | |||
static MidiMessageSequence parseSequence (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::readTrack (reinterpret_cast<const uint8*> (os.getData()), | |||
(int) os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFileHelpers::HeaderDetails> parseHeader (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
return MidiFileHelpers::parseMidiHeader (reinterpret_cast<const uint8*> (os.getData()), | |||
os.getDataSize()); | |||
} | |||
template <typename Fn> | |||
static MidiFileHelpers::Optional<MidiFile> parseFile (Fn&& fn) | |||
{ | |||
MemoryOutputStream os; | |||
fn (os); | |||
MemoryInputStream is (os.getData(), os.getDataSize(), false); | |||
MidiFile mf; | |||
if (mf.readFrom (is)) | |||
return mf; | |||
return {}; | |||
} | |||
static void writeBytes (OutputStream& os, const std::vector<uint8>& bytes) | |||
{ | |||
for (const auto& byte : bytes) | |||
os.writeByte ((char) byte); | |||
} | |||
}; | |||
static MidiFileTest midiFileTests; | |||
#endif | |||
} // namespace juce |
@@ -57,6 +57,31 @@ uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, | |||
} | |||
//============================================================================== | |||
MidiMessage::VariableLengthValue MidiMessage::readVariableLengthValue (const uint8* data, int maxBytesToUse) noexcept | |||
{ | |||
uint32 v = 0; | |||
// The largest allowable variable-length value is 0x0f'ff'ff'ff which is | |||
// represented by the 4-byte stream 0xff 0xff 0xff 0x7f. | |||
// Longer bytestreams risk overflowing a 32-bit signed int. | |||
const auto limit = jmin (maxBytesToUse, 4); | |||
for (int numBytesUsed = 0; numBytesUsed < limit; ++numBytesUsed) | |||
{ | |||
const auto i = data[numBytesUsed]; | |||
v = (v << 7) + (i & 0x7f); | |||
if (! (i & 0x80)) | |||
return { (int) v, numBytesUsed + 1 }; | |||
} | |||
// If this is hit, the input was malformed. Either there were not enough | |||
// bytes of input to construct a full value, or no terminating byte was | |||
// found. This implementation only supports variable-length values of up | |||
// to four bytes. | |||
return {}; | |||
} | |||
int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept | |||
{ | |||
numBytesUsed = 0; | |||
@@ -224,16 +249,8 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||
} | |||
else if (byte == 0xff) | |||
{ | |||
if (sz == 1) | |||
{ | |||
size = 1; | |||
} | |||
else | |||
{ | |||
int n; | |||
const int bytesLeft = readVariableLengthVal (src + 1, n); | |||
size = jmin (sz + 1, n + 2 + bytesLeft); | |||
} | |||
const auto bytesLeft = readVariableLengthValue (src + 1, sz - 1); | |||
size = jmin (sz + 1, bytesLeft.bytesUsed + 2 + bytesLeft.value); | |||
auto dest = allocateSpace (size); | |||
*dest = (uint8) byte; | |||
@@ -682,7 +699,7 @@ bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0x | |||
int MidiMessage::getMetaEventType() const noexcept | |||
{ | |||
auto data = getRawData(); | |||
return *data != 0xff ? -1 : data[1]; | |||
return (size < 2 || *data != 0xff) ? -1 : data[1]; | |||
} | |||
int MidiMessage::getMetaEventLength() const noexcept | |||
@@ -691,8 +708,8 @@ int MidiMessage::getMetaEventLength() const noexcept | |||
if (*data == 0xff) | |||
{ | |||
int n; | |||
return jmin (size - 2, readVariableLengthVal (data + 2, n)); | |||
const auto var = readVariableLengthValue (data + 2, size - 2); | |||
return jmax (0, jmin (size - 2 - var.bytesUsed, var.value)); | |||
} | |||
return 0; | |||
@@ -702,10 +719,9 @@ const uint8* MidiMessage::getMetaEventData() const noexcept | |||
{ | |||
jassert (isMetaEvent()); | |||
int n; | |||
auto d = getRawData() + 2; | |||
readVariableLengthVal (d, n); | |||
return d + n; | |||
const auto var = readVariableLengthValue (d, size - 2); | |||
return d + var.bytesUsed; | |||
} | |||
bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } | |||
@@ -1141,4 +1157,172 @@ const char* MidiMessage::getControllerName (const int n) | |||
return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
struct MidiMessageTest : public UnitTest | |||
{ | |||
MidiMessageTest() | |||
: UnitTest ("MidiMessage", UnitTestCategories::midi) | |||
{} | |||
void runTest() override | |||
{ | |||
using std::begin; | |||
using std::end; | |||
beginTest ("ReadVariableLengthValue should return valid, backward-compatible results"); | |||
{ | |||
const std::vector<uint8> inputs[] | |||
{ | |||
{ 0x00 }, | |||
{ 0x40 }, | |||
{ 0x7f }, | |||
{ 0x81, 0x00 }, | |||
{ 0xc0, 0x00 }, | |||
{ 0xff, 0x7f }, | |||
{ 0x81, 0x80, 0x00 }, | |||
{ 0xc0, 0x80, 0x00 }, | |||
{ 0xff, 0xff, 0x7f }, | |||
{ 0x81, 0x80, 0x80, 0x00 }, | |||
{ 0xc0, 0x80, 0x80, 0x00 }, | |||
{ 0xff, 0xff, 0xff, 0x7f } | |||
}; | |||
const int outputs[] | |||
{ | |||
0x00, | |||
0x40, | |||
0x7f, | |||
0x80, | |||
0x2000, | |||
0x3fff, | |||
0x4000, | |||
0x100000, | |||
0x1fffff, | |||
0x200000, | |||
0x8000000, | |||
0xfffffff, | |||
}; | |||
expectEquals (std::distance (begin (inputs), end (inputs)), | |||
std::distance (begin (outputs), end (outputs))); | |||
size_t index = 0; | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | |||
for (const auto& input : inputs) | |||
{ | |||
auto copy = input; | |||
while (copy.size() < 16) | |||
copy.push_back (0); | |||
const auto result = MidiMessage::readVariableLengthValue (copy.data(), | |||
(int) copy.size()); | |||
expect (result.isValid()); | |||
expectEquals (result.value, outputs[index]); | |||
expectEquals (result.bytesUsed, (int) inputs[index].size()); | |||
int legacyNumUsed = 0; | |||
const auto legacyResult = MidiMessage::readVariableLengthVal (copy.data(), | |||
legacyNumUsed); | |||
expectEquals (result.value, legacyResult); | |||
expectEquals (result.bytesUsed, legacyNumUsed); | |||
++index; | |||
} | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
} | |||
beginTest ("ReadVariableLengthVal should return 0 if input is truncated"); | |||
{ | |||
for (size_t i = 0; i != 16; ++i) | |||
{ | |||
std::vector<uint8> input; | |||
input.resize (i, 0xFF); | |||
const auto result = MidiMessage::readVariableLengthValue (input.data(), | |||
(int) input.size()); | |||
expect (! result.isValid()); | |||
expectEquals (result.value, 0); | |||
expectEquals (result.bytesUsed, 0); | |||
} | |||
} | |||
const std::vector<uint8> metaEvents[] | |||
{ | |||
// Format is 0xff, followed by a 'kind' byte, followed by a variable-length | |||
// 'data-length' value, followed by that many data bytes | |||
{ 0xff, 0x00, 0x02, 0x00, 0x00 }, // Sequence number | |||
{ 0xff, 0x01, 0x00 }, // Text event | |||
{ 0xff, 0x02, 0x00 }, // Copyright notice | |||
{ 0xff, 0x03, 0x00 }, // Track name | |||
{ 0xff, 0x04, 0x00 }, // Instrument name | |||
{ 0xff, 0x05, 0x00 }, // Lyric | |||
{ 0xff, 0x06, 0x00 }, // Marker | |||
{ 0xff, 0x07, 0x00 }, // Cue point | |||
{ 0xff, 0x20, 0x01, 0x00 }, // Channel prefix | |||
{ 0xff, 0x2f, 0x00 }, // End of track | |||
{ 0xff, 0x51, 0x03, 0x01, 0x02, 0x03 }, // Set tempo | |||
{ 0xff, 0x54, 0x05, 0x01, 0x02, 0x03, 0x04, 0x05 }, // SMPTE offset | |||
{ 0xff, 0x58, 0x04, 0x01, 0x02, 0x03, 0x04 }, // Time signature | |||
{ 0xff, 0x59, 0x02, 0x01, 0x02 }, // Key signature | |||
{ 0xff, 0x7f, 0x00 }, // Sequencer-specific | |||
}; | |||
beginTest ("MidiMessage data constructor works for well-formed meta-events"); | |||
{ | |||
const auto status = (uint8) 0x90; | |||
for (const auto& input : metaEvents) | |||
{ | |||
int bytesUsed = 0; | |||
const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status); | |||
expect (msg.isMetaEvent()); | |||
expectEquals (msg.getMetaEventLength(), (int) input.size() - 3); | |||
expectEquals (msg.getMetaEventType(), (int) input[1]); | |||
} | |||
} | |||
beginTest ("MidiMessage data constructor works for malformed meta-events"); | |||
{ | |||
const auto status = (uint8) 0x90; | |||
const auto runTest = [&] (const std::vector<uint8>& input) | |||
{ | |||
int bytesUsed = 0; | |||
const MidiMessage msg (input.data(), (int) input.size(), bytesUsed, status); | |||
expect (msg.isMetaEvent()); | |||
expectEquals (msg.getMetaEventLength(), jmax (0, (int) input.size() - 3)); | |||
expectEquals (msg.getMetaEventType(), input.size() >= 2 ? input[1] : -1); | |||
}; | |||
runTest ({ 0xff }); | |||
for (const auto& input : metaEvents) | |||
{ | |||
auto copy = input; | |||
copy[2] = 0x40; // Set the size of the message to more bytes than are present | |||
runTest (copy); | |||
} | |||
} | |||
} | |||
}; | |||
static MidiMessageTest midiMessageTests; | |||
#endif | |||
} // namespace juce |
@@ -858,11 +858,45 @@ public: | |||
//============================================================================== | |||
/** Reads a midi variable-length integer. | |||
@param data the data to read the number from | |||
@param numBytesUsed on return, this will be set to the number of bytes that were read | |||
This signature has been deprecated in favour of the safer | |||
readVariableLengthValue. | |||
The `data` argument indicates the data to read the number from, | |||
and `numBytesUsed` is used as an out-parameter to indicate the number | |||
of bytes that were read. | |||
*/ | |||
static int readVariableLengthVal (const uint8* data, | |||
int& numBytesUsed) noexcept; | |||
JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data, | |||
int& numBytesUsed) noexcept); | |||
/** Holds information about a variable-length value which was parsed | |||
from a stream of bytes. | |||
A valid value requires that `bytesUsed` is greater than 0. | |||
*/ | |||
struct VariableLengthValue | |||
{ | |||
VariableLengthValue() = default; | |||
VariableLengthValue (int valueIn, int bytesUsedIn) | |||
: value (valueIn), bytesUsed (bytesUsedIn) {} | |||
bool isValid() const noexcept { return bytesUsed > 0; } | |||
int value = 0; | |||
int bytesUsed = 0; | |||
}; | |||
/** Reads a midi variable-length integer, with protection against buffer overflow. | |||
@param data the data to read the number from | |||
@param maxBytesToUse the number of bytes in the region following `data` | |||
@returns a struct containing the parsed value, and the number | |||
of bytes that were read. If parsing fails, both the | |||
`value` and `bytesUsed` fields will be set to 0 and | |||
`isValid()` will return false | |||
*/ | |||
static VariableLengthValue readVariableLengthValue (const uint8* data, | |||
int maxBytesToUse) noexcept; | |||
/** Based on the first byte of a short midi message, this uses a lookup table | |||
to return the message length (either 1, 2, or 3 bytes). | |||
@@ -27,30 +27,33 @@ namespace | |||
{ | |||
const uint8 noLSBValueReceived = 0xff; | |||
const Range<int> allChannels { 1, 17 }; | |||
template <typename Range, typename Value> | |||
void mpeInstrumentFill (Range& range, const Value& value) | |||
{ | |||
std::fill (std::begin (range), std::end (range), value); | |||
} | |||
} | |||
//============================================================================== | |||
MPEInstrument::MPEInstrument() noexcept | |||
{ | |||
std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived); | |||
std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived); | |||
std::fill_n (isMemberChannelSustained, 16, false); | |||
mpeInstrumentFill (lastPressureLowerBitReceivedOnChannel, noLSBValueReceived); | |||
mpeInstrumentFill (lastTimbreLowerBitReceivedOnChannel, noLSBValueReceived); | |||
mpeInstrumentFill (isMemberChannelSustained, false); | |||
pitchbendDimension.value = &MPENote::pitchbend; | |||
pressureDimension.value = &MPENote::pressure; | |||
timbreDimension.value = &MPENote::timbre; | |||
// the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) | |||
std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); | |||
resetLastReceivedValues(); | |||
legacyMode.isEnabled = false; | |||
legacyMode.pitchbendRange = 2; | |||
legacyMode.channelRange = allChannels; | |||
} | |||
MPEInstrument::~MPEInstrument() | |||
{ | |||
} | |||
MPEInstrument::~MPEInstrument() = default; | |||
//============================================================================== | |||
MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept | |||
@@ -58,6 +61,23 @@ MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept | |||
return zoneLayout; | |||
} | |||
void MPEInstrument::resetLastReceivedValues() | |||
{ | |||
struct Defaults | |||
{ | |||
MPEDimension& dimension; | |||
MPEValue defaultValue; | |||
}; | |||
// The default value for pressure is 0, for all other dimensions it is centre | |||
for (const auto& pair : { Defaults { pressureDimension, MPEValue::minValue() }, | |||
Defaults { pitchbendDimension, MPEValue::centreValue() }, | |||
Defaults { timbreDimension, MPEValue::centreValue() } }) | |||
{ | |||
mpeInstrumentFill (pair.dimension.lastValueReceivedOnChannel, pair.defaultValue); | |||
} | |||
} | |||
void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) | |||
{ | |||
releaseAllNotes(); | |||
@@ -65,6 +85,8 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) | |||
const ScopedLock sl (lock); | |||
legacyMode.isEnabled = false; | |||
zoneLayout = newLayout; | |||
resetLastReceivedValues(); | |||
} | |||
//============================================================================== | |||
@@ -376,6 +376,8 @@ private: | |||
LegacyMode legacyMode; | |||
MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | |||
void resetLastReceivedValues(); | |||
void updateDimension (int midiChannel, MPEDimension&, MPEValue); | |||
void updateDimensionMaster (bool, MPEDimension&, MPEValue); | |||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||
@@ -199,7 +199,7 @@ public: | |||
@see getNextSample | |||
*/ | |||
template<typename FloatType> | |||
template <typename FloatType> | |||
void applyEnvelopeToBuffer (AudioBuffer<FloatType>& buffer, int startSample, int numSamples) | |||
{ | |||
jassert (startSample + numSamples <= buffer.getNumSamples()); | |||
@@ -37,7 +37,7 @@ public: | |||
} | |||
private: | |||
template<typename InterpolatorType> | |||
template <typename InterpolatorType> | |||
void runInterplatorTests (const String& interpolatorName) | |||
{ | |||
auto createGaussian = [] (std::vector<float>& destination, float scale, float centreInSamples, float width) | |||
@@ -29,7 +29,7 @@ struct LagrangeResampleHelper | |||
static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } | |||
}; | |||
template<> | |||
template <> | |||
struct LagrangeResampleHelper<0> | |||
{ | |||
static forcedinline void calc (float&, float) noexcept {} | |||
@@ -339,7 +339,7 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||
midiDeviceInfosFromXml.clear(); | |||
enabledMidiInputs.clear(); | |||
forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") | |||
for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) | |||
midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); | |||
auto isIdentifierAvailable = [] (const Array<MidiDeviceInfo>& available, const String& identifier) | |||
@@ -704,7 +704,7 @@ void AudioDeviceManager::restartLastAudioDevice() | |||
{ | |||
// This method will only reload the last device that was running | |||
// before closeAudioDevice() was called - you need to actually open | |||
// one first, with setAudioDevice(). | |||
// one first, with setAudioDeviceSetup(). | |||
jassertfalse; | |||
return; | |||
} | |||
@@ -1146,12 +1146,19 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCall | |||
void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) | |||
{ | |||
for (auto& device : MidiInput::getAvailableDevices()) | |||
if (name.isEmpty()) | |||
{ | |||
if (device.name == name) | |||
removeMidiInputDeviceCallback ({}, callbackToRemove); | |||
} | |||
else | |||
{ | |||
for (auto& device : MidiInput::getAvailableDevices()) | |||
{ | |||
removeMidiInputDeviceCallback (device.identifier, callbackToRemove); | |||
return; | |||
if (device.name == name) | |||
{ | |||
removeMidiInputDeviceCallback (device.identifier, callbackToRemove); | |||
return; | |||
} | |||
} | |||
} | |||
} | |||
@@ -267,7 +267,7 @@ public: | |||
Note that this only reloads the last device that was running before | |||
closeAudioDevice() was called - it doesn't reload any kind of saved-state, | |||
and can only be called after a device has been opened with SetAudioDevice(). | |||
and can only be called after a device has been opened with setAudioDeviceSetup(). | |||
If a device is already open, this call will do nothing. | |||
*/ | |||
@@ -80,9 +80,8 @@ public: | |||
The scanForDevices() method must have been called to create this list. | |||
@param wantInputNames only really used by DirectSound where devices are split up | |||
into inputs and outputs, this indicates whether to use | |||
the input or output name to refer to a pair of devices. | |||
@param wantInputNames for devices which have separate inputs and outputs | |||
this determines which list of names is returned | |||
*/ | |||
virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; | |||
@@ -47,6 +47,35 @@ | |||
#include "native/juce_MidiDataConcatenator.h" | |||
#include "midi_io/ump/juce_UMPProtocols.h" | |||
#include "midi_io/ump/juce_UMPUtils.h" | |||
#include "midi_io/ump/juce_UMPacket.h" | |||
#include "midi_io/ump/juce_UMPSysEx7.h" | |||
#include "midi_io/ump/juce_UMPView.h" | |||
#include "midi_io/ump/juce_UMPIterator.h" | |||
#include "midi_io/ump/juce_UMPackets.h" | |||
#include "midi_io/ump/juce_UMPFactory.h" | |||
#include "midi_io/ump/juce_UMPConversion.h" | |||
#include "midi_io/ump/juce_UMPMidi1ToBytestreamTranslator.h" | |||
#include "midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.h" | |||
#include "midi_io/ump/juce_UMPConverters.h" | |||
#include "midi_io/ump/juce_UMPDispatcher.h" | |||
#include "midi_io/ump/juce_UMPReceiver.h" | |||
#include "midi_io/ump/juce_UMPBytestreamInputHandler.h" | |||
#include "midi_io/ump/juce_UMPU32InputHandler.h" | |||
#include "midi_io/ump/juce_UMPUtils.cpp" | |||
#include "midi_io/ump/juce_UMPView.cpp" | |||
#include "midi_io/ump/juce_UMPSysEx7.cpp" | |||
#include "midi_io/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" | |||
#include "midi_io/ump/juce_UMPTests.cpp" | |||
namespace juce | |||
{ | |||
namespace ump = universal_midi_packets; | |||
} | |||
//============================================================================== | |||
#if JUCE_MAC | |||
#define Point CarbonDummyPointName | |||
@@ -58,7 +87,7 @@ | |||
#undef Component | |||
#include "native/juce_mac_CoreAudio.cpp" | |||
#include "native/juce_mac_CoreMidi.cpp" | |||
#include "native/juce_mac_CoreMidi.mm" | |||
#elif JUCE_IOS | |||
#import <AudioToolbox/AudioToolbox.h> | |||
@@ -70,7 +99,7 @@ | |||
#endif | |||
#include "native/juce_ios_Audio.cpp" | |||
#include "native/juce_mac_CoreMidi.cpp" | |||
#include "native/juce_mac_CoreMidi.mm" | |||
//============================================================================== | |||
#elif JUCE_WINDOWS | |||
@@ -32,7 +32,7 @@ | |||
ID: juce_audio_devices | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE audio and MIDI I/O device classes | |||
description: Classes to play and record from audio and MIDI I/O devices | |||
website: http://www.juce.com/juce | |||
@@ -0,0 +1,134 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
A base class for classes which convert bytestream midi to other formats. | |||
*/ | |||
struct BytestreamInputHandler | |||
{ | |||
virtual ~BytestreamInputHandler() noexcept = default; | |||
virtual void reset() = 0; | |||
virtual void pushMidiData (const void* data, int bytes, double time) = 0; | |||
}; | |||
/** | |||
Parses a continuous bytestream and emits complete MidiMessages whenever a full | |||
message is received. | |||
*/ | |||
struct BytestreamToBytestreamHandler : public BytestreamInputHandler | |||
{ | |||
BytestreamToBytestreamHandler (MidiInput& i, MidiInputCallback& c) | |||
: input (i), callback (c), concatenator (2048) {} | |||
/** | |||
Provides an `operator()` which can create an input handler for a given | |||
MidiInput. | |||
All handler classes should have a similar Factory to facilitate | |||
creation of handlers in generic contexts. | |||
*/ | |||
class Factory | |||
{ | |||
public: | |||
explicit Factory (MidiInputCallback* c) | |||
: callback (c) {} | |||
std::unique_ptr<BytestreamToBytestreamHandler> operator() (MidiInput& i) const | |||
{ | |||
if (callback != nullptr) | |||
return std::make_unique<BytestreamToBytestreamHandler> (i, *callback); | |||
jassertfalse; | |||
return {}; | |||
} | |||
private: | |||
MidiInputCallback* callback = nullptr; | |||
}; | |||
void reset() override { concatenator.reset(); } | |||
void pushMidiData (const void* data, int bytes, double time) override | |||
{ | |||
concatenator.pushMidiData (data, bytes, time, &input, callback); | |||
} | |||
MidiInput& input; | |||
MidiInputCallback& callback; | |||
MidiDataConcatenator concatenator; | |||
}; | |||
/** | |||
Parses a continuous MIDI 1.0 bytestream, and emits full messages in the requested | |||
UMP format. | |||
*/ | |||
struct BytestreamToUMPHandler : public BytestreamInputHandler | |||
{ | |||
BytestreamToUMPHandler (PacketProtocol protocol, Receiver& c) | |||
: recipient (c), dispatcher (protocol, 2048) {} | |||
/** | |||
Provides an `operator()` which can create an input handler for a given | |||
MidiInput. | |||
All handler classes should have a similar Factory to facilitate | |||
creation of handlers in generic contexts. | |||
*/ | |||
class Factory | |||
{ | |||
public: | |||
Factory (PacketProtocol p, Receiver& c) | |||
: protocol (p), callback (c) {} | |||
std::unique_ptr<BytestreamToUMPHandler> operator() (MidiInput&) const | |||
{ | |||
return std::make_unique<BytestreamToUMPHandler> (protocol, callback); | |||
} | |||
private: | |||
PacketProtocol protocol; | |||
Receiver& callback; | |||
}; | |||
void reset() override { dispatcher.reset(); } | |||
void pushMidiData (const void* data, int bytes, double time) override | |||
{ | |||
const auto* ptr = static_cast<const uint8_t*> (data); | |||
dispatcher.dispatch (ptr, ptr + bytes, time, [&] (const View& v) | |||
{ | |||
recipient.packetReceived (v, time); | |||
}); | |||
} | |||
Receiver& recipient; | |||
BytestreamToUMPDispatcher dispatcher; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,326 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Functions to assist conversion of UMP messages to/from other formats, | |||
especially older 'bytestream' formatted MidiMessages. | |||
@tags{Audio} | |||
*/ | |||
struct Conversion | |||
{ | |||
/** Converts from a MIDI 1 bytestream to MIDI 1 on Universal MIDI Packets. | |||
`callback` is a function which accepts a single View argument. | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
static void toMidi1 (const MidiMessage& m, PacketCallbackFunction&& callback) | |||
{ | |||
const auto* data = m.getRawData(); | |||
const auto firstByte = data[0]; | |||
const auto size = m.getRawDataSize(); | |||
if (firstByte != 0xf0) | |||
{ | |||
const auto mask = [size]() -> uint32_t | |||
{ | |||
switch (size) | |||
{ | |||
case 0: return 0xff000000; | |||
case 1: return 0xffff0000; | |||
case 2: return 0xffffff00; | |||
case 3: return 0xffffffff; | |||
} | |||
return 0x00000000; | |||
}(); | |||
const auto extraByte = (uint8_t) ((((firstByte & 0xf0) == 0xf0) ? 0x1 : 0x2) << 0x4); | |||
const PacketX1 packet { mask & Utils::bytesToWord (extraByte, data[0], data[1], data[2]) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
const auto numSysExBytes = m.getSysExDataSize(); | |||
const auto numMessages = SysEx7::getNumPacketsRequiredForDataSize ((uint32_t) numSysExBytes); | |||
auto* dataOffset = m.getSysExData(); | |||
if (numMessages <= 1) | |||
{ | |||
const auto packet = Factory::makeSysExIn1Packet (0, (uint8_t) numSysExBytes, dataOffset); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
constexpr auto byteIncrement = 6; | |||
for (auto i = numSysExBytes; i > 0; i -= byteIncrement, dataOffset += byteIncrement) | |||
{ | |||
const auto func = [&] | |||
{ | |||
if (i == numSysExBytes) | |||
return Factory::makeSysExStart; | |||
if (i <= byteIncrement) | |||
return Factory::makeSysExEnd; | |||
return Factory::makeSysExContinue; | |||
}(); | |||
const auto bytesNow = std::min (byteIncrement, i); | |||
const auto packet = func (0, (uint8_t) bytesNow, dataOffset); | |||
callback (View (packet.data())); | |||
} | |||
} | |||
/** Converts a MidiMessage to one or more messages in UMP format, using | |||
the MIDI 1.0 Protocol. | |||
`packets` is an out-param to allow the caller to control | |||
allocation/deallocation. Returning a new Packets object would | |||
require every call to toMidi1 to allocate. With this version, no | |||
allocations will occur, provided that `packets` has adequate reserved | |||
space. | |||
*/ | |||
static void toMidi1 (const MidiMessage& m, Packets& packets) | |||
{ | |||
toMidi1 (m, [&] (const View& view) { packets.add (view); }); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 8-bit MIDI 2.0 value. */ | |||
static uint8_t scaleTo8 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint8_t) (word7Bit << 0x1); | |||
const auto repeat = (uint8_t) (word7Bit & 0x3f); | |||
const auto mask = (uint8_t) (word7Bit <= 0x40 ? 0x0 : 0xff); | |||
return (uint8_t) (shifted | ((repeat >> 5) & mask)); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | |||
static uint16_t scaleTo16 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint16_t) (word7Bit << 0x9); | |||
const auto repeat = (uint16_t) (word7Bit & 0x3f); | |||
const auto mask = (uint16_t) (word7Bit <= 0x40 ? 0x0 : 0xffff); | |||
return (uint16_t) (shifted | (((repeat << 3) | (repeat >> 3)) & mask)); | |||
} | |||
/** Widens a 14-bit MIDI 1.0 value to a 16-bit MIDI 2.0 value. */ | |||
static uint16_t scaleTo16 (uint16_t word14Bit) | |||
{ | |||
const auto shifted = (uint16_t) (word14Bit << 0x2); | |||
const auto repeat = (uint16_t) (word14Bit & 0x1fff); | |||
const auto mask = (uint16_t) (word14Bit <= 0x2000 ? 0x0 : 0xffff); | |||
return (uint16_t) (shifted | ((repeat >> 11) & mask)); | |||
} | |||
/** Widens a 7-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | |||
static uint32_t scaleTo32 (uint8_t word7Bit) | |||
{ | |||
const auto shifted = (uint32_t) (word7Bit << 0x19); | |||
const auto repeat = (uint32_t) (word7Bit & 0x3f); | |||
const auto mask = (uint32_t) (word7Bit <= 0x40 ? 0x0 : 0xffffffff); | |||
return (uint32_t) (shifted | (((repeat << 19) | |||
| (repeat << 13) | |||
| (repeat << 7) | |||
| (repeat << 1) | |||
| (repeat >> 5)) & mask)); | |||
} | |||
/** Widens a 14-bit MIDI 1.0 value to a 32-bit MIDI 2.0 value. */ | |||
static uint32_t scaleTo32 (uint16_t word14Bit) | |||
{ | |||
const auto shifted = (uint32_t) (word14Bit << 0x12); | |||
const auto repeat = (uint32_t) (word14Bit & 0x1fff); | |||
const auto mask = (uint32_t) (word14Bit <= 0x2000 ? 0x0 : 0xffffffff); | |||
return (uint32_t) (shifted | (((repeat << 5) | (repeat >> 8)) & mask)); | |||
} | |||
/** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint8_t word8Bit) { return (uint8_t) (word8Bit >> 1); } | |||
/** Narrows a 16-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint16_t word16Bit) { return (uint8_t) (word16Bit >> 9); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 7-bit MIDI 1.0 value. */ | |||
static uint8_t scaleTo7 (uint32_t word32Bit) { return (uint8_t) (word32Bit >> 25); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | |||
static uint16_t scaleTo14 (uint16_t word16Bit) { return (uint16_t) (word16Bit >> 2); } | |||
/** Narrows a 32-bit MIDI 2.0 value to a 14-bit MIDI 1.0 value. */ | |||
static uint16_t scaleTo14 (uint32_t word32Bit) { return (uint16_t) (word32Bit >> 18); } | |||
/** Converts UMP messages which may include MIDI 2.0 channel voice messages into | |||
equivalent MIDI 1.0 messages (still in UMP format). | |||
`callback` is a function that accepts a single View argument and will be | |||
called with each converted packet. | |||
Note that not all MIDI 2.0 messages have MIDI 1.0 equivalents, so such | |||
messages will be ignored. | |||
*/ | |||
template <typename Callback> | |||
static void midi2ToMidi1DefaultTranslation (const View& v, Callback&& callback) | |||
{ | |||
const auto firstWord = v[0]; | |||
if (Utils::getMessageType (firstWord) != 0x4) | |||
{ | |||
callback (v); | |||
return; | |||
} | |||
const auto status = Utils::getStatus (firstWord); | |||
const auto typeAndGroup = (uint8_t) ((0x2 << 0x4) | Utils::getGroup (firstWord)); | |||
switch (status) | |||
{ | |||
case 0x8: // note off | |||
case 0x9: // note on | |||
case 0xa: // poly pressure | |||
case 0xb: // control change | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const auto byte2 = (uint8_t) ((firstWord >> 0x08) & 0xff); | |||
const auto byte3 = scaleTo7 (v[1]); | |||
// If this is a note-on, and the scaled byte is 0, | |||
// the scaled velocity should be 1 instead of 0 | |||
const auto needsCorrection = status == 0x9 && byte3 == 0; | |||
const auto correctedByte = (uint8_t) (needsCorrection ? 1 : byte3); | |||
const auto shouldIgnore = status == 0xb && [&] | |||
{ | |||
switch (byte2) | |||
{ | |||
case 0: | |||
case 6: | |||
case 32: | |||
case 38: | |||
case 98: | |||
case 99: | |||
case 100: | |||
case 101: | |||
return true; | |||
} | |||
return false; | |||
}(); | |||
if (shouldIgnore) | |||
return; | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
byte2, | |||
correctedByte) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xd: // channel pressure | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const auto byte2 = scaleTo7 (v[1]); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
byte2, | |||
0) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0x2: // rpn | |||
case 0x3: // nrpn | |||
{ | |||
const auto ccX = (uint8_t) (status == 0x2 ? 101 : 99); | |||
const auto ccY = (uint8_t) (status == 0x2 ? 100 : 98); | |||
const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | |||
const auto data = scaleTo14 (v[1]); | |||
const PacketX1 packets[] | |||
{ | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccX, (uint8_t) ((firstWord >> 0x8) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, ccY, (uint8_t) ((firstWord >> 0x0) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 6, (uint8_t) ((data >> 0x7) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 38, (uint8_t) ((data >> 0x0) & 0x7f)) }, | |||
}; | |||
for (const auto& packet : packets) | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xc: // program change / bank select | |||
{ | |||
if (firstWord & 1) | |||
{ | |||
const auto statusAndChannel = (uint8_t) ((0xb << 0x4) | Utils::getChannel (firstWord)); | |||
const auto secondWord = v[1]; | |||
const PacketX1 packets[] | |||
{ | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 0, (uint8_t) ((secondWord >> 0x8) & 0x7f)) }, | |||
PacketX1 { Utils::bytesToWord (typeAndGroup, statusAndChannel, 32, (uint8_t) ((secondWord >> 0x0) & 0x7f)) }, | |||
}; | |||
for (const auto& packet : packets) | |||
callback (View (packet.data())); | |||
} | |||
const auto statusAndChannel = (uint8_t) ((0xc << 0x4) | Utils::getChannel (firstWord)); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
(uint8_t) ((v[1] >> 0x18) & 0x7f), | |||
0) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xe: // pitch bend | |||
{ | |||
const auto data = scaleTo14 (v[1]); | |||
const auto statusAndChannel = (uint8_t) ((firstWord >> 0x10) & 0xff); | |||
const PacketX1 packet { Utils::bytesToWord (typeAndGroup, | |||
statusAndChannel, | |||
(uint8_t) (data & 0x7f), | |||
(uint8_t) ((data >> 7) & 0x7f)) }; | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
default: // other message types do not translate | |||
return; | |||
} | |||
} | |||
}; | |||
} | |||
} |
@@ -0,0 +1,165 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to MIDI 1.0 messages in UMP format. | |||
@tags{Audio} | |||
*/ | |||
struct ToUMP1Converter | |||
{ | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
Conversion::toMidi1 (m, std::forward<Fn> (fn)); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
Conversion::midi2ToMidi1DefaultTranslation (v, std::forward<Fn> (fn)); | |||
} | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to MIDI 2.0 messages in UMP format. | |||
@tags{Audio} | |||
*/ | |||
struct ToUMP2Converter | |||
{ | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
Conversion::toMidi1 (m, [&] (const View& v) | |||
{ | |||
translator.dispatch (v, fn); | |||
}); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
translator.dispatch (v, std::forward<Fn> (fn)); | |||
} | |||
void reset() | |||
{ | |||
translator.reset(); | |||
} | |||
Midi1ToMidi2DefaultTranslator translator; | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to UMP format. | |||
The packet protocol can be selected using the constructor parameter. | |||
@tags{Audio} | |||
*/ | |||
class GenericUMPConverter | |||
{ | |||
public: | |||
explicit GenericUMPConverter (PacketProtocol m) | |||
: mode (m) {} | |||
void reset() | |||
{ | |||
std::get<1> (converters).reset(); | |||
} | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
switch (mode) | |||
{ | |||
case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (m, std::forward<Fn> (fn)); | |||
case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (m, std::forward<Fn> (fn)); | |||
} | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, Fn&& fn) | |||
{ | |||
switch (mode) | |||
{ | |||
case PacketProtocol::MIDI_1_0: return std::get<0> (converters).convert (v, std::forward<Fn> (fn)); | |||
case PacketProtocol::MIDI_2_0: return std::get<1> (converters).convert (v, std::forward<Fn> (fn)); | |||
} | |||
} | |||
template <typename Fn> | |||
void convert (Iterator begin, Iterator end, Fn&& fn) | |||
{ | |||
std::for_each (begin, end, [&] (const View& v) | |||
{ | |||
convert (v, fn); | |||
}); | |||
} | |||
PacketProtocol getProtocol() const noexcept { return mode; } | |||
private: | |||
std::tuple<ToUMP1Converter, ToUMP2Converter> converters; | |||
const PacketProtocol mode{}; | |||
}; | |||
/** | |||
Allows conversion from bytestream- or Universal MIDI Packet-formatted | |||
messages to bytestream format. | |||
@tags{Audio} | |||
*/ | |||
struct ToBytestreamConverter | |||
{ | |||
explicit ToBytestreamConverter (int storageSize) | |||
: translator (storageSize) {} | |||
template <typename Fn> | |||
void convert (const MidiMessage& m, Fn&& fn) | |||
{ | |||
fn (m); | |||
} | |||
template <typename Fn> | |||
void convert (const View& v, double time, Fn&& fn) | |||
{ | |||
Conversion::midi2ToMidi1DefaultTranslation (v, [&] (const View& midi1) | |||
{ | |||
translator.dispatch (midi1, time, fn); | |||
}); | |||
} | |||
void reset() { translator.reset(); } | |||
Midi1ToBytestreamTranslator translator; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,190 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Parses a raw stream of uint32_t, and calls a user-provided callback every time | |||
a full Universal MIDI Packet is encountered. | |||
@tags{Audio} | |||
*/ | |||
class Dispatcher | |||
{ | |||
public: | |||
/** Clears the dispatcher. */ | |||
void reset() { currentPacketLen = 0; } | |||
/** Calls `callback` with a View of each packet encountered in the range delimited | |||
by `begin` and `end`. | |||
If the range ends part-way through a packet, the next call to `dispatch` will | |||
continue from that point in the packet (unless `reset` is called first). | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
void dispatch (const uint32_t* begin, | |||
const uint32_t* end, | |||
double timeStamp, | |||
PacketCallbackFunction&& callback) | |||
{ | |||
std::for_each (begin, end, [&] (uint32_t word) | |||
{ | |||
nextPacket[currentPacketLen++] = word; | |||
if (currentPacketLen == Utils::getNumWordsForMessageType (nextPacket.front())) | |||
{ | |||
callback (View (nextPacket.data()), timeStamp); | |||
currentPacketLen = 0; | |||
} | |||
}); | |||
} | |||
private: | |||
std::array<uint32_t, 4> nextPacket; | |||
size_t currentPacketLen = 0; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of bytes representing a sequence of bytestream-encoded MIDI 1.0 messages, | |||
converting the messages to UMP format and passing the packets to a user-provided callback | |||
as they become ready. | |||
@tags{Audio} | |||
*/ | |||
class BytestreamToUMPDispatcher | |||
{ | |||
public: | |||
/** Initialises the dispatcher. | |||
Channel messages will be converted to the requested protocol format `pp`. | |||
`storageSize` bytes will be allocated to store incomplete messages. | |||
*/ | |||
explicit BytestreamToUMPDispatcher (PacketProtocol pp, int storageSize) | |||
: concatenator (storageSize), | |||
converter (pp) | |||
{} | |||
void reset() | |||
{ | |||
concatenator.reset(); | |||
converter.reset(); | |||
} | |||
/** Calls `callback` with a View of each converted packet as it becomes ready. | |||
@param begin the first byte in a range of bytes representing bytestream-encoded MIDI messages. | |||
@param end one-past the last byte in a range of bytes representing bytestream-encoded MIDI messages. | |||
@param timestamp a timestamp to apply to the created packets. | |||
@param callback a callback which will be passed a View pointing to each new packet as it becomes ready. | |||
*/ | |||
template <typename PacketCallbackFunction> | |||
void dispatch (const uint8_t* begin, | |||
const uint8_t* end, | |||
double timestamp, | |||
PacketCallbackFunction&& callback) | |||
{ | |||
using CallbackPtr = decltype (std::addressof (callback)); | |||
struct Callback | |||
{ | |||
Callback (BytestreamToUMPDispatcher& d, CallbackPtr c) | |||
: dispatch (d), callbackPtr (c) {} | |||
void handleIncomingMidiMessage (void*, const MidiMessage& msg) const | |||
{ | |||
Conversion::toMidi1 (msg, [&] (const View& view) | |||
{ | |||
dispatch.converter.convert (view, *callbackPtr); | |||
}); | |||
} | |||
void handlePartialSysexMessage (void*, const uint8_t*, int, double) const {} | |||
BytestreamToUMPDispatcher& dispatch; | |||
CallbackPtr callbackPtr = nullptr; | |||
}; | |||
Callback inputCallback { *this, &callback }; | |||
concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback); | |||
} | |||
private: | |||
MidiDataConcatenator concatenator; | |||
GenericUMPConverter converter; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of 32-bit words representing a sequence of UMP-encoded MIDI messages, | |||
converting the messages to MIDI 1.0 bytestream format and passing them to a user-provided | |||
callback as they become ready. | |||
@tags{Audio} | |||
*/ | |||
class ToBytestreamDispatcher | |||
{ | |||
public: | |||
/** Initialises the dispatcher. | |||
`storageSize` bytes will be allocated to store incomplete messages. | |||
*/ | |||
explicit ToBytestreamDispatcher (int storageSize) | |||
: converter (storageSize) {} | |||
/** Clears the dispatcher. */ | |||
void reset() | |||
{ | |||
dispatcher.reset(); | |||
converter.reset(); | |||
} | |||
/** Calls `callback` with converted bytestream-formatted MidiMessage whenever | |||
a new message becomes available. | |||
@param begin the first word in a stream of words representing UMP-encoded MIDI packets. | |||
@param end one-past the last word in a stream of words representing UMP-encoded MIDI packets. | |||
@param timestamp a timestamp to apply to converted messages. | |||
@param callback a callback which will be passed a MidiMessage each time a new message becomes ready. | |||
*/ | |||
template <typename BytestreamMessageCallback> | |||
void dispatch (const uint32_t* begin, | |||
const uint32_t* end, | |||
double timestamp, | |||
BytestreamMessageCallback&& callback) | |||
{ | |||
dispatcher.dispatch (begin, end, timestamp, [&] (const View& view, double time) | |||
{ | |||
converter.convert (view, time, callback); | |||
}); | |||
} | |||
private: | |||
Dispatcher dispatcher; | |||
ToBytestreamConverter converter; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,534 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
This struct holds functions that can be used to create different kinds | |||
of Universal MIDI Packet. | |||
@tags{Audio} | |||
*/ | |||
struct Factory | |||
{ | |||
/** @internal */ | |||
struct Detail | |||
{ | |||
static PacketX1 makeSystem() { return PacketX1{}.withMessageType (1); } | |||
static PacketX1 makeV1() { return PacketX1{}.withMessageType (2); } | |||
static PacketX2 makeV2() { return PacketX2{}.withMessageType (4); } | |||
static PacketX2 makeSysEx (uint8_t group, | |||
uint8_t status, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
jassert (numBytes <= 6); | |||
std::array<uint8_t, 8> bytes{{}}; | |||
bytes[0] = (0x3 << 0x4) | group; | |||
bytes[1] = (uint8_t) (status << 0x4) | numBytes; | |||
std::memcpy (bytes.data() + 2, data, numBytes); | |||
std::array<uint32_t, 2> words; | |||
size_t index = 0; | |||
for (auto& word : words) | |||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); | |||
return PacketX2 { words }; | |||
} | |||
static PacketX4 makeSysEx8 (uint8_t group, | |||
uint8_t status, | |||
uint8_t numBytes, | |||
uint8_t dataStart, | |||
const uint8_t* data) | |||
{ | |||
jassert (numBytes <= 16 - dataStart); | |||
std::array<uint8_t, 16> bytes{{}}; | |||
bytes[0] = (0x5 << 0x4) | group; | |||
bytes[1] = (uint8_t) (status << 0x4) | numBytes; | |||
std::memcpy (bytes.data() + dataStart, data, numBytes); | |||
std::array<uint32_t, 4> words; | |||
size_t index = 0; | |||
for (auto& word : words) | |||
word = ByteOrder::bigEndianInt (bytes.data() + 4 * index++); | |||
return PacketX4 { words }; | |||
} | |||
}; | |||
static PacketX1 makeNoop (uint8_t group) | |||
{ | |||
return PacketX1{}.withGroup (group); | |||
} | |||
static PacketX1 makeJRClock (uint8_t group, uint16_t time) | |||
{ | |||
return PacketX1 { time }.withStatus (1).withGroup (group); | |||
} | |||
static PacketX1 makeJRTimestamp (uint8_t group, uint16_t time) | |||
{ | |||
return PacketX1 { time }.withStatus (2).withGroup (group); | |||
} | |||
static PacketX1 makeTimeCode (uint8_t group, uint8_t code) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf1) | |||
.withU8<2> (code & 0x7f); | |||
} | |||
static PacketX1 makeSongPositionPointer (uint8_t group, uint16_t pos) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf2) | |||
.withU8<2> (pos & 0x7f) | |||
.withU8<3> ((pos >> 7) & 0x7f); | |||
} | |||
static PacketX1 makeSongSelect (uint8_t group, uint8_t song) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf3) | |||
.withU8<2> (song & 0x7f); | |||
} | |||
static PacketX1 makeTuneRequest (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf6); | |||
} | |||
static PacketX1 makeTimingClock (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xf8); | |||
} | |||
static PacketX1 makeStart (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfa); | |||
} | |||
static PacketX1 makeContinue (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfb); | |||
} | |||
static PacketX1 makeStop (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfc); | |||
} | |||
static PacketX1 makeActiveSensing (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xfe); | |||
} | |||
static PacketX1 makeReset (uint8_t group) | |||
{ | |||
return Detail::makeSystem().withGroup (group) | |||
.withU8<1> (0xff); | |||
} | |||
static PacketX1 makeNoteOffV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t velocity) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0x8) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (velocity & 0x7f); | |||
} | |||
static PacketX1 makeNoteOnV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t velocity) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0x9) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (velocity & 0x7f); | |||
} | |||
static PacketX1 makePolyPressureV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t pressure) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xa) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (pressure & 0x7f); | |||
} | |||
static PacketX1 makeControlChangeV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t controller, | |||
uint8_t value) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xb) | |||
.withChannel (channel) | |||
.withU8<2> (controller & 0x7f) | |||
.withU8<3> (value & 0x7f); | |||
} | |||
static PacketX1 makeProgramChangeV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t program) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xc) | |||
.withChannel (channel) | |||
.withU8<2> (program & 0x7f); | |||
} | |||
static PacketX1 makeChannelPressureV1 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t pressure) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xd) | |||
.withChannel (channel) | |||
.withU8<2> (pressure & 0x7f); | |||
} | |||
static PacketX1 makePitchBend (uint8_t group, | |||
uint8_t channel, | |||
uint16_t pitchbend) | |||
{ | |||
return Detail::makeV1().withGroup (group) | |||
.withStatus (0xe) | |||
.withChannel (channel) | |||
.withU8<2> (pitchbend & 0x7f) | |||
.withU8<3> ((pitchbend >> 7) & 0x7f); | |||
} | |||
static PacketX2 makeSysExIn1Packet (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x0, numBytes, data); | |||
} | |||
static PacketX2 makeSysExStart (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x1, numBytes, data); | |||
} | |||
static PacketX2 makeSysExContinue (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x2, numBytes, data); | |||
} | |||
static PacketX2 makeSysExEnd (uint8_t group, | |||
uint8_t numBytes, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx (group, 0x3, numBytes, data); | |||
} | |||
static PacketX2 makeRegisteredPerNoteControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x0) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeAssignablePerNoteControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x1) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRegisteredControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x2) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeAssignableControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x3) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRelativeRegisteredControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x4) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeRelativeAssignableControllerV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t bank, | |||
uint8_t index, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x5) | |||
.withChannel (channel) | |||
.withU8<2> (bank & 0x7f) | |||
.withU8<3> (index & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePerNotePitchBendV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x6) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
enum class NoteAttributeKind : uint8_t | |||
{ | |||
none = 0x00, | |||
manufacturer = 0x01, | |||
profile = 0x02, | |||
pitch7_9 = 0x03 | |||
}; | |||
static PacketX2 makeNoteOffV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
NoteAttributeKind attribute, | |||
uint16_t velocity, | |||
uint16_t attributeValue) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x8) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> ((uint8_t) attribute) | |||
.withU16<2> (velocity) | |||
.withU16<3> (attributeValue); | |||
} | |||
static PacketX2 makeNoteOnV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
NoteAttributeKind attribute, | |||
uint16_t velocity, | |||
uint16_t attributeValue) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0x9) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU8<3> ((uint8_t) attribute) | |||
.withU16<2> (velocity) | |||
.withU16<3> (attributeValue); | |||
} | |||
static PacketX2 makePolyPressureV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xa) | |||
.withChannel (channel) | |||
.withU8<2> (note & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeControlChangeV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t controller, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xb) | |||
.withChannel (channel) | |||
.withU8<2> (controller & 0x7f) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makeProgramChangeV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t optionFlags, | |||
uint8_t program, | |||
uint8_t bankMsb, | |||
uint8_t bankLsb) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xc) | |||
.withChannel (channel) | |||
.withU8<3> (optionFlags) | |||
.withU8<4> (program) | |||
.withU8<6> (bankMsb) | |||
.withU8<7> (bankLsb); | |||
} | |||
static PacketX2 makeChannelPressureV2 (uint8_t group, | |||
uint8_t channel, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xd) | |||
.withChannel (channel) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePitchBendV2 (uint8_t group, | |||
uint8_t channel, | |||
uint32_t data) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xe) | |||
.withChannel (channel) | |||
.withU32<1> (data); | |||
} | |||
static PacketX2 makePerNoteManagementV2 (uint8_t group, | |||
uint8_t channel, | |||
uint8_t note, | |||
uint8_t optionFlags) | |||
{ | |||
return Detail::makeV2().withGroup (group) | |||
.withStatus (0xf) | |||
.withChannel (channel) | |||
.withU8<2> (note) | |||
.withU8<3> (optionFlags); | |||
} | |||
static PacketX4 makeSysEx8in1Packet (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x0, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8Start (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x1, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8Continue (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x2, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeSysEx8End (uint8_t group, | |||
uint8_t numBytes, | |||
uint8_t streamId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x3, numBytes, 3, data).withU8<2> (streamId); | |||
} | |||
static PacketX4 makeMixedDataSetHeader (uint8_t group, | |||
uint8_t dataSetId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x8, 14, 2, data).withChannel (dataSetId); | |||
} | |||
static PacketX4 makeDataSetPayload (uint8_t group, | |||
uint8_t dataSetId, | |||
const uint8_t* data) | |||
{ | |||
return Detail::makeSysEx8 (group, 0x9, 14, 2, data).withChannel (dataSetId); | |||
} | |||
}; | |||
} | |||
} |
@@ -0,0 +1,126 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Enables iteration over a collection of Universal MIDI Packets stored as | |||
a contiguous range of 32-bit words. | |||
This iterator is used by Packets to allow access to the messages | |||
that it contains. | |||
@tags{Audio} | |||
*/ | |||
class Iterator | |||
{ | |||
public: | |||
/** Creates an invalid (singular) iterator. */ | |||
Iterator() noexcept = default; | |||
/** Creates an iterator pointing at `ptr`. */ | |||
explicit Iterator (const uint32_t* ptr, size_t bytes) noexcept | |||
: view (ptr) | |||
#if JUCE_DEBUG | |||
, bytesRemaining (bytes) | |||
#endif | |||
{ | |||
ignoreUnused (bytes); | |||
} | |||
using difference_type = std::iterator_traits<const uint32_t*>::difference_type; | |||
using value_type = View; | |||
using reference = const View&; | |||
using pointer = const View*; | |||
using iterator_category = std::input_iterator_tag; | |||
/** Moves this iterator to the next packet in the range. */ | |||
Iterator& operator++() noexcept | |||
{ | |||
const auto increment = view.size(); | |||
#if JUCE_DEBUG | |||
// If you hit this, the memory region contained a truncated or otherwise | |||
// malformed Universal MIDI Packet. | |||
// The Iterator can only be used on regions containing complete packets! | |||
jassert (increment <= bytesRemaining); | |||
bytesRemaining -= increment; | |||
#endif | |||
view = View (view.data() + increment); | |||
return *this; | |||
} | |||
/** Moves this iterator to the next packet in the range, | |||
returning the value of the iterator before it was | |||
incremented. | |||
*/ | |||
Iterator operator++ (int) noexcept | |||
{ | |||
auto copy = *this; | |||
++(*this); | |||
return copy; | |||
} | |||
/** Returns true if this iterator points to the same address | |||
as another iterator. | |||
*/ | |||
bool operator== (const Iterator& other) const noexcept | |||
{ | |||
return view == other.view; | |||
} | |||
/** Returns false if this iterator points to the same address | |||
as another iterator. | |||
*/ | |||
bool operator!= (const Iterator& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
/** Returns a reference to a View of the packet currently | |||
pointed-to by this iterator. | |||
The View can be queried for its size and content. | |||
*/ | |||
reference operator*() noexcept { return view; } | |||
/** Returns a pointer to a View of the packet currently | |||
pointed-to by this iterator. | |||
The View can be queried for its size and content. | |||
*/ | |||
pointer operator->() noexcept { return &view; } | |||
private: | |||
View view; | |||
#if JUCE_DEBUG | |||
size_t bytesRemaining = 0; | |||
#endif | |||
}; | |||
} | |||
} |
@@ -0,0 +1,213 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Parses a raw stream of uint32_t holding a series of Universal MIDI Packets using | |||
the MIDI 1.0 Protocol, converting to plain (non-UMP) MidiMessages. | |||
@tags{Audio} | |||
*/ | |||
class Midi1ToBytestreamTranslator | |||
{ | |||
public: | |||
/** Ensures that there is room in the internal buffer for a sysex message of at least | |||
`initialBufferSize` bytes. | |||
*/ | |||
explicit Midi1ToBytestreamTranslator (int initialBufferSize) | |||
{ | |||
pendingSysExData.reserve (size_t (initialBufferSize)); | |||
} | |||
/** Clears the concatenator. */ | |||
void reset() | |||
{ | |||
pendingSysExData.clear(); | |||
pendingSysExTime = 0.0; | |||
} | |||
/** Converts a Universal MIDI Packet using the MIDI 1.0 Protocol to | |||
an equivalent MidiMessage. Accumulates SysEx packets into a single | |||
MidiMessage, as appropriate. | |||
@param packet a packet which is using the MIDI 1.0 Protocol. | |||
@param time the timestamp to be applied to these messages. | |||
@param callback a callback which will be called with each converted MidiMessage. | |||
*/ | |||
template <typename MessageCallback> | |||
void dispatch (const View& packet, double time, MessageCallback&& callback) | |||
{ | |||
const auto firstWord = *packet.data(); | |||
if (! pendingSysExData.empty() && shouldPacketTerminateSysExEarly (firstWord)) | |||
pendingSysExData.clear(); | |||
switch (packet.size()) | |||
{ | |||
case 1: | |||
{ | |||
// Utility messages don't translate to bytestream format | |||
if (Utils::getMessageType (firstWord) != 0x00) | |||
callback (fromUmp (PacketX1 { firstWord }, time)); | |||
break; | |||
} | |||
case 2: | |||
{ | |||
if (Utils::getMessageType (firstWord) == 0x3) | |||
processSysEx (PacketX2 { packet[0], packet[1] }, time, callback); | |||
break; | |||
} | |||
case 3: // no 3-word packets in the current spec | |||
case 4: // no 4-word packets translate to bytestream format | |||
default: | |||
break; | |||
} | |||
} | |||
/** Converts from a Universal MIDI Packet to MIDI 1 bytestream format. | |||
This is only capable of converting a single Universal MIDI Packet to | |||
an equivalent bytestream MIDI message. This function cannot understand | |||
multi-packet messages, like SysEx7 messages. | |||
To convert multi-packet messages, use `Midi1ToBytestreamTranslator` | |||
to convert from a UMP MIDI 1.0 stream, or `ToBytestreamDispatcher` | |||
to convert from both MIDI 2.0 and MIDI 1.0. | |||
*/ | |||
static MidiMessage fromUmp (const PacketX1& m, double time = 0) | |||
{ | |||
const auto word = m.front(); | |||
jassert (Utils::getNumWordsForMessageType (word) == 1); | |||
const std::array<uint8_t, 3> bytes { { uint8_t ((word >> 0x10) & 0xff), | |||
uint8_t ((word >> 0x08) & 0xff), | |||
uint8_t ((word >> 0x00) & 0xff) } }; | |||
const auto numBytes = MidiMessage::getMessageLengthFromFirstByte (bytes.front()); | |||
return MidiMessage (bytes.data(), numBytes, time); | |||
} | |||
private: | |||
template <typename MessageCallback> | |||
void processSysEx (const PacketX2& packet, | |||
double time, | |||
MessageCallback&& callback) | |||
{ | |||
switch (getSysEx7Kind (packet[0])) | |||
{ | |||
case SysEx7::Kind::complete: | |||
startSysExMessage (time); | |||
pushBytes (packet); | |||
terminateSysExMessage (callback); | |||
break; | |||
case SysEx7::Kind::begin: | |||
startSysExMessage (time); | |||
pushBytes (packet); | |||
break; | |||
case SysEx7::Kind::continuation: | |||
if (pendingSysExData.empty()) | |||
break; | |||
pushBytes (packet); | |||
break; | |||
case SysEx7::Kind::end: | |||
if (pendingSysExData.empty()) | |||
break; | |||
pushBytes (packet); | |||
terminateSysExMessage (callback); | |||
break; | |||
} | |||
} | |||
void pushBytes (const PacketX2& packet) | |||
{ | |||
const auto bytes = SysEx7::getDataBytes (packet); | |||
pendingSysExData.insert (pendingSysExData.end(), | |||
bytes.data.begin(), | |||
bytes.data.begin() + bytes.size); | |||
} | |||
void startSysExMessage (double time) | |||
{ | |||
pendingSysExTime = time; | |||
pendingSysExData.push_back (0xf0); | |||
} | |||
template <typename MessageCallback> | |||
void terminateSysExMessage (MessageCallback&& callback) | |||
{ | |||
pendingSysExData.push_back (0xf7); | |||
callback (MidiMessage (pendingSysExData.data(), | |||
int (pendingSysExData.size()), | |||
pendingSysExTime)); | |||
pendingSysExData.clear(); | |||
} | |||
static bool shouldPacketTerminateSysExEarly (uint32_t firstWord) | |||
{ | |||
return ! (isSysExContinuation (firstWord) | |||
|| isSystemRealTime (firstWord) | |||
|| isJROrNOP (firstWord)); | |||
} | |||
static SysEx7::Kind getSysEx7Kind (uint32_t word) | |||
{ | |||
return SysEx7::Kind ((word >> 0x14) & 0xf); | |||
} | |||
static bool isJROrNOP (uint32_t word) | |||
{ | |||
return Utils::getMessageType (word) == 0x0; | |||
} | |||
static bool isSysExContinuation (uint32_t word) | |||
{ | |||
if (Utils::getMessageType (word) != 0x3) | |||
return false; | |||
const auto kind = getSysEx7Kind (word); | |||
return kind == SysEx7::Kind::continuation || kind == SysEx7::Kind::end; | |||
} | |||
static bool isSystemRealTime (uint32_t word) | |||
{ | |||
return Utils::getMessageType (word) == 0x1 && ((word >> 0x10) & 0xff) >= 0xf8; | |||
} | |||
std::vector<uint8_t> pendingSysExData; | |||
double pendingSysExTime = 0.0; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,195 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processNoteOnOrOff (const HelperValues helpers) | |||
{ | |||
const auto velocity = helpers.byte2; | |||
const auto needsConversion = (helpers.byte0 >> 0x4) == 0x9 && velocity == 0; | |||
const auto firstByte = needsConversion ? (uint8_t) ((0x8 << 0x4) | (helpers.byte0 & 0xf)) | |||
: helpers.byte0; | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, firstByte, helpers.byte1, 0), | |||
(uint32_t) (Conversion::scaleTo16 (velocity) << 0x10) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processPolyPressure (const HelperValues helpers) | |||
{ | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, helpers.byte1, 0), | |||
Conversion::scaleTo32 (helpers.byte2) | |||
}; | |||
} | |||
bool Midi1ToMidi2DefaultTranslator::processControlChange (const HelperValues helpers, | |||
PacketX2& packet) | |||
{ | |||
const auto statusAndChannel = helpers.byte0; | |||
const auto cc = helpers.byte1; | |||
const auto shouldAccumulate = [&] | |||
{ | |||
switch (cc) | |||
{ | |||
case 6: | |||
case 38: | |||
case 98: | |||
case 99: | |||
case 100: | |||
case 101: | |||
return true; | |||
} | |||
return false; | |||
}(); | |||
const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | |||
const auto channel = (uint8_t) (statusAndChannel & 0xf); | |||
const auto byte = helpers.byte2; | |||
if (shouldAccumulate) | |||
{ | |||
auto& accumulator = groupAccumulators[group][channel]; | |||
if (accumulator.addByte (cc, byte)) | |||
{ | |||
const auto& bytes = accumulator.getBytes(); | |||
const auto bank = bytes[0]; | |||
const auto index = bytes[1]; | |||
const auto msb = bytes[2]; | |||
const auto lsb = bytes[3]; | |||
const auto value = (uint16_t) (((msb & 0x7f) << 7) | (lsb & 0x7f)); | |||
const auto newStatus = (uint8_t) (accumulator.getKind() == PnKind::nrpn ? 0x3 : 0x2); | |||
packet = PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, (uint8_t) ((newStatus << 0x4) | channel), bank, index), | |||
Conversion::scaleTo32 (value) | |||
}; | |||
return true; | |||
} | |||
return false; | |||
} | |||
if (cc == 0) | |||
{ | |||
groupBanks[group][channel].setMsb (byte); | |||
return false; | |||
} | |||
if (cc == 32) | |||
{ | |||
groupBanks[group][channel].setLsb (byte); | |||
return false; | |||
} | |||
packet = PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, statusAndChannel, cc, 0), | |||
Conversion::scaleTo32 (helpers.byte2) | |||
}; | |||
return true; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processProgramChange (const HelperValues helpers) const | |||
{ | |||
const auto group = (uint8_t) (helpers.typeAndGroup & 0xf); | |||
const auto channel = (uint8_t) (helpers.byte0 & 0xf); | |||
const auto bank = groupBanks[group][channel]; | |||
const auto valid = bank.isValid(); | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, valid ? 1 : 0), | |||
Utils::bytesToWord (helpers.byte1, 0, valid ? bank.getMsb() : 0, valid ? bank.getLsb() : 0) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processChannelPressure (const HelperValues helpers) | |||
{ | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | |||
Conversion::scaleTo32 (helpers.byte1) | |||
}; | |||
} | |||
PacketX2 Midi1ToMidi2DefaultTranslator::processPitchBend (const HelperValues helpers) | |||
{ | |||
const auto lsb = helpers.byte1; | |||
const auto msb = helpers.byte2; | |||
const auto value = (uint16_t) (msb << 7 | lsb); | |||
return PacketX2 | |||
{ | |||
Utils::bytesToWord (helpers.typeAndGroup, helpers.byte0, 0, 0), | |||
Conversion::scaleTo32 (value) | |||
}; | |||
} | |||
bool Midi1ToMidi2DefaultTranslator::PnAccumulator::addByte (uint8_t cc, uint8_t byte) | |||
{ | |||
const auto isStart = cc == 99 || cc == 101; | |||
if (isStart) | |||
{ | |||
kind = cc == 99 ? PnKind::nrpn : PnKind::rpn; | |||
index = 0; | |||
} | |||
bytes[index] = byte; | |||
const auto shouldContinue = [&] | |||
{ | |||
switch (index) | |||
{ | |||
case 0: return isStart; | |||
case 1: return kind == PnKind::nrpn ? cc == 98 : cc == 100; | |||
case 2: return cc == 6; | |||
case 3: return cc == 38; | |||
} | |||
return false; | |||
}(); | |||
index = shouldContinue ? index + 1 : 0; | |||
if (index != bytes.size()) | |||
return false; | |||
index = 0; | |||
return true; | |||
} | |||
} | |||
} |
@@ -0,0 +1,187 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Translates a series of MIDI 1 Universal MIDI Packets to corresponding MIDI 2 | |||
packets. | |||
@tags{Audio} | |||
*/ | |||
class Midi1ToMidi2DefaultTranslator | |||
{ | |||
public: | |||
Midi1ToMidi2DefaultTranslator() = default; | |||
/** Converts MIDI 1 Universal MIDI Packets to corresponding MIDI 2 packets, | |||
calling `callback` with each converted packet. | |||
In some cases (such as RPN/NRPN messages) multiple MIDI 1 packets will | |||
convert to a single MIDI 2 packet. In these cases, the translator will | |||
accumulate the full message internally, and send a single callback with | |||
the completed message, once all the individual MIDI 1 packets have been | |||
processed. | |||
*/ | |||
template <typename PacketCallback> | |||
void dispatch (const View& v, PacketCallback&& callback) | |||
{ | |||
const auto firstWord = v[0]; | |||
const auto messageType = Utils::getMessageType (firstWord); | |||
if (messageType != 0x2) | |||
{ | |||
callback (v); | |||
return; | |||
} | |||
const HelperValues helperValues | |||
{ | |||
(uint8_t) ((0x4 << 0x4) | Utils::getGroup (firstWord)), | |||
(uint8_t) ((firstWord >> 0x10) & 0xff), | |||
(uint8_t) ((firstWord >> 0x08) & 0x7f), | |||
(uint8_t) ((firstWord >> 0x00) & 0x7f), | |||
}; | |||
switch (Utils::getStatus (firstWord)) | |||
{ | |||
case 0x8: | |||
case 0x9: | |||
{ | |||
const auto packet = processNoteOnOrOff (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xa: | |||
{ | |||
const auto packet = processPolyPressure (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xb: | |||
{ | |||
PacketX2 packet; | |||
if (processControlChange (helperValues, packet)) | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xc: | |||
{ | |||
const auto packet = processProgramChange (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xd: | |||
{ | |||
const auto packet = processChannelPressure (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
case 0xe: | |||
{ | |||
const auto packet = processPitchBend (helperValues); | |||
callback (View (packet.data())); | |||
return; | |||
} | |||
} | |||
} | |||
void reset() | |||
{ | |||
groupAccumulators = {}; | |||
groupBanks = {}; | |||
} | |||
private: | |||
enum class PnKind { nrpn, rpn }; | |||
struct HelperValues | |||
{ | |||
uint8_t typeAndGroup; | |||
uint8_t byte0; | |||
uint8_t byte1; | |||
uint8_t byte2; | |||
}; | |||
static PacketX2 processNoteOnOrOff (const HelperValues helpers); | |||
static PacketX2 processPolyPressure (const HelperValues helpers); | |||
bool processControlChange (const HelperValues helpers, PacketX2& packet); | |||
PacketX2 processProgramChange (const HelperValues helpers) const; | |||
static PacketX2 processChannelPressure (const HelperValues helpers); | |||
static PacketX2 processPitchBend (const HelperValues helpers); | |||
class PnAccumulator | |||
{ | |||
public: | |||
bool addByte (uint8_t cc, uint8_t byte); | |||
const std::array<uint8_t, 4>& getBytes() const noexcept { return bytes; } | |||
PnKind getKind() const noexcept { return kind; } | |||
private: | |||
std::array<uint8_t, 4> bytes; | |||
uint8_t index = 0; | |||
PnKind kind = PnKind::nrpn; | |||
}; | |||
class Bank | |||
{ | |||
public: | |||
bool isValid() const noexcept { return ! (msb & 0x80); } | |||
uint8_t getMsb() const noexcept { return msb & 0x7f; } | |||
uint8_t getLsb() const noexcept { return lsb & 0x7f; } | |||
void setMsb (uint8_t i) noexcept { msb = i & 0x7f; } | |||
void setLsb (uint8_t i) noexcept { msb &= 0x7f; lsb = i & 0x7f; } | |||
private: | |||
// We use the top bit to indicate whether this bank is valid. | |||
// After reading the spec, it's not clear how we should determine whether | |||
// there are valid values, so we'll just assume that the bank is valid | |||
// once either the lsb or msb have been written. | |||
uint8_t msb = 0x80; | |||
uint8_t lsb = 0x00; | |||
}; | |||
using ChannelAccumulators = std::array<PnAccumulator, 16>; | |||
std::array<ChannelAccumulators, 16> groupAccumulators; | |||
using ChannelBanks = std::array<Bank, 16>; | |||
std::array<ChannelBanks, 16> groupBanks; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** The kinds of MIDI protocol that can be formatted into Universal MIDI Packets. */ | |||
enum class PacketProtocol | |||
{ | |||
MIDI_1_0, | |||
MIDI_2_0, | |||
}; | |||
/** All kinds of MIDI protocol understood by JUCE. */ | |||
enum class MidiProtocol | |||
{ | |||
bytestream, | |||
UMP_MIDI_1_0, | |||
UMP_MIDI_2_0, | |||
}; | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
A base class for classes which receive Universal MIDI Packets from an input. | |||
*/ | |||
struct Receiver | |||
{ | |||
virtual ~Receiver() noexcept = default; | |||
/** This will be called each time a new packet is ready for processing. */ | |||
virtual void packetReceived (const View& packet, double time) = 0; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t SysEx7::getNumPacketsRequiredForDataSize (uint32_t size) | |||
{ | |||
constexpr auto denom = 6; | |||
return (size / denom) + ((size % denom) != 0); | |||
} | |||
SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet) | |||
{ | |||
const auto numBytes = Utils::getChannel (packet[0]); | |||
constexpr uint8_t maxBytes = 6; | |||
jassert (numBytes <= maxBytes); | |||
return | |||
{ | |||
{ packet.getU8<2>(), | |||
packet.getU8<3>(), | |||
packet.getU8<4>(), | |||
packet.getU8<5>(), | |||
packet.getU8<6>(), | |||
packet.getU8<7>() }, | |||
jmin (numBytes, maxBytes) | |||
}; | |||
} | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
This struct acts as a single-file namespace for Univeral MIDI Packet | |||
functionality related to 7-bit SysEx. | |||
@tags{Audio} | |||
*/ | |||
struct SysEx7 | |||
{ | |||
/** Returns the number of 64-bit packets required to hold a series of | |||
SysEx bytes. | |||
The number passed to this function should exclude the leading/trailing | |||
SysEx bytes used in an old midi bytestream, as these are not required | |||
when using Universal MIDI Packets. | |||
*/ | |||
static uint32_t getNumPacketsRequiredForDataSize (uint32_t); | |||
/** The different kinds of UMP SysEx-7 message. */ | |||
enum class Kind : uint8_t | |||
{ | |||
/** The whole message fits in a single 2-word packet. */ | |||
complete = 0, | |||
/** The packet begins a SysEx message that will continue in subsequent packets. */ | |||
begin = 1, | |||
/** The packet is a continuation of an ongoing SysEx message. */ | |||
continuation = 2, | |||
/** The packet terminates an ongoing SysEx message. */ | |||
end = 3 | |||
}; | |||
/** Holds the bytes from a single SysEx-7 packet. */ | |||
struct PacketBytes | |||
{ | |||
std::array<uint8_t, 6> data; | |||
uint8_t size; | |||
}; | |||
/** Extracts the data bytes from a 64-bit data message. */ | |||
static PacketBytes getDataBytes (const PacketX2& packet); | |||
}; | |||
} | |||
} |
@@ -0,0 +1,145 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
A base class for classes which convert Universal MIDI Packets to other | |||
formats. | |||
*/ | |||
struct U32InputHandler | |||
{ | |||
virtual ~U32InputHandler() noexcept = default; | |||
virtual void reset() = 0; | |||
virtual void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) = 0; | |||
}; | |||
/** | |||
Parses a continuous stream of U32 words and emits complete MidiMessages whenever a full | |||
message is received. | |||
*/ | |||
struct U32ToBytestreamHandler : public U32InputHandler | |||
{ | |||
U32ToBytestreamHandler (MidiInput& i, MidiInputCallback& c) | |||
: input (i), callback (c), dispatcher (2048) {} | |||
/** | |||
Provides an `operator()` which can create an input handler for a given | |||
MidiInput. | |||
All handler classes should have a similar Factory to facilitate | |||
creation of handlers in generic contexts. | |||
*/ | |||
class Factory | |||
{ | |||
public: | |||
explicit Factory (MidiInputCallback* c) | |||
: callback (c) {} | |||
std::unique_ptr<U32ToBytestreamHandler> operator() (MidiInput& i) const | |||
{ | |||
if (callback != nullptr) | |||
return std::make_unique<U32ToBytestreamHandler> (i, *callback); | |||
jassertfalse; | |||
return {}; | |||
} | |||
private: | |||
MidiInputCallback* callback = nullptr; | |||
}; | |||
void reset() override { dispatcher.reset(); } | |||
void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override | |||
{ | |||
dispatcher.dispatch (begin, end, time, [this] (const MidiMessage& m) | |||
{ | |||
callback.handleIncomingMidiMessage (&input, m); | |||
}); | |||
} | |||
MidiInput& input; | |||
MidiInputCallback& callback; | |||
ToBytestreamDispatcher dispatcher; | |||
}; | |||
/** | |||
Parses a continuous stream of U32 words and emits full messages in the requested | |||
UMP format. | |||
*/ | |||
struct U32ToUMPHandler : public U32InputHandler | |||
{ | |||
U32ToUMPHandler (PacketProtocol protocol, Receiver& c) | |||
: recipient (c), converter (protocol) {} | |||
/** | |||
Provides an `operator()` which can create an input handler for a given | |||
MidiInput. | |||
All handler classes should have a similar Factory to facilitate | |||
creation of handlers in generic contexts. | |||
*/ | |||
class Factory | |||
{ | |||
public: | |||
Factory (PacketProtocol p, Receiver& c) | |||
: protocol (p), callback (c) {} | |||
std::unique_ptr<U32ToUMPHandler> operator() (MidiInput&) const | |||
{ | |||
return std::make_unique<U32ToUMPHandler> (protocol, callback); | |||
} | |||
private: | |||
PacketProtocol protocol; | |||
Receiver& callback; | |||
}; | |||
void reset() override | |||
{ | |||
dispatcher.reset(); | |||
converter.reset(); | |||
} | |||
void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override | |||
{ | |||
dispatcher.dispatch (begin, end, time, [this] (const View& view, double thisTime) | |||
{ | |||
converter.convert (view, [&] (const View& converted) | |||
{ | |||
recipient.packetReceived (converted, thisTime); | |||
}); | |||
}); | |||
} | |||
Receiver& recipient; | |||
Dispatcher dispatcher; | |||
GenericUMPConverter converter; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t Utils::getNumWordsForMessageType (uint32_t mt) | |||
{ | |||
switch (Utils::getMessageType (mt)) | |||
{ | |||
case 0x0: | |||
case 0x1: | |||
case 0x2: | |||
case 0x6: | |||
case 0x7: | |||
return 1; | |||
case 0x3: | |||
case 0x4: | |||
case 0x8: | |||
case 0x9: | |||
case 0xa: | |||
return 2; | |||
case 0xb: | |||
case 0xc: | |||
return 3; | |||
case 0x5: | |||
case 0xd: | |||
case 0xe: | |||
case 0xf: | |||
return 4; | |||
} | |||
jassertfalse; | |||
return 1; | |||
} | |||
} | |||
} |
@@ -0,0 +1,113 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Helpful types and functions for interacting with Universal MIDI Packets. | |||
@tags{Audio} | |||
*/ | |||
struct Utils | |||
{ | |||
/** Joins 4 bytes into a single 32-bit word. */ | |||
static constexpr uint32_t bytesToWord (uint8_t a, uint8_t b, uint8_t c, uint8_t d) | |||
{ | |||
return uint32_t (a << 0x18 | b << 0x10 | c << 0x08 | d << 0x00); | |||
} | |||
/** Returns the expected number of 32-bit words in a Universal MIDI Packet, given | |||
the first word of the packet. | |||
The result will be between 1 and 4 inclusive. | |||
A result of 1 means that the word is itself a complete packet. | |||
*/ | |||
static uint32_t getNumWordsForMessageType (uint32_t); | |||
/** | |||
Helper functions for setting/getting 4-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U4 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x1c - Index * 4; | |||
static constexpr uint32_t set (uint32_t word, uint8_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xf << shift)) | (uint32_t) ((value & 0xf) << shift); | |||
} | |||
static constexpr uint8_t get (uint32_t word) | |||
{ | |||
return (uint8_t) ((word >> shift) & 0xf); | |||
} | |||
}; | |||
/** | |||
Helper functions for setting/getting 8-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U8 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x18 - Index * 8; | |||
static constexpr uint32_t set (uint32_t word, uint8_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xff << shift)) | (uint32_t) (value << shift); | |||
} | |||
static constexpr uint8_t get (uint32_t word) | |||
{ | |||
return (uint8_t) ((word >> shift) & 0xff); | |||
} | |||
}; | |||
/** | |||
Helper functions for setting/getting 16-bit ranges inside a 32-bit word. | |||
*/ | |||
template <size_t Index> | |||
struct U16 | |||
{ | |||
static constexpr uint32_t shift = (uint32_t) 0x10 - Index * 16; | |||
static constexpr uint32_t set (uint32_t word, uint16_t value) | |||
{ | |||
return (word & ~((uint32_t) 0xffff << shift)) | (uint32_t) (value << shift); | |||
} | |||
static constexpr uint16_t get (uint32_t word) | |||
{ | |||
return (uint16_t) ((word >> shift) & 0xffff); | |||
} | |||
}; | |||
static constexpr uint8_t getMessageType (uint32_t w) noexcept { return U4<0>::get (w); } | |||
static constexpr uint8_t getGroup (uint32_t w) noexcept { return U4<1>::get (w); } | |||
static constexpr uint8_t getStatus (uint32_t w) noexcept { return U4<2>::get (w); } | |||
static constexpr uint8_t getChannel (uint32_t w) noexcept { return U4<3>::get (w); } | |||
}; | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
uint32_t View::size() const noexcept | |||
{ | |||
jassert (ptr != nullptr); | |||
return Utils::getNumWordsForMessageType (*ptr); | |||
} | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Points to a single Universal MIDI Packet. | |||
The packet must be well-formed for member functions to work correctly. | |||
Specifically, the constructor argument must be the beginning of a region of | |||
uint32_t that contains at least `getNumWordsForMessageType(*ddata)` items, | |||
where `data` is the constructor argument. | |||
NOTE: Instances of this class do not own the memory that they point to! | |||
If you need to store a packet pointed-to by a View for later use, copy | |||
the view contents to a Packets collection, or use the Utils::PacketX types. | |||
@tags{Audio} | |||
*/ | |||
class View | |||
{ | |||
public: | |||
/** Create an invalid view. */ | |||
View() noexcept = default; | |||
/** Create a view of the packet starting at address `d`. */ | |||
explicit View (const uint32_t* data) noexcept : ptr (data) {} | |||
/** Get a pointer to the first word in the Universal MIDI Packet currently | |||
pointed-to by this view. | |||
*/ | |||
const uint32_t* data() const noexcept { return ptr; } | |||
/** Get the number of 32-words (between 1 and 4 inclusive) in the Universal | |||
MIDI Packet currently pointed-to by this view. | |||
*/ | |||
uint32_t size() const noexcept; | |||
/** Get a specific word from this packet. | |||
Passing an `index` that is greater than or equal to the result of `size` | |||
will cause undefined behaviour. | |||
*/ | |||
const uint32_t& operator[] (size_t index) const noexcept { return ptr[index]; } | |||
/** Get an iterator pointing to the first word in the packet. */ | |||
const uint32_t* begin() const noexcept { return ptr; } | |||
const uint32_t* cbegin() const noexcept { return ptr; } | |||
/** Get an iterator pointing one-past the last word in the packet. */ | |||
const uint32_t* end() const noexcept { return ptr + size(); } | |||
const uint32_t* cend() const noexcept { return ptr + size(); } | |||
/** Return true if this view is pointing to the same address as another view. */ | |||
bool operator== (const View& other) const noexcept { return ptr == other.ptr; } | |||
/** Return false if this view is pointing to the same address as another view. */ | |||
bool operator!= (const View& other) const noexcept { return ! operator== (other); } | |||
private: | |||
const uint32_t* ptr = nullptr; | |||
}; | |||
} | |||
} |
@@ -0,0 +1,187 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Holds a single Universal MIDI Packet. | |||
*/ | |||
template <size_t numWords> | |||
class Packet | |||
{ | |||
public: | |||
Packet() = default; | |||
template <size_t w = numWords, typename std::enable_if<w == 1, int>::type = 0> | |||
Packet (uint32_t a) | |||
: contents { { a } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 1); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 2, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b) | |||
: contents { { a, b } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 2); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 3, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b, uint32_t c) | |||
: contents { { a, b, c } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 3); | |||
} | |||
template <size_t w = numWords, typename std::enable_if<w == 4, int>::type = 0> | |||
Packet (uint32_t a, uint32_t b, uint32_t c, uint32_t d) | |||
: contents { { a, b, c, d } } | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (a) == 4); | |||
} | |||
template <size_t w, typename std::enable_if<w == numWords, int>::type = 0> | |||
explicit Packet (const std::array<uint32_t, w>& fullPacket) | |||
: contents (fullPacket) | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (fullPacket.front()) == numWords); | |||
} | |||
Packet withMessageType (uint8_t type) const noexcept | |||
{ | |||
return withU4<0> (type); | |||
} | |||
Packet withGroup (uint8_t group) const noexcept | |||
{ | |||
return withU4<1> (group); | |||
} | |||
Packet withStatus (uint8_t status) const noexcept | |||
{ | |||
return withU4<2> (status); | |||
} | |||
Packet withChannel (uint8_t channel) const noexcept | |||
{ | |||
return withU4<3> (channel); | |||
} | |||
uint8_t getMessageType() const noexcept { return getU4<0>(); } | |||
uint8_t getGroup() const noexcept { return getU4<1>(); } | |||
uint8_t getStatus() const noexcept { return getU4<2>(); } | |||
uint8_t getChannel() const noexcept { return getU4<3>(); } | |||
template <size_t index> | |||
Packet withU4 (uint8_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 8; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U4<index % 8>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU8 (uint8_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 4; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U8<index % 4>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU16 (uint16_t value) const noexcept | |||
{ | |||
constexpr auto word = index / 2; | |||
auto copy = *this; | |||
std::get<word> (copy.contents) = Utils::U16<index % 2>::set (copy.template getU32<word>(), value); | |||
return copy; | |||
} | |||
template <size_t index> | |||
Packet withU32 (uint32_t value) const noexcept | |||
{ | |||
auto copy = *this; | |||
std::get<index> (copy.contents) = value; | |||
return copy; | |||
} | |||
template <size_t index> | |||
uint8_t getU4() const noexcept | |||
{ | |||
return Utils::U4<index % 8>::get (this->template getU32<index / 8>()); | |||
} | |||
template <size_t index> | |||
uint8_t getU8() const noexcept | |||
{ | |||
return Utils::U8<index % 4>::get (this->template getU32<index / 4>()); | |||
} | |||
template <size_t index> | |||
uint16_t getU16() const noexcept | |||
{ | |||
return Utils::U16<index % 2>::get (this->template getU32<index / 2>()); | |||
} | |||
template <size_t index> | |||
uint32_t getU32() const noexcept | |||
{ | |||
return std::get<index> (contents); | |||
} | |||
//============================================================================== | |||
using Contents = std::array<uint32_t, numWords>; | |||
using const_iterator = typename Contents::const_iterator; | |||
const_iterator begin() const noexcept { return contents.begin(); } | |||
const_iterator cbegin() const noexcept { return contents.begin(); } | |||
const_iterator end() const noexcept { return contents.end(); } | |||
const_iterator cend() const noexcept { return contents.end(); } | |||
const uint32_t* data() const noexcept { return contents.data(); } | |||
const uint32_t& front() const noexcept { return contents.front(); } | |||
const uint32_t& back() const noexcept { return contents.back(); } | |||
const uint32_t& operator[] (size_t index) const noexcept { return contents[index]; } | |||
private: | |||
Contents contents { {} }; | |||
}; | |||
using PacketX1 = Packet<1>; | |||
using PacketX2 = Packet<2>; | |||
using PacketX3 = Packet<3>; | |||
using PacketX4 = Packet<4>; | |||
} | |||
} |
@@ -0,0 +1,92 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
namespace universal_midi_packets | |||
{ | |||
/** | |||
Holds a collection of Universal MIDI Packets. | |||
Unlike MidiBuffer, this collection does not store any additional information | |||
(e.g. timestamps) alongside the raw messages. | |||
If timestamps are required, these can be added to the container in UMP format, | |||
as Jitter Reduction Utility messages. | |||
@tags{Audio} | |||
*/ | |||
class Packets | |||
{ | |||
public: | |||
/** Adds a single packet to the collection. | |||
The View must be valid for this to work. If the view | |||
points to a malformed message, or if the view points to a region | |||
too short for the contained message, this call will result in | |||
undefined behaviour. | |||
*/ | |||
void add (const View& v) { storage.insert (storage.end(), v.cbegin(), v.cend()); } | |||
void add (const PacketX1& p) { addImpl (p); } | |||
void add (const PacketX2& p) { addImpl (p); } | |||
void add (const PacketX3& p) { addImpl (p); } | |||
void add (const PacketX4& p) { addImpl (p); } | |||
/** Pre-allocates space for at least `numWords` 32-bit words in this collection. */ | |||
void reserve (size_t numWords) { storage.reserve (numWords); } | |||
/** Removes all previously-added packets from this collection. */ | |||
void clear() { storage.clear(); } | |||
/** Gets an iterator pointing to the first packet in this collection. */ | |||
Iterator cbegin() const noexcept { return Iterator (data(), size()); } | |||
Iterator begin() const noexcept { return cbegin(); } | |||
/** Gets an iterator pointing one-past the last packet in this collection. */ | |||
Iterator cend() const noexcept { return Iterator (data() + size(), 0); } | |||
Iterator end() const noexcept { return cend(); } | |||
/** Gets a pointer to the contents of the collection as a range of raw 32-bit words. */ | |||
const uint32_t* data() const noexcept { return storage.data(); } | |||
/** Returns the number of uint32_t words in storage. | |||
Note that this is likely to be larger than the number of packets | |||
currently being stored, as some packets span multiple words. | |||
*/ | |||
size_t size() const noexcept { return storage.size(); } | |||
private: | |||
template <size_t numWords> | |||
void addImpl (const Packet<numWords>& p) | |||
{ | |||
jassert (Utils::getNumWordsForMessageType (p[0]) == numWords); | |||
add (View (p.data())); | |||
} | |||
std::vector<uint32_t> storage; | |||
}; | |||
} | |||
} |
@@ -1663,11 +1663,16 @@ private: | |||
void readInput (AudioBuffer<float>& buffer, const int numSamples, const int blockSizeMs) | |||
{ | |||
for (auto* d : devices) | |||
d->done = (d->numInputChans == 0); | |||
d->done = (d->numInputChans == 0 || d->isWaitingForInput); | |||
for (int tries = 5;;) | |||
float totalWaitTimeMs = blockSizeMs * 5.0f; | |||
constexpr int numReadAttempts = 6; | |||
auto sumPower2s = [] (int maxPower) { return (1 << (maxPower + 1)) - 1; }; | |||
float waitTime = totalWaitTimeMs / (float) sumPower2s (numReadAttempts - 2); | |||
for (int numReadAttemptsRemaining = numReadAttempts;;) | |||
{ | |||
bool anyRemaining = false; | |||
bool anySamplesRemaining = false; | |||
for (auto* d : devices) | |||
{ | |||
@@ -1679,17 +1684,20 @@ private: | |||
d->done = true; | |||
} | |||
else | |||
anyRemaining = true; | |||
{ | |||
anySamplesRemaining = true; | |||
} | |||
} | |||
} | |||
if (! anyRemaining) | |||
if (! anySamplesRemaining) | |||
return; | |||
if (--tries == 0) | |||
if (--numReadAttemptsRemaining == 0) | |||
break; | |||
wait (blockSizeMs); | |||
wait (jmax (1, roundToInt (waitTime))); | |||
waitTime *= 2.0f; | |||
} | |||
for (auto* d : devices) | |||
@@ -1717,7 +1725,9 @@ private: | |||
d->done = true; | |||
} | |||
else | |||
{ | |||
anyRemaining = true; | |||
} | |||
} | |||
} | |||
@@ -1808,6 +1818,8 @@ private: | |||
numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; | |||
numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; | |||
isWaitingForInput = numInputChans > 0; | |||
inputIndex = channelIndex; | |||
outputIndex = channelIndex + numInputChans; | |||
@@ -1892,6 +1904,8 @@ private: | |||
{ | |||
if (numInputChannels > 0) | |||
{ | |||
isWaitingForInput = false; | |||
int start1, size1, start2, size2; | |||
inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); | |||
@@ -1973,6 +1987,7 @@ private: | |||
std::unique_ptr<CoreAudioIODevice> device; | |||
int inputIndex = 0, numInputChans = 0, outputIndex = 0, numOutputChans = 0; | |||
bool useInputs = false, useOutputs = false; | |||
std::atomic<bool> isWaitingForInput { false }; | |||
AbstractFifo inputFifo { 32 }, outputFifo { 32 }; | |||
bool done = false; | |||
@@ -1,732 +0,0 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2020 - Raw Material Software Limited | |||
JUCE is an open source library subject to commercial or open-source | |||
licensing. | |||
The code included in this file is provided under the terms of the ISC license | |||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
To use, copy, modify, and/or distribute this software for any purpose with or | |||
without fee is hereby granted provided that the above copyright notice and | |||
this permission notice appear in all copies. | |||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
DISCLAIMED. | |||
============================================================================== | |||
*/ | |||
namespace juce | |||
{ | |||
#ifndef JUCE_LOG_COREMIDI_ERRORS | |||
#define JUCE_LOG_COREMIDI_ERRORS 1 | |||
#endif | |||
namespace CoreMidiHelpers | |||
{ | |||
//============================================================================== | |||
static bool checkError (OSStatus err, int lineNum) | |||
{ | |||
if (err == noErr) | |||
return true; | |||
#if JUCE_LOG_COREMIDI_ERRORS | |||
Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); | |||
#endif | |||
ignoreUnused (lineNum); | |||
return false; | |||
} | |||
#undef CHECK_ERROR | |||
#define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | |||
static MidiDeviceInfo getMidiObjectInfo (MIDIObjectRef entity) | |||
{ | |||
MidiDeviceInfo info; | |||
{ | |||
ScopedCFString str; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString))) | |||
info.name = String::fromCFString (str.cfString); | |||
} | |||
SInt32 objectID = 0; | |||
if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID))) | |||
{ | |||
info.identifier = String (objectID); | |||
} | |||
else | |||
{ | |||
ScopedCFString str; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyUniqueID, &str.cfString))) | |||
info.identifier = String::fromCFString (str.cfString); | |||
} | |||
return info; | |||
} | |||
static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal) | |||
{ | |||
// NB: don't attempt to use nullptr for refs - it fails in some types of build. | |||
MIDIEntityRef entity = 0; | |||
MIDIEndpointGetEntity (endpoint, &entity); | |||
// probably virtual | |||
if (entity == 0) | |||
return getMidiObjectInfo (endpoint); | |||
auto result = getMidiObjectInfo (endpoint); | |||
// endpoint is empty - try the entity | |||
if (result == MidiDeviceInfo()) | |||
result = getMidiObjectInfo (entity); | |||
// now consider the device | |||
MIDIDeviceRef device = 0; | |||
MIDIEntityGetDevice (entity, &device); | |||
if (device != 0) | |||
{ | |||
auto info = getMidiObjectInfo (device); | |||
if (info != MidiDeviceInfo()) | |||
{ | |||
// if an external device has only one entity, throw away | |||
// the endpoint name and just use the device name | |||
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) | |||
{ | |||
result = info; | |||
} | |||
else if (! result.name.startsWithIgnoreCase (info.name)) | |||
{ | |||
// prepend the device name and identifier to the entity's | |||
result.name = (info.name + " " + result.name).trimEnd(); | |||
result.identifier = info.identifier + " " + result.identifier; | |||
} | |||
} | |||
} | |||
return result; | |||
} | |||
static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint) | |||
{ | |||
MidiDeviceInfo result; | |||
// Does the endpoint have connections? | |||
CFDataRef connections = nullptr; | |||
int numConnections = 0; | |||
MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); | |||
if (connections != nullptr) | |||
{ | |||
numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); | |||
if (numConnections > 0) | |||
{ | |||
auto* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections)); | |||
for (int i = 0; i < numConnections; ++i, ++pid) | |||
{ | |||
auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); | |||
MIDIObjectRef connObject; | |||
MIDIObjectType connObjectType; | |||
auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType); | |||
if (err == noErr) | |||
{ | |||
MidiDeviceInfo deviceInfo; | |||
if (connObjectType == kMIDIObjectType_ExternalSource | |||
|| connObjectType == kMIDIObjectType_ExternalDestination) | |||
{ | |||
// Connected to an external device's endpoint (10.3 and later). | |||
deviceInfo = getEndpointInfo (static_cast<MIDIEndpointRef> (connObject), true); | |||
} | |||
else | |||
{ | |||
// Connected to an external device (10.2) (or something else, catch-all) | |||
deviceInfo = getMidiObjectInfo (connObject); | |||
} | |||
if (deviceInfo != MidiDeviceInfo()) | |||
{ | |||
if (result.name.isNotEmpty()) result.name += ", "; | |||
if (result.identifier.isNotEmpty()) result.identifier += ", "; | |||
result.name += deviceInfo.name; | |||
result.identifier += deviceInfo.identifier; | |||
} | |||
} | |||
} | |||
} | |||
CFRelease (connections); | |||
} | |||
// Here, either the endpoint had no connections, or we failed to obtain names for them. | |||
if (result == MidiDeviceInfo()) | |||
return getEndpointInfo (endpoint, false); | |||
return result; | |||
} | |||
static int createUniqueIDForMidiPort (String deviceName, bool isInput) | |||
{ | |||
String uniqueID; | |||
#ifdef JucePlugin_CFBundleIdentifier | |||
uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); | |||
#else | |||
auto appBundle = File::getSpecialLocation (File::currentApplicationFile); | |||
ScopedCFString appBundlePath (appBundle.getFullPathName()); | |||
if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundlePath.cfString, kCFURLPOSIXPathStyle, true)) | |||
{ | |||
auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL); | |||
CFRelease (bundleURL); | |||
if (bundleRef != nullptr) | |||
{ | |||
if (auto bundleId = CFBundleGetIdentifier (bundleRef)) | |||
uniqueID = String::fromCFString (bundleId); | |||
CFRelease (bundleRef); | |||
} | |||
} | |||
#endif | |||
if (uniqueID.isEmpty()) | |||
uniqueID = String (Random::getSystemRandom().nextInt (1024)); | |||
uniqueID += "." + deviceName + (isInput ? ".input" : ".output"); | |||
return uniqueID.hashCode(); | |||
} | |||
static void enableSimulatorMidiSession() | |||
{ | |||
#if TARGET_OS_SIMULATOR | |||
static bool hasEnabledNetworkSession = false; | |||
if (! hasEnabledNetworkSession) | |||
{ | |||
MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||
session.enabled = YES; | |||
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||
hasEnabledNetworkSession = true; | |||
} | |||
#endif | |||
} | |||
static void globalSystemChangeCallback (const MIDINotification*, void*) | |||
{ | |||
// TODO.. Should pass-on this notification.. | |||
} | |||
static String getGlobalMidiClientName() | |||
{ | |||
if (auto* app = JUCEApplicationBase::getInstance()) | |||
return app->getApplicationName(); | |||
return "JUCE"; | |||
} | |||
static MIDIClientRef getGlobalMidiClient() | |||
{ | |||
static MIDIClientRef globalMidiClient = 0; | |||
if (globalMidiClient == 0) | |||
{ | |||
// Since OSX 10.6, the MIDIClientCreate function will only work | |||
// correctly when called from the message thread! | |||
JUCE_ASSERT_MESSAGE_THREAD | |||
enableSimulatorMidiSession(); | |||
ScopedCFString name (getGlobalMidiClientName()); | |||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
} | |||
return globalMidiClient; | |||
} | |||
static Array<MidiDeviceInfo> findDevices (bool forInput) | |||
{ | |||
// It seems that OSX can be a bit picky about the thread that's first used to | |||
// search for devices. It's safest to use the message thread for calling this. | |||
JUCE_ASSERT_MESSAGE_THREAD | |||
if (getGlobalMidiClient() == 0) | |||
{ | |||
jassertfalse; | |||
return {}; | |||
} | |||
enableSimulatorMidiSession(); | |||
Array<MidiDeviceInfo> devices; | |||
auto numDevices = (forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); | |||
for (ItemCount i = 0; i < numDevices; ++i) | |||
{ | |||
MidiDeviceInfo deviceInfo; | |||
if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) | |||
deviceInfo = getConnectedEndpointInfo (dest); | |||
if (deviceInfo == MidiDeviceInfo()) | |||
deviceInfo.name = deviceInfo.identifier = "<error>"; | |||
devices.add (deviceInfo); | |||
} | |||
return devices; | |||
} | |||
//============================================================================== | |||
class MidiPortAndEndpoint | |||
{ | |||
public: | |||
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept | |||
: port (p), endpoint (ep) | |||
{ | |||
} | |||
~MidiPortAndEndpoint() noexcept | |||
{ | |||
if (port != 0) | |||
MIDIPortDispose (port); | |||
// if port == nullptr, it means we created the endpoint, so it's safe to delete it | |||
if (port == 0 && endpoint != 0) | |||
MIDIEndpointDispose (endpoint); | |||
} | |||
void send (const MIDIPacketList* packets) noexcept | |||
{ | |||
if (port != 0) | |||
MIDISend (port, endpoint, packets); | |||
else | |||
MIDIReceived (endpoint, packets); | |||
} | |||
MIDIPortRef port; | |||
MIDIEndpointRef endpoint; | |||
}; | |||
//============================================================================== | |||
struct MidiPortAndCallback; | |||
CriticalSection callbackLock; | |||
Array<MidiPortAndCallback*> activeCallbacks; | |||
struct MidiPortAndCallback | |||
{ | |||
MidiPortAndCallback (MidiInputCallback& cb) : callback (cb) {} | |||
~MidiPortAndCallback() | |||
{ | |||
active = false; | |||
{ | |||
const ScopedLock sl (callbackLock); | |||
activeCallbacks.removeFirstMatchingValue (this); | |||
} | |||
if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) | |||
CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endpoint)); | |||
} | |||
void handlePackets (const MIDIPacketList* pktlist) | |||
{ | |||
auto time = Time::getMillisecondCounterHiRes() * 0.001; | |||
const ScopedLock sl (callbackLock); | |||
if (activeCallbacks.contains (this) && active) | |||
{ | |||
auto* packet = &pktlist->packet[0]; | |||
for (unsigned int i = 0; i < pktlist->numPackets; ++i) | |||
{ | |||
auto len = readUnaligned<decltype (packet->length)> (&(packet->length)); | |||
concatenator.pushMidiData (packet->data, (int) len, time, input, callback); | |||
packet = MIDIPacketNext (packet); | |||
} | |||
} | |||
} | |||
MidiInput* input = nullptr; | |||
std::unique_ptr<MidiPortAndEndpoint> portAndEndpoint; | |||
std::atomic<bool> active { false }; | |||
private: | |||
MidiInputCallback& callback; | |||
MidiDataConcatenator concatenator { 2048 }; | |||
}; | |||
static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||
{ | |||
static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | |||
} | |||
static Array<MIDIEndpointRef> getEndpoints (bool isInput) | |||
{ | |||
Array<MIDIEndpointRef> endpoints; | |||
auto numDevices = (isInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); | |||
for (ItemCount i = 0; i < numDevices; ++i) | |||
endpoints.add (isInput ? MIDIGetSource (i) : MIDIGetDestination (i)); | |||
return endpoints; | |||
} | |||
} | |||
class MidiInput::Pimpl : public CoreMidiHelpers::MidiPortAndCallback | |||
{ | |||
public: | |||
using MidiPortAndCallback::MidiPortAndCallback; | |||
}; | |||
//============================================================================== | |||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||
{ | |||
return CoreMidiHelpers::findDevices (true); | |||
} | |||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||
{ | |||
return getAvailableDevices().getFirst(); | |||
} | |||
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||
{ | |||
if (deviceIdentifier.isEmpty()) | |||
return nullptr; | |||
using namespace CoreMidiHelpers; | |||
if (auto client = getGlobalMidiClient()) | |||
{ | |||
for (auto& endpoint : getEndpoints (true)) | |||
{ | |||
auto endpointInfo = getConnectedEndpointInfo (endpoint); | |||
if (deviceIdentifier == endpointInfo.identifier) | |||
{ | |||
ScopedCFString cfName; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | |||
{ | |||
MIDIPortRef port; | |||
auto mpc = std::make_unique<Pimpl> (*callback); | |||
if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port))) | |||
{ | |||
if (CHECK_ERROR (MIDIPortConnectSource (port, endpoint, nullptr))) | |||
{ | |||
mpc->portAndEndpoint = std::make_unique<MidiPortAndEndpoint> (port, endpoint); | |||
std::unique_ptr<MidiInput> midiInput (new MidiInput (endpointInfo.name, endpointInfo.identifier)); | |||
mpc->input = midiInput.get(); | |||
auto* ptr = mpc.get(); | |||
midiInput->internal = std::move (mpc); | |||
const ScopedLock sl (callbackLock); | |||
activeCallbacks.add (ptr); | |||
return midiInput; | |||
} | |||
else | |||
{ | |||
CHECK_ERROR (MIDIPortDispose (port)); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
return {}; | |||
} | |||
std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
{ | |||
using namespace CoreMidiHelpers; | |||
jassert (callback != nullptr); | |||
if (auto client = getGlobalMidiClient()) | |||
{ | |||
auto mpc = std::make_unique<Pimpl> (*callback); | |||
mpc->active = false; | |||
MIDIEndpointRef endpoint; | |||
ScopedCFString name (deviceName); | |||
auto err = MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endpoint); | |||
#if JUCE_IOS | |||
if (err == kMIDINotPermitted) | |||
{ | |||
// If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" | |||
// setting in the iOS exporter for your app - this is required if you want to create a MIDI device! | |||
jassertfalse; | |||
return nullptr; | |||
} | |||
#endif | |||
if (CHECK_ERROR (err)) | |||
{ | |||
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); | |||
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | |||
{ | |||
mpc->portAndEndpoint = std::make_unique<MidiPortAndEndpoint> ((UInt32) 0, endpoint); | |||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, String (deviceIdentifier))); | |||
mpc->input = midiInput.get(); | |||
auto* ptr = mpc.get(); | |||
midiInput->internal = std::move (mpc); | |||
const ScopedLock sl (callbackLock); | |||
activeCallbacks.add (ptr); | |||
return midiInput; | |||
} | |||
} | |||
} | |||
return {}; | |||
} | |||
StringArray MidiInput::getDevices() | |||
{ | |||
StringArray deviceNames; | |||
for (auto& d : getAvailableDevices()) | |||
deviceNames.add (d.name); | |||
return deviceNames; | |||
} | |||
int MidiInput::getDefaultDeviceIndex() | |||
{ | |||
return 0; | |||
} | |||
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
{ | |||
return openDevice (getAvailableDevices()[index].identifier, callback); | |||
} | |||
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||
: deviceInfo (deviceName, deviceIdentifier) | |||
{ | |||
} | |||
MidiInput::~MidiInput() = default; | |||
void MidiInput::start() | |||
{ | |||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||
internal->active = true; | |||
} | |||
void MidiInput::stop() | |||
{ | |||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||
internal->active = false; | |||
} | |||
//============================================================================== | |||
class MidiOutput::Pimpl : public CoreMidiHelpers::MidiPortAndEndpoint | |||
{ | |||
public: | |||
using MidiPortAndEndpoint::MidiPortAndEndpoint; | |||
}; | |||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||
{ | |||
return CoreMidiHelpers::findDevices (false); | |||
} | |||
MidiDeviceInfo MidiOutput::getDefaultDevice() | |||
{ | |||
return getAvailableDevices().getFirst(); | |||
} | |||
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier) | |||
{ | |||
if (deviceIdentifier.isEmpty()) | |||
return nullptr; | |||
using namespace CoreMidiHelpers; | |||
if (auto client = getGlobalMidiClient()) | |||
{ | |||
for (auto& endpoint : getEndpoints (false)) | |||
{ | |||
auto endpointInfo = getConnectedEndpointInfo (endpoint); | |||
if (deviceIdentifier == endpointInfo.identifier) | |||
{ | |||
ScopedCFString cfName; | |||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | |||
{ | |||
MIDIPortRef port; | |||
if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) | |||
{ | |||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); | |||
midiOutput->internal = std::make_unique<Pimpl> (port, endpoint); | |||
return midiOutput; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
return {}; | |||
} | |||
std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName) | |||
{ | |||
using namespace CoreMidiHelpers; | |||
if (auto client = getGlobalMidiClient()) | |||
{ | |||
MIDIEndpointRef endpoint; | |||
ScopedCFString name (deviceName); | |||
auto err = MIDISourceCreate (client, name.cfString, &endpoint); | |||
#if JUCE_IOS | |||
if (err == kMIDINotPermitted) | |||
{ | |||
// If you've hit this assertion then you probably haven't enabled the "Audio Background Capability" | |||
// setting in the iOS exporter for your app - this is required if you want to create a MIDI device! | |||
jassertfalse; | |||
return nullptr; | |||
} | |||
#endif | |||
if (CHECK_ERROR (err)) | |||
{ | |||
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, false); | |||
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | |||
{ | |||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); | |||
midiOutput->internal = std::make_unique<Pimpl> ((UInt32) 0, endpoint); | |||
return midiOutput; | |||
} | |||
} | |||
} | |||
return {}; | |||
} | |||
StringArray MidiOutput::getDevices() | |||
{ | |||
StringArray deviceNames; | |||
for (auto& d : getAvailableDevices()) | |||
deviceNames.add (d.name); | |||
return deviceNames; | |||
} | |||
int MidiOutput::getDefaultDeviceIndex() | |||
{ | |||
return 0; | |||
} | |||
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
{ | |||
return openDevice (getAvailableDevices()[index].identifier); | |||
} | |||
MidiOutput::~MidiOutput() | |||
{ | |||
stopBackgroundThread(); | |||
} | |||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
{ | |||
#if JUCE_IOS | |||
const MIDITimeStamp timeStamp = mach_absolute_time(); | |||
#else | |||
const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||
#endif | |||
HeapBlock<MIDIPacketList> allocatedPackets; | |||
MIDIPacketList stackPacket; | |||
auto* packetToSend = &stackPacket; | |||
auto dataSize = (size_t) message.getRawDataSize(); | |||
if (message.isSysEx()) | |||
{ | |||
const int maxPacketSize = 256; | |||
int pos = 0, bytesLeft = (int) dataSize; | |||
const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; | |||
allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); | |||
packetToSend = allocatedPackets; | |||
packetToSend->numPackets = (UInt32) numPackets; | |||
auto* p = packetToSend->packet; | |||
for (int i = 0; i < numPackets; ++i) | |||
{ | |||
p->timeStamp = timeStamp; | |||
p->length = (UInt16) jmin (maxPacketSize, bytesLeft); | |||
memcpy (p->data, message.getRawData() + pos, p->length); | |||
pos += p->length; | |||
bytesLeft -= p->length; | |||
p = MIDIPacketNext (p); | |||
} | |||
} | |||
else if (dataSize < 65536) // max packet size | |||
{ | |||
auto stackCapacity = sizeof (stackPacket.packet->data); | |||
if (dataSize > stackCapacity) | |||
{ | |||
allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); | |||
packetToSend = allocatedPackets; | |||
} | |||
packetToSend->numPackets = 1; | |||
auto& p = *(packetToSend->packet); | |||
p.timeStamp = timeStamp; | |||
p.length = (UInt16) dataSize; | |||
memcpy (p.data, message.getRawData(), dataSize); | |||
} | |||
else | |||
{ | |||
jassertfalse; // packet too large to send! | |||
return; | |||
} | |||
internal->send (packetToSend); | |||
} | |||
#undef CHECK_ERROR | |||
} // namespace juce |
@@ -334,7 +334,7 @@ private: | |||
}; | |||
//============================================================================== | |||
template<class WrapperType> | |||
template <class WrapperType> | |||
struct Win32MidiDeviceQuery | |||
{ | |||
static Array<MidiDeviceInfo> getAvailableDevices() | |||
@@ -782,9 +782,9 @@ private: | |||
public: | |||
virtual ~DeviceCallbackHandler() {}; | |||
virtual HRESULT addDevice (IDeviceInformation*) = 0; | |||
virtual HRESULT removeDevice (IDeviceInformationUpdate*) = 0; | |||
virtual HRESULT updateDevice (IDeviceInformationUpdate*) = 0; | |||
JUCE_COMCALL addDevice (IDeviceInformation*) = 0; | |||
JUCE_COMCALL removeDevice (IDeviceInformationUpdate*) = 0; | |||
JUCE_COMCALL updateDevice (IDeviceInformationUpdate*) = 0; | |||
bool attach (HSTRING deviceSelector, DeviceInformationKind infoKind) | |||
{ | |||
@@ -834,7 +834,7 @@ private: | |||
} | |||
} | |||
WinRTWrapper::ComPtr<IIterable<HSTRING>> iter; | |||
ComSmartPtr<IIterable<HSTRING>> iter; | |||
auto hr = requestedProperties->QueryInterface (__uuidof (IIterable<HSTRING>), (void**) iter.resetAndGetPointerAddress()); | |||
if (FAILED (hr)) | |||
@@ -891,7 +891,7 @@ private: | |||
watcher = nullptr; | |||
} | |||
template<typename InfoType> | |||
template <typename InfoType> | |||
IInspectable* getValueFromDeviceInfo (String key, InfoType* info) | |||
{ | |||
__FIMapView_2_HSTRING_IInspectable* properties; | |||
@@ -924,7 +924,7 @@ private: | |||
String getGUIDFromInspectable (IInspectable& inspectable) | |||
{ | |||
WinRTWrapper::ComPtr<IReference<GUID>> guidRef; | |||
ComSmartPtr<IReference<GUID>> guidRef; | |||
auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>), | |||
(void**) guidRef.resetAndGetPointerAddress()); | |||
@@ -951,7 +951,7 @@ private: | |||
bool getBoolFromInspectable (IInspectable& inspectable) | |||
{ | |||
WinRTWrapper::ComPtr<IReference<bool>> boolRef; | |||
ComSmartPtr<IReference<bool>> boolRef; | |||
auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>), | |||
(void**) boolRef.resetAndGetPointerAddress()); | |||
@@ -978,7 +978,7 @@ private: | |||
struct DeviceEnumerationThread : public Thread | |||
{ | |||
DeviceEnumerationThread (DeviceCallbackHandler& h, | |||
WinRTWrapper::ComPtr<IDeviceWatcher>& w, | |||
ComSmartPtr<IDeviceWatcher>& w, | |||
EventRegistrationToken& added, | |||
EventRegistrationToken& removed, | |||
EventRegistrationToken& updated) | |||
@@ -1012,12 +1012,12 @@ private: | |||
} | |||
DeviceCallbackHandler& handler; | |||
WinRTWrapper::ComPtr<IDeviceWatcher>& watcher; | |||
ComSmartPtr<IDeviceWatcher>& watcher; | |||
EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken; | |||
}; | |||
//============================================================================== | |||
WinRTWrapper::ComPtr<IDeviceWatcher> watcher; | |||
ComSmartPtr<IDeviceWatcher> watcher; | |||
EventRegistrationToken deviceAddedToken { 0 }, | |||
deviceRemovedToken { 0 }, | |||
@@ -1222,7 +1222,7 @@ private: | |||
template <typename COMFactoryType> | |||
struct MidiIODeviceWatcher final : private DeviceCallbackHandler | |||
{ | |||
MidiIODeviceWatcher (WinRTWrapper::ComPtr<COMFactoryType>& comFactory) | |||
MidiIODeviceWatcher (ComSmartPtr<COMFactoryType>& comFactory) | |||
: factory (comFactory) | |||
{ | |||
} | |||
@@ -1407,7 +1407,7 @@ private: | |||
return {}; | |||
} | |||
WinRTWrapper::ComPtr<COMFactoryType>& factory; | |||
ComSmartPtr<COMFactoryType>& factory; | |||
Array<WinRTMIDIDeviceInfo> connectedDevices; | |||
CriticalSection deviceChanges; | |||
@@ -1421,8 +1421,8 @@ private: | |||
struct OpenMidiPortThread : public Thread | |||
{ | |||
OpenMidiPortThread (String threadName, String midiDeviceID, | |||
WinRTWrapper::ComPtr<COMFactoryType>& comFactory, | |||
WinRTWrapper::ComPtr<COMInterfaceType>& comPort) | |||
ComSmartPtr<COMFactoryType>& comFactory, | |||
ComSmartPtr<COMInterfaceType>& comPort) | |||
: Thread (threadName), | |||
deviceID (midiDeviceID), | |||
factory (comFactory), | |||
@@ -1438,7 +1438,7 @@ private: | |||
void run() override | |||
{ | |||
WinRTWrapper::ScopedHString hDeviceId (deviceID); | |||
WinRTWrapper::ComPtr<IAsyncOperation<COMType*>> asyncOp; | |||
ComSmartPtr<IAsyncOperation<COMType*>> asyncOp; | |||
auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); | |||
if (FAILED (hr)) | |||
@@ -1466,8 +1466,8 @@ private: | |||
} | |||
const String deviceID; | |||
WinRTWrapper::ComPtr<COMFactoryType>& factory; | |||
WinRTWrapper::ComPtr<COMInterfaceType>& port; | |||
ComSmartPtr<COMFactoryType>& factory; | |||
ComSmartPtr<COMInterfaceType>& port; | |||
WaitableEvent portOpened { true }; | |||
}; | |||
@@ -1552,7 +1552,7 @@ private: | |||
BLEDeviceWatcher& bleDeviceWatcher; | |||
WinRTMIDIDeviceInfo deviceInfo; | |||
bool isBLEDevice = false; | |||
WinRTWrapper::ComPtr<MIDIPort> midiPort; | |||
ComSmartPtr<MIDIPort> midiPort; | |||
}; | |||
//============================================================================== | |||
@@ -1637,19 +1637,19 @@ private: | |||
if (! isStarted) | |||
return S_OK; | |||
WinRTWrapper::ComPtr<IMidiMessage> message; | |||
ComSmartPtr<IMidiMessage> message; | |||
auto hr = args->get_Message (message.resetAndGetPointerAddress()); | |||
if (FAILED (hr)) | |||
return hr; | |||
WinRTWrapper::ComPtr<IBuffer> buffer; | |||
ComSmartPtr<IBuffer> buffer; | |||
hr = message->get_RawData (buffer.resetAndGetPointerAddress()); | |||
if (FAILED (hr)) | |||
return hr; | |||
WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||
ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||
hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); | |||
if (FAILED (hr)) | |||
@@ -1775,15 +1775,15 @@ private: | |||
String getDeviceName() override { return deviceInfo.name; } | |||
//============================================================================== | |||
WinRTWrapper::ComPtr<IBuffer> buffer; | |||
WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||
ComSmartPtr<IBuffer> buffer; | |||
ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||
uint8_t* bufferData = nullptr; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper); | |||
}; | |||
WinRTWrapper::ComPtr<IMidiInPortStatics> midiInFactory; | |||
WinRTWrapper::ComPtr<IMidiOutPortStatics> midiOutFactory; | |||
ComSmartPtr<IMidiInPortStatics> midiInFactory; | |||
ComSmartPtr<IMidiOutPortStatics> midiOutFactory; | |||
std::unique_ptr<MidiIODeviceWatcher<IMidiInPortStatics>> inputDeviceWatcher; | |||
std::unique_ptr<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher; | |||
@@ -116,9 +116,6 @@ bool check (HRESULT hr) | |||
#define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") | |||
#endif | |||
#define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown | |||
#define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE | |||
enum EDataFlow | |||
{ | |||
eRender = 0, | |||
@@ -337,10 +334,6 @@ JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD" | |||
JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0; | |||
}; | |||
#undef JUCE_COMCALL | |||
#undef JUCE_COMCLASS | |||
#undef JUCE_IUNKNOWNCLASS | |||
//============================================================================== | |||
namespace WasapiClasses | |||
{ | |||
@@ -893,7 +886,7 @@ public: | |||
reservoirWritePos = 0; | |||
} | |||
template<class SourceType> | |||
template <class SourceType> | |||
void updateFormatWithType (SourceType*) noexcept | |||
{ | |||
using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>; | |||
@@ -1053,7 +1046,7 @@ public: | |||
renderClient = nullptr; | |||
} | |||
template<class DestType> | |||
template <class DestType> | |||
void updateFormatWithType (DestType*) | |||
{ | |||
using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>; | |||
@@ -1755,11 +1748,11 @@ private: | |||
ChangeNotificationClient (WASAPIAudioIODeviceType* d) | |||
: ComBaseClassHelper<IMMNotificationClient> (0), device (d) {} | |||
HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } | |||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } | |||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } | |||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } | |||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } | |||
JUCE_COMRESULT OnDeviceAdded (LPCWSTR) { return notify(); } | |||
JUCE_COMRESULT OnDeviceRemoved (LPCWSTR) { return notify(); } | |||
JUCE_COMRESULT OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } | |||
JUCE_COMRESULT OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } | |||
JUCE_COMRESULT OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } | |||
private: | |||
WeakReference<WASAPIAudioIODeviceType> device; | |||
@@ -67,25 +67,25 @@ namespace AiffFileHelpers | |||
Loop sustainLoop; | |||
Loop releaseLoop; | |||
void copyTo (StringPairArray& values) const | |||
void copyTo (std::map<String, String>& values) const | |||
{ | |||
values.set ("MidiUnityNote", String (baseNote)); | |||
values.set ("Detune", String (detune)); | |||
values.emplace ("MidiUnityNote", String (baseNote)); | |||
values.emplace ("Detune", String (detune)); | |||
values.set ("LowNote", String (lowNote)); | |||
values.set ("HighNote", String (highNote)); | |||
values.set ("LowVelocity", String (lowVelocity)); | |||
values.set ("HighVelocity", String (highVelocity)); | |||
values.emplace ("LowNote", String (lowNote)); | |||
values.emplace ("HighNote", String (highNote)); | |||
values.emplace ("LowVelocity", String (lowVelocity)); | |||
values.emplace ("HighVelocity", String (highVelocity)); | |||
values.set ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); | |||
values.emplace ("Gain", String ((int16) ByteOrder::swapIfLittleEndian ((uint16) gain))); | |||
values.set ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more | |||
values.set ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); | |||
values.set ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); | |||
values.set ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); | |||
values.set ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); | |||
values.set ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); | |||
values.set ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); | |||
values.emplace ("NumSampleLoops", String (2)); // always 2 with AIFF, WAV can have more | |||
values.emplace ("Loop0Type", String (ByteOrder::swapIfLittleEndian (sustainLoop.type))); | |||
values.emplace ("Loop0StartIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.startIdentifier))); | |||
values.emplace ("Loop0EndIdentifier", String (ByteOrder::swapIfLittleEndian (sustainLoop.endIdentifier))); | |||
values.emplace ("Loop1Type", String (ByteOrder::swapIfLittleEndian (releaseLoop.type))); | |||
values.emplace ("Loop1StartIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.startIdentifier))); | |||
values.emplace ("Loop1EndIdentifier", String (ByteOrder::swapIfLittleEndian (releaseLoop.endIdentifier))); | |||
} | |||
static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def) | |||
@@ -149,7 +149,7 @@ namespace AiffFileHelpers | |||
input.read (unknown, sizeof (unknown)); | |||
} | |||
void addToMetadata (StringPairArray& metadata) const | |||
void addToMetadata (std::map<String, String>& metadata) const | |||
{ | |||
const bool rootNoteSet = rootNote != 0; | |||
@@ -157,11 +157,11 @@ namespace AiffFileHelpers | |||
setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); | |||
if (rootNoteSet) | |||
metadata.set (AiffAudioFormat::appleRootNote, String (rootNote)); | |||
metadata.emplace (AiffAudioFormat::appleRootNote, String (rootNote)); | |||
metadata.set (AiffAudioFormat::appleBeats, String (numBeats)); | |||
metadata.set (AiffAudioFormat::appleDenominator, String (timeSigDen)); | |||
metadata.set (AiffAudioFormat::appleNumerator, String (timeSigNum)); | |||
metadata.emplace (AiffAudioFormat::appleBeats, String (numBeats)); | |||
metadata.emplace (AiffAudioFormat::appleDenominator, String (timeSigDen)); | |||
metadata.emplace (AiffAudioFormat::appleNumerator, String (timeSigNum)); | |||
const char* keyString = nullptr; | |||
@@ -175,12 +175,14 @@ namespace AiffFileHelpers | |||
} | |||
if (keyString != nullptr) | |||
metadata.set (AiffAudioFormat::appleKey, keyString); | |||
metadata.emplace (AiffAudioFormat::appleKey, keyString); | |||
} | |||
void setBoolFlag (StringPairArray& values, const char* name, bool shouldBeSet) const | |||
void setBoolFlag (std::map<String, String>& values, | |||
const char* name, | |||
bool shouldBeSet) const | |||
{ | |||
values.set (name, shouldBeSet ? "1" : "0"); | |||
values.emplace (name, shouldBeSet ? "1" : "0"); | |||
} | |||
uint32 flags; | |||
@@ -388,6 +390,17 @@ public: | |||
{ | |||
using namespace AiffFileHelpers; | |||
std::map<String, String> metadataValuesMap; | |||
for (int i = 0; i != metadataValues.size(); ++i) | |||
{ | |||
metadataValuesMap.emplace (metadataValues.getAllKeys().getReference (i), | |||
metadataValues.getAllValues().getReference (i)); | |||
} | |||
// If this fails, there were duplicate keys in the metadata | |||
jassert ((size_t) metadataValuesMap.size() == (size_t) metadataValues.size()); | |||
if (input->readInt() == chunkName ("FORM")) | |||
{ | |||
auto len = input->readIntBigEndian(); | |||
@@ -479,8 +492,8 @@ public: | |||
auto numCues = (uint16) input->readShortBigEndian(); | |||
// these two are always the same for AIFF-read files | |||
metadataValues.set ("NumCuePoints", String (numCues)); | |||
metadataValues.set ("NumCueLabels", String (numCues)); | |||
metadataValuesMap.emplace ("NumCuePoints", String (numCues)); | |||
metadataValuesMap.emplace ("NumCueLabels", String (numCues)); | |||
for (uint16 i = 0; i < numCues; ++i) | |||
{ | |||
@@ -497,18 +510,18 @@ public: | |||
input->readByte(); | |||
auto prefixCue = "Cue" + String (i); | |||
metadataValues.set (prefixCue + "Identifier", String (identifier)); | |||
metadataValues.set (prefixCue + "Offset", String (offset)); | |||
metadataValuesMap.emplace (prefixCue + "Identifier", String (identifier)); | |||
metadataValuesMap.emplace (prefixCue + "Offset", String (offset)); | |||
auto prefixLabel = "CueLabel" + String (i); | |||
metadataValues.set (prefixLabel + "Identifier", String (identifier)); | |||
metadataValues.set (prefixLabel + "Text", textBlock.toString()); | |||
metadataValuesMap.emplace (prefixLabel + "Identifier", String (identifier)); | |||
metadataValuesMap.emplace (prefixLabel + "Text", textBlock.toString()); | |||
} | |||
} | |||
else if (type == chunkName ("COMT")) | |||
{ | |||
auto numNotes = (uint16) input->readShortBigEndian(); | |||
metadataValues.set ("NumCueNotes", String (numNotes)); | |||
metadataValuesMap.emplace ("NumCueNotes", String (numNotes)); | |||
for (uint16 i = 0; i < numNotes; ++i) | |||
{ | |||
@@ -520,9 +533,9 @@ public: | |||
input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); | |||
auto prefix = "CueNote" + String (i); | |||
metadataValues.set (prefix + "TimeStamp", String (timestamp)); | |||
metadataValues.set (prefix + "Identifier", String (identifier)); | |||
metadataValues.set (prefix + "Text", textBlock.toString()); | |||
metadataValuesMap.emplace (prefix + "TimeStamp", String (timestamp)); | |||
metadataValuesMap.emplace (prefix + "Identifier", String (identifier)); | |||
metadataValuesMap.emplace (prefix + "Text", textBlock.toString()); | |||
} | |||
} | |||
else if (type == chunkName ("INST")) | |||
@@ -530,16 +543,16 @@ public: | |||
HeapBlock<InstChunk> inst; | |||
inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | |||
input->read (inst, (int) length); | |||
inst->copyTo (metadataValues); | |||
inst->copyTo (metadataValuesMap); | |||
} | |||
else if (type == chunkName ("basc")) | |||
{ | |||
AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues); | |||
AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValuesMap); | |||
} | |||
else if (type == chunkName ("cate")) | |||
{ | |||
metadataValues.set (AiffAudioFormat::appleTag, | |||
AiffFileHelpers::CATEChunk::read (*input, length)); | |||
metadataValuesMap.emplace (AiffAudioFormat::appleTag, | |||
AiffFileHelpers::CATEChunk::read (*input, length)); | |||
} | |||
else if ((hasGotVer && hasGotData && hasGotType) | |||
|| chunkEnd < input->getPosition() | |||
@@ -553,8 +566,10 @@ public: | |||
} | |||
} | |||
if (metadataValues.size() > 0) | |||
metadataValues.set ("MetaDataSource", "AIFF"); | |||
if (metadataValuesMap.size() > 0) | |||
metadataValuesMap.emplace ("MetaDataSource", "AIFF"); | |||
metadataValues.addMap (metadataValuesMap); | |||
} | |||
//============================================================================== | |||
@@ -250,7 +250,7 @@ public: | |||
samplesInReservoir = 0; | |||
} | |||
else if (startSampleInFile < reservoirStart | |||
|| startSampleInFile > reservoirStart + jmax (samplesInReservoir, 511)) | |||
|| startSampleInFile > reservoirStart + jmax (samplesInReservoir, (int64) 511)) | |||
{ | |||
// had some problems with flac crashing if the read pos is aligned more | |||
// accurately than this. Probably fixed in newer versions of the library, though. | |||
@@ -367,7 +367,7 @@ public: | |||
private: | |||
FlacNamespace::FLAC__StreamDecoder* decoder; | |||
AudioBuffer<float> reservoir; | |||
int reservoirStart = 0, samplesInReservoir = 0; | |||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||
bool ok = false, scanningForLength = false; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | |||
@@ -492,7 +492,9 @@ struct MP3Frame | |||
return frequencies[sampleRateIndex]; | |||
} | |||
void decodeHeader (const uint32 header) | |||
enum class ParseSuccessful { no, yes }; | |||
ParseSuccessful decodeHeader (const uint32 header) | |||
{ | |||
jassert (((header >> 10) & 3) != 3); | |||
@@ -527,17 +529,18 @@ struct MP3Frame | |||
jassertfalse; // This means the file is using "free format". Apparently very few decoders | |||
// support this mode, and this one certainly doesn't handle it correctly! | |||
frameSize = 0; | |||
return ParseSuccessful::no; | |||
} | |||
else | |||
switch (layer) | |||
{ | |||
switch (layer) | |||
{ | |||
case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
default: break; | |||
} | |||
case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
default: break; | |||
} | |||
return ParseSuccessful::yes; | |||
} | |||
int layer, frameSize, numChannels, single; | |||
@@ -1430,7 +1433,11 @@ struct MP3Stream | |||
lastFrameSize += nextFrameOffset; | |||
} | |||
frame.decodeHeader ((uint32) stream.readIntBigEndian()); | |||
const auto successful = frame.decodeHeader ((uint32) stream.readIntBigEndian()); | |||
if (successful == MP3Frame::ParseSuccessful::no) | |||
return -1; | |||
headerParsed = true; | |||
frameSize = frame.frameSize; | |||
isFreeFormat = (frameSize == 0); | |||
@@ -160,13 +160,13 @@ public: | |||
{ | |||
while (numSamples > 0) | |||
{ | |||
auto numAvailable = (int) (reservoirStart + samplesInReservoir - startSampleInFile); | |||
auto numAvailable = (reservoirStart + samplesInReservoir - startSampleInFile); | |||
if (startSampleInFile >= reservoirStart && numAvailable > 0) | |||
{ | |||
// got a few samples overlapping, so use them before seeking.. | |||
auto numToUse = jmin (numSamples, numAvailable); | |||
auto numToUse = jmin ((int64) numSamples, numAvailable); | |||
for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||
if (destSamples[i] != nullptr) | |||
@@ -175,8 +175,8 @@ public: | |||
(size_t) numToUse * sizeof (float)); | |||
startSampleInFile += numToUse; | |||
numSamples -= numToUse; | |||
startOffsetInDestBuffer += numToUse; | |||
numSamples -= (int) numToUse; | |||
startOffsetInDestBuffer += (int) numToUse; | |||
if (numSamples == 0) | |||
break; | |||
@@ -194,7 +194,7 @@ public: | |||
int bitStream = 0; | |||
int offset = 0; | |||
int numToRead = samplesInReservoir; | |||
int numToRead = (int) samplesInReservoir; | |||
while (numToRead > 0) | |||
{ | |||
@@ -261,7 +261,7 @@ private: | |||
OggVorbisNamespace::OggVorbis_File ovFile; | |||
OggVorbisNamespace::ov_callbacks callbacks; | |||
AudioBuffer<float> reservoir; | |||
int reservoirStart = 0, samplesInReservoir = 0; | |||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | |||
}; | |||
@@ -993,10 +993,12 @@ public: | |||
input->skipNextBytes (2); | |||
bitsPerSample = (unsigned int) (int) input->readShort(); | |||
if (bitsPerSample > 64) | |||
if (bitsPerSample > 64 && (int) sampleRate != 0) | |||
{ | |||
bytesPerFrame = bytesPerSec / (int) sampleRate; | |||
bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; | |||
if (numChannels != 0) | |||
bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; | |||
} | |||
else | |||
{ | |||
@@ -35,7 +35,7 @@ | |||
ID: juce_audio_formats | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE audio file format codecs | |||
description: Classes for reading and writing various audio file formats. | |||
website: http://www.juce.com/juce | |||
@@ -104,7 +104,7 @@ | |||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 | |||
#endif | |||
#if ! JUCE_MSVC | |||
#if ! JUCE_WINDOWS | |||
#undef JUCE_USE_WINDOWS_MEDIA_FORMAT | |||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | |||
#endif | |||
@@ -1085,24 +1085,28 @@ namespace AAXClasses | |||
SetParameterNormalizedValue (paramID, (double) newValue); | |||
} | |||
void audioProcessorChanged (AudioProcessor* processor) override | |||
void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override | |||
{ | |||
++mNumPlugInChanges; | |||
auto numParameters = juceParameters.getNumParameters(); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (details.parameterInfoChanged) | |||
{ | |||
if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) | |||
auto numParameters = juceParameters.getNumParameters(); | |||
for (int i = 0; i < numParameters; ++i) | |||
{ | |||
auto newName = juceParameters.getParamForIndex (i)->getName (31); | |||
if (auto* p = mParameterManager.GetParameterByID (getAAXParamIDFromJuceIndex (i))) | |||
{ | |||
auto newName = juceParameters.getParamForIndex (i)->getName (31); | |||
if (p->Name() != newName.toRawUTF8()) | |||
p->SetName (AAX_CString (newName.toRawUTF8())); | |||
if (p->Name() != newName.toRawUTF8()) | |||
p->SetName (AAX_CString (newName.toRawUTF8())); | |||
} | |||
} | |||
} | |||
check (Controller()->SetSignalLatency (processor->getLatencySamples())); | |||
if (details.latencyChanged) | |||
check (Controller()->SetSignalLatency (processor->getLatencySamples())); | |||
} | |||
void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override | |||
@@ -1124,6 +1128,20 @@ namespace AAXClasses | |||
case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; | |||
case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); break; | |||
case AAX_eNotificationEvent_ASProcessingState: | |||
{ | |||
if (data != nullptr && size == sizeof (AAX_EProcessingState)) | |||
{ | |||
const auto state = *static_cast<const AAX_EProcessingState*> (data); | |||
const auto nonRealtime = state == AAX_eProcessingState_Start | |||
|| state == AAX_eProcessingState_StartPass | |||
|| state == AAX_eProcessingState_BeginPassGroup; | |||
pluginInstance->setNonRealtime (nonRealtime); | |||
} | |||
break; | |||
} | |||
case AAX_eNotificationEvent_TrackNameChanged: | |||
if (data != nullptr) | |||
{ | |||
@@ -22,6 +22,8 @@ | |||
============================================================================== | |||
*/ | |||
#include <juce_core/system/juce_TargetPlatform.h> | |||
#include <juce_core/system/juce_CompilerWarnings.h> | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
@@ -45,7 +47,8 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshorten-64-to-32", | |||
"-Wzero-as-null-pointer-constant", | |||
"-Wnullable-to-nonnull-conversion", | |||
"-Wgnu-zero-variadic-macro-arguments", | |||
"-Wformat-pedantic") | |||
"-Wformat-pedantic", | |||
"-Wdeprecated-anon-enum-enum-conversion") | |||
#include "../utility/juce_IncludeSystemHeaders.h" | |||
@@ -352,7 +355,7 @@ public: | |||
} | |||
} | |||
err = (busNr == (requestedNumBus - 1) ? noErr : kAudioUnitErr_FormatNotSupported); | |||
err = (busNr == (requestedNumBus - 1) ? (OSStatus) noErr : (OSStatus) kAudioUnitErr_FormatNotSupported); | |||
} | |||
// was there an error? | |||
@@ -1163,16 +1166,24 @@ public: | |||
sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); | |||
} | |||
void audioProcessorChanged (AudioProcessor*) override | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override | |||
{ | |||
PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); | |||
PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); | |||
PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); | |||
PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); | |||
if (details.latencyChanged) | |||
PropertyChanged (kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0); | |||
if (details.parameterInfoChanged) | |||
{ | |||
PropertyChanged (kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); | |||
PropertyChanged (kAudioUnitProperty_ParameterInfo, kAudioUnitScope_Global, 0); | |||
} | |||
refreshCurrentPreset(); | |||
PropertyChanged (kAudioUnitProperty_ClassInfo, kAudioUnitScope_Global, 0); | |||
PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); | |||
if (details.programChanged) | |||
{ | |||
refreshCurrentPreset(); | |||
PropertyChanged (kAudioUnitProperty_PresentPreset, kAudioUnitScope_Global, 0); | |||
} | |||
} | |||
//============================================================================== | |||
@@ -1900,7 +1911,7 @@ private: | |||
return (scope != kAudioUnitScope_Input | |||
&& scope != kAudioUnitScope_Output) | |||
? kAudioUnitErr_InvalidScope : noErr; | |||
? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; | |||
} | |||
OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept | |||
@@ -2040,10 +2051,12 @@ private: | |||
addSupportedLayoutTags(); | |||
for (int i = 0; i < enabledInputs; ++i) | |||
if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) return err; | |||
if ((err = syncAudioUnitWithChannelSet (true, i, juceFilter->getChannelLayoutOfBus (true, i))) != noErr) | |||
return err; | |||
for (int i = 0; i < enabledOutputs; ++i) | |||
if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) return err; | |||
if ((err = syncAudioUnitWithChannelSet (false, i, juceFilter->getChannelLayoutOfBus (false, i))) != noErr) | |||
return err; | |||
return noErr; | |||
} | |||
@@ -2166,7 +2179,7 @@ private: | |||
#endif | |||
// add discrete layout tags | |||
int n = bus->getMaxSupportedChannels(maxChannelsToProbeFor()); | |||
int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); | |||
for (int ch = 0; ch < n; ++ch) | |||
{ | |||
@@ -73,6 +73,8 @@ | |||
#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) | |||
#include <future> | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") | |||
using namespace juce; | |||
@@ -912,7 +914,7 @@ public: | |||
#endif | |||
//============================================================================== | |||
void audioProcessorChanged (AudioProcessor* processor) override | |||
void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails&) override | |||
{ | |||
ignoreUnused (processor); | |||
@@ -1750,7 +1752,7 @@ public: | |||
{ | |||
JUCE_ASSERT_MESSAGE_THREAD | |||
if (processorHolder != nullptr) | |||
if (processorHolder.get() != nullptr) | |||
JuceAudioUnitv3::removeEditor (getAudioProcessor()); | |||
} | |||
@@ -1795,22 +1797,25 @@ public: | |||
void viewDidLayoutSubviews() | |||
{ | |||
if (processorHolder != nullptr && [myself view] != nullptr) | |||
if (auto holder = processorHolder.get()) | |||
{ | |||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | |||
if ([myself view] != nullptr) | |||
{ | |||
if (processorHolder->viewConfiguration != nullptr) | |||
editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); | |||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | |||
{ | |||
if (holder->viewConfiguration != nullptr) | |||
editor->hostMIDIControllerIsAvailable (holder->viewConfiguration->hostHasMIDIController); | |||
editor->setBounds (convertToRectInt ([[myself view] bounds])); | |||
editor->setBounds (convertToRectInt ([[myself view] bounds])); | |||
if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) | |||
{ | |||
#if JUCE_IOS | |||
[peerView setNeedsDisplay]; | |||
#else | |||
[peerView setNeedsDisplay: YES]; | |||
#endif | |||
if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) | |||
{ | |||
#if JUCE_IOS | |||
[peerView setNeedsDisplay]; | |||
#else | |||
[peerView setNeedsDisplay: YES]; | |||
#endif | |||
} | |||
} | |||
} | |||
} | |||
@@ -1818,21 +1823,21 @@ public: | |||
void didReceiveMemoryWarning() | |||
{ | |||
if (processorHolder != nullptr) | |||
if (auto* processor = processorHolder->get()) | |||
if (auto ptr = processorHolder.get()) | |||
if (auto* processor = ptr->get()) | |||
processor->memoryWarningReceived(); | |||
} | |||
void viewDidAppear (bool) | |||
{ | |||
if (processorHolder != nullptr) | |||
if (processorHolder.get() != nullptr) | |||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | |||
editor->setVisible (true); | |||
} | |||
void viewDidDisappear (bool) | |||
{ | |||
if (processorHolder != nullptr) | |||
if (processorHolder.get() != nullptr) | |||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | |||
editor->setVisible (false); | |||
} | |||
@@ -1846,65 +1851,72 @@ public: | |||
//============================================================================== | |||
AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) | |||
{ | |||
AUAudioUnit* retval = nil; | |||
const auto holder = [&] | |||
{ | |||
if (auto initialisedHolder = processorHolder.get()) | |||
return initialisedHolder; | |||
if (! MessageManager::getInstance()->isThisTheMessageThread()) | |||
waitForExecutionOnMainThread ([this] { [myself view]; }); | |||
return processorHolder.get(); | |||
}(); | |||
if (holder == nullptr) | |||
return nullptr; | |||
return (new JuceAudioUnitv3 (holder, descr, 0, error))->getAudioUnit(); | |||
} | |||
private: | |||
template <typename Callback> | |||
static void waitForExecutionOnMainThread (Callback&& callback) | |||
{ | |||
if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
{ | |||
WaitableEvent creationEvent; | |||
callback(); | |||
return; | |||
} | |||
// AUv3 headers say that we may block this thread and that the message thread is guaranteed | |||
// to be unblocked | |||
struct AUCreator : public CallbackMessage | |||
{ | |||
JuceAUViewController& owner; | |||
AudioComponentDescription pDescr; | |||
NSError** pError; | |||
AUAudioUnit*& outAU; | |||
WaitableEvent& e; | |||
AUCreator (JuceAUViewController& parent, const AudioComponentDescription& paramDescr, NSError** paramError, | |||
AUAudioUnit*& outputAU, WaitableEvent& event) | |||
: owner (parent), pDescr (paramDescr), pError (paramError), outAU (outputAU), e (event) | |||
{} | |||
void messageCallback() override | |||
{ | |||
outAU = owner.createAudioUnitOnMessageThread (pDescr, pError); | |||
e.signal(); | |||
} | |||
}; | |||
std::promise<void> promise; | |||
MessageManager::callAsync ([&] | |||
{ | |||
callback(); | |||
promise.set_value(); | |||
}); | |||
(new AUCreator (*this, descr, error, retval, creationEvent))->post(); | |||
creationEvent.wait (-1); | |||
promise.get_future().get(); | |||
} | |||
// There's a chance that createAudioUnit will be called from a background | |||
// thread while the processorHolder is being updated on the main thread. | |||
class LockedProcessorHolder | |||
{ | |||
public: | |||
AudioProcessorHolder::Ptr get() const | |||
{ | |||
const ScopedLock lock (mutex); | |||
return holder; | |||
} | |||
else | |||
LockedProcessorHolder& operator= (const AudioProcessorHolder::Ptr& other) | |||
{ | |||
retval = createAudioUnitOnMessageThread (descr, error); | |||
const ScopedLock lock (mutex); | |||
holder = other; | |||
return *this; | |||
} | |||
return [retval autorelease]; | |||
} | |||
private: | |||
mutable CriticalSection mutex; | |||
AudioProcessorHolder::Ptr holder; | |||
}; | |||
private: | |||
//============================================================================== | |||
AUViewController<AUAudioUnitFactory>* myself; | |||
AudioProcessorHolder::Ptr processorHolder = nullptr; | |||
LockedProcessorHolder processorHolder; | |||
Rectangle<int> preferredSize { 1, 1 }; | |||
//============================================================================== | |||
AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) | |||
{ | |||
JUCE_ASSERT_MESSAGE_THREAD | |||
[myself view]; // this will call [view load] and ensure that the AudioProcessor has been instantiated | |||
if (processorHolder == nullptr) | |||
return nullptr; | |||
return (new JuceAudioUnitv3 (processorHolder, descr, 0, error))->getAudioUnit(); | |||
} | |||
AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } | |||
AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder.get(); } | |||
}; | |||
//============================================================================== | |||
@@ -577,9 +577,9 @@ public: | |||
} | |||
} | |||
void audioProcessorChanged (AudioProcessor*) | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) | |||
{ | |||
if (filter != nullptr && programsHost != nullptr) | |||
if (details.programChanged && filter != nullptr && programsHost != nullptr) | |||
{ | |||
if (filter->getNumPrograms() != lastProgramCount) | |||
{ | |||
@@ -364,6 +364,7 @@ static const String makePresetsFile (AudioProcessor* const filter) | |||
// Header | |||
text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n"; | |||
text += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; | |||
text += "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n"; | |||
text += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n"; | |||
text += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n"; | |||
text += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"; | |||
@@ -385,6 +386,7 @@ static const String makePresetsFile (AudioProcessor* const filter) | |||
text += " rdfs:domain state:State ;\n"; | |||
text += " rdfs:range xsd:base64Binary .\n"; | |||
#endif | |||
text += "\n"; | |||
#endif | |||
// Presets | |||
@@ -790,7 +790,7 @@ public: | |||
ReleaseControl (index + 2); | |||
} | |||
void audioProcessorChanged (AudioProcessor*) override | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override | |||
{ | |||
// xxx is there an RTAS equivalent? | |||
} | |||
@@ -131,7 +131,7 @@ struct UnityAudioEffectState | |||
UnityAudioAmbisonicData* ambisonicData; | |||
template<typename T> | |||
template <typename T> | |||
inline T* getEffectData() const | |||
{ | |||
jassert (effectData != nullptr); | |||
@@ -23,9 +23,10 @@ | |||
============================================================================== | |||
*/ | |||
#include <juce_core/system/juce_TargetPlatform.h> | |||
#if JucePlugin_Build_Unity | |||
#include <juce_core/system/juce_TargetPlatform.h> | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | |||
@@ -205,9 +205,9 @@ struct SharedMessageThread : public Thread | |||
initialiseJuce_GUI(); | |||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
initialised = true; | |||
XWindowSystem::getInstance(); | |||
initialised = true; | |||
while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) | |||
{} | |||
@@ -246,7 +246,6 @@ struct AbletonLiveHostSpecific | |||
class JuceVSTWrapper : public AudioProcessorListener, | |||
public AudioPlayHead, | |||
private Timer, | |||
private AsyncUpdater, | |||
private AudioProcessorParameter::Listener | |||
{ | |||
private: | |||
@@ -791,19 +790,9 @@ public: | |||
void parameterGestureChanged (int, bool) override {} | |||
void audioProcessorChanged (AudioProcessor*) override | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override | |||
{ | |||
vstEffect.latency = processor->getLatencySamples(); | |||
triggerAsyncUpdate(); | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
if (hostCallback != nullptr) | |||
{ | |||
hostCallback (&vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); | |||
hostCallback (&vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); | |||
} | |||
hostChangeUpdater.update (details); | |||
} | |||
bool getPinProperties (Vst2::VstPinInfo& properties, bool direction, int index) const | |||
@@ -1031,21 +1020,17 @@ public: | |||
: wrapper (w) | |||
{ | |||
editor.setOpaque (true); | |||
editor.setVisible (true); | |||
setOpaque (true); | |||
setTopLeftPosition (editor.getPosition()); | |||
editor.setTopLeftPosition (0, 0); | |||
auto b = getLocalArea (&editor, editor.getLocalBounds()); | |||
setSize (b.getWidth(), b.getHeight()); | |||
addAndMakeVisible (editor); | |||
auto editorBounds = getSizeToContainChild(); | |||
setSize (editorBounds.getWidth(), editorBounds.getHeight()); | |||
#if JUCE_WINDOWS | |||
if (! getHostType().isReceptor()) | |||
addMouseListener (this, true); | |||
#endif | |||
setOpaque (true); | |||
ignoreUnused (fakeMouseGenerator); | |||
} | |||
@@ -1055,42 +1040,35 @@ public: | |||
// have been transferred to another parent which takes over ownership. | |||
} | |||
void paint (Graphics&) override {} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (Colours::black); | |||
} | |||
void getEditorBounds (Vst2::VstEditorBounds& bounds) | |||
{ | |||
auto b = getSizeToContainChild(); | |||
bounds = convertToHostBounds ({ 0, 0, (int16) b.getHeight(), (int16) b.getWidth() }); | |||
auto editorBounds = getSizeToContainChild(); | |||
bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); | |||
} | |||
void attachToHost (VstOpCodeArguments args) | |||
{ | |||
setOpaque (true); | |||
setVisible (false); | |||
#if JUCE_WINDOWS | |||
#if JUCE_WINDOWS || JUCE_LINUX | |||
addToDesktop (0, args.ptr); | |||
hostWindow = (HWND) args.ptr; | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
hostWindow = (HostWindowType) args.ptr; | |||
#if JUCE_LINUX | |||
X11Symbols::getInstance()->xReparentWindow (display, | |||
(Window) getWindowHandle(), | |||
(HostWindowType) hostWindow, | |||
0, 0); | |||
#elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
checkHostWindowScaleFactor(); | |||
startTimer (500); | |||
#endif | |||
#elif JUCE_LINUX | |||
addToDesktop (0, args.ptr); | |||
hostWindow = (Window) args.ptr; | |||
X11Symbols::getInstance()->xReparentWindow (display, (Window) getWindowHandle(), hostWindow, 0, 0); | |||
if (auto* peer = getPeer()) | |||
{ | |||
auto screenBounds = peer->localToGlobal (peer->getBounds()); | |||
auto scale = Desktop::getInstance().getDisplays().findDisplayForRect (screenBounds, false).scale | |||
/ Desktop::getInstance().getGlobalScaleFactor(); | |||
setContentScaleFactor ((float) scale); | |||
} | |||
#else | |||
#elif JUCE_MAC | |||
hostWindow = attachComponentToWindowRefVST (this, args.ptr, wrapper.useNSView); | |||
#endif | |||
@@ -1101,15 +1079,10 @@ public: | |||
{ | |||
#if JUCE_MAC | |||
if (hostWindow != nullptr) | |||
{ | |||
detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView); | |||
hostWindow = nullptr; | |||
} | |||
#endif | |||
#if JUCE_LINUX | |||
hostWindow = {}; | |||
#endif | |||
} | |||
void checkVisibility() | |||
@@ -1127,23 +1100,21 @@ public: | |||
void resized() override | |||
{ | |||
auto newBounds = getLocalBounds(); | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (! lastBounds.isEmpty() && isWithin (newBounds.toDouble().getAspectRatio(), lastBounds.toDouble().getAspectRatio(), 0.1)) | |||
return; | |||
lastBounds = newBounds; | |||
#endif | |||
if (auto* ed = getEditorComp()) | |||
if (auto* pluginEditor = getEditorComp()) | |||
{ | |||
ed->setTopLeftPosition (0, 0); | |||
if (! resizingParent) | |||
{ | |||
auto newBounds = getLocalBounds(); | |||
{ | |||
const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true); | |||
pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); | |||
} | |||
if (shouldResizeEditor) | |||
ed->setBounds (ed->getLocalArea (this, newBounds)); | |||
lastBounds = newBounds; | |||
} | |||
updateWindowSize (false); | |||
updateWindowSize(); | |||
} | |||
#if JUCE_MAC && ! JUCE_64BIT | |||
@@ -1154,59 +1125,63 @@ public: | |||
void parentSizeChanged() override | |||
{ | |||
updateWindowSize (true); | |||
updateWindowSize(); | |||
} | |||
void childBoundsChanged (Component*) override | |||
{ | |||
updateWindowSize (false); | |||
if (resizingChild) | |||
return; | |||
auto newBounds = getSizeToContainChild(); | |||
if (newBounds != lastBounds) | |||
{ | |||
updateWindowSize(); | |||
lastBounds = newBounds; | |||
} | |||
} | |||
juce::Rectangle<int> getSizeToContainChild() | |||
{ | |||
if (auto* ed = getEditorComp()) | |||
return getLocalArea (ed, ed->getLocalBounds()); | |||
if (auto* pluginEditor = getEditorComp()) | |||
return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); | |||
return {}; | |||
} | |||
void updateWindowSize (bool resizeEditor) | |||
void updateWindowSize() | |||
{ | |||
if (! isInSizeWindow) | |||
if (! resizingParent | |||
&& getEditorComp() != nullptr | |||
&& hostWindow != HostWindowType{}) | |||
{ | |||
if (auto* ed = getEditorComp()) | |||
{ | |||
ed->setTopLeftPosition (0, 0); | |||
auto pos = getSizeToContainChild(); | |||
auto editorBounds = getSizeToContainChild(); | |||
#if JUCE_MAC | |||
if (wrapper.useNSView) | |||
setTopLeftPosition (0, getHeight() - pos.getHeight()); | |||
#endif | |||
resizeHostWindow (pos.getWidth(), pos.getHeight()); | |||
#if ! JUCE_LINUX // setSize() on linux causes renoise and energyxt to fail. | |||
if (! resizeEditor) // this is needed to prevent an infinite resizing loop due to coordinate rounding | |||
shouldResizeEditor = false; | |||
#if JUCE_MAC | |||
if (wrapper.useNSView) | |||
setTopLeftPosition (0, getHeight() - editorBounds.getHeight()); | |||
#endif | |||
setSize (pos.getWidth(), pos.getHeight()); | |||
resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); | |||
shouldResizeEditor = true; | |||
#else | |||
ignoreUnused (resizeEditor); | |||
{ | |||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||
auto scale = Desktop::getInstance().getGlobalScaleFactor(); | |||
#if JUCE_LINUX // setSize() on linux causes renoise and energyxt to fail. | |||
auto rect = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); | |||
X11Symbols::getInstance()->xResizeWindow (display, (Window) getWindowHandle(), | |||
static_cast<unsigned int> (roundToInt ((float) pos.getWidth() * scale)), | |||
static_cast<unsigned int> (roundToInt ((float) pos.getHeight() * scale))); | |||
#endif | |||
#if JUCE_MAC | |||
resizeHostWindow (pos.getWidth(), pos.getHeight()); // (doing this a second time seems to be necessary in tracktion) | |||
static_cast<unsigned int> (rect.rightmost - rect.leftmost), | |||
static_cast<unsigned int> (rect.lower - rect.upper)); | |||
#else | |||
setSize (editorBounds.getWidth(), editorBounds.getHeight()); | |||
#endif | |||
} | |||
#if JUCE_MAC | |||
resizeHostWindow (editorBounds.getWidth(), editorBounds.getHeight()); // (doing this a second time seems to be necessary in tracktion) | |||
#endif | |||
} | |||
} | |||
@@ -1224,23 +1199,23 @@ public: | |||
if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive()) | |||
{ | |||
const ScopedValueSetter<bool> inSizeWindowSetter (isInSizeWindow, true); | |||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||
sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::hostOpcodeWindowSize, | |||
newWidth, newHeight, nullptr, 0) != 0); | |||
} | |||
} | |||
// some hosts don't support the sizeWindow call, so do it manually.. | |||
if (! sizeWasSuccessful) | |||
{ | |||
// some hosts don't support the sizeWindow call, so do it manually.. | |||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||
#if JUCE_MAC | |||
setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView); | |||
#elif JUCE_LINUX | |||
// (Currently, all linux hosts support sizeWindow, so this should never need to happen) | |||
setSize (newWidth, newHeight); | |||
#else | |||
int dw = 0; | |||
int dh = 0; | |||
@@ -1286,12 +1261,6 @@ public: | |||
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); | |||
#endif | |||
} | |||
if (auto* peer = getPeer()) | |||
{ | |||
peer->handleMovedOrResized(); | |||
repaint(); | |||
} | |||
} | |||
void setContentScaleFactor (float scale) | |||
@@ -1300,27 +1269,22 @@ public: | |||
{ | |||
editorScaleFactor = scale; | |||
if (auto* ed = getEditorComp()) | |||
ed->setScaleFactor (editorScaleFactor); | |||
updateWindowSize (true); | |||
} | |||
} | |||
if (auto* pluginEditor = getEditorComp()) | |||
{ | |||
auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
void checkHostWindowScaleFactor() | |||
{ | |||
auto hostWindowScale = (float) getScaleFactorForWindow (hostWindow); | |||
{ | |||
const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true); | |||
if (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, editorScaleFactor)) | |||
wrapper.handleSetContentScaleFactor (hostWindowScale); | |||
} | |||
pluginEditor->setScaleFactor (editorScaleFactor); | |||
pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); | |||
} | |||
void timerCallback() override | |||
{ | |||
checkHostWindowScaleFactor(); | |||
lastBounds = getSizeToContainChild(); | |||
updateWindowSize(); | |||
} | |||
} | |||
} | |||
#endif | |||
#if JUCE_WINDOWS | |||
void mouseDown (const MouseEvent&) override | |||
@@ -1336,6 +1300,21 @@ public: | |||
if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) | |||
SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); | |||
} | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
void checkHostWindowScaleFactor() | |||
{ | |||
auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); | |||
if (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, editorScaleFactor)) | |||
wrapper.handleSetContentScaleFactor (hostWindowScale); | |||
} | |||
void timerCallback() override | |||
{ | |||
checkHostWindowScaleFactor(); | |||
} | |||
#endif | |||
#endif | |||
#if JUCE_MAC | |||
@@ -1347,6 +1326,7 @@ public: | |||
} | |||
#endif | |||
private: | |||
//============================================================================== | |||
static Vst2::VstEditorBounds convertToHostBounds (const Vst2::VstEditorBounds& rect) | |||
{ | |||
@@ -1364,29 +1344,55 @@ public: | |||
//============================================================================== | |||
JuceVSTWrapper& wrapper; | |||
FakeMouseMoveGenerator fakeMouseGenerator; | |||
bool isInSizeWindow = false; | |||
bool shouldResizeEditor = true; | |||
bool resizingChild = false, resizingParent = false; | |||
float editorScaleFactor = 1.0f; | |||
juce::Rectangle<int> lastBounds; | |||
#if JUCE_MAC | |||
void* hostWindow = nullptr; | |||
#elif JUCE_LINUX | |||
#if JUCE_LINUX | |||
using HostWindowType = ::Window; | |||
::Display* display = XWindowSystem::getInstance()->getDisplay(); | |||
Window hostWindow = {}; | |||
#elif JUCE_WINDOWS | |||
HWND hostWindow = {}; | |||
using HostWindowType = HWND; | |||
WindowsHooks hooks; | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
juce::Rectangle<int> lastBounds; | |||
#endif | |||
#else | |||
using HostWindowType = void*; | |||
#endif | |||
HostWindowType hostWindow = {}; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) | |||
}; | |||
//============================================================================== | |||
private: | |||
struct HostChangeUpdater : private AsyncUpdater | |||
{ | |||
explicit HostChangeUpdater (JuceVSTWrapper& o) : owner (o) {} | |||
~HostChangeUpdater() override { cancelPendingUpdate(); } | |||
void update (const ChangeDetails& details) | |||
{ | |||
if (details.latencyChanged) | |||
owner.vstEffect.latency = owner.processor->getLatencySamples(); | |||
if (details.parameterInfoChanged || details.programChanged) | |||
triggerAsyncUpdate(); | |||
} | |||
private: | |||
void handleAsyncUpdate() override | |||
{ | |||
if (auto* callback = owner.hostCallback) | |||
{ | |||
callback (&owner.vstEffect, Vst2::hostOpcodeUpdateView, 0, 0, nullptr, 0); | |||
callback (&owner.vstEffect, Vst2::hostOpcodeIOModified, 0, 0, nullptr, 0); | |||
} | |||
} | |||
JuceVSTWrapper& owner; | |||
}; | |||
static JuceVSTWrapper* getWrapper (Vst2::VstEffectInterface* v) noexcept { return static_cast<JuceVSTWrapper*> (v->effectPointer); } | |||
bool isProcessLevelOffline() | |||
@@ -1620,9 +1626,9 @@ private: | |||
if (editorComp != nullptr) | |||
{ | |||
editorComp->getEditorBounds (editorBounds); | |||
*((Vst2::VstEditorBounds**) args.ptr) = &editorBounds; | |||
return (pointer_sized_int) &editorBounds; | |||
editorComp->getEditorBounds (editorRect); | |||
*((Vst2::VstEditorBounds**) args.ptr) = &editorRect; | |||
return (pointer_sized_int) &editorRect; | |||
} | |||
return 0; | |||
@@ -2081,7 +2087,7 @@ private: | |||
juce::MemoryBlock chunkMemory; | |||
uint32 chunkMemoryTime = 0; | |||
std::unique_ptr<EditorCompWrapper> editorComp; | |||
Vst2::VstEditorBounds editorBounds; | |||
Vst2::VstEditorBounds editorRect; | |||
MidiBuffer midiEvents; | |||
VSTMidiEventList outgoingEvents; | |||
@@ -2110,6 +2116,8 @@ private: | |||
ThreadLocalValue<bool> inParameterChangedCallback; | |||
HostChangeUpdater hostChangeUpdater { *this }; | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) | |||
}; | |||
@@ -228,9 +228,26 @@ public: | |||
return getParamForVSTParamID (bypassParamID); | |||
} | |||
AudioProcessorParameter* getProgramParameter() const noexcept | |||
{ | |||
return getParamForVSTParamID (JuceAudioProcessor::paramPreset); | |||
} | |||
static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) | |||
{ | |||
return group == nullptr ? Vst::kRootUnitId : group->getID().hashCode(); | |||
if (group == nullptr || group->getParent() == nullptr) | |||
return Vst::kRootUnitId; | |||
// From the VST3 docs (also applicable to unit IDs!): | |||
// Up to 2^31 parameters can be exported with id range [0, 2147483648] | |||
// (the range [2147483649, 429496729] is reserved for host application). | |||
auto unitID = group->getID().hashCode() & 0x7fffffff; | |||
// If you hit this assertion then your group ID is hashing to a value | |||
// reserved by the VST3 SDK. Please use a different group ID. | |||
jassert (unitID != Vst::kRootUnitId); | |||
return unitID; | |||
} | |||
int getNumParameters() const noexcept { return vstParamIDs.size(); } | |||
@@ -260,6 +277,21 @@ private: | |||
{ | |||
parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); | |||
#if JUCE_DEBUG | |||
auto allGroups = parameterGroups; | |||
allGroups.add (&audioProcessor->getParameterTree()); | |||
std::unordered_set<Vst::UnitID> unitIDs; | |||
for (auto* group : allGroups) | |||
{ | |||
auto insertResult = unitIDs.insert (getUnitID (group)); | |||
// If you hit this assertion then either a group ID is not unique or | |||
// you are very unlucky and a hashed group ID is not unique | |||
jassert (insertResult.second); | |||
} | |||
#endif | |||
#if JUCE_FORCE_USE_LEGACY_PARAM_IDS | |||
const bool forceLegacyParamIDs = true; | |||
#else | |||
@@ -306,6 +338,20 @@ private: | |||
vstParamIDs.add (vstParamID); | |||
paramMap.set (static_cast<int32> (vstParamID), juceParam); | |||
} | |||
auto numPrograms = audioProcessor->getNumPrograms(); | |||
if (numPrograms > 1) | |||
{ | |||
ownedProgramParameter = std::make_unique<AudioParameterInt> ("juceProgramParameter", "Program", | |||
0, numPrograms - 1, | |||
audioProcessor->getCurrentProgram()); | |||
juceParameters.params.add (ownedProgramParameter.get()); | |||
vstParamIDs.add (JuceAudioProcessor::paramPreset); | |||
paramMap.set (static_cast<int32> (JuceAudioProcessor::paramPreset), ownedProgramParameter.get()); | |||
} | |||
} | |||
Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param) | |||
@@ -333,7 +379,7 @@ private: | |||
//============================================================================== | |||
LegacyAudioParametersWrapper juceParameters; | |||
HashMap<int32, AudioProcessorParameter*> paramMap; | |||
std::unique_ptr<AudioProcessorParameter> ownedBypassParameter; | |||
std::unique_ptr<AudioProcessorParameter> ownedBypassParameter, ownedProgramParameter; | |||
Array<const AudioProcessorParameterGroup*> parameterGroups; | |||
JuceAudioProcessor() = delete; | |||
@@ -349,8 +395,7 @@ class JuceVST3EditController : public Vst::EditController, | |||
public Vst::IMidiMapping, | |||
public Vst::IUnitInfo, | |||
public Vst::ChannelContext::IInfoListener, | |||
public AudioProcessorListener, | |||
private AudioProcessorParameter::Listener | |||
public AudioProcessorListener | |||
{ | |||
public: | |||
JuceVST3EditController (Vst::IHostApplication* host) | |||
@@ -564,16 +609,20 @@ public: | |||
bool setNormalized (Vst::ParamValue v) override | |||
{ | |||
Vst::ParamValue program = v * info.stepCount; | |||
if (! isPositiveAndBelow ((int) program, owner.getNumPrograms())) | |||
return false; | |||
auto programValue = roundToInt (toPlain (v)); | |||
if (valueNormalized != v) | |||
if (isPositiveAndBelow (programValue, owner.getNumPrograms())) | |||
{ | |||
valueNormalized = v; | |||
changed(); | |||
return true; | |||
if (programValue != owner.getCurrentProgram()) | |||
owner.setCurrentProgram (programValue); | |||
if (valueNormalized != v) | |||
{ | |||
valueNormalized = v; | |||
changed(); | |||
return true; | |||
} | |||
} | |||
return false; | |||
@@ -653,17 +702,21 @@ public: | |||
//============================================================================== | |||
tresult PLUGIN_API setComponentState (IBStream* stream) override | |||
{ | |||
// Cubase and Nuendo need to inform the host of the current parameter values | |||
if (auto* pluginInstance = getPluginInstance()) | |||
{ | |||
for (auto vstParamId : audioProcessor->vstParamIDs) | |||
setParamNormalized (vstParamId, audioProcessor->getParamForVSTParamID (vstParamId)->getValue()); | |||
{ | |||
auto paramValue = [&] | |||
{ | |||
if (vstParamId == JuceAudioProcessor::paramPreset) | |||
return EditController::plainParamToNormalized (JuceAudioProcessor::paramPreset, | |||
pluginInstance->getCurrentProgram()); | |||
auto numPrograms = pluginInstance->getNumPrograms(); | |||
return (double) audioProcessor->getParamForVSTParamID (vstParamId)->getValue(); | |||
}(); | |||
if (numPrograms > 1) | |||
setParamNormalized (JuceAudioProcessor::paramPreset, static_cast<Vst::ParamValue> (pluginInstance->getCurrentProgram()) | |||
/ static_cast<Vst::ParamValue> (numPrograms - 1)); | |||
setParamNormalized (vstParamId, paramValue); | |||
} | |||
} | |||
if (auto* handler = getComponentHandler()) | |||
@@ -882,7 +935,7 @@ public: | |||
} | |||
//============================================================================== | |||
void paramChanged (Vst::ParamID vstParamId, float newValue) | |||
void paramChanged (Vst::ParamID vstParamId, double newValue) | |||
{ | |||
if (inParameterChangedCallback.get()) | |||
{ | |||
@@ -891,8 +944,8 @@ public: | |||
} | |||
// NB: Cubase has problems if performEdit is called without setParamNormalized | |||
EditController::setParamNormalized (vstParamId, (double) newValue); | |||
performEdit (vstParamId, (double) newValue); | |||
EditController::setParamNormalized (vstParamId, newValue); | |||
performEdit (vstParamId, newValue); | |||
} | |||
//============================================================================== | |||
@@ -904,39 +957,43 @@ public: | |||
paramChanged (audioProcessor->getVSTParamIDForIndex (index), newValue); | |||
} | |||
void audioProcessorChanged (AudioProcessor*) override | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override | |||
{ | |||
int32 flags = 0; | |||
for (int32 i = 0; i < parameters.getParameterCount(); ++i) | |||
if (auto* param = dynamic_cast<Param*> (parameters.getParameterByIndex (i))) | |||
if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) | |||
flags |= Vst::kParamTitlesChanged; | |||
if (details.parameterInfoChanged) | |||
{ | |||
for (int32 i = 0; i < parameters.getParameterCount(); ++i) | |||
if (auto* param = dynamic_cast<Param*> (parameters.getParameterByIndex (i))) | |||
if (param->updateParameterInfo() && (flags & Vst::kParamTitlesChanged) == 0) | |||
flags |= Vst::kParamTitlesChanged; | |||
} | |||
if (auto* pluginInstance = getPluginInstance()) | |||
{ | |||
auto newNumPrograms = pluginInstance->getNumPrograms(); | |||
if (newNumPrograms != lastNumPrograms) | |||
if (details.programChanged && audioProcessor->getProgramParameter() != nullptr) | |||
{ | |||
if (newNumPrograms > 1) | |||
auto currentProgram = pluginInstance->getCurrentProgram(); | |||
auto paramValue = roundToInt (EditController::normalizedParamToPlain (JuceAudioProcessor::paramPreset, | |||
EditController::getParamNormalized (JuceAudioProcessor::paramPreset))); | |||
if (currentProgram != paramValue) | |||
{ | |||
auto paramValue = static_cast<Vst::ParamValue> (pluginInstance->getCurrentProgram()) | |||
/ static_cast<Vst::ParamValue> (pluginInstance->getNumPrograms() - 1); | |||
beginEdit (JuceAudioProcessor::paramPreset); | |||
paramChanged (JuceAudioProcessor::paramPreset, | |||
EditController::plainParamToNormalized (JuceAudioProcessor::paramPreset, currentProgram)); | |||
endEdit (JuceAudioProcessor::paramPreset); | |||
EditController::setParamNormalized (JuceAudioProcessor::paramPreset, paramValue); | |||
flags |= Vst::kParamValuesChanged; | |||
} | |||
lastNumPrograms = newNumPrograms; | |||
} | |||
auto newLatencySamples = pluginInstance->getLatencySamples(); | |||
auto latencySamples = pluginInstance->getLatencySamples(); | |||
if (newLatencySamples != lastLatencySamples) | |||
if (details.latencyChanged && latencySamples != lastLatencySamples) | |||
{ | |||
flags |= Vst::kLatencyChanged; | |||
lastLatencySamples = newLatencySamples; | |||
lastLatencySamples = latencySamples; | |||
} | |||
} | |||
@@ -944,19 +1001,6 @@ public: | |||
componentHandler->restartComponent (flags); | |||
} | |||
void parameterValueChanged (int, float newValue) override | |||
{ | |||
// this can only come from the bypass parameter | |||
paramChanged (audioProcessor->bypassParamID, newValue); | |||
} | |||
void parameterGestureChanged (int, bool gestureIsStarting) override | |||
{ | |||
// this can only come from the bypass parameter | |||
if (gestureIsStarting) beginEdit (audioProcessor->bypassParamID); | |||
else endEdit (audioProcessor->bypassParamID); | |||
} | |||
//============================================================================== | |||
AudioProcessor* getPluginInstance() const noexcept | |||
{ | |||
@@ -971,7 +1015,7 @@ private: | |||
friend struct Param; | |||
//============================================================================== | |||
ComSmartPtr<JuceAudioProcessor> audioProcessor; | |||
VSTComSmartPtr<JuceAudioProcessor> audioProcessor; | |||
struct MidiController | |||
{ | |||
@@ -983,11 +1027,42 @@ private: | |||
MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; | |||
Vst::ParamID midiControllerToParameter[numMIDIChannels][Vst::kCountCtrlNumber]; | |||
//============================================================================== | |||
struct OwnedParameterListener : public AudioProcessorParameter::Listener | |||
{ | |||
OwnedParameterListener (JuceVST3EditController& editController, | |||
AudioProcessorParameter& juceParameter, | |||
Vst::ParamID paramID) | |||
: owner (editController), | |||
vstParamID (paramID) | |||
{ | |||
juceParameter.addListener (this); | |||
} | |||
void parameterValueChanged (int, float newValue) override | |||
{ | |||
owner.paramChanged (vstParamID, newValue); | |||
} | |||
void parameterGestureChanged (int, bool gestureIsStarting) override | |||
{ | |||
if (gestureIsStarting) | |||
owner.beginEdit (vstParamID); | |||
else | |||
owner.endEdit (vstParamID); | |||
} | |||
JuceVST3EditController& owner; | |||
Vst::ParamID vstParamID; | |||
}; | |||
std::vector<std::unique_ptr<OwnedParameterListener>> ownedParameterListeners; | |||
//============================================================================== | |||
std::atomic<bool> vst3IsPlaying { false }, | |||
inSetupProcessing { false }; | |||
int lastNumPrograms = 0, lastLatencySamples = 0; | |||
int lastLatencySamples = 0; | |||
#if ! JUCE_MAC | |||
float lastScaleFactorReceived = 1.0f; | |||
@@ -999,10 +1074,11 @@ private: | |||
{ | |||
pluginInstance->addListener (this); | |||
// as the bypass is not part of the regular parameters | |||
// we need to listen for it explicitly | |||
// as the bypass is not part of the regular parameters we need to listen for it explicitly | |||
if (! audioProcessor->bypassIsRegularParameter) | |||
audioProcessor->getBypassParameter()->addListener (this); | |||
ownedParameterListeners.push_back (std::make_unique<OwnedParameterListener> (*this, | |||
*audioProcessor->getBypassParameter(), | |||
audioProcessor->bypassParamID)); | |||
if (parameters.getParameterCount() <= 0) | |||
{ | |||
@@ -1011,6 +1087,10 @@ private: | |||
for (int i = 0; i < n; ++i) | |||
{ | |||
auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | |||
if (vstParamID == JuceAudioProcessor::paramPreset) | |||
continue; | |||
auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); | |||
auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); | |||
auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); | |||
@@ -1019,8 +1099,14 @@ private: | |||
(vstParamID == audioProcessor->bypassParamID))); | |||
} | |||
if (pluginInstance->getNumPrograms() > 1) | |||
if (auto* programParam = audioProcessor->getProgramParameter()) | |||
{ | |||
ownedParameterListeners.push_back (std::make_unique<OwnedParameterListener> (*this, | |||
*programParam, | |||
JuceAudioProcessor::paramPreset)); | |||
parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); | |||
} | |||
} | |||
#if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS | |||
@@ -1030,7 +1116,7 @@ private: | |||
initialiseMidiControllerMappings(); | |||
#endif | |||
audioProcessorChanged (pluginInstance); | |||
audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); | |||
} | |||
} | |||
@@ -1138,8 +1224,8 @@ private: | |||
createContentWrapperComponentIfNeeded(); | |||
#if JUCE_WINDOWS || JUCE_LINUX | |||
component->addToDesktop (0, parent); | |||
component->setOpaque (true); | |||
component->addToDesktop (0, (void*) systemWindow); | |||
component->setVisible (true); | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
@@ -1206,10 +1292,7 @@ private: | |||
if (component != nullptr) | |||
{ | |||
auto w = rect.getWidth(); | |||
auto h = rect.getHeight(); | |||
component->setSize (w, h); | |||
component->setSize (rect.getWidth(), rect.getHeight()); | |||
#if JUCE_MAC | |||
if (cubase10Workaround != nullptr) | |||
@@ -1233,6 +1316,11 @@ private: | |||
tresult PLUGIN_API getSize (ViewRect* size) override | |||
{ | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (getHostType().isAbletonLive() && systemWindow == nullptr) | |||
return kResultFalse; | |||
#endif | |||
if (size != nullptr && component != nullptr) | |||
{ | |||
auto editorBounds = component->getSizeToContainChild(); | |||
@@ -1264,20 +1352,19 @@ private: | |||
{ | |||
*rectToCheck = convertFromHostBounds (*rectToCheck); | |||
auto transformScale = std::sqrt (std::abs (editor->getTransform().getDeterminant())); | |||
auto editorBounds = editor->getLocalArea (component.get(), | |||
Rectangle<int>::leftTopRightBottom (rectToCheck->left, rectToCheck->top, | |||
rectToCheck->right, rectToCheck->bottom).toFloat()); | |||
auto minW = (double) ((float) constrainer->getMinimumWidth() * transformScale); | |||
auto maxW = (double) ((float) constrainer->getMaximumWidth() * transformScale); | |||
auto minH = (double) ((float) constrainer->getMinimumHeight() * transformScale); | |||
auto maxH = (double) ((float) constrainer->getMaximumHeight() * transformScale); | |||
auto minW = (float) constrainer->getMinimumWidth(); | |||
auto maxW = (float) constrainer->getMaximumWidth(); | |||
auto minH = (float) constrainer->getMinimumHeight(); | |||
auto maxH = (float) constrainer->getMaximumHeight(); | |||
auto width = (double) (rectToCheck->right - rectToCheck->left); | |||
auto height = (double) (rectToCheck->bottom - rectToCheck->top); | |||
auto width = jlimit (minW, maxW, editorBounds.getWidth()); | |||
auto height = jlimit (minH, maxH, editorBounds.getHeight()); | |||
width = jlimit (minW, maxW, width); | |||
height = jlimit (minH, maxH, height); | |||
auto aspectRatio = constrainer->getFixedAspectRatio(); | |||
auto aspectRatio = (float) constrainer->getFixedAspectRatio(); | |||
if (aspectRatio != 0.0) | |||
{ | |||
@@ -1285,9 +1372,11 @@ private: | |||
if (getHostType().type == PluginHostType::SteinbergCubase9) | |||
{ | |||
if (editor->getWidth() == width && editor->getHeight() != height) | |||
auto currentEditorBounds = editor->getBounds().toFloat(); | |||
if (currentEditorBounds.getWidth() == width && currentEditorBounds.getHeight() != height) | |||
adjustWidth = true; | |||
else if (editor->getHeight() == height && editor->getWidth() != width) | |||
else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) | |||
adjustWidth = false; | |||
} | |||
@@ -1313,8 +1402,11 @@ private: | |||
} | |||
} | |||
rectToCheck->right = rectToCheck->left + roundToInt (width); | |||
rectToCheck->bottom = rectToCheck->top + roundToInt (height); | |||
auto constrainedRect = component->getLocalArea (editor, Rectangle<float> (width, height)) | |||
.getSmallestIntegerContainer(); | |||
rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); | |||
rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); | |||
*rectToCheck = convertToHostBounds (*rectToCheck); | |||
} | |||
@@ -1349,16 +1441,7 @@ private: | |||
owner->lastScaleFactorReceived = editorScaleFactor; | |||
if (component != nullptr) | |||
{ | |||
if (auto* editor = component->pluginEditor.get()) | |||
{ | |||
editor->setScaleFactor (editorScaleFactor); | |||
component->resizeHostWindow(); | |||
component->setTopLeftPosition (0, 0); | |||
component->repaint(); | |||
} | |||
} | |||
component->setEditorScaleFactor (editorScaleFactor); | |||
} | |||
return kResultTrue; | |||
@@ -1470,14 +1553,18 @@ private: | |||
if (resizingChild) | |||
return; | |||
auto b = getSizeToContainChild(); | |||
auto newBounds = getSizeToContainChild(); | |||
if (lastBounds != b) | |||
if (newBounds != lastBounds) | |||
{ | |||
lastBounds = b; | |||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||
resizeHostWindow(); | |||
#if JUCE_LINUX | |||
if (getHostType().isBitwigStudio()) | |||
repaint(); | |||
#endif | |||
lastBounds = newBounds; | |||
} | |||
} | |||
@@ -1489,33 +1576,12 @@ private: | |||
{ | |||
auto newBounds = getLocalBounds(); | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
if (! lastBounds.isEmpty() && isWithin (newBounds.toDouble().getAspectRatio(), lastBounds.toDouble().getAspectRatio(), 0.1)) | |||
return; | |||
#endif | |||
lastBounds = newBounds; | |||
const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true); | |||
if (auto* constrainer = pluginEditor->getConstrainer()) | |||
{ | |||
auto aspectRatio = constrainer->getFixedAspectRatio(); | |||
if (aspectRatio != 0) | |||
{ | |||
auto width = (double) lastBounds.getWidth(); | |||
auto height = (double) lastBounds.getHeight(); | |||
if (width / height > aspectRatio) | |||
setBounds ({ 0, 0, roundToInt (height * aspectRatio), lastBounds.getHeight() }); | |||
else | |||
setBounds ({ 0, 0, lastBounds.getWidth(), roundToInt (width / aspectRatio) }); | |||
} | |||
const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true); | |||
pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); | |||
} | |||
pluginEditor->setTopLeftPosition (0, 0); | |||
pluginEditor->setBounds (pluginEditor->getLocalArea (this, getLocalBounds())); | |||
lastBounds = newBounds; | |||
} | |||
} | |||
} | |||
@@ -1533,31 +1599,45 @@ private: | |||
{ | |||
if (pluginEditor != nullptr) | |||
{ | |||
auto b = getSizeToContainChild(); | |||
auto w = b.getWidth(); | |||
auto h = b.getHeight(); | |||
auto host = getHostType(); | |||
#if JUCE_WINDOWS | |||
setSize (w, h); | |||
#endif | |||
if (owner.plugFrame != nullptr) | |||
{ | |||
auto newSize = convertToHostBounds ({ 0, 0, b.getWidth(), b.getHeight() }); | |||
auto editorBounds = getSizeToContainChild(); | |||
auto newSize = convertToHostBounds ({ 0, 0, editorBounds.getWidth(), editorBounds.getHeight() }); | |||
{ | |||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||
owner.plugFrame->resizeView (&owner, &newSize); | |||
} | |||
auto host = getHostType(); | |||
#if JUCE_MAC | |||
if (host.isWavelab() || host.isReaper()) | |||
#else | |||
if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio()) | |||
#endif | |||
setBounds (0, 0, w, h); | |||
setBounds (editorBounds.withPosition (0, 0)); | |||
} | |||
} | |||
} | |||
void setEditorScaleFactor (float scale) | |||
{ | |||
if (pluginEditor != nullptr) | |||
{ | |||
auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); | |||
{ | |||
const ScopedValueSetter<bool> resizingChildSetter (resizingChild, true); | |||
pluginEditor->setScaleFactor (scale); | |||
pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); | |||
} | |||
lastBounds = getSizeToContainChild(); | |||
resizeHostWindow(); | |||
repaint(); | |||
} | |||
} | |||
@@ -1599,7 +1679,7 @@ private: | |||
//============================================================================== | |||
ScopedJuceInitialiser_GUI libraryInitialiser; | |||
ComSmartPtr<JuceVST3EditController> owner; | |||
VSTComSmartPtr<JuceVST3EditController> owner; | |||
AudioProcessor& pluginInstance; | |||
std::unique_ptr<ContentWrapperComponent> component; | |||
@@ -2555,20 +2635,11 @@ public: | |||
{ | |||
auto vstParamID = paramQueue->getParameterId(); | |||
if (vstParamID == JuceAudioProcessor::paramPreset) | |||
{ | |||
auto numPrograms = pluginInstance->getNumPrograms(); | |||
auto programValue = roundToInt (value * (jmax (0, numPrograms - 1))); | |||
if (numPrograms > 1 && isPositiveAndBelow (programValue, numPrograms) | |||
&& programValue != pluginInstance->getCurrentProgram()) | |||
pluginInstance->setCurrentProgram (programValue); | |||
} | |||
#if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS | |||
else if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) | |||
if (juceVST3EditController != nullptr && juceVST3EditController->isMidiControllerParamID (vstParamID)) | |||
addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value); | |||
#endif | |||
else | |||
#endif | |||
{ | |||
auto floatValue = static_cast<float> (value); | |||
@@ -2910,9 +2981,9 @@ private: | |||
std::atomic<int> refCount { 1 }; | |||
AudioProcessor* pluginInstance; | |||
ComSmartPtr<Vst::IHostApplication> host; | |||
ComSmartPtr<JuceAudioProcessor> comPluginInstance; | |||
ComSmartPtr<JuceVST3EditController> juceVST3EditController; | |||
VSTComSmartPtr<Vst::IHostApplication> host; | |||
VSTComSmartPtr<JuceAudioProcessor> comPluginInstance; | |||
VSTComSmartPtr<JuceVST3EditController> juceVST3EditController; | |||
/** | |||
Since VST3 does not provide a way of knowing the buffer size and sample rate at any point, | |||
@@ -3246,7 +3317,7 @@ private: | |||
//============================================================================== | |||
std::atomic<int> refCount { 1 }; | |||
const PFactoryInfo factoryInfo; | |||
ComSmartPtr<Vst::IHostApplication> host; | |||
VSTComSmartPtr<Vst::IHostApplication> host; | |||
//============================================================================== | |||
struct ClassEntry | |||
@@ -3268,7 +3339,7 @@ private: | |||
std::vector<std::unique_ptr<ClassEntry>> classes; | |||
//============================================================================== | |||
template<class PClassInfoType> | |||
template <class PClassInfoType> | |||
tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) | |||
{ | |||
if (info != nullptr) | |||
@@ -35,7 +35,7 @@ | |||
ID: juce_audio_plugin_client | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE audio plugin wrapper classes | |||
description: Classes for building VST, VST3, AudioUnit, AAX and RTAS plugins. | |||
website: http://www.juce.com/juce | |||
@@ -23,6 +23,8 @@ | |||
============================================================================== | |||
*/ | |||
#include <juce_core/system/juce_TargetPlatform.h> | |||
#if JucePlugin_Build_AU | |||
#include <juce_core/system/juce_CompilerWarnings.h> | |||
@@ -44,7 +46,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wparentheses", | |||
"-Wnullable-to-nonnull-conversion", | |||
"-Wignored-qualifiers", | |||
"-Wfour-char-constants", | |||
"-Wmissing-prototypes") | |||
"-Wmissing-prototypes", | |||
"-Wdeprecated-anon-enum-enum-conversion", | |||
"-Wambiguous-reversed-operator") | |||
// From MacOS 10.13 and iOS 11 Apple has (sensibly!) stopped defining a whole | |||
// set of functions with rather generic names. However, we still need a couple | |||
@@ -23,6 +23,8 @@ | |||
============================================================================== | |||
*/ | |||
#include <juce_core/system/juce_TargetPlatform.h> | |||
#if JucePlugin_Build_Standalone | |||
#if ! JUCE_MODULE_AVAILABLE_juce_audio_utils | |||
@@ -353,11 +353,22 @@ struct AudioUnitHelpers | |||
auto defaultInputs = processor.getChannelCountOfBus (true, 0); | |||
auto defaultOutputs = processor.getChannelCountOfBus (false, 0); | |||
SortedSet<int> supportedChannels; | |||
struct Channels | |||
{ | |||
SInt16 ins, outs; | |||
std::pair<SInt16, SInt16> makePair() const noexcept { return std::make_pair (ins, outs); } | |||
bool operator< (const Channels& other) const noexcept { return makePair() < other.makePair(); } | |||
bool operator== (const Channels& other) const noexcept { return makePair() == other.makePair(); } | |||
}; | |||
SortedSet<Channels> supportedChannels; | |||
// add the current configuration | |||
if (defaultInputs != 0 || defaultOutputs != 0) | |||
supportedChannels.add ((defaultInputs << 16) | defaultOutputs); | |||
supportedChannels.add ({ static_cast<SInt16> (defaultInputs), | |||
static_cast<SInt16> (defaultOutputs) }); | |||
for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||
{ | |||
@@ -375,19 +386,16 @@ struct AudioUnitHelpers | |||
if (! isNumberOfChannelsSupported (outBus, outChanNum, outLayout)) | |||
continue; | |||
supportedChannels.add (((hasMainInputBus ? outLayout.getMainInputChannels() : 0) << 16) | |||
| (hasMainOutputBus ? outLayout.getMainOutputChannels() : 0)); | |||
supportedChannels.add ({ static_cast<SInt16> (hasMainInputBus ? outLayout.getMainInputChannels() : 0), | |||
static_cast<SInt16> (hasMainOutputBus ? outLayout.getMainOutputChannels() : 0) }); | |||
} | |||
} | |||
auto hasInOutMismatch = false; | |||
for (auto supported : supportedChannels) | |||
for (const auto& supported : supportedChannels) | |||
{ | |||
auto numInputs = (supported >> 16) & 0xffff; | |||
auto numOutputs = (supported >> 0) & 0xffff; | |||
if (numInputs != numOutputs) | |||
if (supported.ins != supported.outs) | |||
{ | |||
hasInOutMismatch = true; | |||
break; | |||
@@ -398,9 +406,10 @@ struct AudioUnitHelpers | |||
for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||
{ | |||
auto channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); | |||
Channels channelConfiguration { static_cast<SInt16> (inChanNum), | |||
static_cast<SInt16> (hasInOutMismatch ? defaultOutputs : inChanNum) }; | |||
if (! supportedChannels.contains (channelConfiguration)) | |||
if (supportedChannels.contains (channelConfiguration)) | |||
{ | |||
hasUnsupportedInput = true; | |||
break; | |||
@@ -409,25 +418,23 @@ struct AudioUnitHelpers | |||
for (auto outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||
{ | |||
auto channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; | |||
Channels channelConfiguration { static_cast<SInt16> (hasInOutMismatch ? defaultInputs : outChanNum), | |||
static_cast<SInt16> (outChanNum) }; | |||
if (! supportedChannels.contains (channelConfiguration)) | |||
if (supportedChannels.contains (channelConfiguration)) | |||
{ | |||
hasUnsupportedOutput = true; | |||
break; | |||
} | |||
} | |||
for (auto supported : supportedChannels) | |||
for (const auto& supported : supportedChannels) | |||
{ | |||
auto numInputs = (supported >> 16) & 0xffff; | |||
auto numOutputs = (supported >> 0) & 0xffff; | |||
AUChannelInfo info; | |||
// see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | |||
info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||
info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||
info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? supported.ins : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||
info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? supported.outs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||
if (info.inChannels == -2 && info.outChannels == -2) | |||
info.inChannels = -1; | |||
@@ -25,6 +25,8 @@ | |||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
#if JUCE_MAC | |||
#include <AudioUnit/AUCocoaUIView.h> | |||
#include <CoreAudioKit/AUGenericView.h> | |||
@@ -247,7 +249,8 @@ namespace AudioUnitFormatHelpers | |||
if (Handle h = Get1IndResource (thngType, i)) | |||
{ | |||
HLock (h); | |||
const uint32* const types = (const uint32*) *h; | |||
uint32 types[3]; | |||
std::memcpy (types, *h, sizeof (types)); | |||
if (types[0] == kAudioUnitType_MusicDevice | |||
|| types[0] == kAudioUnitType_MusicEffect | |||
@@ -759,7 +762,7 @@ public: | |||
zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | |||
stream.mSampleRate = sampleRate; | |||
stream.mFormatID = kAudioFormatLinearPCM; | |||
stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; | |||
stream.mFormatFlags = (int) kAudioFormatFlagsNativeFloatPacked | (int) kAudioFormatFlagIsNonInterleaved | (int) kAudioFormatFlagsNativeEndian; | |||
stream.mFramesPerPacket = 1; | |||
stream.mBytesPerPacket = 4; | |||
stream.mBytesPerFrame = 4; | |||
@@ -1823,12 +1826,12 @@ private: | |||
default: | |||
if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) | |||
{ | |||
updateHostDisplay(); | |||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); | |||
} | |||
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) | |||
{ | |||
sendAllParametersChangedEvents(); | |||
updateHostDisplay(); | |||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); | |||
} | |||
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) | |||
{ | |||
@@ -2912,4 +2915,6 @@ FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() | |||
} // namespace juce | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
#endif |
@@ -374,7 +374,7 @@ public: | |||
destData.setSize ((size_t) numParameters * sizeof (float)); | |||
destData.fillWith (0); | |||
auto* p = (float*) ((char*) destData.getData()); | |||
auto* p = unalignedPointerCast<float*> (destData.getData()); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (auto* param = getParameters()[i]) | |||
@@ -68,12 +68,14 @@ static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexc | |||
#endif | |||
//============================================================================== | |||
inline juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (string); } | |||
inline juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } | |||
inline juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (juce::CharPointer_UTF8 ((juce::CharPointer_UTF8::CharType*) string)); } | |||
inline juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } | |||
// NB: The casts are handled by a Steinberg::UString operator | |||
inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept { return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); } | |||
inline void toString128 (Steinberg::Vst::String128 result, const char* source) | |||
{ | |||
@@ -82,12 +84,7 @@ inline void toString128 (Steinberg::Vst::String128 result, const char* source) | |||
inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||
{ | |||
Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | |||
} | |||
inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
{ | |||
return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | |||
Steinberg::UString (result, 128).assign (toString (source)); | |||
} | |||
#if JUCE_WINDOWS | |||
@@ -355,24 +352,24 @@ static AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak | |||
//============================================================================== | |||
template <class ObjectType> | |||
class ComSmartPtr | |||
class VSTComSmartPtr | |||
{ | |||
public: | |||
ComSmartPtr() noexcept : source (nullptr) {} | |||
ComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } | |||
ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } | |||
~ComSmartPtr() { if (source != nullptr) source->release(); } | |||
VSTComSmartPtr() noexcept : source (nullptr) {} | |||
VSTComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } | |||
VSTComSmartPtr (const VSTComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } | |||
~VSTComSmartPtr() { if (source != nullptr) source->release(); } | |||
operator ObjectType*() const noexcept { return source; } | |||
ObjectType* get() const noexcept { return source; } | |||
ObjectType& operator*() const noexcept { return *source; } | |||
ObjectType* operator->() const noexcept { return source; } | |||
ComSmartPtr& operator= (const ComSmartPtr& other) { return operator= (other.source); } | |||
VSTComSmartPtr& operator= (const VSTComSmartPtr& other) { return operator= (other.source); } | |||
ComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) | |||
VSTComSmartPtr& operator= (ObjectType* const newObjectToTakePossessionOf) | |||
{ | |||
ComSmartPtr p (newObjectToTakePossessionOf); | |||
VSTComSmartPtr p (newObjectToTakePossessionOf); | |||
std::swap (p.source, source); | |||
return *this; | |||
} | |||
@@ -31,10 +31,6 @@ | |||
namespace juce | |||
{ | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
extern void setThreadDPIAwarenessForWindow (HWND); | |||
#endif | |||
using namespace Steinberg; | |||
//============================================================================== | |||
@@ -370,7 +366,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override; | |||
#if ! JUCE_MODAL_LOOPS_PERMITTED | |||
static void menuFinished (int modalResult, ComSmartPtr<ContextMenu> menu) { menu->handleResult (modalResult); } | |||
static void menuFinished (int modalResult, VSTComSmartPtr<ContextMenu> menu) { menu->handleResult (modalResult); } | |||
#endif | |||
private: | |||
@@ -382,7 +378,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
struct ItemAndTarget | |||
{ | |||
Item item; | |||
ComSmartPtr<IContextMenuTarget> target; | |||
VSTComSmartPtr<IContextMenuTarget> target; | |||
}; | |||
Array<ItemAndTarget> items; | |||
@@ -446,7 +442,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid)) | |||
{ | |||
ComSmartPtr<Message> m (new Message (attributeList)); | |||
VSTComSmartPtr<Message> m (new Message (attributeList)); | |||
messageQueue.add (m); | |||
m->addRef(); | |||
*obj = m; | |||
@@ -454,7 +450,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||
} | |||
else if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid)) | |||
{ | |||
ComSmartPtr<AttributeList> l (new AttributeList (this)); | |||
VSTComSmartPtr<AttributeList> l (new AttributeList (this)); | |||
l->addRef(); | |||
*obj = l; | |||
return kResultOk; | |||
@@ -541,14 +537,14 @@ private: | |||
var value; | |||
private: | |||
ComSmartPtr<Vst::IAttributeList> attributeList; | |||
VSTComSmartPtr<Vst::IAttributeList> attributeList; | |||
String messageId; | |||
Atomic<int> refCount; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) | |||
}; | |||
Array<ComSmartPtr<Message>, CriticalSection> messageQueue; | |||
Array<VSTComSmartPtr<Message>, CriticalSection> messageQueue; | |||
//============================================================================== | |||
struct AttributeList : public Vst::IAttributeList | |||
@@ -664,7 +660,7 @@ private: | |||
} | |||
} | |||
owner->messageQueue.add (ComSmartPtr<Message> (new Message (this, id, value))); | |||
owner->messageQueue.add (VSTComSmartPtr<Message> (new Message (this, id, value))); | |||
} | |||
template <typename Type> | |||
@@ -687,7 +683,7 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) | |||
}; | |||
ComSmartPtr<AttributeList> attributeList; | |||
VSTComSmartPtr<AttributeList> attributeList; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) | |||
}; | |||
@@ -731,8 +727,8 @@ struct DescriptionFactory | |||
std::unique_ptr<PClassInfoW> infoW; | |||
{ | |||
ComSmartPtr<IPluginFactory2> pf2; | |||
ComSmartPtr<IPluginFactory3> pf3; | |||
VSTComSmartPtr<IPluginFactory2> pf2; | |||
VSTComSmartPtr<IPluginFactory3> pf3; | |||
if (pf2.loadFrom (factory)) | |||
{ | |||
@@ -752,7 +748,7 @@ struct DescriptionFactory | |||
PluginDescription desc; | |||
{ | |||
ComSmartPtr<Vst::IComponent> component; | |||
VSTComSmartPtr<Vst::IComponent> component; | |||
if (component.loadFrom (factory, info.cid)) | |||
{ | |||
@@ -790,8 +786,8 @@ struct DescriptionFactory | |||
virtual Result performOnDescription (PluginDescription&) = 0; | |||
private: | |||
ComSmartPtr<VST3HostContext> vst3HostContext; | |||
ComSmartPtr<IPluginFactory> factory; | |||
VSTComSmartPtr<VST3HostContext> vst3HostContext; | |||
VSTComSmartPtr<IPluginFactory> factory; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) | |||
}; | |||
@@ -849,7 +845,7 @@ struct DLLHandle | |||
//============================================================================== | |||
/** The factory should begin with a refCount of 1, so don't increment the reference count | |||
(ie: don't use a ComSmartPtr in here)! Its lifetime will be handled by this DLLHandle. | |||
(ie: don't use a VSTComSmartPtr in here)! Its lifetime will be handled by this DLLHandle. | |||
*/ | |||
IPluginFactory* JUCE_CALLTYPE getPluginFactory() | |||
{ | |||
@@ -1102,8 +1098,8 @@ private: | |||
//============================================================================== | |||
bool open (const PluginDescription& description) | |||
{ | |||
ComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()) | |||
.getPluginFactory()); | |||
VSTComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()) | |||
.getPluginFactory()); | |||
if (pluginFactory != nullptr) | |||
{ | |||
@@ -1140,9 +1136,7 @@ private: | |||
//============================================================================== | |||
struct VST3PluginWindow : public AudioProcessorEditor, | |||
public ComponentMovementWatcher, | |||
#if JUCE_WINDOWS || JUCE_LINUX | |||
public ComponentPeer::ScaleFactorListener, | |||
#endif | |||
public IPlugFrame | |||
{ | |||
VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) | |||
@@ -1155,26 +1149,20 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
setVisible (true); | |||
warnOnFailure (view->setFrame (this)); | |||
#if ! JUCE_MAC | |||
view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); | |||
#endif | |||
resizeToFit(); | |||
} | |||
~VST3PluginWindow() override | |||
{ | |||
#if ! JUCE_MAC | |||
if (scaleInterface != nullptr) | |||
scaleInterface->release(); | |||
removeScaleFactorListeners(); | |||
removeScaleFactorListener(); | |||
#if JUCE_LINUX | |||
embeddedComponent.removeClient(); | |||
#endif | |||
#endif | |||
warnOnFailure (view->removed()); | |||
warnOnFailure (view->setFrame (nullptr)); | |||
@@ -1183,6 +1171,8 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
#if JUCE_MAC | |||
embeddedComponent.setView (nullptr); | |||
#elif JUCE_WINDOWS | |||
embeddedComponent.setHWND (nullptr); | |||
#endif | |||
view = nullptr; | |||
@@ -1323,79 +1313,50 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
//============================================================================== | |||
void componentPeerChanged() override | |||
{ | |||
#if ! JUCE_MAC | |||
removeScaleFactorListeners(); | |||
removeScaleFactorListener(); | |||
currentPeer = getTopLevelComponent()->getPeer(); | |||
if (auto* topPeer = getTopLevelComponent()->getPeer()) | |||
topPeer->addScaleFactorListener (this); | |||
#endif | |||
if (currentPeer != nullptr) | |||
{ | |||
currentPeer->addScaleFactorListener (this); | |||
nativeScaleFactor = (float) currentPeer->getPlatformScaleFactor(); | |||
} | |||
} | |||
void componentMovedOrResized (bool, bool wasResized) override | |||
{ | |||
if (recursiveResize) | |||
if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) | |||
return; | |||
auto* topComp = getTopLevelComponent(); | |||
ViewRect rect; | |||
if (topComp->getPeer() != nullptr) | |||
if (view->canResize() == kResultTrue) | |||
{ | |||
#if JUCE_WINDOWS | |||
auto pos = (topComp->getLocalPoint (this, Point<int>()) * nativeScaleFactor).roundToInt(); | |||
#endif | |||
const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | |||
rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); | |||
rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); | |||
ViewRect rect; | |||
view->checkSizeConstraint (&rect); | |||
if (wasResized && view->canResize() == kResultTrue) | |||
{ | |||
rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); | |||
rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); | |||
view->checkSizeConstraint (&rect); | |||
auto w = roundToInt ((float) rect.getWidth() / nativeScaleFactor); | |||
auto h = roundToInt ((float) rect.getHeight() / nativeScaleFactor); | |||
setSize (w, h); | |||
const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | |||
#if JUCE_WINDOWS | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow (pluginHandle); | |||
#endif | |||
SetWindowPos (pluginHandle, 0, | |||
pos.x, pos.y, rect.getWidth(), rect.getHeight(), | |||
isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
#else | |||
embeddedComponent.setBounds (getLocalBounds()); | |||
#endif | |||
view->onSize (&rect); | |||
setSize (roundToInt ((float) rect.getWidth() / nativeScaleFactor), | |||
roundToInt ((float) rect.getHeight() / nativeScaleFactor)); | |||
} | |||
else | |||
{ | |||
warnOnFailure (view->getSize (&rect)); | |||
#if JUCE_WINDOWS | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow (pluginHandle); | |||
#endif | |||
SetWindowPos (pluginHandle, 0, | |||
pos.x, pos.y, rect.getWidth(), rect.getHeight(), | |||
isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
#else | |||
embeddedComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | |||
#endif | |||
} | |||
embeddedComponent.setBounds (getLocalBounds()); | |||
// Some plugins don't update their cursor correctly when mousing out the window | |||
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | |||
view->onSize (&rect); | |||
} | |||
else | |||
{ | |||
warnOnFailure (view->getSize (&rect)); | |||
resizeWithRect (embeddedComponent, rect, nativeScaleFactor); | |||
} | |||
} | |||
// Some plugins don't update their cursor correctly when mousing out the window | |||
Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | |||
} | |||
using ComponentMovementWatcher::componentMovedOrResized; | |||
void componentVisibilityChanged() override | |||
@@ -1407,13 +1368,11 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
componentMovedOrResized (true, true); | |||
} | |||
using ComponentMovementWatcher::componentVisibilityChanged; | |||
#if JUCE_WINDOWS || JUCE_LINUX | |||
void nativeScaleFactorChanged (double newScaleFactor) override | |||
{ | |||
if (pluginHandle == 0 || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||
if (pluginHandle == HandleFormat{} || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||
return; | |||
nativeScaleFactor = (float) newScaleFactor; | |||
@@ -1421,7 +1380,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||
if (scaleInterface != nullptr) | |||
scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | |||
} | |||
#endif | |||
void resizeToFit() | |||
{ | |||
@@ -1458,73 +1416,48 @@ private: | |||
void attachPluginWindow() | |||
{ | |||
#if JUCE_MAC | |||
if (pluginHandle == nil) | |||
#else | |||
if (pluginHandle == 0) | |||
#endif | |||
if (pluginHandle == HandleFormat{}) | |||
{ | |||
#if JUCE_WINDOWS | |||
if (auto* topComp = getTopLevelComponent()) | |||
{ | |||
peer.reset (embeddedComponent.createNewPeer (0, topComp->getWindowHandle())); | |||
pluginHandle = (HandleFormat) peer->getNativeHandle(); | |||
nativeScaleFactor = (float) peer->getPlatformScaleFactor(); | |||
} | |||
#else | |||
embeddedComponent.setBounds (getLocalBounds()); | |||
addAndMakeVisible (embeddedComponent); | |||
#if JUCE_MAC | |||
pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||
jassert (pluginHandle != nil); | |||
#elif JUCE_LINUX | |||
pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||
jassert (pluginHandle != 0); | |||
#endif | |||
#endif | |||
#if JUCE_MAC | |||
if (pluginHandle != nil) | |||
#else | |||
if (pluginHandle != 0) | |||
pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||
#elif JUCE_WINDOWS | |||
pluginHandle = (HandleFormat) embeddedComponent.getHWND(); | |||
#elif JUCE_LINUX | |||
pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||
#endif | |||
warnOnFailure (view->attached ((void*) pluginHandle, defaultVST3WindowType)); | |||
} | |||
#if ! JUCE_MAC | |||
if (auto* topPeer = getTopLevelComponent()->getPeer()) | |||
{ | |||
nativeScaleFactor = 1.0f; // force update | |||
nativeScaleFactorChanged ((float) topPeer->getPlatformScaleFactor()); | |||
if (pluginHandle == HandleFormat{}) | |||
{ | |||
jassertfalse; | |||
return; | |||
} | |||
warnOnFailure (view->attached ((void*) pluginHandle, defaultVST3WindowType)); | |||
if (scaleInterface != nullptr) | |||
scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | |||
} | |||
#endif | |||
} | |||
#if ! JUCE_MAC | |||
void removeScaleFactorListeners() | |||
void removeScaleFactorListener() | |||
{ | |||
if (currentPeer == nullptr) | |||
return; | |||
for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||
if (auto* p = ComponentPeer::getPeer (i)) | |||
p->removeScaleFactorListener (this); | |||
if (ComponentPeer::getPeer (i) == currentPeer) | |||
currentPeer->removeScaleFactorListener (this); | |||
} | |||
#endif | |||
//============================================================================== | |||
Atomic<int> refCount { 1 }; | |||
ComSmartPtr<IPlugView> view; | |||
VSTComSmartPtr<IPlugView> view; | |||
#if JUCE_WINDOWS | |||
struct ChildComponent : public Component | |||
{ | |||
ChildComponent() {} | |||
void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } | |||
using Component::createNewPeer; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) | |||
}; | |||
ChildComponent embeddedComponent; | |||
std::unique_ptr<ComponentPeer> peer; | |||
HWNDComponentWithParent embeddedComponent; | |||
using HandleFormat = HWND; | |||
#elif JUCE_MAC | |||
AutoResizingNSViewComponentWithParent embeddedComponent; | |||
@@ -1538,14 +1471,11 @@ private: | |||
#endif | |||
HandleFormat pluginHandle = {}; | |||
bool recursiveResize = false; | |||
bool recursiveResize = false, hasDoneInitialResize = false; | |||
#if ! JUCE_MAC | |||
ComponentPeer* currentPeer = nullptr; | |||
Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | |||
#endif | |||
float nativeScaleFactor = 1.0f; | |||
bool hasDoneInitialResize = false; | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | |||
@@ -1569,7 +1499,7 @@ struct VST3ComponentHolder | |||
// transfers ownership to the plugin instance! | |||
AudioPluginInstance* createPluginInstance(); | |||
bool fetchController (ComSmartPtr<Vst::IEditController>& editController) | |||
bool fetchController (VSTComSmartPtr<Vst::IEditController>& editController) | |||
{ | |||
if (! isComponentInitialised && ! initialise()) | |||
return false; | |||
@@ -1618,8 +1548,8 @@ struct VST3ComponentHolder | |||
ignoreUnused (success); | |||
jassert (success); | |||
ComSmartPtr<IPluginFactory2> pf2; | |||
ComSmartPtr<IPluginFactory3> pf3; | |||
VSTComSmartPtr<IPluginFactory2> pf2; | |||
VSTComSmartPtr<IPluginFactory3> pf3; | |||
std::unique_ptr<PClassInfo2> info2; | |||
std::unique_ptr<PClassInfoW> infoW; | |||
@@ -1683,7 +1613,7 @@ struct VST3ComponentHolder | |||
JUCE_ASSERT_MESSAGE_THREAD | |||
#endif | |||
factory = ComSmartPtr<IPluginFactory> (module->getPluginFactory()); | |||
factory = VSTComSmartPtr<IPluginFactory> (module->getPluginFactory()); | |||
int classIdx; | |||
if ((classIdx = getClassIndex (module->getName())) < 0) | |||
@@ -1734,9 +1664,9 @@ struct VST3ComponentHolder | |||
//============================================================================== | |||
VST3ModuleHandle::Ptr module; | |||
ComSmartPtr<IPluginFactory> factory; | |||
ComSmartPtr<VST3HostContext> host; | |||
ComSmartPtr<Vst::IComponent> component; | |||
VSTComSmartPtr<IPluginFactory> factory; | |||
VSTComSmartPtr<VST3HostContext> host; | |||
VSTComSmartPtr<Vst::IComponent> component; | |||
FUID cidOfComponent; | |||
bool isComponentInitialised = false; | |||
@@ -2292,7 +2222,7 @@ public: | |||
{ | |||
if (trackInfoListener != nullptr) | |||
{ | |||
ComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||
VSTComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||
trackInfoListener->setChannelContextInfos (l); | |||
} | |||
} | |||
@@ -2427,7 +2357,7 @@ public: | |||
if (getActiveEditor() != nullptr) | |||
return true; | |||
ComSmartPtr<IPlugView> view (tryCreatingView(), false); | |||
VSTComSmartPtr<IPlugView> view (tryCreatingView(), false); | |||
return view != nullptr; | |||
} | |||
@@ -2509,7 +2439,7 @@ public: | |||
bool setStateFromPresetFile (const MemoryBlock& rawData) | |||
{ | |||
MemoryBlock rawDataCopy (rawData); | |||
ComSmartPtr<Steinberg::MemoryStream> memoryStream = new Steinberg::MemoryStream (rawDataCopy.getData(), (int) rawDataCopy.getSize()); | |||
VSTComSmartPtr<Steinberg::MemoryStream> memoryStream = new Steinberg::MemoryStream (rawDataCopy.getData(), (int) rawDataCopy.getSize()); | |||
if (memoryStream == nullptr || holder->component == nullptr) | |||
return false; | |||
@@ -2676,17 +2606,17 @@ private: | |||
std::unique_ptr<PClassInfoW> infoW; | |||
// Rudimentary interfaces: | |||
ComSmartPtr<Vst::IEditController> editController; | |||
ComSmartPtr<Vst::IEditController2> editController2; | |||
ComSmartPtr<Vst::IMidiMapping> midiMapping; | |||
ComSmartPtr<Vst::IAudioProcessor> processor; | |||
ComSmartPtr<Vst::IComponentHandler> componentHandler; | |||
ComSmartPtr<Vst::IComponentHandler2> componentHandler2; | |||
ComSmartPtr<Vst::IUnitInfo> unitInfo; | |||
ComSmartPtr<Vst::IUnitData> unitData; | |||
ComSmartPtr<Vst::IProgramListData> programListData; | |||
ComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | |||
ComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener; | |||
VSTComSmartPtr<Vst::IEditController> editController; | |||
VSTComSmartPtr<Vst::IEditController2> editController2; | |||
VSTComSmartPtr<Vst::IMidiMapping> midiMapping; | |||
VSTComSmartPtr<Vst::IAudioProcessor> processor; | |||
VSTComSmartPtr<Vst::IComponentHandler> componentHandler; | |||
VSTComSmartPtr<Vst::IComponentHandler2> componentHandler2; | |||
VSTComSmartPtr<Vst::IUnitInfo> unitInfo; | |||
VSTComSmartPtr<Vst::IUnitData> unitData; | |||
VSTComSmartPtr<Vst::IProgramListData> programListData; | |||
VSTComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | |||
VSTComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener; | |||
/** The number of IO buses MUST match that of the plugin, | |||
even if there aren't enough channels to process, | |||
@@ -2701,7 +2631,7 @@ private: | |||
//============================================================================== | |||
template <typename Type> | |||
static void appendStateFrom (XmlElement& head, ComSmartPtr<Type>& object, const String& identifier) | |||
static void appendStateFrom (XmlElement& head, VSTComSmartPtr<Type>& object, const String& identifier) | |||
{ | |||
if (object != nullptr) | |||
{ | |||
@@ -2715,7 +2645,7 @@ private: | |||
} | |||
} | |||
static ComSmartPtr<Steinberg::MemoryStream> createMemoryStreamForState (XmlElement& head, StringRef identifier) | |||
static VSTComSmartPtr<Steinberg::MemoryStream> createMemoryStreamForState (XmlElement& head, StringRef identifier) | |||
{ | |||
if (auto* state = head.getChildByName (identifier)) | |||
{ | |||
@@ -2723,7 +2653,7 @@ private: | |||
if (mem.fromBase64Encoding (state->getAllSubText())) | |||
{ | |||
ComSmartPtr<Steinberg::MemoryStream> stream (new Steinberg::MemoryStream(), false); | |||
VSTComSmartPtr<Steinberg::MemoryStream> stream (new Steinberg::MemoryStream(), false); | |||
stream->setSize ((TSize) mem.getSize()); | |||
mem.copyTo (stream->getData(), 0, mem.getSize()); | |||
return stream; | |||
@@ -2733,8 +2663,8 @@ private: | |||
return nullptr; | |||
} | |||
ComSmartPtr<ParamValueQueueList> inputParameterChanges, outputParameterChanges; | |||
ComSmartPtr<MidiEventList> midiInputs, midiOutputs; | |||
VSTComSmartPtr<ParamValueQueueList> inputParameterChanges, outputParameterChanges; | |||
VSTComSmartPtr<MidiEventList> midiInputs, midiOutputs; | |||
Vst::ProcessContext timingInfo; //< Only use this in processBlock()! | |||
bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; | |||
VST3Parameter* bypassParam = nullptr; | |||
@@ -2872,10 +2802,10 @@ private: | |||
setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); | |||
} | |||
static AudioProcessor::BusesProperties getBusProperties (ComSmartPtr<Vst::IComponent>& component) | |||
static AudioProcessor::BusesProperties getBusProperties (VSTComSmartPtr<Vst::IComponent>& component) | |||
{ | |||
AudioProcessor::BusesProperties busProperties; | |||
ComSmartPtr<Vst::IAudioProcessor> processor; | |||
VSTComSmartPtr<Vst::IAudioProcessor> processor; | |||
processor.loadFrom (component.get()); | |||
for (int dirIdx = 0; dirIdx < 2; ++dirIdx) | |||
@@ -3179,7 +3109,9 @@ tresult VST3HostContext::restartComponent (Steinberg::int32 flags) | |||
if (plugin->processor != nullptr) | |||
plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); | |||
plugin->updateHostDisplay(); | |||
plugin->updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) | |||
.withParameterInfoChanged (true)); | |||
return kResultTrue; | |||
} | |||
@@ -3252,7 +3184,7 @@ tresult VST3HostContext::ContextMenu::popup (Steinberg::UCoord x, Steinberg::UCo | |||
// Unfortunately, Steinberg's docs explicitly say this should be modal.. | |||
handleResult (topLevelMenu->showMenu (options)); | |||
#else | |||
topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr<ContextMenu> (this))); | |||
topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, VSTComSmartPtr<ContextMenu> (this))); | |||
#endif | |||
return kResultOk; | |||
@@ -3314,12 +3246,12 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& resul | |||
for every housed plugin. | |||
*/ | |||
ComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) | |||
.getPluginFactory()); | |||
VSTComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) | |||
.getPluginFactory()); | |||
if (pluginFactory != nullptr) | |||
{ | |||
ComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||
VSTComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||
DescriptionLister lister (host, pluginFactory); | |||
lister.findDescriptionsAndPerform (File (fileOrIdentifier)); | |||
@@ -53,6 +53,7 @@ namespace Vst2 | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
#include "juce_VSTMidiEventList.h" | |||
@@ -77,7 +78,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||
//============================================================================== | |||
namespace juce | |||
{ | |||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
#if JUCE_WINDOWS | |||
extern void setThreadDPIAwarenessForWindow (HWND); | |||
#endif | |||
@@ -376,7 +377,7 @@ private: | |||
switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); | |||
switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") })); | |||
forEachXmlChildElement (xml, item) | |||
for (auto* item : xml.getChildIterator()) | |||
{ | |||
if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr); | |||
else if (item->hasTagName ("ValueType")) parseValueType (*item); | |||
@@ -430,7 +431,7 @@ private: | |||
int curEntry = 0; | |||
const int numEntries = item.getNumChildElements(); | |||
forEachXmlChildElementWithTagName (item, entryXml, "Entry") | |||
for (auto* entryXml : item.getChildWithTagNameIterator ("Entry")) | |||
{ | |||
auto entry = new Entry(); | |||
entry->name = entryXml->getStringAttribute ("name"); | |||
@@ -459,7 +460,7 @@ private: | |||
templates.add (temp); | |||
temp->name = item.getStringAttribute ("name"); | |||
forEachXmlChildElement (item, param) | |||
for (auto* param : item.getChildIterator()) | |||
parseParam (*param, nullptr, temp); | |||
} | |||
@@ -508,7 +509,7 @@ private: | |||
} | |||
else | |||
{ | |||
forEachXmlChildElement (item, subItem) | |||
for (auto* subItem : item.getChildIterator()) | |||
{ | |||
if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); | |||
else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); | |||
@@ -1596,8 +1597,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
void handleAsyncUpdate() override | |||
{ | |||
// indicates that something about the plugin has changed.. | |||
updateHostDisplay(); | |||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) | |||
.withParameterInfoChanged (true)); | |||
} | |||
pointer_sized_int handleCallback (int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) | |||
@@ -1763,9 +1764,9 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
{ | |||
if (i != oldProg) | |||
{ | |||
auto prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); | |||
auto prog = addBytesToPointer (set->programs, i * progLen); | |||
if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) | |||
if (getAddressDifference (prog, set) >= (int) dataSize) | |||
return false; | |||
if (fxbSwap (set->numPrograms) > 0) | |||
@@ -1779,9 +1780,9 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
if (fxbSwap (set->numPrograms) > 0) | |||
setCurrentProgram (oldProg); | |||
auto prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); | |||
auto prog = addBytesToPointer (set->programs, oldProg * progLen); | |||
if (((const char*) prog) - ((const char*) set) >= (ssize_t) dataSize) | |||
if (getAddressDifference (prog, set) >= (int) dataSize) | |||
return false; | |||
if (! restoreProgramSettings (prog)) | |||
@@ -1901,14 +1902,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||
auto oldProgram = getCurrentProgram(); | |||
if (oldProgram >= 0) | |||
setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); | |||
setParamsInProgramBlock (addBytesToPointer (set->programs, oldProgram * progLen)); | |||
for (int i = 0; i < numPrograms; ++i) | |||
{ | |||
if (i != oldProgram) | |||
{ | |||
setCurrentProgram (i); | |||
setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); | |||
setParamsInProgramBlock (addBytesToPointer (set->programs, i * progLen)); | |||
} | |||
} | |||
@@ -2586,7 +2587,7 @@ private: | |||
getCurrentProgramName().copyToUTF8 ((char*) dest.getData(), 63); | |||
auto p = (float*) (((char*) dest.getData()) + 64); | |||
auto p = unalignedPointerCast<float*> (((char*) dest.getData()) + 64); | |||
for (int i = 0; i < numParameters; ++i) | |||
if (auto* param = getParameters()[i]) | |||
@@ -2597,7 +2598,7 @@ private: | |||
{ | |||
changeProgramName (getCurrentProgram(), (const char*) m.getData()); | |||
auto p = (float*) (((char*) m.getData()) + 64); | |||
auto p = unalignedPointerCast<float*> (((char*) m.getData()) + 64); | |||
auto numParameters = getParameters().size(); | |||
for (int i = 0; i < numParameters; ++i) | |||
@@ -2858,9 +2859,7 @@ public: | |||
#if JUCE_WINDOWS | |||
if (pluginHWND != 0) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow (pluginHWND); | |||
#endif | |||
MoveWindow (pluginHWND, pos.getX(), pos.getY(), | |||
roundToInt (getWidth() * nativeScaleFactor), | |||
@@ -3123,9 +3122,7 @@ private: | |||
// very dodgy logic to decide which size is right. | |||
if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) | |||
{ | |||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||
setThreadDPIAwarenessForWindow (pluginHWND); | |||
#endif | |||
SetWindowPos (pluginHWND, 0, | |||
0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | |||
@@ -3228,7 +3225,7 @@ private: | |||
bool willCauseRecursiveResize (int w, int h) | |||
{ | |||
auto newScreenBounds = Rectangle<int> (w, h).withPosition (getScreenPosition()); | |||
return Desktop::getInstance().getDisplays().findDisplayForRect (newScreenBounds).scale != nativeScaleFactor; | |||
return Desktop::getInstance().getDisplays().getDisplayForRect (newScreenBounds)->scale != nativeScaleFactor; | |||
} | |||
bool isWindowSizeCorrectForPlugin (int w, int h) | |||
@@ -3737,6 +3734,7 @@ void VSTPluginFormat::aboutToScanVSTShellPlugin (const PluginDescription&) {} | |||
} // namespace juce | |||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
JUCE_END_IGNORE_WARNINGS_MSVC | |||
#endif |
@@ -81,7 +81,97 @@ static bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||
#endif | |||
#if JUCE_MAC || JUCE_IOS | |||
#if JUCE_WINDOWS | |||
//============================================================================== | |||
class HWNDComponentWithParent : public HWNDComponent, | |||
private Timer | |||
{ | |||
public: | |||
HWNDComponentWithParent() | |||
{ | |||
String className ("JUCE_"); | |||
className << String::toHexString (Time::getHighResolutionTicks()); | |||
HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle(); | |||
WNDCLASSEX wc = {}; | |||
wc.cbSize = sizeof (wc); | |||
wc.lpfnWndProc = (WNDPROC) wndProc; | |||
wc.cbWndExtra = 4; | |||
wc.hInstance = moduleHandle; | |||
wc.lpszClassName = className.toWideCharPointer(); | |||
atom = RegisterClassEx (&wc); | |||
jassert (atom != 0); | |||
hwnd = CreateWindow (getClassNameFromAtom(), L"HWNDComponentWithParent", | |||
0, 0, 0, 0, 0, | |||
nullptr, nullptr, moduleHandle, nullptr); | |||
jassert (hwnd != nullptr); | |||
setHWND (hwnd); | |||
startTimer (30); | |||
} | |||
~HWNDComponentWithParent() override | |||
{ | |||
if (IsWindow (hwnd)) | |||
DestroyWindow (hwnd); | |||
UnregisterClass (getClassNameFromAtom(), nullptr); | |||
} | |||
private: | |||
//============================================================================== | |||
static LRESULT CALLBACK wndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) | |||
{ | |||
if (message == WM_SHOWWINDOW && wParam == TRUE) | |||
return 0; | |||
return DefWindowProc (h, message, wParam, lParam); | |||
} | |||
void timerCallback() override | |||
{ | |||
if (HWND child = getChildHWND()) | |||
{ | |||
stopTimer(); | |||
ShowWindow (child, SW_HIDE); | |||
SetParent (child, NULL); | |||
auto windowFlags = GetWindowLongPtr (child, -16); | |||
windowFlags &= ~WS_CHILD; | |||
windowFlags |= WS_POPUP; | |||
SetWindowLongPtr (child, -16, windowFlags); | |||
setHWND (child); | |||
} | |||
} | |||
LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) (pointer_sized_uint) atom; } | |||
HWND getChildHWND() const | |||
{ | |||
if (HWND parent = (HWND) getHWND()) | |||
return GetWindow (parent, GW_CHILD); | |||
return nullptr; | |||
} | |||
//============================================================================== | |||
ATOM atom; | |||
HWND hwnd; | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HWNDComponentWithParent) | |||
}; | |||
#elif JUCE_MAC || JUCE_IOS | |||
#if JUCE_IOS | |||
#define JUCE_IOS_MAC_VIEW UIView | |||
@@ -130,12 +220,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
} | |||
} | |||
}; | |||
#endif | |||
} // namespace juce | |||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wcast-align") | |||
#include "format/juce_AudioPluginFormat.cpp" | |||
#include "format/juce_AudioPluginFormatManager.cpp" | |||
#include "format_types/juce_LegacyAudioParameter.cpp" | |||
@@ -35,7 +35,7 @@ | |||
ID: juce_audio_processors | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE audio processor classes | |||
description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | |||
website: http://www.juce.com/juce | |||
@@ -412,7 +412,7 @@ void AudioProcessor::setLatencySamples (int newLatency) | |||
if (latencySamples != newLatency) | |||
{ | |||
latencySamples = newLatency; | |||
updateHostDisplay(); | |||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withLatencyChanged (true)); | |||
} | |||
} | |||
@@ -423,11 +423,11 @@ AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noex | |||
return listeners[index]; | |||
} | |||
void AudioProcessor::updateHostDisplay() | |||
void AudioProcessor::updateHostDisplay (const AudioProcessorListener::ChangeDetails& details) | |||
{ | |||
for (int i = listeners.size(); --i >= 0;) | |||
if (auto l = getListenerLocked (i)) | |||
l->audioProcessorChanged (this); | |||
l->audioProcessorChanged (this, details); | |||
} | |||
void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) | |||
@@ -445,6 +445,24 @@ void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) | |||
#endif | |||
} | |||
void AudioProcessor::checkForDuplicateGroupIDs (const AudioProcessorParameterGroup& newGroup) | |||
{ | |||
ignoreUnused (newGroup); | |||
#if JUCE_DEBUG | |||
auto groups = newGroup.getSubgroups (true); | |||
groups.add (&newGroup); | |||
for (auto* group : groups) | |||
{ | |||
auto insertResult = groupIDs.insert (group->getID()); | |||
// If you hit this assertion then a group ID is not unique | |||
jassert (insertResult.second); | |||
} | |||
#endif | |||
} | |||
const Array<AudioProcessorParameter*>& AudioProcessor::getParameters() const { return flatParameterList; } | |||
const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() const { return parameterTree; } | |||
@@ -463,6 +481,7 @@ void AudioProcessor::addParameter (AudioProcessorParameter* param) | |||
void AudioProcessor::addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup> group) | |||
{ | |||
jassert (group != nullptr); | |||
checkForDuplicateGroupIDs (*group); | |||
auto oldSize = flatParameterList.size(); | |||
flatParameterList.addArray (group->getParameters (true)); | |||
@@ -483,9 +502,12 @@ void AudioProcessor::setParameterTree (AudioProcessorParameterGroup&& newTree) | |||
{ | |||
#if JUCE_DEBUG | |||
paramIDs.clear(); | |||
groupIDs.clear(); | |||
#endif | |||
parameterTree = std::move (newTree); | |||
checkForDuplicateGroupIDs (parameterTree); | |||
flatParameterList = parameterTree.getParameters (true); | |||
for (int i = 0; i < flatParameterList.size(); ++i) | |||
@@ -993,7 +993,7 @@ public: | |||
It sends a hint to the host that something like the program, number of parameters, | |||
etc, has changed, and that it should update itself. | |||
*/ | |||
void updateHostDisplay(); | |||
void updateHostDisplay (const AudioProcessorListener::ChangeDetails& details = {}); | |||
//============================================================================== | |||
/** Adds a parameter to the AudioProcessor. | |||
@@ -1499,10 +1499,11 @@ private: | |||
#endif | |||
bool textRecursionCheck = false; | |||
std::unordered_set<String> paramIDs; | |||
std::unordered_set<String> paramIDs, groupIDs; | |||
#endif | |||
void checkForDuplicateParamID (AudioProcessorParameter*); | |||
void checkForDuplicateGroupIDs (const AudioProcessorParameterGroup&); | |||
AudioProcessorListener* getListenerLocked (int) const noexcept; | |||
void updateSpeakerFormatStrings(); | |||
@@ -1265,6 +1265,22 @@ void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamples | |||
{ | |||
const ScopedLock sl (getCallbackLock()); | |||
setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock); | |||
const auto newPrepareSettings = [&] | |||
{ | |||
PrepareSettings settings; | |||
settings.precision = getProcessingPrecision(); | |||
settings.sampleRate = sampleRate; | |||
settings.blockSize = estimatedSamplesPerBlock; | |||
settings.valid = true; | |||
return settings; | |||
}(); | |||
if (prepareSettings != newPrepareSettings) | |||
{ | |||
unprepare(); | |||
prepareSettings = newPrepareSettings; | |||
} | |||
} | |||
clearRenderingSequence(); | |||
@@ -1277,16 +1293,23 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
return true; | |||
} | |||
void AudioProcessorGraph::releaseResources() | |||
void AudioProcessorGraph::unprepare() | |||
{ | |||
const ScopedLock sl (getCallbackLock()); | |||
cancelPendingUpdate(); | |||
prepareSettings.valid = false; | |||
isPrepared = 0; | |||
for (auto* n : nodes) | |||
n->unprepare(); | |||
} | |||
void AudioProcessorGraph::releaseResources() | |||
{ | |||
const ScopedLock sl (getCallbackLock()); | |||
cancelPendingUpdate(); | |||
unprepare(); | |||
if (renderSequenceFloat != nullptr) | |||
renderSequenceFloat->releaseBuffers(); | |||
@@ -407,6 +407,24 @@ public: | |||
void setStateInformation (const void* data, int sizeInBytes) override; | |||
private: | |||
struct PrepareSettings | |||
{ | |||
ProcessingPrecision precision = ProcessingPrecision::singlePrecision; | |||
double sampleRate = 0.0; | |||
int blockSize = 0; | |||
bool valid = false; | |||
using Tied = std::tuple<const ProcessingPrecision&, | |||
const double&, | |||
const int&, | |||
const bool&>; | |||
Tied tie() const noexcept { return std::tie (precision, sampleRate, blockSize, valid); } | |||
bool operator== (const PrepareSettings& other) const noexcept { return tie() == other.tie(); } | |||
bool operator!= (const PrepareSettings& other) const noexcept { return tie() != other.tie(); } | |||
}; | |||
//============================================================================== | |||
ReferenceCountedArray<Node> nodes; | |||
NodeID lastNodeID = {}; | |||
@@ -416,11 +434,14 @@ private: | |||
std::unique_ptr<RenderSequenceFloat> renderSequenceFloat; | |||
std::unique_ptr<RenderSequenceDouble> renderSequenceDouble; | |||
PrepareSettings prepareSettings; | |||
friend class AudioGraphIOProcessor; | |||
std::atomic<bool> isPrepared { false }; | |||
void topologyChanged(); | |||
void unprepare(); | |||
void handleAsyncUpdate() override; | |||
void clearRenderingSequence(); | |||
void buildRenderingSequence(); | |||
@@ -57,6 +57,28 @@ public: | |||
int parameterIndex, | |||
float newValue) = 0; | |||
/** Provides details about aspects of an AudioProcessor which have changed. | |||
*/ | |||
struct JUCE_API ChangeDetails | |||
{ | |||
bool latencyChanged = false; | |||
bool parameterInfoChanged = false; | |||
bool programChanged = false; | |||
ChangeDetails withLatencyChanged (bool b) const noexcept { return with (&ChangeDetails::latencyChanged, b); } | |||
ChangeDetails withParameterInfoChanged (bool b) const noexcept { return with (&ChangeDetails::parameterInfoChanged, b); } | |||
ChangeDetails withProgramChanged (bool b) const noexcept { return with (&ChangeDetails::programChanged, b); } | |||
private: | |||
template <typename Member, typename Value> | |||
ChangeDetails with (Member&& member, Value&& value) const noexcept | |||
{ | |||
auto copy = *this; | |||
copy.*member = std::forward<Value> (value); | |||
return copy; | |||
} | |||
}; | |||
/** Called to indicate that something else in the plugin has changed, like its | |||
program, number of parameters, etc. | |||
@@ -67,7 +89,7 @@ public: | |||
to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the | |||
message thread. | |||
*/ | |||
virtual void audioProcessorChanged (AudioProcessor* processor) = 0; | |||
virtual void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) = 0; | |||
/** Indicates that a parameter change gesture has started. | |||
@@ -73,7 +73,7 @@ private: | |||
parameterValueHasChanged = 1; | |||
} | |||
void audioProcessorChanged (AudioProcessor*) override {} | |||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} | |||
//============================================================================== | |||
void timerCallback() override | |||
@@ -378,7 +378,7 @@ void KnownPluginList::recreateFromXml (const XmlElement& xml) | |||
if (xml.hasTagName ("KNOWNPLUGINS")) | |||
{ | |||
forEachXmlChildElement (xml, e) | |||
for (auto* e : xml.getChildIterator()) | |||
{ | |||
PluginDescription info; | |||
@@ -262,7 +262,7 @@ static bool canShowFolderForPlugin (KnownPluginList& list, int index) | |||
static void showFolderForPlugin (KnownPluginList& list, int index) | |||
{ | |||
if (canShowFolderForPlugin (list, index)) | |||
File (list.getTypes()[index].fileOrIdentifier).getParentDirectory().startAsProcess(); | |||
File (list.getTypes()[index].fileOrIdentifier).revealToUser(); | |||
} | |||
void PluginListComponent::removeMissingPlugins() | |||
@@ -162,7 +162,7 @@ private: | |||
return; | |||
unnormalisedValue = newValue; | |||
listeners.call ([=] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); | |||
listeners.call ([this] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); | |||
listenersNeedCalling = false; | |||
needsUpdate = true; | |||
} | |||
@@ -696,10 +696,10 @@ public: | |||
beginTest ("After construction, the value tree has the expected format"); | |||
{ | |||
TestAudioProcessor proc ({ | |||
std::make_unique<AudioProcessorParameterGroup> ("", "", "", | |||
std::make_unique<AudioProcessorParameterGroup> ("A", "", "", | |||
std::make_unique<AudioParameterBool> ("a", "", false), | |||
std::make_unique<AudioParameterFloat> ("b", "", NormalisableRange<float>{}, 0.0f)), | |||
std::make_unique<AudioProcessorParameterGroup> ("", "", "", | |||
std::make_unique<AudioProcessorParameterGroup> ("B", "", "", | |||
std::make_unique<AudioParameterInt> ("c", "", 0, 1, 0), | |||
std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) }); | |||
@@ -35,7 +35,7 @@ | |||
ID: juce_audio_utils | |||
vendor: juce | |||
version: 6.0.4 | |||
version: 6.0.7 | |||
name: JUCE extra audio utility classes | |||
description: Classes for audio-related GUI and miscellaneous tasks. | |||
website: http://www.juce.com/juce | |||
@@ -30,7 +30,7 @@ namespace CDReaderHelpers | |||
{ | |||
inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) | |||
{ | |||
forEachXmlChildElementWithTagName (xml, child, "key") | |||
for (auto* child : xml.getChildWithTagNameIterator ("key")) | |||
if (child->getAllSubText().trim() == key) | |||
return child->getNextElement(); | |||
@@ -71,7 +71,7 @@ namespace CDReaderHelpers | |||
if (trackArray == nullptr) | |||
return "Couldn't find Track Array"; | |||
forEachXmlChildElement (*trackArray, track) | |||
for (auto* track : trackArray->getChildIterator()) | |||
{ | |||
const int trackValue = getIntValueForKey (*track, "Start Block"); | |||
if (trackValue < 0) | |||
@@ -314,25 +314,25 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) | |||
}; | |||
template<> | |||
template <> | |||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::finish (AbstractFifo& f, int num) noexcept | |||
{ | |||
f.finishedRead (num); | |||
} | |||
template<> | |||
template <> | |||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::finish (AbstractFifo& f, int num) noexcept | |||
{ | |||
f.finishedWrite (num); | |||
} | |||
template<> | |||
template <> | |||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::prepare (AbstractFifo& f, int num) noexcept | |||
{ | |||
f.prepareToRead (num, startIndex1, blockSize1, startIndex2, blockSize2); | |||
} | |||
template<> | |||
template <> | |||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::prepare (AbstractFifo& f, int num) noexcept | |||
{ | |||
f.prepareToWrite (num, startIndex1, blockSize1, startIndex2, blockSize2); | |||
@@ -110,16 +110,16 @@ public: | |||
/** Initalises an Array from a list of items. */ | |||
template <typename... OtherElements> | |||
Array (const ElementType& firstNewElement, OtherElements... otherElements) | |||
Array (const ElementType& firstNewElement, OtherElements&&... otherElements) | |||
{ | |||
values.add (firstNewElement, otherElements...); | |||
values.add (firstNewElement, std::forward<OtherElements> (otherElements)...); | |||
} | |||
/** Initalises an Array from a list of items. */ | |||
template <typename... OtherElements> | |||
Array (ElementType&& firstNewElement, OtherElements... otherElements) | |||
Array (ElementType&& firstNewElement, OtherElements&&... otherElements) | |||
{ | |||
values.add (std::move (firstNewElement), otherElements...); | |||
values.add (std::move (firstNewElement), std::forward<OtherElements> (otherElements)...); | |||
} | |||
template <typename TypeToCreateFrom> | |||
@@ -433,18 +433,18 @@ public: | |||
/** Appends multiple new elements at the end of the array. */ | |||
template <typename... OtherElements> | |||
void add (const ElementType& firstNewElement, OtherElements... otherElements) | |||
void add (const ElementType& firstNewElement, OtherElements&&... otherElements) | |||
{ | |||
const ScopedLockType lock (getLock()); | |||
values.add (firstNewElement, otherElements...); | |||
values.add (firstNewElement, std::forward<OtherElements> (otherElements)...); | |||
} | |||
/** Appends multiple new elements at the end of the array. */ | |||
template <typename... OtherElements> | |||
void add (ElementType&& firstNewElement, OtherElements... otherElements) | |||
void add (ElementType&& firstNewElement, OtherElements&&... otherElements) | |||
{ | |||
const ScopedLockType lock (getLock()); | |||
values.add (std::move (firstNewElement), otherElements...); | |||
values.add (std::move (firstNewElement), std::forward<OtherElements> (otherElements)...); | |||
} | |||
/** Inserts a new element into the array at a given position. | |||