| @@ -29,17 +29,6 @@ namespace FloatVectorHelpers | |||
| #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| static bool sse2Present = false; | |||
| static bool isSSE2Available() noexcept | |||
| { | |||
| if (sse2Present) | |||
| return true; | |||
| sse2Present = SystemStats::hasSSE2(); | |||
| return sse2Present; | |||
| } | |||
| inline static bool isAligned (const void* p) noexcept | |||
| { | |||
| return (((pointer_sized_int) p) & 15) == 0; | |||
| @@ -113,7 +102,6 @@ namespace FloatVectorHelpers | |||
| #define JUCE_BEGIN_VEC_OP \ | |||
| typedef FloatVectorHelpers::ModeType<sizeof(*dest)>::Mode Mode; \ | |||
| if (FloatVectorHelpers::isSSE2Available()) \ | |||
| { \ | |||
| const int numLongOps = num / Mode::numParallel; | |||
| @@ -372,11 +360,7 @@ namespace FloatVectorHelpers | |||
| { | |||
| int numLongOps = num / Mode::numParallel; | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (numLongOps > 1 && isSSE2Available()) | |||
| #else | |||
| if (numLongOps > 1) | |||
| #endif | |||
| { | |||
| ParallelType val; | |||
| @@ -446,11 +430,7 @@ namespace FloatVectorHelpers | |||
| { | |||
| int numLongOps = num / Mode::numParallel; | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (numLongOps > 1 && isSSE2Available()) | |||
| #else | |||
| if (numLongOps > 1) | |||
| #endif | |||
| { | |||
| ParallelType mn, mx; | |||
| @@ -1002,12 +982,19 @@ double JUCE_CALLTYPE FloatVectorOperations::findMaximum (const double* src, int | |||
| void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnable) noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| if (FloatVectorHelpers::isSSE2Available()) | |||
| _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); | |||
| _MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); | |||
| #endif | |||
| ignoreUnused (shouldEnable); | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| const int mxcsr = _mm_getcsr(); | |||
| _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| @@ -198,6 +198,12 @@ public: | |||
| Effectively, this is a wrapper around a call to _MM_SET_FLUSH_ZERO_MODE | |||
| */ | |||
| static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; | |||
| /** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. | |||
| This effectively sets the DAZ and FZ bits of the MXCSR register. It's a convenient thing to | |||
| call before audio processing code where you really want to avoid denormalisation performance hits. | |||
| */ | |||
| static void JUCE_CALLTYPE disableDenormalisedNumberSupport() noexcept; | |||
| }; | |||
| @@ -0,0 +1,63 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| struct CatmullRomAlgorithm | |||
| { | |||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||
| { | |||
| const float y0 = inputs[3]; | |||
| const float y1 = inputs[2]; | |||
| const float y2 = inputs[1]; | |||
| const float y3 = inputs[0]; | |||
| const float halfY0 = 0.5f * y0; | |||
| const float halfY3 = 0.5f * y3; | |||
| return y1 + offset * ((0.5f * y2 - halfY0) | |||
| + (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) | |||
| + (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); | |||
| } | |||
| }; | |||
| CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } | |||
| CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} | |||
| void CatmullRomInterpolator::reset() noexcept | |||
| { | |||
| subSamplePos = 1.0; | |||
| for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) | |||
| lastInputSamples[i] = 0; | |||
| } | |||
| int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept | |||
| { | |||
| return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); | |||
| } | |||
| int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept | |||
| { | |||
| return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); | |||
| } | |||
| @@ -0,0 +1,89 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| /** | |||
| Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | |||
| Note that the resampler is stateful, so when there's a break in the continuity | |||
| of the input stream you're feeding it, you should call reset() before feeding | |||
| it any new data. And like with any other stateful filter, if you're resampling | |||
| multiple channels, make sure each one uses its own CatmullRomInterpolator | |||
| object. | |||
| @see LagrangeInterpolator | |||
| */ | |||
| class JUCE_API CatmullRomInterpolator | |||
| { | |||
| public: | |||
| CatmullRomInterpolator() noexcept; | |||
| ~CatmullRomInterpolator() noexcept; | |||
| /** Resets the state of the interpolator. | |||
| Call this when there's a break in the continuity of the input data stream. | |||
| */ | |||
| void reset() noexcept; | |||
| /** Resamples a stream of samples. | |||
| @param speedRatio the number of input samples to use for each output sample | |||
| @param inputSamples the source data to read from. This must contain at | |||
| least (speedRatio * numOutputSamplesToProduce) samples. | |||
| @param outputSamples the buffer to write the results into | |||
| @param numOutputSamplesToProduce the number of output samples that should be created | |||
| @returns the actual number of input samples that were used | |||
| */ | |||
| int process (double speedRatio, | |||
| const float* inputSamples, | |||
| float* outputSamples, | |||
| int numOutputSamplesToProduce) noexcept; | |||
| /** Resamples a stream of samples, adding the results to the output data | |||
| with a gain. | |||
| @param speedRatio the number of input samples to use for each output sample | |||
| @param inputSamples the source data to read from. This must contain at | |||
| least (speedRatio * numOutputSamplesToProduce) samples. | |||
| @param outputSamples the buffer to write the results to - the result values will be added | |||
| to any pre-existing data in this buffer after being multiplied by | |||
| the gain factor | |||
| @param numOutputSamplesToProduce the number of output samples that should be created | |||
| @param gain a gain factor to multiply the resulting samples by before | |||
| adding them to the destination buffer | |||
| @returns the actual number of input samples that were used | |||
| */ | |||
| int processAdding (double speedRatio, | |||
| const float* inputSamples, | |||
| float* outputSamples, | |||
| int numOutputSamplesToProduce, | |||
| float gain) noexcept; | |||
| private: | |||
| float lastInputSamples[5]; | |||
| double subSamplePos; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator) | |||
| }; | |||
| @@ -22,185 +22,179 @@ | |||
| ============================================================================== | |||
| */ | |||
| namespace LagrangeHelpers | |||
| namespace | |||
| { | |||
| template <int k> | |||
| struct ResampleHelper | |||
| { | |||
| static forcedinline void calc (float& a, float b) { a *= b * (1.0f / k); } | |||
| }; | |||
| template<> | |||
| struct ResampleHelper <0> | |||
| { | |||
| static forcedinline void calc (float&, float) {} | |||
| }; | |||
| template <int k> | |||
| static forcedinline float calcCoefficient (float input, const float offset) noexcept | |||
| static forcedinline void pushInterpolationSample (float* lastInputSamples, const float newValue) noexcept | |||
| { | |||
| ResampleHelper <0 - k>::calc (input, -2.0f - offset); | |||
| ResampleHelper <1 - k>::calc (input, -1.0f - offset); | |||
| ResampleHelper <2 - k>::calc (input, 0.0f - offset); | |||
| ResampleHelper <3 - k>::calc (input, 1.0f - offset); | |||
| ResampleHelper <4 - k>::calc (input, 2.0f - offset); | |||
| return input; | |||
| lastInputSamples[4] = lastInputSamples[3]; | |||
| lastInputSamples[3] = lastInputSamples[2]; | |||
| lastInputSamples[2] = lastInputSamples[1]; | |||
| lastInputSamples[1] = lastInputSamples[0]; | |||
| lastInputSamples[0] = newValue; | |||
| } | |||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||
| { | |||
| return calcCoefficient<0> (inputs[4], offset) | |||
| + calcCoefficient<1> (inputs[3], offset) | |||
| + calcCoefficient<2> (inputs[2], offset) | |||
| + calcCoefficient<3> (inputs[1], offset) | |||
| + calcCoefficient<4> (inputs[0], offset); | |||
| } | |||
| static forcedinline void push (float* inputs, const float newValue) noexcept | |||
| { | |||
| inputs[4] = inputs[3]; | |||
| inputs[3] = inputs[2]; | |||
| inputs[2] = inputs[1]; | |||
| inputs[1] = inputs[0]; | |||
| inputs[0] = newValue; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| LagrangeInterpolator::LagrangeInterpolator() { reset(); } | |||
| LagrangeInterpolator::~LagrangeInterpolator() {} | |||
| void LagrangeInterpolator::reset() noexcept | |||
| { | |||
| subSamplePos = 1.0; | |||
| for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) | |||
| lastInputSamples[i] = 0; | |||
| } | |||
| int LagrangeInterpolator::process (const double actualRatio, const float* in, | |||
| float* out, const int numOut) noexcept | |||
| { | |||
| if (actualRatio == 1.0) | |||
| static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept | |||
| { | |||
| memcpy (out, in, (size_t) numOut * sizeof (float)); | |||
| if (numOut >= 4) | |||
| if (numOut >= 5) | |||
| { | |||
| const float* end = in + numOut; | |||
| for (int i = 0; i < 4; ++i) | |||
| lastInputSamples[i] = *--end; | |||
| for (int i = 0; i < 5; ++i) | |||
| lastInputSamples[i] = input[--numOut]; | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numOut; ++i) | |||
| LagrangeHelpers::push (lastInputSamples, in[i]); | |||
| pushInterpolationSample (lastInputSamples, input[i]); | |||
| } | |||
| return numOut; | |||
| } | |||
| const float* const originalIn = in; | |||
| double pos = subSamplePos; | |||
| if (actualRatio < 1.0) | |||
| template <typename InterpolatorType> | |||
| static int interpolate (float* lastInputSamples, double& subSamplePos, const double actualRatio, | |||
| const float* in, float* out, const int numOut) noexcept | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| if (actualRatio == 1.0) | |||
| { | |||
| if (pos >= 1.0) | |||
| memcpy (out, in, (size_t) numOut * sizeof (float)); | |||
| pushInterpolationSamples (lastInputSamples, in, numOut); | |||
| return numOut; | |||
| } | |||
| const float* const originalIn = in; | |||
| double pos = subSamplePos; | |||
| if (actualRatio < 1.0) | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||
| pos -= 1.0; | |||
| if (pos >= 1.0) | |||
| { | |||
| pushInterpolationSample (lastInputSamples, *in++); | |||
| pos -= 1.0; | |||
| } | |||
| *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); | |||
| pos += actualRatio; | |||
| } | |||
| *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); | |||
| pos += actualRatio; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| else | |||
| { | |||
| while (pos < actualRatio) | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||
| pos += 1.0; | |||
| while (pos < actualRatio) | |||
| { | |||
| pushInterpolationSample (lastInputSamples, *in++); | |||
| pos += 1.0; | |||
| } | |||
| pos -= actualRatio; | |||
| *out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); | |||
| } | |||
| pos -= actualRatio; | |||
| *out++ = LagrangeHelpers::valueAtOffset (lastInputSamples, 1.0f - (float) pos); | |||
| } | |||
| } | |||
| subSamplePos = pos; | |||
| return (int) (in - originalIn); | |||
| } | |||
| subSamplePos = pos; | |||
| return (int) (in - originalIn); | |||
| } | |||
| int LagrangeInterpolator::processAdding (const double actualRatio, const float* in, | |||
| float* out, const int numOut, const float gain) noexcept | |||
| { | |||
| if (actualRatio == 1.0) | |||
| template <typename InterpolatorType> | |||
| static int interpolateAdding (float* lastInputSamples, double& subSamplePos, const double actualRatio, | |||
| const float* in, float* out, const int numOut, const float gain) noexcept | |||
| { | |||
| if (gain != 1.0f) | |||
| { | |||
| for (int i = 0; i < numOut; ++i) | |||
| out[i] += in[i] * gain; | |||
| } | |||
| else | |||
| if (actualRatio == 1.0) | |||
| { | |||
| for (int i = 0; i < numOut; ++i) | |||
| out[i] += in[i]; | |||
| FloatVectorOperations::addWithMultiply (out, in, gain, numOut); | |||
| pushInterpolationSamples (lastInputSamples, in, numOut); | |||
| return numOut; | |||
| } | |||
| if (numOut >= 4) | |||
| { | |||
| const float* end = in + numOut; | |||
| const float* const originalIn = in; | |||
| double pos = subSamplePos; | |||
| for (int i = 0; i < 4; ++i) | |||
| lastInputSamples[i] = *--end; | |||
| if (actualRatio < 1.0) | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| if (pos >= 1.0) | |||
| { | |||
| pushInterpolationSample (lastInputSamples, *in++); | |||
| pos -= 1.0; | |||
| } | |||
| *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); | |||
| pos += actualRatio; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for (int i = 0; i < numOut; ++i) | |||
| LagrangeHelpers::push (lastInputSamples, in[i]); | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| while (pos < actualRatio) | |||
| { | |||
| pushInterpolationSample (lastInputSamples, *in++); | |||
| pos += 1.0; | |||
| } | |||
| pos -= actualRatio; | |||
| *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); | |||
| } | |||
| } | |||
| return numOut; | |||
| subSamplePos = pos; | |||
| return (int) (in - originalIn); | |||
| } | |||
| } | |||
| const float* const originalIn = in; | |||
| double pos = subSamplePos; | |||
| //============================================================================== | |||
| template <int k> | |||
| struct LagrangeResampleHelper | |||
| { | |||
| static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } | |||
| }; | |||
| if (actualRatio < 1.0) | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| if (pos >= 1.0) | |||
| { | |||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||
| pos -= 1.0; | |||
| } | |||
| template<> | |||
| struct LagrangeResampleHelper<0> | |||
| { | |||
| static forcedinline void calc (float&, float) noexcept {} | |||
| }; | |||
| *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, (float) pos); | |||
| pos += actualRatio; | |||
| } | |||
| } | |||
| else | |||
| struct LagrangeAlgorithm | |||
| { | |||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||
| { | |||
| for (int i = numOut; --i >= 0;) | |||
| { | |||
| while (pos < actualRatio) | |||
| { | |||
| LagrangeHelpers::push (lastInputSamples, *in++); | |||
| pos += 1.0; | |||
| } | |||
| return calcCoefficient<0> (inputs[4], offset) | |||
| + calcCoefficient<1> (inputs[3], offset) | |||
| + calcCoefficient<2> (inputs[2], offset) | |||
| + calcCoefficient<3> (inputs[1], offset) | |||
| + calcCoefficient<4> (inputs[0], offset); | |||
| } | |||
| pos -= actualRatio; | |||
| *out++ += gain * LagrangeHelpers::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); | |||
| } | |||
| template <int k> | |||
| static forcedinline float calcCoefficient (float input, const float offset) noexcept | |||
| { | |||
| LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); | |||
| LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); | |||
| LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); | |||
| LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); | |||
| LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); | |||
| return input; | |||
| } | |||
| }; | |||
| LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } | |||
| LagrangeInterpolator::~LagrangeInterpolator() noexcept {} | |||
| subSamplePos = pos; | |||
| return (int) (in - originalIn); | |||
| void LagrangeInterpolator::reset() noexcept | |||
| { | |||
| subSamplePos = 1.0; | |||
| for (int i = 0; i < numElementsInArray (lastInputSamples); ++i) | |||
| lastInputSamples[i] = 0; | |||
| } | |||
| int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept | |||
| { | |||
| return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); | |||
| } | |||
| int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept | |||
| { | |||
| return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); | |||
| } | |||
| @@ -22,11 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED | |||
| #define JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | |||
| @@ -35,12 +31,14 @@ | |||
| it any new data. And like with any other stateful filter, if you're resampling | |||
| multiple channels, make sure each one uses its own LagrangeInterpolator | |||
| object. | |||
| @see CatmullRomInterpolator | |||
| */ | |||
| class JUCE_API LagrangeInterpolator | |||
| { | |||
| public: | |||
| LagrangeInterpolator(); | |||
| ~LagrangeInterpolator(); | |||
| LagrangeInterpolator() noexcept; | |||
| ~LagrangeInterpolator() noexcept; | |||
| /** Resets the state of the interpolator. | |||
| Call this when there's a break in the continuity of the input data stream. | |||
| @@ -89,6 +87,3 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) | |||
| }; | |||
| #endif // JUCE_LAGRANGEINTERPOLATOR_H_INCLUDED | |||
| @@ -49,7 +49,7 @@ public: | |||
| { | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Reset to a new sample rate and ramp length. */ | |||
| void reset (double sampleRate, double rampLengthInSeconds) noexcept | |||
| { | |||
| @@ -59,7 +59,7 @@ public: | |||
| countdown = 0; | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Set a new target value. */ | |||
| void setValue (FloatType newValue) noexcept | |||
| { | |||
| @@ -75,7 +75,7 @@ public: | |||
| } | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Compute the next value. */ | |||
| FloatType getNextValue() noexcept | |||
| { | |||
| @@ -88,7 +88,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| FloatType currentValue, target, step; | |||
| int countdown, stepsToTarget; | |||
| }; | |||
| @@ -58,9 +58,7 @@ | |||
| #endif | |||
| #if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #include <Accelerate/Accelerate.h> | |||
| #undef Point | |||
| #else | |||
| #undef JUCE_USE_VDSP_FRAMEWORK | |||
| #endif | |||
| @@ -81,6 +79,7 @@ namespace juce | |||
| #include "effects/juce_IIRFilter.cpp" | |||
| #include "effects/juce_IIRFilterOld.cpp" | |||
| #include "effects/juce_LagrangeInterpolator.cpp" | |||
| #include "effects/juce_CatmullRomInterpolator.cpp" | |||
| #include "effects/juce_FFT.cpp" | |||
| #include "midi/juce_MidiBuffer.cpp" | |||
| #include "midi/juce_MidiFile.cpp" | |||
| @@ -27,7 +27,7 @@ | |||
| #include "../juce_core/juce_core.h" | |||
| //============================================================================= | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -41,6 +41,7 @@ namespace juce | |||
| #include "effects/juce_IIRFilter.h" | |||
| #include "effects/juce_IIRFilterOld.h" | |||
| #include "effects/juce_LagrangeInterpolator.h" | |||
| #include "effects/juce_CatmullRomInterpolator.h" | |||
| #include "effects/juce_FFT.h" | |||
| #include "effects/juce_LinearSmoothedValue.h" | |||
| #include "effects/juce_Reverb.h" | |||
| @@ -371,7 +371,7 @@ int MidiMessage::getNoteNumber() const noexcept | |||
| void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept | |||
| { | |||
| if (isNoteOnOrOff()) | |||
| if (isNoteOnOrOff() || isAftertouch()) | |||
| getData()[1] = (uint8) (newNoteNumber & 127); | |||
| } | |||
| @@ -156,23 +156,34 @@ struct MidiMessageSequenceSorter | |||
| } | |||
| }; | |||
| void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) | |||
| { | |||
| for (int i = 0; i < other.list.size(); ++i) | |||
| { | |||
| const MidiMessage& m = other.list.getUnchecked(i)->message; | |||
| MidiEventHolder* const newOne = new MidiEventHolder (m); | |||
| newOne->message.addToTimeStamp (timeAdjustment); | |||
| list.add (newOne); | |||
| } | |||
| sort(); | |||
| } | |||
| void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||
| double timeAdjustment, | |||
| double firstAllowableTime, | |||
| double endOfAllowableDestTimes) | |||
| { | |||
| firstAllowableTime -= timeAdjustment; | |||
| endOfAllowableDestTimes -= timeAdjustment; | |||
| for (int i = 0; i < other.list.size(); ++i) | |||
| { | |||
| const MidiMessage& m = other.list.getUnchecked(i)->message; | |||
| const double t = m.getTimeStamp(); | |||
| const double t = m.getTimeStamp() + timeAdjustment; | |||
| if (t >= firstAllowableTime && t < endOfAllowableDestTimes) | |||
| { | |||
| MidiEventHolder* const newOne = new MidiEventHolder (m); | |||
| newOne->message.setTimeStamp (timeAdjustment + t); | |||
| newOne->message.setTimeStamp (t); | |||
| list.add (newOne); | |||
| } | |||
| @@ -160,7 +160,6 @@ public: | |||
| void deleteEvent (int index, bool deleteMatchingNoteUp); | |||
| /** Merges another sequence into this one. | |||
| Remember to call updateMatchedPairs() after using this method. | |||
| @param other the sequence to add from | |||
| @@ -178,6 +177,16 @@ public: | |||
| double firstAllowableDestTime, | |||
| double endOfAllowableDestTimes); | |||
| /** Merges another sequence into this one. | |||
| Remember to call updateMatchedPairs() after using this method. | |||
| @param other the sequence to add from | |||
| @param timeAdjustmentDelta an amount to add to the timestamps of the midi events | |||
| as they are read from the other sequence | |||
| */ | |||
| void addSequence (const MidiMessageSequence& other, | |||
| double timeAdjustmentDelta); | |||
| //============================================================================== | |||
| /** Makes sure all the note-on and note-off pairs are up-to-date. | |||
| @@ -331,7 +331,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectContainsRPN (const MidiBuffer& midiBuffer, | |||
| int channel, | |||
| int parameterNumber, | |||
| @@ -343,7 +343,7 @@ private: | |||
| expectContainsRPN (midiBuffer, expected); | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | |||
| { | |||
| MidiBuffer::Iterator iter (midiBuffer); | |||
| @@ -26,7 +26,7 @@ | |||
| #define JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | |||
| parameter number) message. | |||
| */ | |||
| @@ -77,7 +77,7 @@ public: | |||
| */ | |||
| void reset() noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Takes the next in a stream of incoming MIDI CC messages and returns true | |||
| if it forms the last of a sequence that makes an RPN or NPRN. | |||
| @@ -91,7 +91,7 @@ public: | |||
| MidiRPNMessage& result) noexcept; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| struct ChannelState | |||
| { | |||
| ChannelState() noexcept; | |||
| @@ -104,7 +104,7 @@ private: | |||
| bool isNRPN; | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| ChannelState states[16]; | |||
| JUCE_LEAK_DETECTOR (MidiRPNDetector) | |||
| @@ -120,11 +120,11 @@ private: | |||
| class JUCE_API MidiRPNGenerator | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Generates a MIDI sequence representing the given RPN or NRPN message. */ | |||
| static MidiBuffer generate (MidiRPNMessage message); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Generates a MIDI sequence representing an RPN or NRPN message with the | |||
| given parameters. | |||
| @@ -135,14 +135,6 @@ void MPEInstrument::removeListener (Listener* const listenerToRemove) noexcept | |||
| listeners.remove (listenerToRemove); | |||
| } | |||
| MPEInstrument::Listener::Listener() | |||
| { | |||
| } | |||
| MPEInstrument::Listener::~Listener() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| void MPEInstrument::processNextMidiEvent (const MidiMessage& message) | |||
| { | |||
| @@ -1976,7 +1968,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /* This mock class is used for unit testing whether the methods of | |||
| MPEInstrument are called correctly. | |||
| */ | |||
| @@ -2074,7 +2066,7 @@ private: | |||
| ScopedPointer<MPENote> lastNoteFinished; | |||
| private: | |||
| //====================================================================== | |||
| //============================================================================== | |||
| void noteAdded (MPENote) override { noteAddedCallCounter++; } | |||
| void notePressureChanged (MPENote) override { notePressureChangedCallCounter++; } | |||
| @@ -2089,7 +2081,7 @@ private: | |||
| } | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| template <int initial7BitPressure, int initial14BitPitchbend, int initial7BitTimbre> | |||
| class CustomInitialValuesTest : public MPEInstrument | |||
| { | |||
| @@ -2109,7 +2101,7 @@ private: | |||
| } | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectNote (MPENote noteToTest, | |||
| int noteOnVelocity7Bit, | |||
| int pressure7Bit, | |||
| @@ -2141,7 +2133,7 @@ private: | |||
| expect (std::fabs (expected - actual) < maxAbsoluteError); | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| MPEZoneLayout testLayout; | |||
| }; | |||
| @@ -68,7 +68,7 @@ public: | |||
| /** Destructor. */ | |||
| virtual ~MPEInstrument(); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Returns the current zone layout of the instrument. | |||
| This happens by value, to enforce thread-safety and class invariants. | |||
| @@ -98,7 +98,7 @@ public: | |||
| */ | |||
| bool isMasterChannel (int midiChannel) const noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** The MPE note tracking mode. In case there is more than one note playing | |||
| simultaneously on the same MIDI channel, this determines which of these | |||
| notes will be modulated by an incoming MPE message on that channel | |||
| @@ -123,7 +123,7 @@ public: | |||
| /** Set the MPE tracking mode for the timbre dimension. */ | |||
| void setTimbreTrackingMode (TrackingMode modeToUse); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Process a MIDI message and trigger the appropriate method calls | |||
| (noteOn, noteOff etc.) | |||
| @@ -132,7 +132,7 @@ public: | |||
| */ | |||
| virtual void processNextMidiEvent (const MidiMessage& message); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Request a note-on on the given channel, with the given initial note | |||
| number and velocity. | |||
| If the message arrives on a valid note channel, this will create a | |||
| @@ -187,7 +187,7 @@ public: | |||
| */ | |||
| void releaseAllNotes(); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Returns the number of MPE notes currently played by the | |||
| instrument. | |||
| */ | |||
| @@ -221,7 +221,7 @@ public: | |||
| */ | |||
| MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Derive from this class to be informed about any changes in the expressive | |||
| MIDI notes played by this instrument. | |||
| @@ -230,14 +230,11 @@ public: | |||
| Therefore you should never do heavy work such as graphics rendering etc. | |||
| inside those callbacks. | |||
| */ | |||
| class Listener | |||
| class JUCE_API Listener | |||
| { | |||
| public: | |||
| /** Constructor. */ | |||
| Listener(); | |||
| /** Destructor. */ | |||
| virtual ~Listener(); | |||
| virtual ~Listener() {} | |||
| /** Implement this callback to be informed whenever a new expressive | |||
| MIDI note is triggered. | |||
| @@ -278,14 +275,14 @@ public: | |||
| virtual void noteReleased (MPENote finishedNote) = 0; | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Adds a listener. */ | |||
| void addListener (Listener* const listenerToAdd) noexcept; | |||
| void addListener (Listener* listenerToAdd) noexcept; | |||
| /** Removes a listener. */ | |||
| void removeListener (Listener* const listenerToRemove) noexcept; | |||
| void removeListener (Listener* listenerToRemove) noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Puts the instrument into legacy mode. | |||
| As a side effect, this will discard all currently playing notes, | |||
| and call noteReleased for all of them. | |||
| @@ -324,7 +321,7 @@ public: | |||
| void setLegacyModePitchbendRange (int pitchbendRange); | |||
| protected: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** This method defines what initial pitchbend value should be used for newly | |||
| triggered notes. The default is to use the last pitchbend value | |||
| that has been received on the same MIDI channel (or no pitchbend | |||
| @@ -354,7 +351,7 @@ protected: | |||
| MPEValue midiNoteOnVelocity) const; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| CriticalSection lock; | |||
| Array<MPENote> notes; | |||
| MPEZoneLayout zoneLayout; | |||
| @@ -162,7 +162,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) | |||
| { | |||
| uint8 actualBytes[128] = { 0 }; | |||
| @@ -171,7 +171,7 @@ private: | |||
| expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) | |||
| { | |||
| std::size_t pos = 0; | |||
| @@ -103,7 +103,7 @@ class MPENoteTests : public UnitTest | |||
| public: | |||
| MPENoteTests() : UnitTest ("MPENote class") {} | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void runTest() override | |||
| { | |||
| beginTest ("getFrequencyInHertz"); | |||
| @@ -116,7 +116,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectEqualsWithinOneCent (double frequencyInHertzActual, | |||
| double frequencyInHertzExpected) | |||
| { | |||
| @@ -39,7 +39,7 @@ | |||
| */ | |||
| struct JUCE_API MPENote | |||
| { | |||
| //========================================================================== | |||
| //============================================================================== | |||
| enum KeyState | |||
| { | |||
| off = 0, | |||
| @@ -48,7 +48,7 @@ struct JUCE_API MPENote | |||
| keyDownAndSustained = 3 | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Constructor. | |||
| @param midiChannel The MIDI channel of the note, between 2 and 16. | |||
| @@ -88,7 +88,7 @@ struct JUCE_API MPENote | |||
| /** Checks whether the MPE note is valid. */ | |||
| bool isValid() const noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| // Invariants that define the note. | |||
| /** A unique ID. Useful to distinguish the note from other simultaneously | |||
| @@ -107,7 +107,7 @@ struct JUCE_API MPENote | |||
| */ | |||
| uint8 initialNote; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| // The five dimensions of continuous expressive control | |||
| /** The velocity ("strike") of the note-on. | |||
| @@ -146,7 +146,7 @@ struct JUCE_API MPENote | |||
| */ | |||
| MPEValue noteOffVelocity; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Current effective pitchbend of the note in units of semitones, relative | |||
| to initialNote. You should use this to compute the actual effective pitch | |||
| of the note. This value is computed and set by an MPEInstrument to the | |||
| @@ -163,7 +163,7 @@ struct JUCE_API MPENote | |||
| */ | |||
| KeyState keyState; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Returns the current frequency of the note in Hertz. This is the a sum of | |||
| the initialNote and the totalPitchbendInSemitones, converted to Hertz. | |||
| */ | |||
| @@ -55,7 +55,7 @@ | |||
| class JUCE_API MPESynthesiser : public MPESynthesiserBase | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Constructor. | |||
| You'll need to add some voices before it'll make any sound. | |||
| @@ -75,7 +75,7 @@ public: | |||
| /** Destructor. */ | |||
| ~MPESynthesiser(); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Deletes all voices. */ | |||
| void clearVoices(); | |||
| @@ -116,7 +116,7 @@ public: | |||
| */ | |||
| virtual void turnOffAllVoices (bool allowTailOff); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** If set to true, then the synth will try to take over an existing voice if | |||
| it runs out and needs to play another note. | |||
| @@ -128,7 +128,7 @@ public: | |||
| /** Returns true if note-stealing is enabled. */ | |||
| bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||
| This overrides the implementation in MPESynthesiserBase, to additionally | |||
| @@ -137,7 +137,7 @@ public: | |||
| */ | |||
| void setCurrentPlaybackSampleRate (double newRate) override; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Handle incoming MIDI events. | |||
| This method will be called automatically according to the MIDI data passed | |||
| @@ -238,7 +238,7 @@ protected: | |||
| */ | |||
| virtual void noteKeyStateChanged (MPENote changedNote) override; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** This will simply call renderNextBlock for each currently active | |||
| voice and fill the buffer with the sum. | |||
| Override this method if you need to do more work to render your audio. | |||
| @@ -255,7 +255,7 @@ protected: | |||
| int startSample, | |||
| int numSamples) override; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Searches through the voices to find one that's not currently playing, and | |||
| which can play the given MPE note. | |||
| @@ -298,11 +298,11 @@ protected: | |||
| */ | |||
| void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| OwnedArray<MPESynthesiserVoice> voices; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| bool shouldStealVoices; | |||
| CriticalSection voicesLock; | |||
| @@ -47,7 +47,7 @@ | |||
| struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Constructor. */ | |||
| MPESynthesiserBase(); | |||
| @@ -61,7 +61,7 @@ public: | |||
| */ | |||
| MPESynthesiserBase (MPEInstrument* instrument); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Returns the synthesiser's internal MPE zone layout. | |||
| This happens by value, to enforce thread-safety and class invariants. | |||
| */ | |||
| @@ -73,7 +73,7 @@ public: | |||
| */ | |||
| void setZoneLayout (MPEZoneLayout newLayout); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being | |||
| used to render. | |||
| */ | |||
| @@ -84,7 +84,7 @@ public: | |||
| */ | |||
| double getSampleRate() const noexcept { return sampleRate; } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Creates the next block of audio output. | |||
| Call this to make sound. This will chop up the AudioBuffer into subBlock | |||
| @@ -99,7 +99,7 @@ public: | |||
| int startSample, | |||
| int numSamples); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Handle incoming MIDI events (called from renderNextBlock). | |||
| The default implementation provided here simply forwards everything | |||
| @@ -113,7 +113,7 @@ public: | |||
| */ | |||
| virtual void handleMidiEvent (const MidiMessage&); | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||
| When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||
| @@ -130,7 +130,7 @@ public: | |||
| */ | |||
| void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Puts the synthesiser into legacy mode. | |||
| @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||
| @@ -160,7 +160,7 @@ public: | |||
| void setLegacyModePitchbendRange (int pitchbendRange); | |||
| protected: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Implement this method to render your audio inside. | |||
| @see renderNextBlock | |||
| */ | |||
| @@ -176,14 +176,14 @@ protected: | |||
| int /*numSamples*/) {} | |||
| protected: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** @internal */ | |||
| ScopedPointer<MPEInstrument> instrument; | |||
| /** @internal */ | |||
| CriticalSection renderAudioLock; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| double sampleRate; | |||
| int minimumSubBlockSize; | |||
| @@ -37,7 +37,7 @@ | |||
| class JUCE_API MPESynthesiserVoice | |||
| { | |||
| public: | |||
| //======================================================================== | |||
| //============================================================================== | |||
| /** Constructor. */ | |||
| MPESynthesiserVoice(); | |||
| @@ -160,7 +160,7 @@ public: | |||
| bool wasStartedBefore (const MPESynthesiserVoice& other) const noexcept; | |||
| protected: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Resets the state of this voice after a sound has finished playing. | |||
| The subclass must call this when it finishes playing a note and becomes available | |||
| @@ -175,12 +175,12 @@ protected: | |||
| */ | |||
| void clearCurrentNote() noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| double currentSampleRate; | |||
| MPENote currentlyPlayingNote; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| friend class MPESynthesiser; | |||
| uint32 noteStartTime; | |||
| @@ -144,7 +144,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectValuesConsistent (MPEValue value, | |||
| int expectedValueAs7BitInt, | |||
| int expectedValueAs14BitInt, | |||
| @@ -157,7 +157,7 @@ private: | |||
| expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f); | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError) | |||
| { | |||
| const float maxAbsoluteError = jmax (1.0f, std::fabs (expectedValue)) * maxRelativeError; | |||
| @@ -37,7 +37,7 @@ | |||
| class JUCE_API MPEValue | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Default constructor. Constructs an MPEValue corresponding | |||
| to the centre value. | |||
| */ | |||
| @@ -87,7 +87,7 @@ public: | |||
| bool operator!= (const MPEValue& other) const noexcept; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| MPEValue (int normalisedValue); | |||
| int normalisedValue; | |||
| }; | |||
| @@ -144,7 +144,7 @@ bool MPEZone::truncateToFit (MPEZone other) noexcept | |||
| return true; | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| bool MPEZone::operator== (const MPEZone& other) const noexcept | |||
| { | |||
| return masterChannel == other.masterChannel | |||
| @@ -284,7 +284,7 @@ public: | |||
| } | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst, | |||
| int masterChannelSecond, int numNoteChannelsSecond, | |||
| bool expectedRetVal) | |||
| @@ -296,7 +296,7 @@ private: | |||
| expect (second.overlapsWith (first) == expectedRetVal); | |||
| } | |||
| //========================================================================== | |||
| //============================================================================== | |||
| void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst, | |||
| int masterChannelSecond, int numNoteChannelsSecond, | |||
| bool expectedRetVal, | |||
| @@ -127,7 +127,7 @@ struct JUCE_API MPEZone | |||
| bool operator!= (const MPEZone& other) const noexcept; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| int masterChannel; | |||
| int numNoteChannels; | |||
| int perNotePitchbendRange; | |||
| @@ -197,14 +197,6 @@ void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept | |||
| listeners.remove (listenerToRemove); | |||
| } | |||
| MPEZoneLayout::Listener::Listener() | |||
| { | |||
| } | |||
| MPEZoneLayout::Listener::~Listener() | |||
| { | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| @@ -125,18 +125,15 @@ public: | |||
| */ | |||
| MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Listener class. Derive from this class to allow your class to be | |||
| notified about changes to the zone layout. | |||
| */ | |||
| class Listener | |||
| { | |||
| public: | |||
| /** Constructor. */ | |||
| Listener(); | |||
| /** Destructor. */ | |||
| virtual ~Listener(); | |||
| virtual ~Listener() {} | |||
| /** Implement this callback to be notified about any changes to this | |||
| MPEZoneLayout. Will be called whenever a zone is added, zones are | |||
| @@ -145,7 +142,7 @@ public: | |||
| virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; | |||
| }; | |||
| //========================================================================== | |||
| //============================================================================== | |||
| /** Adds a listener. */ | |||
| void addListener (Listener* const listenerToAdd) noexcept; | |||
| @@ -153,7 +150,7 @@ public: | |||
| void removeListener (Listener* const listenerToRemove) noexcept; | |||
| private: | |||
| //========================================================================== | |||
| //============================================================================== | |||
| Array<MPEZone> zones; | |||
| MidiRPNDetector rpnDetector; | |||
| ListenerList<Listener> listeners; | |||
| @@ -46,7 +46,7 @@ public: | |||
| @param source the input source to read from | |||
| @param backgroundThread a background thread that will be used for the | |||
| background read-ahead. This object must not be deleted | |||
| until after any BufferedAudioSources that are using it | |||
| until after any BufferingAudioSources that are using it | |||
| have been deleted! | |||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||
| be deleted when this object is deleted | |||
| @@ -993,9 +993,9 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
| class AudioSampleBufferSource : public PositionableAudioSource | |||
| { | |||
| public: | |||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer) | |||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) | |||
| : buffer (audioBuffer, ownBuffer), | |||
| position (0), looping (false) | |||
| position (0), looping (false), playAcrossAllChannels (playOnAllChannels) | |||
| {} | |||
| //============================================================================== | |||
| @@ -1029,8 +1029,11 @@ public: | |||
| if (samplesToCopy > 0) | |||
| { | |||
| const int maxInChannels = buffer->getNumChannels(); | |||
| const int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), jmax (maxInChannels, 2)); | |||
| int maxInChannels = buffer->getNumChannels(); | |||
| int maxOutChannels = bufferToFill.buffer->getNumChannels(); | |||
| if (! playAcrossAllChannels) | |||
| maxOutChannels = jmin (maxOutChannels, maxInChannels); | |||
| for (int i = 0; i < maxOutChannels; ++i) | |||
| bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||
| @@ -1047,7 +1050,7 @@ private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<AudioSampleBuffer> buffer; | |||
| int position; | |||
| bool looping; | |||
| bool looping, playAcrossAllChannels; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||
| }; | |||
| @@ -1080,10 +1083,10 @@ void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFi | |||
| playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
| } | |||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | |||
| { | |||
| if (buffer != nullptr) | |||
| playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished), true); | |||
| playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | |||
| } | |||
| void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||
| @@ -1134,7 +1137,7 @@ void AudioDeviceManager::playTestSound() | |||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
| playSound (newSound, true); | |||
| playSound (newSound, true, true); | |||
| } | |||
| //============================================================================== | |||
| @@ -445,8 +445,15 @@ public: | |||
| This will output the sound contained in an audio sample buffer. If | |||
| deleteWhenFinished is true then the audio sample buffer will be | |||
| automatically deleted once the sound has finished playing. | |||
| If playOnAllOutputChannels is true, then if there are more output channels | |||
| than buffer channels, then the ones that are available will be re-used on | |||
| multiple outputs so that something is sent to all output channels. If it | |||
| is false, then the buffer will just be played on the first output channels. | |||
| */ | |||
| void playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished = false); | |||
| void playSound (AudioSampleBuffer* buffer, | |||
| bool deleteWhenFinished = false, | |||
| bool playOnAllOutputChannels = false); | |||
| //============================================================================== | |||
| /** Turns on level-measuring. | |||
| @@ -31,7 +31,12 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 | |||
| #include "juce_audio_devices.h" | |||
| //============================================================================== | |||
| @@ -141,7 +146,6 @@ namespace juce | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||
| #include "native/juce_mac_CoreAudio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| @@ -160,8 +164,6 @@ namespace juce | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #include "../juce_core/native/juce_win32_ComSmartPtr.h" | |||
| #include "../juce_events/native/juce_win32_HiddenMessageWindow.h" | |||
| #if JUCE_WASAPI | |||
| #include "native/juce_win32_WASAPI.cpp" | |||
| @@ -203,7 +205,6 @@ namespace juce | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #include "../juce_core/native/juce_android_JNIHelpers.h" | |||
| #include "native/juce_android_Audio.cpp" | |||
| #include "native/juce_android_Midi.cpp" | |||
| @@ -29,7 +29,7 @@ | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| #include "../juce_audio_formats/juce_audio_formats.h" | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_ASIO | |||
| Enables ASIO audio devices (MS Windows only). | |||
| Turning this on means that you'll need to have the Steinberg ASIO SDK installed | |||
| @@ -90,7 +90,7 @@ | |||
| #endif | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_USE_CDREADER | |||
| Enables the AudioCDReader class (on supported platforms). | |||
| */ | |||
| @@ -105,7 +105,7 @@ | |||
| #define JUCE_USE_CDBURNER 0 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -195,25 +195,51 @@ public: | |||
| STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeOut * numDeviceOutputChannels * sizeof (int16)), MODE_STREAM)); | |||
| if (env->CallIntMethod (outputDevice, AudioTrack.getState) != STATE_UNINITIALIZED) | |||
| int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); | |||
| if (outputDeviceState > 0) | |||
| { | |||
| isRunning = true; | |||
| } | |||
| else | |||
| outputDevice.clear(); // failed to open the device | |||
| { | |||
| // failed to open the device | |||
| outputDevice.clear(); | |||
| lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); | |||
| } | |||
| } | |||
| if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) | |||
| { | |||
| numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||
| inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, | |||
| 0 /* (default audio source) */, sampleRate, | |||
| numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||
| ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); | |||
| if (env->CallIntMethod (inputDevice, AudioRecord.getState) != STATE_UNINITIALIZED) | |||
| isRunning = true; | |||
| if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) | |||
| { | |||
| // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio | |||
| // before trying to open an audio input device. This is not going to work! | |||
| jassertfalse; | |||
| inputDevice.clear(); | |||
| lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; | |||
| } | |||
| else | |||
| inputDevice.clear(); // failed to open the device | |||
| { | |||
| numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); | |||
| inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, | |||
| 0 /* (default audio source) */, sampleRate, | |||
| numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, | |||
| ENCODING_PCM_16BIT, | |||
| (jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16)))); | |||
| int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); | |||
| if (inputDeviceState > 0) | |||
| { | |||
| isRunning = true; | |||
| } | |||
| else | |||
| { | |||
| // failed to open the device | |||
| inputDevice.clear(); | |||
| lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); | |||
| } | |||
| } | |||
| } | |||
| if (isRunning) | |||
| @@ -368,7 +394,7 @@ public: | |||
| int minBufferSizeOut, minBufferSizeIn; | |||
| private: | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| CriticalSection callbackLock; | |||
| AudioIODeviceCallback* callback; | |||
| jint sampleRate; | |||
| @@ -145,12 +145,32 @@ public: | |||
| << ", sampleRate = " << sampleRate); | |||
| if (numInputChannels > 0) | |||
| recorder = engine.createRecorder (numInputChannels, sampleRate, | |||
| audioBuffersToEnqueue, actualBufferSize); | |||
| { | |||
| if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) | |||
| { | |||
| // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio | |||
| // before trying to open an audio input device. This is not going to work! | |||
| jassertfalse; | |||
| lastError = "Error opening OpenSL input device: the app was not granted android.permission.RECORD_AUDIO"; | |||
| } | |||
| else | |||
| { | |||
| recorder = engine.createRecorder (numInputChannels, sampleRate, | |||
| audioBuffersToEnqueue, actualBufferSize); | |||
| if (recorder == nullptr) | |||
| lastError = "Error opening OpenSL input device: creating Recorder failed."; | |||
| } | |||
| } | |||
| if (numOutputChannels > 0) | |||
| player = engine.createPlayer (numOutputChannels, sampleRate, | |||
| audioBuffersToEnqueue, actualBufferSize); | |||
| { | |||
| player = engine.createPlayer (numOutputChannels, sampleRate, | |||
| audioBuffersToEnqueue, actualBufferSize); | |||
| if (player == nullptr) | |||
| lastError = "Error opening OpenSL input device: creating Player failed."; | |||
| } | |||
| // pre-fill buffers | |||
| for (int i = 0; i < audioBuffersToEnqueue; ++i) | |||
| @@ -220,7 +240,7 @@ public: | |||
| } | |||
| private: | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| CriticalSection callbackLock; | |||
| AudioIODeviceCallback* callback; | |||
| int actualBufferSize, sampleRate; | |||
| @@ -242,7 +262,7 @@ private: | |||
| defaultBufferSizeIsMultipleOfNative = 1 | |||
| }; | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| static String audioManagerGetProperty (const String& property) | |||
| { | |||
| const LocalRef<jstring> jProperty (javaString (property)); | |||
| @@ -281,7 +301,7 @@ private: | |||
| return androidHasSystemFeature ("android.hardware.audio.low_latency"); | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| @@ -331,7 +351,7 @@ private: | |||
| DBG ("Unable to set audio thread priority: priority is still " << priority); | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| struct Engine | |||
| { | |||
| Engine() | |||
| @@ -400,7 +420,7 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Engine) | |||
| }; | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| struct BufferList | |||
| { | |||
| BufferList (const int numChannels_, const int numBuffers_, const int numSamples_) | |||
| @@ -444,7 +464,7 @@ private: | |||
| WaitableEvent dataArrived; | |||
| }; | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| struct Player | |||
| { | |||
| Player (int numChannels, int sampleRate, Engine& engine, int playerNumBuffers, int playerBufferSize) | |||
| @@ -559,7 +579,7 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) | |||
| }; | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| struct Recorder | |||
| { | |||
| Recorder (int numChannels, int sampleRate, Engine& engine, const int numBuffers, const int numSamples) | |||
| @@ -24,7 +24,9 @@ | |||
| class iOSAudioIODevice; | |||
| //================================================================================================== | |||
| static const char* const iOSAudioDeviceName = "iOS Audio"; | |||
| //============================================================================== | |||
| struct AudioSessionHolder | |||
| { | |||
| AudioSessionHolder(); | |||
| @@ -74,7 +76,7 @@ bool getNotificationValueForKey (NSNotification* notification, NSString* key, NS | |||
| } // juce namespace | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| @interface iOSAudioSessionNative : NSObject | |||
| { | |||
| @private | |||
| @@ -178,7 +180,7 @@ bool getNotificationValueForKey (NSNotification* notification, NSString* key, NS | |||
| @end | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| namespace juce { | |||
| #ifndef JUCE_IOS_AUDIO_LOGGING | |||
| @@ -203,11 +205,12 @@ static void logNSError (NSError* e) | |||
| #define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| class iOSAudioIODevice : public AudioIODevice | |||
| { | |||
| public: | |||
| iOSAudioIODevice (const String& deviceName) : AudioIODevice (deviceName, "Audio") | |||
| iOSAudioIODevice (const String& deviceName) | |||
| : AudioIODevice (deviceName, iOSAudioDeviceName) | |||
| { | |||
| sessionHolder->activeDevices.add (this); | |||
| updateSampleRateAndAudioInput(); | |||
| @@ -350,13 +353,14 @@ public: | |||
| { | |||
| isRunning = false; | |||
| setAudioSessionActive (false); | |||
| if (audioUnit != 0) | |||
| { | |||
| AudioOutputUnitStart (audioUnit); | |||
| AudioComponentInstanceDispose (audioUnit); | |||
| audioUnit = 0; | |||
| } | |||
| setAudioSessionActive (false); | |||
| } | |||
| } | |||
| @@ -463,13 +467,13 @@ public: | |||
| AudioOutputUnitStart (audioUnit); | |||
| } | |||
| if (callback) | |||
| if (callback != nullptr) | |||
| callback->audioDeviceAboutToStart (this); | |||
| } | |||
| } | |||
| private: | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| SharedResourcePointer<AudioSessionHolder> sessionHolder; | |||
| CriticalSection callbackLock; | |||
| NSTimeInterval sampleRate = 0; | |||
| @@ -505,7 +509,7 @@ private: | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||
| const UInt32 numFrames, AudioBufferList* data) | |||
| { | |||
| @@ -592,21 +596,22 @@ private: | |||
| auto session = [AVAudioSession sharedInstance]; | |||
| sampleRate = session.sampleRate; | |||
| audioInputIsAvailable = session.isInputAvailable; | |||
| JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate << "Hz, audioInputAvailable: " << (int) audioInputIsAvailable); | |||
| actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration); | |||
| JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate | |||
| << "Hz, audioInputAvailable: " << (int) audioInputIsAvailable); | |||
| } | |||
| void updateCurrentBufferSize() | |||
| { | |||
| auto session = [AVAudioSession sharedInstance]; | |||
| NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) (preferredBufferSize / sampleRate) : 0.0; | |||
| JUCE_NSERROR_CHECK ([session setPreferredIOBufferDuration: bufferDuration | |||
| error: &error]); | |||
| NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) ((preferredBufferSize + 1) / sampleRate) : 0.0; | |||
| bufferDuration = session.IOBufferDuration; | |||
| actualBufferSize = roundToInt (sampleRate * bufferDuration); | |||
| JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration | |||
| error: &error]); | |||
| updateSampleRateAndAudioInput(); | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||
| UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) | |||
| { | |||
| @@ -614,7 +619,7 @@ private: | |||
| return static_cast<iOSAudioIODevice*> (client)->process (flags, time, numFrames, data); | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| void resetFormat (const int numChannels) noexcept | |||
| { | |||
| zerostruct (format); | |||
| @@ -712,10 +717,10 @@ private: | |||
| class iOSAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| iOSAudioIODeviceType() : AudioIODeviceType ("iOS Audio") {} | |||
| iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {} | |||
| void scanForDevices() {} | |||
| StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray ("iOS Audio"); } | |||
| StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); } | |||
| int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; } | |||
| int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; } | |||
| bool hasSeparateInputsAndOutputs() const { return false; } | |||
| @@ -738,7 +743,7 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() | |||
| return new iOSAudioIODeviceType(); | |||
| } | |||
| //================================================================================================== | |||
| //============================================================================== | |||
| AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } | |||
| AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } | |||
| @@ -31,6 +31,12 @@ | |||
| #ifdef __clang__ | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wnonnull" // aovid some spurious 10.11 SDK warnings | |||
| // The AudioHardwareService stuff was deprecated in 10.11 but there's no replacement yet, | |||
| // so we'll have to silence the warnings here and revisit it in a future OS version.. | |||
| #if defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_11 | |||
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| #endif | |||
| #endif | |||
| //============================================================================== | |||
| @@ -382,65 +388,84 @@ public: | |||
| return 0; | |||
| } | |||
| void updateDetailsFromDevice() | |||
| int getFrameSizeFromDevice() const | |||
| { | |||
| stopTimer(); | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementMaster; | |||
| pa.mSelector = kAudioDevicePropertyBufferFrameSize; | |||
| if (deviceID == 0) | |||
| return; | |||
| UInt32 framesPerBuf = (UInt32) bufferSize; | |||
| UInt32 size = sizeof (framesPerBuf); | |||
| AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); | |||
| return (int) framesPerBuf; | |||
| } | |||
| // this collects all the new details from the device without any locking, then | |||
| // locks + swaps them afterwards. | |||
| bool isDeviceAlive() const | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mScope = kAudioObjectPropertyScopeWildcard; | |||
| pa.mElement = kAudioObjectPropertyElementMaster; | |||
| pa.mSelector = kAudioDevicePropertyDeviceIsAlive; | |||
| UInt32 isAlive; | |||
| UInt32 isAlive = 0; | |||
| UInt32 size = sizeof (isAlive); | |||
| pa.mSelector = kAudioDevicePropertyDeviceIsAlive; | |||
| if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) && isAlive == 0) | |||
| return; | |||
| return deviceID != 0 | |||
| && OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) | |||
| && isAlive != 0; | |||
| } | |||
| const double currentRate = getNominalSampleRate(); | |||
| if (currentRate > 0) | |||
| sampleRate = currentRate; | |||
| bool updateDetailsFromDevice() | |||
| { | |||
| stopTimer(); | |||
| UInt32 framesPerBuf = (UInt32) bufferSize; | |||
| size = sizeof (framesPerBuf); | |||
| pa.mSelector = kAudioDevicePropertyBufferFrameSize; | |||
| AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); | |||
| if (! isDeviceAlive()) | |||
| return false; | |||
| // this collects all the new details from the device without any locking, then | |||
| // locks + swaps them afterwards. | |||
| Array<int> newBufferSizes (getBufferSizesFromDevice()); | |||
| Array<double> newSampleRates (getSampleRatesFromDevice()); | |||
| const double newSampleRate = getNominalSampleRate(); | |||
| const int newBufferSize = getFrameSizeFromDevice(); | |||
| inputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); | |||
| outputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); | |||
| Array<int> newBufferSizes = getBufferSizesFromDevice(); | |||
| Array<double> newSampleRates = getSampleRatesFromDevice(); | |||
| const int newInputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); | |||
| const int newOutputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); | |||
| Array<CallbackDetailsForChannel> newInChans, newOutChans; | |||
| StringArray newInNames (getChannelInfo (true, newInChans)); | |||
| StringArray newOutNames (getChannelInfo (false, newOutChans)); | |||
| const int inputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeInput); | |||
| const int outputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeOutput); | |||
| const int newBitDepth = jmax (getBitDepthFromDevice (kAudioDevicePropertyScopeInput), | |||
| getBitDepthFromDevice (kAudioDevicePropertyScopeOutput)); | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| bitDepth = jmax (inputBitDepth, outputBitDepth); | |||
| if (bitDepth <= 0) | |||
| bitDepth = 32; | |||
| bitDepth = newBitDepth > 0 ? newBitDepth : 32; | |||
| // after getting the new values, lock + apply them | |||
| const ScopedLock sl (callbackLock); | |||
| if (newSampleRate > 0) | |||
| sampleRate = newSampleRate; | |||
| bufferSize = (int) framesPerBuf; | |||
| allocateTempBuffers(); | |||
| inputLatency = newInputLatency; | |||
| outputLatency = newOutputLatency; | |||
| bufferSize = newBufferSize; | |||
| sampleRates.swapWith (newSampleRates); | |||
| bufferSizes.swapWith (newBufferSizes); | |||
| sampleRates.swapWith (newSampleRates); | |||
| bufferSizes.swapWith (newBufferSizes); | |||
| inChanNames.swapWith (newInNames); | |||
| outChanNames.swapWith (newOutNames); | |||
| inChanNames.swapWith (newInNames); | |||
| outChanNames.swapWith (newOutNames); | |||
| inputChannelInfo.swapWith (newInChans); | |||
| outputChannelInfo.swapWith (newOutChans); | |||
| inputChannelInfo.swapWith (newInChans); | |||
| outputChannelInfo.swapWith (newOutChans); | |||
| allocateTempBuffers(); | |||
| } | |||
| return true; | |||
| } | |||
| //============================================================================== | |||
| @@ -763,9 +788,10 @@ public: | |||
| stopTimer(); | |||
| const double oldSampleRate = sampleRate; | |||
| const int oldBufferSize = bufferSize; | |||
| updateDetailsFromDevice(); | |||
| if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) | |||
| if (! updateDetailsFromDevice()) | |||
| owner.stop(); | |||
| else if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) | |||
| owner.restart(); | |||
| } | |||
| @@ -320,9 +320,9 @@ public: | |||
| classId (clsID), | |||
| inputLatency (0), | |||
| outputLatency (0), | |||
| minSize (0), maxSize (0), | |||
| preferredSize (0), | |||
| granularity (0), | |||
| minBufferSize (0), maxBufferSize (0), | |||
| preferredBufferSize (0), | |||
| bufferGranularity (0), | |||
| numClockSources (0), | |||
| currentBlockSizeSamples (0), | |||
| currentBitDepth (16), | |||
| @@ -403,7 +403,7 @@ public: | |||
| Array<double> getAvailableSampleRates() override { return sampleRates; } | |||
| Array<int> getAvailableBufferSizes() override { return bufferSizes; } | |||
| int getDefaultBufferSize() override { return preferredSize; } | |||
| int getDefaultBufferSize() override { return preferredBufferSize; } | |||
| String open (const BigInteger& inputChannels, | |||
| const BigInteger& outputChannels, | |||
| @@ -469,10 +469,10 @@ public: | |||
| removeCurrentDriver(); | |||
| loadDriver(); | |||
| const String error (initDriver()); | |||
| String initError = initDriver(); | |||
| if (error.isNotEmpty()) | |||
| JUCE_ASIO_LOG ("ASIOInit: " + error); | |||
| if (initError.isNotEmpty()) | |||
| JUCE_ASIO_LOG ("ASIOInit: " + initError); | |||
| needToReset = false; | |||
| } | |||
| @@ -489,7 +489,7 @@ public: | |||
| if (err != ASE_OK) | |||
| { | |||
| currentBlockSizeSamples = preferredSize; | |||
| currentBlockSizeSamples = preferredBufferSize; | |||
| JUCE_ASIO_LOG_ERROR ("create buffers 2", err); | |||
| asioObject->disposeBuffers(); | |||
| @@ -561,8 +561,7 @@ public: | |||
| } | |||
| readLatencies(); | |||
| asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity); | |||
| refreshBufferSizes(); | |||
| deviceIsOpen = true; | |||
| JUCE_ASIO_LOG ("starting"); | |||
| @@ -762,7 +761,7 @@ private: | |||
| Array<double> sampleRates; | |||
| Array<int> bufferSizes; | |||
| long inputLatency, outputLatency; | |||
| long minSize, maxSize, preferredSize, granularity; | |||
| long minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity; | |||
| ASIOClockSource clocks[32]; | |||
| int numClockSources; | |||
| @@ -829,23 +828,27 @@ private: | |||
| } | |||
| } | |||
| int readBufferSizes (int bufferSizeSamples) | |||
| long refreshBufferSizes() | |||
| { | |||
| minSize = 0; | |||
| maxSize = 0; | |||
| granularity = 0; | |||
| return asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &preferredBufferSize, &bufferGranularity); | |||
| } | |||
| int readBufferSizes (int bufferSizeSamples) | |||
| { | |||
| minBufferSize = 0; | |||
| maxBufferSize = 0; | |||
| bufferGranularity = 0; | |||
| long newPreferredSize = 0; | |||
| if (asioObject->getBufferSize (&minSize, &maxSize, &newPreferredSize, &granularity) == ASE_OK) | |||
| if (asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &newPreferredSize, &bufferGranularity) == ASE_OK) | |||
| { | |||
| if (preferredSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredSize) | |||
| if (preferredBufferSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredBufferSize) | |||
| shouldUsePreferredSize = true; | |||
| if (bufferSizeSamples < minSize || bufferSizeSamples > maxSize) | |||
| if (bufferSizeSamples < minBufferSize || bufferSizeSamples > maxBufferSize) | |||
| shouldUsePreferredSize = true; | |||
| preferredSize = newPreferredSize; | |||
| preferredBufferSize = newPreferredSize; | |||
| } | |||
| // unfortunate workaround for certain drivers which crash if you make | |||
| @@ -855,11 +858,11 @@ private: | |||
| if (shouldUsePreferredSize) | |||
| { | |||
| JUCE_ASIO_LOG ("Using preferred size for buffer.."); | |||
| long err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity); | |||
| long err = refreshBufferSizes(); | |||
| if (err == ASE_OK) | |||
| { | |||
| bufferSizeSamples = (int) preferredSize; | |||
| bufferSizeSamples = (int) preferredBufferSize; | |||
| } | |||
| else | |||
| { | |||
| @@ -1082,8 +1085,8 @@ private: | |||
| if (i < 2) | |||
| { | |||
| // clear the channels that are used with the dummy stuff | |||
| outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[0], preferredSize); | |||
| outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[1], preferredSize); | |||
| outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[0], preferredBufferSize); | |||
| outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[1], preferredBufferSize); | |||
| } | |||
| } | |||
| } | |||
| @@ -1203,9 +1206,9 @@ private: | |||
| inputFormat.calloc (chansToAllocate); | |||
| outputFormat.calloc (chansToAllocate); | |||
| if ((err = asioObject->getBufferSize (&minSize, &maxSize, &preferredSize, &granularity)) == 0) | |||
| if ((err = refreshBufferSizes()) == 0) | |||
| { | |||
| addBufferSizes (minSize, maxSize, preferredSize, granularity); | |||
| addBufferSizes (minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity); | |||
| double currentRate = getSampleRate(); | |||
| @@ -1226,8 +1229,8 @@ private: | |||
| updateSampleRates(); | |||
| readLatencies(); // ..doing these steps because cubase does so at this stage | |||
| createDummyBuffers (preferredSize); // in initialisation, and some devices fail if we don't. | |||
| readLatencies(); // ..doing these steps because cubase does so at this stage | |||
| createDummyBuffers (preferredBufferSize); // in initialisation, and some devices fail if we don't. | |||
| readLatencies(); | |||
| // start and stop because cubase does it.. | |||
| @@ -1419,6 +1422,12 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ASIOAudioIODevice) | |||
| }; | |||
| template <> | |||
| struct ASIOAudioIODevice::ASIOCallbackFunctions <sizeof(currentASIODev) / sizeof(currentASIODev[0])> | |||
| { | |||
| static void setCallbacksForDevice (ASIOCallbacks&, ASIOAudioIODevice*) noexcept {} | |||
| }; | |||
| //============================================================================== | |||
| class ASIOAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| @@ -112,6 +112,11 @@ bool check (HRESULT hr) | |||
| #define JUCE_COMCLASS(name, guid) struct __declspec (uuid (guid)) name | |||
| #endif | |||
| #if JUCE_MINGW && defined (KSDATAFORMAT_SUBTYPE_PCM) | |||
| #undef KSDATAFORMAT_SUBTYPE_PCM | |||
| #undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT | |||
| #endif | |||
| #ifndef KSDATAFORMAT_SUBTYPE_PCM | |||
| #define KSDATAFORMAT_SUBTYPE_PCM uuidFromString ("00000001-0000-0010-8000-00aa00389b71") | |||
| #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") | |||
| @@ -33,7 +33,7 @@ | |||
| #ifndef FLAC__PRIVATE__METADATA_H | |||
| #define FLAC__PRIVATE__METADATA_H | |||
| #include "FLAC/metadata.h" | |||
| #include "../../../metadata.h" | |||
| /* WATCHOUT: all malloc()ed data in the block is free()ed; this may not | |||
| * be a consistent state (e.g. PICTURE) or equivalent to the initial | |||
| @@ -112,19 +112,28 @@ struct CoreAudioFormatMetatdata | |||
| }; | |||
| //============================================================================== | |||
| struct UserDefinedChunk | |||
| static StringPairArray parseUserDefinedChunk (InputStream& input, int64 size) | |||
| { | |||
| UserDefinedChunk (InputStream& input, int64 size) | |||
| StringPairArray infoStrings; | |||
| const int64 originalPosition = input.getPosition(); | |||
| uint8 uuid[16]; | |||
| input.read (uuid, sizeof (uuid)); | |||
| if (memcmp (uuid, "\x29\x81\x92\x73\xB5\xBF\x4A\xEF\xB7\x8D\x62\xD1\xEF\x90\xBB\x2C", 16) == 0) | |||
| { | |||
| // a user defined chunk contains 16 bytes of a UUID first | |||
| uuid[1] = input.readInt64BigEndian(); | |||
| uuid[0] = input.readInt64BigEndian(); | |||
| const uint32 numEntries = (uint32) input.readIntBigEndian(); | |||
| input.skipNextBytes (size - 16); | |||
| for (uint32 i = 0; i < numEntries && input.getPosition() < originalPosition + size; ++i) | |||
| { | |||
| String keyName = input.readString(); | |||
| infoStrings.set (keyName, input.readString()); | |||
| } | |||
| } | |||
| int64 uuid[2]; | |||
| }; | |||
| input.setPosition (originalPosition + size); | |||
| return infoStrings; | |||
| } | |||
| //============================================================================== | |||
| static StringPairArray parseMidiChunk (InputStream& input, int64 size) | |||
| @@ -288,7 +297,7 @@ struct CoreAudioFormatMetatdata | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("uuid")) | |||
| { | |||
| UserDefinedChunk userDefinedChunk (input, chunkHeader.chunkSize); | |||
| metadataValues.addArray (parseUserDefinedChunk (input, chunkHeader.chunkSize)); | |||
| } | |||
| else if (chunkHeader.chunkType == chunkName ("data")) | |||
| { | |||
| @@ -27,15 +27,11 @@ | |||
| } // (juce namespace) | |||
| #if ! JUCE_WINDOWS | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #define Component CarbonDummyCompName | |||
| #include <QuickTime/Movies.h> | |||
| #include <QuickTime/QTML.h> | |||
| #include <QuickTime/QuickTimeComponents.h> | |||
| #include <QuickTime/MediaHandlers.h> | |||
| #include <QuickTime/ImageCodec.h> | |||
| #undef Point | |||
| #undef Component | |||
| #else | |||
| #if JUCE_MSVC | |||
| #pragma warning (push) | |||
| @@ -31,19 +31,18 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #include "juce_audio_formats.h" | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #define Point CarbonDummyPointName | |||
| #define Component CarbonDummyCompName | |||
| #if JUCE_QUICKTIME | |||
| #import <QTKit/QTKit.h> | |||
| #endif | |||
| #include <AudioToolbox/AudioToolbox.h> | |||
| #undef Component | |||
| #undef Point | |||
| #elif JUCE_IOS | |||
| #import <AudioToolbox/AudioToolbox.h> | |||
| @@ -85,14 +84,9 @@ namespace juce | |||
| { | |||
| #if JUCE_ANDROID | |||
| #include "../juce_core/native/juce_android_JNIHelpers.h" | |||
| #undef JUCE_QUICKTIME | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| #include "../juce_core/native/juce_win32_ComSmartPtr.h" | |||
| #endif | |||
| #include "format/juce_AudioFormat.cpp" | |||
| #include "format/juce_AudioFormatManager.cpp" | |||
| #include "format/juce_AudioFormatReader.cpp" | |||
| @@ -27,7 +27,7 @@ | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_USE_FLAC | |||
| Enables the FLAC audio codec classes (available on all platforms). | |||
| If your app doesn't need to read FLAC files, you might want to disable this to | |||
| @@ -81,7 +81,7 @@ | |||
| #define JUCE_USE_WINDOWS_MEDIA_FORMAT 0 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -387,11 +387,6 @@ public: | |||
| void* getPlatformSpecificData() override { return audioUnit; } | |||
| const String getName() const override { return pluginName; } | |||
| bool silenceInProducesSilenceOut() const override | |||
| { | |||
| return getTailLengthSeconds() <= 0; | |||
| } | |||
| double getTailLengthSeconds() const override | |||
| { | |||
| Float64 tail = 0; | |||
| @@ -252,7 +252,6 @@ public: | |||
| bool acceptsMidi() const { return false; } | |||
| bool producesMidi() const { return false; } | |||
| bool silenceInProducesSilenceOut() const { return plugin == nullptr; } // ..any way to get a proper answer for these? | |||
| double getTailLengthSeconds() const { return 0.0; } | |||
| //============================================================================== | |||
| @@ -143,25 +143,25 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy | |||
| switch (type) | |||
| { | |||
| case AudioChannelSet::ChannelType::left: return kSpeakerL; | |||
| case AudioChannelSet::ChannelType::right: return kSpeakerR; | |||
| case AudioChannelSet::ChannelType::centre: return kSpeakerC; | |||
| case AudioChannelSet::ChannelType::subbass: return kSpeakerLfe; | |||
| case AudioChannelSet::ChannelType::surroundLeft: return kSpeakerLs; | |||
| case AudioChannelSet::ChannelType::surroundRight: return kSpeakerRs; | |||
| case AudioChannelSet::ChannelType::centreLeft: return kSpeakerLc; | |||
| case AudioChannelSet::ChannelType::centreRight: return kSpeakerRc; | |||
| case AudioChannelSet::ChannelType::surround: return kSpeakerS; | |||
| case AudioChannelSet::ChannelType::sideLeft: return kSpeakerSl; | |||
| case AudioChannelSet::ChannelType::sideRight: return kSpeakerSr; | |||
| case AudioChannelSet::ChannelType::topMiddle: return kSpeakerTm; | |||
| case AudioChannelSet::ChannelType::topFrontLeft: return kSpeakerTfl; | |||
| case AudioChannelSet::ChannelType::topFrontCentre: return kSpeakerTfc; | |||
| case AudioChannelSet::ChannelType::topFrontRight: return kSpeakerTfr; | |||
| case AudioChannelSet::ChannelType::topRearLeft: return kSpeakerTrl; | |||
| case AudioChannelSet::ChannelType::topRearCentre: return kSpeakerTrc; | |||
| case AudioChannelSet::ChannelType::topRearRight: return kSpeakerTrr; | |||
| case AudioChannelSet::ChannelType::subbass2: return kSpeakerLfe2; | |||
| case AudioChannelSet::left: return kSpeakerL; | |||
| case AudioChannelSet::right: return kSpeakerR; | |||
| case AudioChannelSet::centre: return kSpeakerC; | |||
| case AudioChannelSet::subbass: return kSpeakerLfe; | |||
| case AudioChannelSet::surroundLeft: return kSpeakerLs; | |||
| case AudioChannelSet::surroundRight: return kSpeakerRs; | |||
| case AudioChannelSet::centreLeft: return kSpeakerLc; | |||
| case AudioChannelSet::centreRight: return kSpeakerRc; | |||
| case AudioChannelSet::surround: return kSpeakerS; | |||
| case AudioChannelSet::sideLeft: return kSpeakerSl; | |||
| case AudioChannelSet::sideRight: return kSpeakerSr; | |||
| case AudioChannelSet::topMiddle: return kSpeakerTm; | |||
| case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | |||
| case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | |||
| case AudioChannelSet::topFrontRight: return kSpeakerTfr; | |||
| case AudioChannelSet::topRearLeft: return kSpeakerTrl; | |||
| case AudioChannelSet::topRearCentre: return kSpeakerTrc; | |||
| case AudioChannelSet::topRearRight: return kSpeakerTrr; | |||
| case AudioChannelSet::subbass2: return kSpeakerLfe2; | |||
| default: break; | |||
| } | |||
| @@ -174,29 +174,29 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak | |||
| switch (type) | |||
| { | |||
| case kSpeakerL: return AudioChannelSet::ChannelType::left; | |||
| case kSpeakerR: return AudioChannelSet::ChannelType::right; | |||
| case kSpeakerC: return AudioChannelSet::ChannelType::centre; | |||
| case kSpeakerLfe: return AudioChannelSet::ChannelType::subbass; | |||
| case kSpeakerLs: return AudioChannelSet::ChannelType::surroundLeft; | |||
| case kSpeakerRs: return AudioChannelSet::ChannelType::surroundRight; | |||
| case kSpeakerLc: return AudioChannelSet::ChannelType::centreLeft; | |||
| case kSpeakerRc: return AudioChannelSet::ChannelType::centreRight; | |||
| case kSpeakerS: return AudioChannelSet::ChannelType::surround; | |||
| case kSpeakerSl: return AudioChannelSet::ChannelType::sideLeft; | |||
| case kSpeakerSr: return AudioChannelSet::ChannelType::sideRight; | |||
| case kSpeakerTm: return AudioChannelSet::ChannelType::topMiddle; | |||
| case kSpeakerTfl: return AudioChannelSet::ChannelType::topFrontLeft; | |||
| case kSpeakerTfc: return AudioChannelSet::ChannelType::topFrontCentre; | |||
| case kSpeakerTfr: return AudioChannelSet::ChannelType::topFrontRight; | |||
| case kSpeakerTrl: return AudioChannelSet::ChannelType::topRearLeft; | |||
| case kSpeakerTrc: return AudioChannelSet::ChannelType::topRearCentre; | |||
| case kSpeakerTrr: return AudioChannelSet::ChannelType::topRearRight; | |||
| case kSpeakerLfe2: return AudioChannelSet::ChannelType::subbass2; | |||
| case kSpeakerL: return AudioChannelSet::left; | |||
| case kSpeakerR: return AudioChannelSet::right; | |||
| case kSpeakerC: return AudioChannelSet::centre; | |||
| case kSpeakerLfe: return AudioChannelSet::subbass; | |||
| case kSpeakerLs: return AudioChannelSet::surroundLeft; | |||
| case kSpeakerRs: return AudioChannelSet::surroundRight; | |||
| case kSpeakerLc: return AudioChannelSet::centreLeft; | |||
| case kSpeakerRc: return AudioChannelSet::centreRight; | |||
| case kSpeakerS: return AudioChannelSet::surround; | |||
| case kSpeakerSl: return AudioChannelSet::sideLeft; | |||
| case kSpeakerSr: return AudioChannelSet::sideRight; | |||
| case kSpeakerTm: return AudioChannelSet::topMiddle; | |||
| case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | |||
| case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | |||
| case kSpeakerTfr: return AudioChannelSet::topFrontRight; | |||
| case kSpeakerTrl: return AudioChannelSet::topRearLeft; | |||
| case kSpeakerTrc: return AudioChannelSet::topRearCentre; | |||
| case kSpeakerTrr: return AudioChannelSet::topRearRight; | |||
| case kSpeakerLfe2: return AudioChannelSet::subbass2; | |||
| default: break; | |||
| } | |||
| return AudioChannelSet::ChannelType::unknown; | |||
| return AudioChannelSet::unknown; | |||
| } | |||
| static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept | |||
| @@ -25,9 +25,6 @@ | |||
| #ifndef JUCE_VST3HEADERS_H_INCLUDED | |||
| #define JUCE_VST3HEADERS_H_INCLUDED | |||
| #undef Point | |||
| #undef Component | |||
| // Wow, those Steinberg guys really don't worry too much about compiler warnings. | |||
| #if _MSC_VER | |||
| #pragma warning (disable: 4505) | |||
| @@ -85,8 +82,6 @@ | |||
| #if JUCE_MINGW | |||
| #define _set_abort_behavior(...) | |||
| #endif | |||
| #define Point CarbonDummyPointName // The VST headers include some system headers that need | |||
| // to match the name our hacky Carbon workaround used. | |||
| #include <base/source/baseiids.cpp> | |||
| #include <base/source/fatomic.cpp> | |||
| #include <base/source/fbuffer.cpp> | |||
| @@ -112,7 +107,6 @@ | |||
| #include <public.sdk/source/vst/vstcomponentbase.cpp> | |||
| #include <public.sdk/source/vst/vstparameters.cpp> | |||
| #include <public.sdk/source/vst/hosting/hostclasses.cpp> | |||
| #undef Point | |||
| //============================================================================== | |||
| namespace Steinberg | |||
| @@ -177,7 +171,5 @@ namespace Steinberg | |||
| #undef DEF_CLASS2 | |||
| #undef DEF_CLASS_W | |||
| #undef END_FACTORY | |||
| #undef Point | |||
| #undef Component | |||
| #endif // JUCE_VST3HEADERS_H_INCLUDED | |||
| @@ -1861,23 +1861,15 @@ public: | |||
| bool producesMidi() const override { return getBusInfo (false, false).channelCount > 0; } | |||
| //============================================================================== | |||
| bool silenceInProducesSilenceOut() const override | |||
| { | |||
| if (processor != nullptr) | |||
| return processor->getTailSamples() == Vst::kNoTail; | |||
| return true; | |||
| } | |||
| /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ | |||
| double getTailLengthSeconds() const override | |||
| { | |||
| if (processor != nullptr) | |||
| { | |||
| const double currentSampleRate = getSampleRate(); | |||
| const double sampleRate = getSampleRate(); | |||
| if (currentSampleRate > 0.0) | |||
| return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / currentSampleRate; | |||
| if (sampleRate > 0.0) | |||
| return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate; | |||
| } | |||
| return 0.0; | |||
| @@ -890,23 +890,18 @@ public: | |||
| return uid; | |||
| } | |||
| bool silenceInProducesSilenceOut() const override | |||
| { | |||
| return effect == nullptr || (effect->flags & effFlagsNoSoundInStop) != 0; | |||
| } | |||
| double getTailLengthSeconds() const override | |||
| { | |||
| if (effect == nullptr) | |||
| return 0.0; | |||
| const double currentSampleRate = getSampleRate(); | |||
| const double sampleRate = getSampleRate(); | |||
| if (currentSampleRate <= 0) | |||
| if (sampleRate <= 0) | |||
| return 0.0; | |||
| VstIntPtr samples = dispatch (effGetTailSize, 0, 0, 0, 0); | |||
| return samples / currentSampleRate; | |||
| return samples / sampleRate; | |||
| } | |||
| bool acceptsMidi() const override { return wantsMidiMessages; } | |||
| @@ -2915,11 +2910,11 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() | |||
| const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); | |||
| FileSearchPath paths; | |||
| paths.add (WindowsRegistry::getValue ("HKLM\\Software\\VST\\VSTPluginsPath", | |||
| paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath", | |||
| programFiles + "\\Steinberg\\VstPlugins")); | |||
| paths.removeNonExistentPaths(); | |||
| paths.add (WindowsRegistry::getValue ("HKLM\\Software\\VST\\VSTPluginsPath", | |||
| paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath", | |||
| programFiles + "\\VstPlugins")); | |||
| return paths; | |||
| #endif | |||
| @@ -31,7 +31,8 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #include "juce_audio_processors.h" | |||
| #include "../juce_gui_extra/juce_gui_extra.h" | |||
| @@ -40,11 +41,7 @@ | |||
| #if JUCE_SUPPORT_CARBON \ | |||
| && ((JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_AU) \ | |||
| || ! (defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6)) | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #define Component CarbonDummyCompName | |||
| #include <Carbon/Carbon.h> | |||
| #undef Point | |||
| #undef Component | |||
| #endif | |||
| #endif | |||
| @@ -29,7 +29,7 @@ | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_PLUGINHOST_VST | |||
| Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||
| installed on your machine. | |||
| @@ -67,8 +67,8 @@ | |||
| #define JUCE_SUPPORT_CARBON 1 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================= | |||
| //============================================================================== | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -28,8 +28,11 @@ bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept | |||
| bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||
| bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||
| const char* AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) noexcept | |||
| String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||
| { | |||
| if (type >= discreteChannel0) | |||
| return String ("Discrete ") + String (type - discreteChannel0 + 1); | |||
| switch (type) | |||
| { | |||
| case left: return NEEDS_TRANS("Left"); | |||
| @@ -63,8 +66,11 @@ const char* AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType ty | |||
| return "Unknown"; | |||
| } | |||
| const char* AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) noexcept | |||
| String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||
| { | |||
| if (type >= discreteChannel0) | |||
| return String (type - discreteChannel0 + 1); | |||
| switch (type) | |||
| { | |||
| case left: return "L"; | |||
| @@ -151,17 +157,17 @@ AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet ( | |||
| AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } | |||
| AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } | |||
| AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight)); } | |||
| AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre)); } | |||
| AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << sideLeft) | (1u << sideRight) | (1u << centre) | (1u << surround) | (1u << wideLeft) | (1u << wideRight)); } | |||
| AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight)); } | |||
| AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre)); } | |||
| AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centre) | (1u << surround) | (1u << wideLeft) | (1u << wideRight)); } | |||
| AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } | |||
| AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight)); } | |||
| AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight)); } | |||
| AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
| AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << topRearLeft) | (1u << topRearRight)); } | |||
| AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight)); } | |||
| AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight)); } | |||
| AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); } | |||
| AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft) | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); } | |||
| AudioChannelSet AudioChannelSet::createFront7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centreLeft) | (1u << centreRight)); } | |||
| AudioChannelSet AudioChannelSet::createFront7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft) | (1u << surroundRight) | (1u << centreLeft) | (1u << centreRight)); } | |||
| @@ -145,10 +145,10 @@ public: | |||
| }; | |||
| /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||
| static const char* getChannelTypeName (ChannelType) noexcept; | |||
| static String getChannelTypeName (ChannelType); | |||
| /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||
| static const char* getAbbreviatedChannelTypeName (ChannelType) noexcept; | |||
| static String getAbbreviatedChannelTypeName (ChannelType); | |||
| //============================================================================== | |||
| /** Adds a channel to the set. */ | |||
| @@ -32,7 +32,7 @@ void JUCE_CALLTYPE AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::Wrapp | |||
| AudioProcessor::AudioProcessor() | |||
| : wrapperType (wrapperTypeBeingCreated.get()), | |||
| playHead (nullptr), | |||
| sampleRate (0), | |||
| currentSampleRate (0), | |||
| blockSize (0), | |||
| latencySamples (0), | |||
| #if JUCE_DEBUG | |||
| @@ -42,23 +42,25 @@ AudioProcessor::AudioProcessor() | |||
| nonRealtime (false), | |||
| processingPrecision (singlePrecision) | |||
| { | |||
| #if ! JucePlugin_IsMidiEffect | |||
| #ifdef JucePlugin_PreferredChannelConfigurations | |||
| const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||
| #else | |||
| const short channelConfigs[][2] = { {2, 2} }; | |||
| #endif | |||
| int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs); | |||
| if (numChannelConfigs > 0) | |||
| { | |||
| #if ! JucePlugin_IsSynth | |||
| busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); | |||
| #endif | |||
| busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); | |||
| } | |||
| #endif | |||
| #if ! JucePlugin_IsMidiEffect | |||
| #if ! JucePlugin_IsSynth | |||
| busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); | |||
| #endif | |||
| busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); | |||
| #ifdef JucePlugin_PreferredChannelConfigurations | |||
| #if ! JucePlugin_IsSynth | |||
| AudioProcessor::setPreferredBusArrangement (true, 0, AudioChannelSet::stereo()); | |||
| #endif | |||
| AudioProcessor::setPreferredBusArrangement (false, 0, AudioChannelSet::stereo()); | |||
| #endif | |||
| #endif | |||
| updateSpeakerFormatStrings(); | |||
| } | |||
| @@ -123,7 +125,7 @@ void AudioProcessor::setPlayConfigDetails (const int newNumIns, | |||
| void AudioProcessor::setRateAndBufferSizeDetails (double newSampleRate, int newBlockSize) noexcept | |||
| { | |||
| sampleRate = newSampleRate; | |||
| currentSampleRate = newSampleRate; | |||
| blockSize = newBlockSize; | |||
| } | |||
| @@ -317,6 +317,14 @@ public: | |||
| changing the channel layout of other buses, for example, if your plug-in requires the same | |||
| number of input and output channels. | |||
| For most basic plug-ins, which do not require side-chains, aux buses or detailed audio | |||
| channel layout information, it is easier to specify the acceptable channel configurations | |||
| via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should | |||
| not override this method. | |||
| If, on the other hand, you decide to override this method then you need to make sure that | |||
| "PlugIn Channel Configurations" field in the Introjucer is empty. | |||
| Note, that you must not do any heavy allocations or calculations in this callback as it may | |||
| be called several hundred times during initialization. If you require any layout specific | |||
| allocations then defer these to prepareToPlay callback. | |||
| @@ -423,7 +431,7 @@ public: | |||
| This can be called from your processBlock() method - it's not guaranteed | |||
| to be valid at any other time, and may return 0 if it's unknown. | |||
| */ | |||
| double getSampleRate() const noexcept { return sampleRate; } | |||
| double getSampleRate() const noexcept { return currentSampleRate; } | |||
| /** Returns the current typical block size that is being used. | |||
| @@ -453,9 +461,6 @@ public: | |||
| */ | |||
| void setLatencySamples (int newLatency); | |||
| /** Returns true if a silent input always produces a silent output. */ | |||
| virtual bool silenceInProducesSilenceOut() const = 0; | |||
| /** Returns the length of the filter's tail, in seconds. */ | |||
| virtual double getTailLengthSeconds() const = 0; | |||
| @@ -883,7 +888,7 @@ public: | |||
| //============================================================================== | |||
| /** This is called by the processor to specify its details before being played. Use this | |||
| version of the function if you are not interested in any sidechain or aux buses | |||
| version of the function if you are not interested in any sidechain and/or aux buses | |||
| and do not care about the layout of channels. Otherwise use setRateAndBufferSizeDetails.*/ | |||
| void setPlayConfigDetails (int numIns, int numOuts, double sampleRate, int blockSize); | |||
| @@ -919,7 +924,7 @@ public: | |||
| WrapperType wrapperType; | |||
| //============================================================================== | |||
| #ifndef DOXYGEN | |||
| #ifndef DOXYGEN | |||
| /** Deprecated: use getTotalNumInputChannels instead. */ | |||
| JUCE_DEPRECATED_WITH_BODY (int getNumInputChannels() const noexcept, { return getTotalNumInputChannels(); }) | |||
| JUCE_DEPRECATED_WITH_BODY (int getNumOutputChannels() const noexcept, { return getTotalNumOutputChannels(); }) | |||
| @@ -946,7 +951,7 @@ public: | |||
| the constructor. */ | |||
| JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | |||
| JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | |||
| #endif | |||
| #endif | |||
| //============================================================================== | |||
| /** Helper function that just converts an xml element into a binary blob. | |||
| @@ -982,7 +987,7 @@ private: | |||
| #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
| Component::SafePointer<AudioProcessorEditor> activeEditor; | |||
| #endif | |||
| double sampleRate; | |||
| double currentSampleRate; | |||
| int blockSize, latencySamples; | |||
| #if JUCE_DEBUG | |||
| bool textRecursionCheck; | |||
| @@ -1005,6 +1010,9 @@ private: | |||
| void disableNonMainBuses (bool isInput); | |||
| void updateSpeakerFormatStrings(); | |||
| // This method is no longer used - you can delete it from your AudioProcessor classes. | |||
| JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessor) | |||
| }; | |||
| @@ -1460,7 +1460,6 @@ void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuff | |||
| midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); | |||
| } | |||
| bool AudioProcessorGraph::silenceInProducesSilenceOut() const { return false; } | |||
| double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } | |||
| bool AudioProcessorGraph::acceptsMidi() const { return true; } | |||
| bool AudioProcessorGraph::producesMidi() const { return true; } | |||
| @@ -1601,11 +1600,6 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<doubl | |||
| processAudio (buffer, midiMessages); | |||
| } | |||
| bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const | |||
| { | |||
| return isOutput(); | |||
| } | |||
| double AudioProcessorGraph::AudioGraphIOProcessor::getTailLengthSeconds() const | |||
| { | |||
| return 0; | |||
| @@ -311,7 +311,6 @@ public: | |||
| void processBlock (AudioBuffer<double>&, MidiBuffer&) override; | |||
| bool supportsDoublePrecisionProcessing() const override; | |||
| bool silenceInProducesSilenceOut() const override; | |||
| double getTailLengthSeconds() const override; | |||
| bool acceptsMidi() const override; | |||
| bool producesMidi() const override; | |||
| @@ -356,7 +355,6 @@ public: | |||
| void setNonRealtime (bool) noexcept override; | |||
| void setPlayHead (AudioPlayHead*) override; | |||
| bool silenceInProducesSilenceOut() const override; | |||
| double getTailLengthSeconds() const override; | |||
| bool acceptsMidi() const override; | |||
| bool producesMidi() const override; | |||
| @@ -50,14 +50,17 @@ void AudioParameterFloat::setValue (float newValue) { value | |||
| float AudioParameterFloat::getDefaultValue() const { return range.convertTo0to1 (defaultValue); } | |||
| int AudioParameterFloat::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); } | |||
| float AudioParameterFloat::getValueForText (const String& text) const { return range.convertTo0to1 (text.getFloatValue()); } | |||
| String AudioParameterFloat::getText (float v, int length) const { return String (range.convertFrom0to1 (v), 2).substring (0, length); } | |||
| AudioParameterFloat& AudioParameterFloat::operator= (float newValue) | |||
| String AudioParameterFloat::getText (float v, int length) const | |||
| { | |||
| const float normalisedValue = range.convertTo0to1 (newValue); | |||
| String asText (range.convertFrom0to1 (v), 2); | |||
| return length > 0 ? asText.substring (0, length) : asText; | |||
| } | |||
| if (value != normalisedValue) | |||
| setValueNotifyingHost (normalisedValue); | |||
| AudioParameterFloat& AudioParameterFloat::operator= (float newValue) | |||
| { | |||
| if (value != newValue) | |||
| setValueNotifyingHost (range.convertTo0to1 (newValue)); | |||
| return *this; | |||
| } | |||
| @@ -87,10 +90,8 @@ String AudioParameterInt::getText (float v, int /*length*/) const { retur | |||
| AudioParameterInt& AudioParameterInt::operator= (int newValue) | |||
| { | |||
| const float normalisedValue = convertTo0to1 (newValue); | |||
| if (value != normalisedValue) | |||
| setValueNotifyingHost (normalisedValue); | |||
| if (get() != newValue) | |||
| setValueNotifyingHost (convertTo0to1 (newValue)); | |||
| return *this; | |||
| } | |||
| @@ -115,10 +116,8 @@ String AudioParameterBool::getText (float v, int /*length*/) const { retur | |||
| AudioParameterBool& AudioParameterBool::operator= (bool newValue) | |||
| { | |||
| const float normalisedValue = newValue ? 1.0f : 0.0f; | |||
| if (value != normalisedValue) | |||
| setValueNotifyingHost (normalisedValue); | |||
| if (get() != newValue) | |||
| setValueNotifyingHost (newValue ? 1.0f : 0.0f); | |||
| return *this; | |||
| } | |||
| @@ -148,10 +147,8 @@ String AudioParameterChoice::getText (float v, int /*length*/) const { retur | |||
| AudioParameterChoice& AudioParameterChoice::operator= (int newValue) | |||
| { | |||
| const float normalisedValue = convertTo0to1 (newValue); | |||
| if (value != normalisedValue) | |||
| setValueNotifyingHost (normalisedValue); | |||
| if (getIndex() != newValue) | |||
| setValueNotifyingHost (convertTo0to1 (newValue)); | |||
| return *this; | |||
| } | |||
| @@ -258,21 +258,21 @@ void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees() | |||
| } | |||
| } | |||
| void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree&, const Identifier& property) | |||
| void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree& tree, const Identifier& property) | |||
| { | |||
| if (property == idPropertyID) | |||
| if (property == idPropertyID && tree.hasType (valueType) && tree.getParent() == state) | |||
| updateParameterConnectionsToChildTrees(); | |||
| } | |||
| void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree&) | |||
| void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree& tree) | |||
| { | |||
| if (parent == state) | |||
| if (parent == state && tree.hasType (valueType)) | |||
| updateParameterConnectionsToChildTrees(); | |||
| } | |||
| void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree&, int) | |||
| void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree& tree, int) | |||
| { | |||
| if (parent == state) | |||
| if (parent == state && tree.hasType (valueType)) | |||
| updateParameterConnectionsToChildTrees(); | |||
| } | |||
| @@ -424,6 +424,13 @@ public: | |||
| return entry != nullptr ? entry->value : ValueType(); | |||
| } | |||
| /** Resets the iterator to its starting position. */ | |||
| void reset() noexcept | |||
| { | |||
| entry = nullptr; | |||
| index = 0; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| const HashMap& hashMap; | |||
| @@ -70,8 +70,24 @@ File& File::operator= (File&& other) noexcept | |||
| const File File::nonexistent; | |||
| //============================================================================== | |||
| static String removeEllipsis (const String& path) | |||
| { | |||
| StringArray toks; | |||
| toks.addTokens (path, File::separatorString, StringRef()); | |||
| for (int i = 1; i < toks.size(); ++i) | |||
| { | |||
| if (toks[i] == ".." && toks[i - 1] != "..") | |||
| { | |||
| toks.removeRange (i - 1, 2); | |||
| i = jmax (0, i - 2); | |||
| } | |||
| } | |||
| return toks.joinIntoString (File::separatorString); | |||
| } | |||
| String File::parseAbsolutePath (const String& p) | |||
| { | |||
| if (p.isEmpty()) | |||
| @@ -81,6 +97,9 @@ String File::parseAbsolutePath (const String& p) | |||
| // Windows.. | |||
| String path (p.replaceCharacter ('/', '\\')); | |||
| if (path.contains ("\\..\\")) | |||
| path = removeEllipsis (path); | |||
| if (path.startsWithChar (separator)) | |||
| { | |||
| if (path[1] != separator) | |||
| @@ -120,6 +139,9 @@ String File::parseAbsolutePath (const String& p) | |||
| String path (p); | |||
| if (path.contains ("/../")) | |||
| path = removeEllipsis (path); | |||
| if (path.startsWithChar ('~')) | |||
| { | |||
| if (path[1] == separator || path[1] == 0) | |||
| @@ -347,62 +369,69 @@ int64 File::hashCode64() const { return fullPath.hashCode64(); } | |||
| //============================================================================== | |||
| bool File::isAbsolutePath (StringRef path) | |||
| { | |||
| return path.text[0] == separator | |||
| const juce_wchar firstChar = *(path.text); | |||
| return firstChar == separator | |||
| #if JUCE_WINDOWS | |||
| || (path.isNotEmpty() && path.text[1] == ':'); | |||
| || (firstChar != 0 && path.text[1] == ':'); | |||
| #else | |||
| || path.text[0] == '~'; | |||
| || firstChar == '~'; | |||
| #endif | |||
| } | |||
| File File::getChildFile (StringRef relativePath) const | |||
| { | |||
| if (isAbsolutePath (relativePath)) | |||
| return File (String (relativePath.text)); | |||
| String::CharPointerType r = relativePath.text; | |||
| if (relativePath[0] != '.') | |||
| return File (addTrailingSeparator (fullPath) + relativePath); | |||
| if (isAbsolutePath (r)) | |||
| return File (String (r)); | |||
| String path (fullPath); | |||
| // It's relative, so remove any ../ or ./ bits at the start.. | |||
| #if JUCE_WINDOWS | |||
| if (relativePath.text.indexOf ((juce_wchar) '/') >= 0) | |||
| return getChildFile (String (relativePath.text).replaceCharacter ('/', '\\')); | |||
| if (r.indexOf ((juce_wchar) '/') >= 0) | |||
| return getChildFile (String (r).replaceCharacter ('/', '\\')); | |||
| #endif | |||
| while (relativePath[0] == '.') | |||
| String path (fullPath); | |||
| while (*r == '.') | |||
| { | |||
| const juce_wchar secondChar = relativePath[1]; | |||
| String::CharPointerType lastPos = r; | |||
| const juce_wchar secondChar = *++r; | |||
| if (secondChar == '.') | |||
| if (secondChar == '.') // remove "../" | |||
| { | |||
| const juce_wchar thirdChar = relativePath[2]; | |||
| const juce_wchar thirdChar = *++r; | |||
| if (thirdChar == 0 || thirdChar == separator) | |||
| if (thirdChar == separator || thirdChar == 0) | |||
| { | |||
| const int lastSlash = path.lastIndexOfChar (separator); | |||
| if (lastSlash >= 0) | |||
| path = path.substring (0, lastSlash); | |||
| relativePath = relativePath.text + (thirdChar == 0 ? 2 : 3); | |||
| while (*r == separator) // ignore duplicate slashes | |||
| ++r; | |||
| } | |||
| else | |||
| { | |||
| r = lastPos; | |||
| break; | |||
| } | |||
| } | |||
| else if (secondChar == separator) | |||
| else if (secondChar == separator || secondChar == 0) // remove "./" | |||
| { | |||
| relativePath = relativePath.text + 2; | |||
| while (*r == separator) // ignore duplicate slashes | |||
| ++r; | |||
| } | |||
| else | |||
| { | |||
| r = lastPos; | |||
| break; | |||
| } | |||
| } | |||
| return File (addTrailingSeparator (path) + relativePath); | |||
| path = addTrailingSeparator (path); | |||
| path.appendCharPointer (r); | |||
| return File (path); | |||
| } | |||
| File File::getSiblingFile (StringRef fileName) const | |||
| @@ -1020,6 +1049,17 @@ public: | |||
| expect (tempFile.getSiblingFile ("foo").isAChildOf (temp)); | |||
| expect (tempFile.hasWriteAccess()); | |||
| expect (home.getChildFile (".") == home); | |||
| expect (home.getChildFile ("..") == home.getParentDirectory()); | |||
| expect (home.getChildFile (".xyz").getFileName() == ".xyz"); | |||
| expect (home.getChildFile ("..xyz").getFileName() == "..xyz"); | |||
| expect (home.getChildFile ("...xyz").getFileName() == "...xyz"); | |||
| expect (home.getChildFile ("./xyz") == home.getChildFile ("xyz")); | |||
| expect (home.getChildFile ("././xyz") == home.getChildFile ("xyz")); | |||
| expect (home.getChildFile ("../xyz") == home.getParentDirectory().getChildFile ("xyz")); | |||
| expect (home.getChildFile (".././xyz") == home.getParentDirectory().getChildFile ("xyz")); | |||
| expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); | |||
| { | |||
| FileOutputStream fo (tempFile); | |||
| fo.write ("0123456789", 10); | |||
| @@ -48,7 +48,7 @@ public: | |||
| //============================================================================== | |||
| /** Creates an (invalid) file object. | |||
| The file is initially set to an empty path, so getFullPath() will return | |||
| The file is initially set to an empty path, so getFullPathName() will return | |||
| an empty string, and comparing the file to File::nonexistent will return | |||
| true. | |||
| @@ -66,10 +66,10 @@ public: | |||
| //============================================================================== | |||
| /** Returns true if the filename matches one of the patterns specified. */ | |||
| bool isFileSuitable (const File& file) const; | |||
| bool isFileSuitable (const File& file) const override; | |||
| /** This always returns true. */ | |||
| bool isDirectorySuitable (const File& file) const; | |||
| bool isDirectorySuitable (const File& file) const override; | |||
| private: | |||
| //============================================================================== | |||
| @@ -44,7 +44,8 @@ | |||
| #define JUCE_JS_KEYWORDS(X) \ | |||
| X(var, "var") X(if_, "if") X(else_, "else") X(do_, "do") X(null_, "null") \ | |||
| X(while_, "while") X(for_, "for") X(break_, "break") X(continue_, "continue") X(undefined, "undefined") \ | |||
| X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") | |||
| X(function, "function") X(return_, "return") X(true_, "true") X(false_, "false") X(new_, "new") \ | |||
| X(typeof_, "typeof") | |||
| namespace TokenTypes | |||
| { | |||
| @@ -71,6 +72,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| setMethod ("trace", trace); | |||
| setMethod ("charToInt", charToInt); | |||
| setMethod ("parseInt", IntegerClass::parseInt); | |||
| setMethod ("typeof", typeof_internal); | |||
| } | |||
| Time timeout; | |||
| @@ -97,12 +99,13 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| && (((a.isUndefined() || a.isVoid()) && (b.isUndefined() || b.isVoid())) || a == b); | |||
| } | |||
| static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } | |||
| static bool isFunction (const var& v) { return dynamic_cast<FunctionObject*> (v.getObject()) != nullptr; } | |||
| static bool isNumericOrUndefined (const var& v) { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool() || v.isUndefined(); } | |||
| static int64 getOctalValue (const String& s) { BigInteger b; b.parseString (s, 8); return b.toInt64(); } | |||
| static Identifier getPrototypeIdentifier() { static const Identifier i ("prototype"); return i; } | |||
| static var* getPropertyPointer (DynamicObject* o, const Identifier& i) { return o->getProperties().getVarPointer (i); } | |||
| static String getTokenName (TokenType t) { return t[0] == '$' ? String (t + 1) : ("'" + String (t) + "'"); } | |||
| static bool isFunction (const var& v) noexcept { return dynamic_cast<FunctionObject*> (v.getObject()) != nullptr; } | |||
| static bool isNumeric (const var& v) noexcept { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool(); } | |||
| static bool isNumericOrUndefined (const var& v) noexcept { return isNumeric (v) || v.isUndefined(); } | |||
| static int64 getOctalValue (const String& s) { BigInteger b; b.parseString (s, 8); return b.toInt64(); } | |||
| static Identifier getPrototypeIdentifier() { static const Identifier i ("prototype"); return i; } | |||
| static var* getPropertyPointer (DynamicObject* o, const Identifier& i) noexcept { return o->getProperties().getVarPointer (i); } | |||
| //============================================================================== | |||
| struct CodeLocation | |||
| @@ -1079,7 +1082,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| if (matchIf (TokenTypes::while_)) return parseDoOrWhileLoop (false); | |||
| if (matchIf (TokenTypes::do_)) return parseDoOrWhileLoop (true); | |||
| if (matchIf (TokenTypes::for_)) return parseForLoop(); | |||
| if (matchIf (TokenTypes::return_)) return new ReturnStatement (location, matchIf (TokenTypes::semicolon) ? new Expression (location) : parseExpression()); | |||
| if (matchIf (TokenTypes::return_)) return parseReturn(); | |||
| if (matchIf (TokenTypes::break_)) return new BreakStatement (location); | |||
| if (matchIf (TokenTypes::continue_)) return new ContinueStatement (location); | |||
| if (matchIf (TokenTypes::function)) return parseFunction(); | |||
| @@ -1111,6 +1114,16 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| return s.release(); | |||
| } | |||
| Statement* parseReturn() | |||
| { | |||
| if (matchIf (TokenTypes::semicolon)) | |||
| return new ReturnStatement (location, new Expression (location)); | |||
| ReturnStatement* r = new ReturnStatement (location, parseExpression()); | |||
| matchIf (TokenTypes::semicolon); | |||
| return r; | |||
| } | |||
| Statement* parseVar() | |||
| { | |||
| ScopedPointer<VarStatement> s (new VarStatement (location)); | |||
| @@ -1345,12 +1358,21 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| return new PostAssignment (location, e, new OpType (location, lhs2, one)); | |||
| } | |||
| Expression* parseTypeof() | |||
| { | |||
| ScopedPointer<FunctionCall> f (new FunctionCall (location)); | |||
| f->object = new UnqualifiedName (location, "typeof"); | |||
| f->arguments.add (parseExpression()); | |||
| return f.release(); | |||
| } | |||
| Expression* parseUnary() | |||
| { | |||
| if (matchIf (TokenTypes::minus)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new SubtractionOp (location, a, b); } | |||
| if (matchIf (TokenTypes::logicalNot)) { ExpPtr a (new LiteralValue (location, (int) 0)), b (parseUnary()); return new EqualsOp (location, a, b); } | |||
| if (matchIf (TokenTypes::plusplus)) return parsePreIncDec<AdditionOp>(); | |||
| if (matchIf (TokenTypes::minusminus)) return parsePreIncDec<SubtractionOp>(); | |||
| if (matchIf (TokenTypes::typeof_)) return parseTypeof(); | |||
| return parseFactor(); | |||
| } | |||
| @@ -1478,6 +1500,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| setMethod ("contains", contains); | |||
| setMethod ("remove", remove); | |||
| setMethod ("join", join); | |||
| setMethod ("push", push); | |||
| } | |||
| static Identifier getClassName() { static const Identifier i ("Array"); return i; } | |||
| @@ -1508,6 +1531,19 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| return strings.joinIntoString (getString (a, 0)); | |||
| } | |||
| static var push (Args a) | |||
| { | |||
| if (Array<var>* array = a.thisObject.getArray()) | |||
| { | |||
| for (int i = 0; i < a.numArguments; ++i) | |||
| array->add (a.arguments[i]); | |||
| return array->size(); | |||
| } | |||
| return var::undefined(); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| @@ -1638,6 +1674,19 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| static var trace (Args a) { Logger::outputDebugString (JSON::toString (a.thisObject)); return var::undefined(); } | |||
| static var charToInt (Args a) { return (int) (getString (a, 0)[0]); } | |||
| static var typeof_internal (Args a) | |||
| { | |||
| var v (get (a, 0)); | |||
| if (v.isVoid()) return "void"; | |||
| if (v.isString()) return "string"; | |||
| if (isNumeric (v)) return "number"; | |||
| if (isFunction (v) || v.isMethod()) return "function"; | |||
| if (v.isObject()) return "object"; | |||
| return "undefined"; | |||
| } | |||
| static var exec (Args a) | |||
| { | |||
| if (RootObject* root = dynamic_cast<RootObject*> (a.thisObject.getObject())) | |||
| @@ -35,7 +35,10 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "native/juce_BasicNativeHeaders.h" | |||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #include "juce_core.h" | |||
| #include <locale> | |||
| @@ -80,6 +83,7 @@ | |||
| #if JUCE_LINUX | |||
| #include <langinfo.h> | |||
| #include <ifaddrs.h> | |||
| #include <sys/resource.h> | |||
| #if JUCE_USE_CURL | |||
| #include <curl/curl.h> | |||
| @@ -140,6 +144,7 @@ namespace juce | |||
| #include "maths/juce_Expression.cpp" | |||
| #include "maths/juce_Random.cpp" | |||
| #include "memory/juce_MemoryBlock.cpp" | |||
| #include "misc/juce_RuntimePermissions.cpp" | |||
| #include "misc/juce_Result.cpp" | |||
| #include "misc/juce_Uuid.cpp" | |||
| #include "network/juce_MACAddress.cpp" | |||
| @@ -180,10 +185,6 @@ namespace juce | |||
| #include "files/juce_WildcardFileFilter.cpp" | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS | |||
| #include "native/juce_osx_ObjCHelpers.h" | |||
| #endif | |||
| #if JUCE_ANDROID | |||
| #include "native/juce_android_JNIHelpers.h" | |||
| #endif | |||
| @@ -203,7 +204,6 @@ namespace juce | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #include "native/juce_win32_ComSmartPtr.h" | |||
| #include "native/juce_win32_Files.cpp" | |||
| #include "native/juce_win32_Network.cpp" | |||
| #include "native/juce_win32_Registry.cpp" | |||
| @@ -229,6 +229,7 @@ namespace juce | |||
| #include "native/juce_android_Network.cpp" | |||
| #include "native/juce_android_SystemStats.cpp" | |||
| #include "native/juce_android_Threads.cpp" | |||
| #include "native/juce_android_RuntimePermissions.cpp" | |||
| #endif | |||
| @@ -41,7 +41,7 @@ | |||
| #include "system/juce_TargetPlatform.h" | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_FORCE_DEBUG | |||
| Normally, JUCE_DEBUG is set to 1 or 0 based on compiler and project settings, | |||
| @@ -51,7 +51,7 @@ | |||
| //#define JUCE_FORCE_DEBUG 0 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_LOG_ASSERTIONS | |||
| If this flag is enabled, the jassert and jassertfalse macros will always use Logger::writeToLog() | |||
| @@ -71,7 +71,7 @@ | |||
| #endif | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_CHECK_MEMORY_LEAKS | |||
| Enables a memory-leak check for certain objects when the app terminates. See the LeakedObjectDetector | |||
| @@ -81,7 +81,7 @@ | |||
| #define JUCE_CHECK_MEMORY_LEAKS 1 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================== | |||
| /** Config: JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES | |||
| In a Visual C++ build, this can be used to stop the required system libs being | |||
| @@ -130,8 +130,12 @@ | |||
| #define JUCE_STRING_UTF_TYPE 8 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================= | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_CORE_INCLUDE_NATIVE_HEADERS | |||
| #include "native/juce_BasicNativeHeaders.h" | |||
| #endif | |||
| #include "system/juce_StandardHeader.h" | |||
| @@ -188,6 +192,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe | |||
| #include "threads/juce_CriticalSection.h" | |||
| #include "maths/juce_Range.h" | |||
| #include "maths/juce_NormalisableRange.h" | |||
| #include "maths/juce_StatisticsAccumulator.h" | |||
| #include "containers/juce_ElementComparator.h" | |||
| #include "containers/juce_ArrayAllocationBase.h" | |||
| #include "containers/juce_Array.h" | |||
| @@ -237,6 +242,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe | |||
| #include "maths/juce_BigInteger.h" | |||
| #include "maths/juce_Expression.h" | |||
| #include "maths/juce_Random.h" | |||
| #include "misc/juce_RuntimePermissions.h" | |||
| #include "misc/juce_Uuid.h" | |||
| #include "misc/juce_WindowsRegistry.h" | |||
| #include "system/juce_SystemStats.h" | |||
| @@ -269,6 +275,19 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe | |||
| #include "containers/juce_PropertySet.h" | |||
| #include "memory/juce_SharedResourcePointer.h" | |||
| #if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) | |||
| #include "native/juce_osx_ObjCHelpers.h" | |||
| #endif | |||
| #if JUCE_CORE_INCLUDE_COM_SMART_PTR && JUCE_WINDOWS | |||
| #include "native/juce_win32_ComSmartPtr.h" | |||
| #endif | |||
| #if JUCE_CORE_INCLUDE_JNI_HELPERS && JUCE_ANDROID | |||
| #include "native/juce_android_JNIHelpers.h" | |||
| #endif | |||
| #ifndef DOXYGEN | |||
| /* | |||
| As the very long class names here try to explain, the purpose of this code is to cause | |||
| @@ -315,7 +315,7 @@ inline static int highestBitInInt (uint32 n) noexcept | |||
| { | |||
| jassert (n != 0); // (the built-in functions may not work for n = 0) | |||
| #if JUCE_GCC | |||
| #if JUCE_GCC || JUCE_CLANG | |||
| return 31 - __builtin_clz (n); | |||
| #elif JUCE_USE_MSVC_INTRINSICS | |||
| unsigned long highest; | |||
| @@ -324,9 +324,9 @@ template <> | |||
| inline float juce_hypot (float a, float b) noexcept | |||
| { | |||
| #if JUCE_MSVC | |||
| return (_hypotf (a, b)); | |||
| return _hypotf (a, b); | |||
| #else | |||
| return (hypotf (a, b)); | |||
| return hypotf (a, b); | |||
| #endif | |||
| } | |||
| #endif | |||
| @@ -356,12 +356,16 @@ const float float_Pi = 3.14159265358979323846f; | |||
| /** Converts an angle in degrees to radians. */ | |||
| template <typename FloatType> | |||
| FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * static_cast<FloatType> (double_Pi / 180.0); } | |||
| inline float degreesToRadians (float degrees) noexcept { return degrees * (float_Pi / 180.0f); } | |||
| /** Converts an angle in degrees to radians. */ | |||
| inline double degreesToRadians (double degrees) noexcept { return degrees * (double_Pi / 180.0); } | |||
| /** Converts an angle in radians to degrees. */ | |||
| template <typename FloatType> | |||
| FloatType radiansToDegrees (FloatType radians) noexcept { return radians * static_cast<FloatType> (180.0 / double_Pi); } | |||
| inline float radiansToDegrees (float radians) noexcept { return radians * (180.0f / float_Pi); } | |||
| /** Converts an angle in radians to degrees. */ | |||
| inline double radiansToDegrees (double radians) noexcept { return radians * (180.0 / double_Pi); } | |||
| //============================================================================== | |||
| @@ -0,0 +1,145 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the juce_core module of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| or without fee is hereby granted, provided that the above copyright notice and this | |||
| permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| ------------------------------------------------------------------------------ | |||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||
| using any other modules, be sure to check that you also comply with their license. | |||
| For more details, visit www.juce.com | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_STATISTICSACCUMULATOR_H_INCLUDED | |||
| #define JUCE_STATISTICSACCUMULATOR_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| A class that measures various statistics about a series of floating point | |||
| values that it is given. | |||
| */ | |||
| template <typename FloatType> | |||
| class StatisticsAccumulator | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Constructs a new StatisticsAccumulator. */ | |||
| StatisticsAccumulator() noexcept | |||
| : count (0), | |||
| minimum ( std::numeric_limits<FloatType>::infinity()), | |||
| maximum (-std::numeric_limits<FloatType>::infinity()) | |||
| {} | |||
| //============================================================================== | |||
| /** Add a new value to the accumulator. | |||
| This will update all running statistics accordingly. | |||
| */ | |||
| void addValue (FloatType v) noexcept | |||
| { | |||
| jassert (juce_isfinite (v)); | |||
| sum += v; | |||
| sumSquares += v * v; | |||
| ++count; | |||
| if (v > maximum) maximum = v; | |||
| if (v < minimum) minimum = v; | |||
| } | |||
| /** Reset the accumulator. | |||
| This will reset all currently saved statistcs. | |||
| */ | |||
| void reset() noexcept { *this = StatisticsAccumulator<FloatType>(); } | |||
| //============================================================================== | |||
| /** Returns the average (arithmetic mean) of all previously added values. | |||
| If no values have been added yet, this will return zero. | |||
| */ | |||
| FloatType getAverage() const noexcept | |||
| { | |||
| return count > 0 ? sum / (FloatType) count | |||
| : FloatType(); | |||
| } | |||
| /** Returns the variance of all previously added values. | |||
| If no values have been added yet, this will return zero. | |||
| */ | |||
| FloatType getVariance() const noexcept | |||
| { | |||
| return count > 0 ? (sumSquares - sum * sum / (FloatType) count) / (FloatType) count | |||
| : FloatType(); | |||
| } | |||
| /** Returns the standard deviation of all previously added values. | |||
| If no values have been added yet, this will return zero. | |||
| */ | |||
| FloatType getStandardDeviation() const noexcept | |||
| { | |||
| return std::sqrt (getVariance()); | |||
| } | |||
| /** Returns the smallest of all previously added values. | |||
| If no values have been added yet, this will return positive infinity. | |||
| */ | |||
| FloatType getMinValue() const noexcept | |||
| { | |||
| return minimum; | |||
| } | |||
| /** Returns the largest of all previously added values. | |||
| If no values have been added yet, this will return negative infinity. | |||
| */ | |||
| FloatType getMaxValue() const noexcept | |||
| { | |||
| return maximum; | |||
| } | |||
| /** Returns how many values have been added to this accumulator. */ | |||
| size_t getCount() const noexcept | |||
| { | |||
| return count; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| struct KahanSum | |||
| { | |||
| KahanSum() noexcept : sum(), error() {} | |||
| operator FloatType() const noexcept { return sum; } | |||
| void JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS operator+= (FloatType value) noexcept | |||
| { | |||
| FloatType correctedValue = value - error; | |||
| FloatType newSum = sum + correctedValue; | |||
| error = (newSum - sum) - correctedValue; | |||
| sum = newSum; | |||
| } | |||
| FloatType sum, error; | |||
| }; | |||
| //============================================================================== | |||
| size_t count; | |||
| KahanSum sum, sumSquares; | |||
| FloatType minimum, maximum; | |||
| }; | |||
| #endif // JUCE_STATISTICSACCUMULATOR_H_INCLUDED | |||
| @@ -201,7 +201,7 @@ private: | |||
| #endif | |||
| //============================================================================== | |||
| #elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_MSVC | |||
| #elif JUCE_GCC || JUCE_CLANG | |||
| #define JUCE_ATOMICS_GCC 1 // GCC with intrinsics | |||
| #if JUCE_IOS || JUCE_ANDROID // (64-bit ops will compile but not link on these mobile OSes) | |||
| @@ -157,7 +157,7 @@ inline uint32 ByteOrder::swap (uint32 n) noexcept | |||
| { | |||
| #if JUCE_MAC || JUCE_IOS | |||
| return OSSwapInt32 (n); | |||
| #elif JUCE_GCC && JUCE_INTEL && ! JUCE_NO_INLINE_ASM | |||
| #elif (JUCE_GCC || JUCE_CLANG) && JUCE_INTEL && ! JUCE_NO_INLINE_ASM | |||
| asm("bswap %%eax" : "=a"(n) : "a"(n)); | |||
| return n; | |||
| #elif JUCE_USE_MSVC_INTRINSICS | |||
| @@ -180,6 +180,12 @@ private: | |||
| //============================================================================== | |||
| ScopedPointer<ObjectType> object; | |||
| bool shouldDelete; | |||
| // This is here to avoid people accidentally taking a second owned copy of | |||
| // a scoped pointer, which is almost certainly not what you intended to do! | |||
| // If you hit a problem with this, you probably meant to say | |||
| // myPointer.setOwned (myScopedPointer.release()) | |||
| void setOwned (const ScopedPointer<ObjectType>&) JUCE_DELETED_FUNCTION; | |||
| }; | |||
| @@ -0,0 +1,48 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the juce_core module of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| or without fee is hereby granted, provided that the above copyright notice and this | |||
| permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| ------------------------------------------------------------------------------ | |||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||
| using any other modules, be sure to check that you also comply with their license. | |||
| For more details, visit www.juce.com | |||
| ============================================================================== | |||
| */ | |||
| #if ! JUCE_ANDROID // We currently don't request runtime permissions on any other platform | |||
| // than Android, so this file contains a dummy implementation for those. | |||
| // This may change in the future. | |||
| void RuntimePermissions::request (PermissionID /*permission*/, Callback callback) | |||
| { | |||
| callback (true); | |||
| } | |||
| bool RuntimePermissions::isRequired (PermissionID /*permission*/) | |||
| { | |||
| return false; | |||
| } | |||
| bool RuntimePermissions::isGranted (PermissionID /*permission*/) | |||
| { | |||
| return true; | |||
| } | |||
| #endif | |||
| @@ -0,0 +1,131 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the juce_core module of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| or without fee is hereby granted, provided that the above copyright notice and this | |||
| permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| ------------------------------------------------------------------------------ | |||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||
| using any other modules, be sure to check that you also comply with their license. | |||
| For more details, visit www.juce.com | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_RUNTIMEPERMISSIONS_H_INCLUDED | |||
| #define JUCE_RUNTIMEPERMISSIONS_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Class to handle app runtime permissions for certain functionality on some platforms. | |||
| The use of this class is currently only required if the app should run on | |||
| Android API level 23 and higher. | |||
| On lower API levels, the permissions are specified in the app manifest. On iOS, | |||
| runtime permission requests are handled automatically by the Apple APIs and not | |||
| manually in the app code. On Windows, OS X, and Linux, runtime permissions are not | |||
| used at all. In all these cases, request() will simply call through to the | |||
| callback with no overhead and pass true (making it safe to use on all platforms). | |||
| For example, to enable audio recording on Android in your cross-platform app, | |||
| you could modify your code as follows: | |||
| Old code: | |||
| audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr); | |||
| New code: | |||
| RuntimePermissions::request ( | |||
| RuntimePermissions::audioRecording, | |||
| [this] (bool wasGranted) | |||
| { | |||
| if (! wasGranted) | |||
| { | |||
| // e.g. display an error or initialise with 0 input channels | |||
| return; | |||
| } | |||
| audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr); | |||
| } | |||
| ); | |||
| */ | |||
| class JUCE_API RuntimePermissions | |||
| { | |||
| public: | |||
| //========================================================================== | |||
| enum PermissionID | |||
| { | |||
| /** Permission to access the microphone (required on Android). | |||
| You need to request this, for example, to initialise an AudioDeviceManager with | |||
| a non-zero number of input channels, and to open the default audio input device. | |||
| */ | |||
| recordAudio = 1, | |||
| /** Permission to scan for and pair to Bluetooth MIDI devices (required on Android). | |||
| You need to request this before calling BluetoothMidiDevicePairingDialogue::open(), | |||
| otherwise no devices will be found. | |||
| */ | |||
| bluetoothMidi = 2, | |||
| }; | |||
| //========================================================================== | |||
| /** Function type of runtime permission request callbacks. */ | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| typedef std::function<void (bool)> Callback; | |||
| #else | |||
| typedef void (*Callback) (bool); | |||
| #endif | |||
| //========================================================================== | |||
| /** Call this method to request a runtime permission. | |||
| @param permission The PermissionID of the permission you want to request. | |||
| @param callback The callback to be called after the request has been granted | |||
| or denied; the argument passed will be true if the permission | |||
| has been granted and false otherwise. | |||
| If no runtime request is required or possible to obtain the permission, the | |||
| callback will be called immediately. The argument passed in will be true | |||
| if the permission is granted or no permission is required on this platform, | |||
| and false otherwise. | |||
| If a runtime request is required to obtain the permission, the callback | |||
| will be called asynchronously after the OS has granted or denied the requested | |||
| permission (typically by displaying a dialog box to the user and waiting until | |||
| the user has responded). | |||
| */ | |||
| static void request (PermissionID permission, Callback callback); | |||
| /** Returns whether a runtime request is required to obtain the permission | |||
| on the current platform. | |||
| */ | |||
| static bool isRequired (PermissionID permission); | |||
| /** Returns true if the app has been already granted this permission, either | |||
| via a previous runtime request or otherwise, or no permission is necessary. | |||
| Note that this can be false even if isRequired returns false. In this case, | |||
| the permission can not be obtained at all at runtime. | |||
| */ | |||
| static bool isGranted (PermissionID permission); | |||
| }; | |||
| #endif // JUCE_RUNTIMEPERMISSIONS_H_INCLUDED | |||
| @@ -0,0 +1,14 @@ | |||
| private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); | |||
| @Override | |||
| public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults) | |||
| { | |||
| boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED); | |||
| if (! permissionsGranted) | |||
| Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID)); | |||
| Long ptrToCallback = permissionCallbackPtrMap.get (permissionID); | |||
| permissionCallbackPtrMap.remove (permissionID); | |||
| androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback); | |||
| } | |||
| @@ -30,14 +30,14 @@ import android.content.DialogInterface; | |||
| import android.content.Context; | |||
| import android.content.Intent; | |||
| import android.content.res.Configuration; | |||
| import android.content.pm.PackageInfo; | |||
| import android.content.pm.PackageManager; | |||
| import android.net.Uri; | |||
| import android.os.Bundle; | |||
| import android.os.Looper; | |||
| import android.os.Handler; | |||
| import android.os.Build; | |||
| import android.os.Process; | |||
| import android.os.ParcelUuid; | |||
| import android.os.Environment; | |||
| import android.view.*; | |||
| import android.view.inputmethod.BaseInputConnection; | |||
| import android.view.inputmethod.EditorInfo; | |||
| @@ -49,19 +49,16 @@ import android.text.InputType; | |||
| import android.util.DisplayMetrics; | |||
| import android.util.Log; | |||
| import java.lang.Runnable; | |||
| import java.util.List; | |||
| import java.util.Arrays; | |||
| import java.util.ArrayList; | |||
| import java.util.HashSet; | |||
| import java.util.Hashtable; | |||
| import java.util.TimerTask; | |||
| import java.util.*; | |||
| import java.io.*; | |||
| import java.net.URL; | |||
| import java.net.HttpURLConnection; | |||
| import android.media.AudioManager; | |||
| import android.media.MediaScannerConnection; | |||
| import android.media.MediaScannerConnection.MediaScannerConnectionClient; | |||
| import android.support.v4.content.ContextCompat; | |||
| import android.support.v4.app.ActivityCompat; | |||
| import android.Manifest; | |||
| $$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer! | |||
| @@ -74,6 +71,73 @@ public class JuceAppActivity extends Activity | |||
| System.loadLibrary ("juce_jni"); | |||
| } | |||
| //============================================================================== | |||
| public boolean isPermissionDeclaredInManifest (int permissionID) | |||
| { | |||
| String permissionToCheck = getAndroidPermissionName(permissionID); | |||
| try | |||
| { | |||
| PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); | |||
| if (info.requestedPermissions != null) | |||
| for (String permission : info.requestedPermissions) | |||
| if (permission.equals (permissionToCheck)) | |||
| return true; | |||
| } | |||
| catch (PackageManager.NameNotFoundException e) | |||
| { | |||
| Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString()); | |||
| } | |||
| Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck); | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| // these have to match the values of enum PermissionID in C++ class RuntimePermissions: | |||
| private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1; | |||
| private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2; | |||
| private static String getAndroidPermissionName (int permissionID) | |||
| { | |||
| switch (permissionID) | |||
| { | |||
| case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO; | |||
| case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION; | |||
| } | |||
| // unknown permission ID! | |||
| assert false; | |||
| return new String(); | |||
| } | |||
| public boolean isPermissionGranted (int permissionID) | |||
| { | |||
| return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED; | |||
| } | |||
| private Map<Integer, Long> permissionCallbackPtrMap; | |||
| public void requestRuntimePermission (int permissionID, long ptrToCallback) | |||
| { | |||
| String permissionName = getAndroidPermissionName (permissionID); | |||
| if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED) | |||
| { | |||
| // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously | |||
| permissionCallbackPtrMap.put (permissionID, ptrToCallback); | |||
| ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID); | |||
| } | |||
| else | |||
| { | |||
| // permissions were already granted before, we can call callback directly | |||
| androidRuntimePermissionsCallback (true, ptrToCallback); | |||
| } | |||
| } | |||
| $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the introjucer! | |||
| //============================================================================== | |||
| public static class MidiPortID extends Object | |||
| { | |||
| @@ -132,10 +196,13 @@ public class JuceAppActivity extends Activity | |||
| super.onCreate (savedInstanceState); | |||
| isScreenSaverEnabled = true; | |||
| hideActionBar(); | |||
| viewHolder = new ViewHolder (this); | |||
| setContentView (viewHolder); | |||
| setVolumeControlStream (AudioManager.STREAM_MUSIC); | |||
| permissionCallbackPtrMap = new HashMap<Integer, Long>(); | |||
| } | |||
| @Override | |||
| @@ -174,6 +241,49 @@ public class JuceAppActivity extends Activity | |||
| getApplicationInfo().dataDir); | |||
| } | |||
| private void hideActionBar() | |||
| { | |||
| // get "getActionBar" method | |||
| java.lang.reflect.Method getActionBarMethod = null; | |||
| try | |||
| { | |||
| getActionBarMethod = this.getClass().getMethod ("getActionBar"); | |||
| } | |||
| catch (SecurityException e) { return; } | |||
| catch (NoSuchMethodException e) { return; } | |||
| if (getActionBarMethod == null) return; | |||
| // invoke "getActionBar" method | |||
| Object actionBar = null; | |||
| try | |||
| { | |||
| actionBar = getActionBarMethod.invoke (this); | |||
| } | |||
| catch (java.lang.IllegalArgumentException e) { return; } | |||
| catch (java.lang.IllegalAccessException e) { return; } | |||
| catch (java.lang.reflect.InvocationTargetException e) { return; } | |||
| if (actionBar == null) return; | |||
| // get "hide" method | |||
| java.lang.reflect.Method actionBarHideMethod = null; | |||
| try | |||
| { | |||
| actionBarHideMethod = actionBar.getClass().getMethod ("hide"); | |||
| } | |||
| catch (SecurityException e) { return; } | |||
| catch (NoSuchMethodException e) { return; } | |||
| if (actionBarHideMethod == null) return; | |||
| // invoke "hide" method | |||
| try | |||
| { | |||
| actionBarHideMethod.invoke (actionBar); | |||
| } | |||
| catch (java.lang.IllegalArgumentException e) {} | |||
| catch (java.lang.IllegalAccessException e) {} | |||
| catch (java.lang.reflect.InvocationTargetException e) {} | |||
| } | |||
| //============================================================================== | |||
| private native void launchApp (String appFile, String appDataDir); | |||
| private native void quitApp(); | |||
| @@ -691,7 +801,7 @@ public class JuceAppActivity extends Activity | |||
| int format, int width, int height); | |||
| } | |||
| public NativeSurfaceView createNativeSurfaceView(long nativeSurfacePtr) | |||
| public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr) | |||
| { | |||
| return new NativeSurfaceView (this, nativeSurfacePtr); | |||
| } | |||
| @@ -917,6 +1027,17 @@ public class JuceAppActivity extends Activity | |||
| : locale.getDisplayLanguage (java.util.Locale.US); | |||
| } | |||
| private static final String getFileLocation (String type) | |||
| { | |||
| return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath(); | |||
| } | |||
| public static final String getDocumentsFolder() { return Environment.getDataDirectory().getAbsolutePath(); } | |||
| public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); } | |||
| public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); } | |||
| public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); } | |||
| public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); } | |||
| //============================================================================== | |||
| private final class SingleMediaScanner implements MediaScannerConnectionClient | |||
| { | |||
| @@ -1041,23 +1162,24 @@ public class JuceAppActivity extends Activity | |||
| return null; | |||
| java.lang.reflect.Method method; | |||
| try { | |||
| try | |||
| { | |||
| method = obj.getClass().getMethod ("getProperty", String.class); | |||
| } catch (SecurityException e) { | |||
| return null; | |||
| } catch (NoSuchMethodException e) { | |||
| return null; | |||
| } | |||
| catch (SecurityException e) { return null; } | |||
| catch (NoSuchMethodException e) { return null; } | |||
| if (method == null) | |||
| return null; | |||
| try { | |||
| try | |||
| { | |||
| return (String) method.invoke (obj, property); | |||
| } catch (java.lang.IllegalArgumentException e) { | |||
| } catch (java.lang.IllegalAccessException e) { | |||
| } catch (java.lang.reflect.InvocationTargetException e) { | |||
| } | |||
| catch (java.lang.IllegalArgumentException e) {} | |||
| catch (java.lang.IllegalAccessException e) {} | |||
| catch (java.lang.reflect.InvocationTargetException e) {} | |||
| return null; | |||
| } | |||
| @@ -1075,8 +1197,9 @@ public class JuceAppActivity extends Activity | |||
| private static class JuceThread extends Thread | |||
| { | |||
| public JuceThread (long host) | |||
| public JuceThread (long host, String threadName, long threadStackSize) | |||
| { | |||
| super (null, null, threadName, threadStackSize); | |||
| _this = host; | |||
| } | |||
| @@ -1089,9 +1212,8 @@ public class JuceAppActivity extends Activity | |||
| private long _this; | |||
| } | |||
| public final Thread createNewThread(long host) | |||
| public final Thread createNewThread(long host, String threadName, long threadStackSize) | |||
| { | |||
| return new JuceThread(host); | |||
| return new JuceThread(host, threadName, threadStackSize); | |||
| } | |||
| } | |||
| @@ -29,7 +29,6 @@ | |||
| #ifndef JUCE_BASICNATIVEHEADERS_H_INCLUDED | |||
| #define JUCE_BASICNATIVEHEADERS_H_INCLUDED | |||
| #include "../system/juce_TargetPlatform.h" | |||
| #undef T | |||
| //============================================================================== | |||
| @@ -42,12 +41,8 @@ | |||
| #import <MobileCoreServices/MobileCoreServices.h> | |||
| #include <sys/fcntl.h> | |||
| #else | |||
| #define Point CarbonDummyPointName | |||
| #define Component CarbonDummyCompName | |||
| #import <Cocoa/Cocoa.h> | |||
| #import <CoreAudio/HostTime.h> | |||
| #undef Point | |||
| #undef Component | |||
| #include <sys/dir.h> | |||
| #endif | |||
| @@ -46,23 +46,27 @@ String File::getVersion() const | |||
| return String(); | |||
| } | |||
| static File getSpecialFile (jmethodID type) | |||
| { | |||
| return File (juceString (LocalRef<jstring> ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, type)))); | |||
| } | |||
| File File::getSpecialLocation (const SpecialLocationType type) | |||
| { | |||
| switch (type) | |||
| { | |||
| case userHomeDirectory: | |||
| case userDocumentsDirectory: | |||
| case userMusicDirectory: | |||
| case userMoviesDirectory: | |||
| case userPicturesDirectory: | |||
| case userApplicationDataDirectory: | |||
| case userDesktopDirectory: | |||
| return File (android.appDataDir); | |||
| case commonApplicationDataDirectory: | |||
| case commonDocumentsDirectory: | |||
| return File (android.appDataDir); | |||
| case userDocumentsDirectory: | |||
| case commonDocumentsDirectory: return getSpecialFile (JuceAppActivity.getDocumentsFolder); | |||
| case userPicturesDirectory: return getSpecialFile (JuceAppActivity.getPicturesFolder); | |||
| case userMusicDirectory: return getSpecialFile (JuceAppActivity.getMusicFolder); | |||
| case userMoviesDirectory: return getSpecialFile (JuceAppActivity.getMoviesFolder); | |||
| case globalApplicationsDirectory: | |||
| return File ("/system/app"); | |||
| @@ -266,6 +266,7 @@ extern AndroidSystem android; | |||
| METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ | |||
| METHOD (postMessage, "postMessage", "(J)V") \ | |||
| METHOD (finish, "finish", "()V") \ | |||
| METHOD (setRequestedOrientation,"setRequestedOrientation", "(I)V") \ | |||
| METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | |||
| METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | |||
| METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | |||
| @@ -276,6 +277,11 @@ extern AndroidSystem android; | |||
| METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||
| METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||
| STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ | |||
| STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ | |||
| STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ | |||
| STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ | |||
| STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ | |||
| STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ | |||
| METHOD (scanFile, "scanFile", "(Ljava/lang/String;)V") \ | |||
| METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | |||
| METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | |||
| @@ -287,7 +293,10 @@ extern AndroidSystem android; | |||
| METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | |||
| METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ | |||
| METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | |||
| METHOD (createNewThread, "createNewThread", "(J)Ljava/lang/Thread;") \ | |||
| METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \ | |||
| METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | |||
| METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | |||
| METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | |||
| DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); | |||
| #undef JNI_CLASS_MEMBERS | |||
| @@ -0,0 +1,90 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the juce_core module of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| or without fee is hereby granted, provided that the above copyright notice and this | |||
| permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| ------------------------------------------------------------------------------ | |||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||
| using any other modules, be sure to check that you also comply with their license. | |||
| For more details, visit www.juce.com | |||
| ============================================================================== | |||
| */ | |||
| namespace | |||
| { | |||
| void handleAndroidCallback (bool permissionWasGranted, RuntimePermissions::Callback* callbackPtr) | |||
| { | |||
| if (callbackPtr == nullptr) | |||
| { | |||
| // got a nullptr passed in from java! this should never happen... | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| std::unique_ptr<RuntimePermissions::Callback> uptr (callbackPtr); | |||
| (*uptr) (permissionWasGranted); | |||
| } | |||
| } | |||
| JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, | |||
| androidRuntimePermissionsCallback, | |||
| void, (JNIEnv* env, jobject /*javaObjectHandle*/, jboolean permissionsGranted, jlong callbackPtr)) | |||
| { | |||
| setEnv (env); | |||
| handleAndroidCallback (permissionsGranted != 0, | |||
| reinterpret_cast<RuntimePermissions::Callback*> (callbackPtr)); | |||
| } | |||
| void RuntimePermissions::request (PermissionID permission, Callback callback) | |||
| { | |||
| if (! android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifest, (jint) permission)) | |||
| { | |||
| // Error! If you want to be able to request this runtime permission, you | |||
| // also need to declare it in your app's manifest. You can do so via | |||
| // the Introjucer. Otherwise this can't work. | |||
| jassertfalse; | |||
| callback (false); | |||
| return; | |||
| } | |||
| if (JUCE_ANDROID_API_VERSION < 23) | |||
| { | |||
| // There is no runtime permission system on API level below 23. As long as the | |||
| // permission is in the manifest (seems to be the case), we can simply ask Android | |||
| // if the app has the permission, and then directly call through to the callback. | |||
| callback (isGranted (permission)); | |||
| return; | |||
| } | |||
| // we need to move the callback object to the heap so Java can keep track of the pointer | |||
| // and asynchronously pass it back to us (to be called and then deleted) | |||
| Callback* callbackPtr = new Callback (std::move (callback)); | |||
| android.activity.callVoidMethod (JuceAppActivity.requestRuntimePermission, permission, (jlong) callbackPtr); | |||
| } | |||
| bool RuntimePermissions::isRequired (PermissionID /*permission*/) | |||
| { | |||
| return JUCE_ANDROID_API_VERSION >= 23; | |||
| } | |||
| bool RuntimePermissions::isGranted (PermissionID permission) | |||
| { | |||
| return android.activity.callBooleanMethod (JuceAppActivity.isPermissionGranted, permission); | |||
| } | |||
| @@ -84,10 +84,8 @@ struct AndroidThreadData | |||
| void JUCE_API juce_threadEntryPoint (void*); | |||
| extern "C" void* threadEntryProc (void*); | |||
| extern "C" void* threadEntryProc (void* userData) | |||
| void* threadEntryProc (AndroidThreadData* priv) | |||
| { | |||
| ScopedPointer<AndroidThreadData> priv (reinterpret_cast<AndroidThreadData*> (userData)); | |||
| priv->tId = (Thread::ThreadID) pthread_self(); | |||
| priv->eventSet.signal(); | |||
| priv->eventGet.wait (-1); | |||
| @@ -100,12 +98,10 @@ extern "C" void* threadEntryProc (void* userData) | |||
| JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, | |||
| void, (JNIEnv* env, jobject device, jlong host)) | |||
| { | |||
| // Java may create a Midi thread which JUCE doesn't know about and this callback may be | |||
| // received on this thread. Java will have already created a JNI Env for this new thread, | |||
| // which we need to tell Juce about | |||
| // This thread does not have a JNIEnv assigned to it yet. So assign it now. | |||
| setEnv (env); | |||
| if (Thread* thread = reinterpret_cast<Thread*> (host)) | |||
| if (AndroidThreadData* thread = reinterpret_cast<AndroidThreadData*> (host)) | |||
| threadEntryProc (thread); | |||
| } | |||
| @@ -114,8 +110,11 @@ void Thread::launchThread() | |||
| threadHandle = 0; | |||
| ScopedPointer<AndroidThreadData> threadPrivateData = new AndroidThreadData (this); | |||
| const LocalRef<jstring> jName (javaString (threadName)); | |||
| jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, (jlong) threadPrivateData.get()); | |||
| jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, | |||
| (jlong) threadPrivateData.get(), | |||
| jName.get(), (jlong) threadStackSize); | |||
| if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) | |||
| { | |||
| @@ -63,7 +63,7 @@ static String getLinkedFile (const String& file) | |||
| HeapBlock<char> buffer (8194); | |||
| const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192); | |||
| return String::fromUTF8 (buffer, jmax (0, numBytes)); | |||
| }; | |||
| } | |||
| bool File::isSymbolicLink() const | |||
| { | |||
| @@ -156,7 +156,10 @@ void CPUInformation::initialise() noexcept | |||
| hasSSE3 = flags.contains ("sse3"); | |||
| has3DNow = flags.contains ("3dnow"); | |||
| hasSSSE3 = flags.contains ("ssse3"); | |||
| hasSSE41 = flags.contains ("sse4_1"); | |||
| hasSSE42 = flags.contains ("sse4_2"); | |||
| hasAVX = flags.contains ("avx"); | |||
| hasAVX2 = flags.contains ("avx2"); | |||
| numCpus = LinuxStatsHelpers::getCpuInfo ("processor").getIntValue() + 1; | |||
| } | |||
| @@ -45,8 +45,14 @@ String String::fromCFString (CFStringRef cfString) | |||
| CFStringRef String::toCFString() const | |||
| { | |||
| const char* const utf8 = toRawUTF8(); | |||
| return CFStringCreateWithBytes (kCFAllocatorDefault, (const UInt8*) utf8, | |||
| (CFIndex) strlen (utf8), kCFStringEncodingUTF8, false); | |||
| if (CFStringRef result = CFStringCreateWithBytes (kCFAllocatorDefault, (const UInt8*) utf8, | |||
| (CFIndex) strlen (utf8), kCFStringEncodingUTF8, false)) | |||
| return result; | |||
| // If CFStringCreateWithBytes fails, it probably means there was a UTF8 format | |||
| // error, so we'll return an empty string rather than a null pointer. | |||
| return String().toCFString(); | |||
| } | |||
| String String::convertToPrecomposedUnicode() const | |||
| @@ -81,27 +81,17 @@ void CPUInformation::initialise() noexcept | |||
| has3DNow = (b & (1u << 31)) != 0; | |||
| hasSSE3 = (c & (1u << 0)) != 0; | |||
| hasSSSE3 = (c & (1u << 9)) != 0; | |||
| hasSSE41 = (c & (1u << 20)) != 0; | |||
| hasSSE42 = (c & (1u << 19)) != 0; | |||
| hasAVX = (c & (1u << 28)) != 0; | |||
| SystemStatsHelpers::doCPUID (a, b, c, d, 7); | |||
| hasAVX2 = (b & (1u << 5)) != 0; | |||
| #endif | |||
| numCpus = (int) [[NSProcessInfo processInfo] activeProcessorCount]; | |||
| } | |||
| #if JUCE_MAC | |||
| struct RLimitInitialiser | |||
| { | |||
| RLimitInitialiser() | |||
| { | |||
| rlimit lim; | |||
| getrlimit (RLIMIT_NOFILE, &lim); | |||
| lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; | |||
| setrlimit (RLIMIT_NOFILE, &lim); | |||
| } | |||
| }; | |||
| static RLimitInitialiser rLimitInitialiser; | |||
| #endif | |||
| //============================================================================== | |||
| #if ! JUCE_IOS | |||
| static String getOSXVersion() | |||
| @@ -55,6 +55,7 @@ WaitableEvent::WaitableEvent (const bool useManualReset) noexcept | |||
| pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT); | |||
| #endif | |||
| pthread_mutex_init (&mutex, &atts); | |||
| pthread_mutexattr_destroy (&atts); | |||
| } | |||
| WaitableEvent::~WaitableEvent() noexcept | |||
| @@ -149,6 +150,45 @@ void JUCE_CALLTYPE Process::terminate() | |||
| #endif | |||
| } | |||
| #if JUCE_MAC || JUCE_LINUX | |||
| bool Process::setMaxNumberOfFileHandles (int newMaxNumber) noexcept | |||
| { | |||
| rlimit lim; | |||
| if (getrlimit (RLIMIT_NOFILE, &lim) == 0) | |||
| { | |||
| if (newMaxNumber <= 0 && lim.rlim_cur == RLIM_INFINITY && lim.rlim_max == RLIM_INFINITY) | |||
| return true; | |||
| if (lim.rlim_cur >= (rlim_t) newMaxNumber) | |||
| return true; | |||
| } | |||
| lim.rlim_cur = lim.rlim_max = newMaxNumber <= 0 ? RLIM_INFINITY : (rlim_t) newMaxNumber; | |||
| return setrlimit (RLIMIT_NOFILE, &lim) == 0; | |||
| } | |||
| struct MaxNumFileHandlesInitialiser | |||
| { | |||
| MaxNumFileHandlesInitialiser() noexcept | |||
| { | |||
| #ifndef JUCE_PREFERRED_MAX_FILE_HANDLES | |||
| enum { JUCE_PREFERRED_MAX_FILE_HANDLES = 8192 }; | |||
| #endif | |||
| // Try to give our app a decent number of file handles by default | |||
| if (! Process::setMaxNumberOfFileHandles (0)) | |||
| { | |||
| for (int num = JUCE_PREFERRED_MAX_FILE_HANDLES; num > 256; num -= 1024) | |||
| if (Process::setMaxNumberOfFileHandles (num)) | |||
| break; | |||
| } | |||
| } | |||
| }; | |||
| static MaxNumFileHandlesInitialiser maxNumFileHandlesInitialiser; | |||
| #endif | |||
| //============================================================================== | |||
| const juce_wchar File::separator = '/'; | |||
| const String File::separatorString ("/"); | |||
| @@ -365,7 +405,7 @@ bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 | |||
| bool File::deleteFile() const | |||
| { | |||
| if (! exists()) | |||
| if (! exists() && ! isSymbolicLink()) | |||
| return true; | |||
| if (isDirectory()) | |||
| @@ -395,7 +435,7 @@ Result File::createDirectoryInternal (const String& fileName) const | |||
| return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777)); | |||
| } | |||
| //===================================================================== | |||
| //============================================================================== | |||
| int64 juce_fileSetPosition (void* handle, int64 pos) | |||
| { | |||
| if (handle != 0 && lseek (getFD (handle), pos, SEEK_SET) == pos) | |||
| @@ -885,13 +925,25 @@ void Thread::launchThread() | |||
| { | |||
| threadHandle = 0; | |||
| pthread_t handle = 0; | |||
| pthread_attr_t attr; | |||
| pthread_attr_t* attrPtr = nullptr; | |||
| if (pthread_create (&handle, 0, threadEntryProc, this) == 0) | |||
| if (pthread_attr_init (&attr) == 0) | |||
| { | |||
| attrPtr = &attr; | |||
| pthread_attr_setstacksize (attrPtr, threadStackSize); | |||
| } | |||
| if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) | |||
| { | |||
| pthread_detach (handle); | |||
| threadHandle = (void*) handle; | |||
| threadId = (ThreadID) threadHandle; | |||
| } | |||
| if (attrPtr != nullptr) | |||
| pthread_attr_destroy (attrPtr); | |||
| } | |||
| void Thread::closeThreadHandle() | |||
| @@ -1029,10 +1081,12 @@ public: | |||
| ActiveProcess (const StringArray& arguments, int streamFlags) | |||
| : childPID (0), pipeHandle (0), readHandle (0) | |||
| { | |||
| String exe (arguments[0].unquoted()); | |||
| // Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX | |||
| // you're trying to launch the .app folder rather than the actual binary inside it?) | |||
| jassert ((! arguments[0].containsChar ('/')) | |||
| || File::getCurrentWorkingDirectory().getChildFile (arguments[0]).existsAsFile()); | |||
| jassert (File::getCurrentWorkingDirectory().getChildFile (exe).existsAsFile() | |||
| || ! exe.containsChar (File::separator)); | |||
| int pipeHandles[2] = { 0 }; | |||
| @@ -1041,7 +1095,7 @@ public: | |||
| Array<char*> argv; | |||
| for (int i = 0; i < arguments.size(); ++i) | |||
| if (arguments[i].isNotEmpty()) | |||
| argv.add (const_cast<char*> (arguments[i].toUTF8().getAddress())); | |||
| argv.add (const_cast<char*> (arguments[i].toRawUTF8())); | |||
| argv.add (nullptr); | |||
| @@ -1075,7 +1129,7 @@ public: | |||
| close (pipeHandles[1]); | |||
| #endif | |||
| if (execvp (argv[0], argv.getRawDataPointer()) < 0) | |||
| if (execvp (exe.toRawUTF8(), argv.getRawDataPointer())) | |||
| _exit (-1); | |||
| } | |||
| else | |||
| @@ -31,13 +31,7 @@ struct RegistryKeyWrapper | |||
| RegistryKeyWrapper (String name, const bool createForWriting, const DWORD wow64Flags) | |||
| : key (0), wideCharValueName (nullptr) | |||
| { | |||
| HKEY rootKey = 0; | |||
| if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) rootKey = HKEY_CURRENT_USER; | |||
| else if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) rootKey = HKEY_LOCAL_MACHINE; | |||
| else if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) rootKey = HKEY_CLASSES_ROOT; | |||
| if (rootKey != 0) | |||
| if (HKEY rootKey = getRootKey (name)) | |||
| { | |||
| name = name.substring (name.indexOfChar ('\\') + 1); | |||
| @@ -63,6 +57,21 @@ struct RegistryKeyWrapper | |||
| RegCloseKey (key); | |||
| } | |||
| static HKEY getRootKey (const String& name) noexcept | |||
| { | |||
| if (name.startsWithIgnoreCase ("HKEY_CURRENT_USER\\")) return HKEY_CURRENT_USER; | |||
| if (name.startsWithIgnoreCase ("HKCU\\")) return HKEY_CURRENT_USER; | |||
| if (name.startsWithIgnoreCase ("HKEY_LOCAL_MACHINE\\")) return HKEY_LOCAL_MACHINE; | |||
| if (name.startsWithIgnoreCase ("HKLM\\")) return HKEY_LOCAL_MACHINE; | |||
| if (name.startsWithIgnoreCase ("HKEY_CLASSES_ROOT\\")) return HKEY_CLASSES_ROOT; | |||
| if (name.startsWithIgnoreCase ("HKCR\\")) return HKEY_CLASSES_ROOT; | |||
| if (name.startsWithIgnoreCase ("HKEY_USERS\\")) return HKEY_USERS; | |||
| if (name.startsWithIgnoreCase ("HKU\\")) return HKEY_USERS; | |||
| jassertfalse; // The name starts with an unknown root key (or maybe an old Win9x type) | |||
| return 0; | |||
| } | |||
| static bool setValue (const String& regValuePath, const DWORD type, | |||
| const void* data, size_t dataSize, const DWORD wow64Flags) | |||
| { | |||
| @@ -58,7 +58,7 @@ static void callCPUID (int result[4], int infoType) | |||
| __try | |||
| #endif | |||
| { | |||
| #if JUCE_GCC | |||
| #if JUCE_GCC || JUCE_CLANG | |||
| __asm__ __volatile__ ("cpuid" : "=a" (result[0]), "=b" (result[1]), "=c" (result[2]),"=d" (result[3]) : "a" (infoType)); | |||
| #else | |||
| __asm | |||
| @@ -107,8 +107,14 @@ void CPUInformation::initialise() noexcept | |||
| hasSSE3 = (info[2] & (1 << 0)) != 0; | |||
| hasAVX = (info[2] & (1 << 28)) != 0; | |||
| hasSSSE3 = (info[2] & (1 << 9)) != 0; | |||
| hasSSE41 = (info[2] & (1 << 19)) != 0; | |||
| hasSSE42 = (info[2] & (1 << 20)) != 0; | |||
| has3DNow = (info[1] & (1 << 31)) != 0; | |||
| callCPUID (info, 7); | |||
| hasAVX2 = (info[1] & (1 << 5)) != 0; | |||
| SYSTEM_INFO systemInfo; | |||
| GetNativeSystemInfo (&systemInfo); | |||
| numCpus = (int) systemInfo.dwNumberOfProcessors; | |||
| @@ -325,7 +331,7 @@ static int64 juce_getClockCycleCounter() noexcept | |||
| // MS intrinsics version... | |||
| return (int64) __rdtsc(); | |||
| #elif JUCE_GCC | |||
| #elif JUCE_GCC || JUCE_CLANG | |||
| // GNU inline asm version... | |||
| unsigned int hi = 0, lo = 0; | |||
| @@ -109,7 +109,8 @@ static unsigned int __stdcall threadEntryProc (void* userData) | |||
| void Thread::launchThread() | |||
| { | |||
| unsigned int newThreadId; | |||
| threadHandle = (void*) _beginthreadex (0, 0, &threadEntryProc, this, 0, &newThreadId); | |||
| threadHandle = (void*) _beginthreadex (0, (unsigned int) threadStackSize, | |||
| &threadEntryProc, this, 0, &newThreadId); | |||
| threadId = (ThreadID) (pointer_sized_int) newThreadId; | |||
| } | |||
| @@ -62,6 +62,11 @@ namespace SocketHelpers | |||
| #endif | |||
| } | |||
| inline bool isValidPortNumber (int port) noexcept | |||
| { | |||
| return isPositiveAndBelow (port, 65536); | |||
| } | |||
| template <typename Type> | |||
| static bool setOption (const SocketHandle handle, int mode, int property, Type value) noexcept | |||
| { | |||
| @@ -115,6 +120,7 @@ namespace SocketHelpers | |||
| { | |||
| // unblock any pending read requests | |||
| ::shutdown (h, SHUT_RDWR); | |||
| { | |||
| // see man-page of recv on linux about a race condition where the | |||
| // shutdown command is lost if the receiving thread does not have | |||
| @@ -133,37 +139,32 @@ namespace SocketHelpers | |||
| #endif | |||
| } | |||
| static bool bindSocket (const SocketHandle handle, const int port, const String& address) noexcept | |||
| static bool bindSocket (const SocketHandle handle, int port, const String& address) noexcept | |||
| { | |||
| if (handle <= 0 || port < 0) | |||
| if (handle <= 0 || ! isValidPortNumber (port)) | |||
| return false; | |||
| struct sockaddr_in servTmpAddr; | |||
| zerostruct (servTmpAddr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | |||
| servTmpAddr.sin_family = PF_INET; | |||
| servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); | |||
| servTmpAddr.sin_port = htons ((uint16) port); | |||
| struct sockaddr_in addr; | |||
| zerostruct (addr); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | |||
| #if JUCE_WINDOWS | |||
| if (address.isNotEmpty()) | |||
| servTmpAddr.sin_addr.s_addr = ::inet_addr (address.toUTF8()); | |||
| #else | |||
| ignoreUnused (address); | |||
| #endif | |||
| addr.sin_family = PF_INET; | |||
| addr.sin_port = htons ((uint16) port); | |||
| addr.sin_addr.s_addr = address.isNotEmpty() ? ::inet_addr (address.toRawUTF8()) | |||
| : htonl (INADDR_ANY); | |||
| return bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) >= 0; | |||
| return ::bind (handle, (struct sockaddr*) &addr, sizeof (addr)) >= 0; | |||
| } | |||
| static int getBoundPort (const SocketHandle handle) noexcept | |||
| { | |||
| if (handle <= 0) | |||
| return -1; | |||
| struct sockaddr_in sin_addr; | |||
| socklen_t len = sizeof (sin_addr); | |||
| if (handle > 0) | |||
| { | |||
| struct sockaddr_in addr; | |||
| socklen_t len = sizeof (addr); | |||
| if (getsockname (handle, (struct sockaddr*) &sin_addr, &len) == 0) | |||
| return ntohs (sin_addr.sin_port); | |||
| if (getsockname (handle, (struct sockaddr*) &addr, &len) == 0) | |||
| return ntohs (addr.sin_port); | |||
| } | |||
| return -1; | |||
| } | |||
| @@ -384,11 +385,11 @@ namespace SocketHelpers | |||
| struct ip_mreq mreq; | |||
| zerostruct (mreq); | |||
| mreq.imr_multiaddr.s_addr = inet_addr (multicastIPAddress.toUTF8()); | |||
| mreq.imr_multiaddr.s_addr = inet_addr (multicastIPAddress.toRawUTF8()); | |||
| mreq.imr_interface.s_addr = INADDR_ANY; | |||
| if (interfaceIPAddress.isNotEmpty()) | |||
| mreq.imr_interface.s_addr = inet_addr (interfaceIPAddress.toUTF8()); | |||
| mreq.imr_interface.s_addr = inet_addr (interfaceIPAddress.toRawUTF8()); | |||
| return setsockopt (handle, IPPROTO_IP, | |||
| join ? IP_ADD_MEMBERSHIP | |||
| @@ -414,6 +415,8 @@ StreamingSocket::StreamingSocket (const String& host, int portNum, int h) | |||
| connected (true), | |||
| isListener (false) | |||
| { | |||
| jassert (SocketHelpers::isValidPortNumber (portNum)); | |||
| SocketHelpers::initSockets(); | |||
| SocketHelpers::resetSocketOptions (h, false, false); | |||
| } | |||
| @@ -455,6 +458,8 @@ bool StreamingSocket::bindToPort (const int port) | |||
| bool StreamingSocket::bindToPort (const int port, const String& addr) | |||
| { | |||
| jassert (SocketHelpers::isValidPortNumber (port)); | |||
| return SocketHelpers::bindSocket (handle, port, addr); | |||
| } | |||
| @@ -467,6 +472,8 @@ bool StreamingSocket::connect (const String& remoteHostName, | |||
| const int remotePortNumber, | |||
| const int timeOutMillisecs) | |||
| { | |||
| jassert (SocketHelpers::isValidPortNumber (remotePortNumber)); | |||
| if (isListener) | |||
| { | |||
| jassertfalse; // a listener socket can't connect to another one! | |||
| @@ -505,6 +512,8 @@ void StreamingSocket::close() | |||
| //============================================================================== | |||
| bool StreamingSocket::createListener (const int newPortNumber, const String& localHostName) | |||
| { | |||
| jassert (SocketHelpers::isValidPortNumber (newPortNumber)); | |||
| if (connected) | |||
| close(); | |||
| @@ -512,17 +521,6 @@ bool StreamingSocket::createListener (const int newPortNumber, const String& loc | |||
| portNumber = newPortNumber; | |||
| isListener = true; | |||
| struct sockaddr_in servTmpAddr; | |||
| zerostruct (servTmpAddr); | |||
| servTmpAddr.sin_family = PF_INET; | |||
| servTmpAddr.sin_addr.s_addr = htonl (INADDR_ANY); | |||
| if (localHostName.isNotEmpty()) | |||
| servTmpAddr.sin_addr.s_addr = ::inet_addr (localHostName.toUTF8()); | |||
| servTmpAddr.sin_port = htons ((uint16) portNumber); | |||
| handle = (int) socket (AF_INET, SOCK_STREAM, 0); | |||
| if (handle < 0) | |||
| @@ -532,15 +530,15 @@ bool StreamingSocket::createListener (const int newPortNumber, const String& loc | |||
| SocketHelpers::makeReusable (handle); | |||
| #endif | |||
| if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 | |||
| || listen (handle, SOMAXCONN) < 0) | |||
| if (SocketHelpers::bindSocket (handle, portNumber, localHostName) | |||
| && listen (handle, SOMAXCONN) >= 0) | |||
| { | |||
| close(); | |||
| return false; | |||
| connected = true; | |||
| return true; | |||
| } | |||
| connected = true; | |||
| return true; | |||
| close(); | |||
| return false; | |||
| } | |||
| StreamingSocket* StreamingSocket::waitForNextConnection() const | |||
| @@ -614,14 +612,12 @@ bool DatagramSocket::bindToPort (const int port) | |||
| bool DatagramSocket::bindToPort (const int port, const String& addr) | |||
| { | |||
| if (handle < 0) | |||
| return false; | |||
| jassert (SocketHelpers::isValidPortNumber (port)); | |||
| if (SocketHelpers::bindSocket (handle, port, addr)) | |||
| { | |||
| isBound = true; | |||
| lastBindAddress = addr; | |||
| return true; | |||
| } | |||
| @@ -673,6 +669,8 @@ int DatagramSocket::read (void* destBuffer, int maxBytesToRead, bool shouldBlock | |||
| int DatagramSocket::write (const String& remoteHostname, int remotePortNumber, | |||
| const void* sourceBuffer, int numBytesToWrite) | |||
| { | |||
| jassert (SocketHelpers::isValidPortNumber (remotePortNumber)); | |||
| if (handle < 0) | |||
| return -1; | |||
| @@ -109,7 +109,7 @@ | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_DEBUG || DOXYGEN | |||
| #if (JUCE_DEBUG && ! JUCE_DISABLE_ASSERTIONS) || DOXYGEN | |||
| /** Writes a string to the standard error stream. | |||
| Note that as well as a single string, you can use this to write multiple items | |||
| as a stream, e.g. | |||
| @@ -290,7 +290,7 @@ | |||
| #elif JUCE_MSVC && ! JUCE_NO_DEPRECATION_WARNINGS | |||
| #define JUCE_DEPRECATED(functionDef) __declspec(deprecated) functionDef | |||
| #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) __declspec(deprecated) functionDef body | |||
| #elif JUCE_GCC && ! JUCE_NO_DEPRECATION_WARNINGS | |||
| #elif (JUCE_GCC || JUCE_CLANG) && ! JUCE_NO_DEPRECATION_WARNINGS | |||
| #define JUCE_DEPRECATED(functionDef) functionDef __attribute__ ((deprecated)) | |||
| #define JUCE_DEPRECATED_WITH_BODY(functionDef, body) functionDef __attribute__ ((deprecated)) body | |||
| #else | |||
| @@ -308,10 +308,19 @@ | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_GCC | |||
| #if JUCE_GCC || JUCE_CLANG | |||
| #define JUCE_PACKED __attribute__((packed)) | |||
| #elif ! DOXYGEN | |||
| #define JUCE_PACKED | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_GCC || DOXYGEN | |||
| /** This can be appended to a function declaration to tell gcc to disable associative | |||
| math optimisations which break some floating point algorithms. */ | |||
| #define JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS __attribute__((__optimize__("no-associative-math"))) | |||
| #else | |||
| #define JUCE_NO_ASSOCIATIVE_MATH_OPTIMISATIONS | |||
| #endif | |||
| #endif // JUCE_PLATFORMDEFS_H_INCLUDED | |||
| @@ -68,7 +68,8 @@ struct CPUInformation | |||
| CPUInformation() noexcept | |||
| : numCpus (0), hasMMX (false), hasSSE (false), | |||
| hasSSE2 (false), hasSSE3 (false), has3DNow (false), | |||
| hasSSSE3 (false), hasAVX (false) | |||
| hasSSSE3 (false), hasSSE41 (false), hasSSE42 (false), | |||
| hasAVX (false), hasAVX2 (false) | |||
| { | |||
| initialise(); | |||
| } | |||
| @@ -76,7 +77,7 @@ struct CPUInformation | |||
| void initialise() noexcept; | |||
| int numCpus; | |||
| bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow, hasSSSE3, hasAVX; | |||
| bool hasMMX, hasSSE, hasSSE2, hasSSE3, has3DNow, hasSSSE3, hasSSE41, hasSSE42, hasAVX, hasAVX2; | |||
| }; | |||
| static const CPUInformation& getCPUInformation() noexcept | |||
| @@ -92,7 +93,10 @@ bool SystemStats::hasSSE() noexcept { return getCPUInformation().hasSS | |||
| bool SystemStats::hasSSE2() noexcept { return getCPUInformation().hasSSE2; } | |||
| bool SystemStats::hasSSE3() noexcept { return getCPUInformation().hasSSE3; } | |||
| bool SystemStats::hasSSSE3() noexcept { return getCPUInformation().hasSSSE3; } | |||
| bool SystemStats::hasSSE41() noexcept { return getCPUInformation().hasSSE41; } | |||
| bool SystemStats::hasSSE42() noexcept { return getCPUInformation().hasSSE42; } | |||
| bool SystemStats::hasAVX() noexcept { return getCPUInformation().hasAVX; } | |||
| bool SystemStats::hasAVX2() noexcept { return getCPUInformation().hasAVX2; } | |||
| //============================================================================== | |||
| @@ -152,13 +152,16 @@ public: | |||
| */ | |||
| static String getCpuVendor(); | |||
| static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ | |||
| static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ | |||
| static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ | |||
| static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ | |||
| static bool hasSSE3() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ | |||
| static bool hasSSSE3() noexcept; /**< Returns true if Intel SSSE3 instructions are available. */ | |||
| static bool hasAVX() noexcept; /**< Returns true if Intel AVX instructions are available. */ | |||
| static bool hasMMX() noexcept; /**< Returns true if Intel MMX instructions are available. */ | |||
| static bool has3DNow() noexcept; /**< Returns true if AMD 3DNOW instructions are available. */ | |||
| static bool hasSSE() noexcept; /**< Returns true if Intel SSE instructions are available. */ | |||
| static bool hasSSE2() noexcept; /**< Returns true if Intel SSE2 instructions are available. */ | |||
| static bool hasSSE3() noexcept; /**< Returns true if Intel SSE3 instructions are available. */ | |||
| static bool hasSSSE3() noexcept; /**< Returns true if Intel SSSE3 instructions are available. */ | |||
| static bool hasSSE41() noexcept; /**< Returns true if Intel SSE4.1 instructions are available. */ | |||
| static bool hasSSE42() noexcept; /**< Returns true if Intel SSE4.2 instructions are available. */ | |||
| static bool hasAVX() noexcept; /**< Returns true if Intel AVX instructions are available. */ | |||
| static bool hasAVX2() noexcept; /**< Returns true if Intel AVX2 instructions are available. */ | |||
| //============================================================================== | |||
| /** Finds out how much RAM is in the machine. | |||
| @@ -39,7 +39,7 @@ | |||
| - Either JUCE_32BIT or JUCE_64BIT, depending on the architecture. | |||
| - Either JUCE_LITTLE_ENDIAN or JUCE_BIG_ENDIAN. | |||
| - Either JUCE_INTEL or JUCE_PPC | |||
| - Either JUCE_GCC or JUCE_MSVC | |||
| - Either JUCE_GCC or JUCE_CLANG or JUCE_MSVC | |||
| */ | |||
| //============================================================================== | |||
| @@ -61,12 +61,8 @@ | |||
| #elif defined (LINUX) || defined (__linux__) | |||
| #define JUCE_LINUX 1 | |||
| #elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #define Component CarbonDummyCompName | |||
| #include <CoreFoundation/CoreFoundation.h> // (needed to find out what platform we're using) | |||
| #include "../native/juce_mac_ClangBugWorkaround.h" | |||
| #undef Point | |||
| #undef Component | |||
| #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | |||
| #define JUCE_IPHONE 1 | |||
| @@ -183,7 +179,6 @@ | |||
| #ifdef __clang__ | |||
| #define JUCE_CLANG 1 | |||
| #define JUCE_GCC 1 | |||
| #elif defined (__GNUC__) | |||
| #define JUCE_GCC 1 | |||
| #elif defined (_MSC_VER) | |||
| @@ -520,6 +520,9 @@ public: | |||
| return false; | |||
| } | |||
| if (numExtraValues == 0) | |||
| return false; | |||
| maxBytesToRead -= numExtraValues; | |||
| if (maxBytesToRead < 0) | |||
| return false; | |||
| @@ -144,6 +144,15 @@ public: | |||
| static void setDockIconVisible (bool isVisible); | |||
| #endif | |||
| #if JUCE_MAC || JUCE_LINUX || DOXYGEN | |||
| //============================================================================== | |||
| /** UNIX ONLY - Attempts to use setrlimit to change the maximum number of file | |||
| handles that the app can open. Pass 0 or less as the parameter to mean | |||
| 'infinite'. Returns true if it succeeds. | |||
| */ | |||
| static bool setMaxNumberOfFileHandles (int maxNumberOfFiles) noexcept; | |||
| #endif | |||
| private: | |||
| Process(); | |||
| JUCE_DECLARE_NON_COPYABLE (Process) | |||
| @@ -26,11 +26,12 @@ | |||
| ============================================================================== | |||
| */ | |||
| Thread::Thread (const String& threadName_) | |||
| Thread::Thread (const String& threadName_, const size_t stackSize) | |||
| : threadName (threadName_), | |||
| threadHandle (nullptr), | |||
| threadId (0), | |||
| threadPriority (5), | |||
| threadStackSize (stackSize), | |||
| affinityMask (0), | |||
| shouldExit (false) | |||
| { | |||
| @@ -53,8 +53,14 @@ public: | |||
| When first created, the thread is not running. Use the startThread() | |||
| method to start it. | |||
| @param threadName The name of the thread which typically appears in | |||
| debug logs and profiles. | |||
| @param threadStackSize The size of the stack of the thread. If this value | |||
| is zero then the default stack size of the OS will | |||
| be used. | |||
| */ | |||
| explicit Thread (const String& threadName); | |||
| explicit Thread (const String& threadName, size_t threadStackSize = 0); | |||
| /** Destructor. | |||
| @@ -270,6 +276,7 @@ private: | |||
| CriticalSection startStopLock; | |||
| WaitableEvent startSuspensionEvent, defaultEvent; | |||
| int threadPriority; | |||
| size_t threadStackSize; | |||
| uint32 affinityMask; | |||
| bool volatile shouldExit; | |||
| @@ -28,55 +28,64 @@ | |||
| namespace TimeHelpers | |||
| { | |||
| static struct tm millisToLocal (const int64 millis) noexcept | |||
| static std::tm millisToLocal (int64 millis) noexcept | |||
| { | |||
| struct tm result; | |||
| const int64 seconds = millis / 1000; | |||
| #if JUCE_WINDOWS && JUCE_MINGW | |||
| time_t now = (time_t) (millis / 1000); | |||
| return *localtime (&now); | |||
| if (seconds < 86400LL || seconds >= 2145916800LL) | |||
| { | |||
| // use extended maths for dates beyond 1970 to 2037.. | |||
| const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000); | |||
| const int64 jdm = seconds + timeZoneAdjustment + 210866803200LL; | |||
| const int days = (int) (jdm / 86400LL); | |||
| const int a = 32044 + days; | |||
| const int b = (4 * a + 3) / 146097; | |||
| const int c = a - (b * 146097) / 4; | |||
| const int d = (4 * c + 3) / 1461; | |||
| const int e = c - (d * 1461) / 4; | |||
| const int m = (5 * e + 2) / 153; | |||
| result.tm_mday = e - (153 * m + 2) / 5 + 1; | |||
| result.tm_mon = m + 2 - 12 * (m / 10); | |||
| result.tm_year = b * 100 + d - 6700 + (m / 10); | |||
| result.tm_wday = (days + 1) % 7; | |||
| result.tm_yday = -1; | |||
| int t = (int) (jdm % 86400LL); | |||
| result.tm_hour = t / 3600; | |||
| t %= 3600; | |||
| result.tm_min = t / 60; | |||
| result.tm_sec = t % 60; | |||
| result.tm_isdst = -1; | |||
| } | |||
| else | |||
| { | |||
| time_t now = static_cast<time_t> (seconds); | |||
| #if JUCE_WINDOWS && JUCE_MINGW | |||
| return *localtime (&now); | |||
| #elif JUCE_WINDOWS | |||
| if (now >= 0 && now <= 0x793406fff) | |||
| localtime_s (&result, &now); | |||
| else | |||
| zerostruct (result); | |||
| #else | |||
| localtime_r (&now, &result); // more thread-safe | |||
| #endif | |||
| } | |||
| #elif JUCE_WINDOWS | |||
| std::tm result; | |||
| millis /= 1000; | |||
| if (_localtime64_s (&result, &millis) != 0) | |||
| zerostruct (result); | |||
| return result; | |||
| #else | |||
| std::tm result; | |||
| time_t now = (time_t) (millis / 1000); | |||
| if (localtime_r (&now, &result) == nullptr) | |||
| zerostruct (result); | |||
| return result; | |||
| #endif | |||
| } | |||
| static std::tm millisToUTC (int64 millis) noexcept | |||
| { | |||
| #if JUCE_WINDOWS && JUCE_MINGW | |||
| time_t now = (time_t) (millis / 1000); | |||
| return *gmtime (&now); | |||
| #elif JUCE_WINDOWS | |||
| std::tm result; | |||
| millis /= 1000; | |||
| if (_gmtime64_s (&result, &millis) != 0) | |||
| zerostruct (result); | |||
| return result; | |||
| #else | |||
| std::tm result; | |||
| time_t now = (time_t) (millis / 1000); | |||
| if (gmtime_r (&now, &result) == nullptr) | |||
| zerostruct (result); | |||
| return result; | |||
| #endif | |||
| } | |||
| static int getUTCOffsetSeconds (const int64 millis) noexcept | |||
| { | |||
| std::tm utc = millisToUTC (millis); | |||
| utc.tm_isdst = -1; // Treat this UTC time as local to find the offset | |||
| return (int) ((millis / 1000) - (int64) mktime (&utc)); | |||
| } | |||
| static int extendedModulo (const int64 value, const int modulo) noexcept | |||
| @@ -85,7 +94,7 @@ namespace TimeHelpers | |||
| : (value - ((value / modulo) + 1) * modulo)); | |||
| } | |||
| static inline String formatString (const String& format, const struct tm* const tm) | |||
| static inline String formatString (const String& format, const std::tm* const tm) | |||
| { | |||
| #if JUCE_ANDROID | |||
| typedef CharPointer_UTF8 StringType; | |||
| @@ -95,17 +104,23 @@ namespace TimeHelpers | |||
| typedef CharPointer_UTF32 StringType; | |||
| #endif | |||
| #ifdef JUCE_MSVC | |||
| if (tm->tm_year < -1900 || tm->tm_year > 8099) | |||
| return String(); // Visual Studio's library can only handle 0 -> 9999 AD | |||
| #endif | |||
| for (size_t bufferSize = 256; ; bufferSize += 256) | |||
| { | |||
| HeapBlock<StringType::CharType> buffer (bufferSize); | |||
| #if JUCE_ANDROID | |||
| const size_t numChars = strftime (buffer, bufferSize - 1, format.toUTF8(), tm); | |||
| #elif JUCE_WINDOWS | |||
| const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); | |||
| #else | |||
| const size_t numChars = wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); | |||
| #endif | |||
| const size_t numChars = | |||
| #if JUCE_ANDROID | |||
| strftime (buffer, bufferSize - 1, format.toUTF8(), tm); | |||
| #elif JUCE_WINDOWS | |||
| wcsftime (buffer, bufferSize - 1, format.toWideCharPointer(), tm); | |||
| #else | |||
| wcsftime (buffer, bufferSize - 1, format.toUTF32(), tm); | |||
| #endif | |||
| if (numChars > 0 || format.isEmpty()) | |||
| return String (StringType (buffer), | |||
| @@ -113,22 +128,70 @@ namespace TimeHelpers | |||
| } | |||
| } | |||
| //============================================================================== | |||
| static inline bool isLeapYear (int year) noexcept | |||
| { | |||
| return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0)); | |||
| } | |||
| static inline int daysFromJan1 (int year, int month) noexcept | |||
| { | |||
| const short dayOfYear[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, | |||
| 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; | |||
| return dayOfYear [(isLeapYear (year) ? 12 : 0) + month]; | |||
| } | |||
| static inline int64 daysFromYear0 (int year) noexcept | |||
| { | |||
| return 365 * (year - 1) + (year / 400) - (year / 100) + (year / 4); | |||
| } | |||
| static inline int64 daysFrom1970 (int year) noexcept | |||
| { | |||
| return daysFromYear0 (year) - daysFromYear0 (1970); | |||
| } | |||
| static inline int64 daysFrom1970 (int year, int month) noexcept | |||
| { | |||
| if (month > 11) | |||
| { | |||
| year += month / 12; | |||
| month %= 12; | |||
| } | |||
| else if (month < 0) | |||
| { | |||
| const int numYears = (11 - month) / 12; | |||
| year -= numYears; | |||
| month += 12 * numYears; | |||
| } | |||
| return daysFrom1970 (year) + daysFromJan1 (year, month); | |||
| } | |||
| // There's no posix function that does a UTC version of mktime, | |||
| // so annoyingly we need to implement this manually.. | |||
| static inline int64 mktime_utc (const std::tm& t) noexcept | |||
| { | |||
| return 24 * 3600 * (daysFrom1970 (t.tm_year + 1900, t.tm_mon) + (t.tm_mday - 1)) | |||
| + 3600 * t.tm_hour | |||
| + 60 * t.tm_min | |||
| + t.tm_sec; | |||
| } | |||
| static uint32 lastMSCounterValue = 0; | |||
| } | |||
| //============================================================================== | |||
| Time::Time() noexcept | |||
| : millisSinceEpoch (0) | |||
| Time::Time() noexcept : millisSinceEpoch (0) | |||
| { | |||
| } | |||
| Time::Time (const Time& other) noexcept | |||
| : millisSinceEpoch (other.millisSinceEpoch) | |||
| Time::Time (const Time& other) noexcept : millisSinceEpoch (other.millisSinceEpoch) | |||
| { | |||
| } | |||
| Time::Time (const int64 ms) noexcept | |||
| : millisSinceEpoch (ms) | |||
| Time::Time (const int64 ms) noexcept : millisSinceEpoch (ms) | |||
| { | |||
| } | |||
| @@ -141,41 +204,26 @@ Time::Time (const int year, | |||
| const int milliseconds, | |||
| const bool useLocalTime) noexcept | |||
| { | |||
| jassert (year > 100); // year must be a 4-digit version | |||
| if (year < 1971 || year >= 2038 || ! useLocalTime) | |||
| std::tm t; | |||
| t.tm_year = year - 1900; | |||
| t.tm_mon = month; | |||
| t.tm_mday = day; | |||
| t.tm_hour = hours; | |||
| t.tm_min = minutes; | |||
| t.tm_sec = seconds; | |||
| t.tm_isdst = -1; | |||
| const int64 time = useLocalTime ? (int64) mktime (&t) | |||
| : TimeHelpers::mktime_utc (t); | |||
| if (time >= 0) | |||
| { | |||
| // use extended maths for dates beyond 1970 to 2037.. | |||
| const int timeZoneAdjustment = useLocalTime ? (31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000)) | |||
| : 0; | |||
| const int a = (13 - month) / 12; | |||
| const int y = year + 4800 - a; | |||
| const int jd = day + (153 * (month + 12 * a - 2) + 2) / 5 | |||
| + (y * 365) + (y / 4) - (y / 100) + (y / 400) | |||
| - 32045; | |||
| const int64 s = ((int64) jd) * 86400LL - 210866803200LL; | |||
| millisSinceEpoch = 1000 * (s + (hours * 3600 + minutes * 60 + seconds - timeZoneAdjustment)) | |||
| + milliseconds; | |||
| millisSinceEpoch = 1000 * time + milliseconds; | |||
| } | |||
| else | |||
| { | |||
| struct tm t; | |||
| t.tm_year = year - 1900; | |||
| t.tm_mon = month; | |||
| t.tm_mday = day; | |||
| t.tm_hour = hours; | |||
| t.tm_min = minutes; | |||
| t.tm_sec = seconds; | |||
| t.tm_isdst = -1; | |||
| millisSinceEpoch = 1000 * (int64) mktime (&t); | |||
| if (millisSinceEpoch < 0) | |||
| millisSinceEpoch = 0; | |||
| else | |||
| millisSinceEpoch += milliseconds; | |||
| jassertfalse; // trying to create a date that is beyond the range that mktime supports! | |||
| millisSinceEpoch = 0; | |||
| } | |||
| } | |||
| @@ -315,7 +363,7 @@ String Time::toString (const bool includeDate, | |||
| String Time::formatted (const String& format) const | |||
| { | |||
| struct tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); | |||
| std::tm t (TimeHelpers::millisToLocal (millisSinceEpoch)); | |||
| return TimeHelpers::formatString (format, &t); | |||
| } | |||
| @@ -388,6 +436,117 @@ String Time::getTimeZone() const noexcept | |||
| return zone[0].substring (0, 3); | |||
| } | |||
| int Time::getUTCOffsetSeconds() const noexcept | |||
| { | |||
| return TimeHelpers::getUTCOffsetSeconds (millisSinceEpoch); | |||
| } | |||
| String Time::getUTCOffsetString (bool includeSemiColon) const | |||
| { | |||
| if (int seconds = getUTCOffsetSeconds()) | |||
| { | |||
| const int minutes = seconds / 60; | |||
| return String::formatted (includeSemiColon ? "%+03d:%02d" | |||
| : "%+03d%02d", | |||
| minutes / 60, | |||
| minutes % 60); | |||
| } | |||
| return "Z"; | |||
| } | |||
| String Time::toISO8601 (bool includeDividerCharacters) const | |||
| { | |||
| return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%02.03f" | |||
| : "%04d%02d%02dT%02d%02d%02.03f", | |||
| getYear(), | |||
| getMonth() + 1, | |||
| getDayOfMonth(), | |||
| getHours(), | |||
| getMinutes(), | |||
| getSeconds() + getMilliseconds() / 1000.0) | |||
| + getUTCOffsetString (includeDividerCharacters); | |||
| } | |||
| static int parseFixedSizeIntAndSkip (String::CharPointerType& t, int numChars, char charToSkip) noexcept | |||
| { | |||
| int n = 0; | |||
| for (int i = numChars; --i >= 0;) | |||
| { | |||
| const int digit = (int) (*t - '0'); | |||
| if (! isPositiveAndBelow (digit, 10)) | |||
| return -1; | |||
| ++t; | |||
| n = n * 10 + digit; | |||
| } | |||
| if (charToSkip != 0 && *t == (juce_wchar) charToSkip) | |||
| ++t; | |||
| return n; | |||
| } | |||
| Time Time::fromISO8601 (StringRef iso) noexcept | |||
| { | |||
| String::CharPointerType t = iso.text; | |||
| const int year = parseFixedSizeIntAndSkip (t, 4, '-'); | |||
| if (year < 0) | |||
| return Time(); | |||
| const int month = parseFixedSizeIntAndSkip (t, 2, '-'); | |||
| if (month < 0) | |||
| return Time(); | |||
| const int day = parseFixedSizeIntAndSkip (t, 2, 0); | |||
| if (day < 0) | |||
| return Time(); | |||
| int hours = 0, minutes = 0, milliseconds = 0; | |||
| if (*t == 'T') | |||
| { | |||
| ++t; | |||
| hours = parseFixedSizeIntAndSkip (t, 2, ':'); | |||
| if (hours < 0) | |||
| return Time(); | |||
| minutes = parseFixedSizeIntAndSkip (t, 2, ':'); | |||
| if (minutes < 0) | |||
| return Time(); | |||
| milliseconds = (int) (1000.0 * CharacterFunctions::readDoubleValue (t)); | |||
| } | |||
| const juce_wchar nextChar = t.getAndAdvance(); | |||
| if (nextChar == '-' || nextChar == '+') | |||
| { | |||
| const int offsetHours = parseFixedSizeIntAndSkip (t, 2, ':'); | |||
| if (offsetHours < 0) | |||
| return Time(); | |||
| const int offsetMinutes = parseFixedSizeIntAndSkip (t, 2, 0); | |||
| if (offsetMinutes < 0) | |||
| return Time(); | |||
| const int offsetMs = (offsetHours * 60 + offsetMinutes) * 60 * 1000; | |||
| milliseconds += nextChar == '-' ? offsetMs : -offsetMs; // NB: this seems backwards but is correct! | |||
| } | |||
| else if (nextChar != 0 && nextChar != 'Z') | |||
| { | |||
| return Time(); | |||
| } | |||
| Time result (year, month - 1, day, hours, minutes, 0, 0, false); | |||
| result.millisSinceEpoch += milliseconds; | |||
| return result; | |||
| } | |||
| String Time::getMonthName (const bool threeLetterVersion) const | |||
| { | |||
| return getMonthName (getMonth(), threeLetterVersion); | |||
| @@ -442,19 +601,76 @@ static int getMonthNumberForCompileDate (const String& m) noexcept | |||
| if (m.equalsIgnoreCase (shortMonthNames[i])) | |||
| return i; | |||
| // If you hit this because your compiler has a non-standard __DATE__ format, | |||
| // let me know so we can add support for it! | |||
| // If you hit this because your compiler has an unusual __DATE__ | |||
| // format, let us know so we can add support for it! | |||
| jassertfalse; | |||
| return 0; | |||
| } | |||
| Time Time::getCompilationDate() | |||
| { | |||
| StringArray dateTokens; | |||
| StringArray dateTokens, timeTokens; | |||
| dateTokens.addTokens (__DATE__, true); | |||
| dateTokens.removeEmptyStrings (true); | |||
| timeTokens.addTokens (__TIME__, ":", StringRef()); | |||
| return Time (dateTokens[2].getIntValue(), | |||
| getMonthNumberForCompileDate (dateTokens[0]), | |||
| dateTokens[1].getIntValue(), 12, 0); | |||
| dateTokens[1].getIntValue(), | |||
| timeTokens[0].getIntValue(), | |||
| timeTokens[1].getIntValue()); | |||
| } | |||
| //============================================================================== | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| class TimeTests : public UnitTest | |||
| { | |||
| public: | |||
| TimeTests() : UnitTest ("Time") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("Time"); | |||
| Time t = Time::getCurrentTime(); | |||
| expect (t > Time()); | |||
| Thread::sleep (15); | |||
| expect (Time::getCurrentTime() > t); | |||
| expect (t.getTimeZone().isNotEmpty()); | |||
| expect (t.getUTCOffsetString (true) == "Z" || t.getUTCOffsetString (true).length() == 6); | |||
| expect (t.getUTCOffsetString (false) == "Z" || t.getUTCOffsetString (false).length() == 5); | |||
| expect (Time::fromISO8601 (t.toISO8601 (true)) == t); | |||
| expect (Time::fromISO8601 (t.toISO8601 (false)) == t); | |||
| expect (Time::fromISO8601 ("2016-02-16") == Time (2016, 1, 16, 0, 0, 0, 0, false)); | |||
| expect (Time::fromISO8601 ("20160216Z") == Time (2016, 1, 16, 0, 0, 0, 0, false)); | |||
| expect (Time::fromISO8601 ("2016-02-16T15:03:57+00:00") == Time (2016, 1, 16, 15, 3, 57, 0, false)); | |||
| expect (Time::fromISO8601 ("20160216T150357+0000") == Time (2016, 1, 16, 15, 3, 57, 0, false)); | |||
| expect (Time::fromISO8601 ("2016-02-16T15:03:57.999+00:00") == Time (2016, 1, 16, 15, 3, 57, 999, false)); | |||
| expect (Time::fromISO8601 ("20160216T150357.999+0000") == Time (2016, 1, 16, 15, 3, 57, 999, false)); | |||
| expect (Time::fromISO8601 ("2016-02-16T15:03:57.999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false)); | |||
| expect (Time::fromISO8601 ("20160216T150357.999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false)); | |||
| expect (Time::fromISO8601 ("2016-02-16T15:03:57.999-02:30") == Time (2016, 1, 16, 17, 33, 57, 999, false)); | |||
| expect (Time::fromISO8601 ("20160216T150357.999-0230") == Time (2016, 1, 16, 17, 33, 57, 999, false)); | |||
| expect (Time (1982, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, true)); | |||
| expect (Time (1970, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, true)); | |||
| expect (Time (2038, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, true)); | |||
| expect (Time (1982, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, false)); | |||
| expect (Time (1970, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, false)); | |||
| expect (Time (2038, 1, 1, 12, 0, 0, 0, false) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, false)); | |||
| } | |||
| }; | |||
| static TimeTests timeTests; | |||
| #endif | |||