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