implement midi autoconnect for arranger

This commit is contained in:
🪞👃🪞 2024-12-25 00:25:48 +01:00
parent f1847b62b8
commit 512e466af1
3 changed files with 107 additions and 13 deletions

View file

@ -31,9 +31,31 @@ transport-release:
arranger: arranger:
reset reset
cargo run --bin tek_arranger cargo run --bin tek_arranger
arranger-ext:
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 _" \
-o "2=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" \
-i "3=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
-o "3=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" \
-i "4=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
-o "4=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1"
arranger-release: arranger-release:
reset reset
cargo run --release --bin tek_arranger cargo run --release --bin tek_arranger
arranger-release-ext:
reset
cargo run --release --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 _" \
-o "2=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" \
-i "3=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
-o "3=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" \
-i "4=Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" \
-o "4=Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1"
groovebox: groovebox:
reset reset

View file

@ -4,22 +4,33 @@ pub fn main () -> Usually<()> {
ArrangerCli::parse().run() ArrangerCli::parse().run()
} }
/// Parses CLI arguments to the `tek_arranger` invocation. /// Launches an interactive MIDI arranger.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
pub struct ArrangerCli { pub struct ArrangerCli {
/// Name of JACK client /// Name of JACK client
#[arg(short, long)] #[arg(short, long)]
name: Option<String>, name: Option<String>,
/// Whether to include a transport toolbar (default: true) /// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)] #[arg(short, long, default_value_t = true)]
transport: bool, transport: bool,
/// Number of tracks /// Number of tracks
#[arg(short = 'x', long, default_value_t = 4)] #[arg(short = 'x', long, default_value_t = 4)]
tracks: usize, tracks: usize,
/// Number of scenes /// Number of scenes
#[arg(short, long, default_value_t = 8)] #[arg(short, long, default_value_t = 8)]
scenes: usize, scenes: usize,
/// MIDI outs to connect each track to.
#[arg(short='i', long)]
midi_from: Vec<String>,
/// MIDI ins to connect each track to.
#[arg(short='o', long)]
midi_to: Vec<String>,
} }
impl ArrangerCli { impl ArrangerCli {
@ -31,8 +42,9 @@ impl ArrangerCli {
} }
Tui::run(JackClient::new(client_name.as_str())?.activate_with(|jack|{ Tui::run(JackClient::new(client_name.as_str())?.activate_with(|jack|{
let mut app = ArrangerTui::try_from(jack)?; let mut app = ArrangerTui::try_from(jack)?;
let jack = jack.read().unwrap();
app.color = ItemPalette::random(); app.color = ItemPalette::random();
add_tracks(&mut app, self.tracks)?; add_tracks(&jack, &mut app, &self)?;
add_scenes(&mut app, self.scenes)?; add_scenes(&mut app, self.scenes)?;
Ok(app) Ok(app)
})?)?; })?)?;
@ -40,7 +52,8 @@ impl ArrangerCli {
} }
} }
fn add_tracks (app: &mut ArrangerTui, n: usize) -> Usually<()> { fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> Usually<()> {
let n = cli.tracks;
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..n { for i in 0..n {
@ -48,6 +61,60 @@ fn add_tracks (app: &mut ArrangerTui, n: usize) -> Usually<()> {
track_color_1.mix(track_color_2, i as f32 / n as f32).into() track_color_1.mix(track_color_2, i as f32 / n as f32).into()
))?; ))?;
track.width = 8; track.width = 8;
let name = track.name.read().unwrap();
track.player.midi_ins.push(
jack.register_port(&format!("{}I", &name), MidiIn::default())?
);
track.player.midi_outs.push(
jack.register_port(&format!("{}O", &name), MidiOut::default())?
);
}
for connection in cli.midi_from.iter() {
let mut split = connection.split("=");
let number = split.next().unwrap().trim();
if let Ok(track) = number.parse::<usize>() {
if track < 1 {
panic!("Tracks are zero-indexed")
}
if track > n {
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
}
if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(&port) {
jack.client().connect_ports(&port, &app.tracks[track-1].player.midi_ins[0])?;
//jack.client().connect_ports(&port, &app.tracks[track].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 cli.midi_to.iter() {
let mut split = connection.split("=");
let number = split.next().unwrap().trim();
if let Ok(track) = number.parse::<usize>() {
if track < 1 {
panic!("Tracks are zero-indexed")
}
if track > n {
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
}
if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(&port) {
jack.client().connect_ports(&app.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(()) Ok(())
} }

View file

@ -4,24 +4,29 @@ pub fn main () -> Usually<()> {
SequencerCli::parse().run() SequencerCli::parse().run()
} }
/// Launches a single interactive MIDI sequencer.
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
pub struct SequencerCli { pub struct SequencerCli {
/// Name of JACK client /// Name of JACK client
#[arg(short, long)] #[arg(short, long)]
name: Option<String>, name: Option<String>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool,
/// MIDI outs to connect to (multiple instances accepted) /// MIDI outs to connect to (multiple instances accepted)
#[arg(short='i', long)] #[arg(short='i', long)]
midi_from: Vec<String>, midi_from: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted) /// MIDI ins to connect to (multiple instances accepted)
#[arg(short='o', long)] #[arg(short='o', long)]
midi_to: Vec<String>, midi_to: Vec<String>,
/// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar) /// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar)
#[arg(short, long)] #[arg(short, long)]
length: Option<usize>, length: Option<usize>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool
} }
impl SequencerCli { impl SequencerCli {
@ -30,8 +35,8 @@ impl SequencerCli {
Tui::run(JackClient::new(name)?.activate_with(|jack|{ Tui::run(JackClient::new(name)?.activate_with(|jack|{
let mut app = SequencerTui::try_from(jack)?; let mut app = SequencerTui::try_from(jack)?;
let jack = jack.read().unwrap(); let jack = jack.read().unwrap();
let midi_in = jack.register_port("in", MidiIn::default())?; let midi_in = jack.register_port("i", MidiIn::default())?;
let midi_out = jack.register_port("out", MidiOut::default())?; let midi_out = jack.register_port("o", MidiOut::default())?;
connect_from(&jack, &midi_in, &self.midi_from)?; connect_from(&jack, &midi_in, &self.midi_from)?;
connect_to(&jack, &midi_out, &self.midi_to)?; connect_to(&jack, &midi_out, &self.midi_to)?;
app.player.midi_ins.push(midi_in); app.player.midi_ins.push(midi_in);
@ -52,7 +57,7 @@ fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> U
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(&port) {
jack.client().connect_ports(&port, &input)?; jack.client().connect_ports(&port, &input)?;
} else { } else {
panic!("Missing MIDI output: {port}"); panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
} }
} }
Ok(()) Ok(())
@ -63,7 +68,7 @@ fn connect_to (jack: &JackClient, output: &Port<MidiOut>, ports: &[String]) -> U
if let Some(port) = jack.port_by_name(&port) { if let Some(port) = jack.port_by_name(&port) {
jack.client().connect_ports(&output, &port)?; jack.client().connect_ports(&output, &port)?;
} else { } else {
panic!("Missing MIDI output: {port}"); panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
} }
} }
Ok(()) Ok(())