mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
add top-level transport UI to launcher
This commit is contained in:
parent
a4a2f645b1
commit
4c9eed6fce
3 changed files with 126 additions and 65 deletions
|
|
@ -1,13 +1,18 @@
|
|||
use crate::prelude::*;
|
||||
pub struct Launcher {
|
||||
name: String,
|
||||
timebase: Arc<Timebase>,
|
||||
cursor: (usize, usize),
|
||||
tracks: Vec<DynamicDevice<Sequencer>>,
|
||||
chains: Vec<DynamicDevice<Chain>>,
|
||||
scenes: Vec<Scene>,
|
||||
show_help: bool,
|
||||
view: LauncherView,
|
||||
name: String,
|
||||
timebase: Arc<Timebase>,
|
||||
transport: Transport,
|
||||
playing: TransportState,
|
||||
monitoring: bool,
|
||||
recording: bool,
|
||||
overdub: bool,
|
||||
cursor: (usize, usize),
|
||||
tracks: Vec<DynamicDevice<Sequencer>>,
|
||||
chains: Vec<DynamicDevice<Chain>>,
|
||||
scenes: Vec<Scene>,
|
||||
show_help: bool,
|
||||
view: LauncherView,
|
||||
}
|
||||
pub enum LauncherView {
|
||||
Tracks,
|
||||
|
|
@ -27,16 +32,24 @@ impl Scene {
|
|||
}
|
||||
}
|
||||
impl Launcher {
|
||||
pub fn new (
|
||||
name: &str,
|
||||
timebase: &Arc<Timebase>,
|
||||
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||
name: name.into(),
|
||||
view: LauncherView::Tracks,
|
||||
timebase: timebase.clone(),
|
||||
cursor: (1, 2),
|
||||
scenes: vec![
|
||||
pub fn new (name: &str,) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
let transport = client.transport();
|
||||
let timebase = Arc::new(Timebase {
|
||||
rate: AtomicUsize::new(client.sample_rate()),
|
||||
tempo: AtomicUsize::new(113000),
|
||||
ppq: AtomicUsize::new(96),
|
||||
});
|
||||
DynamicDevice::new(render, handle, process, Self {
|
||||
name: name.into(),
|
||||
view: LauncherView::Tracks,
|
||||
playing: transport.query_state()?,
|
||||
monitoring: true,
|
||||
recording: false,
|
||||
overdub: true,
|
||||
transport,
|
||||
cursor: (1, 2),
|
||||
scenes: vec![
|
||||
Scene::new(&"Scene#01", &[Some(0), None, None, None]),
|
||||
Scene::new(&"Scene#02", &[None, None, None, None]),
|
||||
Scene::new(&"Scene#03", &[None, None, None, None]),
|
||||
|
|
@ -46,13 +59,13 @@ impl Launcher {
|
|||
Scene::new(&"Scene#07", &[None, None, None, None]),
|
||||
Scene::new(&"Scene#08", &[None, None, None, None]),
|
||||
],
|
||||
tracks: vec![
|
||||
Sequencer::new("Drum", timebase)?,
|
||||
Sequencer::new("Bass", timebase)?,
|
||||
Sequencer::new("Pads", timebase)?,
|
||||
Sequencer::new("Lead", timebase)?,
|
||||
tracks: vec![
|
||||
Sequencer::new("Drum", &timebase)?,
|
||||
Sequencer::new("Bass", &timebase)?,
|
||||
Sequencer::new("Pads", &timebase)?,
|
||||
Sequencer::new("Lead", &timebase)?,
|
||||
],
|
||||
chains: vec![
|
||||
chains: vec![
|
||||
Chain::new("Chain#0000", vec![
|
||||
Box::new(Plugin::new("Plugin#000")?),
|
||||
])?,
|
||||
|
|
@ -66,8 +79,9 @@ impl Launcher {
|
|||
Box::new(Plugin::new("Plugin#003")?),
|
||||
])?,
|
||||
],
|
||||
timebase,
|
||||
show_help: true
|
||||
}))
|
||||
}).activate(client)
|
||||
}
|
||||
fn cols (&self) -> usize {
|
||||
(self.tracks.len() + 1) as usize
|
||||
|
|
@ -111,26 +125,32 @@ impl Launcher {
|
|||
}
|
||||
}
|
||||
impl DevicePorts for Launcher {}
|
||||
pub fn process (_: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
||||
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
||||
state.playing = state.transport.query_state().unwrap();
|
||||
Control::Continue
|
||||
}
|
||||
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let Rect { x, y, width, height } = area;
|
||||
let track_area = Rect { x: x, y: y, width, height: 22 };
|
||||
let seq_area = Rect { x: x, y: y+21, width, height: 20 };
|
||||
let chain_area = Rect { x: x, y: y+40, width, height: 22 };
|
||||
crate::device::sequencer::draw_timer(buf, x + width - 1, y, 0, 0, 0, 0);
|
||||
crate::device::sequencer::draw_play_stop(buf, x + 1, y, &state.playing);
|
||||
crate::device::sequencer::draw_rec(buf, x + 12, y, state.recording);
|
||||
crate::device::sequencer::draw_mon(buf, x + 19, y, state.monitoring);
|
||||
crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub);
|
||||
let track_area = Rect { x: x, y: y+1, width, height: 22 };
|
||||
let seq_area = Rect { x: x, y: y+22, width, height: 20 };
|
||||
let chain_area = Rect { x: x, y: y+41, width, height: 21 };
|
||||
let separator = format!("├{}┤", "-".repeat((width - 2).into()));
|
||||
let scenes = draw_scenes(state, buf, x, y);
|
||||
separator.blit(buf, x, y + 2, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 4, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 21, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 40, Some(Style::default().dim()));
|
||||
let (w, mut highlight) = draw_tracks(state, buf, x, y);
|
||||
let scenes = draw_scenes(state, buf, x, y + 1);
|
||||
separator.blit(buf, x, y + 3, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 5, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 22, Some(Style::default().dim()));
|
||||
separator.blit(buf, x, y + 41, Some(Style::default().dim()));
|
||||
let (w, mut highlight) = draw_tracks(state, buf, track_area.x, track_area.y);
|
||||
if state.col() == 0 {
|
||||
highlight = Some(scenes);
|
||||
}
|
||||
draw_crossings(state, buf, x + w - 2, y);
|
||||
draw_box(buf, area);
|
||||
draw_crossings(state, buf, x + w - 2, y + 1);
|
||||
draw_box(buf, Rect { x, y: y + 1, width, height: height - 1 });
|
||||
let style = Some(Style::default().green().dim());
|
||||
crate::device::chain::draw_as_row(
|
||||
&*state.chains[0].state(), buf, chain_area, style
|
||||
|
|
@ -138,7 +158,7 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect>
|
|||
match state.view {
|
||||
LauncherView::Tracks => draw_box_styled(buf, track_area, style),
|
||||
LauncherView::Sequencer => draw_box_styled(buf, seq_area, style),
|
||||
LauncherView::Chains => draw_box_styled(buf, Rect { height: 19, ..chain_area }, style),
|
||||
LauncherView::Chains => draw_box_styled(buf, Rect { height: 18, ..chain_area }, style),
|
||||
};
|
||||
draw_highlight(state, buf, &highlight);
|
||||
draw_sequencer(state, buf, seq_area.x, seq_area.y + 1, seq_area.width, seq_area.height - 2)?;
|
||||
|
|
@ -305,10 +325,15 @@ pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
|
|||
})
|
||||
}
|
||||
pub const KEYMAP: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
|
||||
[Char('r'), NONE, "rename", "rename current element", rename],
|
||||
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
|
||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Char('n'), NONE, "rename", "rename current element", rename],
|
||||
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
|
||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||
[Char(' '), NONE, "play_toggle", "play or pause", play_toggle],
|
||||
[Char('r'), NONE, "record_toggle", "toggle recording", record_toggle],
|
||||
[Char('d'), NONE, "overdub_toggle", "toggle overdub", overdub_toggle],
|
||||
[Char('m'), NONE, "monitor_toggle", "toggle input monitoring", monitor_toggle],
|
||||
//[Char(' '), SHIFT, "play_start", "play from start", play_start],
|
||||
});
|
||||
pub const KEYMAP_TRACKS: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
|
||||
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||
|
|
@ -389,3 +414,32 @@ fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
|||
}
|
||||
Ok(true)
|
||||
}
|
||||
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||
s.playing = match s.playing {
|
||||
TransportState::Stopped => {
|
||||
s.transport.start()?;
|
||||
TransportState::Starting
|
||||
},
|
||||
_ => {
|
||||
s.transport.stop()?;
|
||||
s.transport.locate(0)?;
|
||||
TransportState::Stopped
|
||||
},
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
fn play_start (s: &mut Launcher) -> Usually<bool> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn record_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||
s.recording = !s.recording;
|
||||
Ok(true)
|
||||
}
|
||||
fn overdub_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||
s.overdub = !s.overdub;
|
||||
Ok(true)
|
||||
}
|
||||
fn monitor_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||
s.monitoring = !s.monitoring;
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ impl Sequencer {
|
|||
}).activate(client)
|
||||
}
|
||||
|
||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||
|
||||
// Update time
|
||||
let mut sequence = &mut self.sequences[self.sequence].notes;
|
||||
|
|
@ -267,24 +267,15 @@ fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
|||
}))
|
||||
}
|
||||
|
||||
fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> {
|
||||
pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usually<Rect> {
|
||||
let Rect { x, y, width, .. } = area;
|
||||
let rep = beat / s.steps;
|
||||
let step = beat % s.steps;
|
||||
let reps = s.steps / s.resolution;
|
||||
let steps = s.steps % s.resolution;
|
||||
let Rect { x, y, width, .. } = area;
|
||||
draw_timer(buf, x + width - 2, y + 1, rep, step, reps, steps);
|
||||
let style = Style::default().gray();
|
||||
let timer = format!("{rep}.{step:02} / {reps}.{steps}");
|
||||
timer.blit(buf, x + width - 2 - timer.len() as u16, y + 1, Some(style.bold().not_dim()));
|
||||
match s.playing {
|
||||
TransportState::Rolling => "▶ PLAYING",
|
||||
TransportState::Starting => "READY ...",
|
||||
TransportState::Stopped => "⏹ STOPPED",
|
||||
}.blit(buf, x + 2, y + 1, Some(match s.playing {
|
||||
TransportState::Stopped => style.dim().bold(),
|
||||
TransportState::Starting => style.not_dim().bold(),
|
||||
TransportState::Rolling => style.not_dim().white().bold()
|
||||
}));
|
||||
draw_play_stop(buf, x + 2, y + 1, &s.playing);
|
||||
let separator = format!("├{}┤", "-".repeat((area.width - 2).into()));
|
||||
separator.blit(buf, x, y + 2, Some(style.dim()));
|
||||
draw_rec(buf, x + 13, y + 1, s.recording);
|
||||
|
|
@ -293,23 +284,41 @@ fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) -> Usu
|
|||
let clips = draw_clips(s, buf, area)?;
|
||||
Ok(Rect { x, y, width: area.width, height: 3 })
|
||||
}
|
||||
|
||||
fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
pub fn draw_timer (
|
||||
buf: &mut Buffer, x: u16, y: u16, rep: usize, step: usize, reps: usize, steps: usize
|
||||
) {
|
||||
let style = Style::default().gray();
|
||||
let timer = format!("{rep}.{step:02} / {reps}.{steps}");
|
||||
timer.blit(buf, x - timer.len() as u16, y, Some(style.bold().not_dim()));
|
||||
}
|
||||
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
|
||||
let style = Style::default().gray();
|
||||
match state {
|
||||
TransportState::Rolling => "▶ PLAYING",
|
||||
TransportState::Starting => "READY ...",
|
||||
TransportState::Stopped => "⏹ STOPPED",
|
||||
}.blit(buf, x, y, Some(match state {
|
||||
TransportState::Stopped => style.dim().bold(),
|
||||
TransportState::Starting => style.not_dim().bold(),
|
||||
TransportState::Rolling => style.not_dim().white().bold()
|
||||
}));
|
||||
}
|
||||
pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
"⏺ REC".blit(buf, x, y, Some(if on {
|
||||
Style::default().bold().red()
|
||||
} else {
|
||||
Style::default().bold().dim()
|
||||
}))
|
||||
}
|
||||
fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
"⏺ DUB".blit(buf, x + 20, y + 1, Some(if on {
|
||||
pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
"⏺ DUB".blit(buf, x, y, Some(if on {
|
||||
Style::default().bold().yellow()
|
||||
} else {
|
||||
Style::default().bold().dim()
|
||||
}))
|
||||
}
|
||||
fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
"⏺ MON".blit(buf, x + 27, y + 1, Some(if on {
|
||||
pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||
"⏺ MON".blit(buf, x, y, Some(if on {
|
||||
Style::default().bold().green()
|
||||
} else {
|
||||
Style::default().bold().dim()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,5 @@ fn main () -> Result<(), Box<dyn Error>> {
|
|||
let _cli = cli::Cli::parse();
|
||||
let xdg = microxdg::XdgApp::new("dawdle")?;
|
||||
crate::config::create_dirs(&xdg)?;
|
||||
let transport = crate::device::Transport::new("Transport")?;
|
||||
let timebase = transport.state.lock().unwrap().timebase();
|
||||
run(Launcher::new("Launcher#0", &timebase)?)
|
||||
run(Launcher::new("Launcher#0")?)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue