use clap::{Parser, Subcommand};
use inquire::Select;
use osynic_midi::{
core::MappingMode,
create_config_interactive,
discovery::{display_configs, display_devices},
interactive::{select_config, select_device, select_mode},
select_local_config, start_mapping,
};
#[derive(Parser)]
#[command(name = "osynic-midi")]
#[command(about = "MIDI to Keyboard mapper for Osynic", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(short, long)]
config: Option<String>,
#[arg(short, long)]
mode: Option<String>,
}
#[derive(Subcommand)]
enum Commands {
ListDevices,
ListConfigs,
Start {
#[arg(short, long)]
config: Option<String>,
#[arg(short, long)]
mode: Option<String>,
},
#[command(
about = "Create a new MIDI mapping configuration file",
long_about = "Interactively create a new MIDI mapping configuration file.\n\n\
You can choose between two modes:\n \
• Notes Mode: Map individual MIDI note numbers to keyboard keys\n \
• Octaves Mode: Map by octaves and pitch within each octave\n\n\
The config file will be saved to the configs/ directory."
)]
Create,
#[command(
name = "load-local",
about = "Load a configuration file from a local path",
long_about = "Load a configuration file from anywhere on your file system.\n\n\
This allows you to use MIDI mapping configs stored outside the default configs/ directory.\n\n\
Usage:\n \
• Interactive: osynic-midi load-local (will prompt for file path)\n \
• With path: osynic-midi load-local -c /path/to/config.json\n \
• With mode: osynic-midi load-local -c config.json -m notes"
)]
LoadLocal {
#[arg(short, long)]
config: Option<String>,
#[arg(short, long)]
mode: Option<String>,
},
}
#[tokio::main]
async fn main() {
let args = Cli::parse();
let result = match args.command {
Some(Commands::ListDevices) => display_devices(),
Some(Commands::ListConfigs) => display_configs(),
Some(Commands::Start { config, mode }) => start_cli_mapping(config, mode).await,
Some(Commands::Create) => {
match create_config_interactive().await {
Ok(_config_filename) => {
Ok(())
}
Err(e) => Err(e),
}
}
Some(Commands::LoadLocal { config, mode }) => load_local_mapping(config, mode).await,
None => {
show_main_menu().await
}
};
if let Err(e) = result {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
async fn start_cli_mapping(
config_path: Option<String>,
mode_arg: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let config_path = match config_path {
Some(path) => path,
None => select_config()?,
};
let mode = match mode_arg {
Some(m) => match m.to_lowercase().as_str() {
"octaves" => MappingMode::Octaves,
"notes" => MappingMode::Notes,
_ => select_mode()?,
},
None => {
match osynic_midi::Config::load(&config_path) {
Ok(mut config) => config
.mapping_mode
.take()
.unwrap_or_else(|| select_mode().unwrap_or(MappingMode::Notes)),
Err(_) => select_mode()?,
}
}
};
let device_name = select_device()?;
start_mapping(config_path, device_name, mode).await
}
async fn load_local_mapping(
config_path: Option<String>,
mode_arg: Option<String>,
) -> Result<(), Box<dyn std::error::Error>> {
let config_path = match config_path {
Some(path) => path,
None => select_local_config()?,
};
let mode = match mode_arg {
Some(m) => match m.to_lowercase().as_str() {
"octaves" => MappingMode::Octaves,
"notes" => MappingMode::Notes,
_ => select_mode()?,
},
None => {
match osynic_midi::Config::load(&config_path) {
Ok(mut config) => config
.mapping_mode
.take()
.unwrap_or_else(|| select_mode().unwrap_or(MappingMode::Notes)),
Err(_) => select_mode()?,
}
}
};
let device_name = select_device()?;
start_mapping(config_path, device_name, mode).await
}
async fn show_main_menu() -> Result<(), Box<dyn std::error::Error>> {
println!("\n🎵 MIDI to Keyboard Mapper - Configuration Menu\n");
let options = vec![
"Use existing configuration (from configs/ folder)",
"Create a new configuration file",
"Load configuration from a custom location",
];
let selected = Select::new("Choose configuration method:", options).prompt()?;
match selected {
"Use existing configuration (from configs/ folder)" => {
start_cli_mapping(None, None).await
}
"Create a new configuration file" => {
match create_config_interactive().await {
Ok(config_filename) => {
let use_now = Select::new(
"Do you want to use this configuration now?",
vec!["Yes", "No"],
)
.prompt()?;
if use_now == "Yes" {
println!("\n🚀 Starting MIDI mapping...");
start_cli_mapping(Some(config_filename), None).await
} else {
println!("\n👍 Configuration saved. You can use it anytime with:");
println!(" osynic-midi start -c {}", config_filename);
Ok(())
}
}
Err(e) => Err(e),
}
}
"Load configuration from a custom location" => {
load_local_mapping(None, None).await
}
_ => Err("Invalid selection".into()),
}
}