use jack::RawMidi; use kanal::{Receiver, Sender}; use std::io::{self, Write}; #[derive(Debug)] enum MidiCommand { ControlChange { cc: u8, value: u8 }, Quit, } struct MidiSender { midi_out: jack::Port, command_receiver: Receiver, } impl MidiSender { fn new( client: &jack::Client, command_receiver: Receiver, ) -> Result> { let midi_out = client .register_port("midi_out", jack::MidiOut::default()) .map_err(|e| format!("Could not create MIDI output port: {}", e))?; Ok(Self { midi_out, command_receiver, }) } } impl jack::ProcessHandler for MidiSender { fn process(&mut self, _client: &jack::Client, ps: &jack::ProcessScope) -> jack::Control { let mut midi_writer = self.midi_out.writer(ps); // Process all pending commands while let Ok(Some(command)) = self.command_receiver.try_recv() { match command { MidiCommand::ControlChange { cc, value } => { let midi_data = [0xB0, cc, value]; // Control Change on channel 1 if let Err(e) = midi_writer.write(&RawMidi { time: 0, bytes: &midi_data, }) { eprintln!("Failed to send MIDI: {}", e); } } MidiCommand::Quit => return jack::Control::Quit, } } jack::Control::Continue } } fn show_help() { println!("\nFCB1010 MIDI simulator"); println!("====================================="); println!("Button mappings:"); println!("1 - Button 1 (CC 20): Record/Arm / Tap Tempo"); println!("2 - Button 2 (CC 21): Play/Mute Track / Click Toggle"); println!("3 - Button 3 (CC 22): Solo Track"); println!("4 - Button 4 (CC 23): Overdub"); println!("5 - Button 5 (CC 24): Clear Track / Clear Column"); println!("6 - Button 6 (CC 25): Column 1 Select"); println!("7 - Button 7 (CC 26): Column 2 Select"); println!("8 - Button 8 (CC 27): Column 3 Select"); println!("9 - Button 9 (CC 28): Column 4 Select"); println!("0 - Button 10 (CC 29): Column 5 Select"); println!("u - UP (CC 30): Row Up / Mode Switch"); println!("d - DOWN (CC 31): Row Down / Mode Switch"); println!("e - Expression A (CC 1): Track/Click Volume (0-127)"); println!("m - Master Volume (CC 7): Master Volume (0-127)"); println!("h - Show this help"); println!("q - Quit\n"); } fn get_expression_value(prompt: &str) -> Result> { print!("{}", prompt); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let value: u8 = input .trim() .parse() .map_err(|_| "Invalid number. Please enter 0-127")?; if value > 127 { return Err("Value must be 0-127".into()); } Ok(value) } fn main() -> Result<(), Box> { println!("Starting FCB1010 MIDI Simulator..."); // Create JACK client let (client, status) = jack::Client::new("FCB1010", jack::ClientOptions::NO_START_SERVER)?; if !status.is_empty() { eprintln!("JACK client status: {:?}", status); } // Create command channel let (command_sender, command_receiver): (Sender, Receiver) = kanal::bounded(32); // Create MIDI sender let midi_sender = MidiSender::new(&client, command_receiver)?; // Activate client let _active_client = client.activate_async((), midi_sender)?; show_help(); // Main input loop loop { print!("Command (h for help): "); io::stdout().flush()?; let mut input = String::new(); io::stdin().read_line(&mut input)?; let input = input.trim().to_lowercase(); if input.is_empty() { continue; } let first_char = input.chars().next().unwrap(); let command = match first_char { '1' => Some(MidiCommand::ControlChange { cc: 20, value: 127 }), '2' => Some(MidiCommand::ControlChange { cc: 21, value: 127 }), '3' => Some(MidiCommand::ControlChange { cc: 22, value: 127 }), '4' => Some(MidiCommand::ControlChange { cc: 23, value: 127 }), '5' => Some(MidiCommand::ControlChange { cc: 24, value: 127 }), '6' => Some(MidiCommand::ControlChange { cc: 25, value: 127 }), '7' => Some(MidiCommand::ControlChange { cc: 26, value: 127 }), '8' => Some(MidiCommand::ControlChange { cc: 27, value: 127 }), '9' => Some(MidiCommand::ControlChange { cc: 28, value: 127 }), '0' => Some(MidiCommand::ControlChange { cc: 29, value: 127 }), 'u' => Some(MidiCommand::ControlChange { cc: 30, value: 127 }), 'd' => Some(MidiCommand::ControlChange { cc: 31, value: 127 }), 'e' => match get_expression_value("Expression A value (0-127): ") { Ok(value) => Some(MidiCommand::ControlChange { cc: 1, value }), Err(e) => { eprintln!("Error: {}", e); None } }, 'm' => match get_expression_value("Master volume value (0-127): ") { Ok(value) => Some(MidiCommand::ControlChange { cc: 7, value }), Err(e) => { eprintln!("Error: {}", e); None } }, 'h' => { show_help(); None } 'q' => { println!("Exiting..."); command_sender.send(MidiCommand::Quit)?; break; } _ => { println!("Unknown command '{}'. Press 'h' for help.", first_char); None } }; if let Some(cmd) = command { if let MidiCommand::ControlChange { cc, value } = &cmd { println!("Sending CC {} with value {}", cc, value); } command_sender.send(cmd)?; } } Ok(()) }