diff options
Diffstat (limited to 'audio/src/program.rs')
| -rw-r--r-- | audio/src/program.rs | 184 |
1 files changed, 184 insertions, 0 deletions
diff --git a/audio/src/program.rs b/audio/src/program.rs new file mode 100644 index 0000000..28a5f41 --- /dev/null +++ b/audio/src/program.rs @@ -0,0 +1,184 @@ +use crate::{ + 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<Instruction>, + pc: usize, + looping: bool, + pause_cnt: u32, + pause_len: u32, +} +impl Program { + pub const fn new(ins: Vec<Instruction>, 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<Self> { + 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; + } +} |