@@ -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 | 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 | --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp | ||||
+++ b/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_CoreAudio()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); | ||||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Bela()); | 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 | 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 | --- a/modules/juce_audio_basics/midi/juce_MidiMessage.h | ||||
+++ b/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 | #endif | ||||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | 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 | 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 | --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | ||||
+++ b/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) | @@ -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) | #if JucePlugin_Build_LV2 && ! defined (JucePlugin_LV2URI) | ||||
#error "You need to define the JucePlugin_LV2URI value!" | #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 | 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 | --- a/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp | ||||
+++ b/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 | #ifndef WM_APPCOMMAND | ||||
#define WM_APPCOMMAND 0x0319 | #define WM_APPCOMMAND 0x0319 | ||||
#endif | #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 | 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 | --- a/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | ||||
+++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | +++ b/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h | ||||
@@ -20,6 +20,11 @@ | @@ -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_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 } }, | { 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 | 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 | --- a/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm | ||||
+++ b/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 | namespace juce | ||||
{ | { | ||||
@@ -44,7 +44,7 @@ index 892312098..411bf2097 100644 | |||||
// Change this to disable logging of various activities | // Change this to disable logging of various activities | ||||
#ifndef AU_LOGGING | #ifndef AU_LOGGING | ||||
#define AU_LOGGING 1 | #define AU_LOGGING 1 | ||||
@@ -274,7 +278,7 @@ namespace AudioUnitFormatHelpers | |||||
@@ -276,7 +280,7 @@ namespace AudioUnitFormatHelpers | |||||
NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; | NSBundle* bundle = [[NSBundle alloc] initWithPath: (NSString*) fileOrIdentifier.toCFString()]; | ||||
NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; | NSArray* audioComponents = [bundle objectForInfoDictionaryKey: @"AudioComponents"]; | ||||
@@ -54,7 +54,7 @@ index 892312098..411bf2097 100644 | |||||
desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); | desc.componentManufacturer = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"manufacturer"])); | ||||
desc.componentType = stringToOSType (nsStringToJuce ((NSString*) [dict valueForKey: @"type"])); | 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 | 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 | --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | ||||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | ||||
@@ -1184,7 +1184,7 @@ public: | @@ -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. */ | /** 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 | 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 | --- a/modules/juce_core/native/juce_osx_ObjCHelpers.h | ||||
+++ b/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); | @@ -71,7 +71,7 @@ inline NSArray* varArrayToNSArray (const var& varToParse); | ||||
@@ -99,7 +99,7 @@ index 407d263d0..f2dc28e36 100644 | |||||
return var (dynamicObject.get()); | return var (dynamicObject.get()); | ||||
} | } | ||||
diff --git a/modules/juce_core/system/juce_CompilerSupport.h b/modules/juce_core/system/juce_CompilerSupport.h | 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 | --- a/modules/juce_core/system/juce_CompilerSupport.h | ||||
+++ b/modules/juce_core/system/juce_CompilerSupport.h | +++ b/modules/juce_core/system/juce_CompilerSupport.h | ||||
@@ -92,7 +92,7 @@ | @@ -92,7 +92,7 @@ | ||||
@@ -112,7 +112,7 @@ index e4d87ab97..ddeef94a5 100644 | |||||
#endif | #endif | ||||
diff --git a/modules/juce_core/system/juce_PlatformDefs.h b/modules/juce_core/system/juce_PlatformDefs.h | 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 | --- a/modules/juce_core/system/juce_PlatformDefs.h | ||||
+++ b/modules/juce_core/system/juce_PlatformDefs.h | +++ b/modules/juce_core/system/juce_PlatformDefs.h | ||||
@@ -99,11 +99,7 @@ namespace juce | @@ -99,11 +99,7 @@ namespace juce | ||||
@@ -129,10 +129,10 @@ index 52db7c6d6..96ce314d8 100644 | |||||
#if __GNUC__ >= 7 | #if __GNUC__ >= 7 | ||||
#define JUCE_FALLTHROUGH [[gnu::fallthrough]]; | #define JUCE_FALLTHROUGH [[gnu::fallthrough]]; | ||||
diff --git a/modules/juce_core/system/juce_TargetPlatform.h b/modules/juce_core/system/juce_TargetPlatform.h | 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 | --- a/modules/juce_core/system/juce_TargetPlatform.h | ||||
+++ b/modules/juce_core/system/juce_TargetPlatform.h | +++ b/modules/juce_core/system/juce_TargetPlatform.h | ||||
@@ -144,8 +144,8 @@ | |||||
@@ -145,8 +145,8 @@ | |||||
#endif | #endif | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -144,7 +144,7 @@ index cf610da9a..9173cc34d 100644 | |||||
#error "Building for OSX 10.6 is no longer supported!" | #error "Building for OSX 10.6 is no longer supported!" | ||||
#endif | #endif | ||||
diff --git a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | 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 | --- a/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | ||||
+++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | +++ b/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm | ||||
@@ -26,6 +26,10 @@ | @@ -26,6 +26,10 @@ | ||||
@@ -159,10 +159,10 @@ index 37bf13094..60467ef61 100644 | |||||
// This class has been renamed from CoreGraphicsImage to avoid a symbol | // 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 | // 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 | 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 | --- a/modules/juce_graphics/native/juce_mac_Fonts.mm | ||||
+++ b/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(); | 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(), | 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 | 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 | --- a/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm | ||||
+++ b/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 NSEventTypeSystemDefined: | ||||
case NSEventTypeApplicationDefined: | case NSEventTypeApplicationDefined: | ||||
case NSEventTypePeriodic: | case NSEventTypePeriodic: | ||||
@@ -215,7 +215,7 @@ index 7adb2a869..48ae2951b 100644 | |||||
#if JUCE_64BIT | #if JUCE_64BIT | ||||
case NSEventTypeDirectTouch: | case NSEventTypeDirectTouch: | ||||
diff --git a/modules/juce_gui_basics/native/juce_mac_Windowing.mm b/modules/juce_gui_basics/native/juce_mac_Windowing.mm | 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 | --- a/modules/juce_gui_basics/native/juce_mac_Windowing.mm | ||||
+++ b/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 | @@ -309,7 +309,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi | ||||
@@ -228,10 +228,10 @@ index 5ffbe393c..ddb5aedc5 100644 | |||||
auto eventPos = [event locationInWindow]; | 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 | 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 | --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | ||||
+++ b/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->toFront (true); | ||||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, getLogicalMousePos (buttonPressEvent, peer->getPlatformScaleFactor()), | peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse, getLogicalMousePos (buttonPressEvent, peer->getPlatformScaleFactor()), | ||||
ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, | ModifierKeys::currentModifiers, MouseInputSource::invalidPressure, | ||||
@@ -239,12 +239,12 @@ index cf8df3b72..6f8417340 100644 | |||||
+ MouseInputSource::invalidOrientation, getEventTime (buttonPressEvent)); | + 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 | 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 | --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h | ||||
+++ b/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, | 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, | void handleMouseWheel (MouseInputSource::InputSourceType type, Point<float> positionWithinPeer, | ||||
int64 time, const MouseWheelDetails&, int touchIndex = 0); | 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 | 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 | --- a/modules/juce_gui_extra/juce_gui_extra.cpp | ||||
+++ b/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_PushNotifications.cpp" | ||||
#include "misc/juce_RecentlyOpenedFilesList.cpp" | #include "misc/juce_RecentlyOpenedFilesList.cpp" | ||||
#include "misc/juce_SplashScreen.cpp" | #include "misc/juce_SplashScreen.cpp" | ||||
@@ -267,7 +267,7 @@ index d4ebed7fd..df48cceaf 100644 | |||||
#include "misc/juce_LiveConstantEditor.cpp" | #include "misc/juce_LiveConstantEditor.cpp" | ||||
#include "misc/juce_AnimatedAppComponent.cpp" | #include "misc/juce_AnimatedAppComponent.cpp" | ||||
@@ -154,7 +156,9 @@ | |||||
@@ -146,7 +148,9 @@ | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
#include "native/juce_mac_NSViewComponent.mm" | #include "native/juce_mac_NSViewComponent.mm" | ||||
#include "native/juce_mac_AppleRemote.mm" | #include "native/juce_mac_AppleRemote.mm" | ||||
@@ -279,7 +279,7 @@ index d4ebed7fd..df48cceaf 100644 | |||||
#if JUCE_IOS | #if JUCE_IOS | ||||
diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h | 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 | --- a/modules/juce_gui_extra/juce_gui_extra.h | ||||
+++ b/modules/juce_gui_extra/juce_gui_extra.h | +++ b/modules/juce_gui_extra/juce_gui_extra.h | ||||
@@ -115,7 +115,9 @@ | @@ -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 | 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 | --- a/modules/juce_core/native/juce_posix_SharedCode.h | ||||
+++ b/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; | auto localSymbol = (void*) juce_getExecutableFile; | ||||
dladdr (localSymbol, &exeInfo); | dladdr (localSymbol, &exeInfo); | ||||
@@ -28,7 +28,7 @@ index d2a302e3e..3dc4602ce 100644 | |||||
return cpid >= 0; | return cpid >= 0; | ||||
diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm | 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 | --- a/modules/juce_core/native/juce_mac_Files.mm | ||||
+++ b/modules/juce_core/native/juce_mac_Files.mm | +++ b/modules/juce_core/native/juce_mac_Files.mm | ||||
@@ -92,23 +92,22 @@ namespace MacFileHelpers | @@ -92,23 +92,22 @@ namespace MacFileHelpers | ||||
@@ -65,10 +65,10 @@ index d26443079..616677e1f 100644 | |||||
#endif | #endif | ||||
} | } | ||||
diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h | 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 | --- a/modules/juce_core/native/juce_posix_SharedCode.h | ||||
+++ b/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) | if (pipe (pipeHandles) == 0) | ||||
{ | { | ||||
@@ -88,7 +88,7 @@ index cfdd2d1cd..332a64959 100644 | |||||
if (result < 0) | if (result < 0) | ||||
{ | { | ||||
@@ -1101,6 +1112,7 @@ public: | |||||
@@ -1113,6 +1124,7 @@ public: | |||||
} | } | ||||
else if (result == 0) | else if (result == 0) | ||||
{ | { | ||||
@@ -96,7 +96,7 @@ index cfdd2d1cd..332a64959 100644 | |||||
// we're the child process.. | // we're the child process.. | ||||
close (pipeHandles[0]); // close the read handle | 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); | dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO); | ||||
close (pipeHandles[1]); | 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 | 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 | --- a/modules/juce_core/native/juce_posix_SharedCode.h | ||||
+++ b/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; | return 0; | ||||
} | } | ||||
@@ -15,7 +15,7 @@ index 332a64959..c6d95a93a 100644 | |||||
int pipeHandle = 0; | int pipeHandle = 0; | ||||
int exitCode = -1; | int exitCode = -1; | ||||
diff --git a/modules/juce_core/native/juce_win32_Threads.cpp b/modules/juce_core/native/juce_win32_Threads.cpp | 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 | --- a/modules/juce_core/native/juce_win32_Threads.cpp | ||||
+++ b/modules/juce_core/native/juce_win32_Threads.cpp | +++ b/modules/juce_core/native/juce_win32_Threads.cpp | ||||
@@ -477,6 +477,11 @@ public: | @@ -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 | 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 | --- a/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | ||||
+++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | +++ b/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp | ||||
@@ -26,6 +26,7 @@ | @@ -26,6 +26,7 @@ | ||||
@@ -10,7 +10,7 @@ index 97327c59e..0eca4dfb6 100644 | |||||
static bool exeIsAvailable (String executable) | static bool exeIsAvailable (String executable) | ||||
{ | { | ||||
ChildProcess child; | ChildProcess child; | ||||
@@ -241,10 +242,11 @@ private: | |||||
@@ -245,10 +246,11 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | ||||
}; | }; | ||||
@@ -23,7 +23,7 @@ index 97327c59e..0eca4dfb6 100644 | |||||
return false; | return false; | ||||
#else | #else | ||||
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog"); | 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*) | 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 | 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 | --- a/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp | ||||
+++ b/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 | 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 | --- a/modules/juce_core/native/juce_posix_SharedCode.h | ||||
+++ b/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) | if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0) | ||||
return false; | 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 | 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 | --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | ||||
+++ b/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 | // You must at least have some channels | ||||
jassert (processor->isMidiEffect() || (maxNumInChannels > 0 || maxNumOutChannels > 0)); | 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 | 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 | --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | ||||
+++ b/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 | void run() override | ||||
{ | { | ||||
initialiseJuce_GUI(); | initialiseJuce_GUI(); | ||||
- initialised = true; | - initialised = true; | ||||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | ||||
+ initialised = true; | |||||
XWindowSystem::getInstance(); | 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 | 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 | --- a/modules/juce_audio_processors/juce_audio_processors.cpp | ||||
+++ b/modules/juce_audio_processors/juce_audio_processors.cpp | +++ b/modules/juce_audio_processors/juce_audio_processors.cpp | ||||
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||||
@@ -11,7 +11,7 @@ index ac194a19d..f494ec3a2 100644 | |||||
#include <X11/Xlib.h> | #include <X11/Xlib.h> | ||||
#include <X11/Xutil.h> | #include <X11/Xutil.h> | ||||
#include <sys/utsname.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 "format_types/juce_LegacyAudioParameter.cpp" | ||||
#include "processors/juce_AudioProcessor.cpp" | #include "processors/juce_AudioProcessor.cpp" | ||||
#include "processors/juce_AudioPluginInstance.cpp" | #include "processors/juce_AudioPluginInstance.cpp" | ||||
@@ -26,7 +26,7 @@ index ac194a19d..f494ec3a2 100644 | |||||
#include "format_types/juce_LADSPAPluginFormat.cpp" | #include "format_types/juce_LADSPAPluginFormat.cpp" | ||||
#include "format_types/juce_VSTPluginFormat.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 | 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 | --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp | ||||
+++ b/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) | @@ -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 | #if JUCE_DEBUG && ! JUCE_DISABLE_AUDIOPROCESSOR_BEGIN_END_GESTURE_CHECKING | ||||
// This will fail if you've called beginParameterChangeGesture() for one | // 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(); | processorLayoutsChanged(); | ||||
} | } | ||||
@@ -52,7 +52,7 @@ index caa55b618..41c547b0c 100644 | |||||
//============================================================================== | //============================================================================== | ||||
void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept | void AudioProcessor::editorBeingDeleted (AudioProcessorEditor* const editor) noexcept | ||||
{ | { | ||||
@@ -840,6 +843,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||||
@@ -862,6 +865,7 @@ AudioProcessorEditor* AudioProcessor::createEditorIfNeeded() | |||||
return ed; | return ed; | ||||
} | } | ||||
@@ -61,7 +61,7 @@ index caa55b618..41c547b0c 100644 | |||||
//============================================================================== | //============================================================================== | ||||
void AudioProcessor::getCurrentProgramStateInformation (juce::MemoryBlock& destData) | 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 | 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 | --- a/modules/juce_audio_processors/processors/juce_AudioProcessor.h | ||||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | +++ b/modules/juce_audio_processors/processors/juce_AudioProcessor.h | ||||
@@ -926,6 +926,7 @@ public: | @@ -926,6 +926,7 @@ public: | ||||
@@ -103,10 +103,10 @@ index dbad562ef..ab9e8a7d6 100644 | |||||
int blockSize = 0, latencySamples = 0; | int blockSize = 0, latencySamples = 0; | ||||
bool suspended = false; | bool suspended = false; | ||||
diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | 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 | --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp | ||||
+++ b/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::isInput() const noexcept { return type == audioInputNode || type == midiInputNode; } | ||||
bool AudioProcessorGraph::AudioGraphIOProcessor::isOutput() const noexcept { return type == audioOutputNode || type == midiOutputNode; } | 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::getNumPrograms() { return 0; } | ||||
int AudioProcessorGraph::AudioGraphIOProcessor::getCurrentProgram() { 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 | 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 | --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | ||||
+++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h | ||||
@@ -355,8 +355,10 @@ public: | @@ -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 | 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 | --- a/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp | ||||
+++ b/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 | + #if JUCE_MSVC | ||||
bool showDialog (IFileDialog& dialog, bool async) const | bool showDialog (IFileDialog& dialog, bool async) const | ||||
{ | { | ||||
FILEOPENDIALOGOPTIONS flags = {}; | FILEOPENDIALOGOPTIONS flags = {}; | ||||
@@ -303,6 +304,7 @@ private: | |||||
@@ -327,6 +328,7 @@ private: | |||||
return result; | return result; | ||||
} | } | ||||
@@ -18,13 +18,16 @@ index 065d873e1..fb9cfc999 100644 | |||||
Array<URL> openDialogPreVista (bool async) | Array<URL> openDialogPreVista (bool async) | ||||
{ | { | ||||
@@ -412,8 +414,10 @@ private: | |||||
@@ -436,11 +438,13 @@ private: | |||||
const Remover remover (*this); | const Remover remover (*this); | ||||
+ #if JUCE_MSVC | + #if JUCE_MSVC | ||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) | |||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista | |||||
&& customComponent == nullptr) | |||||
{ | |||||
return openDialogVistaAndUp (async); | return openDialogVistaAndUp (async); | ||||
} | |||||
+ #endif | + #endif | ||||
return openDialogPreVista (async); | 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 | 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 | --- a/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp | ||||
+++ b/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); | const int numChannels = jmax (numIn, numOut); | ||||
@@ -353,8 +353,8 @@ namespace FloatVectorHelpers | |||||
union signMask64 { double d; uint64 i; }; | union signMask64 { double d; uint64 i; }; | ||||
#if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | #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> | template <typename Mode> | ||||
struct MinMax | struct MinMax | ||||
@@ -32,7 +32,7 @@ | |||||
ID: juce_audio_basics | ID: juce_audio_basics | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE audio and MIDI data classes | name: JUCE audio and MIDI data classes | ||||
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | ||||
website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
@@ -60,9 +60,8 @@ namespace MidiBufferHelpers | |||||
if (maxBytes == 1) | if (maxBytes == 1) | ||||
return 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) | 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 start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); | ||||
auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 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) | void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) | ||||
@@ -237,4 +236,73 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio | |||||
return true; | 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 | } // 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) | 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; | ok = true; | ||||
break; | break; | ||||
@@ -71,21 +125,37 @@ namespace MidiFileHelpers | |||||
} | } | ||||
if (! ok) | 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, | 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; | const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | ||||
// (put a sanity-check on the file size, as midi files are generally small) | // (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 | // 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* a, | ||||
const MidiMessageSequence::MidiEventHolder* b) | 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(); | return a->message.isNoteOff() && b->message.isNoteOn(); | ||||
}); | }); | ||||
addTrack (result); | |||||
if (createMatchingNoteOffs) | if (createMatchingNoteOffs) | ||||
tracks.getLast()->updateMatchedPairs(); | |||||
sequence.updateMatchedPairs(); | |||||
addTrack (sequence); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -443,4 +532,267 @@ bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) | |||||
return true; | 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 | } // 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 | int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept | ||||
{ | { | ||||
numBytesUsed = 0; | numBytesUsed = 0; | ||||
@@ -224,16 +249,8 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||||
} | } | ||||
else if (byte == 0xff) | 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); | auto dest = allocateSpace (size); | ||||
*dest = (uint8) byte; | *dest = (uint8) byte; | ||||
@@ -682,7 +699,7 @@ bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0x | |||||
int MidiMessage::getMetaEventType() const noexcept | int MidiMessage::getMetaEventType() const noexcept | ||||
{ | { | ||||
auto data = getRawData(); | auto data = getRawData(); | ||||
return *data != 0xff ? -1 : data[1]; | |||||
return (size < 2 || *data != 0xff) ? -1 : data[1]; | |||||
} | } | ||||
int MidiMessage::getMetaEventLength() const noexcept | int MidiMessage::getMetaEventLength() const noexcept | ||||
@@ -691,8 +708,8 @@ int MidiMessage::getMetaEventLength() const noexcept | |||||
if (*data == 0xff) | 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; | return 0; | ||||
@@ -702,10 +719,9 @@ const uint8* MidiMessage::getMetaEventData() const noexcept | |||||
{ | { | ||||
jassert (isMetaEvent()); | jassert (isMetaEvent()); | ||||
int n; | |||||
auto d = getRawData() + 2; | 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; } | 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; | 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 | } // namespace juce |
@@ -858,11 +858,45 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
/** Reads a midi variable-length integer. | /** 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 | /** 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). | to return the message length (either 1, 2, or 3 bytes). | ||||
@@ -27,30 +27,33 @@ namespace | |||||
{ | { | ||||
const uint8 noLSBValueReceived = 0xff; | const uint8 noLSBValueReceived = 0xff; | ||||
const Range<int> allChannels { 1, 17 }; | 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 | 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; | pitchbendDimension.value = &MPENote::pitchbend; | ||||
pressureDimension.value = &MPENote::pressure; | pressureDimension.value = &MPENote::pressure; | ||||
timbreDimension.value = &MPENote::timbre; | 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.isEnabled = false; | ||||
legacyMode.pitchbendRange = 2; | legacyMode.pitchbendRange = 2; | ||||
legacyMode.channelRange = allChannels; | legacyMode.channelRange = allChannels; | ||||
} | } | ||||
MPEInstrument::~MPEInstrument() | |||||
{ | |||||
} | |||||
MPEInstrument::~MPEInstrument() = default; | |||||
//============================================================================== | //============================================================================== | ||||
MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept | MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept | ||||
@@ -58,6 +61,23 @@ MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept | |||||
return zoneLayout; | 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) | void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) | ||||
{ | { | ||||
releaseAllNotes(); | releaseAllNotes(); | ||||
@@ -65,6 +85,8 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) | |||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
legacyMode.isEnabled = false; | legacyMode.isEnabled = false; | ||||
zoneLayout = newLayout; | zoneLayout = newLayout; | ||||
resetLastReceivedValues(); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -376,6 +376,8 @@ private: | |||||
LegacyMode legacyMode; | LegacyMode legacyMode; | ||||
MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | MPEDimension pitchbendDimension, pressureDimension, timbreDimension; | ||||
void resetLastReceivedValues(); | |||||
void updateDimension (int midiChannel, MPEDimension&, MPEValue); | void updateDimension (int midiChannel, MPEDimension&, MPEValue); | ||||
void updateDimensionMaster (bool, MPEDimension&, MPEValue); | void updateDimensionMaster (bool, MPEDimension&, MPEValue); | ||||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | ||||
@@ -199,7 +199,7 @@ public: | |||||
@see getNextSample | @see getNextSample | ||||
*/ | */ | ||||
template<typename FloatType> | |||||
template <typename FloatType> | |||||
void applyEnvelopeToBuffer (AudioBuffer<FloatType>& buffer, int startSample, int numSamples) | void applyEnvelopeToBuffer (AudioBuffer<FloatType>& buffer, int startSample, int numSamples) | ||||
{ | { | ||||
jassert (startSample + numSamples <= buffer.getNumSamples()); | jassert (startSample + numSamples <= buffer.getNumSamples()); | ||||
@@ -37,7 +37,7 @@ public: | |||||
} | } | ||||
private: | private: | ||||
template<typename InterpolatorType> | |||||
template <typename InterpolatorType> | |||||
void runInterplatorTests (const String& interpolatorName) | void runInterplatorTests (const String& interpolatorName) | ||||
{ | { | ||||
auto createGaussian = [] (std::vector<float>& destination, float scale, float centreInSamples, float width) | 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); } | static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } | ||||
}; | }; | ||||
template<> | |||||
template <> | |||||
struct LagrangeResampleHelper<0> | struct LagrangeResampleHelper<0> | ||||
{ | { | ||||
static forcedinline void calc (float&, float) noexcept {} | static forcedinline void calc (float&, float) noexcept {} | ||||
@@ -339,7 +339,7 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||||
midiDeviceInfosFromXml.clear(); | midiDeviceInfosFromXml.clear(); | ||||
enabledMidiInputs.clear(); | enabledMidiInputs.clear(); | ||||
forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") | |||||
for (auto* c : xml.getChildWithTagNameIterator ("MIDIINPUT")) | |||||
midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); | midiDeviceInfosFromXml.add ({ c->getStringAttribute ("name"), c->getStringAttribute ("identifier") }); | ||||
auto isIdentifierAvailable = [] (const Array<MidiDeviceInfo>& available, const String& 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 | // This method will only reload the last device that was running | ||||
// before closeAudioDevice() was called - you need to actually open | // before closeAudioDevice() was called - you need to actually open | ||||
// one first, with setAudioDevice(). | |||||
// one first, with setAudioDeviceSetup(). | |||||
jassertfalse; | jassertfalse; | ||||
return; | return; | ||||
} | } | ||||
@@ -1146,12 +1146,19 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCall | |||||
void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) | 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 | Note that this only reloads the last device that was running before | ||||
closeAudioDevice() was called - it doesn't reload any kind of saved-state, | 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. | 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. | 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; | virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; | ||||
@@ -47,6 +47,35 @@ | |||||
#include "native/juce_MidiDataConcatenator.h" | #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 | #if JUCE_MAC | ||||
#define Point CarbonDummyPointName | #define Point CarbonDummyPointName | ||||
@@ -58,7 +87,7 @@ | |||||
#undef Component | #undef Component | ||||
#include "native/juce_mac_CoreAudio.cpp" | #include "native/juce_mac_CoreAudio.cpp" | ||||
#include "native/juce_mac_CoreMidi.cpp" | |||||
#include "native/juce_mac_CoreMidi.mm" | |||||
#elif JUCE_IOS | #elif JUCE_IOS | ||||
#import <AudioToolbox/AudioToolbox.h> | #import <AudioToolbox/AudioToolbox.h> | ||||
@@ -70,7 +99,7 @@ | |||||
#endif | #endif | ||||
#include "native/juce_ios_Audio.cpp" | #include "native/juce_ios_Audio.cpp" | ||||
#include "native/juce_mac_CoreMidi.cpp" | |||||
#include "native/juce_mac_CoreMidi.mm" | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
@@ -32,7 +32,7 @@ | |||||
ID: juce_audio_devices | ID: juce_audio_devices | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE audio and MIDI I/O device classes | name: JUCE audio and MIDI I/O device classes | ||||
description: Classes to play and record from audio and MIDI I/O devices | description: Classes to play and record from audio and MIDI I/O devices | ||||
website: http://www.juce.com/juce | 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) | void readInput (AudioBuffer<float>& buffer, const int numSamples, const int blockSizeMs) | ||||
{ | { | ||||
for (auto* d : devices) | 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) | for (auto* d : devices) | ||||
{ | { | ||||
@@ -1679,17 +1684,20 @@ private: | |||||
d->done = true; | d->done = true; | ||||
} | } | ||||
else | else | ||||
anyRemaining = true; | |||||
{ | |||||
anySamplesRemaining = true; | |||||
} | |||||
} | } | ||||
} | } | ||||
if (! anyRemaining) | |||||
if (! anySamplesRemaining) | |||||
return; | return; | ||||
if (--tries == 0) | |||||
if (--numReadAttemptsRemaining == 0) | |||||
break; | break; | ||||
wait (blockSizeMs); | |||||
wait (jmax (1, roundToInt (waitTime))); | |||||
waitTime *= 2.0f; | |||||
} | } | ||||
for (auto* d : devices) | for (auto* d : devices) | ||||
@@ -1717,7 +1725,9 @@ private: | |||||
d->done = true; | d->done = true; | ||||
} | } | ||||
else | else | ||||
{ | |||||
anyRemaining = true; | anyRemaining = true; | ||||
} | |||||
} | } | ||||
} | } | ||||
@@ -1808,6 +1818,8 @@ private: | |||||
numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; | numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; | ||||
numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; | numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; | ||||
isWaitingForInput = numInputChans > 0; | |||||
inputIndex = channelIndex; | inputIndex = channelIndex; | ||||
outputIndex = channelIndex + numInputChans; | outputIndex = channelIndex + numInputChans; | ||||
@@ -1892,6 +1904,8 @@ private: | |||||
{ | { | ||||
if (numInputChannels > 0) | if (numInputChannels > 0) | ||||
{ | { | ||||
isWaitingForInput = false; | |||||
int start1, size1, start2, size2; | int start1, size1, start2, size2; | ||||
inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); | inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); | ||||
@@ -1973,6 +1987,7 @@ private: | |||||
std::unique_ptr<CoreAudioIODevice> device; | std::unique_ptr<CoreAudioIODevice> device; | ||||
int inputIndex = 0, numInputChans = 0, outputIndex = 0, numOutputChans = 0; | int inputIndex = 0, numInputChans = 0, outputIndex = 0, numOutputChans = 0; | ||||
bool useInputs = false, useOutputs = false; | bool useInputs = false, useOutputs = false; | ||||
std::atomic<bool> isWaitingForInput { false }; | |||||
AbstractFifo inputFifo { 32 }, outputFifo { 32 }; | AbstractFifo inputFifo { 32 }, outputFifo { 32 }; | ||||
bool done = false; | 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 | struct Win32MidiDeviceQuery | ||||
{ | { | ||||
static Array<MidiDeviceInfo> getAvailableDevices() | static Array<MidiDeviceInfo> getAvailableDevices() | ||||
@@ -782,9 +782,9 @@ private: | |||||
public: | public: | ||||
virtual ~DeviceCallbackHandler() {}; | 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) | 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()); | auto hr = requestedProperties->QueryInterface (__uuidof (IIterable<HSTRING>), (void**) iter.resetAndGetPointerAddress()); | ||||
if (FAILED (hr)) | if (FAILED (hr)) | ||||
@@ -891,7 +891,7 @@ private: | |||||
watcher = nullptr; | watcher = nullptr; | ||||
} | } | ||||
template<typename InfoType> | |||||
template <typename InfoType> | |||||
IInspectable* getValueFromDeviceInfo (String key, InfoType* info) | IInspectable* getValueFromDeviceInfo (String key, InfoType* info) | ||||
{ | { | ||||
__FIMapView_2_HSTRING_IInspectable* properties; | __FIMapView_2_HSTRING_IInspectable* properties; | ||||
@@ -924,7 +924,7 @@ private: | |||||
String getGUIDFromInspectable (IInspectable& inspectable) | String getGUIDFromInspectable (IInspectable& inspectable) | ||||
{ | { | ||||
WinRTWrapper::ComPtr<IReference<GUID>> guidRef; | |||||
ComSmartPtr<IReference<GUID>> guidRef; | |||||
auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>), | auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>), | ||||
(void**) guidRef.resetAndGetPointerAddress()); | (void**) guidRef.resetAndGetPointerAddress()); | ||||
@@ -951,7 +951,7 @@ private: | |||||
bool getBoolFromInspectable (IInspectable& inspectable) | bool getBoolFromInspectable (IInspectable& inspectable) | ||||
{ | { | ||||
WinRTWrapper::ComPtr<IReference<bool>> boolRef; | |||||
ComSmartPtr<IReference<bool>> boolRef; | |||||
auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>), | auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>), | ||||
(void**) boolRef.resetAndGetPointerAddress()); | (void**) boolRef.resetAndGetPointerAddress()); | ||||
@@ -978,7 +978,7 @@ private: | |||||
struct DeviceEnumerationThread : public Thread | struct DeviceEnumerationThread : public Thread | ||||
{ | { | ||||
DeviceEnumerationThread (DeviceCallbackHandler& h, | DeviceEnumerationThread (DeviceCallbackHandler& h, | ||||
WinRTWrapper::ComPtr<IDeviceWatcher>& w, | |||||
ComSmartPtr<IDeviceWatcher>& w, | |||||
EventRegistrationToken& added, | EventRegistrationToken& added, | ||||
EventRegistrationToken& removed, | EventRegistrationToken& removed, | ||||
EventRegistrationToken& updated) | EventRegistrationToken& updated) | ||||
@@ -1012,12 +1012,12 @@ private: | |||||
} | } | ||||
DeviceCallbackHandler& handler; | DeviceCallbackHandler& handler; | ||||
WinRTWrapper::ComPtr<IDeviceWatcher>& watcher; | |||||
ComSmartPtr<IDeviceWatcher>& watcher; | |||||
EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken; | EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken; | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
WinRTWrapper::ComPtr<IDeviceWatcher> watcher; | |||||
ComSmartPtr<IDeviceWatcher> watcher; | |||||
EventRegistrationToken deviceAddedToken { 0 }, | EventRegistrationToken deviceAddedToken { 0 }, | ||||
deviceRemovedToken { 0 }, | deviceRemovedToken { 0 }, | ||||
@@ -1222,7 +1222,7 @@ private: | |||||
template <typename COMFactoryType> | template <typename COMFactoryType> | ||||
struct MidiIODeviceWatcher final : private DeviceCallbackHandler | struct MidiIODeviceWatcher final : private DeviceCallbackHandler | ||||
{ | { | ||||
MidiIODeviceWatcher (WinRTWrapper::ComPtr<COMFactoryType>& comFactory) | |||||
MidiIODeviceWatcher (ComSmartPtr<COMFactoryType>& comFactory) | |||||
: factory (comFactory) | : factory (comFactory) | ||||
{ | { | ||||
} | } | ||||
@@ -1407,7 +1407,7 @@ private: | |||||
return {}; | return {}; | ||||
} | } | ||||
WinRTWrapper::ComPtr<COMFactoryType>& factory; | |||||
ComSmartPtr<COMFactoryType>& factory; | |||||
Array<WinRTMIDIDeviceInfo> connectedDevices; | Array<WinRTMIDIDeviceInfo> connectedDevices; | ||||
CriticalSection deviceChanges; | CriticalSection deviceChanges; | ||||
@@ -1421,8 +1421,8 @@ private: | |||||
struct OpenMidiPortThread : public Thread | struct OpenMidiPortThread : public Thread | ||||
{ | { | ||||
OpenMidiPortThread (String threadName, String midiDeviceID, | OpenMidiPortThread (String threadName, String midiDeviceID, | ||||
WinRTWrapper::ComPtr<COMFactoryType>& comFactory, | |||||
WinRTWrapper::ComPtr<COMInterfaceType>& comPort) | |||||
ComSmartPtr<COMFactoryType>& comFactory, | |||||
ComSmartPtr<COMInterfaceType>& comPort) | |||||
: Thread (threadName), | : Thread (threadName), | ||||
deviceID (midiDeviceID), | deviceID (midiDeviceID), | ||||
factory (comFactory), | factory (comFactory), | ||||
@@ -1438,7 +1438,7 @@ private: | |||||
void run() override | void run() override | ||||
{ | { | ||||
WinRTWrapper::ScopedHString hDeviceId (deviceID); | WinRTWrapper::ScopedHString hDeviceId (deviceID); | ||||
WinRTWrapper::ComPtr<IAsyncOperation<COMType*>> asyncOp; | |||||
ComSmartPtr<IAsyncOperation<COMType*>> asyncOp; | |||||
auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); | auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); | ||||
if (FAILED (hr)) | if (FAILED (hr)) | ||||
@@ -1466,8 +1466,8 @@ private: | |||||
} | } | ||||
const String deviceID; | const String deviceID; | ||||
WinRTWrapper::ComPtr<COMFactoryType>& factory; | |||||
WinRTWrapper::ComPtr<COMInterfaceType>& port; | |||||
ComSmartPtr<COMFactoryType>& factory; | |||||
ComSmartPtr<COMInterfaceType>& port; | |||||
WaitableEvent portOpened { true }; | WaitableEvent portOpened { true }; | ||||
}; | }; | ||||
@@ -1552,7 +1552,7 @@ private: | |||||
BLEDeviceWatcher& bleDeviceWatcher; | BLEDeviceWatcher& bleDeviceWatcher; | ||||
WinRTMIDIDeviceInfo deviceInfo; | WinRTMIDIDeviceInfo deviceInfo; | ||||
bool isBLEDevice = false; | bool isBLEDevice = false; | ||||
WinRTWrapper::ComPtr<MIDIPort> midiPort; | |||||
ComSmartPtr<MIDIPort> midiPort; | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -1637,19 +1637,19 @@ private: | |||||
if (! isStarted) | if (! isStarted) | ||||
return S_OK; | return S_OK; | ||||
WinRTWrapper::ComPtr<IMidiMessage> message; | |||||
ComSmartPtr<IMidiMessage> message; | |||||
auto hr = args->get_Message (message.resetAndGetPointerAddress()); | auto hr = args->get_Message (message.resetAndGetPointerAddress()); | ||||
if (FAILED (hr)) | if (FAILED (hr)) | ||||
return hr; | return hr; | ||||
WinRTWrapper::ComPtr<IBuffer> buffer; | |||||
ComSmartPtr<IBuffer> buffer; | |||||
hr = message->get_RawData (buffer.resetAndGetPointerAddress()); | hr = message->get_RawData (buffer.resetAndGetPointerAddress()); | ||||
if (FAILED (hr)) | if (FAILED (hr)) | ||||
return hr; | return hr; | ||||
WinRTWrapper::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||||
ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess; | |||||
hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); | hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); | ||||
if (FAILED (hr)) | if (FAILED (hr)) | ||||
@@ -1775,15 +1775,15 @@ private: | |||||
String getDeviceName() override { return deviceInfo.name; } | 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; | uint8_t* bufferData = nullptr; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper); | 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<IMidiInPortStatics>> inputDeviceWatcher; | ||||
std::unique_ptr<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher; | 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") | #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") | ||||
#endif | #endif | ||||
#define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown | |||||
#define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE | |||||
enum EDataFlow | enum EDataFlow | ||||
{ | { | ||||
eRender = 0, | eRender = 0, | ||||
@@ -337,10 +334,6 @@ JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD" | |||||
JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0; | JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0; | ||||
}; | }; | ||||
#undef JUCE_COMCALL | |||||
#undef JUCE_COMCLASS | |||||
#undef JUCE_IUNKNOWNCLASS | |||||
//============================================================================== | //============================================================================== | ||||
namespace WasapiClasses | namespace WasapiClasses | ||||
{ | { | ||||
@@ -893,7 +886,7 @@ public: | |||||
reservoirWritePos = 0; | reservoirWritePos = 0; | ||||
} | } | ||||
template<class SourceType> | |||||
template <class SourceType> | |||||
void updateFormatWithType (SourceType*) noexcept | void updateFormatWithType (SourceType*) noexcept | ||||
{ | { | ||||
using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>; | using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>; | ||||
@@ -1053,7 +1046,7 @@ public: | |||||
renderClient = nullptr; | renderClient = nullptr; | ||||
} | } | ||||
template<class DestType> | |||||
template <class DestType> | |||||
void updateFormatWithType (DestType*) | void updateFormatWithType (DestType*) | ||||
{ | { | ||||
using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>; | using NativeType = AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>; | ||||
@@ -1755,11 +1748,11 @@ private: | |||||
ChangeNotificationClient (WASAPIAudioIODeviceType* d) | ChangeNotificationClient (WASAPIAudioIODeviceType* d) | ||||
: ComBaseClassHelper<IMMNotificationClient> (0), device (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: | private: | ||||
WeakReference<WASAPIAudioIODeviceType> device; | WeakReference<WASAPIAudioIODeviceType> device; | ||||
@@ -67,25 +67,25 @@ namespace AiffFileHelpers | |||||
Loop sustainLoop; | Loop sustainLoop; | ||||
Loop releaseLoop; | 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) | static uint16 getValue16 (const StringPairArray& values, const char* name, const char* def) | ||||
@@ -149,7 +149,7 @@ namespace AiffFileHelpers | |||||
input.read (unknown, sizeof (unknown)); | input.read (unknown, sizeof (unknown)); | ||||
} | } | ||||
void addToMetadata (StringPairArray& metadata) const | |||||
void addToMetadata (std::map<String, String>& metadata) const | |||||
{ | { | ||||
const bool rootNoteSet = rootNote != 0; | const bool rootNoteSet = rootNote != 0; | ||||
@@ -157,11 +157,11 @@ namespace AiffFileHelpers | |||||
setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); | setBoolFlag (metadata, AiffAudioFormat::appleRootSet, rootNoteSet); | ||||
if (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; | const char* keyString = nullptr; | ||||
@@ -175,12 +175,14 @@ namespace AiffFileHelpers | |||||
} | } | ||||
if (keyString != nullptr) | 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; | uint32 flags; | ||||
@@ -388,6 +390,17 @@ public: | |||||
{ | { | ||||
using namespace AiffFileHelpers; | 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")) | if (input->readInt() == chunkName ("FORM")) | ||||
{ | { | ||||
auto len = input->readIntBigEndian(); | auto len = input->readIntBigEndian(); | ||||
@@ -479,8 +492,8 @@ public: | |||||
auto numCues = (uint16) input->readShortBigEndian(); | auto numCues = (uint16) input->readShortBigEndian(); | ||||
// these two are always the same for AIFF-read files | // 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) | for (uint16 i = 0; i < numCues; ++i) | ||||
{ | { | ||||
@@ -497,18 +510,18 @@ public: | |||||
input->readByte(); | input->readByte(); | ||||
auto prefixCue = "Cue" + String (i); | 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); | 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")) | else if (type == chunkName ("COMT")) | ||||
{ | { | ||||
auto numNotes = (uint16) input->readShortBigEndian(); | auto numNotes = (uint16) input->readShortBigEndian(); | ||||
metadataValues.set ("NumCueNotes", String (numNotes)); | |||||
metadataValuesMap.emplace ("NumCueNotes", String (numNotes)); | |||||
for (uint16 i = 0; i < numNotes; ++i) | for (uint16 i = 0; i < numNotes; ++i) | ||||
{ | { | ||||
@@ -520,9 +533,9 @@ public: | |||||
input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); | input->readIntoMemoryBlock (textBlock, stringLength + (stringLength & 1)); | ||||
auto prefix = "CueNote" + String (i); | 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")) | else if (type == chunkName ("INST")) | ||||
@@ -530,16 +543,16 @@ public: | |||||
HeapBlock<InstChunk> inst; | HeapBlock<InstChunk> inst; | ||||
inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | ||||
input->read (inst, (int) length); | input->read (inst, (int) length); | ||||
inst->copyTo (metadataValues); | |||||
inst->copyTo (metadataValuesMap); | |||||
} | } | ||||
else if (type == chunkName ("basc")) | else if (type == chunkName ("basc")) | ||||
{ | { | ||||
AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValues); | |||||
AiffFileHelpers::BASCChunk (*input).addToMetadata (metadataValuesMap); | |||||
} | } | ||||
else if (type == chunkName ("cate")) | 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) | else if ((hasGotVer && hasGotData && hasGotType) | ||||
|| chunkEnd < input->getPosition() | || 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; | samplesInReservoir = 0; | ||||
} | } | ||||
else if (startSampleInFile < reservoirStart | 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 | // 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. | // accurately than this. Probably fixed in newer versions of the library, though. | ||||
@@ -367,7 +367,7 @@ public: | |||||
private: | private: | ||||
FlacNamespace::FLAC__StreamDecoder* decoder; | FlacNamespace::FLAC__StreamDecoder* decoder; | ||||
AudioBuffer<float> reservoir; | AudioBuffer<float> reservoir; | ||||
int reservoirStart = 0, samplesInReservoir = 0; | |||||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||||
bool ok = false, scanningForLength = false; | bool ok = false, scanningForLength = false; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader) | ||||
@@ -492,7 +492,9 @@ struct MP3Frame | |||||
return frequencies[sampleRateIndex]; | return frequencies[sampleRateIndex]; | ||||
} | } | ||||
void decodeHeader (const uint32 header) | |||||
enum class ParseSuccessful { no, yes }; | |||||
ParseSuccessful decodeHeader (const uint32 header) | |||||
{ | { | ||||
jassert (((header >> 10) & 3) != 3); | jassert (((header >> 10) & 3) != 3); | ||||
@@ -527,17 +529,18 @@ struct MP3Frame | |||||
jassertfalse; // This means the file is using "free format". Apparently very few decoders | 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! | // support this mode, and this one certainly doesn't handle it correctly! | ||||
frameSize = 0; | 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; | int layer, frameSize, numChannels, single; | ||||
@@ -1430,7 +1433,11 @@ struct MP3Stream | |||||
lastFrameSize += nextFrameOffset; | lastFrameSize += nextFrameOffset; | ||||
} | } | ||||
frame.decodeHeader ((uint32) stream.readIntBigEndian()); | |||||
const auto successful = frame.decodeHeader ((uint32) stream.readIntBigEndian()); | |||||
if (successful == MP3Frame::ParseSuccessful::no) | |||||
return -1; | |||||
headerParsed = true; | headerParsed = true; | ||||
frameSize = frame.frameSize; | frameSize = frame.frameSize; | ||||
isFreeFormat = (frameSize == 0); | isFreeFormat = (frameSize == 0); | ||||
@@ -160,13 +160,13 @@ public: | |||||
{ | { | ||||
while (numSamples > 0) | while (numSamples > 0) | ||||
{ | { | ||||
auto numAvailable = (int) (reservoirStart + samplesInReservoir - startSampleInFile); | |||||
auto numAvailable = (reservoirStart + samplesInReservoir - startSampleInFile); | |||||
if (startSampleInFile >= reservoirStart && numAvailable > 0) | if (startSampleInFile >= reservoirStart && numAvailable > 0) | ||||
{ | { | ||||
// got a few samples overlapping, so use them before seeking.. | // 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;) | for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | ||||
if (destSamples[i] != nullptr) | if (destSamples[i] != nullptr) | ||||
@@ -175,8 +175,8 @@ public: | |||||
(size_t) numToUse * sizeof (float)); | (size_t) numToUse * sizeof (float)); | ||||
startSampleInFile += numToUse; | startSampleInFile += numToUse; | ||||
numSamples -= numToUse; | |||||
startOffsetInDestBuffer += numToUse; | |||||
numSamples -= (int) numToUse; | |||||
startOffsetInDestBuffer += (int) numToUse; | |||||
if (numSamples == 0) | if (numSamples == 0) | ||||
break; | break; | ||||
@@ -194,7 +194,7 @@ public: | |||||
int bitStream = 0; | int bitStream = 0; | ||||
int offset = 0; | int offset = 0; | ||||
int numToRead = samplesInReservoir; | |||||
int numToRead = (int) samplesInReservoir; | |||||
while (numToRead > 0) | while (numToRead > 0) | ||||
{ | { | ||||
@@ -261,7 +261,7 @@ private: | |||||
OggVorbisNamespace::OggVorbis_File ovFile; | OggVorbisNamespace::OggVorbis_File ovFile; | ||||
OggVorbisNamespace::ov_callbacks callbacks; | OggVorbisNamespace::ov_callbacks callbacks; | ||||
AudioBuffer<float> reservoir; | AudioBuffer<float> reservoir; | ||||
int reservoirStart = 0, samplesInReservoir = 0; | |||||
int64 reservoirStart = 0, samplesInReservoir = 0; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggReader) | ||||
}; | }; | ||||
@@ -993,10 +993,12 @@ public: | |||||
input->skipNextBytes (2); | input->skipNextBytes (2); | ||||
bitsPerSample = (unsigned int) (int) input->readShort(); | bitsPerSample = (unsigned int) (int) input->readShort(); | ||||
if (bitsPerSample > 64) | |||||
if (bitsPerSample > 64 && (int) sampleRate != 0) | |||||
{ | { | ||||
bytesPerFrame = bytesPerSec / (int) sampleRate; | bytesPerFrame = bytesPerSec / (int) sampleRate; | ||||
bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; | |||||
if (numChannels != 0) | |||||
bitsPerSample = 8 * (unsigned int) bytesPerFrame / numChannels; | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -35,7 +35,7 @@ | |||||
ID: juce_audio_formats | ID: juce_audio_formats | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE audio file format codecs | name: JUCE audio file format codecs | ||||
description: Classes for reading and writing various audio file formats. | description: Classes for reading and writing various audio file formats. | ||||
website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
@@ -104,7 +104,7 @@ | |||||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 | #define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 | ||||
#endif | #endif | ||||
#if ! JUCE_MSVC | |||||
#if ! JUCE_WINDOWS | |||||
#undef JUCE_USE_WINDOWS_MEDIA_FORMAT | #undef JUCE_USE_WINDOWS_MEDIA_FORMAT | ||||
#define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | #define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | ||||
#endif | #endif | ||||
@@ -1085,24 +1085,28 @@ namespace AAXClasses | |||||
SetParameterNormalizedValue (paramID, (double) newValue); | SetParameterNormalizedValue (paramID, (double) newValue); | ||||
} | } | ||||
void audioProcessorChanged (AudioProcessor* processor) override | |||||
void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails& details) override | |||||
{ | { | ||||
++mNumPlugInChanges; | ++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 | void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int parameterIndex) override | ||||
@@ -1124,6 +1128,20 @@ namespace AAXClasses | |||||
case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; | case AAX_eNotificationEvent_EnteringOfflineMode: pluginInstance->setNonRealtime (true); break; | ||||
case AAX_eNotificationEvent_ExitingOfflineMode: pluginInstance->setNonRealtime (false); 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: | case AAX_eNotificationEvent_TrackNameChanged: | ||||
if (data != nullptr) | if (data != nullptr) | ||||
{ | { | ||||
@@ -22,6 +22,8 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#include <juce_core/system/juce_CompilerWarnings.h> | #include <juce_core/system/juce_CompilerWarnings.h> | ||||
#include "../utility/juce_CheckSettingMacros.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", | "-Wzero-as-null-pointer-constant", | ||||
"-Wnullable-to-nonnull-conversion", | "-Wnullable-to-nonnull-conversion", | ||||
"-Wgnu-zero-variadic-macro-arguments", | "-Wgnu-zero-variadic-macro-arguments", | ||||
"-Wformat-pedantic") | |||||
"-Wformat-pedantic", | |||||
"-Wdeprecated-anon-enum-enum-conversion") | |||||
#include "../utility/juce_IncludeSystemHeaders.h" | #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? | // was there an error? | ||||
@@ -1163,16 +1166,24 @@ public: | |||||
sendAUEvent (kAudioUnitEvent_EndParameterChangeGesture, index); | 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 | return (scope != kAudioUnitScope_Input | ||||
&& scope != kAudioUnitScope_Output) | && scope != kAudioUnitScope_Output) | ||||
? kAudioUnitErr_InvalidScope : noErr; | |||||
? (OSStatus) kAudioUnitErr_InvalidScope : (OSStatus) noErr; | |||||
} | } | ||||
OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept | OSStatus elementToBusIdx (AudioUnitScope scope, AudioUnitElement element, bool& isInput, int& busIdx) noexcept | ||||
@@ -2040,10 +2051,12 @@ private: | |||||
addSupportedLayoutTags(); | addSupportedLayoutTags(); | ||||
for (int i = 0; i < enabledInputs; ++i) | 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) | 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; | return noErr; | ||||
} | } | ||||
@@ -2166,7 +2179,7 @@ private: | |||||
#endif | #endif | ||||
// add discrete layout tags | // add discrete layout tags | ||||
int n = bus->getMaxSupportedChannels(maxChannelsToProbeFor()); | |||||
int n = bus->getMaxSupportedChannels (maxChannelsToProbeFor()); | |||||
for (int ch = 0; ch < n; ++ch) | for (int ch = 0; ch < n; ++ch) | ||||
{ | { | ||||
@@ -73,6 +73,8 @@ | |||||
#define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) | #define JUCE_AUDIOUNIT_OBJC_NAME(x) JUCE_JOIN_MACRO (x, AUv3) | ||||
#include <future> | |||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") | JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullability-completeness") | ||||
using namespace juce; | using namespace juce; | ||||
@@ -912,7 +914,7 @@ public: | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
void audioProcessorChanged (AudioProcessor* processor) override | |||||
void audioProcessorChanged (AudioProcessor* processor, const ChangeDetails&) override | |||||
{ | { | ||||
ignoreUnused (processor); | ignoreUnused (processor); | ||||
@@ -1750,7 +1752,7 @@ public: | |||||
{ | { | ||||
JUCE_ASSERT_MESSAGE_THREAD | JUCE_ASSERT_MESSAGE_THREAD | ||||
if (processorHolder != nullptr) | |||||
if (processorHolder.get() != nullptr) | |||||
JuceAudioUnitv3::removeEditor (getAudioProcessor()); | JuceAudioUnitv3::removeEditor (getAudioProcessor()); | ||||
} | } | ||||
@@ -1795,22 +1797,25 @@ public: | |||||
void viewDidLayoutSubviews() | 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() | void didReceiveMemoryWarning() | ||||
{ | { | ||||
if (processorHolder != nullptr) | |||||
if (auto* processor = processorHolder->get()) | |||||
if (auto ptr = processorHolder.get()) | |||||
if (auto* processor = ptr->get()) | |||||
processor->memoryWarningReceived(); | processor->memoryWarningReceived(); | ||||
} | } | ||||
void viewDidAppear (bool) | void viewDidAppear (bool) | ||||
{ | { | ||||
if (processorHolder != nullptr) | |||||
if (processorHolder.get() != nullptr) | |||||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | ||||
editor->setVisible (true); | editor->setVisible (true); | ||||
} | } | ||||
void viewDidDisappear (bool) | void viewDidDisappear (bool) | ||||
{ | { | ||||
if (processorHolder != nullptr) | |||||
if (processorHolder.get() != nullptr) | |||||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | ||||
editor->setVisible (false); | editor->setVisible (false); | ||||
} | } | ||||
@@ -1846,65 +1851,72 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
AUAudioUnit* createAudioUnit (const AudioComponentDescription& descr, NSError** error) | 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; | AUViewController<AUAudioUnitFactory>* myself; | ||||
AudioProcessorHolder::Ptr processorHolder = nullptr; | |||||
LockedProcessorHolder processorHolder; | |||||
Rectangle<int> preferredSize { 1, 1 }; | 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) | if (filter->getNumPrograms() != lastProgramCount) | ||||
{ | { | ||||
@@ -364,6 +364,7 @@ static const String makePresetsFile (AudioProcessor* const filter) | |||||
// Header | // Header | ||||
text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n"; | text += "@prefix atom: <" LV2_ATOM_PREFIX "> .\n"; | ||||
text += "@prefix lv2: <" LV2_CORE_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 pset: <" LV2_PRESETS_PREFIX "> .\n"; | ||||
text += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\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"; | 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:domain state:State ;\n"; | ||||
text += " rdfs:range xsd:base64Binary .\n"; | text += " rdfs:range xsd:base64Binary .\n"; | ||||
#endif | #endif | ||||
text += "\n"; | |||||
#endif | #endif | ||||
// Presets | // Presets | ||||
@@ -790,7 +790,7 @@ public: | |||||
ReleaseControl (index + 2); | ReleaseControl (index + 2); | ||||
} | } | ||||
void audioProcessorChanged (AudioProcessor*) override | |||||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override | |||||
{ | { | ||||
// xxx is there an RTAS equivalent? | // xxx is there an RTAS equivalent? | ||||
} | } | ||||
@@ -131,7 +131,7 @@ struct UnityAudioEffectState | |||||
UnityAudioAmbisonicData* ambisonicData; | UnityAudioAmbisonicData* ambisonicData; | ||||
template<typename T> | |||||
template <typename T> | |||||
inline T* getEffectData() const | inline T* getEffectData() const | ||||
{ | { | ||||
jassert (effectData != nullptr); | jassert (effectData != nullptr); | ||||
@@ -23,9 +23,10 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#if JucePlugin_Build_Unity | #if JucePlugin_Build_Unity | ||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#include "../utility/juce_IncludeModuleHeaders.h" | #include "../utility/juce_IncludeModuleHeaders.h" | ||||
#include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | #include <juce_audio_processors/format_types/juce_LegacyAudioParameter.cpp> | ||||
@@ -205,9 +205,9 @@ struct SharedMessageThread : public Thread | |||||
initialiseJuce_GUI(); | initialiseJuce_GUI(); | ||||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | ||||
initialised = true; | |||||
XWindowSystem::getInstance(); | XWindowSystem::getInstance(); | ||||
initialised = true; | |||||
while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) | while ((! threadShouldExit()) && MessageManager::getInstance()->runDispatchLoopUntil (250)) | ||||
{} | {} | ||||
@@ -246,7 +246,6 @@ struct AbletonLiveHostSpecific | |||||
class JuceVSTWrapper : public AudioProcessorListener, | class JuceVSTWrapper : public AudioProcessorListener, | ||||
public AudioPlayHead, | public AudioPlayHead, | ||||
private Timer, | private Timer, | ||||
private AsyncUpdater, | |||||
private AudioProcessorParameter::Listener | private AudioProcessorParameter::Listener | ||||
{ | { | ||||
private: | private: | ||||
@@ -791,19 +790,9 @@ public: | |||||
void parameterGestureChanged (int, bool) override {} | 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 | bool getPinProperties (Vst2::VstPinInfo& properties, bool direction, int index) const | ||||
@@ -1031,21 +1020,17 @@ public: | |||||
: wrapper (w) | : wrapper (w) | ||||
{ | { | ||||
editor.setOpaque (true); | 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); | addAndMakeVisible (editor); | ||||
auto editorBounds = getSizeToContainChild(); | |||||
setSize (editorBounds.getWidth(), editorBounds.getHeight()); | |||||
#if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
if (! getHostType().isReceptor()) | if (! getHostType().isReceptor()) | ||||
addMouseListener (this, true); | addMouseListener (this, true); | ||||
#endif | #endif | ||||
setOpaque (true); | |||||
ignoreUnused (fakeMouseGenerator); | ignoreUnused (fakeMouseGenerator); | ||||
} | } | ||||
@@ -1055,42 +1040,35 @@ public: | |||||
// have been transferred to another parent which takes over ownership. | // 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) | 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) | void attachToHost (VstOpCodeArguments args) | ||||
{ | { | ||||
setOpaque (true); | |||||
setVisible (false); | setVisible (false); | ||||
#if JUCE_WINDOWS | |||||
#if JUCE_WINDOWS || JUCE_LINUX | |||||
addToDesktop (0, args.ptr); | 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(); | checkHostWindowScaleFactor(); | ||||
startTimer (500); | startTimer (500); | ||||
#endif | #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); | hostWindow = attachComponentToWindowRefVST (this, args.ptr, wrapper.useNSView); | ||||
#endif | #endif | ||||
@@ -1101,15 +1079,10 @@ public: | |||||
{ | { | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
if (hostWindow != nullptr) | if (hostWindow != nullptr) | ||||
{ | |||||
detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView); | detachComponentFromWindowRefVST (this, hostWindow, wrapper.useNSView); | ||||
hostWindow = nullptr; | |||||
} | |||||
#endif | #endif | ||||
#if JUCE_LINUX | |||||
hostWindow = {}; | hostWindow = {}; | ||||
#endif | |||||
} | } | ||||
void checkVisibility() | void checkVisibility() | ||||
@@ -1127,23 +1100,21 @@ public: | |||||
void resized() override | 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 | #if JUCE_MAC && ! JUCE_64BIT | ||||
@@ -1154,59 +1125,63 @@ public: | |||||
void parentSizeChanged() override | void parentSizeChanged() override | ||||
{ | { | ||||
updateWindowSize (true); | |||||
updateWindowSize(); | |||||
} | } | ||||
void childBoundsChanged (Component*) override | void childBoundsChanged (Component*) override | ||||
{ | { | ||||
updateWindowSize (false); | |||||
if (resizingChild) | |||||
return; | |||||
auto newBounds = getSizeToContainChild(); | |||||
if (newBounds != lastBounds) | |||||
{ | |||||
updateWindowSize(); | |||||
lastBounds = newBounds; | |||||
} | |||||
} | } | ||||
juce::Rectangle<int> getSizeToContainChild() | juce::Rectangle<int> getSizeToContainChild() | ||||
{ | { | ||||
if (auto* ed = getEditorComp()) | |||||
return getLocalArea (ed, ed->getLocalBounds()); | |||||
if (auto* pluginEditor = getEditorComp()) | |||||
return getLocalArea (pluginEditor, pluginEditor->getLocalBounds()); | |||||
return {}; | 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(), | 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 | #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()) | 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, | sizeWasSuccessful = (host (wrapper.getAEffect(), Vst2::hostOpcodeWindowSize, | ||||
newWidth, newHeight, nullptr, 0) != 0); | newWidth, newHeight, nullptr, 0) != 0); | ||||
} | } | ||||
} | } | ||||
// some hosts don't support the sizeWindow call, so do it manually.. | |||||
if (! sizeWasSuccessful) | if (! sizeWasSuccessful) | ||||
{ | { | ||||
// some hosts don't support the sizeWindow call, so do it manually.. | |||||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView); | setNativeHostWindowSizeVST (hostWindow, this, newWidth, newHeight, wrapper.useNSView); | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
// (Currently, all linux hosts support sizeWindow, so this should never need to happen) | // (Currently, all linux hosts support sizeWindow, so this should never need to happen) | ||||
setSize (newWidth, newHeight); | setSize (newWidth, newHeight); | ||||
#else | #else | ||||
int dw = 0; | int dw = 0; | ||||
int dh = 0; | int dh = 0; | ||||
@@ -1286,12 +1261,6 @@ public: | |||||
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER); | ||||
#endif | #endif | ||||
} | } | ||||
if (auto* peer = getPeer()) | |||||
{ | |||||
peer->handleMovedOrResized(); | |||||
repaint(); | |||||
} | |||||
} | } | ||||
void setContentScaleFactor (float scale) | void setContentScaleFactor (float scale) | ||||
@@ -1300,27 +1269,22 @@ public: | |||||
{ | { | ||||
editorScaleFactor = scale; | 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 | #if JUCE_WINDOWS | ||||
void mouseDown (const MouseEvent&) override | void mouseDown (const MouseEvent&) override | ||||
@@ -1336,6 +1300,21 @@ public: | |||||
if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) | if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) | ||||
SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); | 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 | #endif | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -1347,6 +1326,7 @@ public: | |||||
} | } | ||||
#endif | #endif | ||||
private: | |||||
//============================================================================== | //============================================================================== | ||||
static Vst2::VstEditorBounds convertToHostBounds (const Vst2::VstEditorBounds& rect) | static Vst2::VstEditorBounds convertToHostBounds (const Vst2::VstEditorBounds& rect) | ||||
{ | { | ||||
@@ -1364,29 +1344,55 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
JuceVSTWrapper& wrapper; | JuceVSTWrapper& wrapper; | ||||
FakeMouseMoveGenerator fakeMouseGenerator; | FakeMouseMoveGenerator fakeMouseGenerator; | ||||
bool isInSizeWindow = false; | |||||
bool shouldResizeEditor = true; | |||||
bool resizingChild = false, resizingParent = false; | |||||
float editorScaleFactor = 1.0f; | 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(); | ::Display* display = XWindowSystem::getInstance()->getDisplay(); | ||||
Window hostWindow = {}; | |||||
#elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
HWND hostWindow = {}; | |||||
using HostWindowType = HWND; | |||||
WindowsHooks hooks; | WindowsHooks hooks; | ||||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||||
juce::Rectangle<int> lastBounds; | |||||
#endif | |||||
#else | |||||
using HostWindowType = void*; | |||||
#endif | #endif | ||||
HostWindowType hostWindow = {}; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorCompWrapper) | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
private: | 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); } | static JuceVSTWrapper* getWrapper (Vst2::VstEffectInterface* v) noexcept { return static_cast<JuceVSTWrapper*> (v->effectPointer); } | ||||
bool isProcessLevelOffline() | bool isProcessLevelOffline() | ||||
@@ -1620,9 +1626,9 @@ private: | |||||
if (editorComp != nullptr) | 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; | return 0; | ||||
@@ -2081,7 +2087,7 @@ private: | |||||
juce::MemoryBlock chunkMemory; | juce::MemoryBlock chunkMemory; | ||||
uint32 chunkMemoryTime = 0; | uint32 chunkMemoryTime = 0; | ||||
std::unique_ptr<EditorCompWrapper> editorComp; | std::unique_ptr<EditorCompWrapper> editorComp; | ||||
Vst2::VstEditorBounds editorBounds; | |||||
Vst2::VstEditorBounds editorRect; | |||||
MidiBuffer midiEvents; | MidiBuffer midiEvents; | ||||
VSTMidiEventList outgoingEvents; | VSTMidiEventList outgoingEvents; | ||||
@@ -2110,6 +2116,8 @@ private: | |||||
ThreadLocalValue<bool> inParameterChangedCallback; | ThreadLocalValue<bool> inParameterChangedCallback; | ||||
HostChangeUpdater hostChangeUpdater { *this }; | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) | ||||
}; | }; | ||||
@@ -228,9 +228,26 @@ public: | |||||
return getParamForVSTParamID (bypassParamID); | return getParamForVSTParamID (bypassParamID); | ||||
} | } | ||||
AudioProcessorParameter* getProgramParameter() const noexcept | |||||
{ | |||||
return getParamForVSTParamID (JuceAudioProcessor::paramPreset); | |||||
} | |||||
static Vst::UnitID getUnitID (const AudioProcessorParameterGroup* group) | 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(); } | int getNumParameters() const noexcept { return vstParamIDs.size(); } | ||||
@@ -260,6 +277,21 @@ private: | |||||
{ | { | ||||
parameterGroups = audioProcessor->getParameterTree().getSubgroups (true); | 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 | #if JUCE_FORCE_USE_LEGACY_PARAM_IDS | ||||
const bool forceLegacyParamIDs = true; | const bool forceLegacyParamIDs = true; | ||||
#else | #else | ||||
@@ -306,6 +338,20 @@ private: | |||||
vstParamIDs.add (vstParamID); | vstParamIDs.add (vstParamID); | ||||
paramMap.set (static_cast<int32> (vstParamID), juceParam); | 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) | Vst::ParamID generateVSTParamIDForParam (AudioProcessorParameter* param) | ||||
@@ -333,7 +379,7 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
LegacyAudioParametersWrapper juceParameters; | LegacyAudioParametersWrapper juceParameters; | ||||
HashMap<int32, AudioProcessorParameter*> paramMap; | HashMap<int32, AudioProcessorParameter*> paramMap; | ||||
std::unique_ptr<AudioProcessorParameter> ownedBypassParameter; | |||||
std::unique_ptr<AudioProcessorParameter> ownedBypassParameter, ownedProgramParameter; | |||||
Array<const AudioProcessorParameterGroup*> parameterGroups; | Array<const AudioProcessorParameterGroup*> parameterGroups; | ||||
JuceAudioProcessor() = delete; | JuceAudioProcessor() = delete; | ||||
@@ -349,8 +395,7 @@ class JuceVST3EditController : public Vst::EditController, | |||||
public Vst::IMidiMapping, | public Vst::IMidiMapping, | ||||
public Vst::IUnitInfo, | public Vst::IUnitInfo, | ||||
public Vst::ChannelContext::IInfoListener, | public Vst::ChannelContext::IInfoListener, | ||||
public AudioProcessorListener, | |||||
private AudioProcessorParameter::Listener | |||||
public AudioProcessorListener | |||||
{ | { | ||||
public: | public: | ||||
JuceVST3EditController (Vst::IHostApplication* host) | JuceVST3EditController (Vst::IHostApplication* host) | ||||
@@ -564,16 +609,20 @@ public: | |||||
bool setNormalized (Vst::ParamValue v) override | 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; | return false; | ||||
@@ -653,17 +702,21 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
tresult PLUGIN_API setComponentState (IBStream* stream) override | tresult PLUGIN_API setComponentState (IBStream* stream) override | ||||
{ | { | ||||
// Cubase and Nuendo need to inform the host of the current parameter values | |||||
if (auto* pluginInstance = getPluginInstance()) | if (auto* pluginInstance = getPluginInstance()) | ||||
{ | { | ||||
for (auto vstParamId : audioProcessor->vstParamIDs) | 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()) | 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()) | if (inParameterChangedCallback.get()) | ||||
{ | { | ||||
@@ -891,8 +944,8 @@ public: | |||||
} | } | ||||
// NB: Cubase has problems if performEdit is called without setParamNormalized | // 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); | paramChanged (audioProcessor->getVSTParamIDForIndex (index), newValue); | ||||
} | } | ||||
void audioProcessorChanged (AudioProcessor*) override | |||||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails& details) override | |||||
{ | { | ||||
int32 flags = 0; | 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()) | 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; | flags |= Vst::kParamValuesChanged; | ||||
} | } | ||||
lastNumPrograms = newNumPrograms; | |||||
} | } | ||||
auto newLatencySamples = pluginInstance->getLatencySamples(); | |||||
auto latencySamples = pluginInstance->getLatencySamples(); | |||||
if (newLatencySamples != lastLatencySamples) | |||||
if (details.latencyChanged && latencySamples != lastLatencySamples) | |||||
{ | { | ||||
flags |= Vst::kLatencyChanged; | flags |= Vst::kLatencyChanged; | ||||
lastLatencySamples = newLatencySamples; | |||||
lastLatencySamples = latencySamples; | |||||
} | } | ||||
} | } | ||||
@@ -944,19 +1001,6 @@ public: | |||||
componentHandler->restartComponent (flags); | 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 | AudioProcessor* getPluginInstance() const noexcept | ||||
{ | { | ||||
@@ -971,7 +1015,7 @@ private: | |||||
friend struct Param; | friend struct Param; | ||||
//============================================================================== | //============================================================================== | ||||
ComSmartPtr<JuceAudioProcessor> audioProcessor; | |||||
VSTComSmartPtr<JuceAudioProcessor> audioProcessor; | |||||
struct MidiController | struct MidiController | ||||
{ | { | ||||
@@ -983,11 +1027,42 @@ private: | |||||
MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; | MidiController parameterToMidiController[(int) numMIDIChannels * (int) Vst::kCountCtrlNumber]; | ||||
Vst::ParamID midiControllerToParameter[numMIDIChannels][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 }, | std::atomic<bool> vst3IsPlaying { false }, | ||||
inSetupProcessing { false }; | inSetupProcessing { false }; | ||||
int lastNumPrograms = 0, lastLatencySamples = 0; | |||||
int lastLatencySamples = 0; | |||||
#if ! JUCE_MAC | #if ! JUCE_MAC | ||||
float lastScaleFactorReceived = 1.0f; | float lastScaleFactorReceived = 1.0f; | ||||
@@ -999,10 +1074,11 @@ private: | |||||
{ | { | ||||
pluginInstance->addListener (this); | 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) | if (! audioProcessor->bypassIsRegularParameter) | ||||
audioProcessor->getBypassParameter()->addListener (this); | |||||
ownedParameterListeners.push_back (std::make_unique<OwnedParameterListener> (*this, | |||||
*audioProcessor->getBypassParameter(), | |||||
audioProcessor->bypassParamID)); | |||||
if (parameters.getParameterCount() <= 0) | if (parameters.getParameterCount() <= 0) | ||||
{ | { | ||||
@@ -1011,6 +1087,10 @@ private: | |||||
for (int i = 0; i < n; ++i) | for (int i = 0; i < n; ++i) | ||||
{ | { | ||||
auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | auto vstParamID = audioProcessor->getVSTParamIDForIndex (i); | ||||
if (vstParamID == JuceAudioProcessor::paramPreset) | |||||
continue; | |||||
auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); | auto* juceParam = audioProcessor->getParamForVSTParamID (vstParamID); | ||||
auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); | auto* parameterGroup = pluginInstance->getParameterTree().getGroupsForParameter (juceParam).getLast(); | ||||
auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); | auto unitID = JuceAudioProcessor::getUnitID (parameterGroup); | ||||
@@ -1019,8 +1099,14 @@ private: | |||||
(vstParamID == audioProcessor->bypassParamID))); | (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)); | parameters.addParameter (new ProgramChangeParameter (*pluginInstance)); | ||||
} | |||||
} | } | ||||
#if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS | #if JUCE_VST3_EMULATE_MIDI_CC_WITH_PARAMETERS | ||||
@@ -1030,7 +1116,7 @@ private: | |||||
initialiseMidiControllerMappings(); | initialiseMidiControllerMappings(); | ||||
#endif | #endif | ||||
audioProcessorChanged (pluginInstance); | |||||
audioProcessorChanged (pluginInstance, ChangeDetails().withParameterInfoChanged (true)); | |||||
} | } | ||||
} | } | ||||
@@ -1138,8 +1224,8 @@ private: | |||||
createContentWrapperComponentIfNeeded(); | createContentWrapperComponentIfNeeded(); | ||||
#if JUCE_WINDOWS || JUCE_LINUX | #if JUCE_WINDOWS || JUCE_LINUX | ||||
component->addToDesktop (0, parent); | |||||
component->setOpaque (true); | component->setOpaque (true); | ||||
component->addToDesktop (0, (void*) systemWindow); | |||||
component->setVisible (true); | component->setVisible (true); | ||||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | ||||
@@ -1206,10 +1292,7 @@ private: | |||||
if (component != nullptr) | 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 JUCE_MAC | ||||
if (cubase10Workaround != nullptr) | if (cubase10Workaround != nullptr) | ||||
@@ -1233,6 +1316,11 @@ private: | |||||
tresult PLUGIN_API getSize (ViewRect* size) override | 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) | if (size != nullptr && component != nullptr) | ||||
{ | { | ||||
auto editorBounds = component->getSizeToContainChild(); | auto editorBounds = component->getSizeToContainChild(); | ||||
@@ -1264,20 +1352,19 @@ private: | |||||
{ | { | ||||
*rectToCheck = convertFromHostBounds (*rectToCheck); | *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) | if (aspectRatio != 0.0) | ||||
{ | { | ||||
@@ -1285,9 +1372,11 @@ private: | |||||
if (getHostType().type == PluginHostType::SteinbergCubase9) | 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; | adjustWidth = true; | ||||
else if (editor->getHeight() == height && editor->getWidth() != width) | |||||
else if (currentEditorBounds.getHeight() == height && currentEditorBounds.getWidth() != width) | |||||
adjustWidth = false; | 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); | *rectToCheck = convertToHostBounds (*rectToCheck); | ||||
} | } | ||||
@@ -1349,16 +1441,7 @@ private: | |||||
owner->lastScaleFactorReceived = editorScaleFactor; | owner->lastScaleFactorReceived = editorScaleFactor; | ||||
if (component != nullptr) | 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; | return kResultTrue; | ||||
@@ -1470,14 +1553,18 @@ private: | |||||
if (resizingChild) | if (resizingChild) | ||||
return; | return; | ||||
auto b = getSizeToContainChild(); | |||||
auto newBounds = getSizeToContainChild(); | |||||
if (lastBounds != b) | |||||
if (newBounds != lastBounds) | |||||
{ | { | ||||
lastBounds = b; | |||||
const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | |||||
resizeHostWindow(); | resizeHostWindow(); | ||||
#if JUCE_LINUX | |||||
if (getHostType().isBitwigStudio()) | |||||
repaint(); | |||||
#endif | |||||
lastBounds = newBounds; | |||||
} | } | ||||
} | } | ||||
@@ -1489,33 +1576,12 @@ private: | |||||
{ | { | ||||
auto newBounds = getLocalBounds(); | 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) | 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) | 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); | const ScopedValueSetter<bool> resizingParentSetter (resizingParent, true); | ||||
owner.plugFrame->resizeView (&owner, &newSize); | owner.plugFrame->resizeView (&owner, &newSize); | ||||
} | } | ||||
auto host = getHostType(); | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
if (host.isWavelab() || host.isReaper()) | if (host.isWavelab() || host.isReaper()) | ||||
#else | #else | ||||
if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio()) | if (host.isWavelab() || host.isAbletonLive() || host.isBitwigStudio()) | ||||
#endif | #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; | ScopedJuceInitialiser_GUI libraryInitialiser; | ||||
ComSmartPtr<JuceVST3EditController> owner; | |||||
VSTComSmartPtr<JuceVST3EditController> owner; | |||||
AudioProcessor& pluginInstance; | AudioProcessor& pluginInstance; | ||||
std::unique_ptr<ContentWrapperComponent> component; | std::unique_ptr<ContentWrapperComponent> component; | ||||
@@ -2555,20 +2635,11 @@ public: | |||||
{ | { | ||||
auto vstParamID = paramQueue->getParameterId(); | 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 | #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); | addParameterChangeToMidiBuffer (offsetSamples, vstParamID, value); | ||||
#endif | |||||
else | else | ||||
#endif | |||||
{ | { | ||||
auto floatValue = static_cast<float> (value); | auto floatValue = static_cast<float> (value); | ||||
@@ -2910,9 +2981,9 @@ private: | |||||
std::atomic<int> refCount { 1 }; | std::atomic<int> refCount { 1 }; | ||||
AudioProcessor* pluginInstance; | 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, | 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 }; | std::atomic<int> refCount { 1 }; | ||||
const PFactoryInfo factoryInfo; | const PFactoryInfo factoryInfo; | ||||
ComSmartPtr<Vst::IHostApplication> host; | |||||
VSTComSmartPtr<Vst::IHostApplication> host; | |||||
//============================================================================== | //============================================================================== | ||||
struct ClassEntry | struct ClassEntry | ||||
@@ -3268,7 +3339,7 @@ private: | |||||
std::vector<std::unique_ptr<ClassEntry>> classes; | std::vector<std::unique_ptr<ClassEntry>> classes; | ||||
//============================================================================== | //============================================================================== | ||||
template<class PClassInfoType> | |||||
template <class PClassInfoType> | |||||
tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) | tresult PLUGIN_API getPClassInfo (Steinberg::int32 index, PClassInfoType* info) | ||||
{ | { | ||||
if (info != nullptr) | if (info != nullptr) | ||||
@@ -35,7 +35,7 @@ | |||||
ID: juce_audio_plugin_client | ID: juce_audio_plugin_client | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE audio plugin wrapper classes | name: JUCE audio plugin wrapper classes | ||||
description: Classes for building VST, VST3, AudioUnit, AAX and RTAS plugins. | description: Classes for building VST, VST3, AudioUnit, AAX and RTAS plugins. | ||||
website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
@@ -23,6 +23,8 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#include <juce_core/system/juce_TargetPlatform.h> | |||||
#if JucePlugin_Build_AU | #if JucePlugin_Build_AU | ||||
#include <juce_core/system/juce_CompilerWarnings.h> | #include <juce_core/system/juce_CompilerWarnings.h> | ||||
@@ -44,7 +46,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wparentheses", | |||||
"-Wnullable-to-nonnull-conversion", | "-Wnullable-to-nonnull-conversion", | ||||
"-Wignored-qualifiers", | "-Wignored-qualifiers", | ||||
"-Wfour-char-constants", | "-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 | // 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 | // 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 JucePlugin_Build_Standalone | ||||
#if ! JUCE_MODULE_AVAILABLE_juce_audio_utils | #if ! JUCE_MODULE_AVAILABLE_juce_audio_utils | ||||
@@ -353,11 +353,22 @@ struct AudioUnitHelpers | |||||
auto defaultInputs = processor.getChannelCountOfBus (true, 0); | auto defaultInputs = processor.getChannelCountOfBus (true, 0); | ||||
auto defaultOutputs = processor.getChannelCountOfBus (false, 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 | // add the current configuration | ||||
if (defaultInputs != 0 || defaultOutputs != 0) | 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) | for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | ||||
{ | { | ||||
@@ -375,19 +386,16 @@ struct AudioUnitHelpers | |||||
if (! isNumberOfChannelsSupported (outBus, outChanNum, outLayout)) | if (! isNumberOfChannelsSupported (outBus, outChanNum, outLayout)) | ||||
continue; | 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; | 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; | hasInOutMismatch = true; | ||||
break; | break; | ||||
@@ -398,9 +406,10 @@ struct AudioUnitHelpers | |||||
for (auto inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | 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; | hasUnsupportedInput = true; | ||||
break; | break; | ||||
@@ -409,25 +418,23 @@ struct AudioUnitHelpers | |||||
for (auto outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | 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; | hasUnsupportedOutput = true; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
for (auto supported : supportedChannels) | |||||
for (const auto& supported : supportedChannels) | |||||
{ | { | ||||
auto numInputs = (supported >> 16) & 0xffff; | |||||
auto numOutputs = (supported >> 0) & 0xffff; | |||||
AUChannelInfo info; | AUChannelInfo info; | ||||
// see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | // 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) | if (info.inChannels == -2 && info.outChannels == -2) | ||||
info.inChannels = -1; | info.inChannels = -1; | ||||
@@ -25,6 +25,8 @@ | |||||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | ||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
#include <AudioUnit/AUCocoaUIView.h> | #include <AudioUnit/AUCocoaUIView.h> | ||||
#include <CoreAudioKit/AUGenericView.h> | #include <CoreAudioKit/AUGenericView.h> | ||||
@@ -247,7 +249,8 @@ namespace AudioUnitFormatHelpers | |||||
if (Handle h = Get1IndResource (thngType, i)) | if (Handle h = Get1IndResource (thngType, i)) | ||||
{ | { | ||||
HLock (h); | HLock (h); | ||||
const uint32* const types = (const uint32*) *h; | |||||
uint32 types[3]; | |||||
std::memcpy (types, *h, sizeof (types)); | |||||
if (types[0] == kAudioUnitType_MusicDevice | if (types[0] == kAudioUnitType_MusicDevice | ||||
|| types[0] == kAudioUnitType_MusicEffect | || 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) | zerostruct (stream); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | ||||
stream.mSampleRate = sampleRate; | stream.mSampleRate = sampleRate; | ||||
stream.mFormatID = kAudioFormatLinearPCM; | stream.mFormatID = kAudioFormatLinearPCM; | ||||
stream.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian; | |||||
stream.mFormatFlags = (int) kAudioFormatFlagsNativeFloatPacked | (int) kAudioFormatFlagIsNonInterleaved | (int) kAudioFormatFlagsNativeEndian; | |||||
stream.mFramesPerPacket = 1; | stream.mFramesPerPacket = 1; | ||||
stream.mBytesPerPacket = 4; | stream.mBytesPerPacket = 4; | ||||
stream.mBytesPerFrame = 4; | stream.mBytesPerFrame = 4; | ||||
@@ -1823,12 +1826,12 @@ private: | |||||
default: | default: | ||||
if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) | if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_ParameterList) | ||||
{ | { | ||||
updateHostDisplay(); | |||||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withParameterInfoChanged (true)); | |||||
} | } | ||||
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) | else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_PresentPreset) | ||||
{ | { | ||||
sendAllParametersChangedEvents(); | sendAllParametersChangedEvents(); | ||||
updateHostDisplay(); | |||||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true)); | |||||
} | } | ||||
else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) | else if (event.mArgument.mProperty.mPropertyID == kAudioUnitProperty_Latency) | ||||
{ | { | ||||
@@ -2912,4 +2915,6 @@ FileSearchPath AudioUnitPluginFormat::getDefaultLocationsToSearch() | |||||
} // namespace juce | } // namespace juce | ||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
#endif | #endif |
@@ -374,7 +374,7 @@ public: | |||||
destData.setSize ((size_t) numParameters * sizeof (float)); | destData.setSize ((size_t) numParameters * sizeof (float)); | ||||
destData.fillWith (0); | destData.fillWith (0); | ||||
auto* p = (float*) ((char*) destData.getData()); | |||||
auto* p = unalignedPointerCast<float*> (destData.getData()); | |||||
for (int i = 0; i < numParameters; ++i) | for (int i = 0; i < numParameters; ++i) | ||||
if (auto* param = getParameters()[i]) | if (auto* param = getParameters()[i]) | ||||
@@ -68,12 +68,14 @@ static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexc | |||||
#endif | #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 | // 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) | 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) | 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 | #if JUCE_WINDOWS | ||||
@@ -355,24 +352,24 @@ static AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak | |||||
//============================================================================== | //============================================================================== | ||||
template <class ObjectType> | template <class ObjectType> | ||||
class ComSmartPtr | |||||
class VSTComSmartPtr | |||||
{ | { | ||||
public: | 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; } | operator ObjectType*() const noexcept { return source; } | ||||
ObjectType* get() const noexcept { return source; } | ObjectType* get() const noexcept { return source; } | ||||
ObjectType& operator*() const noexcept { return *source; } | ObjectType& operator*() 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); | std::swap (p.source, source); | ||||
return *this; | return *this; | ||||
} | } | ||||
@@ -31,10 +31,6 @@ | |||||
namespace juce | namespace juce | ||||
{ | { | ||||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||||
extern void setThreadDPIAwarenessForWindow (HWND); | |||||
#endif | |||||
using namespace Steinberg; | 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; | tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override; | ||||
#if ! JUCE_MODAL_LOOPS_PERMITTED | #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 | #endif | ||||
private: | private: | ||||
@@ -382,7 +378,7 @@ struct VST3HostContext : public Vst::IComponentHandler, // From VST V3.0.0 | |||||
struct ItemAndTarget | struct ItemAndTarget | ||||
{ | { | ||||
Item item; | Item item; | ||||
ComSmartPtr<IContextMenuTarget> target; | |||||
VSTComSmartPtr<IContextMenuTarget> target; | |||||
}; | }; | ||||
Array<ItemAndTarget> items; | 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)) | 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); | messageQueue.add (m); | ||||
m->addRef(); | m->addRef(); | ||||
*obj = m; | *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)) | 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(); | l->addRef(); | ||||
*obj = l; | *obj = l; | ||||
return kResultOk; | return kResultOk; | ||||
@@ -541,14 +537,14 @@ private: | |||||
var value; | var value; | ||||
private: | private: | ||||
ComSmartPtr<Vst::IAttributeList> attributeList; | |||||
VSTComSmartPtr<Vst::IAttributeList> attributeList; | |||||
String messageId; | String messageId; | ||||
Atomic<int> refCount; | Atomic<int> refCount; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) | ||||
}; | }; | ||||
Array<ComSmartPtr<Message>, CriticalSection> messageQueue; | |||||
Array<VSTComSmartPtr<Message>, CriticalSection> messageQueue; | |||||
//============================================================================== | //============================================================================== | ||||
struct AttributeList : public Vst::IAttributeList | 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> | template <typename Type> | ||||
@@ -687,7 +683,7 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttributeList) | ||||
}; | }; | ||||
ComSmartPtr<AttributeList> attributeList; | |||||
VSTComSmartPtr<AttributeList> attributeList; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3HostContext) | ||||
}; | }; | ||||
@@ -731,8 +727,8 @@ struct DescriptionFactory | |||||
std::unique_ptr<PClassInfoW> infoW; | std::unique_ptr<PClassInfoW> infoW; | ||||
{ | { | ||||
ComSmartPtr<IPluginFactory2> pf2; | |||||
ComSmartPtr<IPluginFactory3> pf3; | |||||
VSTComSmartPtr<IPluginFactory2> pf2; | |||||
VSTComSmartPtr<IPluginFactory3> pf3; | |||||
if (pf2.loadFrom (factory)) | if (pf2.loadFrom (factory)) | ||||
{ | { | ||||
@@ -752,7 +748,7 @@ struct DescriptionFactory | |||||
PluginDescription desc; | PluginDescription desc; | ||||
{ | { | ||||
ComSmartPtr<Vst::IComponent> component; | |||||
VSTComSmartPtr<Vst::IComponent> component; | |||||
if (component.loadFrom (factory, info.cid)) | if (component.loadFrom (factory, info.cid)) | ||||
{ | { | ||||
@@ -790,8 +786,8 @@ struct DescriptionFactory | |||||
virtual Result performOnDescription (PluginDescription&) = 0; | virtual Result performOnDescription (PluginDescription&) = 0; | ||||
private: | private: | ||||
ComSmartPtr<VST3HostContext> vst3HostContext; | |||||
ComSmartPtr<IPluginFactory> factory; | |||||
VSTComSmartPtr<VST3HostContext> vst3HostContext; | |||||
VSTComSmartPtr<IPluginFactory> factory; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DescriptionFactory) | 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 | /** 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() | IPluginFactory* JUCE_CALLTYPE getPluginFactory() | ||||
{ | { | ||||
@@ -1102,8 +1098,8 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
bool open (const PluginDescription& description) | 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) | if (pluginFactory != nullptr) | ||||
{ | { | ||||
@@ -1140,9 +1136,7 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
struct VST3PluginWindow : public AudioProcessorEditor, | struct VST3PluginWindow : public AudioProcessorEditor, | ||||
public ComponentMovementWatcher, | public ComponentMovementWatcher, | ||||
#if JUCE_WINDOWS || JUCE_LINUX | |||||
public ComponentPeer::ScaleFactorListener, | public ComponentPeer::ScaleFactorListener, | ||||
#endif | |||||
public IPlugFrame | public IPlugFrame | ||||
{ | { | ||||
VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) | VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) | ||||
@@ -1155,26 +1149,20 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
setVisible (true); | setVisible (true); | ||||
warnOnFailure (view->setFrame (this)); | warnOnFailure (view->setFrame (this)); | ||||
#if ! JUCE_MAC | |||||
view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); | view->queryInterface (Steinberg::IPlugViewContentScaleSupport::iid, (void**) &scaleInterface); | ||||
#endif | |||||
resizeToFit(); | resizeToFit(); | ||||
} | } | ||||
~VST3PluginWindow() override | ~VST3PluginWindow() override | ||||
{ | { | ||||
#if ! JUCE_MAC | |||||
if (scaleInterface != nullptr) | if (scaleInterface != nullptr) | ||||
scaleInterface->release(); | scaleInterface->release(); | ||||
removeScaleFactorListeners(); | |||||
removeScaleFactorListener(); | |||||
#if JUCE_LINUX | #if JUCE_LINUX | ||||
embeddedComponent.removeClient(); | embeddedComponent.removeClient(); | ||||
#endif | #endif | ||||
#endif | |||||
warnOnFailure (view->removed()); | warnOnFailure (view->removed()); | ||||
warnOnFailure (view->setFrame (nullptr)); | warnOnFailure (view->setFrame (nullptr)); | ||||
@@ -1183,6 +1171,8 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
embeddedComponent.setView (nullptr); | embeddedComponent.setView (nullptr); | ||||
#elif JUCE_WINDOWS | |||||
embeddedComponent.setHWND (nullptr); | |||||
#endif | #endif | ||||
view = nullptr; | view = nullptr; | ||||
@@ -1323,79 +1313,50 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
//============================================================================== | //============================================================================== | ||||
void componentPeerChanged() override | 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 | void componentMovedOrResized (bool, bool wasResized) override | ||||
{ | { | ||||
if (recursiveResize) | |||||
if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) | |||||
return; | 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; | using ComponentMovementWatcher::componentMovedOrResized; | ||||
void componentVisibilityChanged() override | void componentVisibilityChanged() override | ||||
@@ -1407,13 +1368,11 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
componentMovedOrResized (true, true); | componentMovedOrResized (true, true); | ||||
} | } | ||||
using ComponentMovementWatcher::componentVisibilityChanged; | using ComponentMovementWatcher::componentVisibilityChanged; | ||||
#if JUCE_WINDOWS || JUCE_LINUX | |||||
void nativeScaleFactorChanged (double newScaleFactor) override | void nativeScaleFactorChanged (double newScaleFactor) override | ||||
{ | { | ||||
if (pluginHandle == 0 || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||||
if (pluginHandle == HandleFormat{} || approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||||
return; | return; | ||||
nativeScaleFactor = (float) newScaleFactor; | nativeScaleFactor = (float) newScaleFactor; | ||||
@@ -1421,7 +1380,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
if (scaleInterface != nullptr) | if (scaleInterface != nullptr) | ||||
scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | ||||
} | } | ||||
#endif | |||||
void resizeToFit() | void resizeToFit() | ||||
{ | { | ||||
@@ -1458,73 +1416,48 @@ private: | |||||
void attachPluginWindow() | 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()); | embeddedComponent.setBounds (getLocalBounds()); | ||||
addAndMakeVisible (embeddedComponent); | 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 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 | #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) | 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 }; | Atomic<int> refCount { 1 }; | ||||
ComSmartPtr<IPlugView> view; | |||||
VSTComSmartPtr<IPlugView> view; | |||||
#if JUCE_WINDOWS | #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; | using HandleFormat = HWND; | ||||
#elif JUCE_MAC | #elif JUCE_MAC | ||||
AutoResizingNSViewComponentWithParent embeddedComponent; | AutoResizingNSViewComponentWithParent embeddedComponent; | ||||
@@ -1538,14 +1471,11 @@ private: | |||||
#endif | #endif | ||||
HandleFormat pluginHandle = {}; | HandleFormat pluginHandle = {}; | ||||
bool recursiveResize = false; | |||||
bool recursiveResize = false, hasDoneInitialResize = false; | |||||
#if ! JUCE_MAC | |||||
ComponentPeer* currentPeer = nullptr; | |||||
Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | ||||
#endif | |||||
float nativeScaleFactor = 1.0f; | float nativeScaleFactor = 1.0f; | ||||
bool hasDoneInitialResize = false; | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | ||||
@@ -1569,7 +1499,7 @@ struct VST3ComponentHolder | |||||
// transfers ownership to the plugin instance! | // transfers ownership to the plugin instance! | ||||
AudioPluginInstance* createPluginInstance(); | AudioPluginInstance* createPluginInstance(); | ||||
bool fetchController (ComSmartPtr<Vst::IEditController>& editController) | |||||
bool fetchController (VSTComSmartPtr<Vst::IEditController>& editController) | |||||
{ | { | ||||
if (! isComponentInitialised && ! initialise()) | if (! isComponentInitialised && ! initialise()) | ||||
return false; | return false; | ||||
@@ -1618,8 +1548,8 @@ struct VST3ComponentHolder | |||||
ignoreUnused (success); | ignoreUnused (success); | ||||
jassert (success); | jassert (success); | ||||
ComSmartPtr<IPluginFactory2> pf2; | |||||
ComSmartPtr<IPluginFactory3> pf3; | |||||
VSTComSmartPtr<IPluginFactory2> pf2; | |||||
VSTComSmartPtr<IPluginFactory3> pf3; | |||||
std::unique_ptr<PClassInfo2> info2; | std::unique_ptr<PClassInfo2> info2; | ||||
std::unique_ptr<PClassInfoW> infoW; | std::unique_ptr<PClassInfoW> infoW; | ||||
@@ -1683,7 +1613,7 @@ struct VST3ComponentHolder | |||||
JUCE_ASSERT_MESSAGE_THREAD | JUCE_ASSERT_MESSAGE_THREAD | ||||
#endif | #endif | ||||
factory = ComSmartPtr<IPluginFactory> (module->getPluginFactory()); | |||||
factory = VSTComSmartPtr<IPluginFactory> (module->getPluginFactory()); | |||||
int classIdx; | int classIdx; | ||||
if ((classIdx = getClassIndex (module->getName())) < 0) | if ((classIdx = getClassIndex (module->getName())) < 0) | ||||
@@ -1734,9 +1664,9 @@ struct VST3ComponentHolder | |||||
//============================================================================== | //============================================================================== | ||||
VST3ModuleHandle::Ptr module; | 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; | FUID cidOfComponent; | ||||
bool isComponentInitialised = false; | bool isComponentInitialised = false; | ||||
@@ -2292,7 +2222,7 @@ public: | |||||
{ | { | ||||
if (trackInfoListener != nullptr) | if (trackInfoListener != nullptr) | ||||
{ | { | ||||
ComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||||
VSTComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||||
trackInfoListener->setChannelContextInfos (l); | trackInfoListener->setChannelContextInfos (l); | ||||
} | } | ||||
} | } | ||||
@@ -2427,7 +2357,7 @@ public: | |||||
if (getActiveEditor() != nullptr) | if (getActiveEditor() != nullptr) | ||||
return true; | return true; | ||||
ComSmartPtr<IPlugView> view (tryCreatingView(), false); | |||||
VSTComSmartPtr<IPlugView> view (tryCreatingView(), false); | |||||
return view != nullptr; | return view != nullptr; | ||||
} | } | ||||
@@ -2509,7 +2439,7 @@ public: | |||||
bool setStateFromPresetFile (const MemoryBlock& rawData) | bool setStateFromPresetFile (const MemoryBlock& rawData) | ||||
{ | { | ||||
MemoryBlock rawDataCopy (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) | if (memoryStream == nullptr || holder->component == nullptr) | ||||
return false; | return false; | ||||
@@ -2676,17 +2606,17 @@ private: | |||||
std::unique_ptr<PClassInfoW> infoW; | std::unique_ptr<PClassInfoW> infoW; | ||||
// Rudimentary interfaces: | // 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, | /** The number of IO buses MUST match that of the plugin, | ||||
even if there aren't enough channels to process, | even if there aren't enough channels to process, | ||||
@@ -2701,7 +2631,7 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
template <typename Type> | 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) | 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)) | if (auto* state = head.getChildByName (identifier)) | ||||
{ | { | ||||
@@ -2723,7 +2653,7 @@ private: | |||||
if (mem.fromBase64Encoding (state->getAllSubText())) | 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()); | stream->setSize ((TSize) mem.getSize()); | ||||
mem.copyTo (stream->getData(), 0, mem.getSize()); | mem.copyTo (stream->getData(), 0, mem.getSize()); | ||||
return stream; | return stream; | ||||
@@ -2733,8 +2663,8 @@ private: | |||||
return nullptr; | 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()! | Vst::ProcessContext timingInfo; //< Only use this in processBlock()! | ||||
bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; | bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; | ||||
VST3Parameter* bypassParam = nullptr; | VST3Parameter* bypassParam = nullptr; | ||||
@@ -2872,10 +2802,10 @@ private: | |||||
setRateAndBufferSizeDetails (setup.sampleRate, (int) setup.maxSamplesPerBlock); | 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; | AudioProcessor::BusesProperties busProperties; | ||||
ComSmartPtr<Vst::IAudioProcessor> processor; | |||||
VSTComSmartPtr<Vst::IAudioProcessor> processor; | |||||
processor.loadFrom (component.get()); | processor.loadFrom (component.get()); | ||||
for (int dirIdx = 0; dirIdx < 2; ++dirIdx) | for (int dirIdx = 0; dirIdx < 2; ++dirIdx) | ||||
@@ -3179,7 +3109,9 @@ tresult VST3HostContext::restartComponent (Steinberg::int32 flags) | |||||
if (plugin->processor != nullptr) | if (plugin->processor != nullptr) | ||||
plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); | plugin->setLatencySamples (jmax (0, (int) plugin->processor->getLatencySamples())); | ||||
plugin->updateHostDisplay(); | |||||
plugin->updateHostDisplay (AudioProcessorListener::ChangeDetails().withProgramChanged (true) | |||||
.withParameterInfoChanged (true)); | |||||
return kResultTrue; | 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.. | // Unfortunately, Steinberg's docs explicitly say this should be modal.. | ||||
handleResult (topLevelMenu->showMenu (options)); | handleResult (topLevelMenu->showMenu (options)); | ||||
#else | #else | ||||
topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr<ContextMenu> (this))); | |||||
topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, VSTComSmartPtr<ContextMenu> (this))); | |||||
#endif | #endif | ||||
return kResultOk; | return kResultOk; | ||||
@@ -3314,12 +3246,12 @@ void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& resul | |||||
for every housed plugin. | for every housed plugin. | ||||
*/ | */ | ||||
ComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) | |||||
.getPluginFactory()); | |||||
VSTComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) | |||||
.getPluginFactory()); | |||||
if (pluginFactory != nullptr) | if (pluginFactory != nullptr) | ||||
{ | { | ||||
ComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||||
VSTComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||||
DescriptionLister lister (host, pluginFactory); | DescriptionLister lister (host, pluginFactory); | ||||
lister.findDescriptionsAndPerform (File (fileOrIdentifier)); | lister.findDescriptionsAndPerform (File (fileOrIdentifier)); | ||||
@@ -53,6 +53,7 @@ namespace Vst2 | |||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | JUCE_END_IGNORE_WARNINGS_GCC_LIKE | ||||
JUCE_END_IGNORE_WARNINGS_MSVC | JUCE_END_IGNORE_WARNINGS_MSVC | ||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | ||||
#include "juce_VSTMidiEventList.h" | #include "juce_VSTMidiEventList.h" | ||||
@@ -77,7 +78,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) | |||||
//============================================================================== | //============================================================================== | ||||
namespace juce | namespace juce | ||||
{ | { | ||||
#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | |||||
#if JUCE_WINDOWS | |||||
extern void setThreadDPIAwarenessForWindow (HWND); | extern void setThreadDPIAwarenessForWindow (HWND); | ||||
#endif | #endif | ||||
@@ -376,7 +377,7 @@ private: | |||||
switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); | switchValueType.entries.add (new Entry({ TRANS("Off"), Range ("[0, 0.5[") })); | ||||
switchValueType.entries.add (new Entry({ TRANS("On"), Range ("[0.5, 1]") })); | 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); | if (item->hasTagName ("Param")) parseParam (*item, nullptr, nullptr); | ||||
else if (item->hasTagName ("ValueType")) parseValueType (*item); | else if (item->hasTagName ("ValueType")) parseValueType (*item); | ||||
@@ -430,7 +431,7 @@ private: | |||||
int curEntry = 0; | int curEntry = 0; | ||||
const int numEntries = item.getNumChildElements(); | const int numEntries = item.getNumChildElements(); | ||||
forEachXmlChildElementWithTagName (item, entryXml, "Entry") | |||||
for (auto* entryXml : item.getChildWithTagNameIterator ("Entry")) | |||||
{ | { | ||||
auto entry = new Entry(); | auto entry = new Entry(); | ||||
entry->name = entryXml->getStringAttribute ("name"); | entry->name = entryXml->getStringAttribute ("name"); | ||||
@@ -459,7 +460,7 @@ private: | |||||
templates.add (temp); | templates.add (temp); | ||||
temp->name = item.getStringAttribute ("name"); | temp->name = item.getStringAttribute ("name"); | ||||
forEachXmlChildElement (item, param) | |||||
for (auto* param : item.getChildIterator()) | |||||
parseParam (*param, nullptr, temp); | parseParam (*param, nullptr, temp); | ||||
} | } | ||||
@@ -508,7 +509,7 @@ private: | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
forEachXmlChildElement (item, subItem) | |||||
for (auto* subItem : item.getChildIterator()) | |||||
{ | { | ||||
if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); | if (subItem->hasTagName ("Param")) parseParam (*subItem, group, nullptr); | ||||
else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); | else if (subItem->hasTagName ("Group")) parseGroup (*subItem, group); | ||||
@@ -1596,8 +1597,8 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
void handleAsyncUpdate() override | 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) | 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) | 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; | return false; | ||||
if (fxbSwap (set->numPrograms) > 0) | if (fxbSwap (set->numPrograms) > 0) | ||||
@@ -1779,9 +1780,9 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
if (fxbSwap (set->numPrograms) > 0) | if (fxbSwap (set->numPrograms) > 0) | ||||
setCurrentProgram (oldProg); | 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; | return false; | ||||
if (! restoreProgramSettings (prog)) | if (! restoreProgramSettings (prog)) | ||||
@@ -1901,14 +1902,14 @@ struct VSTPluginInstance : public AudioPluginInstance, | |||||
auto oldProgram = getCurrentProgram(); | auto oldProgram = getCurrentProgram(); | ||||
if (oldProgram >= 0) | if (oldProgram >= 0) | ||||
setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); | |||||
setParamsInProgramBlock (addBytesToPointer (set->programs, oldProgram * progLen)); | |||||
for (int i = 0; i < numPrograms; ++i) | for (int i = 0; i < numPrograms; ++i) | ||||
{ | { | ||||
if (i != oldProgram) | if (i != oldProgram) | ||||
{ | { | ||||
setCurrentProgram (i); | 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); | 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) | for (int i = 0; i < numParameters; ++i) | ||||
if (auto* param = getParameters()[i]) | if (auto* param = getParameters()[i]) | ||||
@@ -2597,7 +2598,7 @@ private: | |||||
{ | { | ||||
changeProgramName (getCurrentProgram(), (const char*) m.getData()); | 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(); | auto numParameters = getParameters().size(); | ||||
for (int i = 0; i < numParameters; ++i) | for (int i = 0; i < numParameters; ++i) | ||||
@@ -2858,9 +2859,7 @@ public: | |||||
#if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
if (pluginHWND != 0) | if (pluginHWND != 0) | ||||
{ | { | ||||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||||
setThreadDPIAwarenessForWindow (pluginHWND); | setThreadDPIAwarenessForWindow (pluginHWND); | ||||
#endif | |||||
MoveWindow (pluginHWND, pos.getX(), pos.getY(), | MoveWindow (pluginHWND, pos.getX(), pos.getY(), | ||||
roundToInt (getWidth() * nativeScaleFactor), | roundToInt (getWidth() * nativeScaleFactor), | ||||
@@ -3123,9 +3122,7 @@ private: | |||||
// very dodgy logic to decide which size is right. | // very dodgy logic to decide which size is right. | ||||
if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) | if (std::abs (rw - w) > 350 || std::abs (rh - h) > 350) | ||||
{ | { | ||||
#if JUCE_WIN_PER_MONITOR_DPI_AWARE | |||||
setThreadDPIAwarenessForWindow (pluginHWND); | setThreadDPIAwarenessForWindow (pluginHWND); | ||||
#endif | |||||
SetWindowPos (pluginHWND, 0, | SetWindowPos (pluginHWND, 0, | ||||
0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | 0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | ||||
@@ -3228,7 +3225,7 @@ private: | |||||
bool willCauseRecursiveResize (int w, int h) | bool willCauseRecursiveResize (int w, int h) | ||||
{ | { | ||||
auto newScreenBounds = Rectangle<int> (w, h).withPosition (getScreenPosition()); | 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) | bool isWindowSizeCorrectForPlugin (int w, int h) | ||||
@@ -3737,6 +3734,7 @@ void VSTPluginFormat::aboutToScanVSTShellPlugin (const PluginDescription&) {} | |||||
} // namespace juce | } // namespace juce | ||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
JUCE_END_IGNORE_WARNINGS_MSVC | JUCE_END_IGNORE_WARNINGS_MSVC | ||||
#endif | #endif |
@@ -81,7 +81,97 @@ static bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||||
#endif | #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 | #if JUCE_IOS | ||||
#define JUCE_IOS_MAC_VIEW UIView | #define JUCE_IOS_MAC_VIEW UIView | ||||
@@ -130,12 +220,11 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||||
} | } | ||||
} | } | ||||
}; | }; | ||||
#endif | #endif | ||||
} // namespace juce | } // namespace juce | ||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations", "-Wcast-align") | |||||
#include "format/juce_AudioPluginFormat.cpp" | #include "format/juce_AudioPluginFormat.cpp" | ||||
#include "format/juce_AudioPluginFormatManager.cpp" | #include "format/juce_AudioPluginFormatManager.cpp" | ||||
#include "format_types/juce_LegacyAudioParameter.cpp" | #include "format_types/juce_LegacyAudioParameter.cpp" | ||||
@@ -35,7 +35,7 @@ | |||||
ID: juce_audio_processors | ID: juce_audio_processors | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE audio processor classes | name: JUCE audio processor classes | ||||
description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | ||||
website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
@@ -412,7 +412,7 @@ void AudioProcessor::setLatencySamples (int newLatency) | |||||
if (latencySamples != newLatency) | if (latencySamples != newLatency) | ||||
{ | { | ||||
latencySamples = newLatency; | latencySamples = newLatency; | ||||
updateHostDisplay(); | |||||
updateHostDisplay (AudioProcessorListener::ChangeDetails().withLatencyChanged (true)); | |||||
} | } | ||||
} | } | ||||
@@ -423,11 +423,11 @@ AudioProcessorListener* AudioProcessor::getListenerLocked (int index) const noex | |||||
return listeners[index]; | return listeners[index]; | ||||
} | } | ||||
void AudioProcessor::updateHostDisplay() | |||||
void AudioProcessor::updateHostDisplay (const AudioProcessorListener::ChangeDetails& details) | |||||
{ | { | ||||
for (int i = listeners.size(); --i >= 0;) | for (int i = listeners.size(); --i >= 0;) | ||||
if (auto l = getListenerLocked (i)) | if (auto l = getListenerLocked (i)) | ||||
l->audioProcessorChanged (this); | |||||
l->audioProcessorChanged (this, details); | |||||
} | } | ||||
void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) | void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) | ||||
@@ -445,6 +445,24 @@ void AudioProcessor::checkForDuplicateParamID (AudioProcessorParameter* param) | |||||
#endif | #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 Array<AudioProcessorParameter*>& AudioProcessor::getParameters() const { return flatParameterList; } | ||||
const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() const { return parameterTree; } | const AudioProcessorParameterGroup& AudioProcessor::getParameterTree() const { return parameterTree; } | ||||
@@ -463,6 +481,7 @@ void AudioProcessor::addParameter (AudioProcessorParameter* param) | |||||
void AudioProcessor::addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup> group) | void AudioProcessor::addParameterGroup (std::unique_ptr<AudioProcessorParameterGroup> group) | ||||
{ | { | ||||
jassert (group != nullptr); | jassert (group != nullptr); | ||||
checkForDuplicateGroupIDs (*group); | |||||
auto oldSize = flatParameterList.size(); | auto oldSize = flatParameterList.size(); | ||||
flatParameterList.addArray (group->getParameters (true)); | flatParameterList.addArray (group->getParameters (true)); | ||||
@@ -483,9 +502,12 @@ void AudioProcessor::setParameterTree (AudioProcessorParameterGroup&& newTree) | |||||
{ | { | ||||
#if JUCE_DEBUG | #if JUCE_DEBUG | ||||
paramIDs.clear(); | paramIDs.clear(); | ||||
groupIDs.clear(); | |||||
#endif | #endif | ||||
parameterTree = std::move (newTree); | parameterTree = std::move (newTree); | ||||
checkForDuplicateGroupIDs (parameterTree); | |||||
flatParameterList = parameterTree.getParameters (true); | flatParameterList = parameterTree.getParameters (true); | ||||
for (int i = 0; i < flatParameterList.size(); ++i) | 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, | It sends a hint to the host that something like the program, number of parameters, | ||||
etc, has changed, and that it should update itself. | etc, has changed, and that it should update itself. | ||||
*/ | */ | ||||
void updateHostDisplay(); | |||||
void updateHostDisplay (const AudioProcessorListener::ChangeDetails& details = {}); | |||||
//============================================================================== | //============================================================================== | ||||
/** Adds a parameter to the AudioProcessor. | /** Adds a parameter to the AudioProcessor. | ||||
@@ -1499,10 +1499,11 @@ private: | |||||
#endif | #endif | ||||
bool textRecursionCheck = false; | bool textRecursionCheck = false; | ||||
std::unordered_set<String> paramIDs; | |||||
std::unordered_set<String> paramIDs, groupIDs; | |||||
#endif | #endif | ||||
void checkForDuplicateParamID (AudioProcessorParameter*); | void checkForDuplicateParamID (AudioProcessorParameter*); | ||||
void checkForDuplicateGroupIDs (const AudioProcessorParameterGroup&); | |||||
AudioProcessorListener* getListenerLocked (int) const noexcept; | AudioProcessorListener* getListenerLocked (int) const noexcept; | ||||
void updateSpeakerFormatStrings(); | void updateSpeakerFormatStrings(); | ||||
@@ -1265,6 +1265,22 @@ void AudioProcessorGraph::prepareToPlay (double sampleRate, int estimatedSamples | |||||
{ | { | ||||
const ScopedLock sl (getCallbackLock()); | const ScopedLock sl (getCallbackLock()); | ||||
setRateAndBufferSizeDetails (sampleRate, estimatedSamplesPerBlock); | 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(); | clearRenderingSequence(); | ||||
@@ -1277,16 +1293,23 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||||
return true; | return true; | ||||
} | } | ||||
void AudioProcessorGraph::releaseResources() | |||||
void AudioProcessorGraph::unprepare() | |||||
{ | { | ||||
const ScopedLock sl (getCallbackLock()); | |||||
cancelPendingUpdate(); | |||||
prepareSettings.valid = false; | |||||
isPrepared = 0; | isPrepared = 0; | ||||
for (auto* n : nodes) | for (auto* n : nodes) | ||||
n->unprepare(); | n->unprepare(); | ||||
} | |||||
void AudioProcessorGraph::releaseResources() | |||||
{ | |||||
const ScopedLock sl (getCallbackLock()); | |||||
cancelPendingUpdate(); | |||||
unprepare(); | |||||
if (renderSequenceFloat != nullptr) | if (renderSequenceFloat != nullptr) | ||||
renderSequenceFloat->releaseBuffers(); | renderSequenceFloat->releaseBuffers(); | ||||
@@ -407,6 +407,24 @@ public: | |||||
void setStateInformation (const void* data, int sizeInBytes) override; | void setStateInformation (const void* data, int sizeInBytes) override; | ||||
private: | 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; | ReferenceCountedArray<Node> nodes; | ||||
NodeID lastNodeID = {}; | NodeID lastNodeID = {}; | ||||
@@ -416,11 +434,14 @@ private: | |||||
std::unique_ptr<RenderSequenceFloat> renderSequenceFloat; | std::unique_ptr<RenderSequenceFloat> renderSequenceFloat; | ||||
std::unique_ptr<RenderSequenceDouble> renderSequenceDouble; | std::unique_ptr<RenderSequenceDouble> renderSequenceDouble; | ||||
PrepareSettings prepareSettings; | |||||
friend class AudioGraphIOProcessor; | friend class AudioGraphIOProcessor; | ||||
std::atomic<bool> isPrepared { false }; | std::atomic<bool> isPrepared { false }; | ||||
void topologyChanged(); | void topologyChanged(); | ||||
void unprepare(); | |||||
void handleAsyncUpdate() override; | void handleAsyncUpdate() override; | ||||
void clearRenderingSequence(); | void clearRenderingSequence(); | ||||
void buildRenderingSequence(); | void buildRenderingSequence(); | ||||
@@ -57,6 +57,28 @@ public: | |||||
int parameterIndex, | int parameterIndex, | ||||
float newValue) = 0; | 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 | /** Called to indicate that something else in the plugin has changed, like its | ||||
program, number of parameters, etc. | program, number of parameters, etc. | ||||
@@ -67,7 +89,7 @@ public: | |||||
to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the | to trigger an AsyncUpdater or ChangeBroadcaster which you can respond to later on the | ||||
message thread. | 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. | /** Indicates that a parameter change gesture has started. | ||||
@@ -73,7 +73,7 @@ private: | |||||
parameterValueHasChanged = 1; | parameterValueHasChanged = 1; | ||||
} | } | ||||
void audioProcessorChanged (AudioProcessor*) override {} | |||||
void audioProcessorChanged (AudioProcessor*, const ChangeDetails&) override {} | |||||
//============================================================================== | //============================================================================== | ||||
void timerCallback() override | void timerCallback() override | ||||
@@ -378,7 +378,7 @@ void KnownPluginList::recreateFromXml (const XmlElement& xml) | |||||
if (xml.hasTagName ("KNOWNPLUGINS")) | if (xml.hasTagName ("KNOWNPLUGINS")) | ||||
{ | { | ||||
forEachXmlChildElement (xml, e) | |||||
for (auto* e : xml.getChildIterator()) | |||||
{ | { | ||||
PluginDescription info; | PluginDescription info; | ||||
@@ -262,7 +262,7 @@ static bool canShowFolderForPlugin (KnownPluginList& list, int index) | |||||
static void showFolderForPlugin (KnownPluginList& list, int index) | static void showFolderForPlugin (KnownPluginList& list, int index) | ||||
{ | { | ||||
if (canShowFolderForPlugin (list, index)) | if (canShowFolderForPlugin (list, index)) | ||||
File (list.getTypes()[index].fileOrIdentifier).getParentDirectory().startAsProcess(); | |||||
File (list.getTypes()[index].fileOrIdentifier).revealToUser(); | |||||
} | } | ||||
void PluginListComponent::removeMissingPlugins() | void PluginListComponent::removeMissingPlugins() | ||||
@@ -162,7 +162,7 @@ private: | |||||
return; | return; | ||||
unnormalisedValue = newValue; | unnormalisedValue = newValue; | ||||
listeners.call ([=] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); | |||||
listeners.call ([this] (Listener& l) { l.parameterChanged (parameter.paramID, unnormalisedValue); }); | |||||
listenersNeedCalling = false; | listenersNeedCalling = false; | ||||
needsUpdate = true; | needsUpdate = true; | ||||
} | } | ||||
@@ -696,10 +696,10 @@ public: | |||||
beginTest ("After construction, the value tree has the expected format"); | beginTest ("After construction, the value tree has the expected format"); | ||||
{ | { | ||||
TestAudioProcessor proc ({ | TestAudioProcessor proc ({ | ||||
std::make_unique<AudioProcessorParameterGroup> ("", "", "", | |||||
std::make_unique<AudioProcessorParameterGroup> ("A", "", "", | |||||
std::make_unique<AudioParameterBool> ("a", "", false), | std::make_unique<AudioParameterBool> ("a", "", false), | ||||
std::make_unique<AudioParameterFloat> ("b", "", NormalisableRange<float>{}, 0.0f)), | 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<AudioParameterInt> ("c", "", 0, 1, 0), | ||||
std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) }); | std::make_unique<AudioParameterChoice> ("d", "", StringArray { "foo", "bar" }, 0)) }); | ||||
@@ -35,7 +35,7 @@ | |||||
ID: juce_audio_utils | ID: juce_audio_utils | ||||
vendor: juce | vendor: juce | ||||
version: 6.0.4 | |||||
version: 6.0.7 | |||||
name: JUCE extra audio utility classes | name: JUCE extra audio utility classes | ||||
description: Classes for audio-related GUI and miscellaneous tasks. | description: Classes for audio-related GUI and miscellaneous tasks. | ||||
website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
@@ -30,7 +30,7 @@ namespace CDReaderHelpers | |||||
{ | { | ||||
inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key) | 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) | if (child->getAllSubText().trim() == key) | ||||
return child->getNextElement(); | return child->getNextElement(); | ||||
@@ -71,7 +71,7 @@ namespace CDReaderHelpers | |||||
if (trackArray == nullptr) | if (trackArray == nullptr) | ||||
return "Couldn't find Track Array"; | return "Couldn't find Track Array"; | ||||
forEachXmlChildElement (*trackArray, track) | |||||
for (auto* track : trackArray->getChildIterator()) | |||||
{ | { | ||||
const int trackValue = getIntValueForKey (*track, "Start Block"); | const int trackValue = getIntValueForKey (*track, "Start Block"); | ||||
if (trackValue < 0) | if (trackValue < 0) | ||||
@@ -314,25 +314,25 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AbstractFifo) | ||||
}; | }; | ||||
template<> | |||||
template <> | |||||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::finish (AbstractFifo& f, int num) noexcept | inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::finish (AbstractFifo& f, int num) noexcept | ||||
{ | { | ||||
f.finishedRead (num); | f.finishedRead (num); | ||||
} | } | ||||
template<> | |||||
template <> | |||||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::finish (AbstractFifo& f, int num) noexcept | inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::finish (AbstractFifo& f, int num) noexcept | ||||
{ | { | ||||
f.finishedWrite (num); | f.finishedWrite (num); | ||||
} | } | ||||
template<> | |||||
template <> | |||||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::prepare (AbstractFifo& f, int num) noexcept | inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::read>::prepare (AbstractFifo& f, int num) noexcept | ||||
{ | { | ||||
f.prepareToRead (num, startIndex1, blockSize1, startIndex2, blockSize2); | f.prepareToRead (num, startIndex1, blockSize1, startIndex2, blockSize2); | ||||
} | } | ||||
template<> | |||||
template <> | |||||
inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::prepare (AbstractFifo& f, int num) noexcept | inline void AbstractFifo::ScopedReadWrite<AbstractFifo::ReadOrWrite::write>::prepare (AbstractFifo& f, int num) noexcept | ||||
{ | { | ||||
f.prepareToWrite (num, startIndex1, blockSize1, startIndex2, blockSize2); | f.prepareToWrite (num, startIndex1, blockSize1, startIndex2, blockSize2); | ||||
@@ -110,16 +110,16 @@ public: | |||||
/** Initalises an Array from a list of items. */ | /** Initalises an Array from a list of items. */ | ||||
template <typename... OtherElements> | 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. */ | /** Initalises an Array from a list of items. */ | ||||
template <typename... OtherElements> | 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> | template <typename TypeToCreateFrom> | ||||
@@ -433,18 +433,18 @@ public: | |||||
/** Appends multiple new elements at the end of the array. */ | /** Appends multiple new elements at the end of the array. */ | ||||
template <typename... OtherElements> | template <typename... OtherElements> | ||||
void add (const ElementType& firstNewElement, OtherElements... otherElements) | |||||
void add (const ElementType& firstNewElement, OtherElements&&... otherElements) | |||||
{ | { | ||||
const ScopedLockType lock (getLock()); | 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. */ | /** Appends multiple new elements at the end of the array. */ | ||||
template <typename... OtherElements> | template <typename... OtherElements> | ||||
void add (ElementType&& firstNewElement, OtherElements... otherElements) | |||||
void add (ElementType&& firstNewElement, OtherElements&&... otherElements) | |||||
{ | { | ||||
const ScopedLockType lock (getLock()); | 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. | /** Inserts a new element into the array at a given position. | ||||