mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 20:56:43 +01:00
feat: draw bpm and bbt
This commit is contained in:
parent
c06b9d16e2
commit
b1df7bf4e6
3 changed files with 100 additions and 90 deletions
|
|
@ -7,6 +7,7 @@ pub struct Launcher {
|
||||||
monitoring: bool,
|
monitoring: bool,
|
||||||
recording: bool,
|
recording: bool,
|
||||||
overdub: bool,
|
overdub: bool,
|
||||||
|
position: usize,
|
||||||
cursor: (usize, usize),
|
cursor: (usize, usize),
|
||||||
tracks: Vec<DynamicDevice<Sequencer>>,
|
tracks: Vec<DynamicDevice<Sequencer>>,
|
||||||
chains: Vec<DynamicDevice<Chain>>,
|
chains: Vec<DynamicDevice<Chain>>,
|
||||||
|
|
@ -14,6 +15,7 @@ pub struct Launcher {
|
||||||
show_help: bool,
|
show_help: bool,
|
||||||
view: LauncherView,
|
view: LauncherView,
|
||||||
}
|
}
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum LauncherView {
|
pub enum LauncherView {
|
||||||
Tracks,
|
Tracks,
|
||||||
Sequencer,
|
Sequencer,
|
||||||
|
|
@ -49,6 +51,7 @@ impl Launcher {
|
||||||
overdub: true,
|
overdub: true,
|
||||||
transport,
|
transport,
|
||||||
cursor: (1, 2),
|
cursor: (1, 2),
|
||||||
|
position: 0,
|
||||||
scenes: vec![
|
scenes: vec![
|
||||||
Scene::new(&"Scene#01", &[Some(0), None, None, None]),
|
Scene::new(&"Scene#01", &[Some(0), None, None, None]),
|
||||||
Scene::new(&"Scene#02", &[None, None, None, None]),
|
Scene::new(&"Scene#02", &[None, None, None, None]),
|
||||||
|
|
@ -126,16 +129,19 @@ impl Launcher {
|
||||||
}
|
}
|
||||||
impl DevicePorts for Launcher {}
|
impl DevicePorts for Launcher {}
|
||||||
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
||||||
state.playing = state.transport.query_state().unwrap();
|
let transport = state.transport.query().unwrap();
|
||||||
|
state.playing = transport.state;
|
||||||
|
state.position = transport.pos.frame() as usize;
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
let Rect { x, y, width, height } = area;
|
let Rect { x, y, width, height } = area;
|
||||||
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_play_stop(buf, x + 1, y, &state.playing);
|
||||||
crate::device::sequencer::draw_rec(buf, x + 12, y, state.recording);
|
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_mon(buf, x + 19, y, state.monitoring);
|
||||||
crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub);
|
crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub);
|
||||||
|
draw_bpm(buf, x + 33, y, state.timebase.tempo());
|
||||||
|
draw_timer(buf, x + width - 1, y, &state.timebase, state.position);
|
||||||
let track_area = Rect { x: x, y: y+1, width, height: 22 };
|
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 seq_area = Rect { x: x, y: y+22, width, height: 20 };
|
||||||
let chain_area = Rect { x: x, y: y+41, width, height: 21 };
|
let chain_area = Rect { x: x, y: y+41, width, height: 21 };
|
||||||
|
|
@ -154,20 +160,28 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect>
|
||||||
let style = Some(Style::default().green().dim());
|
let style = Some(Style::default().green().dim());
|
||||||
let chain = &*state.chains[0].state();
|
let chain = &*state.chains[0].state();
|
||||||
let (_, plugins) = crate::device::chain::draw_as_row(chain, buf, chain_area, style)?;
|
let (_, plugins) = crate::device::chain::draw_as_row(chain, buf, chain_area, style)?;
|
||||||
match state.view {
|
|
||||||
LauncherView::Tracks => draw_box_styled(buf, track_area, style),
|
if state.view == LauncherView::Tracks {
|
||||||
LauncherView::Sequencer => draw_box_styled(buf, seq_area, style),
|
draw_box_styled(buf, track_area, style);
|
||||||
LauncherView::Chains => draw_box_styled(buf, Rect { height: 18, ..chain_area }, style),
|
}
|
||||||
};
|
|
||||||
draw_highlight(state, buf, &track_highlight, match state.view {
|
draw_highlight(state, buf, &track_highlight, match state.view {
|
||||||
LauncherView::Tracks => Style::default().green().not_dim(),
|
LauncherView::Tracks => Style::default().green().not_dim(),
|
||||||
_ => Style::default().green().dim()
|
_ => Style::default().green().dim()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if state.view == LauncherView::Chains {
|
||||||
|
draw_box_styled(buf, Rect { height: 18, ..chain_area }, style);
|
||||||
|
}
|
||||||
draw_highlight(state, buf, &Some(plugins[chain.focus]), match state.view {
|
draw_highlight(state, buf, &Some(plugins[chain.focus]), match state.view {
|
||||||
LauncherView::Chains => Style::default().green().not_dim(),
|
LauncherView::Chains => Style::default().green().not_dim(),
|
||||||
_ => Style::default().green().dim()
|
_ => Style::default().green().dim()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if state.view == LauncherView::Sequencer {
|
||||||
|
draw_box_styled(buf, seq_area, style);
|
||||||
|
}
|
||||||
draw_sequencer(state, buf, seq_area.x, seq_area.y + 1, seq_area.width, seq_area.height - 2)?;
|
draw_sequencer(state, buf, seq_area.x, seq_area.y + 1, seq_area.width, seq_area.height - 2)?;
|
||||||
|
|
||||||
if state.show_help {
|
if state.show_help {
|
||||||
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
|
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
|
||||||
let hide = "[Left/Right] Track [Up/Down] Scene [,/.] Value [F1] Toggle help ";
|
let hide = "[Left/Right] Track [Up/Down] Scene [,/.] Value [F1] Toggle help ";
|
||||||
|
|
@ -175,6 +189,20 @@ pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect>
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, tempo: usize) {
|
||||||
|
let style = Style::default().not_dim();
|
||||||
|
"BPM"
|
||||||
|
.blit(buf, x, y, Some(style));
|
||||||
|
format!("{:03}.{:03}", tempo / 1000, tempo % 1000)
|
||||||
|
.blit(buf, x + 4, y, Some(style.bold()));
|
||||||
|
}
|
||||||
|
fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, frame: usize) {
|
||||||
|
let tick = (frame as f64 / timebase.frames_per_tick()) as usize;
|
||||||
|
let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq());
|
||||||
|
let (bars, beats) = (beats / 4, beats % 4);
|
||||||
|
let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1);
|
||||||
|
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
|
||||||
|
}
|
||||||
fn draw_scenes (
|
fn draw_scenes (
|
||||||
state: &Launcher, buf: &mut Buffer, x: u16, y: u16,
|
state: &Launcher, buf: &mut Buffer, x: u16, y: u16,
|
||||||
) -> Rect {
|
) -> Rect {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ impl Sequencer {
|
||||||
// Read from sequence into output buffer
|
// Read from sequence into output buffer
|
||||||
if self.playing == TransportState::Rolling {
|
if self.playing == TransportState::Rolling {
|
||||||
let frame = transport.pos.frame() as usize;
|
let frame = transport.pos.frame() as usize;
|
||||||
let quant = self.timebase.fpb() as usize * self.steps / self.resolution;
|
let quant = self.timebase.frames_per_beat() as usize * self.steps / self.resolution;
|
||||||
let ticks = self.timebase.frames_to_ticks(frame, frame + frames, quant);
|
let ticks = self.timebase.frames_to_ticks(frame, frame + frames, quant);
|
||||||
for (time, tick) in ticks.iter() {
|
for (time, tick) in ticks.iter() {
|
||||||
if let Some(events) = sequence.get(&(*tick as u32)) {
|
if let Some(events) = sequence.get(&(*tick as u32)) {
|
||||||
|
|
@ -238,8 +238,8 @@ fn render (s: &Sequencer, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||||
let pos = s.transport.query().unwrap().pos;
|
let pos = s.transport.query().unwrap().pos;
|
||||||
let frame = pos.frame();
|
let frame = pos.frame();
|
||||||
let usecs = s.timebase.frame_to_usec(frame as usize);
|
let usecs = s.timebase.frame_to_usec(frame as usize);
|
||||||
let usec_per_step = s.timebase.usec_per_step(s.resolution as usize);
|
let ustep = s.timebase.usec_per_step(s.resolution as usize);
|
||||||
let steps = usecs / usec_per_step;
|
let steps = usecs / ustep;
|
||||||
let header = draw_header(s, buf, area, steps)?;
|
let header = draw_header(s, buf, area, steps)?;
|
||||||
let piano = match s.mode {
|
let piano = match s.mode {
|
||||||
SequencerView::Tiny =>
|
SequencerView::Tiny =>
|
||||||
|
|
@ -273,7 +273,7 @@ pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) ->
|
||||||
let step = beat % s.steps;
|
let step = beat % s.steps;
|
||||||
let reps = s.steps / s.resolution;
|
let reps = s.steps / s.resolution;
|
||||||
let steps = s.steps % s.resolution;
|
let steps = s.steps % s.resolution;
|
||||||
draw_timer(buf, x + width - 2, y + 1, rep, step, reps, steps);
|
draw_timer(buf, x + width - 2, y + 1, &format!("{rep}.{step:02} / {reps}.{steps}"));
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
draw_play_stop(buf, x + 2, y + 1, &s.playing);
|
draw_play_stop(buf, x + 2, y + 1, &s.playing);
|
||||||
let separator = format!("├{}┤", "-".repeat((area.width - 2).into()));
|
let separator = format!("├{}┤", "-".repeat((area.width - 2).into()));
|
||||||
|
|
@ -285,11 +285,10 @@ pub fn draw_header (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) ->
|
||||||
Ok(Rect { x, y, width: area.width, height: 3 })
|
Ok(Rect { x, y, width: area.width, height: 3 })
|
||||||
}
|
}
|
||||||
pub fn draw_timer (
|
pub fn draw_timer (
|
||||||
buf: &mut Buffer, x: u16, y: u16, rep: usize, step: usize, reps: usize, steps: usize
|
buf: &mut Buffer, x: u16, y: u16, timer: &str
|
||||||
) {
|
) {
|
||||||
let style = Style::default().gray();
|
let style = Some(Style::default().gray().bold().not_dim());
|
||||||
let timer = format!("{rep}.{step:02} / {reps}.{steps}");
|
timer.blit(buf, x - timer.len() as u16, y, style);
|
||||||
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) {
|
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
|
|
@ -554,13 +553,13 @@ fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
|
||||||
let mut s = sequencer.state.lock().unwrap();
|
let mut s = sequencer.state.lock().unwrap();
|
||||||
s.rate = Hz(48000);
|
s.rate = Hz(48000);
|
||||||
s.tempo = Tempo(240_000);
|
s.tempo = Tempo(240_000);
|
||||||
println!("F/S = {:.03}", s.fps());
|
println!("F/S = {:.03}", s.frames_per_second());
|
||||||
println!("B/S = {:.03}", s.bps());
|
println!("B/S = {:.03}", s.beats_per_secon());
|
||||||
println!("F/B = {:.03}", s.fpb());
|
println!("F/B = {:.03}", s.frames_per_beat());
|
||||||
println!("T/B = {:.03}", s.tpb());
|
println!("T/B = {:.03}", s.ticks_per_beat());
|
||||||
println!("F/T = {:.03}", s.fpt());
|
println!("F/T = {:.03}", s.frames_per_tick());
|
||||||
println!("F/L = {:.03}", s.fpl());
|
println!("F/L = {:.03}", s.frames_per_loop());
|
||||||
println!("T/L = {:.03}", s.tpl());
|
println!("T/L = {:.03}", s.ticks_per_loop());
|
||||||
let fpt = s.fpt();
|
let fpt = s.fpt();
|
||||||
let frames_per_chunk = 240;
|
let frames_per_chunk = 240;
|
||||||
let chunk = |chunk: usize| s.frames_to_ticks(
|
let chunk = |chunk: usize| s.frames_to_ticks(
|
||||||
|
|
|
||||||
111
src/time.rs
111
src/time.rs
|
|
@ -32,48 +32,71 @@ pub enum NoteDuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timebase {
|
impl Timebase {
|
||||||
pub fn rate (&self) -> usize {
|
#[inline] pub fn rate (&self) -> usize {
|
||||||
self.rate.load(Ordering::Relaxed)
|
self.rate.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
pub fn tempo (&self) -> usize {
|
#[inline] pub fn tempo (&self) -> usize {
|
||||||
self.tempo.load(Ordering::Relaxed)
|
self.tempo.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
pub fn ppq (&self) -> usize {
|
#[inline] pub fn ppq (&self) -> usize {
|
||||||
self.ppq.load(Ordering::Relaxed)
|
self.ppq.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
#[inline] fn beats_per_second (&self) -> f64 {
|
||||||
/// Beats per second
|
|
||||||
#[inline] fn bps (&self) -> f64 {
|
|
||||||
self.tempo() as f64 / 60000.0
|
self.tempo() as f64 / 60000.0
|
||||||
}
|
}
|
||||||
/// Frames per second
|
#[inline] fn frames_per_second (&self) -> usize {
|
||||||
#[inline] fn fps (&self) -> usize {
|
|
||||||
self.rate()
|
self.rate()
|
||||||
}
|
}
|
||||||
/// Frames per beat
|
#[inline] pub fn frames_per_beat (&self) -> f64 {
|
||||||
#[inline] pub fn fpb (&self) -> f64 {
|
self.frames_per_second() as f64 / self.beats_per_second()
|
||||||
self.fps() as f64 / self.bps()
|
|
||||||
}
|
}
|
||||||
/// Frames per tick FIXME double times
|
#[inline] pub fn frames_per_tick (&self) -> f64 {
|
||||||
#[inline] fn fpt (&self) -> f64 {
|
self.frames_per_second() as f64 / self.ticks_per_second()
|
||||||
self.fps() as f64 / self.tps()
|
|
||||||
}
|
}
|
||||||
/// Frames per loop
|
#[inline] fn frames_per_loop (&self, steps: f64, steps_per_beat: f64) -> f64 {
|
||||||
#[inline] fn fpl (&self, steps: f64, steps_per_beat: f64) -> f64 {
|
self.frames_per_beat() * steps / steps_per_beat
|
||||||
self.fpb() * steps / steps_per_beat
|
|
||||||
}
|
}
|
||||||
/// Ticks per beat
|
#[inline] fn ticks_per_beat (&self) -> f64 {
|
||||||
#[inline] fn tpb (&self) -> f64 {
|
|
||||||
self.ppq.load(Ordering::Relaxed) as f64
|
self.ppq.load(Ordering::Relaxed) as f64
|
||||||
}
|
}
|
||||||
/// Ticks per second
|
#[inline] fn ticks_per_second (&self) -> f64 {
|
||||||
#[inline] fn tps (&self) -> f64 {
|
self.beats_per_second() * self.ticks_per_beat()
|
||||||
self.bps() * self.tpb()
|
}
|
||||||
|
#[inline] pub fn frame_to_usec (&self, frame: usize) -> usize {
|
||||||
|
frame * 1000000 / self.rate()
|
||||||
|
}
|
||||||
|
#[inline] pub fn usec_to_frame (&self, usec: usize) -> usize {
|
||||||
|
usec * self.rate() / 1000
|
||||||
|
}
|
||||||
|
#[inline] pub fn usec_per_bar (&self, beats_per_bar: usize) -> usize {
|
||||||
|
self.usec_per_beat() * beats_per_bar
|
||||||
|
}
|
||||||
|
#[inline] pub fn usec_per_beat (&self) -> usize {
|
||||||
|
60_000_000_000 / self.tempo()
|
||||||
|
}
|
||||||
|
#[inline] pub fn usec_per_step (&self, divisor: usize) -> usize {
|
||||||
|
self.usec_per_beat() / divisor
|
||||||
|
}
|
||||||
|
#[inline] pub fn usec_per_tick (&self) -> usize {
|
||||||
|
self.usec_per_beat() / self.ppq()
|
||||||
|
}
|
||||||
|
#[inline] pub fn note_to_usec (&self, note: &NoteDuration) -> usize {
|
||||||
|
match note {
|
||||||
|
NoteDuration::Nth(time, flies) =>
|
||||||
|
self.usec_per_beat() * *time / *flies,
|
||||||
|
NoteDuration::Dotted(note) =>
|
||||||
|
self.note_to_usec(note) * 3 / 2,
|
||||||
|
NoteDuration::Tuplet(n, note) =>
|
||||||
|
self.note_to_usec(note) * 2 / *n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline] pub fn note_to_frame (&self, note: &NoteDuration) -> usize {
|
||||||
|
self.usec_to_frame(self.note_to_usec(note))
|
||||||
}
|
}
|
||||||
pub fn frames_to_ticks (&self, start: usize, end: usize, fpl: usize) -> Vec<(usize, usize)> {
|
pub fn frames_to_ticks (&self, start: usize, end: usize, fpl: usize) -> Vec<(usize, usize)> {
|
||||||
let start_frame = start % fpl;
|
let start_frame = start % fpl;
|
||||||
let end_frame = end % fpl;
|
let end_frame = end % fpl;
|
||||||
let fpt = self.fpt();
|
let fpt = self.frames_per_tick();
|
||||||
let mut ticks = vec![];
|
let mut ticks = vec![];
|
||||||
let mut add_frame = |frame: f64|{
|
let mut add_frame = |frame: f64|{
|
||||||
let jitter = frame.rem_euclid(fpt);
|
let jitter = frame.rem_euclid(fpt);
|
||||||
|
|
@ -107,48 +130,8 @@ impl Timebase {
|
||||||
}
|
}
|
||||||
ticks
|
ticks
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn frame_to_usec (&self, frame: usize) -> usize {
|
|
||||||
frame * 1000000 / self.rate()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_to_frame (&self, usec: usize) -> usize {
|
|
||||||
usec * self.rate() / 1000
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_bar (&self, beats_per_bar: usize) -> usize {
|
|
||||||
self.usec_per_beat() * beats_per_bar
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_beat (&self) -> usize {
|
|
||||||
60_000_000_000 / self.tempo()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_step (&self, divisor: usize) -> usize {
|
|
||||||
self.usec_per_beat() / divisor
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_tick (&self) -> usize {
|
|
||||||
self.usec_per_beat() / self.ppq()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn usec_per_note (&self, note: &NoteDuration) -> usize {
|
|
||||||
match note {
|
|
||||||
NoteDuration::Nth(time, flies) =>
|
|
||||||
self.usec_per_beat() * *time / *flies,
|
|
||||||
NoteDuration::Dotted(note) =>
|
|
||||||
self.usec_per_note(note) * 3 / 2,
|
|
||||||
NoteDuration::Tuplet(n, note) =>
|
|
||||||
self.usec_per_note(note) * 2 / *n,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn frame_per_note (&self, note: &NoteDuration) -> usize {
|
|
||||||
self.usec_to_frame(self.usec_per_note(note))
|
|
||||||
}
|
|
||||||
pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) {
|
pub fn quantize (&self, step: &NoteDuration, time: usize) -> (usize, usize) {
|
||||||
let step = self.usec_per_note(step);
|
let step = self.note_to_usec(step);
|
||||||
let time = time / step;
|
let time = time / step;
|
||||||
let offset = time % step;
|
let offset = time % step;
|
||||||
(time, offset)
|
(time, offset)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue