use crate::blipbuf::BlipBuf;
use crate::settings::{
CLOCK_RATE, EFFECT_FADEOUT, EFFECT_NONE, EFFECT_SLIDE, EFFECT_VIBRATO, NOISE_VOLUME_FACTOR,
NUM_CLOCKS_PER_TICK, OSCILLATOR_RESOLUTION, PULSE_VOLUME_FACTOR, SQUARE_VOLUME_FACTOR,
TONE_NOISE, TONE_PULSE, TONE_SQUARE, TONE_TRIANGLE, TRIANGLE_VOLUME_FACTOR, VIBRATO_DEPTH,
VIBRATO_FREQUENCY,
};
use crate::types::{Effect, Tone};
const VIBRATO_PERIOD: u32 =
(CLOCK_RATE as f64 / VIBRATO_FREQUENCY / OSCILLATOR_RESOLUTION as f64) as u32;
struct Slide {
pitch: f64,
}
struct Vibrato {
time: u32,
phase: u32,
}
struct FadeOut {
volume: f64,
}
pub struct Oscillator {
pitch: f64,
tone: Tone,
volume: f64,
effect: Effect,
duration: u32,
time: u32,
phase: u32,
amplitude: i16,
noise: u32,
slide: Slide,
vibrato: Vibrato,
fadeout: FadeOut,
}
impl Oscillator {
pub fn new() -> Self {
Self {
pitch: Self::note_to_pitch(0.0),
tone: TONE_TRIANGLE,
volume: 0.0,
effect: EFFECT_NONE,
duration: 0,
time: 0,
phase: 0,
amplitude: 0,
noise: 1,
slide: Slide { pitch: 0.0 },
vibrato: Vibrato { time: 0, phase: 0 },
fadeout: FadeOut { volume: 0.0 },
}
}
pub fn play(&mut self, note: f64, tone: Tone, volume: f64, effect: Effect, duration: u32) {
let last_pitch = self.pitch;
self.pitch = Self::note_to_pitch(note);
self.tone = tone;
self.volume = volume;
self.effect = effect;
self.duration = duration;
if effect == EFFECT_SLIDE {
self.slide.pitch = (self.pitch - last_pitch) / self.duration as f64;
self.pitch = last_pitch;
} else if effect == EFFECT_FADEOUT {
self.fadeout.volume = -self.volume / self.duration as f64;
}
}
pub fn stop(&mut self) {
self.duration = 0;
}
pub fn update(&mut self, blip_buf: &mut BlipBuf) {
if self.duration == 0 {
if self.amplitude != 0 {
blip_buf.add_delta(0, -(self.amplitude as i32));
}
self.time = 0;
self.amplitude = 0;
return;
}
let pitch = self.pitch
+ if self.effect == EFFECT_VIBRATO {
self.pitch * Self::triangle(self.vibrato.phase) * VIBRATO_DEPTH
} else {
0.0
};
let period = (CLOCK_RATE as f64 / pitch / OSCILLATOR_RESOLUTION as f64) as u32;
while self.time < NUM_CLOCKS_PER_TICK {
let last_amplitude = self.amplitude;
self.phase = (self.phase + 1) % OSCILLATOR_RESOLUTION;
self.amplitude = (match self.tone {
TONE_TRIANGLE => Self::triangle(self.phase) * TRIANGLE_VOLUME_FACTOR,
TONE_SQUARE => Self::square(self.phase) * SQUARE_VOLUME_FACTOR,
TONE_PULSE => Self::pulse(self.phase) * PULSE_VOLUME_FACTOR,
TONE_NOISE => self.noise(self.phase) * NOISE_VOLUME_FACTOR,
_ => panic!("Invalid tone '{}'", self.tone),
} * self.volume
* i16::MAX as f64) as i16;
blip_buf.add_delta(
self.time as u64,
self.amplitude as i32 - last_amplitude as i32,
);
self.time += period;
}
match self.effect {
EFFECT_NONE => {}
EFFECT_SLIDE => {
self.pitch += self.slide.pitch;
}
EFFECT_VIBRATO => {
self.vibrato.time += NUM_CLOCKS_PER_TICK;
self.vibrato.phase = (self.vibrato.phase + self.vibrato.time / VIBRATO_PERIOD)
% OSCILLATOR_RESOLUTION;
self.vibrato.time %= VIBRATO_PERIOD;
}
EFFECT_FADEOUT => {
self.volume += self.fadeout.volume;
}
_ => panic!("Invalid effect '{}'", self.effect),
}
self.duration -= 1;
self.time -= NUM_CLOCKS_PER_TICK;
}
fn note_to_pitch(note: f64) -> f64 {
440.0 * ((note - 33.0) / 12.0).exp2()
}
fn triangle(phase: u32) -> f64 {
if phase < OSCILLATOR_RESOLUTION / 2 {
phase as f64 / (OSCILLATOR_RESOLUTION / 4) as f64 - 1.0
} else {
3.0 - phase as f64 / (OSCILLATOR_RESOLUTION / 4) as f64
}
}
const fn square(phase: u32) -> f64 {
if phase < OSCILLATOR_RESOLUTION / 2 {
1.0
} else {
-1.0
}
}
const fn pulse(phase: u32) -> f64 {
if phase < OSCILLATOR_RESOLUTION / 4 {
1.0
} else {
-1.0
}
}
fn noise(&mut self, phase: u32) -> f64 {
if phase % 8 == 0 {
let feedback = (self.noise & 1) ^ (self.noise >> 1 & 1);
self.noise >>= 1;
self.noise |= feedback << 14;
}
(self.noise & 1) as f64 * 2.0 - 1.0
}
}