create input/output per track

This commit is contained in:
🪞👃🪞 2024-11-03 07:51:11 +02:00
parent b1ff549514
commit bc9be689a8
7 changed files with 93 additions and 84 deletions

View file

@ -32,8 +32,8 @@ impl ItemColor {
} }
pub fn random_dark () -> Self { pub fn random_dark () -> Self {
let mut rng = thread_rng(); let mut rng = thread_rng();
let lo = Okhsl::new(-180.0, 0.01, 0.05); let lo = Okhsl::new(-180.0, 0.025, 0.075);
let hi = Okhsl::new( 180.0, 0.5, 0.2); let hi = Okhsl::new( 180.0, 0.5, 0.150);
UniformOkhsl::new(lo, hi).sample(&mut rng).into() UniformOkhsl::new(lo, hi).sample(&mut rng).into()
} }
pub fn random_near (color: Self, distance: f32) -> Self { pub fn random_near (color: Self, distance: f32) -> Self {
@ -47,9 +47,10 @@ impl ItemColor {
impl From<ItemColor> for ItemColorTriplet { impl From<ItemColor> for ItemColorTriplet {
fn from (base: ItemColor) -> Self { fn from (base: ItemColor) -> Self {
let mut light = base.okhsl.clone(); let mut light = base.okhsl.clone();
light.lightness = (light.lightness * 1.15).min(Okhsl::<f32>::max_saturation()); light.lightness = (light.lightness * 1.15).min(Okhsl::<f32>::max_lightness());
let mut dark = base.okhsl.clone(); let mut dark = base.okhsl.clone();
dark.lightness = (dark.lightness * 0.85).max(Okhsl::<f32>::min_saturation()); dark.lightness = (dark.lightness * 0.85).max(Okhsl::<f32>::min_lightness());
dark.saturation = (dark.saturation * 0.85).max(Okhsl::<f32>::min_saturation());
Self { base, light: light.into(), dark: dark.into() } Self { base, light: light.into(), dark: dark.into() }
} }
} }
@ -67,11 +68,7 @@ pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
} }
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> { pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
if let Color::Rgb(r, g, b) = color { if let Color::Rgb(r, g, b) = color {
Okhsl::from_color(Srgb::new( Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
))
} else { } else {
unreachable!("only Color::Rgb is supported") unreachable!("only Color::Rgb is supported")
} }

View file

@ -153,12 +153,21 @@ impl<E: Engine> Arranger<E> {
} }
Ok(Some(true)) Ok(Some(true))
} }
pub fn next_color (&self) -> ItemColor {
if let ArrangementFocus::Clip(track, scene) = self.arrangement.selected {
let track_color = self.arrangement.tracks[track].color;
let scene_color = self.arrangement.scenes[scene].color;
track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25)
} else {
panic!("could not compute next color")
}
}
/// Focus the editor with the current phrase /// Focus the editor with the current phrase
pub fn show_phrase (&mut self) { self.editor.show(self.arrangement.phrase().as_ref()); } pub fn show_phrase (&mut self) { self.editor.show(self.arrangement.phrase().as_ref()); }
/// Focus the editor with the current phrase /// Focus the editor with the current phrase
pub fn edit_phrase (&mut self) { pub fn edit_phrase (&mut self) {
if self.arrangement.phrase().is_none() { if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() {
self.phrases.write().unwrap().append_new(None, None); self.phrases.write().unwrap().append_new(None, Some(self.next_color().into()));
self.arrangement.phrase_put(); self.arrangement.phrase_put();
} }
self.show_phrase(); self.show_phrase();
@ -415,7 +424,7 @@ impl<E: Engine> Arrangement<E> {
|name| ArrangementTrack::new( |name| ArrangementTrack::new(
&self.jack, &self.clock, name, color &self.jack, &self.clock, name, color
), ),
)); )?);
let index = self.tracks.len() - 1; let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index]) Ok(&mut self.tracks[index])
} }
@ -456,7 +465,9 @@ impl<E: Engine> Arrangement<E> {
pub fn scene_prev (&mut self) { pub fn scene_prev (&mut self) {
self.selected.scene_prev() self.selected.scene_prev()
} }
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemColor>) -> Usually<&mut Scene> { pub fn scene_add (
&mut self, name: Option<&str>, color: Option<ItemColor>
) -> Usually<&mut Scene> {
let clips = vec![None;self.tracks.len()]; let clips = vec![None;self.tracks.len()];
let name = name.map(|x|x.to_string()).unwrap_or_else(||self.scene_default_name()); let name = name.map(|x|x.to_string()).unwrap_or_else(||self.scene_default_name());
self.scenes.push(Scene::new(name, clips, color)); self.scenes.push(Scene::new(name, clips, color));
@ -488,11 +499,9 @@ impl<E: Engine> Arrangement<E> {
let scene_index = self.selected.scene(); let scene_index = self.selected.scene();
track_index track_index
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track))) .and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
.map(|(track_index, _)|{ .map(|(track_index, _)|scene_index
scene_index .and_then(|index|self.scenes.get_mut(index))
.and_then(|index|self.scenes.get_mut(index)) .map(|scene|scene.clips[track_index] = None));
.map(|scene|scene.clips[track_index] = None);
});
} }
pub fn phrase_put (&mut self) { pub fn phrase_put (&mut self) {
if let ArrangementFocus::Clip(track, scene) = self.selected { if let ArrangementFocus::Clip(track, scene) = self.selected {
@ -542,13 +551,13 @@ impl ArrangementTrack {
clock: &Arc<TransportTime>, clock: &Arc<TransportTime>,
name: &str, name: &str,
color: Option<ItemColor> color: Option<ItemColor>
) -> Self { ) -> Usually<Self> {
Self { Ok(Self {
name: Arc::new(RwLock::new(name.into())), name: Arc::new(RwLock::new(name.into())),
width: name.len() + 2, width: name.len() + 2,
color: color.unwrap_or_else(ItemColor::random), color: color.unwrap_or_else(ItemColor::random),
player: PhrasePlayer::new(&jack, clock), player: PhrasePlayer::new(&jack, clock, name)?,
} })
} }
pub fn longest_name (tracks: &[Self]) -> usize { pub fn longest_name (tracks: &[Self]) -> usize {
tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max) tracks.iter().map(|s|s.name.read().unwrap().len()).fold(0, usize::max)
@ -583,35 +592,35 @@ impl ArrangementFocus {
} }
}) })
} }
pub fn is_mix (&self) -> bool { match self { Self::Mix => true, _ => false } } pub fn is_mix (&self) -> bool { match self { Self::Mix => true, _ => false } }
pub fn is_track (&self) -> bool { match self { Self::Track(_) => true, _ => false } } pub fn is_track (&self) -> bool { match self { Self::Track(_) => true, _ => false } }
pub fn is_scene (&self) -> bool { match self { Self::Scene(_) => true, _ => false } } pub fn is_scene (&self) -> bool { match self { Self::Scene(_) => true, _ => false } }
pub fn is_clip (&self) -> bool { match self { Self::Clip(_, _) => true, _ => false } } pub fn is_clip (&self) -> bool { match self { Self::Clip(_, _) => true, _ => false } }
pub fn track (&self) -> Option<usize> { pub fn track (&self) -> Option<usize> {
match self { Self::Clip(t, _) => Some(*t), Self::Track(t) => Some(*t), _ => None } match self { Self::Clip(t, _) => Some(*t), Self::Track(t) => Some(*t), _ => None }
} }
pub fn track_next (&mut self, last_track: usize) { pub fn track_next (&mut self, last_track: usize) {
*self = match self { *self = match self {
Self::Mix => Self::Track(0), Self::Mix =>
Self::Track(t) => Self::Track(last_track.min(*t + 1)), Self::Track(0),
Self::Scene(s) => Self::Clip(0, *s), Self::Track(t) =>
Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s), Self::Track(last_track.min(*t + 1)),
Self::Scene(s) =>
Self::Clip(0, *s),
Self::Clip(t, s) =>
Self::Clip(last_track.min(*t + 1), *s),
} }
} }
pub fn track_prev (&mut self) { pub fn track_prev (&mut self) {
*self = match self { *self = match self {
Self::Mix => Self::Mix, Self::Mix =>
Self::Scene(s) => Self::Scene(*s), Self::Mix,
Self::Track(t) => if *t == 0 { Self::Scene(s) =>
Self::Mix Self::Scene(*s),
} else { Self::Track(t) =>
Self::Track(*t - 1) if *t == 0 { Self::Mix } else { Self::Track(*t - 1) },
}, Self::Clip(t, s) =>
Self::Clip(t, s) => if *t == 0 { if *t == 0 { Self::Scene(*s) } else { Self::Clip(t.saturating_sub(1), *s) }
Self::Scene(*s)
} else {
Self::Clip(t.saturating_sub(1), *s)
}
} }
} }
pub fn scene (&self) -> Option<usize> { pub fn scene (&self) -> Option<usize> {
@ -619,26 +628,26 @@ impl ArrangementFocus {
} }
pub fn scene_next (&mut self, last_scene: usize) { pub fn scene_next (&mut self, last_scene: usize) {
*self = match self { *self = match self {
Self::Mix => Self::Scene(0), Self::Mix =>
Self::Track(t) => Self::Clip(*t, 0), Self::Scene(0),
Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)), Self::Track(t) =>
Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)), Self::Clip(*t, 0),
Self::Scene(s) =>
Self::Scene(last_scene.min(*s + 1)),
Self::Clip(t, s) =>
Self::Clip(*t, last_scene.min(*s + 1)),
} }
} }
pub fn scene_prev (&mut self) { pub fn scene_prev (&mut self) {
*self = match self { *self = match self {
Self::Mix => Self::Mix, Self::Mix =>
Self::Track(t) => Self::Track(*t), Self::Mix,
Self::Scene(s) => if *s == 0 { Self::Track(t) =>
Self::Mix Self::Track(*t),
} else { Self::Scene(s) =>
Self::Scene(*s - 1) if *s == 0 { Self::Mix } else { Self::Scene(*s - 1) },
}, Self::Clip(t, s) =>
Self::Clip(t, s) => if *s == 0 { if *s == 0 { Self::Track(*t) } else { Self::Clip(*t, s.saturating_sub(1)) }
Self::Track(*t)
} else {
Self::Clip(*t, s.saturating_sub(1))
}
} }
} }
} }

View file

@ -4,16 +4,16 @@ impl Handle<Tui> for Arranger<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
if !self.handle_focused(from)?.unwrap_or(false) { if !self.handle_focused(from)?.unwrap_or(false) {
match from.event() { match from.event() {
key!(KeyCode::Tab) => { self.focus_next(); }, key!(KeyCode::Tab) => { self.focus_next(); },
key!(Shift-KeyCode::Tab) => { self.focus_prev(); }, key!(Shift-KeyCode::Tab) => { self.focus_prev(); },
key!(KeyCode::BackTab) => { self.focus_prev(); }, key!(KeyCode::BackTab) => { self.focus_prev(); },
key!(Shift-KeyCode::BackTab) => { self.focus_prev(); }, key!(Shift-KeyCode::BackTab) => { self.focus_prev(); },
key!(KeyCode::Up) => { self.focus_up(); }, key!(KeyCode::Up) => { self.focus_up(); },
key!(KeyCode::Down) => { self.focus_down(); }, key!(KeyCode::Down) => { self.focus_down(); },
key!(KeyCode::Left) => { self.focus_left(); }, key!(KeyCode::Left) => { self.focus_left(); },
key!(KeyCode::Right) => { self.focus_right(); }, key!(KeyCode::Right) => { self.focus_right(); },
key!(KeyCode::Char('e')) => { self.edit_phrase(); }, key!(KeyCode::Char('e')) => { self.edit_phrase(); },
key!(KeyCode::Char(' ')) => { self.toggle_play()?; }, key!(KeyCode::Char(' ')) => { self.toggle_play()?; },
key!(KeyCode::Char('n')) => { self.rename_selected(); }, key!(KeyCode::Char('n')) => { self.rename_selected(); },
_ => return Ok(None) _ => return Ok(None)
} }

View file

@ -139,7 +139,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
//let border_bg = Color::Rgb(40, 50, 30); //let border_bg = Color::Rgb(40, 50, 30);
//let border_fg = if self.0.focused { border_hi } else { border_lo }; //let border_fg = if self.0.focused { border_hi } else { border_lo };
//let border = Lozenge(Style::default().bg(border_bg).fg(border_fg)); //let border = Lozenge(Style::default().bg(border_bg).fg(border_fg));
let track_title_h = 5u16; let track_title_h = 3u16;//5u16;
let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track let scene_title_w = 3 + Scene::longest_name(scenes) as u16; // x of 1st track
let clock = &self.0.clock; let clock = &self.0.clock;
let arrangement = Layers::new(move |add|{ let arrangement = Layers::new(move |add|{
@ -210,7 +210,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
.map(|port|port.short_name()) .map(|port|port.short_name())
.transpose()? .transpose()?
.unwrap_or("(none)".into())); .unwrap_or("(none)".into()));
col!(name, input, output, until_next, elapsed) col!(name, /*input, output,*/ until_next, elapsed)
.min_xy(w as u16, track_title_h) .min_xy(w as u16, track_title_h)
.bg(track.color.rgb) .bg(track.color.rgb)
.push_x(scene_title_w) .push_x(scene_title_w)
@ -230,14 +230,11 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let max_w = name.len().min((w as usize).saturating_sub(2)); let max_w = name.len().min((w as usize).saturating_sub(2));
let color = phrase.read().unwrap().color; let color = phrase.read().unwrap().color;
add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?; add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?;
bg = if let Some((_, Some(ref playing))) = track.player.phrase { bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.phrase {
if *playing.read().unwrap() == *phrase.read().unwrap() { if *playing.read().unwrap() == *phrase.read().unwrap() {
color.light.rgb bg = color.light.rgb
} else {
color.dark.rgb
} }
} else {
color.dark.rgb
}; };
}, },
_ => {} _ => {}

View file

@ -393,9 +393,11 @@ impl Eq for Phrase {}
impl PhrasePlayer { impl PhrasePlayer {
pub fn new ( pub fn new (
jack: &Arc<RwLock<JackClient>>, jack: &Arc<RwLock<JackClient>>,
clock: &Arc<TransportTime> clock: &Arc<TransportTime>,
) -> Self { name: &str
Self { ) -> Usually<Self> {
let jack = jack.read().unwrap();
Ok(Self {
clock: clock.clone(), clock: clock.clone(),
phrase: None, phrase: None,
next_phrase: None, next_phrase: None,
@ -404,11 +406,15 @@ impl PhrasePlayer {
monitoring: false, monitoring: false,
recording: false, recording: false,
overdub: true, overdub: true,
midi_inputs: vec![], midi_inputs: vec![
midi_outputs: vec![], jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
],
midi_outputs: vec![
jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
],
midi_out_buf: vec![Vec::with_capacity(16);16384], midi_out_buf: vec![Vec::with_capacity(16);16384],
reset: true, reset: true,
} })
} }
pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; } pub fn toggle_monitor (&mut self) { self.monitoring = !self.monitoring; }
pub fn toggle_record (&mut self) { self.recording = !self.recording; } pub fn toggle_record (&mut self) { self.recording = !self.recording; }

View file

@ -26,7 +26,7 @@ impl SequencerCli {
phrases: Arc::new(RwLock::new(PhrasePool::new())), phrases: Arc::new(RwLock::new(PhrasePool::new())),
editor: PhraseEditor::new(), editor: PhraseEditor::new(),
clock: transport.clock.clone(), clock: transport.clock.clone(),
player: PhrasePlayer::new(jack, &transport.clock), player: PhrasePlayer::new(jack, &transport.clock, "tek_sequencer")?,
transport: self.transport.then_some(Arc::new(RwLock::new(transport))), transport: self.transport.then_some(Arc::new(RwLock::new(transport))),
}; };
if let Some(_) = self.name.as_ref() { if let Some(_) = self.name.as_ref() {

View file

@ -92,8 +92,8 @@ impl Handle<Tui> for PhraseEditor<Tui> {
key!(KeyCode::Esc) => { self.entered = false; }, key!(KeyCode::Esc) => { self.entered = false; },
key!(KeyCode::Char('[')) => if self.entered { self.note_length_dec() }, key!(KeyCode::Char('[')) => if self.entered { self.note_length_dec() },
key!(KeyCode::Char(']')) => if self.entered { self.note_length_inc() }, key!(KeyCode::Char(']')) => if self.entered { self.note_length_inc() },
key!(KeyCode::Char('a')) => if self.entered { self.put(); self.time_cursor_advance() }, key!(KeyCode::Char('a')) => if self.entered { self.put(); self.time_cursor_advance(); },
key!(KeyCode::Char('s')) => if self.entered { self.put() }, key!(KeyCode::Char('s')) => if self.entered { self.put(); },
key!(KeyCode::Char('-')) => self.time_zoom_out(), key!(KeyCode::Char('-')) => self.time_zoom_out(),
key!(KeyCode::Char('_')) => self.time_zoom_out(), key!(KeyCode::Char('_')) => self.time_zoom_out(),
key!(KeyCode::Char('=')) => self.time_zoom_in(), key!(KeyCode::Char('=')) => self.time_zoom_in(),