use crate::audio::{ AUDIO_FPS, 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, Jump(usize), Tempo(u32), 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, tempo: u32, pause: u32, } impl Program { pub const fn new(ins: Vec, looping: bool) -> Self { let pc = ins.len(); Self { ins, pc, looping, tempo: 120, pause: 0, } } 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 l_ins = self.ins; let r_ins = other.ins; let mut l = 0; let mut r = 0; loop { if r >= r_ins.len() { break; } let ins = r_ins[r]; if ins == Instruction::Pause { // inc l until even loop { if l == l_ins.len() { l_ins.push(Instruction::Pause); break; } if l_ins[l] == Instruction::Pause { l += 1; break; } l += 1; } } else { l_ins.insert(l, ins); } r += 1; } Self::new(l_ins, self.looping) } fn exec_ins(&mut self, channels: &mut Channels, ins: Instruction) { use Instruction as I; match ins { I::Pause => { // pause execution until next `exec` call self.pause = (AUDIO_FPS * 4) / self.tempo.clamp(1, 240); } I::Jump(pc) => { self.pc = pc; } I::Tempo(tempo) => { self.tempo = tempo; } 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 > 0 { self.pause -= 1; return; } loop { if self.pause > 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; } }