mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
separate viewing/playing phrase + format
This commit is contained in:
parent
6f91eb085d
commit
ae60b792d6
4 changed files with 127 additions and 138 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue