diff options
Diffstat (limited to 'graphics/src/audio/program.rs')
| -rw-r--r-- | graphics/src/audio/program.rs | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/graphics/src/audio/program.rs b/graphics/src/audio/program.rs new file mode 100644 index 0000000..f31be06 --- /dev/null +++ b/graphics/src/audio/program.rs @@ -0,0 +1,173 @@ +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, + 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, + tempo: u32, + pause: u32, +} +impl Program { + pub const fn new(ins: Vec<Instruction>, looping: bool) -> Self { + let pc = ins.len(); + Self { + ins, + pc, + looping, + tempo: 120, + pause: 0, + } + } + + 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 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 = self.tempo / 60; + } + 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 > 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; + } +} |