Initial release: Orca VM, TUI, MIDI/OSC/UDP output, and release pipeline
Release / build (aarch64, aarch64, aarch64-unknown-linux-gnu) (push) Successful in 5m23s
Release / build (x86_64, ubuntu-latest, x86_64-unknown-linux-gnu) (push) Successful in 5m32s
Release / update-aur (push) Successful in 12s

This commit is contained in:
2026-07-07 17:40:00 +02:00
commit bf388576a3
19 changed files with 3009 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
use std::process::exit;
use orca_rs::io::{EventOutputs, MidiOut, OscOut, UdpOut};
use orca_rs::tui::{self, TuiConfig};
const USAGE: &str = "\
Usage: orca-rs [options] [file.orca]
Open the Orca live-coding environment. If the file does not exist it will
be created on first save; with no file, edits save to untitled.orca.
Options:
--bpm <n> Tempo (default: 120)
--seed <n> Random seed for the R operator (default: 1)
--udp-port <n> Send ';' operator strings over UDP to this port
--udp-host <h> UDP destination host (default: 127.0.0.1)
--osc-port <n> Send '=' operator messages over OSC to this port
--osc-host <h> OSC destination host (default: 127.0.0.1)
--no-midi Disable MIDI output (on by default: creates a virtual
ALSA port named 'orca-rs', visible in PipeWire)
--midi-name <s> Connect to the MIDI output port whose name contains <s>
instead of creating a virtual port
--list-midi List available MIDI output ports and exit
-h, --help Print this message and exit
";
fn die(msg: &str) -> ! {
eprintln!("{msg}");
exit(1);
}
fn parse_num(args: &mut impl Iterator<Item = String>, flag: &str) -> usize {
args.next()
.and_then(|v| v.parse().ok())
.unwrap_or_else(|| die(&format!("{flag} requires a number")))
}
fn main() {
let mut path: Option<String> = None;
let mut bpm: usize = 120;
let mut seed: usize = 1;
let mut udp_host = "127.0.0.1".to_string();
let mut udp_port: Option<u16> = None;
let mut osc_host = "127.0.0.1".to_string();
let mut osc_port: Option<u16> = None;
let mut midi = true;
let mut midi_name: Option<String> = None;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--bpm" => bpm = parse_num(&mut args, "--bpm").clamp(10, 999),
"--seed" => seed = parse_num(&mut args, "--seed"),
"--udp-port" => udp_port = Some(parse_num(&mut args, "--udp-port") as u16),
"--udp-host" => udp_host = args.next().unwrap_or_else(|| die("--udp-host requires a value")),
"--osc-port" => osc_port = Some(parse_num(&mut args, "--osc-port") as u16),
"--osc-host" => osc_host = args.next().unwrap_or_else(|| die("--osc-host requires a value")),
"--midi" => midi = true,
"--no-midi" => midi = false,
"--midi-name" => {
midi = true;
midi_name = Some(args.next().unwrap_or_else(|| die("--midi-name requires a value")));
}
"--list-midi" => {
let ports = MidiOut::list_ports();
if ports.is_empty() {
println!("no MIDI output ports found");
} else {
for p in ports {
println!("{p}");
}
}
return;
}
"-h" | "--help" => {
print!("{USAGE}");
return;
}
_ if path.is_none() && !arg.starts_with('-') => path = Some(arg),
_ => die(USAGE),
}
}
let mut outputs = EventOutputs::default();
if let Some(port) = udp_port {
outputs.udp =
Some(UdpOut::new(&udp_host, port).unwrap_or_else(|e| die(&format!("UDP setup failed: {e}"))));
}
if let Some(port) = osc_port {
outputs.osc =
Some(OscOut::new(&osc_host, port).unwrap_or_else(|e| die(&format!("OSC setup failed: {e}"))));
}
if midi {
outputs.midi = Some(MidiOut::open(midi_name.as_deref()).unwrap_or_else(|e| {
die(&format!("MIDI setup failed: {e} (use --no-midi to run without MIDI)"))
}));
}
if let Err(e) = tui::run(TuiConfig { path, bpm, seed, outputs }) {
die(&e);
}
}