wip: enabling standalone arranger

This commit is contained in:
🪞👃🪞 2024-08-10 14:23:51 +03:00
parent b6da43e93e
commit 7685072e4c
16 changed files with 445 additions and 370 deletions

View file

@ -47,7 +47,7 @@ impl App {
entered: true,
section: AppFocus::default(),
transport: TransportToolbar::new(Some(jack.transport())),
arranger: Arranger::new(),
arranger: Arranger::new(""),
mixer: Mixer::new("")?,
jack: Some(jack),
audio_outs: vec![],

View file

@ -13,4 +13,8 @@ path = "src/lib.rs"
[[bin]]
name = "tek_sequencer"
path = "src/main.rs"
path = "src/sequencer_main.rs"
[[bin]]
name = "tek_arranger"
path = "src/arranger_main.rs"

View file

@ -4,6 +4,8 @@ use crate::*;
/// Represents the tracks and scenes of the composition.
pub struct Arranger {
/// Name of arranger
pub name: String,
/// Display mode of arranger
pub mode: ArrangerViewMode,
/// Currently selected element.
@ -16,28 +18,13 @@ pub struct Arranger {
pub entered: bool,
pub fixed_height: bool,
pub sequencer: Sequencer,
}
/// Display mode of arranger
pub enum ArrangerViewMode {
Vertical,
VerticalCompact,
Horizontal,
}
impl ArrangerViewMode {
fn to_next (&mut self) {
*self = match self {
Self::Vertical => Self::VerticalCompact,
Self::VerticalCompact => Self::Horizontal,
Self::Horizontal => Self::Vertical,
}
}
pub transport: Option<Arc<RwLock<TransportToolbar>>>,
}
impl Arranger {
pub fn new () -> Self {
pub fn new (name: &str) -> Self {
Self {
name: name.into(),
mode: ArrangerViewMode::Vertical,
selected: ArrangerFocus::Clip(0, 0),
scenes: vec![],
@ -46,6 +33,7 @@ impl Arranger {
focused: true,
fixed_height: false,
sequencer: Sequencer::new(),
transport: None
}
}
pub fn activate (&mut self) {
@ -63,7 +51,7 @@ impl Arranger {
_ => {}
}
}
fn show_phrase (&mut self) -> Usually<()> {
pub fn show_phrase (&mut self) -> Usually<()> {
unimplemented!()
//let phrase = self.phrase();
//self.sequencer.show(phrase)
@ -155,57 +143,3 @@ render!(Arranger |self, buf, area| match self.mode {
ArrangerViewMode::VerticalCompact =>
super::arranger_view_v::draw_compact(self, buf, area),
});
/// Key bindings for arranger section.
pub const KEYMAP_ARRANGER: &'static [KeyBinding<Arranger>] = keymap!(Arranger {
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |app: &mut Arranger| {
app.mode.to_next();
Ok(true)
}],
[Up, NONE, "arranger_cursor_up", "move cursor up", |app: &mut Arranger| {
match app.mode {
ArrangerViewMode::Horizontal => app.track_prev(),
_ => app.scene_prev(),
};
app.show_phrase()?;
Ok(true)
}],
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut Arranger| {
match app.mode {
ArrangerViewMode::Horizontal => app.track_next(),
_ => app.scene_next(),
};
app.show_phrase()?;
Ok(true)
}],
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut Arranger| {
match app.mode {
ArrangerViewMode::Horizontal => app.scene_prev(),
_ => app.track_prev(),
};
app.show_phrase()?;
Ok(true)
}],
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut Arranger| {
match app.mode {
ArrangerViewMode::Horizontal => app.scene_next(),
_ => app.track_next(),
};
app.show_phrase()?;
Ok(true)
}],
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut Arranger| {
app.phrase_next();
app.sequencer.phrase = app.phrase().map(Clone::clone);
Ok(true)
}],
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |app: &mut Arranger| {
app.phrase_prev();
app.sequencer.phrase = app.phrase().map(Clone::clone);
Ok(true)
}],
[Enter, NONE, "arranger_activate", "activate item at cursor", |app: &mut Arranger| {
app.activate();
Ok(true)
}],
});

View file

@ -0,0 +1,40 @@
use tek_core::clap::{self, Parser};
use tek_timer::TransportToolbar;
use crate::*;
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct ArrangerCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
/// Pulses per quarter note (arruencer resolution; default: 96)
#[arg(short, long)] ppq: Option<usize>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long)] transport: Option<bool>,
/// Number of tracks
#[arg(short = 'x', long)] tracks: Option<usize>,
/// Number of scenes
#[arg(short, long)] scenes: Option<usize>,
}
impl Arranger {
pub fn from_args () -> Usually<Self> {
let args = ArrangerCli::parse();
let mut arr = Self::new("");
if let Some(name) = args.name {
arr.name = name.clone();
}
if args.transport == Some(true) {
arr.transport = Some(Arc::new(RwLock::new(TransportToolbar::new(None))));
}
if let Some(tracks) = args.tracks {
for track in 0..tracks {
arr.track_add(None)?;
if let Some(scenes) = args.scenes {
}
}
}
Ok(arr)
}
}

View file

@ -0,0 +1,57 @@
use crate::*;
handle!(Arranger |self, e| handle_keymap(self, e, KEYMAP_ARRANGER));
/// Key bindings for arranger section.
pub const KEYMAP_ARRANGER: &'static [KeyBinding<Arranger>] = keymap!(Arranger {
[Char('`'), NONE, "arranger_mode_switch", "switch the display mode", |arranger: &mut Arranger| {
arranger.mode.to_next();
Ok(true)
}],
[Up, NONE, "arranger_cursor_up", "move cursor up", |arranger: &mut Arranger| {
match arranger.mode {
ArrangerViewMode::Horizontal => arranger.track_prev(),
_ => arranger.scene_prev(),
};
arranger.show_phrase()?;
Ok(true)
}],
[Down, NONE, "arranger_cursor_down", "move cursor down", |arranger: &mut Arranger| {
match arranger.mode {
ArrangerViewMode::Horizontal => arranger.track_next(),
_ => arranger.scene_next(),
};
arranger.show_phrase()?;
Ok(true)
}],
[Left, NONE, "arranger_cursor_left", "move cursor left", |arranger: &mut Arranger| {
match arranger.mode {
ArrangerViewMode::Horizontal => arranger.scene_prev(),
_ => arranger.track_prev(),
};
arranger.show_phrase()?;
Ok(true)
}],
[Right, NONE, "arranger_cursor_right", "move cursor right", |arranger: &mut Arranger| {
match arranger.mode {
ArrangerViewMode::Horizontal => arranger.scene_next(),
_ => arranger.track_next(),
};
arranger.show_phrase()?;
Ok(true)
}],
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |arranger: &mut Arranger| {
arranger.phrase_next();
arranger.sequencer.phrase = arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Char(','), NONE, "arranger_decrement", "set previous clip at cursor", |arranger: &mut Arranger| {
arranger.phrase_prev();
arranger.sequencer.phrase = arranger.phrase().map(Clone::clone);
Ok(true)
}],
[Enter, NONE, "arranger_activate", "activate item at cursor", |arranger: &mut Arranger| {
arranger.activate();
Ok(true)
}],
});

View file

@ -0,0 +1,5 @@
include!("lib.rs");
pub fn main () -> Usually<()> {
tek_core::run(Arc::new(RwLock::new(crate::Arranger::from_args()?)))?;
Ok(())
}

View file

@ -0,0 +1,16 @@
/// Display mode of arranger
pub enum ArrangerViewMode {
Vertical,
VerticalCompact,
Horizontal,
}
impl ArrangerViewMode {
pub fn to_next (&mut self) {
*self = match self {
Self::Vertical => Self::VerticalCompact,
Self::VerticalCompact => Self::Horizontal,
Self::Horizontal => Self::Vertical,
}
}
}

View file

@ -13,11 +13,15 @@ submod! {
phrase
sequencer
sequencer_cli
sequencer_control
sequencer_handle
sequencer_render
sequencer_track
arranger
arranger_cli
arranger_focus
arranger_handle
arranger_track
arranger_view
scene
}

View file

@ -22,17 +22,6 @@ pub struct Sequencer {
pub time_axis: ScaledAxis<usize>,
}
render!(Sequencer |self, buf, area| {
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
});
handle!(Sequencer |self, e| handle_keymap(self, e, KEYMAP_SEQUENCER));
impl Sequencer {
pub fn new () -> Self {
Self {
@ -59,279 +48,4 @@ impl Sequencer {
},
}
}
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
/// FIXME: Support phrases longer that 65536 ticks
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
self.phrase = phrase.map(Clone::clone);
if let Some(ref phrase) = self.phrase {
let width = usize::MAX.min(phrase.read().unwrap().length);
let mut buffer = BigBuffer::new(width, 64);
let phrase = phrase.read().unwrap();
fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
fill_seq_fg(&mut buffer, &phrase)?;
self.buffer = buffer;
} else {
self.buffer = Default::default();
}
Ok(())
}
fn style_focus (&self) -> Option<Style> {
Some(if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
})
}
fn style_timer_step (now: usize, step: usize, next_step: usize) -> Style {
if step <= now && now < next_step {
Style::default().yellow().bold().not_dim()
} else {
Style::default()
}
}
fn index_to_color (&self, index: u16, default: Color) -> Color {
let index = index as usize;
if self.keys_in[index] && self.keys_out[index] {
Color::Yellow
} else if self.keys_in[index] {
Color::Red
} else if self.keys_out[index] {
Color::Green
} else {
default
}
}
const H_KEYS_OFFSET: usize = 5;
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
self.horizontal_keys(buf, area)?;
if let Some(ref phrase) = self.phrase {
self.horizontal_timer(buf, area, phrase)?;
}
self.horizontal_notes(buf, area)?;
self.horizontal_cursor(buf, area)?;
self.horizontal_quant(buf, area)?;
Ok(())
}
fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x + Self::H_KEYS_OFFSET as u16,
y: area.y + 1,
width: area.width - Self::H_KEYS_OFFSET as u16,
height: area.height - 2
};
buffer_update(buf, area, &move |cell, x, y|{
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;
if src_x < self.buffer.width && src_y < self.buffer.height - 1 {
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);
});
}
});
Ok(area)
}
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x,
y: area.y + 1,
width: 5,
height: area.height - 2
};
buffer_update(buf, area, &|cell, x, y|{
let y = y + self.note_axis.start as u16;
if x < self.keys.area.width && y < self.keys.area.height {
*cell = self.keys.get(x, y).clone()
}
});
Ok(area)
}
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let quant = ppq_to_name(self.time_axis.scale);
let quant_x = area.x + area.width - 1 - quant.len() as u16;
let quant_y = area.y + area.height - 2;
quant.blit(buf, quant_x, quant_y, self.style_focus())
}
fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
let x = area.x + Self::H_KEYS_OFFSET as u16 + time as u16;
let y = area.y + 1 + note as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" };
c.blit(buf, x, y, self.style_focus())
} else {
Ok(Rect::default())
}
}
fn horizontal_timer (
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
) -> Usually<Rect> {
let phrase = phrase.read().unwrap();
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
let Rect { x, width, .. } = area;
let x2 = x as usize + Self::H_KEYS_OFFSET;
let x3 = x as usize + width as usize;
for x in x2..x3 {
let step = (time0 + x2) * time_z;
let next_step = (time0 + x2 + 1) * time_z;
let style = Self::style_timer_step(now, step as usize, next_step as usize);
"-".blit(buf, x as u16, area.y, Some(style))?;
}
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
}
}
fn keys_vert () -> Buffer {
let area = Rect { x: 0, y: 0, width: 5, height: 64 };
let mut buffer = Buffer::empty(area);
buffer_update(&mut buffer, area, &|cell, x, y| {
let y = 63 - y;
match x {
0 => {
cell.set_char('▀');
let (fg, bg) = key_colors(6 - y % 6);
cell.set_fg(fg);
cell.set_bg(bg);
},
1 => {
cell.set_char('▀');
cell.set_fg(Color::White);
cell.set_bg(Color::White);
},
2 => if y % 6 == 0 {
cell.set_char('C');
},
3 => if y % 6 == 0 {
cell.set_symbol(nth_octave(y / 6));
},
_ => {}
}
});
buffer
}
fn nth_octave (index: u16) -> &'static str {
match index {
0 => "-1",
1 => "0",
2 => "1",
3 => "2",
4 => "3",
5 => "4",
6 => "5",
7 => "6",
8 => "7",
9 => "8",
10 => "9",
_ => unreachable!()
}
}
fn key_colors (index: u16) -> (Color, Color) {
match index % 6 {
0 => (Color::White, Color::Black),
1 => (Color::White, Color::Black),
2 => (Color::White, Color::White),
3 => (Color::Black, Color::White),
4 => (Color::Black, Color::White),
5 => (Color::Black, Color::White),
_ => unreachable!()
}
}
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) -> Usually<()> {
for x in 0..buf.width {
if x as usize >= length {
break
}
let style = Style::default();
buf.get_mut(x, 0).map(|cell|{
cell.set_char('-');
cell.set_style(style);
});
for y in 0 .. buf.height {
buf.get_mut(x, y).map(|cell|{
cell.set_char(char_seq_bg(ppq, x as u16));
cell.set_fg(Color::Gray);
cell.modifier = Modifier::DIM;
});
}
}
Ok(())
}
fn char_seq_bg (ppq: usize, x: u16) -> char {
if ppq == 0 {
'·'
} else if x % (4 * ppq as u16) == 0 {
'│'
} else if x % ppq as u16 == 0 {
'╎'
} else {
'·'
}
}
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) -> Usually<()> {
let mut notes_on = [false;128];
for x in 0..buf.width {
if x as usize >= phrase.length {
break
}
if let Some(notes) = phrase.notes.get(x as usize) {
if phrase.percussive {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
_ => {}
}
}
} else {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } =>
notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
for y in 0..buf.height/2 {
if y >= 64 {
break
}
if let Some(block) = half_block(
notes_on[y as usize * 2],
notes_on[y as usize * 2 + 1],
) {
buf.get_mut(x, y).map(|cell|{
cell.set_char(block);
cell.set_fg(Color::White);
});
}
}
if phrase.percussive {
notes_on.fill(false);
}
}
}
Ok(())
}

View file

@ -1,6 +1,4 @@
//! Command line option parser.
use tek_core::clap::{self, Parser, Subcommand};
use tek_core::clap::{self, Parser};
use tek_timer::TransportToolbar;
use crate::*;

View file

@ -1,5 +1,7 @@
use crate::*;
handle!(Sequencer |self, e| handle_keymap(self, e, KEYMAP_SEQUENCER));
/// Key bindings for phrase editor.
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Up, NONE, "seq_cursor_up", "move cursor up", |sequencer: &mut Sequencer| {

View file

@ -0,0 +1,287 @@
use crate::*;
render!(Sequencer |self, buf, area| {
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
});
impl Sequencer {
/// Select which pattern to display. This pre-renders it to the buffer at full resolution.
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) -> Usually<()> {
self.phrase = phrase.map(Clone::clone);
if let Some(ref phrase) = self.phrase {
let width = usize::MAX.min(phrase.read().unwrap().length);
let mut buffer = BigBuffer::new(width, 64);
let phrase = phrase.read().unwrap();
fill_seq_bg(&mut buffer, phrase.length, self.ppq)?;
fill_seq_fg(&mut buffer, &phrase)?;
self.buffer = buffer;
} else {
self.buffer = Default::default();
}
Ok(())
}
fn style_focus (&self) -> Option<Style> {
Some(if self.focused {
Style::default().green().not_dim()
} else {
Style::default().green().dim()
})
}
fn style_timer_step (now: usize, step: usize, next_step: usize) -> Style {
if step <= now && now < next_step {
Style::default().yellow().bold().not_dim()
} else {
Style::default()
}
}
fn index_to_color (&self, index: u16, default: Color) -> Color {
let index = index as usize;
if self.keys_in[index] && self.keys_out[index] {
Color::Yellow
} else if self.keys_in[index] {
Color::Red
} else if self.keys_out[index] {
Color::Green
} else {
default
}
}
const H_KEYS_OFFSET: usize = 5;
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
self.horizontal_keys(buf, area)?;
if let Some(ref phrase) = self.phrase {
self.horizontal_timer(buf, area, phrase)?;
}
self.horizontal_notes(buf, area)?;
self.horizontal_cursor(buf, area)?;
self.horizontal_quant(buf, area)?;
Ok(())
}
fn horizontal_notes (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x + Self::H_KEYS_OFFSET as u16,
y: area.y + 1,
width: area.width - Self::H_KEYS_OFFSET as u16,
height: area.height - 2
};
buffer_update(buf, area, &move |cell, x, y|{
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;
if src_x < self.buffer.width && src_y < self.buffer.height - 1 {
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);
});
}
});
Ok(area)
}
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if area.height < 2 {
return Ok(area)
}
let area = Rect {
x: area.x,
y: area.y + 1,
width: 5,
height: area.height - 2
};
buffer_update(buf, area, &|cell, x, y|{
let y = y + self.note_axis.start as u16;
if x < self.keys.area.width && y < self.keys.area.height {
*cell = self.keys.get(x, y).clone()
}
});
Ok(area)
}
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let quant = ppq_to_name(self.time_axis.scale);
let quant_x = area.x + area.width - 1 - quant.len() as u16;
let quant_y = area.y + area.height - 2;
quant.blit(buf, quant_x, quant_y, self.style_focus())
}
fn horizontal_cursor (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
let x = area.x + Self::H_KEYS_OFFSET as u16 + time as u16;
let y = area.y + 1 + note as u16 / 2;
let c = if note % 2 == 0 { "" } else { "" };
c.blit(buf, x, y, self.style_focus())
} else {
Ok(Rect::default())
}
}
fn horizontal_timer (
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
) -> Usually<Rect> {
let phrase = phrase.read().unwrap();
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
let Rect { x, width, .. } = area;
let x2 = x as usize + Self::H_KEYS_OFFSET;
let x3 = x as usize + width as usize;
for x in x2..x3 {
let step = (time0 + x2) * time_z;
let next_step = (time0 + x2 + 1) * time_z;
let style = Self::style_timer_step(now, step as usize, next_step as usize);
"-".blit(buf, x as u16, area.y, Some(style))?;
}
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
}
}
fn nth_octave (index: u16) -> &'static str {
match index {
0 => "-1",
1 => "0",
2 => "1",
3 => "2",
4 => "3",
5 => "4",
6 => "5",
7 => "6",
8 => "7",
9 => "8",
10 => "9",
_ => unreachable!()
}
}
fn key_colors (index: u16) -> (Color, Color) {
match index % 6 {
0 => (Color::White, Color::Black),
1 => (Color::White, Color::Black),
2 => (Color::White, Color::White),
3 => (Color::Black, Color::White),
4 => (Color::Black, Color::White),
5 => (Color::Black, Color::White),
_ => unreachable!()
}
}
fn fill_seq_bg (buf: &mut BigBuffer, length: usize, ppq: usize) -> Usually<()> {
for x in 0..buf.width {
if x as usize >= length {
break
}
let style = Style::default();
buf.get_mut(x, 0).map(|cell|{
cell.set_char('-');
cell.set_style(style);
});
for y in 0 .. buf.height {
buf.get_mut(x, y).map(|cell|{
cell.set_char(char_seq_bg(ppq, x as u16));
cell.set_fg(Color::Gray);
cell.modifier = Modifier::DIM;
});
}
}
Ok(())
}
fn char_seq_bg (ppq: usize, x: u16) -> char {
if ppq == 0 {
'·'
} else if x % (4 * ppq as u16) == 0 {
'│'
} else if x % ppq as u16 == 0 {
'╎'
} else {
'·'
}
}
fn fill_seq_fg (buf: &mut BigBuffer, phrase: &Phrase) -> Usually<()> {
let mut notes_on = [false;128];
for x in 0..buf.width {
if x as usize >= phrase.length {
break
}
if let Some(notes) = phrase.notes.get(x as usize) {
if phrase.percussive {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
_ => {}
}
}
} else {
for note in notes {
match note {
MidiMessage::NoteOn { key, .. } =>
notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } =>
notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
for y in 0..buf.height/2 {
if y >= 64 {
break
}
if let Some(block) = half_block(
notes_on[y as usize * 2],
notes_on[y as usize * 2 + 1],
) {
buf.get_mut(x, y).map(|cell|{
cell.set_char(block);
cell.set_fg(Color::White);
});
}
}
if phrase.percussive {
notes_on.fill(false);
}
}
}
Ok(())
}
pub(crate) fn keys_vert () -> Buffer {
let area = Rect { x: 0, y: 0, width: 5, height: 64 };
let mut buffer = Buffer::empty(area);
buffer_update(&mut buffer, area, &|cell, x, y| {
let y = 63 - y;
match x {
0 => {
cell.set_char('▀');
let (fg, bg) = key_colors(6 - y % 6);
cell.set_fg(fg);
cell.set_bg(bg);
},
1 => {
cell.set_char('▀');
cell.set_fg(Color::White);
cell.set_bg(Color::White);
},
2 => if y % 6 == 0 {
cell.set_char('C');
},
3 => if y % 6 == 0 {
cell.set_symbol(nth_octave(y / 6));
},
_ => {}
}
});
buffer
}

View file

@ -1,3 +1,13 @@
# `tek_timer`
This crate implements time sync and JACK transport control.
* Warning: If transport is set rolling by qjackctl, this program can't pause it
* Todo: bpm: shift +/- 0.001
* Todo: quant/sync: shift = next/prev value of same type (normal, triplet, dotted)
* Or: use shift to switch between inc/dec top/bottom value?
* Todo: focus play button
* Todo: focus time position
* Todo: edit numeric values
* Todo: jump to time/bbt markers
* Todo: count xruns

View file

@ -71,14 +71,15 @@ impl TransportToolbar {
}
pub fn toggle_play (&mut self) -> Usually<()> {
let transport = self.transport.as_ref().unwrap();
self.playing = match self.playing.expect("1st frame has not been processed yet") {
TransportState::Stopped => {
self.transport.as_ref().unwrap().start()?;
transport.start()?;
Some(TransportState::Starting)
},
_ => {
self.transport.as_ref().unwrap().stop()?;
self.transport.as_ref().unwrap().locate(0)?;
transport.stop()?;
transport.locate(0)?;
Some(TransportState::Stopped)
},
};

View file

@ -73,8 +73,11 @@ render!(TransportToolbar |self, buf, area| {
let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1);
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim))
let timer = format!("{bars}.{beats}.{pulses:02}");
timer.blit(buf, x + width - timer.len() as u16 - 1, y + 0, Some(not_dim))?;
let timer = format!("{minutes}:{seconds:02}:{msecs:03}");
timer.blit(buf, x + width - timer.len() as u16 - 1, y + 1, Some(not_dim))?;
Ok(area)
}
]).render(buf, area)