summaryrefslogtreecommitdiff
path: root/audio/src/program.rs
diff options
context:
space:
mode:
Diffstat (limited to 'audio/src/program.rs')
-rw-r--r--audio/src/program.rs184
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;
+ }
+}