Moved files
This commit is contained in:
1
simulator/.gitignore
vendored
Normal file
1
simulator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
157
simulator/Cargo.lock
generated
Normal file
157
simulator/Cargo.lock
generated
Normal file
@@ -0,0 +1,157 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "jack"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f70ca699f44c04a32d419fc9ed699aaea89657fc09014bf3fa238e91d13041b9"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"jack-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jack-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kanal"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e3953adf0cd667798b396c2fa13552d6d9b3269d7dd1154c4c416442d1ff574"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "simulator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"jack",
|
||||
"kanal",
|
||||
"wmidi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wmidi"
|
||||
version = "4.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e55f35b40ad0178422d06e9ba845041baf2faf04627b91fde928d0f6a21c712"
|
||||
9
simulator/Cargo.toml
Normal file
9
simulator/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "simulator"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
jack = "0.13"
|
||||
kanal = "0.1"
|
||||
wmidi = "4"
|
||||
184
simulator/src/main.rs
Normal file
184
simulator/src/main.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
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(())
|
||||
}
|
||||
Reference in New Issue
Block a user