| @@ -24040,10 +24040,10 @@ private: | |||
| int64 riffChunkSize = 4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */ | |||
| + 8 + audioDataSize + (audioDataSize & 1) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0) | |||
| + (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0) | |||
| + (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0) | |||
| + (listChunk.getSize() > 0 ? (12 + listChunk.getSize()) : 0) | |||
| + (8 + 28); // (ds64 chunk) | |||
| @@ -29662,6 +29662,15 @@ const MidiMessage MidiMessage::channelPressureChange (const int channel, | |||
| return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); | |||
| } | |||
| bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && data[2] >= 64; } | |||
| bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && data[2] < 64; } | |||
| bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && data[2] >= 64; } | |||
| bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && data[2] < 64; } | |||
| bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && data[2] >= 64; } | |||
| bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && data[2] < 64; } | |||
| bool MidiMessage::isProgramChange() const noexcept | |||
| { | |||
| return (data[0] & 0xf0) == 0xc0; | |||
| @@ -29707,6 +29716,11 @@ bool MidiMessage::isController() const noexcept | |||
| return (data[0] & 0xf0) == 0xb0; | |||
| } | |||
| bool MidiMessage::isControllerOfType (const int controllerType) const noexcept | |||
| { | |||
| return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; | |||
| } | |||
| int MidiMessage::getControllerNumber() const noexcept | |||
| { | |||
| jassert (isController()); | |||
| @@ -38599,7 +38613,9 @@ SynthesiserSound::~SynthesiserSound() | |||
| SynthesiserVoice::SynthesiserVoice() | |||
| : currentSampleRate (44100.0), | |||
| currentlyPlayingNote (-1), | |||
| noteOnTime (0) | |||
| noteOnTime (0), | |||
| keyIsDown (false), | |||
| sostenutoPedalDown (false) | |||
| { | |||
| } | |||
| @@ -38729,46 +38745,47 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, | |||
| } | |||
| if (useEvent) | |||
| { | |||
| if (m.isNoteOn()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| noteOn (channel, | |||
| m.getNoteNumber(), | |||
| m.getFloatVelocity()); | |||
| } | |||
| else if (m.isNoteOff()) | |||
| { | |||
| noteOff (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| true); | |||
| } | |||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||
| { | |||
| allNotesOff (m.getChannel(), true); | |||
| } | |||
| else if (m.isPitchWheel()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| const int wheelPos = m.getPitchWheelValue(); | |||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||
| handlePitchWheel (channel, wheelPos); | |||
| } | |||
| else if (m.isController()) | |||
| { | |||
| handleController (m.getChannel(), | |||
| m.getControllerNumber(), | |||
| m.getControllerValue()); | |||
| } | |||
| } | |||
| handleMidiEvent (m); | |||
| startSample += numThisTime; | |||
| numSamples -= numThisTime; | |||
| } | |||
| } | |||
| void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||
| { | |||
| if (m.isNoteOn()) | |||
| { | |||
| noteOn (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| m.getFloatVelocity()); | |||
| } | |||
| else if (m.isNoteOff()) | |||
| { | |||
| noteOff (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| true); | |||
| } | |||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||
| { | |||
| allNotesOff (m.getChannel(), true); | |||
| } | |||
| else if (m.isPitchWheel()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| const int wheelPos = m.getPitchWheelValue(); | |||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||
| handlePitchWheel (channel, wheelPos); | |||
| } | |||
| else if (m.isController()) | |||
| { | |||
| handleController (m.getChannel(), | |||
| m.getControllerNumber(), | |||
| m.getControllerValue()); | |||
| } | |||
| } | |||
| void Synthesiser::noteOn (const int midiChannel, | |||
| const int midiNoteNumber, | |||
| const float velocity) | |||
| @@ -38782,6 +38799,17 @@ void Synthesiser::noteOn (const int midiChannel, | |||
| if (sound->appliesToNote (midiNoteNumber) | |||
| && sound->appliesToChannel (midiChannel)) | |||
| { | |||
| // If hitting a note that's still ringing, stop it first (it could be | |||
| // still playing because of the sustain or sostenuto pedal). | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||
| && voice->isPlayingChannel (midiChannel)) | |||
| stopVoice (voice, true); | |||
| } | |||
| startVoice (findFreeVoice (sound, shouldStealNotes), | |||
| sound, midiChannel, midiNoteNumber, velocity); | |||
| } | |||
| @@ -38799,17 +38827,27 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
| if (voice->currentlyPlayingSound != nullptr) | |||
| voice->stopNote (false); | |||
| voice->startNote (midiNoteNumber, | |||
| velocity, | |||
| sound, | |||
| voice->startNote (midiNoteNumber, velocity, sound, | |||
| lastPitchWheelValues [midiChannel - 1]); | |||
| voice->currentlyPlayingNote = midiNoteNumber; | |||
| voice->noteOnTime = ++lastNoteOnCounter; | |||
| voice->currentlyPlayingSound = sound; | |||
| voice->keyIsDown = true; | |||
| voice->sostenutoPedalDown = false; | |||
| } | |||
| } | |||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->stopNote (allowTailOff); | |||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||
| } | |||
| void Synthesiser::noteOff (const int midiChannel, | |||
| const int midiNoteNumber, | |||
| const bool allowTailOff) | |||
| @@ -38828,17 +38866,16 @@ void Synthesiser::noteOff (const int midiChannel, | |||
| && sound->appliesToNote (midiNoteNumber) | |||
| && sound->appliesToChannel (midiChannel)) | |||
| { | |||
| voice->stopNote (allowTailOff); | |||
| voice->keyIsDown = false; | |||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||
| if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||
| stopVoice (voice, allowTailOff); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void Synthesiser::allNotesOff (const int midiChannel, | |||
| const bool allowTailOff) | |||
| void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -38849,10 +38886,11 @@ void Synthesiser::allNotesOff (const int midiChannel, | |||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||
| voice->stopNote (allowTailOff); | |||
| } | |||
| sustainPedalsDown.clear(); | |||
| } | |||
| void Synthesiser::handlePitchWheel (const int midiChannel, | |||
| const int wheelValue) | |||
| void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -38861,9 +38899,7 @@ void Synthesiser::handlePitchWheel (const int midiChannel, | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||
| { | |||
| voice->pitchWheelMoved (wheelValue); | |||
| } | |||
| } | |||
| } | |||
| @@ -38871,6 +38907,14 @@ void Synthesiser::handleController (const int midiChannel, | |||
| const int controllerNumber, | |||
| const int controllerValue) | |||
| { | |||
| switch (controllerNumber) | |||
| { | |||
| case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; | |||
| case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; | |||
| case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; | |||
| default: break; | |||
| } | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| @@ -38882,6 +38926,53 @@ void Synthesiser::handleController (const int midiChannel, | |||
| } | |||
| } | |||
| void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| const ScopedLock sl (lock); | |||
| if (isDown) | |||
| { | |||
| sustainPedalsDown.setBit (midiChannel); | |||
| } | |||
| else | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||
| stopVoice (voice, true); | |||
| } | |||
| sustainPedalsDown.clearBit (midiChannel); | |||
| } | |||
| } | |||
| void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel)) | |||
| { | |||
| if (isDown) | |||
| voice->sostenutoPedalDown = true; | |||
| else if (voice->sostenutoPedalDown) | |||
| stopVoice (voice, true); | |||
| } | |||
| } | |||
| } | |||
| void Synthesiser::handleSoftPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| } | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| const bool stealIfNoneAvailable) const | |||
| { | |||
| @@ -74836,16 +74927,14 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& | |||
| jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels); | |||
| jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels); | |||
| if (deviceManager_.getAvailableDeviceTypes().size() > 1) | |||
| const OwnedArray<AudioIODeviceType>& types = deviceManager_.getAvailableDeviceTypes(); | |||
| if (types.size() > 1) | |||
| { | |||
| deviceTypeDropDown = new ComboBox (String::empty); | |||
| for (int i = 0; i < deviceManager_.getAvailableDeviceTypes().size(); ++i) | |||
| { | |||
| deviceTypeDropDown | |||
| ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), | |||
| i + 1); | |||
| } | |||
| for (int i = 0; i < types.size(); ++i) | |||
| deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1); | |||
| addAndMakeVisible (deviceTypeDropDown); | |||
| deviceTypeDropDown->addListener (this); | |||
| @@ -73,7 +73,7 @@ namespace JuceDummyNamespace {} | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 1 | |||
| #define JUCE_MINOR_VERSION 53 | |||
| #define JUCE_BUILDNUMBER 92 | |||
| #define JUCE_BUILDNUMBER 93 | |||
| /** Current Juce version number. | |||
| @@ -39379,7 +39379,7 @@ private: | |||
| void clear() noexcept | |||
| { | |||
| last = 0; | |||
| zeromem (buffer, bufferSize * sizeof (float)); | |||
| buffer.clear (bufferSize); | |||
| } | |||
| void setFeedbackAndDamp (const float f, const float d) noexcept | |||
| @@ -39429,7 +39429,7 @@ private: | |||
| void clear() noexcept | |||
| { | |||
| zeromem (buffer, bufferSize * sizeof (float)); | |||
| buffer.clear (bufferSize); | |||
| } | |||
| inline float process (const float input) noexcept | |||
| @@ -39932,8 +39932,7 @@ public: | |||
| /** Returns the midi note number for note-on and note-off messages. | |||
| If the message isn't a note-on or off, the value returned will be | |||
| meaningless. | |||
| If the message isn't a note-on or off, the value returned is undefined. | |||
| @see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||
| */ | |||
| @@ -39948,7 +39947,6 @@ public: | |||
| /** Returns the velocity of a note-on or note-off message. | |||
| The value returned will be in the range 0 to 127. | |||
| If the message isn't a note-on or off event, it will return 0. | |||
| @see getFloatVelocity | |||
| @@ -39958,7 +39956,6 @@ public: | |||
| /** Returns the velocity of a note-on or note-off message. | |||
| The value returned will be in the range 0 to 1.0 | |||
| If the message isn't a note-on or off event, it will return 0. | |||
| @see getVelocity, setVelocity | |||
| @@ -39983,6 +39980,21 @@ public: | |||
| */ | |||
| void multiplyVelocity (float scaleFactor) noexcept; | |||
| /** Returns true if this message is a 'sustain pedal down' controller message. */ | |||
| bool isSustainPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'sustain pedal up' controller message. */ | |||
| bool isSustainPedalOff() const noexcept; | |||
| /** Returns true if this message is a 'sostenuto pedal down' controller message. */ | |||
| bool isSostenutoPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'sostenuto pedal up' controller message. */ | |||
| bool isSostenutoPedalOff() const noexcept; | |||
| /** Returns true if this message is a 'soft pedal down' controller message. */ | |||
| bool isSoftPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'soft pedal up' controller message. */ | |||
| bool isSoftPedalOff() const noexcept; | |||
| /** Returns true if the message is a program (patch) change message. | |||
| @see getProgramChangeNumber, getGMInstrumentName | |||
| @@ -40111,6 +40123,11 @@ public: | |||
| */ | |||
| int getControllerValue() const noexcept; | |||
| /** Returns true if this message is a controller message and if it has the specified | |||
| controller type. | |||
| */ | |||
| bool isControllerOfType (int controllerType) const noexcept; | |||
| /** Creates a controller message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @@ -48727,6 +48744,8 @@ private: | |||
| int currentlyPlayingNote; | |||
| uint32 noteOnTime; | |||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||
| bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal) | |||
| bool sostenutoPedalDown; | |||
| JUCE_LEAK_DETECTOR (SynthesiserVoice); | |||
| }; | |||
| @@ -48831,6 +48850,8 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||
| */ | |||
| virtual void noteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| @@ -48845,6 +48866,8 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||
| */ | |||
| virtual void noteOff (int midiChannel, | |||
| int midiNoteNumber, | |||
| @@ -48855,7 +48878,8 @@ public: | |||
| This will turn off any voices that are playing a sound on the given midi channel. | |||
| If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||
| which channel they're playing. | |||
| which channel they're playing. Otherwise it represents a valid midi channel, from | |||
| 1 to 16 inclusive. | |||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||
| @@ -48874,7 +48898,7 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| @param midiChannel the midi channel for the event | |||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||
| @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||
| */ | |||
| virtual void handlePitchWheel (int midiChannel, | |||
| @@ -48888,7 +48912,7 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| @param midiChannel the midi channel for the event | |||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||
| @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||
| @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||
| */ | |||
| @@ -48896,6 +48920,10 @@ public: | |||
| int controllerNumber, | |||
| int controllerValue); | |||
| virtual void handleSustainPedal (int midiChannel, bool isDown); | |||
| virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||
| virtual void handleSoftPedal (int midiChannel, bool isDown); | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to | |||
| render. | |||
| @@ -48953,15 +48981,20 @@ protected: | |||
| int midiNoteNumber, | |||
| float velocity); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Temporary method here to cause a compiler error - note the new parameters for this method. | |||
| int findFreeVoice (const bool) const { return 0; } | |||
| #endif | |||
| private: | |||
| double sampleRate; | |||
| uint32 lastNoteOnCounter; | |||
| bool shouldStealNotes; | |||
| BigInteger sustainPedalsDown; | |||
| void handleMidiEvent (const MidiMessage& m); | |||
| void stopVoice (SynthesiserVoice* voice, bool allowTailOff); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| virtual int findFreeVoice (const bool) const { return 0; } | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser); | |||
| }; | |||
| @@ -878,10 +878,10 @@ private: | |||
| int64 riffChunkSize = 4 /* 'RIFF' */ + 8 + 40 /* WAVEFORMATEX */ | |||
| + 8 + audioDataSize + (audioDataSize & 1) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0) | |||
| + (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0) | |||
| + (bwavChunk.getSize() > 0 ? (8 + bwavChunk.getSize()) : 0) | |||
| + (smplChunk.getSize() > 0 ? (8 + smplChunk.getSize()) : 0) | |||
| + (instChunk.getSize() > 0 ? (8 + instChunk.getSize()) : 0) | |||
| + (cueChunk .getSize() > 0 ? (8 + cueChunk .getSize()) : 0) | |||
| + (listChunk.getSize() > 0 ? (12 + listChunk.getSize()) : 0) | |||
| + (8 + 28); // (ds64 chunk) | |||
| @@ -242,7 +242,7 @@ private: | |||
| void clear() noexcept | |||
| { | |||
| last = 0; | |||
| zeromem (buffer, bufferSize * sizeof (float)); | |||
| buffer.clear (bufferSize); | |||
| } | |||
| void setFeedbackAndDamp (const float f, const float d) noexcept | |||
| @@ -293,7 +293,7 @@ private: | |||
| void clear() noexcept | |||
| { | |||
| zeromem (buffer, bufferSize * sizeof (float)); | |||
| buffer.clear (bufferSize); | |||
| } | |||
| inline float process (const float input) noexcept | |||
| @@ -420,6 +420,16 @@ const MidiMessage MidiMessage::channelPressureChange (const int channel, | |||
| return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); | |||
| } | |||
| bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && data[2] >= 64; } | |||
| bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && data[2] < 64; } | |||
| bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && data[2] >= 64; } | |||
| bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && data[2] < 64; } | |||
| bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && data[2] >= 64; } | |||
| bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && data[2] < 64; } | |||
| bool MidiMessage::isProgramChange() const noexcept | |||
| { | |||
| return (data[0] & 0xf0) == 0xc0; | |||
| @@ -465,6 +475,11 @@ bool MidiMessage::isController() const noexcept | |||
| return (data[0] & 0xf0) == 0xb0; | |||
| } | |||
| bool MidiMessage::isControllerOfType (const int controllerType) const noexcept | |||
| { | |||
| return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; | |||
| } | |||
| int MidiMessage::getControllerNumber() const noexcept | |||
| { | |||
| jassert (isController()); | |||
| @@ -256,8 +256,7 @@ public: | |||
| /** Returns the midi note number for note-on and note-off messages. | |||
| If the message isn't a note-on or off, the value returned will be | |||
| meaningless. | |||
| If the message isn't a note-on or off, the value returned is undefined. | |||
| @see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||
| */ | |||
| @@ -273,7 +272,6 @@ public: | |||
| /** Returns the velocity of a note-on or note-off message. | |||
| The value returned will be in the range 0 to 127. | |||
| If the message isn't a note-on or off event, it will return 0. | |||
| @see getFloatVelocity | |||
| @@ -283,7 +281,6 @@ public: | |||
| /** Returns the velocity of a note-on or note-off message. | |||
| The value returned will be in the range 0 to 1.0 | |||
| If the message isn't a note-on or off event, it will return 0. | |||
| @see getVelocity, setVelocity | |||
| @@ -308,6 +305,22 @@ public: | |||
| */ | |||
| void multiplyVelocity (float scaleFactor) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if this message is a 'sustain pedal down' controller message. */ | |||
| bool isSustainPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'sustain pedal up' controller message. */ | |||
| bool isSustainPedalOff() const noexcept; | |||
| /** Returns true if this message is a 'sostenuto pedal down' controller message. */ | |||
| bool isSostenutoPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'sostenuto pedal up' controller message. */ | |||
| bool isSostenutoPedalOff() const noexcept; | |||
| /** Returns true if this message is a 'soft pedal down' controller message. */ | |||
| bool isSoftPedalOn() const noexcept; | |||
| /** Returns true if this message is a 'soft pedal up' controller message. */ | |||
| bool isSoftPedalOff() const noexcept; | |||
| //============================================================================== | |||
| /** Returns true if the message is a program (patch) change message. | |||
| @@ -440,6 +453,11 @@ public: | |||
| */ | |||
| int getControllerValue() const noexcept; | |||
| /** Returns true if this message is a controller message and if it has the specified | |||
| controller type. | |||
| */ | |||
| bool isControllerOfType (int controllerType) const noexcept; | |||
| /** Creates a controller message. | |||
| @param channel the midi channel, in the range 1 to 16 | |||
| @@ -43,7 +43,9 @@ SynthesiserSound::~SynthesiserSound() | |||
| SynthesiserVoice::SynthesiserVoice() | |||
| : currentSampleRate (44100.0), | |||
| currentlyPlayingNote (-1), | |||
| noteOnTime (0) | |||
| noteOnTime (0), | |||
| keyIsDown (false), | |||
| sostenutoPedalDown (false) | |||
| { | |||
| } | |||
| @@ -176,46 +178,47 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, | |||
| } | |||
| if (useEvent) | |||
| { | |||
| if (m.isNoteOn()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| noteOn (channel, | |||
| m.getNoteNumber(), | |||
| m.getFloatVelocity()); | |||
| } | |||
| else if (m.isNoteOff()) | |||
| { | |||
| noteOff (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| true); | |||
| } | |||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||
| { | |||
| allNotesOff (m.getChannel(), true); | |||
| } | |||
| else if (m.isPitchWheel()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| const int wheelPos = m.getPitchWheelValue(); | |||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||
| handlePitchWheel (channel, wheelPos); | |||
| } | |||
| else if (m.isController()) | |||
| { | |||
| handleController (m.getChannel(), | |||
| m.getControllerNumber(), | |||
| m.getControllerValue()); | |||
| } | |||
| } | |||
| handleMidiEvent (m); | |||
| startSample += numThisTime; | |||
| numSamples -= numThisTime; | |||
| } | |||
| } | |||
| void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||
| { | |||
| if (m.isNoteOn()) | |||
| { | |||
| noteOn (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| m.getFloatVelocity()); | |||
| } | |||
| else if (m.isNoteOff()) | |||
| { | |||
| noteOff (m.getChannel(), | |||
| m.getNoteNumber(), | |||
| true); | |||
| } | |||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||
| { | |||
| allNotesOff (m.getChannel(), true); | |||
| } | |||
| else if (m.isPitchWheel()) | |||
| { | |||
| const int channel = m.getChannel(); | |||
| const int wheelPos = m.getPitchWheelValue(); | |||
| lastPitchWheelValues [channel - 1] = wheelPos; | |||
| handlePitchWheel (channel, wheelPos); | |||
| } | |||
| else if (m.isController()) | |||
| { | |||
| handleController (m.getChannel(), | |||
| m.getControllerNumber(), | |||
| m.getControllerValue()); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void Synthesiser::noteOn (const int midiChannel, | |||
| const int midiNoteNumber, | |||
| @@ -230,6 +233,17 @@ void Synthesiser::noteOn (const int midiChannel, | |||
| if (sound->appliesToNote (midiNoteNumber) | |||
| && sound->appliesToChannel (midiChannel)) | |||
| { | |||
| // If hitting a note that's still ringing, stop it first (it could be | |||
| // still playing because of the sustain or sostenuto pedal). | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||
| && voice->isPlayingChannel (midiChannel)) | |||
| stopVoice (voice, true); | |||
| } | |||
| startVoice (findFreeVoice (sound, shouldStealNotes), | |||
| sound, midiChannel, midiNoteNumber, velocity); | |||
| } | |||
| @@ -247,17 +261,27 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
| if (voice->currentlyPlayingSound != nullptr) | |||
| voice->stopNote (false); | |||
| voice->startNote (midiNoteNumber, | |||
| velocity, | |||
| sound, | |||
| voice->startNote (midiNoteNumber, velocity, sound, | |||
| lastPitchWheelValues [midiChannel - 1]); | |||
| voice->currentlyPlayingNote = midiNoteNumber; | |||
| voice->noteOnTime = ++lastNoteOnCounter; | |||
| voice->currentlyPlayingSound = sound; | |||
| voice->keyIsDown = true; | |||
| voice->sostenutoPedalDown = false; | |||
| } | |||
| } | |||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->stopNote (allowTailOff); | |||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||
| } | |||
| void Synthesiser::noteOff (const int midiChannel, | |||
| const int midiNoteNumber, | |||
| const bool allowTailOff) | |||
| @@ -276,17 +300,16 @@ void Synthesiser::noteOff (const int midiChannel, | |||
| && sound->appliesToNote (midiNoteNumber) | |||
| && sound->appliesToChannel (midiChannel)) | |||
| { | |||
| voice->stopNote (allowTailOff); | |||
| voice->keyIsDown = false; | |||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||
| if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||
| stopVoice (voice, allowTailOff); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void Synthesiser::allNotesOff (const int midiChannel, | |||
| const bool allowTailOff) | |||
| void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -297,10 +320,11 @@ void Synthesiser::allNotesOff (const int midiChannel, | |||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||
| voice->stopNote (allowTailOff); | |||
| } | |||
| sustainPedalsDown.clear(); | |||
| } | |||
| void Synthesiser::handlePitchWheel (const int midiChannel, | |||
| const int wheelValue) | |||
| void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -309,9 +333,7 @@ void Synthesiser::handlePitchWheel (const int midiChannel, | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||
| { | |||
| voice->pitchWheelMoved (wheelValue); | |||
| } | |||
| } | |||
| } | |||
| @@ -319,6 +341,14 @@ void Synthesiser::handleController (const int midiChannel, | |||
| const int controllerNumber, | |||
| const int controllerValue) | |||
| { | |||
| switch (controllerNumber) | |||
| { | |||
| case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; | |||
| case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; | |||
| case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; | |||
| default: break; | |||
| } | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| @@ -330,6 +360,53 @@ void Synthesiser::handleController (const int midiChannel, | |||
| } | |||
| } | |||
| void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| const ScopedLock sl (lock); | |||
| if (isDown) | |||
| { | |||
| sustainPedalsDown.setBit (midiChannel); | |||
| } | |||
| else | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||
| stopVoice (voice, true); | |||
| } | |||
| sustainPedalsDown.clearBit (midiChannel); | |||
| } | |||
| } | |||
| void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| const ScopedLock sl (lock); | |||
| for (int i = voices.size(); --i >= 0;) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel)) | |||
| { | |||
| if (isDown) | |||
| voice->sostenutoPedalDown = true; | |||
| else if (voice->sostenutoPedalDown) | |||
| stopVoice (voice, true); | |||
| } | |||
| } | |||
| } | |||
| void Synthesiser::handleSoftPedal (int midiChannel, bool isDown) | |||
| { | |||
| jassert (midiChannel > 0 && midiChannel <= 16); | |||
| } | |||
| //============================================================================== | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| const bool stealIfNoneAvailable) const | |||
| @@ -32,6 +32,7 @@ | |||
| #include "../../memory/juce_ReferenceCountedObject.h" | |||
| #include "../../containers/juce_ReferenceCountedArray.h" | |||
| #include "../../threads/juce_CriticalSection.h" | |||
| #include "../../maths/juce_BigInteger.h" | |||
| //============================================================================== | |||
| @@ -232,6 +233,8 @@ private: | |||
| int currentlyPlayingNote; | |||
| uint32 noteOnTime; | |||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||
| bool keyIsDown; // the voice may still be playing when the key is not down (i.e. sustain pedal) | |||
| bool sostenutoPedalDown; | |||
| JUCE_LEAK_DETECTOR (SynthesiserVoice); | |||
| }; | |||
| @@ -342,6 +345,8 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||
| */ | |||
| virtual void noteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| @@ -356,6 +361,8 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| The midiChannel parameter is the channel, between 1 and 16 inclusive. | |||
| */ | |||
| virtual void noteOff (int midiChannel, | |||
| int midiNoteNumber, | |||
| @@ -366,7 +373,8 @@ public: | |||
| This will turn off any voices that are playing a sound on the given midi channel. | |||
| If midiChannel is 0 or less, then all voices will be turned off, regardless of | |||
| which channel they're playing. | |||
| which channel they're playing. Otherwise it represents a valid midi channel, from | |||
| 1 to 16 inclusive. | |||
| If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||
| (if they can do). If this is false, the notes will all be cut off immediately. | |||
| @@ -385,7 +393,7 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| @param midiChannel the midi channel for the event | |||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||
| @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() | |||
| */ | |||
| virtual void handlePitchWheel (int midiChannel, | |||
| @@ -399,7 +407,7 @@ public: | |||
| This method will be called automatically according to the midi data passed into | |||
| renderNextBlock(), but may be called explicitly too. | |||
| @param midiChannel the midi channel for the event | |||
| @param midiChannel the midi channel, from 1 to 16 inclusive | |||
| @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() | |||
| @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() | |||
| */ | |||
| @@ -407,6 +415,10 @@ public: | |||
| int controllerNumber, | |||
| int controllerValue); | |||
| virtual void handleSustainPedal (int midiChannel, bool isDown); | |||
| virtual void handleSostenutoPedal (int midiChannel, bool isDown); | |||
| virtual void handleSoftPedal (int midiChannel, bool isDown); | |||
| //============================================================================== | |||
| /** Tells the synthesiser what the sample rate is for the audio it's being used to | |||
| render. | |||
| @@ -465,15 +477,20 @@ protected: | |||
| int midiNoteNumber, | |||
| float velocity); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Temporary method here to cause a compiler error - note the new parameters for this method. | |||
| int findFreeVoice (const bool) const { return 0; } | |||
| #endif | |||
| private: | |||
| //============================================================================== | |||
| double sampleRate; | |||
| uint32 lastNoteOnCounter; | |||
| bool shouldStealNotes; | |||
| BigInteger sustainPedalsDown; | |||
| void handleMidiEvent (const MidiMessage& m); | |||
| void stopVoice (SynthesiserVoice* voice, bool allowTailOff); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| virtual int findFreeVoice (const bool) const { return 0; } | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser); | |||
| }; | |||
| @@ -33,7 +33,7 @@ | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 1 | |||
| #define JUCE_MINOR_VERSION 53 | |||
| #define JUCE_BUILDNUMBER 92 | |||
| #define JUCE_BUILDNUMBER 93 | |||
| /** Current Juce version number. | |||
| @@ -925,16 +925,14 @@ AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& | |||
| jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels); | |||
| jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels); | |||
| if (deviceManager_.getAvailableDeviceTypes().size() > 1) | |||
| const OwnedArray<AudioIODeviceType>& types = deviceManager_.getAvailableDeviceTypes(); | |||
| if (types.size() > 1) | |||
| { | |||
| deviceTypeDropDown = new ComboBox (String::empty); | |||
| for (int i = 0; i < deviceManager_.getAvailableDeviceTypes().size(); ++i) | |||
| { | |||
| deviceTypeDropDown | |||
| ->addItem (deviceManager_.getAvailableDeviceTypes().getUnchecked(i)->getTypeName(), | |||
| i + 1); | |||
| } | |||
| for (int i = 0; i < types.size(); ++i) | |||
| deviceTypeDropDown->addItem (types.getUnchecked(i)->getTypeName(), i + 1); | |||
| addAndMakeVisible (deviceTypeDropDown); | |||
| deviceTypeDropDown->addListener (this); | |||