@@ -52,4 +52,5 @@ libs/juce/source/juce_amalgamated.mm | |||
libs/juce/source/modules/juce_box2d/ | |||
libs/juce/source/modules/juce_browser_plugin_client/ | |||
libs/juce/source/modules/juce_opengl/ | |||
libs/juce/source/modules/juce_osc/ | |||
libs/juce/source/modules/juce_video/ |
@@ -91,7 +91,7 @@ | |||
#include "TDStretch.cpp" | |||
#if JUCE_64BIT | |||
#if JUCE_WIN | |||
#if JUCE_WINDOWS | |||
#include "cpu_detect_x64_win.cpp" | |||
#elif JUCE_MAC || JUCE_IOS || JUCE_LINUX | |||
#include "cpu_detect_x64_gcc.cpp" | |||
@@ -42,7 +42,7 @@ | |||
#include "cpu_detect.h" | |||
#ifndef WIN64 | |||
#ifndef _WIN64 | |||
#error wrong platform - this source code file is exclusively for Win64 platform | |||
#endif | |||
@@ -156,7 +156,7 @@ private: | |||
if (xml == nullptr) | |||
return "invalid XmlElement"; | |||
return newLine + xml->createDocument (String::empty, false, includeXmlHeader); | |||
return String(NewLine::getDefault()) + xml->createDocument (String::empty, false, includeXmlHeader); | |||
} | |||
String getStringFromValueTree() const | |||
@@ -1,673 +0,0 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
AudioSampleBuffer::AudioSampleBuffer() noexcept | |||
: numChannels (0), size (0), allocatedBytes (0), | |||
channels (static_cast<float**> (preallocatedChannelSpace)), | |||
isClear (false) | |||
{ | |||
} | |||
AudioSampleBuffer::AudioSampleBuffer (const int numChans, | |||
const int numSamples) noexcept | |||
: numChannels (numChans), | |||
size (numSamples) | |||
{ | |||
jassert (numSamples >= 0); | |||
jassert (numChans >= 0); | |||
allocateData(); | |||
} | |||
AudioSampleBuffer::AudioSampleBuffer (const AudioSampleBuffer& other) noexcept | |||
: numChannels (other.numChannels), | |||
size (other.size), | |||
allocatedBytes (other.allocatedBytes) | |||
{ | |||
if (allocatedBytes == 0) | |||
{ | |||
allocateChannels (other.channels, 0); | |||
} | |||
else | |||
{ | |||
allocateData(); | |||
if (other.isClear) | |||
{ | |||
clear(); | |||
} | |||
else | |||
{ | |||
for (int i = 0; i < numChannels; ++i) | |||
FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::allocateData() | |||
{ | |||
const size_t channelListSize = sizeof (float*) * (size_t) (numChannels + 1); | |||
allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (float) + channelListSize + 32; | |||
allocatedData.malloc (allocatedBytes); | |||
channels = reinterpret_cast<float**> (allocatedData.getData()); | |||
float* chan = (float*) (allocatedData + channelListSize); | |||
for (int i = 0; i < numChannels; ++i) | |||
{ | |||
channels[i] = chan; | |||
chan += size; | |||
} | |||
channels [numChannels] = nullptr; | |||
isClear = false; | |||
} | |||
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||
const int numChans, | |||
const int numSamples) noexcept | |||
: numChannels (numChans), | |||
size (numSamples), | |||
allocatedBytes (0) | |||
{ | |||
jassert (dataToReferTo != nullptr); | |||
jassert (numChans >= 0 && numSamples >= 0); | |||
allocateChannels (dataToReferTo, 0); | |||
} | |||
AudioSampleBuffer::AudioSampleBuffer (float* const* dataToReferTo, | |||
const int numChans, | |||
const int startSample, | |||
const int numSamples) noexcept | |||
: numChannels (numChans), | |||
size (numSamples), | |||
allocatedBytes (0), | |||
isClear (false) | |||
{ | |||
jassert (dataToReferTo != nullptr); | |||
jassert (numChans >= 0 && startSample >= 0 && numSamples >= 0); | |||
allocateChannels (dataToReferTo, startSample); | |||
} | |||
void AudioSampleBuffer::setDataToReferTo (float** dataToReferTo, | |||
const int newNumChannels, | |||
const int newNumSamples) noexcept | |||
{ | |||
jassert (dataToReferTo != nullptr); | |||
jassert (newNumChannels >= 0 && newNumSamples >= 0); | |||
if (allocatedBytes != 0) | |||
{ | |||
allocatedBytes = 0; | |||
allocatedData.free(); | |||
} | |||
numChannels = newNumChannels; | |||
size = newNumSamples; | |||
allocateChannels (dataToReferTo, 0); | |||
jassert (! isClear); | |||
} | |||
void AudioSampleBuffer::allocateChannels (float* const* const dataToReferTo, int offset) | |||
{ | |||
jassert (offset >= 0); | |||
// (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) | |||
if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) | |||
{ | |||
channels = static_cast<float**> (preallocatedChannelSpace); | |||
} | |||
else | |||
{ | |||
allocatedData.malloc ((size_t) numChannels + 1, sizeof (float*)); | |||
channels = reinterpret_cast<float**> (allocatedData.getData()); | |||
} | |||
for (int i = 0; i < numChannels; ++i) | |||
{ | |||
// you have to pass in the same number of valid pointers as numChannels | |||
jassert (dataToReferTo[i] != nullptr); | |||
channels[i] = dataToReferTo[i] + offset; | |||
} | |||
channels [numChannels] = nullptr; | |||
isClear = false; | |||
} | |||
AudioSampleBuffer& AudioSampleBuffer::operator= (const AudioSampleBuffer& other) noexcept | |||
{ | |||
if (this != &other) | |||
{ | |||
setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); | |||
if (other.isClear) | |||
{ | |||
clear(); | |||
} | |||
else | |||
{ | |||
isClear = false; | |||
for (int i = 0; i < numChannels; ++i) | |||
FloatVectorOperations::copy (channels[i], other.channels[i], size); | |||
} | |||
} | |||
return *this; | |||
} | |||
AudioSampleBuffer::~AudioSampleBuffer() noexcept | |||
{ | |||
} | |||
void AudioSampleBuffer::setSize (const int newNumChannels, | |||
const int newNumSamples, | |||
const bool keepExistingContent, | |||
const bool clearExtraSpace, | |||
const bool avoidReallocating) noexcept | |||
{ | |||
jassert (newNumChannels >= 0); | |||
jassert (newNumSamples >= 0); | |||
if (newNumSamples != size || newNumChannels != numChannels) | |||
{ | |||
const size_t allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; | |||
const size_t channelListSize = ((sizeof (float*) * (size_t) (newNumChannels + 1)) + 15) & ~15u; | |||
const size_t newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (float)) | |||
+ channelListSize + 32; | |||
if (keepExistingContent) | |||
{ | |||
HeapBlock<char, true> newData; | |||
newData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
const size_t numSamplesToCopy = (size_t) jmin (newNumSamples, size); | |||
float** const newChannels = reinterpret_cast<float**> (newData.getData()); | |||
float* newChan = reinterpret_cast<float*> (newData + channelListSize); | |||
for (int j = 0; j < newNumChannels; ++j) | |||
{ | |||
newChannels[j] = newChan; | |||
newChan += allocatedSamplesPerChannel; | |||
} | |||
if (! isClear) | |||
{ | |||
const int numChansToCopy = jmin (numChannels, newNumChannels); | |||
for (int i = 0; i < numChansToCopy; ++i) | |||
FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); | |||
} | |||
allocatedData.swapWith (newData); | |||
allocatedBytes = newTotalBytes; | |||
channels = newChannels; | |||
} | |||
else | |||
{ | |||
if (avoidReallocating && allocatedBytes >= newTotalBytes) | |||
{ | |||
if (clearExtraSpace || isClear) | |||
allocatedData.clear (newTotalBytes); | |||
} | |||
else | |||
{ | |||
allocatedBytes = newTotalBytes; | |||
allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); | |||
channels = reinterpret_cast<float**> (allocatedData.getData()); | |||
} | |||
float* chan = reinterpret_cast<float*> (allocatedData + channelListSize); | |||
for (int i = 0; i < newNumChannels; ++i) | |||
{ | |||
channels[i] = chan; | |||
chan += allocatedSamplesPerChannel; | |||
} | |||
} | |||
channels [newNumChannels] = 0; | |||
size = newNumSamples; | |||
numChannels = newNumChannels; | |||
} | |||
} | |||
void AudioSampleBuffer::clear() noexcept | |||
{ | |||
if (! isClear) | |||
{ | |||
for (int i = 0; i < numChannels; ++i) | |||
FloatVectorOperations::clear (channels[i], size); | |||
isClear = true; | |||
} | |||
} | |||
void AudioSampleBuffer::clear (const int startSample, | |||
const int numSamples) noexcept | |||
{ | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (! isClear) | |||
{ | |||
if (startSample == 0 && numSamples == size) | |||
isClear = true; | |||
for (int i = 0; i < numChannels; ++i) | |||
FloatVectorOperations::clear (channels[i] + startSample, numSamples); | |||
} | |||
} | |||
void AudioSampleBuffer::clear (const int channel, | |||
const int startSample, | |||
const int numSamples) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (! isClear) | |||
FloatVectorOperations::clear (channels [channel] + startSample, numSamples); | |||
} | |||
float AudioSampleBuffer::getSample (int channel, int index) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (isPositiveAndBelow (index, size)); | |||
return *(channels [channel] + index); | |||
} | |||
void AudioSampleBuffer::setSample (int channel, int index, float newValue) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (isPositiveAndBelow (index, size)); | |||
*(channels [channel] + index) = newValue; | |||
isClear = false; | |||
} | |||
void AudioSampleBuffer::addSample (int channel, int index, float valueToAdd) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (isPositiveAndBelow (index, size)); | |||
*(channels [channel] + index) += valueToAdd; | |||
isClear = false; | |||
} | |||
void AudioSampleBuffer::applyGain (const int channel, | |||
const int startSample, | |||
int numSamples, | |||
const float gain) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (gain != 1.0f && ! isClear) | |||
{ | |||
float* const d = channels [channel] + startSample; | |||
if (gain == 0.0f) | |||
FloatVectorOperations::clear (d, numSamples); | |||
else | |||
FloatVectorOperations::multiply (d, gain, numSamples); | |||
} | |||
} | |||
void AudioSampleBuffer::applyGainRamp (const int channel, | |||
const int startSample, | |||
int numSamples, | |||
float startGain, | |||
float endGain) noexcept | |||
{ | |||
if (! isClear) | |||
{ | |||
if (startGain == endGain) | |||
{ | |||
applyGain (channel, startSample, numSamples, startGain); | |||
} | |||
else | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
const float increment = (endGain - startGain) / numSamples; | |||
float* d = channels [channel] + startSample; | |||
while (--numSamples >= 0) | |||
{ | |||
*d++ *= startGain; | |||
startGain += increment; | |||
} | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::applyGain (int startSample, int numSamples, float gain) noexcept | |||
{ | |||
for (int i = 0; i < numChannels; ++i) | |||
applyGain (i, startSample, numSamples, gain); | |||
} | |||
void AudioSampleBuffer::applyGain (const float gain) noexcept | |||
{ | |||
applyGain (0, size, gain); | |||
} | |||
void AudioSampleBuffer::applyGainRamp (int startSample, int numSamples, | |||
float startGain, float endGain) noexcept | |||
{ | |||
for (int i = 0; i < numChannels; ++i) | |||
applyGainRamp (i, startSample, numSamples, startGain, endGain); | |||
} | |||
void AudioSampleBuffer::addFrom (const int destChannel, | |||
const int destStartSample, | |||
const AudioSampleBuffer& source, | |||
const int sourceChannel, | |||
const int sourceStartSample, | |||
int numSamples, | |||
const float gain) noexcept | |||
{ | |||
jassert (&source != this || sourceChannel != destChannel); | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||
if (gain != 0.0f && numSamples > 0 && ! source.isClear) | |||
{ | |||
float* const d = channels [destChannel] + destStartSample; | |||
const float* const s = source.channels [sourceChannel] + sourceStartSample; | |||
if (isClear) | |||
{ | |||
isClear = false; | |||
if (gain != 1.0f) | |||
FloatVectorOperations::copyWithMultiply (d, s, gain, numSamples); | |||
else | |||
FloatVectorOperations::copy (d, s, numSamples); | |||
} | |||
else | |||
{ | |||
if (gain != 1.0f) | |||
FloatVectorOperations::addWithMultiply (d, s, gain, numSamples); | |||
else | |||
FloatVectorOperations::add (d, s, numSamples); | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::addFrom (const int destChannel, | |||
const int destStartSample, | |||
const float* source, | |||
int numSamples, | |||
const float gain) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (source != nullptr); | |||
if (gain != 0.0f && numSamples > 0) | |||
{ | |||
float* const d = channels [destChannel] + destStartSample; | |||
if (isClear) | |||
{ | |||
isClear = false; | |||
if (gain != 1.0f) | |||
FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
else | |||
FloatVectorOperations::copy (d, source, numSamples); | |||
} | |||
else | |||
{ | |||
if (gain != 1.0f) | |||
FloatVectorOperations::addWithMultiply (d, source, gain, numSamples); | |||
else | |||
FloatVectorOperations::add (d, source, numSamples); | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::addFromWithRamp (const int destChannel, | |||
const int destStartSample, | |||
const float* source, | |||
int numSamples, | |||
float startGain, | |||
const float endGain) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (source != nullptr); | |||
if (startGain == endGain) | |||
{ | |||
addFrom (destChannel, destStartSample, source, numSamples, startGain); | |||
} | |||
else | |||
{ | |||
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||
{ | |||
isClear = false; | |||
const float increment = (endGain - startGain) / numSamples; | |||
float* d = channels [destChannel] + destStartSample; | |||
while (--numSamples >= 0) | |||
{ | |||
*d++ += startGain * *source++; | |||
startGain += increment; | |||
} | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::copyFrom (const int destChannel, | |||
const int destStartSample, | |||
const AudioSampleBuffer& source, | |||
const int sourceChannel, | |||
const int sourceStartSample, | |||
int numSamples) noexcept | |||
{ | |||
jassert (&source != this || sourceChannel != destChannel); | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); | |||
jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); | |||
if (numSamples > 0) | |||
{ | |||
if (source.isClear) | |||
{ | |||
if (! isClear) | |||
FloatVectorOperations::clear (channels [destChannel] + destStartSample, numSamples); | |||
} | |||
else | |||
{ | |||
isClear = false; | |||
FloatVectorOperations::copy (channels [destChannel] + destStartSample, | |||
source.channels [sourceChannel] + sourceStartSample, | |||
numSamples); | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::copyFrom (const int destChannel, | |||
const int destStartSample, | |||
const float* source, | |||
int numSamples) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (source != nullptr); | |||
if (numSamples > 0) | |||
{ | |||
isClear = false; | |||
FloatVectorOperations::copy (channels [destChannel] + destStartSample, source, numSamples); | |||
} | |||
} | |||
void AudioSampleBuffer::copyFrom (const int destChannel, | |||
const int destStartSample, | |||
const float* source, | |||
int numSamples, | |||
const float gain) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (source != nullptr); | |||
if (numSamples > 0) | |||
{ | |||
float* const d = channels [destChannel] + destStartSample; | |||
if (gain != 1.0f) | |||
{ | |||
if (gain == 0) | |||
{ | |||
if (! isClear) | |||
FloatVectorOperations::clear (d, numSamples); | |||
} | |||
else | |||
{ | |||
isClear = false; | |||
FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); | |||
} | |||
} | |||
else | |||
{ | |||
isClear = false; | |||
FloatVectorOperations::copy (d, source, numSamples); | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::copyFromWithRamp (const int destChannel, | |||
const int destStartSample, | |||
const float* source, | |||
int numSamples, | |||
float startGain, | |||
float endGain) noexcept | |||
{ | |||
jassert (isPositiveAndBelow (destChannel, numChannels)); | |||
jassert (destStartSample >= 0 && destStartSample + numSamples <= size); | |||
jassert (source != nullptr); | |||
if (startGain == endGain) | |||
{ | |||
copyFrom (destChannel, destStartSample, source, numSamples, startGain); | |||
} | |||
else | |||
{ | |||
if (numSamples > 0 && (startGain != 0.0f || endGain != 0.0f)) | |||
{ | |||
isClear = false; | |||
const float increment = (endGain - startGain) / numSamples; | |||
float* d = channels [destChannel] + destStartSample; | |||
while (--numSamples >= 0) | |||
{ | |||
*d++ = startGain * *source++; | |||
startGain += increment; | |||
} | |||
} | |||
} | |||
} | |||
void AudioSampleBuffer::reverse (int channel, int startSample, int numSamples) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (! isClear) | |||
std::reverse (channels[channel] + startSample, | |||
channels[channel] + startSample + numSamples); | |||
} | |||
void AudioSampleBuffer::reverse (int startSample, int numSamples) const noexcept | |||
{ | |||
for (int i = 0; i < numChannels; ++i) | |||
reverse (i, startSample, numSamples); | |||
} | |||
Range<float> AudioSampleBuffer::findMinMax (const int channel, | |||
const int startSample, | |||
int numSamples) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (isClear) | |||
return Range<float>(); | |||
return FloatVectorOperations::findMinAndMax (channels [channel] + startSample, numSamples); | |||
} | |||
float AudioSampleBuffer::getMagnitude (const int channel, | |||
const int startSample, | |||
const int numSamples) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (isClear) | |||
return 0.0f; | |||
const Range<float> r (findMinMax (channel, startSample, numSamples)); | |||
return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); | |||
} | |||
float AudioSampleBuffer::getMagnitude (int startSample, int numSamples) const noexcept | |||
{ | |||
float mag = 0.0f; | |||
if (! isClear) | |||
for (int i = 0; i < numChannels; ++i) | |||
mag = jmax (mag, getMagnitude (i, startSample, numSamples)); | |||
return mag; | |||
} | |||
float AudioSampleBuffer::getRMSLevel (const int channel, | |||
const int startSample, | |||
const int numSamples) const noexcept | |||
{ | |||
jassert (isPositiveAndBelow (channel, numChannels)); | |||
jassert (startSample >= 0 && startSample + numSamples <= size); | |||
if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) | |||
return 0.0f; | |||
const float* const data = channels [channel] + startSample; | |||
double sum = 0.0; | |||
for (int i = 0; i < numSamples; ++i) | |||
{ | |||
const float sample = data [i]; | |||
sum += sample * sample; | |||
} | |||
return (float) std::sqrt (sum / numSamples); | |||
} |
@@ -80,7 +80,6 @@ namespace juce | |||
{ | |||
#include "buffers/juce_AudioDataConverters.cpp" | |||
#include "buffers/juce_AudioSampleBuffer.cpp" | |||
#include "buffers/juce_FloatVectorOperations.cpp" | |||
#include "effects/juce_IIRFilter.cpp" | |||
#include "effects/juce_IIRFilterOld.cpp" | |||
@@ -35,8 +35,8 @@ namespace juce | |||
#undef Factor | |||
#include "buffers/juce_AudioDataConverters.h" | |||
#include "buffers/juce_AudioSampleBuffer.h" | |||
#include "buffers/juce_FloatVectorOperations.h" | |||
#include "buffers/juce_AudioSampleBuffer.h" | |||
#include "effects/juce_Decibels.h" | |||
#include "effects/juce_IIRFilter.h" | |||
#include "effects/juce_IIRFilterOld.h" | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_basics", | |||
"name": "JUCE audio and midi data classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -71,6 +71,18 @@ bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const no | |||
return noteOnTime < other.noteOnTime; | |||
} | |||
void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer, | |||
int startSample, int numSamples) | |||
{ | |||
AudioBuffer<double> subBuffer (outputBuffer.getArrayOfWritePointers(), | |||
outputBuffer.getNumChannels(), | |||
startSample, numSamples); | |||
tempBuffer.makeCopyOf (subBuffer); | |||
renderNextBlock (tempBuffer, 0, numSamples); | |||
subBuffer.makeCopyOf (tempBuffer); | |||
} | |||
//============================================================================== | |||
Synthesiser::Synthesiser() | |||
: sampleRate (0), | |||
@@ -156,8 +168,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||
} | |||
} | |||
void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBuffer& midiData, | |||
int startSample, int numSamples) | |||
template <typename floatType> | |||
void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
const MidiBuffer& midiData, | |||
int startSample, | |||
int numSamples) | |||
{ | |||
// must set the sample rate before using this! | |||
jassert (sampleRate != 0); | |||
@@ -174,7 +189,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
{ | |||
if (! midiIterator.getNextEvent (m, midiEventPos)) | |||
{ | |||
renderVoices (outputBuffer, startSample, numSamples); | |||
renderVoices (outputAudio, startSample, numSamples); | |||
return; | |||
} | |||
@@ -182,7 +197,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
if (samplesToNextMidiMessage >= numSamples) | |||
{ | |||
renderVoices (outputBuffer, startSample, numSamples); | |||
renderVoices (outputAudio, startSample, numSamples); | |||
handleMidiEvent (m); | |||
break; | |||
} | |||
@@ -193,7 +208,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
continue; | |||
} | |||
renderVoices (outputBuffer, startSample, samplesToNextMidiMessage); | |||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||
handleMidiEvent (m); | |||
startSample += samplesToNextMidiMessage; | |||
numSamples -= samplesToNextMidiMessage; | |||
@@ -203,7 +218,23 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
handleMidiEvent (m); | |||
} | |||
void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) | |||
// explicit template instantiation | |||
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>& outputAudio, | |||
const MidiBuffer& midiData, | |||
int startSample, | |||
int numSamples); | |||
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>& outputAudio, | |||
const MidiBuffer& midiData, | |||
int startSample, | |||
int numSamples); | |||
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||
{ | |||
for (int i = voices.size(); --i >= 0;) | |||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||
} | |||
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||
{ | |||
for (int i = voices.size(); --i >= 0;) | |||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||
@@ -182,9 +182,12 @@ public: | |||
involve rendering as little as 1 sample at a time. In between rendering callbacks, | |||
the voice's methods will be called to tell it about note and controller events. | |||
*/ | |||
virtual void renderNextBlock (AudioSampleBuffer& outputBuffer, | |||
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer, | |||
int startSample, | |||
int numSamples) = 0; | |||
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | |||
int startSample, | |||
int numSamples); | |||
/** Changes the voice's reference sample rate. | |||
@@ -255,6 +258,8 @@ private: | |||
SynthesiserSound::Ptr currentlyPlayingSound; | |||
bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||
AudioBuffer<float> tempBuffer; | |||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
// Note the new parameters for this method. | |||
virtual int stopNote (bool) { return 0; } | |||
@@ -504,10 +509,17 @@ public: | |||
both to the audio output buffer and the midi input buffer, so any midi events | |||
with timestamps outside the specified region will be ignored. | |||
*/ | |||
void renderNextBlock (AudioSampleBuffer& outputAudio, | |||
inline void renderNextBlock (AudioBuffer<float>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples); | |||
int numSamples) | |||
{ processNextBlock (outputAudio, inputMidi, startSample, numSamples); } | |||
inline void renderNextBlock (AudioBuffer<double>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples) | |||
{ processNextBlock (outputAudio, inputMidi, startSample, numSamples); } | |||
/** Returns the current target sample rate at which rendering is being done. | |||
Subclasses may need to know this so that they can pitch things correctly. | |||
@@ -545,7 +557,9 @@ protected: | |||
By default this just calls renderNextBlock() on each voice, but you may need | |||
to override it to handle custom cases. | |||
*/ | |||
virtual void renderVoices (AudioSampleBuffer& outputAudio, | |||
virtual void renderVoices (AudioBuffer<float>& outputAudio, | |||
int startSample, int numSamples); | |||
virtual void renderVoices (AudioBuffer<double>& outputAudio, | |||
int startSample, int numSamples); | |||
/** Searches through the voices to find one that's not currently playing, and | |||
@@ -592,6 +606,12 @@ protected: | |||
private: | |||
//============================================================================== | |||
template <typename floatType> | |||
void processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples); | |||
//============================================================================== | |||
double sampleRate; | |||
uint32 lastNoteOnCounter; | |||
int minimumSubBlockSize; | |||
@@ -93,7 +93,6 @@ AudioDeviceManager::AudioDeviceManager() | |||
numOutputChansNeeded (2), | |||
listNeedsScanning (true), | |||
inputLevel (0), | |||
testSoundPosition (0), | |||
cpuUsageMs (0), | |||
timeToCpuScale (0) | |||
{ | |||
@@ -589,8 +588,6 @@ void AudioDeviceManager::stopDevice() | |||
{ | |||
if (currentAudioDevice != nullptr) | |||
currentAudioDevice->stop(); | |||
testSound = nullptr; | |||
} | |||
void AudioDeviceManager::closeAudioDevice() | |||
@@ -762,20 +759,6 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
for (int i = 0; i < numOutputChannels; ++i) | |||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||
} | |||
if (testSound != nullptr) | |||
{ | |||
const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||
const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||
for (int i = 0; i < numOutputChannels; ++i) | |||
for (int j = 0; j < numSamps; ++j) | |||
outputChannelData [i][j] += src[j]; | |||
testSoundPosition += numSamps; | |||
if (testSoundPosition >= testSound->getNumSamples()) | |||
testSound = nullptr; | |||
} | |||
} | |||
void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | |||
@@ -944,42 +927,311 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
} | |||
//============================================================================== | |||
void AudioDeviceManager::playTestSound() | |||
// This is an AudioTransportSource which will own it's assigned source | |||
class AudioSourceOwningTransportSource : public AudioTransportSource | |||
{ | |||
{ // cunningly nested to swap, unlock and delete in that order. | |||
ScopedPointer<AudioSampleBuffer> oldSound; | |||
public: | |||
AudioSourceOwningTransportSource() {} | |||
~AudioSourceOwningTransportSource() { setSource (nullptr); } | |||
void setSource (PositionableAudioSource* newSource) | |||
{ | |||
if (src != newSource) | |||
{ | |||
const ScopedLock sl (audioCallbackLock); | |||
oldSound = testSound; | |||
ScopedPointer<PositionableAudioSource> oldSourceDeleter (src); | |||
src = newSource; | |||
// tell the base class about the new source before deleting the old one | |||
AudioTransportSource::setSource (newSource); | |||
} | |||
} | |||
testSoundPosition = 0; | |||
private: | |||
ScopedPointer<PositionableAudioSource> src; | |||
if (currentAudioDevice != nullptr) | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
}; | |||
//============================================================================== | |||
// An Audio player which will remove itself from the AudioDeviceManager's | |||
// callback list once it finishes playing its source | |||
class AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
private ChangeListener | |||
{ | |||
public: | |||
struct DeleteOnMessageThread : public CallbackMessage | |||
{ | |||
DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {} | |||
void messageCallback() override { delete parent; } | |||
AutoRemovingSourcePlayer* parent; | |||
}; | |||
//============================================================================== | |||
AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource) | |||
: manager (deviceManager), | |||
deleteWhenDone (ownSource), | |||
hasAddedCallback (false), | |||
recursiveEntry (false) | |||
{ | |||
} | |||
void changeListenerCallback (ChangeBroadcaster* newSource) override | |||
{ | |||
if (AudioTransportSource* currentTransport | |||
= dynamic_cast<AudioTransportSource*> (getCurrentSource())) | |||
{ | |||
ignoreUnused (newSource); | |||
jassert (newSource == currentTransport); | |||
if (! currentTransport->isPlaying()) | |||
{ | |||
// this will call audioDeviceStopped! | |||
manager.removeAudioCallback (this); | |||
} | |||
else if (! hasAddedCallback) | |||
{ | |||
hasAddedCallback = true; | |||
manager.addAudioCallback (this); | |||
} | |||
} | |||
} | |||
void audioDeviceStopped() override | |||
{ | |||
if (! recursiveEntry) | |||
{ | |||
ScopedValueSetter<bool> s (recursiveEntry, true, false); | |||
manager.removeAudioCallback (this); | |||
AudioSourcePlayer::audioDeviceStopped(); | |||
if (MessageManager* mm = MessageManager::getInstanceWithoutCreating()) | |||
{ | |||
if (mm->isThisTheMessageThread()) | |||
delete this; | |||
else | |||
(new DeleteOnMessageThread (this))->post(); | |||
} | |||
} | |||
} | |||
void setSource (AudioTransportSource* newSource) | |||
{ | |||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
const int soundLength = (int) sampleRate; | |||
AudioSource* oldSource = getCurrentSource(); | |||
if (AudioTransportSource* oldTransport = dynamic_cast<AudioTransportSource*> (oldSource)) | |||
oldTransport->removeChangeListener (this); | |||
const double frequency = 440.0; | |||
const float amplitude = 0.5f; | |||
if (newSource != nullptr) | |||
newSource->addChangeListener (this); | |||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
AudioSourcePlayer::setSource (newSource); | |||
AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||
if (deleteWhenDone) | |||
delete oldSource; | |||
} | |||
for (int i = 0; i < soundLength; ++i) | |||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||
private: | |||
// only allow myself to be deleted when my audio callback has been removed | |||
~AutoRemovingSourcePlayer() | |||
{ | |||
setSource (nullptr); | |||
} | |||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
AudioDeviceManager& manager; | |||
bool deleteWhenDone, hasAddedCallback, recursiveEntry; | |||
const ScopedLock sl (audioCallbackLock); | |||
testSound = newSound; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
}; | |||
//============================================================================== | |||
// An AudioSource which simply outputs a buffer | |||
class AudioSampleBufferSource : public PositionableAudioSource | |||
{ | |||
public: | |||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool shouldLoop, bool ownBuffer) | |||
: position (0), | |||
buffer (audioBuffer), | |||
looping (shouldLoop), | |||
deleteWhenDone (ownBuffer) | |||
{} | |||
~AudioSampleBufferSource() | |||
{ | |||
if (deleteWhenDone) | |||
delete buffer; | |||
} | |||
//============================================================================== | |||
void setNextReadPosition (int64 newPosition) override | |||
{ | |||
jassert (newPosition >= 0); | |||
if (looping) | |||
newPosition = newPosition % static_cast<int64> (buffer->getNumSamples()); | |||
position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition)); | |||
} | |||
int64 getNextReadPosition() const override | |||
{ | |||
return static_cast<int64> (position); | |||
} | |||
int64 getTotalLength() const override | |||
{ | |||
return static_cast<int64> (buffer->getNumSamples()); | |||
} | |||
bool isLooping() const override | |||
{ | |||
return looping; | |||
} | |||
void setLooping (bool shouldLoop) override | |||
{ | |||
looping = shouldLoop; | |||
} | |||
//============================================================================== | |||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override | |||
{ | |||
ignoreUnused (samplesPerBlockExpected, sampleRate); | |||
} | |||
void releaseResources() override | |||
{} | |||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||
{ | |||
int max = jmin (buffer->getNumSamples() - position, bufferToFill.numSamples); | |||
jassert (max >= 0); | |||
{ | |||
int ch; | |||
int maxInChannels = buffer->getNumChannels(); | |||
int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), | |||
jmax (maxInChannels, 2)); | |||
for (ch = 0; ch < maxOutChannels; ch++) | |||
{ | |||
int inChannel = ch % maxInChannels; | |||
if (max > 0) | |||
bufferToFill.buffer->copyFrom (ch, bufferToFill.startSample, *buffer, inChannel, position, max); | |||
} | |||
for (; ch < bufferToFill.buffer->getNumChannels(); ++ch) | |||
bufferToFill.buffer->clear (ch, bufferToFill.startSample, bufferToFill.numSamples); | |||
} | |||
position += max; | |||
if (looping) | |||
position = position % buffer->getNumSamples(); | |||
} | |||
private: | |||
//============================================================================== | |||
int position; | |||
AudioSampleBuffer* buffer; | |||
bool looping, deleteWhenDone; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||
}; | |||
void AudioDeviceManager::playSound (const File& file) | |||
{ | |||
if (file.existsAsFile()) | |||
{ | |||
AudioFormatManager formatManager; | |||
formatManager.registerBasicFormats(); | |||
playSound (formatManager.createReaderFor (file), true); | |||
} | |||
} | |||
void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) | |||
{ | |||
if (resourceData != nullptr && resourceSize > 0) | |||
{ | |||
AudioFormatManager formatManager; | |||
formatManager.registerBasicFormats(); | |||
MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); | |||
playSound (formatManager.createReaderFor (mem), true); | |||
} | |||
} | |||
void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||
{ | |||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
} | |||
void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||
{ | |||
if (audioSource != nullptr && currentAudioDevice != nullptr) | |||
{ | |||
if (AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource)) | |||
{ | |||
AutoRemovingSourcePlayer* player = new AutoRemovingSourcePlayer (*this, deleteWhenFinished); | |||
player->setSource (transport); | |||
} | |||
else | |||
{ | |||
AudioTransportSource* transportSource; | |||
if (deleteWhenFinished) | |||
{ | |||
AudioSourceOwningTransportSource* owningTransportSource = new AudioSourceOwningTransportSource(); | |||
owningTransportSource->setSource (audioSource); | |||
transportSource = owningTransportSource; | |||
} | |||
else | |||
{ | |||
transportSource = new AudioTransportSource; | |||
transportSource->setSource (audioSource); | |||
} | |||
// recursively call myself | |||
playSound (transportSource, true); | |||
transportSource->start(); | |||
} | |||
} | |||
else | |||
{ | |||
if (deleteWhenFinished) | |||
delete audioSource; | |||
} | |||
} | |||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
{ | |||
playSound (new AudioSampleBufferSource (buffer, false, deleteWhenFinished), true); | |||
} | |||
void AudioDeviceManager::playTestSound() | |||
{ | |||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
const int soundLength = (int) sampleRate; | |||
const double frequency = 440.0; | |||
const float amplitude = 0.5f; | |||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); | |||
for (int i = 0; i < soundLength; ++i) | |||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
playSound (newSound, true); | |||
} | |||
//============================================================================== | |||
void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||
{ | |||
if (enableMeasurement) | |||
@@ -404,6 +404,51 @@ public: | |||
*/ | |||
void playTestSound(); | |||
/** Plays a sound from a file. */ | |||
void playSound (const File& file); | |||
/** Convenient method to play sound from a JUCE resource. */ | |||
void playSound (const void* resourceData, size_t resourceSize); | |||
/** Plays the sound from an audio format reader. | |||
If deleteWhenFinished is true then the format reader will be | |||
automatically deleted once the sound has finished playing. | |||
*/ | |||
void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); | |||
/** Plays the sound from a positionable audio source. | |||
This will output the sound coming from a positionable audio source. | |||
This gives you slightly more control over the sound playback compared | |||
to the other playSound methods. For example, if you would like to | |||
stop the sound prematurely you can call this method with a | |||
TransportAudioSource and then call audioSource->stop. Note that, | |||
you must call audioSource->start to start the playback, if your | |||
audioSource is a TransportAudioSource. | |||
The audio device manager will not hold any references to this audio | |||
source once the audio source has stopped playing for any reason, | |||
for example when the sound has finished playing or when you have | |||
called audioSource->stop. Therefore, calling audioSource->start() on | |||
a finished audioSource will not restart the sound again. If this is | |||
desired simply call playSound with the same audioSource again. | |||
@param audioSource the audio source to play | |||
@param deleteWhenFinished If this is true then the audio source will | |||
be deleted once the device manager has finished playing. | |||
*/ | |||
void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); | |||
/** Plays the sound from an audio sample buffer. | |||
This will output the sound contained in an audio sample buffer. If | |||
deleteWhenFinished is true then the audio sample buffer will be | |||
automatically deleted once the sound has finished playing. | |||
*/ | |||
void playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished = false); | |||
//============================================================================== | |||
/** Turns on level-measuring. | |||
When enabled, the device manager will measure the peak input level | |||
@@ -452,8 +497,6 @@ private: | |||
mutable bool listNeedsScanning; | |||
Atomic<int> inputLevelMeasurementEnabledCount; | |||
double inputLevel; | |||
ScopedPointer<AudioSampleBuffer> testSound; | |||
int testSoundPosition; | |||
AudioSampleBuffer tempBuffer; | |||
struct MidiCallbackInfo | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_devices", | |||
"name": "JUCE audio and midi I/O device classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes to play and record from audio and midi i/o devices.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -22,11 +22,228 @@ | |||
============================================================================== | |||
*/ | |||
StringArray MidiOutput::getDevices() | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \ | |||
METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \ | |||
METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ | |||
METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ | |||
METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \ | |||
METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;") | |||
DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager") | |||
#undef JNI_CLASS_MEMBERS | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (start, "start", "()V" )\ | |||
METHOD (stop, "stop", "()V") \ | |||
METHOD (close, "close", "()V") \ | |||
METHOD (sendMidi, "sendMidi", "([BII)V") | |||
DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort") | |||
#undef JNI_CLASS_MEMBERS | |||
//============================================================================== | |||
class AndroidMidiInput | |||
{ | |||
public: | |||
AndroidMidiInput (MidiInput* midiInput, int portIdx, | |||
juce::MidiInputCallback* midiInputCallback, jobject deviceManager) | |||
: juceMidiInput (midiInput), | |||
callback (midiInputCallback), | |||
midiConcatenator (2048), | |||
javaMidiDevice (getEnv()->CallObjectMethod (deviceManager, | |||
MidiDeviceManager.openMidiInputPortWithJuceIndex, | |||
(jint) portIdx, | |||
(jlong) this)) | |||
{ | |||
} | |||
~AndroidMidiInput() | |||
{ | |||
if (jobject d = javaMidiDevice.get()) | |||
{ | |||
getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||
javaMidiDevice.clear(); | |||
} | |||
} | |||
bool isOpen() const noexcept | |||
{ | |||
return javaMidiDevice != nullptr; | |||
} | |||
void start() | |||
{ | |||
if (jobject d = javaMidiDevice.get()) | |||
getEnv()->CallVoidMethod (d, JuceMidiPort.start); | |||
} | |||
void stop() | |||
{ | |||
if (jobject d = javaMidiDevice.get()) | |||
getEnv()->CallVoidMethod (d, JuceMidiPort.stop); | |||
callback = nullptr; | |||
} | |||
void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) | |||
{ | |||
jassert (byteArray != nullptr); | |||
jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr); | |||
HeapBlock<uint8> buffer (len); | |||
std::memcpy (buffer.getData(), data + offset, len); | |||
midiConcatenator.pushMidiData (buffer.getData(), | |||
len, static_cast<double> (timestamp) * 1.0e-9, | |||
juceMidiInput, *callback); | |||
getEnv()->ReleaseByteArrayElements (byteArray, data, 0); | |||
} | |||
private: | |||
MidiInput* juceMidiInput; | |||
MidiInputCallback* callback; | |||
GlobalRef javaMidiDevice; | |||
MidiDataConcatenator midiConcatenator; | |||
}; | |||
//============================================================================== | |||
class AndroidMidiOutput | |||
{ | |||
StringArray devices; | |||
public: | |||
AndroidMidiOutput (jobject midiDevice) | |||
: javaMidiDevice (midiDevice) | |||
{ | |||
} | |||
~AndroidMidiOutput() | |||
{ | |||
if (jobject d = javaMidiDevice.get()) | |||
{ | |||
getEnv()->CallVoidMethod (d, JuceMidiPort.close); | |||
javaMidiDevice.clear(); | |||
} | |||
} | |||
void send (jbyteArray byteArray, jint offset, jint len) | |||
{ | |||
if (jobject d = javaMidiDevice.get()) | |||
getEnv()->CallVoidMethod (d, | |||
JuceMidiPort.sendMidi, | |||
byteArray, offset, len); | |||
} | |||
private: | |||
GlobalRef javaMidiDevice; | |||
}; | |||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive, | |||
void, (JNIEnv* env, jobject device, jlong host, jbyteArray byteArray, | |||
jint offset, jint count, jlong timestamp)) | |||
{ | |||
// Java may create a Midi thread which JUCE doesn't know about and this callback may be | |||
// received on this thread. Java will have already created a JNI Env for this new thread, | |||
// which we need to tell Juce about | |||
setEnv (env); | |||
reinterpret_cast<AndroidMidiInput*> (host)->receive (byteArray, offset, count, timestamp); | |||
} | |||
//============================================================================== | |||
class AndroidMidiDeviceManager | |||
{ | |||
public: | |||
AndroidMidiDeviceManager () | |||
: deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | |||
{ | |||
} | |||
String getInputPortNameForJuceIndex (int idx) | |||
{ | |||
if (jobject dm = deviceManager.get()) | |||
{ | |||
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx)); | |||
return juceString (string); | |||
} | |||
return String(); | |||
} | |||
String getOutputPortNameForJuceIndex (int idx) | |||
{ | |||
if (jobject dm = deviceManager.get()) | |||
{ | |||
LocalRef<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx)); | |||
return juceString (string); | |||
} | |||
return String(); | |||
} | |||
StringArray getDevices (bool input) | |||
{ | |||
if (jobject dm = deviceManager.get()) | |||
{ | |||
jobjectArray jDevices | |||
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices | |||
: MidiDeviceManager.getJuceAndroidMidiOutputDevices); | |||
// Create a local reference as converting this | |||
// to a JUCE string will call into JNI | |||
LocalRef<jobjectArray> devices (jDevices); | |||
return javaStringArrayToJuce (devices); | |||
} | |||
return StringArray(); | |||
} | |||
AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||
{ | |||
if (jobject dm = deviceManager.get()) | |||
{ | |||
ScopedPointer<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm)); | |||
if (androidMidiInput->isOpen()) | |||
return androidMidiInput.release(); | |||
} | |||
return nullptr; | |||
} | |||
AndroidMidiOutput* openMidiOutputPortWithIndex (int idx) | |||
{ | |||
if (jobject dm = deviceManager.get()) | |||
if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx)) | |||
return new AndroidMidiOutput (javaMidiPort); | |||
return devices; | |||
return nullptr; | |||
} | |||
private: | |||
static StringArray javaStringArrayToJuce (jobjectArray jStrings) | |||
{ | |||
StringArray retval; | |||
JNIEnv* env = getEnv(); | |||
const int count = env->GetArrayLength (jStrings); | |||
for (int i = 0; i < count; ++i) | |||
{ | |||
LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (jStrings, i)); | |||
retval.add (juceString (string)); | |||
} | |||
return retval; | |||
} | |||
GlobalRef deviceManager; | |||
}; | |||
//============================================================================== | |||
StringArray MidiOutput::getDevices() | |||
{ | |||
AndroidMidiDeviceManager manager; | |||
return manager.getDevices (false); | |||
} | |||
int MidiOutput::getDefaultDeviceIndex() | |||
@@ -36,50 +253,109 @@ int MidiOutput::getDefaultDeviceIndex() | |||
MidiOutput* MidiOutput::openDevice (int index) | |||
{ | |||
if (index < 0) | |||
return nullptr; | |||
AndroidMidiDeviceManager manager; | |||
String midiOutputName = manager.getOutputPortNameForJuceIndex (index); | |||
if (midiOutputName.isEmpty()) | |||
{ | |||
// you supplied an invalid device index! | |||
jassertfalse; | |||
return nullptr; | |||
} | |||
if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index)) | |||
{ | |||
MidiOutput* retval = new MidiOutput (midiOutputName); | |||
retval->internal = midiOutput; | |||
return retval; | |||
} | |||
return nullptr; | |||
} | |||
MidiOutput::~MidiOutput() | |||
{ | |||
stopBackgroundThread(); | |||
delete reinterpret_cast<AndroidMidiOutput*> (internal); | |||
} | |||
void MidiOutput::sendMessageNow (const MidiMessage&) | |||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
{ | |||
if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal)) | |||
{ | |||
JNIEnv* env = getEnv(); | |||
const int messageSize = message.getRawDataSize(); | |||
LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize)); | |||
jbyteArray content = messageContent.get(); | |||
jbyte* rawBytes = env->GetByteArrayElements (content, nullptr); | |||
std::memcpy (rawBytes, message.getRawData(), messageSize); | |||
env->ReleaseByteArrayElements (content, rawBytes, 0); | |||
androidMidi->send (content, (jint) 0, (jint) messageSize); | |||
} | |||
} | |||
//============================================================================== | |||
MidiInput::MidiInput (const String& name_) | |||
: name (name_), | |||
internal (0) | |||
MidiInput::MidiInput (const String& nm) : name (nm) | |||
{ | |||
} | |||
MidiInput::~MidiInput() | |||
StringArray MidiInput::getDevices() | |||
{ | |||
AndroidMidiDeviceManager manager; | |||
return manager.getDevices (true); | |||
} | |||
void MidiInput::start() | |||
int MidiInput::getDefaultDeviceIndex() | |||
{ | |||
return 0; | |||
} | |||
void MidiInput::stop() | |||
MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback) | |||
{ | |||
if (index < 0) | |||
return nullptr; | |||
AndroidMidiDeviceManager manager; | |||
String midiInputName = manager.getInputPortNameForJuceIndex (index); | |||
if (midiInputName.isEmpty()) | |||
{ | |||
// you supplied an invalid device index! | |||
jassertfalse; | |||
return nullptr; | |||
} | |||
ScopedPointer<MidiInput> midiInput (new MidiInput (midiInputName)); | |||
midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback); | |||
return midiInput->internal != nullptr ? midiInput.release() | |||
: nullptr; | |||
} | |||
int MidiInput::getDefaultDeviceIndex() | |||
void MidiInput::start() | |||
{ | |||
return 0; | |||
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||
mi->start(); | |||
} | |||
StringArray MidiInput::getDevices() | |||
void MidiInput::stop() | |||
{ | |||
StringArray devs; | |||
return devs; | |||
if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||
mi->stop(); | |||
} | |||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||
MidiInput::~MidiInput() | |||
{ | |||
return nullptr; | |||
delete reinterpret_cast<AndroidMidiInput*> (internal); | |||
} |
@@ -32,7 +32,7 @@ bool isOpenSLAvailable() | |||
//============================================================================== | |||
class OpenSLAudioIODevice : public AudioIODevice, | |||
public Thread | |||
private Thread | |||
{ | |||
public: | |||
OpenSLAudioIODevice (const String& deviceName) | |||
@@ -81,13 +81,28 @@ public: | |||
Array<double> getAvailableSampleRates() override | |||
{ | |||
static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | |||
return Array<double> (rates, numElementsInArray (rates)); | |||
Array<double> retval (rates, numElementsInArray (rates)); | |||
// make sure the native sample rate is pafrt of the list | |||
double native = getNativeSampleRate(); | |||
if (native != 0.0 && ! retval.contains (native)) | |||
retval.add (native); | |||
return retval; | |||
} | |||
Array<int> getAvailableBufferSizes() override | |||
{ | |||
static const int sizes[] = { 256, 512, 768, 1024, 1280, 1600 }; // must all be multiples of the block size | |||
return Array<int> (sizes, numElementsInArray (sizes)); | |||
// we need to offer the lowest possible buffer size which | |||
// is the native buffer size | |||
const int defaultNumMultiples = 8; | |||
const int nativeBufferSize = getNativeBufferSize(); | |||
Array<int> retval; | |||
for (int i = 1; i < defaultNumMultiples; ++i) | |||
retval.add (i * nativeBufferSize); | |||
return retval; | |||
} | |||
String open (const BigInteger& inputChannels, | |||
@@ -116,8 +131,28 @@ public: | |||
outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); | |||
outputBuffer.clear(); | |||
recorder = engine.createRecorder (numInputChannels, sampleRate); | |||
player = engine.createPlayer (numOutputChannels, sampleRate); | |||
const int audioBuffersToEnqueue = hasLowLatencyAudioPath ? buffersToEnqueueForLowLatency | |||
: buffersToEnqueueSlowAudio; | |||
DBG ("OpenSL: numInputChannels = " << numInputChannels | |||
<< ", numOutputChannels = " << numOutputChannels | |||
<< ", nativeBufferSize = " << getNativeBufferSize() | |||
<< ", nativeSampleRate = " << getNativeSampleRate() | |||
<< ", actualBufferSize = " << actualBufferSize | |||
<< ", audioBuffersToEnqueue = " << audioBuffersToEnqueue | |||
<< ", sampleRate = " << sampleRate); | |||
if (numInputChannels > 0) | |||
recorder = engine.createRecorder (numInputChannels, sampleRate, | |||
audioBuffersToEnqueue, actualBufferSize); | |||
if (numOutputChannels > 0) | |||
player = engine.createPlayer (numOutputChannels, sampleRate, | |||
audioBuffersToEnqueue, actualBufferSize); | |||
// pre-fill buffers | |||
for (int i = 0; i < audioBuffersToEnqueue; ++i) | |||
processBuffers(); | |||
startThread (8); | |||
@@ -134,18 +169,30 @@ public: | |||
player = nullptr; | |||
} | |||
int getDefaultBufferSize() override { return 1024; } | |||
int getOutputLatencyInSamples() override { return outputLatency; } | |||
int getInputLatencyInSamples() override { return inputLatency; } | |||
bool isOpen() override { return deviceOpen; } | |||
int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
int getCurrentBitDepth() override { return 16; } | |||
double getCurrentSampleRate() override { return sampleRate; } | |||
BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
String getLastError() override { return lastError; } | |||
bool isPlaying() override { return callback != nullptr; } | |||
int getDefaultBufferSize() override | |||
{ | |||
// Only on a Pro-Audio device will we set the lowest possible buffer size | |||
// by default. We need to be more conservative on other devices | |||
// as they may be low-latency, but still have a crappy CPU. | |||
return (isProAudioDevice() ? 1 : 6) | |||
* defaultBufferSizeIsMultipleOfNative * getNativeBufferSize(); | |||
} | |||
double getCurrentSampleRate() override | |||
{ | |||
return (sampleRate == 0.0 ? getNativeSampleRate() : sampleRate); | |||
} | |||
void start (AudioIODeviceCallback* newCallback) override | |||
{ | |||
stop(); | |||
@@ -184,6 +231,55 @@ private: | |||
struct Player; | |||
struct Recorder; | |||
enum | |||
{ | |||
// The number of buffers to enqueue needs to be at least two for the audio to use the low-latency | |||
// audio path (see "Performance" section in ndk/docs/Additional_library_docs/opensles/index.html) | |||
buffersToEnqueueForLowLatency = 2, | |||
buffersToEnqueueSlowAudio = 4, | |||
defaultBufferSizeIsMultipleOfNative = 1 | |||
}; | |||
//================================================================================================== | |||
static String audioManagerGetProperty (const String& property) | |||
{ | |||
const LocalRef<jstring> jProperty (javaString (property)); | |||
const LocalRef<jstring> text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, | |||
jProperty.get())); | |||
if (text.get() != 0) | |||
return juceString (text); | |||
return String(); | |||
} | |||
static bool androidHasSystemFeature (const String& property) | |||
{ | |||
const LocalRef<jstring> jProperty (javaString (property)); | |||
return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get()); | |||
} | |||
static double getNativeSampleRate() | |||
{ | |||
return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); | |||
} | |||
static int getNativeBufferSize() | |||
{ | |||
const int val = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); | |||
return val > 0 ? val : 512; | |||
} | |||
static bool isProAudioDevice() | |||
{ | |||
return androidHasSystemFeature ("android.hardware.audio.pro"); | |||
} | |||
static bool hasLowLatencyAudioPath() | |||
{ | |||
return androidHasSystemFeature ("android.hardware.audio.low_latency"); | |||
} | |||
//================================================================================================== | |||
AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) | |||
{ | |||
const ScopedLock sl (callbackLock); | |||
@@ -192,29 +288,45 @@ private: | |||
return oldCallback; | |||
} | |||
void run() override | |||
void processBuffers() | |||
{ | |||
if (recorder != nullptr) recorder->start(); | |||
if (player != nullptr) player->start(); | |||
if (recorder != nullptr) | |||
recorder->readNextBlock (inputBuffer, *this); | |||
while (! threadShouldExit()) | |||
{ | |||
if (player != nullptr) player->writeBuffer (outputBuffer, *this); | |||
if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); | |||
const ScopedLock sl (callbackLock); | |||
if (callback != nullptr) | |||
{ | |||
callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | |||
numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | |||
actualBufferSize); | |||
} | |||
else | |||
{ | |||
outputBuffer.clear(); | |||
} | |||
} | |||
if (player != nullptr) | |||
player->writeBuffer (outputBuffer, *this); | |||
} | |||
void run() override | |||
{ | |||
setThreadToAudioPriority (); | |||
if (recorder != nullptr) recorder->start(); | |||
if (player != nullptr) player->start(); | |||
while (! threadShouldExit()) | |||
processBuffers(); | |||
} | |||
void setThreadToAudioPriority () | |||
{ | |||
// see android.os.Process.THREAD_PRIORITY_AUDIO | |||
const int THREAD_PRIORITY_AUDIO = -16; | |||
jint priority = THREAD_PRIORITY_AUDIO; | |||
if (priority != android.activity.callIntMethod (JuceAppActivity.setCurrentThreadPriority, (jint) priority)) | |||
DBG ("Unable to set audio thread priority: priority is still " << priority); | |||
} | |||
//================================================================================================== | |||
@@ -225,7 +337,8 @@ private: | |||
{ | |||
if (library.open ("libOpenSLES.so")) | |||
{ | |||
typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, SLuint32, const SLInterfaceID*, const SLboolean*); | |||
typedef SLresult (*CreateEngineFunc) (SLObjectItf*, SLuint32, const SLEngineOption*, | |||
SLuint32, const SLInterfaceID*, const SLboolean*); | |||
if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) | |||
{ | |||
@@ -252,21 +365,21 @@ private: | |||
if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); | |||
} | |||
Player* createPlayer (const int numChannels, const int sampleRate) | |||
Player* createPlayer (const int numChannels, const int sampleRate, const int numBuffers, const int bufferSize) | |||
{ | |||
if (numChannels <= 0) | |||
return nullptr; | |||
ScopedPointer<Player> player (new Player (numChannels, sampleRate, *this)); | |||
ScopedPointer<Player> player (new Player (numChannels, sampleRate, *this, numBuffers, bufferSize)); | |||
return player->openedOk() ? player.release() : nullptr; | |||
} | |||
Recorder* createRecorder (const int numChannels, const int sampleRate) | |||
Recorder* createRecorder (const int numChannels, const int sampleRate, const int numBuffers, const int bufferSize) | |||
{ | |||
if (numChannels <= 0) | |||
return nullptr; | |||
ScopedPointer<Recorder> recorder (new Recorder (numChannels, sampleRate, *this)); | |||
ScopedPointer<Recorder> recorder (new Recorder (numChannels, sampleRate, *this, numBuffers, bufferSize)); | |||
return recorder->openedOk() ? recorder.release() : nullptr; | |||
} | |||
@@ -288,12 +401,13 @@ private: | |||
//================================================================================================== | |||
struct BufferList | |||
{ | |||
BufferList (const int numChannels_) | |||
: numChannels (numChannels_), bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) | |||
BufferList (const int numChannels_, const int numBuffers_, const int numSamples_) | |||
: numChannels (numChannels_), numBuffers (numBuffers_), numSamples (numSamples_), | |||
bufferSpace (numChannels_ * numSamples * numBuffers), nextBlock (0) | |||
{ | |||
} | |||
int16* waitForFreeBuffer (Thread& threadToCheck) | |||
int16* waitForFreeBuffer (Thread& threadToCheck) noexcept | |||
{ | |||
while (numBlocksOut.get() == numBuffers) | |||
{ | |||
@@ -306,7 +420,7 @@ private: | |||
return getNextBuffer(); | |||
} | |||
int16* getNextBuffer() | |||
int16* getNextBuffer() noexcept | |||
{ | |||
if (++nextBlock == numBuffers) | |||
nextBlock = 0; | |||
@@ -314,13 +428,12 @@ private: | |||
return bufferSpace + nextBlock * numChannels * numSamples; | |||
} | |||
void bufferReturned() { --numBlocksOut; dataArrived.signal(); } | |||
void bufferSent() { ++numBlocksOut; dataArrived.signal(); } | |||
void bufferReturned() noexcept { --numBlocksOut; dataArrived.signal(); } | |||
void bufferSent() noexcept { ++numBlocksOut; dataArrived.signal(); } | |||
int getBufferSizeBytes() const { return numChannels * numSamples * sizeof (int16); } | |||
int getBufferSizeBytes() const noexcept { return numChannels * numSamples * sizeof (int16); } | |||
const int numChannels; | |||
enum { numSamples = 256, numBuffers = 16 }; | |||
const int numChannels, numBuffers, numSamples; | |||
private: | |||
HeapBlock<int16> bufferSpace; | |||
@@ -332,24 +445,23 @@ private: | |||
//================================================================================================== | |||
struct Player | |||
{ | |||
Player (int numChannels, int sampleRate, Engine& engine) | |||
Player (int numChannels, int sampleRate, Engine& engine, int playerNumBuffers, int playerBufferSize) | |||
: playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), | |||
bufferList (numChannels) | |||
bufferList (numChannels, playerNumBuffers, playerBufferSize) | |||
{ | |||
jassert (numChannels == 2); | |||
SLDataFormat_PCM pcmFormat = | |||
{ | |||
SL_DATAFORMAT_PCM, | |||
(SLuint32) numChannels, | |||
(SLuint32) (sampleRate * 1000), // (sample rate units are millihertz) | |||
(SLuint32) (sampleRate * 1000), | |||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||
SL_PCMSAMPLEFORMAT_FIXED_16, | |||
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, | |||
(numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT), | |||
SL_BYTEORDER_LITTLEENDIAN | |||
}; | |||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, | |||
static_cast<SLuint32> (bufferList.numBuffers) }; | |||
SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; | |||
SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; | |||
@@ -385,10 +497,11 @@ private: | |||
void start() | |||
{ | |||
jassert (openedOk()); | |||
check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); | |||
} | |||
void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) | |||
void writeBuffer (const AudioSampleBuffer& buffer, Thread& thread) noexcept | |||
{ | |||
jassert (buffer.getNumChannels() == bufferList.numChannels); | |||
jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | |||
@@ -398,26 +511,27 @@ private: | |||
while (numSamples > 0) | |||
{ | |||
int16* const destBuffer = bufferList.waitForFreeBuffer (thread); | |||
if (destBuffer == nullptr) | |||
break; | |||
for (int i = 0; i < bufferList.numChannels; ++i) | |||
if (int16* const destBuffer = bufferList.waitForFreeBuffer (thread)) | |||
{ | |||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType; | |||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType; | |||
for (int i = 0; i < bufferList.numChannels; ++i) | |||
{ | |||
typedef AudioData::Pointer<AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> DstSampleType; | |||
typedef AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SrcSampleType; | |||
DstSampleType dstData (destBuffer + i, bufferList.numChannels); | |||
SrcSampleType srcData (buffer.getReadPointer (i, offset)); | |||
dstData.convertSamples (srcData, bufferList.numSamples); | |||
} | |||
DstSampleType dstData (destBuffer + i, bufferList.numChannels); | |||
SrcSampleType srcData (buffer.getReadPointer (i, offset)); | |||
dstData.convertSamples (srcData, bufferList.numSamples); | |||
} | |||
check ((*playerBufferQueue)->Enqueue (playerBufferQueue, destBuffer, bufferList.getBufferSizeBytes())); | |||
bufferList.bufferSent(); | |||
enqueueBuffer (destBuffer); | |||
numSamples -= bufferList.numSamples; | |||
offset += bufferList.numSamples; | |||
numSamples -= bufferList.numSamples; | |||
offset += bufferList.numSamples; | |||
} | |||
else | |||
{ | |||
break; | |||
} | |||
} | |||
} | |||
@@ -428,10 +542,16 @@ private: | |||
BufferList bufferList; | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||
void enqueueBuffer (int16* buffer) noexcept | |||
{ | |||
check ((*playerBufferQueue)->Enqueue (playerBufferQueue, buffer, bufferList.getBufferSizeBytes())); | |||
bufferList.bufferSent(); | |||
} | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
{ | |||
jassert (queue == static_cast <Player*> (context)->playerBufferQueue); (void) queue; | |||
static_cast <Player*> (context)->bufferList.bufferReturned(); | |||
jassert (queue == static_cast<Player*> (context)->playerBufferQueue); (void) queue; | |||
static_cast<Player*> (context)->bufferList.bufferReturned(); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) | |||
@@ -440,13 +560,11 @@ private: | |||
//================================================================================================== | |||
struct Recorder | |||
{ | |||
Recorder (int numChannels, int sampleRate, Engine& engine) | |||
Recorder (int numChannels, int sampleRate, Engine& engine, const int numBuffers, const int numSamples) | |||
: recorderObject (nullptr), recorderRecord (nullptr), | |||
recorderBufferQueue (nullptr), configObject (nullptr), | |||
bufferList (numChannels) | |||
bufferList (numChannels, numBuffers, numSamples) | |||
{ | |||
jassert (numChannels == 1); // STEREO doesn't always work!! | |||
SLDataFormat_PCM pcmFormat = | |||
{ | |||
SL_DATAFORMAT_PCM, | |||
@@ -461,7 +579,8 @@ private: | |||
SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; | |||
SLDataSource audioSrc = { &ioDevice, nullptr }; | |||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, bufferList.numBuffers }; | |||
SLDataLocator_AndroidSimpleBufferQueue bufferQueue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, | |||
static_cast<SLuint32> (bufferList.numBuffers) }; | |||
SLDataSink audioSink = { &bufferQueue, &pcmFormat }; | |||
const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | |||
@@ -474,16 +593,14 @@ private: | |||
{ | |||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); | |||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); | |||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDCONFIGURATION, &configObject)); | |||
// not all android versions seem to have a config object | |||
SLresult result = (*recorderObject)->GetInterface (recorderObject, | |||
*engine.SL_IID_ANDROIDCONFIGURATION, &configObject); | |||
if (result != SL_RESULT_SUCCESS) | |||
configObject = nullptr; | |||
check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); | |||
check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||
for (int i = bufferList.numBuffers; --i >= 0;) | |||
{ | |||
int16* const buffer = bufferList.getNextBuffer(); | |||
jassert (buffer != nullptr); | |||
enqueueBuffer (buffer); | |||
} | |||
} | |||
} | |||
} | |||
@@ -519,25 +636,27 @@ private: | |||
while (numSamples > 0) | |||
{ | |||
int16* const srcBuffer = bufferList.waitForFreeBuffer (thread); | |||
if (srcBuffer == nullptr) | |||
break; | |||
for (int i = 0; i < bufferList.numChannels; ++i) | |||
if (int16* const srcBuffer = bufferList.waitForFreeBuffer (thread)) | |||
{ | |||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType; | |||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType; | |||
for (int i = 0; i < bufferList.numChannels; ++i) | |||
{ | |||
typedef AudioData::Pointer<AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> DstSampleType; | |||
typedef AudioData::Pointer<AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::Const> SrcSampleType; | |||
DstSampleType dstData (buffer.getWritePointer (i, offset)); | |||
SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); | |||
dstData.convertSamples (srcData, bufferList.numSamples); | |||
} | |||
DstSampleType dstData (buffer.getWritePointer (i, offset)); | |||
SrcSampleType srcData (srcBuffer + i, bufferList.numChannels); | |||
dstData.convertSamples (srcData, bufferList.numSamples); | |||
} | |||
enqueueBuffer (srcBuffer); | |||
enqueueBuffer (srcBuffer); | |||
numSamples -= bufferList.numSamples; | |||
offset += bufferList.numSamples; | |||
numSamples -= bufferList.numSamples; | |||
offset += bufferList.numSamples; | |||
} | |||
else | |||
{ | |||
break; | |||
} | |||
} | |||
} | |||
@@ -558,16 +677,16 @@ private: | |||
BufferList bufferList; | |||
void enqueueBuffer (int16* buffer) | |||
void enqueueBuffer (int16* buffer) noexcept | |||
{ | |||
check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); | |||
bufferList.bufferSent(); | |||
} | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
{ | |||
jassert (queue == static_cast <Recorder*> (context)->recorderBufferQueue); (void) queue; | |||
static_cast <Recorder*> (context)->bufferList.bufferReturned(); | |||
jassert (queue == static_cast<Recorder*> (context)->recorderBufferQueue); (void) queue; | |||
static_cast<Recorder*> (context)->bufferList.bufferReturned(); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) | |||
@@ -581,7 +700,7 @@ private: | |||
ScopedPointer<Recorder> recorder; | |||
//============================================================================== | |||
static bool check (const SLresult result) | |||
static bool check (const SLresult result) noexcept | |||
{ | |||
jassert (result == SL_RESULT_SUCCESS); | |||
return result == SL_RESULT_SUCCESS; | |||
@@ -598,14 +717,14 @@ public: | |||
OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} | |||
//============================================================================== | |||
void scanForDevices() {} | |||
StringArray getDeviceNames (bool wantInputNames) const { return StringArray (openSLTypeName); } | |||
int getDefaultDeviceIndex (bool forInput) const { return 0; } | |||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const { return device != nullptr ? 0 : -1; } | |||
bool hasSeparateInputsAndOutputs() const { return false; } | |||
void scanForDevices() override {} | |||
StringArray getDeviceNames (bool wantInputNames) const override { return StringArray (openSLTypeName); } | |||
int getDefaultDeviceIndex (bool forInput) const override { return 0; } | |||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const override { return device != nullptr ? 0 : -1; } | |||
bool hasSeparateInputsAndOutputs() const override { return false; } | |||
AudioIODevice* createDevice (const String& outputDeviceName, | |||
const String& inputDeviceName) | |||
const String& inputDeviceName) override | |||
{ | |||
ScopedPointer<OpenSLAudioIODevice> dev; | |||
@@ -233,7 +233,7 @@ public: | |||
for (int i = 0; i < numStreams; ++i) | |||
{ | |||
const AudioBuffer& b = bufList->mBuffers[i]; | |||
const ::AudioBuffer& b = bufList->mBuffers[i]; | |||
for (unsigned int j = 0; j < b.mNumberChannels; ++j) | |||
{ | |||
@@ -1945,7 +1945,7 @@ private: | |||
for (int i = 0; i < numStreams; ++i) | |||
{ | |||
const AudioBuffer& b = bufList->mBuffers[i]; | |||
const ::AudioBuffer& b = bufList->mBuffers[i]; | |||
total += b.mNumberChannels; | |||
} | |||
} | |||
@@ -380,7 +380,7 @@ public: | |||
&destinationAudioFormat); | |||
if (status == noErr) | |||
{ | |||
bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (AudioBuffer)); | |||
bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (::AudioBuffer)); | |||
bufferList->mNumberBuffers = numChannels; | |||
ok = true; | |||
} | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_formats", | |||
"name": "JUCE audio file format codecs", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for reading and writing various audio file formats.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -45,6 +45,11 @@ | |||
#pragma clang diagnostic ignored "-Wsign-conversion" | |||
#endif | |||
#ifdef _MSC_VER | |||
#pragma warning (push) | |||
#pragma warning (disable : 4127) | |||
#endif | |||
#include "AAX_Exports.cpp" | |||
#include "AAX_ICollection.h" | |||
#include "AAX_IComponentDescriptor.h" | |||
@@ -63,6 +68,10 @@ | |||
#include "AAX_UtilsNative.h" | |||
#include "AAX_Enums.h" | |||
#ifdef _MSC_VER | |||
#pragma warning (pop) | |||
#endif | |||
#ifdef __clang__ | |||
#pragma clang diagnostic pop | |||
#endif | |||
@@ -504,10 +504,8 @@ public: | |||
return numChannelConfigs; | |||
} | |||
UInt32 GetAudioChannelLayout (AudioUnitScope scope, | |||
AudioUnitElement element, | |||
AudioChannelLayout *outLayoutPtr, | |||
Boolean &outWritable) override | |||
UInt32 GetAudioChannelLayout (AudioUnitScope scope, AudioUnitElement element, | |||
AudioChannelLayout* outLayoutPtr, Boolean& outWritable) override | |||
{ | |||
// fallback to old code if this plug-in does not have multi channel IO | |||
if (! hasMultiChannelConfiguration()) | |||
@@ -886,7 +884,7 @@ public: | |||
for (unsigned int i = 0; i < outBuffer.mNumberBuffers; ++i) | |||
{ | |||
AudioBuffer& buf = outBuffer.mBuffers[i]; | |||
::AudioBuffer& buf = outBuffer.mBuffers[i]; | |||
if (buf.mNumberChannels == 1) | |||
{ | |||
@@ -908,7 +906,7 @@ public: | |||
for (unsigned int i = 0; i < inBuffer.mNumberBuffers; ++i) | |||
{ | |||
const AudioBuffer& buf = inBuffer.mBuffers[i]; | |||
const ::AudioBuffer& buf = inBuffer.mBuffers[i]; | |||
if (buf.mNumberChannels == 1) | |||
{ | |||
@@ -1027,7 +1025,7 @@ public: | |||
for (unsigned int i = 0; i < outBuffer.mNumberBuffers; ++i) | |||
{ | |||
AudioBuffer& buf = outBuffer.mBuffers[i]; | |||
::AudioBuffer& buf = outBuffer.mBuffers[i]; | |||
if (buf.mNumberChannels > 1) | |||
{ | |||
@@ -69,6 +69,11 @@ | |||
#pragma clang diagnostic ignored "-Wnon-virtual-dtor" | |||
#endif | |||
#ifdef _MSC_VER | |||
#pragma warning (push) | |||
#pragma warning (disable : 4458) | |||
#endif | |||
/* These files come with the Steinberg VST SDK - to get them, you'll need to | |||
visit the Steinberg website and agree to whatever is currently required to | |||
get them. The best version to get is the VST3 SDK, which also contains | |||
@@ -96,6 +101,10 @@ | |||
namespace juce { extern Steinberg::FUID getJuceVST3ComponentIID(); } | |||
#endif | |||
#ifdef _MSC_VER | |||
#pragma warning (pop) | |||
#endif | |||
#ifdef __clang__ | |||
#pragma clang diagnostic pop | |||
#endif | |||
@@ -243,6 +252,27 @@ class JuceVSTWrapper : public AudioEffectX, | |||
private Timer, | |||
private AsyncUpdater | |||
{ | |||
private: | |||
//============================================================================== | |||
template <typename FloatType> | |||
struct VstTempBuffers | |||
{ | |||
VstTempBuffers() {} | |||
~VstTempBuffers() { release(); } | |||
void release() noexcept | |||
{ | |||
for (int i = tempChannels.size(); --i >= 0;) | |||
delete[] (tempChannels.getUnchecked(i)); | |||
tempChannels.clear(); | |||
} | |||
HeapBlock<FloatType*> channels; | |||
Array<FloatType*> tempChannels; // see note in processReplacing() | |||
juce::AudioBuffer<FloatType> processTempBuffer; | |||
}; | |||
public: | |||
//============================================================================== | |||
JuceVSTWrapper (audioMasterCallback audioMasterCB, AudioProcessor* const af) | |||
@@ -264,7 +294,6 @@ public: | |||
#else | |||
useNSView (false), | |||
#endif | |||
processTempBuffer (1, 1), | |||
hostWindow (0) | |||
{ | |||
filter->setPlayConfigDetails (numInChans, numOutChans, 0, 0); | |||
@@ -280,6 +309,7 @@ public: | |||
setNumOutputs (numOutChans); | |||
canProcessReplacing (true); | |||
canDoubleReplacing (filter->supportsDoublePrecisionProcessing()); | |||
isSynth ((JucePlugin_IsSynth) != 0); | |||
setInitialDelay (filter->getLatencySamples()); | |||
@@ -306,7 +336,6 @@ public: | |||
jassert (editorComp == 0); | |||
channels.free(); | |||
deleteTempChannels(); | |||
jassert (activePlugins.contains (this)); | |||
@@ -496,23 +525,27 @@ public: | |||
void process (float** inputs, float** outputs, VstInt32 numSamples) | |||
{ | |||
VstTempBuffers<float>& tmpBuffers = floatTempBuffers; | |||
const int numIn = numInChans; | |||
const int numOut = numOutChans; | |||
processTempBuffer.setSize (numIn, numSamples, false, false, true); | |||
tmpBuffers.processTempBuffer.setSize (numIn, numSamples, false, false, true); | |||
for (int i = numIn; --i >= 0;) | |||
processTempBuffer.copyFrom (i, 0, outputs[i], numSamples); | |||
tmpBuffers.processTempBuffer.copyFrom (i, 0, outputs[i], numSamples); | |||
processReplacing (inputs, outputs, numSamples); | |||
AudioSampleBuffer dest (outputs, numOut, numSamples); | |||
for (int i = jmin (numIn, numOut); --i >= 0;) | |||
dest.addFrom (i, 0, processTempBuffer, i, 0, numSamples); | |||
dest.addFrom (i, 0, tmpBuffers.processTempBuffer, i, 0, numSamples); | |||
} | |||
void processReplacing (float** inputs, float** outputs, VstInt32 numSamples) override | |||
template <typename FloatType> | |||
void internalProcessReplacing (FloatType** inputs, FloatType** outputs, | |||
VstInt32 numSamples, VstTempBuffers<FloatType>& tmpBuffers) | |||
{ | |||
if (firstProcessCallback) | |||
{ | |||
@@ -557,7 +590,7 @@ public: | |||
int i; | |||
for (i = 0; i < numOut; ++i) | |||
{ | |||
float* chan = tempChannels.getUnchecked(i); | |||
FloatType* chan = tmpBuffers.tempChannels.getUnchecked(i); | |||
if (chan == nullptr) | |||
{ | |||
@@ -570,24 +603,24 @@ public: | |||
{ | |||
if (outputs[j] == chan) | |||
{ | |||
chan = new float [blockSize * 2]; | |||
tempChannels.set (i, chan); | |||
chan = new FloatType [blockSize * 2]; | |||
tmpBuffers.tempChannels.set (i, chan); | |||
break; | |||
} | |||
} | |||
} | |||
if (i < numIn && chan != inputs[i]) | |||
memcpy (chan, inputs[i], sizeof (float) * (size_t) numSamples); | |||
memcpy (chan, inputs[i], sizeof (FloatType) * (size_t) numSamples); | |||
channels[i] = chan; | |||
tmpBuffers.channels[i] = chan; | |||
} | |||
for (; i < numIn; ++i) | |||
channels[i] = inputs[i]; | |||
tmpBuffers.channels[i] = inputs[i]; | |||
{ | |||
AudioSampleBuffer chans (channels, jmax (numIn, numOut), numSamples); | |||
AudioBuffer<FloatType> chans (tmpBuffers.channels, jmax (numIn, numOut), numSamples); | |||
if (isBypassed) | |||
filter->processBlockBypassed (chans, midiEvents); | |||
@@ -597,8 +630,8 @@ public: | |||
// copy back any temp channels that may have been used.. | |||
for (i = 0; i < numOut; ++i) | |||
if (const float* const chan = tempChannels.getUnchecked(i)) | |||
memcpy (outputs[i], chan, sizeof (float) * (size_t) numSamples); | |||
if (const FloatType* const chan = tmpBuffers.tempChannels.getUnchecked(i)) | |||
memcpy (outputs[i], chan, sizeof (FloatType) * (size_t) numSamples); | |||
} | |||
} | |||
@@ -644,16 +677,47 @@ public: | |||
} | |||
} | |||
void processReplacing (float** inputs, float** outputs, VstInt32 sampleFrames) override | |||
{ | |||
jassert (! filter->isUsingDoublePrecision()); | |||
internalProcessReplacing (inputs, outputs, sampleFrames, floatTempBuffers); | |||
} | |||
void processDoubleReplacing (double** inputs, double** outputs, VstInt32 sampleFrames) override | |||
{ | |||
jassert (filter->isUsingDoublePrecision()); | |||
internalProcessReplacing (inputs, outputs, sampleFrames, doubleTempBuffers); | |||
} | |||
//============================================================================== | |||
VstInt32 startProcess() override { return 0; } | |||
VstInt32 stopProcess() override { return 0; } | |||
//============================================================================== | |||
bool setProcessPrecision (VstInt32 vstPrecision) override | |||
{ | |||
if (! isProcessing) | |||
{ | |||
if (filter != nullptr) | |||
{ | |||
filter->setProcessingPrecision (vstPrecision == kVstProcessPrecision64 && filter->supportsDoublePrecisionProcessing() | |||
? AudioProcessor::doublePrecision | |||
: AudioProcessor::singlePrecision); | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
void resume() override | |||
{ | |||
if (filter != nullptr) | |||
{ | |||
isProcessing = true; | |||
channels.calloc ((size_t) (numInChans + numOutChans)); | |||
floatTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); | |||
doubleTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); | |||
double rate = getSampleRate(); | |||
jassert (rate > 0); | |||
@@ -695,7 +759,8 @@ public: | |||
outgoingEvents.freeEvents(); | |||
isProcessing = false; | |||
channels.free(); | |||
floatTempBuffers.channels.free(); | |||
doubleTempBuffers.channels.free(); | |||
deleteTempChannels(); | |||
} | |||
@@ -1453,9 +1518,8 @@ private: | |||
int numInChans, numOutChans; | |||
bool isProcessing, isBypassed, hasShutdown, isInSizeWindow, firstProcessCallback; | |||
bool shouldDeleteEditor, useNSView; | |||
HeapBlock<float*> channels; | |||
Array<float*> tempChannels; // see note in processReplacing() | |||
AudioSampleBuffer processTempBuffer; | |||
VstTempBuffers<float> floatTempBuffers; | |||
VstTempBuffers<double> doubleTempBuffers; | |||
#if JUCE_MAC | |||
void* hostWindow; | |||
@@ -1513,15 +1577,22 @@ private: | |||
#endif | |||
//============================================================================== | |||
void deleteTempChannels() | |||
template <typename FloatType> | |||
void deleteTempChannels (VstTempBuffers<FloatType>& tmpBuffers) | |||
{ | |||
for (int i = tempChannels.size(); --i >= 0;) | |||
delete[] (tempChannels.getUnchecked(i)); | |||
tempChannels.clear(); | |||
tmpBuffers.release(); | |||
if (filter != nullptr) | |||
tempChannels.insertMultiple (0, nullptr, filter->getNumInputChannels() + filter->getNumOutputChannels()); | |||
{ | |||
int numChannels = filter->getNumInputChannels() + filter->getNumOutputChannels(); | |||
tmpBuffers.tempChannels.insertMultiple (0, nullptr, numChannels); | |||
} | |||
} | |||
void deleteTempChannels() | |||
{ | |||
deleteTempChannels (floatTempBuffers); | |||
deleteTempChannels (doubleTempBuffers); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) | |||
@@ -743,6 +743,14 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVST3EditController) | |||
}; | |||
namespace | |||
{ | |||
template <typename FloatType> struct AudioBusPointerHelper {}; | |||
template <> struct AudioBusPointerHelper<float> { static inline float** impl (Vst::AudioBusBuffers& data) noexcept { return data.channelBuffers32; } }; | |||
template <> struct AudioBusPointerHelper<double> { static inline double** impl (Vst::AudioBusBuffers& data) noexcept { return data.channelBuffers64; } }; | |||
} | |||
//============================================================================== | |||
class JuceVST3Component : public Vst::IComponent, | |||
public Vst::IAudioProcessor, | |||
@@ -956,8 +964,8 @@ public: | |||
? (int) processSetup.maxSamplesPerBlock | |||
: bufferSize; | |||
channelList.clear(); | |||
channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1); | |||
allocateChannelLists (channelListFloat); | |||
allocateChannelLists (channelListDouble); | |||
preparePlugin (sampleRate, bufferSize); | |||
} | |||
@@ -1440,7 +1448,9 @@ public: | |||
tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override | |||
{ | |||
return symbolicSampleSize == Vst::kSample32 ? kResultTrue : kResultFalse; | |||
return (symbolicSampleSize == Vst::kSample32 | |||
|| (getPluginInstance().supportsDoublePrecisionProcessing() | |||
&& symbolicSampleSize == Vst::kSample64)) ? kResultTrue : kResultFalse; | |||
} | |||
Steinberg::uint32 PLUGIN_API getLatencySamples() override | |||
@@ -1456,6 +1466,10 @@ public: | |||
processSetup = newSetup; | |||
processContext.sampleRate = processSetup.sampleRate; | |||
getPluginInstance().setProcessingPrecision (newSetup.symbolicSampleSize == Vst::kSample64 | |||
? AudioProcessor::doublePrecision | |||
: AudioProcessor::singlePrecision); | |||
preparePlugin (processSetup.sampleRate, processSetup.maxSamplesPerBlock); | |||
return kResultTrue; | |||
@@ -1532,9 +1546,13 @@ public: | |||
tresult PLUGIN_API process (Vst::ProcessData& data) override | |||
{ | |||
if (pluginInstance == nullptr) | |||
return kResultFalse; | |||
if ((processSetup.symbolicSampleSize == Vst::kSample64) != pluginInstance->isUsingDoublePrecision()) | |||
return kResultFalse; | |||
if (data.processContext != nullptr) | |||
processContext = *data.processContext; | |||
else | |||
@@ -1551,60 +1569,19 @@ public: | |||
const int numMidiEventsComingIn = midiBuffer.getNumEvents(); | |||
#endif | |||
const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
int totalChans = 0; | |||
while (totalChans < numInputChans) | |||
if (getHostType().isWavelab()) | |||
{ | |||
channelList.set (totalChans, data.inputs[0].channelBuffers32[totalChans]); | |||
++totalChans; | |||
} | |||
while (totalChans < numOutputChans) | |||
{ | |||
channelList.set (totalChans, data.outputs[0].channelBuffers32[totalChans]); | |||
++totalChans; | |||
} | |||
AudioSampleBuffer buffer; | |||
if (totalChans != 0) | |||
buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); | |||
else if (getHostType().isWavelab() | |||
&& pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels() > 0) | |||
return kResultFalse; | |||
{ | |||
const ScopedLock sl (pluginInstance->getCallbackLock()); | |||
pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); | |||
const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
if (data.inputParameterChanges != nullptr) | |||
processParameterChanges (*data.inputParameterChanges); | |||
if (pluginInstance->isSuspended()) | |||
{ | |||
buffer.clear(); | |||
} | |||
else | |||
{ | |||
if (isBypassed()) | |||
pluginInstance->processBlockBypassed (buffer, midiBuffer); | |||
else | |||
pluginInstance->processBlock (buffer, midiBuffer); | |||
} | |||
if ((pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels()) > 0 | |||
&& (numInputChans + numOutputChans) == 0) | |||
return kResultFalse; | |||
} | |||
for (int i = 0; i < numOutputChans; ++i) | |||
FloatVectorOperations::copy (data.outputs[0].channelBuffers32[i], buffer.getReadPointer (i), (int) data.numSamples); | |||
// clear extra busses.. | |||
if (data.outputs != nullptr) | |||
for (int i = 1; i < data.numOutputs; ++i) | |||
for (int f = 0; f < data.outputs[i].numChannels; ++f) | |||
FloatVectorOperations::clear (data.outputs[i].channelBuffers32[f], (int) data.numSamples); | |||
if (processSetup.symbolicSampleSize == Vst::kSample32) processAudio<float> (data, channelListFloat); | |||
else if (processSetup.symbolicSampleSize == Vst::kSample64) processAudio<double> (data, channelListDouble); | |||
else jassertfalse; | |||
#if JucePlugin_ProducesMidiOutput | |||
if (data.outputEvents != nullptr) | |||
@@ -1648,7 +1625,8 @@ private: | |||
Vst::BusList audioInputs, audioOutputs, eventInputs, eventOutputs; | |||
MidiBuffer midiBuffer; | |||
Array<float*> channelList; | |||
Array<float*> channelListFloat; | |||
Array<double*> channelListDouble; | |||
ScopedJuceInitialiser_GUI libraryInitialiser; | |||
@@ -1656,6 +1634,52 @@ private: | |||
static const char* kJucePrivateDataIdentifier; | |||
//============================================================================== | |||
template <typename FloatType> | |||
void processAudio (Vst::ProcessData& data, Array<FloatType*>& channelList) | |||
{ | |||
const int totalChans = prepareChannelLists (channelList, data); | |||
AudioBuffer<FloatType> buffer; | |||
if (totalChans != 0) | |||
buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); | |||
{ | |||
const ScopedLock sl (pluginInstance->getCallbackLock()); | |||
pluginInstance->setNonRealtime (data.processMode == Vst::kOffline); | |||
if (data.inputParameterChanges != nullptr) | |||
processParameterChanges (*data.inputParameterChanges); | |||
if (pluginInstance->isSuspended()) | |||
{ | |||
buffer.clear(); | |||
} | |||
else | |||
{ | |||
if (isBypassed()) | |||
pluginInstance->processBlockBypassed (buffer, midiBuffer); | |||
else | |||
pluginInstance->processBlock (buffer, midiBuffer); | |||
} | |||
} | |||
if (data.outputs != nullptr) | |||
{ | |||
for (int i = 0; i < data.numOutputs; ++i) | |||
FloatVectorOperations::copy (getPointerForAudioBus<FloatType> (data.outputs[0])[i], | |||
buffer.getReadPointer (i), (int) data.numSamples); | |||
} | |||
// clear extra busses.. | |||
if (data.outputs != nullptr) | |||
for (int i = 1; i < data.numOutputs; ++i) | |||
for (int f = 0; f < data.outputs[i].numChannels; ++f) | |||
FloatVectorOperations::clear (getPointerForAudioBus<FloatType> (data.outputs[i])[f], (int) data.numSamples); | |||
} | |||
//============================================================================== | |||
void addBusTo (Vst::BusList& busList, Vst::Bus* newBus) | |||
{ | |||
@@ -1680,6 +1704,50 @@ private: | |||
return nullptr; | |||
} | |||
//============================================================================== | |||
template <typename FloatType> | |||
void allocateChannelLists (Array<FloatType*>& channelList) | |||
{ | |||
channelList.clear(); | |||
channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1); | |||
} | |||
template <typename FloatType> | |||
static FloatType** getPointerForAudioBus (Vst::AudioBusBuffers& data) noexcept | |||
{ | |||
return AudioBusPointerHelper<FloatType>::impl (data); | |||
} | |||
template <typename FloatType> | |||
static int prepareChannelLists (Array<FloatType*>& channelList, Vst::ProcessData& data) noexcept | |||
{ | |||
int totalChans = 0; | |||
FloatType** inChannelBuffers = | |||
data.inputs != nullptr ? getPointerForAudioBus<FloatType> (data.inputs[0]) : nullptr; | |||
FloatType** outChannelBuffers = | |||
data.outputs != nullptr ? getPointerForAudioBus<FloatType> (data.outputs[0]) : nullptr; | |||
const int numInputChans = (data.inputs != nullptr && inChannelBuffers != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && outChannelBuffers != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
for (int idx = 0; totalChans < numInputChans; ++idx) | |||
{ | |||
channelList.set (totalChans, inChannelBuffers[idx]); | |||
++totalChans; | |||
} | |||
// note that the loop bounds are correct: as VST-3 is always process replacing | |||
// we already know the output channel buffers of the first numInputChans channels | |||
for (int idx = 0; totalChans < numOutputChans; ++idx) | |||
{ | |||
channelList.set (totalChans, outChannelBuffers[idx]); | |||
++totalChans; | |||
} | |||
return totalChans; | |||
} | |||
//============================================================================== | |||
enum InternalParameters | |||
{ | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_plugin_client", | |||
"name": "JUCE audio plugin wrapper classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for building VST, VST3, RTAS, AAX and AU plugins.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -51,7 +51,8 @@ public: | |||
if (Component* const comp = Desktop::getInstance().findComponentAt (screenPos.roundToInt())) | |||
if (ComponentPeer* const peer = comp->getPeer()) | |||
if (! peer->isFocused()) | |||
peer->handleMouseEvent (0, peer->globalToLocal (screenPos), mods, Time::currentTimeMillis()); | |||
peer->handleMouseEvent (0, peer->globalToLocal (screenPos), mods, | |||
MouseInputSource::invalidPressure, Time::currentTimeMillis()); | |||
} | |||
} | |||
@@ -1223,7 +1223,7 @@ private: | |||
//============================================================================== | |||
size_t getAudioBufferSizeInBytes() const noexcept | |||
{ | |||
return offsetof (AudioBufferList, mBuffers) + (sizeof (AudioBuffer) * numOutputBusChannels); | |||
return offsetof (AudioBufferList, mBuffers) + (sizeof (::AudioBuffer) * numOutputBusChannels); | |||
} | |||
AudioBufferList* getAudioBufferListForBus (AudioUnitElement busIndex) const noexcept | |||
@@ -344,21 +344,25 @@ private: | |||
}; | |||
//============================================================================== | |||
namespace VST3BufferExchange | |||
template <typename FloatType> | |||
struct VST3BufferExchange | |||
{ | |||
typedef Array<float*> Bus; | |||
typedef Array<FloatType*> Bus; | |||
typedef Array<Bus> BusMap; | |||
static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, float** raw) { vstBuffers.channelBuffers32 = raw; } | |||
static inline void assignRawPointer (Steinberg::Vst::AudioBusBuffers& vstBuffers, double** raw) { vstBuffers.channelBuffers64 = raw; } | |||
/** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' | |||
@warning For speed, does not check the channel count and offsets | |||
according to the AudioSampleBuffer | |||
*/ | |||
void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, | |||
Bus& bus, | |||
AudioSampleBuffer& buffer, | |||
int numChannels, int channelStartOffset, | |||
int sampleOffset = 0) | |||
static void associateBufferTo (Steinberg::Vst::AudioBusBuffers& vstBuffers, | |||
Bus& bus, | |||
AudioBuffer<FloatType>& buffer, | |||
int numChannels, int channelStartOffset, | |||
int sampleOffset = 0) | |||
{ | |||
const int channelEnd = numChannels + channelStartOffset; | |||
jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); | |||
@@ -368,7 +372,7 @@ namespace VST3BufferExchange | |||
for (int i = channelStartOffset; i < channelEnd; ++i) | |||
bus.add (buffer.getWritePointer (i, sampleOffset)); | |||
vstBuffers.channelBuffers32 = bus.getRawDataPointer(); | |||
assignRawPointer (vstBuffers, bus.getRawDataPointer()); | |||
vstBuffers.numChannels = numChannels; | |||
vstBuffers.silenceFlags = 0; | |||
} | |||
@@ -376,7 +380,7 @@ namespace VST3BufferExchange | |||
static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||
Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||
AudioSampleBuffer& source) | |||
AudioBuffer<FloatType>& source) | |||
{ | |||
const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||
@@ -387,18 +391,16 @@ namespace VST3BufferExchange | |||
busMapToUse.add (Bus()); | |||
if (numChansForBus > 0) | |||
{ | |||
associateBufferTo (result.getReference (index), | |||
busMapToUse.getReference (index), | |||
source, numChansForBus, channelIndexOffset); | |||
} | |||
channelIndexOffset += numChansForBus; | |||
} | |||
inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||
AudioSampleBuffer& source) | |||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||
AudioBuffer<FloatType>& source) | |||
{ | |||
int channelIndexOffset = 0; | |||
@@ -407,10 +409,10 @@ namespace VST3BufferExchange | |||
arrangements.getUnchecked (i), source); | |||
} | |||
inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
Steinberg::Vst::IAudioProcessor& processor, | |||
BusMap& busMapToUse, bool isInput, int numBusses, | |||
AudioSampleBuffer& source) | |||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
Steinberg::Vst::IAudioProcessor& processor, | |||
BusMap& busMapToUse, bool isInput, int numBusses, | |||
AudioBuffer<FloatType>& source) | |||
{ | |||
int channelIndexOffset = 0; | |||
@@ -420,6 +422,28 @@ namespace VST3BufferExchange | |||
getArrangementForBus (&processor, isInput, i), | |||
source); | |||
} | |||
} | |||
}; | |||
template <typename FloatType> | |||
struct VST3FloatAndDoubleBusMapCompositeHelper {}; | |||
struct VST3FloatAndDoubleBusMapComposite | |||
{ | |||
VST3BufferExchange<float>::BusMap floatVersion; | |||
VST3BufferExchange<double>::BusMap doubleVersion; | |||
template <typename FloatType> | |||
inline typename VST3BufferExchange<FloatType>::BusMap& get() { return VST3FloatAndDoubleBusMapCompositeHelper<FloatType>::get (*this); } | |||
}; | |||
template <> struct VST3FloatAndDoubleBusMapCompositeHelper<float> | |||
{ | |||
static inline VST3BufferExchange<float>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.floatVersion; } | |||
}; | |||
template <> struct VST3FloatAndDoubleBusMapCompositeHelper<double> | |||
{ | |||
static inline VST3BufferExchange<double>::BusMap& get (VST3FloatAndDoubleBusMapComposite& impl) { return impl.doubleVersion; } | |||
}; | |||
#endif // JUCE_VST3COMMON_H_INCLUDED |
@@ -1702,7 +1702,7 @@ public: | |||
using namespace Vst; | |||
ProcessSetup setup; | |||
setup.symbolicSampleSize = kSample32; | |||
setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32; | |||
setup.maxSamplesPerBlock = estimatedSamplesPerBlock; | |||
setup.sampleRate = newSampleRate; | |||
setup.processMode = isNonRealtime() ? kOffline : kRealtime; | |||
@@ -1769,39 +1769,56 @@ public: | |||
JUCE_CATCH_ALL_ASSERT | |||
} | |||
void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override | |||
bool supportsDoublePrecisionProcessing() const override | |||
{ | |||
using namespace Vst; | |||
return (processor->canProcessSampleSize (Vst::kSample64) == kResultTrue); | |||
} | |||
if (isActive | |||
&& processor != nullptr | |||
&& processor->canProcessSampleSize (kSample32) == kResultTrue) | |||
{ | |||
const int numSamples = buffer.getNumSamples(); | |||
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | |||
{ | |||
jassert (! isUsingDoublePrecision()); | |||
if (isActive && processor != nullptr) | |||
processAudio (buffer, midiMessages, Vst::kSample32); | |||
} | |||
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | |||
{ | |||
jassert (isUsingDoublePrecision()); | |||
if (isActive && processor != nullptr) | |||
processAudio (buffer, midiMessages, Vst::kSample64); | |||
} | |||
template <typename FloatType> | |||
void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, | |||
Vst::SymbolicSampleSizes sampleSize) | |||
{ | |||
using namespace Vst; | |||
const int numSamples = buffer.getNumSamples(); | |||
ProcessData data; | |||
data.processMode = isNonRealtime() ? kOffline : kRealtime; | |||
data.symbolicSampleSize = kSample32; | |||
data.numInputs = numInputAudioBusses; | |||
data.numOutputs = numOutputAudioBusses; | |||
data.inputParameterChanges = inputParameterChanges; | |||
data.outputParameterChanges = outputParameterChanges; | |||
data.numSamples = (Steinberg::int32) numSamples; | |||
ProcessData data; | |||
data.processMode = isNonRealtime() ? kOffline : kRealtime; | |||
data.symbolicSampleSize = sampleSize; | |||
data.numInputs = numInputAudioBusses; | |||
data.numOutputs = numOutputAudioBusses; | |||
data.inputParameterChanges = inputParameterChanges; | |||
data.outputParameterChanges = outputParameterChanges; | |||
data.numSamples = (Steinberg::int32) numSamples; | |||
updateTimingInformation (data, getSampleRate()); | |||
updateTimingInformation (data, getSampleRate()); | |||
for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) | |||
buffer.clear (i, 0, numSamples); | |||
for (int i = getNumInputChannels(); i < buffer.getNumChannels(); ++i) | |||
buffer.clear (i, 0, numSamples); | |||
associateTo (data, buffer); | |||
associateTo (data, midiMessages); | |||
associateTo (data, buffer); | |||
associateTo (data, midiMessages); | |||
processor->process (data); | |||
processor->process (data); | |||
MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); | |||
MidiEventList::toMidiBuffer (midiMessages, *midiOutputs); | |||
inputParameterChanges->clearAllQueues(); | |||
} | |||
inputParameterChanges->clearAllQueues(); | |||
} | |||
//============================================================================== | |||
@@ -2151,7 +2168,7 @@ private: | |||
*/ | |||
int numInputAudioBusses, numOutputAudioBusses; | |||
Array<Vst::SpeakerArrangement> inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). | |||
VST3BufferExchange::BusMap inputBusMap, outputBusMap; | |||
VST3FloatAndDoubleBusMapComposite inputBusMap, outputBusMap; | |||
Array<Vst::AudioBusBuffers> inputBusses, outputBusses; | |||
//============================================================================== | |||
@@ -2393,12 +2410,11 @@ private: | |||
} | |||
//============================================================================== | |||
void associateTo (Vst::ProcessData& destination, AudioSampleBuffer& buffer) | |||
template <typename FloatType> | |||
void associateTo (Vst::ProcessData& destination, AudioBuffer<FloatType>& buffer) | |||
{ | |||
using namespace VST3BufferExchange; | |||
mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer); | |||
mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer); | |||
VST3BufferExchange<FloatType>::mapBufferToBusses (inputBusses, inputBusMap.get<FloatType>(), inputArrangements, buffer); | |||
VST3BufferExchange<FloatType>::mapBufferToBusses (outputBusses, outputBusMap.get<FloatType>(), outputArrangements, buffer); | |||
destination.inputs = inputBusses.getRawDataPointer(); | |||
destination.outputs = outputBusses.getRawDataPointer(); | |||
@@ -715,8 +715,7 @@ public: | |||
name (mh->pluginName), | |||
wantsMidiMessages (false), | |||
initialised (false), | |||
isPowerOn (false), | |||
tempBuffer (1, 1) | |||
isPowerOn (false) | |||
{ | |||
try | |||
{ | |||
@@ -938,6 +937,18 @@ public: | |||
dispatch (effSetSampleRate, 0, 0, 0, (float) rate); | |||
dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); | |||
if (supportsDoublePrecisionProcessing()) | |||
{ | |||
VstInt32 vstPrecision = isUsingDoublePrecision() ? kVstProcessPrecision64 | |||
: kVstProcessPrecision32; | |||
// if you get an assertion here then your plug-in claims it supports double precision | |||
// but returns an error when we try to change the precision | |||
VstIntPtr err = dispatch (effSetProcessPrecision, 0, (VstIntPtr) vstPrecision, 0, 0); | |||
jassert (err > 0); | |||
ignoreUnused (err); | |||
} | |||
tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); | |||
if (! isPowerOn) | |||
@@ -980,110 +991,22 @@ public: | |||
} | |||
} | |||
void processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) override | |||
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override | |||
{ | |||
const int numSamples = buffer.getNumSamples(); | |||
if (initialised) | |||
{ | |||
if (AudioPlayHead* const currentPlayHead = getPlayHead()) | |||
{ | |||
AudioPlayHead::CurrentPositionInfo position; | |||
if (currentPlayHead->getCurrentPosition (position)) | |||
{ | |||
vstHostTime.samplePos = (double) position.timeInSamples; | |||
vstHostTime.tempo = position.bpm; | |||
vstHostTime.timeSigNumerator = position.timeSigNumerator; | |||
vstHostTime.timeSigDenominator = position.timeSigDenominator; | |||
vstHostTime.ppqPos = position.ppqPosition; | |||
vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; | |||
vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; | |||
VstInt32 newTransportFlags = 0; | |||
if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; | |||
if (position.isRecording) newTransportFlags |= kVstTransportRecording; | |||
if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) | |||
vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; | |||
else | |||
vstHostTime.flags &= ~kVstTransportChanged; | |||
switch (position.frameRate) | |||
{ | |||
case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; | |||
case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; | |||
case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; | |||
default: break; | |||
} | |||
if (position.isLooping) | |||
{ | |||
vstHostTime.cycleStartPos = position.ppqLoopStart; | |||
vstHostTime.cycleEndPos = position.ppqLoopEnd; | |||
vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); | |||
} | |||
else | |||
{ | |||
vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); | |||
} | |||
} | |||
} | |||
vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); | |||
if (wantsMidiMessages) | |||
{ | |||
midiEventsToSend.clear(); | |||
midiEventsToSend.ensureSize (1); | |||
MidiBuffer::Iterator iter (midiMessages); | |||
const uint8* midiData; | |||
int numBytesOfMidiData, samplePosition; | |||
while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) | |||
{ | |||
midiEventsToSend.addEvent (midiData, numBytesOfMidiData, | |||
jlimit (0, numSamples - 1, samplePosition)); | |||
} | |||
effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); | |||
} | |||
_clearfp(); | |||
if ((effect->flags & effFlagsCanReplacing) != 0) | |||
{ | |||
effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), numSamples); | |||
} | |||
else | |||
{ | |||
tempBuffer.setSize (effect->numOutputs, numSamples); | |||
tempBuffer.clear(); | |||
effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), numSamples); | |||
for (int i = effect->numOutputs; --i >= 0;) | |||
buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), numSamples); | |||
} | |||
} | |||
else | |||
{ | |||
// Not initialised, so just bypass.. | |||
for (int i = 0; i < getNumOutputChannels(); ++i) | |||
buffer.clear (i, 0, buffer.getNumSamples()); | |||
} | |||
jassert (! isUsingDoublePrecision()); | |||
processAudio (buffer, midiMessages); | |||
} | |||
{ | |||
// copy any incoming midi.. | |||
const ScopedLock sl (midiInLock); | |||
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override | |||
{ | |||
jassert (isUsingDoublePrecision()); | |||
processAudio (buffer, midiMessages); | |||
} | |||
midiMessages.swapWith (incomingMidi); | |||
incomingMidi.clear(); | |||
} | |||
bool supportsDoublePrecisionProcessing() const override | |||
{ | |||
return ((effect->flags & effFlagsCanReplacing) != 0 | |||
&& (effect->flags & effFlagsCanDoubleReplacing) != 0); | |||
} | |||
//============================================================================== | |||
@@ -1673,12 +1596,132 @@ private: | |||
CriticalSection lock; | |||
bool wantsMidiMessages, initialised, isPowerOn; | |||
mutable StringArray programNames; | |||
AudioSampleBuffer tempBuffer; | |||
AudioBuffer<float> tempBuffer; | |||
CriticalSection midiInLock; | |||
MidiBuffer incomingMidi; | |||
VSTMidiEventList midiEventsToSend; | |||
VstTimeInfo vstHostTime; | |||
//============================================================================== | |||
template <typename FloatType> | |||
void processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
const int numSamples = buffer.getNumSamples(); | |||
if (initialised) | |||
{ | |||
if (AudioPlayHead* const currentPlayHead = getPlayHead()) | |||
{ | |||
AudioPlayHead::CurrentPositionInfo position; | |||
if (currentPlayHead->getCurrentPosition (position)) | |||
{ | |||
vstHostTime.samplePos = (double) position.timeInSamples; | |||
vstHostTime.tempo = position.bpm; | |||
vstHostTime.timeSigNumerator = position.timeSigNumerator; | |||
vstHostTime.timeSigDenominator = position.timeSigDenominator; | |||
vstHostTime.ppqPos = position.ppqPosition; | |||
vstHostTime.barStartPos = position.ppqPositionOfLastBarStart; | |||
vstHostTime.flags |= kVstTempoValid | kVstTimeSigValid | kVstPpqPosValid | kVstBarsValid; | |||
VstInt32 newTransportFlags = 0; | |||
if (position.isPlaying) newTransportFlags |= kVstTransportPlaying; | |||
if (position.isRecording) newTransportFlags |= kVstTransportRecording; | |||
if (newTransportFlags != (vstHostTime.flags & (kVstTransportPlaying | kVstTransportRecording))) | |||
vstHostTime.flags = (vstHostTime.flags & ~(kVstTransportPlaying | kVstTransportRecording)) | newTransportFlags | kVstTransportChanged; | |||
else | |||
vstHostTime.flags &= ~kVstTransportChanged; | |||
switch (position.frameRate) | |||
{ | |||
case AudioPlayHead::fps24: setHostTimeFrameRate (0, 24.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps25: setHostTimeFrameRate (1, 25.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps2997: setHostTimeFrameRate (2, 29.97, position.timeInSeconds); break; | |||
case AudioPlayHead::fps30: setHostTimeFrameRate (3, 30.0, position.timeInSeconds); break; | |||
case AudioPlayHead::fps2997drop: setHostTimeFrameRate (4, 29.97, position.timeInSeconds); break; | |||
case AudioPlayHead::fps30drop: setHostTimeFrameRate (5, 29.97, position.timeInSeconds); break; | |||
default: break; | |||
} | |||
if (position.isLooping) | |||
{ | |||
vstHostTime.cycleStartPos = position.ppqLoopStart; | |||
vstHostTime.cycleEndPos = position.ppqLoopEnd; | |||
vstHostTime.flags |= (kVstCyclePosValid | kVstTransportCycleActive); | |||
} | |||
else | |||
{ | |||
vstHostTime.flags &= ~(kVstCyclePosValid | kVstTransportCycleActive); | |||
} | |||
} | |||
} | |||
vstHostTime.nanoSeconds = getVSTHostTimeNanoseconds(); | |||
if (wantsMidiMessages) | |||
{ | |||
midiEventsToSend.clear(); | |||
midiEventsToSend.ensureSize (1); | |||
MidiBuffer::Iterator iter (midiMessages); | |||
const uint8* midiData; | |||
int numBytesOfMidiData, samplePosition; | |||
while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) | |||
{ | |||
midiEventsToSend.addEvent (midiData, numBytesOfMidiData, | |||
jlimit (0, numSamples - 1, samplePosition)); | |||
} | |||
effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend.events, 0); | |||
} | |||
_clearfp(); | |||
invokeProcessFunction (buffer, numSamples); | |||
} | |||
else | |||
{ | |||
// Not initialised, so just bypass.. | |||
for (int i = 0; i < getNumOutputChannels(); ++i) | |||
buffer.clear (i, 0, buffer.getNumSamples()); | |||
} | |||
{ | |||
// copy any incoming midi.. | |||
const ScopedLock sl (midiInLock); | |||
midiMessages.swapWith (incomingMidi); | |||
incomingMidi.clear(); | |||
} | |||
} | |||
//============================================================================== | |||
inline void invokeProcessFunction (AudioBuffer<float>& buffer, VstInt32 sampleFrames) | |||
{ | |||
if ((effect->flags & effFlagsCanReplacing) != 0) | |||
{ | |||
effect->processReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); | |||
} | |||
else | |||
{ | |||
tempBuffer.setSize (effect->numOutputs, sampleFrames); | |||
tempBuffer.clear(); | |||
effect->process (effect, buffer.getArrayOfWritePointers(), tempBuffer.getArrayOfWritePointers(), sampleFrames); | |||
for (int i = effect->numOutputs; --i >= 0;) | |||
buffer.copyFrom (i, 0, tempBuffer.getReadPointer (i), sampleFrames); | |||
} | |||
} | |||
inline void invokeProcessFunction (AudioBuffer<double>& buffer, VstInt32 sampleFrames) | |||
{ | |||
effect->processDoubleReplacing (effect, buffer.getArrayOfWritePointers(), buffer.getArrayOfWritePointers(), sampleFrames); | |||
} | |||
//============================================================================== | |||
void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept | |||
{ | |||
@@ -166,5 +166,7 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||
#include "scanning/juce_KnownPluginList.cpp" | |||
#include "scanning/juce_PluginDirectoryScanner.cpp" | |||
#include "scanning/juce_PluginListComponent.cpp" | |||
#include "utilities/juce_AudioProcessorValueTreeState.cpp" | |||
#include "utilities/juce_AudioProcessorParameters.cpp" | |||
} |
@@ -92,6 +92,12 @@ class AudioProcessor; | |||
#include "format_types/juce_VST3PluginFormat.h" | |||
#include "scanning/juce_PluginDirectoryScanner.h" | |||
#include "scanning/juce_PluginListComponent.h" | |||
#include "utilities/juce_AudioProcessorValueTreeState.h" | |||
#include "utilities/juce_AudioProcessorParameterWithID.h" | |||
#include "utilities/juce_AudioParameterFloat.h" | |||
#include "utilities/juce_AudioParameterInt.h" | |||
#include "utilities/juce_AudioParameterBool.h" | |||
#include "utilities/juce_AudioParameterChoice.h" | |||
} | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_processors", | |||
"name": "JUCE audio plugin hosting classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for loading and playing VST, AU, or internally-generated audio processors.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -17,7 +17,8 @@ | |||
"browse": [ "processors/*", | |||
"format/*", | |||
"format_types/*", | |||
"scanning/*" | |||
"scanning/*", | |||
"utilities/*" | |||
], | |||
"OSXFrameworks": "CoreAudio CoreMIDI AudioToolbox", | |||
@@ -38,7 +38,8 @@ AudioProcessor::AudioProcessor() | |||
numOutputChannels (0), | |||
latencySamples (0), | |||
suspended (false), | |||
nonRealtime (false) | |||
nonRealtime (false), | |||
processingPrecision (singlePrecision) | |||
{ | |||
} | |||
@@ -325,7 +326,33 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) | |||
} | |||
void AudioProcessor::reset() {} | |||
void AudioProcessor::processBlockBypassed (AudioSampleBuffer&, MidiBuffer&) {} | |||
void AudioProcessor::processBlockBypassed (AudioBuffer<float>&, MidiBuffer&) {} | |||
void AudioProcessor::processBlockBypassed (AudioBuffer<double>&, MidiBuffer&) {} | |||
void AudioProcessor::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
ignoreUnused (buffer, midiMessages); | |||
// If you hit this assertion then either the caller called the double | |||
// precision version of processBlock on a processor which does not support it | |||
// (i.e. supportsDoublePrecisionProcessing() returns false), or the implementation | |||
// of the AudioProcessor forgot to override the double precision version of this method | |||
jassertfalse; | |||
} | |||
void AudioProcessor::setProcessingPrecision (ProcessingPrecision precision) noexcept | |||
{ | |||
// If you hit this assertion then you're trying to use double precision | |||
// processing on a processor which does not support it! | |||
jassert (precision != doublePrecision || supportsDoublePrecisionProcessing()); | |||
processingPrecision = precision; | |||
} | |||
bool AudioProcessor::supportsDoublePrecisionProcessing() const | |||
{ | |||
return false; | |||
} | |||
#if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
//============================================================================== | |||
@@ -48,6 +48,14 @@ protected: | |||
AudioProcessor(); | |||
public: | |||
//============================================================================== | |||
enum ProcessingPrecision | |||
{ | |||
singlePrecision, | |||
doublePrecision | |||
}; | |||
//============================================================================== | |||
/** Destructor. */ | |||
virtual ~AudioProcessor(); | |||
@@ -125,9 +133,74 @@ public: | |||
processBlock() method to send out an asynchronous message. You could also use | |||
the AsyncUpdater class in a similar way. | |||
*/ | |||
virtual void processBlock (AudioSampleBuffer& buffer, | |||
virtual void processBlock (AudioBuffer<float>& buffer, | |||
MidiBuffer& midiMessages) = 0; | |||
/** Renders the next block. | |||
When this method is called, the buffer contains a number of channels which is | |||
at least as great as the maximum number of input and output channels that | |||
this filter is using. It will be filled with the filter's input data and | |||
should be replaced with the filter's output. | |||
So for example if your filter has 2 input channels and 4 output channels, then | |||
the buffer will contain 4 channels, the first two being filled with the | |||
input data. Your filter should read these, do its processing, and replace | |||
the contents of all 4 channels with its output. | |||
Or if your filter has 5 inputs and 2 outputs, the buffer will have 5 channels, | |||
all filled with data, and your filter should overwrite the first 2 of these | |||
with its output. But be VERY careful not to write anything to the last 3 | |||
channels, as these might be mapped to memory that the host assumes is read-only! | |||
Note that if you have more outputs than inputs, then only those channels that | |||
correspond to an input channel are guaranteed to contain sensible data - e.g. | |||
in the case of 2 inputs and 4 outputs, the first two channels contain the input, | |||
but the last two channels may contain garbage, so you should be careful not to | |||
let this pass through without being overwritten or cleared. | |||
Also note that the buffer may have more channels than are strictly necessary, | |||
but you should only read/write from the ones that your filter is supposed to | |||
be using. | |||
The number of samples in these buffers is NOT guaranteed to be the same for every | |||
callback, and may be more or less than the estimated value given to prepareToPlay(). | |||
Your code must be able to cope with variable-sized blocks, or you're going to get | |||
clicks and crashes! | |||
Also note that some hosts will occasionally decide to pass a buffer containing | |||
zero samples, so make sure that your algorithm can deal with that! | |||
If the filter is receiving a midi input, then the midiMessages array will be filled | |||
with the midi messages for this block. Each message's timestamp will indicate the | |||
message's time, as a number of samples from the start of the block. | |||
Any messages left in the midi buffer when this method has finished are assumed to | |||
be the filter's midi output. This means that your filter should be careful to | |||
clear any incoming messages from the array if it doesn't want them to be passed-on. | |||
Be very careful about what you do in this callback - it's going to be called by | |||
the audio thread, so any kind of interaction with the UI is absolutely | |||
out of the question. If you change a parameter in here and need to tell your UI to | |||
update itself, the best way is probably to inherit from a ChangeBroadcaster, let | |||
the UI components register as listeners, and then call sendChangeMessage() inside the | |||
processBlock() method to send out an asynchronous message. You could also use | |||
the AsyncUpdater class in a similar way. | |||
*/ | |||
virtual void processBlock (AudioBuffer<double>& buffer, | |||
MidiBuffer& midiMessages); | |||
/** Renders the next block when the processor is being bypassed. | |||
The default implementation of this method will pass-through any incoming audio, but | |||
you may override this method e.g. to add latency compensation to the data to match | |||
the processor's latency characteristics. This will avoid situations where bypassing | |||
will shift the signal forward in time, possibly creating pre-echo effects and odd timings. | |||
Another use for this method would be to cross-fade or morph between the wet (not bypassed) | |||
and dry (bypassed) signals. | |||
*/ | |||
virtual void processBlockBypassed (AudioBuffer<float>& buffer, | |||
MidiBuffer& midiMessages); | |||
/** Renders the next block when the processor is being bypassed. | |||
The default implementation of this method will pass-through any incoming audio, but | |||
you may override this method e.g. to add latency compensation to the data to match | |||
@@ -136,9 +209,46 @@ public: | |||
Another use for this method would be to cross-fade or morph between the wet (not bypassed) | |||
and dry (bypassed) signals. | |||
*/ | |||
virtual void processBlockBypassed (AudioSampleBuffer& buffer, | |||
virtual void processBlockBypassed (AudioBuffer<double>& buffer, | |||
MidiBuffer& midiMessages); | |||
//============================================================================== | |||
/** Returns true if the Audio processor supports double precision floating point processing. | |||
The default implementation will always return false. | |||
If you return true here then you must override the double precision versions | |||
of processBlock. Additionally, you must call getProcessingPrecision() in | |||
your prepareToPlay method to determine the precision with which you need to | |||
allocate your internal buffers. | |||
@see getProcessingPrecision, setProcessingPrecision | |||
*/ | |||
virtual bool supportsDoublePrecisionProcessing() const; | |||
/** Returns the precision-mode of the processor. | |||
Depending on the result of this method you MUST call the corresponding version | |||
of processBlock. The default processing precision is single precision. | |||
@see setProcessingPrecision, supportsDoublePrecisionProcessing | |||
*/ | |||
ProcessingPrecision getProcessingPrecision() const noexcept { return processingPrecision; } | |||
/** Returns true if the current precision is set to doublePrecision. */ | |||
bool isUsingDoublePrecision() const noexcept { return processingPrecision == doublePrecision; } | |||
/** Changes the processing precision of the receiver. A client of the AudioProcessor | |||
calls this function to indicate which version of processBlock (single or double | |||
precision) it intends to call. The client MUST call this function before calling | |||
the prepareToPlay method so that the receiver can do any necessary allocations | |||
in the prepareToPlay() method. An implementation of prepareToPlay() should call | |||
getProcessingPrecision() to determine with which precision it should allocate | |||
it's internal buffers. | |||
Note that setting the processing precision to double floating point precision | |||
on a receiver which does not support double precision processing (i.e. | |||
supportsDoublePrecisionProcessing() returns false) will result in an assertion. | |||
@see getProcessingPrecision, supportsDoublePrecisionProcessing | |||
*/ | |||
void setProcessingPrecision (ProcessingPrecision precision) noexcept; | |||
//============================================================================== | |||
/** Returns the current AudioPlayHead object that should be used to find | |||
out the state and position of the playhead. | |||
@@ -742,6 +852,7 @@ private: | |||
double sampleRate; | |||
int blockSize, numInputChannels, numOutputChannels, latencySamples; | |||
bool suspended, nonRealtime; | |||
ProcessingPrecision processingPrecision; | |||
CriticalSection callbackLock, listenerLock; | |||
String inputSpeakerArrangement, outputSpeakerArrangement; | |||
@@ -25,28 +25,81 @@ | |||
const int AudioProcessorGraph::midiChannelIndex = 0x1000; | |||
//============================================================================== | |||
namespace GraphRenderingOps | |||
template <typename FloatType, typename Impl> struct FloatDoubleUtil {}; | |||
template <typename Tag, typename Type> struct FloatDoubleType {}; | |||
template <typename Tag> | |||
struct FloatAndDoubleComposition | |||
{ | |||
typedef typename FloatDoubleType<Tag, float>::Type FloatType; | |||
typedef typename FloatDoubleType<Tag, double>::Type DoubleType; | |||
template <typename FloatingType> | |||
inline typename FloatDoubleType<Tag, FloatingType>::Type& get() noexcept | |||
{ | |||
return FloatDoubleUtil<FloatingType, FloatAndDoubleComposition<Tag> >::get (*this); | |||
} | |||
FloatType floatVersion; | |||
DoubleType doubleVersion; | |||
}; | |||
template <typename Impl> struct FloatDoubleUtil<float, Impl> { static inline typename Impl::FloatType& get (Impl& i) noexcept { return i.floatVersion; } }; | |||
template <typename Impl> struct FloatDoubleUtil<double, Impl> { static inline typename Impl::DoubleType& get (Impl& i) noexcept { return i.doubleVersion; } }; | |||
struct FloatPlaceholder; | |||
template <typename FloatingType> struct FloatDoubleType<HeapBlock<FloatPlaceholder>, FloatingType> { typedef HeapBlock<FloatingType> Type; }; | |||
template <typename FloatingType> struct FloatDoubleType<HeapBlock<FloatPlaceholder*>, FloatingType> { typedef HeapBlock<FloatingType*> Type; }; | |||
template <typename FloatingType> struct FloatDoubleType<AudioBuffer<FloatPlaceholder>, FloatingType> { typedef AudioBuffer<FloatingType> Type; }; | |||
template <typename FloatingType> struct FloatDoubleType<AudioBuffer<FloatPlaceholder>*, FloatingType> { typedef AudioBuffer<FloatingType>* Type; }; | |||
//============================================================================== | |||
struct AudioGraphRenderingOp | |||
namespace GraphRenderingOps | |||
{ | |||
AudioGraphRenderingOp() noexcept {} | |||
virtual ~AudioGraphRenderingOp() {} | |||
virtual void perform (AudioSampleBuffer& sharedBufferChans, | |||
struct AudioGraphRenderingOpBase | |||
{ | |||
AudioGraphRenderingOpBase() noexcept {} | |||
virtual ~AudioGraphRenderingOpBase() {} | |||
virtual void perform (AudioBuffer<float>& sharedBufferChans, | |||
const OwnedArray<MidiBuffer>& sharedMidiBuffers, | |||
const int numSamples) = 0; | |||
virtual void perform (AudioBuffer<double>& sharedBufferChans, | |||
const OwnedArray<MidiBuffer>& sharedMidiBuffers, | |||
const int numSamples) = 0; | |||
JUCE_LEAK_DETECTOR (AudioGraphRenderingOp) | |||
JUCE_LEAK_DETECTOR (AudioGraphRenderingOpBase) | |||
}; | |||
// use CRTP | |||
template <class Child> | |||
struct AudioGraphRenderingOp : public AudioGraphRenderingOpBase | |||
{ | |||
void perform (AudioBuffer<float>& sharedBufferChans, | |||
const OwnedArray<MidiBuffer>& sharedMidiBuffers, | |||
const int numSamples) override | |||
{ | |||
static_cast<Child*> (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); | |||
} | |||
void perform (AudioBuffer<double>& sharedBufferChans, | |||
const OwnedArray<MidiBuffer>& sharedMidiBuffers, | |||
const int numSamples) override | |||
{ | |||
static_cast<Child*> (this)->perform (sharedBufferChans, sharedMidiBuffers, numSamples); | |||
} | |||
}; | |||
//============================================================================== | |||
struct ClearChannelOp : public AudioGraphRenderingOp | |||
struct ClearChannelOp : public AudioGraphRenderingOp<ClearChannelOp> | |||
{ | |||
ClearChannelOp (const int channel) noexcept : channelNum (channel) {} | |||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
{ | |||
sharedBufferChans.clear (channelNum, 0, numSamples); | |||
} | |||
@@ -57,13 +110,14 @@ struct ClearChannelOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct CopyChannelOp : public AudioGraphRenderingOp | |||
struct CopyChannelOp : public AudioGraphRenderingOp<CopyChannelOp> | |||
{ | |||
CopyChannelOp (const int srcChan, const int dstChan) noexcept | |||
: srcChannelNum (srcChan), dstChannelNum (dstChan) | |||
{} | |||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
{ | |||
sharedBufferChans.copyFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); | |||
} | |||
@@ -74,13 +128,14 @@ struct CopyChannelOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct AddChannelOp : public AudioGraphRenderingOp | |||
struct AddChannelOp : public AudioGraphRenderingOp<AddChannelOp> | |||
{ | |||
AddChannelOp (const int srcChan, const int dstChan) noexcept | |||
: srcChannelNum (srcChan), dstChannelNum (dstChan) | |||
{} | |||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
{ | |||
sharedBufferChans.addFrom (dstChannelNum, 0, sharedBufferChans, srcChannelNum, 0, numSamples); | |||
} | |||
@@ -91,11 +146,12 @@ struct AddChannelOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct ClearMidiBufferOp : public AudioGraphRenderingOp | |||
struct ClearMidiBufferOp : public AudioGraphRenderingOp<ClearMidiBufferOp> | |||
{ | |||
ClearMidiBufferOp (const int buffer) noexcept : bufferNum (buffer) {} | |||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int) | |||
{ | |||
sharedMidiBuffers.getUnchecked (bufferNum)->clear(); | |||
} | |||
@@ -106,13 +162,14 @@ struct ClearMidiBufferOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct CopyMidiBufferOp : public AudioGraphRenderingOp | |||
struct CopyMidiBufferOp : public AudioGraphRenderingOp<CopyMidiBufferOp> | |||
{ | |||
CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept | |||
: srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) | |||
{} | |||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int) | |||
{ | |||
*sharedMidiBuffers.getUnchecked (dstBufferNum) = *sharedMidiBuffers.getUnchecked (srcBufferNum); | |||
} | |||
@@ -123,13 +180,14 @@ struct CopyMidiBufferOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct AddMidiBufferOp : public AudioGraphRenderingOp | |||
struct AddMidiBufferOp : public AudioGraphRenderingOp<AddMidiBufferOp> | |||
{ | |||
AddMidiBufferOp (const int srcBuffer, const int dstBuffer) | |||
: srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) | |||
{} | |||
void perform (AudioSampleBuffer&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>&, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples) | |||
{ | |||
sharedMidiBuffers.getUnchecked (dstBufferNum) | |||
->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); | |||
@@ -141,24 +199,27 @@ struct AddMidiBufferOp : public AudioGraphRenderingOp | |||
}; | |||
//============================================================================== | |||
struct DelayChannelOp : public AudioGraphRenderingOp | |||
struct DelayChannelOp : public AudioGraphRenderingOp<DelayChannelOp> | |||
{ | |||
DelayChannelOp (const int chan, const int delaySize) | |||
: channel (chan), | |||
bufferSize (delaySize + 1), | |||
readIndex (0), writeIndex (delaySize) | |||
{ | |||
buffer.calloc ((size_t) bufferSize); | |||
buffer.floatVersion. calloc ((size_t) bufferSize); | |||
buffer.doubleVersion.calloc ((size_t) bufferSize); | |||
} | |||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>&, const int numSamples) | |||
{ | |||
float* data = sharedBufferChans.getWritePointer (channel, 0); | |||
FloatType* data = sharedBufferChans.getWritePointer (channel, 0); | |||
HeapBlock<FloatType>& block = buffer.get<FloatType>(); | |||
for (int i = numSamples; --i >= 0;) | |||
{ | |||
buffer [writeIndex] = *data; | |||
*data++ = buffer [readIndex]; | |||
block [writeIndex] = *data; | |||
*data++ = block [readIndex]; | |||
if (++readIndex >= bufferSize) readIndex = 0; | |||
if (++writeIndex >= bufferSize) writeIndex = 0; | |||
@@ -166,41 +227,67 @@ struct DelayChannelOp : public AudioGraphRenderingOp | |||
} | |||
private: | |||
HeapBlock<float> buffer; | |||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder> > buffer; | |||
const int channel, bufferSize; | |||
int readIndex, writeIndex; | |||
JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) | |||
}; | |||
//============================================================================== | |||
struct ProcessBufferOp : public AudioGraphRenderingOp | |||
struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||
{ | |||
ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, | |||
const Array<int>& audioChannels, | |||
const Array<int>& audioChannelsUsed, | |||
const int totalNumChans, | |||
const int midiBuffer) | |||
: node (n), | |||
processor (n->getProcessor()), | |||
audioChannelsToUse (audioChannels), | |||
audioChannelsToUse (audioChannelsUsed), | |||
totalChans (jmax (1, totalNumChans)), | |||
midiBufferToUse (midiBuffer) | |||
{ | |||
channels.calloc ((size_t) totalChans); | |||
audioChannels.floatVersion. calloc ((size_t) totalChans); | |||
audioChannels.doubleVersion.calloc ((size_t) totalChans); | |||
while (audioChannelsToUse.size() < totalChans) | |||
audioChannelsToUse.add (0); | |||
} | |||
void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples) | |||
template <typename FloatType> | |||
void perform (AudioBuffer<FloatType>& sharedBufferChans, const OwnedArray<MidiBuffer>& sharedMidiBuffers, const int numSamples) | |||
{ | |||
HeapBlock<FloatType*>& channels = audioChannels.get<FloatType>(); | |||
for (int i = totalChans; --i >= 0;) | |||
channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (i), 0); | |||
AudioSampleBuffer buffer (channels, totalChans, numSamples); | |||
AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | |||
processor->processBlock (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||
callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||
} | |||
void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
processor->processBlock (buffer, midiMessages); | |||
} | |||
void callProcess (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
if (processor->isUsingDoublePrecision()) | |||
{ | |||
processor->processBlock (buffer, midiMessages); | |||
} | |||
else | |||
{ | |||
// if the processor is in single precision mode but the graph in double | |||
// precision then we need to convert between buffer formats. Note, that | |||
// this will only happen if the processor does not support double | |||
// precision processing. | |||
tempBuffer.makeCopyOf (buffer); | |||
processor->processBlock (tempBuffer, midiMessages); | |||
buffer.makeCopyOf (tempBuffer); | |||
} | |||
} | |||
const AudioProcessorGraph::Node::Ptr node; | |||
@@ -208,7 +295,8 @@ struct ProcessBufferOp : public AudioGraphRenderingOp | |||
private: | |||
Array<int> audioChannelsToUse; | |||
HeapBlock<float*> channels; | |||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder*> > audioChannels; | |||
AudioBuffer<float> tempBuffer; | |||
const int totalChans; | |||
const int midiBufferToUse; | |||
@@ -861,13 +949,17 @@ AudioProcessorGraph::Node::Node (const uint32 nodeID, AudioProcessor* const p) n | |||
} | |||
void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, | |||
AudioProcessorGraph* const graph) | |||
AudioProcessorGraph* const graph, ProcessingPrecision precision) | |||
{ | |||
if (! isPrepared) | |||
{ | |||
isPrepared = true; | |||
setParentGraph (graph); | |||
// try to align the precision of the processor and the graph | |||
processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | |||
: singlePrecision); | |||
processor->setPlayConfigDetails (processor->getNumInputChannels(), | |||
processor->getNumOutputChannels(), | |||
newSampleRate, newBlockSize); | |||
@@ -892,10 +984,53 @@ void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph | |||
ioProc->setParentGraph (graph); | |||
} | |||
//============================================================================== | |||
struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers | |||
{ | |||
AudioProcessorGraphBufferHelpers() | |||
{ | |||
currentAudioInputBuffer.floatVersion = nullptr; | |||
currentAudioInputBuffer.doubleVersion = nullptr; | |||
} | |||
void setRenderingBufferSize (int newNumChannels, int newNumSamples) | |||
{ | |||
renderingBuffers.floatVersion. setSize (newNumChannels, newNumSamples); | |||
renderingBuffers.doubleVersion.setSize (newNumChannels, newNumSamples); | |||
renderingBuffers.floatVersion. clear(); | |||
renderingBuffers.doubleVersion.clear(); | |||
} | |||
void release() | |||
{ | |||
renderingBuffers.floatVersion. setSize (1, 1); | |||
renderingBuffers.doubleVersion.setSize (1, 1); | |||
currentAudioInputBuffer.floatVersion = nullptr; | |||
currentAudioInputBuffer.doubleVersion = nullptr; | |||
currentAudioOutputBuffer.floatVersion. setSize (1, 1); | |||
currentAudioOutputBuffer.doubleVersion.setSize (1, 1); | |||
} | |||
void prepareInOutBuffers(int newNumChannels, int newNumSamples) | |||
{ | |||
currentAudioInputBuffer.floatVersion = nullptr; | |||
currentAudioInputBuffer.doubleVersion = nullptr; | |||
currentAudioOutputBuffer.floatVersion. setSize (newNumChannels, newNumSamples); | |||
currentAudioOutputBuffer.doubleVersion.setSize (newNumChannels, newNumSamples); | |||
} | |||
FloatAndDoubleComposition<AudioBuffer<FloatPlaceholder> > renderingBuffers; | |||
FloatAndDoubleComposition<AudioBuffer<FloatPlaceholder>*> currentAudioInputBuffer; | |||
FloatAndDoubleComposition<AudioBuffer<FloatPlaceholder> > currentAudioOutputBuffer; | |||
}; | |||
//============================================================================== | |||
AudioProcessorGraph::AudioProcessorGraph() | |||
: lastNodeId (0), | |||
currentAudioInputBuffer (nullptr), | |||
: lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | |||
currentMidiInputBuffer (nullptr) | |||
{ | |||
} | |||
@@ -1140,7 +1275,7 @@ bool AudioProcessorGraph::removeIllegalConnections() | |||
static void deleteRenderOpArray (Array<void*>& ops) | |||
{ | |||
for (int i = ops.size(); --i >= 0;) | |||
delete static_cast<GraphRenderingOps::AudioGraphRenderingOp*> (ops.getUnchecked(i)); | |||
delete static_cast<GraphRenderingOps::AudioGraphRenderingOpBase*> (ops.getUnchecked(i)); | |||
} | |||
void AudioProcessorGraph::clearRenderingSequence() | |||
@@ -1193,7 +1328,7 @@ void AudioProcessorGraph::buildRenderingSequence() | |||
{ | |||
Node* const node = nodes.getUnchecked(i); | |||
node->prepare (getSampleRate(), getBlockSize(), this); | |||
node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); | |||
int j = 0; | |||
for (; j < orderedNodes.size(); ++j) | |||
@@ -1214,8 +1349,7 @@ void AudioProcessorGraph::buildRenderingSequence() | |||
// swap over to the new rendering sequence.. | |||
const ScopedLock sl (getCallbackLock()); | |||
renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); | |||
renderingBuffers.clear(); | |||
audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); | |||
for (int i = midiBuffers.size(); --i >= 0;) | |||
midiBuffers.getUnchecked(i)->clear(); | |||
@@ -1238,8 +1372,8 @@ void AudioProcessorGraph::handleAsyncUpdate() | |||
//============================================================================== | |||
void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) | |||
{ | |||
currentAudioInputBuffer = nullptr; | |||
currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); | |||
audioBuffers->prepareInOutBuffers (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); | |||
currentMidiInputBuffer = nullptr; | |||
currentMidiOutputBuffer.clear(); | |||
@@ -1247,16 +1381,19 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||
buildRenderingSequence(); | |||
} | |||
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
{ | |||
return true; | |||
} | |||
void AudioProcessorGraph::releaseResources() | |||
{ | |||
for (int i = 0; i < nodes.size(); ++i) | |||
nodes.getUnchecked(i)->unprepare(); | |||
renderingBuffers.setSize (1, 1); | |||
audioBuffers->release(); | |||
midiBuffers.clear(); | |||
currentAudioInputBuffer = nullptr; | |||
currentAudioOutputBuffer.setSize (1, 1); | |||
currentMidiInputBuffer = nullptr; | |||
currentMidiOutputBuffer.clear(); | |||
} | |||
@@ -1287,8 +1424,13 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) | |||
nodes.getUnchecked(i)->getProcessor()->setPlayHead (audioPlayHead); | |||
} | |||
void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) | |||
template <typename FloatType> | |||
void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
AudioBuffer<FloatType>& renderingBuffers = audioBuffers->renderingBuffers.get<FloatType>(); | |||
AudioBuffer<FloatType>*& currentAudioInputBuffer = audioBuffers->currentAudioInputBuffer.get<FloatType>(); | |||
AudioBuffer<FloatType>& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | |||
const int numSamples = buffer.getNumSamples(); | |||
currentAudioInputBuffer = &buffer; | |||
@@ -1299,8 +1441,8 @@ void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& m | |||
for (int i = 0; i < renderingOps.size(); ++i) | |||
{ | |||
GraphRenderingOps::AudioGraphRenderingOp* const op | |||
= (GraphRenderingOps::AudioGraphRenderingOp*) renderingOps.getUnchecked(i); | |||
GraphRenderingOps::AudioGraphRenderingOpBase* const op | |||
= (GraphRenderingOps::AudioGraphRenderingOpBase*) renderingOps.getUnchecked(i); | |||
op->perform (renderingBuffers, midiBuffers, numSamples); | |||
} | |||
@@ -1331,6 +1473,21 @@ bool AudioProcessorGraph::producesMidi() const { return tru | |||
void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} | |||
void AudioProcessorGraph::setStateInformation (const void*, int) {} | |||
void AudioProcessorGraph::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
} | |||
void AudioProcessorGraph::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
} | |||
// explicit template instantiation | |||
template void AudioProcessorGraph::processAudio<float> ( AudioBuffer<float>& buffer, | |||
MidiBuffer& midiMessages); | |||
template void AudioProcessorGraph::processAudio<double> (AudioBuffer<double>& buffer, | |||
MidiBuffer& midiMessages); | |||
//============================================================================== | |||
AudioProcessorGraph::AudioGraphIOProcessor::AudioGraphIOProcessor (const IODeviceType deviceType) | |||
@@ -1384,19 +1541,31 @@ void AudioProcessorGraph::AudioGraphIOProcessor::releaseResources() | |||
{ | |||
} | |||
void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer& buffer, | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::supportsDoublePrecisionProcessing() const | |||
{ | |||
return true; | |||
} | |||
template <typename FloatType> | |||
void AudioProcessorGraph::AudioGraphIOProcessor::processAudio (AudioBuffer<FloatType>& buffer, | |||
MidiBuffer& midiMessages) | |||
{ | |||
AudioBuffer<FloatType>*& currentAudioInputBuffer = | |||
graph->audioBuffers->currentAudioInputBuffer.get<FloatType>(); | |||
AudioBuffer<FloatType>& currentAudioOutputBuffer = | |||
graph->audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | |||
jassert (graph != nullptr); | |||
switch (type) | |||
{ | |||
case audioOutputNode: | |||
{ | |||
for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), | |||
for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), | |||
buffer.getNumChannels()); --i >= 0;) | |||
{ | |||
graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); | |||
currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); | |||
} | |||
break; | |||
@@ -1404,10 +1573,10 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer | |||
case audioInputNode: | |||
{ | |||
for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), | |||
for (int i = jmin (currentAudioInputBuffer->getNumChannels(), | |||
buffer.getNumChannels()); --i >= 0;) | |||
{ | |||
buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); | |||
buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); | |||
} | |||
break; | |||
@@ -1426,6 +1595,18 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer | |||
} | |||
} | |||
void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<float>& buffer, | |||
MidiBuffer& midiMessages) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
} | |||
void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioBuffer<double>& buffer, | |||
MidiBuffer& midiMessages) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
} | |||
bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const | |||
{ | |||
return isOutput(); | |||
@@ -25,7 +25,6 @@ | |||
#ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | |||
#define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
A type of AudioProcessor which plays back a graph of other AudioProcessors. | |||
@@ -92,7 +91,7 @@ public: | |||
Node (uint32 nodeId, AudioProcessor*) noexcept; | |||
void setParentGraph (AudioProcessorGraph*) const; | |||
void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*); | |||
void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); | |||
void unprepare(); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) | |||
@@ -308,7 +307,9 @@ public: | |||
void fillInPluginDescription (PluginDescription&) const override; | |||
void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; | |||
void releaseResources() override; | |||
void processBlock (AudioSampleBuffer&, MidiBuffer&) override; | |||
void processBlock (AudioBuffer<float>& , MidiBuffer&) override; | |||
void processBlock (AudioBuffer<double>&, MidiBuffer&) override; | |||
bool supportsDoublePrecisionProcessing() const override; | |||
const String getInputChannelName (int channelIndex) const override; | |||
const String getOutputChannelName (int channelIndex) const override; | |||
@@ -340,6 +341,10 @@ public: | |||
const IODeviceType type; | |||
AudioProcessorGraph* graph; | |||
//============================================================================== | |||
template <typename floatType> | |||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) | |||
}; | |||
@@ -347,7 +352,9 @@ public: | |||
const String getName() const override; | |||
void prepareToPlay (double, int) override; | |||
void releaseResources() override; | |||
void processBlock (AudioSampleBuffer&, MidiBuffer&) override; | |||
void processBlock (AudioBuffer<float>&, MidiBuffer&) override; | |||
void processBlock (AudioBuffer<double>&, MidiBuffer&) override; | |||
bool supportsDoublePrecisionProcessing() const override; | |||
void reset() override; | |||
void setNonRealtime (bool) noexcept override; | |||
@@ -376,17 +383,21 @@ public: | |||
void setStateInformation (const void* data, int sizeInBytes) override; | |||
private: | |||
//============================================================================== | |||
template <typename floatType> | |||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
//============================================================================== | |||
ReferenceCountedArray<Node> nodes; | |||
OwnedArray<Connection> connections; | |||
uint32 lastNodeId; | |||
AudioSampleBuffer renderingBuffers; | |||
OwnedArray<MidiBuffer> midiBuffers; | |||
Array<void*> renderingOps; | |||
friend class AudioGraphIOProcessor; | |||
AudioSampleBuffer* currentAudioInputBuffer; | |||
AudioSampleBuffer currentAudioOutputBuffer; | |||
struct AudioProcessorGraphBufferHelpers; | |||
ScopedPointer<AudioProcessorGraphBufferHelpers> audioBuffers; | |||
MidiBuffer* currentMidiInputBuffer; | |||
MidiBuffer currentMidiOutputBuffer; | |||
@@ -0,0 +1,63 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
/** | |||
Provides a class of AudioProcessorParameter that can be used as a boolean value. | |||
@see AudioParameterFloat, AudioParameterInt, AudioParameterChoice | |||
*/ | |||
class JUCE_API AudioParameterBool : public AudioProcessorParameterWithID | |||
{ | |||
public: | |||
/** Creates a AudioParameterBool with an ID and name. | |||
On creation, its value is set to the default value. | |||
*/ | |||
AudioParameterBool (String parameterID, String name, bool defaultValue); | |||
/** Destructor. */ | |||
~AudioParameterBool(); | |||
/** Returns the parameter's current boolean value. */ | |||
bool get() const noexcept { return value >= 0.5f; } | |||
/** Returns the parameter's current boolean value. */ | |||
operator bool() const noexcept { return get(); } | |||
/** Changes the parameter's current value to a new boolean. */ | |||
AudioParameterBool& operator= (bool newValue); | |||
private: | |||
//============================================================================== | |||
float value, defaultValue; | |||
float getValue() const override; | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterBool) | |||
}; |
@@ -0,0 +1,78 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
/** | |||
Provides a class of AudioProcessorParameter that can be used to select | |||
an indexed, named choice from a list. | |||
@see AudioParameterFloat, AudioParameterInt, AudioParameterBool | |||
*/ | |||
class JUCE_API AudioParameterChoice : public AudioProcessorParameterWithID | |||
{ | |||
public: | |||
/** Creates a AudioParameterChoice with an ID, name, and list of items. | |||
On creation, its value is set to the default index. | |||
*/ | |||
AudioParameterChoice (String parameterID, String name, | |||
const StringArray& choices, | |||
int defaultItemIndex); | |||
/** Destructor. */ | |||
~AudioParameterChoice(); | |||
/** Returns the current index of the selected item. */ | |||
int getIndex() const noexcept { return roundToInt (value); } | |||
/** Returns the current index of the selected item. */ | |||
operator int() const noexcept { return getIndex(); } | |||
/** Returns the name of the currently selected item. */ | |||
String getCurrentChoiceName() const noexcept { return choices[getIndex()]; } | |||
/** Returns the name of the currently selected item. */ | |||
operator String() const noexcept { return getCurrentChoiceName(); } | |||
/** Changes the selected item to a new index. */ | |||
AudioParameterChoice& operator= (int newValue); | |||
/** Provides access to the list of choices that this parameter is working with. */ | |||
const StringArray choices; | |||
private: | |||
//============================================================================== | |||
float value, defaultValue; | |||
float getValue() const override; | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
int limitRange (int) const noexcept; | |||
float convertTo0to1 (int) const noexcept; | |||
int convertFrom0to1 (float) const noexcept; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterChoice) | |||
}; |
@@ -0,0 +1,79 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
/** | |||
A subclass of AudioProcessorParameter that provides an easy way to create a | |||
parameter which maps onto a given NormalisableRange. | |||
@see AudioParameterInt, AudioParameterBool, AudioParameterChoice | |||
*/ | |||
class JUCE_API AudioParameterFloat : public AudioProcessorParameterWithID | |||
{ | |||
public: | |||
/** Creates a AudioParameterFloat with an ID, name, and range. | |||
On creation, its value is set to the default value. | |||
*/ | |||
AudioParameterFloat (String parameterID, String name, | |||
NormalisableRange<float> normalisableRange, | |||
float defaultValue); | |||
/** Creates a AudioParameterFloat with an ID, name, and range. | |||
On creation, its value is set to the default value. | |||
For control over skew factors, you can use the other | |||
constructor and provide a NormalisableRange. | |||
*/ | |||
AudioParameterFloat (String parameterID, String name, | |||
float minValue, | |||
float maxValue, | |||
float defaultValue); | |||
/** Destructor. */ | |||
~AudioParameterFloat(); | |||
/** Returns the parameter's current value. */ | |||
float get() const noexcept { return value; } | |||
/** Returns the parameter's current value. */ | |||
operator float() const noexcept { return value; } | |||
/** Changes the parameter's current value. */ | |||
AudioParameterFloat& operator= (float newValue); | |||
/** Provides access to the parameter's range. */ | |||
NormalisableRange<float> range; | |||
private: | |||
//============================================================================== | |||
float value, defaultValue; | |||
float getValue() const override; | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterFloat) | |||
}; |
@@ -0,0 +1,77 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
/** | |||
Provides a class of AudioProcessorParameter that can be used as an | |||
integer value with a given range. | |||
@see AudioParameterFloat, AudioParameterBool, AudioParameterChoice | |||
*/ | |||
class JUCE_API AudioParameterInt : public AudioProcessorParameterWithID | |||
{ | |||
public: | |||
/** Creates an AudioParameterInt with an ID, name, and range. | |||
Note that the min and max range values are inclusive. | |||
On creation, its value is set to the default value. | |||
*/ | |||
AudioParameterInt (String parameterID, String name, | |||
int minValue, int maxValue, | |||
int defaultValue); | |||
/** Destructor. */ | |||
~AudioParameterInt(); | |||
/** Returns the parameter's current value as an integer. */ | |||
int get() const noexcept { return roundToInt (value); } | |||
/** Returns the parameter's current value as an integer. */ | |||
operator int() const noexcept { return get(); } | |||
/** Changes the parameter's current value to a new integer. | |||
The value passed in will be snapped to the permitted range before being used. | |||
*/ | |||
AudioParameterInt& operator= (int newValue); | |||
/** Returns the parameter's range. */ | |||
Range<int> getRange() const noexcept { return Range<int> (minValue, maxValue); } | |||
private: | |||
//============================================================================== | |||
int minValue, maxValue; | |||
float value, defaultValue; | |||
float getValue() const override; | |||
void setValue (float newValue) override; | |||
float getDefaultValue() const override; | |||
int getNumSteps() const override; | |||
String getText (float, int) const override; | |||
float getValueForText (const String&) const override; | |||
int limitRange (int) const noexcept; | |||
float convertTo0to1 (int) const noexcept; | |||
int convertFrom0to1 (float) const noexcept; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioParameterInt) | |||
}; |
@@ -0,0 +1,55 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
/** | |||
This abstract base class is used by some AudioProcessorParameter helper classes. | |||
@see AudioParameterFloat, AudioParameterInt, AudioParameterBool, AudioParameterChoice | |||
*/ | |||
class JUCE_API AudioProcessorParameterWithID : public AudioProcessorParameter | |||
{ | |||
public: | |||
/** Creation of this object requires providing a name and ID which will be | |||
constant for its lifetime. | |||
*/ | |||
AudioProcessorParameterWithID (String parameterID, String name); | |||
/** Destructor. */ | |||
~AudioProcessorParameterWithID(); | |||
/** Provides access to the parameter's ID string. */ | |||
const String paramID; | |||
/** Provides access to the parameter's name. */ | |||
const String name; | |||
private: | |||
String label; | |||
String getName (int) const override; | |||
String getLabel() const override; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameterWithID) | |||
}; |
@@ -0,0 +1,157 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
// This file contains the implementations of the various AudioParameter[XYZ] classes.. | |||
AudioProcessorParameterWithID::AudioProcessorParameterWithID (String pid, String nm) : paramID (pid), name (nm) {} | |||
AudioProcessorParameterWithID::~AudioProcessorParameterWithID() {} | |||
String AudioProcessorParameterWithID::getName (int maximumStringLength) const { return name.substring (0, maximumStringLength); } | |||
String AudioProcessorParameterWithID::getLabel() const { return label; } | |||
//============================================================================== | |||
AudioParameterFloat::AudioParameterFloat (String pid, String nm, NormalisableRange<float> r, float def) | |||
: AudioProcessorParameterWithID (pid, nm), range (r), value (def), defaultValue (def) | |||
{ | |||
} | |||
AudioParameterFloat::AudioParameterFloat (String pid, String nm, float minValue, float maxValue, float def) | |||
: AudioProcessorParameterWithID (pid, nm), range (minValue, maxValue), value (def), defaultValue (def) | |||
{ | |||
} | |||
AudioParameterFloat::~AudioParameterFloat() {} | |||
float AudioParameterFloat::getValue() const { return range.convertTo0to1 (value); } | |||
void AudioParameterFloat::setValue (float newValue) { value = range.convertFrom0to1 (newValue); } | |||
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) | |||
{ | |||
const float normalisedValue = range.convertTo0to1 (newValue); | |||
if (value != normalisedValue) | |||
setValueNotifyingHost (normalisedValue); | |||
return *this; | |||
} | |||
//============================================================================== | |||
AudioParameterInt::AudioParameterInt (String pid, String nm, int mn, int mx, int def) | |||
: AudioProcessorParameterWithID (pid, nm), | |||
minValue (mn), maxValue (mx), | |||
value ((float) def), | |||
defaultValue (convertTo0to1 (def)) | |||
{ | |||
jassert (minValue < maxValue); // must have a non-zero range of values! | |||
} | |||
AudioParameterInt::~AudioParameterInt() {} | |||
int AudioParameterInt::limitRange (int v) const noexcept { return jlimit (minValue, maxValue, v); } | |||
float AudioParameterInt::convertTo0to1 (int v) const noexcept { return (limitRange (v) - minValue) / (float) (maxValue - minValue); } | |||
int AudioParameterInt::convertFrom0to1 (float v) const noexcept { return limitRange (roundToInt ((v * (float) (maxValue - minValue)) + minValue)); } | |||
float AudioParameterInt::getValue() const { return convertTo0to1 (roundToInt (value)); } | |||
void AudioParameterInt::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } | |||
float AudioParameterInt::getDefaultValue() const { return defaultValue; } | |||
int AudioParameterInt::getNumSteps() const { return AudioProcessorParameterWithID::getNumSteps(); } | |||
float AudioParameterInt::getValueForText (const String& text) const { return convertTo0to1 (text.getIntValue()); } | |||
String AudioParameterInt::getText (float v, int /*length*/) const { return String (convertFrom0to1 (v)); } | |||
AudioParameterInt& AudioParameterInt::operator= (int newValue) | |||
{ | |||
const float normalisedValue = convertTo0to1 (newValue); | |||
if (value != normalisedValue) | |||
setValueNotifyingHost (normalisedValue); | |||
return *this; | |||
} | |||
//============================================================================== | |||
AudioParameterBool::AudioParameterBool (String pid, String nm, bool def) | |||
: AudioProcessorParameterWithID (pid, nm), | |||
value (def ? 1.0f : 0.0f), | |||
defaultValue (value) | |||
{ | |||
} | |||
AudioParameterBool::~AudioParameterBool() {} | |||
float AudioParameterBool::getValue() const { return value; } | |||
void AudioParameterBool::setValue (float newValue) { value = newValue; } | |||
float AudioParameterBool::getDefaultValue() const { return defaultValue; } | |||
int AudioParameterBool::getNumSteps() const { return 2; } | |||
float AudioParameterBool::getValueForText (const String& text) const { return text.getIntValue() != 0 ? 1.0f : 0.0f; } | |||
String AudioParameterBool::getText (float v, int /*length*/) const { return String ((int) (v > 0.5f ? 1 : 0)); } | |||
AudioParameterBool& AudioParameterBool::operator= (bool newValue) | |||
{ | |||
const float normalisedValue = newValue ? 1.0f : 0.0f; | |||
if (value != normalisedValue) | |||
setValueNotifyingHost (normalisedValue); | |||
return *this; | |||
} | |||
//============================================================================== | |||
AudioParameterChoice::AudioParameterChoice (String pid, String nm, const StringArray& c, int def) | |||
: AudioProcessorParameterWithID (pid, nm), choices (c), | |||
value ((float) def), | |||
defaultValue (convertTo0to1 (def)) | |||
{ | |||
jassert (choices.size() > 0); // you must supply an actual set of items to choose from! | |||
} | |||
AudioParameterChoice::~AudioParameterChoice() {} | |||
int AudioParameterChoice::limitRange (int v) const noexcept { return jlimit (0, choices.size() - 1, v); } | |||
float AudioParameterChoice::convertTo0to1 (int v) const noexcept { return limitRange (v) / (float) choices.size(); } | |||
int AudioParameterChoice::convertFrom0to1 (float v) const noexcept { return limitRange ((int) (v * (float) choices.size())); } | |||
float AudioParameterChoice::getValue() const { return convertTo0to1 (roundToInt (value)); } | |||
void AudioParameterChoice::setValue (float newValue) { value = (float) convertFrom0to1 (newValue); } | |||
float AudioParameterChoice::getDefaultValue() const { return defaultValue; } | |||
int AudioParameterChoice::getNumSteps() const { return choices.size(); } | |||
float AudioParameterChoice::getValueForText (const String& text) const { return convertTo0to1 (choices.indexOf (text)); } | |||
String AudioParameterChoice::getText (float v, int /*length*/) const { return choices [convertFrom0to1 (v)]; } | |||
AudioParameterChoice& AudioParameterChoice::operator= (int newValue) | |||
{ | |||
const float normalisedValue = convertTo0to1 (newValue); | |||
if (value != normalisedValue) | |||
setValueNotifyingHost (normalisedValue); | |||
return *this; | |||
} |
@@ -0,0 +1,512 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
//============================================================================== | |||
struct AudioProcessorValueTreeState::Parameter : public AudioProcessorParameter, | |||
private ValueTree::Listener | |||
{ | |||
Parameter (AudioProcessorValueTreeState& s, | |||
String parameterID, String paramName, String labelText, | |||
NormalisableRange<float> r, float defaultVal, | |||
std::function<String (float)> valueToText, | |||
std::function<float (const String&)> textToValue) | |||
: owner (s), paramID (parameterID), name (paramName), label (labelText), | |||
valueToTextFunction (valueToText), | |||
textToValueFunction (textToValue), | |||
range (r), value (defaultVal), defaultValue (defaultVal), | |||
listenersNeedCalling (true) | |||
{ | |||
state.addListener (this); | |||
needsUpdate.set (1); | |||
} | |||
~Parameter() | |||
{ | |||
// should have detached all callbacks before destroying the parameters! | |||
jassert (listeners.size() <= 1); | |||
} | |||
float getValue() const override { return range.convertTo0to1 (value); } | |||
float getDefaultValue() const override { return range.convertTo0to1 (defaultValue); } | |||
String getName (int maximumStringLength) const override { return name.substring (0, maximumStringLength); } | |||
String getLabel() const override { return label; } | |||
float getValueForText (const String& text) const override | |||
{ | |||
return range.convertTo0to1 (textToValueFunction != nullptr ? textToValueFunction (text) | |||
: text.getFloatValue()); | |||
} | |||
String getText (float v, int length) const override | |||
{ | |||
return valueToTextFunction != nullptr ? valueToTextFunction (range.convertFrom0to1 (v)) | |||
: AudioProcessorParameter::getText (v, length); | |||
} | |||
void setValue (float newValue) override | |||
{ | |||
newValue = range.snapToLegalValue (range.convertFrom0to1 (newValue)); | |||
if (value != newValue || listenersNeedCalling) | |||
{ | |||
value = newValue; | |||
listeners.call (&AudioProcessorValueTreeState::Listener::parameterChanged, paramID, value); | |||
listenersNeedCalling = false; | |||
needsUpdate.set (1); | |||
} | |||
} | |||
void setNewState (const ValueTree& v) | |||
{ | |||
state = v; | |||
updateFromValueTree(); | |||
} | |||
void setUnnormalisedValue (float newUnnormalisedValue) | |||
{ | |||
if (value != newUnnormalisedValue) | |||
{ | |||
const float newValue = range.convertTo0to1 (newUnnormalisedValue); | |||
setValueNotifyingHost (newValue); | |||
} | |||
} | |||
void updateFromValueTree() | |||
{ | |||
setUnnormalisedValue (state.getProperty (owner.valuePropertyID, defaultValue)); | |||
} | |||
void copyValueToValueTree() | |||
{ | |||
if (state.isValid()) | |||
state.setProperty (owner.valuePropertyID, value, owner.undoManager); | |||
} | |||
void valueTreePropertyChanged (ValueTree&, const Identifier& property) override | |||
{ | |||
if (property == owner.valuePropertyID) | |||
updateFromValueTree(); | |||
} | |||
void valueTreeChildAdded (ValueTree&, ValueTree&) override {} | |||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} | |||
void valueTreeChildOrderChanged (ValueTree&, int, int) override {} | |||
void valueTreeParentChanged (ValueTree&) override {} | |||
static Parameter* getParameterForID (AudioProcessor& processor, StringRef paramID) noexcept | |||
{ | |||
const int numParams = processor.getParameters().size(); | |||
for (int i = 0; i < numParams; ++i) | |||
{ | |||
AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); | |||
// When using this class, you must allow it to manage all the parameters in your AudioProcessor, and | |||
// not add any parameter objects of other types! | |||
jassert (dynamic_cast<Parameter*> (ap) != nullptr); | |||
Parameter* const p = static_cast<Parameter*> (ap); | |||
if (paramID == p->paramID) | |||
return p; | |||
} | |||
return nullptr; | |||
} | |||
AudioProcessorValueTreeState& owner; | |||
ValueTree state; | |||
String paramID, name, label; | |||
ListenerList<AudioProcessorValueTreeState::Listener> listeners; | |||
std::function<String (float)> valueToTextFunction; | |||
std::function<float (const String&)> textToValueFunction; | |||
NormalisableRange<float> range; | |||
float value, defaultValue; | |||
Atomic<int> needsUpdate; | |||
bool listenersNeedCalling; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Parameter) | |||
}; | |||
//============================================================================== | |||
AudioProcessorValueTreeState::AudioProcessorValueTreeState (AudioProcessor& p, UndoManager* um) | |||
: processor (p), | |||
undoManager (um), | |||
valueType ("PARAM"), | |||
valuePropertyID ("value"), | |||
idPropertyID ("id"), | |||
updatingConnections (false) | |||
{ | |||
startTimerHz (10); | |||
state.addListener (this); | |||
} | |||
AudioProcessorValueTreeState::~AudioProcessorValueTreeState() {} | |||
AudioProcessorParameter* AudioProcessorValueTreeState::createAndAddParameter (String paramID, String paramName, String labelText, | |||
NormalisableRange<float> r, float defaultVal, | |||
std::function<String (float)> valueToTextFunction, | |||
std::function<float (const String&)> textToValueFunction) | |||
{ | |||
// All parameters must be created before giving this manager a ValueTree state! | |||
jassert (! state.isValid()); | |||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
Parameter* p = new Parameter (*this, paramID, paramName, labelText, r, | |||
defaultVal, valueToTextFunction, textToValueFunction); | |||
processor.addParameter (p); | |||
return p; | |||
} | |||
void AudioProcessorValueTreeState::addParameterListener (StringRef paramID, Listener* listener) | |||
{ | |||
if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
p->listeners.add (listener); | |||
} | |||
void AudioProcessorValueTreeState::removeParameterListener (StringRef paramID, Listener* listener) | |||
{ | |||
if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
p->listeners.remove (listener); | |||
} | |||
Value AudioProcessorValueTreeState::getParameterAsValue (StringRef paramID) const | |||
{ | |||
if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
return p->state.getPropertyAsValue (valuePropertyID, undoManager); | |||
return Value(); | |||
} | |||
NormalisableRange<float> AudioProcessorValueTreeState::getParameterRange (StringRef paramID) const noexcept | |||
{ | |||
if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
return p->range; | |||
return NormalisableRange<float>(); | |||
} | |||
AudioProcessorParameter* AudioProcessorValueTreeState::getParameter (StringRef paramID) const noexcept | |||
{ | |||
return Parameter::getParameterForID (processor, paramID); | |||
} | |||
float* AudioProcessorValueTreeState::getRawParameterValue (StringRef paramID) const noexcept | |||
{ | |||
if (Parameter* p = Parameter::getParameterForID (processor, paramID)) | |||
return &(p->value); | |||
return nullptr; | |||
} | |||
ValueTree AudioProcessorValueTreeState::getOrCreateChildValueTree (const String& paramID) | |||
{ | |||
ValueTree v (state.getChildWithProperty (idPropertyID, paramID)); | |||
if (! v.isValid()) | |||
{ | |||
v = ValueTree (valueType); | |||
v.setProperty (idPropertyID, paramID, undoManager); | |||
state.addChild (v, -1, undoManager); | |||
} | |||
return v; | |||
} | |||
void AudioProcessorValueTreeState::updateParameterConnectionsToChildTrees() | |||
{ | |||
if (! updatingConnections) | |||
{ | |||
ScopedValueSetter<bool> svs (updatingConnections, true, false); | |||
const int numParams = processor.getParameters().size(); | |||
for (int i = 0; i < numParams; ++i) | |||
{ | |||
AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); | |||
jassert (dynamic_cast<Parameter*> (ap) != nullptr); | |||
Parameter* p = static_cast<Parameter*> (ap); | |||
p->setNewState (getOrCreateChildValueTree (p->paramID)); | |||
} | |||
} | |||
} | |||
void AudioProcessorValueTreeState::valueTreePropertyChanged (ValueTree&, const Identifier& property) | |||
{ | |||
if (property == idPropertyID) | |||
updateParameterConnectionsToChildTrees(); | |||
} | |||
void AudioProcessorValueTreeState::valueTreeChildAdded (ValueTree& parent, ValueTree&) | |||
{ | |||
if (parent == state) | |||
updateParameterConnectionsToChildTrees(); | |||
} | |||
void AudioProcessorValueTreeState::valueTreeChildRemoved (ValueTree& parent, ValueTree&, int) | |||
{ | |||
if (parent == state) | |||
updateParameterConnectionsToChildTrees(); | |||
} | |||
void AudioProcessorValueTreeState::valueTreeRedirected (ValueTree& v) | |||
{ | |||
if (v == state) | |||
updateParameterConnectionsToChildTrees(); | |||
} | |||
void AudioProcessorValueTreeState::valueTreeChildOrderChanged (ValueTree&, int, int) {} | |||
void AudioProcessorValueTreeState::valueTreeParentChanged (ValueTree&) {} | |||
void AudioProcessorValueTreeState::timerCallback() | |||
{ | |||
const int numParams = processor.getParameters().size(); | |||
bool anythingUpdated = false; | |||
for (int i = 0; i < numParams; ++i) | |||
{ | |||
AudioProcessorParameter* const ap = processor.getParameters().getUnchecked(i); | |||
jassert (dynamic_cast<Parameter*> (ap) != nullptr); | |||
Parameter* p = static_cast<Parameter*> (ap); | |||
if (p->needsUpdate.compareAndSetBool (0, 1)) | |||
{ | |||
p->copyValueToValueTree(); | |||
anythingUpdated = true; | |||
} | |||
} | |||
startTimer (anythingUpdated ? 1000 / 50 | |||
: jlimit (50, 500, getTimerInterval() + 20)); | |||
} | |||
AudioProcessorValueTreeState::Listener::Listener() {} | |||
AudioProcessorValueTreeState::Listener::~Listener() {} | |||
//============================================================================== | |||
struct AttachedControlBase : public AudioProcessorValueTreeState::Listener, | |||
public AsyncUpdater | |||
{ | |||
AttachedControlBase (AudioProcessorValueTreeState& s, const String& p) | |||
: state (s), paramID (p), lastValue (0) | |||
{ | |||
state.addParameterListener (paramID, this); | |||
} | |||
void removeListener() | |||
{ | |||
state.removeParameterListener (paramID, this); | |||
} | |||
void setNewUnnormalisedValue (float newUnnormalisedValue) | |||
{ | |||
if (AudioProcessorParameter* p = state.getParameter (paramID)) | |||
{ | |||
const float newValue = state.getParameterRange (paramID) | |||
.convertTo0to1 (newUnnormalisedValue); | |||
if (p->getValue() != newValue) | |||
p->setValueNotifyingHost (newValue); | |||
} | |||
} | |||
void sendInitialUpdate() | |||
{ | |||
if (float* v = state.getRawParameterValue (paramID)) | |||
parameterChanged (paramID, *v); | |||
} | |||
void parameterChanged (const String&, float newValue) override | |||
{ | |||
lastValue = newValue; | |||
if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
{ | |||
cancelPendingUpdate(); | |||
setValue (newValue); | |||
} | |||
else | |||
{ | |||
triggerAsyncUpdate(); | |||
} | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
setValue (lastValue); | |||
} | |||
virtual void setValue (float) = 0; | |||
AudioProcessorValueTreeState& state; | |||
String paramID; | |||
float lastValue; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AttachedControlBase) | |||
}; | |||
//============================================================================== | |||
struct AudioProcessorValueTreeState::SliderAttachment::Pimpl : private AttachedControlBase, | |||
private Slider::Listener | |||
{ | |||
Pimpl (AudioProcessorValueTreeState& s, const String& p, Slider& sl) | |||
: AttachedControlBase (s, p), slider (sl) | |||
{ | |||
NormalisableRange<float> range (s.getParameterRange (paramID)); | |||
slider.setRange (range.start, range.end, range.interval); | |||
if (AudioProcessorParameter* param = state.getParameter (paramID)) | |||
slider.setDoubleClickReturnValue (true, range.convertFrom0to1 (param->getDefaultValue())); | |||
sendInitialUpdate(); | |||
slider.addListener (this); | |||
} | |||
~Pimpl() | |||
{ | |||
slider.removeListener (this); | |||
removeListener(); | |||
} | |||
void setValue (float newValue) override | |||
{ | |||
slider.setValue (newValue, sendNotificationSync); | |||
} | |||
void sliderValueChanged (Slider* s) override | |||
{ | |||
if (! ModifierKeys::getCurrentModifiers().isRightButtonDown()) | |||
setNewUnnormalisedValue ((float) s->getValue()); | |||
} | |||
void sliderDragStarted (Slider*) override | |||
{ | |||
if (AudioProcessorParameter* p = state.getParameter (paramID)) | |||
p->beginChangeGesture(); | |||
} | |||
void sliderDragEnded (Slider*) override | |||
{ | |||
if (AudioProcessorParameter* p = state.getParameter (paramID)) | |||
p->endChangeGesture(); | |||
} | |||
Slider& slider; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||
}; | |||
AudioProcessorValueTreeState::SliderAttachment::SliderAttachment (AudioProcessorValueTreeState& s, const String& p, Slider& sl) | |||
: pimpl (new Pimpl (s, p, sl)) | |||
{ | |||
} | |||
AudioProcessorValueTreeState::SliderAttachment::~SliderAttachment() {} | |||
//============================================================================== | |||
struct AudioProcessorValueTreeState::ComboBoxAttachment::Pimpl : private AttachedControlBase, | |||
private ComboBox::Listener | |||
{ | |||
Pimpl (AudioProcessorValueTreeState& s, const String& p, ComboBox& c) | |||
: AttachedControlBase (s, p), combo (c) | |||
{ | |||
sendInitialUpdate(); | |||
combo.addListener (this); | |||
} | |||
~Pimpl() | |||
{ | |||
combo.removeListener (this); | |||
removeListener(); | |||
} | |||
void setValue (float newValue) override | |||
{ | |||
combo.setSelectedItemIndex (roundToInt (newValue), sendNotificationSync); | |||
} | |||
void comboBoxChanged (ComboBox* comboBox) override | |||
{ | |||
setNewUnnormalisedValue ((float) comboBox->getSelectedId() - 1.0f); | |||
} | |||
ComboBox& combo; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||
}; | |||
AudioProcessorValueTreeState::ComboBoxAttachment::ComboBoxAttachment (AudioProcessorValueTreeState& s, const String& p, ComboBox& c) | |||
: pimpl (new Pimpl (s, p, c)) | |||
{ | |||
} | |||
AudioProcessorValueTreeState::ComboBoxAttachment::~ComboBoxAttachment() {} | |||
//============================================================================== | |||
struct AudioProcessorValueTreeState::ButtonAttachment::Pimpl : private AttachedControlBase, | |||
private Button::Listener | |||
{ | |||
Pimpl (AudioProcessorValueTreeState& s, const String& p, Button& b) | |||
: AttachedControlBase (s, p), button (b) | |||
{ | |||
sendInitialUpdate(); | |||
button.addListener (this); | |||
} | |||
~Pimpl() | |||
{ | |||
button.removeListener (this); | |||
removeListener(); | |||
} | |||
void setValue (float newValue) override | |||
{ | |||
button.setToggleState (newValue >= 0.5f, sendNotificationSync); | |||
} | |||
void buttonClicked (Button* b) override | |||
{ | |||
setNewUnnormalisedValue (b->getToggleState() ? 1.0f : 0.0f); | |||
} | |||
Button& button; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||
}; | |||
AudioProcessorValueTreeState::ButtonAttachment::ButtonAttachment (AudioProcessorValueTreeState& s, const String& p, Button& b) | |||
: pimpl (new Pimpl (s, p, b)) | |||
{ | |||
} | |||
AudioProcessorValueTreeState::ButtonAttachment::~ButtonAttachment() {} | |||
#endif |
@@ -0,0 +1,226 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2013 - Raw Material Software 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. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_AUDIOPROCESSORVALUETREESTATE_H_INCLUDED | |||
#define JUCE_AUDIOPROCESSORVALUETREESTATE_H_INCLUDED | |||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS || defined (DOXYGEN) | |||
/** | |||
This class contains a ValueTree which is used to manage an AudioProcessor's entire state. | |||
It has its own internal class of parameter object which are linked to values | |||
within its ValueTree, and which are each identified by a string ID. | |||
To use: Create a AudioProcessorValueTreeState, and give it some parameters | |||
using createParameter(). | |||
You can get access to the underlying ValueTree object via the state member variable, | |||
so you can add extra properties to it as necessary. | |||
It also provides some utility child classes for connecting parameters directly to | |||
GUI controls like sliders. | |||
*/ | |||
class JUCE_API AudioProcessorValueTreeState : private Timer, | |||
private ValueTree::Listener | |||
{ | |||
public: | |||
/** Creates a state object for a given processor. | |||
The UndoManager is optional and can be a nullptr. | |||
After creating your state object, you should add parameters with the | |||
createAndAddParameter() method. Note that each AudioProcessorValueTreeState | |||
should be attached to only one processor, and must have the same lifetime as the | |||
processor, as they will have dependencies on each other. | |||
*/ | |||
AudioProcessorValueTreeState (AudioProcessor& processorToConnectTo, | |||
UndoManager* undoManagerToUse); | |||
/** Destructor. */ | |||
~AudioProcessorValueTreeState(); | |||
/** Creates and returns a new parameter object for controlling a parameter | |||
with the given ID. | |||
Calling this will create and add a special type of AudioProcessorParameter to the | |||
AudioProcessor to which this state is attached. | |||
@param parameterID A unique string ID for the new parameter | |||
@param parameterName The name that the parameter will return from AudioProcessorParameter::getName() | |||
@param labelText The label that the parameter will return from AudioProcessorParameter::getLabel() | |||
@param valueRange A mapping that will be used to determine the value range which this parameter uses | |||
@param defaultValue A default value for the parameter (in non-normalised units) | |||
@param valueToTextFunction A function that will convert a non-normalised value to a string for the | |||
AudioProcessorParameter::getText() method. This can be nullptr to use the | |||
default implementation | |||
@param textToValueFunction The inverse of valueToTextFunction | |||
@returns the parameter object that was created | |||
*/ | |||
AudioProcessorParameter* createAndAddParameter (String parameterID, | |||
String parameterName, | |||
String labelText, | |||
NormalisableRange<float> valueRange, | |||
float defaultValue, | |||
std::function<String (float)> valueToTextFunction, | |||
std::function<float (const String&)> textToValueFunction); | |||
/** Returns a parameter by its ID string. */ | |||
AudioProcessorParameter* getParameter (StringRef parameterID) const noexcept; | |||
/** Returns a pointer to a floating point representation of a particular | |||
parameter which a realtime process can read to find out its current value. | |||
*/ | |||
float* getRawParameterValue (StringRef parameterID) const noexcept; | |||
/** A listener class that can be attached to an AudioProcessorValueTreeState. | |||
Use AudioProcessorValueTreeState::addParameterListener() to register a callback. | |||
*/ | |||
struct JUCE_API Listener | |||
{ | |||
Listener(); | |||
virtual ~Listener(); | |||
/** This callback method is called by the AudioProcessorValueTreeState when a parameter changes. */ | |||
virtual void parameterChanged (const String& parameterID, float newValue) = 0; | |||
}; | |||
/** Attaches a callback to one of the parameters, which will be called when the parameter changes. */ | |||
void addParameterListener (StringRef parameterID, Listener* listener); | |||
/** Removes a callback that was previously added with addParameterCallback(). */ | |||
void removeParameterListener (StringRef parameterID, Listener* listener); | |||
/** Returns a Value object that can be used to control a particular parameter. */ | |||
Value getParameterAsValue (StringRef parameterID) const; | |||
/** Returns the range that was set when the given parameter was created. */ | |||
NormalisableRange<float> getParameterRange (StringRef parameterID) const noexcept; | |||
/** A reference to the processor with which this state is associated. */ | |||
AudioProcessor& processor; | |||
/** The state of the whole processor. | |||
You can replace this with your own ValueTree object, and can add properties and | |||
children to the tree. This class will automatically add children for each of the | |||
parameter objects that are created by createParameter(). | |||
*/ | |||
ValueTree state; | |||
/** Provides access to the undo manager that this object is using. */ | |||
UndoManager* const undoManager; | |||
//============================================================================== | |||
/** An object of this class maintains a connection between a Slider and a parameter | |||
in an AudioProcessorValueTreeState. | |||
During the lifetime of this SliderAttachment object, it keeps the two things in | |||
sync, making it easy to connect a slider to a parameter. When this object is | |||
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState | |||
and Slider aren't deleted before this object! | |||
*/ | |||
class JUCE_API SliderAttachment | |||
{ | |||
public: | |||
SliderAttachment (AudioProcessorValueTreeState& stateToControl, | |||
const String& parameterID, | |||
Slider& sliderToControl); | |||
~SliderAttachment(); | |||
private: | |||
struct Pimpl; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment) | |||
}; | |||
//============================================================================== | |||
/** An object of this class maintains a connection between a ComboBox and a parameter | |||
in an AudioProcessorValueTreeState. | |||
During the lifetime of this SliderAttachment object, it keeps the two things in | |||
sync, making it easy to connect a combo box to a parameter. When this object is | |||
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState | |||
and ComboBox aren't deleted before this object! | |||
*/ | |||
class JUCE_API ComboBoxAttachment | |||
{ | |||
public: | |||
ComboBoxAttachment (AudioProcessorValueTreeState& stateToControl, | |||
const String& parameterID, | |||
ComboBox& comboBoxToControl); | |||
~ComboBoxAttachment(); | |||
private: | |||
struct Pimpl; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment) | |||
}; | |||
//============================================================================== | |||
/** An object of this class maintains a connection between a Button and a parameter | |||
in an AudioProcessorValueTreeState. | |||
During the lifetime of this SliderAttachment object, it keeps the two things in | |||
sync, making it easy to connect a button to a parameter. When this object is | |||
deleted, the connection is broken. Make sure that your AudioProcessorValueTreeState | |||
and Button aren't deleted before this object! | |||
*/ | |||
class JUCE_API ButtonAttachment | |||
{ | |||
public: | |||
ButtonAttachment (AudioProcessorValueTreeState& stateToControl, | |||
const String& parameterID, | |||
Button& buttonToControl); | |||
~ButtonAttachment(); | |||
private: | |||
struct Pimpl; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment) | |||
}; | |||
private: | |||
//============================================================================== | |||
struct Parameter; | |||
friend struct Parameter; | |||
ValueTree getOrCreateChildValueTree (const String&); | |||
void timerCallback() override; | |||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override; | |||
void valueTreeChildAdded (ValueTree&, ValueTree&) override; | |||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override; | |||
void valueTreeChildOrderChanged (ValueTree&, int, int) override; | |||
void valueTreeParentChanged (ValueTree&) override; | |||
void valueTreeRedirected (ValueTree&) override; | |||
void updateParameterConnectionsToChildTrees(); | |||
Identifier valueType, valuePropertyID, idPropertyID; | |||
bool updatingConnections; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorValueTreeState) | |||
}; | |||
#endif | |||
#endif // JUCE_AUDIOPROCESSORVALUETREESTATE_H_INCLUDED |
@@ -80,12 +80,16 @@ public: | |||
deviceManager (dm), | |||
noItemsMessage (noItems) | |||
{ | |||
items = MidiInput::getDevices(); | |||
updateDevices(); | |||
setModel (this); | |||
setOutlineThickness (1); | |||
} | |||
void updateDevices() | |||
{ | |||
items = MidiInput::getDevices(); | |||
} | |||
int getNumRows() override | |||
{ | |||
return items.size(); | |||
@@ -1011,11 +1015,19 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& | |||
midiInputsLabel = new Label (String::empty, TRANS ("Active MIDI inputs:")); | |||
midiInputsLabel->setJustificationType (Justification::topRight); | |||
midiInputsLabel->attachToComponent (midiInputsList, true); | |||
if (BluetoothMidiDevicePairingDialogue::isAvailable()) | |||
{ | |||
addAndMakeVisible (bluetoothButton = new TextButton (TRANS("Bluetooth MIDI"), | |||
TRANS("Scan for bluetooth MIDI devices"))); | |||
bluetoothButton->addListener (this); | |||
} | |||
} | |||
else | |||
{ | |||
midiInputsList = nullptr; | |||
midiInputsLabel = nullptr; | |||
bluetoothButton = nullptr; | |||
} | |||
if (showMidiOutputSelector) | |||
@@ -1034,6 +1046,7 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& | |||
deviceManager.addChangeListener (this); | |||
updateAllControls(); | |||
startTimer (1000); | |||
} | |||
AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent() | |||
@@ -1073,10 +1086,26 @@ void AudioDeviceSelectorComponent::resized() | |||
r.removeFromTop (space); | |||
} | |||
if (bluetoothButton != nullptr) | |||
{ | |||
bluetoothButton->setBounds (r.removeFromTop (24)); | |||
r.removeFromTop (space); | |||
} | |||
if (midiOutputSelector != nullptr) | |||
midiOutputSelector->setBounds (r.removeFromTop (itemHeight)); | |||
} | |||
void AudioDeviceSelectorComponent::timerCallback() | |||
{ | |||
// TODO | |||
// unfortunately, the AudioDeviceManager only gives us changeListenerCallbacks | |||
// if an audio device has changed, but not if a MIDI device has changed. | |||
// This needs to be implemented properly. Until then, we use a workaround | |||
// where we update the whole component once per second on a timer callback. | |||
updateAllControls(); | |||
} | |||
void AudioDeviceSelectorComponent::comboBoxChanged (ComboBox* comboBoxThatHasChanged) | |||
{ | |||
if (comboBoxThatHasChanged == deviceTypeDropDown) | |||
@@ -1133,6 +1162,7 @@ void AudioDeviceSelectorComponent::updateAllControls() | |||
if (midiInputsList != nullptr) | |||
{ | |||
midiInputsList->updateDevices(); | |||
midiInputsList->updateContent(); | |||
midiInputsList->repaint(); | |||
} | |||
@@ -1159,3 +1189,9 @@ void AudioDeviceSelectorComponent::updateAllControls() | |||
resized(); | |||
} | |||
void AudioDeviceSelectorComponent::buttonClicked (Button* btn) | |||
{ | |||
if (bluetoothButton == btn) | |||
BluetoothMidiDevicePairingDialogue::open(); | |||
} |
@@ -37,7 +37,9 @@ | |||
*/ | |||
class JUCE_API AudioDeviceSelectorComponent : public Component, | |||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||
private ChangeListener | |||
private ChangeListener, | |||
private Button::Listener, | |||
private Timer | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -84,8 +86,13 @@ public: | |||
//============================================================================== | |||
/** @internal */ | |||
void resized() override; | |||
/** @internal */ | |||
void timerCallback() override; | |||
private: | |||
//============================================================================== | |||
void buttonClicked (Button*) override; | |||
//============================================================================== | |||
ScopedPointer<ComboBox> deviceTypeDropDown; | |||
ScopedPointer<Label> deviceTypeDropDownLabel; | |||
@@ -101,6 +108,7 @@ private: | |||
ScopedPointer<MidiInputSelectorComponentListBox> midiInputsList; | |||
ScopedPointer<ComboBox> midiOutputSelector; | |||
ScopedPointer<Label> midiInputsLabel, midiOutputLabel; | |||
ScopedPointer<TextButton> bluetoothButton; | |||
void comboBoxChanged (ComboBox*) override; | |||
void changeListenerCallback (ChangeBroadcaster*) override; | |||
@@ -0,0 +1,76 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_BLUETOOTHMIDIDEVICPAIRINGCOMPONENT_H_INCLUDED | |||
#define JUCE_BLUETOOTHMIDIDEVICPAIRINGCOMPONENT_H_INCLUDED | |||
class BluetoothMidiSelectorOverlay; | |||
//============================================================================== | |||
/** | |||
Opens a Bluetooth MIDI pairing dialogue that allows the user to view and | |||
connect to Bluetooth MIDI devices that are currently found nearby. | |||
The dialogue will ignore non-MIDI Bluetooth devices. | |||
Only after a Bluetooth MIDI device has been paired will its MIDI ports | |||
be available through JUCE's MidiInput and MidiOutput classes. | |||
This dialogue is currently only available on iOS and Android. On OSX, | |||
you should instead pair Bluetooth MIDI devices using the "Audio MIDI Setup" | |||
app (located in /Applications/Utilities). On Windows, you should use | |||
the system settings. On Linux, Bluetooth MIDI devices are currently not | |||
supported. | |||
*/ | |||
class BluetoothMidiDevicePairingDialogue | |||
{ | |||
public: | |||
/** Opens the Bluetooth MIDI pairing dialogue, if it is available. | |||
@return true if the dialogue was opened, false on error. | |||
*/ | |||
static bool open(); | |||
/** Checks if a Bluetooth MIDI pairing dialogue is available on this | |||
platform. | |||
On iOS, this will be true for iOS versions 8.0 and higher. | |||
On Android, this will be true only for Android SDK versions 23 and | |||
higher, and additionally only if the device itself supports MIDI | |||
over Bluetooth. | |||
On deskrop platforms, this will typically be false as the bluetooth | |||
pairing is not done inside the app but by other means. | |||
@return true if the Bluetooth MIDI pairing dialogue is available, | |||
false otherwise. | |||
*/ | |||
static bool isAvailable(); | |||
}; | |||
#endif // JUCE_BLUETOOTHMIDIDEVICPAIRINGCOMPONENT_H_INCLUDED |
@@ -49,4 +49,21 @@ namespace juce | |||
#include "gui/juce_AudioAppComponent.cpp" | |||
#include "players/juce_AudioProcessorPlayer.cpp" | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra | |||
#include "../juce_gui_extra/embedding/juce_UIViewComponent.h" | |||
#endif | |||
#if JUCE_MAC | |||
#include "native/juce_mac_BluetoothMidiDevicePairingDialogue.mm" | |||
#elif JUCE_IOS | |||
#include "native/juce_ios_BluetoothMidiDevicePairingDialogue.mm" | |||
#elif JUCE_ANDROID | |||
#include "../juce_core/native/juce_android_JNIHelpers.h" | |||
#include "native/juce_android_BluetoothMidiDevicePairingDialogue.cpp" | |||
#elif JUCE_LINUX | |||
#include "native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp" | |||
#elif JUCE_WINDOWS | |||
#include "native/juce_win_BluetoothMidiDevicePairingDialogue.cpp" | |||
#endif | |||
} |
@@ -41,6 +41,7 @@ namespace juce | |||
#include "gui/juce_AudioVisualiserComponent.h" | |||
#include "gui/juce_MidiKeyboardComponent.h" | |||
#include "gui/juce_AudioAppComponent.h" | |||
#include "gui/juce_BluetoothMidiDevicePairingDialogue.h" | |||
#include "players/juce_AudioProcessorPlayer.h" | |||
} | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_utils", | |||
"name": "JUCE extra audio utility classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for audio-related GUI and miscellaneous tasks.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -17,5 +17,8 @@ | |||
{ "file": "juce_audio_utils.mm", "target": "xcode" } ], | |||
"browse": [ "gui/*", | |||
"players/*" ] | |||
"players/*", | |||
"native/*" ], | |||
"iOSFrameworks": "CoreAudioKit" | |||
} |
@@ -0,0 +1,438 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
//============================================================================== | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (getMidiBluetoothAddresses, "getMidiBluetoothAddresses", "()[Ljava/lang/String;") \ | |||
METHOD (pairBluetoothMidiDevice, "pairBluetoothMidiDevice", "(Ljava/lang/String;)Z") \ | |||
METHOD (unpairBluetoothMidiDevice, "unpairBluetoothMidiDevice", "(Ljava/lang/String;)V") \ | |||
METHOD (getHumanReadableStringForBluetoothAddress, "getHumanReadableStringForBluetoothAddress", "(Ljava/lang/String;)Ljava/lang/String;") \ | |||
METHOD (isBluetoothDevicePaired, "isBluetoothDevicePaired", "(Ljava/lang/String;)Z") | |||
DECLARE_JNI_CLASS (AndroidBluetoothManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager"); | |||
#undef JNI_CLASS_MEMBERS | |||
//============================================================================== | |||
struct AndroidBluetoothMidiInterface | |||
{ | |||
static StringArray getBluetoothMidiDevicesNearby() | |||
{ | |||
StringArray retval; | |||
JNIEnv* env = getEnv(); | |||
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
// if this is null then bluetooth is not enabled | |||
if (btManager.get() == nullptr) | |||
return StringArray(); | |||
jobjectArray jDevices = (jobjectArray) env->CallObjectMethod (btManager.get(), | |||
AndroidBluetoothManager.getMidiBluetoothAddresses); | |||
LocalRef<jobjectArray> devices (jDevices); | |||
const int count = env->GetArrayLength (devices.get()); | |||
for (int i = 0; i < count; ++i) | |||
{ | |||
LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (devices.get(), i)); | |||
retval.add (juceString (string)); | |||
} | |||
return retval; | |||
} | |||
//========================================================================== | |||
static bool pairBluetoothMidiDevice (const String& bluetoothAddress) | |||
{ | |||
JNIEnv* env = getEnv(); | |||
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
if (btManager.get() == nullptr) | |||
return false; | |||
jboolean result = env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.pairBluetoothMidiDevice, | |||
javaString (bluetoothAddress).get()); | |||
return result; | |||
} | |||
static void unpairBluetoothMidiDevice (const String& bluetoothAddress) | |||
{ | |||
JNIEnv* env = getEnv(); | |||
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
if (btManager.get() != nullptr) | |||
env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.unpairBluetoothMidiDevice, | |||
javaString (bluetoothAddress).get()); | |||
} | |||
//========================================================================== | |||
static String getHumanReadableStringForBluetoothAddress (const String& address) | |||
{ | |||
JNIEnv* env = getEnv(); | |||
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
if (btManager.get() == nullptr) | |||
return address; | |||
LocalRef<jstring> string ((jstring) env->CallObjectMethod (btManager.get(), | |||
AndroidBluetoothManager.getHumanReadableStringForBluetoothAddress, | |||
javaString (address).get())); | |||
if (string.get() == nullptr) | |||
return address; | |||
return juceString (string); | |||
} | |||
//========================================================================== | |||
static bool isBluetoothDevicePaired (const String& address) | |||
{ | |||
JNIEnv* env = getEnv(); | |||
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
if (btManager.get() == nullptr) | |||
return false; | |||
return env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.isBluetoothDevicePaired, | |||
javaString (address).get()); | |||
} | |||
}; | |||
//============================================================================== | |||
struct AndroidBluetoothMidiDevice | |||
{ | |||
enum ConnectionStatus | |||
{ | |||
offline, | |||
connected, | |||
disconnected, | |||
connecting, | |||
disconnecting | |||
}; | |||
AndroidBluetoothMidiDevice (String deviceName, String address, ConnectionStatus status) | |||
: name (deviceName), bluetoothAddress (address), connectionStatus (status) | |||
{ | |||
// can't create a device without a valid name and bluetooth address! | |||
jassert (! name.isEmpty()); | |||
jassert (! bluetoothAddress.isEmpty()); | |||
} | |||
bool operator== (const AndroidBluetoothMidiDevice& other) const noexcept | |||
{ | |||
return bluetoothAddress == other.bluetoothAddress; | |||
} | |||
bool operator!= (const AndroidBluetoothMidiDevice& other) const noexcept | |||
{ | |||
return ! operator== (other); | |||
} | |||
const String name, bluetoothAddress; | |||
ConnectionStatus connectionStatus; | |||
}; | |||
//============================================================================== | |||
class AndroidBluetoothMidiDevicesListBox : public ListBox, | |||
private ListBoxModel, | |||
private Timer | |||
{ | |||
public: | |||
//========================================================================== | |||
AndroidBluetoothMidiDevicesListBox() | |||
: timerPeriodInMs (1000) | |||
{ | |||
setRowHeight (40); | |||
setModel (this); | |||
setOutlineThickness (1); | |||
updateDeviceList(); | |||
startTimer (timerPeriodInMs); | |||
} | |||
void pairDeviceThreadFinished() // callback from PairDeviceThread | |||
{ | |||
updateDeviceList(); | |||
startTimer (timerPeriodInMs); | |||
} | |||
private: | |||
//========================================================================== | |||
typedef AndroidBluetoothMidiDevice::ConnectionStatus DeviceStatus; | |||
int getNumRows() override | |||
{ | |||
return devices.size(); | |||
} | |||
void paintListBoxItem (int rowNumber, Graphics& g, | |||
int width, int height, bool rowIsSelected) override | |||
{ | |||
if (isPositiveAndBelow (rowNumber, devices.size())) | |||
{ | |||
const AndroidBluetoothMidiDevice& device = devices.getReference (rowNumber); | |||
const String statusString (getDeviceStatusString (device.connectionStatus)); | |||
g.fillAll (Colours::white); | |||
const float xmargin = 3.0f; | |||
const float ymargin = 3.0f; | |||
const float fontHeight = 0.4f * height; | |||
const float deviceNameWidth = 0.6f * width; | |||
g.setFont (fontHeight); | |||
g.setColour (getDeviceNameFontColour (device.connectionStatus)); | |||
g.drawText (device.name, | |||
xmargin, ymargin, | |||
deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin), | |||
Justification::topLeft, true); | |||
g.setColour (getDeviceStatusFontColour (device.connectionStatus)); | |||
g.drawText (statusString, | |||
deviceNameWidth + xmargin, ymargin, | |||
width - deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin), | |||
Justification::topRight, true); | |||
g.setColour (Colours::grey); | |||
g.drawHorizontalLine (height - 1, xmargin, width); | |||
} | |||
} | |||
//========================================================================== | |||
static Colour getDeviceNameFontColour (DeviceStatus deviceStatus) noexcept | |||
{ | |||
if (deviceStatus == AndroidBluetoothMidiDevice::offline) | |||
return Colours::grey; | |||
return Colours::black; | |||
} | |||
static Colour getDeviceStatusFontColour (DeviceStatus deviceStatus) noexcept | |||
{ | |||
if (deviceStatus == AndroidBluetoothMidiDevice::offline | |||
|| deviceStatus == AndroidBluetoothMidiDevice::connecting | |||
|| deviceStatus == AndroidBluetoothMidiDevice::disconnecting) | |||
return Colours::grey; | |||
if (deviceStatus == AndroidBluetoothMidiDevice::connected) | |||
return Colours::green; | |||
return Colours::black; | |||
} | |||
static String getDeviceStatusString (DeviceStatus deviceStatus) noexcept | |||
{ | |||
if (deviceStatus == AndroidBluetoothMidiDevice::offline) return "Offline"; | |||
if (deviceStatus == AndroidBluetoothMidiDevice::connected) return "Connected"; | |||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnected) return "Not connected"; | |||
if (deviceStatus == AndroidBluetoothMidiDevice::connecting) return "Connecting..."; | |||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnecting) return "Disconnecting..."; | |||
// unknown device state! | |||
jassertfalse; | |||
return "Status unknown"; | |||
} | |||
//========================================================================== | |||
void listBoxItemClicked (int row, const MouseEvent&) override | |||
{ | |||
const AndroidBluetoothMidiDevice& device = devices.getReference (row); | |||
if (device.connectionStatus == AndroidBluetoothMidiDevice::disconnected) | |||
disconnectedDeviceClicked (row); | |||
else if (device.connectionStatus == AndroidBluetoothMidiDevice::connected) | |||
connectedDeviceClicked (row); | |||
} | |||
void timerCallback() override | |||
{ | |||
updateDeviceList(); | |||
} | |||
//========================================================================== | |||
struct PairDeviceThread : public Thread, | |||
private AsyncUpdater | |||
{ | |||
PairDeviceThread (const String& bluetoothAddressOfDeviceToPair, | |||
AndroidBluetoothMidiDevicesListBox& ownerListBox) | |||
: Thread ("JUCE Bluetooth MIDI Device Pairing Thread"), | |||
bluetoothAddress (bluetoothAddressOfDeviceToPair), | |||
owner (&ownerListBox) | |||
{ | |||
startThread(); | |||
} | |||
void run() override | |||
{ | |||
AndroidBluetoothMidiInterface::pairBluetoothMidiDevice (bluetoothAddress); | |||
triggerAsyncUpdate(); | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
if (owner != nullptr) | |||
owner->pairDeviceThreadFinished(); | |||
delete this; | |||
} | |||
private: | |||
String bluetoothAddress; | |||
Component::SafePointer<AndroidBluetoothMidiDevicesListBox> owner; | |||
}; | |||
//========================================================================== | |||
void disconnectedDeviceClicked (int row) | |||
{ | |||
stopTimer(); | |||
AndroidBluetoothMidiDevice& device = devices.getReference (row); | |||
device.connectionStatus = AndroidBluetoothMidiDevice::connecting; | |||
updateContent(); | |||
repaint(); | |||
new PairDeviceThread (device.bluetoothAddress, *this); | |||
} | |||
void connectedDeviceClicked (int row) | |||
{ | |||
AndroidBluetoothMidiDevice& device = devices.getReference (row); | |||
device.connectionStatus = AndroidBluetoothMidiDevice::disconnecting; | |||
updateContent(); | |||
repaint(); | |||
AndroidBluetoothMidiInterface::unpairBluetoothMidiDevice (device.bluetoothAddress); | |||
} | |||
//========================================================================== | |||
void updateDeviceList() | |||
{ | |||
StringArray bluetoothAddresses = AndroidBluetoothMidiInterface::getBluetoothMidiDevicesNearby(); | |||
Array<AndroidBluetoothMidiDevice> newDevices; | |||
for (String* address = bluetoothAddresses.begin(); | |||
address != bluetoothAddresses.end(); ++address) | |||
{ | |||
String name = AndroidBluetoothMidiInterface::getHumanReadableStringForBluetoothAddress (*address); | |||
DeviceStatus status = AndroidBluetoothMidiInterface::isBluetoothDevicePaired (*address) | |||
? AndroidBluetoothMidiDevice::connected | |||
: AndroidBluetoothMidiDevice::disconnected; | |||
newDevices.add (AndroidBluetoothMidiDevice (name, *address, status)); | |||
} | |||
devices.swapWith (newDevices); | |||
updateContent(); | |||
repaint(); | |||
} | |||
Array<AndroidBluetoothMidiDevice> devices; | |||
const int timerPeriodInMs; | |||
}; | |||
//============================================================================== | |||
class BluetoothMidiSelectorOverlay : public Component | |||
{ | |||
public: | |||
BluetoothMidiSelectorOverlay() | |||
{ | |||
setAlwaysOnTop (true); | |||
setVisible (true); | |||
addToDesktop (ComponentPeer::windowHasDropShadow); | |||
setBounds (0, 0, getParentWidth(), getParentHeight()); | |||
toFront (true); | |||
addAndMakeVisible (bluetoothDevicesList); | |||
enterModalState (true, nullptr, true); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (Colours::black.withAlpha (0.6f)); | |||
g.setColour (Colour (0xffdfdfdf)); | |||
Rectangle<int> overlayBounds = getOverlayBounds(); | |||
g.fillRect (overlayBounds); | |||
g.setColour (Colours::black); | |||
g.setFont (16); | |||
g.drawText ("Bluetooth MIDI Devices", | |||
overlayBounds.removeFromTop (20).reduced (3, 3), | |||
Justification::topLeft, true); | |||
overlayBounds.removeFromTop (2); | |||
g.setFont (12); | |||
g.drawText ("tap to connect/disconnect", | |||
overlayBounds.removeFromTop (18).reduced (3, 3), | |||
Justification::topLeft, true); | |||
} | |||
void inputAttemptWhenModal() override { exitModalState (0); } | |||
void mouseDrag (const MouseEvent&) override {} | |||
void mouseDown (const MouseEvent&) override { exitModalState (0); } | |||
void resized() override { update(); } | |||
void parentSizeChanged() override { update(); } | |||
private: | |||
void update() | |||
{ | |||
setBounds (0, 0, getParentWidth(), getParentHeight()); | |||
bluetoothDevicesList.setBounds (getOverlayBounds().withTrimmedTop (40)); | |||
} | |||
Rectangle<int> getOverlayBounds() const noexcept | |||
{ | |||
const int pw = getParentWidth(); | |||
const int ph = getParentHeight(); | |||
return Rectangle<int> (pw, ph).withSizeKeepingCentre (jmin (400, pw - 14), | |||
jmin (300, ph - 40)); | |||
} | |||
AndroidBluetoothMidiDevicesListBox bluetoothDevicesList; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay) | |||
}; | |||
//============================================================================== | |||
bool BluetoothMidiDevicePairingDialogue::open() | |||
{ | |||
BluetoothMidiSelectorOverlay* overlay = new BluetoothMidiSelectorOverlay; | |||
return true; | |||
} | |||
bool BluetoothMidiDevicePairingDialogue::isAvailable() | |||
{ | |||
jobject btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); | |||
return btManager != nullptr; | |||
} |
@@ -0,0 +1,138 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
// Note: for the Bluetooth Midi selector overlay, we need the class | |||
// UIViewComponent from the juce_gui_extra module. If this module is not | |||
// included in your app, BluetoothMidiDevicePairingDialogue::open() will fail | |||
// and return false. | |||
// It is also not available in the iPhone/iPad simulator. | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! TARGET_IPHONE_SIMULATOR | |||
} // (juce namespace) | |||
#include <CoreAudioKit/CoreAudioKit.h> | |||
//============================================================================== | |||
@interface BluetoothSelectorView : NSObject | |||
@property CABTMIDICentralViewController *central; | |||
- (UIView*) getView; | |||
@end | |||
//============================================================================== | |||
@implementation BluetoothSelectorView | |||
- (instancetype) init | |||
{ | |||
self = [super init]; | |||
self.central = [[CABTMIDICentralViewController alloc] init]; | |||
return self; | |||
} | |||
- (UIView*) getView | |||
{ | |||
return self.central.view; | |||
} | |||
@end | |||
namespace juce | |||
{ | |||
//============================================================================== | |||
class BluetoothMidiSelectorOverlay : public Component | |||
{ | |||
public: | |||
BluetoothMidiSelectorOverlay() | |||
{ | |||
setAlwaysOnTop (true); | |||
setVisible (true); | |||
addToDesktop (ComponentPeer::windowHasDropShadow); | |||
setBounds (0, 0, getParentWidth(), getParentHeight()); | |||
toFront (true); | |||
nativeSelectorComponent.setView ([[[BluetoothSelectorView alloc] init] getView]); | |||
addAndMakeVisible (nativeSelectorComponent); | |||
enterModalState (true, nullptr, true); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
g.fillAll (Colours::black.withAlpha (0.5f)); | |||
} | |||
void inputAttemptWhenModal() override { close(); } | |||
void mouseDrag (const MouseEvent&) override {} | |||
void mouseDown (const MouseEvent&) override { close(); } | |||
void resized () override { update(); } | |||
void parentSizeChanged() override { update(); } | |||
private: | |||
void update() | |||
{ | |||
const int pw = getParentWidth(); | |||
const int ph = getParentHeight(); | |||
nativeSelectorComponent.setBounds (Rectangle<int> (pw, ph) | |||
.withSizeKeepingCentre (jmin (400, pw - 14), | |||
jmin (500, ph - 40))); | |||
} | |||
void close() | |||
{ | |||
exitModalState (0); | |||
setVisible (false); | |||
} | |||
UIViewComponent nativeSelectorComponent; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay) | |||
}; | |||
#endif // JUCE_MODULE_AVAILABLE_juce_gui_extra && ! TARGET_IPHONE_SIMULATOR | |||
//============================================================================== | |||
bool BluetoothMidiDevicePairingDialogue::open() | |||
{ | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! TARGET_IPHONE_SIMULATOR | |||
if (isAvailable()) | |||
{ | |||
new BluetoothMidiSelectorOverlay(); | |||
return true; | |||
} | |||
#endif | |||
return false; | |||
} | |||
bool BluetoothMidiDevicePairingDialogue::isAvailable() | |||
{ | |||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! TARGET_IPHONE_SIMULATOR | |||
return NSClassFromString ([NSString stringWithUTF8String: "CABTMIDICentralViewController"]) != nil; | |||
#else | |||
return false; | |||
#endif | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
bool BluetoothMidiDevicePairingDialogue::open() | |||
{ | |||
// not implemented on Linux yet! | |||
// You should check whether the dialogue is available on your system | |||
// using isAvailable() before calling open(). | |||
jassertfalse; | |||
return false; | |||
} | |||
bool BluetoothMidiDevicePairingDialogue::isAvailable() | |||
{ | |||
return false; | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
bool BluetoothMidiDevicePairingDialogue::open() | |||
{ | |||
// Do not call this on OSX. Instead, you should pair Bluetooth MIDI devices | |||
// using the "Audio MIDI Setup" app (located in /Applications/Utilities). | |||
jassertfalse; | |||
return false; | |||
} | |||
bool BluetoothMidiDevicePairingDialogue::isAvailable() | |||
{ | |||
return false; | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
============================================================================== | |||
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. | |||
============================================================================== | |||
*/ | |||
bool BluetoothMidiDevicePairingDialogue::open() | |||
{ | |||
// not implemented on Windows yet! | |||
// You should check whether the dialogue is available on your system | |||
// using isAvailable() before calling open(). | |||
jassertfalse; | |||
return false; | |||
} | |||
bool BluetoothMidiDevicePairingDialogue::isAvailable() | |||
{ | |||
return false; | |||
} |
@@ -22,11 +22,12 @@ | |||
============================================================================== | |||
*/ | |||
AudioProcessorPlayer::AudioProcessorPlayer() | |||
AudioProcessorPlayer::AudioProcessorPlayer(bool doDoublePrecisionProcessing) | |||
: processor (nullptr), | |||
sampleRate (0), | |||
blockSize (0), | |||
isPrepared (false), | |||
isDoublePrecision (doDoublePrecisionProcessing), | |||
numInputChans (0), | |||
numOutputChans (0) | |||
{ | |||
@@ -45,6 +46,12 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) | |||
if (processorToPlay != nullptr && sampleRate > 0 && blockSize > 0) | |||
{ | |||
processorToPlay->setPlayConfigDetails (numInputChans, numOutputChans, sampleRate, blockSize); | |||
const bool supportsDouble = processorToPlay->supportsDoublePrecisionProcessing() && isDoublePrecision; | |||
AudioProcessor::ProcessingPrecision precision = supportsDouble ? AudioProcessor::doublePrecision | |||
: AudioProcessor::singlePrecision; | |||
processorToPlay->setProcessingPrecision (precision); | |||
processorToPlay->prepareToPlay (sampleRate, blockSize); | |||
} | |||
@@ -62,6 +69,28 @@ void AudioProcessorPlayer::setProcessor (AudioProcessor* const processorToPlay) | |||
} | |||
} | |||
void AudioProcessorPlayer::setDoublePrecisionProcessing (bool doublePrecision) | |||
{ | |||
if (doublePrecision != isDoublePrecision) | |||
{ | |||
const ScopedLock sl (lock); | |||
if (processor != nullptr) | |||
{ | |||
processor->releaseResources(); | |||
const bool supportsDouble = processor->supportsDoublePrecisionProcessing() && doublePrecision; | |||
AudioProcessor::ProcessingPrecision precision = supportsDouble ? AudioProcessor::doublePrecision | |||
: AudioProcessor::singlePrecision; | |||
processor->setProcessingPrecision (precision); | |||
processor->prepareToPlay (sampleRate, blockSize); | |||
} | |||
isDoublePrecision = doublePrecision; | |||
} | |||
} | |||
//============================================================================== | |||
void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChannelData, | |||
const int numInputChannels, | |||
@@ -126,7 +155,17 @@ void AudioProcessorPlayer::audioDeviceIOCallback (const float** const inputChann | |||
if (! processor->isSuspended()) | |||
{ | |||
processor->processBlock (buffer, incomingMidi); | |||
if (processor->isUsingDoublePrecision()) | |||
{ | |||
conversionBuffer.makeCopyOf (buffer); | |||
processor->processBlock (conversionBuffer, incomingMidi); | |||
buffer.makeCopyOf (conversionBuffer); | |||
} | |||
else | |||
{ | |||
processor->processBlock (buffer, incomingMidi); | |||
} | |||
return; | |||
} | |||
} | |||
@@ -25,7 +25,6 @@ | |||
#ifndef JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED | |||
#define JUCE_AUDIOPROCESSORPLAYER_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
An AudioIODeviceCallback object which streams audio through an AudioProcessor. | |||
@@ -43,7 +42,7 @@ class JUCE_API AudioProcessorPlayer : public AudioIODeviceCallback, | |||
{ | |||
public: | |||
//============================================================================== | |||
AudioProcessorPlayer(); | |||
AudioProcessorPlayer(bool doDoublePrecisionProcessing = false); | |||
/** Destructor. */ | |||
virtual ~AudioProcessorPlayer(); | |||
@@ -65,6 +64,20 @@ public: | |||
*/ | |||
MidiMessageCollector& getMidiMessageCollector() noexcept { return messageCollector; } | |||
/** Switch between double and single floating point precisions processing. | |||
The audio IO callbacks will still operate in single floating point | |||
precision, however, all internal processing including the | |||
AudioProcessor will be processed in double floating point precision if | |||
the AudioProcessor supports it (see | |||
AudioProcessor::supportsDoublePrecisionProcessing()). | |||
Otherwise, the processing will remain single precision irrespective of | |||
the parameter doublePrecision. */ | |||
void setDoublePrecisionProcessing (bool doublePrecision); | |||
/** Returns true if this player processes internally processes the samples with | |||
double floating point precision. */ | |||
inline bool getDoublePrecisionProcessing() { return isDoublePrecision; } | |||
//============================================================================== | |||
/** @internal */ | |||
void audioDeviceIOCallback (const float**, int, float**, int, int) override; | |||
@@ -81,11 +94,12 @@ private: | |||
CriticalSection lock; | |||
double sampleRate; | |||
int blockSize; | |||
bool isPrepared; | |||
bool isPrepared, isDoublePrecision; | |||
int numInputChans, numOutputChans; | |||
HeapBlock<float*> channels; | |||
AudioSampleBuffer tempBuffer; | |||
AudioBuffer<float> tempBuffer; | |||
AudioBuffer<double> conversionBuffer; | |||
MidiBuffer incomingMidi; | |||
MidiMessageCollector messageCollector; | |||
@@ -216,13 +216,18 @@ public: | |||
} | |||
//============================================================================== | |||
/** Returns the current number of elements in the array. | |||
*/ | |||
/** Returns the current number of elements in the array. */ | |||
inline int size() const noexcept | |||
{ | |||
return numUsed; | |||
} | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool empty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns one of the elements in the array. | |||
If the index passed in is beyond the range of valid elements, this | |||
will return a default value. | |||
@@ -886,6 +886,41 @@ File File::createTempFile (StringRef fileNameEnding) | |||
return tempFile; | |||
} | |||
bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const | |||
{ | |||
if (linkFileToCreate.exists()) | |||
{ | |||
if (! linkFileToCreate.isSymbolicLink()) | |||
{ | |||
// user has specified an existing file / directory as the link | |||
// this is bad! the user could end up unintentionally destroying data | |||
jassertfalse; | |||
return false; | |||
} | |||
if (overwriteExisting) | |||
linkFileToCreate.deleteFile(); | |||
} | |||
#if JUCE_MAC || JUCE_LINUX | |||
// one common reason for getting an error here is that the file already exists | |||
if (symlink (fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) | |||
{ | |||
jassertfalse; | |||
return false; | |||
} | |||
return true; | |||
#elif JUCE_WINDOWS | |||
return CreateSymbolicLink (linkFileToCreate.getFullPathName().toWideCharPointer(), | |||
fullPath.toWideCharPointer(), | |||
isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE; | |||
#else | |||
jassertfalse; // symbolic links not supported on this platform! | |||
return false; | |||
#endif | |||
} | |||
//============================================================================== | |||
MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) | |||
: address (nullptr), range (0, file.getSize()), fileHandle (0) | |||
@@ -360,14 +360,6 @@ public: | |||
*/ | |||
bool isHidden() const; | |||
/** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ | |||
bool isLink() const; | |||
/** If this file is a link or alias, this returns the file that it points to. | |||
If the file isn't actually link, it'll just return itself. | |||
*/ | |||
File getLinkedTarget() const; | |||
/** Returns a unique identifier for the file, if one is available. | |||
Depending on the OS and file-system, this may be a unix inode number or | |||
@@ -880,7 +872,6 @@ public: | |||
*/ | |||
static File createTempFile (StringRef fileNameEnding); | |||
//============================================================================== | |||
/** Returns the current working directory. | |||
@see setAsCurrentWorkingDirectory | |||
@@ -946,8 +937,28 @@ public: | |||
/** Adds a separator character to the end of a path if it doesn't already have one. */ | |||
static String addTrailingSeparator (const String& path); | |||
#if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
//============================================================================== | |||
/** Tries to create a symbolic link and returns a boolean to indicate success */ | |||
bool createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const; | |||
/** Returns true if this file is a link or alias that can be followed using getLinkedTarget(). */ | |||
bool isSymbolicLink() const; | |||
/** If this file is a link or alias, this returns the file that it points to. | |||
If the file isn't actually link, it'll just return itself. | |||
*/ | |||
File getLinkedTarget() const; | |||
#if JUCE_WINDOWS | |||
/** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ | |||
bool createShortcut (const String& description, const File& linkFileToCreate) const; | |||
/** Windows ONLY - Returns true if this is a win32 .LNK file. */ | |||
bool isShortcut() const; | |||
#endif | |||
//============================================================================== | |||
#if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
/** OSX ONLY - Finds the OSType of a file from the its resources. */ | |||
OSType getMacOSType() const; | |||
@@ -960,11 +971,6 @@ public: | |||
void addToDock() const; | |||
#endif | |||
#if JUCE_WINDOWS | |||
/** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ | |||
bool createLink (const String& description, const File& linkFileToCreate) const; | |||
#endif | |||
private: | |||
//============================================================================== | |||
String fullPath; | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_core", | |||
"name": "JUCE core classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "ISC Permissive", | |||
@@ -136,6 +136,8 @@ public: | |||
return v; | |||
} | |||
Range<ValueType> getRange() const noexcept { return Range<ValueType> (start, end); } | |||
/** The start of the non-normalised range. */ | |||
ValueType start; | |||
@@ -48,24 +48,54 @@ public: | |||
static uint64 swap (uint64 value) noexcept; | |||
//============================================================================== | |||
/** Swaps the byte order of a 16-bit int if the CPU is big-endian */ | |||
/** Swaps the byte order of a 16-bit unsigned int if the CPU is big-endian */ | |||
static uint16 swapIfBigEndian (uint16 value) noexcept; | |||
/** Swaps the byte order of a 32-bit int if the CPU is big-endian */ | |||
/** Swaps the byte order of a 32-bit unsigned int if the CPU is big-endian */ | |||
static uint32 swapIfBigEndian (uint32 value) noexcept; | |||
/** Swaps the byte order of a 64-bit int if the CPU is big-endian */ | |||
/** Swaps the byte order of a 64-bit unsigned int if the CPU is big-endian */ | |||
static uint64 swapIfBigEndian (uint64 value) noexcept; | |||
/** Swaps the byte order of a 16-bit int if the CPU is little-endian */ | |||
/** Swaps the byte order of a 16-bit signed int if the CPU is big-endian */ | |||
static int16 swapIfBigEndian (int16 value) noexcept; | |||
/** Swaps the byte order of a 32-bit signed int if the CPU is big-endian */ | |||
static int32 swapIfBigEndian (int32 value) noexcept; | |||
/** Swaps the byte order of a 64-bit signed int if the CPU is big-endian */ | |||
static int64 swapIfBigEndian (int64 value) noexcept; | |||
/** Swaps the byte order of a 32-bit float if the CPU is big-endian */ | |||
static float swapIfBigEndian (float value) noexcept; | |||
/** Swaps the byte order of a 64-bit float if the CPU is big-endian */ | |||
static double swapIfBigEndian (double value) noexcept; | |||
/** Swaps the byte order of a 16-bit unsigned int if the CPU is little-endian */ | |||
static uint16 swapIfLittleEndian (uint16 value) noexcept; | |||
/** Swaps the byte order of a 32-bit int if the CPU is little-endian */ | |||
/** Swaps the byte order of a 32-bit unsigned int if the CPU is little-endian */ | |||
static uint32 swapIfLittleEndian (uint32 value) noexcept; | |||
/** Swaps the byte order of a 64-bit int if the CPU is little-endian */ | |||
/** Swaps the byte order of a 64-bit unsigned int if the CPU is little-endian */ | |||
static uint64 swapIfLittleEndian (uint64 value) noexcept; | |||
/** Swaps the byte order of a 16-bit signed int if the CPU is little-endian */ | |||
static int16 swapIfLittleEndian (int16 value) noexcept; | |||
/** Swaps the byte order of a 32-bit signed int if the CPU is little-endian */ | |||
static int32 swapIfLittleEndian (int32 value) noexcept; | |||
/** Swaps the byte order of a 64-bit signed int if the CPU is little-endian */ | |||
static int64 swapIfLittleEndian (int64 value) noexcept; | |||
/** Swaps the byte order of a 32-bit float if the CPU is little-endian */ | |||
static float swapIfLittleEndian (float value) noexcept; | |||
/** Swaps the byte order of a 64-bit float if the CPU is little-endian */ | |||
static double swapIfLittleEndian (double value) noexcept; | |||
//============================================================================== | |||
/** Turns 4 bytes into a little-endian integer. */ | |||
static uint32 littleEndianInt (const void* bytes) noexcept; | |||
@@ -161,9 +191,21 @@ inline uint64 ByteOrder::swap (uint64 value) noexcept | |||
inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return v; } | |||
inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return v; } | |||
inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return v; } | |||
inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return v; } | |||
inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return v; } | |||
inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return v; } | |||
inline float ByteOrder::swapIfBigEndian (const float v) noexcept { return v; } | |||
inline double ByteOrder::swapIfBigEndian (const double v) noexcept { return v; } | |||
inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return swap (v); } | |||
inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return swap (v); } | |||
inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return swap (v); } | |||
inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return static_cast<int16> (swap (static_cast<uint16> (v))); } | |||
inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return static_cast<int32> (swap (static_cast<uint32> (v))); } | |||
inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return static_cast<int64> (swap (static_cast<uint64> (v))); } | |||
inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } | |||
inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } | |||
inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return *static_cast<const uint32*> (bytes); } | |||
inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return *static_cast<const uint64*> (bytes); } | |||
inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return *static_cast<const uint16*> (bytes); } | |||
@@ -175,9 +217,21 @@ inline uint64 ByteOrder::swap (uint64 value) noexcept | |||
inline uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return swap (v); } | |||
inline uint32 ByteOrder::swapIfBigEndian (const uint32 v) noexcept { return swap (v); } | |||
inline uint64 ByteOrder::swapIfBigEndian (const uint64 v) noexcept { return swap (v); } | |||
inline int16 ByteOrder::swapIfBigEndian (const int16 v) noexcept { return static_cast<int16> (swap (static_cast<uint16> (v))); } | |||
inline int32 ByteOrder::swapIfBigEndian (const int32 v) noexcept { return static_cast<int16> (swap (static_cast<uint16> (v))); } | |||
inline int64 ByteOrder::swapIfBigEndian (const int64 v) noexcept { return static_cast<int16> (swap (static_cast<uint16> (v))); } | |||
inline float ByteOrder::swapIfBigEndian (const float v) noexcept { union { uint32 asUInt; float asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } | |||
inline double ByteOrder::swapIfBigEndian (const double v) noexcept { union { uint64 asUInt; double asFloat; } n; n.asFloat = v; n.asUInt = ByteOrder::swap (n.asUInt); return n.asFloat; } | |||
inline uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return v; } | |||
inline uint32 ByteOrder::swapIfLittleEndian (const uint32 v) noexcept { return v; } | |||
inline uint64 ByteOrder::swapIfLittleEndian (const uint64 v) noexcept { return v; } | |||
inline int16 ByteOrder::swapIfLittleEndian (const int16 v) noexcept { return v; } | |||
inline int32 ByteOrder::swapIfLittleEndian (const int32 v) noexcept { return v; } | |||
inline int64 ByteOrder::swapIfLittleEndian (const int64 v) noexcept { return v; } | |||
inline float ByteOrder::swapIfLittleEndian (const float v) noexcept { return v; } | |||
inline double ByteOrder::swapIfLittleEndian (const double v) noexcept { return v; } | |||
inline uint32 ByteOrder::littleEndianInt (const void* const bytes) noexcept { return swap (*static_cast<const uint32*> (bytes)); } | |||
inline uint64 ByteOrder::littleEndianInt64 (const void* const bytes) noexcept { return swap (*static_cast<const uint64*> (bytes)); } | |||
inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return swap (*static_cast<const uint16*> (bytes)); } | |||
@@ -0,0 +1,835 @@ | |||
//============================================================================== | |||
public class BluetoothManager extends ScanCallback | |||
{ | |||
BluetoothManager() | |||
{ | |||
ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder(); | |||
scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID)); | |||
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder(); | |||
scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES) | |||
.setScanMode (ScanSettings.SCAN_MODE_LOW_POWER) | |||
.setScanMode (ScanSettings.MATCH_MODE_STICKY); | |||
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | |||
if (bluetoothAdapter == null) | |||
{ | |||
Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter"); | |||
return; | |||
} | |||
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); | |||
if (bluetoothLeScanner == null) | |||
{ | |||
Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner"); | |||
return; | |||
} | |||
bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()), | |||
scanSettingsBuilder.build(), | |||
this); | |||
} | |||
public String[] getMidiBluetoothAddresses() | |||
{ | |||
return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]); | |||
} | |||
public String getHumanReadableStringForBluetoothAddress (String address) | |||
{ | |||
BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address); | |||
return btDevice.getName(); | |||
} | |||
public boolean isBluetoothDevicePaired (String address) | |||
{ | |||
return getAndroidMidiDeviceManager().isBluetoothDevicePaired (address); | |||
} | |||
public boolean pairBluetoothMidiDevice(String address) | |||
{ | |||
BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address); | |||
if (btDevice == null) | |||
{ | |||
Log.d ("JUCE", "failed to create buletooth device from address"); | |||
return false; | |||
} | |||
MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE); | |||
PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm); | |||
if (midiDevice != null) | |||
{ | |||
getAndroidMidiDeviceManager().addDeviceToList (midiDevice); | |||
return true; | |||
} | |||
return false; | |||
} | |||
public void unpairBluetoothMidiDevice (String address) | |||
{ | |||
getAndroidMidiDeviceManager().unpairBluetoothDevice (address); | |||
} | |||
public void onScanFailed (int errorCode) | |||
{ | |||
} | |||
public void onScanResult (int callbackType, ScanResult result) | |||
{ | |||
if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES | |||
|| callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH) | |||
{ | |||
BluetoothDevice device = result.getDevice(); | |||
if (device != null) | |||
bluetoothMidiDevices.add (device.getAddress()); | |||
} | |||
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) | |||
{ | |||
Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST"); | |||
BluetoothDevice device = result.getDevice(); | |||
if (device != null) | |||
{ | |||
bluetoothMidiDevices.remove (device.getAddress()); | |||
unpairBluetoothMidiDevice (device.getAddress()); | |||
} | |||
} | |||
} | |||
public void onBatchScanResults (List<ScanResult> results) | |||
{ | |||
for (ScanResult result : results) | |||
onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result); | |||
} | |||
private BluetoothLeScanner scanner; | |||
private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"; | |||
private HashSet<String> bluetoothMidiDevices = new HashSet<String>(); | |||
} | |||
public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort | |||
{ | |||
private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); | |||
public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort) | |||
{ | |||
parent = device; | |||
juceHost = host; | |||
port = midiPort; | |||
} | |||
@Override | |||
public boolean isInputPort() | |||
{ | |||
return true; | |||
} | |||
@Override | |||
public void start() | |||
{ | |||
port.connect (this); | |||
} | |||
@Override | |||
public void stop() | |||
{ | |||
port.disconnect (this); | |||
} | |||
@Override | |||
public void close() | |||
{ | |||
stop(); | |||
try | |||
{ | |||
port.close(); | |||
} | |||
catch (IOException e) | |||
{ | |||
Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString()); | |||
} | |||
if (parent != null) | |||
{ | |||
parent.removePort (port.getPortNumber(), true); | |||
parent = null; | |||
} | |||
} | |||
public void onSend (byte[] msg, int offset, int count, long timestamp) | |||
{ | |||
if (count > 0) | |||
handleReceive (juceHost, msg, offset, count, timestamp); | |||
} | |||
@Override | |||
public MidiPortID getPortId() | |||
{ | |||
return new MidiPortID (port.getPortNumber(), true); | |||
} | |||
@Override | |||
public void sendMidi (byte[] msg, int offset, int count) | |||
{ | |||
} | |||
private PhysicalMidiDevice parent = null; | |||
private long juceHost = 0; | |||
private MidiOutputPort port; | |||
} | |||
public static class JuceMidiOutputPort implements JuceMidiPort | |||
{ | |||
public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort) | |||
{ | |||
parent = device; | |||
port = midiPort; | |||
} | |||
@Override | |||
public boolean isInputPort() | |||
{ | |||
return false; | |||
} | |||
@Override | |||
public void start() | |||
{ | |||
} | |||
@Override | |||
public void stop() | |||
{ | |||
} | |||
@Override | |||
public void sendMidi (byte[] msg, int offset, int count) | |||
{ | |||
try | |||
{ | |||
port.send(msg, offset, count); | |||
} | |||
catch (IOException e) | |||
{ | |||
Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString()); | |||
} | |||
} | |||
@Override | |||
public void close() | |||
{ | |||
try | |||
{ | |||
port.close(); | |||
} | |||
catch (IOException e) | |||
{ | |||
Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString()); | |||
} | |||
if (parent != null) | |||
{ | |||
parent.removePort (port.getPortNumber(), false); | |||
parent = null; | |||
} | |||
} | |||
@Override | |||
public MidiPortID getPortId() | |||
{ | |||
return new MidiPortID (port.getPortNumber(), false); | |||
} | |||
private PhysicalMidiDevice parent = null; | |||
private MidiInputPort port; | |||
} | |||
public static class PhysicalMidiDevice | |||
{ | |||
private static class MidiDeviceThread extends Thread | |||
{ | |||
public Handler handler = null; | |||
public Object sync = null; | |||
public MidiDeviceThread (Object syncrhonization) | |||
{ | |||
sync = syncrhonization; | |||
} | |||
public void run() | |||
{ | |||
Looper.prepare(); | |||
synchronized (sync) | |||
{ | |||
handler = new Handler(); | |||
sync.notifyAll(); | |||
} | |||
Looper.loop(); | |||
} | |||
} | |||
private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener | |||
{ | |||
public Object sync = null; | |||
public boolean isWaiting = true; | |||
public android.media.midi.MidiDevice theDevice = null; | |||
public MidiDeviceOpenCallback (Object waiter) | |||
{ | |||
sync = waiter; | |||
} | |||
public void onDeviceOpened (MidiDevice device) | |||
{ | |||
synchronized (sync) | |||
{ | |||
theDevice = device; | |||
isWaiting = false; | |||
sync.notifyAll(); | |||
} | |||
} | |||
} | |||
public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm) | |||
{ | |||
Object waitForCreation = new Object(); | |||
MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); | |||
thread.start(); | |||
synchronized (waitForCreation) | |||
{ | |||
while (thread.handler == null) | |||
{ | |||
try | |||
{ | |||
waitForCreation.wait(); | |||
} | |||
catch (InterruptedException e) | |||
{ | |||
Log.d ("JUCE", "Wait was interrupted but we don't care"); | |||
} | |||
} | |||
} | |||
Object waitForDevice = new Object(); | |||
MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); | |||
synchronized (waitForDevice) | |||
{ | |||
mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler); | |||
while (openCallback.isWaiting) | |||
{ | |||
try | |||
{ | |||
waitForDevice.wait(); | |||
} | |||
catch (InterruptedException e) | |||
{ | |||
Log.d ("JUCE", "Wait was interrupted but we don't care"); | |||
} | |||
} | |||
} | |||
if (openCallback.theDevice == null) | |||
{ | |||
Log.d ("JUCE", "openBluetoothDevice failed"); | |||
return null; | |||
} | |||
PhysicalMidiDevice device = new PhysicalMidiDevice(); | |||
device.handle = openCallback.theDevice; | |||
device.info = device.handle.getInfo(); | |||
device.bluetoothAddress = bluetoothDevice.getAddress(); | |||
device.midiManager = mm; | |||
return device; | |||
} | |||
public void unpair() | |||
{ | |||
if (! bluetoothAddress.equals ("") && handle != null) | |||
{ | |||
JuceMidiPort ports[] = new JuceMidiPort[0]; | |||
ports = juceOpenedPorts.values().toArray(ports); | |||
for (int i = 0; i < ports.length; ++i) | |||
ports[i].close(); | |||
juceOpenedPorts.clear(); | |||
try | |||
{ | |||
handle.close(); | |||
} | |||
catch (IOException e) | |||
{ | |||
Log.d ("JUCE", "handle.close(): IOException = " + e.toString()); | |||
} | |||
handle = null; | |||
} | |||
} | |||
public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm) | |||
{ | |||
PhysicalMidiDevice device = new PhysicalMidiDevice(); | |||
device.info = info; | |||
device.midiManager = mm; | |||
return device; | |||
} | |||
public PhysicalMidiDevice() | |||
{ | |||
bluetoothAddress = ""; | |||
juceOpenedPorts = new Hashtable<MidiPortID, JuceMidiPort>(); | |||
handle = null; | |||
} | |||
public MidiDeviceInfo.PortInfo[] getPorts() | |||
{ | |||
return info.getPorts(); | |||
} | |||
public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName) | |||
{ | |||
String portName = port.getName(); | |||
if (portName.equals ("")) | |||
portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ") | |||
+ Integer.toString (portIndexToUseInName); | |||
return getHumanReadableDeviceName() + " " + portName; | |||
} | |||
public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName) | |||
{ | |||
MidiDeviceInfo.PortInfo[] ports = info.getPorts(); | |||
for (MidiDeviceInfo.PortInfo port : ports) | |||
{ | |||
if (port.getType() == portType) | |||
{ | |||
if (port.getPortNumber() == androidPortID) | |||
return getHumanReadableNameForPort (port, portIndexToUseInName); | |||
} | |||
} | |||
return "Unknown"; | |||
} | |||
public String getHumanReadableDeviceName() | |||
{ | |||
Bundle bundle = info.getProperties(); | |||
return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device"); | |||
} | |||
public void checkIfDeviceCanBeClosed() | |||
{ | |||
if (juceOpenedPorts.size() == 0) | |||
{ | |||
// never close bluetooth LE devices, otherwise they unpair and we have | |||
// no idea how many ports they have. | |||
// Only remove bluetooth devices when we specifically unpair | |||
if (bluetoothAddress.equals ("")) | |||
{ | |||
try | |||
{ | |||
handle.close(); | |||
handle = null; | |||
} | |||
catch (IOException e) | |||
{ | |||
Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString()); | |||
} | |||
} | |||
} | |||
} | |||
public void removePort (int portIdx, boolean isInput) | |||
{ | |||
MidiPortID portID = new MidiPortID (portIdx, isInput); | |||
JuceMidiPort port = juceOpenedPorts.get (portID); | |||
if (port != null) | |||
{ | |||
juceOpenedPorts.remove (portID); | |||
checkIfDeviceCanBeClosed(); | |||
return; | |||
} | |||
// tried to remove a port that was never added | |||
assert false; | |||
} | |||
public JuceMidiPort openPort (int portIdx, boolean isInput, long host) | |||
{ | |||
open(); | |||
if (handle == null) | |||
{ | |||
Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open"); | |||
return null; | |||
} | |||
// make sure that the port is not already open | |||
if (findPortForIdx (portIdx, isInput) != null) | |||
{ | |||
Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!"); | |||
return null; | |||
} | |||
JuceMidiPort retval = null; | |||
if (isInput) | |||
{ | |||
MidiOutputPort androidPort = handle.openOutputPort (portIdx); | |||
if (androidPort == null) | |||
{ | |||
Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = " | |||
+ Integer.toString (portIdx) + ") failed!"); | |||
return null; | |||
} | |||
retval = new JuceMidiInputPort (this, host, androidPort); | |||
} | |||
else | |||
{ | |||
MidiInputPort androidPort = handle.openInputPort (portIdx); | |||
if (androidPort == null) | |||
{ | |||
Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = " | |||
+ Integer.toString (portIdx) + ") failed!"); | |||
return null; | |||
} | |||
retval = new JuceMidiOutputPort (this, androidPort); | |||
} | |||
juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval); | |||
return retval; | |||
} | |||
private JuceMidiPort findPortForIdx (int idx, boolean isInput) | |||
{ | |||
return juceOpenedPorts.get (new MidiPortID (idx, isInput)); | |||
} | |||
// opens the device | |||
private synchronized void open() | |||
{ | |||
if (handle != null) | |||
return; | |||
Object waitForCreation = new Object(); | |||
MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); | |||
thread.start(); | |||
synchronized(waitForCreation) | |||
{ | |||
while (thread.handler == null) | |||
{ | |||
try | |||
{ | |||
waitForCreation.wait(); | |||
} | |||
catch (InterruptedException e) | |||
{ | |||
Log.d ("JUCE", "wait was interrupted but we don't care"); | |||
} | |||
} | |||
} | |||
Object waitForDevice = new Object(); | |||
MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); | |||
synchronized (waitForDevice) | |||
{ | |||
midiManager.openDevice (info, openCallback, thread.handler); | |||
while (openCallback.isWaiting) | |||
{ | |||
try | |||
{ | |||
waitForDevice.wait(); | |||
} | |||
catch (InterruptedException e) | |||
{ | |||
Log.d ("JUCE", "wait was interrupted but we don't care"); | |||
} | |||
} | |||
} | |||
handle = openCallback.theDevice; | |||
} | |||
private MidiDeviceInfo info; | |||
private Hashtable<MidiPortID, JuceMidiPort> juceOpenedPorts; | |||
public MidiDevice handle; | |||
public String bluetoothAddress; | |||
private MidiManager midiManager; | |||
} | |||
//============================================================================== | |||
public class MidiDeviceManager extends MidiManager.DeviceCallback | |||
{ | |||
public class MidiPortPath | |||
{ | |||
public PhysicalMidiDevice midiDevice; | |||
public int androidMidiPortID; | |||
public int portIndexToUseInName; | |||
} | |||
public class JuceDeviceList | |||
{ | |||
public ArrayList<MidiPortPath> inPorts = new ArrayList<MidiPortPath>(); | |||
public ArrayList<MidiPortPath> outPorts = new ArrayList<MidiPortPath>(); | |||
} | |||
// We need to keep a thread local copy of the devices | |||
// which we returned the last time | |||
// getJuceAndroidMidiIn/OutputDevices() was called | |||
private final ThreadLocal<JuceDeviceList> lastDevicesReturned = | |||
new ThreadLocal<JuceDeviceList>() | |||
{ | |||
@Override protected JuceDeviceList initialValue() | |||
{ | |||
return new JuceDeviceList(); | |||
} | |||
}; | |||
public MidiDeviceManager() | |||
{ | |||
physicalMidiDevices = new ArrayList<PhysicalMidiDevice>(); | |||
manager = (MidiManager) getSystemService (MIDI_SERVICE); | |||
if (manager == null) | |||
{ | |||
Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service"); | |||
return; | |||
} | |||
manager.registerDeviceCallback (this, null); | |||
MidiDeviceInfo[] foundDevices = manager.getDevices(); | |||
for (MidiDeviceInfo info : foundDevices) | |||
physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager)); | |||
} | |||
// specifically add a device to the list | |||
public void addDeviceToList (PhysicalMidiDevice device) | |||
{ | |||
physicalMidiDevices.add (device); | |||
} | |||
public void unpairBluetoothDevice (String address) | |||
{ | |||
for (int i = 0; i < physicalMidiDevices.size(); ++i) | |||
{ | |||
PhysicalMidiDevice device = physicalMidiDevices.get(i); | |||
if (device.bluetoothAddress.equals (address)) | |||
{ | |||
physicalMidiDevices.remove (i); | |||
device.unpair(); | |||
return; | |||
} | |||
} | |||
} | |||
public boolean isBluetoothDevicePaired (String address) | |||
{ | |||
for (int i = 0; i < physicalMidiDevices.size(); ++i) | |||
{ | |||
PhysicalMidiDevice device = physicalMidiDevices.get(i); | |||
if (device.bluetoothAddress.equals (address)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
public String[] getJuceAndroidMidiInputDevices() | |||
{ | |||
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||
} | |||
public String[] getJuceAndroidMidiOutputDevices() | |||
{ | |||
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||
} | |||
private String[] getJuceAndroidMidiDevices (int portType) | |||
{ | |||
ArrayList<MidiPortPath> listOfReturnedDevices = new ArrayList<MidiPortPath>(); | |||
List<String> deviceNames = new ArrayList<String>(); | |||
for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices) | |||
{ | |||
int portIdx = 0; | |||
MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts(); | |||
for (MidiDeviceInfo.PortInfo port : ports) | |||
{ | |||
if (port.getType() == portType) | |||
{ | |||
MidiPortPath path = new MidiPortPath(); | |||
path.midiDevice = physicalMidiDevice; | |||
path.androidMidiPortID = port.getPortNumber(); | |||
path.portIndexToUseInName = ++portIdx; | |||
listOfReturnedDevices.add (path); | |||
deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port, | |||
path.portIndexToUseInName)); | |||
} | |||
} | |||
} | |||
String[] deviceNamesArray = new String[deviceNames.size()]; | |||
if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT) | |||
{ | |||
lastDevicesReturned.get().inPorts.clear(); | |||
lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices); | |||
} | |||
else | |||
{ | |||
lastDevicesReturned.get().outPorts.clear(); | |||
lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices); | |||
} | |||
return deviceNames.toArray(deviceNamesArray); | |||
} | |||
public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) | |||
{ | |||
ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().inPorts; | |||
if (index >= lastDevices.size() || index < 0) | |||
return null; | |||
MidiPortPath path = lastDevices.get (index); | |||
return path.midiDevice.openPort (path.androidMidiPortID, true, host); | |||
} | |||
public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) | |||
{ | |||
ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().outPorts; | |||
if (index >= lastDevices.size() || index < 0) | |||
return null; | |||
MidiPortPath path = lastDevices.get (index); | |||
return path.midiDevice.openPort (path.androidMidiPortID, false, 0); | |||
} | |||
public String getInputPortNameForJuceIndex (int index) | |||
{ | |||
ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().inPorts; | |||
if (index >= lastDevices.size() || index < 0) | |||
return ""; | |||
MidiPortPath path = lastDevices.get (index); | |||
return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT, | |||
path.androidMidiPortID, | |||
path.portIndexToUseInName); | |||
} | |||
public String getOutputPortNameForJuceIndex (int index) | |||
{ | |||
ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().outPorts; | |||
if (index >= lastDevices.size() || index < 0) | |||
return ""; | |||
MidiPortPath path = lastDevices.get (index); | |||
return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, | |||
path.androidMidiPortID, | |||
path.portIndexToUseInName); | |||
} | |||
public void onDeviceAdded (MidiDeviceInfo info) | |||
{ | |||
PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager); | |||
// Do not add bluetooth devices as they are already added by the native bluetooth dialog | |||
if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH) | |||
physicalMidiDevices.add (device); | |||
} | |||
public void onDeviceRemoved (MidiDeviceInfo info) | |||
{ | |||
for (int i = 0; i < physicalMidiDevices.size(); ++i) | |||
{ | |||
if (physicalMidiDevices.get(i).info.getId() == info.getId()) | |||
{ | |||
physicalMidiDevices.remove (i); | |||
return; | |||
} | |||
} | |||
// Don't assert here as this may be called again after a bluetooth device is unpaired | |||
} | |||
public void onDeviceStatusChanged (MidiDeviceStatus status) | |||
{ | |||
} | |||
private ArrayList<PhysicalMidiDevice> physicalMidiDevices; | |||
private MidiManager manager; | |||
} | |||
public MidiDeviceManager getAndroidMidiDeviceManager() | |||
{ | |||
if (getSystemService (MIDI_SERVICE) == null) | |||
return null; | |||
synchronized (JuceAppActivity.class) | |||
{ | |||
if (midiDeviceManager == null) | |||
midiDeviceManager = new MidiDeviceManager(); | |||
} | |||
return midiDeviceManager; | |||
} | |||
public BluetoothManager getAndroidBluetoothManager() | |||
{ | |||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); | |||
if (adapter == null) | |||
return null; | |||
if (adapter.getBluetoothLeScanner() == null) | |||
return null; | |||
synchronized (JuceAppActivity.class) | |||
{ | |||
if (bluetoothManager == null) | |||
bluetoothManager = new BluetoothManager(); | |||
} | |||
return bluetoothManager; | |||
} |
@@ -0,0 +1,81 @@ | |||
//============================================================================== | |||
public class BluetoothManager | |||
{ | |||
BluetoothManager() | |||
{ | |||
} | |||
public String[] getMidiBluetoothAddresses() | |||
{ | |||
String[] bluetoothAddresses = new String[0]; | |||
return bluetoothAddresses; | |||
} | |||
public String getHumanReadableStringForBluetoothAddress (String address) | |||
{ | |||
return address; | |||
} | |||
public boolean isBluetoothDevicePaired (String address) | |||
{ | |||
return false; | |||
} | |||
public boolean pairBluetoothMidiDevice(String address) | |||
{ | |||
return false; | |||
} | |||
public void unpairBluetoothMidiDevice (String address) | |||
{ | |||
} | |||
} | |||
//============================================================================== | |||
public class MidiDeviceManager | |||
{ | |||
public MidiDeviceManager() | |||
{ | |||
} | |||
public String[] getJuceAndroidMidiInputDevices() | |||
{ | |||
return new String[0]; | |||
} | |||
public String[] getJuceAndroidMidiOutputDevices() | |||
{ | |||
return new String[0]; | |||
} | |||
public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) | |||
{ | |||
return null; | |||
} | |||
public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) | |||
{ | |||
return null; | |||
} | |||
public String getInputPortNameForJuceIndex (int index) | |||
{ | |||
return ""; | |||
} | |||
public String getOutputPortNameForJuceIndex (int index) | |||
{ | |||
return ""; | |||
} | |||
} | |||
public MidiDeviceManager getAndroidMidiDeviceManager() | |||
{ | |||
return null; | |||
} | |||
public BluetoothManager getAndroidBluetoothManager() | |||
{ | |||
return null; | |||
} |
@@ -30,28 +30,41 @@ import android.content.DialogInterface; | |||
import android.content.Context; | |||
import android.content.Intent; | |||
import android.content.res.Configuration; | |||
import android.content.pm.PackageManager; | |||
import android.net.Uri; | |||
import android.os.Bundle; | |||
import android.os.Looper; | |||
import android.os.Handler; | |||
import android.os.Build; | |||
import android.os.Process; | |||
import android.os.ParcelUuid; | |||
import android.view.*; | |||
import android.view.inputmethod.BaseInputConnection; | |||
import android.view.inputmethod.EditorInfo; | |||
import android.view.inputmethod.InputConnection; | |||
import android.view.inputmethod.InputMethodManager; | |||
import android.graphics.*; | |||
import android.opengl.*; | |||
import android.text.ClipboardManager; | |||
import android.text.InputType; | |||
import android.util.DisplayMetrics; | |||
import android.util.Log; | |||
import java.lang.Runnable; | |||
import java.util.List; | |||
import java.util.Arrays; | |||
import java.util.ArrayList; | |||
import java.util.HashSet; | |||
import java.util.Hashtable; | |||
import java.util.TimerTask; | |||
import java.io.*; | |||
import java.net.URL; | |||
import java.net.HttpURLConnection; | |||
import javax.microedition.khronos.egl.EGLConfig; | |||
import javax.microedition.khronos.opengles.GL10; | |||
import android.media.AudioManager; | |||
import android.media.MediaScannerConnection; | |||
import android.media.MediaScannerConnection.MediaScannerConnectionClient; | |||
$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer! | |||
//============================================================================== | |||
public class JuceAppActivity extends Activity | |||
{ | |||
@@ -61,6 +74,58 @@ public class JuceAppActivity extends Activity | |||
System.loadLibrary ("juce_jni"); | |||
} | |||
//============================================================================== | |||
public static class MidiPortID extends Object | |||
{ | |||
public MidiPortID (int index, boolean direction) | |||
{ | |||
androidIndex = index; | |||
isInput = direction; | |||
} | |||
public int androidIndex; | |||
public boolean isInput; | |||
@Override | |||
public int hashCode() | |||
{ | |||
Integer i = new Integer (androidIndex); | |||
return i.hashCode() * (isInput ? -1 : 1); | |||
} | |||
@Override | |||
public boolean equals (Object obj) | |||
{ | |||
if (obj == null) | |||
return false; | |||
if (getClass() != obj.getClass()) | |||
return false; | |||
MidiPortID other = (MidiPortID) obj; | |||
return (androidIndex == other.androidIndex && isInput == other.isInput); | |||
} | |||
} | |||
public interface JuceMidiPort | |||
{ | |||
boolean isInputPort(); | |||
// start, stop does nothing on an output port | |||
void start(); | |||
void stop(); | |||
void close(); | |||
MidiPortID getPortId(); | |||
// send will do nothing on an input port | |||
void sendMidi (byte[] msg, int offset, int count); | |||
} | |||
//============================================================================== | |||
$$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the introjucer! | |||
//============================================================================== | |||
@Override | |||
public void onCreate (Bundle savedInstanceState) | |||
{ | |||
@@ -85,9 +150,6 @@ public class JuceAppActivity extends Activity | |||
@Override | |||
protected void onPause() | |||
{ | |||
if (viewHolder != null) | |||
viewHolder.onPause(); | |||
suspendApp(); | |||
super.onPause(); | |||
} | |||
@@ -96,10 +158,6 @@ public class JuceAppActivity extends Activity | |||
protected void onResume() | |||
{ | |||
super.onResume(); | |||
if (viewHolder != null) | |||
viewHolder.onResume(); | |||
resumeApp(); | |||
} | |||
@@ -142,7 +200,10 @@ public class JuceAppActivity extends Activity | |||
//============================================================================== | |||
private ViewHolder viewHolder; | |||
private MidiDeviceManager midiDeviceManager = null; | |||
private BluetoothManager bluetoothManager = null; | |||
private boolean isScreenSaverEnabled; | |||
private java.util.Timer keepAliveTimer; | |||
public final ComponentPeerView createNewView (boolean opaque, long host) | |||
{ | |||
@@ -159,7 +220,7 @@ public class JuceAppActivity extends Activity | |||
group.removeView (view); | |||
} | |||
public final void deleteOpenGLView (OpenGLView view) | |||
public final void deleteNativeSurfaceView (NativeSurfaceView view) | |||
{ | |||
ViewGroup group = (ViewGroup) (view.getParent()); | |||
@@ -187,28 +248,6 @@ public class JuceAppActivity extends Activity | |||
} | |||
} | |||
public final void onPause() | |||
{ | |||
for (int i = getChildCount(); --i >= 0;) | |||
{ | |||
View v = getChildAt (i); | |||
if (v instanceof ComponentPeerView) | |||
((ComponentPeerView) v).onPause(); | |||
} | |||
} | |||
public final void onResume() | |||
{ | |||
for (int i = getChildCount(); --i >= 0;) | |||
{ | |||
View v = getChildAt (i); | |||
if (v instanceof ComponentPeerView) | |||
((ComponentPeerView) v).onResume(); | |||
} | |||
} | |||
private final int getDPI() | |||
{ | |||
DisplayMetrics metrics = new DisplayMetrics(); | |||
@@ -230,14 +269,46 @@ public class JuceAppActivity extends Activity | |||
if (isScreenSaverEnabled != enabled) | |||
{ | |||
isScreenSaverEnabled = enabled; | |||
if (keepAliveTimer != null) | |||
{ | |||
keepAliveTimer.cancel(); | |||
keepAliveTimer = null; | |||
} | |||
if (enabled) | |||
{ | |||
getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |||
} | |||
else | |||
{ | |||
getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |||
// If no user input is received after about 3 seconds, the OS will lower the | |||
// task's priority, so this timer forces it to be kept active. | |||
keepAliveTimer = new java.util.Timer(); | |||
keepAliveTimer.scheduleAtFixedRate (new TimerTask() | |||
{ | |||
@Override | |||
public void run() | |||
{ | |||
android.app.Instrumentation instrumentation = new android.app.Instrumentation(); | |||
try | |||
{ | |||
instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN); | |||
} | |||
catch (Exception e) | |||
{ | |||
} | |||
} | |||
}, 2000, 2000); | |||
} | |||
} | |||
} | |||
public final boolean getScreenSaver () | |||
public final boolean getScreenSaver() | |||
{ | |||
return isScreenSaverEnabled; | |||
} | |||
@@ -546,70 +617,83 @@ public class JuceAppActivity extends Activity | |||
{ | |||
return true; //xxx needs to check overlapping views | |||
} | |||
} | |||
public final void onPause() | |||
{ | |||
for (int i = getChildCount(); --i >= 0;) | |||
{ | |||
View v = getChildAt (i); | |||
//============================================================================== | |||
public static class NativeSurfaceView extends SurfaceView | |||
implements SurfaceHolder.Callback | |||
{ | |||
private long nativeContext = 0; | |||
if (v instanceof OpenGLView) | |||
((OpenGLView) v).onPause(); | |||
} | |||
NativeSurfaceView (Context context, long nativeContextPtr) | |||
{ | |||
super (context); | |||
nativeContext = nativeContextPtr; | |||
} | |||
public final void onResume() | |||
public Surface getNativeSurface() | |||
{ | |||
for (int i = getChildCount(); --i >= 0;) | |||
{ | |||
View v = getChildAt (i); | |||
Surface retval = null; | |||
if (v instanceof OpenGLView) | |||
((OpenGLView) v).onResume(); | |||
} | |||
SurfaceHolder holder = getHolder(); | |||
if (holder != null) | |||
retval = holder.getSurface(); | |||
return retval; | |||
} | |||
public OpenGLView createGLView() | |||
//============================================================================== | |||
@Override | |||
public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) | |||
{ | |||
OpenGLView glView = new OpenGLView (getContext()); | |||
addView (glView); | |||
return glView; | |||
surfaceChangedNative (nativeContext, holder, format, width, height); | |||
} | |||
} | |||
//============================================================================== | |||
public final class OpenGLView extends GLSurfaceView | |||
implements GLSurfaceView.Renderer | |||
{ | |||
OpenGLView (Context context) | |||
@Override | |||
public void surfaceCreated (SurfaceHolder holder) | |||
{ | |||
super (context); | |||
setEGLContextClientVersion (2); | |||
setRenderer (this); | |||
setRenderMode (RENDERMODE_WHEN_DIRTY); | |||
surfaceCreatedNative (nativeContext, holder); | |||
} | |||
@Override | |||
public void surfaceDestroyed (SurfaceHolder holder) | |||
{ | |||
surfaceDestroyedNative (nativeContext, holder); | |||
} | |||
@Override | |||
public void onSurfaceCreated (GL10 unused, EGLConfig config) | |||
protected void dispatchDraw (Canvas canvas) | |||
{ | |||
contextCreated(); | |||
super.dispatchDraw (canvas); | |||
dispatchDrawNative (nativeContext, canvas); | |||
} | |||
//============================================================================== | |||
@Override | |||
public void onSurfaceChanged (GL10 unused, int width, int height) | |||
protected void onAttachedToWindow () | |||
{ | |||
contextChangedSize(); | |||
super.onAttachedToWindow(); | |||
getHolder().addCallback (this); | |||
} | |||
@Override | |||
public void onDrawFrame (GL10 unused) | |||
protected void onDetachedFromWindow () | |||
{ | |||
render(); | |||
super.onDetachedFromWindow(); | |||
getHolder().removeCallback (this); | |||
} | |||
private native void contextCreated(); | |||
private native void contextChangedSize(); | |||
private native void render(); | |||
//============================================================================== | |||
private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas); | |||
private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder); | |||
private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); | |||
private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, | |||
int format, int width, int height); | |||
} | |||
public NativeSurfaceView createNativeSurfaceView(long nativeSurfacePtr) | |||
{ | |||
return new NativeSurfaceView (this, nativeSurfacePtr); | |||
} | |||
//============================================================================== | |||
@@ -944,4 +1028,70 @@ public class JuceAppActivity extends Activity | |||
return null; | |||
} | |||
public final int getAndroidSDKVersion() | |||
{ | |||
return android.os.Build.VERSION.SDK_INT; | |||
} | |||
public final String audioManagerGetProperty (String property) | |||
{ | |||
Object obj = getSystemService (AUDIO_SERVICE); | |||
if (obj == null) | |||
return null; | |||
java.lang.reflect.Method method; | |||
try { | |||
method = obj.getClass().getMethod ("getProperty", String.class); | |||
} catch (SecurityException e) { | |||
return null; | |||
} catch (NoSuchMethodException e) { | |||
return null; | |||
} | |||
if (method == null) | |||
return null; | |||
try { | |||
return (String) method.invoke (obj, property); | |||
} catch (java.lang.IllegalArgumentException e) { | |||
} catch (java.lang.IllegalAccessException e) { | |||
} catch (java.lang.reflect.InvocationTargetException e) { | |||
} | |||
return null; | |||
} | |||
public final int setCurrentThreadPriority (int priority) | |||
{ | |||
android.os.Process.setThreadPriority (android.os.Process.myTid(), priority); | |||
return android.os.Process.getThreadPriority (android.os.Process.myTid()); | |||
} | |||
public final boolean hasSystemFeature (String property) | |||
{ | |||
return getPackageManager().hasSystemFeature (property); | |||
} | |||
private static class JuceThread extends Thread | |||
{ | |||
public JuceThread (long host) | |||
{ | |||
_this = host; | |||
} | |||
public void run() | |||
{ | |||
runThread(_this); | |||
} | |||
private native void runThread (long host); | |||
private long _this; | |||
} | |||
public final Thread createNewThread(long host) | |||
{ | |||
return new JuceThread(host); | |||
} | |||
} |
@@ -36,6 +36,10 @@ | |||
//============================================================================== | |||
extern JNIEnv* getEnv() noexcept; | |||
// You should rarely need to use this function. Only if you expect callbacks | |||
// on a java thread which you did not create yourself. | |||
extern void setEnv (JNIEnv* env) noexcept; | |||
//============================================================================== | |||
class GlobalRef | |||
{ | |||
@@ -236,6 +240,8 @@ private: | |||
#define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ | |||
extern "C" __attribute__ ((visibility("default"))) JUCE_ARM_SOFT_FLOAT_ABI returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params | |||
//============================================================================== | |||
class AndroidSystem | |||
{ | |||
@@ -253,142 +259,11 @@ public: | |||
extern AndroidSystem android; | |||
//============================================================================== | |||
class ThreadLocalJNIEnvHolder | |||
{ | |||
public: | |||
ThreadLocalJNIEnvHolder() noexcept | |||
: jvm (nullptr) | |||
{ | |||
zeromem (threads, sizeof (threads)); | |||
zeromem (envs, sizeof (envs)); | |||
} | |||
void initialise (JNIEnv* env) | |||
{ | |||
// NB: the DLL can be left loaded by the JVM, so the same static | |||
// objects can end up being reused by subsequent runs of the app | |||
zeromem (threads, sizeof (threads)); | |||
zeromem (envs, sizeof (envs)); | |||
env->GetJavaVM (&jvm); | |||
addEnv (env); | |||
} | |||
JNIEnv* attach() noexcept | |||
{ | |||
if (android.activity != nullptr) | |||
{ | |||
if (JNIEnv* env = attachToCurrentThread()) | |||
{ | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
return addEnv (env); | |||
} | |||
jassertfalse; | |||
} | |||
return nullptr; | |||
} | |||
void detach() noexcept | |||
{ | |||
if (android.activity != nullptr) | |||
{ | |||
jvm->DetachCurrentThread(); | |||
removeCurrentThreadFromCache(); | |||
} | |||
} | |||
void removeCurrentThreadFromCache() | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
for (int i = 0; i < maxThreads; ++i) | |||
{ | |||
if (threads[i] == thisThread) | |||
{ | |||
threads[i] = 0; | |||
envs[i] = nullptr; | |||
} | |||
} | |||
} | |||
JNIEnv* getOrAttach() noexcept | |||
{ | |||
if (JNIEnv* env = get()) | |||
return env; | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
if (JNIEnv* env = get()) | |||
return env; | |||
if (JNIEnv* env = attachToCurrentThread()) | |||
return addEnv (env); | |||
return nullptr; | |||
} | |||
private: | |||
JavaVM* jvm; | |||
enum { maxThreads = 32 }; | |||
pthread_t threads [maxThreads]; | |||
JNIEnv* envs [maxThreads]; | |||
SpinLock addRemoveLock; | |||
JNIEnv* addEnv (JNIEnv* env) noexcept | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
for (int i = 0; i < maxThreads; ++i) | |||
{ | |||
if (threads[i] == 0) | |||
{ | |||
envs[i] = env; | |||
threads[i] = thisThread; | |||
return env; | |||
} | |||
} | |||
jassertfalse; // too many threads! | |||
return nullptr; | |||
} | |||
JNIEnv* get() const noexcept | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
for (int i = 0; i < maxThreads; ++i) | |||
if (threads[i] == thisThread) | |||
return envs[i]; | |||
return nullptr; | |||
} | |||
JNIEnv* attachToCurrentThread() | |||
{ | |||
JNIEnv* env = nullptr; | |||
jvm->AttachCurrentThread (&env, nullptr); | |||
return env; | |||
} | |||
}; | |||
extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; | |||
struct AndroidThreadScope | |||
{ | |||
AndroidThreadScope() { threadLocalJNIEnvHolder.attach(); } | |||
~AndroidThreadScope() { threadLocalJNIEnvHolder.detach(); } | |||
}; | |||
//============================================================================== | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ | |||
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ | |||
METHOD (deleteOpenGLView, "deleteOpenGLView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;)V") \ | |||
METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ | |||
METHOD (postMessage, "postMessage", "(J)V") \ | |||
METHOD (finish, "finish", "()V") \ | |||
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | |||
@@ -405,7 +280,14 @@ struct AndroidThreadScope | |||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | |||
METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | |||
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ | |||
METHOD (getScreenSaver, "getScreenSaver", "()Z") | |||
METHOD (getScreenSaver, "getScreenSaver", "()Z") \ | |||
METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \ | |||
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | |||
METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | |||
METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | |||
METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \ | |||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | |||
METHOD (createNewThread, "createNewThread", "(J)Ljava/lang/Thread;") \ | |||
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); | |||
#undef JNI_CLASS_MEMBERS | |||
@@ -435,6 +317,19 @@ DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); | |||
DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | |||
#undef JNI_CLASS_MEMBERS | |||
//============================================================================== | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (start, "start", "()V") \ | |||
METHOD (stop, "stop", "()V") \ | |||
METHOD (setName, "setName", "(Ljava/lang/String;)V") \ | |||
METHOD (getName, "getName", "()Ljava/lang/String;") \ | |||
METHOD (getId, "getId", "()J") \ | |||
STATICMETHOD (currentThread, "currentThread", "()Ljava/lang/Thread;") \ | |||
METHOD (setPriority, "setPriority", "(I)V") \ | |||
DECLARE_JNI_CLASS (JuceThread, "java/lang/Thread"); | |||
#undef JNI_CLASS_MEMBERS | |||
//============================================================================== | |||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||
METHOD (constructor, "<init>", "(IIII)V") \ | |||
@@ -98,24 +98,19 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c | |||
} | |||
//============================================================================== | |||
ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; | |||
#if JUCE_DEBUG | |||
static bool systemInitialised = false; | |||
#endif | |||
ThreadLocalValue<JNIEnv*> androidJNIEnv; | |||
JNIEnv* getEnv() noexcept | |||
{ | |||
#if JUCE_DEBUG | |||
if (! systemInitialised) | |||
{ | |||
DBG ("*** Call to getEnv() when system not initialised"); | |||
jassertfalse; | |||
std::exit (EXIT_FAILURE); | |||
} | |||
#endif | |||
JNIEnv* env = androidJNIEnv.get(); | |||
jassert (env != nullptr); | |||
return threadLocalJNIEnvHolder.getOrAttach(); | |||
return env; | |||
} | |||
void setEnv (JNIEnv* env) noexcept | |||
{ | |||
androidJNIEnv.get() = env; | |||
} | |||
extern "C" jint JNI_OnLoad (JavaVM*, void*) | |||
@@ -134,11 +129,6 @@ void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring | |||
dpi = 160; | |||
JNIClassBase::initialiseAllClasses (env); | |||
threadLocalJNIEnvHolder.initialise (env); | |||
#if JUCE_DEBUG | |||
systemInitialised = true; | |||
#endif | |||
activity = GlobalRef (act); | |||
appFile = juceString (env, file); | |||
appDataDir = juceString (env, dataDir); | |||
@@ -148,10 +138,6 @@ void AndroidSystem::shutdown (JNIEnv* env) | |||
{ | |||
activity.clear(); | |||
#if JUCE_DEBUG | |||
systemInitialised = false; | |||
#endif | |||
JNIClassBase::releaseAllClasses (env); | |||
} | |||
@@ -253,7 +239,7 @@ String SystemStats::getLogonName() | |||
if (struct passwd* const pw = getpwuid (getuid())) | |||
return CharPointer_UTF8 (pw->pw_name); | |||
return String::empty; | |||
return String(); | |||
} | |||
String SystemStats::getFullUserName() | |||
@@ -267,7 +253,7 @@ String SystemStats::getComputerName() | |||
if (gethostname (name, sizeof (name) - 1) == 0) | |||
return name; | |||
return String::empty; | |||
return String(); | |||
} | |||
@@ -74,3 +74,235 @@ JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() | |||
JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} | |||
JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} | |||
struct AndroidThreadData | |||
{ | |||
AndroidThreadData (Thread* thread) noexcept | |||
: owner (thread), tId (0) | |||
{ | |||
} | |||
Thread* owner; | |||
Thread::ThreadID tId; | |||
WaitableEvent eventSet, eventGet; | |||
}; | |||
void JUCE_API juce_threadEntryPoint (void*); | |||
extern "C" void* threadEntryProc (void*); | |||
extern "C" void* threadEntryProc (void* userData) | |||
{ | |||
ScopedPointer<AndroidThreadData> priv (reinterpret_cast<AndroidThreadData*> (userData)); | |||
priv->tId = (Thread::ThreadID) pthread_self(); | |||
priv->eventSet.signal(); | |||
priv->eventGet.wait (-1); | |||
juce_threadEntryPoint (priv->owner); | |||
return nullptr; | |||
} | |||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceThread), runThread, | |||
void, (JNIEnv* env, jobject device, jlong host)) | |||
{ | |||
// Java may create a Midi thread which JUCE doesn't know about and this callback may be | |||
// received on this thread. Java will have already created a JNI Env for this new thread, | |||
// which we need to tell Juce about | |||
setEnv (env); | |||
if (Thread* thread = reinterpret_cast<Thread*> (host)) | |||
threadEntryProc (thread); | |||
} | |||
void Thread::launchThread() | |||
{ | |||
threadHandle = 0; | |||
ScopedPointer<AndroidThreadData> threadPrivateData = new AndroidThreadData (this); | |||
jobject juceNewThread = android.activity.callObjectMethod (JuceAppActivity.createNewThread, (jlong) threadPrivateData.get()); | |||
if (jobject juceThread = getEnv()->NewGlobalRef (juceNewThread)) | |||
{ | |||
AndroidThreadData* priv = threadPrivateData.release(); | |||
threadHandle = (void*) juceThread; | |||
getEnv()->CallVoidMethod (juceThread, JuceThread.start); | |||
priv->eventSet.wait (-1); | |||
threadId = priv->tId; | |||
priv->eventGet.signal(); | |||
} | |||
} | |||
void Thread::closeThreadHandle() | |||
{ | |||
if (threadHandle != 0) | |||
{ | |||
jobject juceThread = reinterpret_cast<jobject> (threadHandle); | |||
getEnv()->DeleteGlobalRef (juceThread); | |||
threadHandle = 0; | |||
} | |||
threadId = 0; | |||
} | |||
void Thread::killThread() | |||
{ | |||
if (threadHandle != 0) | |||
{ | |||
jobject juceThread = reinterpret_cast<jobject> (threadHandle); | |||
getEnv()->CallVoidMethod (juceThread, JuceThread.stop); | |||
} | |||
} | |||
void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name) | |||
{ | |||
LocalRef<jobject> juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); | |||
if (jobject t = juceThread.get()) | |||
getEnv()->CallVoidMethod (t, JuceThread.setName, javaString (name).get()); | |||
} | |||
bool Thread::setThreadPriority (void* handle, int priority) | |||
{ | |||
if (handle == nullptr) | |||
{ | |||
LocalRef<jobject> juceThread (getEnv()->CallStaticObjectMethod (JuceThread, JuceThread.currentThread)); | |||
if (jobject t = juceThread.get()) | |||
return setThreadPriority (t, priority); | |||
return false; | |||
} | |||
jobject juceThread = reinterpret_cast<jobject> (handle); | |||
const int minPriority = 1; | |||
const int maxPriority = 10; | |||
jint javaPriority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | |||
getEnv()->CallVoidMethod (juceThread, JuceThread.setPriority, javaPriority); | |||
return true; | |||
} | |||
//============================================================================== | |||
struct HighResolutionTimer::Pimpl | |||
{ | |||
struct HighResolutionThread : public Thread | |||
{ | |||
HighResolutionThread (HighResolutionTimer::Pimpl& parent) | |||
: Thread ("High Resolution Timer"), pimpl (parent) | |||
{ | |||
startThread(); | |||
} | |||
void run() override | |||
{ | |||
pimpl.timerThread(); | |||
} | |||
private: | |||
HighResolutionTimer::Pimpl& pimpl; | |||
}; | |||
//============================================================================== | |||
Pimpl (HighResolutionTimer& t) : owner (t) {} | |||
~Pimpl() | |||
{ | |||
stop(); | |||
} | |||
void start (int newPeriod) | |||
{ | |||
if (periodMs != newPeriod) | |||
{ | |||
if (thread.get() == nullptr | |||
|| thread->getThreadId() != Thread::getCurrentThreadId() | |||
|| thread->threadShouldExit()) | |||
{ | |||
stop(); | |||
periodMs = newPeriod; | |||
thread = new HighResolutionThread (*this); | |||
} | |||
else | |||
{ | |||
periodMs = newPeriod; | |||
} | |||
} | |||
} | |||
void stop() | |||
{ | |||
if (thread.get() != nullptr) | |||
{ | |||
thread->signalThreadShouldExit(); | |||
if (thread->getThreadId() != Thread::getCurrentThreadId()) | |||
{ | |||
thread->waitForThreadToExit (-1); | |||
thread = nullptr; | |||
} | |||
} | |||
} | |||
HighResolutionTimer& owner; | |||
int volatile periodMs; | |||
private: | |||
ScopedPointer<Thread> thread; | |||
void timerThread() | |||
{ | |||
jassert (thread.get() != nullptr); | |||
int lastPeriod = periodMs; | |||
Clock clock (lastPeriod); | |||
while (! thread->threadShouldExit()) | |||
{ | |||
clock.wait(); | |||
owner.hiResTimerCallback(); | |||
if (lastPeriod != periodMs) | |||
{ | |||
lastPeriod = periodMs; | |||
clock = Clock (lastPeriod); | |||
} | |||
} | |||
periodMs = 0; | |||
} | |||
struct Clock | |||
{ | |||
Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | |||
{ | |||
} | |||
void wait() noexcept | |||
{ | |||
struct timespec t; | |||
t.tv_sec = (time_t) (delta / 1000000000); | |||
t.tv_nsec = (long) (delta % 1000000000); | |||
nanosleep (&t, nullptr); | |||
} | |||
uint64 delta; | |||
}; | |||
static bool setThreadToRealtime (pthread_t thread, uint64 periodMs) | |||
{ | |||
ignoreUnused (periodMs); | |||
struct sched_param param; | |||
param.sched_priority = sched_get_priority_max (SCHED_RR); | |||
return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0; | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||
}; |
@@ -65,7 +65,7 @@ static String getLinkedFile (const String& file) | |||
return String::fromUTF8 (buffer, jmax (0, numBytes)); | |||
}; | |||
bool File::isLink() const | |||
bool File::isSymbolicLink() const | |||
{ | |||
return getLinkedFile (getFullPathName()).isNotEmpty(); | |||
} | |||
@@ -158,7 +158,7 @@ File File::getSpecialLocation (const SpecialLocationType type) | |||
case hostApplicationPath: | |||
{ | |||
const File f ("/proc/self/exe"); | |||
return f.isLink() ? f.getLinkedTarget() : juce_getExecutableFile(); | |||
return f.isSymbolicLink() ? f.getLinkedTarget() : juce_getExecutableFile(); | |||
} | |||
default: | |||
@@ -284,7 +284,7 @@ static NSString* getFileLink (const String& path) | |||
#endif | |||
} | |||
bool File::isLink() const | |||
bool File::isSymbolicLink() const | |||
{ | |||
return getFileLink (fullPath) != nil; | |||
} | |||
@@ -400,7 +400,12 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
{ | |||
NSURL* filenameAsURL = [NSURL URLWithString: juceStringToNS (fileName)]; | |||
NSString* fileNameAsNS (juceStringToNS (fileName)); | |||
NSURL* filenameAsURL ([NSURL URLWithString: fileNameAsNS]); | |||
if (filenameAsURL == nil) | |||
filenameAsURL = [NSURL fileURLWithPath: fileNameAsNS]; | |||
#if JUCE_IOS | |||
(void) parameters; | |||
@@ -867,6 +867,7 @@ void InterProcessLock::exit() | |||
} | |||
//============================================================================== | |||
#if ! JUCE_ANDROID | |||
void JUCE_API juce_threadEntryPoint (void*); | |||
extern "C" void* threadEntryProc (void*); | |||
@@ -874,10 +875,6 @@ extern "C" void* threadEntryProc (void* userData) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
{ | |||
#if JUCE_ANDROID | |||
const AndroidThreadScope androidEnv; | |||
#endif | |||
juce_threadEntryPoint (userData); | |||
} | |||
@@ -951,6 +948,7 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||
param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | |||
return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | |||
} | |||
#endif | |||
Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | |||
{ | |||
@@ -1180,6 +1178,7 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||
} | |||
//============================================================================== | |||
#if ! JUCE_ANDROID | |||
struct HighResolutionTimer::Pimpl | |||
{ | |||
Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) | |||
@@ -1286,20 +1285,6 @@ private: | |||
uint64_t time, delta; | |||
#elif JUCE_ANDROID | |||
Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | |||
{ | |||
} | |||
void wait() noexcept | |||
{ | |||
struct timespec t; | |||
t.tv_sec = (time_t) (delta / 1000000000); | |||
t.tv_nsec = (long) (delta % 1000000000); | |||
nanosleep (&t, nullptr); | |||
} | |||
uint64 delta; | |||
#else | |||
Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | |||
{ | |||
@@ -1348,3 +1333,5 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE (Pimpl) | |||
}; | |||
#endif |
@@ -631,13 +631,45 @@ String File::getVersion() const | |||
} | |||
//============================================================================== | |||
bool File::isLink() const | |||
bool File::isSymbolicLink() const | |||
{ | |||
return (GetFileAttributes (fullPath.toWideCharPointer()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0; | |||
} | |||
bool File::isShortcut() const | |||
{ | |||
return hasFileExtension (".lnk"); | |||
} | |||
File File::getLinkedTarget() const | |||
{ | |||
{ | |||
HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), | |||
GENERIC_READ, FILE_SHARE_READ, nullptr, | |||
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); | |||
if (h != INVALID_HANDLE_VALUE) | |||
{ | |||
DWORD requiredSize = ::GetFinalPathNameByHandleW (h, nullptr, 0, FILE_NAME_NORMALIZED); | |||
if (requiredSize > 0) | |||
{ | |||
HeapBlock<WCHAR> buffer (requiredSize + 2); | |||
buffer.clear (requiredSize + 2); | |||
requiredSize = ::GetFinalPathNameByHandleW (h, buffer, requiredSize, FILE_NAME_NORMALIZED); | |||
if (requiredSize > 0) | |||
{ | |||
CloseHandle (h); | |||
return File (String (buffer)); | |||
} | |||
} | |||
CloseHandle (h); | |||
} | |||
} | |||
File result (*this); | |||
String p (getFullPathName()); | |||
@@ -664,7 +696,7 @@ File File::getLinkedTarget() const | |||
return result; | |||
} | |||
bool File::createLink (const String& description, const File& linkFileToCreate) const | |||
bool File::createShortcut (const String& description, const File& linkFileToCreate) const | |||
{ | |||
linkFileToCreate.deleteFile(); | |||
@@ -42,6 +42,7 @@ | |||
#define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | |||
#define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 | |||
#define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | |||
#define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 | |||
#if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) | |||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | |||
@@ -93,6 +94,10 @@ | |||
#define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | |||
#endif | |||
#if __has_feature (cxx_static_assert) | |||
#define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 | |||
#endif | |||
#ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL | |||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | |||
#endif | |||
@@ -115,6 +120,7 @@ | |||
#if _MSC_VER >= 1600 | |||
#define JUCE_COMPILER_SUPPORTS_NULLPTR 1 | |||
#define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | |||
#define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 | |||
#endif | |||
#if _MSC_VER >= 1700 | |||
@@ -149,20 +149,44 @@ | |||
#endif | |||
//============================================================================== | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
template <bool b> struct JuceStaticAssert; | |||
template <> struct JuceStaticAssert<true> { static void dummy() {} }; | |||
} | |||
#if ! DOXYGEN | |||
#define JUCE_JOIN_MACRO_HELPER(a, b) a ## b | |||
#define JUCE_STRINGIFY_MACRO_HELPER(a) #a | |||
#endif | |||
/** A compile-time assertion macro. | |||
If the expression parameter is false, the macro will cause a compile error. (The actual error | |||
message that the compiler generates may be completely bizarre and seem to have no relation to | |||
the place where you put the static_assert though!) | |||
/** A good old-fashioned C macro concatenation helper. | |||
This combines two items (which may themselves be macros) into a single string, | |||
avoiding the pitfalls of the ## macro operator. | |||
*/ | |||
#define static_jassert(expression) juce::JuceStaticAssert<expression>::dummy(); | |||
#define JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) | |||
/** A handy C macro for stringifying any symbol, rather than just a macro parameter. */ | |||
#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) | |||
//============================================================================== | |||
#ifdef JUCE_COMPILER_SUPPORTS_STATIC_ASSERT | |||
/** A compile-time assertion macro. | |||
If the expression parameter is false, the macro will cause a compile error. (The actual error | |||
message that the compiler generates may be completely bizarre and seem to have no relation to | |||
the place where you put the static_assert though!) | |||
*/ | |||
#define static_jassert(expression) static_assert (expression); | |||
#else | |||
#ifndef DOXYGEN | |||
namespace juce | |||
{ | |||
template <bool b> struct JuceStaticAssert; | |||
template <> struct JuceStaticAssert<true> { static void dummy() {} }; | |||
} | |||
#endif | |||
/** A compile-time assertion macro. | |||
If the expression parameter is false, the macro will cause a compile error. (The actual error | |||
message that the compiler generates may be completely bizarre and seem to have no relation to | |||
the place where you put the static_assert though!) | |||
*/ | |||
#define static_jassert(expression) juce::JuceStaticAssert<expression>::dummy(); | |||
#endif | |||
/** This is a shorthand macro for declaring stubs for a class's copy constructor and operator=. | |||
@@ -207,24 +231,6 @@ namespace juce | |||
static void* operator new (size_t) JUCE_DELETED_FUNCTION; \ | |||
static void operator delete (void*) JUCE_DELETED_FUNCTION; | |||
//============================================================================== | |||
#if ! DOXYGEN | |||
#define JUCE_JOIN_MACRO_HELPER(a, b) a ## b | |||
#define JUCE_STRINGIFY_MACRO_HELPER(a) #a | |||
#endif | |||
/** A good old-fashioned C macro concatenation helper. | |||
This combines two items (which may themselves be macros) into a single string, | |||
avoiding the pitfalls of the ## macro operator. | |||
*/ | |||
#define JUCE_JOIN_MACRO(item1, item2) JUCE_JOIN_MACRO_HELPER (item1, item2) | |||
/** A handy C macro for stringifying any symbol, rather than just a macro parameter. | |||
*/ | |||
#define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) | |||
//============================================================================== | |||
#if JUCE_MSVC && ! defined (DOXYGEN) | |||
#define JUCE_WARNING_HELPER(file, line, mess) message(file "(" JUCE_STRINGIFY (line) ") : Warning: " #mess) | |||
@@ -34,9 +34,9 @@ | |||
See also SystemStats::getJUCEVersion() for a string version. | |||
*/ | |||
#define JUCE_MAJOR_VERSION 3 | |||
#define JUCE_MINOR_VERSION 2 | |||
#define JUCE_BUILDNUMBER 0 | |||
#define JUCE_MAJOR_VERSION 4 | |||
#define JUCE_MINOR_VERSION 0 | |||
#define JUCE_BUILDNUMBER 1 | |||
/** Current Juce version number. | |||
@@ -108,6 +108,16 @@ bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept | |||
return iswalnum ((wint_t) character) != 0; | |||
} | |||
bool CharacterFunctions::isPrintable (const char character) noexcept | |||
{ | |||
return (character >= ' ' && character <= '~'); | |||
} | |||
bool CharacterFunctions::isPrintable (const juce_wchar character) noexcept | |||
{ | |||
return iswprint ((wint_t) character) != 0; | |||
} | |||
int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept | |||
{ | |||
unsigned int d = (unsigned int) digit - '0'; | |||
@@ -110,6 +110,16 @@ public: | |||
/** Checks whether a character is alphabetic or numeric. */ | |||
static bool isLetterOrDigit (juce_wchar character) noexcept; | |||
/** Checks whether a character is a printable character, i.e. alphabetic, numeric, | |||
a punctuation character or a space. | |||
*/ | |||
static bool isPrintable (char character) noexcept; | |||
/** Checks whether a character is a printable character, i.e. alphabetic, numeric, | |||
a punctuation character or a space. | |||
*/ | |||
static bool isPrintable (juce_wchar character) noexcept; | |||
/** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ | |||
static int getHexDigitValue (juce_wchar digit) noexcept; | |||
@@ -430,7 +430,22 @@ namespace NumberToStringConverters | |||
return t; | |||
} | |||
static char* numberToString (char* t, unsigned int v) noexcept | |||
static char* numberToString (char* t, const unsigned int v) noexcept | |||
{ | |||
return printDigits (t, v); | |||
} | |||
static char* numberToString (char* t, const long n) noexcept | |||
{ | |||
if (n >= 0) | |||
return printDigits (t, static_cast<unsigned long> (n)); | |||
t = printDigits (t, static_cast<unsigned long> (-(n + 1)) + 1); | |||
*--t = '-'; | |||
return t; | |||
} | |||
static char* numberToString (char* t, const unsigned long v) noexcept | |||
{ | |||
return printDigits (t, v); | |||
} | |||
@@ -517,6 +532,8 @@ String::String (const short number) : text (NumberToStringConverters::c | |||
String::String (const unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} | |||
String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} | |||
String::String (const uint64 number) : text (NumberToStringConverters::createFromInteger (number)) {} | |||
String::String (const long number) : text (NumberToStringConverters::createFromInteger (number)) {} | |||
String::String (const unsigned long number) : text (NumberToStringConverters::createFromInteger (number)) {} | |||
String::String (const float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} | |||
String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} | |||
@@ -795,34 +812,29 @@ String& String::operator+= (const juce_wchar ch) | |||
} | |||
#endif | |||
String& String::operator+= (const int number) | |||
namespace StringHelpers | |||
{ | |||
char buffer [16]; | |||
char* end = buffer + numElementsInArray (buffer); | |||
char* start = NumberToStringConverters::numberToString (end, number); | |||
#if (JUCE_STRING_UTF_TYPE == 8) | |||
appendCharPointer (CharPointerType (start), CharPointerType (end)); | |||
#else | |||
appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); | |||
#endif | |||
return *this; | |||
} | |||
template <typename T> | |||
inline String& operationAddAssign (String& str, const T number) | |||
{ | |||
char buffer [(sizeof(T) * 8) / 2]; | |||
char* end = buffer + numElementsInArray (buffer); | |||
char* start = NumberToStringConverters::numberToString (end, number); | |||
String& String::operator+= (int64 number) | |||
{ | |||
char buffer [32]; | |||
char* end = buffer + numElementsInArray (buffer); | |||
char* start = NumberToStringConverters::numberToString (end, number); | |||
#if (JUCE_STRING_UTF_TYPE == 8) | |||
str.appendCharPointer (String::CharPointerType (start), String::CharPointerType (end)); | |||
#else | |||
str.appendCharPointer (String::CharPointer_ASCII (start), String::CharPointer_ASCII (end)); | |||
#endif | |||
#if (JUCE_STRING_UTF_TYPE == 8) | |||
appendCharPointer (CharPointerType (start), CharPointerType (end)); | |||
#else | |||
appendCharPointer (CharPointer_ASCII (start), CharPointer_ASCII (end)); | |||
#endif | |||
return *this; | |||
return str; | |||
} | |||
} | |||
String& String::operator+= (const int number) { return StringHelpers::operationAddAssign<int> (*this, number); } | |||
String& String::operator+= (const int64 number) { return StringHelpers::operationAddAssign<int64> (*this, number); } | |||
String& String::operator+= (const uint64 number) { return StringHelpers::operationAddAssign<uint64> (*this, number); } | |||
//============================================================================== | |||
JUCE_API String JUCE_CALLTYPE operator+ (const char* const s1, const String& s2) { String s (s1); return s += s2; } | |||
JUCE_API String JUCE_CALLTYPE operator+ (const wchar_t* const s1, const String& s2) { String s (s1); return s += s2; } | |||
@@ -853,11 +865,13 @@ JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, StringRef s2) | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int number) { return s1 += number; } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const short number) { return s1 += (int) number; } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += (int) number; } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned short number) { return s1 += (uint64) number; } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const long number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const unsigned long number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const int64 number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const float number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const double number) { return s1 += String (number); } | |||
JUCE_API String& JUCE_CALLTYPE operator<< (String& s1, const uint64 number) { return s1 += String (number); } | |||
JUCE_API OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const String& text) | |||
{ | |||
@@ -2174,7 +2188,8 @@ StringRef::StringRef (const String& string) noexcept : text (string.getCharPoin | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
#define STRINGIFY2(X) #X | |||
#define STRINGIFY(X) STRINGIFY2(X) | |||
class StringTests : public UnitTest | |||
{ | |||
public: | |||
@@ -2325,6 +2340,116 @@ public: | |||
s2 << StringRef ("def"); | |||
expect (s2 == "1234567890xyz123123def"); | |||
// int16 | |||
{ | |||
String numStr (std::numeric_limits<int16>::max()); | |||
expect (numStr == "32767"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<int16>::min()); | |||
expect (numStr == "-32768"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int16>::max(); | |||
expect (numStr == "32767"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int16>::min(); | |||
expect (numStr == "-32768"); | |||
} | |||
// uint16 | |||
{ | |||
String numStr (std::numeric_limits<uint16>::max()); | |||
expect (numStr == "65535"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<uint16>::min()); | |||
expect (numStr == "0"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<uint16>::max(); | |||
expect (numStr == "65535"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<uint16>::min(); | |||
expect (numStr == "0"); | |||
} | |||
// int32 | |||
{ | |||
String numStr (std::numeric_limits<int32>::max()); | |||
expect (numStr == "2147483647"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<int32>::min()); | |||
expect (numStr == "-2147483648"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int32>::max(); | |||
expect (numStr == "2147483647"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int32>::min(); | |||
expect (numStr == "-2147483648"); | |||
} | |||
// uint32 | |||
{ | |||
String numStr (std::numeric_limits<uint32>::max()); | |||
expect (numStr == "4294967295"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<uint32>::min()); | |||
expect (numStr == "0"); | |||
} | |||
// int64 | |||
{ | |||
String numStr (std::numeric_limits<int64>::max()); | |||
expect (numStr == "9223372036854775807"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<int64>::min()); | |||
expect (numStr == "-9223372036854775808"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int64>::max(); | |||
expect (numStr == "9223372036854775807"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<int64>::min(); | |||
expect (numStr == "-9223372036854775808"); | |||
} | |||
// uint64 | |||
{ | |||
String numStr (std::numeric_limits<uint64>::max()); | |||
expect (numStr == "18446744073709551615"); | |||
} | |||
{ | |||
String numStr (std::numeric_limits<uint64>::min()); | |||
expect (numStr == "0"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<uint64>::max(); | |||
expect (numStr == "18446744073709551615"); | |||
} | |||
{ | |||
String numStr; | |||
numStr << std::numeric_limits<uint64>::min(); | |||
expect (numStr == "0"); | |||
} | |||
// size_t | |||
{ | |||
String numStr (std::numeric_limits<size_t>::min()); | |||
expect (numStr == "0"); | |||
} | |||
beginTest ("Numeric conversions"); | |||
expect (String::empty.getIntValue() == 0); | |||
expect (String::empty.getDoubleValue() == 0.0); | |||
@@ -210,7 +210,11 @@ public: | |||
/** Appends a decimal number at the end of this string. */ | |||
String& operator+= (int numberToAppend); | |||
/** Appends a decimal number at the end of this string. */ | |||
String& operator+= (long numberToAppend); | |||
/** Appends a decimal number at the end of this string. */ | |||
String& operator+= (int64 numberToAppend); | |||
/** Appends a decimal number at the end of this string. */ | |||
String& operator+= (uint64 numberToAppend); | |||
/** Appends a character at the end of this string. */ | |||
String& operator+= (char characterToAppend); | |||
/** Appends a character at the end of this string. */ | |||
@@ -937,6 +941,16 @@ public: | |||
*/ | |||
explicit String (uint64 largeIntegerValue); | |||
/** Creates a string containing this signed long integer as a decimal number. | |||
@see getIntValue, getFloatValue, getDoubleValue, toHexString | |||
*/ | |||
explicit String (long decimalInteger); | |||
/** Creates a string containing this unsigned long integer as a decimal number. | |||
@see getIntValue, getFloatValue, getDoubleValue, toHexString | |||
*/ | |||
explicit String (unsigned long decimalInteger); | |||
/** Creates a string representing this floating-point number. | |||
@param floatValue the value to convert to a string | |||
@see getDoubleValue, getIntValue | |||
@@ -173,6 +173,12 @@ void StringArray::addArray (const StringArray& otherArray, int startIndex, int n | |||
strings.add (otherArray.strings.getReference (startIndex++)); | |||
} | |||
void StringArray::mergeArray (const StringArray& otherArray, const bool ignoreCase) | |||
{ | |||
for (int i = 0; i < otherArray.size(); ++i) | |||
addIfNotAlreadyThere (otherArray[i], ignoreCase); | |||
} | |||
void StringArray::set (const int index, const String& newString) | |||
{ | |||
strings.set (index, newString); | |||
@@ -209,6 +209,15 @@ public: | |||
int startIndex = 0, | |||
int numElementsToAdd = -1); | |||
/** Merges the strings from another array into this one. | |||
This will not add a string that already exists. | |||
@param other the array to add | |||
@param ignoreCase ignore case when merging | |||
*/ | |||
void mergeArray (const StringArray& other, | |||
bool ignoreCase = false); | |||
/** Breaks up a string into tokens and adds them to this array. | |||
This will tokenise the given string using whitespace characters as the | |||
@@ -157,6 +157,47 @@ public: | |||
expect (result, failureMessage); | |||
} | |||
//============================================================================== | |||
/** Checks that the result of an expression does not throw an exception. */ | |||
#define expectDoesNotThrow(expr) \ | |||
try \ | |||
{ \ | |||
(expr); \ | |||
expect (true); \ | |||
} \ | |||
catch (...) \ | |||
{ \ | |||
expect (false, "Expected: does not throw an exception, Actual: throws."); \ | |||
} | |||
/** Checks that the result of an expression throws an exception. */ | |||
#define expectThrows(expr) \ | |||
try \ | |||
{ \ | |||
(expr); \ | |||
expect (false, "Expected: throws an exception, Actual: does not throw."); \ | |||
} \ | |||
catch (...) \ | |||
{ \ | |||
expect (true); \ | |||
} | |||
/** Checks that the result of an expression throws an exception of a certain type. */ | |||
#define expectThrowsType(expr, type) \ | |||
try \ | |||
{ \ | |||
(expr); \ | |||
expect (false, "Expected: throws an exception of type " #type ", Actual: does not throw."); \ | |||
} \ | |||
catch (type&) \ | |||
{ \ | |||
expect (true); \ | |||
} \ | |||
catch (...) \ | |||
{ \ | |||
expect (false, "Expected: throws an exception of type " #type ", Actual: throws another type."); \ | |||
} | |||
//============================================================================== | |||
/** Writes a message to the test log. | |||
This can only be called from within your runTest() method. | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_cryptography", | |||
"name": "JUCE cryptography classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for various basic cryptography functions, including RSA, Blowfish, MD5, SHA, etc.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_data_structures", | |||
"name": "JUCE data model helper classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for undo/redo management, and smart data structures.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_events", | |||
"name": "JUCE message and event handling classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for running an application's main event loop and sending/receiving messages, timers, etc.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -44,6 +44,8 @@ bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* cons | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (JNIEnv* env, jobject activity, jlong value)) | |||
{ | |||
setEnv (env); | |||
JUCE_TRY | |||
{ | |||
MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_graphics", | |||
"name": "JUCE graphics classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Classes for 2D vector graphics, image loading/saving, font handling, etc.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -2384,7 +2384,7 @@ void Component::internalMouseEnter (MouseInputSource source, Point<float> relati | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
this, this, time, relativePos, time, 0, false); | |||
mouseEnter (me); | |||
@@ -2403,7 +2403,7 @@ void Component::internalMouseExit (MouseInputSource source, Point<float> relativ | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
this, this, time, relativePos, time, 0, false); | |||
mouseExit (me); | |||
@@ -2416,7 +2416,7 @@ void Component::internalMouseExit (MouseInputSource source, Point<float> relativ | |||
MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); | |||
} | |||
void Component::internalMouseDown (MouseInputSource source, Point<float> relativePos, Time time) | |||
void Component::internalMouseDown (MouseInputSource source, Point<float> relativePos, Time time, float pressure) | |||
{ | |||
Desktop& desktop = Desktop::getInstance(); | |||
BailOutChecker checker (this); | |||
@@ -2435,7 +2435,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||
{ | |||
// allow blocked mouse-events to go to global listeners.. | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
this, this, time, relativePos, time, | |||
pressure, this, this, time, relativePos, time, | |||
source.getNumberOfMultipleClicks(), false); | |||
desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); | |||
@@ -2468,7 +2468,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||
repaint(); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
this, this, time, relativePos, time, | |||
pressure, this, this, time, relativePos, time, | |||
source.getNumberOfMultipleClicks(), false); | |||
mouseDown (me); | |||
@@ -2492,7 +2492,7 @@ void Component::internalMouseUp (MouseInputSource source, Point<float> relativeP | |||
repaint(); | |||
const MouseEvent me (source, relativePos, | |||
oldModifiers, this, this, time, | |||
oldModifiers, MouseInputSource::invalidPressure, this, this, time, | |||
getLocalPoint (nullptr, source.getLastMouseDownPosition()), | |||
source.getLastMouseDownTime(), | |||
source.getNumberOfMultipleClicks(), | |||
@@ -2523,14 +2523,14 @@ void Component::internalMouseUp (MouseInputSource source, Point<float> relativeP | |||
} | |||
} | |||
void Component::internalMouseDrag (MouseInputSource source, Point<float> relativePos, Time time) | |||
void Component::internalMouseDrag (MouseInputSource source, Point<float> relativePos, Time time, float pressure) | |||
{ | |||
if (! isCurrentlyBlockedByAnotherModalComponent()) | |||
{ | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, | |||
source.getCurrentModifiers(), this, this, time, | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
pressure, this, this, time, | |||
getLocalPoint (nullptr, source.getLastMouseDownPosition()), | |||
source.getLastMouseDownTime(), | |||
source.getNumberOfMultipleClicks(), | |||
@@ -2559,7 +2559,7 @@ void Component::internalMouseMove (MouseInputSource source, Point<float> relativ | |||
{ | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
this, this, time, relativePos, time, 0, false); | |||
mouseMove (me); | |||
@@ -2578,7 +2578,7 @@ void Component::internalMouseWheel (MouseInputSource source, Point<float> relati | |||
Desktop& desktop = Desktop::getInstance(); | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
this, this, time, relativePos, time, 0, false); | |||
if (isCurrentlyBlockedByAnotherModalComponent()) | |||
@@ -2605,7 +2605,7 @@ void Component::internalMagnifyGesture (MouseInputSource source, Point<float> re | |||
{ | |||
if (! isCurrentlyBlockedByAnotherModalComponent()) | |||
{ | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
this, this, time, relativePos, time, 0, false); | |||
mouseMagnify (me, amount); | |||
@@ -2300,9 +2300,9 @@ private: | |||
//============================================================================== | |||
void internalMouseEnter (MouseInputSource, Point<float>, Time); | |||
void internalMouseExit (MouseInputSource, Point<float>, Time); | |||
void internalMouseDown (MouseInputSource, Point<float>, Time); | |||
void internalMouseDown (MouseInputSource, Point<float>, Time, float); | |||
void internalMouseUp (MouseInputSource, Point<float>, Time, const ModifierKeys oldModifiers); | |||
void internalMouseDrag (MouseInputSource, Point<float>, Time); | |||
void internalMouseDrag (MouseInputSource, Point<float>, Time, float); | |||
void internalMouseMove (MouseInputSource, Point<float>, Time); | |||
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&); | |||
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float); | |||
@@ -92,7 +92,7 @@ LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept | |||
if (currentLookAndFeel == nullptr) | |||
{ | |||
if (defaultLookAndFeel == nullptr) | |||
defaultLookAndFeel = new LookAndFeel_V2(); | |||
defaultLookAndFeel = new LookAndFeel_V3(); | |||
currentLookAndFeel = defaultLookAndFeel; | |||
} | |||
@@ -246,7 +246,7 @@ void Desktop::sendMouseMove() | |||
const Time now (Time::getCurrentTime()); | |||
const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), | |||
target, target, now, pos, now, 0, false); | |||
MouseInputSource::invalidPressure, target, target, now, pos, now, 0, false); | |||
if (me.mods.isAnyMouseButtonDown()) | |||
mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_gui_basics", | |||
"name": "JUCE GUI core classes", | |||
"version": "3.2.0", | |||
"version": "4.0.1", | |||
"description": "Basic user-interface components and related classes.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -25,6 +25,7 @@ | |||
MouseEvent::MouseEvent (MouseInputSource inputSource, | |||
Point<float> pos, | |||
ModifierKeys modKeys, | |||
float force, | |||
Component* const eventComp, | |||
Component* const originator, | |||
Time time, | |||
@@ -36,6 +37,7 @@ MouseEvent::MouseEvent (MouseInputSource inputSource, | |||
x (roundToInt (pos.x)), | |||
y (roundToInt (pos.y)), | |||
mods (modKeys), | |||
pressure (force), | |||
eventComponent (eventComp), | |||
originalComponent (originator), | |||
eventTime (time), | |||
@@ -57,22 +59,22 @@ MouseEvent MouseEvent::getEventRelativeTo (Component* const otherComponent) cons | |||
jassert (otherComponent != nullptr); | |||
return MouseEvent (source, otherComponent->getLocalPoint (eventComponent, position), | |||
mods, otherComponent, originalComponent, eventTime, | |||
mods, pressure, otherComponent, originalComponent, eventTime, | |||
otherComponent->getLocalPoint (eventComponent, mouseDownPos), | |||
mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); | |||
} | |||
MouseEvent MouseEvent::withNewPosition (Point<float> newPosition) const noexcept | |||
{ | |||
return MouseEvent (source, newPosition, mods, eventComponent, originalComponent, | |||
eventTime, mouseDownPos, mouseDownTime, | |||
return MouseEvent (source, newPosition, mods, pressure, eventComponent, | |||
originalComponent, eventTime, mouseDownPos, mouseDownTime, | |||
numberOfClicks, wasMovedSinceMouseDown != 0); | |||
} | |||
MouseEvent MouseEvent::withNewPosition (Point<int> newPosition) const noexcept | |||
{ | |||
return MouseEvent (source, newPosition.toFloat(), mods, eventComponent, originalComponent, | |||
eventTime, mouseDownPos, mouseDownTime, | |||
return MouseEvent (source, newPosition.toFloat(), mods, pressure, eventComponent, | |||
originalComponent, eventTime, mouseDownPos, mouseDownTime, | |||
numberOfClicks, wasMovedSinceMouseDown != 0); | |||
} | |||
@@ -112,6 +114,8 @@ int MouseEvent::getScreenY() const { return getScre | |||
int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().x; } | |||
int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().y; } | |||
bool MouseEvent::isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } | |||
//============================================================================== | |||
static int doubleClickTimeOutMs = 400; | |||
@@ -44,6 +44,9 @@ public: | |||
@param source the source that's invoking the event | |||
@param position the position of the mouse, relative to the component that is passed-in | |||
@param modifiers the key modifiers at the time of the event | |||
@param pressure the pressure of the touch or stylus, in the range 0 to 1. Devices that | |||
do not support force information may return 0.0, 1.0, or a negative value, | |||
depending on the platform | |||
@param eventComponent the component that the mouse event applies to | |||
@param originator the component that originally received the event | |||
@param eventTime the time the event happened | |||
@@ -59,6 +62,7 @@ public: | |||
MouseEvent (MouseInputSource source, | |||
Point<float> position, | |||
ModifierKeys modifiers, | |||
float pressure, | |||
Component* eventComponent, | |||
Component* originator, | |||
Time eventTime, | |||
@@ -109,6 +113,13 @@ public: | |||
*/ | |||
const ModifierKeys mods; | |||
/** The pressure of the touch or stylus for this event. | |||
The range is 0 (soft) to 1 (hard). | |||
If the input device doesn't provide any pressure data, it may return a negative | |||
value here, or 0.0 or 1.0, depending on the platform. | |||
*/ | |||
float pressure; | |||
/** The component that this event applies to. | |||
This is usually the component that the mouse was over at the time, but for mouse-drag | |||
@@ -224,6 +235,9 @@ public: | |||
*/ | |||
int getLengthOfMousePress() const noexcept; | |||
/** Returns true if the pressure value for this event is meaningful. */ | |||
bool isPressureValid() const noexcept; | |||
//============================================================================== | |||
/** The position of the mouse when the event occurred. | |||
@@ -27,7 +27,7 @@ class MouseInputSourceInternal : private AsyncUpdater | |||
public: | |||
//============================================================================== | |||
MouseInputSourceInternal (const int i, const bool isMouse) | |||
: index (i), isMouseDevice (isMouse), | |||
: index (i), isMouseDevice (isMouse), pressure (0.0f), | |||
isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), | |||
lastPeer (nullptr), currentCursorHandle (nullptr), | |||
mouseEventCounter (0), mouseMovedSignificantlySincePressed (false) | |||
@@ -40,17 +40,17 @@ public: | |||
return buttonState.isAnyMouseButtonDown(); | |||
} | |||
Component* getComponentUnderMouse() const | |||
Component* getComponentUnderMouse() const noexcept | |||
{ | |||
return componentUnderMouse.get(); | |||
} | |||
ModifierKeys getCurrentModifiers() const | |||
ModifierKeys getCurrentModifiers() const noexcept | |||
{ | |||
return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); | |||
} | |||
ComponentPeer* getPeer() | |||
ComponentPeer* getPeer() noexcept | |||
{ | |||
if (! ComponentPeer::isValidPeer (lastPeer)) | |||
lastPeer = nullptr; | |||
@@ -102,6 +102,8 @@ public: | |||
MouseInputSource::setRawMousePosition (ScalingHelpers::scaledScreenPosToUnscaled (p)); | |||
} | |||
bool isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } | |||
//============================================================================== | |||
#if JUCE_DUMP_MOUSE_EVENTS | |||
#define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ | |||
@@ -132,13 +134,13 @@ public: | |||
void sendMouseDown (Component& comp, Point<float> screenPos, Time time) | |||
{ | |||
JUCE_MOUSE_EVENT_DBG ("down") | |||
comp.internalMouseDown (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); | |||
comp.internalMouseDown (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure); | |||
} | |||
void sendMouseDrag (Component& comp, Point<float> screenPos, Time time) | |||
{ | |||
JUCE_MOUSE_EVENT_DBG ("drag") | |||
comp.internalMouseDrag (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time); | |||
comp.internalMouseDrag (MouseInputSource (this), screenPosToLocalPos (comp, screenPos), time, pressure); | |||
} | |||
void sendMouseUp (Component& comp, Point<float> screenPos, Time time, const ModifierKeys oldMods) | |||
@@ -287,15 +289,18 @@ public: | |||
} | |||
//============================================================================== | |||
void handleEvent (ComponentPeer& newPeer, Point<float> positionWithinPeer, Time time, const ModifierKeys newMods) | |||
void handleEvent (ComponentPeer& newPeer, Point<float> positionWithinPeer, Time time, | |||
const ModifierKeys newMods, float newPressure) | |||
{ | |||
lastTime = time; | |||
const bool pressureChanged = (pressure != newPressure); | |||
pressure = newPressure; | |||
++mouseEventCounter; | |||
const Point<float> screenPos (newPeer.localToGlobal (positionWithinPeer)); | |||
if (isDragging() && newMods.isAnyMouseButtonDown()) | |||
{ | |||
setScreenPos (screenPos, time, false); | |||
setScreenPos (screenPos, time, pressureChanged); | |||
} | |||
else | |||
{ | |||
@@ -307,8 +312,9 @@ public: | |||
return; // some modal events have been dispatched, so the current event is now out-of-date | |||
peer = getPeer(); | |||
if (peer != nullptr) | |||
setScreenPos (screenPos, time, false); | |||
setScreenPos (screenPos, time, pressureChanged); | |||
} | |||
} | |||
} | |||
@@ -470,6 +476,7 @@ public: | |||
const bool isMouseDevice; | |||
Point<float> lastScreenPos, unboundedMouseOffset; // NB: these are unscaled coords | |||
ModifierKeys buttonState; | |||
float pressure; | |||
bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; | |||
@@ -542,14 +549,16 @@ MouseInputSource& MouseInputSource::operator= (const MouseInputSource& other) no | |||
return *this; | |||
} | |||
bool MouseInputSource::isMouse() const { return pimpl->isMouseDevice; } | |||
bool MouseInputSource::isTouch() const { return ! isMouse(); } | |||
bool MouseInputSource::canHover() const { return isMouse(); } | |||
bool MouseInputSource::hasMouseWheel() const { return isMouse(); } | |||
int MouseInputSource::getIndex() const { return pimpl->index; } | |||
bool MouseInputSource::isDragging() const { return pimpl->isDragging(); } | |||
Point<float> MouseInputSource::getScreenPosition() const { return pimpl->getScreenPosition(); } | |||
ModifierKeys MouseInputSource::getCurrentModifiers() const { return pimpl->getCurrentModifiers(); } | |||
bool MouseInputSource::isMouse() const noexcept { return pimpl->isMouseDevice; } | |||
bool MouseInputSource::isTouch() const noexcept { return ! isMouse(); } | |||
bool MouseInputSource::canHover() const noexcept { return isMouse(); } | |||
bool MouseInputSource::hasMouseWheel() const noexcept { return isMouse(); } | |||
int MouseInputSource::getIndex() const noexcept { return pimpl->index; } | |||
bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } | |||
Point<float> MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); } | |||
ModifierKeys MouseInputSource::getCurrentModifiers() const noexcept { return pimpl->getCurrentModifiers(); } | |||
float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } | |||
bool MouseInputSource::isPressureValid() const noexcept { return pimpl->isPressureValid(); } | |||
Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } | |||
void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } | |||
int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } | |||
@@ -567,9 +576,9 @@ void MouseInputSource::revealCursor() { pimpl | |||
void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } | |||
void MouseInputSource::setScreenPosition (Point<float> p) { pimpl->setScreenPosition (p); } | |||
void MouseInputSource::handleEvent (ComponentPeer& peer, Point<float> pos, int64 time, ModifierKeys mods) | |||
void MouseInputSource::handleEvent (ComponentPeer& peer, Point<float> pos, int64 time, ModifierKeys mods, float pressure) | |||
{ | |||
pimpl->handleEvent (peer, pos, Time (time), mods.withOnlyMouseButtons()); | |||
pimpl->handleEvent (peer, pos, Time (time), mods.withOnlyMouseButtons(), pressure); | |||
} | |||
void MouseInputSource::handleWheel (ComponentPeer& peer, Point<float> pos, int64 time, const MouseWheelDetails& wheel) | |||
@@ -582,6 +591,8 @@ void MouseInputSource::handleMagnifyGesture (ComponentPeer& peer, Point<float> p | |||
pimpl->handleMagnifyGesture (peer, pos, Time (time), scaleFactor); | |||
} | |||
const float MouseInputSource::invalidPressure = 0.0f; | |||
//============================================================================== | |||
struct MouseInputSource::SourceList : public Timer | |||
{ | |||
@@ -60,18 +60,18 @@ public: | |||
//============================================================================== | |||
/** Returns true if this object represents a normal desk-based mouse device. */ | |||
bool isMouse() const; | |||
bool isMouse() const noexcept; | |||
/** Returns true if this object represents a source of touch events - i.e. a finger or stylus. */ | |||
bool isTouch() const; | |||
bool isTouch() const noexcept; | |||
/** Returns true if this source has an on-screen pointer that can hover over | |||
items without clicking them. | |||
*/ | |||
bool canHover() const; | |||
bool canHover() const noexcept; | |||
/** Returns true if this source may have a scroll wheel. */ | |||
bool hasMouseWheel() const; | |||
bool hasMouseWheel() const noexcept; | |||
/** Returns this source's index in the global list of possible sources. | |||
If the system only has a single mouse, there will only be a single MouseInputSource | |||
@@ -82,18 +82,28 @@ public: | |||
number 0, and then if a second touch happens while the first is still down, it | |||
will have index 1, etc. | |||
*/ | |||
int getIndex() const; | |||
int getIndex() const noexcept; | |||
/** Returns true if this device is currently being pressed. */ | |||
bool isDragging() const; | |||
bool isDragging() const noexcept; | |||
/** Returns the last-known screen position of this source. */ | |||
Point<float> getScreenPosition() const; | |||
Point<float> getScreenPosition() const noexcept; | |||
/** Returns a set of modifiers that indicate which buttons are currently | |||
held down on this device. | |||
*/ | |||
ModifierKeys getCurrentModifiers() const; | |||
ModifierKeys getCurrentModifiers() const noexcept; | |||
/** Returns the device's current touch or pen pressure. | |||
The range is 0 (soft) to 1 (hard). | |||
If the input device doesn't provide any pressure data, it may return a negative | |||
value here, or 0.0 or 1.0, depending on the platform. | |||
*/ | |||
float getCurrentPressure() const noexcept; | |||
/** Returns true if the current pressure value is meaningful. */ | |||
bool isPressureValid() const noexcept; | |||
/** Returns the component that was last known to be under this pointer. */ | |||
Component* getComponentUnderMouse() const; | |||
@@ -164,6 +174,11 @@ public: | |||
/** Attempts to set this mouse pointer's screen position. */ | |||
void setScreenPosition (Point<float> newPosition); | |||
/** A default value for pressure, which is used when a device doesn't support it, or for | |||
mouse-moves, mouse-ups, etc. | |||
*/ | |||
static const float invalidPressure; | |||
private: | |||
//============================================================================== | |||
friend class ComponentPeer; | |||
@@ -174,7 +189,7 @@ private: | |||
struct SourceList; | |||
explicit MouseInputSource (MouseInputSourceInternal*) noexcept; | |||
void handleEvent (ComponentPeer&, Point<float>, int64 time, ModifierKeys); | |||
void handleEvent (ComponentPeer&, Point<float>, int64 time, ModifierKeys, float); | |||
void handleWheel (ComponentPeer&, Point<float>, int64 time, const MouseWheelDetails&); | |||
void handleMagnifyGesture (ComponentPeer&, Point<float>, int64 time, float scaleFactor); | |||
@@ -33,6 +33,8 @@ namespace juce | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, | |||
jstring appFile, jstring appDataDir)) | |||
{ | |||
setEnv (env); | |||
android.initialise (env, activity, appFile, appDataDir); | |||
DBG (SystemStats::getJUCEVersion()); | |||
@@ -56,18 +58,24 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* en | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject activity)) | |||
{ | |||
setEnv (env); | |||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
app->suspended(); | |||
} | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject activity)) | |||
{ | |||
setEnv (env); | |||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
app->resumed(); | |||
} | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity)) | |||
{ | |||
setEnv (env); | |||
JUCEApplicationBase::appWillTerminateByForce(); | |||
android.shutdown (env); | |||
@@ -98,7 +106,6 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); | |||
METHOD (invalidate, "invalidate", "(IIII)V") \ | |||
METHOD (containsPoint, "containsPoint", "(II)Z") \ | |||
METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | |||
METHOD (createGLView, "createGLView", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;") \ | |||
DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | |||
#undef JNI_CLASS_MEMBERS | |||
@@ -332,7 +339,7 @@ public: | |||
lastMousePos = pos; | |||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time); | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure, time); | |||
if (isValidPeer (this)) | |||
handleMouseDragCallback (index, sysPos, time); | |||
@@ -346,8 +353,8 @@ public: | |||
jassert (index < 64); | |||
touchesDown = (touchesDown | (1 << (index & 63))); | |||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons() | |||
.withFlags (ModifierKeys::leftButtonModifier), time); | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier), | |||
MouseInputSource::invalidPressure, time); | |||
} | |||
void handleMouseUpCallback (int index, Point<float> pos, int64 time) | |||
@@ -361,7 +368,7 @@ public: | |||
if (touchesDown == 0) | |||
currentModifiers = currentModifiers.withoutMouseButtons(); | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time); | |||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure, time); | |||
} | |||
void handleKeyDownCallback (int k, int kc) | |||
@@ -582,6 +589,7 @@ int64 AndroidComponentPeer::touchesDown = 0; | |||
#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ | |||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ | |||
{ \ | |||
setEnv (env); \ | |||
if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \ | |||
peer->juceMethodInvocation; \ | |||
} | |||
@@ -601,12 +609,6 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void*) | |||
return new AndroidComponentPeer (*this, styleFlags); | |||
} | |||
jobject createOpenGLView (ComponentPeer* peer) | |||
{ | |||
jobject parentView = static_cast<jobject> (peer->getNativeHandle()); | |||
return getEnv()->CallObjectMethod (parentView, ComponentPeerView.createGLView); | |||
} | |||
//============================================================================== | |||
bool Desktop::canUseSemiTransparentWindows() noexcept | |||
{ | |||
@@ -700,6 +702,8 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy | |||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject activity, | |||
jlong callbackAsLong, jint result)) | |||
{ | |||
setEnv (env); | |||
if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong) | |||
{ | |||
callback->modalStateFinished (result); | |||
@@ -748,6 +752,8 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv | |||
jint screenWidth, jint screenHeight, | |||
jint dpi)) | |||
{ | |||
setEnv (env); | |||
android.screenWidth = screenWidth; | |||
android.screenHeight = screenHeight; | |||
android.dpi = dpi; | |||
@@ -767,7 +767,11 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||
{ | |||
UITouch* touch = [touches objectAtIndex: i]; | |||
#if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||
if ([touch phase] == UITouchPhaseStationary && touch.maximumPossibleForce <= 0) | |||
#else | |||
if ([touch phase] == UITouchPhaseStationary) | |||
#endif | |||
continue; | |||
CGPoint p = [touch locationInView: view]; | |||
@@ -788,7 +792,9 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||
modsToSend = currentModifiers; | |||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | |||
handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), time); | |||
handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), | |||
MouseInputSource::invalidPressure, time); | |||
if (! isValidPeer (this)) // (in case this component was deleted by the event) | |||
return; | |||
} | |||
@@ -810,13 +816,24 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||
modsToSend = currentModifiers = currentModifiers.withoutMouseButtons(); | |||
} | |||
handleMouseEvent (touchIndex, pos, modsToSend, time); | |||
float pressure = MouseInputSource::invalidPressure; | |||
#if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||
if (touch.maximumPossibleForce > 0) | |||
// NB: other devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range: | |||
pressure = jlimit (0.0001f, 0.9999f, (float) (touch.force / touch.maximumPossibleForce)); | |||
#endif | |||
handleMouseEvent (touchIndex, pos, modsToSend, pressure, time); | |||
if (! isValidPeer (this)) // (in case this component was deleted by the event) | |||
return; | |||
if (isUp || isCancel) | |||
{ | |||
handleMouseEvent (touchIndex, Point<float> (-1.0f, -1.0f), modsToSend, time); | |||
handleMouseEvent (touchIndex, Point<float> (-1.0f, -1.0f), | |||
modsToSend, MouseInputSource::invalidPressure, time); | |||
if (! isValidPeer (this)) | |||
return; | |||
} | |||
@@ -24,6 +24,15 @@ | |||
extern bool isIOSAppActive; | |||
struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules | |||
{ | |||
virtual ~AppInactivityCallback() {} | |||
virtual void appBecomingInactive() = 0; | |||
}; | |||
// This is an internal list of callbacks (but currently used between modules) | |||
Array<AppInactivityCallback*> appBecomingInactiveCallbacks; | |||
} // (juce namespace) | |||
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate> | |||
@@ -89,6 +98,9 @@ extern bool isIOSAppActive; | |||
{ | |||
ignoreUnused (application); | |||
isIOSAppActive = false; | |||
for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) | |||
appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive(); | |||
} | |||
@end | |||