/*************************************************************************************
 * 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 "SFZEG.h"

namespace sfzero
{

static const float fastReleaseTime = 0.01f;

EG::EG()
    : segment_(), sampleRate_(0), exponentialDecay_(false), level_(0), slope_(0), samplesUntilNextSegment_(0), segmentIsExponential_(false)
{
}

void EG::setExponentialDecay(bool newExponentialDecay) { exponentialDecay_ = newExponentialDecay; }

void EG::startNote(const EGParameters *newParameters, float floatVelocity, double newSampleRate,
                           const EGParameters *velMod)
{
  parameters_ = *newParameters;
  if (velMod)
  {
    parameters_.delay += floatVelocity * velMod->delay;
    parameters_.attack += floatVelocity * velMod->attack;
    parameters_.hold += floatVelocity * velMod->hold;
    parameters_.decay += floatVelocity * velMod->decay;
    parameters_.sustain += floatVelocity * velMod->sustain;
    if (parameters_.sustain < 0.0)
    {
      parameters_.sustain = 0.0;
    }
    else if (parameters_.sustain > 100.0)
    {
      parameters_.sustain = 100.0;
    }
    parameters_.release += floatVelocity * velMod->release;
  }
  sampleRate_ = newSampleRate;

  startDelay();
}

void EG::nextSegment()
{
  switch (segment_)
  {
  case Delay:
    startAttack();
    break;

  case Attack:
    startHold();
    break;

  case Hold:
    startDecay();
    break;

  case Decay:
    startSustain();
    break;

  case Sustain:
    // Shouldn't be called.
    break;

  case Release:
  default:
    segment_ = Done;
    break;
  }
}

void EG::noteOff() { startRelease(); }

void EG::fastRelease()
{
  segment_ = Release;
  samplesUntilNextSegment_ = static_cast<int>(fastReleaseTime * sampleRate_);
  slope_ = -level_ / samplesUntilNextSegment_;
  segmentIsExponential_ = false;
}

void EG::startDelay()
{
  if (parameters_.delay <= 0)
  {
    startAttack();
  }
  else
  {
    segment_ = Delay;
    level_ = 0.0;
    slope_ = 0.0;
    samplesUntilNextSegment_ = static_cast<int>(parameters_.delay * sampleRate_);
    segmentIsExponential_ = false;
  }
}

void EG::startAttack()
{
  if (parameters_.attack <= 0)
  {
    startHold();
  }
  else
  {
    segment_ = Attack;
    level_ = parameters_.start / 100.0f;
    samplesUntilNextSegment_ = static_cast<int>(parameters_.attack * sampleRate_);
    slope_ = 1.0f / samplesUntilNextSegment_;
    segmentIsExponential_ = false;
  }
}

void EG::startHold()
{
  if (parameters_.hold <= 0)
  {
    level_ = 1.0;
    startDecay();
  }
  else
  {
    segment_ = Hold;
    samplesUntilNextSegment_ = static_cast<int>(parameters_.hold * sampleRate_);
    level_ = 1.0;
    slope_ = 0.0;
    segmentIsExponential_ = false;
  }
}

void EG::startDecay()
{
  if (parameters_.decay <= 0)
  {
    startSustain();
  }
  else
  {
    segment_ = Decay;
    samplesUntilNextSegment_ = static_cast<int>(parameters_.decay * sampleRate_);
    level_ = 1.0;
    if (exponentialDecay_)
    {
      // I don't truly understand this; just following what LinuxSampler does.
      float mysterySlope = -9.226f / samplesUntilNextSegment_;
      slope_ = exp(mysterySlope);
      segmentIsExponential_ = true;
      if (parameters_.sustain > 0.0)
      {
        // Again, this is following LinuxSampler's example, which is similar to
        // SF2-style decay, where "decay" specifies the time it would take to
        // get to zero, not to the sustain level.  The SFZ spec is not that
        // specific about what "decay" means, so perhaps it's really supposed
        // to specify the time to reach the sustain level.
        samplesUntilNextSegment_ = static_cast<int>(log((parameters_.sustain / 100.0) / level_) / mysterySlope);
        if (samplesUntilNextSegment_ <= 0)
        {
          startSustain();
        }
      }
    }
    else
    {
      slope_ = (parameters_.sustain / 100.0f - 1.0f) / samplesUntilNextSegment_;
      segmentIsExponential_ = false;
    }
  }
}

void EG::startSustain()
{
  if (parameters_.sustain <= 0)
  {
    startRelease();
  }
  else
  {
    segment_ = Sustain;
    level_ = parameters_.sustain / 100.0f;
    slope_ = 0.0;
    samplesUntilNextSegment_ = 0x7FFFFFFF;
    segmentIsExponential_ = false;
  }
}

void EG::startRelease()
{
  float release = parameters_.release;

  if (release <= 0)
  {
    // Enforce a short release, to prevent clicks.
    release = fastReleaseTime;
  }

  segment_ = Release;
  samplesUntilNextSegment_ = static_cast<int>(release * sampleRate_);
  if (exponentialDecay_)
  {
    // I don't truly understand this; just following what LinuxSampler does.
    float mysterySlope = -9.226f / samplesUntilNextSegment_;
    slope_ = exp(mysterySlope);
    segmentIsExponential_ = true;
  }
  else
  {
    slope_ = -level_ / samplesUntilNextSegment_;
    segmentIsExponential_ = false;
  }
}

const float EG::BottomLevel = 0.001f;

}
