| @@ -46,8 +46,15 @@ AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept | |||
| { | |||
| allocateData(); | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
| if (other.isClear) | |||
| { | |||
| clear(); | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
| } | |||
| } | |||
| } | |||
| @@ -66,6 +73,7 @@ void AudioSampleBuffer::allocateData() | |||
| } | |||
| channels [numChannels] = nullptr; | |||
| isClear = false; | |||
| } | |||
| AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||
| @@ -85,7 +93,8 @@ AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||
| const int numSamples) noexcept | |||
| : numChannels (numChans), | |||
| size (numSamples), | |||
| allocatedBytes (0) | |||
| allocatedBytes (0), | |||
| isClear (false) | |||
| { | |||
| jassert (numChans > 0); | |||
| allocateChannels (dataToReferTo, startSample); | |||
| @@ -104,6 +113,7 @@ void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, | |||
| size = newNumSamples; | |||
| allocateChannels (dataToReferTo, 0); | |||
| jassert (! isClear); | |||
| } | |||
| void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) | |||
| @@ -128,6 +138,7 @@ void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int | |||
| } | |||
| channels [numChannels] = nullptr; | |||
| isClear = false; | |||
| } | |||
| AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept | |||
| @@ -136,8 +147,15 @@ AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) | |||
| { | |||
| setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
| if (other.isClear) | |||
| { | |||
| clear(); | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
| } | |||
| } | |||
| return *this; | |||
| @@ -165,13 +183,13 @@ void AudioSampleBuffer::setSize (const int newNumChannels, | |||
| if (keepExistingContent) | |||
| { | |||
| HeapBlock <char, true> newData; | |||
| newData.allocate (newTotalBytes, clearExtraSpace); | |||
| HeapBlock<char, true> newData; | |||
| newData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
| const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); | |||
| float** const newChannels = reinterpret_cast <float**> (newData.getData()); | |||
| float* newChan = reinterpret_cast <float*> (newData + channelListSize); | |||
| float** const newChannels = reinterpret_cast<float**> (newData.getData()); | |||
| float* newChan = reinterpret_cast<float*> (newData + channelListSize); | |||
| for (int j = 0; j < newNumChannels; ++j) | |||
| { | |||
| @@ -179,9 +197,12 @@ void AudioSampleBuffer::setSize (const int newNumChannels, | |||
| newChan += allocatedSamplesPerChannel; | |||
| } | |||
| const int numChansToCopy = jmin (numChannels, newNumChannels); | |||
| for (int i = 0; i < numChansToCopy; ++i) | |||
| FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); | |||
| 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; | |||
| @@ -191,17 +212,17 @@ void AudioSampleBuffer::setSize (const int newNumChannels, | |||
| { | |||
| if (avoidReallocating && allocatedBytes >= newTotalBytes) | |||
| { | |||
| if (clearExtraSpace) | |||
| if (clearExtraSpace || isClear) | |||
| allocatedData.clear (newTotalBytes); | |||
| } | |||
| else | |||
| { | |||
| allocatedBytes = newTotalBytes; | |||
| allocatedData.allocate (newTotalBytes, clearExtraSpace); | |||
| channels = reinterpret_cast <float**> (allocatedData.getData()); | |||
| allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
| channels = reinterpret_cast<float**> (allocatedData.getData()); | |||
| } | |||
| float* chan = reinterpret_cast <float*> (allocatedData + channelListSize); | |||
| float* chan = reinterpret_cast<float*> (allocatedData + channelListSize); | |||
| for (int i = 0; i < newNumChannels; ++i) | |||
| { | |||
| channels[i] = chan; | |||
| @@ -217,8 +238,13 @@ void AudioSampleBuffer::setSize (const int newNumChannels, | |||
| void AudioSampleBuffer::clear() noexcept | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::clear (channels[i], size); | |||
| if (! isClear) | |||
| { | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::clear (channels[i], size); | |||
| isClear = true; | |||
| } | |||
| } | |||
| void AudioSampleBuffer::clear (const int startSample, | |||
| @@ -226,8 +252,14 @@ void AudioSampleBuffer::clear (const int startSample, | |||
| { | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::clear (channels[i] + startSample, numSamples); | |||
| if (! isClear) | |||
| { | |||
| if (startSample == 0 && numSamples == size) | |||
| isClear = true; | |||
| for (int i = 0; i < numChannels; ++i) | |||
| FloatVectorOperations::clear (channels[i] + startSample, numSamples); | |||
| } | |||
| } | |||
| void AudioSampleBuffer::clear (const int channel, | |||
| @@ -237,7 +269,31 @@ void AudioSampleBuffer::clear (const int channel, | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| FloatVectorOperations::clear (channels [channel] + startSample, numSamples); | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (channels [channel] + startSample, numSamples); | |||
| } | |||
| float AudioSampleBuffer::getSample (int channel, int index) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| return *(channels [channel] + index); | |||
| } | |||
| void AudioSampleBuffer::setSample (int channel, int index, float newValue) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| *(channels [channel] + index) = newValue; | |||
| isClear = false; | |||
| } | |||
| void AudioSampleBuffer::addSample (int channel, int index, float valueToAdd) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (isPositiveAndBelow (index, size)); | |||
| *(channels [channel] + index) += valueToAdd; | |||
| isClear = false; | |||
| } | |||
| void AudioSampleBuffer::applyGain (const int channel, | |||
| @@ -248,7 +304,7 @@ void AudioSampleBuffer::applyGain (const int channel, | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (gain != 1.0f) | |||
| if (gain != 1.0f && ! isClear) | |||
| { | |||
| float* const d = channels [channel] + startSample; | |||
| @@ -265,22 +321,25 @@ void AudioSampleBuffer::applyGainRamp (const int channel, | |||
| float startGain, | |||
| float endGain) noexcept | |||
| { | |||
| if (startGain == endGain) | |||
| if (! isClear) | |||
| { | |||
| applyGain (channel, startSample, numSamples, startGain); | |||
| } | |||
| else | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (startGain == endGain) | |||
| { | |||
| applyGain (channel, startSample, numSamples, startGain); | |||
| } | |||
| else | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [channel] + startSample; | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [channel] + startSample; | |||
| while (--numSamples >= 0) | |||
| { | |||
| *d++ *= startGain; | |||
| startGain += increment; | |||
| while (--numSamples >= 0) | |||
| { | |||
| *d++ *= startGain; | |||
| startGain += increment; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -317,15 +376,27 @@ void AudioSampleBuffer::addFrom (const int destChannel, | |||
| jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||
| jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||
| if (gain != 0.0f && numSamples > 0) | |||
| if (gain != 0.0f && numSamples > 0 && ! source.isClear) | |||
| { | |||
| float* const d = channels [destChannel] + destStartSample; | |||
| const float* const s = source.channels [sourceChannel] + sourceStartSample; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::addWithMultiply (d, s, gain, numSamples); | |||
| if (isClear) | |||
| { | |||
| isClear = false; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::copyWithMultiply (d, s, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::copy (d, s, numSamples); | |||
| } | |||
| else | |||
| FloatVectorOperations::add (d, s, numSamples); | |||
| { | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::addWithMultiply (d, s, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::add (d, s, numSamples); | |||
| } | |||
| } | |||
| } | |||
| @@ -343,10 +414,22 @@ void AudioSampleBuffer::addFrom (const int destChannel, | |||
| { | |||
| float* const d = channels [destChannel] + destStartSample; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::addWithMultiply (d, source, gain, numSamples); | |||
| if (isClear) | |||
| { | |||
| isClear = false; | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::copy (d, source, numSamples); | |||
| } | |||
| else | |||
| FloatVectorOperations::add (d, source, numSamples); | |||
| { | |||
| if (gain != 1.0f) | |||
| FloatVectorOperations::addWithMultiply (d, source, gain, numSamples); | |||
| else | |||
| FloatVectorOperations::add (d, source, numSamples); | |||
| } | |||
| } | |||
| } | |||
| @@ -369,6 +452,7 @@ void AudioSampleBuffer::addFromWithRamp (const int destChannel, | |||
| { | |||
| if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||
| { | |||
| isClear = false; | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [destChannel] + destStartSample; | |||
| @@ -395,9 +479,20 @@ void AudioSampleBuffer::copyFrom (const int destChannel, | |||
| jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||
| if (numSamples > 0) | |||
| FloatVectorOperations::copy (channels [destChannel] + destStartSample, | |||
| source.channels [sourceChannel] + sourceStartSample, | |||
| numSamples); | |||
| { | |||
| if (source.isClear) | |||
| { | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| FloatVectorOperations::copy (channels [destChannel] + destStartSample, | |||
| source.channels [sourceChannel] + sourceStartSample, | |||
| numSamples); | |||
| } | |||
| } | |||
| } | |||
| void AudioSampleBuffer::copyFrom (const int destChannel, | |||
| @@ -410,7 +505,10 @@ void AudioSampleBuffer::copyFrom (const int destChannel, | |||
| jassert (source != nullptr); | |||
| if (numSamples > 0) | |||
| { | |||
| isClear = false; | |||
| FloatVectorOperations::copy (channels [destChannel] + destStartSample, source, numSamples); | |||
| } | |||
| } | |||
| void AudioSampleBuffer::copyFrom (const int destChannel, | |||
| @@ -425,17 +523,24 @@ void AudioSampleBuffer::copyFrom (const int destChannel, | |||
| if (numSamples > 0) | |||
| { | |||
| float* d = channels [destChannel] + destStartSample; | |||
| float* const d = channels [destChannel] + destStartSample; | |||
| if (gain != 1.0f) | |||
| { | |||
| if (gain == 0) | |||
| FloatVectorOperations::clear (d, numSamples); | |||
| { | |||
| if (! isClear) | |||
| FloatVectorOperations::clear (d, numSamples); | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| isClear = false; | |||
| FloatVectorOperations::copy (d, source, numSamples); | |||
| } | |||
| } | |||
| @@ -460,6 +565,7 @@ void AudioSampleBuffer::copyFromWithRamp (const int destChannel, | |||
| { | |||
| if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||
| { | |||
| isClear = false; | |||
| const float increment = (endGain - startGain) / numSamples; | |||
| float* d = channels [destChannel] + destStartSample; | |||
| @@ -477,8 +583,9 @@ void AudioSampleBuffer::reverse (int channel, int startSample, int numSamples) c | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| std::reverse (channels[channel] + startSample, | |||
| channels[channel] + startSample + numSamples); | |||
| if (! isClear) | |||
| std::reverse (channels[channel] + startSample, | |||
| channels[channel] + startSample + numSamples); | |||
| } | |||
| void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept | |||
| @@ -487,17 +594,17 @@ void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept | |||
| reverse (i, startSample, numSamples); | |||
| } | |||
| void AudioSampleBuffer::findMinMax (const int channel, | |||
| const int startSample, | |||
| int numSamples, | |||
| float& minVal, | |||
| float& maxVal) const noexcept | |||
| Range<float> AudioSampleBuffer::findMinMax (const int channel, | |||
| const int startSample, | |||
| int numSamples) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| FloatVectorOperations::findMinAndMax (channels [channel] + startSample, | |||
| numSamples, minVal, maxVal); | |||
| if (isClear) | |||
| return Range<float>(); | |||
| return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); | |||
| } | |||
| float AudioSampleBuffer::getMagnitude (const int channel, | |||
| @@ -507,18 +614,21 @@ float AudioSampleBuffer::getMagnitude (const int channel, | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| float mn, mx; | |||
| findMinMax (channel, startSample, numSamples, mn, mx); | |||
| if (isClear) | |||
| return 0.0f; | |||
| const Range<float> r (findMinMax (channel, startSample, numSamples)); | |||
| return jmax (mn, -mn, mx, -mx); | |||
| return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); | |||
| } | |||
| float AudioSampleBuffer::getMagnitude (int startSample, int numSamples) const noexcept | |||
| { | |||
| float mag = 0.0f; | |||
| for (int i = 0; i < numChannels; ++i) | |||
| mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||
| if (! isClear) | |||
| for (int i = 0; i < numChannels; ++i) | |||
| mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||
| return mag; | |||
| } | |||
| @@ -530,7 +640,7 @@ float AudioSampleBuffer::getRMSLevel (const int channel, | |||
| jassert (isPositiveAndBelow (channel, numChannels)); | |||
| jassert (startSample >= 0 && startSample + numSamples <= size); | |||
| if (numSamples <= 0 || channel < 0 || channel >= numChannels) | |||
| if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) | |||
| return 0.0f; | |||
| const float* const data = channels [channel] + startSample; | |||
| @@ -116,27 +116,58 @@ public: | |||
| */ | |||
| int getNumSamples() const noexcept { return size; } | |||
| /** Returns a pointer one of the buffer's channels. | |||
| /** 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, | |||
| so be careful when using it! | |||
| If you need to write to the data, do NOT call this method and const_cast the | |||
| result! Instead, you must call getWritePointer so that the buffer knows you're | |||
| planning on modifying the data. | |||
| */ | |||
| float* getSampleData (const int channelNumber) const noexcept | |||
| const float* getReadPointer (int channelNumber) const noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| return channels [channelNumber]; | |||
| } | |||
| /** Returns a pointer to a sample in one of the buffer's channels. | |||
| /** 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 or index are out of range, | |||
| so be careful when using it! | |||
| If you need to write to the data, do NOT call this method and const_cast the | |||
| 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 | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| jassert (isPositiveAndBelow (sampleIndex, size)); | |||
| return channels [channelNumber] + sampleIndex; | |||
| } | |||
| For speed, this doesn't check whether the channel and sample number | |||
| are out-of-range, so be careful when using it! | |||
| /** Returns a writeable pointer to one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number is out of range, | |||
| so be careful when using it! | |||
| Note that if you're not planning on writing to the data, you should always | |||
| use getReadPointer instead. | |||
| */ | |||
| float* getSampleData (const int channelNumber, | |||
| const int sampleOffset) const noexcept | |||
| float* getWritePointer (int channelNumber) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| jassert (isPositiveAndBelow (sampleOffset, size)); | |||
| return channels [channelNumber] + sampleOffset; | |||
| isClear = false; | |||
| return channels [channelNumber]; | |||
| } | |||
| /** Returns a writeable pointer to one of the buffer's channels. | |||
| For speed, this doesn't check whether the channel number or index are out of range, | |||
| so be careful when using it! | |||
| Note that if you're not planning on writing to the data, you should | |||
| use getReadPointer instead. | |||
| */ | |||
| float* getWritePointer (int channelNumber, int sampleIndex) noexcept | |||
| { | |||
| jassert (isPositiveAndBelow (channelNumber, numChannels)); | |||
| jassert (isPositiveAndBelow (sampleIndex, size)); | |||
| isClear = false; | |||
| return channels [channelNumber] + sampleIndex; | |||
| } | |||
| /** Returns an array of pointers to the channels in the buffer. | |||
| @@ -144,7 +175,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. | |||
| */ | |||
| float** getArrayOfChannels() const noexcept { return channels; } | |||
| const float** getArrayOfReadPointers() const noexcept { return const_cast<const float**> (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; } | |||
| //============================================================================== | |||
| /** Changes the buffer's size or number of channels. | |||
| @@ -216,6 +254,36 @@ public: | |||
| int startSample, | |||
| int numSamples) noexcept; | |||
| /** Returns true if the buffer has been entirely cleared. | |||
| Note that this does not actually measure the contents of the buffer - it simply | |||
| returns a flag that is set when the buffer is cleared, and which is reset whenever | |||
| functions like getWritePointer() are invoked. That means the method does not take | |||
| any time, but it may return false negatives when in fact the buffer is still empty. | |||
| */ | |||
| bool hasBeenCleared() const noexcept { return isClear; } | |||
| //============================================================================== | |||
| /** Returns a sample from 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. | |||
| */ | |||
| float getSample (int channel, int sampleIndex) const noexcept; | |||
| /** 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; | |||
| /** 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; | |||
| /** Applies a gain multiple to a region of one channel. | |||
| For speed, this doesn't check whether the channel and sample number | |||
| @@ -392,19 +460,15 @@ public: | |||
| float endGain) noexcept; | |||
| /** Finds the highest and lowest sample values in a given range. | |||
| /** Returns a Range indicating the lowest and highest sample values in a given section. | |||
| @param channel the channel to read from | |||
| @param startSample the start sample within the channel | |||
| @param numSamples the number of samples to check | |||
| @param minVal on return, the lowest value that was found | |||
| @param maxVal on return, the highest value that was found | |||
| */ | |||
| void findMinMax (int channel, | |||
| int startSample, | |||
| int numSamples, | |||
| float& minVal, | |||
| float& maxVal) const noexcept; | |||
| Range<float> findMinMax (int channel, | |||
| int startSample, | |||
| int numSamples) const noexcept; | |||
| /** Finds the highest absolute sample value within a region of a channel. */ | |||
| float getMagnitude (int channel, | |||
| @@ -426,13 +490,27 @@ public: | |||
| /** Reverses a part of the buffer. */ | |||
| void reverse (int startSample, int numSamples) const noexcept; | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| // Note that these methods have now been replaced by getReadPointer() and getWritePointer() | |||
| JUCE_DEPRECATED (const float* getSampleData (int channel) const) { return getReadPointer (channel); } | |||
| JUCE_DEPRECATED (const float* getSampleData (int channel, int index) const) { return getReadPointer (channel, index); } | |||
| JUCE_DEPRECATED (float* getSampleData (int channel)) { return getWritePointer (channel); } | |||
| JUCE_DEPRECATED (float* getSampleData (int channel, int index)) { return getWritePointer (channel, index); } | |||
| // These have been replaced by getArrayOfReadPointers() and getArrayOfWritePointers() | |||
| JUCE_DEPRECATED (const float** getArrayOfChannels() const) { return getArrayOfReadPointers(); } | |||
| JUCE_DEPRECATED (float** getArrayOfChannels()) { return getArrayOfWritePointers(); } | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| int numChannels, size; | |||
| size_t allocatedBytes; | |||
| float** channels; | |||
| HeapBlock <char, true> allocatedData; | |||
| HeapBlock<char, true> allocatedData; | |||
| float* preallocatedChannelSpace [32]; | |||
| bool isClear; | |||
| void allocateData(); | |||
| void allocateChannels (float* const* dataToReferTo, int offset); | |||
| @@ -38,48 +38,90 @@ public: | |||
| /** Clears a vector of floats. */ | |||
| static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; | |||
| /** Clears a vector of doubles. */ | |||
| static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept; | |||
| /** Copies a repeated value into a vector of floats. */ | |||
| static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; | |||
| /** Copies a repeated value into a vector of doubles. */ | |||
| static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; | |||
| /** Copies a vector of floats. */ | |||
| static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; | |||
| /** Copies a vector of doubles. */ | |||
| static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; | |||
| /** Copies a vector of floats, multiplying each value by a given multiplier */ | |||
| static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||
| /** Copies a vector of doubles, multiplying each value by a given multiplier */ | |||
| static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||
| /** Adds a fixed value to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; | |||
| /** Adds a fixed value to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, float amount, int numValues) noexcept; | |||
| static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||
| /** Adds the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
| /** Adds the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||
| /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
| static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||
| /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
| static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; | |||
| /** Multiplies the destination values by the source values. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept; | |||
| /** Multiplies the destination values by the source values. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; | |||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; | |||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||
| /** Copies a source vector to a destination, negating each value. */ | |||
| static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; | |||
| /** Copies a source vector to a destination, negating each value. */ | |||
| static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; | |||
| /** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ | |||
| static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; | |||
| /** Finds the miniumum and maximum values in the given array. */ | |||
| static void JUCE_CALLTYPE findMinAndMax (const float* src, int numValues, float& minResult, float& maxResult) noexcept; | |||
| static Range<float> JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; | |||
| /** Finds the miniumum and maximum values in the given array. */ | |||
| static Range<double> JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; | |||
| /** Finds the miniumum value in the given array. */ | |||
| static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; | |||
| /** Finds the miniumum value in the given array. */ | |||
| static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; | |||
| /** Finds the maximum value in the given array. */ | |||
| static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; | |||
| /** Finds the maximum value in the given array. */ | |||
| static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; | |||
| /** On Intel CPUs, this method enables or disables the SSE flush-to-zero mode. | |||
| Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE | |||
| */ | |||
| @@ -36,6 +36,10 @@ | |||
| #include "AppConfig.h" | |||
| #include "juce_audio_basics.h" | |||
| #if JUCE_MINGW && ! defined (__SSE2__) | |||
| #define JUCE_USE_SSE_INTRINSICS 0 | |||
| #endif | |||
| #ifndef JUCE_USE_SSE_INTRINSICS | |||
| #define JUCE_USE_SSE_INTRINSICS 1 | |||
| #endif | |||
| @@ -350,11 +350,13 @@ void MidiFile::convertTimestampTicksToSeconds() | |||
| } | |||
| //============================================================================== | |||
| bool MidiFile::writeTo (OutputStream& out) | |||
| bool MidiFile::writeTo (OutputStream& out, int midiFileType) | |||
| { | |||
| jassert (midiFileType >= 0 && midiFileType <= 2); | |||
| out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd")); | |||
| out.writeIntBigEndian (6); | |||
| out.writeShortBigEndian (1); // type | |||
| out.writeShortBigEndian ((short) midiFileType); | |||
| out.writeShortBigEndian ((short) tracks.size()); | |||
| out.writeShortBigEndian (timeFormat); | |||
| @@ -151,9 +151,11 @@ public: | |||
| bool readFrom (InputStream& sourceStream); | |||
| /** Writes the midi tracks as a standard midi file. | |||
| The midiFileType value is written as the file's format type, which can be 0, 1 | |||
| or 2 - see the midi file spec for more info about that. | |||
| @returns true if the operation succeeded. | |||
| */ | |||
| bool writeTo (OutputStream& destStream); | |||
| bool writeTo (OutputStream& destStream, int midiFileType = 1); | |||
| /** Converts the timestamp of all the midi events from midi ticks to seconds. | |||
| @@ -152,7 +152,8 @@ MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | |||
| } | |||
| } | |||
| MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, double t) | |||
| MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | |||
| double t, bool sysexHasEmbeddedLength) | |||
| : timeStamp (t) | |||
| { | |||
| const uint8* src = static_cast<const uint8*> (srcData); | |||
| @@ -175,7 +176,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||
| if (byte == 0xf0) | |||
| { | |||
| const uint8* d = src; | |||
| bool haveReadAllLengthBytes = false; | |||
| bool haveReadAllLengthBytes = ! sysexHasEmbeddedLength; | |||
| int numVariableLengthSysexBytes = 0; | |||
| while (d < src + sz) | |||
| @@ -82,10 +82,14 @@ public: | |||
| has in fact been dropped. | |||
| @param timeStamp the time to give the midi message - this value doesn't | |||
| use any particular units, so will be application-specific | |||
| @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether | |||
| to expect the data to begin with a variable-length field | |||
| indicating its size | |||
| */ | |||
| MidiMessage (const void* data, int maxBytesToUse, | |||
| int& numBytesUsed, uint8 lastStatusByte, | |||
| double timeStamp = 0); | |||
| double timeStamp = 0, | |||
| bool sysexHasEmbeddedLength = true); | |||
| /** Creates an active-sense message. | |||
| Since the MidiMessage has to contain a valid message, this default constructor | |||
| @@ -94,10 +98,10 @@ public: | |||
| MidiMessage() noexcept; | |||
| /** Creates a copy of another midi message. */ | |||
| MidiMessage (const MidiMessage& other); | |||
| MidiMessage (const MidiMessage&); | |||
| /** Creates a copy of another midi message, with a different timestamp. */ | |||
| MidiMessage (const MidiMessage& other, double newTimeStamp); | |||
| MidiMessage (const MidiMessage&, double newTimeStamp); | |||
| /** Destructor. */ | |||
| ~MidiMessage(); | |||
| @@ -106,8 +110,8 @@ public: | |||
| MidiMessage& operator= (const MidiMessage& other); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| MidiMessage (MidiMessage&& other) noexcept; | |||
| MidiMessage& operator= (MidiMessage&& other) noexcept; | |||
| MidiMessage (MidiMessage&&) noexcept; | |||
| MidiMessage& operator= (MidiMessage&&) noexcept; | |||
| #endif | |||
| //============================================================================== | |||
| @@ -72,6 +72,6 @@ void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& buff | |||
| for (int i = 0; i < numChannels; ++i) | |||
| iirFilters.getUnchecked(i) | |||
| ->processSamples (bufferToFill.buffer->getSampleData (i, bufferToFill.startSample), | |||
| ->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample), | |||
| bufferToFill.numSamples); | |||
| } | |||
| @@ -121,7 +121,7 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf | |||
| // for down-sampling, pre-apply the filter.. | |||
| for (int i = channelsToProcess; --i >= 0;) | |||
| applyFilter (buffer.getSampleData (i, endOfBufferPos), numToDo, filterStates[i]); | |||
| applyFilter (buffer.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]); | |||
| } | |||
| sampsInBuffer += numToDo; | |||
| @@ -130,8 +130,8 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf | |||
| for (int channel = 0; channel < channelsToProcess; ++channel) | |||
| { | |||
| destBuffers[channel] = info.buffer->getSampleData (channel, info.startSample); | |||
| srcBuffers[channel] = buffer.getSampleData (channel, 0); | |||
| destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample); | |||
| srcBuffers[channel] = buffer.getReadPointer (channel); | |||
| } | |||
| int nextPos = (bufferPos + 1) % bufferSize; | |||
| @@ -163,14 +163,14 @@ void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& inf | |||
| { | |||
| // for up-sampling, apply the filter after transposing.. | |||
| for (int i = channelsToProcess; --i >= 0;) | |||
| applyFilter (info.buffer->getSampleData (i, info.startSample), info.numSamples, filterStates[i]); | |||
| applyFilter (info.buffer->getWritePointer (i, info.startSample), info.numSamples, filterStates[i]); | |||
| } | |||
| else if (localRatio <= 1.0001 && info.numSamples > 0) | |||
| { | |||
| // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities | |||
| for (int i = channelsToProcess; --i >= 0;) | |||
| { | |||
| const float* const endOfBuffer = info.buffer->getSampleData (i, info.startSample + info.numSamples - 1); | |||
| const float* const endOfBuffer = info.buffer->getReadPointer (i, info.startSample + info.numSamples - 1); | |||
| FilterState& fs = filterStates[i]; | |||
| if (info.numSamples > 1) | |||
| @@ -81,7 +81,8 @@ private: | |||
| double coefficients[6]; | |||
| SpinLock ratioLock; | |||
| const int numChannels; | |||
| HeapBlock<float*> destBuffers, srcBuffers; | |||
| HeapBlock<float*> destBuffers; | |||
| HeapBlock<const float*> srcBuffers; | |||
| void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); | |||
| void createLowPass (double proportionalRate); | |||
| @@ -48,12 +48,12 @@ void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferT | |||
| if (! bypass) | |||
| { | |||
| float* const firstChannel = bufferToFill.buffer->getSampleData (0, bufferToFill.startSample); | |||
| float* const firstChannel = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); | |||
| if (bufferToFill.buffer->getNumChannels() > 1) | |||
| { | |||
| reverb.processStereo (firstChannel, | |||
| bufferToFill.buffer->getSampleData (1, bufferToFill.startSample), | |||
| bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample), | |||
| bufferToFill.numSamples); | |||
| } | |||
| else | |||
| @@ -70,6 +70,6 @@ void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& | |||
| currentPhase += phasePerSample; | |||
| for (int j = info.buffer->getNumChannels(); --j >= 0;) | |||
| *info.buffer->getSampleData (j, info.startSample + i) = sample; | |||
| info.buffer->setSample (j, info.startSample + i, sample); | |||
| } | |||
| } | |||
| @@ -58,6 +58,11 @@ void SynthesiserVoice::clearCurrentNote() | |||
| void SynthesiserVoice::aftertouchChanged (int) {} | |||
| bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept | |||
| { | |||
| return noteOnTime < other.noteOnTime; | |||
| } | |||
| //============================================================================== | |||
| Synthesiser::Synthesiser() | |||
| : sampleRate (0), | |||
| @@ -407,12 +412,11 @@ void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
| } | |||
| //============================================================================== | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| const bool stealIfNoneAvailable) const | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, const bool stealIfNoneAvailable) const | |||
| { | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| @@ -421,22 +425,25 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| } | |||
| if (stealIfNoneAvailable) | |||
| { | |||
| // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||
| SynthesiserVoice* oldest = nullptr; | |||
| return findVoiceToSteal (soundToPlay); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| return nullptr; | |||
| } | |||
| if (voice->canPlaySound (soundToPlay) | |||
| && (oldest == nullptr || oldest->noteOnTime > voice->noteOnTime)) | |||
| oldest = voice; | |||
| } | |||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay) const | |||
| { | |||
| // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||
| SynthesiserVoice* oldest = nullptr; | |||
| jassert (oldest != nullptr); | |||
| return oldest; | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->canPlaySound (soundToPlay) | |||
| && (oldest == nullptr || voice->wasStartedBefore (*oldest))) | |||
| oldest = voice; | |||
| } | |||
| return nullptr; | |||
| jassert (oldest != nullptr); | |||
| return oldest; | |||
| } | |||
| @@ -199,6 +199,9 @@ public: | |||
| /** Returns true if the sostenuto pedal is currently active for this voice. */ | |||
| bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } | |||
| /** Returns true if this voice started playing its current note before the other voice did. */ | |||
| bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
| protected: | |||
| //============================================================================== | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| @@ -481,6 +484,12 @@ protected: | |||
| virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||
| const bool stealIfNoneAvailable) const; | |||
| /** Chooses a voice that is most suitable for being re-used. | |||
| The default method returns the one that has been playing for the longest, but | |||
| you may want to override this and do something more cunning instead. | |||
| */ | |||
| virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay) const; | |||
| /** Starts a specified voice playing a particular sound. | |||
| You'll probably never need to call this, it's used internally by noteOn(), but | |||
| @@ -728,7 +728,7 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
| callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, | |||
| outputChannelData, numOutputChannels, numSamples); | |||
| float** const tempChans = tempBuffer.getArrayOfChannels(); | |||
| float** const tempChans = tempBuffer.getArrayOfWritePointers(); | |||
| for (int i = callbacks.size(); --i > 0;) | |||
| { | |||
| @@ -757,7 +757,7 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
| if (testSound != nullptr) | |||
| { | |||
| const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||
| const float* const src = testSound->getSampleData (0, testSoundPosition); | |||
| const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| for (int j = 0; j < numSamps; ++j) | |||
| @@ -951,16 +951,15 @@ void AudioDeviceManager::playTestSound() | |||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| const int soundLength = (int) sampleRate; | |||
| AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||
| float* samples = newSound->getSampleData (0); | |||
| const double frequency = 440.0; | |||
| const float amplitude = 0.5f; | |||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
| AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||
| for (int i = 0; i < soundLength; ++i) | |||
| samples[i] = amplitude * (float) std::sin (i * phasePerSample); | |||
| 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); | |||
| @@ -304,7 +304,7 @@ public: | |||
| for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan) | |||
| { | |||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getSampleData (chan)); | |||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan)); | |||
| if (chan < numDeviceInputChannels) | |||
| { | |||
| @@ -328,8 +328,8 @@ public: | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback ((const float**) inputChannelBuffer.getArrayOfChannels(), numClientInputChannels, | |||
| outputChannelBuffer.getArrayOfChannels(), numClientOutputChannels, | |||
| callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels, | |||
| outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| @@ -349,7 +349,7 @@ public: | |||
| { | |||
| AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels); | |||
| const float* const sourceChanData = outputChannelBuffer.getSampleData (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||
| const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData); | |||
| d.convertSamples (s, actualBufferSize); | |||
| } | |||
| @@ -179,10 +179,8 @@ public: | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback (numInputChannels > 0 ? (const float**) inputBuffer.getArrayOfChannels() : nullptr, | |||
| numInputChannels, | |||
| numOutputChannels > 0 ? outputBuffer.getArrayOfChannels() : nullptr, | |||
| numOutputChannels, | |||
| callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | |||
| numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| @@ -404,7 +402,7 @@ private: | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType; | |||
| DstSampleType dstData (destBuffer + i, bufferList.numChannels); | |||
| SrcSampleType srcData (buffer.getSampleData (i, offset)); | |||
| SrcSampleType srcData (buffer.getReadPointer (i, offset)); | |||
| dstData.convertSamples (srcData, bufferList.numSamples); | |||
| } | |||
| @@ -522,7 +520,7 @@ private: | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType; | |||
| typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType; | |||
| DstSampleType dstData (buffer.getSampleData (i, offset)); | |||
| DstSampleType dstData (buffer.getWritePointer (i, offset)); | |||
| SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); | |||
| dstData.convertSamples (srcData, bufferList.numSamples); | |||
| } | |||
| @@ -227,10 +227,10 @@ private: | |||
| zeromem (outputChannels, sizeof (outputChannels)); | |||
| for (int i = 0; i < numInputChannels; ++i) | |||
| inputChannels[i] = floatData.getSampleData (i); | |||
| inputChannels[i] = floatData.getWritePointer (i); | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| outputChannels[i] = floatData.getSampleData (i + numInputChannels); | |||
| outputChannels[i] = floatData.getWritePointer (i + numInputChannels); | |||
| } | |||
| } | |||
| @@ -311,7 +311,7 @@ public: | |||
| bool writeToOutputDevice (AudioSampleBuffer& outputChannelBuffer, const int numSamples) | |||
| { | |||
| jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels()); | |||
| float** const data = outputChannelBuffer.getArrayOfChannels(); | |||
| float* const* const data = outputChannelBuffer.getArrayOfWritePointers(); | |||
| snd_pcm_sframes_t numDone = 0; | |||
| if (isInterleaved) | |||
| @@ -343,7 +343,7 @@ public: | |||
| bool readFromInputDevice (AudioSampleBuffer& inputChannelBuffer, const int numSamples) | |||
| { | |||
| jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels()); | |||
| float** const data = inputChannelBuffer.getArrayOfChannels(); | |||
| float* const* const data = inputChannelBuffer.getArrayOfWritePointers(); | |||
| if (isInterleaved) | |||
| { | |||
| @@ -497,7 +497,7 @@ public: | |||
| { | |||
| if (inputChannels[i]) | |||
| { | |||
| inputChannelDataForCallback.add (inputChannelBuffer.getSampleData (i)); | |||
| inputChannelDataForCallback.add (inputChannelBuffer.getReadPointer (i)); | |||
| currentInputChans.setBit (i); | |||
| } | |||
| } | |||
| @@ -516,7 +516,7 @@ public: | |||
| { | |||
| if (outputChannels[i]) | |||
| { | |||
| outputChannelDataForCallback.add (outputChannelBuffer.getSampleData (i)); | |||
| outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (i)); | |||
| currentOutputChans.setBit (i); | |||
| } | |||
| } | |||
| @@ -666,7 +666,7 @@ public: | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback ((const float**) inputChannelDataForCallback.getRawDataPointer(), | |||
| callback->audioDeviceIOCallback (inputChannelDataForCallback.getRawDataPointer(), | |||
| inputChannelDataForCallback.size(), | |||
| outputChannelDataForCallback.getRawDataPointer(), | |||
| outputChannelDataForCallback.size(), | |||
| @@ -736,7 +736,8 @@ private: | |||
| CriticalSection callbackLock; | |||
| AudioSampleBuffer inputChannelBuffer, outputChannelBuffer; | |||
| Array<float*> inputChannelDataForCallback, outputChannelDataForCallback; | |||
| Array<const float*> inputChannelDataForCallback; | |||
| Array<float*> outputChannelDataForCallback; | |||
| unsigned int minChansOut, maxChansOut; | |||
| unsigned int minChansIn, maxChansIn; | |||
| @@ -129,9 +129,9 @@ private: | |||
| typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat; | |||
| CDSampleFormat left (buffer, 2); | |||
| left.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (0)), numSamples); | |||
| left.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (0)), numSamples); | |||
| CDSampleFormat right (buffer + 2, 2); | |||
| right.convertSamples (SourceSampleFormat (tempBuffer.getSampleData (1)), numSamples); | |||
| right.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (1)), numSamples); | |||
| source->readPosition += numSamples; | |||
| } | |||
| @@ -1319,14 +1319,15 @@ private: | |||
| AudioSampleBuffer buffer (fifos.getNumChannels(), numSamples); | |||
| buffer.clear(); | |||
| Array<float*> inputChans, outputChans; | |||
| Array<const float*> inputChans; | |||
| Array<float*> outputChans; | |||
| for (int i = 0; i < devices.size(); ++i) | |||
| { | |||
| DeviceWrapper& d = *devices.getUnchecked(i); | |||
| for (int j = 0; j < d.numInputChans; ++j) inputChans.add (buffer.getSampleData (d.inputIndex + j)); | |||
| for (int j = 0; j < d.numOutputChans; ++j) outputChans.add (buffer.getSampleData (d.outputIndex + j)); | |||
| for (int j = 0; j < d.numInputChans; ++j) inputChans.add (buffer.getReadPointer (d.inputIndex + j)); | |||
| for (int j = 0; j < d.numOutputChans; ++j) outputChans.add (buffer.getWritePointer (d.outputIndex + j)); | |||
| } | |||
| const int numInputChans = inputChans.size(); | |||
| @@ -1535,8 +1536,8 @@ private: | |||
| for (int i = 0; i < numInputChans; ++i) | |||
| { | |||
| const int index = inputIndex + i; | |||
| float* const dest = destBuffer.getSampleData (index); | |||
| const float* const src = owner.fifos.getSampleData (index); | |||
| float* const dest = destBuffer.getWritePointer (index); | |||
| const float* const src = owner.fifos.getReadPointer (index); | |||
| if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); | |||
| if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); | |||
| @@ -1561,8 +1562,8 @@ private: | |||
| for (int i = 0; i < numOutputChans; ++i) | |||
| { | |||
| const int index = outputIndex + i; | |||
| float* const dest = owner.fifos.getSampleData (index); | |||
| const float* const src = srcBuffer.getSampleData (index); | |||
| float* const dest = owner.fifos.getWritePointer (index); | |||
| const float* const src = srcBuffer.getReadPointer (index); | |||
| if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); | |||
| if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2); | |||
| @@ -1590,7 +1591,7 @@ private: | |||
| for (int i = 0; i < numInputChannels; ++i) | |||
| { | |||
| float* const dest = buf.getSampleData (inputIndex + i); | |||
| float* const dest = buf.getWritePointer (inputIndex + i); | |||
| const float* const src = inputChannelData[i]; | |||
| if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); | |||
| @@ -1622,7 +1623,7 @@ private: | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| { | |||
| float* const dest = outputChannelData[i]; | |||
| const float* const src = buf.getSampleData (outputIndex + i); | |||
| const float* const src = buf.getReadPointer (outputIndex + i); | |||
| if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); | |||
| if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); | |||
| @@ -391,9 +391,9 @@ bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
| AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat; | |||
| CDSampleFormat left (buffer, 2); | |||
| left.convertSamples (SourceSampleFormat (sourceBuffer.getSampleData (0)), samplesPerBlock); | |||
| left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock); | |||
| CDSampleFormat right (buffer + 2, 2); | |||
| right.convertSamples (SourceSampleFormat (sourceBuffer.getSampleData (1)), samplesPerBlock); | |||
| right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock); | |||
| hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock); | |||
| @@ -998,10 +998,8 @@ public: | |||
| if (isStarted) | |||
| { | |||
| callback->audioDeviceIOCallback (const_cast <const float**> (inputBuffers.getArrayOfChannels()), | |||
| inputBuffers.getNumChannels(), | |||
| outputBuffers.getArrayOfChannels(), | |||
| outputBuffers.getNumChannels(), | |||
| callback->audioDeviceIOCallback (inputBuffers.getArrayOfReadPointers(), inputBuffers.getNumChannels(), | |||
| outputBuffers.getArrayOfWritePointers(), outputBuffers.getNumChannels(), | |||
| bufferSizeSamples); | |||
| } | |||
| else | |||
| @@ -1105,13 +1103,8 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, | |||
| for (int i = 0; i <= enabledInputs.getHighestBit(); i += 2) | |||
| { | |||
| float* left = nullptr; | |||
| if (enabledInputs[i]) | |||
| left = inputBuffers.getSampleData (numIns++); | |||
| float* right = nullptr; | |||
| if (enabledInputs[i + 1]) | |||
| right = inputBuffers.getSampleData (numIns++); | |||
| float* left = enabledInputs[i] ? inputBuffers.getWritePointer (numIns++) : nullptr; | |||
| float* right = enabledInputs[i + 1] ? inputBuffers.getWritePointer (numIns++) : nullptr; | |||
| if (left != nullptr || right != nullptr) | |||
| inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex], | |||
| @@ -1131,13 +1124,8 @@ String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, | |||
| for (int i = 0; i <= enabledOutputs.getHighestBit(); i += 2) | |||
| { | |||
| float* left = nullptr; | |||
| if (enabledOutputs[i]) | |||
| left = outputBuffers.getSampleData (numOuts++); | |||
| float* right = nullptr; | |||
| if (enabledOutputs[i + 1]) | |||
| right = outputBuffers.getSampleData (numOuts++); | |||
| float* left = enabledOutputs[i] ? outputBuffers.getWritePointer (numOuts++) : nullptr; | |||
| float* right = enabledOutputs[i + 1] ? outputBuffers.getWritePointer (numOuts++) : nullptr; | |||
| if (left != nullptr || right != nullptr) | |||
| outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex], | |||
| @@ -632,7 +632,7 @@ public: | |||
| { | |||
| closeClient(); | |||
| captureClient = nullptr; | |||
| reservoir.setSize (0); | |||
| reservoir.reset(); | |||
| } | |||
| template <class SourceType> | |||
| @@ -1063,8 +1063,8 @@ public: | |||
| AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32); | |||
| AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32); | |||
| float** const inputBuffers = ins.getArrayOfChannels(); | |||
| float** const outputBuffers = outs.getArrayOfChannels(); | |||
| float** const inputBuffers = ins.getArrayOfWritePointers(); | |||
| float** const outputBuffers = outs.getArrayOfWritePointers(); | |||
| ins.clear(); | |||
| while (! threadShouldExit()) | |||
| @@ -115,7 +115,7 @@ void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, | |||
| for (int i = numOutputs; i < numInputs; ++i) | |||
| { | |||
| channels[numActiveChans] = tempBuffer.getSampleData (i - numOutputs, 0); | |||
| channels[numActiveChans] = tempBuffer.getWritePointer (i - numOutputs); | |||
| memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); | |||
| ++numActiveChans; | |||
| } | |||
| @@ -61,6 +61,7 @@ namespace FlacNamespace | |||
| #define FLAC__HAS_X86INTRIN 1 | |||
| #endif | |||
| #undef __STDC_LIMIT_MACROS | |||
| #define __STDC_LIMIT_MACROS 1 | |||
| #define flac_max jmax | |||
| #define flac_min jmin | |||
| @@ -175,7 +176,7 @@ public: | |||
| for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||
| if (destSamples[i] != nullptr) | |||
| memcpy (destSamples[i] + startOffsetInDestBuffer, | |||
| reservoir.getSampleData (i, (int) (startSampleInFile - reservoirStart)), | |||
| reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)), | |||
| sizeof (int) * (size_t) num); | |||
| startOffsetInDestBuffer += num; | |||
| @@ -242,7 +243,7 @@ public: | |||
| if (src != nullptr) | |||
| { | |||
| int* const dest = reinterpret_cast<int*> (reservoir.getSampleData(i)); | |||
| int* const dest = reinterpret_cast<int*> (reservoir.getWritePointer(i)); | |||
| for (int j = 0; j < numSamples; ++j) | |||
| dest[j] = src[j] << bitsToShift; | |||
| @@ -516,12 +516,21 @@ struct MP3Frame | |||
| { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 } } | |||
| }; | |||
| switch (layer) | |||
| if (bitrateIndex == 0) | |||
| { | |||
| case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
| case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
| case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
| default: break; | |||
| jassertfalse; // This means the file is using "free format". Apparently very few decoders | |||
| // support this mode, and this one certainly doesn't handle it correctly! | |||
| frameSize = 0; | |||
| } | |||
| else | |||
| { | |||
| switch (layer) | |||
| { | |||
| case 1: frameSize = (((frameSizes[lsf][0][bitrateIndex] * 12000) / getFrequency() + padding) * 4) - 4; break; | |||
| case 2: frameSize = (frameSizes[lsf][1][bitrateIndex] * 144000) / getFrequency() + (padding - 4); break; | |||
| case 3: frameSize = (bitrateIndex == 0) ? 0 : ((frameSizes[lsf][2][bitrateIndex] * 144000) / (getFrequency() << lsf) + (padding - 4)); break; | |||
| default: break; | |||
| } | |||
| } | |||
| } | |||
| @@ -1451,7 +1460,7 @@ struct MP3Stream | |||
| bufferPointer = bufferSpace[bufferSpaceIndex] + 512; | |||
| bitIndex = 0; | |||
| if (lastFrameSize == -1) | |||
| if (lastFrameSize < 0) | |||
| return 1; | |||
| } | |||
| @@ -1513,8 +1522,14 @@ struct MP3Stream | |||
| else | |||
| { | |||
| const int nextFrameOffset = scanForNextFrameHeader (true); | |||
| wasFreeFormat = isFreeFormat; | |||
| if (nextFrameOffset < 0) | |||
| { | |||
| lastFrameSize = frameSize; | |||
| return result; | |||
| } | |||
| frameSize = nextFrameOffset + sideInfoSize + dataSize; | |||
| lastFrameSizeNoPadding = frameSize - frame.padding; | |||
| @@ -173,7 +173,7 @@ public: | |||
| for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;) | |||
| if (destSamples[i] != nullptr) | |||
| memcpy (destSamples[i] + startOffsetInDestBuffer, | |||
| reservoir.getSampleData (i, (int) (startSampleInFile - reservoirStart)), | |||
| reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)), | |||
| sizeof (float) * (size_t) numToUse); | |||
| startSampleInFile += numToUse; | |||
| @@ -210,11 +210,7 @@ public: | |||
| jassert (samps <= numToRead); | |||
| for (int i = jmin ((int) numChannels, reservoir.getNumChannels()); --i >= 0;) | |||
| { | |||
| memcpy (reservoir.getSampleData (i, offset), | |||
| dataIn[i], | |||
| sizeof (float) * (size_t) samps); | |||
| } | |||
| memcpy (reservoir.getWritePointer (i, offset), dataIn[i], sizeof (float) * (size_t) samps); | |||
| numToRead -= samps; | |||
| offset += samps; | |||
| @@ -983,11 +983,13 @@ private: | |||
| switch (numChannels) | |||
| { | |||
| case 1: return 0; | |||
| case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
| case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 7: return 1 + 2 + 4 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
| case 8: return 1 + 2 + 4 + 8 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
| case 2: return 1 + 2; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | |||
| case 3: return 1 + 2 + 4; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | |||
| case 4: return 1 + 2 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 5: return 1 + 2 + 4 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 6: return 1 + 2 + 4 + 8 + 16 + 32; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | |||
| case 7: return 1 + 2 + 4 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
| case 8: return 1 + 2 + 4 + 8 + 16 + 32 + 512 + 1024; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT | |||
| default: break; | |||
| } | |||
| @@ -344,6 +344,7 @@ Array<int> WindowsMediaAudioFormat::getPossibleBitDepths() { return Array<i | |||
| bool WindowsMediaAudioFormat::canDoStereo() { return true; } | |||
| bool WindowsMediaAudioFormat::canDoMono() { return true; } | |||
| bool WindowsMediaAudioFormat::isCompressed() { return true; } | |||
| //============================================================================== | |||
| AudioFormatReader* WindowsMediaAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails) | |||
| @@ -40,6 +40,7 @@ public: | |||
| Array<int> getPossibleBitDepths() override; | |||
| bool canDoStereo() override; | |||
| bool canDoMono() override; | |||
| bool isCompressed() override; | |||
| //============================================================================== | |||
| AudioFormatReader* createReaderFor (InputStream*, bool deleteStreamIfOpeningFails) override; | |||
| @@ -106,7 +106,7 @@ static void readChannels (AudioFormatReader& reader, | |||
| const int64 readerStartSample, const int numTargetChannels) | |||
| { | |||
| for (int j = 0; j < numTargetChannels; ++j) | |||
| chans[j] = reinterpret_cast<int*> (buffer->getSampleData (j, startSample)); | |||
| chans[j] = reinterpret_cast<int*> (buffer->getWritePointer (j, startSample)); | |||
| chans[numTargetChannels] = nullptr; | |||
| reader.read (chans, numTargetChannels, readerStartSample, numSamples, true); | |||
| @@ -128,8 +128,8 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer, | |||
| if (numTargetChannels <= 2) | |||
| { | |||
| int* const dest0 = reinterpret_cast<int*> (buffer->getSampleData (0, startSample)); | |||
| int* const dest1 = reinterpret_cast<int*> (numTargetChannels > 1 ? buffer->getSampleData (1, startSample) : nullptr); | |||
| int* const dest0 = reinterpret_cast<int*> (buffer->getWritePointer (0, startSample)); | |||
| int* const dest1 = reinterpret_cast<int*> (numTargetChannels > 1 ? buffer->getWritePointer (1, startSample) : nullptr); | |||
| int* chans[3]; | |||
| if (useReaderLeftChan == useReaderRightChan) | |||
| @@ -168,36 +168,35 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer, | |||
| if (! usesFloatingPointData) | |||
| for (int j = 0; j < numTargetChannels; ++j) | |||
| if (float* const d = buffer->getSampleData (j, startSample)) | |||
| if (float* const d = buffer->getWritePointer (j, startSample)) | |||
| FloatVectorOperations::convertFixedToFloat (d, reinterpret_cast<const int*> (d), 1.0f / 0x7fffffff, numSamples); | |||
| } | |||
| } | |||
| template <typename SampleType> | |||
| static inline void getChannelMinAndMax (SampleType* channel, const int numSamples, SampleType& mn, SampleType& mx) | |||
| static Range<SampleType> getChannelMinAndMax (SampleType* channel, int numSamples) noexcept | |||
| { | |||
| findMinAndMax (channel, numSamples, mn, mx); | |||
| return Range<SampleType>::findMinAndMax (channel, numSamples); | |||
| } | |||
| static inline void getChannelMinAndMax (float* channel, const int numSamples, float& mn, float& mx) | |||
| static Range<float> getChannelMinAndMax (float* channel, int numSamples) noexcept | |||
| { | |||
| FloatVectorOperations::findMinAndMax (channel, numSamples, mn, mx); | |||
| return FloatVectorOperations::findMinAndMax (channel, numSamples); | |||
| } | |||
| template <typename SampleType> | |||
| static void getStereoMinAndMax (SampleType* const* channels, const int numChannels, const int numSamples, | |||
| SampleType& lmin, SampleType& lmax, SampleType& rmin, SampleType& rmax) | |||
| { | |||
| SampleType bufMin, bufMax; | |||
| getChannelMinAndMax (channels[0], numSamples, bufMin, bufMax); | |||
| lmax = jmax (lmax, bufMax); | |||
| lmin = jmin (lmin, bufMin); | |||
| Range<SampleType> range (getChannelMinAndMax (channels[0], numSamples)); | |||
| lmax = jmax (lmax, range.getEnd()); | |||
| lmin = jmin (lmin, range.getStart()); | |||
| if (numChannels > 1) | |||
| { | |||
| getChannelMinAndMax (channels[1], numSamples, bufMin, bufMax); | |||
| rmax = jmax (rmax, bufMax); | |||
| rmin = jmin (rmin, bufMin); | |||
| range = getChannelMinAndMax (channels[1], numSamples); | |||
| rmax = jmax (rmax, range.getEnd()); | |||
| rmin = jmin (rmin, range.getStart()); | |||
| } | |||
| else | |||
| { | |||
| @@ -222,7 +221,7 @@ void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples | |||
| const int bufferSize = (int) jmin (numSamples, (int64) 4096); | |||
| AudioSampleBuffer tempSampleBuffer ((int) numChannels, bufferSize); | |||
| float** const floatBuffer = tempSampleBuffer.getArrayOfChannels(); | |||
| float* const* const floatBuffer = tempSampleBuffer.getArrayOfWritePointers(); | |||
| int* const* intBuffer = reinterpret_cast<int* const*> (floatBuffer); | |||
| if (usesFloatingPointData) | |||
| @@ -68,7 +68,7 @@ bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, | |||
| int* buffers [128] = { 0 }; | |||
| for (int i = tempBuffer.getNumChannels(); --i >= 0;) | |||
| buffers[i] = reinterpret_cast<int*> (tempBuffer.getSampleData (i, 0)); | |||
| buffers[i] = reinterpret_cast<int*> (tempBuffer.getWritePointer (i, 0)); | |||
| if (numSamplesToRead < 0) | |||
| numSamplesToRead = reader.lengthInSamples; | |||
| @@ -170,13 +170,13 @@ bool AudioFormatWriter::writeFromAudioSampleBuffer (const AudioSampleBuffer& sou | |||
| jassert (startSample >= 0 && startSample + numSamples <= source.getNumSamples() && numSourceChannels > 0); | |||
| if (startSample == 0) | |||
| return writeFromFloatArrays (source.getArrayOfChannels(), numSourceChannels, numSamples); | |||
| return writeFromFloatArrays (source.getArrayOfReadPointers(), numSourceChannels, numSamples); | |||
| const float* chans [256]; | |||
| jassert ((int) numChannels < numElementsInArray (chans)); | |||
| for (int i = 0; i < numSourceChannels; ++i) | |||
| chans[i] = source.getSampleData (i, startSample); | |||
| chans[i] = source.getReadPointer (i, startSample); | |||
| chans[numSourceChannels] = nullptr; | |||
| @@ -77,7 +77,7 @@ bool BufferingAudioReader::readSamples (int** destSamples, int numDestChannels, | |||
| dest += startOffsetInDestBuffer; | |||
| if (j < (int) numChannels) | |||
| FloatVectorOperations::copy (dest, block->buffer.getSampleData (j, offset), numToDo); | |||
| FloatVectorOperations::copy (dest, block->buffer.getReadPointer (j, offset), numToDo); | |||
| else | |||
| FloatVectorOperations::clear (dest, numToDo); | |||
| } | |||
| @@ -154,12 +154,12 @@ void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSa | |||
| { | |||
| if (const SamplerSound* const playingSound = static_cast <SamplerSound*> (getCurrentlyPlayingSound().get())) | |||
| { | |||
| const float* const inL = playingSound->data->getSampleData (0, 0); | |||
| const float* const inL = playingSound->data->getReadPointer (0); | |||
| const float* const inR = playingSound->data->getNumChannels() > 1 | |||
| ? playingSound->data->getSampleData (1, 0) : nullptr; | |||
| ? playingSound->data->getReadPointer (1) : nullptr; | |||
| float* outL = outputBuffer.getSampleData (0, startSample); | |||
| float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getSampleData (1, startSample) : nullptr; | |||
| float* outL = outputBuffer.getWritePointer (0, startSample); | |||
| float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer (1, startSample) : nullptr; | |||
| while (--numSamples >= 0) | |||
| { | |||
| @@ -530,7 +530,7 @@ public: | |||
| { | |||
| abl->mBuffers[j].mNumberChannels = 1; | |||
| abl->mBuffers[j].mDataByteSize = sizeof (float) * numSamples; | |||
| abl->mBuffers[j].mData = buffer.getSampleData (i * numOutputBusChannels + j, 0); | |||
| abl->mBuffers[j].mData = buffer.getWritePointer (i * numOutputBusChannels + j); | |||
| } | |||
| } | |||
| @@ -1050,7 +1050,7 @@ private: | |||
| if (bufferChannel < currentBuffer->getNumChannels()) | |||
| { | |||
| memcpy (ioData->mBuffers[i].mData, | |||
| currentBuffer->getSampleData (bufferChannel, 0), | |||
| currentBuffer->getReadPointer (bufferChannel), | |||
| sizeof (float) * inNumberFrames); | |||
| } | |||
| else | |||
| @@ -294,13 +294,13 @@ public: | |||
| { | |||
| for (int i = 0; i < inputs.size(); ++i) | |||
| plugin->connect_port (handle, inputs[i], | |||
| i < buffer.getNumChannels() ? buffer.getSampleData (i) : nullptr); | |||
| i < buffer.getNumChannels() ? buffer.getWritePointer (i) : nullptr); | |||
| if (plugin->run != nullptr) | |||
| { | |||
| for (int i = 0; i < outputs.size(); ++i) | |||
| plugin->connect_port (handle, outputs.getUnchecked(i), | |||
| i < buffer.getNumChannels() ? buffer.getSampleData (i) : nullptr); | |||
| i < buffer.getNumChannels() ? buffer.getWritePointer (i) : nullptr); | |||
| plugin->run (handle, numSamples); | |||
| return; | |||
| @@ -312,7 +312,7 @@ public: | |||
| tempBuffer.clear(); | |||
| for (int i = 0; i < outputs.size(); ++i) | |||
| plugin->connect_port (handle, outputs.getUnchecked(i), tempBuffer.getSampleData (i)); | |||
| plugin->connect_port (handle, outputs.getUnchecked(i), tempBuffer.getWritePointer (i)); | |||
| plugin->run_adding (handle, numSamples); | |||
| @@ -339,7 +339,7 @@ namespace VST3BufferExchange | |||
| */ | |||
| void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, | |||
| Bus& bus, | |||
| const AudioSampleBuffer& buffer, | |||
| AudioSampleBuffer& buffer, | |||
| int numChannels, int channelStartOffset, | |||
| int sampleOffset = 0) noexcept | |||
| { | |||
| @@ -349,7 +349,7 @@ namespace VST3BufferExchange | |||
| bus.clearQuick(); | |||
| for (int i = channelStartOffset; i < channelEnd; ++i) | |||
| bus.add (buffer.getSampleData (i, sampleOffset)); | |||
| bus.add (buffer.getWritePointer (i, sampleOffset)); | |||
| vstBuffers.channelBuffers32 = bus.getRawDataPointer(); | |||
| vstBuffers.numChannels = numChannels; | |||
| @@ -175,9 +175,9 @@ static void setStateForAllBussesOfType (Vst::IComponent* component, | |||
| //============================================================================== | |||
| /** Assigns a complete AudioSampleBuffer's channels to an AudioBusBuffers' */ | |||
| static void associateWholeBufferTo (Vst::AudioBusBuffers& vstBuffers, const AudioSampleBuffer& buffer) noexcept | |||
| static void associateWholeBufferTo (Vst::AudioBusBuffers& vstBuffers, AudioSampleBuffer& buffer) noexcept | |||
| { | |||
| vstBuffers.channelBuffers32 = buffer.getArrayOfChannels(); | |||
| vstBuffers.channelBuffers32 = buffer.getArrayOfWritePointers(); | |||
| vstBuffers.numChannels = buffer.getNumChannels(); | |||
| vstBuffers.silenceFlags = 0; | |||
| } | |||
| @@ -224,6 +224,8 @@ static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playH | |||
| } | |||
| break; | |||
| case AudioPlayHead::fpsUnknown: break; | |||
| default: jassertfalse; break; // New frame rate? | |||
| } | |||
| @@ -1634,7 +1636,11 @@ public: | |||
| bool hasEditor() const override | |||
| { | |||
| ComSmartPtr<IPlugView> view (tryCreatingView()); //N.B.: Must use a ComSmartPtr to not delete the view from the plugin permanently! | |||
| // (if possible, avoid creating a second instance of the editor, because that crashes some plugins) | |||
| if (getActiveEditor() != nullptr) | |||
| return true; | |||
| ComSmartPtr<IPlugView> view (tryCreatingView()); | |||
| return view != nullptr; | |||
| } | |||
| @@ -1752,7 +1758,7 @@ private: | |||
| //============================================================================== | |||
| VST3ModuleHandle::Ptr module; | |||
| friend class VST3HostContext; | |||
| friend VST3HostContext; | |||
| ComSmartPtr<VST3HostContext> host; | |||
| // Information objects: | |||
| @@ -1062,17 +1062,17 @@ public: | |||
| if ((effect->flags & effFlagsCanReplacing) != 0) | |||
| { | |||
| effect->processReplacing (effect, buffer.getArrayOfChannels(), buffer.getArrayOfChannels(), numSamples); | |||
| effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), numSamples); | |||
| } | |||
| else | |||
| { | |||
| tempBuffer.setSize (effect->numOutputs, numSamples); | |||
| tempBuffer.clear(); | |||
| effect->process (effect, buffer.getArrayOfChannels(), tempBuffer.getArrayOfChannels(), numSamples); | |||
| effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), numSamples); | |||
| for (int i = effect->numOutputs; --i >= 0;) | |||
| buffer.copyFrom (i, 0, tempBuffer.getSampleData (i), numSamples); | |||
| buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), numSamples); | |||
| } | |||
| } | |||
| else | |||
| @@ -175,7 +175,7 @@ public: | |||
| void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray <MidiBuffer>&, const int numSamples) | |||
| { | |||
| float* data = sharedBufferChans.getSampleData (channel, 0); | |||
| float* data = sharedBufferChans.getWritePointer (channel, 0); | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| @@ -219,7 +219,7 @@ public: | |||
| void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray <MidiBuffer>& sharedMidiBuffers, const int numSamples) | |||
| { | |||
| for (int i = totalChans; --i >= 0;) | |||
| channels[i] = sharedBufferChans.getSampleData (audioChannelsToUse.getUnchecked (i), 0); | |||
| channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); | |||
| AudioSampleBuffer buffer (channels, totalChans, numSamples); | |||
| @@ -230,8 +230,8 @@ public: | |||
| AudioProcessor* const processor; | |||
| private: | |||
| Array <int> audioChannelsToUse; | |||
| HeapBlock <float*> channels; | |||
| Array<int> audioChannelsToUse; | |||
| HeapBlock<float*> channels; | |||
| int totalChans; | |||
| int midiBufferToUse; | |||
| @@ -754,7 +754,7 @@ String File::createLegalPathName (const String& original) | |||
| String s (original); | |||
| String start; | |||
| if (s[1] == ':') | |||
| if (s.isNotEmpty() && s[1] == ':') | |||
| { | |||
| start = s.substring (0, 2); | |||
| s = s.substring (2); | |||
| @@ -34,6 +34,9 @@ | |||
| /** A general-purpose range object, that simply represents any linear range with | |||
| a start and end point. | |||
| Note that when checking whether values fall within the range, the start value is | |||
| considered to be inclusive, and the end of the range exclusive. | |||
| The templated parameter is expected to be a primitive integer or floating point | |||
| type, though class types could also be used if they behave in a number-like way. | |||
| */ | |||
| @@ -210,7 +213,10 @@ public: | |||
| return jlimit (start, end, value); | |||
| } | |||
| /** Returns true if the given range lies entirely inside this range. */ | |||
| /** Returns true if the given range lies entirely inside this range. | |||
| When making this comparison, the start value is considered to be inclusive, | |||
| and the end of the range exclusive. | |||
| */ | |||
| bool contains (Range other) const noexcept | |||
| { | |||
| return start <= other.start && end >= other.end; | |||
| @@ -237,6 +243,13 @@ public: | |||
| jmax (end, other.end)); | |||
| } | |||
| /** Returns the smallest range that contains both this one and the given value. */ | |||
| Range getUnionWith (const ValueType valueToInclude) const noexcept | |||
| { | |||
| return Range (jmin (valueToInclude, start), | |||
| jmax (valueToInclude, end)); | |||
| } | |||
| /** Returns a given range, after moving it forwards or backwards to fit it | |||
| within this range. | |||
| @@ -255,6 +268,26 @@ public: | |||
| : rangeToConstrain.movedToStartAt (jlimit (start, end - otherLen, rangeToConstrain.getStart())); | |||
| } | |||
| /** Scans an array of values for its min and max, and returns these as a Range. */ | |||
| static Range findMinAndMax (const ValueType* values, int numValues) noexcept | |||
| { | |||
| if (numValues <= 0) | |||
| return Range(); | |||
| const ValueType first (*values++); | |||
| Range r (first, first); | |||
| while (--numValues > 0) // (> 0 rather than >= 0 because we've already taken the first sample) | |||
| { | |||
| const ValueType v (*values++); | |||
| if (r.end < v) r.end = v; | |||
| if (v < r.start) r.start = v; | |||
| } | |||
| return r; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| ValueType start, end; | |||
| @@ -127,8 +127,7 @@ void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) | |||
| { | |||
| if (newSize <= 0) | |||
| { | |||
| data.free(); | |||
| size = 0; | |||
| reset(); | |||
| } | |||
| else | |||
| { | |||
| @@ -149,6 +148,12 @@ void MemoryBlock::setSize (const size_t newSize, const bool initialiseToZero) | |||
| } | |||
| } | |||
| void MemoryBlock::reset() | |||
| { | |||
| data.free(); | |||
| size = 0; | |||
| } | |||
| void MemoryBlock::ensureSize (const size_t minimumSize, const bool initialiseToZero) | |||
| { | |||
| if (size < minimumSize) | |||
| @@ -108,9 +108,9 @@ public: | |||
| /** Resizes the memory block. | |||
| This will try to keep as much of the block's current content as it can, | |||
| and can optionally be made to clear any new space that gets allocated at | |||
| the end of the block. | |||
| Any data that is present in both the old and new sizes will be retained. | |||
| When enlarging the block, the new space that is allocated at the end can either be | |||
| cleared, or left uninitialised. | |||
| @param newSize the new desired size for the block | |||
| @param initialiseNewSpaceToZero if the block gets enlarged, this determines | |||
| @@ -133,6 +133,9 @@ public: | |||
| void ensureSize (const size_t minimumSize, | |||
| bool initialiseNewSpaceToZero = false); | |||
| /** Frees all the blocks data, setting its size to 0. */ | |||
| void reset(); | |||
| //============================================================================== | |||
| /** Fills the entire memory block with a repeated byte value. | |||
| This is handy for clearing a block of memory to zero. | |||
| @@ -32,12 +32,7 @@ | |||
| //============================================================================== | |||
| /** | |||
| A wrapper for a streaming (TCP) socket. | |||
| This allows low-level use of sockets; for an easier-to-use messaging layer on top of | |||
| sockets, you could also try the InterprocessConnection class. | |||
| @see DatagramSocket, InterprocessConnection, InterprocessConnectionServer | |||
| Represents a MAC network card adapter address ID. | |||
| */ | |||
| class JUCE_API MACAddress | |||
| { | |||
| @@ -75,8 +75,7 @@ URL::URL (const URL& other) | |||
| postData (other.postData), | |||
| parameterNames (other.parameterNames), | |||
| parameterValues (other.parameterValues), | |||
| filesToUpload (other.filesToUpload), | |||
| mimeTypes (other.mimeTypes) | |||
| filesToUpload (other.filesToUpload) | |||
| { | |||
| } | |||
| @@ -87,7 +86,6 @@ URL& URL::operator= (const URL& other) | |||
| parameterNames = other.parameterNames; | |||
| parameterValues = other.parameterValues; | |||
| filesToUpload = other.filesToUpload; | |||
| mimeTypes = other.mimeTypes; | |||
| return *this; | |||
| } | |||
| @@ -98,8 +96,7 @@ bool URL::operator== (const URL& other) const | |||
| && postData == other.postData | |||
| && parameterNames == other.parameterNames | |||
| && parameterValues == other.parameterValues | |||
| && filesToUpload == other.filesToUpload | |||
| && mimeTypes == other.mimeTypes; | |||
| && filesToUpload == other.filesToUpload; | |||
| } | |||
| bool URL::operator!= (const URL& other) const | |||
| @@ -156,62 +153,6 @@ namespace URLHelpers | |||
| return url.indexOfChar (findStartOfNetLocation (url), '/') + 1; | |||
| } | |||
| static void createHeadersAndPostData (const URL& url, String& headers, MemoryBlock& postData) | |||
| { | |||
| MemoryOutputStream data (postData, false); | |||
| if (url.getFilesToUpload().size() > 0) | |||
| { | |||
| // need to upload some files, so do it as multi-part... | |||
| const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); | |||
| headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; | |||
| data << "--" << boundary; | |||
| for (int i = 0; i < url.getParameterNames().size(); ++i) | |||
| { | |||
| data << "\r\nContent-Disposition: form-data; name=\"" | |||
| << url.getParameterNames() [i] | |||
| << "\"\r\n\r\n" | |||
| << url.getParameterValues() [i] | |||
| << "\r\n--" | |||
| << boundary; | |||
| } | |||
| for (int i = 0; i < url.getFilesToUpload().size(); ++i) | |||
| { | |||
| const File file (url.getFilesToUpload().getAllValues() [i]); | |||
| const String paramName (url.getFilesToUpload().getAllKeys() [i]); | |||
| data << "\r\nContent-Disposition: form-data; name=\"" << paramName | |||
| << "\"; filename=\"" << file.getFileName() << "\"\r\n"; | |||
| const String mimeType (url.getMimeTypesOfUploadFiles() | |||
| .getValue (paramName, String())); | |||
| if (mimeType.isNotEmpty()) | |||
| data << "Content-Type: " << mimeType << "\r\n"; | |||
| data << "Content-Transfer-Encoding: binary\r\n\r\n" | |||
| << file << "\r\n--" << boundary; | |||
| } | |||
| data << "--\r\n"; | |||
| } | |||
| else | |||
| { | |||
| data << getMangledParameters (url) | |||
| << url.getPostData(); | |||
| // if the user-supplied headers didn't contain a content-type, add one now.. | |||
| if (! headers.containsIgnoreCase ("Content-Type")) | |||
| headers << "Content-Type: application/x-www-form-urlencoded\r\n"; | |||
| headers << "Content-length: " << (int) data.getDataSize() << "\r\n"; | |||
| } | |||
| } | |||
| static void concatenatePaths (String& path, const String& suffix) | |||
| { | |||
| if (! path.endsWithChar ('/')) | |||
| @@ -296,6 +237,64 @@ URL URL::getChildURL (const String& subPath) const | |||
| return u; | |||
| } | |||
| void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPostData) const | |||
| { | |||
| MemoryOutputStream data (headersAndPostData, false); | |||
| data << URLHelpers::getMangledParameters (*this); | |||
| if (filesToUpload.size() > 0) | |||
| { | |||
| // (this doesn't currently support mixing custom post-data with uploads..) | |||
| jassert (postData.isEmpty()); | |||
| const String boundary (String::toHexString (Random::getSystemRandom().nextInt64())); | |||
| headers << "Content-Type: multipart/form-data; boundary=" << boundary << "\r\n"; | |||
| data << "--" << boundary; | |||
| for (int i = 0; i < parameterNames.size(); ++i) | |||
| { | |||
| data << "\r\nContent-Disposition: form-data; name=\"" << parameterNames[i] | |||
| << "\"\r\n\r\n" << parameterValues[i] | |||
| << "\r\n--" << boundary; | |||
| } | |||
| for (int i = 0; i < filesToUpload.size(); ++i) | |||
| { | |||
| const Upload& f = *filesToUpload.getObjectPointerUnchecked(i); | |||
| data << "\r\nContent-Disposition: form-data; name=\"" << f.parameterName | |||
| << "\"; filename=\"" << f.filename << "\"\r\n"; | |||
| if (f.mimeType.isNotEmpty()) | |||
| data << "Content-Type: " << f.mimeType << "\r\n"; | |||
| data << "Content-Transfer-Encoding: binary\r\n\r\n"; | |||
| if (f.data != nullptr) | |||
| data << *f.data; | |||
| else | |||
| data << f.file; | |||
| data << "\r\n--" << boundary; | |||
| } | |||
| data << "--\r\n"; | |||
| } | |||
| else | |||
| { | |||
| data << postData; | |||
| // if the user-supplied headers didn't contain a content-type, add one now.. | |||
| if (! headers.containsIgnoreCase ("Content-Type")) | |||
| headers << "Content-Type: application/x-www-form-urlencoded\r\n"; | |||
| headers << "Content-length: " << (int) data.getDataSize() << "\r\n"; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| bool URL::isProbablyAWebsiteURL (const String& possibleURL) | |||
| { | |||
| @@ -339,7 +338,7 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
| headers << "\r\n"; | |||
| if (usePostCommand) | |||
| URLHelpers::createHeadersAndPostData (*this, headers, headersAndPostData); | |||
| createHeadersAndPostData (headers, headersAndPostData); | |||
| if (! headers.endsWithChar ('\n')) | |||
| headers << "\r\n"; | |||
| @@ -394,33 +393,44 @@ URL URL::withParameter (const String& parameterName, | |||
| return u; | |||
| } | |||
| URL URL::withFileToUpload (const String& parameterName, | |||
| const File& fileToUpload, | |||
| const String& mimeType) const | |||
| URL URL::withPOSTData (const String& newPostData) const | |||
| { | |||
| jassert (mimeType.isNotEmpty()); // You need to supply a mime type! | |||
| URL u (*this); | |||
| u.filesToUpload.set (parameterName, fileToUpload.getFullPathName()); | |||
| u.mimeTypes.set (parameterName, mimeType); | |||
| u.postData = newPostData; | |||
| return u; | |||
| } | |||
| URL URL::withPOSTData (const String& newPostData) const | |||
| URL::Upload::Upload (const String& param, const String& name, | |||
| const String& mime, const File& f, MemoryBlock* mb) | |||
| : parameterName (param), filename (name), mimeType (mime), file (f), data (mb) | |||
| { | |||
| jassert (mimeType.isNotEmpty()); // You need to supply a mime type! | |||
| } | |||
| URL URL::withUpload (Upload* const f) const | |||
| { | |||
| URL u (*this); | |||
| u.postData = newPostData; | |||
| for (int i = u.filesToUpload.size(); --i >= 0;) | |||
| if (u.filesToUpload.getObjectPointerUnchecked(i)->parameterName == f->parameterName) | |||
| u.filesToUpload.remove (i); | |||
| u.filesToUpload.add (f); | |||
| return u; | |||
| } | |||
| const StringPairArray& URL::getFilesToUpload() const | |||
| URL URL::withFileToUpload (const String& parameterName, const File& fileToUpload, | |||
| const String& mimeType) const | |||
| { | |||
| return filesToUpload; | |||
| return withUpload (new Upload (parameterName, fileToUpload.getFileName(), | |||
| mimeType, fileToUpload, nullptr)); | |||
| } | |||
| const StringPairArray& URL::getMimeTypesOfUploadFiles() const | |||
| URL URL::withDataToUpload (const String& parameterName, const String& filename, | |||
| const MemoryBlock& fileContentToUpload, const String& mimeType) const | |||
| { | |||
| return mimeTypes; | |||
| return withUpload (new Upload (parameterName, filename, mimeType, File(), | |||
| new MemoryBlock (fileContentToUpload))); | |||
| } | |||
| //============================================================================== | |||
| @@ -137,18 +137,35 @@ public: | |||
| URL withParameter (const String& parameterName, | |||
| const String& parameterValue) const; | |||
| /** Returns a copy of this URl, with a file-upload type parameter added to it. | |||
| /** Returns a copy of this URL, with a file-upload type parameter added to it. | |||
| When performing a POST where one of your parameters is a binary file, this | |||
| lets you specify the file. | |||
| Note that the filename is stored, but the file itself won't actually be read | |||
| until this URL is later used to create a network input stream. | |||
| until this URL is later used to create a network input stream. If you want to | |||
| upload data from memory, use withDataToUpload(). | |||
| @see withDataToUpload | |||
| */ | |||
| URL withFileToUpload (const String& parameterName, | |||
| const File& fileToUpload, | |||
| const String& mimeType) const; | |||
| /** Returns a copy of this URL, with a file-upload type parameter added to it. | |||
| When performing a POST where one of your parameters is a binary file, this | |||
| lets you specify the file content. | |||
| Note that the filename parameter should not be a full path, it's just the | |||
| last part of the filename. | |||
| @see withFileToUpload | |||
| */ | |||
| URL withDataToUpload (const String& parameterName, | |||
| const String& filename, | |||
| const MemoryBlock& fileContentToUpload, | |||
| const String& mimeType) const; | |||
| /** Returns an array of the names of all the URL's parameters. | |||
| E.g. for the url "www.fish.com?type=haddock&amount=some+fish", this array would | |||
| @@ -175,17 +192,6 @@ public: | |||
| */ | |||
| const StringArray& getParameterValues() const noexcept { return parameterValues; } | |||
| /** Returns the set of files that should be uploaded as part of a POST operation. | |||
| This is the set of files that were added to the URL with the withFileToUpload() | |||
| method. | |||
| */ | |||
| const StringPairArray& getFilesToUpload() const; | |||
| /** Returns the set of mime types associated with each of the upload files. | |||
| */ | |||
| const StringPairArray& getMimeTypesOfUploadFiles() const; | |||
| /** Returns a copy of this URL, with a block of data to send as the POST data. | |||
| If you're setting the POST data, be careful not to have any parameters set | |||
| @@ -343,10 +349,23 @@ private: | |||
| //============================================================================== | |||
| String url, postData; | |||
| StringArray parameterNames, parameterValues; | |||
| StringPairArray filesToUpload, mimeTypes; | |||
| struct Upload : public ReferenceCountedObject | |||
| { | |||
| Upload (const String&, const String&, const String&, const File&, MemoryBlock*); | |||
| String parameterName, filename, mimeType; | |||
| File file; | |||
| ScopedPointer<MemoryBlock> data; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Upload) | |||
| }; | |||
| ReferenceCountedArray<Upload> filesToUpload; | |||
| URL (const String&, int); | |||
| void addParameter (const String&, const String&); | |||
| void createHeadersAndPostData (String&, MemoryBlock&) const; | |||
| URL withUpload (Upload*) const; | |||
| JUCE_LEAK_DETECTOR (URL) | |||
| }; | |||
| @@ -58,6 +58,7 @@ public: | |||
| MacOSX_10_6 = 0x1006, | |||
| MacOSX_10_7 = 0x1007, | |||
| MacOSX_10_8 = 0x1008, | |||
| MacOSX_10_9 = 0x1009, | |||
| Win2000 = 0x4105, | |||
| WinXP = 0x4106, | |||
| @@ -105,18 +105,8 @@ void ReadWriteLock::enterWrite() const noexcept | |||
| const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | |||
| const SpinLock::ScopedLockType sl (accessLock); | |||
| for (;;) | |||
| while (! tryEnterWriteInternal (threadId)) | |||
| { | |||
| if (readerThreads.size() + numWriters == 0 | |||
| || threadId == writerThreadId | |||
| || (readerThreads.size() == 1 | |||
| && readerThreads.getReference(0).threadID == threadId)) | |||
| { | |||
| writerThreadId = threadId; | |||
| ++numWriters; | |||
| break; | |||
| } | |||
| ++numWaitingWriters; | |||
| accessLock.exit(); | |||
| waitEvent.wait (100); | |||
| @@ -127,13 +117,15 @@ void ReadWriteLock::enterWrite() const noexcept | |||
| bool ReadWriteLock::tryEnterWrite() const noexcept | |||
| { | |||
| const Thread::ThreadID threadId = Thread::getCurrentThreadId(); | |||
| const SpinLock::ScopedLockType sl (accessLock); | |||
| return tryEnterWriteInternal (Thread::getCurrentThreadId()); | |||
| } | |||
| bool ReadWriteLock::tryEnterWriteInternal (Thread::ThreadID threadId) const noexcept | |||
| { | |||
| if (readerThreads.size() + numWriters == 0 | |||
| || threadId == writerThreadId | |||
| || (readerThreads.size() == 1 | |||
| && readerThreads.getReference(0).threadID == threadId)) | |||
| || (readerThreads.size() == 1 && readerThreads.getReference(0).threadID == threadId)) | |||
| { | |||
| writerThreadId = threadId; | |||
| ++numWriters; | |||
| @@ -143,6 +143,8 @@ private: | |||
| mutable Array <ThreadRecursionCount> readerThreads; | |||
| bool tryEnterWriteInternal (Thread::ThreadID) const noexcept; | |||
| JUCE_DECLARE_NON_COPYABLE (ReadWriteLock) | |||
| }; | |||
| @@ -75,8 +75,7 @@ public: | |||
| operator var() const; | |||
| /** Returns the value as a string. | |||
| This is alternative to writing things like "myValue.getValue().toString()". | |||
| This is a shortcut for "myValue.getValue().toString()". | |||
| */ | |||
| String toString() const; | |||
| @@ -77,7 +77,7 @@ private: | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (ChildProcessPingThread) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread) | |||
| }; | |||
| //============================================================================== | |||
| @@ -128,7 +128,7 @@ public: | |||
| */ | |||
| Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; } | |||
| /** Returns true if the caller thread has currenltly got the message manager locked. | |||
| /** Returns true if the caller thread has currently got the message manager locked. | |||
| see the MessageManagerLock class for more info about this. | |||
| @@ -295,20 +295,12 @@ Timer::TimerThread* Timer::TimerThread::instance = nullptr; | |||
| Timer::TimerThread::LockType Timer::TimerThread::lock; | |||
| //============================================================================== | |||
| #if JUCE_DEBUG | |||
| static SortedSet <Timer*> activeTimers; | |||
| #endif | |||
| Timer::Timer() noexcept | |||
| : countdownMs (0), | |||
| periodMs (0), | |||
| previous (nullptr), | |||
| next (nullptr) | |||
| { | |||
| #if JUCE_DEBUG | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| activeTimers.add (this); | |||
| #endif | |||
| } | |||
| Timer::Timer (const Timer&) noexcept | |||
| @@ -317,30 +309,17 @@ Timer::Timer (const Timer&) noexcept | |||
| previous (nullptr), | |||
| next (nullptr) | |||
| { | |||
| #if JUCE_DEBUG | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| activeTimers.add (this); | |||
| #endif | |||
| } | |||
| Timer::~Timer() | |||
| { | |||
| stopTimer(); | |||
| #if JUCE_DEBUG | |||
| activeTimers.removeValue (this); | |||
| #endif | |||
| } | |||
| void Timer::startTimer (const int interval) noexcept | |||
| { | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| #if JUCE_DEBUG | |||
| // this isn't a valid object! Your timer might be a dangling pointer or something.. | |||
| jassert (activeTimers.contains (this)); | |||
| #endif | |||
| if (periodMs == 0) | |||
| { | |||
| countdownMs = interval; | |||
| @@ -357,11 +336,6 @@ void Timer::stopTimer() noexcept | |||
| { | |||
| const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||
| #if JUCE_DEBUG | |||
| // this isn't a valid object! Your timer might be a dangling pointer or something.. | |||
| jassert (activeTimers.contains (this)); | |||
| #endif | |||
| if (periodMs > 0) | |||
| { | |||
| TimerThread::remove (this); | |||
| @@ -36,6 +36,11 @@ | |||
| If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface() | |||
| to copy glyphs into this face. | |||
| NOTE! For most people this class is almost certainly NOT the right tool to use! | |||
| If what you want to do is to embed a font into your exe, then your best plan is | |||
| probably to embed your TTF/OTF font file into your binary using the Introjucer, | |||
| and then call Typeface::createSystemTypefaceFor() to load it from memory. | |||
| @see Typeface, Font | |||
| */ | |||
| class JUCE_API CustomTypeface : public Typeface | |||
| @@ -108,6 +113,10 @@ public: | |||
| /** Saves this typeface as a Juce-formatted font file. | |||
| A CustomTypeface can be created to reload the data that is written - see the CustomTypeface | |||
| constructor. | |||
| NOTE! Since this class was written, support was added for loading real font files from | |||
| memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
| is more appropriate than using this class to store it in a proprietory format. | |||
| */ | |||
| bool writeToStream (OutputStream& outputStream); | |||
| @@ -254,6 +254,11 @@ Rectangle<float> Path::getBoundsTransformed (const AffineTransform& transform) c | |||
| } | |||
| //============================================================================== | |||
| void Path::preallocateSpace (int numExtraCoordsToMakeSpaceFor) | |||
| { | |||
| data.ensureAllocatedSize ((int) numElements + numExtraCoordsToMakeSpaceFor); | |||
| } | |||
| void Path::startNewSubPath (const float x, const float y) | |||
| { | |||
| JUCE_CHECK_COORDS_ARE_VALID (x, y); | |||
| @@ -263,7 +268,7 @@ void Path::startNewSubPath (const float x, const float y) | |||
| else | |||
| bounds.extend (x, y); | |||
| data.ensureAllocatedSize ((int) numElements + 3); | |||
| preallocateSpace (3); | |||
| data.elements [numElements++] = moveMarker; | |||
| data.elements [numElements++] = x; | |||
| @@ -282,7 +287,7 @@ void Path::lineTo (const float x, const float y) | |||
| if (numElements == 0) | |||
| startNewSubPath (0, 0); | |||
| data.ensureAllocatedSize ((int) numElements + 3); | |||
| preallocateSpace (3); | |||
| data.elements [numElements++] = lineMarker; | |||
| data.elements [numElements++] = x; | |||
| @@ -305,7 +310,7 @@ void Path::quadraticTo (const float x1, const float y1, | |||
| if (numElements == 0) | |||
| startNewSubPath (0, 0); | |||
| data.ensureAllocatedSize ((int) numElements + 5); | |||
| preallocateSpace (5); | |||
| data.elements [numElements++] = quadMarker; | |||
| data.elements [numElements++] = x1; | |||
| @@ -334,7 +339,7 @@ void Path::cubicTo (const float x1, const float y1, | |||
| if (numElements == 0) | |||
| startNewSubPath (0, 0); | |||
| data.ensureAllocatedSize ((int) numElements + 7); | |||
| preallocateSpace (7); | |||
| data.elements [numElements++] = cubicMarker; | |||
| data.elements [numElements++] = x1; | |||
| @@ -362,7 +367,7 @@ void Path::closeSubPath() | |||
| if (numElements > 0 | |||
| && data.elements [numElements - 1] != closeSubPathMarker) | |||
| { | |||
| data.ensureAllocatedSize ((int) numElements + 1); | |||
| preallocateSpace (1); | |||
| data.elements [numElements++] = closeSubPathMarker; | |||
| } | |||
| } | |||
| @@ -399,7 +404,7 @@ void Path::addRectangle (const float x, const float y, | |||
| if (w < 0) std::swap (x1, x2); | |||
| if (h < 0) std::swap (y1, y2); | |||
| data.ensureAllocatedSize ((int) numElements + 13); | |||
| preallocateSpace (13); | |||
| if (numElements == 0) | |||
| { | |||
| @@ -560,6 +560,18 @@ public: | |||
| */ | |||
| void swapWithPath (Path&) noexcept; | |||
| //============================================================================== | |||
| /** Preallocates enough space for adding the given number of coordinates to the path. | |||
| If you're about to add a large number of lines or curves to the path, it can make | |||
| the task much more efficient to call this first and avoid costly reallocations | |||
| as the structure grows. | |||
| The actual value to pass is a bit tricky to calculate because the space required | |||
| depends on what you're adding - e.g. each lineTo() or startNewSubPath() will | |||
| require 3 coords (x, y and a type marker). Each quadraticTo() will need 5, and | |||
| a cubicTo() will require 7. Closing a sub-path will require 1. | |||
| */ | |||
| void preallocateSpace (int numExtraCoordsToMakeSpaceFor); | |||
| //============================================================================== | |||
| /** Applies a 2D transform to all the vertices in the path. | |||
| @@ -742,12 +754,11 @@ public: | |||
| */ | |||
| void restoreFromString (StringRef stringVersion); | |||
| private: | |||
| //============================================================================== | |||
| friend class PathFlatteningIterator; | |||
| friend class Path::Iterator; | |||
| ArrayAllocationBase <float, DummyCriticalSection> data; | |||
| ArrayAllocationBase<float, DummyCriticalSection> data; | |||
| size_t numElements; | |||
| struct PathBounds | |||
| @@ -164,53 +164,16 @@ public: | |||
| //============================================================================== | |||
| void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, Point<float> pos) | |||
| { | |||
| ++accessCounter; | |||
| CachedGlyphType* glyph = nullptr; | |||
| const ScopedReadLock srl (lock); | |||
| for (int i = glyphs.size(); --i >= 0;) | |||
| { | |||
| CachedGlyphType* const g = glyphs.getUnchecked (i); | |||
| if (g->glyph == glyphNumber && g->font == font) | |||
| { | |||
| glyph = g; | |||
| ++hits; | |||
| break; | |||
| } | |||
| } | |||
| if (glyph == nullptr) | |||
| if (ReferenceCountedObjectPtr<CachedGlyphType> glyph = findOrCreateGlyph (font, glyphNumber)) | |||
| { | |||
| ++misses; | |||
| const ScopedWriteLock swl (lock); | |||
| if (hits.value + misses.value > glyphs.size() * 16) | |||
| { | |||
| if (misses.value * 2 > hits.value) | |||
| addNewGlyphSlots (32); | |||
| hits.set (0); | |||
| misses.set (0); | |||
| glyph = glyphs.getLast(); | |||
| } | |||
| else | |||
| { | |||
| glyph = findLeastRecentlyUsedGlyph(); | |||
| } | |||
| jassert (glyph != nullptr); | |||
| glyph->generate (font, glyphNumber); | |||
| glyph->lastAccessCount = ++accessCounter; | |||
| glyph->draw (target, pos); | |||
| } | |||
| glyph->lastAccessCount = accessCounter.value; | |||
| glyph->draw (target, pos); | |||
| } | |||
| void reset() | |||
| { | |||
| const ScopedWriteLock swl (lock); | |||
| const ScopedLock sl (lock); | |||
| glyphs.clear(); | |||
| addNewGlyphSlots (120); | |||
| hits.set (0); | |||
| @@ -219,9 +182,54 @@ public: | |||
| private: | |||
| friend struct ContainerDeletePolicy<CachedGlyphType>; | |||
| OwnedArray<CachedGlyphType> glyphs; | |||
| ReferenceCountedArray<CachedGlyphType> glyphs; | |||
| Atomic<int> accessCounter, hits, misses; | |||
| ReadWriteLock lock; | |||
| CriticalSection lock; | |||
| ReferenceCountedObjectPtr<CachedGlyphType> findOrCreateGlyph (const Font& font, int glyphNumber) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| if (CachedGlyphType* g = findExistingGlyph (font, glyphNumber)) | |||
| { | |||
| ++hits; | |||
| return g; | |||
| } | |||
| ++misses; | |||
| CachedGlyphType* g = getGlyphForReuse(); | |||
| jassert (g != nullptr); | |||
| g->generate (font, glyphNumber); | |||
| return g; | |||
| } | |||
| CachedGlyphType* findExistingGlyph (const Font& font, int glyphNumber) const | |||
| { | |||
| for (int i = 0; i < glyphs.size(); ++i) | |||
| { | |||
| CachedGlyphType* const g = glyphs.getUnchecked (i); | |||
| if (g->glyph == glyphNumber && g->font == font) | |||
| return g; | |||
| } | |||
| return nullptr; | |||
| } | |||
| CachedGlyphType* getGlyphForReuse() | |||
| { | |||
| if (hits.value + misses.value > glyphs.size() * 16) | |||
| { | |||
| if (misses.value * 2 > hits.value) | |||
| addNewGlyphSlots (32); | |||
| hits.set (0); | |||
| misses.set (0); | |||
| return glyphs.getLast(); | |||
| } | |||
| return findLeastRecentlyUsedGlyph(); | |||
| } | |||
| void addNewGlyphSlots (int num) | |||
| { | |||
| @@ -240,7 +248,8 @@ private: | |||
| { | |||
| CachedGlyphType* const glyph = glyphs.getUnchecked(i); | |||
| if (glyph->lastAccessCount <= oldestCounter) | |||
| if (glyph->lastAccessCount <= oldestCounter | |||
| && glyph->getReferenceCount() == 1) | |||
| { | |||
| oldestCounter = glyph->lastAccessCount; | |||
| oldest = glyph; | |||
| @@ -262,7 +271,7 @@ private: | |||
| //============================================================================== | |||
| /** Caches a glyph as an edge-table. */ | |||
| template <class RendererType> | |||
| class CachedGlyphEdgeTable | |||
| class CachedGlyphEdgeTable : public ReferenceCountedObject | |||
| { | |||
| public: | |||
| CachedGlyphEdgeTable() : glyph (0), lastAccessCount (0) {} | |||
| @@ -120,7 +120,8 @@ namespace TTFNameExtractor | |||
| for (int i = 0; i < (int) ByteOrder::swapIfLittleEndian (offsetTable.numTables); ++i) | |||
| { | |||
| TableDirectory tableDirectory = { 0 }; | |||
| TableDirectory tableDirectory; | |||
| zerostruct (tableDirectory); | |||
| input.read (&tableDirectory, sizeof (tableDirectory)); | |||
| if (String (tableDirectory.tag, sizeof (tableDirectory.tag)).equalsIgnoreCase ("name")) | |||
| @@ -360,10 +360,12 @@ public: | |||
| { | |||
| virtual ~LookAndFeelMethods() {} | |||
| virtual void drawButtonBackground (Graphics&, Button& button, const Colour& backgroundColour, | |||
| virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour, | |||
| bool isMouseOverButton, bool isButtonDown) = 0; | |||
| virtual Font getTextButtonFont (TextButton& button) = 0; | |||
| virtual Font getTextButtonFont (TextButton&) = 0; | |||
| virtual void changeTextButtonWidthToFitText (TextButton&, int newHeight) = 0; | |||
| /** Draws the text for a TextButton. */ | |||
| virtual void drawButtonText (Graphics&, TextButton&, bool isMouseOverButton, bool isButtonDown) = 0; | |||
| @@ -74,7 +74,7 @@ Font HyperlinkButton::getFontToUse() const | |||
| void HyperlinkButton::changeWidthToFitText() | |||
| { | |||
| setSize (getFontToUse().getStringWidth (getName()) + 6, getHeight()); | |||
| setSize (getFontToUse().getStringWidth (getButtonText()) + 6, getHeight()); | |||
| } | |||
| void HyperlinkButton::colourChanged() | |||
| @@ -22,6 +22,10 @@ | |||
| ============================================================================== | |||
| */ | |||
| TextButton::TextButton() : Button (String()) | |||
| { | |||
| } | |||
| TextButton::TextButton (const String& name, const String& toolTip) | |||
| : Button (name) | |||
| { | |||
| @@ -32,21 +36,15 @@ TextButton::~TextButton() | |||
| { | |||
| } | |||
| void TextButton::paintButton (Graphics& g, | |||
| bool isMouseOverButton, | |||
| bool isButtonDown) | |||
| void TextButton::paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) | |||
| { | |||
| LookAndFeel& lf = getLookAndFeel(); | |||
| lf.drawButtonBackground (g, *this, | |||
| findColour (getToggleState() ? buttonOnColourId | |||
| : buttonColourId), | |||
| isMouseOverButton, | |||
| isButtonDown); | |||
| lf.drawButtonText (g, *this, | |||
| isMouseOverButton, | |||
| isButtonDown); | |||
| findColour (getToggleState() ? buttonOnColourId : buttonColourId), | |||
| isMouseOverButton, isButtonDown); | |||
| lf.drawButtonText (g, *this, isMouseOverButton, isButtonDown); | |||
| } | |||
| void TextButton::colourChanged() | |||
| @@ -61,9 +59,5 @@ Font TextButton::getFont() | |||
| void TextButton::changeWidthToFitText (const int newHeight) | |||
| { | |||
| if (newHeight >= 0) | |||
| setSize (jmax (1, getWidth()), newHeight); | |||
| setSize (getFont().getStringWidth (getButtonText()) + getHeight(), | |||
| getHeight()); | |||
| getLookAndFeel().changeTextButtonWidthToFitText (*this, newHeight); | |||
| } | |||
| @@ -37,17 +37,18 @@ class JUCE_API TextButton : public Button | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a TextButton. | |||
| /** Creates a TextButton. */ | |||
| TextButton(); | |||
| /** Creates a TextButton. | |||
| @param buttonName the text to put in the button (the component's name is also | |||
| initially set to this string, but these can be changed later | |||
| using the setName() and setButtonText() methods) | |||
| @param toolTip an optional string to use as a toolip | |||
| @see Button | |||
| */ | |||
| TextButton (const String& buttonName = String::empty, | |||
| const String& toolTip = String::empty); | |||
| explicit TextButton (const String& buttonName, | |||
| const String& toolTip = String::empty); | |||
| /** Destructor. */ | |||
| ~TextButton(); | |||
| @@ -74,19 +75,18 @@ public: | |||
| //============================================================================== | |||
| /** Resizes the button to fit neatly around its current text. | |||
| If newHeight is >= 0, the button's height will be changed to this | |||
| value. If it's less than zero, its height will be unaffected. | |||
| */ | |||
| void changeWidthToFitText (int newHeight = -1); | |||
| /** This can be overridden to use different fonts than the default one. | |||
| Note that you'll need to set the font's size appropriately, too. | |||
| */ | |||
| virtual Font getFont(); | |||
| protected: | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void paintButton (Graphics&, bool isMouseOverButton, bool isButtonDown) override; | |||
| /** @internal */ | |||
| @@ -69,13 +69,15 @@ public: | |||
| if (newState.width <= 0) newState.width = 100; | |||
| if (newState.height <= 0) newState.height = 100; | |||
| Point<float> viewboxXY; | |||
| if (xml->hasAttribute ("viewBox")) | |||
| { | |||
| const String viewBoxAtt (xml->getStringAttribute ("viewBox")); | |||
| String::CharPointerType viewParams (viewBoxAtt.getCharPointer()); | |||
| Point<float> vxy, vwh; | |||
| Point<float> vwh; | |||
| if (parseCoords (viewParams, vxy, true) | |||
| if (parseCoords (viewParams, viewboxXY, true) | |||
| && parseCoords (viewParams, vwh, true) | |||
| && vwh.x > 0 | |||
| && vwh.y > 0) | |||
| @@ -105,7 +107,7 @@ public: | |||
| } | |||
| newState.transform = RectanglePlacement (placementFlags) | |||
| .getTransformToFit (Rectangle<float> (vxy.x, vxy.y, vwh.x, vwh.y), | |||
| .getTransformToFit (Rectangle<float> (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y), | |||
| Rectangle<float> (newState.width, newState.height)) | |||
| .followedBy (newState.transform); | |||
| } | |||
| @@ -118,7 +120,10 @@ public: | |||
| newState.parseSubElements (xml, *drawable); | |||
| drawable->setContentArea (RelativeRectangle (Rectangle<float> (newState.viewBoxW, newState.viewBoxH))); | |||
| drawable->setContentArea (RelativeRectangle (RelativeCoordinate (viewboxXY.x), | |||
| RelativeCoordinate (viewboxXY.x + newState.viewBoxW), | |||
| RelativeCoordinate (viewboxXY.y), | |||
| RelativeCoordinate (viewboxXY.y + newState.viewBoxH))); | |||
| drawable->resetBoundingBoxToContentArea(); | |||
| return drawable; | |||
| @@ -243,6 +243,16 @@ Font LookAndFeel_V2::getTextButtonFont (TextButton& button) | |||
| return button.getFont(); | |||
| } | |||
| void LookAndFeel_V2::changeTextButtonWidthToFitText (TextButton& b, int newHeight) | |||
| { | |||
| if (newHeight >= 0) | |||
| b.setSize (jmax (1, b.getWidth()), newHeight); | |||
| else | |||
| newHeight = b.getHeight(); | |||
| b.setSize (getTextButtonFont (b).getStringWidth (b.getButtonText()) + newHeight, newHeight); | |||
| } | |||
| void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*isMouseOverButton*/, bool /*isButtonDown*/) | |||
| { | |||
| Font font (getTextButtonFont (button)); | |||
| @@ -46,6 +46,7 @@ public: | |||
| void drawButtonText (Graphics&, TextButton& button, | |||
| bool isMouseOverButton, bool isButtonDown) override; | |||
| void changeTextButtonWidthToFitText (TextButton&, int newHeight) override; | |||
| void drawToggleButton (Graphics&, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) override; | |||
| @@ -98,7 +98,6 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||
| args.add (startPath); | |||
| args.add (filters.replaceCharacter (';', ' ')); | |||
| args.add ("2>/dev/null"); | |||
| } | |||
| else | |||
| { | |||
| @@ -132,6 +131,8 @@ void FileChooser::showPlatformDialog (Array<File>& results, | |||
| args.add ("--filename=" + file.getFileName()); | |||
| } | |||
| args.add ("2>/dev/null"); | |||
| ChildProcess child; | |||
| if (child.start (args, ChildProcess::wantStdOut)) | |||
| @@ -141,7 +141,7 @@ public: | |||
| [window setHasShadow: ((windowStyleFlags & windowHasDropShadow) != 0)]; | |||
| if (component.isAlwaysOnTop()) | |||
| [window setLevel: NSFloatingWindowLevel]; | |||
| setAlwaysOnTop (true); | |||
| [window setContentView: view]; | |||
| [window setAutodisplay: YES]; | |||
| @@ -202,7 +202,9 @@ public: | |||
| { | |||
| if (shouldBeVisible) | |||
| { | |||
| ++insideToFrontCall; | |||
| [window orderFront: nil]; | |||
| --insideToFrontCall; | |||
| handleBroughtToFront(); | |||
| } | |||
| else | |||
| @@ -439,8 +441,10 @@ public: | |||
| bool setAlwaysOnTop (bool alwaysOnTop) override | |||
| { | |||
| if (! isSharedWindow) | |||
| [window setLevel: alwaysOnTop ? NSFloatingWindowLevel | |||
| [window setLevel: alwaysOnTop ? ((getStyleFlags() & windowIsTemporary) != 0 ? NSPopUpMenuWindowLevel | |||
| : NSFloatingWindowLevel) | |||
| : NSNormalWindowLevel]; | |||
| return true; | |||
| } | |||
| @@ -102,17 +102,38 @@ bool Desktop::canUseSemiTransparentWindows() noexcept | |||
| #endif | |||
| #ifndef MONITOR_DPI_TYPE | |||
| enum Monitor_DPI_Type | |||
| { | |||
| MDT_Effective_DPI = 0, | |||
| MDT_Angular_DPI = 1, | |||
| MDT_Raw_DPI = 2, | |||
| MDT_Default = MDT_Effective_DPI | |||
| }; | |||
| enum Process_DPI_Awareness | |||
| { | |||
| Process_DPI_Unaware = 0, | |||
| Process_System_DPI_Aware = 1, | |||
| Process_Per_Monitor_DPI_Aware = 2 | |||
| }; | |||
| #endif | |||
| typedef BOOL (WINAPI* RegisterTouchWindowFunc) (HWND, ULONG); | |||
| typedef BOOL (WINAPI* GetTouchInputInfoFunc) (HTOUCHINPUT, UINT, TOUCHINPUT*, int); | |||
| typedef BOOL (WINAPI* CloseTouchInputHandleFunc) (HTOUCHINPUT); | |||
| typedef BOOL (WINAPI* GetGestureInfoFunc) (HGESTUREINFO, GESTUREINFO*); | |||
| typedef BOOL (WINAPI* SetProcessDPIAwareFunc)(); | |||
| typedef BOOL (WINAPI* SetProcessDPIAwarenessFunc) (Process_DPI_Awareness); | |||
| typedef HRESULT (WINAPI* GetDPIForMonitorFunc) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*); | |||
| static RegisterTouchWindowFunc registerTouchWindow = nullptr; | |||
| static GetTouchInputInfoFunc getTouchInputInfo = nullptr; | |||
| static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; | |||
| static GetGestureInfoFunc getGestureInfo = nullptr; | |||
| static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; | |||
| static RegisterTouchWindowFunc registerTouchWindow = nullptr; | |||
| static GetTouchInputInfoFunc getTouchInputInfo = nullptr; | |||
| static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; | |||
| static GetGestureInfoFunc getGestureInfo = nullptr; | |||
| static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; | |||
| static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr; | |||
| static GetDPIForMonitorFunc getDPIForMonitor = nullptr; | |||
| static bool hasCheckedForMultiTouch = false; | |||
| @@ -158,17 +179,33 @@ static void setDPIAwareness() | |||
| { | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| { | |||
| if (setProcessDPIAware == nullptr) | |||
| if (setProcessDPIAwareness == nullptr) | |||
| { | |||
| setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); | |||
| HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll"); | |||
| if (shcoreModule != 0) | |||
| { | |||
| setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness"); | |||
| getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor"); | |||
| if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr | |||
| // && SUCCEEDED (setProcessDPIAwareness (Process_Per_Monitor_DPI_Aware))) | |||
| && SUCCEEDED (setProcessDPIAwareness (Process_System_DPI_Aware))) // (keep using this mode temporarily..) | |||
| return; | |||
| } | |||
| if (setProcessDPIAware != nullptr) | |||
| setProcessDPIAware(); | |||
| if (setProcessDPIAware == nullptr) | |||
| { | |||
| setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); | |||
| if (setProcessDPIAware != nullptr) | |||
| setProcessDPIAware(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| static double getDPI() | |||
| static double getGlobalDPI() | |||
| { | |||
| setDPIAwareness(); | |||
| @@ -181,7 +218,7 @@ static double getDPI() | |||
| double Desktop::getDefaultMasterScale() | |||
| { | |||
| return JUCEApplicationBase::isStandaloneApp() ? getDPI() / 96.0 | |||
| return JUCEApplicationBase::isStandaloneApp() ? getGlobalDPI() / 96.0 | |||
| : 1.0; | |||
| } | |||
| @@ -1076,6 +1113,20 @@ public: | |||
| JUCE_DECLARE_NON_COPYABLE (JuceDropTarget) | |||
| }; | |||
| #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client | |||
| static bool offerKeyMessageToJUCEWindow (MSG& m) | |||
| { | |||
| if (m.message == WM_KEYDOWN || m.message == WM_KEYUP) | |||
| if (Component::getCurrentlyFocusedComponent() != nullptr) | |||
| if (HWNDComponentPeer* h = getOwnerOfWindow (m.hwnd)) | |||
| if (m.message == WM_KEYDOWN ? h->doKeyDown (m.wParam) | |||
| : h->doKeyUp (m.wParam)) | |||
| return true; | |||
| return false; | |||
| } | |||
| #endif | |||
| private: | |||
| HWND hwnd, parentToAddTo; | |||
| ScopedPointer<DropShadower> shadower; | |||
| @@ -1973,28 +2024,25 @@ private: | |||
| used = handleKeyPress (extendedKeyModifier | (int) key, 0) || used; | |||
| break; | |||
| case VK_ADD: | |||
| case VK_SUBTRACT: | |||
| case VK_MULTIPLY: | |||
| case VK_DIVIDE: | |||
| case VK_SEPARATOR: | |||
| case VK_DECIMAL: | |||
| used = handleKeyUpOrDown (true); | |||
| break; | |||
| default: | |||
| used = handleKeyUpOrDown (true); | |||
| { | |||
| MSG msg; | |||
| if (! PeekMessage (&msg, hwnd, WM_CHAR, WM_DEADCHAR, PM_NOREMOVE)) | |||
| { | |||
| // if there isn't a WM_CHAR or WM_DEADCHAR message pending, we need to | |||
| // manually generate the key-press event that matches this key-down. | |||
| const UINT keyChar = MapVirtualKey ((UINT) key, 2); | |||
| const UINT scanCode = MapVirtualKey ((UINT) key, 0); | |||
| BYTE keyState[256]; | |||
| GetKeyboardState (keyState); | |||
| WCHAR text[16] = { 0 }; | |||
| if (ToUnicode ((UINT) key, scanCode, keyState, text, 8, 0) != 1) | |||
| text[0] = 0; | |||
| const UINT keyChar = MapVirtualKey ((UINT) key, 2); | |||
| used = handleKeyPress ((int) LOWORD (keyChar), 0) || used; | |||
| used = handleKeyPress ((int) LOWORD (keyChar), (juce_wchar) text[0]) || used; | |||
| } | |||
| } | |||
| @@ -2246,6 +2294,10 @@ private: | |||
| } | |||
| } | |||
| void handleDPIChange() // happens when a window moves to a screen with a different DPI. | |||
| { | |||
| } | |||
| //============================================================================== | |||
| public: | |||
| static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) | |||
| @@ -2488,6 +2540,10 @@ private: | |||
| doSettingChange(); | |||
| break; | |||
| case 0x2e0: // WM_DPICHANGED | |||
| handleDPIChange(); | |||
| break; | |||
| case WM_INITMENU: | |||
| initialiseSysMenu ((HMENU) wParam); | |||
| break; | |||
| @@ -2890,6 +2946,10 @@ bool KeyPress::isKeyCurrentlyDown (const int keyCode) | |||
| return HWNDComponentPeer::isKeyDown (k); | |||
| } | |||
| #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client | |||
| bool offerKeyMessageToJUCEWindow (MSG& m) { return HWNDComponentPeer::offerKeyMessageToJUCEWindow (m); } | |||
| #endif | |||
| //============================================================================== | |||
| bool JUCE_CALLTYPE Process::isForegroundProcess() | |||
| { | |||
| @@ -3183,9 +3243,11 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis | |||
| //============================================================================== | |||
| struct MonitorInfo | |||
| { | |||
| MonitorInfo (Rectangle<int> rect, bool main) noexcept : isMain (main), bounds (rect) {} | |||
| MonitorInfo (Rectangle<int> rect, bool main, double d) noexcept | |||
| : bounds (rect), dpi (d), isMain (main) {} | |||
| Rectangle<int> bounds; | |||
| double dpi; | |||
| bool isMain; | |||
| }; | |||
| @@ -3195,7 +3257,17 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userIn | |||
| info.cbSize = sizeof (info); | |||
| GetMonitorInfo (hm, &info); | |||
| const bool isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0; | |||
| ((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain)); | |||
| double dpi = 0; | |||
| if (getDPIForMonitor != nullptr) | |||
| { | |||
| UINT dpiX = 0, dpiY = 0; | |||
| if (SUCCEEDED (getDPIForMonitor (hm, MDT_Default, &dpiX, &dpiY))) | |||
| dpi = (dpiX + dpiY) / 2.0; | |||
| } | |||
| ((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain, dpi)); | |||
| return TRUE; | |||
| } | |||
| @@ -3207,8 +3279,10 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
| Array<MonitorInfo> monitors; | |||
| EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitors); | |||
| const double globalDPI = getGlobalDPI(); | |||
| if (monitors.size() == 0) | |||
| monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true)); | |||
| monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true, globalDPI)); | |||
| // make sure the first in the list is the main monitor | |||
| for (int i = 1; i < monitors.size(); ++i) | |||
| @@ -3218,15 +3292,22 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
| RECT workArea; | |||
| SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); | |||
| const double dpi = getDPI(); // (this has only one value for all monitors) | |||
| for (int i = 0; i < monitors.size(); ++i) | |||
| { | |||
| Display d; | |||
| d.userArea = d.totalArea = monitors.getReference(i).bounds / masterScale; | |||
| d.isMain = monitors.getReference(i).isMain; | |||
| d.scale = masterScale; | |||
| d.dpi = dpi; | |||
| d.dpi = monitors.getReference(i).dpi; | |||
| if (d.dpi == 0) | |||
| { | |||
| d.scale = masterScale; | |||
| d.dpi = globalDPI; | |||
| } | |||
| else | |||
| { | |||
| d.scale = d.dpi / 96.0; | |||
| } | |||
| if (d.isMain) | |||
| d.userArea = d.userArea.getIntersection (rectangleFromRECT (workArea) / masterScale); | |||
| @@ -892,7 +892,7 @@ public: | |||
| if (isAbsoluteDragMode (e.mods) || (maximum - minimum) / sliderRegionSize < interval) | |||
| { | |||
| dragMode = notDragging; | |||
| dragMode = absoluteDrag; | |||
| handleAbsoluteDrag (e); | |||
| } | |||
| else | |||
| @@ -23,7 +23,7 @@ | |||
| */ | |||
| CallOutBox::CallOutBox (Component& c, const Rectangle<int>& area, Component* const parent) | |||
| : borderSpace (20), arrowSize (16.0f), content (c) | |||
| : arrowSize (16.0f), content (c) | |||
| { | |||
| addAndMakeVisible (content); | |||
| @@ -86,10 +86,14 @@ CallOutBox& CallOutBox::launchAsynchronously (Component* content, const Rectangl | |||
| void CallOutBox::setArrowSize (const float newSize) | |||
| { | |||
| arrowSize = newSize; | |||
| borderSpace = jmax (20, (int) arrowSize); | |||
| refreshPath(); | |||
| } | |||
| int CallOutBox::getBorderSize() const noexcept | |||
| { | |||
| return jmax (20, (int) arrowSize); | |||
| } | |||
| void CallOutBox::paint (Graphics& g) | |||
| { | |||
| getLookAndFeel().drawCallOutBoxBackground (*this, g, outline, background); | |||
| @@ -97,6 +101,7 @@ void CallOutBox::paint (Graphics& g) | |||
| void CallOutBox::resized() | |||
| { | |||
| const int borderSpace = getBorderSize(); | |||
| content.setTopLeftPosition (borderSpace, borderSpace); | |||
| refreshPath(); | |||
| } | |||
| @@ -168,6 +173,8 @@ void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const R | |||
| targetArea = newAreaToPointTo; | |||
| availableArea = newAreaToFitIn; | |||
| const int borderSpace = getBorderSize(); | |||
| Rectangle<int> newBounds (content.getWidth() + borderSpace * 2, | |||
| content.getHeight() + borderSpace * 2); | |||
| @@ -149,10 +149,11 @@ public: | |||
| bool keyPressed (const KeyPress&) override; | |||
| /** @internal */ | |||
| void handleCommandMessage (int) override; | |||
| /** @internal */ | |||
| int getBorderSize() const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| int borderSpace; | |||
| float arrowSize; | |||
| Component& content; | |||
| Path outline; | |||
| @@ -41,17 +41,17 @@ void WebBrowserComponent::goToURL (const String& url, | |||
| { | |||
| lastURL = url; | |||
| lastHeaders.clear(); | |||
| if (headers != nullptr) | |||
| lastHeaders = *headers; | |||
| else | |||
| lastHeaders.clear(); | |||
| lastPostData.setSize (0); | |||
| if (postData != nullptr) | |||
| lastPostData = *postData; | |||
| else | |||
| lastPostData.reset(); | |||
| blankPageShown = false; | |||
| } | |||
| void WebBrowserComponent::stop() | |||
| @@ -46,17 +46,17 @@ void WebBrowserComponent::goToURL (const String& url, | |||
| { | |||
| lastURL = url; | |||
| lastHeaders.clear(); | |||
| if (headers != nullptr) | |||
| lastHeaders = *headers; | |||
| else | |||
| lastHeaders.clear(); | |||
| lastPostData.setSize (0); | |||
| if (postData != nullptr) | |||
| lastPostData = *postData; | |||
| else | |||
| lastPostData.reset(); | |||
| blankPageShown = false; | |||
| } | |||
| void WebBrowserComponent::stop() | |||
| @@ -265,13 +265,15 @@ void WebBrowserComponent::goToURL (const String& url, | |||
| { | |||
| lastURL = url; | |||
| lastHeaders.clear(); | |||
| if (headers != nullptr) | |||
| lastHeaders = *headers; | |||
| else | |||
| lastHeaders.clear(); | |||
| lastPostData.setSize (0); | |||
| if (postData != nullptr) | |||
| lastPostData = *postData; | |||
| else | |||
| lastPostData.reset(); | |||
| blankPageShown = false; | |||
| @@ -214,13 +214,15 @@ void WebBrowserComponent::goToURL (const String& url, | |||
| { | |||
| lastURL = url; | |||
| lastHeaders.clear(); | |||
| if (headers != nullptr) | |||
| lastHeaders = *headers; | |||
| else | |||
| lastHeaders.clear(); | |||
| lastPostData.setSize (0); | |||
| if (postData != nullptr) | |||
| lastPostData = *postData; | |||
| else | |||
| lastPostData.reset(); | |||
| blankPageShown = false; | |||