separate viewing/playing phrase + format

This commit is contained in:
🪞👃🪞 2024-10-03 21:41:14 +03:00
parent 6f91eb085d
commit ae60b792d6
4 changed files with 127 additions and 138 deletions

View file

@ -367,6 +367,18 @@ impl Widget for &str {
Ok(to.blit(&self, x, y, None)) Ok(to.blit(&self, x, y, None))
} }
} }
impl Widget for String {
type Engine = Tui;
fn layout (&self, _: [u16;2]) -> Perhaps<[u16;2]> {
// TODO: line breaks
Ok(Some([self.chars().count() as u16, 1]))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let [x, y, ..] = to.area();
//let [w, h] = self.layout(to.area().wh())?.unwrap();
Ok(to.blit(&self, x, y, None))
}
}
impl<T: Widget<Engine = Tui>> Widget for DebugOverlay<Tui, T> { impl<T: Widget<Engine = Tui>> Widget for DebugOverlay<Tui, T> {
type Engine = Tui; type Engine = Tui;

View file

@ -7,19 +7,19 @@ pub fn main () -> Usually<()> { ArrangerCli::parse().run() }
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>,
/// Pulses per quarter note (arruencer resolution; default: 96) /// Pulses per quarter note (arruencer resolution; default: 96)
#[arg(short, long)] #[arg(short, long)]
ppq: Option<usize>, ppq: Option<usize>,
/// 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 = 8)] #[arg(short = 'x', long, default_value_t = 8)]
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,
} }
impl ArrangerCli { impl ArrangerCli {
/// Run the arranger TUI from CLI arguments. /// Run the arranger TUI from CLI arguments.
@ -183,10 +183,6 @@ impl Content for SequencerProxy<Tui> {
fn content (&self) -> impl Widget<Engine = Tui> { "" } fn content (&self) -> impl Widget<Engine = Tui> { "" }
} }
impl Focusable<Tui> for SequencerProxy<Tui> { impl Focusable<Tui> for SequencerProxy<Tui> {
fn is_focused (&self) -> bool { fn is_focused (&self) -> bool { self.1 }
self.1 fn set_focused (&mut self, focus: bool) { self.1 = focus }
}
fn set_focused (&mut self, focus: bool) {
self.1 = focus
}
} }

View file

@ -36,12 +36,12 @@ impl<E: Engine> Arranger<E> {
match self.selected { match self.selected {
ArrangerFocus::Scene(s) => { ArrangerFocus::Scene(s) => {
for (track_index, track) in self.tracks.iter_mut().enumerate() { for (track_index, track) in self.tracks.iter_mut().enumerate() {
track.sequence = self.scenes[s].clips[track_index]; track.playing_phrase = self.scenes[s].clips[track_index];
track.reset = true; track.reset = true;
} }
}, },
ArrangerFocus::Clip(t, s) => { ArrangerFocus::Clip(t, s) => {
self.tracks[t].sequence = self.scenes[s].clips[t]; self.tracks[t].playing_phrase = self.scenes[s].clips[t];
self.tracks[t].reset = true; self.tracks[t].reset = true;
}, },
_ => {} _ => {}
@ -433,7 +433,7 @@ impl Scene {
.all(|(track_index, phrase_index)|match phrase_index { .all(|(track_index, phrase_index)|match phrase_index {
Some(i) => tracks Some(i) => tracks
.get(track_index) .get(track_index)
.map(|track|track.sequence == Some(*i)) .map(|track|track.playing_phrase == Some(*i))
.unwrap_or(false), .unwrap_or(false),
None => true None => true
}) })
@ -444,71 +444,80 @@ impl Scene {
/// Phrase editor. /// Phrase editor.
pub struct Sequencer<E: Engine> { pub struct Sequencer<E: Engine> {
pub name: Arc<RwLock<String>>, pub name: Arc<RwLock<String>>,
pub mode: bool, pub mode: bool,
pub focused: bool, pub focused: bool,
pub entered: bool, pub entered: bool,
pub phrase: Option<Arc<RwLock<Phrase>>>, pub phrase: Option<Arc<RwLock<Phrase>>>,
pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>, pub transport: Option<Arc<RwLock<TransportToolbar<E>>>>,
pub buffer: BigBuffer, /// The full piano roll is rendered to this buffer
pub keys: Buffer, pub buffer: BigBuffer,
/// The full piano keys is rendered to this buffer
pub keys: Buffer,
/// Highlight input keys /// Highlight input keys
pub keys_in: [bool; 128], pub keys_in: [bool; 128],
/// Highlight output keys /// Highlight output keys
pub keys_out: [bool; 128], pub keys_out: [bool; 128],
pub now: usize, /// Current point in playing phrase
pub ppq: usize, pub now: usize,
pub note_axis: FixedAxis<usize>, /// Temporal resolution (default 96)
pub time_axis: ScaledAxis<usize>, pub ppq: usize,
/// Scroll/room in pitch axis
pub note_axis: FixedAxis<usize>,
/// Scroll/room in time axis
pub time_axis: ScaledAxis<usize>,
/// Play input through output. /// Play input through output.
pub monitoring: bool, pub monitoring: bool,
/// Write input to sequence. /// Write input to sequence.
pub recording: bool, pub recording: bool,
/// Overdub input to sequence. /// Overdub input to sequence.
pub overdub: bool, pub overdub: bool,
/// Map: tick -> MIDI events at tick /// Map: tick -> MIDI events at tick
pub phrases: Vec<Arc<RwLock<Phrase>>>, pub phrases: Vec<Arc<RwLock<Phrase>>>,
/// Phrase selector /// Phrase currently being played
pub sequence: Option<usize>, pub playing_phrase: Option<usize>,
/// Phrase currently being viewed
pub viewing_phrase: Option<usize>,
/// Output from current sequence. /// Output from current sequence.
pub midi_out: Option<Port<MidiOut>>, pub midi_out: Option<Port<MidiOut>>,
/// MIDI output buffer /// MIDI output buffer
midi_out_buf: Vec<Vec<Vec<u8>>>, midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Send all notes off /// Send all notes off
pub reset: bool, // TODO?: after Some(nframes) pub reset: bool, // TODO?: after Some(nframes)
/// Highlight keys on piano roll. /// Highlight keys on piano roll.
pub notes_in: [bool;128], pub notes_in: [bool;128],
/// Highlight keys on piano roll. /// Highlight keys on piano roll.
pub notes_out: [bool;128], pub notes_out: [bool;128],
} }
impl<E: Engine> Sequencer<E> { impl<E: Engine> Sequencer<E> {
pub fn new (name: &str) -> Self { pub fn new (name: &str) -> Self {
Self { Self {
name: Arc::new(RwLock::new(name.into())), name: Arc::new(RwLock::new(name.into())),
monitoring: false, monitoring: false,
recording: false, recording: false,
overdub: true, overdub: true,
phrases: vec![], phrases: vec![],
sequence: None, viewing_phrase: None,
midi_out: None, playing_phrase: None,
midi_out_buf: vec![Vec::with_capacity(16);16384], midi_out: None,
reset: true, midi_out_buf: vec![Vec::with_capacity(16);16384],
notes_in: [false;128], reset: true,
notes_out: [false;128], notes_in: [false;128],
buffer: Default::default(), notes_out: [false;128],
keys: keys_vert(), buffer: Default::default(),
entered: false, keys: keys_vert(),
focused: false, entered: false,
mode: false, focused: false,
keys_in: [false;128], mode: false,
keys_out: [false;128], keys_in: [false;128],
phrase: None, keys_out: [false;128],
now: 0, phrase: None,
ppq: 96, now: 0,
transport: None, ppq: 96,
note_axis: FixedAxis { start: 12, point: Some(36) }, transport: None,
time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, note_axis: FixedAxis { start: 12, point: Some(36) },
time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) },
} }
} }
pub fn toggle_monitor (&mut self) { pub fn toggle_monitor (&mut self) {
@ -549,7 +558,7 @@ impl<E: Engine> Sequencer<E> {
if let ( if let (
Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase) Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase)
) = ( ) = (
playing, started, self.sequence.and_then(|id|self.phrases.get_mut(id)) playing, started, self.playing_phrase.and_then(|id|self.phrases.get_mut(id))
) { ) {
phrase.read().map(|phrase|{ phrase.read().map(|phrase|{
if self.midi_out.is_some() { if self.midi_out.is_some() {

View file

@ -180,7 +180,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> {
let name = name.read().unwrap(); let name = name.read().unwrap();
let name = format!("{clip:02} {}", name); let name = format!("{clip:02} {}", name);
add(&name.as_str().push_x(1))?; add(&name.as_str().push_x(1))?;
if (track as &Sequencer<_>).sequence == Some(*clip) { if (track as &Sequencer<_>).playing_phrase == Some(*clip) {
color = COLOR_PLAYING color = COLOR_PLAYING
} else { } else {
color = COLOR_BG1 color = COLOR_BG1
@ -473,10 +473,6 @@ impl<'a> Content for HorizontalArranger<'a, Tui> {
todo!() todo!()
}, |_: &mut TuiOutput|{ }, |_: &mut TuiOutput|{
todo!() todo!()
}),
// gain
CustomWidget::new(|_|{
todo!()
//let Self(tracks) = self; //let Self(tracks) = self;
//let mut area = to.area(); //let mut area = to.area();
//let off = Some(Style::default().dim()); //let off = Some(Style::default().dim());
@ -496,6 +492,10 @@ impl<'a> Content for HorizontalArranger<'a, Tui> {
//} //}
//area.width = 4; //area.width = 4;
//Ok(Some(area)) //Ok(Some(area))
}),
// gain
CustomWidget::new(|_|{
todo!()
}, |_: &mut TuiOutput|{ }, |_: &mut TuiOutput|{
todo!() todo!()
//let Self(tracks) = self; //let Self(tracks) = self;
@ -772,12 +772,12 @@ impl Content for Sequencer<Tui> {
let length = phrase.length; let length = phrase.length;
let looped = phrase.looped; let looped = phrase.looped;
add(&"")?; add(&"")?;
add(&col! {"Length: ", format!("{length}").as_str(),})?; add(&col!("Length: ", format!("{length}").as_str()))?;
add(&"")?; add(&"")?;
add(&col! { "Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0", })?; add(&col!("Loop [ ]", "From: ", " 1.1.1", "Length: ", " 1.0.0"))?;
add(&"")?; add(&"")?;
add(&col! { "Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]" add(&col!("Notes: ", "C#0-C#9 ", "[ /2 ]", "[ x2 ]",
, "[ Rev ]", "[ Inv ]", "[ Dup ]" })?; "[ Rev ]", "[ Inv ]", "[ Dup ]"))?;
} }
Ok(()) Ok(())
}).min_x(10); }).min_x(10);
@ -785,13 +785,12 @@ impl Content for Sequencer<Tui> {
// keys // keys
CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{
if to.area().h() < 2 { return Ok(()) } if to.area().h() < 2 { return Ok(()) }
to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{ Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{
let y = y + self.note_axis.start as u16; let y = y + self.note_axis.start as u16;
if x < self.keys.area.width && y < self.keys.area.height { if x < self.keys.area.width && y < self.keys.area.height {
*cell = self.keys.get(x, y).clone() *cell = self.keys.get(x, y).clone()
} }
}); }))
Ok(())
}).fill_y(), }).fill_y(),
// playhead // playhead
CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{ CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{
@ -817,7 +816,7 @@ impl Content for Sequencer<Tui> {
let offset = Sequencer::H_KEYS_OFFSET as u16; let offset = Sequencer::H_KEYS_OFFSET as u16;
if to.area().h() < 2 || to.area().w() < offset { return Ok(()) } if to.area().h() < 2 || to.area().w() < offset { return Ok(()) }
let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2); let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2);
to.buffer_update(area, &move |cell, x, y|{ Ok(to.buffer_update(area, &move |cell, x, y|{
cell.set_bg(Color::Rgb(20, 20, 20)); cell.set_bg(Color::Rgb(20, 20, 20));
let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize; let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize;
let src_y = (y as usize + self.note_axis.start) as usize; let src_y = (y as usize + self.note_axis.start) as usize;
@ -825,8 +824,7 @@ impl Content for Sequencer<Tui> {
let src = self.buffer.get(src_x, self.buffer.height - src_y); let src = self.buffer.get(src_x, self.buffer.height - src_y);
src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); }); src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); });
} }
}); }))
Ok(())
}).fill_x(), }).fill_x(),
// note cursor // note cursor
CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{ CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{
@ -839,14 +837,16 @@ impl Content for Sequencer<Tui> {
} }
Ok(()) Ok(())
}), }),
//zoom // zoom
CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{ CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{
let [x, y, w, h] = to.area.xywh(); let [x, y, w, h] = to.area.xywh();
let quant = ppq_to_name(self.time_axis.scale); let quant = ppq_to_name(self.time_axis.scale);
let x = x + w - 1 - quant.len() as u16; Ok(to.blit(
let y = y + h - 2; &quant,
to.blit(&quant, x, y, self.style_focus()); x + w - 1 - quant.len() as u16,
Ok(()) y + h - 2,
self.style_focus()
))
}), }),
); );
row!(toolbar, content).fill_x() row!(toolbar, content).fill_x()
@ -910,45 +910,30 @@ const NTH_OCTAVE: [&'static str;11] = [
impl Handle<Tui> for Sequencer<Tui> { impl Handle<Tui> for Sequencer<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> { fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
match from.event() { match from.event() {
// NONE, "seq_cursor_up", "move cursor up", |sequencer: &mut Sequencer| {
key!(KeyCode::Up) => {
match self.entered {
true => { self.note_axis.point_dec(); },
false => { self.note_axis.start_dec(); },
}
Ok(Some(true))
},
// NONE, "seq_cursor_down", "move cursor down", |self: &mut Sequencer| {
key!(KeyCode::Down) => {
match self.entered {
true => { self.note_axis.point_inc(); },
false => { self.note_axis.start_inc(); },
}
Ok(Some(true))
},
// NONE, "seq_cursor_left", "move cursor up", |self: &mut Sequencer| {
key!(KeyCode::Left) => {
match self.entered {
true => { self.time_axis.point_dec(); },
false => { self.time_axis.start_dec(); },
}
Ok(Some(true))
},
// NONE, "seq_cursor_right", "move cursor up", |self: &mut Sequencer| {
key!(KeyCode::Right) => {
match self.entered {
true => { self.time_axis.point_inc(); },
false => { self.time_axis.start_inc(); },
}
Ok(Some(true))
},
// NONE, "seq_mode_switch", "switch the display mode", |self: &mut Sequencer| {
key!(KeyCode::Char('`')) => { key!(KeyCode::Char('`')) => {
self.mode = !self.mode; self.mode = !self.mode;
Ok(Some(true))
}, },
_ => Ok(None) key!(KeyCode::Up) => match self.entered {
true => { self.note_axis.point_dec(); },
false => { self.note_axis.start_dec(); },
},
key!(KeyCode::Down) => match self.entered {
true => { self.note_axis.point_inc(); },
false => { self.note_axis.start_inc(); },
},
key!(KeyCode::Left) => match self.entered {
true => { self.time_axis.point_dec(); },
false => { self.time_axis.start_dec(); },
},
key!(KeyCode::Right) => match self.entered {
true => { self.time_axis.point_inc(); },
false => { self.time_axis.start_inc(); },
},
_ => {
return Ok(None)
}
} }
return Ok(Some(true))
} }
} }
@ -1013,9 +998,7 @@ impl Handle<Tui> for TransportToolbar<Tui> {
impl Content for TransportToolbar<Tui> { impl Content for TransportToolbar<Tui> {
type Engine = Tui; type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> { fn content (&self) -> impl Widget<Engine = Tui> {
row! { row!(
// play/pause
self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled( self.focus.wrap(self.focused, TransportToolbarFocus::PlayPause, &Styled(
match self.playing { match self.playing {
Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), Some(TransportState::Stopped) => Some(GRAY_DIM.bold()),
@ -1030,37 +1013,26 @@ impl Content for TransportToolbar<Tui> {
_ => unreachable!(), _ => unreachable!(),
} }
).min_xy(11, 2).push_x(1)), ).min_xy(11, 2).push_x(1)),
// bpm
self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, col! { self.focus.wrap(self.focused, TransportToolbarFocus::Bpm, &Outset::X(1u16, col! {
"BPM", "BPM", format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0)
format!("{}.{:03}", self.bpm as usize, (self.bpm * 1000.0) % 1000.0).as_str()
})), })),
// quant
self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, col! { self.focus.wrap(self.focused, TransportToolbarFocus::Quant, &Outset::X(1u16, col! {
"QUANT", ppq_to_name(self.quant as usize) "QUANT", ppq_to_name(self.quant as usize)
})), })),
// sync
self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, col! { self.focus.wrap(self.focused, TransportToolbarFocus::Sync, &Outset::X(1u16, col! {
"SYNC", ppq_to_name(self.sync as usize) "SYNC", ppq_to_name(self.sync as usize)
})), })),
// clock
self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{ self.focus.wrap(self.focused, TransportToolbarFocus::Clock, &{
let Self { frame: _frame, pulse, ppq, usecs, .. } = self; let Self { frame: _frame, pulse, ppq, usecs, .. } = self;
let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60); let (minutes, seconds) = (seconds / 60, seconds % 60);
Outset::X(1u16, col! { let time1 = format!("{bars}.{beats}.{pulses:02}");
format!("{bars}.{beats}.{pulses:02}").as_str(), let time2 = format!("{minutes}:{seconds:02}:{msecs:03}");
format!("{minutes}:{seconds:02}:{msecs:03}").as_str(), col!(time1.as_str(), time2.as_str()).outset_x(1)
})
}), }),
).fill_x().bg(Color::Rgb(40, 50, 30))
}.fill_x().bg(Color::Rgb(40, 50, 30))
} }
} }