thorium 0.4.0

Lokathor does stuff, ium
Documentation
#![allow(missing_docs)]

use super::*;

#[derive(Debug, Clone, Copy)]
pub struct FrequencySweep {
  /// The frequency setting is an 11-bit value.
  ///
  /// * `131072/(2048-x)` Hz
  /// * Should always be set in the range `0..2048`
  pub frequency_setting: u16,

  /// How often the frequency will sweep up or down.
  ///
  /// * Given in 1/128th Sec units, correct range is +/- 7.
  /// * `+` sweeps the frequency up, `-` sweeps the frequency down.
  /// * Use 0 for no sweeping.
  pub rate: i8,

  /// How much to sweep by when a sweep happens.
  pub shift: u8,

  /// How many samples until we trigger a sweep and re-assess things.
  pub samples: usize,
}
impl Default for FrequencySweep {
  fn default() -> Self {
    Self::new(0, 0, 0, 1)
  }
}
impl FrequencySweep {
  /// Gets the frequency setting that is as close as possible to the target.
  pub fn setting_for(target: i32) -> u16 {
    2048 - ((131_072 / target.max(64)) as u16).max(1)
  }

  /// Given a rate and SPS, how many samples until the effect triggers.
  ///
  /// If the rate is 0, it will "never" trigger, so usize::MAX is given.
  pub fn samples_until_trigger(rate: i8, samples_per_second: i32) -> usize {
    debug_assert!(samples_per_second > 0);
    debug_assert!(rate < 8 && rate > -8);
    if rate != 0 {
      ((samples_per_second / 128) * i32::from(rate.abs())) as usize
    } else {
      core::usize::MAX
    }
  }

  /// Constructs a new `FrequencySweep` with appropriate settings
  ///
  /// Does a `debug_assert!` that all values are correct.
  pub fn new(freq: i32, rate: i8, shift: u8, samples_per_second: i32) -> Self {
    debug_assert!(samples_per_second > 0);
    debug_assert!(rate < 8 && rate > -8);
    debug_assert!(shift < 8);
    Self {
      frequency_setting: Self::setting_for(freq),
      rate,
      shift,
      samples: Self::samples_until_trigger(rate, samples_per_second),
    }
  }

  /// Obtains the real frequency of the current frequency setting.
  pub fn frequency(&self) -> i32 {
    debug_assert!(self.frequency_setting < 2048);
    131_072 / (2048 - i32::from(self.frequency_setting))
  }

  pub fn in_samples_per_out_sample(&self, samples_per_second: i32) -> f32 {
    (32.0 * self.frequency() as f32) / samples_per_second as f32
  }

  /// Ticks down a sample, returns if the effect actually changed the frequency.
  pub fn tick_sample(&mut self, samples_per_second: i32) -> SweepTickResult {
    debug_assert!(self.samples > 0);
    self.samples -= 1;
    let out = if self.samples == 0 {
      let sweep_dir: i32 = i32::from(self.rate.signum());
      let t_0: i32 = i32::from(self.frequency_setting);
      let t_next: i32 = t_0 + (sweep_dir * (t_0 / (2 << self.shift)).max(1));
      if t_next > 0 && t_next < 0b111_1111_1111 {
        self.frequency_setting = t_next as u16;
        self.samples = Self::samples_until_trigger(self.rate, samples_per_second);
        SweepTickResult::FrequencyChange
      } else {
        self.samples = core::usize::MAX;
        SweepTickResult::CancelSound
      }
    } else {
      SweepTickResult::NoChange
    };
    debug_assert!(self.samples > 0);
    out
  }
}

#[derive(Debug, Clone, Copy)]
pub enum SweepTickResult {
  NoChange,
  FrequencyChange,
  CancelSound,
}

#[derive(Debug, Default, Clone, Copy)]
pub struct LevelEnvelope {
  /// How often to adjust the envelope up or down.
  ///
  /// * Given in 1/64th Sec units, correct range is +/- 7.
  /// * Use 0 for no envelope update.
  pub rate: i8,

  /// The level (0.0 through 1.0) when the sound is "on"
  pub level: f32,

  /// How many samples until we trigger the envelops and re-assess things.
  pub samples: usize,
}
impl LevelEnvelope {
  /// Computes the samples until the envelope triggers
  pub fn samples_until_trigger(rate: i8, samples_per_second: i32) -> usize {
    debug_assert!(samples_per_second > 0);
    if rate != 0 {
      ((samples_per_second / 64) * i32::from(rate.abs())) as usize
    } else {
      core::usize::MAX
    }
  }

  /// Constructs a new `LevelEnvelope` with a correct samples remaining.
  pub fn new(rate: i8, level: f32, samples_per_second: i32) -> Self {
    debug_assert!(rate < 8 && rate > -8);
    debug_assert!(level >= 0.0 && level <= 1.0);
    debug_assert!(samples_per_second > 0);
    Self {
      rate,
      level,
      samples: Self::samples_until_trigger(rate, samples_per_second),
    }
  }

  pub fn tick_sample(&mut self, samples_per_second: i32) {
    debug_assert!(self.samples > 0);
    self.samples -= 1;
    if self.samples == 0 {
      const ONE_FIFTEENTH: f32 = 1.0 / 15.0;
      let delta = f32::from(self.rate.signum()) * ONE_FIFTEENTH;
      self.level = self.level + delta;
      if self.level > 1.0 {
        self.level = 1.0;
        self.samples = core::usize::MAX;
      } else if self.level < 0.0 {
        self.level = 0.0;
        self.samples = core::usize::MAX;
      } else {
        self.samples = Self::samples_until_trigger(self.rate, samples_per_second);
      }
    }
    debug_assert!(self.samples > 0);
  }
}

/// A pulse voice produces a square wave with some optional effects.
///
/// * The "sweep" effect can change the frequency. If frequency shifts out of
///   range the sound stops playback.
/// * The "envelope" effect can change the level of active sound moments in the
///   square wave. When the sound level hits max or min it just stays there.\
#[derive(Debug, Default, Clone, Copy)]
pub struct PulseVoice {
  /// How many samples are left to play out.
  pub play_samples_remaining: usize,

  /// The sweep effect of this voice.
  pub sweep: FrequencySweep,

  /// The sounds level envelope of this voice.
  pub envelope: LevelEnvelope,

  /// The time percentage of "on" sound (0.0 through 1.0)
  pub duty: f32,

  /// The current position within the wave pattern (<32.0).
  pub wave_point: f32,
}
impl PulseVoice {
  pub fn fill_sound_buffer(
    &mut self, samples_per_second: i32, buf: &mut [StereoI16], left_max: i16, right_max: i16,
  ) -> usize {
    debug_assert!(self.wave_point < 32.0);
    let left_max_f = f32::from(left_max);
    let right_max_f = f32::from(right_max);
    let mut samples_written: usize = 0;
    let mut in_samples_per_out_sample = self.sweep.in_samples_per_out_sample(samples_per_second);
    let mut buf_iter = buf.iter_mut();
    let duty_point = self.duty * 32.0;
    while self.play_samples_remaining > 0 {
      self.play_samples_remaining -= 1;
      if let Some(sample_mut) = buf_iter.next() {
        // GATHER SAMPLE
        let here_level_norm: f32 = if in_samples_per_out_sample > 1.0 {
          // With high rates of in sample per out sample we need to average the
          // samples we're outputting.
          let mut total = 0.0;
          let count = in_samples_per_out_sample as u32;
          for _ in 0..count {
            self.wave_point += 1.0;
            if self.wave_point >= 32.0 {
              self.wave_point -= 32.0;
            }
            total += if self.wave_point <= duty_point {
              self.envelope.level
            } else {
              0.0
            };
          }
          self.wave_point += in_samples_per_out_sample.fract();
          if self.wave_point >= 32.0 {
            self.wave_point -= 32.0;
          }
          total += if self.wave_point <= duty_point {
            self.envelope.level * in_samples_per_out_sample.fract()
          } else {
            0.0
          };
          total / in_samples_per_out_sample
        } else {
          // At lower rates we can just spit out one sample
          self.wave_point += in_samples_per_out_sample;
          if self.wave_point >= 32.0 {
            self.wave_point -= 32.0;
          }
          if self.wave_point <= duty_point {
            self.envelope.level
          } else {
            0.0
          }
        };
        debug_assert!(here_level_norm >= 0.0 && here_level_norm <= 1.0);
        let here_pos_neg_norm = (here_level_norm * 2.0) - 1.0;
        // RECORD SAMPLE
        sample_mut.left += (left_max_f * here_pos_neg_norm) as i16;
        sample_mut.right += (right_max_f * here_pos_neg_norm) as i16;
        // UPDATE OUR RECORDS
        samples_written += 1;
        match self.sweep.tick_sample(samples_per_second) {
          SweepTickResult::NoChange => {}
          SweepTickResult::FrequencyChange => {
            in_samples_per_out_sample = self.sweep.in_samples_per_out_sample(samples_per_second);
          }
          SweepTickResult::CancelSound => {
            self.play_samples_remaining = 0;
          }
        }
        self.envelope.tick_sample(samples_per_second);
      } else {
        break;
      }
    }
    samples_written
  }
}

#[derive(Debug, Clone)]
pub struct WaveVoice {
  /// How many samples are left to play out.
  pub play_samples_remaining: usize,

  /// The sweep effect of this voice.
  pub sweep: FrequencySweep,

  /// The sounds level envelope of this voice.
  ///
  /// Must be normalized values, must be 32 slots.
  pub wave: Vec<f32>,

  /// The current position within the wave pattern (<32.0).
  pub wave_point: f32,
}
impl Default for WaveVoice {
  fn default() -> Self {
    Self {
      play_samples_remaining: 0,
      sweep: Default::default(),
      wave: vec![0.0; 32],
      wave_point: 0.0,
    }
  }
}
impl WaveVoice {
  pub fn fill_sound_buffer(
    &mut self, samples_per_second: i32, buf: &mut [StereoI16], left_max: i16, right_max: i16,
  ) -> usize {
    debug_assert!(self.wave_point < 32.0);
    debug_assert_eq!(self.wave.len(), 32);
    let left_max_f = f32::from(left_max);
    let right_max_f = f32::from(right_max);
    let mut samples_written: usize = 0;
    let mut in_samples_per_out_sample = self.sweep.in_samples_per_out_sample(samples_per_second);
    let mut buf_iter = buf.iter_mut();
    while self.play_samples_remaining > 0 {
      self.play_samples_remaining -= 1;
      if let Some(sample_mut) = buf_iter.next() {
        // GATHER SAMPLE
        let here_level_norm: f32 = if in_samples_per_out_sample > 1.0 {
          // With high rates of in sample per out sample we need to average the
          // samples we're outputting.
          let mut total = 0.0;
          let count = in_samples_per_out_sample as u32;
          for _ in 0..count {
            self.wave_point += 1.0;
            if self.wave_point >= 32.0 {
              self.wave_point -= 32.0;
            }
            total += self.wave[self.wave_point as usize];
          }
          self.wave_point += in_samples_per_out_sample.fract();
          if self.wave_point >= 32.0 {
            self.wave_point -= 32.0;
          }
          total += self.wave[self.wave_point as usize];
          total / in_samples_per_out_sample
        } else {
          // At lower rates we can just spit out one sample
          self.wave_point += in_samples_per_out_sample;
          if self.wave_point >= 32.0 {
            self.wave_point -= 32.0;
          }
          self.wave[self.wave_point as usize]
        };
        debug_assert!(here_level_norm >= 0.0 && here_level_norm <= 1.0);
        let here_pos_neg_norm = (here_level_norm * 2.0) - 1.0;
        // RECORD SAMPLE
        sample_mut.left += (left_max_f * here_pos_neg_norm) as i16;
        sample_mut.right += (right_max_f * here_pos_neg_norm) as i16;
        // UPDATE OUR RECORDS
        samples_written += 1;
        match self.sweep.tick_sample(samples_per_second) {
          SweepTickResult::NoChange => {}
          SweepTickResult::FrequencyChange => {
            in_samples_per_out_sample = self.sweep.in_samples_per_out_sample(samples_per_second);
          }
          SweepTickResult::CancelSound => {
            self.play_samples_remaining = 0;
          }
        }
      } else {
        break;
      }
    }
    samples_written
  }
}

#[derive(Debug, Clone, Copy)]
pub struct NoiseCounter {
  pub counter: u16,
  pub is_7bit: bool,
}
impl Default for NoiseCounter {
  fn default() -> Self {
    Self {
      counter: core::u16::MAX,
      is_7bit: false,
    }
  }
}
impl NoiseCounter {
  pub fn tick(&mut self) {
    let a = self.counter & 0b1;
    let b = (self.counter & 0b10) >> 1;
    let new_bit = a ^ b;
    self.counter >>= 1;
    self.counter |= new_bit << 14;
    if self.is_7bit {
      self.counter &= !(1 << 6);
      self.counter |= new_bit << 6;
    }
  }

  pub fn high(&self) -> bool {
    (self.counter & 0b1) > 0
  }
}

#[derive(Debug, Default, Clone, Copy)]
pub struct NoiseVoice {
  /// How many samples are left to play out.
  pub play_samples_remaining: usize,

  /// The level effect of this voice.
  pub level: LevelEnvelope,

  /// The shift clock frequency.
  pub frequency: i32,

  /// The polynomial counter.
  pub counter: NoiseCounter,

  pub freq_point: f32,
}
impl NoiseVoice {
  /// Computes a frequency from GB `s` and `r` values.
  pub fn frequency_for(shift: u8, ratio: u8) -> i32 {
    debug_assert!(shift < 8);
    debug_assert!(ratio < 8);
    if ratio == 0 {
      (524_288 * 2) / 2i32.pow(u32::from(shift) + 1)
    } else {
      524_288 / i32::from(ratio) / 2i32.pow(u32::from(shift) + 1)
    }
  }

  pub fn fill_sound_buffer(
    &mut self, samples_per_second: i32, buf: &mut [StereoI16], left_max: i16, right_max: i16,
  ) -> usize {
    debug_assert!(self.freq_point < 1.0);
    let left_max_f = f32::from(left_max);
    let right_max_f = f32::from(right_max);
    let mut samples_written: usize = 0;
    let in_samples_per_out_sample = self.frequency as f32 / samples_per_second as f32;
    let mut buf_iter = buf.iter_mut();
    while self.play_samples_remaining > 0 {
      self.play_samples_remaining -= 1;
      if let Some(sample_mut) = buf_iter.next() {
        // GATHER SAMPLE
        let here_level_norm: f32 = if in_samples_per_out_sample > 1.0 {
          // With high rates of in sample per out sample we need to average the
          // samples we're outputting.
          let mut total = 0.0;
          let count = in_samples_per_out_sample as u32;
          for _ in 0..count {
            self.counter.tick();
            total += if self.counter.high() { self.level.level } else { 0.0 };
          }
          self.freq_point += in_samples_per_out_sample.fract();
          if self.freq_point >= 1.0 {
            self.counter.tick();
            self.freq_point -= 1.0;
          }
            total += if self.counter.high() { self.level.level } else { 0.0 };
          total / in_samples_per_out_sample
        } else {
          // At lower rates we can just spit out one sample
          self.freq_point += in_samples_per_out_sample;
          if self.freq_point >= 1.0 {
            self.counter.tick();
            self.freq_point -= 1.0;
          }
          if self.counter.high() { self.level.level } else { 0.0 }
        };
        debug_assert!(here_level_norm >= 0.0 && here_level_norm <= 1.0);
        let here_pos_neg_norm = (here_level_norm * 2.0) - 1.0;
        // RECORD SAMPLE
        sample_mut.left += (left_max_f * here_pos_neg_norm) as i16;
        sample_mut.right += (right_max_f * here_pos_neg_norm) as i16;
        // UPDATE OUR RECORDS
        samples_written += 1;
        self.level.tick_sample(samples_per_second);
      } else {
        break;
      }
    }
    samples_written
  }
}

#[derive(Debug, Default, Clone)]
pub struct DMGAPU {
  pub pulse_a: PulseVoice,
  pub pulse_b: PulseVoice,
  pub wave: WaveVoice,
  pub noise: NoiseVoice,
}

impl DMGAPU {
  /// Fills the sound buffer.
  pub fn fill_sound_buffer(&mut self, samples_per_second: i32, buf: &mut [StereoI16]) {
    let sps = samples_per_second;
    self.pulse_a.fill_sound_buffer(sps, buf, 7000, 7000);
    self.pulse_b.fill_sound_buffer(sps, buf, 7000, 7000);
    self.wave.fill_sound_buffer(sps, buf, 7000, 7000);
    self.noise.fill_sound_buffer(sps, buf, 7000, 7000);
  }
}