diff --git a/src/todo_cli_mixer.rs b/.old/todo_cli_mixer.rs similarity index 100% rename from src/todo_cli_mixer.rs rename to .old/todo_cli_mixer.rs diff --git a/src/todo_cli_plugin.rs b/.old/todo_cli_plugin.rs similarity index 100% rename from src/todo_cli_plugin.rs rename to .old/todo_cli_plugin.rs diff --git a/src/todo_cli_sampler.rs b/.old/todo_cli_sampler.rs similarity index 100% rename from src/todo_cli_sampler.rs rename to .old/todo_cli_sampler.rs diff --git a/Cargo.toml b/Cargo.toml index be28a0e0..2c4a04a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,7 @@ version = "0.2.0" [dependencies] tek = { path = "./tek" } clap = { version = "4.5.4", features = [ "derive" ] } + +[[bin]] +name = "tek" +path = "./tek.rs" diff --git a/Justfile b/Justfile index eab61b0c..77701a20 100644 --- a/Justfile +++ b/Justfile @@ -6,34 +6,31 @@ status: cloc --by-file src/ git status +upstreams := "codeberg origin" # TODO! + amend: git commit --amend push: - git push -u codeberg main - git push -u origin main + git push -u codeberg main && git push -u origin main tpush: - git push --tags -u codeberg - git push --tags -u origin + git push --tags -u codeberg && git push --tags -u origin fpush: - git push -fu codeberg main - git push -fu origin main + git push -fu codeberg main && git push -fu origin main ftpush: - git push --tags -fu codeberg - git push --tags -fu origin + git push --tags -fu codeberg && git push --tags -fu origin + +run: + reset && cargo run clock: - reset - cargo run --bin tek -- clock + reset && cargo run -- clock clock-release: - reset - cargo run --release -- clock + reset && cargo run --release -- clock arranger: - reset - cargo run --bin tek -- arranger + reset && cargo run --bin tek -- arranger arranger-ext: - reset - cargo run --bin tek -- arranger -n tek \ + reset && cargo run --bin tek -- arranger -n tek \ -i "1=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \ -o "1=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" \ -i "2=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \ @@ -59,22 +56,22 @@ arranger-release-ext: groovebox: reset - cargo run -- groovebox -b 174 + cargo run -- -n tek -b 174 groovebox groovebox-ext: reset - cargo run -- groovebox -n tek \ - -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + cargo run -- -n tek -b 174 groovebox \ + -i "Midi-Bridge:nanoKEY Studio.*capture.*" \ -l "Komplete Audio 6 Pro:capture_AUX1" \ -r "Komplete Audio 6 Pro:capture_AUX1" \ -L "Komplete Audio 6 Pro:playback_AUX1" \ -R "Komplete Audio 6 Pro:playback_AUX1" groovebox-release: reset - cargo run --release -- groovebox + cargo run --release -- -n tek -b 174 groovebox groovebox-release-ext: reset - cargo run --release -- groovebox -n tek \ - -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + cargo run --release -- -n tek -b 174 groovebox \ + -i "Midi-Bridge:nanoKEY Studio.*capture.*" \ -l "Komplete Audio 6 Pro:capture_AUX1" \ -r "Komplete Audio 6 Pro:capture_AUX1" \ -L "Komplete Audio 6 Pro:playback_AUX1" \ diff --git a/README.md b/README.md index 08eeb3ed..f108a437 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,13 @@ or [**matrix** `@unspeaker:matrix.org`](https://matrix.to/#/@unspeaker:matrix.or ![Screenshot](https://codeberg.org/unspeaker/tek/releases/download/0.2.0-rc.7/Screenshot%20From%202025-01-02%2023-18-05.png) +## usage + ``` Usage: tek [OPTIONS] Commands: - clock A standalone transport view + clock A standalone transport clock sequencer A MIDI sequencer sampler A MIDI-controlled audio sampler groovebox Sequencer and sampler together @@ -30,13 +32,21 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -t, --name Name of JACK client - -S, --sync-lead Whether to attempt to become transport master - -s, --sync-follow Whether to sync to external transport master - -b, --bpm Initial tempo in beats per minute - -h, --help Print help - -V, --version Print version - + -n, --name Name of JACK client + -S, --sync-lead Whether to attempt to become transport master + -s, --sync-follow Whether to sync to external transport master + -b, --bpm Initial tempo in beats per minute + -t, --show-clock Whether to include a transport toolbar (default: true) + -I, --midi-from MIDI outs to connect to (multiple instances accepted) + -i, --midi-from-re MIDI outs to connect to (multiple instances accepted) + -O, --midi-to MIDI ins to connect to (multiple instances accepted) + -o, --midi-to-re MIDI ins to connect to (multiple instances accepted) + -l, --left-from Audio outs to connect to left input + -r, --right-from Audio outs to connect to right input + -L, --left-to Audio ins to connect from left output + -R, --right-to Audio ins to connect from right output + -h, --help Print help + -V, --version Print version ``` the project roadmap is at https://codeberg.org/unspeaker/tek/milestones diff --git a/jack/src/jack_port.rs b/jack/src/jack_port.rs index d93bb208..502731ce 100644 --- a/jack/src/jack_port.rs +++ b/jack/src/jack_port.rs @@ -62,6 +62,15 @@ pub struct PortConnection { pub status: Vec<(Port, PortConnectionStatus)>, } impl PortConnection { + pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) + -> Vec + { + let mut connections = vec![]; + for port in exact.iter() { connections.push(Self::exact(port)) } + for port in re.iter() { connections.push(Self::regexp(port)) } + for port in re_all.iter() { connections.push(Self::regexp_all(port)) } + connections + } /// Connect to this exact port pub fn exact (name: impl AsRef) -> Self { let name = PortConnectionName::Exact(name.as_ref().into()); diff --git a/src/main.rs b/tek.rs similarity index 81% rename from src/main.rs rename to tek.rs index 7187a859..be256efc 100644 --- a/src/main.rs +++ b/tek.rs @@ -8,7 +8,7 @@ pub struct TekCli { /// Which app to initialize #[command(subcommand)] mode: TekMode, /// Name of JACK client - #[arg(short='t', long)] name: Option, + #[arg(short='n', long)] name: Option, /// Whether to attempt to become transport master #[arg(short='S', long, default_value_t = false)] sync_lead: bool, /// Whether to sync to external transport master @@ -26,13 +26,13 @@ pub struct TekCli { /// MIDI ins to connect to (multiple instances accepted) #[arg(short='o', long)] midi_to_re: Vec, /// Audio outs to connect to left input - #[arg(short='l', long)] l_from: Vec, + #[arg(short='l', long)] left_from: Vec, /// Audio outs to connect to right input - #[arg(short='r', long)] r_from: Vec, + #[arg(short='r', long)] right_from: Vec, /// Audio ins to connect from left output - #[arg(short='L', long)] l_to: Vec, + #[arg(short='L', long)] left_to: Vec, /// Audio ins to connect from right output - #[arg(short='R', long)] r_to: Vec, + #[arg(short='R', long)] right_to: Vec, } #[derive(Debug, Clone, Subcommand)] @@ -48,11 +48,11 @@ pub enum TekMode { /// Multi-track MIDI sequencer. Arranger { /// Number of tracks - #[arg(short = 'x', long, default_value_t = 16)] tracks: usize, + #[arg(short = 'x', long, default_value_t = 16)] tracks: usize, /// Width of tracks - #[arg(short = 'w', long, default_value_t = 6)] track_width: usize, + #[arg(short = 'w', long, default_value_t = 6)] track_width: usize, /// Number of scenes - #[arg(short = 'y', long, default_value_t = 8)] scenes: usize, + #[arg(short = 'y', long, default_value_t = 8)] scenes: usize, }, /// TODO: A MIDI-controlled audio mixer Mixer, @@ -64,16 +64,17 @@ pub enum TekMode { /// Application entrypoint. pub fn main () -> Usually<()> { - let cli = TekCli::parse(); - let name = cli.name.as_ref().map_or("tek", |x|x.as_str()); - let jack = JackConnection::new(name)?; - let engine = Tui::new()?; - let mut midi_froms = vec![]; - let mut midi_tos = vec![]; - for port in cli.midi_from.iter() { midi_froms.push(PortConnection::exact(port.into())) } - for port in cli.midi_from_re.iter() { midi_froms.push(PortConnection::regexp(port.into())) } - for port in cli.midi_to.iter() { midi_tos.push(PortConnection::exact(port.into())) } - for port in cli.midi_to_re.iter() { midi_tos.push(PortConnection::regexp(port.into())) } + let cli = TekCli::parse(); + let name = cli.name.as_ref().map_or("tek", |x|x.as_str()); + let jack = JackConnection::new(name)?; + let engine = Tui::new()?; + let empty = &[] as &[&str]; + let midi_froms = PortConnection::collect(&cli.midi_from, &cli.midi_from_re, empty); + let midi_tos = PortConnection::collect(&cli.midi_to, &cli.midi_to_re, empty); + let left_froms = PortConnection::collect(&cli.left_from, empty, empty); + let left_tos = PortConnection::collect(&cli.left_to, empty, empty); + let right_froms = PortConnection::collect(&cli.right_from, empty, empty); + let right_tos = PortConnection::collect(&cli.right_to, empty, empty); Ok(match cli.mode { TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { @@ -112,7 +113,8 @@ pub fn main () -> Usually<()> { note_lo: 36.into(), note_pt: 36.into(), color: ItemPalette::from(Color::Rgb(64, 128, 32)), - state: Sampler::new(jack, &"sampler", &midi_froms, &[&l_from, &r_from], &[&l_to, &r_to])?, + state: Sampler::new(jack, &"sampler", &midi_froms, + &[&left_froms, &right_froms], &[&left_tos, &right_tos])?, } ))?)?, @@ -121,7 +123,8 @@ pub fn main () -> Usually<()> { let color = Some(ItemColor::random().into()); let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, length, None, color))); let mut player = MidiPlayer::new(jack, &"sequencer", Some(&clip), &midi_froms, &midi_tos)?; - let sampler = Sampler::new(jack, &"sampler", &midi_froms, &[&l_from, &r_from], &[&l_to, &r_to])?; + let sampler = Sampler::new(jack, &"sampler", &midi_froms, + &[&left_froms, &right_froms], &[&left_tos, &right_tos])?; jack.read().unwrap().client().connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?; let app = Groovebox { player, diff --git a/tek/src/arranger/arranger_track.rs b/tek/src/arranger/arranger_track.rs index e96c32ed..a24f8c78 100644 --- a/tek/src/arranger/arranger_track.rs +++ b/tek/src/arranger/arranger_track.rs @@ -27,67 +27,68 @@ impl Arranger { &mut self, count: usize, width: usize, - midi_from: &[impl AsRef], - midi_to: &[impl AsRef], + midi_from: &[PortConnection], + midi_to: &[PortConnection], ) -> Usually<()> { let jack = self.jack.clone(); let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); let mut track = self.track_add(None, Some(color))?; - track.width = width; - let port = JackPort::::new(&jack, &format!("{}I", &track.name), &[])?; + track.width = width; + let port = JackPort::::new(&jack, &format!("{}I", &track.name), midi_from)?; track.player.midi_ins.push(port); - let port = JackPort::::new(&jack, &format!("{}O", &track.name), &[])?; + let port = JackPort::::new(&jack, &format!("{}O", &track.name), midi_to)?; track.player.midi_outs.push(port); } - for connection in midi_from.iter() { - let mut split = connection.as_ref().split("="); - let number = split.next().unwrap().trim(); - if let Ok(track) = number.parse::() { - if track < 1 { - panic!("Tracks start from 1") - } - if track > count { - panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") - } - if let Some(port) = split.next() { - if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { - //jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } else { - panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") - } - } else { - panic!("Failed to parse track number: {number}") - } - } - for connection in midi_to.iter() { - let mut split = connection.as_ref().split("="); - let number = split.next().unwrap().trim(); - if let Ok(track) = number.parse::() { - if track < 1 { - panic!("Tracks start from 1") - } - if track > count { - panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") - } - if let Some(port) = split.next() { - if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { - //jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } else { - panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") - } - } else { - panic!("Failed to parse track number: {number}") - } - } + // TODO: port per track: + //for connection in midi_from.iter() { + //let mut split = connection.as_ref().split("="); + //let number = split.next().unwrap().trim(); + //if let Ok(track) = number.parse::() { + //if track < 1 { + //panic!("Tracks start from 1") + //} + //if track > count { + //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") + //} + //if let Some(port) = split.next() { + //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { + ////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?; + //} else { + //panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + //} + //} else { + //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") + //} + //} else { + //panic!("Failed to parse track number: {number}") + //} + //} + //for connection in midi_to.iter() { + //let mut split = connection.as_ref().split("="); + //let number = split.next().unwrap().trim(); + //if let Ok(track) = number.parse::() { + //if track < 1 { + //panic!("Tracks start from 1") + //} + //if track > count { + //panic!("Tried to connect track {track} or {count}. Pass -t {track} to increase number of tracks.") + //} + //if let Some(port) = split.next() { + //if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() { + ////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?; + //} else { + //panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + //} + //} else { + //panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME") + //} + //} else { + //panic!("Failed to parse track number: {number}") + //} + //} Ok(()) } }