diff --git a/firmware/litex-fw/src/main.rs b/firmware/litex-fw/src/main.rs index 1b7926d..ad8a72c 100644 --- a/firmware/litex-fw/src/main.rs +++ b/firmware/litex-fw/src/main.rs @@ -254,7 +254,7 @@ impl State { let voice = &self.voice_manager.voices[n_voice]; shifter[n_voice].set_pitch(voice.pitch); lpf[n_voice].set_cutoff((voice.amplitude * 8000f32) as i16); - lpf[n_voice].set_resonance(opts.resonance.value); + lpf[n_voice].set_resonance(opts.adsr.resonance.value); } } } @@ -367,17 +367,31 @@ where let opts_view = opts.view(); let vy: usize = 205; + + // Draw the current screen text + Text::with_alignment( + opts.screen.value.into(), + Point::new(5, (vy-10) as i32), + match (opts.selected(), opts.modify) { + (None, _) => font_small_white, + _ => font_small_grey, + }, + Alignment::Left, + ).draw(d)?; + for (n, opt) in opts_view.iter().enumerate() { let mut font = font_small_grey; - if opts.selected == n { - font = font_small_white; - if opts.modify { - Text::with_alignment( - "-", - Point::new(62, (vy+10*n) as i32), - font, - Alignment::Left, - ).draw(d)?; + if let Some(n_selected) = opts.selected() { + if n_selected == n { + font = font_small_white; + if opts.modify { + Text::with_alignment( + "-", + Point::new(62, (vy+10*n) as i32), + font, + Alignment::Left, + ).draw(d)?; + } } } Text::with_alignment( @@ -694,9 +708,11 @@ fn main() -> ! { state.trace.len_us()) }); - for (n_voice, voice) in voices.iter().enumerate() { - draw_voice(&mut disp, (55+37*n_voice) as u32, - n_voice as u32, voice).ok(); + if opts.screen.value == opt::Screen::Adsr { + for (n_voice, voice) in voices.iter().enumerate() { + draw_voice(&mut disp, (55+37*n_voice) as u32, + n_voice as u32, voice).ok(); + } } draw_options(&mut disp, &opts).ok(); @@ -706,8 +722,7 @@ fn main() -> ! { draw_ms(&mut disp, irq0_len_us, Point::new(5, 245), character_style).ok(); - { - + if opts.screen.value == opt::Screen::Scope { let samples = critical_section::with(|cs| { let scope = &mut oscope.borrow_ref_mut(cs); if scope.full() { diff --git a/firmware/litex-fw/src/opt.rs b/firmware/litex-fw/src/opt.rs index 5788990..5f62c73 100644 --- a/firmware/litex-fw/src/opt.rs +++ b/firmware/litex-fw/src/opt.rs @@ -1,8 +1,11 @@ use heapless::String; +use heapless::Vec; use strum_macros::{EnumIter, IntoStaticStr}; pub type OptionString = String<32>; +pub type OptionView<'a> = Vec<&'a dyn OptionTrait, 10>; +pub type OptionViewMut<'a> = Vec<&'a mut dyn OptionTrait, 10>; pub trait OptionTrait { fn name(&self) -> &OptionString; @@ -34,62 +37,92 @@ pub enum EnumTest { ValueC } +#[derive(Clone, Copy, PartialEq, EnumIter, IntoStaticStr)] +#[strum(serialize_all = "SCREAMING-KEBAB-CASE")] +pub enum Screen { + Adsr, + Scope, +} + + #[derive(Clone)] -pub struct Options { - pub modify: bool, - pub selected: usize, +pub struct AdsrOptions { + pub selected: Option, pub attack_ms: NumOption, pub decay_ms: NumOption, pub release_ms: NumOption, pub resonance: NumOption, +} + +#[derive(Clone)] +pub struct ScopeOptions { + pub selected: Option, pub delay_len: NumOption, pub enum_test: EnumOption, } +#[derive(Clone)] +pub struct Options { + pub modify: bool, + pub screen: EnumOption, + + pub adsr: AdsrOptions, + pub scope: ScopeOptions, +} + impl Options { pub fn new() -> Options { Options { - modify: false, - selected: 0, - delay_len: NumOption{ - name: "delayln".into(), - value: 511, - step: 1, - min: 128, - max: 511, - }, - attack_ms: NumOption{ - name: "attack".into(), - value: 100, - step: 50, - min: 0, - max: 5000, + modify: true, + screen: EnumOption { + name: "screen".into(), + value: Screen::Adsr, }, - decay_ms: NumOption{ - name: "decay".into(), - value: 100, - step: 50, - min: 0, - max: 5000, - }, - release_ms: NumOption{ - name: "release".into(), - value: 300, - step: 50, - min: 0, - max: 5000, - }, - resonance: NumOption{ - name: "reso".into(), - value: 10000, - step: 1000, - min: 0, - max: 20000, - }, - enum_test: EnumOption{ - name: "enumt".into(), - value: EnumTest::ValueA, + adsr: AdsrOptions { + selected: None, + attack_ms: NumOption{ + name: "attack".into(), + value: 100, + step: 50, + min: 0, + max: 5000, + }, + decay_ms: NumOption{ + name: "decay".into(), + value: 100, + step: 50, + min: 0, + max: 5000, + }, + release_ms: NumOption{ + name: "release".into(), + value: 300, + step: 50, + min: 0, + max: 5000, + }, + resonance: NumOption{ + name: "reso".into(), + value: 10000, + step: 1000, + min: 0, + max: 20000, + }, }, + scope: ScopeOptions { + selected: None, + delay_len: NumOption{ + name: "delayln".into(), + value: 511, + step: 1, + min: 128, + max: 511, + }, + enum_test: EnumOption{ + name: "enumt".into(), + value: EnumTest::ValueA, + }, + } } } @@ -98,45 +131,79 @@ impl Options { } pub fn tick_up(&mut self) { - let selected = self.selected; - if self.modify { - self.view_mut()[selected].tick_up(); - } else if selected < self.view().len()-1 { - self.selected = selected + 1; + if let Some(n_selected) = self.selected() { + if self.modify { + self.view_mut()[n_selected].tick_up(); + } else if n_selected < self.view().len()-1 { + *self.selected_mut() = Some(n_selected + 1); + } + } else if self.modify { + self.screen.tick_up(); + } else if !self.view().is_empty() { + *self.selected_mut() = Some(0); } } pub fn tick_down(&mut self) { - let selected = self.selected; - if self.modify { - self.view_mut()[selected].tick_down(); - } else if selected != 0 { - self.selected = selected - 1; + if let Some(n_selected) = self.selected() { + if self.modify { + self.view_mut()[n_selected].tick_down(); + } else if n_selected != 0 { + *self.selected_mut() = Some(n_selected - 1); + } else { + *self.selected_mut() = None; + } + } else if self.modify { + self.screen.tick_down(); + } + } + + pub fn selected(&self) -> Option { + match self.screen.value { + Screen::Adsr => self.adsr.selected, + Screen::Scope => self.scope.selected, + } + } + + pub fn selected_mut(&mut self) -> &mut Option { + match self.screen.value { + Screen::Adsr => &mut self.adsr.selected, + Screen::Scope => &mut self.scope.selected, } } #[allow(dead_code)] - pub fn view(&self) -> [& dyn OptionTrait; 6] { - [ - &self.enum_test, - &self.attack_ms, - &self.decay_ms, - &self.release_ms, - &self.resonance, - &self.delay_len, - ] + pub fn view(&self) -> OptionView { + match self.screen.value { + Screen::Adsr => OptionView::from_slice(&[ + &self.adsr.attack_ms, + &self.adsr.decay_ms, + &self.adsr.release_ms, + &self.adsr.resonance, + ]), + Screen::Scope => OptionView::from_slice(&[ + &self.scope.delay_len, + &self.scope.enum_test, + ]), + }.unwrap() } #[allow(dead_code)] - pub fn view_mut(&mut self) -> [&mut dyn OptionTrait; 6] { - [ - &mut self.enum_test, - &mut self.attack_ms, - &mut self.decay_ms, - &mut self.release_ms, - &mut self.resonance, - &mut self.delay_len, - ] + fn view_mut(&mut self) -> OptionViewMut { + let mut r = OptionViewMut::new(); + match self.screen.value { + Screen::Adsr => { + r.push(&mut self.adsr.attack_ms).ok(); + r.push(&mut self.adsr.decay_ms).ok(); + r.push(&mut self.adsr.release_ms).ok(); + r.push(&mut self.adsr.resonance).ok(); + }, + Screen::Scope => { + r.push(&mut self.scope.delay_len).ok(); + r.push(&mut self.scope.enum_test).ok(); + }, + } + r } } diff --git a/firmware/litex-fw/src/voice.rs b/firmware/litex-fw/src/voice.rs index 1bbbd78..61fb62a 100644 --- a/firmware/litex-fw/src/voice.rs +++ b/firmware/litex-fw/src/voice.rs @@ -136,9 +136,9 @@ impl VoiceManager { if v.adsr.is_none() { v.adsr = Some( AdsrParams { - attack_ms: opts.attack_ms.value, - decay_ms: opts.decay_ms.value, - release_ms: opts.release_ms.value, + attack_ms: opts.adsr.attack_ms.value, + decay_ms: opts.adsr.decay_ms.value, + release_ms: opts.adsr.release_ms.value, attack_amplitude: 1.0f32 * (v.velocity as f32 / 128.0f32), sustain_amplitude: 0.8f32 * (v.velocity as f32 / 128.0f32), });