2025-06-10 21:24:33 +02:00

185 lines
6.1 KiB
Rust

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<jack::MidiOut>,
command_receiver: Receiver<MidiCommand>,
}
impl MidiSender {
fn new(
client: &jack::Client,
command_receiver: Receiver<MidiCommand>,
) -> Result<Self, Box<dyn std::error::Error>> {
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<u8, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<MidiCommand>, Receiver<MidiCommand>) =
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(())
}