| 
							- /*************************************************************************************
 -  * Original code copyright (C) 2012 Steve Folta
 -  * Converted to Juce module (C) 2016 Leo Olivers
 -  * Forked from https://github.com/stevefolta/SFZero
 -  * For license info please see the LICENSE file distributed with this source code
 -  *************************************************************************************/
 - 
 - #include "SFZDebug.h"
 - #include "SFZRegion.h"
 - #include "SFZSample.h"
 - #include "SFZSound.h"
 - #include "SFZVoice.h"
 - 
 - #include "water/midi/MidiMessage.h"
 - 
 - #include <cmath>
 - 
 - namespace sfzero
 - {
 - 
 - static const float globalGain = -1.0;
 - 
 - Voice::Voice()
 -     : region_(nullptr), trigger_(0), curMidiNote_(0), curPitchWheel_(0), pitchRatio_(0), noteGainLeft_(0), noteGainRight_(0),
 -       sourceSamplePosition_(0), sampleEnd_(0), loopStart_(0), loopEnd_(0), numLoops_(0), curVelocity_(0)
 - {
 -   ampeg_.setExponentialDecay(true);
 - }
 - 
 - Voice::~Voice() {}
 - 
 - bool Voice::canPlaySound(water::SynthesiserSound *sound) { return dynamic_cast<Sound *>(sound) != nullptr; }
 - 
 - void Voice::startNote(int midiNoteNumber, float floatVelocity, water::SynthesiserSound *soundIn,
 -                               int currentPitchWheelPosition)
 - {
 -   Sound *sound = dynamic_cast<Sound *>(soundIn);
 - 
 -   if (sound == nullptr)
 -   {
 -     killNote();
 -     return;
 -   }
 - 
 -   int velocity = static_cast<int>(floatVelocity * 127.0);
 -   curVelocity_ = velocity;
 -   if (region_ == nullptr)
 -   {
 -     region_ = sound->getRegionFor(midiNoteNumber, velocity);
 -   }
 -   if ((region_ == nullptr) || (region_->sample == nullptr) || (region_->sample->getBuffer() == nullptr))
 -   {
 -     killNote();
 -     return;
 -   }
 -   if (region_->negative_end)
 -   {
 -     killNote();
 -     return;
 -   }
 - 
 -   // Pitch.
 -   curMidiNote_ = midiNoteNumber;
 -   curPitchWheel_ = currentPitchWheelPosition;
 -   calcPitchRatio();
 - 
 -   // Gain.
 -   double noteGainDB = globalGain + region_->volume;
 -   // Thanks to <http:://www.drealm.info/sfz/plj-sfz.xhtml> for explaining the
 -   // velocity curve in a way that I could understand, although they mean
 -   // "log10" when they say "log".
 -   double velocityGainDB = -20.0 * log10((127.0 * 127.0) / (velocity * velocity));
 -   velocityGainDB *= region_->amp_veltrack / 100.0;
 -   noteGainDB += velocityGainDB;
 -   noteGainLeft_ = noteGainRight_ = decibelsToGain(noteGainDB);
 -   // The SFZ spec is silent about the pan curve, but a 3dB pan law seems
 -   // common.  This sqrt() curve matches what Dimension LE does; Alchemy Free
 -   // seems closer to sin(adjustedPan * pi/2).
 -   double adjustedPan = (region_->pan + 100.0) / 200.0;
 -   noteGainLeft_ *= static_cast<float>(sqrt(1.0 - adjustedPan));
 -   noteGainRight_ *= static_cast<float>(sqrt(adjustedPan));
 -   ampeg_.startNote(®ion_->ampeg, floatVelocity, getSampleRate(), ®ion_->ampeg_veltrack);
 - 
 -   // Offset/end.
 -   sourceSamplePosition_ = static_cast<double>(region_->offset);
 -   sampleEnd_ = region_->sample->getSampleLength();
 -   if ((region_->end > 0) && (region_->end < sampleEnd_))
 -   {
 -     sampleEnd_ = region_->end + 1;
 -   }
 - 
 -   // Loop.
 -   loopStart_ = loopEnd_ = 0;
 -   Region::LoopMode loopMode = region_->loop_mode;
 -   if (loopMode == Region::sample_loop)
 -   {
 -     if (region_->sample->getLoopStart() < region_->sample->getLoopEnd())
 -     {
 -       loopMode = Region::loop_continuous;
 -     }
 -     else
 -     {
 -       loopMode = Region::no_loop;
 -     }
 -   }
 -   if ((loopMode != Region::no_loop) && (loopMode != Region::one_shot))
 -   {
 -     if (region_->loop_start < region_->loop_end)
 -     {
 -       loopStart_ = region_->loop_start;
 -       loopEnd_ = region_->loop_end;
 -     }
 -     else
 -     {
 -       loopStart_ = region_->sample->getLoopStart();
 -       loopEnd_ = region_->sample->getLoopEnd();
 -     }
 -   }
 -   numLoops_ = 0;
 - }
 - 
 - void Voice::stopNote(float /*velocity*/, bool allowTailOff)
 - {
 -   if (!allowTailOff || (region_ == nullptr))
 -   {
 -     killNote();
 -     return;
 -   }
 - 
 -   if (region_->loop_mode != Region::one_shot)
 -   {
 -     ampeg_.noteOff();
 -   }
 -   if (region_->loop_mode == Region::loop_sustain)
 -   {
 -     // Continue playing, but stop looping.
 -     loopEnd_ = loopStart_;
 -   }
 - }
 - 
 - void Voice::stopNoteForGroup()
 - {
 -   if (region_->off_mode == Region::fast)
 -   {
 -     ampeg_.fastRelease();
 -   }
 -   else
 -   {
 -     ampeg_.noteOff();
 -   }
 - }
 - 
 - void Voice::stopNoteQuick() { ampeg_.fastRelease(); }
 - void Voice::pitchWheelMoved(int newValue)
 - {
 -   if (region_ == nullptr)
 -   {
 -     return;
 -   }
 - 
 -   curPitchWheel_ = newValue;
 -   calcPitchRatio();
 - }
 - 
 - void Voice::controllerMoved(int /*controllerNumber*/, int /*newValue*/) { /***/}
 - void Voice::renderNextBlock(water::AudioSampleBuffer &outputBuffer, int startSample, int numSamples)
 - {
 -   if (region_ == nullptr)
 -   {
 -     return;
 -   }
 - 
 -   water::AudioSampleBuffer *buffer = region_->sample->getBuffer();
 -   const float *inL = buffer->getReadPointer(0, 0);
 -   const float *inR = buffer->getNumChannels() > 1 ? buffer->getReadPointer(1, 0) : nullptr;
 - 
 -   float *outL = outputBuffer.getWritePointer(0, startSample);
 -   float *outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer(1, startSample) : nullptr;
 - 
 -   int bufferNumSamples = buffer->getNumSamples(); // leoo
 - 
 -   // Cache some values, to give them at least some chance of ending up in
 -   // registers.
 -   double sourceSamplePosition = this->sourceSamplePosition_;
 -   float ampegGain = ampeg_.getLevel();
 -   float ampegSlope = ampeg_.getSlope();
 -   int samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment();
 -   bool ampSegmentIsExponential = ampeg_.getSegmentIsExponential();
 -   float loopStart = static_cast<float>(this->loopStart_);
 -   float loopEnd = static_cast<float>(this->loopEnd_);
 -   float sampleEnd = static_cast<float>(this->sampleEnd_);
 - 
 -   while (--numSamples >= 0)
 -   {
 -     int pos = static_cast<int>(sourceSamplePosition);
 -     jassert(pos >= 0 && pos < bufferNumSamples); // leoo
 -     float alpha = static_cast<float>(sourceSamplePosition - pos);
 -     float invAlpha = 1.0f - alpha;
 -     int nextPos = pos + 1;
 -     if ((loopStart < loopEnd) && (nextPos > loopEnd))
 -     {
 -       nextPos = static_cast<int>(loopStart);
 -     }
 - 
 -     // Simple linear interpolation with buffer overrun check
 -     float nextL = nextPos < bufferNumSamples ? inL[nextPos] : inL[pos];
 -     float nextR = inR ? (nextPos < bufferNumSamples ? inR[nextPos] : inR[pos]) : nextL;
 -     float l = (inL[pos] * invAlpha + nextL * alpha);
 -     float r = inR ? (inR[pos] * invAlpha + nextR * alpha) : l;
 - 
 -     //// Simple linear interpolation, old version (possible buffer overrun with non-loop??)
 -     // float l = (inL[pos] * invAlpha + inL[nextPos] * alpha);
 -     // float r = inR ? (inR[pos] * invAlpha + inR[nextPos] * alpha) : l;
 - 
 -     float gainLeft = noteGainLeft_ * ampegGain;
 -     float gainRight = noteGainRight_ * ampegGain;
 -     l *= gainLeft;
 -     r *= gainRight;
 -     // Shouldn't we dither here?
 - 
 -     if (outR)
 -     {
 -       *outL++ += l;
 -       *outR++ += r;
 -     }
 -     else
 -     {
 -       *outL++ += (l + r) * 0.5f;
 -     }
 - 
 -     // Next sample.
 -     sourceSamplePosition += pitchRatio_;
 -     if ((loopStart < loopEnd) && (sourceSamplePosition > loopEnd))
 -     {
 -       sourceSamplePosition = loopStart;
 -       numLoops_ += 1;
 -     }
 - 
 -     // Update EG.
 -     if (ampSegmentIsExponential)
 -     {
 -       ampegGain *= ampegSlope;
 -     }
 -     else
 -     {
 -       ampegGain += ampegSlope;
 -     }
 -     if (--samplesUntilNextAmpSegment < 0)
 -     {
 -       ampeg_.setLevel(ampegGain);
 -       ampeg_.nextSegment();
 -       ampegGain = ampeg_.getLevel();
 -       ampegSlope = ampeg_.getSlope();
 -       samplesUntilNextAmpSegment = ampeg_.getSamplesUntilNextSegment();
 -       ampSegmentIsExponential = ampeg_.getSegmentIsExponential();
 -     }
 - 
 -     if ((sourceSamplePosition >= sampleEnd) || ampeg_.isDone())
 -     {
 -       killNote();
 -       break;
 -     }
 -   }
 - 
 -   this->sourceSamplePosition_ = sourceSamplePosition;
 -   ampeg_.setLevel(ampegGain);
 -   ampeg_.setSamplesUntilNextSegment(samplesUntilNextAmpSegment);
 - }
 - 
 - bool Voice::isPlayingNoteDown() { return region_ && region_->trigger != Region::release; }
 - 
 - bool Voice::isPlayingOneShot() { return region_ && region_->loop_mode == Region::one_shot; }
 - 
 - int Voice::getGroup() { return region_ ? region_->group : 0; }
 - 
 - water::int64 Voice::getOffBy() { return region_ ? region_->off_by : 0; }
 - 
 - void Voice::setRegion(Region *nextRegion) { region_ = nextRegion; }
 - 
 - water::String Voice::infoString()
 - {
 -   const char *egSegmentNames[] = {"delay", "attack", "hold", "decay", "sustain", "release", "done"};
 - 
 -   const static int numEGSegments(sizeof(egSegmentNames) / sizeof(egSegmentNames[0]));
 - 
 -   const char *egSegmentName = "-Invalid-";
 -   int egSegmentIndex = ampeg_.segmentIndex();
 -   if ((egSegmentIndex >= 0) && (egSegmentIndex < numEGSegments))
 -   {
 -     egSegmentName = egSegmentNames[egSegmentIndex];
 -   }
 - 
 -   water::String info;
 -   info << "note: " << curMidiNote_ << ", vel: " << curVelocity_ << ", pan: " << region_->pan << ", eg: " << egSegmentName
 -        << ", loops: " << numLoops_;
 -   return info;
 - }
 - 
 - void Voice::calcPitchRatio()
 - {
 -   double note = curMidiNote_;
 - 
 -   note += region_->transpose;
 -   note += region_->tune / 100.0;
 - 
 -   double adjustedPitch = region_->pitch_keycenter + (note - region_->pitch_keycenter) * (region_->pitch_keytrack / 100.0);
 -   if (curPitchWheel_ != 8192)
 -   {
 -     double wheel = ((2.0 * curPitchWheel_ / 16383.0) - 1.0);
 -     if (wheel > 0)
 -     {
 -       adjustedPitch += wheel * region_->bend_up / 100.0;
 -     }
 -     else
 -     {
 -       adjustedPitch += wheel * region_->bend_down / -100.0;
 -     }
 -   }
 -   double targetFreq = fractionalMidiNoteInHz(adjustedPitch);
 -   double naturalFreq = water::MidiMessage::getMidiNoteInHertz(region_->pitch_keycenter);
 -   pitchRatio_ = (targetFreq * region_->sample->getSampleRate()) / (naturalFreq * getSampleRate());
 - }
 - 
 - void Voice::killNote()
 - {
 -   region_ = nullptr;
 -   clearCurrentNote();
 - }
 - 
 - double Voice::fractionalMidiNoteInHz(double note, double freqOfA)
 - {
 -   // Like MidiMessage::getMidiNoteInHertz(), but with a float note.
 -   note -= 69;
 -   // Now 0 = A
 -   return freqOfA * pow(2.0, note / 12.0);
 - }
 - 
 - }
 
 
  |