From 0399a96410b4eb3ac8a548951e33cec26f0c3370 Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 13 Nov 2015 09:16:50 +0100 Subject: [PATCH] Update juce and zynaddsubfx --- .../buffers/juce_AudioSampleBuffer.h | 731 +++++++++++++++--- .../juce_audio_basics/juce_audio_basics.cpp | 1 - .../juce_audio_basics/juce_audio_basics.h | 2 +- .../synthesisers/juce_Synthesiser.cpp | 43 +- .../synthesisers/juce_Synthesiser.h | 28 +- .../audio_io/juce_AudioDeviceManager.cpp | 324 +++++++- .../audio_io/juce_AudioDeviceManager.h | 47 +- .../native/juce_android_Midi.cpp | 312 +++++++- .../native/juce_android_OpenSL.cpp | 317 +++++--- .../native/juce_mac_CoreAudio.cpp | 18 +- .../codecs/juce_AiffAudioFormat.cpp | 2 +- .../codecs/juce_CoreAudioFormat.cpp | 2 +- .../juce_AudioUnitPluginFormat.mm | 3 +- .../format_types/juce_LADSPAPluginFormat.cpp | 1 + .../format_types/juce_VST3Common.h | 62 +- .../format_types/juce_VST3PluginFormat.cpp | 79 +- .../format_types/juce_VSTPluginFormat.cpp | 252 +++--- .../juce_audio_processors.cpp | 2 + .../juce_audio_processors.h | 6 + .../processors/juce_AudioProcessor.cpp | 31 +- .../processors/juce_AudioProcessor.h | 115 ++- .../processors/juce_AudioProcessorGraph.cpp | 289 +++++-- .../processors/juce_AudioProcessorGraph.h | 25 +- .../processors/juce_PluginDescription.cpp | 4 + .../processors/juce_PluginDescription.h | 5 + .../scanning/juce_KnownPluginList.cpp | 9 + .../scanning/juce_KnownPluginList.h | 3 +- .../modules/juce_core/containers/juce_Array.h | 9 +- source/modules/juce_core/files/juce_File.cpp | 35 + source/modules/juce_core/files/juce_File.h | 36 +- .../juce_core/maths/juce_NormalisableRange.h | 2 + .../modules/juce_core/memory/juce_ByteOrder.h | 66 +- .../native/java/JuceAppActivity.java | 294 +++++-- .../native/juce_android_JNIHelpers.h | 161 +--- .../native/juce_android_SystemStats.cpp | 36 +- .../juce_core/native/juce_android_Threads.cpp | 232 ++++++ .../native/juce_linux_CommonFile.cpp | 2 +- .../juce_core/native/juce_linux_Files.cpp | 2 +- .../juce_core/native/juce_mac_Files.mm | 9 +- .../juce_core/native/juce_posix_SharedCode.h | 23 +- .../juce_core/native/juce_win32_Files.cpp | 36 +- .../juce_core/system/juce_CompilerSupport.h | 6 + .../juce_core/system/juce_PlatformDefs.h | 64 +- .../juce_core/system/juce_StandardHeader.h | 8 +- .../text/juce_CharacterFunctions.cpp | 10 + .../juce_core/text/juce_CharacterFunctions.h | 10 + source/modules/juce_core/text/juce_String.cpp | 179 ++++- source/modules/juce_core/text/juce_String.h | 14 + .../juce_core/text/juce_StringArray.cpp | 6 + .../modules/juce_core/text/juce_StringArray.h | 9 + .../juce_core/unit_tests/juce_UnitTest.h | 41 + .../native/juce_android_Messaging.cpp | 2 + .../native/juce_android_Fonts.cpp | 2 +- .../components/juce_Component.cpp | 24 +- .../components/juce_Component.h | 4 +- .../components/juce_Desktop.cpp | 4 +- .../juce_gui_basics/mouse/juce_MouseEvent.cpp | 14 +- .../juce_gui_basics/mouse/juce_MouseEvent.h | 14 + .../mouse/juce_MouseInputSource.cpp | 49 +- .../mouse/juce_MouseInputSource.h | 33 +- .../native/juce_android_Windowing.cpp | 28 +- .../native/juce_ios_UIViewComponentPeer.mm | 23 +- .../native/juce_ios_Windowing.mm | 12 + .../native/juce_linux_Windowing.cpp | 15 +- .../native/juce_mac_NSViewComponentPeer.mm | 23 +- .../native/juce_win32_Windowing.cpp | 28 +- .../windows/juce_ComponentPeer.cpp | 4 +- .../windows/juce_ComponentPeer.h | 2 +- .../native/juce_mac_SystemTrayIcon.cpp | 7 +- .../native/juce_win32_ActiveXComponent.cpp | 1 + .../native/juce_win32_SystemTrayIcon.cpp | 4 +- .../zynaddsubfx/Misc/MiddleWare.cpp | 21 +- .../zynaddsubfx/Misc/PresetExtractor.cpp | 41 +- .../zynaddsubfx/Params/ADnoteParameters.cpp | 13 + .../zynaddsubfx/Params/ADnoteParameters.h | 8 +- .../zynaddsubfx/Synth/ADnote.cpp | 13 +- .../native-plugins/zynaddsubfx/UI/ADnoteUI.fl | 2 +- .../zynaddsubfx/UI/Fl_Osc_Dial.cpp | 10 +- 78 files changed, 3458 insertions(+), 946 deletions(-) diff --git a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 12e2de807..19e91bb88 100644 --- a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -28,15 +28,22 @@ //============================================================================== /** - A multi-channel buffer of 32-bit floating point audio samples. + A multi-channel buffer of floating point audio samples. + @see AudioSampleBuffer */ -class JUCE_API AudioSampleBuffer +template +class AudioBuffer { public: //============================================================================== /** Creates an empty buffer with 0 channels and 0 length. */ - AudioSampleBuffer() noexcept; + AudioBuffer() noexcept + : numChannels (0), size (0), allocatedBytes (0), + channels (static_cast (preallocatedChannelSpace)), + isClear (false) + { + } //============================================================================== /** Creates a buffer with a specified number of channels and samples. @@ -48,8 +55,16 @@ public: when the buffer is deleted. If the memory can't be allocated, this will throw a std::bad_alloc exception. */ - AudioSampleBuffer (int numChannels, - int numSamples) noexcept; + AudioBuffer (int numChannelsToAllocate, + int numSamplesToAllocate) noexcept + : numChannels (numChannelsToAllocate), + size (numSamplesToAllocate) + { + jassert (size >= 0); + jassert (numChannels >= 0); + + allocateData(); + } /** Creates a buffer using a pre-allocated block of memory. @@ -61,14 +76,22 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param numChannelsToUse the number of channels to use - this must correspond to the number of elements in the array passed in @param numSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - AudioSampleBuffer (float* const* dataToReferTo, - int numChannels, - int numSamples) noexcept; + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, 0); + } /** Creates a buffer using a pre-allocated block of memory. @@ -80,16 +103,25 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param numChannelsToUse the number of channels to use - this must correspond to the number of elements in the array passed in @param startSample the offset within the arrays at which the data begins @param numSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - AudioSampleBuffer (float* const* dataToReferTo, - int numChannels, - int startSample, - int numSamples) noexcept; + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int startSample, + int numSamples) noexcept + : numChannels (numChannelsToUse), + size (numSamples), + allocatedBytes (0), + isClear (false) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && startSample >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, startSample); + } /** Copies another buffer. @@ -97,28 +129,71 @@ public: using an external data buffer, in which case boths buffers will just point to the same shared block of data. */ - AudioSampleBuffer (const AudioSampleBuffer&) noexcept; + AudioBuffer (const AudioBuffer& other) noexcept + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes) + { + if (allocatedBytes == 0) + { + allocateChannels (other.channels, 0); + } + else + { + allocateData(); + + if (other.isClear) + { + clear(); + } + else + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + } /** Copies another buffer onto this one. This buffer's size will be changed to that of the other buffer. */ - AudioSampleBuffer& operator= (const AudioSampleBuffer&) noexcept; + AudioBuffer& operator= (const AudioBuffer& other) noexcept + { + if (this != &other) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); + + if (other.isClear) + { + clear(); + } + else + { + isClear = false; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + + return *this; + } /** Destructor. This will free any memory allocated by the buffer. */ - ~AudioSampleBuffer() noexcept; + ~AudioBuffer() noexcept {} //============================================================================== /** Returns the number of channels of audio data that this buffer contains. @see getSampleData */ - int getNumChannels() const noexcept { return numChannels; } + int getNumChannels() const noexcept { return numChannels; } /** Returns the number of samples allocated in each of the buffer's channels. @see getSampleData */ - int getNumSamples() const noexcept { return size; } + int getNumSamples() const noexcept { return size; } /** Returns a pointer to an array of read-only samples in one of the buffer's channels. For speed, this doesn't check whether the channel number is out of range, @@ -127,7 +202,7 @@ public: result! Instead, you must call getWritePointer so that the buffer knows you're planning on modifying the data. */ - const float* getReadPointer (int channelNumber) const noexcept + const Type* getReadPointer (int channelNumber) const noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); return channels [channelNumber]; @@ -140,7 +215,7 @@ public: result! Instead, you must call getWritePointer so that the buffer knows you're planning on modifying the data. */ - const float* getReadPointer (int channelNumber, int sampleIndex) const noexcept + const Type* getReadPointer (int channelNumber, int sampleIndex) const noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); jassert (isPositiveAndBelow (sampleIndex, size)); @@ -153,7 +228,7 @@ public: Note that if you're not planning on writing to the data, you should always use getReadPointer instead. */ - float* getWritePointer (int channelNumber) noexcept + Type* getWritePointer (int channelNumber) noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); isClear = false; @@ -166,7 +241,7 @@ public: Note that if you're not planning on writing to the data, you should use getReadPointer instead. */ - float* getWritePointer (int channelNumber, int sampleIndex) noexcept + Type* getWritePointer (int channelNumber, int sampleIndex) noexcept { jassert (isPositiveAndBelow (channelNumber, numChannels)); jassert (isPositiveAndBelow (sampleIndex, size)); @@ -179,14 +254,14 @@ public: Don't modify any of the pointers that are returned, and bear in mind that these will become invalid if the buffer is resized. */ - const float** getArrayOfReadPointers() const noexcept { return const_cast (channels); } + const Type** getArrayOfReadPointers() const noexcept { return const_cast (channels); } /** Returns an array of pointers to the channels in the buffer. Don't modify any of the pointers that are returned, and bear in mind that these will become invalid if the buffer is resized. */ - float** getArrayOfWritePointers() noexcept { isClear = false; return channels; } + Type** getArrayOfWritePointers() noexcept { isClear = false; return channels; } //============================================================================== /** Changes the buffer's size or number of channels. @@ -212,8 +287,72 @@ public: int newNumSamples, bool keepExistingContent = false, bool clearExtraSpace = false, - bool avoidReallocating = false) noexcept; - + bool avoidReallocating = false) noexcept + { + jassert (newNumChannels >= 0); + jassert (newNumSamples >= 0); + + if (newNumSamples != size || newNumChannels != numChannels) + { + const size_t allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; + const size_t channelListSize = ((sizeof (Type*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; + const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (Type)) + + channelListSize + 32; + + if (keepExistingContent) + { + HeapBlock newData; + newData.allocate (newTotalBytes, clearExtraSpace || isClear); + + const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); + + Type** const newChannels = reinterpret_cast (newData.getData()); + Type* newChan = reinterpret_cast (newData + channelListSize); + + for (int j = 0; j < newNumChannels; ++j) + { + newChannels[j] = newChan; + newChan += allocatedSamplesPerChannel; + } + + if (! isClear) + { + const int numChansToCopy = jmin (numChannels, newNumChannels); + for (int i = 0; i < numChansToCopy; ++i) + FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); + } + + allocatedData.swapWith (newData); + allocatedBytes = newTotalBytes; + channels = newChannels; + } + else + { + if (avoidReallocating && allocatedBytes >= newTotalBytes) + { + if (clearExtraSpace || isClear) + allocatedData.clear (newTotalBytes); + } + else + { + allocatedBytes = newTotalBytes; + allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); + channels = reinterpret_cast (allocatedData.getData()); + } + + Type* chan = reinterpret_cast (allocatedData + channelListSize); + for (int i = 0; i < newNumChannels; ++i) + { + channels[i] = chan; + chan += allocatedSamplesPerChannel; + } + } + + channels [newNumChannels] = 0; + size = newNumSamples; + numChannels = newNumChannels; + } + } /** Makes this buffer point to a pre-allocated set of channel data arrays. @@ -228,18 +367,69 @@ public: for each channel that should be used by this buffer. The buffer will only refer to this memory, it won't try to delete it when the buffer is deleted or resized. - @param numChannels the number of channels to use - this must correspond to the + @param newNumChannels the number of channels to use - this must correspond to the number of elements in the array passed in - @param numSamples the number of samples to use - this must correspond to the + @param newNumSamples the number of samples to use - this must correspond to the size of the arrays passed in */ - void setDataToReferTo (float** dataToReferTo, - int numChannels, - int numSamples) noexcept; + void setDataToReferTo (Type** dataToReferTo, + const int newNumChannels, + const int newNumSamples) noexcept + { + jassert (dataToReferTo != nullptr); + jassert (newNumChannels >= 0 && newNumSamples >= 0); + + if (allocatedBytes != 0) + { + allocatedBytes = 0; + allocatedData.free(); + } + + numChannels = newNumChannels; + size = newNumSamples; + + allocateChannels (dataToReferTo, 0); + jassert (! isClear); + } + + /** Resizes this buffer to match the given one, and copies all of its content across. + The source buffer can contain a different floating point type, so this can be used to + convert between 32 and 64 bit float buffer types. + */ + template + void makeCopyOf (const AudioBuffer& other) + { + setSize (other.getNumChannels(), other.getNumSamples()); + + if (other.hasBeenCleared()) + { + clear(); + } + else + { + for (int chan = 0; chan < numChannels; ++chan) + { + Type* const dest = channels[chan]; + const OtherType* const src = other.getReadPointer (chan); + + for (int i = 0; i < size; ++i) + dest[i] = static_cast (src[i]); + } + } + } //============================================================================== /** Clears all the samples in all channels. */ - void clear() noexcept; + void clear() noexcept + { + if (! isClear) + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i], size); + + isClear = true; + } + } /** Clears a specified region of all the channels. @@ -247,7 +437,19 @@ public: are in-range, so be careful! */ void clear (int startSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + { + if (startSample == 0 && numSamples == size) + isClear = true; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i] + startSample, numSamples); + } + } /** Clears a specified region of just one channel. @@ -256,7 +458,14 @@ public: */ void clear (int channel, int startSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + FloatVectorOperations::clear (channels [channel] + startSample, numSamples); + } /** Returns true if the buffer has been entirely cleared. Note that this does not actually measure the contents of the buffer - it simply @@ -272,21 +481,38 @@ public: an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - float getSample (int channel, int sampleIndex) const noexcept; + Type getSample (int channel, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return *(channels [channel] + sampleIndex); + } /** Sets a sample in the buffer. The channel and index are not checked - they are expected to be in-range. If not, an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - void setSample (int destChannel, int destSample, float newValue) noexcept; + void setSample (int destChannel, int destSample, Type newValue) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) = newValue; + isClear = false; + } /** Adds a value to a sample in the buffer. The channel and index are not checked - they are expected to be in-range. If not, an assertion will be thrown, but in a release build, you're into 'undefined behaviour' territory. */ - void addSample (int destChannel, int destSample, float valueToAdd) noexcept; + void addSample (int destChannel, int destSample, Type valueToAdd) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels [destChannel] + destSample) += valueToAdd; + isClear = false; + } /** Applies a gain multiple to a region of one channel. @@ -296,7 +522,21 @@ public: void applyGain (int channel, int startSample, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (gain != 1.0f && ! isClear) + { + Type* const d = channels [channel] + startSample; + + if (gain == 0.0f) + FloatVectorOperations::clear (d, numSamples); + else + FloatVectorOperations::multiply (d, gain, numSamples); + } + } /** Applies a gain multiple to a region of all the channels. @@ -305,10 +545,17 @@ public: */ void applyGain (int startSample, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGain (i, startSample, numSamples, gain); + } /** Applies a gain multiple to all the audio data. */ - void applyGain (float gain) noexcept; + void applyGain (Type gain) noexcept + { + applyGain (0, size, gain); + } /** Applies a range of gains to a region of a channel. @@ -322,8 +569,31 @@ public: void applyGainRamp (int channel, int startSample, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + if (! isClear) + { + if (startGain == endGain) + { + applyGain (channel, startSample, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [channel] + startSample; + + while (--numSamples >= 0) + { + *d++ *= startGain; + startGain += increment; + } + } + } + } /** Applies a range of gains to a region of all channels. @@ -336,8 +606,12 @@ public: */ void applyGainRamp (int startSample, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGainRamp (i, startSample, numSamples, startGain, endGain); + } /** Adds samples from another buffer to this one. @@ -354,11 +628,42 @@ public: */ void addFrom (int destChannel, int destStartSample, - const AudioSampleBuffer& source, + const AudioBuffer& source, int sourceChannel, int sourceStartSample, int numSamples, - float gainToApplyToSource = 1.0f) noexcept; + Type gainToApplyToSource = (Type) 1) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (gainToApplyToSource != 0.0f && numSamples > 0 && ! source.isClear) + { + Type* const d = channels [destChannel] + destStartSample; + const Type* const s = source.channels [sourceChannel] + sourceStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, s, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, s, numSamples); + } + } + } + /** Adds samples from an array of floats to one of the channels. @@ -373,9 +678,37 @@ public: */ void addFrom (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float gainToApplyToSource = 1.0f) noexcept; + Type gainToApplyToSource = (Type) 1) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (gainToApplyToSource != 0.0f && numSamples > 0) + { + Type* const d = channels [destChannel] + destStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, source, numSamples); + } + else + { + if (gainToApplyToSource != 1.0f) + FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, source, numSamples); + } + } + } + /** Adds samples from an array of floats, applying a gain ramp to them. @@ -390,10 +723,35 @@ public: */ void addFromWithRamp (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float startGain, - float endGain) noexcept; + Type startGain, + Type endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (startGain == endGain) + { + addFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ += startGain * *source++; + startGain += increment; + } + } + } + } /** Copies samples from another buffer to this one. @@ -408,10 +766,33 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const AudioSampleBuffer& source, + const AudioBuffer& source, int sourceChannel, int sourceStartSample, - int numSamples) noexcept; + int numSamples) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (numSamples > 0) + { + if (source.isClear) + { + if (! isClear) + FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copy (channels [destChannel] + destStartSample, + source.channels [sourceChannel] + sourceStartSample, + numSamples); + } + } + } /** Copies samples from an array of floats into one of the channels. @@ -424,8 +805,19 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const float* source, - int numSamples) noexcept; + const Type* source, + int numSamples) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + FloatVectorOperations::copy (channels [destChannel] + destStartSample, source, numSamples); + } + } /** Copies samples from an array of floats into one of the channels, applying a gain to it. @@ -439,9 +831,38 @@ public: */ void copyFrom (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float gain) noexcept; + Type gain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + Type* const d = channels [destChannel] + destStartSample; + + if (gain != 1.0f) + { + if (gain == 0) + { + if (! isClear) + FloatVectorOperations::clear (d, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); + } + } + else + { + isClear = false; + FloatVectorOperations::copy (d, source, numSamples); + } + } + } /** Copies samples from an array of floats into one of the channels, applying a gain ramp. @@ -458,11 +879,35 @@ public: */ void copyFromWithRamp (int destChannel, int destStartSample, - const float* source, + const Type* source, int numSamples, - float startGain, - float endGain) noexcept; - + Type startGain, + Type endGain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (startGain == endGain) + { + copyFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) + { + isClear = false; + const Type increment = (endGain - startGain) / numSamples; + Type* d = channels [destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ = startGain * *source++; + startGain += increment; + } + } + } + } /** Returns a Range indicating the lowest and highest sample values in a given section. @@ -470,57 +915,159 @@ public: @param startSample the start sample within the channel @param numSamples the number of samples to check */ - Range findMinMax (int channel, - int startSample, - int numSamples) const noexcept; + Range findMinMax (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return Range(); + + return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); + } + /** Finds the highest absolute sample value within a region of a channel. */ - float getMagnitude (int channel, - int startSample, - int numSamples) const noexcept; + Type getMagnitude (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (isClear) + return 0.0f; + + const Range r (findMinMax (channel, startSample, numSamples)); + + return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); + } /** Finds the highest absolute sample value within a region on all channels. */ - float getMagnitude (int startSample, - int numSamples) const noexcept; + Type getMagnitude (int startSample, + int numSamples) const noexcept + { + Type mag = 0.0f; + + if (! isClear) + for (int i = 0; i < numChannels; ++i) + mag = jmax (mag, getMagnitude (i, startSample, numSamples)); + + return mag; + } /** Returns the root mean squared level for a region of a channel. */ - float getRMSLevel (int channel, - int startSample, - int numSamples) const noexcept; + Type getRMSLevel (int channel, + int startSample, + int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) + return 0.0f; + + const Type* const data = channels [channel] + startSample; + double sum = 0.0; + + for (int i = 0; i < numSamples; ++i) + { + const Type sample = data [i]; + sum += sample * sample; + } + + return (Type) std::sqrt (sum / numSamples); + } /** Reverses a part of a channel. */ - void reverse (int channel, int startSample, int numSamples) const noexcept; + void reverse (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && startSample + numSamples <= size); + + if (! isClear) + std::reverse (channels[channel] + startSample, + channels[channel] + startSample + numSamples); + } /** Reverses a part of the buffer. */ - void reverse (int startSample, int numSamples) const noexcept; + void reverse (int startSample, int numSamples) const noexcept + { + for (int i = 0; i < numChannels; ++i) + reverse (i, startSample, numSamples); + } - //============================================================================== - #ifndef DOXYGEN - // Note that these methods have now been replaced by getReadPointer() and getWritePointer() - JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel) const, { return getReadPointer (channel); }) - JUCE_DEPRECATED_WITH_BODY (const float* getSampleData (int channel, int index) const, { return getReadPointer (channel, index); }) - JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel), { return getWritePointer (channel); }) - JUCE_DEPRECATED_WITH_BODY (float* getSampleData (int channel, int index), { return getWritePointer (channel, index); }) - - // These have been replaced by getArrayOfReadPointers() and getArrayOfWritePointers() - JUCE_DEPRECATED_WITH_BODY (const float** getArrayOfChannels() const, { return getArrayOfReadPointers(); }) - JUCE_DEPRECATED_WITH_BODY (float** getArrayOfChannels(), { return getArrayOfWritePointers(); }) - #endif private: //============================================================================== int numChannels, size; size_t allocatedBytes; - float** channels; + Type** channels; HeapBlock allocatedData; - float* preallocatedChannelSpace [32]; + Type* preallocatedChannelSpace [32]; bool isClear; - void allocateData(); - void allocateChannels (float* const*, int offset); + void allocateData() + { + const size_t channelListSize = sizeof (Type*) * (size_t) (numChannels + 1); + allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; + allocatedData.malloc (allocatedBytes); + channels = reinterpret_cast (allocatedData.getData()); + + Type* chan = (Type*) (allocatedData + channelListSize); + for (int i = 0; i < numChannels; ++i) + { + channels[i] = chan; + chan += size; + } + + channels [numChannels] = nullptr; + isClear = false; + } + + void allocateChannels (Type* const* const dataToReferTo, int offset) + { + jassert (offset >= 0); + + // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = static_cast (preallocatedChannelSpace); + } + else + { + allocatedData.malloc ((size_t) numChannels + 1, sizeof (Type*)); + channels = reinterpret_cast (allocatedData.getData()); + } + + for (int i = 0; i < numChannels; ++i) + { + // you have to pass in the same number of valid pointers as numChannels + jassert (dataToReferTo[i] != nullptr); + + channels[i] = dataToReferTo[i] + offset; + } + + channels [numChannels] = nullptr; + isClear = false; + } - JUCE_LEAK_DETECTOR (AudioSampleBuffer) + JUCE_LEAK_DETECTOR (AudioBuffer) }; +//============================================================================== +/** + A multi-channel buffer of 32-bit floating point audio samples. + + This typedef is here for backwards compatibility with the older AudioSampleBuffer + class, which was fixed for 32-bit data, but is otherwise the same as the new + templated AudioBuffer class. + + @see AudioBuffer +*/ +typedef AudioBuffer AudioSampleBuffer; + #endif // JUCE_AUDIOSAMPLEBUFFER_H_INCLUDED diff --git a/source/modules/juce_audio_basics/juce_audio_basics.cpp b/source/modules/juce_audio_basics/juce_audio_basics.cpp index ca5aa9e16..3919502d6 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/source/modules/juce_audio_basics/juce_audio_basics.cpp @@ -80,7 +80,6 @@ namespace juce { #include "buffers/juce_AudioDataConverters.cpp" -#include "buffers/juce_AudioSampleBuffer.cpp" #include "buffers/juce_FloatVectorOperations.cpp" #include "effects/juce_IIRFilter.cpp" #include "effects/juce_IIRFilterOld.cpp" diff --git a/source/modules/juce_audio_basics/juce_audio_basics.h b/source/modules/juce_audio_basics/juce_audio_basics.h index 2d7c12027..a58d98948 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.h +++ b/source/modules/juce_audio_basics/juce_audio_basics.h @@ -35,8 +35,8 @@ namespace juce #undef Factor #include "buffers/juce_AudioDataConverters.h" -#include "buffers/juce_AudioSampleBuffer.h" #include "buffers/juce_FloatVectorOperations.h" +#include "buffers/juce_AudioSampleBuffer.h" #include "effects/juce_Decibels.h" #include "effects/juce_IIRFilter.h" #include "effects/juce_IIRFilterOld.h" diff --git a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 16ef359ac..2aa10fb7c 100644 --- a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -71,6 +71,18 @@ bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const no return noteOnTime < other.noteOnTime; } +void SynthesiserVoice::renderNextBlock (AudioBuffer& outputBuffer, + int startSample, int numSamples) +{ + AudioBuffer subBuffer (outputBuffer.getArrayOfWritePointers(), + outputBuffer.getNumChannels(), + startSample, numSamples); + + tempBuffer.makeCopyOf (subBuffer); + renderNextBlock (tempBuffer, 0, numSamples); + subBuffer.makeCopyOf (tempBuffer); +} + //============================================================================== Synthesiser::Synthesiser() : sampleRate (0), @@ -156,8 +168,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) } } -void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBuffer& midiData, - int startSample, int numSamples) +template +void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples) { // must set the sample rate before using this! jassert (sampleRate != 0); @@ -174,7 +189,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu { if (! midiIterator.getNextEvent (m, midiEventPos)) { - renderVoices (outputBuffer, startSample, numSamples); + renderVoices (outputAudio, startSample, numSamples); return; } @@ -182,7 +197,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu if (samplesToNextMidiMessage >= numSamples) { - renderVoices (outputBuffer, startSample, numSamples); + renderVoices (outputAudio, startSample, numSamples); handleMidiEvent (m); break; } @@ -193,7 +208,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu continue; } - renderVoices (outputBuffer, startSample, samplesToNextMidiMessage); + renderVoices (outputAudio, startSample, samplesToNextMidiMessage); handleMidiEvent (m); startSample += samplesToNextMidiMessage; numSamples -= samplesToNextMidiMessage; @@ -203,7 +218,23 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu handleMidiEvent (m); } -void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) +// explicit template instantiation +template void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples); +template void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples); + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) +{ + for (int i = voices.size(); --i >= 0;) + voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); +} + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) { for (int i = voices.size(); --i >= 0;) voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); diff --git a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index f255fa360..0cf92dc7d 100644 --- a/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/source/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -182,9 +182,12 @@ public: involve rendering as little as 1 sample at a time. In between rendering callbacks, the voice's methods will be called to tell it about note and controller events. */ - virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, + virtual void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) = 0; + virtual void renderNextBlock (AudioBuffer& outputBuffer, + int startSample, + int numSamples); /** Changes the voice's reference sample rate. @@ -255,6 +258,8 @@ private: SynthesiserSound::Ptr currentlyPlayingSound; bool keyIsDown, sustainPedalDown, sostenutoPedalDown; + AudioBuffer tempBuffer; + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE // Note the new parameters for this method. virtual int stopNote (bool) { return 0; } @@ -504,10 +509,17 @@ public: both to the audio output buffer and the midi input buffer, so any midi events with timestamps outside the specified region will be ignored. */ - void renderNextBlock (AudioSampleBuffer& outputAudio, + inline void renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, int startSample, - int numSamples); + int numSamples) + { processNextBlock (outputAudio, inputMidi, startSample, numSamples); } + + inline void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples) + { processNextBlock (outputAudio, inputMidi, startSample, numSamples); } /** Returns the current target sample rate at which rendering is being done. Subclasses may need to know this so that they can pitch things correctly. @@ -545,7 +557,9 @@ protected: By default this just calls renderNextBlock() on each voice, but you may need to override it to handle custom cases. */ - virtual void renderVoices (AudioSampleBuffer& outputAudio, + virtual void renderVoices (AudioBuffer& outputAudio, + int startSample, int numSamples); + virtual void renderVoices (AudioBuffer& outputAudio, int startSample, int numSamples); /** Searches through the voices to find one that's not currently playing, and @@ -592,6 +606,12 @@ protected: private: //============================================================================== + template + void processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); + //============================================================================== double sampleRate; uint32 lastNoteOnCounter; int minimumSubBlockSize; diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 9d03ac08e..ffd068a95 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -93,7 +93,6 @@ AudioDeviceManager::AudioDeviceManager() numOutputChansNeeded (2), listNeedsScanning (true), inputLevel (0), - testSoundPosition (0), cpuUsageMs (0), timeToCpuScale (0) { @@ -589,8 +588,6 @@ void AudioDeviceManager::stopDevice() { if (currentAudioDevice != nullptr) currentAudioDevice->stop(); - - testSound = nullptr; } void AudioDeviceManager::closeAudioDevice() @@ -762,20 +759,6 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat for (int i = 0; i < numOutputChannels; ++i) zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); } - - if (testSound != nullptr) - { - const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); - const float* const src = testSound->getReadPointer (0, testSoundPosition); - - for (int i = 0; i < numOutputChannels; ++i) - for (int j = 0; j < numSamps; ++j) - outputChannelData [i][j] += src[j]; - - testSoundPosition += numSamps; - if (testSoundPosition >= testSound->getNumSamples()) - testSound = nullptr; - } } void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) @@ -944,42 +927,311 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) } //============================================================================== -void AudioDeviceManager::playTestSound() +// This is an AudioTransportSource which will own it's assigned source +class AudioSourceOwningTransportSource : public AudioTransportSource { - { // cunningly nested to swap, unlock and delete in that order. - ScopedPointer oldSound; +public: + AudioSourceOwningTransportSource() {} + ~AudioSourceOwningTransportSource() { setSource (nullptr); } + void setSource (PositionableAudioSource* newSource) + { + if (src != newSource) { - const ScopedLock sl (audioCallbackLock); - oldSound = testSound; + ScopedPointer oldSourceDeleter (src); + src = newSource; + + // tell the base class about the new source before deleting the old one + AudioTransportSource::setSource (newSource); } } - testSoundPosition = 0; +private: + ScopedPointer src; - if (currentAudioDevice != nullptr) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) +}; + +//============================================================================== +// An Audio player which will remove itself from the AudioDeviceManager's +// callback list once it finishes playing its source +class AutoRemovingSourcePlayer : public AudioSourcePlayer, + private ChangeListener +{ +public: + struct DeleteOnMessageThread : public CallbackMessage + { + DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {} + void messageCallback() override { delete parent; } + + AutoRemovingSourcePlayer* parent; + }; + + //============================================================================== + AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource) + : manager (deviceManager), + deleteWhenDone (ownSource), + hasAddedCallback (false), + recursiveEntry (false) + { + } + + void changeListenerCallback (ChangeBroadcaster* newSource) override + { + if (AudioTransportSource* currentTransport + = dynamic_cast (getCurrentSource())) + { + ignoreUnused (newSource); + jassert (newSource == currentTransport); + + if (! currentTransport->isPlaying()) + { + // this will call audioDeviceStopped! + manager.removeAudioCallback (this); + } + else if (! hasAddedCallback) + { + hasAddedCallback = true; + manager.addAudioCallback (this); + } + } + } + + void audioDeviceStopped() override + { + if (! recursiveEntry) + { + ScopedValueSetter s (recursiveEntry, true, false); + + manager.removeAudioCallback (this); + AudioSourcePlayer::audioDeviceStopped(); + + if (MessageManager* mm = MessageManager::getInstanceWithoutCreating()) + { + if (mm->isThisTheMessageThread()) + delete this; + else + (new DeleteOnMessageThread (this))->post(); + } + } + } + + void setSource (AudioTransportSource* newSource) { - const double sampleRate = currentAudioDevice->getCurrentSampleRate(); - const int soundLength = (int) sampleRate; + AudioSource* oldSource = getCurrentSource(); + + if (AudioTransportSource* oldTransport = dynamic_cast (oldSource)) + oldTransport->removeChangeListener (this); - const double frequency = 440.0; - const float amplitude = 0.5f; + if (newSource != nullptr) + newSource->addChangeListener (this); - const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); + AudioSourcePlayer::setSource (newSource); - AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); + if (deleteWhenDone) + delete oldSource; + } - for (int i = 0; i < soundLength; ++i) - newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); +private: + // only allow myself to be deleted when my audio callback has been removed + ~AutoRemovingSourcePlayer() + { + setSource (nullptr); + } - newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); - newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); + AudioDeviceManager& manager; + bool deleteWhenDone, hasAddedCallback, recursiveEntry; - const ScopedLock sl (audioCallbackLock); - testSound = newSound; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) +}; + +//============================================================================== +// An AudioSource which simply outputs a buffer +class AudioSampleBufferSource : public PositionableAudioSource +{ +public: + AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool shouldLoop, bool ownBuffer) + : position (0), + buffer (audioBuffer), + looping (shouldLoop), + deleteWhenDone (ownBuffer) + {} + + ~AudioSampleBufferSource() + { + if (deleteWhenDone) + delete buffer; + } + + //============================================================================== + void setNextReadPosition (int64 newPosition) override + { + jassert (newPosition >= 0); + + if (looping) + newPosition = newPosition % static_cast (buffer->getNumSamples()); + + position = jmin (buffer->getNumSamples(), static_cast (newPosition)); + } + + int64 getNextReadPosition() const override + { + return static_cast (position); + } + + int64 getTotalLength() const override + { + return static_cast (buffer->getNumSamples()); + } + + bool isLooping() const override + { + return looping; + } + + void setLooping (bool shouldLoop) override + { + looping = shouldLoop; + } + + //============================================================================== + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override + { + ignoreUnused (samplesPerBlockExpected, sampleRate); + } + + void releaseResources() override + {} + + void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override + { + int max = jmin (buffer->getNumSamples() - position, bufferToFill.numSamples); + + jassert (max >= 0); + { + int ch; + int maxInChannels = buffer->getNumChannels(); + int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), + jmax (maxInChannels, 2)); + + for (ch = 0; ch < maxOutChannels; ch++) + { + int inChannel = ch % maxInChannels; + + if (max > 0) + bufferToFill.buffer->copyFrom (ch, bufferToFill.startSample, *buffer, inChannel, position, max); + } + + for (; ch < bufferToFill.buffer->getNumChannels(); ++ch) + bufferToFill.buffer->clear (ch, bufferToFill.startSample, bufferToFill.numSamples); + } + + position += max; + + if (looping) + position = position % buffer->getNumSamples(); + } + +private: + //============================================================================== + int position; + AudioSampleBuffer* buffer; + bool looping, deleteWhenDone; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) +}; + +void AudioDeviceManager::playSound (const File& file) +{ + if (file.existsAsFile()) + { + AudioFormatManager formatManager; + + formatManager.registerBasicFormats(); + playSound (formatManager.createReaderFor (file), true); + } +} + +void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) +{ + if (resourceData != nullptr && resourceSize > 0) + { + AudioFormatManager formatManager; + formatManager.registerBasicFormats(); + MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); + playSound (formatManager.createReaderFor (mem), true); + } +} + +void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) +{ + playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); +} + +void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) +{ + if (audioSource != nullptr && currentAudioDevice != nullptr) + { + if (AudioTransportSource* transport = dynamic_cast (audioSource)) + { + AutoRemovingSourcePlayer* player = new AutoRemovingSourcePlayer (*this, deleteWhenFinished); + player->setSource (transport); + } + else + { + AudioTransportSource* transportSource; + + if (deleteWhenFinished) + { + AudioSourceOwningTransportSource* owningTransportSource = new AudioSourceOwningTransportSource(); + owningTransportSource->setSource (audioSource); + transportSource = owningTransportSource; + } + else + { + transportSource = new AudioTransportSource; + transportSource->setSource (audioSource); + } + + // recursively call myself + playSound (transportSource, true); + transportSource->start(); + } + } + else + { + if (deleteWhenFinished) + delete audioSource; } } +void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) +{ + playSound (new AudioSampleBufferSource (buffer, false, deleteWhenFinished), true); +} + +void AudioDeviceManager::playTestSound() +{ + const double sampleRate = currentAudioDevice->getCurrentSampleRate(); + const int soundLength = (int) sampleRate; + + const double frequency = 440.0; + const float amplitude = 0.5f; + + const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); + + AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); + + for (int i = 0; i < soundLength; ++i) + newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); + + newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); + newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); + + playSound (newSound, true); +} + +//============================================================================== void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) { if (enableMeasurement) diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h index 978ed3508..5c97e1c46 100644 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h @@ -404,6 +404,51 @@ public: */ void playTestSound(); + /** Plays a sound from a file. */ + void playSound (const File& file); + + /** Convenient method to play sound from a JUCE resource. */ + void playSound (const void* resourceData, size_t resourceSize); + + /** Plays the sound from an audio format reader. + + If deleteWhenFinished is true then the format reader will be + automatically deleted once the sound has finished playing. + */ + void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); + + /** Plays the sound from a positionable audio source. + + This will output the sound coming from a positionable audio source. + This gives you slightly more control over the sound playback compared + to the other playSound methods. For example, if you would like to + stop the sound prematurely you can call this method with a + TransportAudioSource and then call audioSource->stop. Note that, + you must call audioSource->start to start the playback, if your + audioSource is a TransportAudioSource. + + The audio device manager will not hold any references to this audio + source once the audio source has stopped playing for any reason, + for example when the sound has finished playing or when you have + called audioSource->stop. Therefore, calling audioSource->start() on + a finished audioSource will not restart the sound again. If this is + desired simply call playSound with the same audioSource again. + + @param audioSource the audio source to play + @param deleteWhenFinished If this is true then the audio source will + be deleted once the device manager has finished playing. + */ + void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); + + /** Plays the sound from an audio sample buffer. + + This will output the sound contained in an audio sample buffer. If + deleteWhenFinished is true then the audio sample buffer will be + automatically deleted once the sound has finished playing. + */ + void playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished = false); + + //============================================================================== /** Turns on level-measuring. When enabled, the device manager will measure the peak input level @@ -452,8 +497,6 @@ private: mutable bool listNeedsScanning; Atomic inputLevelMeasurementEnabledCount; double inputLevel; - ScopedPointer testSound; - int testSoundPosition; AudioSampleBuffer tempBuffer; struct MidiCallbackInfo diff --git a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp index 59ddf3edb..01a5ead54 100644 --- a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -22,11 +22,228 @@ ============================================================================== */ -StringArray MidiOutput::getDevices() +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \ + METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \ + METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ + METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ + METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \ + METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;") + DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (start, "start", "()V" )\ + METHOD (stop, "stop", "()V") \ + METHOD (close, "close", "()V") \ + METHOD (sendMidi, "sendMidi", "([BII)V") + DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort") +#undef JNI_CLASS_MEMBERS + + +//============================================================================== +class AndroidMidiInput +{ +public: + AndroidMidiInput (MidiInput* midiInput, int portIdx, + juce::MidiInputCallback* midiInputCallback, jobject deviceManager) + : juceMidiInput (midiInput), + callback (midiInputCallback), + midiConcatenator (2048), + javaMidiDevice (getEnv()->CallObjectMethod (deviceManager, + MidiDeviceManager.openMidiInputPortWithJuceIndex, + (jint) portIdx, + (jlong) this)) + { + } + + ~AndroidMidiInput() + { + if (jobject d = javaMidiDevice.get()) + { + getEnv()->CallVoidMethod (d, JuceMidiPort.close); + javaMidiDevice.clear(); + } + } + + bool isOpen() const noexcept + { + return javaMidiDevice != nullptr; + } + + void start() + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, JuceMidiPort.start); + } + + void stop() + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, JuceMidiPort.stop); + + callback = nullptr; + } + + void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) + { + jassert (byteArray != nullptr); + jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr); + + HeapBlock buffer (len); + std::memcpy (buffer.getData(), data + offset, len); + + midiConcatenator.pushMidiData (buffer.getData(), + len, static_cast (timestamp) * 1.0e-9, + juceMidiInput, *callback); + + getEnv()->ReleaseByteArrayElements (byteArray, data, 0); + } + +private: + MidiInput* juceMidiInput; + MidiInputCallback* callback; + GlobalRef javaMidiDevice; + MidiDataConcatenator midiConcatenator; +}; + +//============================================================================== +class AndroidMidiOutput { - StringArray devices; +public: + AndroidMidiOutput (jobject midiDevice) + : javaMidiDevice (midiDevice) + { + } + + ~AndroidMidiOutput() + { + if (jobject d = javaMidiDevice.get()) + { + getEnv()->CallVoidMethod (d, JuceMidiPort.close); + javaMidiDevice.clear(); + } + } + + void send (jbyteArray byteArray, jint offset, jint len) + { + if (jobject d = javaMidiDevice.get()) + getEnv()->CallVoidMethod (d, + JuceMidiPort.sendMidi, + byteArray, offset, len); + } + +private: + GlobalRef javaMidiDevice; +}; + +JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive, + void, (JNIEnv* env, jobject device, jlong host, jbyteArray byteArray, + jint offset, jint count, jlong timestamp)) +{ + // Java may create a Midi thread which JUCE doesn't know about and this callback may be + // received on this thread. Java will have already created a JNI Env for this new thread, + // which we need to tell Juce about + setEnv (env); + + reinterpret_cast (host)->receive (byteArray, offset, count, timestamp); +} + +//============================================================================== +class AndroidMidiDeviceManager +{ +public: + AndroidMidiDeviceManager () + : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) + { + } + + String getInputPortNameForJuceIndex (int idx) + { + if (jobject dm = deviceManager.get()) + { + LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx)); + return juceString (string); + } + + return String(); + } + + String getOutputPortNameForJuceIndex (int idx) + { + if (jobject dm = deviceManager.get()) + { + LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx)); + return juceString (string); + } + + return String(); + } + + StringArray getDevices (bool input) + { + if (jobject dm = deviceManager.get()) + { + jobjectArray jDevices + = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices + : MidiDeviceManager.getJuceAndroidMidiOutputDevices); + + // Create a local reference as converting this + // to a JUCE string will call into JNI + LocalRef devices (jDevices); + return javaStringArrayToJuce (devices); + } + + return StringArray(); + } + + AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) + { + if (jobject dm = deviceManager.get()) + { + ScopedPointer androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm)); + + if (androidMidiInput->isOpen()) + return androidMidiInput.release(); + } + + return nullptr; + } + + AndroidMidiOutput* openMidiOutputPortWithIndex (int idx) + { + if (jobject dm = deviceManager.get()) + if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx)) + return new AndroidMidiOutput (javaMidiPort); - return devices; + return nullptr; + } + +private: + static StringArray javaStringArrayToJuce (jobjectArray jStrings) + { + StringArray retval; + + JNIEnv* env = getEnv(); + const int count = env->GetArrayLength (jStrings); + + for (int i = 0; i < count; ++i) + { + LocalRef string ((jstring) env->GetObjectArrayElement (jStrings, i)); + retval.add (juceString (string)); + } + + return retval; + } + + GlobalRef deviceManager; +}; + +//============================================================================== +StringArray MidiOutput::getDevices() +{ + AndroidMidiDeviceManager manager; + return manager.getDevices (false); } int MidiOutput::getDefaultDeviceIndex() @@ -36,50 +253,109 @@ int MidiOutput::getDefaultDeviceIndex() MidiOutput* MidiOutput::openDevice (int index) { + if (index < 0) + return nullptr; + + AndroidMidiDeviceManager manager; + + String midiOutputName = manager.getOutputPortNameForJuceIndex (index); + + if (midiOutputName.isEmpty()) + { + // you supplied an invalid device index! + jassertfalse; + return nullptr; + } + + if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index)) + { + MidiOutput* retval = new MidiOutput (midiOutputName); + retval->internal = midiOutput; + + return retval; + } + return nullptr; } MidiOutput::~MidiOutput() { stopBackgroundThread(); + + delete reinterpret_cast (internal); } -void MidiOutput::sendMessageNow (const MidiMessage&) +void MidiOutput::sendMessageNow (const MidiMessage& message) { + if (AndroidMidiOutput* androidMidi = reinterpret_cast(internal)) + { + JNIEnv* env = getEnv(); + const int messageSize = message.getRawDataSize(); + + LocalRef messageContent = LocalRef (env->NewByteArray (messageSize)); + jbyteArray content = messageContent.get(); + + jbyte* rawBytes = env->GetByteArrayElements (content, nullptr); + std::memcpy (rawBytes, message.getRawData(), messageSize); + env->ReleaseByteArrayElements (content, rawBytes, 0); + + androidMidi->send (content, (jint) 0, (jint) messageSize); + } } //============================================================================== -MidiInput::MidiInput (const String& name_) - : name (name_), - internal (0) +MidiInput::MidiInput (const String& nm) : name (nm) { } -MidiInput::~MidiInput() +StringArray MidiInput::getDevices() { + AndroidMidiDeviceManager manager; + return manager.getDevices (true); } -void MidiInput::start() +int MidiInput::getDefaultDeviceIndex() { + return 0; } -void MidiInput::stop() +MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback) { + if (index < 0) + return nullptr; + + AndroidMidiDeviceManager manager; + + String midiInputName = manager.getInputPortNameForJuceIndex (index); + + if (midiInputName.isEmpty()) + { + // you supplied an invalid device index! + jassertfalse; + return nullptr; + } + + ScopedPointer midiInput (new MidiInput (midiInputName)); + + midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback); + + return midiInput->internal != nullptr ? midiInput.release() + : nullptr; } -int MidiInput::getDefaultDeviceIndex() +void MidiInput::start() { - return 0; + if (AndroidMidiInput* mi = reinterpret_cast (internal)) + mi->start(); } -StringArray MidiInput::getDevices() +void MidiInput::stop() { - StringArray devs; - - return devs; + if (AndroidMidiInput* mi = reinterpret_cast (internal)) + mi->stop(); } -MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) +MidiInput::~MidiInput() { - return nullptr; + delete reinterpret_cast (internal); } diff --git a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index f4c09bb71..e2e6f8340 100644 --- a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -32,7 +32,7 @@ bool isOpenSLAvailable() //============================================================================== class OpenSLAudioIODevice : public AudioIODevice, - public Thread + private Thread { public: OpenSLAudioIODevice (const String& deviceName) @@ -81,13 +81,28 @@ public: Array getAvailableSampleRates() override { static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; - return Array (rates, numElementsInArray (rates)); + Array retval (rates, numElementsInArray (rates)); + + // make sure the native sample rate is pafrt of the list + double native = getNativeSampleRate(); + if (native != 0.0 && ! retval.contains (native)) + retval.add (native); + + return retval; } Array getAvailableBufferSizes() override { - static const int sizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size - return Array (sizes, numElementsInArray (sizes)); + // we need to offer the lowest possible buffer size which + // is the native buffer size + const int defaultNumMultiples = 8; + const int nativeBufferSize = getNativeBufferSize(); + + Array retval; + for (int i = 1; i < defaultNumMultiples; ++i) + retval.add (i * nativeBufferSize); + + return retval; } String open (const BigInteger& inputChannels, @@ -116,8 +131,28 @@ public: outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); outputBuffer.clear(); - recorder = engine.createRecorder (numInputChannels, sampleRate); - player = engine.createPlayer (numOutputChannels, sampleRate); + const int audioBuffersToEnqueue = hasLowLatencyAudioPath() ? buffersToEnqueueForLowLatency + : buffersToEnqueueSlowAudio; + + DBG ("OpenSL: numInputChannels = " << numInputChannels + << ", numOutputChannels = " << numOutputChannels + << ", nativeBufferSize = " << getNativeBufferSize() + << ", nativeSampleRate = " << getNativeSampleRate() + << ", actualBufferSize = " << actualBufferSize + << ", audioBuffersToEnqueue = " << audioBuffersToEnqueue + << ", sampleRate = " << sampleRate); + + if (numInputChannels > 0) + recorder = engine.createRecorder (numInputChannels, sampleRate, + audioBuffersToEnqueue, actualBufferSize); + + if (numOutputChannels > 0) + player = engine.createPlayer (numOutputChannels, sampleRate, + audioBuffersToEnqueue, actualBufferSize); + + // pre-fill buffers + for (int i = 0; i < audioBuffersToEnqueue; ++i) + processBuffers(); startThread (8); @@ -134,18 +169,30 @@ public: player = nullptr; } - int getDefaultBufferSize() override { return 1024; } int getOutputLatencyInSamples() override { return outputLatency; } int getInputLatencyInSamples() override { return inputLatency; } bool isOpen() override { return deviceOpen; } int getCurrentBufferSizeSamples() override { return actualBufferSize; } int getCurrentBitDepth() override { return 16; } - double getCurrentSampleRate() override { return sampleRate; } BigInteger getActiveOutputChannels() const override { return activeOutputChans; } BigInteger getActiveInputChannels() const override { return activeInputChans; } String getLastError() override { return lastError; } bool isPlaying() override { return callback != nullptr; } + int getDefaultBufferSize() override + { + // Only on a Pro-Audio device will we set the lowest possible buffer size + // by default. We need to be more conservative on other devices + // as they may be low-latency, but still have a crappy CPU. + return (isProAudioDevice() ? 1 : 6) + * defaultBufferSizeIsMultipleOfNative * getNativeBufferSize(); + } + + double getCurrentSampleRate() override + { + return (sampleRate == 0.0 ? getNativeSampleRate() : sampleRate); + } + void start (AudioIODeviceCallback* newCallback) override { stop(); @@ -184,6 +231,55 @@ private: struct Player; struct Recorder; + enum + { + // The number of buffers to enqueue needs to be at least two for the audio to use the low-latency + // audio path (see "Performance" section in ndk/docs/Additional_library_docs/opensles/index.html) + buffersToEnqueueForLowLatency = 2, + buffersToEnqueueSlowAudio = 4, + defaultBufferSizeIsMultipleOfNative = 1 + }; + + //================================================================================================== + static String audioManagerGetProperty (const String& property) + { + const LocalRef jProperty (javaString (property)); + const LocalRef text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, + jProperty.get())); + if (text.get() != 0) + return juceString (text); + + return String(); + } + + static bool androidHasSystemFeature (const String& property) + { + const LocalRef jProperty (javaString (property)); + return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get()); + } + + static double getNativeSampleRate() + { + return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); + } + + static int getNativeBufferSize() + { + const int val = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); + return val > 0 ? val : 512; + } + + static bool isProAudioDevice() + { + return androidHasSystemFeature ("android.hardware.audio.pro"); + } + + static bool hasLowLatencyAudioPath() + { + return androidHasSystemFeature ("android.hardware.audio.low_latency"); + } + + //================================================================================================== AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) { const ScopedLock sl (callbackLock); @@ -192,29 +288,45 @@ private: return oldCallback; } - void run() override + void processBuffers() { - if (recorder != nullptr) recorder->start(); - if (player != nullptr) player->start(); + if (recorder != nullptr) + recorder->readNextBlock (inputBuffer, *this); - 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(); - } } + + if (player != nullptr) + player->writeBuffer (outputBuffer, *this); + } + + void run() override + { + setThreadToAudioPriority (); + + if (recorder != nullptr) recorder->start(); + if (player != nullptr) player->start(); + + while (! threadShouldExit()) + processBuffers(); + } + + void setThreadToAudioPriority () + { + // see android.os.Process.THREAD_PRIORITY_AUDIO + const int THREAD_PRIORITY_AUDIO = -16; + jint priority = THREAD_PRIORITY_AUDIO; + + if (priority != android.activity.callIntMethod (JuceAppActivity.setCurrentThreadPriority, (jint) priority)) + DBG ("Unable to set audio thread priority: priority is still " << priority); } //================================================================================================== @@ -225,7 +337,8 @@ private: { if (library.open ("libOpenSLES.so")) { - typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*); + typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, + SLuint32, const SLInterfaceID*, const SLboolean*); if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) { @@ -252,21 +365,21 @@ private: if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); } - Player* createPlayer (const int numChannels, const int sampleRate) + Player* createPlayer (const int numChannels, const int sampleRate, const int numBuffers, const int bufferSize) { if (numChannels <= 0) return nullptr; - ScopedPointer player (new Player (numChannels, sampleRate, *this)); + ScopedPointer player (new Player (numChannels, sampleRate, *this, numBuffers, bufferSize)); return player->openedOk() ? player.release() : nullptr; } - Recorder* createRecorder (const int numChannels, const int sampleRate) + Recorder* createRecorder (const int numChannels, const int sampleRate, const int numBuffers, const int bufferSize) { if (numChannels <= 0) return nullptr; - ScopedPointer recorder (new Recorder (numChannels, sampleRate, *this)); + ScopedPointer recorder (new Recorder (numChannels, sampleRate, *this, numBuffers, bufferSize)); return recorder->openedOk() ? recorder.release() : nullptr; } @@ -288,12 +401,13 @@ private: //================================================================================================== struct BufferList { - BufferList (const int numChannels_) - : numChannels (numChannels_), bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) + BufferList (const int numChannels_, const int numBuffers_, const int numSamples_) + : numChannels (numChannels_), numBuffers (numBuffers_), numSamples (numSamples_), + bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) { } - int16* waitForFreeBuffer (Thread& threadToCheck) + int16* waitForFreeBuffer (Thread& threadToCheck) noexcept { while (numBlocksOut.get() == numBuffers) { @@ -306,7 +420,7 @@ private: return getNextBuffer(); } - int16* getNextBuffer() + int16* getNextBuffer() noexcept { if (++nextBlock == numBuffers) nextBlock = 0; @@ -314,13 +428,12 @@ private: return bufferSpace + nextBlock * numChannels * numSamples; } - void bufferReturned() { --numBlocksOut; dataArrived.signal(); } - void bufferSent() { ++numBlocksOut; dataArrived.signal(); } + void bufferReturned() noexcept { --numBlocksOut; dataArrived.signal(); } + void bufferSent() noexcept { ++numBlocksOut; dataArrived.signal(); } - int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } + int getBufferSizeBytes() const noexcept { return numChannels * numSamples * sizeof (int16); } - const int numChannels; - enum { numSamples = 256, numBuffers = 16 }; + const int numChannels, numBuffers, numSamples; private: HeapBlock bufferSpace; @@ -332,24 +445,23 @@ private: //================================================================================================== struct Player { - Player (int numChannels, int sampleRate, Engine& engine) + Player (int numChannels, int sampleRate, Engine& engine, int playerNumBuffers, int playerBufferSize) : playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), - bufferList (numChannels) + bufferList (numChannels, playerNumBuffers, playerBufferSize) { - jassert (numChannels == 2); - SLDataFormat_PCM pcmFormat = { SL_DATAFORMAT_PCM, (SLuint32) numChannels, - (SLuint32) (sampleRate * 1000), // (sample rate units are millihertz) + (SLuint32) (sampleRate * 1000), SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, - SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, + (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), SL_BYTEORDER_LITTLEENDIAN }; - SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; + SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + static_cast (bufferList.numBuffers) }; SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; @@ -385,10 +497,11 @@ private: void start() { jassert (openedOk()); + check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); } - void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) + void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) noexcept { jassert (buffer.getNumChannels() == bufferList.numChannels); jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); @@ -398,26 +511,27 @@ private: while (numSamples > 0) { - int16* const destBuffer = bufferList.waitForFreeBuffer (thread); - - if (destBuffer == nullptr) - break; - - for (int i = 0; i < bufferList.numChannels; ++i) + if (int16* const destBuffer = bufferList.waitForFreeBuffer (thread)) { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; + for (int i = 0; i < bufferList.numChannels; ++i) + { + typedef AudioData::Pointer DstSampleType; + typedef AudioData::Pointer SrcSampleType; - DstSampleType dstData (destBuffer + i, bufferList.numChannels); - SrcSampleType srcData (buffer.getReadPointer (i, offset)); - dstData.convertSamples (srcData, bufferList.numSamples); - } + DstSampleType dstData (destBuffer + i, bufferList.numChannels); + SrcSampleType srcData (buffer.getReadPointer (i, offset)); + dstData.convertSamples (srcData, bufferList.numSamples); + } - check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes())); - bufferList.bufferSent(); + enqueueBuffer (destBuffer); - numSamples -= bufferList.numSamples; - offset += bufferList.numSamples; + numSamples -= bufferList.numSamples; + offset += bufferList.numSamples; + } + else + { + break; + } } } @@ -428,10 +542,16 @@ private: BufferList bufferList; - static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) + void enqueueBuffer (int16* buffer) noexcept + { + check ((*playerBufferQueue)->Enqueue (playerBufferQueue, buffer, bufferList.getBufferSizeBytes())); + bufferList.bufferSent(); + } + + static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept { - jassert (queue == static_cast (context)->playerBufferQueue); (void) queue; - static_cast (context)->bufferList.bufferReturned(); + jassert (queue == static_cast (context)->playerBufferQueue); (void) queue; + static_cast (context)->bufferList.bufferReturned(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) @@ -440,13 +560,11 @@ private: //================================================================================================== struct Recorder { - Recorder (int numChannels, int sampleRate, Engine& engine) + Recorder (int numChannels, int sampleRate, Engine& engine, const int numBuffers, const int numSamples) : recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr), configObject (nullptr), - bufferList (numChannels) + bufferList (numChannels, numBuffers, numSamples) { - jassert (numChannels == 1); // STEREO doesn't always work!! - SLDataFormat_PCM pcmFormat = { SL_DATAFORMAT_PCM, @@ -461,7 +579,8 @@ private: SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; SLDataSource audioSrc = { &ioDevice, nullptr }; - SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; + SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + static_cast (bufferList.numBuffers) }; SLDataSink audioSink = { &bufferQueue, &pcmFormat }; const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; @@ -474,16 +593,14 @@ 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)); + // not all android versions seem to have a config object + SLresult result = (*recorderObject)->GetInterface (recorderObject, + *engine.SL_IID_ANDROIDCONFIGURATION, &configObject); + if (result != SL_RESULT_SUCCESS) + configObject = nullptr; + check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); - - for (int i = bufferList.numBuffers; --i >= 0;) - { - int16* const buffer = bufferList.getNextBuffer(); - jassert (buffer != nullptr); - enqueueBuffer (buffer); - } } } } @@ -519,25 +636,27 @@ private: while (numSamples > 0) { - int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); - - if (srcBuffer == nullptr) - break; - - for (int i = 0; i < bufferList.numChannels; ++i) + if (int16* const srcBuffer = bufferList.waitForFreeBuffer (thread)) { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; + for (int i = 0; i < bufferList.numChannels; ++i) + { + typedef AudioData::Pointer DstSampleType; + typedef AudioData::Pointer SrcSampleType; - DstSampleType dstData (buffer.getWritePointer (i, offset)); - SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); - dstData.convertSamples (srcData, bufferList.numSamples); - } + DstSampleType dstData (buffer.getWritePointer (i, offset)); + SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); + dstData.convertSamples (srcData, bufferList.numSamples); + } - enqueueBuffer (srcBuffer); + enqueueBuffer (srcBuffer); - numSamples -= bufferList.numSamples; - offset += bufferList.numSamples; + numSamples -= bufferList.numSamples; + offset += bufferList.numSamples; + } + else + { + break; + } } } @@ -558,16 +677,16 @@ private: BufferList bufferList; - void enqueueBuffer (int16* buffer) + void enqueueBuffer (int16* buffer) noexcept { check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); bufferList.bufferSent(); } - static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) + static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept { - jassert (queue == static_cast (context)->recorderBufferQueue); (void) queue; - static_cast (context)->bufferList.bufferReturned(); + jassert (queue == static_cast (context)->recorderBufferQueue); (void) queue; + static_cast (context)->bufferList.bufferReturned(); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) @@ -581,7 +700,7 @@ private: ScopedPointer recorder; //============================================================================== - static bool check (const SLresult result) + static bool check (const SLresult result) noexcept { jassert (result == SL_RESULT_SUCCESS); return result == SL_RESULT_SUCCESS; @@ -598,14 +717,14 @@ public: OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} //============================================================================== - void scanForDevices() {} - StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); } - int getDefaultDeviceIndex (bool forInput) const { return 0; } - int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } - bool hasSeparateInputsAndOutputs() const { return false; } + void scanForDevices() override {} + StringArray getDeviceNames (bool wantInputNames) const override { return StringArray (openSLTypeName); } + int getDefaultDeviceIndex (bool forInput) const override { return 0; } + int getIndexOfDevice (AudioIODevice* device, bool asInput) const override { return device != nullptr ? 0 : -1; } + bool hasSeparateInputsAndOutputs() const override { return false; } AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) + const String& inputDeviceName) override { ScopedPointer dev; diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index 601fbf2c0..31af32b6e 100644 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -233,7 +233,7 @@ public: for (int i = 0; i < numStreams; ++i) { - const AudioBuffer& b = bufList->mBuffers[i]; + const ::AudioBuffer& b = bufList->mBuffers[i]; for (unsigned int j = 0; j < b.mNumberChannels; ++j) { @@ -352,14 +352,20 @@ public: int getLatencyFromDevice (AudioObjectPropertyScope scope) const { - UInt32 lat = 0; - UInt32 size = sizeof (lat); + UInt32 latency = 0; + UInt32 size = sizeof (latency); AudioObjectPropertyAddress pa; pa.mElement = kAudioObjectPropertyElementMaster; pa.mSelector = kAudioDevicePropertyLatency; pa.mScope = scope; - AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &lat); - return (int) lat; + AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &latency); + + UInt32 safetyOffset = 0; + size = sizeof (safetyOffset); + pa.mSelector = kAudioDevicePropertySafetyOffset; + AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &safetyOffset); + + return (int) (latency + safetyOffset); } int getBitDepthFromDevice (AudioObjectPropertyScope scope) const @@ -1945,7 +1951,7 @@ private: for (int i = 0; i < numStreams; ++i) { - const AudioBuffer& b = bufList->mBuffers[i]; + const ::AudioBuffer& b = bufList->mBuffers[i]; total += b.mNumberChannels; } } diff --git a/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp index 86bc8432b..6c9d6c858 100644 --- a/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_AiffAudioFormat.cpp @@ -249,7 +249,7 @@ namespace AiffFileHelpers data += isGenre ? 118 : 50; - if (data[0] == 0) + if (data < dataEnd && data[0] == 0) { if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50; else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118; diff --git a/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp b/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp index 9b4d556d0..d11b17b34 100644 --- a/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp +++ b/source/modules/juce_audio_formats/codecs/juce_CoreAudioFormat.cpp @@ -380,7 +380,7 @@ public: &destinationAudioFormat); if (status == noErr) { - bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (AudioBuffer)); + bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (::AudioBuffer)); bufferList->mNumberBuffers = numChannels; ok = true; } diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index 214853b5b..9464344cc 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -374,6 +374,7 @@ public: ^ ((int) componentDesc.componentSubType) ^ ((int) componentDesc.componentManufacturer); desc.lastFileModTime = Time(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); desc.pluginFormatName = "AudioUnit"; desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); desc.manufacturerName = manufacturer; @@ -1223,7 +1224,7 @@ private: //============================================================================== size_t getAudioBufferSizeInBytes() const noexcept { - return offsetof (AudioBufferList, mBuffers) + (sizeof (AudioBuffer) * numOutputBusChannels); + return offsetof (AudioBufferList, mBuffers) + (sizeof (::AudioBuffer) * numOutputBusChannels); } AudioBufferList* getAudioBufferListForBus (AudioUnitElement busIndex) const noexcept diff --git a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp index 71d99b596..bfc53449d 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_LADSPAPluginFormat.cpp @@ -220,6 +220,7 @@ public: desc.fileOrIdentifier = module->file.getFullPathName(); desc.uid = getUID(); desc.lastFileModTime = module->file.getLastModificationTime(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); desc.pluginFormatName = "LADSPA"; desc.category = getCategory(); desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String(); diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index e4769807c..ec33c618b 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -344,21 +344,25 @@ private: }; //============================================================================== -namespace VST3BufferExchange +template +struct VST3BufferExchange { - typedef Array Bus; + typedef Array Bus; typedef Array BusMap; + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } + static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } + /** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' @warning For speed, does not check the channel count and offsets according to the AudioSampleBuffer */ - void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, - Bus& bus, - AudioSampleBuffer& buffer, - int numChannels, int channelStartOffset, - int sampleOffset = 0) + static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, + Bus& bus, + AudioBuffer& buffer, + int numChannels, int channelStartOffset, + int sampleOffset = 0) { const int channelEnd = numChannels + channelStartOffset; jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); @@ -368,7 +372,7 @@ namespace VST3BufferExchange for (int i = channelStartOffset; i < channelEnd; ++i) bus.add (buffer.getWritePointer (i, sampleOffset)); - vstBuffers.channelBuffers32 = bus.getRawDataPointer(); + assignRawPointer (vstBuffers, bus.getRawDataPointer()); vstBuffers.numChannels = numChannels; vstBuffers.silenceFlags = 0; } @@ -376,7 +380,7 @@ namespace VST3BufferExchange static void mapArrangementToBusses (int& channelIndexOffset, int index, Array& result, BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, - AudioSampleBuffer& source) + AudioBuffer& source) { const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); @@ -387,18 +391,16 @@ namespace VST3BufferExchange busMapToUse.add (Bus()); if (numChansForBus > 0) - { associateBufferTo (result.getReference (index), busMapToUse.getReference (index), source, numChansForBus, channelIndexOffset); - } channelIndexOffset += numChansForBus; } - inline void mapBufferToBusses (Array& result, BusMap& busMapToUse, - const Array& arrangements, - AudioSampleBuffer& source) + static inline void mapBufferToBusses (Array& result, BusMap& busMapToUse, + const Array& arrangements, + AudioBuffer& source) { int channelIndexOffset = 0; @@ -407,10 +409,10 @@ namespace VST3BufferExchange arrangements.getUnchecked (i), source); } - inline void mapBufferToBusses (Array& result, - Steinberg::Vst::IAudioProcessor& processor, - BusMap& busMapToUse, bool isInput, int numBusses, - AudioSampleBuffer& source) + static inline void mapBufferToBusses (Array& result, + Steinberg::Vst::IAudioProcessor& processor, + BusMap& busMapToUse, bool isInput, int numBusses, + AudioBuffer& source) { int channelIndexOffset = 0; @@ -420,6 +422,28 @@ namespace VST3BufferExchange getArrangementForBus (&processor, isInput, i), source); } -} +}; + +template +struct VST3FloatAndDoubleBusMapCompositeHelper {}; + +struct VST3FloatAndDoubleBusMapComposite +{ + VST3BufferExchange::BusMap floatVersion; + VST3BufferExchange::BusMap doubleVersion; + template + inline typename VST3BufferExchange::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper::get (*this); } +}; + + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } +}; + +template <> struct VST3FloatAndDoubleBusMapCompositeHelper +{ + static inline VST3BufferExchange::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } +}; #endif // JUCE_VST3COMMON_H_INCLUDED diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index ff1cff425..1c661ba74 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -110,6 +110,7 @@ static void createPluginDescription (PluginDescription& description, { description.fileOrIdentifier = pluginFile.getFullPathName(); description.lastFileModTime = pluginFile.getLastModificationTime(); + description.lastInfoUpdateTime = Time::getCurrentTime(); description.manufacturerName = company; description.name = name; description.descriptiveName = name; @@ -1702,7 +1703,7 @@ public: using namespace Vst; ProcessSetup setup; - setup.symbolicSampleSize = kSample32; + setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32; setup.maxSamplesPerBlock = estimatedSamplesPerBlock; setup.sampleRate = newSampleRate; setup.processMode = isNonRealtime() ? kOffline : kRealtime; @@ -1769,39 +1770,56 @@ public: JUCE_CATCH_ALL_ASSERT } - void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + bool supportsDoublePrecisionProcessing() const override { - using namespace Vst; + return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); + } - if (isActive - && processor != nullptr - && processor->canProcessSampleSize (kSample32) == kResultTrue) - { - const int numSamples = buffer.getNumSamples(); + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (! isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample32); + } + + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + + if (isActive && processor != nullptr) + processAudio (buffer, midiMessages, Vst::kSample64); + } + + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, + Vst::SymbolicSampleSizes sampleSize) + { + using namespace Vst; + const int numSamples = buffer.getNumSamples(); - ProcessData data; - data.processMode = isNonRealtime() ? kOffline : kRealtime; - data.symbolicSampleSize = kSample32; - data.numInputs = numInputAudioBusses; - data.numOutputs = numOutputAudioBusses; - data.inputParameterChanges = inputParameterChanges; - data.outputParameterChanges = outputParameterChanges; - data.numSamples = (Steinberg::int32) numSamples; + ProcessData data; + data.processMode = isNonRealtime() ? kOffline : kRealtime; + data.symbolicSampleSize = sampleSize; + data.numInputs = numInputAudioBusses; + data.numOutputs = numOutputAudioBusses; + data.inputParameterChanges = inputParameterChanges; + data.outputParameterChanges = outputParameterChanges; + data.numSamples = (Steinberg::int32) numSamples; - updateTimingInformation (data, getSampleRate()); + updateTimingInformation (data, getSampleRate()); - for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) - buffer.clear (i, 0, numSamples); + for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) + buffer.clear (i, 0, numSamples); - associateTo (data, buffer); - associateTo (data, midiMessages); + associateTo (data, buffer); + associateTo (data, midiMessages); - processor->process (data); + processor->process (data); - MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); + MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); - inputParameterChanges->clearAllQueues(); - } + inputParameterChanges->clearAllQueues(); } //============================================================================== @@ -2151,7 +2169,7 @@ private: */ int numInputAudioBusses, numOutputAudioBusses; Array inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). - VST3BufferExchange::BusMap inputBusMap, outputBusMap; + VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; Array inputBusses, outputBusses; //============================================================================== @@ -2393,12 +2411,11 @@ private: } //============================================================================== - void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer) + template + void associateTo (Vst::ProcessData& destination, AudioBuffer& buffer) { - using namespace VST3BufferExchange; - - mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer); - mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer); + VST3BufferExchange::mapBufferToBusses (inputBusses, inputBusMap.get(), inputArrangements, buffer); + VST3BufferExchange::mapBufferToBusses (outputBusses, outputBusMap.get(), outputArrangements, buffer); destination.inputs = inputBusses.getRawDataPointer(); destination.outputs = outputBusses.getRawDataPointer(); diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index c86b08c5b..ee797b832 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -715,8 +715,7 @@ public: name (mh->pluginName), wantsMidiMessages (false), initialised (false), - isPowerOn (false), - tempBuffer (1, 1) + isPowerOn (false) { try { @@ -805,6 +804,7 @@ public: desc.fileOrIdentifier = module->file.getFullPathName(); desc.uid = getUID(); desc.lastFileModTime = module->file.getLastModificationTime(); + desc.lastInfoUpdateTime = Time::getCurrentTime(); desc.pluginFormatName = "VST"; desc.category = getCategory(); @@ -938,6 +938,18 @@ public: dispatch (effSetSampleRate, 0, 0, 0, (float) rate); dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); + if (supportsDoublePrecisionProcessing()) + { + VstInt32 vstPrecision = isUsingDoublePrecision() ? kVstProcessPrecision64 + : kVstProcessPrecision32; + + // if you get an assertion here then your plug-in claims it supports double precision + // but returns an error when we try to change the precision + VstIntPtr err = dispatch (effSetProcessPrecision, 0, (VstIntPtr) vstPrecision, 0, 0); + jassert (err > 0); + ignoreUnused (err); + } + tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); if (! isPowerOn) @@ -980,110 +992,22 @@ public: } } - void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override { - const int numSamples = buffer.getNumSamples(); - - if (initialised) - { - if (AudioPlayHead* const currentPlayHead = getPlayHead()) - { - AudioPlayHead::CurrentPositionInfo position; - if (currentPlayHead->getCurrentPosition (position)) - { - - vstHostTime.samplePos = (double) position.timeInSamples; - vstHostTime.tempo = position.bpm; - vstHostTime.timeSigNumerator = position.timeSigNumerator; - vstHostTime.timeSigDenominator = position.timeSigDenominator; - vstHostTime.ppqPos = position.ppqPosition; - vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; - vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; - - VstInt32 newTransportFlags = 0; - if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; - if (position.isRecording) newTransportFlags |= kVstTransportRecording; - - if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) - vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; - else - vstHostTime.flags &= ~kVstTransportChanged; - - switch (position.frameRate) - { - case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; - case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; - case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; - case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; - case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; - case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; - default: break; - } - - if (position.isLooping) - { - vstHostTime.cycleStartPos = position.ppqLoopStart; - vstHostTime.cycleEndPos = position.ppqLoopEnd; - vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); - } - else - { - vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); - } - } - } - - vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); - - if (wantsMidiMessages) - { - midiEventsToSend.clear(); - midiEventsToSend.ensureSize (1); - - MidiBuffer::Iterator iter (midiMessages); - const uint8* midiData; - int numBytesOfMidiData, samplePosition; - - while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) - { - midiEventsToSend.addEvent (midiData, numBytesOfMidiData, - jlimit (0, numSamples - 1, samplePosition)); - } - - effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); - } - - _clearfp(); - - if ((effect->flags & effFlagsCanReplacing) != 0) - { - effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), numSamples); - } - else - { - tempBuffer.setSize (effect->numOutputs, numSamples); - tempBuffer.clear(); - - effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), numSamples); - - for (int i = effect->numOutputs; --i >= 0;) - buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), numSamples); - } - } - else - { - // Not initialised, so just bypass.. - for (int i = 0; i < getNumOutputChannels(); ++i) - buffer.clear (i, 0, buffer.getNumSamples()); - } + jassert (! isUsingDoublePrecision()); + processAudio (buffer, midiMessages); + } - { - // copy any incoming midi.. - const ScopedLock sl (midiInLock); + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override + { + jassert (isUsingDoublePrecision()); + processAudio (buffer, midiMessages); + } - midiMessages.swapWith (incomingMidi); - incomingMidi.clear(); - } + bool supportsDoublePrecisionProcessing() const override + { + return ((effect->flags & effFlagsCanReplacing) != 0 + && (effect->flags & effFlagsCanDoubleReplacing) != 0); } //============================================================================== @@ -1673,12 +1597,132 @@ private: CriticalSection lock; bool wantsMidiMessages, initialised, isPowerOn; mutable StringArray programNames; - AudioSampleBuffer tempBuffer; + AudioBuffer tempBuffer; CriticalSection midiInLock; MidiBuffer incomingMidi; VSTMidiEventList midiEventsToSend; VstTimeInfo vstHostTime; + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + const int numSamples = buffer.getNumSamples(); + + if (initialised) + { + if (AudioPlayHead* const currentPlayHead = getPlayHead()) + { + AudioPlayHead::CurrentPositionInfo position; + + if (currentPlayHead->getCurrentPosition (position)) + { + + vstHostTime.samplePos = (double) position.timeInSamples; + vstHostTime.tempo = position.bpm; + vstHostTime.timeSigNumerator = position.timeSigNumerator; + vstHostTime.timeSigDenominator = position.timeSigDenominator; + vstHostTime.ppqPos = position.ppqPosition; + vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; + vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; + + VstInt32 newTransportFlags = 0; + if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; + if (position.isRecording) newTransportFlags |= kVstTransportRecording; + + if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) + vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; + else + vstHostTime.flags &= ~kVstTransportChanged; + + switch (position.frameRate) + { + case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; + case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; + case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; + case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; + case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; + default: break; + } + + if (position.isLooping) + { + vstHostTime.cycleStartPos = position.ppqLoopStart; + vstHostTime.cycleEndPos = position.ppqLoopEnd; + vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); + } + else + { + vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); + } + } + } + + vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); + + if (wantsMidiMessages) + { + midiEventsToSend.clear(); + midiEventsToSend.ensureSize (1); + + MidiBuffer::Iterator iter (midiMessages); + const uint8* midiData; + int numBytesOfMidiData, samplePosition; + + while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) + { + midiEventsToSend.addEvent (midiData, numBytesOfMidiData, + jlimit (0, numSamples - 1, samplePosition)); + } + + effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); + } + + _clearfp(); + + invokeProcessFunction (buffer, numSamples); + } + else + { + // Not initialised, so just bypass.. + for (int i = 0; i < getNumOutputChannels(); ++i) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + { + // copy any incoming midi.. + const ScopedLock sl (midiInLock); + + midiMessages.swapWith (incomingMidi); + incomingMidi.clear(); + } + } + + //============================================================================== + inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + { + if ((effect->flags & effFlagsCanReplacing) != 0) + { + effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + } + else + { + tempBuffer.setSize (effect->numOutputs, sampleFrames); + tempBuffer.clear(); + + effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), sampleFrames); + + for (int i = effect->numOutputs; --i >= 0;) + buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), sampleFrames); + } + } + + inline void invokeProcessFunction (AudioBuffer& buffer, VstInt32 sampleFrames) + { + effect->processDoubleReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); + } + //============================================================================== void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept { diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp index ca91cd6b6..29b2362a1 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -166,5 +166,7 @@ void AutoResizingNSViewComponentWithParent::timerCallback() #include "scanning/juce_KnownPluginList.cpp" #include "scanning/juce_PluginDirectoryScanner.cpp" #include "scanning/juce_PluginListComponent.cpp" +#include "utilities/juce_AudioProcessorValueTreeState.cpp" +#include "utilities/juce_AudioProcessorParameters.cpp" } diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h index 5ccfe2b83..c7c312f7a 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -92,6 +92,12 @@ class AudioProcessor; #include "format_types/juce_VST3PluginFormat.h" #include "scanning/juce_PluginDirectoryScanner.h" #include "scanning/juce_PluginListComponent.h" +#include "utilities/juce_AudioProcessorValueTreeState.h" +#include "utilities/juce_AudioProcessorParameterWithID.h" +#include "utilities/juce_AudioParameterFloat.h" +#include "utilities/juce_AudioParameterInt.h" +#include "utilities/juce_AudioParameterBool.h" +#include "utilities/juce_AudioParameterChoice.h" } diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp index 4c78894d4..322cdc415 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp @@ -38,7 +38,8 @@ AudioProcessor::AudioProcessor() numOutputChannels (0), latencySamples (0), suspended (false), - nonRealtime (false) + nonRealtime (false), + processingPrecision (singlePrecision) { } @@ -325,7 +326,33 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) } void AudioProcessor::reset() {} -void AudioProcessor::processBlockBypassed (AudioSampleBuffer&, MidiBuffer&) {} +void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} +void AudioProcessor::processBlockBypassed (AudioBuffer&, MidiBuffer&) {} + +void AudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + ignoreUnused (buffer, midiMessages); + + // If you hit this assertion then either the caller called the double + // precision version of processBlock on a processor which does not support it + // (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation + // of the AudioProcessor forgot to override the double precision version of this method + jassertfalse; +} + +void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noexcept +{ + // If you hit this assertion then you're trying to use double precision + // processing on a processor which does not support it! + jassert (precision != doublePrecision || supportsDoublePrecisionProcessing()); + + processingPrecision = precision; +} + +bool AudioProcessor::supportsDoublePrecisionProcessing() const +{ + return false; +} #if ! JUCE_AUDIO_PROCESSOR_NO_GUI //============================================================================== diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 62fc0c7da..4443dec35 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -48,6 +48,14 @@ protected: AudioProcessor(); public: + //============================================================================== + enum ProcessingPrecision + { + singlePrecision, + doublePrecision + }; + + //============================================================================== /** Destructor. */ virtual ~AudioProcessor(); @@ -125,9 +133,74 @@ public: processBlock() method to send out an asynchronous message. You could also use the AsyncUpdater class in a similar way. */ - virtual void processBlock (AudioSampleBuffer& buffer, + virtual void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) = 0; + /** Renders the next block. + + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has 2 input channels and 4 output channels, then + the buffer will contain 4 channels, the first two being filled with the + input data. Your filter should read these, do its processing, and replace + the contents of all 4 channels with its output. + + Or if your filter has 5 inputs and 2 outputs, the buffer will have 5 channels, + all filled with data, and your filter should overwrite the first 2 of these + with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but you should only read/write from the ones that your filter is supposed to + be using. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + Also note that some hosts will occasionally decide to pass a buffer containing + zero samples, so make sure that your algorithm can deal with that! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. + + Be very careful about what you do in this callback - it's going to be called by + the audio thread, so any kind of interaction with the UI is absolutely + out of the question. If you change a parameter in here and need to tell your UI to + update itself, the best way is probably to inherit from a ChangeBroadcaster, let + the UI components register as listeners, and then call sendChangeMessage() inside the + processBlock() method to send out an asynchronous message. You could also use + the AsyncUpdater class in a similar way. + */ + virtual void processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages); + + /** Renders the next block when the processor is being bypassed. + The default implementation of this method will pass-through any incoming audio, but + you may override this method e.g. to add latency compensation to the data to match + the processor's latency characteristics. This will avoid situations where bypassing + will shift the signal forward in time, possibly creating pre-echo effects and odd timings. + Another use for this method would be to cross-fade or morph between the wet (not bypassed) + and dry (bypassed) signals. + */ + virtual void processBlockBypassed (AudioBuffer& buffer, + MidiBuffer& midiMessages); + /** Renders the next block when the processor is being bypassed. The default implementation of this method will pass-through any incoming audio, but you may override this method e.g. to add latency compensation to the data to match @@ -136,9 +209,46 @@ public: Another use for this method would be to cross-fade or morph between the wet (not bypassed) and dry (bypassed) signals. */ - virtual void processBlockBypassed (AudioSampleBuffer& buffer, + virtual void processBlockBypassed (AudioBuffer& buffer, MidiBuffer& midiMessages); + //============================================================================== + /** Returns true if the Audio processor supports double precision floating point processing. + The default implementation will always return false. + If you return true here then you must override the double precision versions + of processBlock. Additionally, you must call getProcessingPrecision() in + your prepareToPlay method to determine the precision with which you need to + allocate your internal buffers. + @see getProcessingPrecision, setProcessingPrecision + */ + virtual bool supportsDoublePrecisionProcessing() const; + + /** Returns the precision-mode of the processor. + Depending on the result of this method you MUST call the corresponding version + of processBlock. The default processing precision is single precision. + @see setProcessingPrecision, supportsDoublePrecisionProcessing + */ + ProcessingPrecision getProcessingPrecision() const noexcept { return processingPrecision; } + + /** Returns true if the current precision is set to doublePrecision. */ + bool isUsingDoublePrecision() const noexcept { return processingPrecision == doublePrecision; } + + /** Changes the processing precision of the receiver. A client of the AudioProcessor + calls this function to indicate which version of processBlock (single or double + precision) it intends to call. The client MUST call this function before calling + the prepareToPlay method so that the receiver can do any necessary allocations + in the prepareToPlay() method. An implementation of prepareToPlay() should call + getProcessingPrecision() to determine with which precision it should allocate + it's internal buffers. + + Note that setting the processing precision to double floating point precision + on a receiver which does not support double precision processing (i.e. + supportsDoublePrecisionProcessing() returns false) will result in an assertion. + + @see getProcessingPrecision, supportsDoublePrecisionProcessing + */ + void setProcessingPrecision (ProcessingPrecision precision) noexcept; + //============================================================================== /** Returns the current AudioPlayHead object that should be used to find out the state and position of the playhead. @@ -742,6 +852,7 @@ private: double sampleRate; int blockSize, numInputChannels, numOutputChannels, latencySamples; bool suspended, nonRealtime; + ProcessingPrecision processingPrecision; CriticalSection callbackLock, listenerLock; String inputSpeakerArrangement, outputSpeakerArrangement; diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 28abea5dc..bc44fe044 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -25,28 +25,81 @@ const int AudioProcessorGraph::midiChannelIndex = 0x1000; //============================================================================== -namespace GraphRenderingOps +template struct FloatDoubleUtil {}; +template struct FloatDoubleType {}; + +template +struct FloatAndDoubleComposition { + typedef typename FloatDoubleType::Type FloatType; + typedef typename FloatDoubleType::Type DoubleType; + + template + inline typename FloatDoubleType::Type& get() noexcept + { + return FloatDoubleUtil >::get (*this); + } + + FloatType floatVersion; + DoubleType doubleVersion; +}; + +template struct FloatDoubleUtil { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; +template struct FloatDoubleUtil { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; + +struct FloatPlaceholder; + +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef HeapBlock Type; }; +template struct FloatDoubleType, FloatingType> { typedef AudioBuffer Type; }; +template struct FloatDoubleType*, FloatingType> { typedef AudioBuffer* Type; }; //============================================================================== -struct AudioGraphRenderingOp +namespace GraphRenderingOps { - AudioGraphRenderingOp() noexcept {} - virtual ~AudioGraphRenderingOp() {} - virtual void perform (AudioSampleBuffer& sharedBufferChans, +struct AudioGraphRenderingOpBase +{ + AudioGraphRenderingOpBase() noexcept {} + virtual ~AudioGraphRenderingOpBase() {} + + virtual void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) = 0; + + virtual void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) = 0; - JUCE_LEAK_DETECTOR (AudioGraphRenderingOp) + JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) +}; + +// use CRTP +template +struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase +{ + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } + + void perform (AudioBuffer& sharedBufferChans, + const OwnedArray& sharedMidiBuffers, + const int numSamples) override + { + static_cast (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); + } }; //============================================================================== -struct ClearChannelOp : public AudioGraphRenderingOp +struct ClearChannelOp : public AudioGraphRenderingOp { ClearChannelOp (const int channel) noexcept : channelNum (channel) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.clear (channelNum, 0, numSamples); } @@ -57,13 +110,14 @@ struct ClearChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct CopyChannelOp : public AudioGraphRenderingOp +struct CopyChannelOp : public AudioGraphRenderingOp { CopyChannelOp (const int srcChan, const int dstChan) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } @@ -74,13 +128,14 @@ struct CopyChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct AddChannelOp : public AudioGraphRenderingOp +struct AddChannelOp : public AudioGraphRenderingOp { AddChannelOp (const int srcChan, const int dstChan) noexcept : srcChannelNum (srcChan), dstChannelNum (dstChan) {} - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); } @@ -91,11 +146,12 @@ struct AddChannelOp : public AudioGraphRenderingOp }; //============================================================================== -struct ClearMidiBufferOp : public AudioGraphRenderingOp +struct ClearMidiBufferOp : public AudioGraphRenderingOp { ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) { sharedMidiBuffers.getUnchecked (bufferNum)->clear(); } @@ -106,13 +162,14 @@ struct ClearMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct CopyMidiBufferOp : public AudioGraphRenderingOp +struct CopyMidiBufferOp : public AudioGraphRenderingOp { CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int) { *sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); } @@ -123,13 +180,14 @@ struct CopyMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct AddMidiBufferOp : public AudioGraphRenderingOp +struct AddMidiBufferOp : public AudioGraphRenderingOp { AddMidiBufferOp (const int srcBuffer, const int dstBuffer) : srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) {} - void perform (AudioSampleBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) + template + void perform (AudioBuffer&, const OwnedArray& sharedMidiBuffers, const int numSamples) { sharedMidiBuffers.getUnchecked (dstBufferNum) ->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); @@ -141,24 +199,27 @@ struct AddMidiBufferOp : public AudioGraphRenderingOp }; //============================================================================== -struct DelayChannelOp : public AudioGraphRenderingOp +struct DelayChannelOp : public AudioGraphRenderingOp { DelayChannelOp (const int chan, const int delaySize) : channel (chan), bufferSize (delaySize + 1), readIndex (0), writeIndex (delaySize) { - buffer.calloc ((size_t) bufferSize); + buffer.floatVersion. calloc ((size_t) bufferSize); + buffer.doubleVersion.calloc ((size_t) bufferSize); } - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray&, const int numSamples) { - float* data = sharedBufferChans.getWritePointer (channel, 0); + FloatType* data = sharedBufferChans.getWritePointer (channel, 0); + HeapBlock& block = buffer.get(); for (int i = numSamples; --i >= 0;) { - buffer [writeIndex] = *data; - *data++ = buffer [readIndex]; + block [writeIndex] = *data; + *data++ = block [readIndex]; if (++readIndex >= bufferSize) readIndex = 0; if (++writeIndex >= bufferSize) writeIndex = 0; @@ -166,41 +227,67 @@ struct DelayChannelOp : public AudioGraphRenderingOp } private: - HeapBlock buffer; + FloatAndDoubleComposition > buffer; const int channel, bufferSize; int readIndex, writeIndex; JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) }; - //============================================================================== -struct ProcessBufferOp : public AudioGraphRenderingOp +struct ProcessBufferOp : public AudioGraphRenderingOp { ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, - const Array& audioChannels, + const Array& audioChannelsUsed, const int totalNumChans, const int midiBuffer) : node (n), processor (n->getProcessor()), - audioChannelsToUse (audioChannels), + audioChannelsToUse (audioChannelsUsed), totalChans (jmax (1, totalNumChans)), midiBufferToUse (midiBuffer) { - channels.calloc ((size_t) totalChans); + audioChannels.floatVersion. calloc ((size_t) totalChans); + audioChannels.doubleVersion.calloc ((size_t) totalChans); while (audioChannelsToUse.size() < totalChans) audioChannelsToUse.add (0); } - void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) + template + void perform (AudioBuffer& sharedBufferChans, const OwnedArray& sharedMidiBuffers, const int numSamples) { + HeapBlock& channels = audioChannels.get(); + for (int i = totalChans; --i >= 0;) channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); - AudioSampleBuffer buffer (channels, totalChans, numSamples); + AudioBuffer buffer (channels, totalChans, numSamples); - processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + processor->processBlock (buffer, midiMessages); + } + + void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) + { + if (processor->isUsingDoublePrecision()) + { + processor->processBlock (buffer, midiMessages); + } + else + { + // if the processor is in single precision mode but the graph in double + // precision then we need to convert between buffer formats. Note, that + // this will only happen if the processor does not support double + // precision processing. + tempBuffer.makeCopyOf (buffer); + processor->processBlock (tempBuffer, midiMessages); + buffer.makeCopyOf (tempBuffer); + } } const AudioProcessorGraph::Node::Ptr node; @@ -208,7 +295,8 @@ struct ProcessBufferOp : public AudioGraphRenderingOp private: Array audioChannelsToUse; - HeapBlock channels; + FloatAndDoubleComposition > audioChannels; + AudioBuffer tempBuffer; const int totalChans; const int midiBufferToUse; @@ -861,13 +949,17 @@ AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) n } void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, - AudioProcessorGraph* const graph) + AudioProcessorGraph* const graph, ProcessingPrecision precision) { if (! isPrepared) { isPrepared = true; setParentGraph (graph); + // try to align the precision of the processor and the graph + processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision + : singlePrecision); + processor->setPlayConfigDetails (processor->getNumInputChannels(), processor->getNumOutputChannels(), newSampleRate, newBlockSize); @@ -892,10 +984,53 @@ void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph ioProc->setParentGraph (graph); } +//============================================================================== +struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers +{ + AudioProcessorGraphBufferHelpers() + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + } + + void setRenderingBufferSize (int newNumChannels, int newNumSamples) + { + renderingBuffers.floatVersion. setSize (newNumChannels, newNumSamples); + renderingBuffers.doubleVersion.setSize (newNumChannels, newNumSamples); + + renderingBuffers.floatVersion. clear(); + renderingBuffers.doubleVersion.clear(); + } + + void release() + { + renderingBuffers.floatVersion. setSize (1, 1); + renderingBuffers.doubleVersion.setSize (1, 1); + + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (1, 1); + currentAudioOutputBuffer.doubleVersion.setSize (1, 1); + } + + void prepareInOutBuffers(int newNumChannels, int newNumSamples) + { + currentAudioInputBuffer.floatVersion = nullptr; + currentAudioInputBuffer.doubleVersion = nullptr; + + currentAudioOutputBuffer.floatVersion. setSize (newNumChannels, newNumSamples); + currentAudioOutputBuffer.doubleVersion.setSize (newNumChannels, newNumSamples); + } + + FloatAndDoubleComposition > renderingBuffers; + FloatAndDoubleComposition*> currentAudioInputBuffer; + FloatAndDoubleComposition > currentAudioOutputBuffer; +}; + //============================================================================== AudioProcessorGraph::AudioProcessorGraph() - : lastNodeId (0), - currentAudioInputBuffer (nullptr), + : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), currentMidiInputBuffer (nullptr) { } @@ -1140,7 +1275,7 @@ bool AudioProcessorGraph::removeIllegalConnections() static void deleteRenderOpArray (Array& ops) { for (int i = ops.size(); --i >= 0;) - delete static_cast (ops.getUnchecked(i)); + delete static_cast (ops.getUnchecked(i)); } void AudioProcessorGraph::clearRenderingSequence() @@ -1193,7 +1328,7 @@ void AudioProcessorGraph::buildRenderingSequence() { Node* const node = nodes.getUnchecked(i); - node->prepare (getSampleRate(), getBlockSize(), this); + node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); int j = 0; for (; j < orderedNodes.size(); ++j) @@ -1214,8 +1349,7 @@ void AudioProcessorGraph::buildRenderingSequence() // swap over to the new rendering sequence.. const ScopedLock sl (getCallbackLock()); - renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); - renderingBuffers.clear(); + audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); for (int i = midiBuffers.size(); --i >= 0;) midiBuffers.getUnchecked(i)->clear(); @@ -1238,8 +1372,8 @@ void AudioProcessorGraph::handleAsyncUpdate() //============================================================================== void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) { - currentAudioInputBuffer = nullptr; - currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); + audioBuffers->prepareInOutBuffers (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); + currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); @@ -1247,16 +1381,19 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam buildRenderingSequence(); } +bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const +{ + return true; +} + void AudioProcessorGraph::releaseResources() { for (int i = 0; i < nodes.size(); ++i) nodes.getUnchecked(i)->unprepare(); - renderingBuffers.setSize (1, 1); + audioBuffers->release(); midiBuffers.clear(); - currentAudioInputBuffer = nullptr; - currentAudioOutputBuffer.setSize (1, 1); currentMidiInputBuffer = nullptr; currentMidiOutputBuffer.clear(); } @@ -1287,8 +1424,13 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); } -void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) +template +void AudioProcessorGraph::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) { + AudioBuffer& renderingBuffers = audioBuffers->renderingBuffers.get(); + AudioBuffer*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get(); + AudioBuffer& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get(); + const int numSamples = buffer.getNumSamples(); currentAudioInputBuffer = &buffer; @@ -1299,8 +1441,8 @@ void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& m for (int i = 0; i < renderingOps.size(); ++i) { - GraphRenderingOps::AudioGraphRenderingOp* const op - = (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); + GraphRenderingOps::AudioGraphRenderingOpBase* const op + = (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); op->perform (renderingBuffers, midiBuffers, numSamples); } @@ -1331,6 +1473,21 @@ bool AudioProcessorGraph::producesMidi() const { return tru void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} void AudioProcessorGraph::setStateInformation (const void*, int) {} +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +void AudioProcessorGraph::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +// explicit template instantiation +template void AudioProcessorGraph::processAudio ( AudioBuffer& buffer, + MidiBuffer& midiMessages); +template void AudioProcessorGraph::processAudio (AudioBuffer& buffer, + MidiBuffer& midiMessages); //============================================================================== AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) @@ -1384,19 +1541,31 @@ void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() { } -void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, +bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessing() const +{ + return true; +} + +template +void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages) { + AudioBuffer*& currentAudioInputBuffer = + graph->audioBuffers->currentAudioInputBuffer.get(); + + AudioBuffer& currentAudioOutputBuffer = + graph->audioBuffers->currentAudioOutputBuffer.get(); + jassert (graph != nullptr); switch (type) { case audioOutputNode: { - for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), + for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), buffer.getNumChannels()); --i >= 0;) { - graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); + currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); } break; @@ -1404,10 +1573,10 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer case audioInputNode: { - for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), + for (int i = jmin (currentAudioInputBuffer->getNumChannels(), buffer.getNumChannels()); --i >= 0;) { - buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); + buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); } break; @@ -1426,6 +1595,18 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer } } +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + +void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer& buffer, + MidiBuffer& midiMessages) +{ + processAudio (buffer, midiMessages); +} + bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const { return isOutput(); diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 680b86e1f..fa27fc799 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -25,7 +25,6 @@ #ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED #define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED - //============================================================================== /** A type of AudioProcessor which plays back a graph of other AudioProcessors. @@ -92,7 +91,7 @@ public: Node (uint32 nodeId, AudioProcessor*) noexcept; void setParentGraph (AudioProcessorGraph*) const; - void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*); + void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); void unprepare(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) @@ -308,7 +307,9 @@ public: void fillInPluginDescription (PluginDescription&) const override; void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; void releaseResources() override; - void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer& , MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; const String getInputChannelName (int channelIndex) const override; const String getOutputChannelName (int channelIndex) const override; @@ -340,6 +341,10 @@ public: const IODeviceType type; AudioProcessorGraph* graph; + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) }; @@ -347,7 +352,9 @@ public: const String getName() const override; void prepareToPlay (double, int) override; void releaseResources() override; - void processBlock (AudioSampleBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer&, MidiBuffer&) override; + bool supportsDoublePrecisionProcessing() const override; void reset() override; void setNonRealtime (bool) noexcept override; @@ -376,17 +383,21 @@ public: void setStateInformation (const void* data, int sizeInBytes) override; private: + //============================================================================== + template + void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages); + //============================================================================== ReferenceCountedArray nodes; OwnedArray connections; uint32 lastNodeId; - AudioSampleBuffer renderingBuffers; OwnedArray midiBuffers; Array renderingOps; friend class AudioGraphIOProcessor; - AudioSampleBuffer* currentAudioInputBuffer; - AudioSampleBuffer currentAudioOutputBuffer; + struct AudioProcessorGraphBufferHelpers; + ScopedPointer audioBuffers; + MidiBuffer* currentMidiInputBuffer; MidiBuffer currentMidiOutputBuffer; diff --git a/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp b/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp index 2e647ecd6..c8d8df81d 100644 --- a/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp +++ b/source/modules/juce_audio_processors/processors/juce_PluginDescription.cpp @@ -44,6 +44,7 @@ PluginDescription::PluginDescription (const PluginDescription& other) version (other.version), fileOrIdentifier (other.fileOrIdentifier), lastFileModTime (other.lastFileModTime), + lastInfoUpdateTime (other.lastInfoUpdateTime), uid (other.uid), isInstrument (other.isInstrument), numInputChannels (other.numInputChannels), @@ -64,6 +65,7 @@ PluginDescription& PluginDescription::operator= (const PluginDescription& other) uid = other.uid; isInstrument = other.isInstrument; lastFileModTime = other.lastFileModTime; + lastInfoUpdateTime = other.lastInfoUpdateTime; numInputChannels = other.numInputChannels; numOutputChannels = other.numOutputChannels; hasSharedContainer = other.hasSharedContainer; @@ -108,6 +110,7 @@ XmlElement* PluginDescription::createXml() const e->setAttribute ("uid", String::toHexString (uid)); e->setAttribute ("isInstrument", isInstrument); e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); + e->setAttribute ("infoUpdateTime", String::toHexString (lastInfoUpdateTime.toMilliseconds())); e->setAttribute ("numInputs", numInputChannels); e->setAttribute ("numOutputs", numOutputChannels); e->setAttribute ("isShell", hasSharedContainer); @@ -129,6 +132,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml) uid = xml.getStringAttribute ("uid").getHexValue32(); isInstrument = xml.getBoolAttribute ("isInstrument", false); lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); + lastInfoUpdateTime = Time (xml.getStringAttribute ("infoUpdateTime").getHexValue64()); numInputChannels = xml.getIntAttribute ("numInputs"); numOutputChannels = xml.getIntAttribute ("numOutputs"); hasSharedContainer = xml.getBoolAttribute ("isShell", false); diff --git a/source/modules/juce_audio_processors/processors/juce_PluginDescription.h b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h index 83effceac..4b888ec10 100644 --- a/source/modules/juce_audio_processors/processors/juce_PluginDescription.h +++ b/source/modules/juce_audio_processors/processors/juce_PluginDescription.h @@ -81,6 +81,11 @@ public: */ Time lastFileModTime; + /** The last time that this information was updated. This would typically have + been during a scan when this plugin was first tested or found to have changed. + */ + Time lastInfoUpdateTime; + /** A unique ID for the plug-in. Note that this might not be unique between formats, e.g. a VST and some diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp index ad37837ce..ce569230f 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.cpp @@ -263,6 +263,7 @@ struct PluginSorter case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName); break; case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break; case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break; + case KnownPluginList::sortByInfoUpdateTime: diff = compare (first->lastInfoUpdateTime, second->lastInfoUpdateTime); break; default: break; } @@ -278,6 +279,14 @@ private: return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false); } + static int compare (Time a, Time b) noexcept + { + if (a < b) return -1; + if (b < a) return 1; + + return 0; + } + const KnownPluginList::SortMethod method; const int direction; diff --git a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h index d04dd894f..7bd38f06c 100644 --- a/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h +++ b/source/modules/juce_audio_processors/scanning/juce_KnownPluginList.h @@ -136,7 +136,8 @@ public: sortByCategory, sortByManufacturer, sortByFormat, - sortByFileSystemLocation + sortByFileSystemLocation, + sortByInfoUpdateTime }; //============================================================================== diff --git a/source/modules/juce_core/containers/juce_Array.h b/source/modules/juce_core/containers/juce_Array.h index c34b74383..2211fcf85 100644 --- a/source/modules/juce_core/containers/juce_Array.h +++ b/source/modules/juce_core/containers/juce_Array.h @@ -216,13 +216,18 @@ public: } //============================================================================== - /** Returns the current number of elements in the array. - */ + /** Returns the current number of elements in the array. */ inline int size() const noexcept { return numUsed; } + /** Returns true if the array is empty, false otherwise. */ + inline bool empty() const noexcept + { + return size() == 0; + } + /** Returns one of the elements in the array. If the index passed in is beyond the range of valid elements, this will return a default value. diff --git a/source/modules/juce_core/files/juce_File.cpp b/source/modules/juce_core/files/juce_File.cpp index 87b9fb46f..de6790e9c 100644 --- a/source/modules/juce_core/files/juce_File.cpp +++ b/source/modules/juce_core/files/juce_File.cpp @@ -886,6 +886,41 @@ File File::createTempFile (StringRef fileNameEnding) return tempFile; } +bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const +{ + if (linkFileToCreate.exists()) + { + if (! linkFileToCreate.isSymbolicLink()) + { + // user has specified an existing file / directory as the link + // this is bad! the user could end up unintentionally destroying data + jassertfalse; + return false; + } + + if (overwriteExisting) + linkFileToCreate.deleteFile(); + } + + #if JUCE_MAC || JUCE_LINUX + // one common reason for getting an error here is that the file already exists + if (symlink (fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) + { + jassertfalse; + return false; + } + + return true; + #elif JUCE_WINDOWS + return CreateSymbolicLink (linkFileToCreate.getFullPathName().toWideCharPointer(), + fullPath.toWideCharPointer(), + isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE; + #else + jassertfalse; // symbolic links not supported on this platform! + return false; + #endif +} + //============================================================================== MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) : address (nullptr), range (0, file.getSize()), fileHandle (0) diff --git a/source/modules/juce_core/files/juce_File.h b/source/modules/juce_core/files/juce_File.h index c8e3bbcaa..f54096cc9 100644 --- a/source/modules/juce_core/files/juce_File.h +++ b/source/modules/juce_core/files/juce_File.h @@ -360,14 +360,6 @@ public: */ bool isHidden() const; - /** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ - bool isLink() const; - - /** If this file is a link or alias, this returns the file that it points to. - If the file isn't actually link, it'll just return itself. - */ - 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 @@ -880,7 +872,6 @@ public: */ static File createTempFile (StringRef fileNameEnding); - //============================================================================== /** Returns the current working directory. @see setAsCurrentWorkingDirectory @@ -946,8 +937,28 @@ public: /** Adds a separator character to the end of a path if it doesn't already have one. */ static String addTrailingSeparator (const String& path); - #if JUCE_MAC || JUCE_IOS || DOXYGEN //============================================================================== + /** Tries to create a symbolic link and returns a boolean to indicate success */ + bool createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const; + + /** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ + bool isSymbolicLink() const; + + /** If this file is a link or alias, this returns the file that it points to. + If the file isn't actually link, it'll just return itself. + */ + File getLinkedTarget() const; + + #if JUCE_WINDOWS + /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ + bool createShortcut (const String& description, const File& linkFileToCreate) const; + + /** Windows ONLY - Returns true if this is a win32 .LNK file. */ + bool isShortcut() const; + #endif + + //============================================================================== + #if JUCE_MAC || JUCE_IOS || DOXYGEN /** OSX ONLY - Finds the OSType of a file from the its resources. */ OSType getMacOSType() const; @@ -960,11 +971,6 @@ public: void addToDock() const; #endif - #if JUCE_WINDOWS - /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ - bool createLink (const String& description, const File& linkFileToCreate) const; - #endif - private: //============================================================================== String fullPath; diff --git a/source/modules/juce_core/maths/juce_NormalisableRange.h b/source/modules/juce_core/maths/juce_NormalisableRange.h index f8a9bc850..150162da6 100644 --- a/source/modules/juce_core/maths/juce_NormalisableRange.h +++ b/source/modules/juce_core/maths/juce_NormalisableRange.h @@ -136,6 +136,8 @@ public: return v; } + Range getRange() const noexcept { return Range (start, end); } + /** The start of the non-normalised range. */ ValueType start; diff --git a/source/modules/juce_core/memory/juce_ByteOrder.h b/source/modules/juce_core/memory/juce_ByteOrder.h index b4f7df64b..680f432f3 100644 --- a/source/modules/juce_core/memory/juce_ByteOrder.h +++ b/source/modules/juce_core/memory/juce_ByteOrder.h @@ -48,24 +48,54 @@ public: static uint64 swap (uint64 value) noexcept; //============================================================================== - /** Swaps the byte order of a 16-bit int if the CPU is big-endian */ + /** Swaps the byte order of a 16-bit unsigned int if the CPU is big-endian */ static uint16 swapIfBigEndian (uint16 value) noexcept; - /** Swaps the byte order of a 32-bit int if the CPU is big-endian */ + /** Swaps the byte order of a 32-bit unsigned int if the CPU is big-endian */ static uint32 swapIfBigEndian (uint32 value) noexcept; - /** Swaps the byte order of a 64-bit int if the CPU is big-endian */ + /** Swaps the byte order of a 64-bit unsigned int if the CPU is big-endian */ static uint64 swapIfBigEndian (uint64 value) noexcept; - /** Swaps the byte order of a 16-bit int if the CPU is little-endian */ + /** Swaps the byte order of a 16-bit signed int if the CPU is big-endian */ + static int16 swapIfBigEndian (int16 value) noexcept; + + /** Swaps the byte order of a 32-bit signed int if the CPU is big-endian */ + static int32 swapIfBigEndian (int32 value) noexcept; + + /** Swaps the byte order of a 64-bit signed int if the CPU is big-endian */ + static int64 swapIfBigEndian (int64 value) noexcept; + + /** Swaps the byte order of a 32-bit float if the CPU is big-endian */ + static float swapIfBigEndian (float value) noexcept; + + /** Swaps the byte order of a 64-bit float if the CPU is big-endian */ + static double swapIfBigEndian (double value) noexcept; + + /** Swaps the byte order of a 16-bit unsigned int if the CPU is little-endian */ static uint16 swapIfLittleEndian (uint16 value) noexcept; - /** Swaps the byte order of a 32-bit int if the CPU is little-endian */ + /** Swaps the byte order of a 32-bit unsigned int if the CPU is little-endian */ static uint32 swapIfLittleEndian (uint32 value) noexcept; - /** Swaps the byte order of a 64-bit int if the CPU is little-endian */ + /** Swaps the byte order of a 64-bit unsigned int if the CPU is little-endian */ static uint64 swapIfLittleEndian (uint64 value) noexcept; + /** Swaps the byte order of a 16-bit signed int if the CPU is little-endian */ + static int16 swapIfLittleEndian (int16 value) noexcept; + + /** Swaps the byte order of a 32-bit signed int if the CPU is little-endian */ + static int32 swapIfLittleEndian (int32 value) noexcept; + + /** Swaps the byte order of a 64-bit signed int if the CPU is little-endian */ + static int64 swapIfLittleEndian (int64 value) noexcept; + + /** Swaps the byte order of a 32-bit float if the CPU is little-endian */ + static float swapIfLittleEndian (float value) noexcept; + + /** Swaps the byte order of a 64-bit float if the CPU is little-endian */ + static double swapIfLittleEndian (double value) noexcept; + //============================================================================== /** Turns 4 bytes into a little-endian integer. */ static uint32 littleEndianInt (const void* bytes) noexcept; @@ -161,9 +191,21 @@ inline uint64 ByteOrder::swap (uint64 value) noexcept inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return v; } inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return v; } inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return v; } + inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return v; } + inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return v; } + inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return v; } + inline float ByteOrder::swapIfBigEndian (const float v) noexcept { return v; } + inline double ByteOrder::swapIfBigEndian (const double v) noexcept { return v; } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return swap (v); } inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return swap (v); } inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return swap (v); } + inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return static_cast (swap (static_cast (v))); } +inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return *static_cast (bytes); } inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return *static_cast (bytes); } inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return *static_cast (bytes); } @@ -175,9 +217,21 @@ inline uint64 ByteOrder::swap (uint64 value) noexcept inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return swap (v); } inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return swap (v); } inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return swap (v); } + inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return static_cast (swap (static_cast (v))); } + inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return static_cast (swap (static_cast (v))); } + inline float ByteOrder::swapIfBigEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline double ByteOrder::swapIfBigEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } + inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return v; } inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return v; } inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return v; } + inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return v; } + inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return v; } + inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return v; } + inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { return v; } + inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { return v; } + inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return swap (*static_cast (bytes)); } diff --git a/source/modules/juce_core/native/java/JuceAppActivity.java b/source/modules/juce_core/native/java/JuceAppActivity.java index ea5f18577..f61b29a1e 100644 --- a/source/modules/juce_core/native/java/JuceAppActivity.java +++ b/source/modules/juce_core/native/java/JuceAppActivity.java @@ -30,28 +30,41 @@ import android.content.DialogInterface; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; +import android.os.Looper; +import android.os.Handler; +import android.os.Build; +import android.os.Process; +import android.os.ParcelUuid; import android.view.*; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.graphics.*; -import android.opengl.*; import android.text.ClipboardManager; import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; +import java.lang.Runnable; +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.TimerTask; import java.io.*; import java.net.URL; import java.net.HttpURLConnection; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; import android.media.AudioManager; import android.media.MediaScannerConnection; import android.media.MediaScannerConnection.MediaScannerConnectionClient; +$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer! + + //============================================================================== public class JuceAppActivity extends Activity { @@ -61,6 +74,58 @@ public class JuceAppActivity extends Activity System.loadLibrary ("juce_jni"); } + //============================================================================== + public static class MidiPortID extends Object + { + public MidiPortID (int index, boolean direction) + { + androidIndex = index; + isInput = direction; + } + + public int androidIndex; + public boolean isInput; + + @Override + public int hashCode() + { + Integer i = new Integer (androidIndex); + return i.hashCode() * (isInput ? -1 : 1); + } + + @Override + public boolean equals (Object obj) + { + if (obj == null) + return false; + + if (getClass() != obj.getClass()) + return false; + + MidiPortID other = (MidiPortID) obj; + return (androidIndex == other.androidIndex && isInput == other.isInput); + } + } + + public interface JuceMidiPort + { + boolean isInputPort(); + + // start, stop does nothing on an output port + void start(); + void stop(); + + void close(); + MidiPortID getPortId(); + + // send will do nothing on an input port + void sendMidi (byte[] msg, int offset, int count); + } + + //============================================================================== + $$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the introjucer! + + //============================================================================== @Override public void onCreate (Bundle savedInstanceState) { @@ -85,9 +150,6 @@ public class JuceAppActivity extends Activity @Override protected void onPause() { - if (viewHolder != null) - viewHolder.onPause(); - suspendApp(); super.onPause(); } @@ -96,10 +158,6 @@ public class JuceAppActivity extends Activity protected void onResume() { super.onResume(); - - if (viewHolder != null) - viewHolder.onResume(); - resumeApp(); } @@ -142,7 +200,10 @@ public class JuceAppActivity extends Activity //============================================================================== private ViewHolder viewHolder; + private MidiDeviceManager midiDeviceManager = null; + private BluetoothManager bluetoothManager = null; private boolean isScreenSaverEnabled; + private java.util.Timer keepAliveTimer; public final ComponentPeerView createNewView (boolean opaque, long host) { @@ -159,7 +220,7 @@ public class JuceAppActivity extends Activity group.removeView (view); } - public final void deleteOpenGLView (OpenGLView view) + public final void deleteNativeSurfaceView (NativeSurfaceView view) { ViewGroup group = (ViewGroup) (view.getParent()); @@ -187,28 +248,6 @@ public class JuceAppActivity extends Activity } } - public final void onPause() - { - for (int i = getChildCount(); --i >= 0;) - { - View v = getChildAt (i); - - if (v instanceof ComponentPeerView) - ((ComponentPeerView) v).onPause(); - } - } - - public final void onResume() - { - for (int i = getChildCount(); --i >= 0;) - { - View v = getChildAt (i); - - if (v instanceof ComponentPeerView) - ((ComponentPeerView) v).onResume(); - } - } - private final int getDPI() { DisplayMetrics metrics = new DisplayMetrics(); @@ -230,14 +269,46 @@ public class JuceAppActivity extends Activity if (isScreenSaverEnabled != enabled) { isScreenSaverEnabled = enabled; + + if (keepAliveTimer != null) + { + keepAliveTimer.cancel(); + keepAliveTimer = null; + } + if (enabled) + { getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else + { getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // If no user input is received after about 3 seconds, the OS will lower the + // task's priority, so this timer forces it to be kept active. + keepAliveTimer = new java.util.Timer(); + + keepAliveTimer.scheduleAtFixedRate (new TimerTask() + { + @Override + public void run() + { + android.app.Instrumentation instrumentation = new android.app.Instrumentation(); + + try + { + instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN); + } + catch (Exception e) + { + } + } + }, 2000, 2000); + } } } - public final boolean getScreenSaver () + public final boolean getScreenSaver() { return isScreenSaverEnabled; } @@ -546,70 +617,83 @@ public class JuceAppActivity extends Activity { return true; //xxx needs to check overlapping views } + } - public final void onPause() - { - for (int i = getChildCount(); --i >= 0;) - { - View v = getChildAt (i); + //============================================================================== + public static class NativeSurfaceView extends SurfaceView + implements SurfaceHolder.Callback + { + private long nativeContext = 0; - if (v instanceof OpenGLView) - ((OpenGLView) v).onPause(); - } + NativeSurfaceView (Context context, long nativeContextPtr) + { + super (context); + nativeContext = nativeContextPtr; } - public final void onResume() + public Surface getNativeSurface() { - for (int i = getChildCount(); --i >= 0;) - { - View v = getChildAt (i); + Surface retval = null; - if (v instanceof OpenGLView) - ((OpenGLView) v).onResume(); - } + SurfaceHolder holder = getHolder(); + if (holder != null) + retval = holder.getSurface(); + + return retval; } - public OpenGLView createGLView() + //============================================================================== + @Override + public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) { - OpenGLView glView = new OpenGLView (getContext()); - addView (glView); - return glView; + surfaceChangedNative (nativeContext, holder, format, width, height); } - } - //============================================================================== - public final class OpenGLView extends GLSurfaceView - implements GLSurfaceView.Renderer - { - OpenGLView (Context context) + @Override + public void surfaceCreated (SurfaceHolder holder) { - super (context); - setEGLContextClientVersion (2); - setRenderer (this); - setRenderMode (RENDERMODE_WHEN_DIRTY); + surfaceCreatedNative (nativeContext, holder); + } + + @Override + public void surfaceDestroyed (SurfaceHolder holder) + { + surfaceDestroyedNative (nativeContext, holder); } @Override - public void onSurfaceCreated (GL10 unused, EGLConfig config) + protected void dispatchDraw (Canvas canvas) { - contextCreated(); + super.dispatchDraw (canvas); + dispatchDrawNative (nativeContext, canvas); } + //============================================================================== @Override - public void onSurfaceChanged (GL10 unused, int width, int height) + protected void onAttachedToWindow () { - contextChangedSize(); + super.onAttachedToWindow(); + getHolder().addCallback (this); } @Override - public void onDrawFrame (GL10 unused) + protected void onDetachedFromWindow () { - render(); + super.onDetachedFromWindow(); + getHolder().removeCallback (this); } - private native void contextCreated(); - private native void contextChangedSize(); - private native void render(); + //============================================================================== + private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas); + private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder); + private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); + private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, + int format, int width, int height); + } + + public NativeSurfaceView createNativeSurfaceView(long nativeSurfacePtr) + { + return new NativeSurfaceView (this, nativeSurfacePtr); } //============================================================================== @@ -944,4 +1028,70 @@ public class JuceAppActivity extends Activity return null; } + + public final int getAndroidSDKVersion() + { + return android.os.Build.VERSION.SDK_INT; + } + + public final String audioManagerGetProperty (String property) + { + Object obj = getSystemService (AUDIO_SERVICE); + if (obj == null) + return null; + + java.lang.reflect.Method method; + try { + method = obj.getClass().getMethod ("getProperty", String.class); + } catch (SecurityException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } + + if (method == null) + return null; + + try { + return (String) method.invoke (obj, property); + } catch (java.lang.IllegalArgumentException e) { + } catch (java.lang.IllegalAccessException e) { + } catch (java.lang.reflect.InvocationTargetException e) { + } + + return null; + } + + public final int setCurrentThreadPriority (int priority) + { + android.os.Process.setThreadPriority (android.os.Process.myTid(), priority); + return android.os.Process.getThreadPriority (android.os.Process.myTid()); + } + + public final boolean hasSystemFeature (String property) + { + return getPackageManager().hasSystemFeature (property); + } + + private static class JuceThread extends Thread + { + public JuceThread (long host) + { + _this = host; + } + + public void run() + { + runThread(_this); + } + + private native void runThread (long host); + private long _this; + } + + public final Thread createNewThread(long host) + { + return new JuceThread(host); + } + } diff --git a/source/modules/juce_core/native/juce_android_JNIHelpers.h b/source/modules/juce_core/native/juce_android_JNIHelpers.h index 49c5aa178..0776d7199 100644 --- a/source/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/source/modules/juce_core/native/juce_android_JNIHelpers.h @@ -36,6 +36,10 @@ //============================================================================== extern JNIEnv* getEnv() noexcept; +// You should rarely need to use this function. Only if you expect callbacks +// on a java thread which you did not create yourself. +extern void setEnv (JNIEnv* env) noexcept; + //============================================================================== class GlobalRef { @@ -236,6 +240,8 @@ private: #define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ extern "C" __attribute__ ((visibility("default"))) JUCE_ARM_SOFT_FLOAT_ABI returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params + + //============================================================================== class AndroidSystem { @@ -253,142 +259,11 @@ public: extern AndroidSystem android; -//============================================================================== -class ThreadLocalJNIEnvHolder -{ -public: - ThreadLocalJNIEnvHolder() noexcept - : jvm (nullptr) - { - zeromem (threads, sizeof (threads)); - zeromem (envs, sizeof (envs)); - } - - void initialise (JNIEnv* env) - { - // NB: the DLL can be left loaded by the JVM, so the same static - // objects can end up being reused by subsequent runs of the app - zeromem (threads, sizeof (threads)); - zeromem (envs, sizeof (envs)); - - env->GetJavaVM (&jvm); - addEnv (env); - } - - JNIEnv* attach() noexcept - { - if (android.activity != nullptr) - { - if (JNIEnv* env = attachToCurrentThread()) - { - SpinLock::ScopedLockType sl (addRemoveLock); - return addEnv (env); - } - - jassertfalse; - } - - return nullptr; - } - - void detach() noexcept - { - if (android.activity != nullptr) - { - jvm->DetachCurrentThread(); - removeCurrentThreadFromCache(); - } - } - - void removeCurrentThreadFromCache() - { - const pthread_t thisThread = pthread_self(); - - SpinLock::ScopedLockType sl (addRemoveLock); - - for (int i = 0; i < maxThreads; ++i) - { - if (threads[i] == thisThread) - { - threads[i] = 0; - envs[i] = nullptr; - } - } - } - - JNIEnv* getOrAttach() noexcept - { - if (JNIEnv* env = get()) - return env; - - SpinLock::ScopedLockType sl (addRemoveLock); - - if (JNIEnv* env = get()) - return env; - - if (JNIEnv* env = attachToCurrentThread()) - return addEnv (env); - - return nullptr; - } - -private: - JavaVM* jvm; - enum { maxThreads = 32 }; - pthread_t threads [maxThreads]; - JNIEnv* envs [maxThreads]; - SpinLock addRemoveLock; - - JNIEnv* addEnv (JNIEnv* env) noexcept - { - const pthread_t thisThread = pthread_self(); - - for (int i = 0; i < maxThreads; ++i) - { - if (threads[i] == 0) - { - envs[i] = env; - threads[i] = thisThread; - return env; - } - } - - jassertfalse; // too many threads! - return nullptr; - } - - JNIEnv* get() const noexcept - { - const pthread_t thisThread = pthread_self(); - - for (int i = 0; i < maxThreads; ++i) - if (threads[i] == thisThread) - return envs[i]; - - return nullptr; - } - - JNIEnv* attachToCurrentThread() - { - JNIEnv* env = nullptr; - jvm->AttachCurrentThread (&env, nullptr); - return env; - } -}; - -extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; - -struct AndroidThreadScope -{ - AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); } - ~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); } -}; - //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ - METHOD (deleteOpenGLView, "deleteOpenGLView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;)V") \ + METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ METHOD (postMessage, "postMessage", "(J)V") \ METHOD (finish, "finish", "()V") \ METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ @@ -405,7 +280,14 @@ struct AndroidThreadScope METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ - METHOD (getScreenSaver, "getScreenSaver", "()Z") + METHOD (getScreenSaver, "getScreenSaver", "()Z") \ + METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \ + METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ + METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ + METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ + METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ + METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ + METHOD (createNewThread, "createNewThread", "(J)Ljava/lang/Thread;") \ DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); #undef JNI_CLASS_MEMBERS @@ -435,6 +317,19 @@ DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); #undef JNI_CLASS_MEMBERS +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (start, "start", "()V") \ + METHOD (stop, "stop", "()V") \ + METHOD (setName, "setName", "(Ljava/lang/String;)V") \ + METHOD (getName, "getName", "()Ljava/lang/String;") \ + METHOD (getId, "getId", "()J") \ + STATICMETHOD (currentThread, "currentThread", "()Ljava/lang/Thread;") \ + METHOD (setPriority, "setPriority", "(I)V") \ + +DECLARE_JNI_CLASS (JuceThread, "java/lang/Thread"); +#undef JNI_CLASS_MEMBERS + //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ METHOD (constructor, "", "(IIII)V") \ diff --git a/source/modules/juce_core/native/juce_android_SystemStats.cpp b/source/modules/juce_core/native/juce_android_SystemStats.cpp index 07facfc56..16e468bbc 100644 --- a/source/modules/juce_core/native/juce_android_SystemStats.cpp +++ b/source/modules/juce_core/native/juce_android_SystemStats.cpp @@ -98,24 +98,19 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c } //============================================================================== -ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; - -#if JUCE_DEBUG -static bool systemInitialised = false; -#endif +ThreadLocalValue androidJNIEnv; JNIEnv* getEnv() noexcept { - #if JUCE_DEBUG - if (! systemInitialised) - { - DBG ("*** Call to getEnv() when system not initialised"); - jassertfalse; - std::exit (EXIT_FAILURE); - } - #endif + JNIEnv* env = androidJNIEnv.get(); + jassert (env != nullptr); - return threadLocalJNIEnvHolder.getOrAttach(); + return env; +} + +void setEnv (JNIEnv* env) noexcept +{ + androidJNIEnv.get() = env; } extern "C" jint JNI_OnLoad (JavaVM*, void*) @@ -134,11 +129,6 @@ void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dpi = 160; JNIClassBase::initialiseAllClasses (env); - threadLocalJNIEnvHolder.initialise (env); - #if JUCE_DEBUG - systemInitialised = true; - #endif - activity = GlobalRef (act); appFile = juceString (env, file); appDataDir = juceString (env, dataDir); @@ -148,10 +138,6 @@ void AndroidSystem::shutdown (JNIEnv* env) { activity.clear(); - #if JUCE_DEBUG - systemInitialised = false; - #endif - JNIClassBase::releaseAllClasses (env); } @@ -253,7 +239,7 @@ String SystemStats::getLogonName() if (struct passwd* const pw = getpwuid (getuid())) return CharPointer_UTF8 (pw->pw_name); - return String::empty; + return String(); } String SystemStats::getFullUserName() @@ -267,7 +253,7 @@ String SystemStats::getComputerName() if (gethostname (name, sizeof (name) - 1) == 0) return name; - return String::empty; + return String(); } diff --git a/source/modules/juce_core/native/juce_android_Threads.cpp b/source/modules/juce_core/native/juce_android_Threads.cpp index b601e9792..2cca156bb 100644 --- a/source/modules/juce_core/native/juce_android_Threads.cpp +++ b/source/modules/juce_core/native/juce_android_Threads.cpp @@ -74,3 +74,235 @@ JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} + +struct AndroidThreadData +{ + AndroidThreadData (Thread* thread) noexcept + : owner (thread), tId (0) + { + } + + Thread* owner; + Thread::ThreadID tId; + WaitableEvent eventSet, eventGet; +}; + +void JUCE_API juce_threadEntryPoint (void*); + +extern "C" void* threadEntryProc (void*); +extern "C" void* threadEntryProc (void* userData) +{ + ScopedPointer priv (reinterpret_cast (userData)); + priv->tId = (Thread::ThreadID) pthread_self(); + priv->eventSet.signal(); + priv->eventGet.wait (-1); + + juce_threadEntryPoint (priv->owner); + + return nullptr; +} + +JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, + void, (JNIEnv* env, jobject device, jlong host)) +{ + // Java may create a Midi thread which JUCE doesn't know about and this callback may be + // received on this thread. Java will have already created a JNI Env for this new thread, + // which we need to tell Juce about + setEnv (env); + + if (Thread* thread = reinterpret_cast (host)) + threadEntryProc (thread); +} + +void Thread::launchThread() +{ + threadHandle = 0; + + ScopedPointer threadPrivateData = new AndroidThreadData (this); + + jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, (jlong) threadPrivateData.get()); + + if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) + { + AndroidThreadData* priv = threadPrivateData.release(); + + threadHandle = (void*) juceThread; + getEnv()->CallVoidMethod (juceThread, JuceThread.start); + + priv->eventSet.wait (-1); + threadId = priv->tId; + priv->eventGet.signal(); + } +} + +void Thread::closeThreadHandle() +{ + if (threadHandle != 0) + { + jobject juceThread = reinterpret_cast (threadHandle); + getEnv()->DeleteGlobalRef (juceThread); + threadHandle = 0; + } + + threadId = 0; +} + +void Thread::killThread() +{ + if (threadHandle != 0) + { + jobject juceThread = reinterpret_cast (threadHandle); + getEnv()->CallVoidMethod (juceThread, JuceThread.stop); + } +} + +void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) +{ + LocalRef juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); + + if (jobject t = juceThread.get()) + getEnv()->CallVoidMethod (t, JuceThread.setName, javaString (name).get()); +} + +bool Thread::setThreadPriority (void* handle, int priority) +{ + if (handle == nullptr) + { + LocalRef juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); + + if (jobject t = juceThread.get()) + return setThreadPriority (t, priority); + + return false; + } + + jobject juceThread = reinterpret_cast (handle); + + const int minPriority = 1; + const int maxPriority = 10; + + jint javaPriority = ((maxPriority - minPriority) * priority) / 10 + minPriority; + + getEnv()->CallVoidMethod (juceThread, JuceThread.setPriority, javaPriority); + + return true; +} + +//============================================================================== +struct HighResolutionTimer::Pimpl +{ + struct HighResolutionThread : public Thread + { + HighResolutionThread (HighResolutionTimer::Pimpl& parent) + : Thread ("High Resolution Timer"), pimpl (parent) + { + startThread(); + } + + void run() override + { + pimpl.timerThread(); + } + + private: + HighResolutionTimer::Pimpl& pimpl; + }; + + //============================================================================== + Pimpl (HighResolutionTimer& t) : owner (t) {} + + ~Pimpl() + { + stop(); + } + + void start (int newPeriod) + { + if (periodMs != newPeriod) + { + if (thread.get() == nullptr + || thread->getThreadId() != Thread::getCurrentThreadId() + || thread->threadShouldExit()) + { + stop(); + + periodMs = newPeriod; + + thread = new HighResolutionThread (*this); + } + else + { + periodMs = newPeriod; + } + } + } + + void stop() + { + if (thread.get() != nullptr) + { + thread->signalThreadShouldExit(); + + if (thread->getThreadId() != Thread::getCurrentThreadId()) + { + thread->waitForThreadToExit (-1); + thread = nullptr; + } + } + } + + HighResolutionTimer& owner; + int volatile periodMs; + +private: + ScopedPointer thread; + + void timerThread() + { + jassert (thread.get() != nullptr); + + int lastPeriod = periodMs; + Clock clock (lastPeriod); + + while (! thread->threadShouldExit()) + { + clock.wait(); + owner.hiResTimerCallback(); + + if (lastPeriod != periodMs) + { + lastPeriod = periodMs; + clock = Clock (lastPeriod); + } + } + + periodMs = 0; + } + + struct Clock + { + Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) + { + } + + void wait() noexcept + { + struct timespec t; + t.tv_sec = (time_t) (delta / 1000000000); + t.tv_nsec = (long) (delta % 1000000000); + nanosleep (&t, nullptr); + } + + uint64 delta; + }; + + static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) + { + ignoreUnused (periodMs); + struct sched_param param; + param.sched_priority = sched_get_priority_max (SCHED_RR); + return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; + } + + JUCE_DECLARE_NON_COPYABLE (Pimpl) +}; diff --git a/source/modules/juce_core/native/juce_linux_CommonFile.cpp b/source/modules/juce_core/native/juce_linux_CommonFile.cpp index 5f6c93b72..9f2d3e938 100644 --- a/source/modules/juce_core/native/juce_linux_CommonFile.cpp +++ b/source/modules/juce_core/native/juce_linux_CommonFile.cpp @@ -65,7 +65,7 @@ static String getLinkedFile (const String& file) return String::fromUTF8 (buffer, jmax (0, numBytes)); }; -bool File::isLink() const +bool File::isSymbolicLink() const { return getLinkedFile (getFullPathName()).isNotEmpty(); } diff --git a/source/modules/juce_core/native/juce_linux_Files.cpp b/source/modules/juce_core/native/juce_linux_Files.cpp index a8f641b41..1f2a7e5a9 100644 --- a/source/modules/juce_core/native/juce_linux_Files.cpp +++ b/source/modules/juce_core/native/juce_linux_Files.cpp @@ -158,7 +158,7 @@ File File::getSpecialLocation (const SpecialLocationType type) case hostApplicationPath: { const File f ("/proc/self/exe"); - return f.isLink() ? f.getLinkedTarget() : juce_getExecutableFile(); + return f.isSymbolicLink() ? f.getLinkedTarget() : juce_getExecutableFile(); } default: diff --git a/source/modules/juce_core/native/juce_mac_Files.mm b/source/modules/juce_core/native/juce_mac_Files.mm index e65a59bad..035cc43ed 100644 --- a/source/modules/juce_core/native/juce_mac_Files.mm +++ b/source/modules/juce_core/native/juce_mac_Files.mm @@ -284,7 +284,7 @@ static NSString* getFileLink (const String& path) #endif } -bool File::isLink() const +bool File::isSymbolicLink() const { return getFileLink (fullPath) != nil; } @@ -400,7 +400,12 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& { JUCE_AUTORELEASEPOOL { - NSURL* filenameAsURL = [NSURL URLWithString: juceStringToNS (fileName)]; + NSString* fileNameAsNS (juceStringToNS (fileName)); + + NSURL* filenameAsURL ([NSURL URLWithString: fileNameAsNS]); + + if (filenameAsURL == nil) + filenameAsURL = [NSURL fileURLWithPath: fileNameAsNS]; #if JUCE_IOS (void) parameters; diff --git a/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h index c4fa9c54c..edfcf5d79 100644 --- a/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -867,6 +867,7 @@ void InterProcessLock::exit() } //============================================================================== +#if ! JUCE_ANDROID void JUCE_API juce_threadEntryPoint (void*); extern "C" void* threadEntryProc (void*); @@ -874,10 +875,6 @@ extern "C" void* threadEntryProc (void* userData) { JUCE_AUTORELEASEPOOL { - #if JUCE_ANDROID - const AndroidThreadScope androidEnv; - #endif - juce_threadEntryPoint (userData); } @@ -951,6 +948,7 @@ bool Thread::setThreadPriority (void* handle, int priority) param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; } +#endif Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() { @@ -1180,6 +1178,7 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) } //============================================================================== +#if ! JUCE_ANDROID struct HighResolutionTimer::Pimpl { Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) @@ -1286,20 +1285,6 @@ private: uint64_t time, delta; - #elif JUCE_ANDROID - Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) - { - } - - void wait() noexcept - { - struct timespec t; - t.tv_sec = (time_t) (delta / 1000000000); - t.tv_nsec = (long) (delta % 1000000000); - nanosleep (&t, nullptr); - } - - uint64 delta; #else Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) { @@ -1348,3 +1333,5 @@ private: JUCE_DECLARE_NON_COPYABLE (Pimpl) }; + +#endif diff --git a/source/modules/juce_core/native/juce_win32_Files.cpp b/source/modules/juce_core/native/juce_win32_Files.cpp index 5408efaea..18a1999f5 100644 --- a/source/modules/juce_core/native/juce_win32_Files.cpp +++ b/source/modules/juce_core/native/juce_win32_Files.cpp @@ -631,13 +631,45 @@ String File::getVersion() const } //============================================================================== -bool File::isLink() const +bool File::isSymbolicLink() const +{ + return (GetFileAttributes (fullPath.toWideCharPointer()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0; +} + +bool File::isShortcut() const { return hasFileExtension (".lnk"); } File File::getLinkedTarget() const { + { + HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), + GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + + if (h != INVALID_HANDLE_VALUE) + { + DWORD requiredSize = ::GetFinalPathNameByHandleW (h, nullptr, 0, FILE_NAME_NORMALIZED); + + if (requiredSize > 0) + { + HeapBlock buffer (requiredSize + 2); + buffer.clear (requiredSize + 2); + + requiredSize = ::GetFinalPathNameByHandleW (h, buffer, requiredSize, FILE_NAME_NORMALIZED); + + if (requiredSize > 0) + { + CloseHandle (h); + return File (String (buffer)); + } + } + + CloseHandle (h); + } + } + File result (*this); String p (getFullPathName()); @@ -664,7 +696,7 @@ File File::getLinkedTarget() const return result; } -bool File::createLink (const String& description, const File& linkFileToCreate) const +bool File::createShortcut (const String& description, const File& linkFileToCreate) const { linkFileToCreate.deleteFile(); diff --git a/source/modules/juce_core/system/juce_CompilerSupport.h b/source/modules/juce_core/system/juce_CompilerSupport.h index 103cd5132..9b903c8e1 100644 --- a/source/modules/juce_core/system/juce_CompilerSupport.h +++ b/source/modules/juce_core/system/juce_CompilerSupport.h @@ -42,6 +42,7 @@ #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 #define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 + #define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 @@ -93,6 +94,10 @@ #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 #endif + #if __has_feature (cxx_static_assert) + #define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 + #endif + #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 #endif @@ -115,6 +120,7 @@ #if _MSC_VER >= 1600 #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 + #define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 #endif #if _MSC_VER >= 1700 diff --git a/source/modules/juce_core/system/juce_PlatformDefs.h b/source/modules/juce_core/system/juce_PlatformDefs.h index 64dad9000..54ab399a6 100644 --- a/source/modules/juce_core/system/juce_PlatformDefs.h +++ b/source/modules/juce_core/system/juce_PlatformDefs.h @@ -149,20 +149,44 @@ #endif //============================================================================== -#ifndef DOXYGEN -namespace juce -{ - template struct JuceStaticAssert; - template <> struct JuceStaticAssert { static void dummy() {} }; -} +#if ! DOXYGEN + #define JUCE_JOIN_MACRO_HELPER(a, b) a ## b + #define JUCE_STRINGIFY_MACRO_HELPER(a) #a #endif -/** A compile-time assertion macro. - If the expression parameter is false, the macro will cause a compile error. (The actual error - message that the compiler generates may be completely bizarre and seem to have no relation to - the place where you put the static_assert though!) +/** A good old-fashioned C macro concatenation helper. + This combines two items (which may themselves be macros) into a single string, + avoiding the pitfalls of the ## macro operator. */ -#define static_jassert(expression) juce::JuceStaticAssert::dummy(); +#define JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) + +/** A handy C macro for stringifying any symbol, rather than just a macro parameter. */ +#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) + +//============================================================================== +#if JUCE_COMPILER_SUPPORTS_STATIC_ASSERT + /** A compile-time assertion macro. + If the expression parameter is false, the macro will cause a compile error. (The actual error + message that the compiler generates may be completely bizarre and seem to have no relation to + the place where you put the static_assert though!) + */ + #define static_jassert(expression) static_assert(expression, #expression); +#else + #ifndef DOXYGEN + namespace juce + { + template struct JuceStaticAssert; + template <> struct JuceStaticAssert { static void dummy() {} }; + } + #endif + + /** A compile-time assertion macro. + If the expression parameter is false, the macro will cause a compile error. (The actual error + message that the compiler generates may be completely bizarre and seem to have no relation to + the place where you put the static_assert though!) + */ + #define static_jassert(expression) juce::JuceStaticAssert::dummy(); +#endif /** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. @@ -207,24 +231,6 @@ namespace juce static void* operator new (size_t) JUCE_DELETED_FUNCTION; \ static void operator delete (void*) JUCE_DELETED_FUNCTION; - -//============================================================================== -#if ! DOXYGEN - #define JUCE_JOIN_MACRO_HELPER(a, b) a ## b - #define JUCE_STRINGIFY_MACRO_HELPER(a) #a -#endif - -/** A good old-fashioned C macro concatenation helper. - This combines two items (which may themselves be macros) into a single string, - avoiding the pitfalls of the ## macro operator. -*/ -#define JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) - -/** A handy C macro for stringifying any symbol, rather than just a macro parameter. -*/ -#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) - - //============================================================================== #if JUCE_MSVC && ! defined (DOXYGEN) #define JUCE_WARNING_HELPER(file, line, mess) message(file "(" JUCE_STRINGIFY (line) ") : Warning: " #mess) diff --git a/source/modules/juce_core/system/juce_StandardHeader.h b/source/modules/juce_core/system/juce_StandardHeader.h index af40d3fc0..dded5d8d6 100644 --- a/source/modules/juce_core/system/juce_StandardHeader.h +++ b/source/modules/juce_core/system/juce_StandardHeader.h @@ -34,9 +34,9 @@ See also SystemStats::getJUCEVersion() for a string version. */ -#define JUCE_MAJOR_VERSION 3 -#define JUCE_MINOR_VERSION 2 -#define JUCE_BUILDNUMBER 0 +#define JUCE_MAJOR_VERSION 4 +#define JUCE_MINOR_VERSION 0 +#define JUCE_BUILDNUMBER 1 /** Current Juce version number. @@ -52,8 +52,8 @@ //============================================================================== #include // included before platform defs to provide a definition of _LIBCPP_VERSION -#include "juce_PlatformDefs.h" #include "juce_CompilerSupport.h" +#include "juce_PlatformDefs.h" //============================================================================== // Now we'll include some common OS headers.. diff --git a/source/modules/juce_core/text/juce_CharacterFunctions.cpp b/source/modules/juce_core/text/juce_CharacterFunctions.cpp index 9b9d7a9d8..d0a505f7f 100644 --- a/source/modules/juce_core/text/juce_CharacterFunctions.cpp +++ b/source/modules/juce_core/text/juce_CharacterFunctions.cpp @@ -108,6 +108,16 @@ bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept return iswalnum ((wint_t) character) != 0; } +bool CharacterFunctions::isPrintable (const char character) noexcept +{ + return (character >= ' ' && character <= '~'); +} + +bool CharacterFunctions::isPrintable (const juce_wchar character) noexcept +{ + return iswprint ((wint_t) character) != 0; +} + int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept { unsigned int d = (unsigned int) digit - '0'; diff --git a/source/modules/juce_core/text/juce_CharacterFunctions.h b/source/modules/juce_core/text/juce_CharacterFunctions.h index 485d0e332..3fce59b5b 100644 --- a/source/modules/juce_core/text/juce_CharacterFunctions.h +++ b/source/modules/juce_core/text/juce_CharacterFunctions.h @@ -110,6 +110,16 @@ public: /** Checks whether a character is alphabetic or numeric. */ static bool isLetterOrDigit (juce_wchar character) noexcept; + /** Checks whether a character is a printable character, i.e. alphabetic, numeric, + a punctuation character or a space. + */ + static bool isPrintable (char character) noexcept; + + /** Checks whether a character is a printable character, i.e. alphabetic, numeric, + a punctuation character or a space. + */ + static bool isPrintable (juce_wchar character) noexcept; + /** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ static int getHexDigitValue (juce_wchar digit) noexcept; diff --git a/source/modules/juce_core/text/juce_String.cpp b/source/modules/juce_core/text/juce_String.cpp index 26bf4d107..7527ca0a5 100644 --- a/source/modules/juce_core/text/juce_String.cpp +++ b/source/modules/juce_core/text/juce_String.cpp @@ -430,7 +430,22 @@ namespace NumberToStringConverters return t; } - static char* numberToString (char* t, unsigned int v) noexcept + static char* numberToString (char* t, const unsigned int v) noexcept + { + return printDigits (t, v); + } + + static char* numberToString (char* t, const long n) noexcept + { + if (n >= 0) + return printDigits (t, static_cast (n)); + + t = printDigits (t, static_cast (-(n + 1)) + 1); + *--t = '-'; + return t; + } + + static char* numberToString (char* t, const unsigned long v) noexcept { return printDigits (t, v); } @@ -517,6 +532,8 @@ String::String (const short number) : text (NumberToStringConverters::c String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const long number) : text (NumberToStringConverters::createFromInteger (number)) {} +String::String (const unsigned long number) : text (NumberToStringConverters::createFromInteger (number)) {} String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} @@ -795,34 +812,29 @@ String& String::operator+= (const juce_wchar ch) } #endif -String& String::operator+= (const int number) +namespace StringHelpers { - char buffer [16]; - char* end = buffer + numElementsInArray (buffer); - char* start = NumberToStringConverters::numberToString (end, number); - - #if (JUCE_STRING_UTF_TYPE == 8) - appendCharPointer (CharPointerType (start), CharPointerType (end)); - #else - appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); - #endif - return *this; -} + template + inline String& operationAddAssign (String& str, const T number) + { + char buffer [(sizeof(T) * 8) / 2]; + char* end = buffer + numElementsInArray (buffer); + char* start = NumberToStringConverters::numberToString (end, number); -String& String::operator+= (int64 number) -{ - char buffer [32]; - char* end = buffer + numElementsInArray (buffer); - char* start = NumberToStringConverters::numberToString (end, number); + #if (JUCE_STRING_UTF_TYPE == 8) + str.appendCharPointer (String::CharPointerType (start), String::CharPointerType (end)); + #else + str.appendCharPointer (String::CharPointer_ASCII (start), String::CharPointer_ASCII (end)); + #endif - #if (JUCE_STRING_UTF_TYPE == 8) - appendCharPointer (CharPointerType (start), CharPointerType (end)); - #else - appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); - #endif - return *this; + return str; + } } +String& String::operator+= (const int number) { return StringHelpers::operationAddAssign (*this, number); } +String& String::operator+= (const int64 number) { return StringHelpers::operationAddAssign (*this, number); } +String& String::operator+= (const uint64 number) { return StringHelpers::operationAddAssign (*this, number); } + //============================================================================== JUCE_API String JUCE_CALLTYPE operator+ (const char* const s1, const String& s2) { String s (s1); return s += s2; } JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* const s1, const String& s2) { String s (s1); return s += s2; } @@ -853,11 +865,13 @@ JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, StringRef s2) JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } -JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned short number) { return s1 += (uint64) number; } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned long number) { return s1 += String (number); } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int64 number) { return s1 += String (number); } +JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } -JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) { @@ -2174,7 +2188,8 @@ StringRef::StringRef (const String& string) noexcept : text (string.getCharPoin //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS - + #define STRINGIFY2(X) #X + #define STRINGIFY(X) STRINGIFY2(X) class StringTests : public UnitTest { public: @@ -2325,6 +2340,116 @@ public: s2 << StringRef ("def"); expect (s2 == "1234567890xyz123123def"); + // int16 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "32767"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "-32768"); + } + { + String numStr; + numStr << std::numeric_limits::max(); + expect (numStr == "32767"); + } + { + String numStr; + numStr << std::numeric_limits::min(); + expect (numStr == "-32768"); + } + // uint16 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "65535"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "0"); + } + { + String numStr; + numStr << std::numeric_limits::max(); + expect (numStr == "65535"); + } + { + String numStr; + numStr << std::numeric_limits::min(); + expect (numStr == "0"); + } + // int32 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "2147483647"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "-2147483648"); + } + { + String numStr; + numStr << std::numeric_limits::max(); + expect (numStr == "2147483647"); + } + { + String numStr; + numStr << std::numeric_limits::min(); + expect (numStr == "-2147483648"); + } + // uint32 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "4294967295"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "0"); + } + // int64 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "9223372036854775807"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "-9223372036854775808"); + } + { + String numStr; + numStr << std::numeric_limits::max(); + expect (numStr == "9223372036854775807"); + } + { + String numStr; + numStr << std::numeric_limits::min(); + expect (numStr == "-9223372036854775808"); + } + // uint64 + { + String numStr (std::numeric_limits::max()); + expect (numStr == "18446744073709551615"); + } + { + String numStr (std::numeric_limits::min()); + expect (numStr == "0"); + } + { + String numStr; + numStr << std::numeric_limits::max(); + expect (numStr == "18446744073709551615"); + } + { + String numStr; + numStr << std::numeric_limits::min(); + expect (numStr == "0"); + } + // size_t + { + String numStr (std::numeric_limits::min()); + expect (numStr == "0"); + } + beginTest ("Numeric conversions"); expect (String::empty.getIntValue() == 0); expect (String::empty.getDoubleValue() == 0.0); diff --git a/source/modules/juce_core/text/juce_String.h b/source/modules/juce_core/text/juce_String.h index a93a46cc8..d59b744cc 100644 --- a/source/modules/juce_core/text/juce_String.h +++ b/source/modules/juce_core/text/juce_String.h @@ -210,7 +210,11 @@ public: /** Appends a decimal number at the end of this string. */ String& operator+= (int numberToAppend); /** Appends a decimal number at the end of this string. */ + String& operator+= (long numberToAppend); + /** Appends a decimal number at the end of this string. */ String& operator+= (int64 numberToAppend); + /** Appends a decimal number at the end of this string. */ + String& operator+= (uint64 numberToAppend); /** Appends a character at the end of this string. */ String& operator+= (char characterToAppend); /** Appends a character at the end of this string. */ @@ -937,6 +941,16 @@ public: */ explicit String (uint64 largeIntegerValue); + /** Creates a string containing this signed long integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (long decimalInteger); + + /** Creates a string containing this unsigned long integer as a decimal number. + @see getIntValue, getFloatValue, getDoubleValue, toHexString + */ + explicit String (unsigned long decimalInteger); + /** Creates a string representing this floating-point number. @param floatValue the value to convert to a string @see getDoubleValue, getIntValue diff --git a/source/modules/juce_core/text/juce_StringArray.cpp b/source/modules/juce_core/text/juce_StringArray.cpp index de6ac3e87..7ccdcb863 100644 --- a/source/modules/juce_core/text/juce_StringArray.cpp +++ b/source/modules/juce_core/text/juce_StringArray.cpp @@ -173,6 +173,12 @@ void StringArray::addArray (const StringArray& otherArray, int startIndex, int n strings.add (otherArray.strings.getReference (startIndex++)); } +void StringArray::mergeArray (const StringArray& otherArray, const bool ignoreCase) +{ + for (int i = 0; i < otherArray.size(); ++i) + addIfNotAlreadyThere (otherArray[i], ignoreCase); +} + void StringArray::set (const int index, const String& newString) { strings.set (index, newString); diff --git a/source/modules/juce_core/text/juce_StringArray.h b/source/modules/juce_core/text/juce_StringArray.h index 6136575a1..a36d64e2c 100644 --- a/source/modules/juce_core/text/juce_StringArray.h +++ b/source/modules/juce_core/text/juce_StringArray.h @@ -209,6 +209,15 @@ public: int startIndex = 0, int numElementsToAdd = -1); + /** Merges the strings from another array into this one. + This will not add a string that already exists. + + @param other the array to add + @param ignoreCase ignore case when merging + */ + void mergeArray (const StringArray& other, + bool ignoreCase = false); + /** Breaks up a string into tokens and adds them to this array. This will tokenise the given string using whitespace characters as the diff --git a/source/modules/juce_core/unit_tests/juce_UnitTest.h b/source/modules/juce_core/unit_tests/juce_UnitTest.h index 1c80b4edd..9ea9689ce 100644 --- a/source/modules/juce_core/unit_tests/juce_UnitTest.h +++ b/source/modules/juce_core/unit_tests/juce_UnitTest.h @@ -157,6 +157,47 @@ public: expect (result, failureMessage); } + //============================================================================== + /** Checks that the result of an expression does not throw an exception. */ + #define expectDoesNotThrow(expr) \ + try \ + { \ + (expr); \ + expect (true); \ + } \ + catch (...) \ + { \ + expect (false, "Expected: does not throw an exception, Actual: throws."); \ + } + + /** Checks that the result of an expression throws an exception. */ + #define expectThrows(expr) \ + try \ + { \ + (expr); \ + expect (false, "Expected: throws an exception, Actual: does not throw."); \ + } \ + catch (...) \ + { \ + expect (true); \ + } + + /** Checks that the result of an expression throws an exception of a certain type. */ + #define expectThrowsType(expr, type) \ + try \ + { \ + (expr); \ + expect (false, "Expected: throws an exception of type " #type ", Actual: does not throw."); \ + } \ + catch (type&) \ + { \ + expect (true); \ + } \ + catch (...) \ + { \ + expect (false, "Expected: throws an exception of type " #type ", Actual: throws another type."); \ + } + //============================================================================== /** Writes a message to the test log. This can only be called from within your runTest() method. diff --git a/source/modules/juce_events/native/juce_android_Messaging.cpp b/source/modules/juce_events/native/juce_android_Messaging.cpp index 73cc3a4ae..d51b214cd 100644 --- a/source/modules/juce_events/native/juce_android_Messaging.cpp +++ b/source/modules/juce_events/native/juce_android_Messaging.cpp @@ -44,6 +44,8 @@ bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* cons JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (JNIEnv* env, jobject activity, jlong value)) { + setEnv (env); + JUCE_TRY { MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; diff --git a/source/modules/juce_graphics/native/juce_android_Fonts.cpp b/source/modules/juce_graphics/native/juce_android_Fonts.cpp index 78d2b4962..6ce3e23c9 100644 --- a/source/modules/juce_graphics/native/juce_android_Fonts.cpp +++ b/source/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -165,7 +165,7 @@ public: } AndroidTypeface (const void* data, size_t size) - : Typeface (String(), String()) + : Typeface (String (static_cast (reinterpret_cast (data))), String()) { JNIEnv* const env = getEnv(); diff --git a/source/modules/juce_gui_basics/components/juce_Component.cpp b/source/modules/juce_gui_basics/components/juce_Component.cpp index a02f6a56e..925da676f 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -2384,7 +2384,7 @@ void Component::internalMouseEnter (MouseInputSource source, Point relati BailOutChecker checker (this); - const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, this, this, time, relativePos, time, 0, false); mouseEnter (me); @@ -2403,7 +2403,7 @@ void Component::internalMouseExit (MouseInputSource source, Point relativ BailOutChecker checker (this); - const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, this, this, time, relativePos, time, 0, false); mouseExit (me); @@ -2416,7 +2416,7 @@ void Component::internalMouseExit (MouseInputSource source, Point relativ MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); } -void Component::internalMouseDown (MouseInputSource source, Point relativePos, Time time) +void Component::internalMouseDown (MouseInputSource source, Point relativePos, Time time, float pressure) { Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); @@ -2435,7 +2435,7 @@ void Component::internalMouseDown (MouseInputSource source, Point relativ { // allow blocked mouse-events to go to global listeners.. const MouseEvent me (source, relativePos, source.getCurrentModifiers(), - this, this, time, relativePos, time, + pressure, this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); @@ -2468,7 +2468,7 @@ void Component::internalMouseDown (MouseInputSource source, Point relativ repaint(); const MouseEvent me (source, relativePos, source.getCurrentModifiers(), - this, this, time, relativePos, time, + pressure, this, this, time, relativePos, time, source.getNumberOfMultipleClicks(), false); mouseDown (me); @@ -2492,7 +2492,7 @@ void Component::internalMouseUp (MouseInputSource source, Point relativeP repaint(); const MouseEvent me (source, relativePos, - oldModifiers, this, this, time, + oldModifiers, MouseInputSource::invalidPressure, this, this, time, getLocalPoint (nullptr, source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), @@ -2523,14 +2523,14 @@ void Component::internalMouseUp (MouseInputSource source, Point relativeP } } -void Component::internalMouseDrag (MouseInputSource source, Point relativePos, Time time) +void Component::internalMouseDrag (MouseInputSource source, Point relativePos, Time time, float pressure) { if (! isCurrentlyBlockedByAnotherModalComponent()) { BailOutChecker checker (this); - const MouseEvent me (source, relativePos, - source.getCurrentModifiers(), this, this, time, + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + pressure, this, this, time, getLocalPoint (nullptr, source.getLastMouseDownPosition()), source.getLastMouseDownTime(), source.getNumberOfMultipleClicks(), @@ -2559,7 +2559,7 @@ void Component::internalMouseMove (MouseInputSource source, Point relativ { BailOutChecker checker (this); - const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, this, this, time, relativePos, time, 0, false); mouseMove (me); @@ -2578,7 +2578,7 @@ void Component::internalMouseWheel (MouseInputSource source, Point relati Desktop& desktop = Desktop::getInstance(); BailOutChecker checker (this); - const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, this, this, time, relativePos, time, 0, false); if (isCurrentlyBlockedByAnotherModalComponent()) @@ -2605,7 +2605,7 @@ void Component::internalMagnifyGesture (MouseInputSource source, Point re { if (! isCurrentlyBlockedByAnotherModalComponent()) { - const MouseEvent me (source, relativePos, source.getCurrentModifiers(), + const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, this, this, time, relativePos, time, 0, false); mouseMagnify (me, amount); diff --git a/source/modules/juce_gui_basics/components/juce_Component.h b/source/modules/juce_gui_basics/components/juce_Component.h index 9b93c4ef0..d4bbd58f5 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.h +++ b/source/modules/juce_gui_basics/components/juce_Component.h @@ -2300,9 +2300,9 @@ private: //============================================================================== void internalMouseEnter (MouseInputSource, Point, Time); void internalMouseExit (MouseInputSource, Point, Time); - void internalMouseDown (MouseInputSource, Point, Time); + void internalMouseDown (MouseInputSource, Point, Time, float); void internalMouseUp (MouseInputSource, Point, Time, const ModifierKeys oldModifiers); - void internalMouseDrag (MouseInputSource, Point, Time); + void internalMouseDrag (MouseInputSource, Point, Time, float); void internalMouseMove (MouseInputSource, Point, Time); void internalMouseWheel (MouseInputSource, Point, Time, const MouseWheelDetails&); void internalMagnifyGesture (MouseInputSource, Point, Time, float); diff --git a/source/modules/juce_gui_basics/components/juce_Desktop.cpp b/source/modules/juce_gui_basics/components/juce_Desktop.cpp index a80c5808d..56f818b60 100644 --- a/source/modules/juce_gui_basics/components/juce_Desktop.cpp +++ b/source/modules/juce_gui_basics/components/juce_Desktop.cpp @@ -92,7 +92,7 @@ LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept if (currentLookAndFeel == nullptr) { if (defaultLookAndFeel == nullptr) - defaultLookAndFeel = new LookAndFeel_V2(); + defaultLookAndFeel = new LookAndFeel_V3(); currentLookAndFeel = defaultLookAndFeel; } @@ -246,7 +246,7 @@ void Desktop::sendMouseMove() const Time now (Time::getCurrentTime()); const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), - target, target, now, pos, now, 0, false); + MouseInputSource::invalidPressure, target, target, now, pos, now, 0, false); if (me.mods.isAnyMouseButtonDown()) mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp index 517b1982a..bf5cb87a7 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.cpp @@ -25,6 +25,7 @@ MouseEvent::MouseEvent (MouseInputSource inputSource, Point pos, ModifierKeys modKeys, + float force, Component* const eventComp, Component* const originator, Time time, @@ -36,6 +37,7 @@ MouseEvent::MouseEvent (MouseInputSource inputSource, x (roundToInt (pos.x)), y (roundToInt (pos.y)), mods (modKeys), + pressure (force), eventComponent (eventComp), originalComponent (originator), eventTime (time), @@ -57,22 +59,22 @@ MouseEvent MouseEvent::getEventRelativeTo (Component* const otherComponent) cons jassert (otherComponent != nullptr); return MouseEvent (source, otherComponent->getLocalPoint (eventComponent, position), - mods, otherComponent, originalComponent, eventTime, + mods, pressure, otherComponent, originalComponent, eventTime, otherComponent->getLocalPoint (eventComponent, mouseDownPos), mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); } MouseEvent MouseEvent::withNewPosition (Point newPosition) const noexcept { - return MouseEvent (source, newPosition, mods, eventComponent, originalComponent, - eventTime, mouseDownPos, mouseDownTime, + return MouseEvent (source, newPosition, mods, pressure, eventComponent, + originalComponent, eventTime, mouseDownPos, mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); } MouseEvent MouseEvent::withNewPosition (Point newPosition) const noexcept { - return MouseEvent (source, newPosition.toFloat(), mods, eventComponent, originalComponent, - eventTime, mouseDownPos, mouseDownTime, + return MouseEvent (source, newPosition.toFloat(), mods, pressure, eventComponent, + originalComponent, eventTime, mouseDownPos, mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); } @@ -112,6 +114,8 @@ int MouseEvent::getScreenY() const { return getScre int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().x; } int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().y; } +bool MouseEvent::isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } + //============================================================================== static int doubleClickTimeOutMs = 400; diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h index 5cc6c789d..9d993e31d 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h +++ b/source/modules/juce_gui_basics/mouse/juce_MouseEvent.h @@ -44,6 +44,9 @@ public: @param source the source that's invoking the event @param position the position of the mouse, relative to the component that is passed-in @param modifiers the key modifiers at the time of the event + @param pressure the pressure of the touch or stylus, in the range 0 to 1. Devices that + do not support force information may return 0.0, 1.0, or a negative value, + depending on the platform @param eventComponent the component that the mouse event applies to @param originator the component that originally received the event @param eventTime the time the event happened @@ -59,6 +62,7 @@ public: MouseEvent (MouseInputSource source, Point position, ModifierKeys modifiers, + float pressure, Component* eventComponent, Component* originator, Time eventTime, @@ -109,6 +113,13 @@ public: */ const ModifierKeys mods; + /** The pressure of the touch or stylus for this event. + The range is 0 (soft) to 1 (hard). + If the input device doesn't provide any pressure data, it may return a negative + value here, or 0.0 or 1.0, depending on the platform. + */ + float pressure; + /** The component that this event applies to. This is usually the component that the mouse was over at the time, but for mouse-drag @@ -224,6 +235,9 @@ public: */ int getLengthOfMousePress() const noexcept; + /** Returns true if the pressure value for this event is meaningful. */ + bool isPressureValid() const noexcept; + //============================================================================== /** The position of the mouse when the event occurred. diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp index 77be37136..2302105cd 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.cpp @@ -27,7 +27,7 @@ class MouseInputSourceInternal : private AsyncUpdater public: //============================================================================== MouseInputSourceInternal (const int i, const bool isMouse) - : index (i), isMouseDevice (isMouse), + : index (i), isMouseDevice (isMouse), pressure (0.0f), isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), lastPeer (nullptr), currentCursorHandle (nullptr), mouseEventCounter (0), mouseMovedSignificantlySincePressed (false) @@ -40,17 +40,17 @@ public: return buttonState.isAnyMouseButtonDown(); } - Component* getComponentUnderMouse() const + Component* getComponentUnderMouse() const noexcept { return componentUnderMouse.get(); } - ModifierKeys getCurrentModifiers() const + ModifierKeys getCurrentModifiers() const noexcept { return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); } - ComponentPeer* getPeer() + ComponentPeer* getPeer() noexcept { if (! ComponentPeer::isValidPeer (lastPeer)) lastPeer = nullptr; @@ -102,6 +102,8 @@ public: MouseInputSource::setRawMousePosition (ScalingHelpers::scaledScreenPosToUnscaled (p)); } + bool isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } + //============================================================================== #if JUCE_DUMP_MOUSE_EVENTS #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ @@ -132,13 +134,13 @@ public: void sendMouseDown (Component& comp, Point screenPos, Time time) { JUCE_MOUSE_EVENT_DBG ("down") - comp.internalMouseDown (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); + comp.internalMouseDown (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure); } void sendMouseDrag (Component& comp, Point screenPos, Time time) { JUCE_MOUSE_EVENT_DBG ("drag") - comp.internalMouseDrag (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); + comp.internalMouseDrag (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure); } void sendMouseUp (Component& comp, Point screenPos, Time time, const ModifierKeys oldMods) @@ -287,15 +289,18 @@ public: } //============================================================================== - void handleEvent (ComponentPeer& newPeer, Point positionWithinPeer, Time time, const ModifierKeys newMods) + void handleEvent (ComponentPeer& newPeer, Point positionWithinPeer, Time time, + const ModifierKeys newMods, float newPressure) { lastTime = time; + const bool pressureChanged = (pressure != newPressure); + pressure = newPressure; ++mouseEventCounter; const Point screenPos (newPeer.localToGlobal (positionWithinPeer)); if (isDragging() && newMods.isAnyMouseButtonDown()) { - setScreenPos (screenPos, time, false); + setScreenPos (screenPos, time, pressureChanged); } else { @@ -307,8 +312,9 @@ public: return; // some modal events have been dispatched, so the current event is now out-of-date peer = getPeer(); + if (peer != nullptr) - setScreenPos (screenPos, time, false); + setScreenPos (screenPos, time, pressureChanged); } } } @@ -470,6 +476,7 @@ public: const bool isMouseDevice; Point lastScreenPos, unboundedMouseOffset; // NB: these are unscaled coords ModifierKeys buttonState; + float pressure; bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; @@ -542,14 +549,16 @@ MouseInputSource& MouseInputSource::operator= (const MouseInputSource& other) no return *this; } -bool MouseInputSource::isMouse() const { return pimpl->isMouseDevice; } -bool MouseInputSource::isTouch() const { return ! isMouse(); } -bool MouseInputSource::canHover() const { return isMouse(); } -bool MouseInputSource::hasMouseWheel() const { return isMouse(); } -int MouseInputSource::getIndex() const { return pimpl->index; } -bool MouseInputSource::isDragging() const { return pimpl->isDragging(); } -Point MouseInputSource::getScreenPosition() const { return pimpl->getScreenPosition(); } -ModifierKeys MouseInputSource::getCurrentModifiers() const { return pimpl->getCurrentModifiers(); } +bool MouseInputSource::isMouse() const noexcept { return pimpl->isMouseDevice; } +bool MouseInputSource::isTouch() const noexcept { return ! isMouse(); } +bool MouseInputSource::canHover() const noexcept { return isMouse(); } +bool MouseInputSource::hasMouseWheel() const noexcept { return isMouse(); } +int MouseInputSource::getIndex() const noexcept { return pimpl->index; } +bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } +Point MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); } +ModifierKeys MouseInputSource::getCurrentModifiers() const noexcept { return pimpl->getCurrentModifiers(); } +float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } +bool MouseInputSource::isPressureValid() const noexcept { return pimpl->isPressureValid(); } Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } @@ -567,9 +576,9 @@ void MouseInputSource::revealCursor() { pimpl void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } void MouseInputSource::setScreenPosition (Point p) { pimpl->setScreenPosition (p); } -void MouseInputSource::handleEvent (ComponentPeer& peer, Point pos, int64 time, ModifierKeys mods) +void MouseInputSource::handleEvent (ComponentPeer& peer, Point pos, int64 time, ModifierKeys mods, float pressure) { - pimpl->handleEvent (peer, pos, Time (time), mods.withOnlyMouseButtons()); + pimpl->handleEvent (peer, pos, Time (time), mods.withOnlyMouseButtons(), pressure); } void MouseInputSource::handleWheel (ComponentPeer& peer, Point pos, int64 time, const MouseWheelDetails& wheel) @@ -582,6 +591,8 @@ void MouseInputSource::handleMagnifyGesture (ComponentPeer& peer, Point p pimpl->handleMagnifyGesture (peer, pos, Time (time), scaleFactor); } +const float MouseInputSource::invalidPressure = 0.0f; + //============================================================================== struct MouseInputSource::SourceList : public Timer { diff --git a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.h b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.h index 7bf19c5cf..bbceb508d 100644 --- a/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.h +++ b/source/modules/juce_gui_basics/mouse/juce_MouseInputSource.h @@ -60,18 +60,18 @@ public: //============================================================================== /** Returns true if this object represents a normal desk-based mouse device. */ - bool isMouse() const; + bool isMouse() const noexcept; /** Returns true if this object represents a source of touch events - i.e. a finger or stylus. */ - bool isTouch() const; + bool isTouch() const noexcept; /** Returns true if this source has an on-screen pointer that can hover over items without clicking them. */ - bool canHover() const; + bool canHover() const noexcept; /** Returns true if this source may have a scroll wheel. */ - bool hasMouseWheel() const; + bool hasMouseWheel() const noexcept; /** Returns this source's index in the global list of possible sources. If the system only has a single mouse, there will only be a single MouseInputSource @@ -82,18 +82,28 @@ public: number 0, and then if a second touch happens while the first is still down, it will have index 1, etc. */ - int getIndex() const; + int getIndex() const noexcept; /** Returns true if this device is currently being pressed. */ - bool isDragging() const; + bool isDragging() const noexcept; /** Returns the last-known screen position of this source. */ - Point getScreenPosition() const; + Point getScreenPosition() const noexcept; /** Returns a set of modifiers that indicate which buttons are currently held down on this device. */ - ModifierKeys getCurrentModifiers() const; + ModifierKeys getCurrentModifiers() const noexcept; + + /** Returns the device's current touch or pen pressure. + The range is 0 (soft) to 1 (hard). + If the input device doesn't provide any pressure data, it may return a negative + value here, or 0.0 or 1.0, depending on the platform. + */ + float getCurrentPressure() const noexcept; + + /** Returns true if the current pressure value is meaningful. */ + bool isPressureValid() const noexcept; /** Returns the component that was last known to be under this pointer. */ Component* getComponentUnderMouse() const; @@ -164,6 +174,11 @@ public: /** Attempts to set this mouse pointer's screen position. */ void setScreenPosition (Point newPosition); + /** A default value for pressure, which is used when a device doesn't support it, or for + mouse-moves, mouse-ups, etc. + */ + static const float invalidPressure; + private: //============================================================================== friend class ComponentPeer; @@ -174,7 +189,7 @@ private: struct SourceList; explicit MouseInputSource (MouseInputSourceInternal*) noexcept; - void handleEvent (ComponentPeer&, Point, int64 time, ModifierKeys); + void handleEvent (ComponentPeer&, Point, int64 time, ModifierKeys, float); void handleWheel (ComponentPeer&, Point, int64 time, const MouseWheelDetails&); void handleMagnifyGesture (ComponentPeer&, Point, int64 time, float scaleFactor); diff --git a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp index c1627ec3d..106e70572 100644 --- a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -33,6 +33,8 @@ namespace juce JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, jstring appFile, jstring appDataDir)) { + setEnv (env); + android.initialise (env, activity, appFile, appDataDir); DBG (SystemStats::getJUCEVersion()); @@ -56,18 +58,24 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* en JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject activity)) { + setEnv (env); + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) app->suspended(); } JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject activity)) { + setEnv (env); + if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) app->resumed(); } JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity)) { + setEnv (env); + JUCEApplicationBase::appWillTerminateByForce(); android.shutdown (env); @@ -98,7 +106,6 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); METHOD (invalidate, "invalidate", "(IIII)V") \ METHOD (containsPoint, "containsPoint", "(II)Z") \ 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"); #undef JNI_CLASS_MEMBERS @@ -332,7 +339,7 @@ public: lastMousePos = pos; // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. - handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time); + handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure, time); if (isValidPeer (this)) handleMouseDragCallback (index, sysPos, time); @@ -346,8 +353,8 @@ public: jassert (index < 64); touchesDown = (touchesDown | (1 << (index & 63))); currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); - handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons() - .withFlags (ModifierKeys::leftButtonModifier), time); + handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier), + MouseInputSource::invalidPressure, time); } void handleMouseUpCallback (int index, Point pos, int64 time) @@ -361,7 +368,7 @@ public: if (touchesDown == 0) currentModifiers = currentModifiers.withoutMouseButtons(); - handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time); + handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure, time); } void handleKeyDownCallback (int k, int kc) @@ -582,6 +589,7 @@ int64 AndroidComponentPeer::touchesDown = 0; #define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ { \ + setEnv (env); \ if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \ peer->juceMethodInvocation; \ } @@ -601,12 +609,6 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void*) return new AndroidComponentPeer (*this, styleFlags); } -jobject createOpenGLView (ComponentPeer* peer) -{ - jobject parentView = static_cast (peer->getNativeHandle()); - return getEnv()->CallObjectMethod (parentView, ComponentPeerView.createGLView); -} - //============================================================================== bool Desktop::canUseSemiTransparentWindows() noexcept { @@ -700,6 +702,8 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject activity, jlong callbackAsLong, jint result)) { + setEnv (env); + if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong) { callback->modalStateFinished (result); @@ -748,6 +752,8 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv jint screenWidth, jint screenHeight, jint dpi)) { + setEnv (env); + android.screenWidth = screenWidth; android.screenHeight = screenHeight; android.dpi = dpi; diff --git a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm index 32f8c2487..f6de8d650 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm @@ -767,7 +767,11 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons { UITouch* touch = [touches objectAtIndex: i]; + #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 + if ([touch phase] == UITouchPhaseStationary && touch.maximumPossibleForce <= 0) + #else if ([touch phase] == UITouchPhaseStationary) + #endif continue; CGPoint p = [touch locationInView: view]; @@ -788,7 +792,9 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons modsToSend = currentModifiers; // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. - handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), time); + handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), + MouseInputSource::invalidPressure, time); + if (! isValidPeer (this)) // (in case this component was deleted by the event) return; } @@ -810,13 +816,24 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons modsToSend = currentModifiers = currentModifiers.withoutMouseButtons(); } - handleMouseEvent (touchIndex, pos, modsToSend, time); + float pressure = MouseInputSource::invalidPressure; + + #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 + if (touch.maximumPossibleForce > 0) + // NB: other devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range: + pressure = jlimit (0.0001f, 0.9999f, (float) (touch.force / touch.maximumPossibleForce)); + #endif + + handleMouseEvent (touchIndex, pos, modsToSend, pressure, time); + if (! isValidPeer (this)) // (in case this component was deleted by the event) return; if (isUp || isCancel) { - handleMouseEvent (touchIndex, Point (-1.0f, -1.0f), modsToSend, time); + handleMouseEvent (touchIndex, Point (-1.0f, -1.0f), + modsToSend, MouseInputSource::invalidPressure, time); + if (! isValidPeer (this)) return; } diff --git a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm index 4200e3449..4d69dd53e 100644 --- a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm @@ -24,6 +24,15 @@ extern bool isIOSAppActive; +struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules +{ + virtual ~AppInactivityCallback() {} + virtual void appBecomingInactive() = 0; +}; + +// This is an internal list of callbacks (but currently used between modules) +Array appBecomingInactiveCallbacks; + } // (juce namespace) @interface JuceAppStartupDelegate : NSObject @@ -89,6 +98,9 @@ extern bool isIOSAppActive; { ignoreUnused (application); isIOSAppActive = false; + + for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) + appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive(); } @end diff --git a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp index 8a32b6875..79f81e929 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_Windowing.cpp @@ -2215,7 +2215,8 @@ public: { currentModifiers = currentModifiers.withFlags (buttonModifierFlag); toFront (true); - handleMouseEvent (0, getMousePos (buttonPressEvent), currentModifiers, getEventTime (buttonPressEvent)); + handleMouseEvent (0, getMousePos (buttonPressEvent), currentModifiers, + MouseInputSource::invalidPressure, getEventTime (buttonPressEvent)); } void handleButtonPressEvent (const XButtonPressedEvent& buttonPressEvent) @@ -2253,7 +2254,8 @@ public: if (dragState.dragging) handleExternalDragButtonReleaseEvent(); - handleMouseEvent (0, getMousePos (buttonRelEvent), currentModifiers, getEventTime (buttonRelEvent)); + handleMouseEvent (0, getMousePos (buttonRelEvent), currentModifiers, + MouseInputSource::invalidPressure, getEventTime (buttonRelEvent)); clearLastMousePos(); } @@ -2267,7 +2269,8 @@ public: if (dragState.dragging) handleExternalDragMotionNotify(); - handleMouseEvent (0, getMousePos (movedEvent), currentModifiers, getEventTime (movedEvent)); + handleMouseEvent (0, getMousePos (movedEvent), currentModifiers, + MouseInputSource::invalidPressure, getEventTime (movedEvent)); } void handleEnterNotifyEvent (const XEnterWindowEvent& enterEvent) @@ -2280,7 +2283,8 @@ public: if (! currentModifiers.isAnyMouseButtonDown()) { updateKeyModifiers ((int) enterEvent.state); - handleMouseEvent (0, getMousePos (enterEvent), currentModifiers, getEventTime (enterEvent)); + handleMouseEvent (0, getMousePos (enterEvent), currentModifiers, + MouseInputSource::invalidPressure, getEventTime (enterEvent)); } } @@ -2293,7 +2297,8 @@ public: || leaveEvent.mode == NotifyUngrab) { updateKeyModifiers ((int) leaveEvent.state); - handleMouseEvent (0, getMousePos (leaveEvent), currentModifiers, getEventTime (leaveEvent)); + handleMouseEvent (0, getMousePos (leaveEvent), currentModifiers, + MouseInputSource::invalidPressure, getEventTime (leaveEvent)); } } diff --git a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 0f1bf2e25..096bb8f0f 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -587,7 +587,8 @@ public: sendMouseEvent (ev); else // moved into another window which overlaps this one, so trigger an exit - handleMouseEvent (0, Point (-1.0f, -1.0f), currentModifiers, getMouseTime (ev)); + handleMouseEvent (0, Point (-1.0f, -1.0f), currentModifiers, + getMousePressure (ev), getMouseTime (ev)); showArrowCursorIfNeeded(); } @@ -678,7 +679,8 @@ public: void sendMouseEvent (NSEvent* ev) { updateModifiers (ev); - handleMouseEvent (0, getMousePos (ev, view), currentModifiers, getMouseTime (ev)); + handleMouseEvent (0, getMousePos (ev, view), currentModifiers, + getMousePressure (ev), getMouseTime (ev)); } bool handleKeyEvent (NSEvent* ev, bool isKeyDown) @@ -1080,10 +1082,23 @@ public: return keyCode; } - static int64 getMouseTime (NSEvent* e) + static int64 getMouseTime (NSEvent* e) noexcept { return (Time::currentTimeMillis() - Time::getMillisecondCounter()) - + (int64) ([e timestamp] * 1000.0); + + (int64) ([e timestamp] * 1000.0); + } + + static float getMousePressure (NSEvent* e) noexcept + { + @try + { + if (e.type != NSMouseEntered && e.type != NSMouseExited) + return (float) e.pressure; + } + @catch (NSException* e) {} + @finally {} + + return 0.0f; } static Point getMousePos (NSEvent* e, NSView* view) diff --git a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 0dcf00b8f..df708e312 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -1663,9 +1663,9 @@ private: } //============================================================================== - void doMouseEvent (Point position) + void doMouseEvent (Point position, float pressure) { - handleMouseEvent (0, position, currentModifiers, getMouseEventTime()); + handleMouseEvent (0, position, currentModifiers, pressure, getMouseEventTime()); } StringArray getAvailableRenderingEngines() override @@ -1760,7 +1760,7 @@ private: if (now >= lastMouseTime + minTimeBetweenMouses) { lastMouseTime = now; - doMouseEvent (position); + doMouseEvent (position, MouseInputSource::invalidPressure); } } @@ -1780,7 +1780,7 @@ private: updateModifiersFromWParam (wParam); isDragging = true; - doMouseEvent (position); + doMouseEvent (position, MouseInputSource::invalidPressure); } } @@ -1801,7 +1801,7 @@ private: // NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can // arrive without a mouse-down, so in that case we need to avoid sending a message. if (wasDragging) - doMouseEvent (position); + doMouseEvent (position, MouseInputSource::invalidPressure); } void doCaptureChanged() @@ -1821,7 +1821,7 @@ private: void doMouseExit() { isMouseOver = false; - doMouseEvent (getCurrentMousePos()); + doMouseEvent (getCurrentMousePos(), MouseInputSource::invalidPressure); } ComponentPeer* findPeerUnderMouse (Point& localPos) @@ -1925,6 +1925,7 @@ private: const int64 time = getMouseEventTime(); const Point pos (globalToLocal (Point (static_cast (TOUCH_COORD_TO_PIXEL (touch.x)), static_cast (TOUCH_COORD_TO_PIXEL (touch.y))))); + const float pressure = MouseInputSource::invalidPressure; ModifierKeys modsToSend (currentModifiers); if (isDown) @@ -1932,9 +1933,10 @@ private: currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); modsToSend = currentModifiers; - // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. - handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), time); - if (! isValidPeer (this)) // (in case this component was deleted by the event) + // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. + handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), pressure, time); + + if (! isValidPeer (this)) // (in case this component was deleted by the event) return false; } else if (isUp) @@ -1956,13 +1958,15 @@ private: currentModifiers = currentModifiers.withoutMouseButtons(); } - handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, time); + handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, pressure, time); + if (! isValidPeer (this)) // (in case this component was deleted by the event) return false; if (isUp || isCancel) { - handleMouseEvent (touchIndex, Point (-10.0f, -10.0f), currentModifiers, time); + handleMouseEvent (touchIndex, Point (-10.0f, -10.0f), currentModifiers, pressure, time); + if (! isValidPeer (this)) return false; } @@ -2248,7 +2252,7 @@ private: if (contains (pos.roundToInt(), false)) { - doMouseEvent (pos); + doMouseEvent (pos, MouseInputSource::invalidPressure); if (! isValidPeer (this)) return true; diff --git a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index 370983813..cb71b2198 100644 --- a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -85,10 +85,10 @@ bool ComponentPeer::isKioskMode() const } //============================================================================== -void ComponentPeer::handleMouseEvent (int touchIndex, Point pos, ModifierKeys newMods, int64 time) +void ComponentPeer::handleMouseEvent (int touchIndex, Point pos, ModifierKeys newMods, float newPressure, int64 time) { if (MouseInputSource* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (touchIndex)) - MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods); + MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods, newPressure); } void ComponentPeer::handleMouseWheel (int touchIndex, Point pos, int64 time, const MouseWheelDetails& wheel) diff --git a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h index c2a609d6b..a79a1fbd0 100644 --- a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -306,7 +306,7 @@ public: virtual void setAlpha (float newAlpha) = 0; //============================================================================== - void handleMouseEvent (int touchIndex, Point positionWithinPeer, ModifierKeys newMods, int64 time); + void handleMouseEvent (int touchIndex, Point positionWithinPeer, ModifierKeys newMods, float pressure, int64 time); void handleMouseWheel (int touchIndex, Point positionWithinPeer, int64 time, const MouseWheelDetails&); void handleMagnifyGesture (int touchIndex, Point positionWithinPeer, int64 time, float scaleFactor); diff --git a/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp b/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp index c1d66ff24..c04e10904 100644 --- a/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp +++ b/source/modules/juce_gui_extra/native/juce_mac_SystemTrayIcon.cpp @@ -104,6 +104,7 @@ public: const Time now (Time::getCurrentTime()); MouseInputSource mouseSource = Desktop::getInstance().getMainMouseSource(); + const float pressure = (float) e.pressure; if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up { @@ -113,17 +114,17 @@ public: owner.mouseDown (MouseEvent (mouseSource, Point(), eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier : ModifierKeys::rightButtonModifier), - &owner, &owner, now, + pressure, &owner, &owner, now, Point(), now, 1, false)); owner.mouseUp (MouseEvent (mouseSource, Point(), eventMods.withoutMouseButtons(), - &owner, &owner, now, + pressure, &owner, &owner, now, Point(), now, 1, false)); } else if (type == NSMouseMoved) { owner.mouseMove (MouseEvent (mouseSource, Point(), eventMods, - &owner, &owner, now, + pressure, &owner, &owner, now, Point(), now, 1, false)); } } diff --git a/source/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp b/source/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp index 2d2555707..b35b6fcb5 100644 --- a/source/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp +++ b/source/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp @@ -189,6 +189,7 @@ namespace ActiveXHelpers peer->handleMouseEvent (0, Point (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left, GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top).toFloat(), ModifierKeys::getCurrentModifiersRealtime(), + MouseInputSource::invalidPressure, getMouseEventTime()); break; diff --git a/source/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp b/source/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp index 4b4007a1d..8737955c7 100644 --- a/source/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp +++ b/source/modules/juce_gui_extra/native/juce_win32_SystemTrayIcon.cpp @@ -111,8 +111,8 @@ public: const Time eventTime (getMouseEventTime()); const MouseEvent e (Desktop::getInstance().getMainMouseSource(), - Point(), eventMods, &owner, &owner, eventTime, - Point(), eventTime, 1, false); + Point(), eventMods, MouseInputSource::invalidPressure, + &owner, &owner, eventTime, Point(), eventTime, 1, false); if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN) { diff --git a/source/native-plugins/zynaddsubfx/Misc/MiddleWare.cpp b/source/native-plugins/zynaddsubfx/Misc/MiddleWare.cpp index b3265dacc..59e635be6 100644 --- a/source/native-plugins/zynaddsubfx/Misc/MiddleWare.cpp +++ b/source/native-plugins/zynaddsubfx/Misc/MiddleWare.cpp @@ -877,6 +877,12 @@ rtosc::Ports bankPorts = { #undef rObject #define rObject MiddleWareImpl + +#ifndef STRINGIFY +#define STRINGIFY2(a) #a +#define STRINGIFY(a) STRINGIFY2(a) +#endif + /* * BASE/part#/kititem# * BASE/part#/kit#/adpars/voice#/oscil/\* @@ -885,15 +891,20 @@ rtosc::Ports bankPorts = { * BASE/part#/kit#/padpars/oscil/\* */ static rtosc::Ports middwareSnoopPorts = { - {"part#16/kit#8/adpars/VoicePar#8/OscilSmp/", 0, &OscilGen::non_realtime_ports, + {"part#" STRINGIFY(NUM_MIDI_PARTS) + "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/adpars/VoicePar#" + STRINGIFY(NUM_VOICES) "/OscilSmp/", 0, &OscilGen::non_realtime_ports, rBegin; impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); rEnd}, - {"part#16/kit#8/adpars/VoicePar#8/FMSmp/", 0, &OscilGen::non_realtime_ports, + {"part#" STRINGIFY(NUM_MIDI_PARTS) + "/kit#" STRINGIFY(NUM_KIT_ITEMS) + "/adpars/VoicePar#" STRINGIFY(NUM_VOICES) "/FMSmp/", 0, &OscilGen::non_realtime_ports, rBegin impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); rEnd}, - {"part#16/kit#8/padpars/", 0, &PADnoteParameters::non_realtime_ports, + {"part#" STRINGIFY(NUM_MIDI_PARTS) + "/kit#" STRINGIFY(NUM_KIT_ITEMS) "/padpars/", 0, &PADnoteParameters::non_realtime_ports, rBegin impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d); rEnd}, @@ -1062,6 +1073,10 @@ static rtosc::Ports middlewareReplyPorts = { impl.loadPart(part, impl.master->bank.ins[program].filename.c_str(), impl.master); impl.uToB->write(("/part"+to_s(part)+"/Pname").c_str(), "s", impl.master->bank.ins[program].name.c_str()); rEnd}, + {"setbank:c", 0, 0, + rBegin; + impl.loadPendingBank(rtosc_argument(msg,0).i, impl.master->bank); + rEnd}, {"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd}, {"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd}, {"undo_change", 0, 0, diff --git a/source/native-plugins/zynaddsubfx/Misc/PresetExtractor.cpp b/source/native-plugins/zynaddsubfx/Misc/PresetExtractor.cpp index a6b49c4a0..71824f948 100644 --- a/source/native-plugins/zynaddsubfx/Misc/PresetExtractor.cpp +++ b/source/native-plugins/zynaddsubfx/Misc/PresetExtractor.cpp @@ -70,7 +70,6 @@ const rtosc::Ports real_preset_ports = assert(d.obj); std::string args = rtosc_argument_string(msg); d.reply(d.loc, "s", "clipboard paste..."); - printf("\nClipboard Paste...\n"); if(args == "s") presetPaste(mw, rtosc_argument(msg, 0).s, ""); else if(args == "ss") @@ -146,7 +145,7 @@ class Capture:public rtosc::RtData virtual void reply(const char *path, const char *args, ...) { - printf("reply(%p)(%s)(%s)...\n", msgbuf, path, args); + //printf("reply(%p)(%s)(%s)...\n", msgbuf, path, args); //printf("size is %d\n", sizeof(msgbuf)); va_list va; va_start(va,args); @@ -199,7 +198,7 @@ std::string doCopy(MiddleWare &mw, string url, string name) mw.doReadOnlyOp([&xml, url, name, &mw](){ Master *m = mw.spawnMaster(); //Get the pointer - printf("capture at <%s>\n", (url+"self").c_str()); + //printf("capture at <%s>\n", (url+"self").c_str()); T *t = (T*)capture(m, url+"self"); assert(t); //Extract Via mxml @@ -216,6 +215,10 @@ void doPaste(MiddleWare &mw, string url, string type, XMLwrapper &xml, Ts&&... a //Generate a new object T *t = new T(std::forward(args)...); + //Old workaround for LFO parameters + if(strstr(type.c_str(), "Plfo")) + type = "Plfo"; + if(xml.enterbranch(type) == 0) return; @@ -227,7 +230,7 @@ void doPaste(MiddleWare &mw, string url, string type, XMLwrapper &xml, Ts&&... a rtosc_message(buffer, 1024, path.c_str(), "b", sizeof(void*), &t); if(!Master::ports.apropos(path.c_str())) fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); - printf("Sending info to '%s'\n", buffer); + //printf("Sending info to '%s'\n", buffer); mw.transmitMsg(buffer); //Let the pointer be reclaimed later @@ -237,7 +240,7 @@ template std::string doArrayCopy(MiddleWare &mw, int field, string url, string name) { XMLwrapper xml; - printf("Getting info from '%s'<%d>\n", url.c_str(), field); + //printf("Getting info from '%s'<%d>\n", url.c_str(), field); mw.doReadOnlyOp([&xml, url, field, name, &mw](){ Master *m = mw.spawnMaster(); //Get the pointer @@ -270,7 +273,7 @@ void doArrayPaste(MiddleWare &mw, int field, string url, string type, rtosc_message(buffer, 1024, path.c_str(), "bi", sizeof(void*), &t, field); if(!Master::ports.apropos(path.c_str())) fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); - printf("Sending info to '%s'<%d>\n", buffer, field); + //printf("Sending info to '%s'<%d>\n", buffer, field); mw.transmitMsg(buffer); //Let the pointer be reclaimed later @@ -285,7 +288,7 @@ void doArrayPaste(MiddleWare &mw, int field, string url, string type, */ void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string url, XMLwrapper &data) { - printf("Class Paste\n"); + //printf("Class Paste\n"); if(type == "EnvelopeParams") doPaste(mw, url, type_, data); else if(type == "LFOParams") @@ -311,7 +314,7 @@ void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string ur std::string doClassCopy(std::string type, MiddleWare &mw, string url, string name) { - printf("doClassCopy(%p)\n", mw.spawnMaster()->uToB); + //printf("doClassCopy(%p)\n", mw.spawnMaster()->uToB); if(type == "EnvelopeParams") return doCopy(mw, url, name); else if(type == "LFOParams") @@ -361,14 +364,14 @@ std::string getUrlPresetType(std::string url, MiddleWare &mw) //Get the pointer result = capture(m, url+"preset-type"); }); - printf("preset type = %s\n", result.c_str()); + //printf("preset type = %s\n", result.c_str()); return result; } std::string getUrlType(std::string url) { assert(!url.empty()); - printf("Searching for '%s'\n", (url+"self").c_str()); + //printf("Searching for '%s'\n", (url+"self").c_str()); auto self = Master::ports.apropos((url+"self").c_str()); if(!self) fprintf(stderr, "Warning: URL Metadata Not Found For '%s'\n", url.c_str()); @@ -409,12 +412,12 @@ void presetCopy(MiddleWare &mw, std::string url, std::string name) { (void) name; doClassCopy(getUrlType(url), mw, url, name); - printf("PresetCopy()\n"); + //printf("PresetCopy()\n"); } void presetPaste(MiddleWare &mw, std::string url, std::string name) { (void) name; - printf("PresetPaste()\n"); + //printf("PresetPaste()\n"); string data = ""; XMLwrapper xml; if(name.empty()) { @@ -433,13 +436,13 @@ void presetPaste(MiddleWare &mw, std::string url, std::string name) void presetCopyArray(MiddleWare &mw, std::string url, int field, std::string name) { (void) name; - printf("PresetArrayCopy()\n"); + //printf("PresetArrayCopy()\n"); doClassArrayCopy(getUrlType(url), field, mw, url, name); } void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string name) { (void) name; - printf("PresetArrayPaste()\n"); + //printf("PresetArrayPaste()\n"); string data = ""; XMLwrapper xml; if(name.empty()) { @@ -452,7 +455,7 @@ void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string na if(xml.loadXMLfile(name)) return; } - printf("Performing Paste...\n"); + //printf("Performing Paste...\n"); doClassArrayPaste(getUrlType(url), getUrlPresetType(url, mw), field, mw, url, xml); } #if 0 @@ -464,19 +467,19 @@ void presetPaste(std::string url, int) #endif void presetDelete(int) { - printf("PresetDelete()\n"); + printf("PresetDelete()\n"); } void presetRescan() { - printf("PresetRescan()\n"); + printf("PresetRescan()\n"); } std::string presetClipboardType() { - printf("PresetClipboardType()\n"); + printf("PresetClipboardType()\n"); return "dummy"; } bool presetCheckClipboardType() { - printf("PresetCheckClipboardType()\n"); + printf("PresetCheckClipboardType()\n"); return true; } diff --git a/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.cpp b/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.cpp index 127b1dd85..7254a0499 100644 --- a/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.cpp +++ b/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.cpp @@ -111,6 +111,8 @@ static const Ports voicePorts = { rToggle(PFilterEnabled, "Filter Enable"), rToggle(PFilterEnvelopeEnabled, "Filter Envelope Enable"), rToggle(PFilterLfoEnabled, "Filter LFO Enable"), + rParamZyn(PFilterVelocityScale, "Filter Velocity Magnitude"), + rParamZyn(PFilterVelocityScaleFunction, "Filter Velocity Function Shape"), //Modulator Stuff @@ -428,6 +430,8 @@ void ADnoteVoiceParam::defaults() PFilterEnabled = 0; PFilterEnvelopeEnabled = 0; PFilterLfoEnabled = 0; + PFilterVelocityScale = 0; + PFilterVelocityScaleFunction = 64; PFMEnabled = 0; //I use the internal oscillator (-1) @@ -664,6 +668,8 @@ void ADnoteVoiceParam::add2XML(XMLwrapper *xml, bool fmoscilused) if((PFilterEnabled != 0) || (!xml->minimal)) { xml->beginbranch("FILTER_PARAMETERS"); + xml->addpar("velocity_sensing_amplitude", PFilterVelocityScale); + xml->addpar("velocity_sensing", PFilterVelocityScaleFunction); xml->beginbranch("FILTER"); VoiceFilter->add2XML(xml); xml->endbranch(); @@ -974,6 +980,8 @@ void ADnoteVoiceParam::paste(ADnoteVoiceParam &a) RCopy(FilterEnvelope); copy(PFilterLfoEnabled); + copy(PFilterVelocityScale); + copy(PFilterVelocityScaleFunction); RCopy(FilterLfo); @@ -1115,6 +1123,11 @@ void ADnoteVoiceParam::getfromXML(XMLwrapper *xml, unsigned nvoice) } if(xml->enterbranch("FILTER_PARAMETERS")) { + PFilterVelocityScale = xml->getpar127("velocity_sensing_amplitude", + PFilterVelocityScale); + PFilterVelocityScaleFunction = xml->getpar127( + "velocity_sensing", + PFilterVelocityScaleFunction); if(xml->enterbranch("FILTER")) { VoiceFilter->getfromXML(xml); xml->exitbranch(); diff --git a/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.h b/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.h index 1edf5fba3..c86215205 100644 --- a/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.h +++ b/source/native-plugins/zynaddsubfx/Params/ADnoteParameters.h @@ -240,10 +240,16 @@ struct ADnoteVoiceParam { unsigned char PFilterEnvelopeEnabled; EnvelopeParams *FilterEnvelope; - /* LFO Envelope */ + /* Filter LFO */ unsigned char PFilterLfoEnabled; LFOParams *FilterLfo; + // filter velocity sensing + unsigned char PFilterVelocityScale; + + // filter velocity sensing + unsigned char PFilterVelocityScaleFunction; + /**************************** * MODULLATOR PARAMETERS * ****************************/ diff --git a/source/native-plugins/zynaddsubfx/Synth/ADnote.cpp b/source/native-plugins/zynaddsubfx/Synth/ADnote.cpp index bd6fa003b..a9b946bf3 100644 --- a/source/native-plugins/zynaddsubfx/Synth/ADnote.cpp +++ b/source/native-plugins/zynaddsubfx/Synth/ADnote.cpp @@ -317,7 +317,11 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) NoteVoicePar[nvoice].FilterLfo = NULL; NoteVoicePar[nvoice].FilterCenterPitch = - pars.VoicePar[nvoice].VoiceFilter->getfreq(); + pars.VoicePar[nvoice].VoiceFilter->getfreq() + + pars.VoicePar[nvoice].PFilterVelocityScale + / 127.0f * 6.0f //velocity sensing + * (VelF(velocity, + pars.VoicePar[nvoice].PFilterVelocityScaleFunction) - 1); NoteVoicePar[nvoice].filterbypass = pars.VoicePar[nvoice].Pfilterbypass; @@ -511,7 +515,12 @@ void ADnote::legatonote(LegatoParams lpars) NoteVoicePar[nvoice].FilterCenterPitch = - pars.VoicePar[nvoice].VoiceFilter->getfreq(); + pars.VoicePar[nvoice].VoiceFilter->getfreq() + + pars.VoicePar[nvoice].PFilterVelocityScale + / 127.0f * 6.0f //velocity sensing + * (VelF(velocity, + pars.VoicePar[nvoice].PFilterVelocityScaleFunction) - 1); + NoteVoicePar[nvoice].filterbypass = pars.VoicePar[nvoice].Pfilterbypass; diff --git a/source/native-plugins/zynaddsubfx/UI/ADnoteUI.fl b/source/native-plugins/zynaddsubfx/UI/ADnoteUI.fl index f9b8dfcf1..7270fbf6b 100644 --- a/source/native-plugins/zynaddsubfx/UI/ADnoteUI.fl +++ b/source/native-plugins/zynaddsubfx/UI/ADnoteUI.fl @@ -764,7 +764,7 @@ o->redraw();} Fl_Group {} { label {ADsynth Voice - Filter} open xywh {250 30 275 75} box FLAT_BOX color 50 align 144 - code0 {o->init("", osc_i, loc, "VoiceFilter/");} + code0 {o->init(loc + "PFilter", osc_i, loc, "VoiceFilter/");} class FilterUI } {} Fl_Group voicefilterenvgroup { diff --git a/source/native-plugins/zynaddsubfx/UI/Fl_Osc_Dial.cpp b/source/native-plugins/zynaddsubfx/UI/Fl_Osc_Dial.cpp index 929d9be9b..77ceb297e 100644 --- a/source/native-plugins/zynaddsubfx/UI/Fl_Osc_Dial.cpp +++ b/source/native-plugins/zynaddsubfx/UI/Fl_Osc_Dial.cpp @@ -111,6 +111,8 @@ void Fl_Osc_Dial::mark_dead(void) dead = true; } +#define VEL_PFX "VelocityScale" + void Fl_Osc_Dial::rebase(std::string new_base) { if(dead || loc == "/") @@ -141,8 +143,12 @@ void Fl_Osc_Dial::rebase(std::string new_base) } std::string new_loc = new_base.substr(0, match_pos+1); - printf("Moving '%s' to\n", (loc+ext).c_str()); - printf(" '%s'\n", (new_loc+ext).c_str()); + if (!strncmp(ext.c_str(), VEL_PFX, sizeof(VEL_PFX)-1) && + strstr(loc.c_str(), "/VoicePar")) + new_loc = new_loc + "PFilter"; + // printf("Moving '%s' to\n", (loc+ext).c_str()); + // printf(" '%s'\n", (new_loc+ext).c_str()); + // printf("Ext: %s\n", ext.c_str()); oscMove(loc+ext, new_loc+ext); loc = new_loc; }