/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #pragma once class MPEDemoSynthVoice : public MPESynthesiserVoice { public: //============================================================================== MPEDemoSynthVoice() : phase (0.0), phaseDelta (0.0), tailOff (0.0) { } //============================================================================== void noteStarted() override { jassert (currentlyPlayingNote.isValid()); jassert (currentlyPlayingNote.keyState == MPENote::keyDown || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained); level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat()); frequency.setValue (currentlyPlayingNote.getFrequencyInHertz()); timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat()); phase = 0.0; const double cyclesPerSample = frequency.getNextValue() / currentSampleRate; phaseDelta = 2.0 * double_Pi * cyclesPerSample; tailOff = 0.0; } void noteStopped (bool allowTailOff) override { jassert (currentlyPlayingNote.keyState == MPENote::off); if (allowTailOff) { // start a tail-off by setting this flag. The render callback will pick up on // this and do a fade out, calling clearCurrentNote() when it's finished. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the // stopNote method could be called more than once. tailOff = 1.0; } else { // we're being told to stop playing immediately, so reset everything.. clearCurrentNote(); phaseDelta = 0.0; } } void notePressureChanged() override { level.setValue (currentlyPlayingNote.pressure.asUnsignedFloat()); } void notePitchbendChanged() override { frequency.setValue (currentlyPlayingNote.getFrequencyInHertz()); } void noteTimbreChanged() override { timbre.setValue (currentlyPlayingNote.timbre.asUnsignedFloat()); } void noteKeyStateChanged() override { } void setCurrentSampleRate (double newRate) override { if (currentSampleRate != newRate) { noteStopped (false); currentSampleRate = newRate; level.reset (currentSampleRate, smoothingLengthInSeconds); timbre.reset (currentSampleRate, smoothingLengthInSeconds); frequency.reset (currentSampleRate, smoothingLengthInSeconds); } } //============================================================================== virtual void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override { if (phaseDelta != 0.0) { if (tailOff > 0) { while (--numSamples >= 0) { const float currentSample = getNextSample() * (float) tailOff; for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); ++startSample; tailOff *= 0.99; if (tailOff <= 0.005) { clearCurrentNote(); phaseDelta = 0.0; break; } } } else { while (--numSamples >= 0) { const float currentSample = getNextSample(); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); ++startSample; } } } } private: //============================================================================== float getNextSample() noexcept { const double levelDb = (level.getNextValue() - 1.0) * maxLevelDb; const double amplitude = std::pow (10.0f, 0.05f * levelDb) * maxLevel; // timbre is used to blend between a sine and a square. const double f1 = std::sin (phase); const double f2 = std::copysign (1.0, f1); const double a2 = timbre.getNextValue(); const double a1 = 1.0 - a2; const float nextSample = float (amplitude * ((a1 * f1) + (a2 * f2))); const double cyclesPerSample = frequency.getNextValue() / currentSampleRate; phaseDelta = 2.0 * double_Pi * cyclesPerSample; phase = std::fmod (phase + phaseDelta, 2.0 * double_Pi); return nextSample; } //============================================================================== LinearSmoothedValue level, timbre, frequency; double phase, phaseDelta, tailOff; const double maxLevel = 0.05f; const double maxLevelDb = 31.0f; const double smoothingLengthInSeconds = 0.01; };