@@ -80,7 +80,6 @@ namespace juce | |||||
{ | { | ||||
#include "buffers/juce_AudioDataConverters.cpp" | #include "buffers/juce_AudioDataConverters.cpp" | ||||
#include "buffers/juce_AudioSampleBuffer.cpp" | |||||
#include "buffers/juce_FloatVectorOperations.cpp" | #include "buffers/juce_FloatVectorOperations.cpp" | ||||
#include "effects/juce_IIRFilter.cpp" | #include "effects/juce_IIRFilter.cpp" | ||||
#include "effects/juce_IIRFilterOld.cpp" | #include "effects/juce_IIRFilterOld.cpp" | ||||
@@ -35,8 +35,8 @@ namespace juce | |||||
#undef Factor | #undef Factor | ||||
#include "buffers/juce_AudioDataConverters.h" | #include "buffers/juce_AudioDataConverters.h" | ||||
#include "buffers/juce_AudioSampleBuffer.h" | |||||
#include "buffers/juce_FloatVectorOperations.h" | #include "buffers/juce_FloatVectorOperations.h" | ||||
#include "buffers/juce_AudioSampleBuffer.h" | |||||
#include "effects/juce_Decibels.h" | #include "effects/juce_Decibels.h" | ||||
#include "effects/juce_IIRFilter.h" | #include "effects/juce_IIRFilter.h" | ||||
#include "effects/juce_IIRFilterOld.h" | #include "effects/juce_IIRFilterOld.h" | ||||
@@ -71,6 +71,18 @@ bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const no | |||||
return noteOnTime < other.noteOnTime; | 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() | Synthesiser::Synthesiser() | ||||
: sampleRate (0), | : 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! | // must set the sample rate before using this! | ||||
jassert (sampleRate != 0); | jassert (sampleRate != 0); | ||||
@@ -174,7 +189,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||||
{ | { | ||||
if (! midiIterator.getNextEvent (m, midiEventPos)) | if (! midiIterator.getNextEvent (m, midiEventPos)) | ||||
{ | { | ||||
renderVoices (outputBuffer, startSample, numSamples); | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
return; | return; | ||||
} | } | ||||
@@ -182,7 +197,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||||
if (samplesToNextMidiMessage >= numSamples) | if (samplesToNextMidiMessage >= numSamples) | ||||
{ | { | ||||
renderVoices (outputBuffer, startSample, numSamples); | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
break; | break; | ||||
} | } | ||||
@@ -193,7 +208,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||||
continue; | continue; | ||||
} | } | ||||
renderVoices (outputBuffer, startSample, samplesToNextMidiMessage); | |||||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
startSample += samplesToNextMidiMessage; | startSample += samplesToNextMidiMessage; | ||||
numSamples -= samplesToNextMidiMessage; | numSamples -= samplesToNextMidiMessage; | ||||
@@ -203,7 +218,23 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||||
handleMidiEvent (m); | 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;) | for (int i = voices.size(); --i >= 0;) | ||||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | 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, | 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. | 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 startSample, | ||||
int numSamples) = 0; | int numSamples) = 0; | ||||
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | |||||
int startSample, | |||||
int numSamples); | |||||
/** Changes the voice's reference sample rate. | /** Changes the voice's reference sample rate. | ||||
@@ -255,6 +258,8 @@ private: | |||||
SynthesiserSound::Ptr currentlyPlayingSound; | SynthesiserSound::Ptr currentlyPlayingSound; | ||||
bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | ||||
AudioBuffer<float> tempBuffer; | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | ||||
// Note the new parameters for this method. | // Note the new parameters for this method. | ||||
virtual int stopNote (bool) { return 0; } | 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 | both to the audio output buffer and the midi input buffer, so any midi events | ||||
with timestamps outside the specified region will be ignored. | with timestamps outside the specified region will be ignored. | ||||
*/ | */ | ||||
void renderNextBlock (AudioSampleBuffer& outputAudio, | |||||
inline void renderNextBlock (AudioBuffer<float>& outputAudio, | |||||
const MidiBuffer& inputMidi, | const MidiBuffer& inputMidi, | ||||
int startSample, | 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. | /** 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. | 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 | By default this just calls renderNextBlock() on each voice, but you may need | ||||
to override it to handle custom cases. | 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); | int startSample, int numSamples); | ||||
/** Searches through the voices to find one that's not currently playing, and | /** Searches through the voices to find one that's not currently playing, and | ||||
@@ -592,6 +606,12 @@ protected: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
template <typename floatType> | |||||
void processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
const MidiBuffer& inputMidi, | |||||
int startSample, | |||||
int numSamples); | |||||
//============================================================================== | |||||
double sampleRate; | double sampleRate; | ||||
uint32 lastNoteOnCounter; | uint32 lastNoteOnCounter; | ||||
int minimumSubBlockSize; | int minimumSubBlockSize; | ||||
@@ -93,7 +93,6 @@ AudioDeviceManager::AudioDeviceManager() | |||||
numOutputChansNeeded (2), | numOutputChansNeeded (2), | ||||
listNeedsScanning (true), | listNeedsScanning (true), | ||||
inputLevel (0), | inputLevel (0), | ||||
testSoundPosition (0), | |||||
cpuUsageMs (0), | cpuUsageMs (0), | ||||
timeToCpuScale (0) | timeToCpuScale (0) | ||||
{ | { | ||||
@@ -589,8 +588,6 @@ void AudioDeviceManager::stopDevice() | |||||
{ | { | ||||
if (currentAudioDevice != nullptr) | if (currentAudioDevice != nullptr) | ||||
currentAudioDevice->stop(); | currentAudioDevice->stop(); | ||||
testSound = nullptr; | |||||
} | } | ||||
void AudioDeviceManager::closeAudioDevice() | void AudioDeviceManager::closeAudioDevice() | ||||
@@ -762,20 +759,6 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||||
for (int i = 0; i < numOutputChannels; ++i) | for (int i = 0; i < numOutputChannels; ++i) | ||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | 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) | 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) | void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | ||||
{ | { | ||||
if (enableMeasurement) | if (enableMeasurement) | ||||
@@ -404,6 +404,51 @@ public: | |||||
*/ | */ | ||||
void playTestSound(); | 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. | /** Turns on level-measuring. | ||||
When enabled, the device manager will measure the peak input level | When enabled, the device manager will measure the peak input level | ||||
@@ -452,8 +497,6 @@ private: | |||||
mutable bool listNeedsScanning; | mutable bool listNeedsScanning; | ||||
Atomic<int> inputLevelMeasurementEnabledCount; | Atomic<int> inputLevelMeasurementEnabledCount; | ||||
double inputLevel; | double inputLevel; | ||||
ScopedPointer<AudioSampleBuffer> testSound; | |||||
int testSoundPosition; | |||||
AudioSampleBuffer tempBuffer; | AudioSampleBuffer tempBuffer; | ||||
struct MidiCallbackInfo | struct MidiCallbackInfo | ||||
@@ -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() | int MidiOutput::getDefaultDeviceIndex() | ||||
@@ -36,50 +253,109 @@ int MidiOutput::getDefaultDeviceIndex() | |||||
MidiOutput* MidiOutput::openDevice (int index) | 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; | return nullptr; | ||||
} | } | ||||
MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
{ | { | ||||
stopBackgroundThread(); | 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, | class OpenSLAudioIODevice : public AudioIODevice, | ||||
public Thread | |||||
private Thread | |||||
{ | { | ||||
public: | public: | ||||
OpenSLAudioIODevice (const String& deviceName) | OpenSLAudioIODevice (const String& deviceName) | ||||
@@ -81,13 +81,28 @@ public: | |||||
Array<double> getAvailableSampleRates() override | Array<double> getAvailableSampleRates() override | ||||
{ | { | ||||
static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | ||||
return Array<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 | 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, | String open (const BigInteger& inputChannels, | ||||
@@ -116,8 +131,28 @@ public: | |||||
outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); | outputBuffer.setSize (jmax (1, numOutputChannels), actualBufferSize); | ||||
outputBuffer.clear(); | 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); | startThread (8); | ||||
@@ -134,18 +169,30 @@ public: | |||||
player = nullptr; | player = nullptr; | ||||
} | } | ||||
int getDefaultBufferSize() override { return 1024; } | |||||
int getOutputLatencyInSamples() override { return outputLatency; } | int getOutputLatencyInSamples() override { return outputLatency; } | ||||
int getInputLatencyInSamples() override { return inputLatency; } | int getInputLatencyInSamples() override { return inputLatency; } | ||||
bool isOpen() override { return deviceOpen; } | bool isOpen() override { return deviceOpen; } | ||||
int getCurrentBufferSizeSamples() override { return actualBufferSize; } | int getCurrentBufferSizeSamples() override { return actualBufferSize; } | ||||
int getCurrentBitDepth() override { return 16; } | int getCurrentBitDepth() override { return 16; } | ||||
double getCurrentSampleRate() override { return sampleRate; } | |||||
BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | ||||
BigInteger getActiveInputChannels() const override { return activeInputChans; } | BigInteger getActiveInputChannels() const override { return activeInputChans; } | ||||
String getLastError() override { return lastError; } | String getLastError() override { return lastError; } | ||||
bool isPlaying() override { return callback != nullptr; } | 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 | void start (AudioIODeviceCallback* newCallback) override | ||||
{ | { | ||||
stop(); | stop(); | ||||
@@ -184,6 +231,55 @@ private: | |||||
struct Player; | struct Player; | ||||
struct Recorder; | 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) | AudioIODeviceCallback* setCallback (AudioIODeviceCallback* const newCallback) | ||||
{ | { | ||||
const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
@@ -192,29 +288,45 @@ private: | |||||
return oldCallback; | 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); | const ScopedLock sl (callbackLock); | ||||
if (callback != nullptr) | if (callback != nullptr) | ||||
{ | |||||
callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | ||||
numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | ||||
actualBufferSize); | actualBufferSize); | ||||
} | |||||
else | else | ||||
{ | |||||
outputBuffer.clear(); | 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")) | 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")) | if (CreateEngineFunc createEngine = (CreateEngineFunc) library.getFunction ("slCreateEngine")) | ||||
{ | { | ||||
@@ -252,21 +365,21 @@ private: | |||||
if (engineObject != nullptr) (*engineObject)->Destroy (engineObject); | 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) | if (numChannels <= 0) | ||||
return nullptr; | 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; | 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) | if (numChannels <= 0) | ||||
return nullptr; | 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; | return recorder->openedOk() ? recorder.release() : nullptr; | ||||
} | } | ||||
@@ -288,12 +401,13 @@ private: | |||||
//================================================================================================== | //================================================================================================== | ||||
struct BufferList | 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) | while (numBlocksOut.get() == numBuffers) | ||||
{ | { | ||||
@@ -306,7 +420,7 @@ private: | |||||
return getNextBuffer(); | return getNextBuffer(); | ||||
} | } | ||||
int16* getNextBuffer() | |||||
int16* getNextBuffer() noexcept | |||||
{ | { | ||||
if (++nextBlock == numBuffers) | if (++nextBlock == numBuffers) | ||||
nextBlock = 0; | nextBlock = 0; | ||||
@@ -314,13 +428,12 @@ private: | |||||
return bufferSpace + nextBlock * numChannels * numSamples; | 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: | private: | ||||
HeapBlock<int16> bufferSpace; | HeapBlock<int16> bufferSpace; | ||||
@@ -332,24 +445,23 @@ private: | |||||
//================================================================================================== | //================================================================================================== | ||||
struct Player | 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), | : playerObject (nullptr), playerPlay (nullptr), playerBufferQueue (nullptr), | ||||
bufferList (numChannels) | |||||
bufferList (numChannels, playerNumBuffers, playerBufferSize) | |||||
{ | { | ||||
jassert (numChannels == 2); | |||||
SLDataFormat_PCM pcmFormat = | SLDataFormat_PCM pcmFormat = | ||||
{ | { | ||||
SL_DATAFORMAT_PCM, | SL_DATAFORMAT_PCM, | ||||
(SLuint32) numChannels, | (SLuint32) numChannels, | ||||
(SLuint32) (sampleRate * 1000), // (sample rate units are millihertz) | |||||
(SLuint32) (sampleRate * 1000), | |||||
SL_PCMSAMPLEFORMAT_FIXED_16, | SL_PCMSAMPLEFORMAT_FIXED_16, | ||||
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 | 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 }; | SLDataSource audioSrc = { &bufferQueue, &pcmFormat }; | ||||
SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; | SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, engine.outputMixObject }; | ||||
@@ -385,10 +497,11 @@ private: | |||||
void start() | void start() | ||||
{ | { | ||||
jassert (openedOk()); | jassert (openedOk()); | ||||
check ((*playerPlay)->SetPlayState (playerPlay, SL_PLAYSTATE_PLAYING)); | 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.getNumChannels() == bufferList.numChannels); | ||||
jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | jassert (buffer.getNumSamples() < bufferList.numSamples * bufferList.numBuffers); | ||||
@@ -398,26 +511,27 @@ private: | |||||
while (numSamples > 0) | 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; | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Player) | ||||
@@ -440,13 +560,11 @@ private: | |||||
//================================================================================================== | //================================================================================================== | ||||
struct Recorder | 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), | : recorderObject (nullptr), recorderRecord (nullptr), | ||||
recorderBufferQueue (nullptr), configObject (nullptr), | recorderBufferQueue (nullptr), configObject (nullptr), | ||||
bufferList (numChannels) | |||||
bufferList (numChannels, numBuffers, numSamples) | |||||
{ | { | ||||
jassert (numChannels == 1); // STEREO doesn't always work!! | |||||
SLDataFormat_PCM pcmFormat = | SLDataFormat_PCM pcmFormat = | ||||
{ | { | ||||
SL_DATAFORMAT_PCM, | SL_DATAFORMAT_PCM, | ||||
@@ -461,7 +579,8 @@ private: | |||||
SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; | SLDataLocator_IODevice ioDevice = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; | ||||
SLDataSource audioSrc = { &ioDevice, 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 }; | SLDataSink audioSink = { &bufferQueue, &pcmFormat }; | ||||
const SLInterfaceID interfaceIDs[] = { *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE }; | 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_RECORD, &recorderRecord)); | ||||
check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); | 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 ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); | ||||
check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | 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) | 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; | BufferList bufferList; | ||||
void enqueueBuffer (int16* buffer) | |||||
void enqueueBuffer (int16* buffer) noexcept | |||||
{ | { | ||||
check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); | check ((*recorderBufferQueue)->Enqueue (recorderBufferQueue, buffer, bufferList.getBufferSizeBytes())); | ||||
bufferList.bufferSent(); | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Recorder) | ||||
@@ -581,7 +700,7 @@ private: | |||||
ScopedPointer<Recorder> recorder; | ScopedPointer<Recorder> recorder; | ||||
//============================================================================== | //============================================================================== | ||||
static bool check (const SLresult result) | |||||
static bool check (const SLresult result) noexcept | |||||
{ | { | ||||
jassert (result == SL_RESULT_SUCCESS); | jassert (result == SL_RESULT_SUCCESS); | ||||
return result == SL_RESULT_SUCCESS; | return result == SL_RESULT_SUCCESS; | ||||
@@ -598,14 +717,14 @@ public: | |||||
OpenSLAudioDeviceType() : AudioIODeviceType (openSLTypeName) {} | 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, | AudioIODevice* createDevice (const String& outputDeviceName, | ||||
const String& inputDeviceName) | |||||
const String& inputDeviceName) override | |||||
{ | { | ||||
ScopedPointer<OpenSLAudioIODevice> dev; | ScopedPointer<OpenSLAudioIODevice> dev; | ||||
@@ -233,7 +233,7 @@ public: | |||||
for (int i = 0; i < numStreams; ++i) | 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) | for (unsigned int j = 0; j < b.mNumberChannels; ++j) | ||||
{ | { | ||||
@@ -352,14 +352,20 @@ public: | |||||
int getLatencyFromDevice (AudioObjectPropertyScope scope) const | int getLatencyFromDevice (AudioObjectPropertyScope scope) const | ||||
{ | { | ||||
UInt32 lat = 0; | |||||
UInt32 size = sizeof (lat); | |||||
UInt32 latency = 0; | |||||
UInt32 size = sizeof (latency); | |||||
AudioObjectPropertyAddress pa; | AudioObjectPropertyAddress pa; | ||||
pa.mElement = kAudioObjectPropertyElementMaster; | pa.mElement = kAudioObjectPropertyElementMaster; | ||||
pa.mSelector = kAudioDevicePropertyLatency; | pa.mSelector = kAudioDevicePropertyLatency; | ||||
pa.mScope = scope; | pa.mScope = scope; | ||||
AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &lat); | |||||
return (int) lat; | |||||
AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &latency); | |||||
UInt32 safetyOffset = 0; | |||||
size = sizeof (safetyOffset); | |||||
pa.mSelector = kAudioDevicePropertySafetyOffset; | |||||
AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &safetyOffset); | |||||
return (int) (latency + safetyOffset); | |||||
} | } | ||||
int getBitDepthFromDevice (AudioObjectPropertyScope scope) const | int getBitDepthFromDevice (AudioObjectPropertyScope scope) const | ||||
@@ -1945,7 +1951,7 @@ private: | |||||
for (int i = 0; i < numStreams; ++i) | for (int i = 0; i < numStreams; ++i) | ||||
{ | { | ||||
const AudioBuffer& b = bufList->mBuffers[i]; | |||||
const ::AudioBuffer& b = bufList->mBuffers[i]; | |||||
total += b.mNumberChannels; | total += b.mNumberChannels; | ||||
} | } | ||||
} | } | ||||
@@ -249,7 +249,7 @@ namespace AiffFileHelpers | |||||
data += isGenre ? 118 : 50; | data += isGenre ? 118 : 50; | ||||
if (data[0] == 0) | |||||
if (data < dataEnd && data[0] == 0) | |||||
{ | { | ||||
if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50; | if (data + 52 < dataEnd && isValidTag (data + 50)) data += 50; | ||||
else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118; | else if (data + 120 < dataEnd && isValidTag (data + 118)) data += 118; | ||||
@@ -380,7 +380,7 @@ public: | |||||
&destinationAudioFormat); | &destinationAudioFormat); | ||||
if (status == noErr) | if (status == noErr) | ||||
{ | { | ||||
bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (AudioBuffer)); | |||||
bufferList.malloc (1, sizeof (AudioBufferList) + numChannels * sizeof (::AudioBuffer)); | |||||
bufferList->mNumberBuffers = numChannels; | bufferList->mNumberBuffers = numChannels; | ||||
ok = true; | ok = true; | ||||
} | } | ||||
@@ -374,6 +374,7 @@ public: | |||||
^ ((int) componentDesc.componentSubType) | ^ ((int) componentDesc.componentSubType) | ||||
^ ((int) componentDesc.componentManufacturer); | ^ ((int) componentDesc.componentManufacturer); | ||||
desc.lastFileModTime = Time(); | desc.lastFileModTime = Time(); | ||||
desc.lastInfoUpdateTime = Time::getCurrentTime(); | |||||
desc.pluginFormatName = "AudioUnit"; | desc.pluginFormatName = "AudioUnit"; | ||||
desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); | desc.category = AudioUnitFormatHelpers::getCategory (componentDesc.componentType); | ||||
desc.manufacturerName = manufacturer; | desc.manufacturerName = manufacturer; | ||||
@@ -1223,7 +1224,7 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
size_t getAudioBufferSizeInBytes() const noexcept | 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 | AudioBufferList* getAudioBufferListForBus (AudioUnitElement busIndex) const noexcept | ||||
@@ -220,6 +220,7 @@ public: | |||||
desc.fileOrIdentifier = module->file.getFullPathName(); | desc.fileOrIdentifier = module->file.getFullPathName(); | ||||
desc.uid = getUID(); | desc.uid = getUID(); | ||||
desc.lastFileModTime = module->file.getLastModificationTime(); | desc.lastFileModTime = module->file.getLastModificationTime(); | ||||
desc.lastInfoUpdateTime = Time::getCurrentTime(); | |||||
desc.pluginFormatName = "LADSPA"; | desc.pluginFormatName = "LADSPA"; | ||||
desc.category = getCategory(); | desc.category = getCategory(); | ||||
desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String(); | desc.manufacturerName = plugin != nullptr ? String (plugin->Maker) : String(); | ||||
@@ -344,21 +344,25 @@ private: | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
namespace VST3BufferExchange | |||||
template <typename FloatType> | |||||
struct VST3BufferExchange | |||||
{ | { | ||||
typedef Array<float*> Bus; | |||||
typedef Array<FloatType*> Bus; | |||||
typedef Array<Bus> BusMap; | 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' | /** Assigns a series of AudioSampleBuffer's channels to an AudioBusBuffers' | ||||
@warning For speed, does not check the channel count and offsets | @warning For speed, does not check the channel count and offsets | ||||
according to the AudioSampleBuffer | 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; | const int channelEnd = numChannels + channelStartOffset; | ||||
jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); | jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); | ||||
@@ -368,7 +372,7 @@ namespace VST3BufferExchange | |||||
for (int i = channelStartOffset; i < channelEnd; ++i) | for (int i = channelStartOffset; i < channelEnd; ++i) | ||||
bus.add (buffer.getWritePointer (i, sampleOffset)); | bus.add (buffer.getWritePointer (i, sampleOffset)); | ||||
vstBuffers.channelBuffers32 = bus.getRawDataPointer(); | |||||
assignRawPointer (vstBuffers, bus.getRawDataPointer()); | |||||
vstBuffers.numChannels = numChannels; | vstBuffers.numChannels = numChannels; | ||||
vstBuffers.silenceFlags = 0; | vstBuffers.silenceFlags = 0; | ||||
} | } | ||||
@@ -376,7 +380,7 @@ namespace VST3BufferExchange | |||||
static void mapArrangementToBusses (int& channelIndexOffset, int index, | static void mapArrangementToBusses (int& channelIndexOffset, int index, | ||||
Array<Steinberg::Vst::AudioBusBuffers>& result, | Array<Steinberg::Vst::AudioBusBuffers>& result, | ||||
BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | ||||
AudioSampleBuffer& source) | |||||
AudioBuffer<FloatType>& source) | |||||
{ | { | ||||
const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | ||||
@@ -387,18 +391,16 @@ namespace VST3BufferExchange | |||||
busMapToUse.add (Bus()); | busMapToUse.add (Bus()); | ||||
if (numChansForBus > 0) | if (numChansForBus > 0) | ||||
{ | |||||
associateBufferTo (result.getReference (index), | associateBufferTo (result.getReference (index), | ||||
busMapToUse.getReference (index), | busMapToUse.getReference (index), | ||||
source, numChansForBus, channelIndexOffset); | source, numChansForBus, channelIndexOffset); | ||||
} | |||||
channelIndexOffset += numChansForBus; | 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; | int channelIndexOffset = 0; | ||||
@@ -407,10 +409,10 @@ namespace VST3BufferExchange | |||||
arrangements.getUnchecked (i), source); | 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; | int channelIndexOffset = 0; | ||||
@@ -420,6 +422,28 @@ namespace VST3BufferExchange | |||||
getArrangementForBus (&processor, isInput, i), | getArrangementForBus (&processor, isInput, i), | ||||
source); | 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 | #endif // JUCE_VST3COMMON_H_INCLUDED |
@@ -110,6 +110,7 @@ static void createPluginDescription (PluginDescription& description, | |||||
{ | { | ||||
description.fileOrIdentifier = pluginFile.getFullPathName(); | description.fileOrIdentifier = pluginFile.getFullPathName(); | ||||
description.lastFileModTime = pluginFile.getLastModificationTime(); | description.lastFileModTime = pluginFile.getLastModificationTime(); | ||||
description.lastInfoUpdateTime = Time::getCurrentTime(); | |||||
description.manufacturerName = company; | description.manufacturerName = company; | ||||
description.name = name; | description.name = name; | ||||
description.descriptiveName = name; | description.descriptiveName = name; | ||||
@@ -1702,7 +1703,7 @@ public: | |||||
using namespace Vst; | using namespace Vst; | ||||
ProcessSetup setup; | ProcessSetup setup; | ||||
setup.symbolicSampleSize = kSample32; | |||||
setup.symbolicSampleSize = isUsingDoublePrecision() ? kSample64 : kSample32; | |||||
setup.maxSamplesPerBlock = estimatedSamplesPerBlock; | setup.maxSamplesPerBlock = estimatedSamplesPerBlock; | ||||
setup.sampleRate = newSampleRate; | setup.sampleRate = newSampleRate; | ||||
setup.processMode = isNonRealtime() ? kOffline : kRealtime; | setup.processMode = isNonRealtime() ? kOffline : kRealtime; | ||||
@@ -1769,39 +1770,56 @@ public: | |||||
JUCE_CATCH_ALL_ASSERT | 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 +2169,7 @@ private: | |||||
*/ | */ | ||||
int numInputAudioBusses, numOutputAudioBusses; | int numInputAudioBusses, numOutputAudioBusses; | ||||
Array<Vst::SpeakerArrangement> inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). | 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; | Array<Vst::AudioBusBuffers> inputBusses, outputBusses; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -2393,12 +2411,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.inputs = inputBusses.getRawDataPointer(); | ||||
destination.outputs = outputBusses.getRawDataPointer(); | destination.outputs = outputBusses.getRawDataPointer(); | ||||
@@ -715,8 +715,7 @@ public: | |||||
name (mh->pluginName), | name (mh->pluginName), | ||||
wantsMidiMessages (false), | wantsMidiMessages (false), | ||||
initialised (false), | initialised (false), | ||||
isPowerOn (false), | |||||
tempBuffer (1, 1) | |||||
isPowerOn (false) | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
@@ -805,6 +804,7 @@ public: | |||||
desc.fileOrIdentifier = module->file.getFullPathName(); | desc.fileOrIdentifier = module->file.getFullPathName(); | ||||
desc.uid = getUID(); | desc.uid = getUID(); | ||||
desc.lastFileModTime = module->file.getLastModificationTime(); | desc.lastFileModTime = module->file.getLastModificationTime(); | ||||
desc.lastInfoUpdateTime = Time::getCurrentTime(); | |||||
desc.pluginFormatName = "VST"; | desc.pluginFormatName = "VST"; | ||||
desc.category = getCategory(); | desc.category = getCategory(); | ||||
@@ -938,6 +938,18 @@ public: | |||||
dispatch (effSetSampleRate, 0, 0, 0, (float) rate); | dispatch (effSetSampleRate, 0, 0, 0, (float) rate); | ||||
dispatch (effSetBlockSize, 0, jmax (16, samplesPerBlockExpected), 0, 0); | 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); | tempBuffer.setSize (jmax (1, effect->numOutputs), samplesPerBlockExpected); | ||||
if (! isPowerOn) | if (! isPowerOn) | ||||
@@ -980,110 +992,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 +1597,132 @@ private: | |||||
CriticalSection lock; | CriticalSection lock; | ||||
bool wantsMidiMessages, initialised, isPowerOn; | bool wantsMidiMessages, initialised, isPowerOn; | ||||
mutable StringArray programNames; | mutable StringArray programNames; | ||||
AudioSampleBuffer tempBuffer; | |||||
AudioBuffer<float> tempBuffer; | |||||
CriticalSection midiInLock; | CriticalSection midiInLock; | ||||
MidiBuffer incomingMidi; | MidiBuffer incomingMidi; | ||||
VSTMidiEventList midiEventsToSend; | VSTMidiEventList midiEventsToSend; | ||||
VstTimeInfo vstHostTime; | 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 | void setHostTimeFrameRate (long frameRateIndex, double frameRate, double currentTime) noexcept | ||||
{ | { | ||||
@@ -166,5 +166,7 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||||
#include "scanning/juce_KnownPluginList.cpp" | #include "scanning/juce_KnownPluginList.cpp" | ||||
#include "scanning/juce_PluginDirectoryScanner.cpp" | #include "scanning/juce_PluginDirectoryScanner.cpp" | ||||
#include "scanning/juce_PluginListComponent.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 "format_types/juce_VST3PluginFormat.h" | ||||
#include "scanning/juce_PluginDirectoryScanner.h" | #include "scanning/juce_PluginDirectoryScanner.h" | ||||
#include "scanning/juce_PluginListComponent.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" | |||||
} | } | ||||
@@ -38,7 +38,8 @@ AudioProcessor::AudioProcessor() | |||||
numOutputChannels (0), | numOutputChannels (0), | ||||
latencySamples (0), | latencySamples (0), | ||||
suspended (false), | suspended (false), | ||||
nonRealtime (false) | |||||
nonRealtime (false), | |||||
processingPrecision (singlePrecision) | |||||
{ | { | ||||
} | } | ||||
@@ -325,7 +326,33 @@ void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) | |||||
} | } | ||||
void AudioProcessor::reset() {} | 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 | #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | ||||
//============================================================================== | //============================================================================== | ||||
@@ -48,6 +48,14 @@ protected: | |||||
AudioProcessor(); | AudioProcessor(); | ||||
public: | public: | ||||
//============================================================================== | |||||
enum ProcessingPrecision | |||||
{ | |||||
singlePrecision, | |||||
doublePrecision | |||||
}; | |||||
//============================================================================== | |||||
/** Destructor. */ | /** Destructor. */ | ||||
virtual ~AudioProcessor(); | virtual ~AudioProcessor(); | ||||
@@ -125,9 +133,74 @@ public: | |||||
processBlock() method to send out an asynchronous message. You could also use | processBlock() method to send out an asynchronous message. You could also use | ||||
the AsyncUpdater class in a similar way. | the AsyncUpdater class in a similar way. | ||||
*/ | */ | ||||
virtual void processBlock (AudioSampleBuffer& buffer, | |||||
virtual void processBlock (AudioBuffer<float>& buffer, | |||||
MidiBuffer& midiMessages) = 0; | 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. | /** Renders the next block when the processor is being bypassed. | ||||
The default implementation of this method will pass-through any incoming audio, but | 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 | 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) | Another use for this method would be to cross-fade or morph between the wet (not bypassed) | ||||
and dry (bypassed) signals. | and dry (bypassed) signals. | ||||
*/ | */ | ||||
virtual void processBlockBypassed (AudioSampleBuffer& buffer, | |||||
virtual void processBlockBypassed (AudioBuffer<double>& buffer, | |||||
MidiBuffer& midiMessages); | 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 | /** Returns the current AudioPlayHead object that should be used to find | ||||
out the state and position of the playhead. | out the state and position of the playhead. | ||||
@@ -742,6 +852,7 @@ private: | |||||
double sampleRate; | double sampleRate; | ||||
int blockSize, numInputChannels, numOutputChannels, latencySamples; | int blockSize, numInputChannels, numOutputChannels, latencySamples; | ||||
bool suspended, nonRealtime; | bool suspended, nonRealtime; | ||||
ProcessingPrecision processingPrecision; | |||||
CriticalSection callbackLock, listenerLock; | CriticalSection callbackLock, listenerLock; | ||||
String inputSpeakerArrangement, outputSpeakerArrangement; | String inputSpeakerArrangement, outputSpeakerArrangement; | ||||
@@ -25,28 +25,81 @@ | |||||
const int AudioProcessorGraph::midiChannelIndex = 0x1000; | 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 OwnedArray<MidiBuffer>& sharedMidiBuffers, | ||||
const int numSamples) = 0; | 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) {} | 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); | 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 | CopyChannelOp (const int srcChan, const int dstChan) noexcept | ||||
: srcChannelNum (srcChan), dstChannelNum (dstChan) | : 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); | 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 | AddChannelOp (const int srcChan, const int dstChan) noexcept | ||||
: srcChannelNum (srcChan), dstChannelNum (dstChan) | : 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); | 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) {} | 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(); | 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 | CopyMidiBufferOp (const int srcBuffer, const int dstBuffer) noexcept | ||||
: srcBufferNum (srcBuffer), dstBufferNum (dstBuffer) | : 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); | *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) | AddMidiBufferOp (const int srcBuffer, const int dstBuffer) | ||||
: srcBufferNum (srcBuffer), dstBufferNum (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) | sharedMidiBuffers.getUnchecked (dstBufferNum) | ||||
->addEvents (*sharedMidiBuffers.getUnchecked (srcBufferNum), 0, numSamples, 0); | ->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) | DelayChannelOp (const int chan, const int delaySize) | ||||
: channel (chan), | : channel (chan), | ||||
bufferSize (delaySize + 1), | bufferSize (delaySize + 1), | ||||
readIndex (0), writeIndex (delaySize) | 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;) | for (int i = numSamples; --i >= 0;) | ||||
{ | { | ||||
buffer [writeIndex] = *data; | |||||
*data++ = buffer [readIndex]; | |||||
block [writeIndex] = *data; | |||||
*data++ = block [readIndex]; | |||||
if (++readIndex >= bufferSize) readIndex = 0; | if (++readIndex >= bufferSize) readIndex = 0; | ||||
if (++writeIndex >= bufferSize) writeIndex = 0; | if (++writeIndex >= bufferSize) writeIndex = 0; | ||||
@@ -166,41 +227,67 @@ struct DelayChannelOp : public AudioGraphRenderingOp | |||||
} | } | ||||
private: | private: | ||||
HeapBlock<float> buffer; | |||||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder> > buffer; | |||||
const int channel, bufferSize; | const int channel, bufferSize; | ||||
int readIndex, writeIndex; | int readIndex, writeIndex; | ||||
JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) | JUCE_DECLARE_NON_COPYABLE (DelayChannelOp) | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
struct ProcessBufferOp : public AudioGraphRenderingOp | |||||
struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||||
{ | { | ||||
ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, | ProcessBufferOp (const AudioProcessorGraph::Node::Ptr& n, | ||||
const Array<int>& audioChannels, | |||||
const Array<int>& audioChannelsUsed, | |||||
const int totalNumChans, | const int totalNumChans, | ||||
const int midiBuffer) | const int midiBuffer) | ||||
: node (n), | : node (n), | ||||
processor (n->getProcessor()), | processor (n->getProcessor()), | ||||
audioChannelsToUse (audioChannels), | |||||
audioChannelsToUse (audioChannelsUsed), | |||||
totalChans (jmax (1, totalNumChans)), | totalChans (jmax (1, totalNumChans)), | ||||
midiBufferToUse (midiBuffer) | midiBufferToUse (midiBuffer) | ||||
{ | { | ||||
channels.calloc ((size_t) totalChans); | |||||
audioChannels.floatVersion. calloc ((size_t) totalChans); | |||||
audioChannels.doubleVersion.calloc ((size_t) totalChans); | |||||
while (audioChannelsToUse.size() < totalChans) | while (audioChannelsToUse.size() < totalChans) | ||||
audioChannelsToUse.add (0); | 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;) | for (int i = totalChans; --i >= 0;) | ||||
channels[i] = sharedBufferChans.getWritePointer (audioChannelsToUse.getUnchecked (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; | const AudioProcessorGraph::Node::Ptr node; | ||||
@@ -208,7 +295,8 @@ struct ProcessBufferOp : public AudioGraphRenderingOp | |||||
private: | private: | ||||
Array<int> audioChannelsToUse; | Array<int> audioChannelsToUse; | ||||
HeapBlock<float*> channels; | |||||
FloatAndDoubleComposition<HeapBlock<FloatPlaceholder*> > audioChannels; | |||||
AudioBuffer<float> tempBuffer; | |||||
const int totalChans; | const int totalChans; | ||||
const int midiBufferToUse; | 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, | void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int newBlockSize, | ||||
AudioProcessorGraph* const graph) | |||||
AudioProcessorGraph* const graph, ProcessingPrecision precision) | |||||
{ | { | ||||
if (! isPrepared) | if (! isPrepared) | ||||
{ | { | ||||
isPrepared = true; | isPrepared = true; | ||||
setParentGraph (graph); | setParentGraph (graph); | ||||
// try to align the precision of the processor and the graph | |||||
processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | |||||
: singlePrecision); | |||||
processor->setPlayConfigDetails (processor->getNumInputChannels(), | processor->setPlayConfigDetails (processor->getNumInputChannels(), | ||||
processor->getNumOutputChannels(), | processor->getNumOutputChannels(), | ||||
newSampleRate, newBlockSize); | newSampleRate, newBlockSize); | ||||
@@ -892,10 +984,53 @@ void AudioProcessorGraph::Node::setParentGraph (AudioProcessorGraph* const graph | |||||
ioProc->setParentGraph (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() | AudioProcessorGraph::AudioProcessorGraph() | ||||
: lastNodeId (0), | |||||
currentAudioInputBuffer (nullptr), | |||||
: lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | |||||
currentMidiInputBuffer (nullptr) | currentMidiInputBuffer (nullptr) | ||||
{ | { | ||||
} | } | ||||
@@ -1140,7 +1275,7 @@ bool AudioProcessorGraph::removeIllegalConnections() | |||||
static void deleteRenderOpArray (Array<void*>& ops) | static void deleteRenderOpArray (Array<void*>& ops) | ||||
{ | { | ||||
for (int i = ops.size(); --i >= 0;) | 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() | void AudioProcessorGraph::clearRenderingSequence() | ||||
@@ -1193,7 +1328,7 @@ void AudioProcessorGraph::buildRenderingSequence() | |||||
{ | { | ||||
Node* const node = nodes.getUnchecked(i); | Node* const node = nodes.getUnchecked(i); | ||||
node->prepare (getSampleRate(), getBlockSize(), this); | |||||
node->prepare (getSampleRate(), getBlockSize(), this, getProcessingPrecision()); | |||||
int j = 0; | int j = 0; | ||||
for (; j < orderedNodes.size(); ++j) | for (; j < orderedNodes.size(); ++j) | ||||
@@ -1214,8 +1349,7 @@ void AudioProcessorGraph::buildRenderingSequence() | |||||
// swap over to the new rendering sequence.. | // swap over to the new rendering sequence.. | ||||
const ScopedLock sl (getCallbackLock()); | const ScopedLock sl (getCallbackLock()); | ||||
renderingBuffers.setSize (numRenderingBuffersNeeded, getBlockSize()); | |||||
renderingBuffers.clear(); | |||||
audioBuffers->setRenderingBufferSize (numRenderingBuffersNeeded, getBlockSize()); | |||||
for (int i = midiBuffers.size(); --i >= 0;) | for (int i = midiBuffers.size(); --i >= 0;) | ||||
midiBuffers.getUnchecked(i)->clear(); | midiBuffers.getUnchecked(i)->clear(); | ||||
@@ -1238,8 +1372,8 @@ void AudioProcessorGraph::handleAsyncUpdate() | |||||
//============================================================================== | //============================================================================== | ||||
void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) | void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSamplesPerBlock) | ||||
{ | { | ||||
currentAudioInputBuffer = nullptr; | |||||
currentAudioOutputBuffer.setSize (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); | |||||
audioBuffers->prepareInOutBuffers (jmax (1, getNumOutputChannels()), estimatedSamplesPerBlock); | |||||
currentMidiInputBuffer = nullptr; | currentMidiInputBuffer = nullptr; | ||||
currentMidiOutputBuffer.clear(); | currentMidiOutputBuffer.clear(); | ||||
@@ -1247,16 +1381,19 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||||
buildRenderingSequence(); | buildRenderingSequence(); | ||||
} | } | ||||
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||||
{ | |||||
return true; | |||||
} | |||||
void AudioProcessorGraph::releaseResources() | void AudioProcessorGraph::releaseResources() | ||||
{ | { | ||||
for (int i = 0; i < nodes.size(); ++i) | for (int i = 0; i < nodes.size(); ++i) | ||||
nodes.getUnchecked(i)->unprepare(); | nodes.getUnchecked(i)->unprepare(); | ||||
renderingBuffers.setSize (1, 1); | |||||
audioBuffers->release(); | |||||
midiBuffers.clear(); | midiBuffers.clear(); | ||||
currentAudioInputBuffer = nullptr; | |||||
currentAudioOutputBuffer.setSize (1, 1); | |||||
currentMidiInputBuffer = nullptr; | currentMidiInputBuffer = nullptr; | ||||
currentMidiOutputBuffer.clear(); | currentMidiOutputBuffer.clear(); | ||||
} | } | ||||
@@ -1287,8 +1424,13 @@ void AudioProcessorGraph::setPlayHead (AudioPlayHead* audioPlayHead) | |||||
nodes.getUnchecked(i)->getProcessor()->setPlayHead (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(); | const int numSamples = buffer.getNumSamples(); | ||||
currentAudioInputBuffer = &buffer; | currentAudioInputBuffer = &buffer; | ||||
@@ -1299,8 +1441,8 @@ void AudioProcessorGraph::processBlock (AudioSampleBuffer& buffer, MidiBuffer& m | |||||
for (int i = 0; i < renderingOps.size(); ++i) | 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); | op->perform (renderingBuffers, midiBuffers, numSamples); | ||||
} | } | ||||
@@ -1331,6 +1473,21 @@ bool AudioProcessorGraph::producesMidi() const { return tru | |||||
void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} | void AudioProcessorGraph::getStateInformation (juce::MemoryBlock&) {} | ||||
void AudioProcessorGraph::setStateInformation (const void*, int) {} | 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) | 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) | MidiBuffer& midiMessages) | ||||
{ | { | ||||
AudioBuffer<FloatType>*& currentAudioInputBuffer = | |||||
graph->audioBuffers->currentAudioInputBuffer.get<FloatType>(); | |||||
AudioBuffer<FloatType>& currentAudioOutputBuffer = | |||||
graph->audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | |||||
jassert (graph != nullptr); | jassert (graph != nullptr); | ||||
switch (type) | switch (type) | ||||
{ | { | ||||
case audioOutputNode: | case audioOutputNode: | ||||
{ | { | ||||
for (int i = jmin (graph->currentAudioOutputBuffer.getNumChannels(), | |||||
for (int i = jmin (currentAudioOutputBuffer.getNumChannels(), | |||||
buffer.getNumChannels()); --i >= 0;) | buffer.getNumChannels()); --i >= 0;) | ||||
{ | { | ||||
graph->currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); | |||||
currentAudioOutputBuffer.addFrom (i, 0, buffer, i, 0, buffer.getNumSamples()); | |||||
} | } | ||||
break; | break; | ||||
@@ -1404,10 +1573,10 @@ void AudioProcessorGraph::AudioGraphIOProcessor::processBlock (AudioSampleBuffer | |||||
case audioInputNode: | case audioInputNode: | ||||
{ | { | ||||
for (int i = jmin (graph->currentAudioInputBuffer->getNumChannels(), | |||||
for (int i = jmin (currentAudioInputBuffer->getNumChannels(), | |||||
buffer.getNumChannels()); --i >= 0;) | buffer.getNumChannels()); --i >= 0;) | ||||
{ | { | ||||
buffer.copyFrom (i, 0, *graph->currentAudioInputBuffer, i, 0, buffer.getNumSamples()); | |||||
buffer.copyFrom (i, 0, *currentAudioInputBuffer, i, 0, buffer.getNumSamples()); | |||||
} | } | ||||
break; | 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 | bool AudioProcessorGraph::AudioGraphIOProcessor::silenceInProducesSilenceOut() const | ||||
{ | { | ||||
return isOutput(); | return isOutput(); | ||||
@@ -25,7 +25,6 @@ | |||||
#ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | #ifndef JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | ||||
#define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | #define JUCE_AUDIOPROCESSORGRAPH_H_INCLUDED | ||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
A type of AudioProcessor which plays back a graph of other AudioProcessors. | A type of AudioProcessor which plays back a graph of other AudioProcessors. | ||||
@@ -92,7 +91,7 @@ public: | |||||
Node (uint32 nodeId, AudioProcessor*) noexcept; | Node (uint32 nodeId, AudioProcessor*) noexcept; | ||||
void setParentGraph (AudioProcessorGraph*) const; | void setParentGraph (AudioProcessorGraph*) const; | ||||
void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*); | |||||
void prepare (double newSampleRate, int newBlockSize, AudioProcessorGraph*, ProcessingPrecision); | |||||
void unprepare(); | void unprepare(); | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Node) | ||||
@@ -308,7 +307,9 @@ public: | |||||
void fillInPluginDescription (PluginDescription&) const override; | void fillInPluginDescription (PluginDescription&) const override; | ||||
void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; | void prepareToPlay (double newSampleRate, int estimatedSamplesPerBlock) override; | ||||
void releaseResources() 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 getInputChannelName (int channelIndex) const override; | ||||
const String getOutputChannelName (int channelIndex) const override; | const String getOutputChannelName (int channelIndex) const override; | ||||
@@ -340,6 +341,10 @@ public: | |||||
const IODeviceType type; | const IODeviceType type; | ||||
AudioProcessorGraph* graph; | AudioProcessorGraph* graph; | ||||
//============================================================================== | |||||
template <typename floatType> | |||||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioGraphIOProcessor) | ||||
}; | }; | ||||
@@ -347,7 +352,9 @@ public: | |||||
const String getName() const override; | const String getName() const override; | ||||
void prepareToPlay (double, int) override; | void prepareToPlay (double, int) override; | ||||
void releaseResources() 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 reset() override; | ||||
void setNonRealtime (bool) noexcept override; | void setNonRealtime (bool) noexcept override; | ||||
@@ -376,17 +383,21 @@ public: | |||||
void setStateInformation (const void* data, int sizeInBytes) override; | void setStateInformation (const void* data, int sizeInBytes) override; | ||||
private: | private: | ||||
//============================================================================== | |||||
template <typename floatType> | |||||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||||
//============================================================================== | //============================================================================== | ||||
ReferenceCountedArray<Node> nodes; | ReferenceCountedArray<Node> nodes; | ||||
OwnedArray<Connection> connections; | OwnedArray<Connection> connections; | ||||
uint32 lastNodeId; | uint32 lastNodeId; | ||||
AudioSampleBuffer renderingBuffers; | |||||
OwnedArray<MidiBuffer> midiBuffers; | OwnedArray<MidiBuffer> midiBuffers; | ||||
Array<void*> renderingOps; | Array<void*> renderingOps; | ||||
friend class AudioGraphIOProcessor; | friend class AudioGraphIOProcessor; | ||||
AudioSampleBuffer* currentAudioInputBuffer; | |||||
AudioSampleBuffer currentAudioOutputBuffer; | |||||
struct AudioProcessorGraphBufferHelpers; | |||||
ScopedPointer<AudioProcessorGraphBufferHelpers> audioBuffers; | |||||
MidiBuffer* currentMidiInputBuffer; | MidiBuffer* currentMidiInputBuffer; | ||||
MidiBuffer currentMidiOutputBuffer; | MidiBuffer currentMidiOutputBuffer; | ||||
@@ -44,6 +44,7 @@ PluginDescription::PluginDescription (const PluginDescription& other) | |||||
version (other.version), | version (other.version), | ||||
fileOrIdentifier (other.fileOrIdentifier), | fileOrIdentifier (other.fileOrIdentifier), | ||||
lastFileModTime (other.lastFileModTime), | lastFileModTime (other.lastFileModTime), | ||||
lastInfoUpdateTime (other.lastInfoUpdateTime), | |||||
uid (other.uid), | uid (other.uid), | ||||
isInstrument (other.isInstrument), | isInstrument (other.isInstrument), | ||||
numInputChannels (other.numInputChannels), | numInputChannels (other.numInputChannels), | ||||
@@ -64,6 +65,7 @@ PluginDescription& PluginDescription::operator= (const PluginDescription& other) | |||||
uid = other.uid; | uid = other.uid; | ||||
isInstrument = other.isInstrument; | isInstrument = other.isInstrument; | ||||
lastFileModTime = other.lastFileModTime; | lastFileModTime = other.lastFileModTime; | ||||
lastInfoUpdateTime = other.lastInfoUpdateTime; | |||||
numInputChannels = other.numInputChannels; | numInputChannels = other.numInputChannels; | ||||
numOutputChannels = other.numOutputChannels; | numOutputChannels = other.numOutputChannels; | ||||
hasSharedContainer = other.hasSharedContainer; | hasSharedContainer = other.hasSharedContainer; | ||||
@@ -108,6 +110,7 @@ XmlElement* PluginDescription::createXml() const | |||||
e->setAttribute ("uid", String::toHexString (uid)); | e->setAttribute ("uid", String::toHexString (uid)); | ||||
e->setAttribute ("isInstrument", isInstrument); | e->setAttribute ("isInstrument", isInstrument); | ||||
e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); | e->setAttribute ("fileTime", String::toHexString (lastFileModTime.toMilliseconds())); | ||||
e->setAttribute ("infoUpdateTime", String::toHexString (lastInfoUpdateTime.toMilliseconds())); | |||||
e->setAttribute ("numInputs", numInputChannels); | e->setAttribute ("numInputs", numInputChannels); | ||||
e->setAttribute ("numOutputs", numOutputChannels); | e->setAttribute ("numOutputs", numOutputChannels); | ||||
e->setAttribute ("isShell", hasSharedContainer); | e->setAttribute ("isShell", hasSharedContainer); | ||||
@@ -129,6 +132,7 @@ bool PluginDescription::loadFromXml (const XmlElement& xml) | |||||
uid = xml.getStringAttribute ("uid").getHexValue32(); | uid = xml.getStringAttribute ("uid").getHexValue32(); | ||||
isInstrument = xml.getBoolAttribute ("isInstrument", false); | isInstrument = xml.getBoolAttribute ("isInstrument", false); | ||||
lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); | lastFileModTime = Time (xml.getStringAttribute ("fileTime").getHexValue64()); | ||||
lastInfoUpdateTime = Time (xml.getStringAttribute ("infoUpdateTime").getHexValue64()); | |||||
numInputChannels = xml.getIntAttribute ("numInputs"); | numInputChannels = xml.getIntAttribute ("numInputs"); | ||||
numOutputChannels = xml.getIntAttribute ("numOutputs"); | numOutputChannels = xml.getIntAttribute ("numOutputs"); | ||||
hasSharedContainer = xml.getBoolAttribute ("isShell", false); | hasSharedContainer = xml.getBoolAttribute ("isShell", false); | ||||
@@ -81,6 +81,11 @@ public: | |||||
*/ | */ | ||||
Time lastFileModTime; | Time lastFileModTime; | ||||
/** The last time that this information was updated. This would typically have | |||||
been during a scan when this plugin was first tested or found to have changed. | |||||
*/ | |||||
Time lastInfoUpdateTime; | |||||
/** A unique ID for the plug-in. | /** A unique ID for the plug-in. | ||||
Note that this might not be unique between formats, e.g. a VST and some | Note that this might not be unique between formats, e.g. a VST and some | ||||
@@ -263,6 +263,7 @@ struct PluginSorter | |||||
case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName); break; | case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName); break; | ||||
case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break; | case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break; | ||||
case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break; | case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break; | ||||
case KnownPluginList::sortByInfoUpdateTime: diff = compare (first->lastInfoUpdateTime, second->lastInfoUpdateTime); break; | |||||
default: break; | default: break; | ||||
} | } | ||||
@@ -278,6 +279,14 @@ private: | |||||
return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false); | return path.replaceCharacter ('\\', '/').upToLastOccurrenceOf ("/", false, false); | ||||
} | } | ||||
static int compare (Time a, Time b) noexcept | |||||
{ | |||||
if (a < b) return -1; | |||||
if (b < a) return 1; | |||||
return 0; | |||||
} | |||||
const KnownPluginList::SortMethod method; | const KnownPluginList::SortMethod method; | ||||
const int direction; | const int direction; | ||||
@@ -136,7 +136,8 @@ public: | |||||
sortByCategory, | sortByCategory, | ||||
sortByManufacturer, | sortByManufacturer, | ||||
sortByFormat, | sortByFormat, | ||||
sortByFileSystemLocation | |||||
sortByFileSystemLocation, | |||||
sortByInfoUpdateTime | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -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 | inline int size() const noexcept | ||||
{ | { | ||||
return numUsed; | 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. | /** Returns one of the elements in the array. | ||||
If the index passed in is beyond the range of valid elements, this | If the index passed in is beyond the range of valid elements, this | ||||
will return a default value. | will return a default value. | ||||
@@ -886,6 +886,41 @@ File File::createTempFile (StringRef fileNameEnding) | |||||
return tempFile; | 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) | MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode) | ||||
: address (nullptr), range (0, file.getSize()), fileHandle (0) | : address (nullptr), range (0, file.getSize()), fileHandle (0) | ||||
@@ -360,14 +360,6 @@ public: | |||||
*/ | */ | ||||
bool isHidden() const; | 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. | /** 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 | 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); | static File createTempFile (StringRef fileNameEnding); | ||||
//============================================================================== | //============================================================================== | ||||
/** Returns the current working directory. | /** Returns the current working directory. | ||||
@see setAsCurrentWorkingDirectory | @see setAsCurrentWorkingDirectory | ||||
@@ -946,8 +937,28 @@ public: | |||||
/** Adds a separator character to the end of a path if it doesn't already have one. */ | /** Adds a separator character to the end of a path if it doesn't already have one. */ | ||||
static String addTrailingSeparator (const String& path); | 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. */ | /** OSX ONLY - Finds the OSType of a file from the its resources. */ | ||||
OSType getMacOSType() const; | OSType getMacOSType() const; | ||||
@@ -960,11 +971,6 @@ public: | |||||
void addToDock() const; | void addToDock() const; | ||||
#endif | #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: | private: | ||||
//============================================================================== | //============================================================================== | ||||
String fullPath; | String fullPath; | ||||
@@ -136,6 +136,8 @@ public: | |||||
return v; | return v; | ||||
} | } | ||||
Range<ValueType> getRange() const noexcept { return Range<ValueType> (start, end); } | |||||
/** The start of the non-normalised range. */ | /** The start of the non-normalised range. */ | ||||
ValueType start; | ValueType start; | ||||
@@ -48,24 +48,54 @@ public: | |||||
static uint64 swap (uint64 value) noexcept; | 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; | 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; | 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; | 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; | 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; | 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; | 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. */ | /** Turns 4 bytes into a little-endian integer. */ | ||||
static uint32 littleEndianInt (const void* bytes) noexcept; | 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 uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return v; } | ||||
inline uint32 ByteOrder::swapIfBigEndian (const uint32 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 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 uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return swap (v); } | ||||
inline uint32 ByteOrder::swapIfLittleEndian (const uint32 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 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 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 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); } | 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 uint16 ByteOrder::swapIfBigEndian (const uint16 v) noexcept { return swap (v); } | ||||
inline uint32 ByteOrder::swapIfBigEndian (const uint32 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 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 uint16 ByteOrder::swapIfLittleEndian (const uint16 v) noexcept { return v; } | ||||
inline uint32 ByteOrder::swapIfLittleEndian (const uint32 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 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 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 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)); } | inline uint16 ByteOrder::littleEndianShort (const void* const bytes) noexcept { return swap (*static_cast<const uint16*> (bytes)); } | ||||
@@ -30,28 +30,41 @@ import android.content.DialogInterface; | |||||
import android.content.Context; | import android.content.Context; | ||||
import android.content.Intent; | import android.content.Intent; | ||||
import android.content.res.Configuration; | import android.content.res.Configuration; | ||||
import android.content.pm.PackageManager; | |||||
import android.net.Uri; | import android.net.Uri; | ||||
import android.os.Bundle; | 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.*; | ||||
import android.view.inputmethod.BaseInputConnection; | import android.view.inputmethod.BaseInputConnection; | ||||
import android.view.inputmethod.EditorInfo; | import android.view.inputmethod.EditorInfo; | ||||
import android.view.inputmethod.InputConnection; | import android.view.inputmethod.InputConnection; | ||||
import android.view.inputmethod.InputMethodManager; | import android.view.inputmethod.InputMethodManager; | ||||
import android.graphics.*; | import android.graphics.*; | ||||
import android.opengl.*; | |||||
import android.text.ClipboardManager; | import android.text.ClipboardManager; | ||||
import android.text.InputType; | import android.text.InputType; | ||||
import android.util.DisplayMetrics; | import android.util.DisplayMetrics; | ||||
import android.util.Log; | 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.io.*; | ||||
import java.net.URL; | import java.net.URL; | ||||
import java.net.HttpURLConnection; | import java.net.HttpURLConnection; | ||||
import javax.microedition.khronos.egl.EGLConfig; | |||||
import javax.microedition.khronos.opengles.GL10; | |||||
import android.media.AudioManager; | import android.media.AudioManager; | ||||
import android.media.MediaScannerConnection; | import android.media.MediaScannerConnection; | ||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient; | 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 | public class JuceAppActivity extends Activity | ||||
{ | { | ||||
@@ -61,6 +74,58 @@ public class JuceAppActivity extends Activity | |||||
System.loadLibrary ("juce_jni"); | 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 | @Override | ||||
public void onCreate (Bundle savedInstanceState) | public void onCreate (Bundle savedInstanceState) | ||||
{ | { | ||||
@@ -85,9 +150,6 @@ public class JuceAppActivity extends Activity | |||||
@Override | @Override | ||||
protected void onPause() | protected void onPause() | ||||
{ | { | ||||
if (viewHolder != null) | |||||
viewHolder.onPause(); | |||||
suspendApp(); | suspendApp(); | ||||
super.onPause(); | super.onPause(); | ||||
} | } | ||||
@@ -96,10 +158,6 @@ public class JuceAppActivity extends Activity | |||||
protected void onResume() | protected void onResume() | ||||
{ | { | ||||
super.onResume(); | super.onResume(); | ||||
if (viewHolder != null) | |||||
viewHolder.onResume(); | |||||
resumeApp(); | resumeApp(); | ||||
} | } | ||||
@@ -142,7 +200,10 @@ public class JuceAppActivity extends Activity | |||||
//============================================================================== | //============================================================================== | ||||
private ViewHolder viewHolder; | private ViewHolder viewHolder; | ||||
private MidiDeviceManager midiDeviceManager = null; | |||||
private BluetoothManager bluetoothManager = null; | |||||
private boolean isScreenSaverEnabled; | private boolean isScreenSaverEnabled; | ||||
private java.util.Timer keepAliveTimer; | |||||
public final ComponentPeerView createNewView (boolean opaque, long host) | public final ComponentPeerView createNewView (boolean opaque, long host) | ||||
{ | { | ||||
@@ -159,7 +220,7 @@ public class JuceAppActivity extends Activity | |||||
group.removeView (view); | group.removeView (view); | ||||
} | } | ||||
public final void deleteOpenGLView (OpenGLView view) | |||||
public final void deleteNativeSurfaceView (NativeSurfaceView view) | |||||
{ | { | ||||
ViewGroup group = (ViewGroup) (view.getParent()); | 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() | private final int getDPI() | ||||
{ | { | ||||
DisplayMetrics metrics = new DisplayMetrics(); | DisplayMetrics metrics = new DisplayMetrics(); | ||||
@@ -230,14 +269,46 @@ public class JuceAppActivity extends Activity | |||||
if (isScreenSaverEnabled != enabled) | if (isScreenSaverEnabled != enabled) | ||||
{ | { | ||||
isScreenSaverEnabled = enabled; | isScreenSaverEnabled = enabled; | ||||
if (keepAliveTimer != null) | |||||
{ | |||||
keepAliveTimer.cancel(); | |||||
keepAliveTimer = null; | |||||
} | |||||
if (enabled) | if (enabled) | ||||
{ | |||||
getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | ||||
} | |||||
else | else | ||||
{ | |||||
getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | 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; | return isScreenSaverEnabled; | ||||
} | } | ||||
@@ -546,70 +617,83 @@ public class JuceAppActivity extends Activity | |||||
{ | { | ||||
return true; //xxx needs to check overlapping views | 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 | @Override | ||||
public void onSurfaceCreated (GL10 unused, EGLConfig config) | |||||
protected void dispatchDraw (Canvas canvas) | |||||
{ | { | ||||
contextCreated(); | |||||
super.dispatchDraw (canvas); | |||||
dispatchDrawNative (nativeContext, canvas); | |||||
} | } | ||||
//============================================================================== | |||||
@Override | @Override | ||||
public void onSurfaceChanged (GL10 unused, int width, int height) | |||||
protected void onAttachedToWindow () | |||||
{ | { | ||||
contextChangedSize(); | |||||
super.onAttachedToWindow(); | |||||
getHolder().addCallback (this); | |||||
} | } | ||||
@Override | @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; | 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; | 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 | class GlobalRef | ||||
{ | { | ||||
@@ -236,6 +240,8 @@ private: | |||||
#define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ | #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 | extern "C" __attribute__ ((visibility("default"))) JUCE_ARM_SOFT_FLOAT_ABI returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params | ||||
//============================================================================== | //============================================================================== | ||||
class AndroidSystem | class AndroidSystem | ||||
{ | { | ||||
@@ -253,142 +259,11 @@ public: | |||||
extern AndroidSystem android; | 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) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | ||||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ | METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ | ||||
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ | 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 (postMessage, "postMessage", "(J)V") \ | ||||
METHOD (finish, "finish", "()V") \ | METHOD (finish, "finish", "()V") \ | ||||
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | ||||
@@ -405,7 +280,14 @@ struct AndroidThreadScope | |||||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | ||||
METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | ||||
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ | 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); | DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); | ||||
#undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
@@ -435,6 +317,19 @@ DECLARE_JNI_CLASS (Paint, "android/graphics/Paint"); | |||||
DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | ||||
#undef JNI_CLASS_MEMBERS | #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) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | ||||
METHOD (constructor, "<init>", "(IIII)V") \ | 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 | 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*) | extern "C" jint JNI_OnLoad (JavaVM*, void*) | ||||
@@ -134,11 +129,6 @@ void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring | |||||
dpi = 160; | dpi = 160; | ||||
JNIClassBase::initialiseAllClasses (env); | JNIClassBase::initialiseAllClasses (env); | ||||
threadLocalJNIEnvHolder.initialise (env); | |||||
#if JUCE_DEBUG | |||||
systemInitialised = true; | |||||
#endif | |||||
activity = GlobalRef (act); | activity = GlobalRef (act); | ||||
appFile = juceString (env, file); | appFile = juceString (env, file); | ||||
appDataDir = juceString (env, dataDir); | appDataDir = juceString (env, dataDir); | ||||
@@ -148,10 +138,6 @@ void AndroidSystem::shutdown (JNIEnv* env) | |||||
{ | { | ||||
activity.clear(); | activity.clear(); | ||||
#if JUCE_DEBUG | |||||
systemInitialised = false; | |||||
#endif | |||||
JNIClassBase::releaseAllClasses (env); | JNIClassBase::releaseAllClasses (env); | ||||
} | } | ||||
@@ -253,7 +239,7 @@ String SystemStats::getLogonName() | |||||
if (struct passwd* const pw = getpwuid (getuid())) | if (struct passwd* const pw = getpwuid (getuid())) | ||||
return CharPointer_UTF8 (pw->pw_name); | return CharPointer_UTF8 (pw->pw_name); | ||||
return String::empty; | |||||
return String(); | |||||
} | } | ||||
String SystemStats::getFullUserName() | String SystemStats::getFullUserName() | ||||
@@ -267,7 +253,7 @@ String SystemStats::getComputerName() | |||||
if (gethostname (name, sizeof (name) - 1) == 0) | if (gethostname (name, sizeof (name) - 1) == 0) | ||||
return name; | 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::raisePrivilege() {} | ||||
JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} | 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)); | return String::fromUTF8 (buffer, jmax (0, numBytes)); | ||||
}; | }; | ||||
bool File::isLink() const | |||||
bool File::isSymbolicLink() const | |||||
{ | { | ||||
return getLinkedFile (getFullPathName()).isNotEmpty(); | return getLinkedFile (getFullPathName()).isNotEmpty(); | ||||
} | } | ||||
@@ -158,7 +158,7 @@ File File::getSpecialLocation (const SpecialLocationType type) | |||||
case hostApplicationPath: | case hostApplicationPath: | ||||
{ | { | ||||
const File f ("/proc/self/exe"); | const File f ("/proc/self/exe"); | ||||
return f.isLink() ? f.getLinkedTarget() : juce_getExecutableFile(); | |||||
return f.isSymbolicLink() ? f.getLinkedTarget() : juce_getExecutableFile(); | |||||
} | } | ||||
default: | default: | ||||
@@ -284,7 +284,7 @@ static NSString* getFileLink (const String& path) | |||||
#endif | #endif | ||||
} | } | ||||
bool File::isLink() const | |||||
bool File::isSymbolicLink() const | |||||
{ | { | ||||
return getFileLink (fullPath) != nil; | return getFileLink (fullPath) != nil; | ||||
} | } | ||||
@@ -400,7 +400,12 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||||
{ | { | ||||
JUCE_AUTORELEASEPOOL | 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 | #if JUCE_IOS | ||||
(void) parameters; | (void) parameters; | ||||
@@ -867,6 +867,7 @@ void InterProcessLock::exit() | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
#if ! JUCE_ANDROID | |||||
void JUCE_API juce_threadEntryPoint (void*); | void JUCE_API juce_threadEntryPoint (void*); | ||||
extern "C" void* threadEntryProc (void*); | extern "C" void* threadEntryProc (void*); | ||||
@@ -874,10 +875,6 @@ extern "C" void* threadEntryProc (void* userData) | |||||
{ | { | ||||
JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
{ | { | ||||
#if JUCE_ANDROID | |||||
const AndroidThreadScope androidEnv; | |||||
#endif | |||||
juce_threadEntryPoint (userData); | juce_threadEntryPoint (userData); | ||||
} | } | ||||
@@ -951,6 +948,7 @@ bool Thread::setThreadPriority (void* handle, int priority) | |||||
param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority; | ||||
return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; | ||||
} | } | ||||
#endif | |||||
Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId() | ||||
{ | { | ||||
@@ -1180,6 +1178,7 @@ bool ChildProcess::start (const StringArray& args, int streamFlags) | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
#if ! JUCE_ANDROID | |||||
struct HighResolutionTimer::Pimpl | struct HighResolutionTimer::Pimpl | ||||
{ | { | ||||
Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) | Pimpl (HighResolutionTimer& t) : owner (t), thread (0), shouldStop (false) | ||||
@@ -1286,20 +1285,6 @@ private: | |||||
uint64_t time, delta; | 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 | #else | ||||
Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | Clock (double millis) noexcept : delta ((uint64) (millis * 1000000)) | ||||
{ | { | ||||
@@ -1348,3 +1333,5 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE (Pimpl) | 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"); | return hasFileExtension (".lnk"); | ||||
} | } | ||||
File File::getLinkedTarget() const | 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); | File result (*this); | ||||
String p (getFullPathName()); | String p (getFullPathName()); | ||||
@@ -664,7 +696,7 @@ File File::getLinkedTarget() const | |||||
return result; | return result; | ||||
} | } | ||||
bool File::createLink (const String& description, const File& linkFileToCreate) const | |||||
bool File::createShortcut (const String& description, const File& linkFileToCreate) const | |||||
{ | { | ||||
linkFileToCreate.deleteFile(); | linkFileToCreate.deleteFile(); | ||||
@@ -42,6 +42,7 @@ | |||||
#define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | ||||
#define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 | #define JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS 1 | ||||
#define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 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) | #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL) | ||||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | ||||
@@ -93,6 +94,10 @@ | |||||
#define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | #define JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES 1 | ||||
#endif | #endif | ||||
#if __has_feature (cxx_static_assert) | |||||
#define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 | |||||
#endif | |||||
#ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL | #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL | ||||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | ||||
#endif | #endif | ||||
@@ -115,6 +120,7 @@ | |||||
#if _MSC_VER >= 1600 | #if _MSC_VER >= 1600 | ||||
#define JUCE_COMPILER_SUPPORTS_NULLPTR 1 | #define JUCE_COMPILER_SUPPORTS_NULLPTR 1 | ||||
#define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | #define JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS 1 | ||||
#define JUCE_COMPILER_SUPPORTS_STATIC_ASSERT 1 | |||||
#endif | #endif | ||||
#if _MSC_VER >= 1700 | #if _MSC_VER >= 1700 | ||||
@@ -149,20 +149,44 @@ | |||||
#endif | #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 | #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) | |||||
//============================================================================== | |||||
#if JUCE_COMPILER_SUPPORTS_STATIC_ASSERT | |||||
/** A compile-time assertion macro. | |||||
If the expression parameter is false, the macro will cause a compile error. (The actual error | |||||
message that the compiler generates may be completely bizarre and seem to have no relation to | |||||
the place where you put the static_assert though!) | |||||
*/ | |||||
#define static_jassert(expression) static_assert(expression, #expression); | |||||
#else | |||||
#ifndef DOXYGEN | |||||
namespace juce | |||||
{ | |||||
template <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=. | /** 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 new (size_t) JUCE_DELETED_FUNCTION; \ | ||||
static void operator delete (void*) 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) | #if JUCE_MSVC && ! defined (DOXYGEN) | ||||
#define JUCE_WARNING_HELPER(file, line, mess) message(file "(" JUCE_STRINGIFY (line) ") : Warning: " #mess) | #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. | 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. | /** Current Juce version number. | ||||
@@ -52,8 +52,8 @@ | |||||
//============================================================================== | //============================================================================== | ||||
#include <vector> // included before platform defs to provide a definition of _LIBCPP_VERSION | #include <vector> // included before platform defs to provide a definition of _LIBCPP_VERSION | ||||
#include "juce_PlatformDefs.h" | |||||
#include "juce_CompilerSupport.h" | #include "juce_CompilerSupport.h" | ||||
#include "juce_PlatformDefs.h" | |||||
//============================================================================== | //============================================================================== | ||||
// Now we'll include some common OS headers.. | // Now we'll include some common OS headers.. | ||||
@@ -108,6 +108,16 @@ bool CharacterFunctions::isLetterOrDigit (const juce_wchar character) noexcept | |||||
return iswalnum ((wint_t) character) != 0; | 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 | int CharacterFunctions::getHexDigitValue (const juce_wchar digit) noexcept | ||||
{ | { | ||||
unsigned int d = (unsigned int) digit - '0'; | unsigned int d = (unsigned int) digit - '0'; | ||||
@@ -110,6 +110,16 @@ public: | |||||
/** Checks whether a character is alphabetic or numeric. */ | /** Checks whether a character is alphabetic or numeric. */ | ||||
static bool isLetterOrDigit (juce_wchar character) noexcept; | 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. */ | /** 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; | static int getHexDigitValue (juce_wchar digit) noexcept; | ||||
@@ -430,7 +430,22 @@ namespace NumberToStringConverters | |||||
return t; | 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); | 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 unsigned short number) : text (NumberToStringConverters::createFromInteger ((unsigned int) number)) {} | ||||
String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} | String::String (const int64 number) : text (NumberToStringConverters::createFromInteger (number)) {} | ||||
String::String (const uint64 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 float number) : text (NumberToStringConverters::createFromDouble ((double) number, 0)) {} | ||||
String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} | String::String (const double number) : text (NumberToStringConverters::createFromDouble (number, 0)) {} | ||||
@@ -795,34 +812,29 @@ String& String::operator+= (const juce_wchar ch) | |||||
} | } | ||||
#endif | #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 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; } | 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 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 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 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 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 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) | 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 | #if JUCE_UNIT_TESTS | ||||
#define STRINGIFY2(X) #X | |||||
#define STRINGIFY(X) STRINGIFY2(X) | |||||
class StringTests : public UnitTest | class StringTests : public UnitTest | ||||
{ | { | ||||
public: | public: | ||||
@@ -2325,6 +2340,116 @@ public: | |||||
s2 << StringRef ("def"); | s2 << StringRef ("def"); | ||||
expect (s2 == "1234567890xyz123123def"); | 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"); | beginTest ("Numeric conversions"); | ||||
expect (String::empty.getIntValue() == 0); | expect (String::empty.getIntValue() == 0); | ||||
expect (String::empty.getDoubleValue() == 0.0); | expect (String::empty.getDoubleValue() == 0.0); | ||||
@@ -210,7 +210,11 @@ public: | |||||
/** Appends a decimal number at the end of this string. */ | /** Appends a decimal number at the end of this string. */ | ||||
String& operator+= (int numberToAppend); | String& operator+= (int numberToAppend); | ||||
/** Appends a decimal number at the end of this string. */ | /** 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); | 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. */ | /** Appends a character at the end of this string. */ | ||||
String& operator+= (char characterToAppend); | String& operator+= (char characterToAppend); | ||||
/** Appends a character at the end of this string. */ | /** Appends a character at the end of this string. */ | ||||
@@ -937,6 +941,16 @@ public: | |||||
*/ | */ | ||||
explicit String (uint64 largeIntegerValue); | 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. | /** Creates a string representing this floating-point number. | ||||
@param floatValue the value to convert to a string | @param floatValue the value to convert to a string | ||||
@see getDoubleValue, getIntValue | @see getDoubleValue, getIntValue | ||||
@@ -173,6 +173,12 @@ void StringArray::addArray (const StringArray& otherArray, int startIndex, int n | |||||
strings.add (otherArray.strings.getReference (startIndex++)); | 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) | void StringArray::set (const int index, const String& newString) | ||||
{ | { | ||||
strings.set (index, newString); | strings.set (index, newString); | ||||
@@ -209,6 +209,15 @@ public: | |||||
int startIndex = 0, | int startIndex = 0, | ||||
int numElementsToAdd = -1); | 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. | /** Breaks up a string into tokens and adds them to this array. | ||||
This will tokenise the given string using whitespace characters as the | This will tokenise the given string using whitespace characters as the | ||||
@@ -157,6 +157,47 @@ public: | |||||
expect (result, failureMessage); | 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. | /** Writes a message to the test log. | ||||
This can only be called from within your runTest() method. | This can only be called from within your runTest() method. | ||||
@@ -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)) | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (JNIEnv* env, jobject activity, jlong value)) | ||||
{ | { | ||||
setEnv (env); | |||||
JUCE_TRY | JUCE_TRY | ||||
{ | { | ||||
MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; | MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; | ||||
@@ -165,7 +165,7 @@ public: | |||||
} | } | ||||
AndroidTypeface (const void* data, size_t size) | AndroidTypeface (const void* data, size_t size) | ||||
: Typeface (String(), String()) | |||||
: Typeface (String (static_cast<uint64> (reinterpret_cast<uintptr_t> (data))), String()) | |||||
{ | { | ||||
JNIEnv* const env = getEnv(); | JNIEnv* const env = getEnv(); | ||||
@@ -2384,7 +2384,7 @@ void Component::internalMouseEnter (MouseInputSource source, Point<float> relati | |||||
BailOutChecker checker (this); | 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); | this, this, time, relativePos, time, 0, false); | ||||
mouseEnter (me); | mouseEnter (me); | ||||
@@ -2403,7 +2403,7 @@ void Component::internalMouseExit (MouseInputSource source, Point<float> relativ | |||||
BailOutChecker checker (this); | 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); | this, this, time, relativePos, time, 0, false); | ||||
mouseExit (me); | mouseExit (me); | ||||
@@ -2416,7 +2416,7 @@ void Component::internalMouseExit (MouseInputSource source, Point<float> relativ | |||||
MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseExit, me); | 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(); | Desktop& desktop = Desktop::getInstance(); | ||||
BailOutChecker checker (this); | BailOutChecker checker (this); | ||||
@@ -2435,7 +2435,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||||
{ | { | ||||
// allow blocked mouse-events to go to global listeners.. | // allow blocked mouse-events to go to global listeners.. | ||||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | ||||
this, this, time, relativePos, time, | |||||
pressure, this, this, time, relativePos, time, | |||||
source.getNumberOfMultipleClicks(), false); | source.getNumberOfMultipleClicks(), false); | ||||
desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); | desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseDown, me); | ||||
@@ -2468,7 +2468,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||||
repaint(); | repaint(); | ||||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | const MouseEvent me (source, relativePos, source.getCurrentModifiers(), | ||||
this, this, time, relativePos, time, | |||||
pressure, this, this, time, relativePos, time, | |||||
source.getNumberOfMultipleClicks(), false); | source.getNumberOfMultipleClicks(), false); | ||||
mouseDown (me); | mouseDown (me); | ||||
@@ -2492,7 +2492,7 @@ void Component::internalMouseUp (MouseInputSource source, Point<float> relativeP | |||||
repaint(); | repaint(); | ||||
const MouseEvent me (source, relativePos, | const MouseEvent me (source, relativePos, | ||||
oldModifiers, this, this, time, | |||||
oldModifiers, MouseInputSource::invalidPressure, this, this, time, | |||||
getLocalPoint (nullptr, source.getLastMouseDownPosition()), | getLocalPoint (nullptr, source.getLastMouseDownPosition()), | ||||
source.getLastMouseDownTime(), | source.getLastMouseDownTime(), | ||||
source.getNumberOfMultipleClicks(), | 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()) | if (! isCurrentlyBlockedByAnotherModalComponent()) | ||||
{ | { | ||||
BailOutChecker checker (this); | 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()), | getLocalPoint (nullptr, source.getLastMouseDownPosition()), | ||||
source.getLastMouseDownTime(), | source.getLastMouseDownTime(), | ||||
source.getNumberOfMultipleClicks(), | source.getNumberOfMultipleClicks(), | ||||
@@ -2559,7 +2559,7 @@ void Component::internalMouseMove (MouseInputSource source, Point<float> relativ | |||||
{ | { | ||||
BailOutChecker checker (this); | 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); | this, this, time, relativePos, time, 0, false); | ||||
mouseMove (me); | mouseMove (me); | ||||
@@ -2578,7 +2578,7 @@ void Component::internalMouseWheel (MouseInputSource source, Point<float> relati | |||||
Desktop& desktop = Desktop::getInstance(); | Desktop& desktop = Desktop::getInstance(); | ||||
BailOutChecker checker (this); | 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); | this, this, time, relativePos, time, 0, false); | ||||
if (isCurrentlyBlockedByAnotherModalComponent()) | if (isCurrentlyBlockedByAnotherModalComponent()) | ||||
@@ -2605,7 +2605,7 @@ void Component::internalMagnifyGesture (MouseInputSource source, Point<float> re | |||||
{ | { | ||||
if (! isCurrentlyBlockedByAnotherModalComponent()) | 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); | this, this, time, relativePos, time, 0, false); | ||||
mouseMagnify (me, amount); | mouseMagnify (me, amount); | ||||
@@ -2300,9 +2300,9 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
void internalMouseEnter (MouseInputSource, Point<float>, Time); | void internalMouseEnter (MouseInputSource, Point<float>, Time); | ||||
void internalMouseExit (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 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 internalMouseMove (MouseInputSource, Point<float>, Time); | ||||
void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&); | void internalMouseWheel (MouseInputSource, Point<float>, Time, const MouseWheelDetails&); | ||||
void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float); | void internalMagnifyGesture (MouseInputSource, Point<float>, Time, float); | ||||
@@ -92,7 +92,7 @@ LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept | |||||
if (currentLookAndFeel == nullptr) | if (currentLookAndFeel == nullptr) | ||||
{ | { | ||||
if (defaultLookAndFeel == nullptr) | if (defaultLookAndFeel == nullptr) | ||||
defaultLookAndFeel = new LookAndFeel_V2(); | |||||
defaultLookAndFeel = new LookAndFeel_V3(); | |||||
currentLookAndFeel = defaultLookAndFeel; | currentLookAndFeel = defaultLookAndFeel; | ||||
} | } | ||||
@@ -246,7 +246,7 @@ void Desktop::sendMouseMove() | |||||
const Time now (Time::getCurrentTime()); | const Time now (Time::getCurrentTime()); | ||||
const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::getCurrentModifiers(), | 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()) | if (me.mods.isAnyMouseButtonDown()) | ||||
mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); | mouseListeners.callChecked (checker, &MouseListener::mouseDrag, me); | ||||
@@ -25,6 +25,7 @@ | |||||
MouseEvent::MouseEvent (MouseInputSource inputSource, | MouseEvent::MouseEvent (MouseInputSource inputSource, | ||||
Point<float> pos, | Point<float> pos, | ||||
ModifierKeys modKeys, | ModifierKeys modKeys, | ||||
float force, | |||||
Component* const eventComp, | Component* const eventComp, | ||||
Component* const originator, | Component* const originator, | ||||
Time time, | Time time, | ||||
@@ -36,6 +37,7 @@ MouseEvent::MouseEvent (MouseInputSource inputSource, | |||||
x (roundToInt (pos.x)), | x (roundToInt (pos.x)), | ||||
y (roundToInt (pos.y)), | y (roundToInt (pos.y)), | ||||
mods (modKeys), | mods (modKeys), | ||||
pressure (force), | |||||
eventComponent (eventComp), | eventComponent (eventComp), | ||||
originalComponent (originator), | originalComponent (originator), | ||||
eventTime (time), | eventTime (time), | ||||
@@ -57,22 +59,22 @@ MouseEvent MouseEvent::getEventRelativeTo (Component* const otherComponent) cons | |||||
jassert (otherComponent != nullptr); | jassert (otherComponent != nullptr); | ||||
return MouseEvent (source, otherComponent->getLocalPoint (eventComponent, position), | return MouseEvent (source, otherComponent->getLocalPoint (eventComponent, position), | ||||
mods, otherComponent, originalComponent, eventTime, | |||||
mods, pressure, otherComponent, originalComponent, eventTime, | |||||
otherComponent->getLocalPoint (eventComponent, mouseDownPos), | otherComponent->getLocalPoint (eventComponent, mouseDownPos), | ||||
mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); | mouseDownTime, numberOfClicks, wasMovedSinceMouseDown != 0); | ||||
} | } | ||||
MouseEvent MouseEvent::withNewPosition (Point<float> newPosition) const noexcept | 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); | numberOfClicks, wasMovedSinceMouseDown != 0); | ||||
} | } | ||||
MouseEvent MouseEvent::withNewPosition (Point<int> newPosition) const noexcept | 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); | numberOfClicks, wasMovedSinceMouseDown != 0); | ||||
} | } | ||||
@@ -112,6 +114,8 @@ int MouseEvent::getScreenY() const { return getScre | |||||
int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().x; } | int MouseEvent::getMouseDownScreenX() const { return getMouseDownScreenPosition().x; } | ||||
int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().y; } | int MouseEvent::getMouseDownScreenY() const { return getMouseDownScreenPosition().y; } | ||||
bool MouseEvent::isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } | |||||
//============================================================================== | //============================================================================== | ||||
static int doubleClickTimeOutMs = 400; | static int doubleClickTimeOutMs = 400; | ||||
@@ -44,6 +44,9 @@ public: | |||||
@param source the source that's invoking the event | @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 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 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 eventComponent the component that the mouse event applies to | ||||
@param originator the component that originally received the event | @param originator the component that originally received the event | ||||
@param eventTime the time the event happened | @param eventTime the time the event happened | ||||
@@ -59,6 +62,7 @@ public: | |||||
MouseEvent (MouseInputSource source, | MouseEvent (MouseInputSource source, | ||||
Point<float> position, | Point<float> position, | ||||
ModifierKeys modifiers, | ModifierKeys modifiers, | ||||
float pressure, | |||||
Component* eventComponent, | Component* eventComponent, | ||||
Component* originator, | Component* originator, | ||||
Time eventTime, | Time eventTime, | ||||
@@ -109,6 +113,13 @@ public: | |||||
*/ | */ | ||||
const ModifierKeys mods; | 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. | /** The component that this event applies to. | ||||
This is usually the component that the mouse was over at the time, but for mouse-drag | 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; | 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. | /** The position of the mouse when the event occurred. | ||||
@@ -27,7 +27,7 @@ class MouseInputSourceInternal : private AsyncUpdater | |||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
MouseInputSourceInternal (const int i, const bool isMouse) | MouseInputSourceInternal (const int i, const bool isMouse) | ||||
: index (i), isMouseDevice (isMouse), | |||||
: index (i), isMouseDevice (isMouse), pressure (0.0f), | |||||
isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), | isUnboundedMouseModeOn (false), isCursorVisibleUntilOffscreen (false), | ||||
lastPeer (nullptr), currentCursorHandle (nullptr), | lastPeer (nullptr), currentCursorHandle (nullptr), | ||||
mouseEventCounter (0), mouseMovedSignificantlySincePressed (false) | mouseEventCounter (0), mouseMovedSignificantlySincePressed (false) | ||||
@@ -40,17 +40,17 @@ public: | |||||
return buttonState.isAnyMouseButtonDown(); | return buttonState.isAnyMouseButtonDown(); | ||||
} | } | ||||
Component* getComponentUnderMouse() const | |||||
Component* getComponentUnderMouse() const noexcept | |||||
{ | { | ||||
return componentUnderMouse.get(); | return componentUnderMouse.get(); | ||||
} | } | ||||
ModifierKeys getCurrentModifiers() const | |||||
ModifierKeys getCurrentModifiers() const noexcept | |||||
{ | { | ||||
return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); | return ModifierKeys::getCurrentModifiers().withoutMouseButtons().withFlags (buttonState.getRawFlags()); | ||||
} | } | ||||
ComponentPeer* getPeer() | |||||
ComponentPeer* getPeer() noexcept | |||||
{ | { | ||||
if (! ComponentPeer::isValidPeer (lastPeer)) | if (! ComponentPeer::isValidPeer (lastPeer)) | ||||
lastPeer = nullptr; | lastPeer = nullptr; | ||||
@@ -102,6 +102,8 @@ public: | |||||
MouseInputSource::setRawMousePosition (ScalingHelpers::scaledScreenPosToUnscaled (p)); | MouseInputSource::setRawMousePosition (ScalingHelpers::scaledScreenPosToUnscaled (p)); | ||||
} | } | ||||
bool isPressureValid() const noexcept { return pressure > 0.0f && pressure < 1.0f; } | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_DUMP_MOUSE_EVENTS | #if JUCE_DUMP_MOUSE_EVENTS | ||||
#define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ | #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ | ||||
@@ -132,13 +134,13 @@ public: | |||||
void sendMouseDown (Component& comp, Point<float> screenPos, Time time) | void sendMouseDown (Component& comp, Point<float> screenPos, Time time) | ||||
{ | { | ||||
JUCE_MOUSE_EVENT_DBG ("down") | 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) | void sendMouseDrag (Component& comp, Point<float> screenPos, Time time) | ||||
{ | { | ||||
JUCE_MOUSE_EVENT_DBG ("drag") | 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) | 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; | lastTime = time; | ||||
const bool pressureChanged = (pressure != newPressure); | |||||
pressure = newPressure; | |||||
++mouseEventCounter; | ++mouseEventCounter; | ||||
const Point<float> screenPos (newPeer.localToGlobal (positionWithinPeer)); | const Point<float> screenPos (newPeer.localToGlobal (positionWithinPeer)); | ||||
if (isDragging() && newMods.isAnyMouseButtonDown()) | if (isDragging() && newMods.isAnyMouseButtonDown()) | ||||
{ | { | ||||
setScreenPos (screenPos, time, false); | |||||
setScreenPos (screenPos, time, pressureChanged); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -307,8 +312,9 @@ public: | |||||
return; // some modal events have been dispatched, so the current event is now out-of-date | return; // some modal events have been dispatched, so the current event is now out-of-date | ||||
peer = getPeer(); | peer = getPeer(); | ||||
if (peer != nullptr) | if (peer != nullptr) | ||||
setScreenPos (screenPos, time, false); | |||||
setScreenPos (screenPos, time, pressureChanged); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -470,6 +476,7 @@ public: | |||||
const bool isMouseDevice; | const bool isMouseDevice; | ||||
Point<float> lastScreenPos, unboundedMouseOffset; // NB: these are unscaled coords | Point<float> lastScreenPos, unboundedMouseOffset; // NB: these are unscaled coords | ||||
ModifierKeys buttonState; | ModifierKeys buttonState; | ||||
float pressure; | |||||
bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; | bool isUnboundedMouseModeOn, isCursorVisibleUntilOffscreen; | ||||
@@ -542,14 +549,16 @@ MouseInputSource& MouseInputSource::operator= (const MouseInputSource& other) no | |||||
return *this; | 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(); } | Component* MouseInputSource::getComponentUnderMouse() const { return pimpl->getComponentUnderMouse(); } | ||||
void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } | void MouseInputSource::triggerFakeMove() const { pimpl->triggerFakeMove(); } | ||||
int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } | int MouseInputSource::getNumberOfMultipleClicks() const noexcept { return pimpl->getNumberOfMultipleClicks(); } | ||||
@@ -567,9 +576,9 @@ void MouseInputSource::revealCursor() { pimpl | |||||
void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } | void MouseInputSource::forceMouseCursorUpdate() { pimpl->revealCursor (true); } | ||||
void MouseInputSource::setScreenPosition (Point<float> p) { pimpl->setScreenPosition (p); } | 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) | 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); | pimpl->handleMagnifyGesture (peer, pos, Time (time), scaleFactor); | ||||
} | } | ||||
const float MouseInputSource::invalidPressure = 0.0f; | |||||
//============================================================================== | //============================================================================== | ||||
struct MouseInputSource::SourceList : public Timer | struct MouseInputSource::SourceList : public Timer | ||||
{ | { | ||||
@@ -60,18 +60,18 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns true if this object represents a normal desk-based mouse device. */ | /** 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. */ | /** 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 | /** Returns true if this source has an on-screen pointer that can hover over | ||||
items without clicking them. | items without clicking them. | ||||
*/ | */ | ||||
bool canHover() const; | |||||
bool canHover() const noexcept; | |||||
/** Returns true if this source may have a scroll wheel. */ | /** 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. | /** 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 | 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 | number 0, and then if a second touch happens while the first is still down, it | ||||
will have index 1, etc. | will have index 1, etc. | ||||
*/ | */ | ||||
int getIndex() const; | |||||
int getIndex() const noexcept; | |||||
/** Returns true if this device is currently being pressed. */ | /** 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. */ | /** 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 | /** Returns a set of modifiers that indicate which buttons are currently | ||||
held down on this device. | 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. */ | /** Returns the component that was last known to be under this pointer. */ | ||||
Component* getComponentUnderMouse() const; | Component* getComponentUnderMouse() const; | ||||
@@ -164,6 +174,11 @@ public: | |||||
/** Attempts to set this mouse pointer's screen position. */ | /** Attempts to set this mouse pointer's screen position. */ | ||||
void setScreenPosition (Point<float> newPosition); | 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: | private: | ||||
//============================================================================== | //============================================================================== | ||||
friend class ComponentPeer; | friend class ComponentPeer; | ||||
@@ -174,7 +189,7 @@ private: | |||||
struct SourceList; | struct SourceList; | ||||
explicit MouseInputSource (MouseInputSourceInternal*) noexcept; | 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 handleWheel (ComponentPeer&, Point<float>, int64 time, const MouseWheelDetails&); | ||||
void handleMagnifyGesture (ComponentPeer&, Point<float>, int64 time, float scaleFactor); | 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, | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, | ||||
jstring appFile, jstring appDataDir)) | jstring appFile, jstring appDataDir)) | ||||
{ | { | ||||
setEnv (env); | |||||
android.initialise (env, activity, appFile, appDataDir); | android.initialise (env, activity, appFile, appDataDir); | ||||
DBG (SystemStats::getJUCEVersion()); | 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)) | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject activity)) | ||||
{ | { | ||||
setEnv (env); | |||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | ||||
app->suspended(); | app->suspended(); | ||||
} | } | ||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject activity)) | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject activity)) | ||||
{ | { | ||||
setEnv (env); | |||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | ||||
app->resumed(); | app->resumed(); | ||||
} | } | ||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity)) | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject activity)) | ||||
{ | { | ||||
setEnv (env); | |||||
JUCEApplicationBase::appWillTerminateByForce(); | JUCEApplicationBase::appWillTerminateByForce(); | ||||
android.shutdown (env); | android.shutdown (env); | ||||
@@ -98,7 +106,6 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); | |||||
METHOD (invalidate, "invalidate", "(IIII)V") \ | METHOD (invalidate, "invalidate", "(IIII)V") \ | ||||
METHOD (containsPoint, "containsPoint", "(II)Z") \ | METHOD (containsPoint, "containsPoint", "(II)Z") \ | ||||
METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | ||||
METHOD (createGLView, "createGLView", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;") \ | |||||
DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | ||||
#undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
@@ -332,7 +339,7 @@ public: | |||||
lastMousePos = pos; | lastMousePos = pos; | ||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | // 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)) | if (isValidPeer (this)) | ||||
handleMouseDragCallback (index, sysPos, time); | handleMouseDragCallback (index, sysPos, time); | ||||
@@ -346,8 +353,8 @@ public: | |||||
jassert (index < 64); | jassert (index < 64); | ||||
touchesDown = (touchesDown | (1 << (index & 63))); | touchesDown = (touchesDown | (1 << (index & 63))); | ||||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); | 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) | void handleMouseUpCallback (int index, Point<float> pos, int64 time) | ||||
@@ -361,7 +368,7 @@ public: | |||||
if (touchesDown == 0) | if (touchesDown == 0) | ||||
currentModifiers = currentModifiers.withoutMouseButtons(); | currentModifiers = currentModifiers.withoutMouseButtons(); | ||||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), time); | |||||
handleMouseEvent (index, pos, currentModifiers.withoutMouseButtons(), MouseInputSource::invalidPressure, time); | |||||
} | } | ||||
void handleKeyDownCallback (int k, int kc) | void handleKeyDownCallback (int k, int kc) | ||||
@@ -582,6 +589,7 @@ int64 AndroidComponentPeer::touchesDown = 0; | |||||
#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ | #define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ | ||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ | JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ | ||||
{ \ | { \ | ||||
setEnv (env); \ | |||||
if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \ | if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \ | ||||
peer->juceMethodInvocation; \ | peer->juceMethodInvocation; \ | ||||
} | } | ||||
@@ -601,12 +609,6 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void*) | |||||
return new AndroidComponentPeer (*this, styleFlags); | 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 | 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, | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject activity, | ||||
jlong callbackAsLong, jint result)) | jlong callbackAsLong, jint result)) | ||||
{ | { | ||||
setEnv (env); | |||||
if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong) | if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong) | ||||
{ | { | ||||
callback->modalStateFinished (result); | callback->modalStateFinished (result); | ||||
@@ -748,6 +752,8 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv | |||||
jint screenWidth, jint screenHeight, | jint screenWidth, jint screenHeight, | ||||
jint dpi)) | jint dpi)) | ||||
{ | { | ||||
setEnv (env); | |||||
android.screenWidth = screenWidth; | android.screenWidth = screenWidth; | ||||
android.screenHeight = screenHeight; | android.screenHeight = screenHeight; | ||||
android.dpi = dpi; | android.dpi = dpi; | ||||
@@ -767,7 +767,11 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||||
{ | { | ||||
UITouch* touch = [touches objectAtIndex: i]; | 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) | if ([touch phase] == UITouchPhaseStationary) | ||||
#endif | |||||
continue; | continue; | ||||
CGPoint p = [touch locationInView: view]; | CGPoint p = [touch locationInView: view]; | ||||
@@ -788,7 +792,9 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||||
modsToSend = currentModifiers; | modsToSend = currentModifiers; | ||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | // 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) | if (! isValidPeer (this)) // (in case this component was deleted by the event) | ||||
return; | return; | ||||
} | } | ||||
@@ -810,13 +816,24 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||||
modsToSend = currentModifiers = currentModifiers.withoutMouseButtons(); | 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) | if (! isValidPeer (this)) // (in case this component was deleted by the event) | ||||
return; | return; | ||||
if (isUp || isCancel) | 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)) | if (! isValidPeer (this)) | ||||
return; | return; | ||||
} | } | ||||
@@ -24,6 +24,15 @@ | |||||
extern bool isIOSAppActive; | 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) | } // (juce namespace) | ||||
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate> | @interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate> | ||||
@@ -89,6 +98,9 @@ extern bool isIOSAppActive; | |||||
{ | { | ||||
ignoreUnused (application); | ignoreUnused (application); | ||||
isIOSAppActive = false; | isIOSAppActive = false; | ||||
for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) | |||||
appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive(); | |||||
} | } | ||||
@end | @end | ||||
@@ -2215,7 +2215,8 @@ public: | |||||
{ | { | ||||
currentModifiers = currentModifiers.withFlags (buttonModifierFlag); | currentModifiers = currentModifiers.withFlags (buttonModifierFlag); | ||||
toFront (true); | toFront (true); | ||||
handleMouseEvent (0, getMousePos (buttonPressEvent), currentModifiers, getEventTime (buttonPressEvent)); | |||||
handleMouseEvent (0, getMousePos (buttonPressEvent), currentModifiers, | |||||
MouseInputSource::invalidPressure, getEventTime (buttonPressEvent)); | |||||
} | } | ||||
void handleButtonPressEvent (const XButtonPressedEvent& buttonPressEvent) | void handleButtonPressEvent (const XButtonPressedEvent& buttonPressEvent) | ||||
@@ -2253,7 +2254,8 @@ public: | |||||
if (dragState.dragging) | if (dragState.dragging) | ||||
handleExternalDragButtonReleaseEvent(); | handleExternalDragButtonReleaseEvent(); | ||||
handleMouseEvent (0, getMousePos (buttonRelEvent), currentModifiers, getEventTime (buttonRelEvent)); | |||||
handleMouseEvent (0, getMousePos (buttonRelEvent), currentModifiers, | |||||
MouseInputSource::invalidPressure, getEventTime (buttonRelEvent)); | |||||
clearLastMousePos(); | clearLastMousePos(); | ||||
} | } | ||||
@@ -2267,7 +2269,8 @@ public: | |||||
if (dragState.dragging) | if (dragState.dragging) | ||||
handleExternalDragMotionNotify(); | handleExternalDragMotionNotify(); | ||||
handleMouseEvent (0, getMousePos (movedEvent), currentModifiers, getEventTime (movedEvent)); | |||||
handleMouseEvent (0, getMousePos (movedEvent), currentModifiers, | |||||
MouseInputSource::invalidPressure, getEventTime (movedEvent)); | |||||
} | } | ||||
void handleEnterNotifyEvent (const XEnterWindowEvent& enterEvent) | void handleEnterNotifyEvent (const XEnterWindowEvent& enterEvent) | ||||
@@ -2280,7 +2283,8 @@ public: | |||||
if (! currentModifiers.isAnyMouseButtonDown()) | if (! currentModifiers.isAnyMouseButtonDown()) | ||||
{ | { | ||||
updateKeyModifiers ((int) enterEvent.state); | updateKeyModifiers ((int) enterEvent.state); | ||||
handleMouseEvent (0, getMousePos (enterEvent), currentModifiers, getEventTime (enterEvent)); | |||||
handleMouseEvent (0, getMousePos (enterEvent), currentModifiers, | |||||
MouseInputSource::invalidPressure, getEventTime (enterEvent)); | |||||
} | } | ||||
} | } | ||||
@@ -2293,7 +2297,8 @@ public: | |||||
|| leaveEvent.mode == NotifyUngrab) | || leaveEvent.mode == NotifyUngrab) | ||||
{ | { | ||||
updateKeyModifiers ((int) leaveEvent.state); | updateKeyModifiers ((int) leaveEvent.state); | ||||
handleMouseEvent (0, getMousePos (leaveEvent), currentModifiers, getEventTime (leaveEvent)); | |||||
handleMouseEvent (0, getMousePos (leaveEvent), currentModifiers, | |||||
MouseInputSource::invalidPressure, getEventTime (leaveEvent)); | |||||
} | } | ||||
} | } | ||||
@@ -587,7 +587,8 @@ public: | |||||
sendMouseEvent (ev); | sendMouseEvent (ev); | ||||
else | else | ||||
// moved into another window which overlaps this one, so trigger an exit | // moved into another window which overlaps this one, so trigger an exit | ||||
handleMouseEvent (0, Point<float> (-1.0f, -1.0f), currentModifiers, getMouseTime (ev)); | |||||
handleMouseEvent (0, Point<float> (-1.0f, -1.0f), currentModifiers, | |||||
getMousePressure (ev), getMouseTime (ev)); | |||||
showArrowCursorIfNeeded(); | showArrowCursorIfNeeded(); | ||||
} | } | ||||
@@ -678,7 +679,8 @@ public: | |||||
void sendMouseEvent (NSEvent* ev) | void sendMouseEvent (NSEvent* ev) | ||||
{ | { | ||||
updateModifiers (ev); | updateModifiers (ev); | ||||
handleMouseEvent (0, getMousePos (ev, view), currentModifiers, getMouseTime (ev)); | |||||
handleMouseEvent (0, getMousePos (ev, view), currentModifiers, | |||||
getMousePressure (ev), getMouseTime (ev)); | |||||
} | } | ||||
bool handleKeyEvent (NSEvent* ev, bool isKeyDown) | bool handleKeyEvent (NSEvent* ev, bool isKeyDown) | ||||
@@ -1080,10 +1082,23 @@ public: | |||||
return keyCode; | return keyCode; | ||||
} | } | ||||
static int64 getMouseTime (NSEvent* e) | |||||
static int64 getMouseTime (NSEvent* e) noexcept | |||||
{ | { | ||||
return (Time::currentTimeMillis() - Time::getMillisecondCounter()) | return (Time::currentTimeMillis() - Time::getMillisecondCounter()) | ||||
+ (int64) ([e timestamp] * 1000.0); | |||||
+ (int64) ([e timestamp] * 1000.0); | |||||
} | |||||
static float getMousePressure (NSEvent* e) noexcept | |||||
{ | |||||
@try | |||||
{ | |||||
if (e.type != NSMouseEntered && e.type != NSMouseExited) | |||||
return (float) e.pressure; | |||||
} | |||||
@catch (NSException* e) {} | |||||
@finally {} | |||||
return 0.0f; | |||||
} | } | ||||
static Point<float> getMousePos (NSEvent* e, NSView* view) | static Point<float> getMousePos (NSEvent* e, NSView* view) | ||||
@@ -1663,9 +1663,9 @@ private: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void doMouseEvent (Point<float> position) | |||||
void doMouseEvent (Point<float> position, float pressure) | |||||
{ | { | ||||
handleMouseEvent (0, position, currentModifiers, getMouseEventTime()); | |||||
handleMouseEvent (0, position, currentModifiers, pressure, getMouseEventTime()); | |||||
} | } | ||||
StringArray getAvailableRenderingEngines() override | StringArray getAvailableRenderingEngines() override | ||||
@@ -1760,7 +1760,7 @@ private: | |||||
if (now >= lastMouseTime + minTimeBetweenMouses) | if (now >= lastMouseTime + minTimeBetweenMouses) | ||||
{ | { | ||||
lastMouseTime = now; | lastMouseTime = now; | ||||
doMouseEvent (position); | |||||
doMouseEvent (position, MouseInputSource::invalidPressure); | |||||
} | } | ||||
} | } | ||||
@@ -1780,7 +1780,7 @@ private: | |||||
updateModifiersFromWParam (wParam); | updateModifiersFromWParam (wParam); | ||||
isDragging = true; | isDragging = true; | ||||
doMouseEvent (position); | |||||
doMouseEvent (position, MouseInputSource::invalidPressure); | |||||
} | } | ||||
} | } | ||||
@@ -1801,7 +1801,7 @@ private: | |||||
// NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can | // NB: under some circumstances (e.g. double-clicking a native title bar), a mouse-up can | ||||
// arrive without a mouse-down, so in that case we need to avoid sending a message. | // arrive without a mouse-down, so in that case we need to avoid sending a message. | ||||
if (wasDragging) | if (wasDragging) | ||||
doMouseEvent (position); | |||||
doMouseEvent (position, MouseInputSource::invalidPressure); | |||||
} | } | ||||
void doCaptureChanged() | void doCaptureChanged() | ||||
@@ -1821,7 +1821,7 @@ private: | |||||
void doMouseExit() | void doMouseExit() | ||||
{ | { | ||||
isMouseOver = false; | isMouseOver = false; | ||||
doMouseEvent (getCurrentMousePos()); | |||||
doMouseEvent (getCurrentMousePos(), MouseInputSource::invalidPressure); | |||||
} | } | ||||
ComponentPeer* findPeerUnderMouse (Point<float>& localPos) | ComponentPeer* findPeerUnderMouse (Point<float>& localPos) | ||||
@@ -1925,6 +1925,7 @@ private: | |||||
const int64 time = getMouseEventTime(); | const int64 time = getMouseEventTime(); | ||||
const Point<float> pos (globalToLocal (Point<float> (static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.x)), | const Point<float> pos (globalToLocal (Point<float> (static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.x)), | ||||
static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.y))))); | static_cast<float> (TOUCH_COORD_TO_PIXEL (touch.y))))); | ||||
const float pressure = MouseInputSource::invalidPressure; | |||||
ModifierKeys modsToSend (currentModifiers); | ModifierKeys modsToSend (currentModifiers); | ||||
if (isDown) | if (isDown) | ||||
@@ -1932,9 +1933,10 @@ private: | |||||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); | currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); | ||||
modsToSend = currentModifiers; | modsToSend = currentModifiers; | ||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | |||||
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), time); | |||||
if (! isValidPeer (this)) // (in case this component was deleted by the event) | |||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | |||||
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend.withoutMouseButtons(), pressure, time); | |||||
if (! isValidPeer (this)) // (in case this component was deleted by the event) | |||||
return false; | return false; | ||||
} | } | ||||
else if (isUp) | else if (isUp) | ||||
@@ -1956,13 +1958,15 @@ private: | |||||
currentModifiers = currentModifiers.withoutMouseButtons(); | currentModifiers = currentModifiers.withoutMouseButtons(); | ||||
} | } | ||||
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, time); | |||||
handleMouseEvent (touchIndex, pos.toFloat(), modsToSend, pressure, time); | |||||
if (! isValidPeer (this)) // (in case this component was deleted by the event) | if (! isValidPeer (this)) // (in case this component was deleted by the event) | ||||
return false; | return false; | ||||
if (isUp || isCancel) | if (isUp || isCancel) | ||||
{ | { | ||||
handleMouseEvent (touchIndex, Point<float> (-10.0f, -10.0f), currentModifiers, time); | |||||
handleMouseEvent (touchIndex, Point<float> (-10.0f, -10.0f), currentModifiers, pressure, time); | |||||
if (! isValidPeer (this)) | if (! isValidPeer (this)) | ||||
return false; | return false; | ||||
} | } | ||||
@@ -2248,7 +2252,7 @@ private: | |||||
if (contains (pos.roundToInt(), false)) | if (contains (pos.roundToInt(), false)) | ||||
{ | { | ||||
doMouseEvent (pos); | |||||
doMouseEvent (pos, MouseInputSource::invalidPressure); | |||||
if (! isValidPeer (this)) | if (! isValidPeer (this)) | ||||
return true; | return true; | ||||
@@ -85,10 +85,10 @@ bool ComponentPeer::isKioskMode() const | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void ComponentPeer::handleMouseEvent (int touchIndex, Point<float> pos, ModifierKeys newMods, int64 time) | |||||
void ComponentPeer::handleMouseEvent (int touchIndex, Point<float> pos, ModifierKeys newMods, float newPressure, int64 time) | |||||
{ | { | ||||
if (MouseInputSource* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (touchIndex)) | if (MouseInputSource* mouse = Desktop::getInstance().mouseSources->getOrCreateMouseInputSource (touchIndex)) | ||||
MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods); | |||||
MouseInputSource (*mouse).handleEvent (*this, pos, time, newMods, newPressure); | |||||
} | } | ||||
void ComponentPeer::handleMouseWheel (int touchIndex, Point<float> pos, int64 time, const MouseWheelDetails& wheel) | void ComponentPeer::handleMouseWheel (int touchIndex, Point<float> pos, int64 time, const MouseWheelDetails& wheel) | ||||
@@ -306,7 +306,7 @@ public: | |||||
virtual void setAlpha (float newAlpha) = 0; | virtual void setAlpha (float newAlpha) = 0; | ||||
//============================================================================== | //============================================================================== | ||||
void handleMouseEvent (int touchIndex, Point<float> positionWithinPeer, ModifierKeys newMods, int64 time); | |||||
void handleMouseEvent (int touchIndex, Point<float> positionWithinPeer, ModifierKeys newMods, float pressure, int64 time); | |||||
void handleMouseWheel (int touchIndex, Point<float> positionWithinPeer, int64 time, const MouseWheelDetails&); | void handleMouseWheel (int touchIndex, Point<float> positionWithinPeer, int64 time, const MouseWheelDetails&); | ||||
void handleMagnifyGesture (int touchIndex, Point<float> positionWithinPeer, int64 time, float scaleFactor); | void handleMagnifyGesture (int touchIndex, Point<float> positionWithinPeer, int64 time, float scaleFactor); | ||||
@@ -104,6 +104,7 @@ public: | |||||
const Time now (Time::getCurrentTime()); | const Time now (Time::getCurrentTime()); | ||||
MouseInputSource mouseSource = Desktop::getInstance().getMainMouseSource(); | MouseInputSource mouseSource = Desktop::getInstance().getMainMouseSource(); | ||||
const float pressure = (float) e.pressure; | |||||
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up | if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up | ||||
{ | { | ||||
@@ -113,17 +114,17 @@ public: | |||||
owner.mouseDown (MouseEvent (mouseSource, Point<float>(), | owner.mouseDown (MouseEvent (mouseSource, Point<float>(), | ||||
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier | eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier | ||||
: ModifierKeys::rightButtonModifier), | : ModifierKeys::rightButtonModifier), | ||||
&owner, &owner, now, | |||||
pressure, &owner, &owner, now, | |||||
Point<float>(), now, 1, false)); | Point<float>(), now, 1, false)); | ||||
owner.mouseUp (MouseEvent (mouseSource, Point<float>(), eventMods.withoutMouseButtons(), | owner.mouseUp (MouseEvent (mouseSource, Point<float>(), eventMods.withoutMouseButtons(), | ||||
&owner, &owner, now, | |||||
pressure, &owner, &owner, now, | |||||
Point<float>(), now, 1, false)); | Point<float>(), now, 1, false)); | ||||
} | } | ||||
else if (type == NSMouseMoved) | else if (type == NSMouseMoved) | ||||
{ | { | ||||
owner.mouseMove (MouseEvent (mouseSource, Point<float>(), eventMods, | owner.mouseMove (MouseEvent (mouseSource, Point<float>(), eventMods, | ||||
&owner, &owner, now, | |||||
pressure, &owner, &owner, now, | |||||
Point<float>(), now, 1, false)); | Point<float>(), now, 1, false)); | ||||
} | } | ||||
} | } | ||||
@@ -189,6 +189,7 @@ namespace ActiveXHelpers | |||||
peer->handleMouseEvent (0, Point<int> (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left, | peer->handleMouseEvent (0, Point<int> (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left, | ||||
GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top).toFloat(), | GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top).toFloat(), | ||||
ModifierKeys::getCurrentModifiersRealtime(), | ModifierKeys::getCurrentModifiersRealtime(), | ||||
MouseInputSource::invalidPressure, | |||||
getMouseEventTime()); | getMouseEventTime()); | ||||
break; | break; | ||||
@@ -111,8 +111,8 @@ public: | |||||
const Time eventTime (getMouseEventTime()); | const Time eventTime (getMouseEventTime()); | ||||
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), | const MouseEvent e (Desktop::getInstance().getMainMouseSource(), | ||||
Point<float>(), eventMods, &owner, &owner, eventTime, | |||||
Point<float>(), eventTime, 1, false); | |||||
Point<float>(), eventMods, MouseInputSource::invalidPressure, | |||||
&owner, &owner, eventTime, Point<float>(), eventTime, 1, false); | |||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN) | if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN) | ||||
{ | { | ||||
@@ -877,6 +877,12 @@ rtosc::Ports bankPorts = { | |||||
#undef rObject | #undef rObject | ||||
#define rObject MiddleWareImpl | #define rObject MiddleWareImpl | ||||
#ifndef STRINGIFY | |||||
#define STRINGIFY2(a) #a | |||||
#define STRINGIFY(a) STRINGIFY2(a) | |||||
#endif | |||||
/* | /* | ||||
* BASE/part#/kititem# | * BASE/part#/kititem# | ||||
* BASE/part#/kit#/adpars/voice#/oscil/\* | * BASE/part#/kit#/adpars/voice#/oscil/\* | ||||
@@ -885,15 +891,20 @@ rtosc::Ports bankPorts = { | |||||
* BASE/part#/kit#/padpars/oscil/\* | * BASE/part#/kit#/padpars/oscil/\* | ||||
*/ | */ | ||||
static rtosc::Ports middwareSnoopPorts = { | static rtosc::Ports middwareSnoopPorts = { | ||||
{"part#16/kit#8/adpars/VoicePar#8/OscilSmp/", 0, &OscilGen::non_realtime_ports, | |||||
{"part#" STRINGIFY(NUM_MIDI_PARTS) | |||||
"/kit#" STRINGIFY(NUM_KIT_ITEMS) "/adpars/VoicePar#" | |||||
STRINGIFY(NUM_VOICES) "/OscilSmp/", 0, &OscilGen::non_realtime_ports, | |||||
rBegin; | rBegin; | ||||
impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); | impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); | ||||
rEnd}, | rEnd}, | ||||
{"part#16/kit#8/adpars/VoicePar#8/FMSmp/", 0, &OscilGen::non_realtime_ports, | |||||
{"part#" STRINGIFY(NUM_MIDI_PARTS) | |||||
"/kit#" STRINGIFY(NUM_KIT_ITEMS) | |||||
"/adpars/VoicePar#" STRINGIFY(NUM_VOICES) "/FMSmp/", 0, &OscilGen::non_realtime_ports, | |||||
rBegin | rBegin | ||||
impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); | impl.obj_store.handleOscil(chomp(chomp(chomp(chomp(chomp(msg))))), d); | ||||
rEnd}, | rEnd}, | ||||
{"part#16/kit#8/padpars/", 0, &PADnoteParameters::non_realtime_ports, | |||||
{"part#" STRINGIFY(NUM_MIDI_PARTS) | |||||
"/kit#" STRINGIFY(NUM_KIT_ITEMS) "/padpars/", 0, &PADnoteParameters::non_realtime_ports, | |||||
rBegin | rBegin | ||||
impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d); | impl.obj_store.handlePad(chomp(chomp(chomp(msg))), d); | ||||
rEnd}, | rEnd}, | ||||
@@ -1062,6 +1073,10 @@ static rtosc::Ports middlewareReplyPorts = { | |||||
impl.loadPart(part, impl.master->bank.ins[program].filename.c_str(), impl.master); | impl.loadPart(part, impl.master->bank.ins[program].filename.c_str(), impl.master); | ||||
impl.uToB->write(("/part"+to_s(part)+"/Pname").c_str(), "s", impl.master->bank.ins[program].name.c_str()); | impl.uToB->write(("/part"+to_s(part)+"/Pname").c_str(), "s", impl.master->bank.ins[program].name.c_str()); | ||||
rEnd}, | rEnd}, | ||||
{"setbank:c", 0, 0, | |||||
rBegin; | |||||
impl.loadPendingBank(rtosc_argument(msg,0).i, impl.master->bank); | |||||
rEnd}, | |||||
{"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd}, | {"undo_pause:", 0, 0, rBegin; impl.recording_undo = false; rEnd}, | ||||
{"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd}, | {"undo_resume:", 0, 0, rBegin; impl.recording_undo = true; rEnd}, | ||||
{"undo_change", 0, 0, | {"undo_change", 0, 0, | ||||
@@ -70,7 +70,6 @@ const rtosc::Ports real_preset_ports = | |||||
assert(d.obj); | assert(d.obj); | ||||
std::string args = rtosc_argument_string(msg); | std::string args = rtosc_argument_string(msg); | ||||
d.reply(d.loc, "s", "clipboard paste..."); | d.reply(d.loc, "s", "clipboard paste..."); | ||||
printf("\nClipboard Paste...\n"); | |||||
if(args == "s") | if(args == "s") | ||||
presetPaste(mw, rtosc_argument(msg, 0).s, ""); | presetPaste(mw, rtosc_argument(msg, 0).s, ""); | ||||
else if(args == "ss") | else if(args == "ss") | ||||
@@ -146,7 +145,7 @@ class Capture:public rtosc::RtData | |||||
virtual void reply(const char *path, const char *args, ...) | virtual void reply(const char *path, const char *args, ...) | ||||
{ | { | ||||
printf("reply(%p)(%s)(%s)...\n", msgbuf, path, args); | |||||
//printf("reply(%p)(%s)(%s)...\n", msgbuf, path, args); | |||||
//printf("size is %d\n", sizeof(msgbuf)); | //printf("size is %d\n", sizeof(msgbuf)); | ||||
va_list va; | va_list va; | ||||
va_start(va,args); | va_start(va,args); | ||||
@@ -199,7 +198,7 @@ std::string doCopy(MiddleWare &mw, string url, string name) | |||||
mw.doReadOnlyOp([&xml, url, name, &mw](){ | mw.doReadOnlyOp([&xml, url, name, &mw](){ | ||||
Master *m = mw.spawnMaster(); | Master *m = mw.spawnMaster(); | ||||
//Get the pointer | //Get the pointer | ||||
printf("capture at <%s>\n", (url+"self").c_str()); | |||||
//printf("capture at <%s>\n", (url+"self").c_str()); | |||||
T *t = (T*)capture<void*>(m, url+"self"); | T *t = (T*)capture<void*>(m, url+"self"); | ||||
assert(t); | assert(t); | ||||
//Extract Via mxml | //Extract Via mxml | ||||
@@ -216,6 +215,10 @@ void doPaste(MiddleWare &mw, string url, string type, XMLwrapper &xml, Ts&&... a | |||||
//Generate a new object | //Generate a new object | ||||
T *t = new T(std::forward<Ts>(args)...); | T *t = new T(std::forward<Ts>(args)...); | ||||
//Old workaround for LFO parameters | |||||
if(strstr(type.c_str(), "Plfo")) | |||||
type = "Plfo"; | |||||
if(xml.enterbranch(type) == 0) | if(xml.enterbranch(type) == 0) | ||||
return; | return; | ||||
@@ -227,7 +230,7 @@ void doPaste(MiddleWare &mw, string url, string type, XMLwrapper &xml, Ts&&... a | |||||
rtosc_message(buffer, 1024, path.c_str(), "b", sizeof(void*), &t); | rtosc_message(buffer, 1024, path.c_str(), "b", sizeof(void*), &t); | ||||
if(!Master::ports.apropos(path.c_str())) | if(!Master::ports.apropos(path.c_str())) | ||||
fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | ||||
printf("Sending info to '%s'\n", buffer); | |||||
//printf("Sending info to '%s'\n", buffer); | |||||
mw.transmitMsg(buffer); | mw.transmitMsg(buffer); | ||||
//Let the pointer be reclaimed later | //Let the pointer be reclaimed later | ||||
@@ -237,7 +240,7 @@ template<class T> | |||||
std::string doArrayCopy(MiddleWare &mw, int field, string url, string name) | std::string doArrayCopy(MiddleWare &mw, int field, string url, string name) | ||||
{ | { | ||||
XMLwrapper xml; | XMLwrapper xml; | ||||
printf("Getting info from '%s'<%d>\n", url.c_str(), field); | |||||
//printf("Getting info from '%s'<%d>\n", url.c_str(), field); | |||||
mw.doReadOnlyOp([&xml, url, field, name, &mw](){ | mw.doReadOnlyOp([&xml, url, field, name, &mw](){ | ||||
Master *m = mw.spawnMaster(); | Master *m = mw.spawnMaster(); | ||||
//Get the pointer | //Get the pointer | ||||
@@ -270,7 +273,7 @@ void doArrayPaste(MiddleWare &mw, int field, string url, string type, | |||||
rtosc_message(buffer, 1024, path.c_str(), "bi", sizeof(void*), &t, field); | rtosc_message(buffer, 1024, path.c_str(), "bi", sizeof(void*), &t, field); | ||||
if(!Master::ports.apropos(path.c_str())) | if(!Master::ports.apropos(path.c_str())) | ||||
fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | fprintf(stderr, "Warning: Missing Paste URL: '%s'\n", path.c_str()); | ||||
printf("Sending info to '%s'<%d>\n", buffer, field); | |||||
//printf("Sending info to '%s'<%d>\n", buffer, field); | |||||
mw.transmitMsg(buffer); | mw.transmitMsg(buffer); | ||||
//Let the pointer be reclaimed later | //Let the pointer be reclaimed later | ||||
@@ -285,7 +288,7 @@ void doArrayPaste(MiddleWare &mw, int field, string url, string type, | |||||
*/ | */ | ||||
void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string url, XMLwrapper &data) | void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string url, XMLwrapper &data) | ||||
{ | { | ||||
printf("Class Paste\n"); | |||||
//printf("Class Paste\n"); | |||||
if(type == "EnvelopeParams") | if(type == "EnvelopeParams") | ||||
doPaste<EnvelopeParams>(mw, url, type_, data); | doPaste<EnvelopeParams>(mw, url, type_, data); | ||||
else if(type == "LFOParams") | else if(type == "LFOParams") | ||||
@@ -311,7 +314,7 @@ void doClassPaste(std::string type, std::string type_, MiddleWare &mw, string ur | |||||
std::string doClassCopy(std::string type, MiddleWare &mw, string url, string name) | std::string doClassCopy(std::string type, MiddleWare &mw, string url, string name) | ||||
{ | { | ||||
printf("doClassCopy(%p)\n", mw.spawnMaster()->uToB); | |||||
//printf("doClassCopy(%p)\n", mw.spawnMaster()->uToB); | |||||
if(type == "EnvelopeParams") | if(type == "EnvelopeParams") | ||||
return doCopy<EnvelopeParams>(mw, url, name); | return doCopy<EnvelopeParams>(mw, url, name); | ||||
else if(type == "LFOParams") | else if(type == "LFOParams") | ||||
@@ -361,14 +364,14 @@ std::string getUrlPresetType(std::string url, MiddleWare &mw) | |||||
//Get the pointer | //Get the pointer | ||||
result = capture<std::string>(m, url+"preset-type"); | result = capture<std::string>(m, url+"preset-type"); | ||||
}); | }); | ||||
printf("preset type = %s\n", result.c_str()); | |||||
//printf("preset type = %s\n", result.c_str()); | |||||
return result; | return result; | ||||
} | } | ||||
std::string getUrlType(std::string url) | std::string getUrlType(std::string url) | ||||
{ | { | ||||
assert(!url.empty()); | assert(!url.empty()); | ||||
printf("Searching for '%s'\n", (url+"self").c_str()); | |||||
//printf("Searching for '%s'\n", (url+"self").c_str()); | |||||
auto self = Master::ports.apropos((url+"self").c_str()); | auto self = Master::ports.apropos((url+"self").c_str()); | ||||
if(!self) | if(!self) | ||||
fprintf(stderr, "Warning: URL Metadata Not Found For '%s'\n", url.c_str()); | fprintf(stderr, "Warning: URL Metadata Not Found For '%s'\n", url.c_str()); | ||||
@@ -409,12 +412,12 @@ void presetCopy(MiddleWare &mw, std::string url, std::string name) | |||||
{ | { | ||||
(void) name; | (void) name; | ||||
doClassCopy(getUrlType(url), mw, url, name); | doClassCopy(getUrlType(url), mw, url, name); | ||||
printf("PresetCopy()\n"); | |||||
//printf("PresetCopy()\n"); | |||||
} | } | ||||
void presetPaste(MiddleWare &mw, std::string url, std::string name) | void presetPaste(MiddleWare &mw, std::string url, std::string name) | ||||
{ | { | ||||
(void) name; | (void) name; | ||||
printf("PresetPaste()\n"); | |||||
//printf("PresetPaste()\n"); | |||||
string data = ""; | string data = ""; | ||||
XMLwrapper xml; | XMLwrapper xml; | ||||
if(name.empty()) { | if(name.empty()) { | ||||
@@ -433,13 +436,13 @@ void presetPaste(MiddleWare &mw, std::string url, std::string name) | |||||
void presetCopyArray(MiddleWare &mw, std::string url, int field, std::string name) | void presetCopyArray(MiddleWare &mw, std::string url, int field, std::string name) | ||||
{ | { | ||||
(void) name; | (void) name; | ||||
printf("PresetArrayCopy()\n"); | |||||
//printf("PresetArrayCopy()\n"); | |||||
doClassArrayCopy(getUrlType(url), field, mw, url, name); | doClassArrayCopy(getUrlType(url), field, mw, url, name); | ||||
} | } | ||||
void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string name) | void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string name) | ||||
{ | { | ||||
(void) name; | (void) name; | ||||
printf("PresetArrayPaste()\n"); | |||||
//printf("PresetArrayPaste()\n"); | |||||
string data = ""; | string data = ""; | ||||
XMLwrapper xml; | XMLwrapper xml; | ||||
if(name.empty()) { | if(name.empty()) { | ||||
@@ -452,7 +455,7 @@ void presetPasteArray(MiddleWare &mw, std::string url, int field, std::string na | |||||
if(xml.loadXMLfile(name)) | if(xml.loadXMLfile(name)) | ||||
return; | return; | ||||
} | } | ||||
printf("Performing Paste...\n"); | |||||
//printf("Performing Paste...\n"); | |||||
doClassArrayPaste(getUrlType(url), getUrlPresetType(url, mw), field, mw, url, xml); | doClassArrayPaste(getUrlType(url), getUrlPresetType(url, mw), field, mw, url, xml); | ||||
} | } | ||||
#if 0 | #if 0 | ||||
@@ -464,19 +467,19 @@ void presetPaste(std::string url, int) | |||||
#endif | #endif | ||||
void presetDelete(int) | void presetDelete(int) | ||||
{ | { | ||||
printf("PresetDelete()\n"); | |||||
printf("PresetDelete()<UNIMPLEMENTED>\n"); | |||||
} | } | ||||
void presetRescan() | void presetRescan() | ||||
{ | { | ||||
printf("PresetRescan()\n"); | |||||
printf("PresetRescan()<UNIMPLEMENTED>\n"); | |||||
} | } | ||||
std::string presetClipboardType() | std::string presetClipboardType() | ||||
{ | { | ||||
printf("PresetClipboardType()\n"); | |||||
printf("PresetClipboardType()<UNIMPLEMENTED>\n"); | |||||
return "dummy"; | return "dummy"; | ||||
} | } | ||||
bool presetCheckClipboardType() | bool presetCheckClipboardType() | ||||
{ | { | ||||
printf("PresetCheckClipboardType()\n"); | |||||
printf("PresetCheckClipboardType()<UNIMPLEMENTED>\n"); | |||||
return true; | return true; | ||||
} | } |
@@ -111,6 +111,8 @@ static const Ports voicePorts = { | |||||
rToggle(PFilterEnabled, "Filter Enable"), | rToggle(PFilterEnabled, "Filter Enable"), | ||||
rToggle(PFilterEnvelopeEnabled, "Filter Envelope Enable"), | rToggle(PFilterEnvelopeEnabled, "Filter Envelope Enable"), | ||||
rToggle(PFilterLfoEnabled, "Filter LFO Enable"), | rToggle(PFilterLfoEnabled, "Filter LFO Enable"), | ||||
rParamZyn(PFilterVelocityScale, "Filter Velocity Magnitude"), | |||||
rParamZyn(PFilterVelocityScaleFunction, "Filter Velocity Function Shape"), | |||||
//Modulator Stuff | //Modulator Stuff | ||||
@@ -428,6 +430,8 @@ void ADnoteVoiceParam::defaults() | |||||
PFilterEnabled = 0; | PFilterEnabled = 0; | ||||
PFilterEnvelopeEnabled = 0; | PFilterEnvelopeEnabled = 0; | ||||
PFilterLfoEnabled = 0; | PFilterLfoEnabled = 0; | ||||
PFilterVelocityScale = 0; | |||||
PFilterVelocityScaleFunction = 64; | |||||
PFMEnabled = 0; | PFMEnabled = 0; | ||||
//I use the internal oscillator (-1) | //I use the internal oscillator (-1) | ||||
@@ -664,6 +668,8 @@ void ADnoteVoiceParam::add2XML(XMLwrapper *xml, bool fmoscilused) | |||||
if((PFilterEnabled != 0) || (!xml->minimal)) { | if((PFilterEnabled != 0) || (!xml->minimal)) { | ||||
xml->beginbranch("FILTER_PARAMETERS"); | xml->beginbranch("FILTER_PARAMETERS"); | ||||
xml->addpar("velocity_sensing_amplitude", PFilterVelocityScale); | |||||
xml->addpar("velocity_sensing", PFilterVelocityScaleFunction); | |||||
xml->beginbranch("FILTER"); | xml->beginbranch("FILTER"); | ||||
VoiceFilter->add2XML(xml); | VoiceFilter->add2XML(xml); | ||||
xml->endbranch(); | xml->endbranch(); | ||||
@@ -974,6 +980,8 @@ void ADnoteVoiceParam::paste(ADnoteVoiceParam &a) | |||||
RCopy(FilterEnvelope); | RCopy(FilterEnvelope); | ||||
copy(PFilterLfoEnabled); | copy(PFilterLfoEnabled); | ||||
copy(PFilterVelocityScale); | |||||
copy(PFilterVelocityScaleFunction); | |||||
RCopy(FilterLfo); | RCopy(FilterLfo); | ||||
@@ -1115,6 +1123,11 @@ void ADnoteVoiceParam::getfromXML(XMLwrapper *xml, unsigned nvoice) | |||||
} | } | ||||
if(xml->enterbranch("FILTER_PARAMETERS")) { | if(xml->enterbranch("FILTER_PARAMETERS")) { | ||||
PFilterVelocityScale = xml->getpar127("velocity_sensing_amplitude", | |||||
PFilterVelocityScale); | |||||
PFilterVelocityScaleFunction = xml->getpar127( | |||||
"velocity_sensing", | |||||
PFilterVelocityScaleFunction); | |||||
if(xml->enterbranch("FILTER")) { | if(xml->enterbranch("FILTER")) { | ||||
VoiceFilter->getfromXML(xml); | VoiceFilter->getfromXML(xml); | ||||
xml->exitbranch(); | xml->exitbranch(); | ||||
@@ -240,10 +240,16 @@ struct ADnoteVoiceParam { | |||||
unsigned char PFilterEnvelopeEnabled; | unsigned char PFilterEnvelopeEnabled; | ||||
EnvelopeParams *FilterEnvelope; | EnvelopeParams *FilterEnvelope; | ||||
/* LFO Envelope */ | |||||
/* Filter LFO */ | |||||
unsigned char PFilterLfoEnabled; | unsigned char PFilterLfoEnabled; | ||||
LFOParams *FilterLfo; | LFOParams *FilterLfo; | ||||
// filter velocity sensing | |||||
unsigned char PFilterVelocityScale; | |||||
// filter velocity sensing | |||||
unsigned char PFilterVelocityScaleFunction; | |||||
/**************************** | /**************************** | ||||
* MODULLATOR PARAMETERS * | * MODULLATOR PARAMETERS * | ||||
****************************/ | ****************************/ | ||||
@@ -317,7 +317,11 @@ ADnote::ADnote(ADnoteParameters *pars_, SynthParams &spars) | |||||
NoteVoicePar[nvoice].FilterLfo = NULL; | NoteVoicePar[nvoice].FilterLfo = NULL; | ||||
NoteVoicePar[nvoice].FilterCenterPitch = | NoteVoicePar[nvoice].FilterCenterPitch = | ||||
pars.VoicePar[nvoice].VoiceFilter->getfreq(); | |||||
pars.VoicePar[nvoice].VoiceFilter->getfreq() | |||||
+ pars.VoicePar[nvoice].PFilterVelocityScale | |||||
/ 127.0f * 6.0f //velocity sensing | |||||
* (VelF(velocity, | |||||
pars.VoicePar[nvoice].PFilterVelocityScaleFunction) - 1); | |||||
NoteVoicePar[nvoice].filterbypass = | NoteVoicePar[nvoice].filterbypass = | ||||
pars.VoicePar[nvoice].Pfilterbypass; | pars.VoicePar[nvoice].Pfilterbypass; | ||||
@@ -511,7 +515,12 @@ void ADnote::legatonote(LegatoParams lpars) | |||||
NoteVoicePar[nvoice].FilterCenterPitch = | NoteVoicePar[nvoice].FilterCenterPitch = | ||||
pars.VoicePar[nvoice].VoiceFilter->getfreq(); | |||||
pars.VoicePar[nvoice].VoiceFilter->getfreq() | |||||
+ pars.VoicePar[nvoice].PFilterVelocityScale | |||||
/ 127.0f * 6.0f //velocity sensing | |||||
* (VelF(velocity, | |||||
pars.VoicePar[nvoice].PFilterVelocityScaleFunction) - 1); | |||||
NoteVoicePar[nvoice].filterbypass = | NoteVoicePar[nvoice].filterbypass = | ||||
pars.VoicePar[nvoice].Pfilterbypass; | pars.VoicePar[nvoice].Pfilterbypass; | ||||
@@ -764,7 +764,7 @@ o->redraw();} | |||||
Fl_Group {} { | Fl_Group {} { | ||||
label {ADsynth Voice - Filter} open | label {ADsynth Voice - Filter} open | ||||
xywh {250 30 275 75} box FLAT_BOX color 50 align 144 | xywh {250 30 275 75} box FLAT_BOX color 50 align 144 | ||||
code0 {o->init("", osc_i, loc, "VoiceFilter/");} | |||||
code0 {o->init(loc + "PFilter", osc_i, loc, "VoiceFilter/");} | |||||
class FilterUI | class FilterUI | ||||
} {} | } {} | ||||
Fl_Group voicefilterenvgroup { | Fl_Group voicefilterenvgroup { | ||||
@@ -111,6 +111,8 @@ void Fl_Osc_Dial::mark_dead(void) | |||||
dead = true; | dead = true; | ||||
} | } | ||||
#define VEL_PFX "VelocityScale" | |||||
void Fl_Osc_Dial::rebase(std::string new_base) | void Fl_Osc_Dial::rebase(std::string new_base) | ||||
{ | { | ||||
if(dead || loc == "/") | if(dead || loc == "/") | ||||
@@ -141,8 +143,12 @@ void Fl_Osc_Dial::rebase(std::string new_base) | |||||
} | } | ||||
std::string new_loc = new_base.substr(0, match_pos+1); | std::string new_loc = new_base.substr(0, match_pos+1); | ||||
printf("Moving '%s' to\n", (loc+ext).c_str()); | |||||
printf(" '%s'\n", (new_loc+ext).c_str()); | |||||
if (!strncmp(ext.c_str(), VEL_PFX, sizeof(VEL_PFX)-1) && | |||||
strstr(loc.c_str(), "/VoicePar")) | |||||
new_loc = new_loc + "PFilter"; | |||||
// printf("Moving '%s' to\n", (loc+ext).c_str()); | |||||
// printf(" '%s'\n", (new_loc+ext).c_str()); | |||||
// printf("Ext: %s\n", ext.c_str()); | |||||
oscMove(loc+ext, new_loc+ext); | oscMove(loc+ext, new_loc+ext); | ||||
loc = new_loc; | loc = new_loc; | ||||
} | } |