mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: enabling standalone arranger
This commit is contained in:
parent
b6da43e93e
commit
7685072e4c
16 changed files with 445 additions and 370 deletions
|
|
@ -47,7 +47,7 @@ impl App {
|
||||||
entered: true,
|
entered: true,
|
||||||
section: AppFocus::default(),
|
section: AppFocus::default(),
|
||||||
transport: TransportToolbar::new(Some(jack.transport())),
|
transport: TransportToolbar::new(Some(jack.transport())),
|
||||||
arranger: Arranger::new(),
|
arranger: Arranger::new(""),
|
||||||
mixer: Mixer::new("")?,
|
mixer: Mixer::new("")?,
|
||||||
jack: Some(jack),
|
jack: Some(jack),
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,8 @@ path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tek_sequencer"
|
name = "tek_sequencer"
|
||||||
path = "src/main.rs"
|
path = "src/sequencer_main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek_arranger"
|
||||||
|
path = "src/arranger_main.rs"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ use crate::*;
|
||||||
|
|
||||||
/// Represents the tracks and scenes of the composition.
|
/// Represents the tracks and scenes of the composition.
|
||||||
pub struct Arranger {
|
pub struct Arranger {
|
||||||
|
/// Name of arranger
|
||||||
|
pub name: String,
|
||||||
/// Display mode of arranger
|
/// Display mode of arranger
|
||||||
pub mode: ArrangerViewMode,
|
pub mode: ArrangerViewMode,
|
||||||
/// Currently selected element.
|
/// Currently selected element.
|
||||||
|
|
@ -16,28 +18,13 @@ pub struct Arranger {
|
||||||
pub entered: bool,
|
pub entered: bool,
|
||||||
pub fixed_height: bool,
|
pub fixed_height: bool,
|
||||||
pub sequencer: Sequencer,
|
pub sequencer: Sequencer,
|
||||||
}
|
pub transport: Option<Arc<RwLock<TransportToolbar>>>,
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arranger {
|
impl Arranger {
|
||||||
pub fn new () -> Self {
|
pub fn new (name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
name: name.into(),
|
||||||
mode: ArrangerViewMode::Vertical,
|
mode: ArrangerViewMode::Vertical,
|
||||||
selected: ArrangerFocus::Clip(0, 0),
|
selected: ArrangerFocus::Clip(0, 0),
|
||||||
scenes: vec![],
|
scenes: vec![],
|
||||||
|
|
@ -46,6 +33,7 @@ impl Arranger {
|
||||||
focused: true,
|
focused: true,
|
||||||
fixed_height: false,
|
fixed_height: false,
|
||||||
sequencer: Sequencer::new(),
|
sequencer: Sequencer::new(),
|
||||||
|
transport: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn activate (&mut self) {
|
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!()
|
unimplemented!()
|
||||||
//let phrase = self.phrase();
|
//let phrase = self.phrase();
|
||||||
//self.sequencer.show(phrase)
|
//self.sequencer.show(phrase)
|
||||||
|
|
@ -155,57 +143,3 @@ render!(Arranger |self, buf, area| match self.mode {
|
||||||
ArrangerViewMode::VerticalCompact =>
|
ArrangerViewMode::VerticalCompact =>
|
||||||
super::arranger_view_v::draw_compact(self, buf, area),
|
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)
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
|
||||||
40
crates/tek_sequencer/src/arranger_cli.rs
Normal file
40
crates/tek_sequencer/src/arranger_cli.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
57
crates/tek_sequencer/src/arranger_handle.rs
Normal file
57
crates/tek_sequencer/src/arranger_handle.rs
Normal 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)
|
||||||
|
}],
|
||||||
|
});
|
||||||
5
crates/tek_sequencer/src/arranger_main.rs
Normal file
5
crates/tek_sequencer/src/arranger_main.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
include!("lib.rs");
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
tek_core::run(Arc::new(RwLock::new(crate::Arranger::from_args()?)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
16
crates/tek_sequencer/src/arranger_view.rs
Normal file
16
crates/tek_sequencer/src/arranger_view.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,11 +13,15 @@ submod! {
|
||||||
phrase
|
phrase
|
||||||
sequencer
|
sequencer
|
||||||
sequencer_cli
|
sequencer_cli
|
||||||
sequencer_control
|
sequencer_handle
|
||||||
|
sequencer_render
|
||||||
sequencer_track
|
sequencer_track
|
||||||
arranger
|
arranger
|
||||||
|
arranger_cli
|
||||||
arranger_focus
|
arranger_focus
|
||||||
|
arranger_handle
|
||||||
arranger_track
|
arranger_track
|
||||||
|
arranger_view
|
||||||
scene
|
scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,17 +22,6 @@ pub struct Sequencer {
|
||||||
pub time_axis: ScaledAxis<usize>,
|
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 {
|
impl Sequencer {
|
||||||
pub fn new () -> Self {
|
pub fn new () -> Self {
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
//! Command line option parser.
|
use tek_core::clap::{self, Parser};
|
||||||
|
|
||||||
use tek_core::clap::{self, Parser, Subcommand};
|
|
||||||
use tek_timer::TransportToolbar;
|
use tek_timer::TransportToolbar;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
handle!(Sequencer |self, e| handle_keymap(self, e, KEYMAP_SEQUENCER));
|
||||||
|
|
||||||
/// Key bindings for phrase editor.
|
/// Key bindings for phrase editor.
|
||||||
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
||||||
[Up, NONE, "seq_cursor_up", "move cursor up", |sequencer: &mut Sequencer| {
|
[Up, NONE, "seq_cursor_up", "move cursor up", |sequencer: &mut Sequencer| {
|
||||||
287
crates/tek_sequencer/src/sequencer_render.rs
Normal file
287
crates/tek_sequencer/src/sequencer_render.rs
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
# `tek_timer`
|
# `tek_timer`
|
||||||
|
|
||||||
This crate implements time sync and JACK transport control.
|
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
|
||||||
|
|
|
||||||
|
|
@ -71,14 +71,15 @@ impl TransportToolbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
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") {
|
self.playing = match self.playing.expect("1st frame has not been processed yet") {
|
||||||
TransportState::Stopped => {
|
TransportState::Stopped => {
|
||||||
self.transport.as_ref().unwrap().start()?;
|
transport.start()?;
|
||||||
Some(TransportState::Starting)
|
Some(TransportState::Starting)
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
self.transport.as_ref().unwrap().stop()?;
|
transport.stop()?;
|
||||||
self.transport.as_ref().unwrap().locate(0)?;
|
transport.locate(0)?;
|
||||||
Some(TransportState::Stopped)
|
Some(TransportState::Stopped)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,11 @@ render!(TransportToolbar |self, buf, area| {
|
||||||
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);
|
||||||
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
|
let timer = format!("{bars}.{beats}.{pulses:02}");
|
||||||
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim))
|
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)
|
]).render(buf, area)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue