diff --git a/libs/juce/source/modules/juce_audio_basics/effects/juce_Reverb.h b/libs/juce/source/modules/juce_audio_basics/effects/juce_Reverb.h index fceb0a00..53457a60 100644 --- a/libs/juce/source/modules/juce_audio_basics/effects/juce_Reverb.h +++ b/libs/juce/source/modules/juce_audio_basics/effects/juce_Reverb.h @@ -181,7 +181,7 @@ public: for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series output = allPass[0][j].process (output); - samples[i] = output * wet1 + input * dry; + samples[i] = output * wet1 + samples[i] * dry; } } diff --git a/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index effefbbb..94a6b9bc 100644 --- a/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -29,6 +29,7 @@ MidiMessageSequence::MidiMessageSequence() MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) { list.addCopiesOf (other.list); + updateMatchedPairs(); } MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) @@ -52,17 +53,17 @@ void MidiMessageSequence::clear() list.clear(); } -int MidiMessageSequence::getNumEvents() const +int MidiMessageSequence::getNumEvents() const noexcept { return list.size(); } -MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const +MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const noexcept { return list [index]; } -double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const +double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const noexcept { if (const MidiEventHolder* const meh = list [index]) if (meh->noteOffObject != nullptr) @@ -71,7 +72,7 @@ double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const return 0.0; } -int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const +int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcept { if (const MidiEventHolder* const meh = list [index]) return list.indexOf (meh->noteOffObject); @@ -79,12 +80,12 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const return -1; } -int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const +int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept { return list.indexOf (event); } -int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const +int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const noexcept { const int numEvents = list.size(); @@ -97,17 +98,17 @@ int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const } //============================================================================== -double MidiMessageSequence::getStartTime() const +double MidiMessageSequence::getStartTime() const noexcept { return getEventTime (0); } -double MidiMessageSequence::getEndTime() const +double MidiMessageSequence::getEndTime() const noexcept { return getEventTime (list.size() - 1); } -double MidiMessageSequence::getEventTime (const int index) const +double MidiMessageSequence::getEventTime (const int index) const noexcept { if (const MidiEventHolder* const meh = list [index]) return meh->message.getTimeStamp(); @@ -181,13 +182,13 @@ void MidiMessageSequence::addSequence (const MidiMessageSequence& other, } //============================================================================== -void MidiMessageSequence::sort() +void MidiMessageSequence::sort() noexcept { MidiMessageSequenceSorter sorter; list.sort (sorter, true); } -void MidiMessageSequence::updateMatchedPairs() +void MidiMessageSequence::updateMatchedPairs() noexcept { for (int i = 0; i < list.size(); ++i) { @@ -226,7 +227,7 @@ void MidiMessageSequence::updateMatchedPairs() } } -void MidiMessageSequence::addTimeToMessages (const double delta) +void MidiMessageSequence::addTimeToMessages (const double delta) noexcept { for (int i = list.size(); --i >= 0;) { diff --git a/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h b/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h index d91ed8b2..b62940c2 100644 --- a/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h +++ b/libs/juce/source/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h @@ -91,47 +91,47 @@ public: void clear(); /** Returns the number of events in the sequence. */ - int getNumEvents() const; + int getNumEvents() const noexcept; /** Returns a pointer to one of the events. */ - MidiEventHolder* getEventPointer (int index) const; + MidiEventHolder* getEventPointer (int index) const noexcept; /** Returns the time of the note-up that matches the note-on at this index. If the event at this index isn't a note-on, it'll just return 0. @see MidiMessageSequence::MidiEventHolder::noteOffObject */ - double getTimeOfMatchingKeyUp (int index) const; + double getTimeOfMatchingKeyUp (int index) const noexcept; /** Returns the index of the note-up that matches the note-on at this index. If the event at this index isn't a note-on, it'll just return -1. @see MidiMessageSequence::MidiEventHolder::noteOffObject */ - int getIndexOfMatchingKeyUp (int index) const; + int getIndexOfMatchingKeyUp (int index) const noexcept; /** Returns the index of an event. */ - int getIndexOf (MidiEventHolder* event) const; + int getIndexOf (MidiEventHolder* event) const noexcept; /** Returns the index of the first event on or after the given timestamp. If the time is beyond the end of the sequence, this will return the number of events. */ - int getNextIndexAtTime (double timeStamp) const; + int getNextIndexAtTime (double timeStamp) const noexcept; //============================================================================== /** Returns the timestamp of the first event in the sequence. @see getEndTime */ - double getStartTime() const; + double getStartTime() const noexcept; /** Returns the timestamp of the last event in the sequence. @see getStartTime */ - double getEndTime() const; + double getEndTime() const noexcept; /** Returns the timestamp of the event at a given index. If the index is out-of-range, this will return 0.0 */ - double getEventTime (int index) const; + double getEventTime (int index) const noexcept; //============================================================================== /** Inserts a midi message into the sequence. @@ -185,13 +185,13 @@ public: will scan the list and make sure all the note-offs in the MidiEventHolder structures are pointing at the correct ones. */ - void updateMatchedPairs(); + void updateMatchedPairs() noexcept; /** Forces a sort of the sequence. You may need to call this if you've manually modified the timestamps of some events such that the overall order now needs updating. */ - void sort(); + void sort() noexcept; //============================================================================== /** Copies all the messages for a particular midi channel to another sequence. @@ -224,7 +224,7 @@ public: /** Adds an offset to the timestamps of all events in the sequence. @param deltaTime the amount to add to each timestamp. */ - void addTimeToMessages (double deltaTime); + void addTimeToMessages (double deltaTime) noexcept; //============================================================================== /** Scans through the sequence to determine the state of any midi controllers at diff --git a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index c31493a1..79a1b51c 100644 --- a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -859,8 +859,11 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCall if (name.isEmpty() || isMidiInputEnabled (name)) { const ScopedLock sl (midiCallbackLock); - midiCallbacks.add (callbackToAdd); - midiCallbackDevices.add (name); + + MidiCallbackInfo mc; + mc.deviceName = name; + mc.callback = callbackToAdd; + midiCallbacks.add (mc); } } @@ -868,11 +871,12 @@ void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputC { for (int i = midiCallbacks.size(); --i >= 0;) { - if (midiCallbackDevices[i] == name && midiCallbacks.getUnchecked(i) == callbackToRemove) + const MidiCallbackInfo& mc = midiCallbacks.getReference(i); + + if (mc.callback == callbackToRemove && mc.deviceName == name) { const ScopedLock sl (midiCallbackLock); midiCallbacks.remove (i); - midiCallbackDevices.remove (i); } } } @@ -881,16 +885,14 @@ void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const { if (! message.isActiveSense()) { - const bool isDefaultSource = (source == nullptr || source == enabledMidiInputs.getFirst()); - const ScopedLock sl (midiCallbackLock); - for (int i = midiCallbackDevices.size(); --i >= 0;) + for (int i = 0; i < midiCallbacks.size(); ++i) { - const String name (midiCallbackDevices[i]); + const MidiCallbackInfo& mc = midiCallbacks.getReference(i); - if ((isDefaultSource && name.isEmpty()) || (name.isNotEmpty() && name == source->getName())) - midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); + if (mc.deviceName.isEmpty() || mc.deviceName == source->getName()) + mc.callback->handleIncomingMidiMessage (source, message); } } } diff --git a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 839edc28..f1e483f3 100644 --- a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -209,10 +209,9 @@ public: //============================================================================== /** Returns the current device properties that are in use. - @see setAudioDeviceSetup */ - void getAudioDeviceSetup (AudioDeviceSetup& setup); + void getAudioDeviceSetup (AudioDeviceSetup& result); /** Changes the current device or its settings. @@ -261,9 +260,7 @@ public: void setCurrentAudioDeviceType (const String& type, bool treatAsChosenDevice); - /** Closes the currently-open device. - You can call restartLastAudioDevice() later to reopen it in the same state that it was just in. */ @@ -305,8 +302,8 @@ public: //============================================================================== /** Returns the average proportion of available CPU being spent inside the audio callbacks. - - Returns a value between 0 and 1.0 + @returns A value between 0 and 1.0 to indicate the approximate proportion of CPU + time spent in the callbacks. */ double getCpuUsage() const; @@ -333,16 +330,16 @@ public: void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled); /** Returns true if a given midi input device is being used. - @see setMidiInputEnabled */ bool isMidiInputEnabled (const String& midiInputDeviceName) const; /** Registers a listener for callbacks when midi events arrive from a midi input. - The device name can be empty to indicate that it wants events from whatever the - current "default" device is. Or it can be the name of one of the midi input devices - (see MidiInput::getDevices() for the names). + The device name can be empty to indicate that it wants to receive all incoming + events from all the enabled MIDI inputs. Or it can be the name of one of the + MIDI input devices if it just wants the events from that device. (see + MidiInput::getDevices() for the list of device names). Only devices which are enabled (see the setMidiInputEnabled() method) will have their events forwarded on to listeners. @@ -350,8 +347,7 @@ public: void addMidiInputCallback (const String& midiInputDeviceName, MidiInputCallback* callback); - /** Removes a listener that was previously registered with addMidiInputCallback(). - */ + /** Removes a listener that was previously registered with addMidiInputCallback(). */ void removeMidiInputCallback (const String& midiInputDeviceName, MidiInputCallback* callback); @@ -371,22 +367,17 @@ public: void setDefaultMidiOutput (const String& deviceName); /** Returns the name of the default midi output. - @see setDefaultMidiOutput, getDefaultMidiOutput */ - String getDefaultMidiOutputName() const { return defaultMidiOutputName; } + const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputName; } /** Returns the current default midi output device. - - If no device has been selected, or the device can't be opened, this will - return 0. - + If no device has been selected, or the device can't be opened, this will return nullptr. @see getDefaultMidiOutputName */ MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput; } - /** Returns a list of the types of device supported. - */ + /** Returns a list of the types of device supported. */ const OwnedArray& getAvailableDeviceTypes(); //============================================================================== @@ -429,9 +420,7 @@ public: void enableInputLevelMeasurement (bool enableMeasurement); /** Returns the current input level. - To use this, you must first enable it by calling enableInputLevelMeasurement(). - See enableInputLevelMeasurement() for more info. */ double getCurrentInputLevel() const; @@ -468,10 +457,16 @@ private: int testSoundPosition; AudioSampleBuffer tempBuffer; + struct MidiCallbackInfo + { + String deviceName; + MidiInputCallback* callback; + }; + StringArray midiInsFromXml; OwnedArray enabledMidiInputs; - Array midiCallbacks; - StringArray midiCallbackDevices; + Array midiCallbacks; + String defaultMidiOutputName; ScopedPointer defaultMidiOutput; CriticalSection audioCallbackLock, midiCallbackLock; diff --git a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp index 1386d0c5..95221ba0 100644 --- a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp +++ b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp @@ -22,20 +22,16 @@ ============================================================================== */ -AudioIODevice::AudioIODevice (const String& deviceName, const String& typeName_) - : name (deviceName), - typeName (typeName_) +AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName) + : name (deviceName), typeName (deviceTypeName) { } -AudioIODevice::~AudioIODevice() -{ -} +AudioIODevice::~AudioIODevice() {} -bool AudioIODevice::hasControlPanel() const -{ - return false; -} +void AudioIODeviceCallback::audioDeviceError (const String&) {} +bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; } +bool AudioIODevice::hasControlPanel() const { return false; } bool AudioIODevice::showControlPanel() { @@ -43,6 +39,3 @@ bool AudioIODevice::showControlPanel() // their hasControlPanel() method. return false; } - -//============================================================================== -void AudioIODeviceCallback::audioDeviceError (const String&) {} diff --git a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h index 6817ae69..8ce41d09 100644 --- a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h @@ -289,6 +289,11 @@ public: */ virtual bool showControlPanel(); + /** On devices which support it, this allows automatic gain control or other + mic processing to be disabled. + If the device doesn't support this operation, it'll return false. + */ + virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled); //============================================================================== protected: diff --git a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index da32b2b4..d6cd99a5 100644 --- a/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/libs/juce/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -34,7 +34,7 @@ method. Each of the objects returned can then be used to list the available devices of that type. E.g. @code - OwnedArray types; + OwnedArray types; myAudioDeviceManager.createAudioDeviceTypes (types); for (int i = 0; i < types.size(); ++i) diff --git a/libs/juce/source/modules/juce_audio_devices/juce_audio_devices.cpp b/libs/juce/source/modules/juce_audio_devices/juce_audio_devices.cpp index cd91ecf6..a9733db0 100644 --- a/libs/juce/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/libs/juce/source/modules/juce_audio_devices/juce_audio_devices.cpp @@ -125,6 +125,7 @@ #if JUCE_USE_ANDROID_OPENSLES #include #include + #include #endif #endif diff --git a/libs/juce/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/libs/juce/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index 842b42e5..e02951d3 100644 --- a/libs/juce/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/libs/juce/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -80,7 +80,7 @@ public: Array getAvailableSampleRates() override { - static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; + static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; return Array (rates, numElementsInArray (rates)); } @@ -165,29 +165,9 @@ public: oldCallback->audioDeviceStopped(); } - void run() override + bool setAudioPreprocessingEnabled (bool enable) override { - if (recorder != nullptr) recorder->start(); - if (player != nullptr) player->start(); - - while (! threadShouldExit()) - { - if (player != nullptr) player->writeBuffer (outputBuffer, *this); - if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); - - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - { - callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, - numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, - actualBufferSize); - } - else - { - outputBuffer.clear(); - } - } + return recorder != nullptr && recorder->setAudioPreprocessingEnabled (enable); } private: @@ -212,6 +192,31 @@ private: return oldCallback; } + void run() override + { + if (recorder != nullptr) recorder->start(); + if (player != nullptr) player->start(); + + while (! threadShouldExit()) + { + if (player != nullptr) player->writeBuffer (outputBuffer, *this); + if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); + + const ScopedLock sl (callbackLock); + + if (callback != nullptr) + { + callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, + numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, + actualBufferSize); + } + else + { + outputBuffer.clear(); + } + } + } + //================================================================================================== struct Engine { @@ -230,6 +235,7 @@ private: SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY"); SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD"); + SL_IID_ANDROIDCONFIGURATION = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDCONFIGURATION"); check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE)); check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface)); @@ -271,6 +277,7 @@ private: SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE; SLInterfaceID* SL_IID_PLAY; SLInterfaceID* SL_IID_RECORD; + SLInterfaceID* SL_IID_ANDROIDCONFIGURATION; private: DynamicLibrary library; @@ -434,7 +441,8 @@ private: struct Recorder { Recorder (int numChannels, int sampleRate, Engine& engine) - : recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr), + : recorderObject (nullptr), recorderRecord (nullptr), + recorderBufferQueue (nullptr), configObject (nullptr), bufferList (numChannels) { jassert (numChannels == 1); // STEREO doesn't always work!! @@ -466,6 +474,7 @@ private: { check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); + check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDCONFIGURATION, &configObject)); check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); @@ -532,10 +541,20 @@ private: } } + bool setAudioPreprocessingEnabled (bool enable) + { + SLuint32 mode = enable ? SL_ANDROID_RECORDING_PRESET_GENERIC + : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + + return configObject != nullptr + && check ((*configObject)->SetConfiguration (configObject, SL_ANDROID_KEY_RECORDING_PRESET, &mode, sizeof (mode))); + } + private: SLObjectItf recorderObject; SLRecordItf recorderRecord; SLAndroidSimpleBufferQueueItf recorderBufferQueue; + SLAndroidConfigurationItf configObject; BufferList bufferList; diff --git a/libs/juce/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/libs/juce/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp index 6016a514..b7d16110 100644 --- a/libs/juce/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ b/libs/juce/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp @@ -67,7 +67,12 @@ public: return s; } - Array getAvailableSampleRates() override { return sampleRates; } + Array getAvailableSampleRates() override + { + // can't find a good way to actually ask the device for which of these it supports.. + static const double rates[] = { 8000.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0 }; + return Array (rates, numElementsInArray (rates)); + } Array getAvailableBufferSizes() override { @@ -79,7 +84,7 @@ public: return r; } - int getDefaultBufferSize() override { return 1024; } + int getDefaultBufferSize() override { return 1024; } String open (const BigInteger& inputChannelsWanted, const BigInteger& outputChannelsWanted, @@ -117,10 +122,9 @@ public: AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); fixAudioRouteIfSetToReceiver(); - updateDeviceInfo(); setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, targetSampleRate); - updateSampleRates(); + updateDeviceInfo(); setSessionFloat32Property (kAudioSessionProperty_PreferredHardwareIOBufferDuration, preferredBufferSize / sampleRate); updateCurrentBufferSize(); @@ -162,8 +166,15 @@ public: BigInteger getActiveOutputChannels() const override { return activeOutputChans; } BigInteger getActiveInputChannels() const override { return activeInputChans; } - int getOutputLatencyInSamples() override { return 0; } //xxx - int getInputLatencyInSamples() override { return 0; } //xxx + int getOutputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareOutputLatency); } + int getInputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareInputLatency); } + + int getLatency (AudioSessionPropertyID propID) + { + Float32 latency = 0; + getSessionProperty (propID, latency); + return roundToInt (latency * getCurrentSampleRate()); + } void start (AudioIODeviceCallback* newCallback) override { @@ -197,11 +208,16 @@ public: bool isPlaying() override { return isRunning && callback != nullptr; } String getLastError() override { return lastError; } + bool setAudioPreprocessingEnabled (bool enable) override + { + return setSessionUInt32Property (kAudioSessionProperty_Mode, enable ? kAudioSessionMode_Default + : kAudioSessionMode_Measurement); + } + private: //================================================================================================== CriticalSection callbackLock; Float64 sampleRate; - Array sampleRates; int numInputChannels, numOutputChannels; int preferredBufferSize, actualBufferSize; bool isRunning; @@ -322,38 +338,6 @@ private: getSessionProperty (kAudioSessionProperty_AudioInputAvailable, audioInputIsAvailable); } - void updateSampleRates() - { - getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate); - - sampleRates.clear(); - sampleRates.add (sampleRate); - - const int commonSampleRates[] = { 8000, 16000, 22050, 32000, 44100, 48000 }; - - for (int i = 0; i < numElementsInArray (commonSampleRates); ++i) - { - Float64 rate = (Float64) commonSampleRates[i]; - - if (rate != sampleRate) - { - setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, rate); - - Float64 actualSampleRate = 0.0; - getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, actualSampleRate); - - if (actualSampleRate == rate) - sampleRates.add (actualSampleRate); - } - } - - DefaultElementComparator comparator; - sampleRates.sort (comparator); - - setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, sampleRate); - getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate); - } - void updateCurrentBufferSize() { Float32 bufferDuration = sampleRate > 0 ? (Float32) (preferredBufferSize / sampleRate) : 0.0f; @@ -455,12 +439,12 @@ private: static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) { - return static_cast (client)->process (flags, time, numFrames, data); + return static_cast (client)->process (flags, time, numFrames, data); } static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue) { - static_cast (client)->routingChanged (propertyValue); + static_cast (client)->routingChanged (propertyValue); } //================================================================================================== @@ -552,9 +536,9 @@ private: return AudioSessionGetProperty (propID, &valueSize, &result); } - static void setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } - static void setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } - static void setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } + static bool setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } + static bool setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } + static bool setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) }; diff --git a/libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 625f1b80..e51dd7c0 100644 --- a/libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/libs/juce/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -144,6 +144,7 @@ public: : owner (d), inputLatency (0), outputLatency (0), + bitDepth (32), callback (nullptr), #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 audioProcID (0), @@ -352,6 +353,22 @@ public: return (int) lat; } + int getBitDepthFromDevice (AudioObjectPropertyScope scope) const + { + AudioObjectPropertyAddress pa; + pa.mElement = kAudioObjectPropertyElementMaster; + pa.mSelector = kAudioStreamPropertyPhysicalFormat; + pa.mScope = scope; + + AudioStreamBasicDescription asbd; + UInt32 size = sizeof (asbd); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &asbd))) + return (int) asbd.mBitsPerChannel; + + return 0; + } + void updateDetailsFromDevice() { stopTimer(); @@ -392,6 +409,13 @@ public: StringArray newInNames (getChannelInfo (true, newInChans)); StringArray newOutNames (getChannelInfo (false, newOutChans)); + const int inputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeInput); + const int outputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeOutput); + + bitDepth = jmax (inputBitDepth, outputBitDepth); + if (bitDepth <= 0) + bitDepth = 32; + // after getting the new values, lock + apply them const ScopedLock sl (callbackLock); @@ -728,6 +752,7 @@ public: //============================================================================== CoreAudioIODevice& owner; int inputLatency, outputLatency; + int bitDepth; BigInteger activeInputChans, activeOutputChans; StringArray inChanNames, outChanNames; Array sampleRates; @@ -886,7 +911,7 @@ public: Array getAvailableBufferSizes() override { return internal->bufferSizes; } double getCurrentSampleRate() override { return internal->getSampleRate(); } - int getCurrentBitDepth() override { return 32; } // no way to find out, so just assume it's high.. + int getCurrentBitDepth() override { return internal->bitDepth; } int getCurrentBufferSizeSamples() override { return internal->getBufferSize(); } int getDefaultBufferSize() override @@ -1387,7 +1412,7 @@ private: d.done = (d.numInputChans == 0); } - for (int tries = 3;;) + for (int tries = 5;;) { bool anyRemaining = false; @@ -1434,7 +1459,7 @@ private: d.done = (d.numOutputChans == 0); } - for (int tries = 3;;) + for (int tries = 5;;) { bool anyRemaining = false; diff --git a/libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index d7edd45d..f5b0b9a6 100644 --- a/libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/libs/juce/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -888,13 +888,13 @@ AiffAudioFormat::~AiffAudioFormat() Array AiffAudioFormat::getPossibleSampleRates() { const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; - return Array (rates); + return Array (rates); } Array AiffAudioFormat::getPossibleBitDepths() { const int depths[] = { 8, 16, 24, 0 }; - return Array (depths); + return Array (depths); } bool AiffAudioFormat::canDoStereo() { return true; } diff --git a/libs/juce/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/libs/juce/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index bb8abe54..9eed8171 100644 --- a/libs/juce/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/libs/juce/source/modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -29,161 +29,77 @@ extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); //============================================================================== /** - A class that can be used to run a simple standalone application containing your filter. + An object that creates and plays a standalone instance of an AudioProcessor. - Just create one of these objects in your JUCEApplicationBase::initialise() method, and - let it do its work. It will create your filter object using the same createPluginFilter() function - that the other plugin wrappers use. + The object will create your processor using the same createPluginFilter() + function that the other plugin wrappers use, and will run it through the + computer's audio/MIDI devices using AudioDeviceManager and AudioProcessorPlayer. */ -class StandaloneFilterWindow : public DocumentWindow, - public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) +class StandalonePluginHolder { public: - //============================================================================== - /** Creates a window with a given title and colour. + /** Creates an instance of the default plugin. + The settings object can be a PropertySet that the class should use to store its settings - the object that is passed-in will be owned by this class and deleted automatically when no longer needed. (It can also be null) */ - StandaloneFilterWindow (const String& title, - Colour backgroundColour, - PropertySet* settingsToUse) - : DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), - settings (settingsToUse), - optionsButton ("options") + StandalonePluginHolder (PropertySet* settingsToUse) + : settings (settingsToUse) { - setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); - - Component::addAndMakeVisible (optionsButton); - optionsButton.addListener (this); - optionsButton.setTriggeredOnMouseDown (true); - - createFilter(); - - if (filter == nullptr) - { - jassertfalse // Your filter didn't create correctly! In a standalone app that's not too great. - JUCEApplicationBase::quit(); - } - - filter->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, - JucePlugin_MaxNumOutputChannels, - 44100, 512); - - deviceManager = new AudioDeviceManager(); - deviceManager->addAudioCallback (&player); - deviceManager->addMidiInputCallback (String::empty, &player); - - player.setProcessor (filter); - - ScopedPointer savedState; - - if (settings != nullptr) - savedState = settings->getXmlValue ("audioSetup"); - - deviceManager->initialise (filter->getNumInputChannels(), - filter->getNumOutputChannels(), - savedState, - true); - - if (settings != nullptr) - { - MemoryBlock data; - - if (data.fromBase64Encoding (settings->getValue ("filterState")) - && data.getSize() > 0) - { - filter->setStateInformation (data.getData(), (int) data.getSize()); - } - } - - setContentOwned (filter->createEditorIfNeeded(), true); - - if (settings != nullptr) - { - const int x = settings->getIntValue ("windowX", -100); - const int y = settings->getIntValue ("windowY", -100); - - if (x != -100 && y != -100) - setBoundsConstrained (juce::Rectangle (x, y, getWidth(), getHeight())); - else - centreWithSize (getWidth(), getHeight()); - } - else - { - centreWithSize (getWidth(), getHeight()); - } + createPlugin(); + setupAudioDevices(); + reloadPluginState(); + startPlaying(); } - ~StandaloneFilterWindow() + ~StandalonePluginHolder() { - if (settings != nullptr) - { - settings->setValue ("windowX", getX()); - settings->setValue ("windowY", getY()); - - if (deviceManager != nullptr) - { - ScopedPointer xml (deviceManager->createStateXml()); - settings->setValue ("audioSetup", xml); - } - } - - deviceManager->removeMidiInputCallback (String::empty, &player); - deviceManager->removeAudioCallback (&player); - deviceManager = nullptr; - - if (settings != nullptr && filter != nullptr) - { - MemoryBlock data; - filter->getStateInformation (data); - - settings->setValue ("filterState", data.toBase64Encoding()); - } - - deleteFilter(); + deletePlugin(); + shutDownAudioDevices(); } //============================================================================== - AudioProcessor* getAudioProcessor() const noexcept { return filter; } - AudioDeviceManager* getDeviceManager() const noexcept { return deviceManager; } - - void createFilter() + void createPlugin() { AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Standalone); - filter = createPluginFilter(); + processor = createPluginFilter(); + jassert (processor != nullptr); // Your createPluginFilter() function must return a valid object! AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Undefined); + + processor->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, + JucePlugin_MaxNumOutputChannels, + 44100, 512); } - /** Deletes and re-creates the filter and its UI. */ - void resetFilter() + void deletePlugin() { - deleteFilter(); - createFilter(); - - if (filter != nullptr) - { - if (deviceManager != nullptr) - player.setProcessor (filter); + stopPlaying(); + processor = nullptr; + } - setContentOwned (filter->createEditorIfNeeded(), true); - } + static String getFilePatterns (const String& fileSuffix) + { + if (fileSuffix.isEmpty()) + return String(); - if (settings != nullptr) - settings->removeValue ("filterState"); + return (fileSuffix.startsWithChar ('.') ? "*" : "*.") + fileSuffix; } - /** Pops up a dialog letting the user save the filter's state to a file. */ - void saveState() + + //============================================================================== + /** Pops up a dialog letting the user save the processor's state to a file. */ + void askUserToSaveState (const String& fileSuffix = String()) { FileChooser fc (TRANS("Save current state"), settings != nullptr ? File (settings->getValue ("lastStateFile")) - : File::nonexistent); + : File::getSpecialLocation (File::userDocumentsDirectory), + getFilePatterns (fileSuffix)); if (fc.browseForFileToSave (true)) { MemoryBlock data; - filter->getStateInformation (data); + processor->getStateInformation (data); if (! fc.getResult().replaceWithData (data.getData(), data.getSize())) { @@ -194,12 +110,13 @@ public: } } - /** Pops up a dialog letting the user re-load the filter's state from a file. */ - void loadState() + /** Pops up a dialog letting the user re-load the processor's state from a file. */ + void askUserToLoadState (const String& fileSuffix = String()) { FileChooser fc (TRANS("Load a saved state"), settings != nullptr ? File (settings->getValue ("lastStateFile")) - : File::nonexistent); + : File::getSpecialLocation (File::userDocumentsDirectory), + getFilePatterns (fileSuffix)); if (fc.browseForFileToOpen()) { @@ -207,7 +124,7 @@ public: if (fc.getResult().loadFileAsData (data)) { - filter->setStateInformation (data.getData(), (int) data.getSize()); + processor->setStateInformation (data.getData(), (int) data.getSize()); } else { @@ -218,20 +135,33 @@ public: } } - /** Shows the audio properties dialog box modally. */ - virtual void showAudioSettingsDialog() + //============================================================================== + void startPlaying() + { + player.setProcessor (processor); + } + + void stopPlaying() + { + player.setProcessor (nullptr); + } + + //============================================================================== + /** Shows an audio properties dialog box modally. */ + void showAudioSettingsDialog() { DialogWindow::LaunchOptions o; - o.content.setOwned (new AudioDeviceSelectorComponent (*deviceManager, - filter->getNumInputChannels(), - filter->getNumInputChannels(), - filter->getNumOutputChannels(), - filter->getNumOutputChannels(), - true, false, true, false)); + o.content.setOwned (new AudioDeviceSelectorComponent (deviceManager, + processor->getNumInputChannels(), + processor->getNumInputChannels(), + processor->getNumOutputChannels(), + processor->getNumOutputChannels(), + true, false, + true, false)); o.content->setSize (500, 450); o.dialogTitle = TRANS("Audio Settings"); - o.dialogBackgroundColour = Colours::lightgrey; + o.dialogBackgroundColour = Colour (0xfff0f0f0); o.escapeKeyTriggersCloseButton = true; o.useNativeTitleBar = true; o.resizable = false; @@ -239,66 +169,214 @@ public: o.launchAsync(); } - //============================================================================== - /** @internal */ - void closeButtonPressed() override + void saveAudioDeviceState() { - JUCEApplicationBase::quit(); + if (settings != nullptr) + { + ScopedPointer xml (deviceManager.createStateXml()); + settings->setValue ("audioSetup", xml); + } } - /** @internal */ - void buttonClicked (Button*) override + void reloadAudioDeviceState() { - if (filter != nullptr) + ScopedPointer savedState; + + if (settings != nullptr) + savedState = settings->getXmlValue ("audioSetup"); + + deviceManager.initialise (processor->getNumInputChannels(), + processor->getNumOutputChannels(), + savedState, + true); + } + + //============================================================================== + void savePluginState() + { + if (settings != nullptr && processor != nullptr) { - PopupMenu m; - m.addItem (1, TRANS("Audio Settings...")); - m.addSeparator(); - m.addItem (2, TRANS("Save current state...")); - m.addItem (3, TRANS("Load a saved state...")); - m.addSeparator(); - m.addItem (4, TRANS("Reset to default state")); - - switch (m.showAt (&optionsButton)) - { - case 1: showAudioSettingsDialog(); break; - case 2: saveState(); break; - case 3: loadState(); break; - case 4: resetFilter(); break; - default: break; - } + MemoryBlock data; + processor->getStateInformation (data); + + settings->setValue ("filterState", data.toBase64Encoding()); } } - /** @internal */ - void resized() override + void reloadPluginState() { - DocumentWindow::resized(); - optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); + if (settings != nullptr) + { + MemoryBlock data; + + if (data.fromBase64Encoding (settings->getValue ("filterState")) && data.getSize() > 0) + processor->setStateInformation (data.getData(), (int) data.getSize()); + } } -private: //============================================================================== ScopedPointer settings; - ScopedPointer filter; - ScopedPointer deviceManager; + ScopedPointer processor; + AudioDeviceManager deviceManager; AudioProcessorPlayer player; - TextButton optionsButton; - void deleteFilter() +private: + void setupAudioDevices() { - player.setProcessor (nullptr); + deviceManager.addAudioCallback (&player); + deviceManager.addMidiInputCallback (String::empty, &player); + + reloadAudioDeviceState(); + } + + void shutDownAudioDevices() + { + saveAudioDeviceState(); + + deviceManager.removeMidiInputCallback (String::empty, &player); + deviceManager.removeAudioCallback (&player); + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandalonePluginHolder) +}; + + +//============================================================================== +/** + A class that can be used to run a simple standalone application containing your filter. + + Just create one of these objects in your JUCEApplicationBase::initialise() method, and + let it do its work. It will create your filter object using the same createPluginFilter() function + that the other plugin wrappers use. +*/ +class StandaloneFilterWindow : public DocumentWindow, + public ButtonListener // (can't use Button::Listener due to VC2005 bug) +{ +public: + //============================================================================== + /** Creates a window with a given title and colour. + The settings object can be a PropertySet that the class should use to + store its settings - the object that is passed-in will be owned by this + class and deleted automatically when no longer needed. (It can also be null) + */ + StandaloneFilterWindow (const String& title, + Colour backgroundColour, + PropertySet* settingsToUse) + : DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), + optionsButton ("options") + { + setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); - if (filter != nullptr && getContentComponent() != nullptr) + Component::addAndMakeVisible (optionsButton); + optionsButton.addListener (this); + optionsButton.setTriggeredOnMouseDown (true); + + pluginHolder = new StandalonePluginHolder (settingsToUse); + + createEditorComp(); + + if (PropertySet* props = pluginHolder->settings) { - filter->editorBeingDeleted (dynamic_cast (getContentComponent())); + const int x = props->getIntValue ("windowX", -100); + const int y = props->getIntValue ("windowY", -100); + + if (x != -100 && y != -100) + setBoundsConstrained (juce::Rectangle (x, y, getWidth(), getHeight())); + else + centreWithSize (getWidth(), getHeight()); + } + else + { + centreWithSize (getWidth(), getHeight()); + } + } + + ~StandaloneFilterWindow() + { + if (PropertySet* props = pluginHolder->settings) + { + props->setValue ("windowX", getX()); + props->setValue ("windowY", getY()); + } + + pluginHolder->stopPlaying(); + deleteEditorComp(); + pluginHolder = nullptr; + } + + //============================================================================== + AudioProcessor* getAudioProcessor() const noexcept { return pluginHolder->processor; } + AudioDeviceManager& getDeviceManager() const noexcept { return pluginHolder->deviceManager; } + + void createEditorComp() + { + setContentOwned (getAudioProcessor()->createEditorIfNeeded(), true); + } + + void deleteEditorComp() + { + if (AudioProcessorEditor* ed = dynamic_cast (getContentComponent())) + { + pluginHolder->processor->editorBeingDeleted (ed); clearContentComponent(); } + } - filter = nullptr; + /** Deletes and re-creates the plugin, resetting it to its default state. */ + void resetToDefaultState() + { + pluginHolder->stopPlaying(); + deleteEditorComp(); + pluginHolder->deletePlugin(); + + if (PropertySet* props = pluginHolder->settings) + props->removeValue ("filterState"); + + pluginHolder->createPlugin(); + createEditorComp(); + pluginHolder->startPlaying(); + } + + //============================================================================== + void closeButtonPressed() override + { + JUCEApplicationBase::quit(); + } + + void buttonClicked (Button*) override + { + PopupMenu m; + m.addItem (1, TRANS("Audio Settings...")); + m.addSeparator(); + m.addItem (2, TRANS("Save current state...")); + m.addItem (3, TRANS("Load a saved state...")); + m.addSeparator(); + m.addItem (4, TRANS("Reset to default state")); + + switch (m.showAt (&optionsButton)) + { + case 1: pluginHolder->showAudioSettingsDialog(); break; + case 2: pluginHolder->askUserToSaveState(); break; + case 3: pluginHolder->askUserToLoadState(); break; + case 4: resetToDefaultState(); break; + default: break; + } + } + + void resized() override + { + DocumentWindow::resized(); + optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); } + ScopedPointer pluginHolder; + +private: + //============================================================================== + TextButton optionsButton; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow) }; + #endif // JUCE_STANDALONEFILTERWINDOW_H_INCLUDED diff --git a/libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp b/libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp index 0d281f10..bc4b02a0 100644 --- a/libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp +++ b/libs/juce/source/modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp @@ -1052,7 +1052,8 @@ public: Timer::callPendingTimersSynchronously(); for (int i = ComponentPeer::getNumPeers(); --i >= 0;) - ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); + if (ComponentPeer* p = ComponentPeer::getPeer(i)) + p->performAnyPendingRepaintsNow(); recursionCheck = false; } diff --git a/libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp b/libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp index 5704acd0..ba8ca4cd 100644 --- a/libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp +++ b/libs/juce/source/modules/juce_audio_plugin_client/VST3/juce_VST3_Wrapper.cpp @@ -279,9 +279,9 @@ public: } //============================================================================== - void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override { beginEdit ((Steinberg::uint32) index); } - void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override { performEdit ((Steinberg::uint32) index, (double) newValue); } - void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override { endEdit ((Steinberg::uint32) index); } + void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override { beginEdit ((Vst::ParamID) index); } + void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override { performEdit ((Vst::ParamID) index, (double) newValue); } + void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override { endEdit ((Vst::ParamID) index); } void audioProcessorChanged (AudioProcessor*) override { @@ -788,60 +788,66 @@ public: (int) htonl (bank->content.data.size))); } - void loadVST3PresetFile (const char* data, int size) + bool loadVST3PresetFile (const char* data, int size) { - // At offset 4 there's a little-endian version number which seems to typically be 1 - // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" - const int chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); - const char* const chunkList = data + chunkListOffset; - jassert (memcmp (chunkList, "List", 4) == 0); - const int entryCount = (int) ByteOrder::littleEndianInt (chunkList + 4); - jassert (entryCount > 0); + if (size < 48) + return false; - for (int i = 0; i < entryCount; ++i) - { - const char* const entry = chunkList + 8 + 20 * i; + // At offset 4 there's a little-endian version number which seems to typically be 1 + // At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" + const int chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); + jassert (memcmp (data + chunkListOffset, "List", 4) == 0); + const int entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); + jassert (entryCount > 0); - if (memcmp (entry, "Comp", 4) == 0) - { - // "Comp" entries seem to contain the data. - juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (entry + 4); - juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (entry + 12); + for (int i = 0; i < entryCount; ++i) + { + const int entryOffset = chunkListOffset + 8 + 20 * i; - #if JUCE_32BIT - jassert (chunkOffset <= (juce::uint64) 0xffffffff); - #endif - jassert (chunkSize <= 0x7fffffff); + if (entryOffset + 20 > size) + return false; - loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); - } - } - } + if (memcmp (data + entryOffset, "Comp", 4) == 0) + { + // "Comp" entries seem to contain the data. + juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); + juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); - bool loadVST2CompatibleState (const char* data, int size) - { - if (size < 4) - return false; - - if ('VstW' == htonl (*(juce::int32*) data)) - { - loadVST2VstWBlock (data, size); - return true; - } - - if (memcmp (data, "VST3", 4) == 0) - { - // In Cubase 5, when loading VST3 .vstpreset files, - // we get the whole content of the files to load. - // In Cubase 7 we get just the contents within and - // we go directly to the loadVST2VstW codepath instead. - loadVST3PresetFile (data, size); - return true; - } - - return false; - } + if (chunkOffset + chunkSize > size) + { + jassertfalse; + return false; + } + + loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); + } + } + + return true; + } + + bool loadVST2CompatibleState (const char* data, int size) + { + if (size < 4) + return false; + + if (htonl (*(juce::int32*) data) == 'VstW') + { + loadVST2VstWBlock (data, size); + return true; + } + if (memcmp (data, "VST3", 4) == 0) + { + // In Cubase 5, when loading VST3 .vstpreset files, + // we get the whole content of the files to load. + // In Cubase 7 we get just the contents within and + // we go directly to the loadVST2VstW codepath instead. + return loadVST3PresetFile (data, size); + } + + return false; + } #endif bool loadStateData (const void* data, int size) @@ -885,14 +891,12 @@ public: for (;;) { Steinberg::int32 bytesRead = 0; + const Steinberg::tresult status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); - if (state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead) == kResultTrue && bytesRead > 0) - { - allData.write (buffer, bytesRead); - continue; - } + if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab())) + break; - break; + allData.write (buffer, bytesRead); } } @@ -1099,10 +1103,12 @@ public: Steinberg::int32 counter = 0; FOREACH_CAST (IPtr, Vst::AudioBus, bus, list) + { if (counter < numBusses) bus->setArrangement (arrangement[counter]); counter++; + } ENDFOR return kResultTrue; @@ -1114,6 +1120,8 @@ public: tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override { + (void) inputs; (void) outputs; + #if JucePlugin_MaxNumInputChannels > 0 if (setBusArrangementFor (audioInputs, inputs, numIns) != kResultTrue) return kResultFalse; @@ -1238,8 +1246,8 @@ public: const int numMidiEventsComingIn = midiBuffer.getNumEvents(); #endif - const int numInputChans = data.inputs != nullptr ? (int) data.inputs[0].numChannels : 0; - const int numOutputChans = data.outputs != nullptr ? (int) data.outputs[0].numChannels : 0; + const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; + const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; int totalChans = 0; @@ -1255,7 +1263,13 @@ public: ++totalChans; } - AudioSampleBuffer buffer (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); + AudioSampleBuffer buffer; + + if (totalChans != 0) + buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); + else if (getHostType().isWavelab() + && pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels() > 0) + return kResultFalse; { const ScopedLock sl (pluginInstance->getCallbackLock()); diff --git a/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h b/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h index 6336b5bd..d0af2013 100644 --- a/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h +++ b/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormat.h @@ -54,7 +54,7 @@ public: (e.g. VST shells) can use a single DLL to create a set of different plugin subtypes, so in that case, each subtype is returned as a separate object. */ - virtual void findAllTypesForFile (OwnedArray & results, + virtual void findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) = 0; /** Tries to recreate a type from a previously generated PluginDescription. diff --git a/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h b/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h index 7e502b39..a6f1607d 100644 --- a/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h +++ b/libs/juce/source/modules/juce_audio_processors/format/juce_AudioPluginFormatManager.h @@ -89,7 +89,7 @@ public: private: //============================================================================== - OwnedArray formats; + OwnedArray formats; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) }; diff --git a/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h b/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h index ef6d59a1..46c2b10e 100644 --- a/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h +++ b/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.h @@ -37,7 +37,7 @@ public: //============================================================================== String getName() const override { return "AudioUnit"; } - void findAllTypesForFile (OwnedArray &, const String& fileOrIdentifier) override; + void findAllTypesForFile (OwnedArray&, const String& fileOrIdentifier) override; AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; diff --git a/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 60d4c996..a4e58b16 100644 --- a/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/libs/juce/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -138,7 +138,7 @@ namespace AudioUnitFormatHelpers fileOrIdentifier.lastIndexOfChar ('/')) + 1)); StringArray tokens; - tokens.addTokens (s, ",", String()); + tokens.addTokens (s, ",", StringRef()); tokens.removeEmptyStrings(); if (tokens.size() == 3) @@ -915,7 +915,7 @@ private: bool automatable; }; - OwnedArray parameters; + OwnedArray parameters; MidiDataConcatenator midiConcatenator; CriticalSection midiInLock; @@ -1600,7 +1600,7 @@ AudioUnitPluginFormat::~AudioUnitPluginFormat() { } -void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray & results, +void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray& results, const String& fileOrIdentifier) { if (! fileMightContainThisPluginType (fileOrIdentifier)) diff --git a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index c91411cc..d7e66cdc 100644 --- a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -85,6 +85,18 @@ static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept //============================================================================== +static Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg::Vst::IAudioProcessor* processor, + bool isInput, int busIndex) +{ + Steinberg::Vst::SpeakerArrangement arrangement = Steinberg::Vst::SpeakerArr::kEmpty; + + if (processor != nullptr) + processor->getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput, + (Steinberg::int32) busIndex, arrangement); + + return arrangement; +} + /** For the sake of simplicity, there can only be 1 arrangement type per channel count. i.e.: 4 channels == k31Cine OR k40Cine */ @@ -126,9 +138,9 @@ class ComSmartPtr { public: ComSmartPtr() noexcept : source (nullptr) {} - ComSmartPtr (ObjectType* object) noexcept : source (object) { if (source != nullptr) source->addRef(); } - ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } - ~ComSmartPtr() { if (source != nullptr) source->release(); } + 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(); } operator ObjectType*() const noexcept { return source; } ObjectType* get() const noexcept { return source; } @@ -341,7 +353,7 @@ namespace VST3BufferExchange Bus& bus, AudioSampleBuffer& buffer, int numChannels, int channelStartOffset, - int sampleOffset = 0) noexcept + int sampleOffset = 0) { const int channelEnd = numChannels + channelStartOffset; jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); @@ -356,37 +368,52 @@ namespace VST3BufferExchange vstBuffers.silenceFlags = 0; } - static void mapBufferToBusses (Array& result, - Steinberg::Vst::IAudioProcessor& processor, - BusMap& busMapToUse, - bool isInput, int numBusses, - AudioSampleBuffer& source) + static void mapArrangementToBusses (int& channelIndexOffset, int index, + Array& result, + BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, + AudioSampleBuffer& source) { - int channelIndexOffset = 0; + const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); - for (int i = 0; i < numBusses; ++i) + if (index >= result.size()) + result.add (Steinberg::Vst::AudioBusBuffers()); + + if (index >= busMapToUse.size()) + busMapToUse.add (Bus()); + + if (numChansForBus > 0) { - Steinberg::Vst::SpeakerArrangement arrangement = 0; - processor.getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput, - (Steinberg::int32) i, arrangement); + associateBufferTo (result.getReference (index), + busMapToUse.getReference (index), + source, numChansForBus, channelIndexOffset); + } - const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); + channelIndexOffset += numChansForBus; + } - if (i >= result.size()) - result.add (Steinberg::Vst::AudioBusBuffers()); + static void mapBufferToBusses (Array& result, BusMap& busMapToUse, + const Array& arrangements, + AudioSampleBuffer& source) + { + int channelIndexOffset = 0; - if (i >= busMapToUse.size()) - busMapToUse.add (Bus()); + for (int i = 0; i < arrangements.size(); ++i) + mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, + arrangements.getUnchecked (i), source); + } - if (numChansForBus > 0) - { - associateBufferTo (result.getReference (i), - busMapToUse.getReference (i), - source, numChansForBus, channelIndexOffset); - } + static void mapBufferToBusses (Array& result, + Steinberg::Vst::IAudioProcessor& processor, + BusMap& busMapToUse, bool isInput, int numBusses, + AudioSampleBuffer& source) + { + int channelIndexOffset = 0; - channelIndexOffset += numChansForBus; - } + for (int i = 0; i < numBusses; ++i) + mapArrangementToBusses (channelIndexOffset, i, + result, busMapToUse, + getArrangementForBus (&processor, isInput, i), + source); } } diff --git a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 4dfd4f81..7abe6e3d 100644 --- a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -31,6 +31,7 @@ #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 #endif +#include #include "juce_VST3Headers.h" #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY @@ -111,6 +112,7 @@ static void createPluginDescription (PluginDescription& description, description.lastFileModTime = pluginFile.getLastModificationTime(); description.manufacturerName = company; description.name = name; + description.descriptiveName = name; description.pluginFormatName = "VST3"; description.numInputChannels = numInputs; description.numOutputChannels = numOutputs; @@ -378,32 +380,70 @@ public: FUnknown* getFUnknown() { return static_cast (this); } + static bool hasFlag (Steinberg::int32 source, Steinberg::int32 flag) noexcept + { + return (source & flag) == flag; + } + //============================================================================== - tresult PLUGIN_API beginEdit (Vst::ParamID) override + tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override { - // XXX todo.. - return kResultFalse; + const int index = getIndexOfParamID (paramID); + + if (index < 0) + return kResultFalse; + + owner->beginParameterChangeGesture (index); + return kResultTrue; } - tresult PLUGIN_API performEdit (Vst::ParamID id, Vst::ParamValue valueNormalized) override + tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override { - if (owner != nullptr) - return owner->editController->setParamNormalized (id, valueNormalized); + const int index = getIndexOfParamID (paramID); - return kResultFalse; + if (index < 0) + return kResultFalse; + + owner->sendParamChangeMessageToListeners (index, (float) valueNormalized); + return owner->editController->setParamNormalized (paramID, valueNormalized); } - tresult PLUGIN_API endEdit (Vst::ParamID) override + tresult PLUGIN_API endEdit (Vst::ParamID paramID) override { - // XXX todo.. - return kResultFalse; + const int index = getIndexOfParamID (paramID); + + if (index < 0) + return kResultFalse; + + owner->endParameterChangeGesture (index); + return kResultTrue; } - tresult PLUGIN_API restartComponent (Steinberg::int32) override + tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override { if (owner != nullptr) { - owner->reset(); + if (hasFlag (flags, Vst::kReloadComponent)) + owner->reset(); + + if (hasFlag (flags, Vst::kIoChanged)) + { + double sampleRate = owner->getSampleRate(); + int numSamples = owner->getBlockSize(); + + if (sampleRate <= 8000.0) + sampleRate = 44100.0; + + if (numSamples <= 0) + numSamples = 1024; + + owner->prepareToPlay (owner->getSampleRate(), owner->getBlockSize()); + } + + if (hasFlag (flags, Vst::kLatencyChanged)) + if (owner->processor != nullptr) + owner->setLatencySamples (jmax (0, (int) owner->processor->getLatencySamples())); + owner->updateHostDisplay(); return kResultTrue; } @@ -437,9 +477,171 @@ public: return kResultFalse; } + //============================================================================== + class ContextMenu : public Vst::IContextMenu + { + public: + ContextMenu (VST3PluginInstance& pluginInstance) : owner (pluginInstance) {} + virtual ~ContextMenu() {} + + JUCE_DECLARE_VST3_COM_REF_METHODS + JUCE_DECLARE_VST3_COM_QUERY_METHODS + + Steinberg::int32 PLUGIN_API getItemCount() override { return (Steinberg::int32) items.size(); } + + tresult PLUGIN_API addItem (const Item& item, IContextMenuTarget* target) override + { + jassert (target != nullptr); + + ItemAndTarget newItem; + newItem.item = item; + newItem.target = target; + + items.add (newItem); + return kResultOk; + } + + tresult PLUGIN_API removeItem (const Item& toRemove, IContextMenuTarget* target) override + { + for (int i = items.size(); --i >= 0;) + { + ItemAndTarget& item = items.getReference(i); + + if (item.item.tag == toRemove.tag && item.target == target) + items.remove (i); + } + + return kResultOk; + } + + tresult PLUGIN_API getItem (Steinberg::int32 tag, Item& result, IContextMenuTarget** target) override + { + for (int i = 0; i < items.size(); ++i) + { + const ItemAndTarget& item = items.getReference(i); + + if (item.item.tag == tag) + { + result = item.item; + + if (target != nullptr) + *target = item.target; + + return kResultTrue; + } + } + + zerostruct (result); + return kResultFalse; + } + + tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override + { + Array subItemStack; + OwnedArray menuStack; + PopupMenu* topLevelMenu = menuStack.add (new PopupMenu()); + + for (int i = 0; i < items.size(); ++i) + { + const Item& item = items.getReference (i).item; + + PopupMenu* menuToUse = menuStack.getLast(); + + if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled)) + { + subItemStack.add (&item); + menuStack.add (new PopupMenu()); + } + else if (hasFlag (item.flags, Item::kIsGroupEnd)) + { + if (const Item* subItem = subItemStack.getLast()) + { + if (PopupMenu* m = menuStack [menuStack.size() - 2]) + m->addSubMenu (toString (subItem->name), *menuToUse, + ! hasFlag (subItem->flags, Item::kIsDisabled), + nullptr, + hasFlag (subItem->flags, Item::kIsChecked)); + + menuStack.removeLast (1); + subItemStack.removeLast (1); + } + } + else if (hasFlag (item.flags, Item::kIsSeparator)) + { + menuToUse->addSeparator(); + } + else + { + menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement, + toString (item.name), + ! hasFlag (item.flags, Item::kIsDisabled), + hasFlag (item.flags, Item::kIsChecked)); + } + } + + PopupMenu::Options options; + + if (AudioProcessorEditor* ed = owner.getActiveEditor()) + options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1)); + + #if JUCE_MODAL_LOOPS_PERMITTED + // Unfortunately, Steinberg's docs explicitly say this should be modal.. + handleResult (topLevelMenu->showMenu (options)); + #else + topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr (this))); + #endif + + return kResultOk; + } + + #if ! JUCE_MODAL_LOOPS_PERMITTED + static void menuFinished (int modalResult, ComSmartPtr menu) { menu->handleResult (modalResult); } + #endif + + private: + enum { zeroTagReplacement = 0x7fffffff }; + + Atomic refCount; + VST3PluginInstance& owner; + + struct ItemAndTarget + { + Item item; + ComSmartPtr target; + }; + + Array items; + + void handleResult (int result) + { + if (result == 0) + return; + + if (result == zeroTagReplacement) + result = 0; + + for (int i = 0; i < items.size(); ++i) + { + const ItemAndTarget& item = items.getReference(i); + + if ((int) item.item.tag == result) + { + if (item.target != nullptr) + item.target->executeMenuItem ((Steinberg::int32) result); + + break; + } + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContextMenu) + }; + Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override { - jassertfalse; + if (owner != nullptr) + return new ContextMenu (*owner); + return nullptr; } @@ -549,9 +751,42 @@ public: private: //============================================================================== - Atomic refCount; + VST3PluginInstance* const owner; + Atomic refCount; String appName; - VST3PluginInstance* owner; + + typedef std::map ParamMapType; + ParamMapType paramToIndexMap; + + int getIndexOfParamID (Vst::ParamID paramID) + { + if (owner == nullptr || owner->editController == nullptr) + return -1; + + int result = getMappedParamID (paramID); + + if (result < 0) + { + const int numParams = owner->editController->getParameterCount(); + + for (int i = 0; i < numParams; ++i) + { + Vst::ParameterInfo paramInfo; + owner->editController->getParameterInfo (i, paramInfo); + paramToIndexMap[paramInfo.id] = i; + } + + result = getMappedParamID (paramID); + } + + return result; + } + + int getMappedParamID (Vst::ParamID paramID) + { + const ParamMapType::iterator it (paramToIndexMap.find (paramID)); + return it != paramToIndexMap.end() ? it->second : -1; + } //============================================================================== class Message : public Vst::IMessage @@ -577,9 +812,9 @@ private: JUCE_DECLARE_VST3_COM_REF_METHODS JUCE_DECLARE_VST3_COM_QUERY_METHODS - FIDString PLUGIN_API getMessageID() { return messageId.toRawUTF8(); } - void PLUGIN_API setMessageID (FIDString id) { messageId = toString (id); } - Vst::IAttributeList* PLUGIN_API getAttributes() { return attributeList; } + FIDString PLUGIN_API getMessageID() override { return messageId.toRawUTF8(); } + void PLUGIN_API setMessageID (FIDString id) override { messageId = toString (id); } + Vst::IAttributeList* PLUGIN_API getAttributes() override { return attributeList; } var value; @@ -587,7 +822,7 @@ private: VST3HostContext& owner; ComSmartPtr attributeList; String messageId; - Atomic refCount; + Atomic refCount; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) }; @@ -715,7 +950,7 @@ private: private: VST3HostContext* owner; - Atomic refCount; + Atomic refCount; //============================================================================== template @@ -1195,7 +1430,8 @@ public: VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) : AudioProcessorEditor (owner), ComponentMovementWatcher (this), - view (pluginView), + refCount (1), + view (pluginView, false), pluginHandle (nullptr), recursiveResize (false) { @@ -1266,20 +1502,32 @@ public: { rect.right = (Steinberg::int32) getWidth(); rect.bottom = (Steinberg::int32) getHeight(); + view->checkSizeConstraint (&rect); + + setSize ((int) rect.getWidth(), (int) rect.getHeight()); + + #if JUCE_WINDOWS + SetWindowPos (pluginHandle, 0, + pos.x, pos.y, rect.getWidth(), rect.getHeight(), + isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); + #elif JUCE_MAC + dummyComponent.setBounds (getLocalBounds()); + #endif + view->onSize (&rect); } else { warnOnFailure (view->getSize (&rect)); - } - #if JUCE_WINDOWS - SetWindowPos (pluginHandle, 0, - pos.x, pos.y, rect.getWidth(), rect.getHeight(), - isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); - #elif JUCE_MAC - dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); - #endif + #if JUCE_WINDOWS + SetWindowPos (pluginHandle, 0, + pos.x, pos.y, rect.getWidth(), rect.getHeight(), + isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); + #elif JUCE_MAC + dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); + #endif + } // Some plugins don't update their cursor correctly when mousing out the window Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); @@ -1364,7 +1612,7 @@ private: if (peer != nullptr) pluginHandle = (HandleFormat) peer->getNativeHandle(); #elif JUCE_MAC - dummyComponent.setBounds (getBounds().withZeroOrigin()); + dummyComponent.setBounds (getLocalBounds()); addAndMakeVisible (dummyComponent); pluginHandle = (NSView*) dummyComponent.getView(); jassert (pluginHandle != nil); @@ -1391,7 +1639,8 @@ public: midiInputs (new MidiEventList()), midiOutputs (new MidiEventList()), isComponentInitialised (false), - isControllerInitialised (false) + isControllerInitialised (false), + isActive (false) { host = new VST3HostContext (this); } @@ -1413,7 +1662,6 @@ public: if (isControllerInitialised) editController->terminate(); if (isComponentInitialised) component->terminate(); - //Deletion order appears to matter: componentConnection = nullptr; editControllerConnection = nullptr; unitData = nullptr; @@ -1425,6 +1673,8 @@ public: editController2 = nullptr; editController = nullptr; component = nullptr; + host = nullptr; + module = nullptr; } bool initialise() @@ -1478,8 +1728,27 @@ public: return module != nullptr ? module->name : String::empty; } + void repopulateArrangements() + { + inputArrangements.clearQuick(); + outputArrangements.clearQuick(); + + // NB: Some plugins need a valid arrangement despite specifying 0 for their I/O busses + for (int i = 0; i < jmax (1, numInputAudioBusses); ++i) + inputArrangements.add (getArrangementForBus (processor, true, i)); + + for (int i = 0; i < jmax (1, numOutputAudioBusses); ++i) + outputArrangements.add (getArrangementForBus (processor, false, i)); + } + void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) override { + // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins: + if (isActive + && getSampleRate() == sampleRate + && getBlockSize() == estimatedSamplesPerBlock) + return; + using namespace Vst; ProcessSetup setup; @@ -1495,20 +1764,25 @@ public: editController->setComponentHandler (host); - Array inArrangements, outArrangements; + if (inputArrangements.size() <= 0 || outputArrangements.size() <= 0) + repopulateArrangements(); - for (int i = 0; i < numInputAudioBusses; ++i) - inArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (true, true, i).channelCount))); + warnOnFailure (processor->setBusArrangements (inputArrangements.getRawDataPointer(), numInputAudioBusses, + outputArrangements.getRawDataPointer(), numOutputAudioBusses)); - for (int i = 0; i < numOutputAudioBusses; ++i) - outArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (false, true, i).channelCount))); + // Update the num. busses in case the configuration has been modified by the plugin. (May affect number of channels!): + const int newNumInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); + const int newNumOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); - warnOnFailure (processor->setBusArrangements (inArrangements.getRawDataPointer(), numInputAudioBusses, - outArrangements.getRawDataPointer(), numOutputAudioBusses)); + // Repopulate arrangements if the number of busses have changed: + if (numInputAudioBusses != newNumInputAudioBusses + || numOutputAudioBusses != newNumOutputAudioBusses) + { + numInputAudioBusses = newNumInputAudioBusses; + numOutputAudioBusses = newNumOutputAudioBusses; - // Update the num. busses in case the configuration has been modified by the plugin. (May affect number of channels!): - numInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); - numOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); + repopulateArrangements(); + } // Needed for having the same sample rate in processBlock(); some plugins need this! setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), @@ -1517,21 +1791,30 @@ public: setStateForAllBusses (true); + setLatencySamples (jmax (0, (int) processor->getLatencySamples())); + warnOnFailure (component->setActive (true)); warnOnFailure (processor->setProcessing (true)); + + isActive = true; } void releaseResources() override { + if (! isActive) + return; // Avoids redundantly calling things like setActive + JUCE_TRY { + isActive = false; + setStateForAllBusses (false); if (processor != nullptr) - processor->setProcessing (false); + warnOnFailure (processor->setProcessing (false)); if (component != nullptr) - component->setActive (false); + warnOnFailure (component->setActive (false)); } JUCE_CATCH_ALL_ASSERT } @@ -1540,7 +1823,8 @@ public: { using namespace Vst; - if (processor != nullptr + if (isActive + && processor != nullptr && processor->canProcessSampleSize (kSample32) == kResultTrue) { const int numSamples = buffer.getNumSamples(); @@ -1612,15 +1896,22 @@ public: //============================================================================== bool silenceInProducesSilenceOut() const override { - return processor == nullptr; + if (processor != nullptr) + return processor->getTailSamples() == Vst::kNoTail; + + return true; } /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ double getTailLengthSeconds() const override { if (processor != nullptr) - return (double) jmin ((int) jmax ((Steinberg::uint32) 0, processor->getTailSamples()), 0x7fffffff) - * getSampleRate(); + { + const double sampleRate = getSampleRate(); + + if (sampleRate > 0.0) + return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate; + } return 0.0; } @@ -1640,7 +1931,7 @@ public: if (getActiveEditor() != nullptr) return true; - ComSmartPtr view (tryCreatingView()); + ComSmartPtr view (tryCreatingView(), false); return view != nullptr; } @@ -1706,6 +1997,16 @@ public: return toString (result); } + //============================================================================== + void reset() override + { + if (component != nullptr) + { + component->setActive (false); + component->setActive (true); + } + } + //============================================================================== void getStateInformation (MemoryBlock& destData) override { @@ -1785,6 +2086,7 @@ private: as very poorly specified by the Steinberg SDK */ int numInputAudioBusses, numOutputAudioBusses; + Array inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). VST3BufferExchange::BusMap inputBusMap, outputBusMap; Array inputBusses, outputBusses; @@ -1829,21 +2131,21 @@ private: JUCE_DECLARE_VST3_COM_REF_METHODS JUCE_DECLARE_VST3_COM_QUERY_METHODS - Steinberg::int32 PLUGIN_API getParameterCount() { return 0; } + Steinberg::int32 PLUGIN_API getParameterCount() override { return 0; } - Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32) + Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32) override { return nullptr; } - Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID&, Steinberg::int32& index) + Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID&, Steinberg::int32& index) override { index = 0; return nullptr; } private: - Atomic refCount; + Atomic refCount; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterChangeList) }; @@ -1851,7 +2153,7 @@ private: ComSmartPtr inputParameterChanges, outputParameterChanges; ComSmartPtr midiInputs, midiOutputs; Vst::ProcessContext timingInfo; //< Only use this in processBlock()! - bool isComponentInitialised, isControllerInitialised; + bool isComponentInitialised, isControllerInitialised, isActive; //============================================================================== bool fetchComponentAndController (IPluginFactory* factory, const Steinberg::int32 numClasses) @@ -2031,11 +2333,11 @@ private: Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const { Vst::BusInfo busInfo; + busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; + busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; - component->getBusInfo (forAudio ? Vst::kAudio : Vst::kEvent, - forInput ? Vst::kInput : Vst::kOutput, + component->getBusInfo (busInfo.mediaType, busInfo.direction, (Steinberg::int32) index, busInfo); - return busInfo; } @@ -2056,11 +2358,8 @@ private: { using namespace VST3BufferExchange; - mapBufferToBusses (inputBusses, *processor, inputBusMap, - true, numInputAudioBusses, buffer); - - mapBufferToBusses (outputBusses, *processor, outputBusMap, - false, numOutputAudioBusses, buffer); + mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer); + mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer); destination.inputs = inputBusses.getRawDataPointer(); destination.outputs = outputBusses.getRawDataPointer(); diff --git a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index c6084e39..458d59bb 100644 --- a/libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/libs/juce/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -1285,7 +1285,8 @@ public: handleUpdateNowIfNeeded(); for (int i = ComponentPeer::getNumPeers(); --i >= 0;) - ComponentPeer::getPeer(i)->performAnyPendingRepaintsNow(); + if (ComponentPeer* p = ComponentPeer::getPeer(i)) + p->performAnyPendingRepaintsNow(); } break; @@ -2317,10 +2318,8 @@ private: { if (isOpen) { - #if ! (JUCE_MAC && JUCE_SUPPORT_CARBON) JUCE_VST_LOG ("Closing VST UI: " + plugin.getName()); isOpen = false; - dispatch (effEditClose, 0, 0, 0, 0); stopTimer(); @@ -2336,7 +2335,6 @@ private: pluginWindow = 0; pluginProc = 0; #endif - #endif } } diff --git a/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index ee3064cf..7ae9dd41 100644 --- a/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -44,11 +44,7 @@ class JUCE_API AudioProcessor { protected: //============================================================================== - /** Constructor. - - You can also do your initialisation tasks in the initialiseFilterInfo() - call, which will be made after this object has been created. - */ + /** Constructor. */ AudioProcessor(); public: diff --git a/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 7deb9b59..d9446823 100644 --- a/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/libs/juce/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -388,11 +388,11 @@ public: private: //============================================================================== - ReferenceCountedArray nodes; - OwnedArray connections; + ReferenceCountedArray nodes; + OwnedArray connections; uint32 lastNodeId; AudioSampleBuffer renderingBuffers; - OwnedArray midiBuffers; + OwnedArray midiBuffers; Array renderingOps; friend class AudioGraphIOProcessor; diff --git a/libs/juce/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp b/libs/juce/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp index 4b9da9b7..c94411d7 100644 --- a/libs/juce/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp +++ b/libs/juce/source/modules/juce_audio_processors/processors/juce_GenericAudioProcessorEditor.cpp @@ -27,12 +27,12 @@ class ProcessorParameterPropertyComp : public PropertyComponent, private Timer { public: - ProcessorParameterPropertyComp (const String& name, AudioProcessor& p, const int index_) + ProcessorParameterPropertyComp (const String& name, AudioProcessor& p, int paramIndex) : PropertyComponent (name), owner (p), - index (index_), + index (paramIndex), paramHasChanged (false), - slider (p, index_) + slider (p, paramIndex) { startTimer (100); addAndMakeVisible (slider); @@ -44,15 +44,19 @@ public: owner.removeListener (this); } - void refresh() + void refresh() override { paramHasChanged = false; - slider.setValue (owner.getParameter (index), dontSendNotification); + + if (slider.getThumbBeingDragged() < 0) + slider.setValue (owner.getParameter (index), dontSendNotification); + + slider.updateText(); } - void audioProcessorChanged (AudioProcessor*) {} + void audioProcessorChanged (AudioProcessor*) override {} - void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) + void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) override { if (parameterIndex == index) paramHasChanged = true; @@ -76,27 +80,34 @@ private: class ParamSlider : public Slider { public: - ParamSlider (AudioProcessor& p, const int index_) - : owner (p), - index (index_) + ParamSlider (AudioProcessor& p, int paramIndex) : owner (p), index (paramIndex) { - setRange (0.0, 1.0, 0.0); + const int steps = owner.getParameterNumSteps (index); + + if (steps > 1 && steps < 0x7fffffff) + setRange (0.0, 1.0, 1.0 / (steps - 1.0)); + else + setRange (0.0, 1.0); + setSliderStyle (Slider::LinearBar); setTextBoxIsEditable (false); - setScrollWheelEnabled (false); + setScrollWheelEnabled (true); } - void valueChanged() + void valueChanged() override { const float newVal = (float) getValue(); if (owner.getParameter (index) != newVal) + { owner.setParameterNotifyingHost (index, newVal); + updateText(); + } } - String getTextFromValue (double /*value*/) + String getTextFromValue (double /*value*/) override { - return owner.getParameterText (index); + return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); } private: diff --git a/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp b/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp index 64801fc3..07ea37e5 100644 --- a/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp +++ b/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioDeviceSelectorComponent.cpp @@ -75,11 +75,10 @@ class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox : public private ListBoxModel { public: - MidiInputSelectorComponentListBox (AudioDeviceManager& dm, - const String& noItemsMessage_) + MidiInputSelectorComponentListBox (AudioDeviceManager& dm, const String& noItems) : ListBox (String::empty, nullptr), deviceManager (dm), - noItemsMessage (noItemsMessage_) + noItemsMessage (noItems) { items = MidiInput::getDevices(); @@ -87,15 +86,12 @@ public: setOutlineThickness (1); } - int getNumRows() + int getNumRows() override { return items.size(); } - void paintListBoxItem (int row, - Graphics& g, - int width, int height, - bool rowIsSelected) + void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override { if (isPositiveAndBelow (row, items.size())) { @@ -118,7 +114,7 @@ public: } } - void listBoxItemClicked (int row, const MouseEvent& e) + void listBoxItemClicked (int row, const MouseEvent& e) override { selectRow (row); @@ -126,12 +122,12 @@ public: flipEnablement (row); } - void listBoxItemDoubleClicked (int row, const MouseEvent&) + void listBoxItemDoubleClicked (int row, const MouseEvent&) override { flipEnablement (row); } - void returnKeyPressed (int row) + void returnKeyPressed (int row) override { flipEnablement (row); } diff --git a/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp b/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp index f15f8656..f5cf5d6c 100644 --- a/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp +++ b/libs/juce/source/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp @@ -342,7 +342,7 @@ public: } private: - Array data; + Array data; int peakLevel; void ensureSize (const int thumbSamples) @@ -414,7 +414,7 @@ public: } private: - Array data; + Array data; double cachedStart, cachedTimePerPixel; int numChannelsCached, numSamplesCached; bool cacheNeedsRefilling; diff --git a/libs/juce/source/modules/juce_core/containers/juce_AbstractFifo.cpp b/libs/juce/source/modules/juce_core/containers/juce_AbstractFifo.cpp index c14625a2..65b1615c 100644 --- a/libs/juce/source/modules/juce_core/containers/juce_AbstractFifo.cpp +++ b/libs/juce/source/modules/juce_core/containers/juce_AbstractFifo.cpp @@ -35,7 +35,7 @@ AbstractFifo::AbstractFifo (const int capacity) noexcept AbstractFifo::~AbstractFifo() {} int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } -int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady(); } +int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady() - 1; } int AbstractFifo::getNumReady() const noexcept { diff --git a/libs/juce/source/modules/juce_core/containers/juce_Array.h b/libs/juce/source/modules/juce_core/containers/juce_Array.h index 8c0f5926..375576ad 100644 --- a/libs/juce/source/modules/juce_core/containers/juce_Array.h +++ b/libs/juce/source/modules/juce_core/containers/juce_Array.h @@ -42,7 +42,7 @@ - it must be able to be relocated in memory by a memcpy without this causing any problems - so objects whose functionality relies on external pointers or references to themselves can not be used. - You can of course have an array of pointers to any kind of object, e.g. Array , but if + You can of course have an array of pointers to any kind of object, e.g. Array, but if you do this, the array doesn't take any ownership of the objects - see the OwnedArray class or the ReferenceCountedArray class for more powerful ways of holding lists of objects. diff --git a/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.cpp b/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.cpp index 439a2637..739f7158 100644 --- a/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.cpp +++ b/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.cpp @@ -31,7 +31,7 @@ DynamicObject::DynamicObject() } DynamicObject::DynamicObject (const DynamicObject& other) - : properties (other.properties) + : ReferenceCountedObject(), properties (other.properties) { } diff --git a/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.h b/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.h index b2ba93a3..5c624a4d 100644 --- a/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.h +++ b/libs/juce/source/modules/juce_core/containers/juce_DynamicObject.h @@ -58,7 +58,7 @@ public: virtual bool hasProperty (const Identifier& propertyName) const; /** Returns a named property. - This returns a void if no such property exists. + This returns var::null if no such property exists. */ virtual var getProperty (const Identifier& propertyName) const; diff --git a/libs/juce/source/modules/juce_core/files/juce_File.h b/libs/juce/source/modules/juce_core/files/juce_File.h index cfcba5e6..28c2e706 100644 --- a/libs/juce/source/modules/juce_core/files/juce_File.h +++ b/libs/juce/source/modules/juce_core/files/juce_File.h @@ -361,6 +361,14 @@ public: */ File getLinkedTarget() const; + /** Returns a unique identifier for the file, if one is available. + + Depending on the OS and file-system, this may be a unix inode number or + a win32 file identifier, or 0 if it fails to find one. The number will + be unique on the filesystem, but not globally. + */ + uint64 getFileIdentifier() const; + //============================================================================== /** Returns the last modification time of this file. diff --git a/libs/juce/source/modules/juce_core/files/juce_FileInputStream.cpp b/libs/juce/source/modules/juce_core/files/juce_FileInputStream.cpp index 426f3adc..801ccfaa 100644 --- a/libs/juce/source/modules/juce_core/files/juce_FileInputStream.cpp +++ b/libs/juce/source/modules/juce_core/files/juce_FileInputStream.cpp @@ -28,40 +28,33 @@ int64 juce_fileSetPosition (void* handle, int64 pos); + //============================================================================== FileInputStream::FileInputStream (const File& f) : file (f), fileHandle (nullptr), currentPosition (0), - status (Result::ok()), - needToSeek (true) + status (Result::ok()) { openHandle(); } -FileInputStream::~FileInputStream() -{ - closeHandle(); -} - -//============================================================================== int64 FileInputStream::getTotalLength() { + // You should always check that a stream opened successfully before using it! + jassert (openedOk()); + return file.getSize(); } int FileInputStream::read (void* buffer, int bytesToRead) { + // You should always check that a stream opened successfully before using it! jassert (openedOk()); - jassert (buffer != nullptr && bytesToRead >= 0); - - if (needToSeek) - { - if (juce_fileSetPosition (fileHandle, currentPosition) < 0) - return 0; - needToSeek = false; - } + // The buffer should never be null, and a negative size is probably a + // sign that something is broken! + jassert (buffer != nullptr && bytesToRead >= 0); const size_t num = readInternal (buffer, (size_t) bytesToRead); currentPosition += num; @@ -81,15 +74,11 @@ int64 FileInputStream::getPosition() bool FileInputStream::setPosition (int64 pos) { + // You should always check that a stream opened successfully before using it! jassert (openedOk()); if (pos != currentPosition) - { - pos = jlimit ((int64) 0, getTotalLength(), pos); - - needToSeek |= (currentPosition != pos); - currentPosition = pos; - } + currentPosition = juce_fileSetPosition (fileHandle, pos); - return true; + return currentPosition == pos; } diff --git a/libs/juce/source/modules/juce_core/files/juce_FileInputStream.h b/libs/juce/source/modules/juce_core/files/juce_FileInputStream.h index fd5a5c61..35963baa 100644 --- a/libs/juce/source/modules/juce_core/files/juce_FileInputStream.h +++ b/libs/juce/source/modules/juce_core/files/juce_FileInputStream.h @@ -40,10 +40,11 @@ class JUCE_API FileInputStream : public InputStream { public: //============================================================================== - /** Creates a FileInputStream. + /** Creates a FileInputStream to read from the given file. - @param fileToRead the file to read from - if the file can't be accessed for some - reason, then the stream will just contain no data + After creating a FileInputStream, you should use openedOk() or failedToOpen() + to make sure that it's OK before trying to read from it! If it failed, you + can call getStatus() to get more error information. */ explicit FileInputStream (const File& fileToRead); @@ -73,24 +74,23 @@ public: //============================================================================== int64 getTotalLength() override; - int read (void* destBuffer, int maxBytesToRead) override; + int read (void*, int) override; bool isExhausted() override; int64 getPosition() override; - bool setPosition (int64 pos) override; + bool setPosition (int64) override; private: //============================================================================== - File file; + const File file; void* fileHandle; int64 currentPosition; Result status; - bool needToSeek; void openHandle(); - void closeHandle(); - size_t readInternal (void* buffer, size_t numBytes); + size_t readInternal (void*, size_t); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) }; + #endif // JUCE_FILEINPUTSTREAM_H_INCLUDED diff --git a/libs/juce/source/modules/juce_core/javascript/juce_Javascript.cpp b/libs/juce/source/modules/juce_core/javascript/juce_Javascript.cpp index 00dc44b8..16641d64 100644 --- a/libs/juce/source/modules/juce_core/javascript/juce_Javascript.cpp +++ b/libs/juce/source/modules/juce_core/javascript/juce_Javascript.cpp @@ -768,7 +768,7 @@ struct JavascriptEngine::RootObject : public DynamicObject { FunctionObject() noexcept {} - FunctionObject (const FunctionObject& other) : functionCode (other.functionCode) + FunctionObject (const FunctionObject& other) : DynamicObject(), functionCode (other.functionCode) { ExpressionTreeBuilder tb (functionCode); tb.parseFunctionParamsAndBody (*this); diff --git a/libs/juce/source/modules/juce_core/memory/juce_SharedResourcePointer.h b/libs/juce/source/modules/juce_core/memory/juce_SharedResourcePointer.h index d765af40..53d314b4 100644 --- a/libs/juce/source/modules/juce_core/memory/juce_SharedResourcePointer.h +++ b/libs/juce/source/modules/juce_core/memory/juce_SharedResourcePointer.h @@ -139,7 +139,7 @@ private: static SharedObjectHolder& getSharedObjectHolder() noexcept { - static char holder [sizeof (SharedObjectHolder)] = { 0 }; + static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 }; return *reinterpret_cast (holder); } diff --git a/libs/juce/source/modules/juce_core/native/java/JuceAppActivity.java b/libs/juce/source/modules/juce_core/native/java/JuceAppActivity.java index 418b032a..882d0e0c 100644 --- a/libs/juce/source/modules/juce_core/native/java/JuceAppActivity.java +++ b/libs/juce/source/modules/juce_core/native/java/JuceAppActivity.java @@ -399,16 +399,21 @@ public final class JuceAppActivity extends Activity private native void handleKeyDown (long host, int keycode, int textchar); private native void handleKeyUp (long host, int keycode, int textchar); - public void showKeyboard (boolean shouldShow) + public void showKeyboard (String type) { InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE); if (imm != null) { - if (shouldShow) - imm.showSoftInput (this, InputMethodManager.SHOW_FORCED); + if (type.length() > 0) + { + imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); + imm.setInputMethod (getWindowToken(), type); + } else + { imm.hideSoftInputFromWindow (getWindowToken(), 0); + } } } diff --git a/libs/juce/source/modules/juce_core/native/juce_android_SystemStats.cpp b/libs/juce/source/modules/juce_core/native/juce_android_SystemStats.cpp index 472f08bc..8223b892 100644 --- a/libs/juce/source/modules/juce_core/native/juce_android_SystemStats.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_android_SystemStats.cpp @@ -179,6 +179,16 @@ namespace AndroidStatsHelpers JuceAppActivity.getLocaleValue, isRegion))); } + + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) + DECLARE_JNI_CLASS (BuildClass, "android/os/Build"); + #undef JNI_CLASS_MEMBERS + + String getAndroidOsBuildValue (const char* fieldName) + { + return juceString (LocalRef ((jstring) getEnv()->GetStaticObjectField ( + BuildClass, getEnv()->GetStaticFieldID (BuildClass, fieldName, "Ljava/lang/String;")))); + } } //============================================================================== @@ -194,7 +204,8 @@ String SystemStats::getOperatingSystemName() String SystemStats::getDeviceDescription() { - return String::empty; + return AndroidStatsHelpers::getAndroidOsBuildValue ("MODEL") + + "-" + AndroidStatsHelpers::getAndroidOsBuildValue ("SERIAL"); } bool SystemStats::isOperatingSystem64Bit() @@ -262,7 +273,7 @@ String SystemStats::getComputerName() String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } -String SystemStats::getDisplayLanguage() { return getUserLanguage(); } +String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } //============================================================================== void CPUInformation::initialise() noexcept diff --git a/libs/juce/source/modules/juce_core/native/juce_linux_Network.cpp b/libs/juce/source/modules/juce_core/native/juce_linux_Network.cpp index 36aeeb80..7543a77d 100644 --- a/libs/juce/source/modules/juce_core/native/juce_linux_Network.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_linux_Network.cpp @@ -46,7 +46,10 @@ void MACAddress::findAllAddresses (Array& result) && (ifr.ifr_flags & IFF_LOOPBACK) == 0 && ioctl (s, SIOCGIFHWADDR, &ifr) == 0) { - result.addIfNotAlreadyThere (MACAddress ((const uint8*) ifr.ifr_hwaddr.sa_data)); + MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data); + + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); } } diff --git a/libs/juce/source/modules/juce_core/native/juce_linux_SystemStats.cpp b/libs/juce/source/modules/juce_core/native/juce_linux_SystemStats.cpp index a98c4905..939edb47 100644 --- a/libs/juce/source/modules/juce_core/native/juce_linux_SystemStats.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_linux_SystemStats.cpp @@ -139,7 +139,7 @@ static String getLocaleValue (nl_item key) String SystemStats::getUserLanguage() { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); } String SystemStats::getUserRegion() { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); } -String SystemStats::getDisplayLanguage() { return getUserLanguage(); } +String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } //============================================================================== void CPUInformation::initialise() noexcept diff --git a/libs/juce/source/modules/juce_core/native/juce_mac_Network.mm b/libs/juce/source/modules/juce_core/native/juce_mac_Network.mm index 5cce0418..130759e6 100644 --- a/libs/juce/source/modules/juce_core/native/juce_mac_Network.mm +++ b/libs/juce/source/modules/juce_core/native/juce_mac_Network.mm @@ -39,12 +39,17 @@ void MACAddress::findAllAddresses (Array& result) { const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; - #ifndef IFT_ETHER - #define IFT_ETHER 6 - #endif + #ifndef IFT_ETHER + enum { IFT_ETHER = 6 }; + #endif if (sadd->sdl_type == IFT_ETHER) - result.addIfNotAlreadyThere (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); + { + MACAddress ma (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); + + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); + } } } diff --git a/libs/juce/source/modules/juce_core/native/juce_mac_SystemStats.mm b/libs/juce/source/modules/juce_core/native/juce_mac_SystemStats.mm index 45967ee5..01e333dc 100644 --- a/libs/juce/source/modules/juce_core/native/juce_mac_SystemStats.mm +++ b/libs/juce/source/modules/juce_core/native/juce_mac_SystemStats.mm @@ -124,7 +124,7 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() return iOS; #else StringArray parts; - parts.addTokens (getOSXVersion(), ".", String()); + parts.addTokens (getOSXVersion(), ".", StringRef()); jassert (parts[0].getIntValue() == 10); const int major = parts[1].getIntValue(); diff --git a/libs/juce/source/modules/juce_core/native/juce_posix_SharedCode.h b/libs/juce/source/modules/juce_core/native/juce_posix_SharedCode.h index ca5768dd..1be83e58 100644 --- a/libs/juce/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/libs/juce/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -280,6 +280,12 @@ int64 File::getSize() const return juce_stat (fullPath, info) ? info.st_size : 0; } +uint64 File::getFileIdentifier() const +{ + juce_statStruct info; + return juce_stat (fullPath, info) ? (uint64) info.st_ino : 0; +} + //============================================================================== bool File::hasWriteAccess() const { @@ -391,13 +397,10 @@ void FileInputStream::openHandle() status = getResultForErrno(); } -void FileInputStream::closeHandle() +FileInputStream::~FileInputStream() { if (fileHandle != 0) - { close (getFD (fileHandle)); - fileHandle = 0; - } } size_t FileInputStream::readInternal (void* const buffer, const size_t numBytes) @@ -1017,12 +1020,12 @@ public: // we're the child process.. close (pipeHandles[0]); // close the read handle - if ((streamFlags | wantStdOut) != 0) + if ((streamFlags & wantStdOut) != 0) dup2 (pipeHandles[1], 1); // turns the pipe into stdout else close (STDOUT_FILENO); - if ((streamFlags | wantStdErr) != 0) + if ((streamFlags & wantStdErr) != 0) dup2 (pipeHandles[1], 2); else close (STDERR_FILENO); @@ -1210,7 +1213,7 @@ private: { mach_timebase_info_data_t timebase; (void) mach_timebase_info (&timebase); - delta = (((uint64_t) (millis * 1000000.0)) * timebase.numer) / timebase.denom; + delta = (((uint64_t) (millis * 1000000.0)) * timebase.denom) / timebase.numer; time = mach_absolute_time(); } diff --git a/libs/juce/source/modules/juce_core/native/juce_win32_Files.cpp b/libs/juce/source/modules/juce_core/native/juce_win32_Files.cpp index 6f0b3f4f..80ad084e 100644 --- a/libs/juce/source/modules/juce_core/native/juce_win32_Files.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_win32_Files.cpp @@ -234,7 +234,7 @@ void FileInputStream::openHandle() status = WindowsFileHelpers::getResultForLastError(); } -void FileInputStream::closeHandle() +FileInputStream::~FileInputStream() { CloseHandle ((HANDLE) fileHandle); } @@ -474,6 +474,28 @@ int64 File::getVolumeTotalSize() const return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true); } +uint64 File::getFileIdentifier() const +{ + uint64 result = 0; + + HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), + GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + + if (h != INVALID_HANDLE_VALUE) + { + BY_HANDLE_FILE_INFORMATION info; + zerostruct (info); + + if (GetFileInformationByHandle (h, &info)) + result = (((uint64) info.nFileIndexHigh) << 32) | info.nFileIndexLow; + + CloseHandle (h); + } + + return result; +} + //============================================================================== bool File::isOnCDRomDrive() const { diff --git a/libs/juce/source/modules/juce_core/native/juce_win32_Network.cpp b/libs/juce/source/modules/juce_core/native/juce_win32_Network.cpp index 22e50279..e7af2c1b 100644 --- a/libs/juce/source/modules/juce_core/native/juce_win32_Network.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_win32_Network.cpp @@ -287,7 +287,7 @@ private: if (bytesToDo > 0 && ! InternetWriteFile (request, - static_cast (postData.getData()) + bytesSent, + static_cast (postData.getData()) + bytesSent, (DWORD) bytesToDo, &bytesDone)) { break; @@ -342,7 +342,13 @@ struct GetAdaptersInfoHelper namespace MACAddressHelpers { - void getViaGetAdaptersInfo (Array& result) + static void addAddress (Array& result, const MACAddress& ma) + { + if (! ma.isNull()) + result.addIfNotAlreadyThere (ma); + } + + static void getViaGetAdaptersInfo (Array& result) { GetAdaptersInfoHelper gah; @@ -350,11 +356,11 @@ namespace MACAddressHelpers { for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) if (adapter->AddressLength >= 6) - result.addIfNotAlreadyThere (MACAddress (adapter->Address)); + addAddress (result, MACAddress (adapter->Address)); } } - void getViaNetBios (Array& result) + static void getViaNetBios (Array& result) { DynamicLibrary dll ("netapi32.dll"); JUCE_LOAD_WINAPI_FUNCTION (dll, Netbios, NetbiosCall, UCHAR, (PNCB)) @@ -396,7 +402,7 @@ namespace MACAddressHelpers ncb.ncb_length = sizeof (ASTAT); if (NetbiosCall (&ncb) == 0 && astat.adapt.adapter_type == 0xfe) - result.addIfNotAlreadyThere (MACAddress (astat.adapt.adapter_address)); + addAddress (result, MACAddress (astat.adapt.adapter_address)); } } } diff --git a/libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp b/libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp index 6bac9af0..86f10516 100644 --- a/libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp +++ b/libs/juce/source/modules/juce_core/native/juce_win32_SystemStats.cpp @@ -428,8 +428,16 @@ String SystemStats::getDisplayLanguage() DynamicLibrary dll ("kernel32.dll"); JUCE_LOAD_WINAPI_FUNCTION (dll, GetUserDefaultUILanguage, getUserDefaultUILanguage, LANGID, (void)) - if (getUserDefaultUILanguage != nullptr) - return getLocaleValue (MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT), LOCALE_SISO639LANGNAME, "en"); + if (getUserDefaultUILanguage == nullptr) + return "en"; - return "en"; + const DWORD langID = MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT); + + String mainLang (getLocaleValue (langID, LOCALE_SISO639LANGNAME, "en")); + String region (getLocaleValue (langID, LOCALE_SISO3166CTRYNAME, nullptr)); + + if (region.isNotEmpty()) + mainLang << '-' << region; + + return mainLang; } diff --git a/libs/juce/source/modules/juce_core/network/juce_URL.cpp b/libs/juce/source/modules/juce_core/network/juce_URL.cpp index 496b00b9..49db1f9f 100644 --- a/libs/juce/source/modules/juce_core/network/juce_URL.cpp +++ b/libs/juce/source/modules/juce_core/network/juce_URL.cpp @@ -241,8 +241,6 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPost { MemoryOutputStream data (headersAndPostData, false); - data << URLHelpers::getMangledParameters (*this); - if (filesToUpload.size() > 0) { // (this doesn't currently support mixing custom post-data with uploads..) @@ -285,7 +283,8 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPost } else { - data << postData; + data << URLHelpers::getMangledParameters (*this) + << postData; // if the user-supplied headers didn't contain a content-type, add one now.. if (! headers.containsIgnoreCase ("Content-Type")) diff --git a/libs/juce/source/modules/juce_core/system/juce_SystemStats.h b/libs/juce/source/modules/juce_core/system/juce_SystemStats.h index d9132f5a..833bf5a0 100644 --- a/libs/juce/source/modules/juce_core/system/juce_SystemStats.h +++ b/libs/juce/source/modules/juce_core/system/juce_SystemStats.h @@ -118,7 +118,9 @@ public: static String getUserRegion(); /** Returns the user's display language. - The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2) + The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2). + Note that depending on the OS and region, this may also be followed by a dash + and a sub-region code, e.g "en-GB" */ static String getDisplayLanguage(); diff --git a/libs/juce/source/modules/juce_core/system/juce_TargetPlatform.h b/libs/juce/source/modules/juce_core/system/juce_TargetPlatform.h index dcdd921c..86adc385 100644 --- a/libs/juce/source/modules/juce_core/system/juce_TargetPlatform.h +++ b/libs/juce/source/modules/juce_core/system/juce_TargetPlatform.h @@ -161,7 +161,7 @@ #define JUCE_32BIT 1 #endif - #ifdef __arm__ + #if defined (__arm__) || defined (__arm64__) #define JUCE_ARM 1 #elif __MMX__ || __SSE__ || __amd64__ #define JUCE_INTEL 1 diff --git a/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.cpp b/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.cpp index 4563a8a9..e6118d31 100644 --- a/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.cpp +++ b/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.cpp @@ -194,11 +194,11 @@ LocalisedStrings* LocalisedStrings::getCurrentMappings() String LocalisedStrings::translateWithCurrentMappings (const String& text) { return juce::translate (text); } String LocalisedStrings::translateWithCurrentMappings (const char* text) { return juce::translate (text); } -String translate (const String& text) { return juce::translate (text, text); } -String translate (const char* text) { return juce::translate (String (text)); } -String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); } +JUCE_API String translate (const String& text) { return juce::translate (text, text); } +JUCE_API String translate (const char* text) { return juce::translate (String (text)); } +JUCE_API String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); } -String translate (const String& text, const String& resultIfNotFound) +JUCE_API String translate (const String& text, const String& resultIfNotFound) { const SpinLock::ScopedLockType sl (currentMappingsLock); diff --git a/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.h b/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.h index e8f70f73..a594919b 100644 --- a/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.h +++ b/libs/juce/source/modules/juce_core/text/juce_LocalisedStrings.h @@ -226,22 +226,22 @@ private: /** Uses the LocalisedStrings class to translate the given string literal. @see LocalisedStrings */ -String translate (const String& stringLiteral); +JUCE_API String translate (const String& stringLiteral); /** Uses the LocalisedStrings class to translate the given string literal. @see LocalisedStrings */ -String translate (const char* stringLiteral); +JUCE_API String translate (const char* stringLiteral); /** Uses the LocalisedStrings class to translate the given string literal. @see LocalisedStrings */ -String translate (CharPointer_UTF8 stringLiteral); +JUCE_API String translate (CharPointer_UTF8 stringLiteral); /** Uses the LocalisedStrings class to translate the given string literal. @see LocalisedStrings */ -String translate (const String& stringLiteral, const String& resultIfNotFound); +JUCE_API String translate (const String& stringLiteral, const String& resultIfNotFound); #endif // JUCE_LOCALISEDSTRINGS_H_INCLUDED diff --git a/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp b/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp index f20b1447..ece2121f 100644 --- a/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp +++ b/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.cpp @@ -53,7 +53,7 @@ File PropertiesFile::Options::getDefaultFile() const File dir (commonToAllUsers ? "/Library/" : "~/Library/"); - if (osxLibrarySubFolder != "Preferences" && osxLibrarySubFolder != "Application Support") + if (osxLibrarySubFolder != "Preferences" && ! osxLibrarySubFolder.startsWith ("Application Support")) { /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple have changed their advice, and now stipulate that settings should go in "Library/Application Support". @@ -61,7 +61,8 @@ File PropertiesFile::Options::getDefaultFile() const Because older apps would be broken by a silent change in this class's behaviour, you must now explicitly set the osxLibrarySubFolder value to indicate which path you want to use. - In newer apps, you should always set this to "Application Support". + In newer apps, you should always set this to "Application Support" + or "Application Support/YourSubFolderName". If your app needs to load settings files that were created by older versions of juce and you want to maintain backwards-compatibility, then you can set this to "Preferences". diff --git a/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.h b/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.h index dc8929aa..f3d9551b 100644 --- a/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.h +++ b/libs/juce/source/modules/juce_data_structures/app_properties/juce_PropertiesFile.h @@ -85,7 +85,8 @@ public: Because older apps would be broken by a silent change in this class's behaviour, you must now explicitly set the osxLibrarySubFolder value to indicate which path you want to use. - In newer apps, you should always set this to "Application Support". + In newer apps, you should always set this to "Application Support" or + "Application Support/YourSubFolderName". If your app needs to load settings files that were created by older versions of juce and you want to maintain backwards-compatibility, then you can set this to "Preferences". diff --git a/libs/juce/source/modules/juce_data_structures/values/juce_Value.cpp b/libs/juce/source/modules/juce_data_structures/values/juce_Value.cpp index 477936ea..dee3a47c 100644 --- a/libs/juce/source/modules/juce_data_structures/values/juce_Value.cpp +++ b/libs/juce/source/modules/juce_data_structures/values/juce_Value.cpp @@ -22,76 +22,18 @@ ============================================================================== */ -class SharedValueSourceUpdater : public ReferenceCountedObject, - private AsyncUpdater -{ -public: - SharedValueSourceUpdater() : sourcesBeingIterated (nullptr) {} - ~SharedValueSourceUpdater() { masterReference.clear(); } - - void update (Value::ValueSource* const source) - { - sourcesNeedingAnUpdate.add (source); - - if (sourcesBeingIterated == nullptr) - triggerAsyncUpdate(); - } - - void valueDeleted (Value::ValueSource* const source) - { - sourcesNeedingAnUpdate.removeValue (source); - - if (sourcesBeingIterated != nullptr) - sourcesBeingIterated->removeValue (source); - } - - WeakReference::Master masterReference; - -private: - typedef SortedSet SourceSet; - SourceSet sourcesNeedingAnUpdate; - SourceSet* sourcesBeingIterated; - - void handleAsyncUpdate() override - { - const ReferenceCountedObjectPtr localRef (this); - - { - const ScopedValueSetter inside (sourcesBeingIterated, nullptr, nullptr); - int maxLoops = 10; - - while (sourcesNeedingAnUpdate.size() > 0) - { - if (--maxLoops == 0) - { - triggerAsyncUpdate(); - break; - } - - SourceSet sources; - sources.swapWith (sourcesNeedingAnUpdate); - sourcesBeingIterated = &sources; - - for (int i = sources.size(); --i >= 0;) - if (i < sources.size()) - sources.getUnchecked(i)->sendChangeMessage (true); - } - } - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedValueSourceUpdater) -}; - -static WeakReference sharedUpdater; - Value::ValueSource::ValueSource() { } Value::ValueSource::~ValueSource() { - if (asyncUpdater != nullptr) - static_cast (asyncUpdater.get())->valueDeleted (this); + cancelPendingUpdate(); +} + +void Value::ValueSource::handleAsyncUpdate() +{ + sendChangeMessage (true); } void Value::ValueSource::sendChangeMessage (const bool synchronous) @@ -103,7 +45,8 @@ void Value::ValueSource::sendChangeMessage (const bool synchronous) if (synchronous) { const ReferenceCountedObjectPtr localRef (this); - asyncUpdater = nullptr; + + cancelPendingUpdate(); for (int i = numListeners; --i >= 0;) if (Value* const v = valuesWithListeners[i]) @@ -111,22 +54,7 @@ void Value::ValueSource::sendChangeMessage (const bool synchronous) } else { - SharedValueSourceUpdater* updater = static_cast (asyncUpdater.get()); - - if (updater == nullptr) - { - if (sharedUpdater == nullptr) - { - asyncUpdater = updater = new SharedValueSourceUpdater(); - sharedUpdater = updater; - } - else - { - asyncUpdater = updater = sharedUpdater.get(); - } - } - - updater->update (this); + triggerAsyncUpdate(); } } } @@ -195,13 +123,13 @@ Value& Value::operator= (const Value& other) #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS Value::Value (Value&& other) noexcept - : value (static_cast &&> (other.value)) + : value (static_cast&&> (other.value)) { } Value& Value::operator= (Value&& other) noexcept { - value = static_cast &&> (other.value); + value = static_cast&&> (other.value); return *this; } #endif diff --git a/libs/juce/source/modules/juce_data_structures/values/juce_Value.h b/libs/juce/source/modules/juce_data_structures/values/juce_Value.h index 5b23e1ae..d67c173e 100644 --- a/libs/juce/source/modules/juce_data_structures/values/juce_Value.h +++ b/libs/juce/source/modules/juce_data_structures/values/juce_Value.h @@ -167,7 +167,8 @@ public: of a ValueSource object. If you're feeling adventurous, you can create your own custom ValueSource classes to allow Value objects to represent your own custom data items. */ - class JUCE_API ValueSource : public SingleThreadedReferenceCountedObject + class JUCE_API ValueSource : public ReferenceCountedObject, + private AsyncUpdater { public: ValueSource(); @@ -192,8 +193,10 @@ public: protected: //============================================================================== friend class Value; - SortedSet valuesWithListeners; - ReferenceCountedObjectPtr asyncUpdater; + SortedSet valuesWithListeners; + + private: + void handleAsyncUpdate() override; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource) }; @@ -210,8 +213,8 @@ public: private: //============================================================================== friend class ValueSource; - ReferenceCountedObjectPtr value; - ListenerList listeners; + ReferenceCountedObjectPtr value; + ListenerList listeners; void callListeners(); diff --git a/libs/juce/source/modules/juce_events/broadcasters/juce_AsyncUpdater.h b/libs/juce/source/modules/juce_events/broadcasters/juce_AsyncUpdater.h index f2b8d355..79ab6e1c 100644 --- a/libs/juce/source/modules/juce_events/broadcasters/juce_AsyncUpdater.h +++ b/libs/juce/source/modules/juce_events/broadcasters/juce_AsyncUpdater.h @@ -68,8 +68,8 @@ public: callback happens, this will cancel the handleAsyncUpdate() callback. Note that this method simply cancels the next callback - if a callback is already - in progress on a different thread, this won't block until it finishes, so there's - no guarantee that the callback isn't still running when you return from + in progress on a different thread, this won't block until the callback finishes, so + there's no guarantee that the callback isn't still running when the method returns. */ void cancelPendingUpdate() noexcept; diff --git a/libs/juce/source/modules/juce_events/native/juce_win32_Messaging.cpp b/libs/juce/source/modules/juce_events/native/juce_win32_Messaging.cpp index f1a78133..0bc0f781 100644 --- a/libs/juce/source/modules/juce_events/native/juce_win32_Messaging.cpp +++ b/libs/juce/source/modules/juce_events/native/juce_win32_Messaging.cpp @@ -100,7 +100,7 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPend using namespace WindowsMessageHelpers; MSG m; - if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, 0)) + if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE)) return false; if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) diff --git a/libs/juce/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/libs/juce/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 3442b1d4..a0dcd80f 100644 --- a/libs/juce/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/libs/juce/source/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -438,14 +438,14 @@ void Graphics::drawRect (Rectangle r, const float lineThickness) const //============================================================================== void Graphics::fillEllipse (const Rectangle& area) const { - fillEllipse (area.getX(), area.getY(), area.getWidth(), area.getHeight()); + Path p; + p.addEllipse (area); + fillPath (p); } -void Graphics::fillEllipse (float x, float y, float width, float height) const +void Graphics::fillEllipse (float x, float y, float w, float h) const { - Path p; - p.addEllipse (x, y, width, height); - fillPath (p); + fillEllipse (Rectangle (x, y, w, h)); } void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const diff --git a/libs/juce/source/modules/juce_graphics/fonts/juce_CustomTypeface.h b/libs/juce/source/modules/juce_graphics/fonts/juce_CustomTypeface.h index 0acc4f17..25db68b7 100644 --- a/libs/juce/source/modules/juce_graphics/fonts/juce_CustomTypeface.h +++ b/libs/juce/source/modules/juce_graphics/fonts/juce_CustomTypeface.h @@ -52,6 +52,11 @@ public: /** Loads a typeface from a previously saved stream. The stream must have been created by writeToStream(). + + NOTE! Since this class was written, support was added for loading real font files from + memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font + is more appropriate than using this class to store it in a proprietary format. + @see writeToStream */ explicit CustomTypeface (InputStream& serialisedTypefaceStream); @@ -116,7 +121,7 @@ public: NOTE! Since this class was written, support was added for loading real font files from memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font - is more appropriate than using this class to store it in a proprietory format. + is more appropriate than using this class to store it in a proprietary format. */ bool writeToStream (OutputStream& outputStream); diff --git a/libs/juce/source/modules/juce_graphics/fonts/juce_Font.h b/libs/juce/source/modules/juce_graphics/fonts/juce_Font.h index 85cac64d..6649d36d 100644 --- a/libs/juce/source/modules/juce_graphics/fonts/juce_Font.h +++ b/libs/juce/source/modules/juce_graphics/fonts/juce_Font.h @@ -169,7 +169,7 @@ public: */ static const String& getDefaultSansSerifFontName(); - /** Returns a typeface font family that represents the default sans-serif font. + /** Returns a typeface font family that represents the default serif font. Note that this method just returns a generic placeholder string that means "the default serif font" - it's not the actual font family of this font. @@ -178,7 +178,7 @@ public: */ static const String& getDefaultSerifFontName(); - /** Returns a typeface font family that represents the default sans-serif font. + /** Returns a typeface font family that represents the default monospaced font. Note that this method just returns a generic placeholder string that means "the default monospaced font" - it's not the actual font family of this font. @@ -187,10 +187,10 @@ public: */ static const String& getDefaultMonospacedFontName(); - /** Returns a typeface font style that represents the default sans-serif font. + /** Returns a font style name that represents the default style. Note that this method just returns a generic placeholder string that means "the default - font style" - it's not the actual font style of this font. + font style" - it's not the actual name of the font style of any particular font. @see setTypefaceStyle */ diff --git a/libs/juce/source/modules/juce_graphics/geometry/juce_Path.cpp b/libs/juce/source/modules/juce_graphics/geometry/juce_Path.cpp index 210844aa..5a6b2f86 100644 --- a/libs/juce/source/modules/juce_graphics/geometry/juce_Path.cpp +++ b/libs/juce/source/modules/juce_graphics/geometry/juce_Path.cpp @@ -436,9 +436,7 @@ void Path::addRectangle (const float x, const float y, data.elements [numElements++] = closeSubPathMarker; } -void Path::addRoundedRectangle (const float x, const float y, - const float w, const float h, - float csx, float csy) +void Path::addRoundedRectangle (float x, float y, float w, float h, float csx, float csy) { addRoundedRectangle (x, y, w, h, csx, csy, true, true, true, true); } @@ -498,9 +496,7 @@ void Path::addRoundedRectangle (const float x, const float y, const float w, con closeSubPath(); } -void Path::addRoundedRectangle (const float x, const float y, - const float w, const float h, - float cs) +void Path::addRoundedRectangle (float x, float y, float w, float h, float cs) { addRoundedRectangle (x, y, w, h, cs, cs); } @@ -527,15 +523,19 @@ void Path::addQuadrilateral (const float x1, const float y1, closeSubPath(); } -void Path::addEllipse (const float x, const float y, - const float w, const float h) +void Path::addEllipse (float x, float y, float w, float h) { - const float hw = w * 0.5f; + addEllipse (Rectangle (x, y, w, h)); +} + +void Path::addEllipse (Rectangle area) +{ + const float hw = area.getWidth() * 0.5f; const float hw55 = hw * 0.55f; - const float hh = h * 0.5f; + const float hh = area.getHeight() * 0.5f; const float hh55 = hh * 0.55f; - const float cx = x + hw; - const float cy = y + hh; + const float cx = area.getX() + hw; + const float cy = area.getY() + hh; startNewSubPath (cx, cy - hh); cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); diff --git a/libs/juce/source/modules/juce_graphics/geometry/juce_Path.h b/libs/juce/source/modules/juce_graphics/geometry/juce_Path.h index 7e364aa7..ac319f96 100644 --- a/libs/juce/source/modules/juce_graphics/geometry/juce_Path.h +++ b/libs/juce/source/modules/juce_graphics/geometry/juce_Path.h @@ -391,13 +391,17 @@ public: float x4, float y4); /** Adds an ellipse to the path. - The shape is added as a new sub-path. (Any currently open paths will be left open). - @see addArc */ void addEllipse (float x, float y, float width, float height); + /** Adds an ellipse to the path. + The shape is added as a new sub-path. (Any currently open paths will be left open). + @see addArc + */ + void addEllipse (Rectangle area); + /** Adds an elliptical arc to the current path. Note that when specifying the start and end angles, the curve will be drawn either clockwise diff --git a/libs/juce/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm b/libs/juce/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm index 03e47fff..44e344f1 100644 --- a/libs/juce/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm +++ b/libs/juce/source/modules/juce_graphics/native/juce_mac_CoreGraphicsContext.mm @@ -29,7 +29,7 @@ class CoreGraphicsImage : public ImagePixelData { public: CoreGraphicsImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage) - : ImagePixelData (format, w, h) + : ImagePixelData (format, w, h), cachedImageRef (0) { pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1); lineStride = (pixelStride * jmax (1, width) + 3) & ~3; @@ -47,11 +47,13 @@ public: ~CoreGraphicsImage() { + freeCachedImageRef(); CGContextRelease (context); } LowLevelGraphicsContext* createLowLevelContext() override { + freeCachedImageRef(); sendDataChangeMessage(); return new CoreGraphicsContext (context, height, 1.0f); } @@ -64,7 +66,10 @@ public: bitmap.pixelStride = pixelStride; if (mode != Image::BitmapData::readOnly) + { + freeCachedImageRef(); sendDataChangeMessage(); + } } ImagePixelData* clone() override @@ -77,6 +82,27 @@ public: ImageType* createType() const override { return new NativeImageType(); } //============================================================================== + static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace) + { + CoreGraphicsImage* const cgim = dynamic_cast (juceImage.getPixelData()); + + if (cgim != nullptr && cgim->cachedImageRef != 0) + { + CGImageRetain (cgim->cachedImageRef); + return cgim->cachedImageRef; + } + + CGImageRef ref = createImage (juceImage, colourSpace, false); + + if (cgim != nullptr) + { + CGImageRetain (ref); + cgim->cachedImageRef = ref; + } + + return ref; + } + static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, const bool mustOutliveSource) { const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly); @@ -106,10 +132,20 @@ public: //============================================================================== CGContextRef context; + CGImageRef cachedImageRef; HeapBlock imageData; int pixelStride, lineStride; private: + void freeCachedImageRef() + { + if (cachedImageRef != 0) + { + CGImageRelease (cachedImageRef); + cachedImageRef = 0; + } + } + static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) { #if JUCE_BIG_ENDIAN @@ -454,7 +490,7 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans { const int iw = sourceImage.getWidth(); const int ih = sourceImage.getHeight(); - CGImageRef image = CoreGraphicsImage::createImage (sourceImage, rgbColourSpace, false); + CGImageRef image = CoreGraphicsImage::getCachedImageRef (sourceImage, rgbColourSpace); CGContextSaveGState (context); CGContextSetAlpha (context, state->fillType.getOpacity()); diff --git a/libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm b/libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm index 7b1ad706..81a29a80 100644 --- a/libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/libs/juce/source/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -1280,15 +1280,25 @@ Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) } #if JUCE_CORETEXT_AVAILABLE -static bool containsNoMemoryTypefaces (const AttributedString& text) +static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) { const int numCharacterAttributes = text.getNumAttributes(); for (int i = 0; i < numCharacterAttributes; ++i) + { if (const Font* const f = text.getAttribute (i)->getFont()) + { if (OSXTypeface* tf = dynamic_cast (f->getTypeface())) + { if (tf->isMemoryFont) return false; + } + else if (dynamic_cast (f->getTypeface()) != nullptr) + { + return false; + } + } + } return true; } @@ -1299,7 +1309,7 @@ bool TextLayout::createNativeLayout (const AttributedString& text) #if JUCE_CORETEXT_AVAILABLE // Seems to be an unfathomable bug in CoreText which prevents the layout working with // typefaces that were loaded from memory, so have to fallback if we hit any of those.. - if (containsNoMemoryTypefaces (text)) + if (canAllTypefacesBeUsedInLayout (text)) { CoreTextTypeLayout::createLayout (*this, text); return true; diff --git a/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp b/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp index 81dd0c37..c31d1874 100644 --- a/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp +++ b/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.cpp @@ -91,7 +91,7 @@ ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const C while (target != nullptr) { - Array commandIDs; + Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) @@ -113,7 +113,7 @@ ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const C if (target != nullptr) { - Array commandIDs; + Array commandIDs; target->getAllCommands (commandIDs); if (commandIDs.contains (commandID)) diff --git a/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h b/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h index 474c4ed4..c14faa81 100644 --- a/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h +++ b/libs/juce/source/modules/juce_gui_basics/commands/juce_ApplicationCommandTarget.h @@ -134,7 +134,7 @@ public: Your target should add all the command IDs that it handles to the array that is passed-in. */ - virtual void getAllCommands (Array & commands) = 0; + virtual void getAllCommands (Array& commands) = 0; /** This must provide details about one of the commands that this target can perform. diff --git a/libs/juce/source/modules/juce_gui_basics/components/juce_Component.cpp b/libs/juce/source/modules/juce_gui_basics/components/juce_Component.cpp index 7e3b9f47..8734ad71 100644 --- a/libs/juce/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/libs/juce/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -659,7 +659,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) bool wasFullscreen = false; bool wasMinimised = false; - ComponentBoundsConstrainer* currentConstainer = nullptr; + ComponentBoundsConstrainer* currentConstrainer = nullptr; Rectangle oldNonFullScreenBounds; int oldRenderingEngine = -1; @@ -669,7 +669,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) wasFullscreen = peer->isFullScreen(); wasMinimised = peer->isMinimised(); - currentConstainer = peer->getConstrainer(); + currentConstrainer = peer->getConstrainer(); oldNonFullScreenBounds = peer->getNonFullScreenBounds(); oldRenderingEngine = peer->getCurrentRenderingEngine(); @@ -720,7 +720,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) peer->setAlwaysOnTop (true); #endif - peer->setConstrainer (currentConstainer); + peer->setConstrainer (currentConstrainer); repaint(); internalHierarchyChanged(); diff --git a/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp b/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp index 00f170ce..a6e7b071 100644 --- a/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp +++ b/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.cpp @@ -234,6 +234,17 @@ void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFo } } +bool ModalComponentManager::cancelAllModalComponents() +{ + const int numModal = getNumModalComponents(); + + for (int i = numModal; --i >= 0;) + if (Component* const c = getModalComponent(i)) + c->exitModalState (0); + + return numModal > 0; +} + #if JUCE_MODAL_LOOPS_PERMITTED class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback { diff --git a/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h b/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h index d927202e..b21421eb 100644 --- a/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h +++ b/libs/juce/source/modules/juce_gui_basics/components/juce_ModalComponentManager.h @@ -107,6 +107,11 @@ public: /** Brings any modal components to the front. */ void bringModalComponentsToFront (bool topOneShouldGrabFocus = true); + /** Calls exitModalState (0) on any components that are currently modal. + @returns true if any components were modal; false if nothing needed cancelling + */ + bool cancelAllModalComponents(); + #if JUCE_MODAL_LOOPS_PERMITTED /** Runs the event loop until the currently topmost modal component is dismissed, and returns the exit code for that component. diff --git a/libs/juce/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp b/libs/juce/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp index b301a9d5..a61f0ac2 100644 --- a/libs/juce/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp +++ b/libs/juce/source/modules/juce_gui_basics/drawables/juce_SVGParser.cpp @@ -349,6 +349,11 @@ public: if (! carryOn) break; } + + // paths that finish back at their start position often seem to be + // left without a 'z', so need to be closed explicitly.. + if (path.getCurrentPosition() == subpathStart) + path.closeSubPath(); } private: diff --git a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp index adf1a747..29618a36 100644 --- a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp +++ b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FileChooserDialogBox.cpp @@ -203,7 +203,7 @@ void FileChooserDialogBox::okButtonPressed() { AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, TRANS("File already exists"), - TRANS("There's already a file called: FLMN") + TRANS("There's already a file called: FLNM") .replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName()) + "\n\n" + TRANS("Are you sure you want to overwrite it?"), diff --git a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp index e4f18bcd..4e5036ec 100644 --- a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp +++ b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.cpp @@ -68,6 +68,13 @@ void FilenameComponent::resized() getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton); } +KeyboardFocusTraverser* FilenameComponent::createFocusTraverser() +{ + // This prevents the sub-components from grabbing focus if the + // FilenameComponent has been set to refuse focus. + return getWantsKeyboardFocus() ? Component::createFocusTraverser() : nullptr; +} + void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText) { browseButtonText = newBrowseButtonText; diff --git a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h index 4456c11e..bbae76ee 100644 --- a/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h +++ b/libs/juce/source/modules/juce_gui_basics/filebrowser/juce_FilenameComponent.h @@ -205,6 +205,8 @@ public: void fileDragEnter (const StringArray&, int, int) override; /** @internal */ void fileDragExit (const StringArray&) override; + /** @internal */ + KeyboardFocusTraverser* createFocusTraverser() override; private: //============================================================================== diff --git a/libs/juce/source/modules/juce_gui_basics/keyboard/juce_TextInputTarget.h b/libs/juce/source/modules/juce_gui_basics/keyboard/juce_TextInputTarget.h index eaf4bb04..9770f5dc 100644 --- a/libs/juce/source/modules/juce_gui_basics/keyboard/juce_TextInputTarget.h +++ b/libs/juce/source/modules/juce_gui_basics/keyboard/juce_TextInputTarget.h @@ -71,6 +71,23 @@ public: /** Returns the position of the caret, relative to the component's origin. */ virtual Rectangle getCaretRectangle() = 0; + + /** A set of possible on-screen keyboard types, for use in the + getKeyboardType() method. + */ + enum VirtualKeyboardType + { + textKeyboard = 0, + numericKeyboard, + urlKeyboard, + emailAddressKeyboard, + phoneNumberKeyboard + }; + + /** Returns the target's preference for the type of keyboard that would be most appropriate. + This may be ignored, depending on the capabilities of the OS. + */ + virtual VirtualKeyboardType getKeyboardType() { return textKeyboard; } }; diff --git a/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp b/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp index f4212595..4e832af8 100644 --- a/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp +++ b/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V2.cpp @@ -452,8 +452,7 @@ void LookAndFeel_V2::drawAlertBox (Graphics& g, AlertWindow& alert, colour = alert.getAlertType() == AlertWindow::InfoIcon ? (uint32) 0x605555ff : (uint32) 0x40b69900; character = alert.getAlertType() == AlertWindow::InfoIcon ? 'i' : '?'; - icon.addEllipse ((float) iconRect.getX(), (float) iconRect.getY(), - (float) iconRect.getWidth(), (float) iconRect.getHeight()); + icon.addEllipse (iconRect.toFloat()); } GlyphArrangement ga; @@ -1183,13 +1182,11 @@ void LookAndFeel_V2::drawLabel (Graphics& g, Label& label) g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); g.setFont (font); - g.drawFittedText (label.getText(), - label.getHorizontalBorderSize(), - label.getVerticalBorderSize(), - label.getWidth() - 2 * label.getHorizontalBorderSize(), - label.getHeight() - 2 * label.getVerticalBorderSize(), - label.getJustificationType(), - jmax (1, (int) (label.getHeight() / font.getHeight())), + + Rectangle textArea (label.getBorderSize().subtractedFrom (label.getLocalBounds())); + + g.drawFittedText (label.getText(), textArea, label.getJustificationType(), + jmax (1, (int) (textArea.getHeight() / font.getHeight())), label.getMinimumHorizontalScale()); g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); diff --git a/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp b/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp index 92630723..e64288e7 100644 --- a/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp +++ b/libs/juce/source/modules/juce_gui_basics/lookandfeel/juce_LookAndFeel_V3.cpp @@ -30,8 +30,8 @@ LookAndFeel_V3::LookAndFeel_V3() setColour (TextButton::buttonColourId, textButtonColour); setColour (ComboBox::buttonColourId, textButtonColour); setColour (TextEditor::outlineColourId, Colours::transparentBlack); - setColour (TabbedButtonBar::tabOutlineColourId, Colour (0xff999999)); - setColour (TabbedComponent::outlineColourId, Colour (0xff999999)); + setColour (TabbedButtonBar::tabOutlineColourId, Colour (0x66000000)); + setColour (TabbedComponent::outlineColourId, Colour (0x66000000)); setColour (Slider::trackColourId, Colour (0xbbffffff)); setColour (Slider::thumbColourId, Colour (0xffddddff)); setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); diff --git a/libs/juce/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/libs/juce/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index 28a28dc5..42df4543 100644 --- a/libs/juce/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/libs/juce/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -102,9 +102,9 @@ public: //============================================================================== #if JUCE_DUMP_MOUSE_EVENTS - #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " desc << " #" << source.getIndex() \ + #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ << ": " << screenPosToLocalPos (comp, screenPos).toString() \ - << " - Comp: " << String::toHexString ((int) &comp)); + << " - Comp: " << String::toHexString ((pointer_sized_int) &comp)); #else #define JUCE_MOUSE_EVENT_DBG(desc) #endif diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/libs/juce/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 86864fc7..02353be7 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -91,7 +91,7 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); METHOD (hasFocus, "hasFocus", "()Z") \ METHOD (invalidate, "invalidate", "(IIII)V") \ METHOD (containsPoint, "containsPoint", "(II)Z") \ - METHOD (showKeyboard, "showKeyboard", "(Z)V") \ + METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ METHOD (createGLView, "createGLView", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;") \ DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); @@ -378,14 +378,30 @@ public: handleFocusLoss(); } - void textInputRequired (const Point&) override + static const char* getVirtualKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept { - view.callVoidMethod (ComponentPeerView.showKeyboard, true); + switch (type) + { + case TextInputTarget::textKeyboard: return "text"; + case TextInputTarget::numericKeyboard: return "number"; + case TextInputTarget::urlKeyboard: return "textUri"; + case TextInputTarget::emailAddressKeyboard: return "textEmailAddress"; + case TextInputTarget::phoneNumberKeyboard: return "phone"; + default: jassertfalse; break; + } + + return "text"; + } + + void textInputRequired (Point, TextInputTarget& target) override + { + view.callVoidMethod (ComponentPeerView.showKeyboard, + javaString (getVirtualKeyboardType (target.getKeyboardType())).get()); } void dismissPendingTextInput() override { - view.callVoidMethod (ComponentPeerView.showKeyboard, false); + view.callVoidMethod (ComponentPeerView.showKeyboard, javaString ("").get()); } //============================================================================== diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/libs/juce/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm index 5f197599..79195864 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm @@ -168,7 +168,7 @@ public: void viewFocusLoss(); bool isFocused() const override; void grabFocus() override; - void textInputRequired (const Point&) override; + void textInputRequired (Point, TextInputTarget&) override; BOOL textViewReplaceCharacters (Range, const String&); void updateHiddenTextContent (TextInputTarget*); @@ -702,10 +702,7 @@ void UIViewComponentPeer::toFront (bool makeActiveWindow) void UIViewComponentPeer::toBehind (ComponentPeer* other) { - UIViewComponentPeer* const otherPeer = dynamic_cast (other); - jassert (otherPeer != nullptr); // wrong type of window? - - if (otherPeer != nullptr) + if (UIViewComponentPeer* const otherPeer = dynamic_cast (other)) { if (isSharedWindow) { @@ -716,6 +713,10 @@ void UIViewComponentPeer::toBehind (ComponentPeer* other) // don't know how to do this } } + else + { + jassertfalse; // wrong type of window? + } } void UIViewComponentPeer::setIcon (const Image& /*newIcon*/) @@ -828,12 +829,28 @@ void UIViewComponentPeer::grabFocus() } } -void UIViewComponentPeer::textInputRequired (const Point&) +void UIViewComponentPeer::textInputRequired (Point, TextInputTarget&) +{ +} + +static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept { + switch (type) + { + case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet; + case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation; + case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL; + case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; + case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; + default: jassertfalse; break; + } + + return UIKeyboardTypeDefault; } void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target) { + view->hiddenTextView.keyboardType = getUIKeyboardType (target->getKeyboardType()); view->hiddenTextView.text = juceStringToNS (target->getTextInRange (Range (0, target->getHighlightedRegion().getStart()))); view->hiddenTextView.selectedRange = NSMakeRange (target->getHighlightedRegion().getStart(), 0); } diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/libs/juce/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 82c6400c..6b9031fb 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -189,7 +189,7 @@ private: - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex { - owner->buttonClicked (buttonIndex); + owner->buttonClicked ((int) buttonIndex); alertView.hidden = true; } diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/libs/juce/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 3a8ddfa0..91746d2b 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -1217,7 +1217,7 @@ public: } } - void textInputRequired (const Point&) override {} + void textInputRequired (Point, TextInputTarget&) override {} void repaint (const Rectangle& area) override { @@ -1327,6 +1327,7 @@ public: default: #if JUCE_USE_XSHM + if (XSHMHelpers::isShmAvailable()) { ScopedXLock xlock; if (event.xany.type == XShmGetEventBase (display)) @@ -1902,7 +1903,8 @@ private: for (const Rectangle* i = originalRepaintRegion.begin(), * const e = originalRepaintRegion.end(); i != e; ++i) { #if JUCE_USE_XSHM - ++shmPaintsPending; + if (XSHMHelpers::isShmAvailable()) + ++shmPaintsPending; #endif static_cast (image.getPixelData()) @@ -3045,7 +3047,7 @@ void Desktop::Displays::findDisplays (float masterScale) screens[j].height) * masterScale; d.isMain = (index == 0); d.scale = masterScale; - d.dpi = getDisplayDPI (index); + d.dpi = getDisplayDPI (0); // (all screens share the same DPI) displays.add (d); } diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/libs/juce/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index de062b2d..32568f50 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -1136,8 +1136,7 @@ public: bool isFocused() const override { - return isSharedWindow ? this == currentlyFocusedPeer - : [window isKeyWindow]; + return this == currentlyFocusedPeer; } void grabFocus() override @@ -1151,7 +1150,7 @@ public: } } - void textInputRequired (const Point&) override {} + void textInputRequired (Point, TextInputTarget&) override {} //============================================================================== void repaint (const Rectangle& area) override diff --git a/libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index dcf9473a..94878cf4 100644 --- a/libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/libs/juce/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -877,7 +877,7 @@ public: shouldDeactivateTitleBar = oldDeactivate; } - void textInputRequired (const Point&) override + void textInputRequired (Point, TextInputTarget&) override { if (! hasCreatedCaret) { @@ -902,10 +902,15 @@ public: void performAnyPendingRepaintsNow() override { - MSG m; - if (component.isVisible() - && (PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE) || isUsingUpdateLayeredWindow())) - handlePaintMessage(); + if (component.isVisible()) + { + WeakReference localRef (&component); + MSG m; + + if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE)) + if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp) + handlePaintMessage(); + } } //============================================================================== diff --git a/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp b/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp index 66b54812..60b66d93 100644 --- a/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp +++ b/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.cpp @@ -74,7 +74,7 @@ ChoicePropertyComponent::ChoicePropertyComponent (const String& name) ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, const String& name, const StringArray& choiceList, - const Array & correspondingValues) + const Array& correspondingValues) : PropertyComponent (name), choices (choiceList), isCustomClass (false) diff --git a/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h b/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h index be01bac9..2956ae66 100644 --- a/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h +++ b/libs/juce/source/modules/juce_gui_basics/properties/juce_ChoicePropertyComponent.h @@ -70,7 +70,7 @@ public: ChoicePropertyComponent (const Value& valueToControl, const String& propertyName, const StringArray& choices, - const Array & correspondingValues); + const Array& correspondingValues); /** Destructor. */ ~ChoicePropertyComponent(); diff --git a/libs/juce/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp b/libs/juce/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp index 0163a16f..ffdcfd17 100644 --- a/libs/juce/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp +++ b/libs/juce/source/modules/juce_gui_basics/widgets/juce_ComboBox.cpp @@ -49,7 +49,7 @@ ComboBox::ComboBox (const String& name) noChoicesMessage (TRANS("(no choices)")) { setRepaintsOnMouseActivity (true); - ComboBox::lookAndFeelChanged(); + lookAndFeelChanged(); currentId.addListener (this); } @@ -410,12 +410,22 @@ void ComboBox::enablementChanged() repaint(); } +void ComboBox::colourChanged() +{ + lookAndFeelChanged(); +} + +void ComboBox::parentHierarchyChanged() +{ + lookAndFeelChanged(); +} + void ComboBox::lookAndFeelChanged() { repaint(); { - ScopedPointer