/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ SamplerSound::SamplerSound (const String& soundName, AudioFormatReader& source, const BigInteger& notes, const int midiNoteForNormalPitch, const double attackTimeSecs, const double releaseTimeSecs, const double maxSampleLengthSeconds) : name (soundName), midiNotes (notes), midiRootNote (midiNoteForNormalPitch) { sourceSampleRate = source.sampleRate; if (sourceSampleRate <= 0 || source.lengthInSamples <= 0) { length = 0; attackSamples = 0; releaseSamples = 0; } else { length = jmin ((int) source.lengthInSamples, (int) (maxSampleLengthSeconds * sourceSampleRate)); data = new AudioSampleBuffer (jmin (2, (int) source.numChannels), length + 4); source.read (data, 0, length + 4, 0, true, true); attackSamples = roundToInt (attackTimeSecs * sourceSampleRate); releaseSamples = roundToInt (releaseTimeSecs * sourceSampleRate); } } SamplerSound::~SamplerSound() { } bool SamplerSound::appliesToNote (int midiNoteNumber) { return midiNotes [midiNoteNumber]; } bool SamplerSound::appliesToChannel (int /*midiChannel*/) { return true; } //============================================================================== SamplerVoice::SamplerVoice() : pitchRatio (0.0), sourceSamplePosition (0.0), lgain (0.0f), rgain (0.0f), attackReleaseLevel (0), attackDelta (0), releaseDelta (0), isInAttack (false), isInRelease (false) { } SamplerVoice::~SamplerVoice() { } bool SamplerVoice::canPlaySound (SynthesiserSound* sound) { return dynamic_cast (sound) != nullptr; } void SamplerVoice::startNote (const int midiNoteNumber, const float velocity, SynthesiserSound* s, const int /*currentPitchWheelPosition*/) { if (const SamplerSound* const sound = dynamic_cast (s)) { pitchRatio = pow (2.0, (midiNoteNumber - sound->midiRootNote) / 12.0) * sound->sourceSampleRate / getSampleRate(); sourceSamplePosition = 0.0; lgain = velocity; rgain = velocity; isInAttack = (sound->attackSamples > 0); isInRelease = false; if (isInAttack) { attackReleaseLevel = 0.0f; attackDelta = (float) (pitchRatio / sound->attackSamples); } else { attackReleaseLevel = 1.0f; attackDelta = 0.0f; } if (sound->releaseSamples > 0) releaseDelta = (float) (-pitchRatio / sound->releaseSamples); else releaseDelta = -1.0f; } else { jassertfalse; // this object can only play SamplerSounds! } } void SamplerVoice::stopNote (float /*velocity*/, bool allowTailOff) { if (allowTailOff) { isInAttack = false; isInRelease = true; } else { clearCurrentNote(); } } void SamplerVoice::pitchWheelMoved (const int /*newValue*/) { } void SamplerVoice::controllerMoved (const int /*controllerNumber*/, const int /*newValue*/) { } //============================================================================== void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) { if (const SamplerSound* const playingSound = static_cast (getCurrentlyPlayingSound().get())) { const float* const inL = playingSound->data->getReadPointer (0); const float* const inR = playingSound->data->getNumChannels() > 1 ? playingSound->data->getReadPointer (1) : nullptr; float* outL = outputBuffer.getWritePointer (0, startSample); float* outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer (1, startSample) : nullptr; while (--numSamples >= 0) { const int pos = (int) sourceSamplePosition; const float alpha = (float) (sourceSamplePosition - pos); const float invAlpha = 1.0f - alpha; // just using a very simple linear interpolation here.. float l = (inL [pos] * invAlpha + inL [pos + 1] * alpha); float r = (inR != nullptr) ? (inR [pos] * invAlpha + inR [pos + 1] * alpha) : l; l *= lgain; r *= rgain; if (isInAttack) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += attackDelta; if (attackReleaseLevel >= 1.0f) { attackReleaseLevel = 1.0f; isInAttack = false; } } else if (isInRelease) { l *= attackReleaseLevel; r *= attackReleaseLevel; attackReleaseLevel += releaseDelta; if (attackReleaseLevel <= 0.0f) { stopNote (0.0f, false); break; } } if (outR != nullptr) { *outL++ += l; *outR++ += r; } else { *outL++ += (l + r) * 0.5f; } sourceSamplePosition += pitchRatio; if (sourceSamplePosition > playingSound->length) { stopNote (0.0f, false); break; } } } }