#[derive(Default, Clone, Copy)] pub enum State { // system is asleep, waiting for wake from coin insertion DeepSleep, // system is in low-power mode, dimmed lights, waiting for interaction Idle, // system is active. on entry: play coin sound. on button press: play different sound #[default] Active, } mod settings { #[derive(Debug, Default, Clone, Copy)] pub enum Level { Off, Low, #[default] Medium, High, Maximum, } impl Level { pub fn next(&mut self) { *self = match self { Self::Off => Self::Low, Self::Low => Self::Medium, Self::Medium => Self::High, Self::High => Self::Maximum, Self::Maximum => Self::Off, }; } } // volume control impl Level { pub fn as_volume_divisor(&self) -> u8 { match self { Self::Off => u8::MAX, Self::Low => 4, Self::Medium => 3, Self::High => 2, Self::Maximum => 1, } } pub fn as_brightness_divisor(&self) -> u8 { match self { Self::Off => u8::MAX, Self::Low => 8, Self::Medium => 6, Self::High => 4, Self::Maximum => 2, } } } #[derive(Clone, Copy)] pub struct Settings { pub brightness: Level, pub volume: Level, pub button_sound_index: usize, } impl Default for Settings { fn default() -> Self { Self { brightness: Level::Medium, volume: Level::Medium, button_sound_index: 0, } } } } pub mod sequencer { pub struct BasicSequence<'a> { sequence: &'a [u8], index: usize, } impl<'a> BasicSequence<'a> { pub fn new(sequence: &'a [u8]) -> Self { Self { sequence, index: 0 } } pub fn next(&mut self) { self.index += 1; if self.index > self.sequence.len() - 1 { self.index = 0; } } pub fn get_value(&self) -> u8 { self.sequence[self.index] } } pub struct SequenceEntry { pub frequency_hz: u16, pub duration_ms: u16, } pub struct DynamicSequence<'a> { sequence: &'a [SequenceEntry], index: usize, // timer stuff system_tick_rate_hz: usize, ticks_remaining: usize, // playback stuff num_loops: usize, loop_count: usize, // misc enabled: bool, } impl<'a> DynamicSequence<'a> { pub fn new(sequence: &'a [SequenceEntry], system_tick_rate_hz: usize) -> Self { Self { sequence, index: 0, system_tick_rate_hz, ticks_remaining: 0, num_loops: 1, loop_count: 0, enabled: false, } } pub fn play_sequence(&mut self, sequence: &'a [SequenceEntry], num_loops: usize) { self.sequence = sequence; self.num_loops = num_loops; self.loop_count = 0; self.enabled = true; self.index = 0; self.ticks_remaining = 0; } pub fn enable(&mut self) { self.enabled = true; } pub fn disable(&mut self) { self.enabled = false; } pub fn tick(&mut self) { if self.enabled { self.ticks_remaining = self.ticks_remaining.saturating_sub(1); } } pub fn need_service(&self) -> bool { self.enabled && self.ticks_remaining == 0 } pub fn service(&mut self) -> Option { let entry = &self.sequence[self.index]; self.ticks_remaining = self.system_tick_rate_hz * (entry.duration_ms as usize) / 1000; self.index = self.index.saturating_add(1); let out = if self.loop_count > self.num_loops { None } else { Some(entry.frequency_hz) }; if self.index > self.sequence.len() - 1 { self.index = 0; self.loop_count = self.loop_count.saturating_add(1); } out } } } use crate::insert_coin::{ DacService, LedService, Service, SimplePwmCore, TickService, TickServiceData, TickTimerService, }; use crate::synthesizer::SynthesizerService; pub use settings::Settings; #[cfg(feature = "enable_print")] use ch32_hal::println; pub struct TimerConfig { pub sp_timer_ms: usize, pub lp_timer_ms: usize, pub batt_adc_timer_ms: usize, pub usb_adc_timer_ms: usize, pub led0_timer_ms: usize, pub led1_timer_ms: usize, pub led2_timer_ms: usize, } pub struct Timers { sp_timer: TickTimerService, lp_timer: TickTimerService, batt_adc_timer: TickTimerService, usb_adc_timer: TickTimerService, led0_timer: TickTimerService, led1_timer: TickTimerService, led2_timer: TickTimerService, } impl Timers { pub fn new(config: TimerConfig, system_tick_rate_hz: usize) -> Self { Self { sp_timer: TickTimerService::new( TickServiceData::new(config.sp_timer_ms * system_tick_rate_hz / 1000), false, ), lp_timer: TickTimerService::new( TickServiceData::new(config.lp_timer_ms * system_tick_rate_hz / 1000), false, ), batt_adc_timer: TickTimerService::new( TickServiceData::new(config.batt_adc_timer_ms * system_tick_rate_hz / 1000), false, ), usb_adc_timer: TickTimerService::new( TickServiceData::new(config.usb_adc_timer_ms * system_tick_rate_hz / 1000), false, ), led0_timer: TickTimerService::new( TickServiceData::new(config.led0_timer_ms * system_tick_rate_hz / 1000), true, ), led1_timer: TickTimerService::new( TickServiceData::new(config.led1_timer_ms * system_tick_rate_hz / 1000), true, ), led2_timer: TickTimerService::new( TickServiceData::new(config.led2_timer_ms * system_tick_rate_hz / 1000), true, ), } } pub fn tick(&mut self) { self.sp_timer.tick(); self.lp_timer.tick(); self.batt_adc_timer.tick(); self.usb_adc_timer.tick(); self.led0_timer.tick(); self.led1_timer.tick(); self.led2_timer.tick(); } pub fn need_service(&self) -> bool { self.sp_timer.need_service() | self.lp_timer.need_service() | self.batt_adc_timer.need_service() | self.usb_adc_timer.need_service() | self.led0_timer.need_service() | self.led1_timer.need_service() | self.led2_timer.need_service() } pub fn init(&mut self) { self.led0_timer.reset(); self.led0_timer.enable(true); } } // pub struct ServiceConfigs { // } // things that sort of don't touch hardware but also do? // TODO: make this merged with the interfaces maybe // but also maybe not, since these are things that require servicing pub struct Services { pub led0: LedService, pub led1: LedService, pub led2: LedService, pub synth0: SynthesizerService, pub sample_player: DacService<'static>, pub sequencer: sequencer::DynamicSequence<'static>, } impl Services { pub fn tick(&mut self) { self.synth0.tick(); self.sample_player.tick(); self.sequencer.tick(); } } pub struct Config { pub system_tick_rate_hz: usize, pub timers: TimerConfig, } pub struct Sequences { pub led0: sequencer::BasicSequence<'static>, pub led1: sequencer::BasicSequence<'static>, pub led2: sequencer::BasicSequence<'static>, pub audio: &'static [(&'static [sequencer::SequenceEntry], usize)], } // things that touch hardware pub struct Interfaces { pub pwm_core: SimplePwmCore<'static, ch32_hal::peripherals::TIM1>, pub adc_core: crate::AdcCore, } pub struct App { state: State, pub settings: Settings, timers: Timers, services: Services, sequences: Sequences, interfaces: Interfaces, } use settings::Level; impl App { pub fn new( config: Config, services: Services, sequences: Sequences, interfaces: Interfaces, ) -> Self { Self { state: State::default(), settings: Settings::default(), timers: Timers::new(config.timers, config.system_tick_rate_hz), services, sequences, interfaces, } } pub fn init(&mut self) { // self.timers.init(); self.timers.batt_adc_timer.reset(); self.timers.batt_adc_timer.enable(true); self.timers.usb_adc_timer.reset(); self.timers.usb_adc_timer.enable(true); self.timers.led0_timer.reset(); self.timers.led0_timer.enable(true); self.timers.led1_timer.reset(); self.timers.led1_timer.enable(true); self.timers.led2_timer.reset(); self.timers.led2_timer.enable(true); self.services.synth0.set_freq(1); self.services.synth0.disable(); self.services.sequencer.disable(); } pub fn set_state(&mut self, state: State) { self.state = state } pub fn state(&self) -> State { self.state } pub fn settings(&self) -> Settings { self.settings } pub fn tick(&mut self) { self.timers.tick(); self.services.tick(); } pub fn service(&mut self) { // timers if self.timers.sp_timer.need_service() { self.timers.sp_timer.service(); #[cfg(feature = "enable_print")] println!("sp service"); self.timers.sp_timer.reset(); self.main_button_short_press(); } if self.timers.lp_timer.need_service() { self.timers.lp_timer.service(); #[cfg(feature = "enable_print")] println!("lp service"); self.timers.lp_timer.reset(); self.main_button_long_press(); } if self.timers.batt_adc_timer.need_service() { self.timers.batt_adc_timer.service(); let bv = self.interfaces.adc_core.get_battery_voltage(); #[cfg(feature = "enable_print")] println!("batt adc service: {bv}"); // TODO: // do stuff if the battery voltage is below some threshold } if self.timers.usb_adc_timer.need_service() { self.timers.usb_adc_timer.service(); #[cfg(feature = "enable_print")] println!("usb adc service"); } if self.timers.led0_timer.need_service() { let out = match self.settings.brightness { Level::Off => 0, Level::Low => 5, Level::Medium => 25, Level::High => 75, Level::Maximum => { self.sequences.led0.next(); self.sequences.led0.get_value() / 6 } }; self.timers.led0_timer.service(); self.services.led0.set_amplitude(out); // #[cfg(feature = "enable_print")] // println!("led0 sevice {}", self.sequences.led0.get_value()); } if self.timers.led1_timer.need_service() { let out = match self.settings.brightness { Level::Off => 0, Level::Low => 5, Level::Medium => 25, Level::High => 75, Level::Maximum => { self.sequences.led1.next(); self.sequences.led1.get_value() / 6 } }; self.timers.led1_timer.service(); self.services.led1.set_amplitude(out); // #[cfg(feature = "enable_print")] // println!("led1 service"); } if self.timers.led2_timer.need_service() { let out = match self.settings.brightness { Level::Off => 0, Level::Low => 5, Level::Medium => 25, Level::High => 75, Level::Maximum => { self.sequences.led2.next(); self.sequences.led2.get_value() / 6 } }; self.timers.led2_timer.service(); self.services.led2.set_amplitude(out); // #[cfg(feature = "enable_print")] // println!("led2 service"); } // services if self.services.led0.need_service() { self.interfaces .pwm_core .write_amplitude(self.services.led0.channel, self.services.led0.amplitude); self.services.led0.service(); } if self.services.led1.need_service() { self.interfaces .pwm_core .write_amplitude(self.services.led1.channel, self.services.led1.amplitude); self.services.led1.service(); } if self.services.led2.need_service() { self.interfaces .pwm_core .write_amplitude(self.services.led2.channel, self.services.led2.amplitude); self.services.led2.service(); } // TODO: disable when you get to the end automatically // in the sequencer, not here if self.services.sequencer.need_service() { if let Some(out) = self.services.sequencer.service() { if out == 0 { self.services.synth0.disable(); } else { self.services.synth0.enable(); self.services.synth0.set_freq(out.into()); } } else { self.services.sequencer.disable(); self.services.synth0.disable(); } } if self.services.synth0.need_service() { let out = match self.services.synth0.service() { Some(value) => value / 6 / self.settings.volume.as_volume_divisor(), None => 0, }; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out); } if self.services.sample_player.need_service() { self.services.sample_player.service(); let out = self.services.sample_player.get_amplitude() / 2; self.interfaces .pwm_core .write_amplitude(ch32_hal::timer::Channel::Ch4, out as u8); } } } // interfaces to the app (for buttons, etc.) impl App { pub fn shut_down(&mut self) { self.interfaces .pwm_core .write_amplitude(self.services.led0.channel, 0); self.interfaces .pwm_core .write_amplitude(self.services.led1.channel, 0); self.interfaces .pwm_core .write_amplitude(self.services.led2.channel, 0); self.interfaces .pwm_core .disable(ch32_hal::timer::Channel::Ch4); } pub fn volume_button(&mut self) { self.settings.volume.next(); #[cfg(feature = "enable_print")] println!("new volume: {:?}", self.settings.volume); } pub fn brightness_button(&mut self) { self.settings.brightness.next(); #[cfg(feature = "enable_print")] println!("new brightness: {:?}", self.settings.brightness); } pub fn main_button_press(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("main button press"); self.timers.sp_timer.reset(); self.timers.lp_timer.reset(); self.timers.sp_timer.enable(true); self.timers.lp_timer.enable(true); self.main_button_click(); } pub fn main_button_release(&mut self) { // TODO // #[cfg(feature = "enable_print")] // println!("main button release"); // let timers = ( // self.timers.sp_timer.is_enabled(), // self.timers.lp_timer.is_enabled(), // ); self.timers.sp_timer.reset(); self.timers.lp_timer.reset(); // match timers { // // click // (true, true) => self.main_button_click(), // // short press // (false, true) => self.main_button_short_press(), // // long press // (false, false) => self.main_button_long_press(), // // anything else is not possible // _ => {} // } } pub fn coin_detect(&mut self) { #[cfg(feature = "enable_print")] println!("coin detect"); // self.services.sample_player.play_sample(); self.services .sequencer .play_sequence(&crate::sequences::COIN_CHIRP, 0); } } // Events impl App { fn main_button_click(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("click"); let data = self.sequences.audio[self.settings.button_sound_index]; self.services.sequencer.play_sequence(data.0, data.1); self.settings.button_sound_index += 1; if self.settings.button_sound_index > self.sequences.audio.len() - 1 { self.settings.button_sound_index = 0; } self.services.synth0.enable(); // TODO: // this is a hack to stop the coin thing from playing. self.services.sample_player.disable(); } fn main_button_short_press(&self) { // TODO #[cfg(feature = "enable_print")] println!("short press"); } fn main_button_long_press(&mut self) { // TODO #[cfg(feature = "enable_print")] println!("long press"); self.set_state(State::DeepSleep); } } // Getters impl App { pub fn get_state(&self) -> State { self.state } } // TODO LIST // BROKEN: // 1. audio scaling causes crash // 2. popping on sample playback // 3. actual app sequence (start at idle?) // // AUDIO: // 3. amp_en control // // LED: // // INTERFACE: // 1. short press handling // 2. long press handling // // SYSTEM: // 1. deep sleep // 2. battery voltage monitoring // 3. battery voltage cutoff // // STRETCH TODO LIST // 1. clean up edge detector // 2. better handling for pwm scaling (brightness / volume) // 3. better interrupt handling structs // 4. led DynamicSequence