// Copyright 2012 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.
//
// -----------------------------------------------------------------------------
//
// Oscillator - analog style waveforms.

#include "braids/analog_oscillator.h"

#include "stmlib/utils/dsp.h"

#include "braids/resources.h"
#include "braids/parameter_interpolation.h"

namespace braids {

using namespace stmlib;

static const size_t kNumZones = 15;

static const uint16_t kHighestNote = 128 * 128;
static const uint16_t kPitchTableStart = 128 * 128;
static const uint16_t kOctave = 12 * 128;

uint32_t AnalogOscillator::ComputePhaseIncrement(int16_t midi_pitch) {
  if (midi_pitch >= kHighestNote) {
    midi_pitch = kHighestNote - 1;
  }
  
  int32_t ref_pitch = midi_pitch;
  ref_pitch -= kPitchTableStart;
  
  size_t num_shifts = 0;
  while (ref_pitch < 0) {
    ref_pitch += kOctave;
    ++num_shifts;
  }
  
  uint32_t a = lut_oscillator_increments[ref_pitch >> 4];
  uint32_t b = lut_oscillator_increments[(ref_pitch >> 4) + 1];
  uint32_t phase_increment = a + \
      (static_cast<int32_t>(b - a) * (ref_pitch & 0xf) >> 4);
  phase_increment >>= num_shifts;
  return phase_increment;
}

void AnalogOscillator::Render(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  RenderFn fn = fn_table_[shape_];
  
  if (shape_ != previous_shape_) {
    Init();
    previous_shape_ = shape_;
  }
  
  phase_increment_ = ComputePhaseIncrement(pitch_);
  
  if (pitch_ > kHighestNote) {
    pitch_ = kHighestNote;
  } else if (pitch_ < 0) {
    pitch_ = 0;
  }
  
  (this->*fn)(sync_in, buffer, sync_out, size);
}

void AnalogOscillator::RenderCSaw(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  int32_t next_sample = next_sample_;
  while (size--) {
    bool sync_reset = false;
    bool self_reset = false;
    bool transition_during_reset = false;
    uint32_t reset_time = 0;
    INTERPOLATE_PHASE_INCREMENT
    uint32_t pw = static_cast<uint32_t>(parameter_) * 49152;
    if (pw < 8 * phase_increment) {
      pw = 8 * phase_increment;
    }
    
    int32_t this_sample = next_sample;
    next_sample = 0;

    if (*sync_in) {
      // sync_in contain the fractional reset time.
      reset_time = static_cast<uint32_t>(*sync_in - 1) << 9;
      uint32_t phase_at_reset = phase_ + \
          (65535 - reset_time) * (phase_increment >> 16);
      sync_reset = true;
      transition_during_reset = false;
      if (phase_at_reset < phase_ || (!high_ && phase_at_reset >= pw)) {
        transition_during_reset = true;
      }
      if (phase_ >= pw) {
        discontinuity_depth_ = -2048 + (aux_parameter_ >> 2);
        int32_t before = (phase_at_reset >> 18);
        int16_t after = discontinuity_depth_;
        int32_t discontinuity = after - before;
        this_sample += discontinuity * ThisBlepSample(reset_time) >> 15;
        next_sample += discontinuity * NextBlepSample(reset_time) >> 15;
      }
    }
    sync_in++;

    phase_ += phase_increment;
    if (phase_ < phase_increment) {
      self_reset = true;
    }
    if (sync_out) {
      if (phase_ < phase_increment) {
        *sync_out++ = phase_ / (phase_increment >> 7) + 1;
      } else {
        *sync_out++ = 0;
      }
    }
    
    while (transition_during_reset || !sync_reset) {
      if (!high_) {
        if (phase_ < pw) {
          break;
        }
        uint32_t t = (phase_ - pw) / (phase_increment >> 16);
        int16_t before = discontinuity_depth_;
        int16_t after = phase_ >> 18;
        int16_t discontinuity = after - before;
        this_sample += discontinuity * ThisBlepSample(t) >> 15;
        next_sample += discontinuity * NextBlepSample(t) >> 15;
        high_ = true;
      }
      if (high_) {
        if (!self_reset) {
          break;
        }
        self_reset = false;
        discontinuity_depth_ = -2048 + (aux_parameter_ >> 2);
        uint32_t t = phase_ / (phase_increment >> 16);
        int16_t before = 16383;
        int16_t after = discontinuity_depth_;
        int16_t discontinuity = after - before;
        this_sample += discontinuity * ThisBlepSample(t) >> 15;
        next_sample += discontinuity * NextBlepSample(t) >> 15;
        high_ = false;
      }
    }

    if (sync_reset) {
      phase_ = reset_time * (phase_increment >> 16);
      high_ = false;
    }

    next_sample += phase_ < pw
        ? discontinuity_depth_
        : phase_ >> 18;
    *buffer++ = (this_sample - 8192) << 1;
  }
  next_sample_ = next_sample;
  END_INTERPOLATE_PHASE_INCREMENT
}

void AnalogOscillator::RenderSquare(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  if (parameter_ > 32000) {
    parameter_ = 32000;
  }
  
  int32_t next_sample = next_sample_;
  while (size--) {
    bool sync_reset = false;
    bool self_reset = false;
    bool transition_during_reset = false;
    uint32_t reset_time = 0;
    
    INTERPOLATE_PHASE_INCREMENT
    uint32_t pw = static_cast<uint32_t>(32768 - parameter_) << 16;
    
    int32_t this_sample = next_sample;
    next_sample = 0;
    
    if (*sync_in) {
      // sync_in contain the fractional reset time.
      reset_time = static_cast<uint32_t>(*sync_in - 1) << 9;
      uint32_t phase_at_reset = phase_ + \
          (65535 - reset_time) * (phase_increment >> 16);
      sync_reset = true;
      if (phase_at_reset < phase_ || (!high_ && phase_at_reset >= pw)) {
        transition_during_reset = true;
      }
      if (phase_at_reset >= pw) {
        this_sample -= ThisBlepSample(reset_time);
        next_sample -= NextBlepSample(reset_time);
      }
    }
    sync_in++;
    
    phase_ += phase_increment;
    if (phase_ < phase_increment) {
      self_reset = true;
    }
    
    if (sync_out) {
      if (phase_ < phase_increment) {
        *sync_out++ = phase_ / (phase_increment >> 7) + 1;
      } else {
        *sync_out++ = 0;
      }
    }
    
    while (transition_during_reset || !sync_reset) {
      if (!high_) {
        if (phase_ < pw) {
          break;
        }
        uint32_t t = (phase_ - pw) / (phase_increment >> 16);
        this_sample += ThisBlepSample(t);
        next_sample += NextBlepSample(t);
        high_ = true;
      }
      if (high_) {
        if (!self_reset) {
          break;
        }
        self_reset = false;
        uint32_t t = phase_ / (phase_increment >> 16);
        this_sample -= ThisBlepSample(t);
        next_sample -= NextBlepSample(t);
        high_ = false;
      }
    }
    
    if (sync_reset) {
      phase_ = reset_time * (phase_increment >> 16);
      high_ = false;
    }
    
    next_sample += phase_ < pw ? 0 : 32767;
    *buffer++ = (this_sample - 16384) << 1;
  }
  next_sample_ = next_sample;
  END_INTERPOLATE_PHASE_INCREMENT
}

void AnalogOscillator::RenderSaw(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  int32_t next_sample = next_sample_;
  while (size--) {
    bool sync_reset = false;
    bool self_reset = false;
    bool transition_during_reset = false;
    uint32_t reset_time = 0;
    
    INTERPOLATE_PHASE_INCREMENT
    int32_t this_sample = next_sample;
    next_sample = 0;

    if (*sync_in) {
      // sync_in contain the fractional reset time.
      reset_time = static_cast<uint32_t>(*sync_in - 1) << 9;
      uint32_t phase_at_reset = phase_ + \
          (65535 - reset_time) * (phase_increment >> 16);
      sync_reset = true;
      if (phase_at_reset < phase_) {
        transition_during_reset = true;
      }
      int32_t discontinuity = phase_at_reset >> 17;
      this_sample -= discontinuity * ThisBlepSample(reset_time) >> 15;
      next_sample -= discontinuity * NextBlepSample(reset_time) >> 15;
    }
    sync_in++;

    phase_ += phase_increment;
    if (phase_ < phase_increment) {
      self_reset = true;
    }

    if (sync_out) {
      if (phase_ < phase_increment) {
        *sync_out++ = phase_ / (phase_increment >> 7) + 1;
      } else {
        *sync_out++ = 0;
      }
    }

    if ((transition_during_reset || !sync_reset) && self_reset) {
      uint32_t t = phase_ / (phase_increment >> 16);
      this_sample -= ThisBlepSample(t);
      next_sample -= NextBlepSample(t);
    }

    if (sync_reset) {
      phase_ = reset_time * (phase_increment >> 16);
      high_ = false;
    }
    
    next_sample += phase_ >> 17;
    *buffer++ = (this_sample - 16384) << 1;
  }
  next_sample_ = next_sample;
  END_INTERPOLATE_PHASE_INCREMENT
}

void AnalogOscillator::RenderVariableSaw(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  int32_t next_sample = next_sample_;
  if (parameter_ < 1024) {
    parameter_ = 1024;
  }
  while (size--) {
    bool sync_reset = false;
    bool self_reset = false;
    bool transition_during_reset = false;
    uint32_t reset_time = 0;

    INTERPOLATE_PHASE_INCREMENT
    uint32_t pw = static_cast<uint32_t>(parameter_) << 16;

    int32_t this_sample = next_sample;
    next_sample = 0;

    if (*sync_in) {
      // sync_in contain the fractional reset time.
      reset_time = static_cast<uint32_t>(*sync_in - 1) << 9;
      uint32_t phase_at_reset = phase_ + \
          (65535 - reset_time) * (phase_increment >> 16);
      sync_reset = true;
      if (phase_at_reset < phase_ || (!high_ && phase_at_reset >= pw)) {
        transition_during_reset = true;
      }
      int32_t before = (phase_at_reset >> 18) + ((phase_at_reset - pw) >> 18);
      int32_t after = (0 >> 18) + ((0 - pw) >> 18);
      int32_t discontinuity = after - before;
      this_sample += discontinuity * ThisBlepSample(reset_time) >> 15;
      next_sample += discontinuity * NextBlepSample(reset_time) >> 15;
    }
    sync_in++;

    phase_ += phase_increment;
    if (phase_ < phase_increment) {
      self_reset = true;
    }

    if (sync_out) {
      if (phase_ < phase_increment) {
        *sync_out++ = phase_ / (phase_increment >> 7) + 1;
      } else {
        *sync_out++ = 0;
      }
    }

    while (transition_during_reset || !sync_reset) {
      if (!high_) {
        if (phase_ < pw) {
          break;
        }
        uint32_t t = (phase_ - pw) / (phase_increment >> 16);
        this_sample -= ThisBlepSample(t) >> 1;
        next_sample -= NextBlepSample(t) >> 1;
        high_ = true;
      }
      if (high_) {
        if (!self_reset) {
          break;
        }
        self_reset = false;
        uint32_t t = phase_ / (phase_increment >> 16);
        this_sample -= ThisBlepSample(t) >> 1;
        next_sample -= NextBlepSample(t) >> 1;
        high_ = false;
      }
    }

    if (sync_reset) {
      phase_ = reset_time * (phase_increment >> 16);
      high_ = false;
    }
    
    next_sample += phase_ >> 18;
    next_sample += (phase_ - pw) >> 18;
    *buffer++ = (this_sample - 16384) << 1;
  }
  next_sample_ = next_sample;
  END_INTERPOLATE_PHASE_INCREMENT
}

void AnalogOscillator::RenderTriangle(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  uint32_t phase = phase_;
  while (size--) {
    INTERPOLATE_PHASE_INCREMENT
    
    int16_t triangle;
    uint16_t phase_16;
    
    if (*sync_in++) {
      phase = 0;
    }
    
    phase += phase_increment >> 1;
    phase_16 = phase >> 16;
    triangle = (phase_16 << 1) ^ (phase_16 & 0x8000 ? 0xffff : 0x0000);
    triangle += 32768;
    *buffer = triangle >> 1;
    
    phase += phase_increment >> 1;
    phase_16 = phase >> 16;
    triangle = (phase_16 << 1) ^ (phase_16 & 0x8000 ? 0xffff : 0x0000);
    triangle += 32768;
    *buffer++ += triangle >> 1;
  }
  phase_ = phase;
  END_INTERPOLATE_PHASE_INCREMENT
}

void AnalogOscillator::RenderSine(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  uint32_t phase = phase_;
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  while (size--) {
    INTERPOLATE_PHASE_INCREMENT
    phase += phase_increment;
    if (*sync_in++) {
      phase = 0;
    }
    *buffer++ = Interpolate824(wav_sine, phase);
  }
  END_INTERPOLATE_PHASE_INCREMENT
  phase_ = phase;
}

void AnalogOscillator::RenderTriangleFold(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  uint32_t phase = phase_;
  
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  BEGIN_INTERPOLATE_PARAMETER
  
  while (size--) {
    INTERPOLATE_PARAMETER
    INTERPOLATE_PHASE_INCREMENT
    
    uint16_t phase_16;
    int16_t triangle;
    int16_t gain = 2048 + (parameter * 30720 >> 15);
    
    if (*sync_in++) {
      phase = 0;
    }
    
    // 2x oversampled WF.
    phase += phase_increment >> 1;
    phase_16 = phase >> 16;
    triangle = (phase_16 << 1) ^ (phase_16 & 0x8000 ? 0xffff : 0x0000);
    triangle += 32768;
    triangle = triangle * gain >> 15;
    triangle = Interpolate88(ws_tri_fold, triangle + 32768);
    *buffer = triangle >> 1;
    
    phase += phase_increment >> 1;
    phase_16 = phase >> 16;
    triangle = (phase_16 << 1) ^ (phase_16 & 0x8000 ? 0xffff : 0x0000);
    triangle += 32768;
    triangle = triangle * gain >> 15;
    triangle = Interpolate88(ws_tri_fold, triangle + 32768);
    *buffer++ += triangle >> 1;
  }
  
  END_INTERPOLATE_PARAMETER
  END_INTERPOLATE_PHASE_INCREMENT
    
  phase_ = phase;
}

void AnalogOscillator::RenderSineFold(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  uint32_t phase = phase_;
  
  BEGIN_INTERPOLATE_PHASE_INCREMENT
  BEGIN_INTERPOLATE_PARAMETER
  
  while (size--) {
    INTERPOLATE_PARAMETER
    INTERPOLATE_PHASE_INCREMENT
    
    int16_t sine;
    int16_t gain = 2048 + (parameter * 30720 >> 15);
    
    if (*sync_in++) {
      phase = 0;
    }
    
    // 2x oversampled WF.
    phase += phase_increment >> 1;
    sine = Interpolate824(wav_sine, phase);
    sine = sine * gain >> 15;
    sine = Interpolate88(ws_sine_fold, sine + 32768);
    *buffer = sine >> 1;
    
    phase += phase_increment >> 1;
    sine = Interpolate824(wav_sine, phase);
    sine = sine * gain >> 15;
    sine = Interpolate88(ws_sine_fold, sine + 32768);
    *buffer++ += sine >> 1;
  }
  
  END_INTERPOLATE_PARAMETER
  END_INTERPOLATE_PHASE_INCREMENT
  
  phase_ = phase;
}

void AnalogOscillator::RenderBuzz(
    const uint8_t* sync_in,
    int16_t* buffer,
    uint8_t* sync_out,
    size_t size) {
  int32_t shifted_pitch = pitch_ + ((32767 - parameter_) >> 1);
  uint16_t crossfade = shifted_pitch << 6;
  size_t index = (shifted_pitch >> 10);
  if (index >= kNumZones) {
    index = kNumZones - 1;
  }
  const int16_t* wave_1 = waveform_table[WAV_BANDLIMITED_COMB_0 + index];
  index += 1;
  if (index >= kNumZones) {
    index = kNumZones - 1;
  }
  const int16_t* wave_2 = waveform_table[WAV_BANDLIMITED_COMB_0 + index];
  while (size--) {
    phase_ += phase_increment_;
    if (*sync_in++) {
      phase_ = 0;
    }
    *buffer++ = Crossfade(wave_1, wave_2, phase_, crossfade);
  }
}

/* static */
AnalogOscillator::RenderFn AnalogOscillator::fn_table_[] = {
  &AnalogOscillator::RenderSaw,
  &AnalogOscillator::RenderVariableSaw,
  &AnalogOscillator::RenderCSaw,
  &AnalogOscillator::RenderSquare,
  &AnalogOscillator::RenderTriangle,
  &AnalogOscillator::RenderSine,
  &AnalogOscillator::RenderTriangleFold,
  &AnalogOscillator::RenderSineFold,
  &AnalogOscillator::RenderBuzz,
};

}  // namespace braids
