#define SAMPLES 1024




class Kick {
  public:
    float frequency;
    float base_frequency;
    float frequency_range;
    float saturation = 2;
    float gain = 1.0;
    EnvelopeSimpleLinear volume_envelope;
    EnvelopeSimpleLog pitch_envelope;
    EnvelopeSigh pitch_envelope_sigh;
    LPF lpf;
    HPF hpf;
    void setup();
    void update();
    void reset();
    void trigger();
    float render();
    void advanceOscillator();
    void setVolumeDecay(float speed);
    void setPitchDecay(float speed);
    void setPitchAmount(float amount);
    void setFrequency(float frequency);
    void setSaturation(float saturation);

  private:
    int wavetable[SAMPLES];
    float osc_progress = 0.0;
};

void Kick::setup() {
  // Fill the wavetable with a sine-wave with values from 511 to -511
  for (int i = 0; i < SAMPLES; i++) {
    this->wavetable[i] = saturate(this->saturation*sin(2 * M_PI * i  / SAMPLES)) * 511;
  }

  this->volume_envelope.amount = 1.0;
  lpf.setFrequency(1000.0);
  lpf.setResonance(0.4);
  hpf.setFrequency(30.0);
  hpf.setResonance(0.4);
}

void Kick::update() {
  this->volume_envelope.update();
  this->pitch_envelope.update();
  this->pitch_envelope_sigh.update();
}

void Kick::reset() {
  this->volume_envelope.reset();
  this->pitch_envelope.reset();
  this->pitch_envelope_sigh.reset();
  this->osc_progress = 0;
}

void Kick::trigger() {
  this->reset();
  this->pitch_envelope_sigh.trigger();
}

float Kick::render() {
  float orig_sample = this->volume_envelope.value * this->wavetable[(int)this->osc_progress];
  float filtered_sample = hpf.render(lpf.render(orig_sample));
  float sample = (orig_sample*2.0 + filtered_sample*0.3) * this->gain;
  return sample / 2 + 511;
}

void Kick::advanceOscillator() {
  this->osc_progress += this->frequency + this->pitch_envelope.value + this->pitch_envelope_sigh.value;
  if (this->osc_progress > SAMPLES) {
    this->osc_progress = 0;
  }
}

void Kick::setVolumeDecay(float speed) {
  speed = 0.000005 + speed / 500.0;
  this->volume_envelope.setSpeed(speed);
}

void Kick::setPitchDecay(float speed) {
  speed = 0.00001 + speed / 800.0;
  this->pitch_envelope.setSpeed(speed);
}

void Kick::setPitchAmount(float amount) {
  this->pitch_envelope.setAmount(amount);
}

void Kick::setFrequency(float frequency) {
  this->frequency = this->base_frequency + frequency * this->frequency_range;
}

void Kick::setSaturation(float saturation) {
  this->saturation = 1. + max(0.0, saturation);
}