diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 655fd08..3288ac2 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -280,6 +280,7 @@ dependencies = [ "defmt-rtt", "embedded-hal", "embedded-midi", + "midi-types", "nb 1.1.0", "panic-halt", "stm32f0xx-hal", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index ab5c3de..18d6b3b 100755 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -2,6 +2,15 @@ edition = "2021" name = "stm32f042c4-blinky" version = "0.1.0" +default-run = "main" + +[[bin]] +name = "main" +path = "src/main.rs" + +[[bin]] +name = "mapper" +path = "src/mapper.rs" [dependencies] cortex-m = { version = "0.7", features = ["inline-asm", "critical-section-single-core"] } @@ -9,6 +18,7 @@ cortex-m-rt = "0.7" critical-section = "1" embedded-hal = "0.2" embedded-midi = "0.1.2" +midi-types = "0.1.7" nb = "1" panic-halt = "0.2" stm32f0xx-hal = { version = "0.18", features = ["stm32f042", "rt"] } diff --git a/firmware/src/main.rs b/firmware/src/main.rs index cb240cd..0043f39 100755 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -2,6 +2,7 @@ #![no_main] use cortex_m_rt::entry; +use defmt::warn; use hal::prelude::*; use hal::stm32; use panic_halt as _; @@ -10,7 +11,6 @@ use stm32f0xx_hal as hal; use defmt::info; use defmt_rtt as _; -use embedded_midi::Channel; use hal::{delay::Delay, serial::Serial}; defmt::timestamp!("{=u32}", { @@ -22,27 +22,16 @@ defmt::timestamp!("{=u32}", { }); struct Bus { - latch_en: - stm32f0xx_hal::gpio::gpioa::PA0>, - select_en: stm32f0xx_hal::gpio::gpiob::PB8< - stm32f0xx_hal::gpio::Output, - >, - pb0: - stm32f0xx_hal::gpio::gpiob::PB0>, - pb1: - stm32f0xx_hal::gpio::gpiob::PB1>, - pb2: - stm32f0xx_hal::gpio::gpiob::PB2>, - pb3: - stm32f0xx_hal::gpio::gpiob::PB3>, - pb4: - stm32f0xx_hal::gpio::gpiob::PB4>, - pb5: - stm32f0xx_hal::gpio::gpiob::PB5>, - pb6: - stm32f0xx_hal::gpio::gpiob::PB6>, - pb7: - stm32f0xx_hal::gpio::gpiob::PB7>, + latch_en: hal::gpio::gpioa::PA0>, + select_en: hal::gpio::gpiob::PB8>, + pb0: hal::gpio::gpiob::PB0>, + pb1: hal::gpio::gpiob::PB1>, + pb2: hal::gpio::gpiob::PB2>, + pb3: hal::gpio::gpiob::PB3>, + pb4: hal::gpio::gpiob::PB4>, + pb5: hal::gpio::gpiob::PB5>, + pb6: hal::gpio::gpiob::PB6>, + pb7: hal::gpio::gpiob::PB7>, } #[entry] @@ -68,9 +57,8 @@ fn main() -> ! { let gpioa = dp.GPIOA.split(&mut rcc); let gpiob = dp.GPIOB.split(&mut rcc); - let button_1_5 = cortex_m::interrupt::free(|cs| gpiob.pb11.into_floating_input(cs)); - - let button_6_10 = cortex_m::interrupt::free(|cs| gpiob.pb13.into_floating_input(cs)); + let button_1_5 = cortex_m::interrupt::free(|cs| gpiob.pb13.into_floating_input(cs)); + let button_6_10 = cortex_m::interrupt::free(|cs| gpiob.pb11.into_floating_input(cs)); let mut bus = cortex_m::interrupt::free(|cs| Bus { latch_en: gpioa.pa0.into_push_pull_output(cs), @@ -92,79 +80,105 @@ fn main() -> ! { Serial::usart1(dp.USART1, (tx, rx), 31_250.bps(), &mut rcc) }); - let (_tx, rx) = serial.split(); - let mut midi = embedded_midi::MidiIn::new(rx); + let (tx, _rx) = serial.split(); + let mut midi = embedded_midi::MidiOut::new(tx); + + bus.output(3, 0xFF, &mut delay); + bus.output(10, 0xFF, &mut delay); + bus.output(11, 0xFF, &mut delay); // Main loop - let mut ic_03: u8 = 0; - let mut ic_10: u8 = 0; - let mut ic_11: u8 = 0; - + let mut button_states = [false; 12]; loop { - let message = midi.read(); - match message { - Ok(embedded_midi::MidiMessage::ControlChange(channel, control, value)) - if channel == Channel::new(0) => - { - bus.led_button_1(&mut delay); - if control == embedded_midi::Control::new(20) { - bus.led_button_2(&mut delay); - ic_03 = value.into(); - } else if control == embedded_midi::Control::new(21) { - bus.led_button_2(&mut delay); - let value: u8 = value.into(); - ic_03 = 128_u8 + value; - } else if control == embedded_midi::Control::new(22) { - bus.led_button_2(&mut delay); - ic_10 = value.into(); - } else if control == embedded_midi::Control::new(23) { - bus.led_button_2(&mut delay); - let value: u8 = value.into(); - ic_10 = 128_u8 + value; - } else if control == embedded_midi::Control::new(24) { - bus.led_button_2(&mut delay); - ic_11 = value.into(); - } else if control == embedded_midi::Control::new(25) { - bus.led_button_2(&mut delay); - let value: u8 = value.into(); - ic_11 = 128_u8 + value; + for i in 0_u8..6_u8 { + bus.output(11, 4_u8 << i, &mut delay); + delay.delay_us(1u16); + let previous_state = &mut button_states[i as usize]; + let new_state = button_1_5.is_high().unwrap_or(false); + if new_state != *previous_state { + *previous_state = new_state; + let control = control_from_button(i); + if new_state { + info!("Button {} pressed", i); + if midi + .write(&embedded_midi::MidiMessage::ControlChange( + embedded_midi::Channel::C1, + control, + midi_types::Value7::new(127), + )) + .is_err() + { + warn!("Failed to send MIDI message"); + } + } else { + info!("Button {} released", i); + if midi + .write(&embedded_midi::MidiMessage::ControlChange( + embedded_midi::Channel::C1, + control, + midi_types::Value7::new(0), + )) + .is_err() + { + warn!("Failed to send MIDI message"); + } } - bus.output(03, ic_03, &mut delay); - bus.output(10, ic_10, &mut delay); - bus.output(11, ic_11, &mut delay); } - Ok(_) => { - info!("unhandled midi message"); - } - Err(nb::Error::WouldBlock) => {} - Err(nb::Error::Other(hal::serial::Error::Framing)) => { - info!("Error::Framing"); - } - Err(nb::Error::Other(hal::serial::Error::Noise)) => { - info!("Error::Noise"); - } - Err(nb::Error::Other(hal::serial::Error::Overrun)) => { - info!("Error::Overrun"); - } - Err(nb::Error::Other(hal::serial::Error::Parity)) => { - info!("Error::Parity"); - } - Err(nb::Error::Other(_)) => { - info!("Error::Other"); - } - } - if button_1_5.is_high().unwrap_or(false) { - info!("Button 1-5 pressed"); - delay.delay_ms(500u16); - } - if button_6_10.is_high().unwrap_or(false) { - info!("Button 6-10 pressed"); - delay.delay_ms(500u16); + let i = i + 6; + let previous_state = &mut button_states[i as usize]; + let new_state = button_6_10.is_high().unwrap_or(false); + if new_state != *previous_state { + *previous_state = new_state; + let control = control_from_button(i); + if new_state { + info!("Button {} pressed", i); + if midi + .write(&embedded_midi::MidiMessage::ControlChange( + embedded_midi::Channel::C1, + control, + midi_types::Value7::new(127), + )) + .is_err() + { + warn!("Failed to send MIDI message"); + } + } else { + info!("Button {} released", i); + if midi + .write(&embedded_midi::MidiMessage::ControlChange( + embedded_midi::Channel::C1, + control, + midi_types::Value7::new(0), + )) + .is_err() + { + warn!("Failed to send MIDI message"); + } + } + } } } } +fn control_from_button(button: u8) -> embedded_midi::Control { + match button { + 0 => embedded_midi::Control::new(20), + 1 => embedded_midi::Control::new(21), + 2 => embedded_midi::Control::new(22), + 3 => embedded_midi::Control::new(23), + 4 => embedded_midi::Control::new(24), + 5 => embedded_midi::Control::new(31), + 6 => embedded_midi::Control::new(25), + 7 => embedded_midi::Control::new(26), + 8 => embedded_midi::Control::new(27), + 9 => embedded_midi::Control::new(28), + 10 => embedded_midi::Control::new(29), + 11 => embedded_midi::Control::new(30), + _ => embedded_midi::Control::new(0), + } +} + impl Bus { fn output(&mut self, ic: u8, data: u8, delay: &mut Delay) { match ic { @@ -198,126 +212,10 @@ impl Bus { } fn select(&mut self, line: u8, delay: &mut Delay) { - self.select_en.set_low().ok(); - delay.delay_us(1u16); - match line { - 0 => { - self.pb0.set_low().ok(); - self.pb1.set_low().ok(); - self.pb2.set_low().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 1 => { - self.pb0.set_high().ok(); - self.pb1.set_low().ok(); - self.pb2.set_low().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 2 => { - self.pb0.set_low().ok(); - self.pb1.set_high().ok(); - self.pb2.set_low().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 3 => { - self.pb0.set_high().ok(); - self.pb1.set_high().ok(); - self.pb2.set_low().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 4 => { - self.pb0.set_low().ok(); - self.pb1.set_low().ok(); - self.pb2.set_high().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 5 => { - self.pb0.set_high().ok(); - self.pb1.set_low().ok(); - self.pb2.set_high().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 6 => { - self.pb0.set_low().ok(); - self.pb1.set_high().ok(); - self.pb2.set_high().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - 7 => { - self.pb0.set_high().ok(); - self.pb1.set_high().ok(); - self.pb2.set_high().ok(); - self.pb3.set_low().ok(); - self.pb4.set_low().ok(); - self.pb5.set_low().ok(); - self.pb6.set_low().ok(); - self.pb7.set_low().ok(); - } - _ => {} - } + self.write(line); delay.delay_us(1u16); self.latch_en.set_high().ok(); delay.delay_us(1u16); self.latch_en.set_low().ok(); - delay.delay_us(1u16); - self.select_en.set_high().ok(); - } - - fn led_button_1(&mut self, delay: &mut Delay) { - self.output(03, 0b1111_1011, delay); - self.output(10, 0b0001_0000, delay); - self.output(11, 0b0000_0000, delay); - } - - fn led_button_2(&mut self, delay: &mut Delay) { - self.output(03, 0b1111_1101, delay); - self.output(10, 0b0001_0000, delay); - self.output(11, 0b0000_0000, delay); - } - - fn led_button_3(&mut self, delay: &mut Delay) { - self.output(03, 0b1111_1110, delay); - self.output(10, 0b0001_0000, delay); - self.output(11, 0b0000_0000, delay); - } - - fn led_button_9(&mut self, delay: &mut Delay) { - self.output(03, 0b1110_1111, delay); - self.output(10, 0b0001_0000, delay); - self.output(11, 0b0000_0000, delay); - } - - fn led_button_10(&mut self, delay: &mut Delay) { - self.output(03, 0b1111_0111, delay); - self.output(10, 0b0001_0000, delay); - self.output(11, 0b0000_0000, delay); } } diff --git a/firmware/src/mapper.rs b/firmware/src/mapper.rs new file mode 100755 index 0000000..d72491f --- /dev/null +++ b/firmware/src/mapper.rs @@ -0,0 +1,188 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use hal::prelude::*; +use hal::stm32; +use panic_halt as _; +use stm32f0xx_hal as hal; + +use defmt::info; +use defmt_rtt as _; + +use embedded_midi::Channel; +use hal::{delay::Delay, serial::Serial}; + +defmt::timestamp!("{=u32}", { + static mut COUNTER: u32 = 0; + unsafe { + COUNTER = COUNTER.wrapping_add(1); + COUNTER + } +}); + +struct Bus { + latch_en: hal::gpio::gpioa::PA0>, + select_en: hal::gpio::gpiob::PB8>, + pb0: hal::gpio::gpiob::PB0>, + pb1: hal::gpio::gpiob::PB1>, + pb2: hal::gpio::gpiob::PB2>, + pb3: hal::gpio::gpiob::PB3>, + pb4: hal::gpio::gpiob::PB4>, + pb5: hal::gpio::gpiob::PB5>, + pb6: hal::gpio::gpiob::PB6>, + pb7: hal::gpio::gpiob::PB7>, +} + +#[entry] +fn main() -> ! { + // Get device peripherals + let mut dp = stm32::Peripherals::take().unwrap(); + let cp = cortex_m::Peripherals::take().unwrap(); + + // Configure the clock to 48 MHz + let mut rcc = dp + .RCC + .configure() + .hsi48() + .enable_crs(dp.CRS) + .sysclk(48.mhz()) + .pclk(24.mhz()) + .freeze(&mut dp.FLASH); + + // Set up delay provider + let mut delay = Delay::new(cp.SYST, &rcc); + + // Configure GPIO + let gpioa = dp.GPIOA.split(&mut rcc); + let gpiob = dp.GPIOB.split(&mut rcc); + + let button_1_5 = cortex_m::interrupt::free(|cs| gpiob.pb13.into_floating_input(cs)); + let button_6_10 = cortex_m::interrupt::free(|cs| gpiob.pb11.into_floating_input(cs)); + + let mut bus = cortex_m::interrupt::free(|cs| Bus { + latch_en: gpioa.pa0.into_push_pull_output(cs), + select_en: gpiob.pb8.into_open_drain_output(cs), + pb0: gpiob.pb0.into_push_pull_output(cs), + pb1: gpiob.pb1.into_push_pull_output(cs), + pb2: gpiob.pb2.into_push_pull_output(cs), + pb3: gpiob.pb3.into_push_pull_output(cs), + pb4: gpiob.pb4.into_push_pull_output(cs), + pb5: gpiob.pb5.into_push_pull_output(cs), + pb6: gpiob.pb6.into_push_pull_output(cs), + pb7: gpiob.pb7.into_push_pull_output(cs), + }); + + // Configure serial port + let serial = cortex_m::interrupt::free(|cs| { + let tx = gpioa.pa9.into_alternate_af1(cs); + let rx = gpioa.pa10.into_alternate_af1(cs); + Serial::usart1(dp.USART1, (tx, rx), 31_250.bps(), &mut rcc) + }); + + let (_tx, rx) = serial.split(); + let mut midi = embedded_midi::MidiIn::new(rx); + + // Main loop + let mut ic_03: u8 = 0; + let mut ic_10: u8 = 0; + let mut ic_11: u8 = 0; + + loop { + let message = midi.read(); + match message { + Ok(embedded_midi::MidiMessage::ControlChange(channel, control, value)) + if channel == Channel::new(0) => + { + if control == embedded_midi::Control::new(20) { + ic_03 = value.into(); + } else if control == embedded_midi::Control::new(21) { + let value: u8 = value.into(); + ic_03 = 128_u8 + value; + } else if control == embedded_midi::Control::new(22) { + ic_10 = value.into(); + } else if control == embedded_midi::Control::new(23) { + let value: u8 = value.into(); + ic_10 = 128_u8 + value; + } else if control == embedded_midi::Control::new(24) { + ic_11 = value.into(); + } else if control == embedded_midi::Control::new(25) { + let value: u8 = value.into(); + ic_11 = 128_u8 + value; + } + bus.output(03, ic_03, &mut delay); + bus.output(10, ic_10, &mut delay); + bus.output(11, ic_11, &mut delay); + } + Ok(_) => { + info!("unhandled midi message"); + } + Err(nb::Error::WouldBlock) => {} + Err(nb::Error::Other(hal::serial::Error::Framing)) => { + info!("Error::Framing"); + } + Err(nb::Error::Other(hal::serial::Error::Noise)) => { + info!("Error::Noise"); + } + Err(nb::Error::Other(hal::serial::Error::Overrun)) => { + info!("Error::Overrun"); + } + Err(nb::Error::Other(hal::serial::Error::Parity)) => { + info!("Error::Parity"); + } + Err(nb::Error::Other(_)) => { + info!("Error::Other"); + } + } + if button_1_5.is_high().unwrap_or(false) { + info!("Button 1-5 pressed"); + delay.delay_ms(500u16); + } + + if button_6_10.is_high().unwrap_or(false) { + info!("Button 6-10 pressed"); + delay.delay_ms(500u16); + } + } +} + +impl Bus { + fn output(&mut self, ic: u8, data: u8, delay: &mut Delay) { + match ic { + 0 | 3 => self.select(1, delay), + 1 | 10 => self.select(0, delay), + 2 | 11 => self.select(5, delay), + + _ => self.select(2, delay), + } + delay.delay_us(1u16); + self.select_en(true); + delay.delay_us(1u16); + self.write(data); + delay.delay_us(1u16); + self.select_en(false); + } + + fn write(&mut self, data: u8) { + self.pb0.set_state((data & 0b_0000_0001 != 0).into()).ok(); + self.pb1.set_state((data & 0b_0000_0010 != 0).into()).ok(); + self.pb2.set_state((data & 0b_0000_0100 != 0).into()).ok(); + self.pb3.set_state((data & 0b_0000_1000 != 0).into()).ok(); + self.pb4.set_state((data & 0b_0001_0000 != 0).into()).ok(); + self.pb5.set_state((data & 0b_0010_0000 != 0).into()).ok(); + self.pb6.set_state((data & 0b_0100_0000 != 0).into()).ok(); + self.pb7.set_state((data & 0b_1000_0000 != 0).into()).ok(); + } + + fn select_en(&mut self, enable: bool) { + self.select_en.set_state(enable.into()).ok(); + } + + fn select(&mut self, line: u8, delay: &mut Delay) { + self.write(line); + delay.delay_us(1u16); + self.latch_en.set_high().ok(); + delay.delay_us(1u16); + self.latch_en.set_low().ok(); + } +} diff --git a/mapping.json b/mapping.json new file mode 100644 index 0000000..41e591c --- /dev/null +++ b/mapping.json @@ -0,0 +1,177 @@ +{ + "notes": [ + { + "description": "00 00001 0010 00 [G] [G] [G] [2] []", + "register_values": { + "IC10": 255, + "IC11": 0, + "IC03": 253 + } + }, + { + "description": "00 00000 1100 00 [P] [P] [P] [3] []", + "register_values": { + "IC10": 255, + "IC03": 254, + "IC11": 0 + } + }, + { + "description": "00 00010 0000 01 [] [F] [F] [1] []", + "register_values": { + "IC10": 255, + "IC03": 251, + "IC11": 0 + } + }, + { + "description": "00 00100 0000 10 [] [E] [E] [0] []", + "register_values": { + "IC03": 247, + "IC10": 255, + "IC11": 0 + } + }, + { + "description": "10 01000 0000 00 [] [D] [D] [9] []", + "register_values": { + "IC11": 0, + "IC03": 239, + "IC10": 255 + } + }, + { + "description": "00 00000 1100 00 [P] [P] [P] [3] []", + "register_values": { + "IC10": 255, + "IC03": 222, + "IC11": 0 + } + }, + { + "description": "00 00001 0010 00 [G] [G] [G] [2] []", + "register_values": { + "IC11": 0, + "IC03": 221, + "IC10": 255 + } + }, + { + "description": "00 00010 0000 01 [] [F] [F] [1] []", + "register_values": { + "IC11": 0, + "IC03": 219, + "IC10": 255 + } + }, + { + "description": "00 00100 0000 10 [] [E] [E] [0] []", + "register_values": { + "IC03": 215, + "IC11": 0, + "IC10": 255 + } + }, + { + "description": "10 01000 0000 00 [] [D] [D] [9] []", + "register_values": { + "IC10": 255, + "IC03": 207, + "IC11": 0 + } + }, + { + "description": "00 00000 1100 00 [P] [P] [P] [3] []", + "register_values": { + "IC10": 255, + "IC03": 190, + "IC11": 0 + } + }, + { + "description": "00 00001 0010 00 [G] [G] [G] [2] []", + "register_values": { + "IC03": 189, + "IC11": 0, + "IC10": 255 + } + }, + { + "description": "00 00010 0000 01 [] [F] [F] [1] []", + "register_values": { + "IC10": 255, + "IC03": 187, + "IC11": 0 + } + }, + { + "description": "00 00100 0000 10 [] [E] [E] [0] []", + "register_values": { + "IC11": 0, + "IC03": 183, + "IC10": 255 + } + }, + { + "description": "10 01000 0000 00 [] [D] [D] [9] []", + "register_values": { + "IC11": 0, + "IC03": 175, + "IC10": 255 + } + }, + { + "description": "01 10000 1101 00 [B, C, E, F, G, P] [A, B, C, P] [A, B, C, P] [3, 4, 5, 6, 7, 8] []", + "register_values": { + "IC11": 0, + "IC10": 255, + "IC03": 158 + } + }, + { + "description": "01 10001 0011 00 [B, C, E, F, G] [A, B, C, G] [A, B, C, G] [2, 4, 5, 6, 7, 8] []", + "register_values": { + "IC10": 255, + "IC11": 0, + "IC03": 157 + } + }, + { + "description": "01 10001 0010 00 [B, C, E, F, G] [] [A, B, V, G] [2, 4, 5, 6, 7, 8] []", + "register_values": { + "IC10": 253, + "IC11": 0, + "IC03": 157 + } + }, + { + "description": "00 00000 0000 00 [] [] [] [] [1, 6]", + "register_values": { + "IC11": 4, + "IC10": 255, + "IC03": 255 + } + }, + { + "description": "00 00000 0000 00 [] [] [] [] [2, 7]", + "register_values": { + "IC03": 255, + "IC10": 255, + "IC11": 8 + } + }, + { + "description": "00 00000 0000 00 [] [] [] [] [3, 7]", + "register_values": { + "IC03": 255, + "IC10": 255, + "IC11": 16 + } + } + ], + "register_values": { + "IC03": 0, + "IC10": 0, + "IC11": 0 + } +} \ No newline at end of file diff --git a/xtask/src/args.rs b/xtask/src/args.rs index 6a30d20..7cc4a06 100644 --- a/xtask/src/args.rs +++ b/xtask/src/args.rs @@ -12,4 +12,5 @@ pub struct Args { pub enum Command { Run, Collect, + Mapper, } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1b58275..cf8e8c7 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,5 +1,6 @@ mod args; mod collect; +mod mapper; mod run; mod workspace; @@ -20,5 +21,8 @@ async fn main() { Some(args::Command::Collect) => { collect::collect().await; } + Some(args::Command::Mapper) => { + mapper::mapper().await; + } } } diff --git a/xtask/src/mapper.rs b/xtask/src/mapper.rs new file mode 100644 index 0000000..8788f72 --- /dev/null +++ b/xtask/src/mapper.rs @@ -0,0 +1,153 @@ +use std::sync::Arc; +use tokio::process::{Child, Command}; +use tokio::sync::Mutex; +use tokio_util::sync::CancellationToken; + +type ProcessHandle = Arc>>; + +pub async fn mapper() { + let qjackctl_handle: ProcessHandle = Arc::new(Mutex::new(None)); + let firmware_handle: ProcessHandle = Arc::new(Mutex::new(None)); + let mapper_handle: ProcessHandle = Arc::new(Mutex::new(None)); + + let cancel_token = CancellationToken::new(); + + let cleanup_token = cancel_token.clone(); + + tokio::spawn(async move { + tokio::signal::ctrl_c() + .await + .expect("Failed to listen for ctrl+c"); + println!("Received Ctrl+C, shutting down..."); + cleanup_token.cancel(); + }); + + // Start processes + println!("Starting qjackctl..."); + let qjackctl = spawn_qjackctl().await; + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + + println!("Starting firmware debugger in new terminal..."); + let firmware = spawn_firmware_in_terminal().await; + + println!("Starting mapper..."); + let mapper = spawn_mapper().await; + + // Store handles for cleanup + *qjackctl_handle.lock().await = Some(qjackctl); + *firmware_handle.lock().await = Some(firmware); + *mapper_handle.lock().await = Some(mapper); + + // Get references back for waiting (firmware not monitored since gnome-terminal exits immediately) + let mut qjackctl = qjackctl_handle.lock().await.take().unwrap(); + let mut mapper = mapper_handle.lock().await.take().unwrap(); + + // Wait for any process to exit or cancellation + tokio::select! { + result = qjackctl.wait() => { + println!("qjackctl exited: {:?}", result); + kill_process(&mut mapper, "mapper").await; + cleanup_processes(vec![firmware_handle]).await; + stop_jack_daemon().await; + } + result = mapper.wait() => { + println!("mapper exited: {:?}", result); + kill_process(&mut qjackctl, "qjackctl").await; + cleanup_processes(vec![firmware_handle]).await; + stop_jack_daemon().await; + } + _ = cancel_token.cancelled() => { + println!("Shutdown requested"); + kill_process(&mut mapper, "mapper").await; + kill_process(&mut qjackctl, "qjackctl").await; + cleanup_processes(vec![firmware_handle]).await; + stop_jack_daemon().await; + } + } + + println!("All processes stopped"); +} + +async fn cleanup_processes(handles: Vec) { + for handle in handles { + if let Some(mut child) = handle.lock().await.take() { + let _ = child.kill().await; + } + } +} + +async fn stop_jack_daemon() { + println!("Stopping JACK daemon..."); + + #[cfg(target_os = "windows")] + let (jack, args) = ("taskkill", vec!["/f", "/im", "jackd.exe"]); + + #[cfg(target_os = "linux")] + let (jack, args) = ("pkill", vec!["-f", "jackd"]); + + Command::new(jack) + .args(args) + .output() + .await + .expect("Failed to stop JACK daemon"); +} + +async fn spawn_qjackctl() -> Child { + #[cfg(target_os = "windows")] + let qjackctl = "C:\\Program Files\\JACK2\\qjackctl\\qjackctl.exe"; + #[cfg(target_os = "linux")] + let qjackctl = "qjackctl"; + + Command::new(qjackctl) + .arg("/start-server") + .spawn() + .expect("Could not start qjackctl") +} + +async fn spawn_firmware_in_terminal() -> Child { + #[cfg(target_os = "windows")] + { + Command::new("C:\\Program Files\\Git\\git-bash.exe") + .args(["-c", "cd firmware && cargo run; read -p 'Press Enter to close...'"]) + .spawn() + .expect("Could not start firmware debugger in git-bash") + } + + #[cfg(target_os = "linux")] + { + Command::new("gnome-terminal") + .args([ + "--title=Firmware Debugger - FCB Looper", + "--", + "bash", "-c", + "cd firmware && echo 'Starting firmware debugger...' && cargo run; echo 'Firmware debugger exited. Press Enter to close...'; read" + ]) + .spawn() + .expect("Could not start firmware debugger in gnome-terminal") + } +} + +async fn spawn_mapper() -> Child { + Command::new("cargo") + .args(["run", "--package", "mapper"]) + .stdin(std::process::Stdio::inherit()) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .spawn() + .expect("Could not start mapper") +} + +async fn kill_process(child: &mut Child, name: &str) { + match child.kill().await { + Ok(()) => println!("Stopped {}", name), + Err(e) => { + // Don't print error if process already exited + let error_msg = e.to_string().to_lowercase(); + if !error_msg.contains("no such process") && + !error_msg.contains("not found") && + !error_msg.contains("invalid argument") { + eprintln!("Failed to stop {}: {}", name, e); + } + } + } +} \ No newline at end of file diff --git a/xtask/src/run.rs b/xtask/src/run.rs index e90c393..91af653 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -124,13 +124,8 @@ async fn spawn_qjackctl() -> Child { #[cfg(target_os = "linux")] let qjackctl = "qjackctl"; - #[cfg(target_os = "windows")] - let qjackctl_arg = "/start-server"; - #[cfg(target_os = "linux")] - let qjackctl_arg = "--start-server"; - Command::new(qjackctl) - .arg(qjackctl_arg) + .arg("/start-server") .spawn() .expect("Could not start qjackctl") }