// Copyright 2015 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// Polyblep oscillator used for string synth synthesis.

#ifndef RINGS_DSP_STRING_SYNTH_OSCILLATOR_H_
#define RINGS_DSP_STRING_SYNTH_OSCILLATOR_H_

#include "stmlib/stmlib.h"

#include "stmlib/dsp/dsp.h"
#include "stmlib/dsp/parameter_interpolator.h"
#include "stmlib/dsp/units.h"

namespace rings {

using namespace stmlib;

enum OscillatorShape {
  OSCILLATOR_SHAPE_BRIGHT_SQUARE,
  OSCILLATOR_SHAPE_SQUARE,
  OSCILLATOR_SHAPE_DARK_SQUARE,
  OSCILLATOR_SHAPE_TRIANGLE,
};

class StringSynthOscillator {
 public:
  StringSynthOscillator() { }
  ~StringSynthOscillator() { }

  inline void Init() {
    phase_ = 0.0f;
    phase_increment_ = 0.01f;
    filter_state_ = 0.0f;
    high_ = false;

    next_sample_ = 0.0f;
    next_sample_saw_ = 0.0f;

    gain_ = 0.0f;
    gain_saw_ = 0.0f;
  }
  
  template<OscillatorShape shape, bool interpolate_pitch>
  inline void Render(
      float target_increment,
      float target_gain,
      float target_gain_saw,
      float* out,
      size_t size) {
    // Cut harmonics above 12kHz, and low-pass harmonics above 8kHz to clear
    // highs
    if (target_increment >= 0.17f) {
      target_gain *= 1.0f - (target_increment - 0.17f) * 12.5f;
      if (target_increment >= 0.25f) {
        return;
      }
    }
    float phase = phase_;
    ParameterInterpolator phase_increment(
        &phase_increment_,
        target_increment,
        size);
    ParameterInterpolator gain(&gain_, target_gain, size);
    ParameterInterpolator gain_saw(&gain_saw_, target_gain_saw, size);

    float next_sample = next_sample_;
    float next_sample_saw = next_sample_saw_;
    float filter_state = filter_state_;
    bool high = high_;

    while (size--) {
      float this_sample = next_sample;
      float this_sample_saw = next_sample_saw;
      next_sample = 0.0f;
      next_sample_saw = 0.0f;

      float increment = interpolate_pitch
          ? phase_increment.Next()
          : target_increment;
      phase += increment;
    
      float sample = 0.0f;
      const float pw = 0.5f;

      if (!high && phase >= pw) {
        float t = (phase - pw) / increment;
        this_sample += ThisBlepSample(t);
        next_sample += NextBlepSample(t);
        high = true;
      }
      if (phase >= 1.0f) {
        phase -= 1.0f;
        float t = phase / increment;
        float a = ThisBlepSample(t);
        float b = NextBlepSample(t);
        this_sample -= a;
        next_sample -= b;
        this_sample_saw -= a;
        next_sample_saw -= b;
        high = false;
      }
      
      next_sample += phase < pw ? 0.0f : 1.0f;
      next_sample_saw += phase;
      
      if (shape == OSCILLATOR_SHAPE_TRIANGLE) {
        const float integrator_coefficient = increment * 0.125f;
        this_sample = 64.0f * (this_sample - 0.5f);
        filter_state += integrator_coefficient * (this_sample - filter_state);
        sample = filter_state;
      } else if (shape == OSCILLATOR_SHAPE_DARK_SQUARE) {
        const float integrator_coefficient = increment * 2.0f;
        this_sample = 4.0f * (this_sample - 0.5f);
        filter_state += integrator_coefficient * (this_sample - filter_state);
        sample = filter_state;
      } else if (shape == OSCILLATOR_SHAPE_BRIGHT_SQUARE) {
        const float integrator_coefficient = increment * 2.0f;
        this_sample = 2.0f * this_sample - 1.0f;
        filter_state += integrator_coefficient * (this_sample - filter_state);
        sample = (this_sample - filter_state) * 0.5f;
      } else {
        this_sample = 2.0f * this_sample - 1.0f;
        sample = this_sample;
      }
      this_sample_saw = 2.0f * this_sample_saw - 1.0f;
      
      *out++ += sample * gain.Next() + this_sample_saw * gain_saw.Next();
    }
    high_ = high;
    phase_ = phase;
    next_sample_ = next_sample;
    next_sample_saw_ = next_sample_saw;
    filter_state_ = filter_state;
  }

 private:
  static inline float ThisBlepSample(float t) {
    return 0.5f * t * t;
  }
  static inline float NextBlepSample(float t) {
    t = 1.0f - t;
    return -0.5f * t * t;
  }

  bool high_;
  float phase_;
  float phase_increment_;
  float next_sample_;
  float next_sample_saw_;
  float filter_state_;
  float gain_;
  float gain_saw_;

  DISALLOW_COPY_AND_ASSIGN(StringSynthOscillator);
};

}  // namespace rings

#endif  // RINGS_DSP_STRING_SYNTH_OSCILLATOR_H_
