///|
/// Create a new ADSR envelope.
pub fn new_envelope(config : EnvelopeConfig, sample_rate : Int) -> Envelope {
  Envelope::{
    config,
    sample_rate,
    phase: EnvelopePhase::Attack,
    level: 0.0,
    phase_position: 0,
    release_start_level: 0.0,
  }
}

///|
/// Advance envelope by one sample and return the current gain level.
pub fn envelope_tick(env : Envelope) -> Float {
  match env.phase {
    Attack => {
      let attack_samples = (env.config.attack * Float::from_int(env.sample_rate)).to_int()
      if attack_samples <= 0 {
        env.level = 1.0
        env.phase = EnvelopePhase::Decay
        env.phase_position = 0
        return env.level
      }
      env.level = Float::from_int(env.phase_position) /
        Float::from_int(attack_samples)
      env.phase_position = env.phase_position + 1
      if env.phase_position >= attack_samples {
        env.level = 1.0
        env.phase = EnvelopePhase::Decay
        env.phase_position = 0
      }
      env.level
    }
    Decay => {
      let decay_samples = (env.config.decay * Float::from_int(env.sample_rate)).to_int()
      if decay_samples <= 0 {
        env.level = env.config.sustain
        env.phase = EnvelopePhase::Sustain
        env.phase_position = 0
        return env.level
      }
      let t = Float::from_int(env.phase_position) /
        Float::from_int(decay_samples)
      env.level = 1.0 + (env.config.sustain - 1.0) * t
      env.phase_position = env.phase_position + 1
      if env.phase_position >= decay_samples {
        env.level = env.config.sustain
        env.phase = EnvelopePhase::Sustain
        env.phase_position = 0
      }
      env.level
    }
    Sustain => {
      env.level = env.config.sustain
      env.level
    }
    Release => {
      let release_samples = (env.config.release_time *
      Float::from_int(env.sample_rate)).to_int()
      if release_samples <= 0 {
        env.level = 0.0
        env.phase = EnvelopePhase::Done
        return env.level
      }
      let t = Float::from_int(env.phase_position) /
        Float::from_int(release_samples)
      env.level = env.release_start_level * (1.0 - t)
      env.phase_position = env.phase_position + 1
      if env.phase_position >= release_samples {
        env.level = 0.0
        env.phase = EnvelopePhase::Done
      }
      if env.level < 0.0 {
        env.level = 0.0
      }
      env.level
    }
    Done => 0.0
  }
}

///|
/// Trigger the release phase of the envelope.
pub fn envelope_release(env : Envelope) -> Unit {
  match env.phase {
    Done => ()
    _ => {
      env.release_start_level = env.level
      env.phase = EnvelopePhase::Release
      env.phase_position = 0
    }
  }
}

///|
/// Reset the envelope to the beginning of the attack phase.
pub fn envelope_reset(env : Envelope) -> Unit {
  env.phase = EnvelopePhase::Attack
  env.level = 0.0
  env.phase_position = 0
  env.release_start_level = 0.0
}