use crate::audio::{ Channels, channel::{Channel, DutyCycle}, parse, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ChanSpec { PulseA, PulseB, Triangle, Noise, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Instruction { Pause, PauseLen(u32), Jump(usize), SetVolume(ChanSpec, u8), SetPitch(ChanSpec, u8), SetPulseDutyA(DutyCycle), SetPulseDutyB(DutyCycle), SetNoiseMode(bool), } fn map_pitch(pitch: u8) -> f32 { const HALF_STEP: f32 = 1.059_463_1; HALF_STEP.powf(pitch as f32 - 128.0) } const fn map_volume(volume: u8) -> f32 { if volume > 100 { 1.0 } else { (volume as f32) / 100.0 } } #[derive(Debug, Clone)] pub struct Program { ins: Vec, pc: usize, looping: bool, pause_cnt: u32, pause_len: u32, } impl Program { pub const fn new(ins: Vec, looping: bool) -> Self { let pc = ins.len(); Self { ins, pc, looping, pause_cnt: 0, pause_len: 4, } } pub fn parse(src: &str, looping: bool) -> parse::Result { let ins = parse::parse(src)?; Ok(Self::new(ins, looping)) } pub fn merge(self, other: Self) -> Self { let mut res = vec![]; let mut l = 0; let mut r = 0; let l_ins = self.ins; let r_ins = other.ins; loop { if l >= l_ins.len() && r >= r_ins.len() { // were done here break; } let mut has_pause = false; while l < l_ins.len() { let ins = l_ins[l]; l += 1; if matches!(ins, Instruction::Pause) { has_pause = true; break; } res.push(ins); } while r < r_ins.len() { let ins = r_ins[r]; r += 1; if matches!(ins, Instruction::Pause) { has_pause = true; break; } res.push(ins); } if has_pause { res.push(Instruction::Pause); } } Self::new(res, self.looping) } fn exec_ins(&mut self, channels: &mut Channels, ins: Instruction) { use Instruction as I; match ins { I::Pause => { self.pause_cnt = self.pause_len; } I::PauseLen(pause_len) => { self.pause_len = pause_len; } I::Jump(pc) => { self.pc = pc; } I::SetVolume(chan_spec, volume) => { // set the volume (amplitude) on a given channel use ChanSpec as C; let v = map_volume(volume); match chan_spec { C::PulseA => channels.pulse_a.set_volume(v), C::PulseB => channels.pulse_b.set_volume(v), C::Triangle => channels.triangle.set_volume(v), C::Noise => channels.noise.set_volume(v), } } I::SetPitch(chan_spec, pitch) => { // set the pitch (freq) on a given channel use ChanSpec as C; let p = map_pitch(pitch); match chan_spec { C::PulseA => channels.pulse_a.set_pitch(p), C::PulseB => channels.pulse_b.set_pitch(p), C::Triangle => channels.triangle.set_pitch(p), C::Noise => channels.noise.set_pitch(p), } } I::SetPulseDutyA(duty_cycle) => { channels.pulse_a.set_duty(duty_cycle); } I::SetPulseDutyB(duty_cycle) => { channels.pulse_b.set_duty(duty_cycle); } I::SetNoiseMode(mode) => { channels.noise.set_mode(mode); } } } pub fn exec(&mut self, channels: &mut Channels) { if self.pause_cnt > 0 { self.pause_cnt -= 1; } loop { if self.pause_cnt > 0 { break; } if self.finished() { if self.looping { self.pc = 0; } else { break; } } let ins = self.ins[self.pc]; self.exec_ins(channels, ins); self.pc += 1; } } pub const fn finished(&self) -> bool { self.pc >= self.ins.len() } pub const fn play(&mut self) { self.pc = 0; } pub const fn set_looping(&mut self, looping: bool) { self.looping = looping; } }