mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
finish applying port autoconnect refactor, move entry point to top level, update usage
This commit is contained in:
parent
fe70b57dc1
commit
c3de403645
9 changed files with 126 additions and 102 deletions
|
|
@ -6,3 +6,7 @@ version = "0.2.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek = { path = "./tek" }
|
tek = { path = "./tek" }
|
||||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek"
|
||||||
|
path = "./tek.rs"
|
||||||
|
|
|
||||||
41
Justfile
41
Justfile
|
|
@ -6,34 +6,31 @@ status:
|
||||||
cloc --by-file src/
|
cloc --by-file src/
|
||||||
git status
|
git status
|
||||||
|
|
||||||
|
upstreams := "codeberg origin" # TODO!
|
||||||
|
|
||||||
amend:
|
amend:
|
||||||
git commit --amend
|
git commit --amend
|
||||||
push:
|
push:
|
||||||
git push -u codeberg main
|
git push -u codeberg main && git push -u origin main
|
||||||
git push -u origin main
|
|
||||||
tpush:
|
tpush:
|
||||||
git push --tags -u codeberg
|
git push --tags -u codeberg && git push --tags -u origin
|
||||||
git push --tags -u origin
|
|
||||||
fpush:
|
fpush:
|
||||||
git push -fu codeberg main
|
git push -fu codeberg main && git push -fu origin main
|
||||||
git push -fu origin main
|
|
||||||
ftpush:
|
ftpush:
|
||||||
git push --tags -fu codeberg
|
git push --tags -fu codeberg && git push --tags -fu origin
|
||||||
git push --tags -fu origin
|
|
||||||
|
run:
|
||||||
|
reset && cargo run
|
||||||
|
|
||||||
clock:
|
clock:
|
||||||
reset
|
reset && cargo run -- clock
|
||||||
cargo run --bin tek -- clock
|
|
||||||
clock-release:
|
clock-release:
|
||||||
reset
|
reset && cargo run --release -- clock
|
||||||
cargo run --release -- clock
|
|
||||||
|
|
||||||
arranger:
|
arranger:
|
||||||
reset
|
reset && cargo run --bin tek -- arranger
|
||||||
cargo run --bin tek -- arranger
|
|
||||||
arranger-ext:
|
arranger-ext:
|
||||||
reset
|
reset && cargo run --bin tek -- arranger -n tek \
|
||||||
cargo run --bin tek -- arranger -n tek \
|
|
||||||
-i "1=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
|
-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" \
|
-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 _" \
|
-i "2=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
|
||||||
|
|
@ -59,22 +56,22 @@ arranger-release-ext:
|
||||||
|
|
||||||
groovebox:
|
groovebox:
|
||||||
reset
|
reset
|
||||||
cargo run -- groovebox -b 174
|
cargo run -- -n tek -b 174 groovebox
|
||||||
groovebox-ext:
|
groovebox-ext:
|
||||||
reset
|
reset
|
||||||
cargo run -- groovebox -n tek \
|
cargo run -- -n tek -b 174 groovebox \
|
||||||
-i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \
|
-i "Midi-Bridge:nanoKEY Studio.*capture.*" \
|
||||||
-l "Komplete Audio 6 Pro:capture_AUX1" \
|
-l "Komplete Audio 6 Pro:capture_AUX1" \
|
||||||
-r "Komplete Audio 6 Pro:capture_AUX1" \
|
-r "Komplete Audio 6 Pro:capture_AUX1" \
|
||||||
-L "Komplete Audio 6 Pro:playback_AUX1" \
|
-L "Komplete Audio 6 Pro:playback_AUX1" \
|
||||||
-R "Komplete Audio 6 Pro:playback_AUX1"
|
-R "Komplete Audio 6 Pro:playback_AUX1"
|
||||||
groovebox-release:
|
groovebox-release:
|
||||||
reset
|
reset
|
||||||
cargo run --release -- groovebox
|
cargo run --release -- -n tek -b 174 groovebox
|
||||||
groovebox-release-ext:
|
groovebox-release-ext:
|
||||||
reset
|
reset
|
||||||
cargo run --release -- groovebox -n tek \
|
cargo run --release -- -n tek -b 174 groovebox \
|
||||||
-i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \
|
-i "Midi-Bridge:nanoKEY Studio.*capture.*" \
|
||||||
-l "Komplete Audio 6 Pro:capture_AUX1" \
|
-l "Komplete Audio 6 Pro:capture_AUX1" \
|
||||||
-r "Komplete Audio 6 Pro:capture_AUX1" \
|
-r "Komplete Audio 6 Pro:capture_AUX1" \
|
||||||
-L "Komplete Audio 6 Pro:playback_AUX1" \
|
-L "Komplete Audio 6 Pro:playback_AUX1" \
|
||||||
|
|
|
||||||
26
README.md
26
README.md
|
|
@ -15,11 +15,13 @@ or [**matrix** `@unspeaker:matrix.org`](https://matrix.to/#/@unspeaker:matrix.or
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: tek [OPTIONS] <COMMAND>
|
Usage: tek [OPTIONS] <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
clock A standalone transport view
|
clock A standalone transport clock
|
||||||
sequencer A MIDI sequencer
|
sequencer A MIDI sequencer
|
||||||
sampler A MIDI-controlled audio sampler
|
sampler A MIDI-controlled audio sampler
|
||||||
groovebox Sequencer and sampler together
|
groovebox Sequencer and sampler together
|
||||||
|
|
@ -30,13 +32,21 @@ Commands:
|
||||||
help Print this message or the help of the given subcommand(s)
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-t, --name <NAME> Name of JACK client
|
-n, --name <NAME> Name of JACK client
|
||||||
-S, --sync-lead Whether to attempt to become transport master
|
-S, --sync-lead Whether to attempt to become transport master
|
||||||
-s, --sync-follow Whether to sync to external transport master
|
-s, --sync-follow Whether to sync to external transport master
|
||||||
-b, --bpm <BPM> Initial tempo in beats per minute
|
-b, --bpm <BPM> Initial tempo in beats per minute
|
||||||
-h, --help Print help
|
-t, --show-clock Whether to include a transport toolbar (default: true)
|
||||||
-V, --version Print version
|
-I, --midi-from <MIDI_FROM> MIDI outs to connect to (multiple instances accepted)
|
||||||
|
-i, --midi-from-re <MIDI_FROM_RE> MIDI outs to connect to (multiple instances accepted)
|
||||||
|
-O, --midi-to <MIDI_TO> MIDI ins to connect to (multiple instances accepted)
|
||||||
|
-o, --midi-to-re <MIDI_TO_RE> MIDI ins to connect to (multiple instances accepted)
|
||||||
|
-l, --left-from <LEFT_FROM> Audio outs to connect to left input
|
||||||
|
-r, --right-from <RIGHT_FROM> Audio outs to connect to right input
|
||||||
|
-L, --left-to <LEFT_TO> Audio ins to connect from left output
|
||||||
|
-R, --right-to <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
|
the project roadmap is at https://codeberg.org/unspeaker/tek/milestones
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,15 @@ pub struct PortConnection {
|
||||||
pub status: Vec<(Port<Unowned>, PortConnectionStatus)>,
|
pub status: Vec<(Port<Unowned>, PortConnectionStatus)>,
|
||||||
}
|
}
|
||||||
impl PortConnection {
|
impl PortConnection {
|
||||||
|
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
||||||
|
-> Vec<Self>
|
||||||
|
{
|
||||||
|
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
|
/// Connect to this exact port
|
||||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
pub fn exact (name: impl AsRef<str>) -> Self {
|
||||||
let name = PortConnectionName::Exact(name.as_ref().into());
|
let name = PortConnectionName::Exact(name.as_ref().into());
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pub struct TekCli {
|
||||||
/// Which app to initialize
|
/// Which app to initialize
|
||||||
#[command(subcommand)] mode: TekMode,
|
#[command(subcommand)] mode: TekMode,
|
||||||
/// Name of JACK client
|
/// Name of JACK client
|
||||||
#[arg(short='t', long)] name: Option<String>,
|
#[arg(short='n', long)] name: Option<String>,
|
||||||
/// Whether to attempt to become transport master
|
/// Whether to attempt to become transport master
|
||||||
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
||||||
/// Whether to sync to external transport master
|
/// Whether to sync to external transport master
|
||||||
|
|
@ -26,13 +26,13 @@ pub struct TekCli {
|
||||||
/// MIDI ins to connect to (multiple instances accepted)
|
/// MIDI ins to connect to (multiple instances accepted)
|
||||||
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
||||||
/// Audio outs to connect to left input
|
/// Audio outs to connect to left input
|
||||||
#[arg(short='l', long)] l_from: Vec<String>,
|
#[arg(short='l', long)] left_from: Vec<String>,
|
||||||
/// Audio outs to connect to right input
|
/// Audio outs to connect to right input
|
||||||
#[arg(short='r', long)] r_from: Vec<String>,
|
#[arg(short='r', long)] right_from: Vec<String>,
|
||||||
/// Audio ins to connect from left output
|
/// Audio ins to connect from left output
|
||||||
#[arg(short='L', long)] l_to: Vec<String>,
|
#[arg(short='L', long)] left_to: Vec<String>,
|
||||||
/// Audio ins to connect from right output
|
/// Audio ins to connect from right output
|
||||||
#[arg(short='R', long)] r_to: Vec<String>,
|
#[arg(short='R', long)] right_to: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Subcommand)]
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
|
|
@ -48,11 +48,11 @@ pub enum TekMode {
|
||||||
/// Multi-track MIDI sequencer.
|
/// Multi-track MIDI sequencer.
|
||||||
Arranger {
|
Arranger {
|
||||||
/// Number of tracks
|
/// 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
|
/// 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
|
/// 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
|
/// TODO: A MIDI-controlled audio mixer
|
||||||
Mixer,
|
Mixer,
|
||||||
|
|
@ -64,16 +64,17 @@ pub enum TekMode {
|
||||||
|
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
let cli = TekCli::parse();
|
let cli = TekCli::parse();
|
||||||
let name = cli.name.as_ref().map_or("tek", |x|x.as_str());
|
let name = cli.name.as_ref().map_or("tek", |x|x.as_str());
|
||||||
let jack = JackConnection::new(name)?;
|
let jack = JackConnection::new(name)?;
|
||||||
let engine = Tui::new()?;
|
let engine = Tui::new()?;
|
||||||
let mut midi_froms = vec![];
|
let empty = &[] as &[&str];
|
||||||
let mut midi_tos = vec![];
|
let midi_froms = PortConnection::collect(&cli.midi_from, &cli.midi_from_re, empty);
|
||||||
for port in cli.midi_from.iter() { midi_froms.push(PortConnection::exact(port.into())) }
|
let midi_tos = PortConnection::collect(&cli.midi_to, &cli.midi_to_re, empty);
|
||||||
for port in cli.midi_from_re.iter() { midi_froms.push(PortConnection::regexp(port.into())) }
|
let left_froms = PortConnection::collect(&cli.left_from, empty, empty);
|
||||||
for port in cli.midi_to.iter() { midi_tos.push(PortConnection::exact(port.into())) }
|
let left_tos = PortConnection::collect(&cli.left_to, empty, empty);
|
||||||
for port in cli.midi_to_re.iter() { midi_tos.push(PortConnection::regexp(port.into())) }
|
let right_froms = PortConnection::collect(&cli.right_from, empty, empty);
|
||||||
|
let right_tos = PortConnection::collect(&cli.right_to, empty, empty);
|
||||||
Ok(match cli.mode {
|
Ok(match cli.mode {
|
||||||
|
|
||||||
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui {
|
||||||
|
|
@ -112,7 +113,8 @@ pub fn main () -> Usually<()> {
|
||||||
note_lo: 36.into(),
|
note_lo: 36.into(),
|
||||||
note_pt: 36.into(),
|
note_pt: 36.into(),
|
||||||
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
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 color = Some(ItemColor::random().into());
|
||||||
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, length, None, color)));
|
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 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)?;
|
jack.read().unwrap().client().connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?;
|
||||||
let app = Groovebox {
|
let app = Groovebox {
|
||||||
player,
|
player,
|
||||||
|
|
@ -27,67 +27,68 @@ impl Arranger {
|
||||||
&mut self,
|
&mut self,
|
||||||
count: usize,
|
count: usize,
|
||||||
width: usize,
|
width: usize,
|
||||||
midi_from: &[impl AsRef<str>],
|
midi_from: &[PortConnection],
|
||||||
midi_to: &[impl AsRef<str>],
|
midi_to: &[PortConnection],
|
||||||
) -> Usually<()> {
|
) -> Usually<()> {
|
||||||
let jack = self.jack.clone();
|
let jack = self.jack.clone();
|
||||||
let track_color_1 = ItemColor::random();
|
let track_color_1 = ItemColor::random();
|
||||||
let track_color_2 = ItemColor::random();
|
let track_color_2 = ItemColor::random();
|
||||||
for i in 0..count {
|
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))?;
|
let mut track = self.track_add(None, Some(color))?;
|
||||||
track.width = width;
|
track.width = width;
|
||||||
let port = JackPort::<MidiIn>::new(&jack, &format!("{}I", &track.name), &[])?;
|
let port = JackPort::<MidiIn>::new(&jack, &format!("{}I", &track.name), midi_from)?;
|
||||||
track.player.midi_ins.push(port);
|
track.player.midi_ins.push(port);
|
||||||
let port = JackPort::<MidiOut>::new(&jack, &format!("{}O", &track.name), &[])?;
|
let port = JackPort::<MidiOut>::new(&jack, &format!("{}O", &track.name), midi_to)?;
|
||||||
track.player.midi_outs.push(port);
|
track.player.midi_outs.push(port);
|
||||||
}
|
}
|
||||||
for connection in midi_from.iter() {
|
// TODO: port per track:
|
||||||
let mut split = connection.as_ref().split("=");
|
//for connection in midi_from.iter() {
|
||||||
let number = split.next().unwrap().trim();
|
//let mut split = connection.as_ref().split("=");
|
||||||
if let Ok(track) = number.parse::<usize>() {
|
//let number = split.next().unwrap().trim();
|
||||||
if track < 1 {
|
//if let Ok(track) = number.parse::<usize>() {
|
||||||
panic!("Tracks start from 1")
|
//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 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() {
|
//if let Some(port) = split.next() {
|
||||||
//jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?;
|
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
||||||
} else {
|
////jack.read().unwrap().client().connect_ports(port, &self.tracks[track-1].player.midi_ins[0])?;
|
||||||
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
|
//} 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!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||||
} else {
|
//}
|
||||||
panic!("Failed to parse track number: {number}")
|
//} else {
|
||||||
}
|
//panic!("Failed to parse track number: {number}")
|
||||||
}
|
//}
|
||||||
for connection in midi_to.iter() {
|
//}
|
||||||
let mut split = connection.as_ref().split("=");
|
//for connection in midi_to.iter() {
|
||||||
let number = split.next().unwrap().trim();
|
//let mut split = connection.as_ref().split("=");
|
||||||
if let Ok(track) = number.parse::<usize>() {
|
//let number = split.next().unwrap().trim();
|
||||||
if track < 1 {
|
//if let Ok(track) = number.parse::<usize>() {
|
||||||
panic!("Tracks start from 1")
|
//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 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() {
|
//if let Some(port) = split.next() {
|
||||||
//jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?;
|
//if let Some(port) = jack.read().unwrap().client().port_by_name(port).as_ref() {
|
||||||
} else {
|
////jack.read().unwrap().client().connect_ports(&self.tracks[track-1].player.midi_outs[0], port)?;
|
||||||
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
|
//} 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!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
|
||||||
} else {
|
//}
|
||||||
panic!("Failed to parse track number: {number}")
|
//} else {
|
||||||
}
|
//panic!("Failed to parse track number: {number}")
|
||||||
}
|
//}
|
||||||
|
//}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue