| @@ -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; | |||
| } | |||