mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: sequencer now copies from buffers
This commit is contained in:
parent
aa478099d9
commit
2fc8e84551
15 changed files with 310 additions and 256 deletions
3
Justfile
3
Justfile
|
|
@ -4,3 +4,6 @@ status:
|
|||
cargo c
|
||||
cloc --by-file src/
|
||||
git status
|
||||
push:
|
||||
git push -u codeberg main
|
||||
git push -u origin main
|
||||
|
|
|
|||
|
|
@ -78,11 +78,11 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
|||
Ok(true)
|
||||
}],
|
||||
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
||||
app.sequencer.time_zoom = prev_note_length(app.sequencer.time_zoom);
|
||||
app.sequencer.time_axis.scale_mut(&prev_note_length);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
||||
app.sequencer.time_zoom = next_note_length(app.sequencer.time_zoom);
|
||||
app.sequencer.time_axis.scale_mut(&next_note_length);
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
false => app.arranger.scene_prev(),
|
||||
true => app.arranger.track_prev(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "arranger_cursor_down", "move cursor down", |app: &mut App| {
|
||||
|
|
@ -19,7 +19,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
false => app.arranger.scene_next(),
|
||||
true => app.arranger.track_next(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "arranger_cursor_left", "move cursor left", |app: &mut App| {
|
||||
|
|
@ -27,7 +27,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
false => app.arranger.track_prev(),
|
||||
true => app.arranger.scene_prev(),
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "arranger_cursor_right", "move cursor right", |app: &mut App| {
|
||||
|
|
@ -35,7 +35,7 @@ pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
false => app.arranger.track_next(),
|
||||
true => app.arranger.scene_next()
|
||||
};
|
||||
app.sequencer.phrase = app.arranger.phrase().map(Clone::clone);
|
||||
app.sequencer.show(app.arranger.phrase())?;
|
||||
Ok(true)
|
||||
}],
|
||||
[Char('.'), NONE, "arranger_increment", "set next clip at cursor", |app: &mut App| {
|
||||
|
|
|
|||
|
|
@ -3,26 +3,30 @@ use crate::{core::*, model::App};
|
|||
/// Key bindings for phrase editor.
|
||||
pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
||||
[Up, NONE, "seq_cursor_up", "move cursor up", |app: &mut App| {
|
||||
app.sequencer.note_cursor = app.sequencer.note_cursor.saturating_sub(1);
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_dec(); },
|
||||
false => { app.sequencer.note_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Down, NONE, "seq_cursor_down", "move cursor up", |app: &mut App| {
|
||||
app.sequencer.note_cursor = app.sequencer.note_cursor + 1;
|
||||
[Down, NONE, "seq_cursor_down", "move cursor down", |app: &mut App| {
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.note_axis.point_inc(); },
|
||||
false => { app.sequencer.note_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Left, NONE, "seq_cursor_left", "move cursor up", |app: &mut App| {
|
||||
if app.sequencer.entered {
|
||||
app.sequencer.time_cursor = app.sequencer.time_cursor.saturating_sub(1);
|
||||
} else {
|
||||
app.sequencer.time_start = app.sequencer.time_start.saturating_sub(1);
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_dec(); },
|
||||
false => { app.sequencer.time_axis.start_dec(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
[Right, NONE, "seq_cursor_right", "move cursor up", |app: &mut App| {
|
||||
if app.sequencer.entered {
|
||||
app.sequencer.time_cursor = app.sequencer.time_cursor + 1;
|
||||
} else {
|
||||
app.sequencer.time_start = app.sequencer.time_start + 1;
|
||||
match app.sequencer.entered {
|
||||
true => { app.sequencer.time_axis.point_inc(); },
|
||||
false => { app.sequencer.time_axis.start_inc(); },
|
||||
}
|
||||
Ok(true)
|
||||
}],
|
||||
|
|
@ -35,4 +39,3 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
|
|||
// [CapsLock, NONE, "advance", "Toggle auto advance", nop],
|
||||
// [Char('w'), NONE, "rest", "Advance by note duration", nop],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ pub fn run <T> (state: Arc<RwLock<T>>) -> Usually<Arc<RwLock<T>>>
|
|||
terminal_setup()?;
|
||||
panic_hook_setup();
|
||||
let main_thread = main_thread(&exited, &state)?;
|
||||
main_thread.join().unwrap();
|
||||
main_thread.join().expect("main thread failed");
|
||||
terminal_teardown()?;
|
||||
Ok(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ pub fn write_midi_output (writer: &mut ::jack::MidiWriter, output: &MIDIChunk, f
|
|||
}
|
||||
}
|
||||
|
||||
/// (ppq, name)
|
||||
pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
||||
/// (pulses, name)
|
||||
pub const NOTE_DURATIONS: [(u16, &str);26] = [
|
||||
(1, "1/384"),
|
||||
(2, "1/192"),
|
||||
(3, "1/128"),
|
||||
|
|
@ -63,16 +63,7 @@ pub const NOTE_DURATIONS: [(usize, &str);26] = [
|
|||
(6144, "16/1"),
|
||||
];
|
||||
|
||||
pub fn ppq_to_name (ppq: usize) -> &'static str {
|
||||
for (length, name) in &NOTE_DURATIONS {
|
||||
if *length == ppq {
|
||||
return name
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
||||
pub fn prev_note_length (ppq: usize) -> usize {
|
||||
pub fn prev_note_length (ppq: u16) -> u16 {
|
||||
for i in 1..=16 {
|
||||
let length = NOTE_DURATIONS[16-i].0;
|
||||
if length < ppq {
|
||||
|
|
@ -82,7 +73,7 @@ pub fn prev_note_length (ppq: usize) -> usize {
|
|||
ppq
|
||||
}
|
||||
|
||||
pub fn next_note_length (ppq: usize) -> usize {
|
||||
pub fn next_note_length (ppq: u16) -> u16 {
|
||||
for (length, _) in &NOTE_DURATIONS {
|
||||
if *length > ppq {
|
||||
return *length
|
||||
|
|
@ -90,3 +81,12 @@ pub fn next_note_length (ppq: usize) -> usize {
|
|||
}
|
||||
ppq
|
||||
}
|
||||
|
||||
pub fn ppq_to_name (ppq: u16) -> &'static str {
|
||||
for (length, name) in &NOTE_DURATIONS {
|
||||
if *length == ppq {
|
||||
return name
|
||||
}
|
||||
}
|
||||
""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,49 +2,35 @@ use crate::core::*;
|
|||
pub(crate) use ratatui::prelude::CrosstermBackend;
|
||||
pub(crate) use ratatui::style::{Stylize, Style, Color};
|
||||
pub(crate) use ratatui::layout::Rect;
|
||||
pub(crate) use ratatui::buffer::Buffer;
|
||||
pub(crate) use ratatui::buffer::{Buffer, Cell};
|
||||
use ratatui::widgets::WidgetRef;
|
||||
|
||||
pub fn buffer_update (
|
||||
buffer: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16)
|
||||
) {
|
||||
for row in 0..area.height {
|
||||
let y = area.y + row;
|
||||
for col in 0..area.width {
|
||||
let x = area.x + col;
|
||||
callback(buffer.get_mut(x, y), col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn fill_fg (buf: &mut Buffer, area: Rect, color: Color) {
|
||||
let Rect { x, y, width, height } = area;
|
||||
for y in y..y+height {
|
||||
if y >= buf.area.height {
|
||||
break
|
||||
}
|
||||
for x in x..x+width {
|
||||
if x >= buf.area.width {
|
||||
break
|
||||
}
|
||||
buf.get_mut(x, y).set_fg(color);
|
||||
}
|
||||
}
|
||||
buffer_update(buf, area, &|cell,_,_|{cell.set_fg(color);})
|
||||
}
|
||||
pub fn fill_bg (buf: &mut Buffer, area: Rect, color: Color) {
|
||||
let Rect { x, y, width, height } = area;
|
||||
for y in y..y+height {
|
||||
if y >= buf.area.height {
|
||||
break
|
||||
}
|
||||
for x in x..x+width {
|
||||
if x >= buf.area.width {
|
||||
break
|
||||
}
|
||||
buf.get_mut(x, y).set_bg(color);
|
||||
}
|
||||
}
|
||||
buffer_update(buf, area, &|cell,_,_|{cell.set_bg(color);})
|
||||
}
|
||||
pub fn fill_char (buf: &mut Buffer, area: Rect, c: char) {
|
||||
let Rect { x, y, width, height } = area;
|
||||
for y in y..y+height {
|
||||
if y >= buf.area.height {
|
||||
break
|
||||
}
|
||||
for x in x..x+width {
|
||||
if x >= buf.area.width {
|
||||
break
|
||||
}
|
||||
buf.get_mut(x, y).set_char(c);
|
||||
}
|
||||
buffer_update(buf, area, &|cell,_,_|{cell.set_char(c);})
|
||||
}
|
||||
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||
match (lower, upper) {
|
||||
(true, true) => Some('█'),
|
||||
(true, false) => Some('▄'),
|
||||
(false, true) => Some('▀'),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Application state.
|
||||
|
||||
submod! { arranger looper mixer phrase plugin sampler sequencer scene track transport }
|
||||
submod! { axis arranger looper mixer phrase plugin sampler sequencer scene track transport }
|
||||
|
||||
use crate::core::*;
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ process!(App |self, _client, scope| {
|
|||
&self.transport.timebase,
|
||||
self.transport.playing,
|
||||
self.transport.started,
|
||||
self.transport.quant,
|
||||
self.transport.quant as usize,
|
||||
reset,
|
||||
&scope,
|
||||
(current_frames as usize, self.chunk_size),
|
||||
|
|
|
|||
33
src/model/axis.rs
Normal file
33
src/model/axis.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
|
||||
impl $A<$T> {
|
||||
pub fn start_inc (&mut self) -> $T {
|
||||
self.start = self.start + 1;
|
||||
self.start
|
||||
}
|
||||
pub fn start_dec (&mut self) -> $T {
|
||||
self.start = self.start.saturating_sub(1);
|
||||
self.start
|
||||
}
|
||||
pub fn point_inc (&mut self) -> Option<$T> {
|
||||
self.point = self.point.map(|p|p + 1);
|
||||
self.point
|
||||
}
|
||||
pub fn point_dec (&mut self) -> Option<$T> {
|
||||
self.point = self.point.map(|p|p.saturating_sub(1));
|
||||
self.point
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
pub struct FixedAxis<T> { pub start: T, pub point: Option<T> }
|
||||
impl_axis_common!(FixedAxis u16);
|
||||
impl_axis_common!(FixedAxis usize);
|
||||
|
||||
pub struct ScaledAxis<T> { pub start: T, pub scale: T, pub point: Option<T> }
|
||||
impl_axis_common!(ScaledAxis u16);
|
||||
impl_axis_common!(ScaledAxis usize);
|
||||
impl<T: Copy> ScaledAxis<T> {
|
||||
pub fn scale_mut (&mut self, cb: &impl Fn(T)->T) {
|
||||
self.scale = cb(self.scale)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,9 @@ pub struct Phrase {
|
|||
pub name: String,
|
||||
pub length: usize,
|
||||
pub notes: PhraseData,
|
||||
pub looped: Option<(usize, usize)>
|
||||
pub looped: Option<(usize, usize)>,
|
||||
/// Immediate note-offs in view
|
||||
pub percussive: bool
|
||||
}
|
||||
|
||||
impl Default for Phrase {
|
||||
|
|
@ -34,7 +36,8 @@ impl Phrase {
|
|||
name: name.to_string(),
|
||||
length,
|
||||
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
|
||||
looped: Some((0, length))
|
||||
looped: Some((0, length)),
|
||||
percussive: true,
|
||||
}
|
||||
}
|
||||
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
|
||||
|
|
|
|||
|
|
@ -1,46 +1,160 @@
|
|||
use crate::{core::*, model::Phrase};
|
||||
use crate::{core::*, model::{FixedAxis, ScaledAxis, Phrase}};
|
||||
|
||||
pub struct Sequencer {
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
pub mode: bool,
|
||||
pub buffer: Buffer,
|
||||
pub now: usize,
|
||||
pub ppq: usize,
|
||||
pub note_cursor: usize,
|
||||
pub note_start: usize,
|
||||
pub time_cursor: usize,
|
||||
pub time_start: usize,
|
||||
pub time_zoom: usize,
|
||||
|
||||
pub focused: bool,
|
||||
pub entered: bool,
|
||||
|
||||
pub phrase: Option<Arc<RwLock<Phrase>>>,
|
||||
pub buffer: Buffer,
|
||||
pub keys: Buffer,
|
||||
/// Highlight input keys
|
||||
pub notes_in: [bool; 128],
|
||||
pub keys_in: [bool; 128],
|
||||
/// Highlight output keys
|
||||
pub notes_out: [bool; 128],
|
||||
pub keys_out: [bool; 128],
|
||||
|
||||
pub now: usize,
|
||||
pub ppq: usize,
|
||||
pub note_axis: FixedAxis<u16>,
|
||||
pub time_axis: ScaledAxis<u16>,
|
||||
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new () -> Self {
|
||||
Self {
|
||||
buffer: Buffer::empty(Rect::default()),
|
||||
keys: keys_vert(),
|
||||
entered: false,
|
||||
focused: false,
|
||||
mode: false,
|
||||
note_cursor: 0,
|
||||
note_start: 0,
|
||||
notes_in: [false;128],
|
||||
notes_out: [false;128],
|
||||
keys_in: [false;128],
|
||||
keys_out: [false;128],
|
||||
phrase: None,
|
||||
time_cursor: 0,
|
||||
time_start: 0,
|
||||
time_zoom: 12,
|
||||
now: 0,
|
||||
ppq: 96
|
||||
ppq: 96,
|
||||
note_axis: FixedAxis {
|
||||
start: 12,
|
||||
point: Some(36)
|
||||
},
|
||||
time_axis: ScaledAxis {
|
||||
start: 0,
|
||||
scale: 24,
|
||||
point: Some(0)
|
||||
},
|
||||
}
|
||||
}
|
||||
pub fn show (&mut self, phrase: Option<&Arc<RwLock<Phrase>>>) {
|
||||
/// 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 = u16::MAX.min(phrase.read().unwrap().length as u16);
|
||||
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height: 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 = Buffer::empty(Rect::default())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
cell.set_char('▀');
|
||||
match x {
|
||||
0 => {
|
||||
let (fg, bg) = key_colors(y);
|
||||
cell.set_fg(fg);
|
||||
cell.set_bg(bg);
|
||||
},
|
||||
1 => {
|
||||
cell.set_fg(Color::White);
|
||||
cell.set_bg(Color::White);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
buffer
|
||||
}
|
||||
|
||||
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 Buffer, length: usize, ppq: usize) -> Usually<()> {
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= length {
|
||||
break
|
||||
}
|
||||
let style = Style::default();
|
||||
let cell = buf.get_mut(x, buf.area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
let character = if ppq > 0 && x as usize % ppq == 0 { '|' } else { '·' };
|
||||
for y in 0 .. buf.area.height - buf.area.y {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(character);
|
||||
cell.set_fg(Color::Gray);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill_seq_fg (buf: &mut Buffer, phrase: &Phrase) -> Usually<()> {
|
||||
let mut notes_on = [false;128];
|
||||
for x in 0 .. buf.area.width - buf.area.x {
|
||||
if x as usize >= phrase.length {
|
||||
break
|
||||
}
|
||||
if let Some(notes) = phrase.notes.get(x as usize) {
|
||||
for note in notes {
|
||||
if phrase.percussive {
|
||||
match note {
|
||||
MidiMessage::NoteOn { key, .. } =>
|
||||
notes_on[key.as_int() as usize] = true,
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
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.area.height - buf.area.y) / 2 {
|
||||
if y >= 64 {
|
||||
break
|
||||
}
|
||||
if let Some(block) = half_block(
|
||||
notes_on[y as usize * 2],
|
||||
notes_on[y as usize * 2 + 1],
|
||||
) {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_fg(Color::White);
|
||||
}
|
||||
}
|
||||
if phrase.percussive {
|
||||
notes_on.fill(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ impl Track {
|
|||
(frame0.saturating_sub(start_frame), frames, period)
|
||||
);
|
||||
}
|
||||
});
|
||||
}).unwrap();
|
||||
let mut phrase = phrase.write().unwrap();
|
||||
let length = phrase.length;
|
||||
// Monitor and record input
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ pub struct TransportToolbar {
|
|||
/// JACK transport handle.
|
||||
transport: Option<Transport>,
|
||||
/// Quantization factor
|
||||
pub quant: usize,
|
||||
pub quant: u16,
|
||||
/// Global sync quant
|
||||
pub sync: usize,
|
||||
pub sync: u16,
|
||||
/// Current transport state
|
||||
pub playing: Option<TransportState>,
|
||||
/// Current position according to transport
|
||||
|
|
@ -56,7 +56,7 @@ impl TransportToolbar {
|
|||
playing: Some(TransportState::Stopped),
|
||||
started: None,
|
||||
quant: 24,
|
||||
sync: timebase.ppq() as usize * 4,
|
||||
sync: timebase.ppq() as u16 * 4,
|
||||
transport,
|
||||
timebase,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@ impl Sequencer {
|
|||
Style::default()
|
||||
}
|
||||
}
|
||||
fn index_to_color (&self, index: usize, default: Color) -> Color {
|
||||
if self.notes_in[index] && self.notes_out[index] {
|
||||
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.notes_in[index] {
|
||||
} else if self.keys_in[index] {
|
||||
Color::Red
|
||||
} else if self.notes_out[index] {
|
||||
} else if self.keys_out[index] {
|
||||
Color::Green
|
||||
} else {
|
||||
default
|
||||
|
|
@ -44,166 +45,77 @@ impl Sequencer {
|
|||
|
||||
fn horizontal_draw (&self, buf: &mut Buffer, area: Rect) -> Usually<()> {
|
||||
self.horizontal_keys(buf, area)?;
|
||||
self.horizontal_quant(buf, area)?;
|
||||
self.horizontal_timer(buf, area, &self.phrase)?;
|
||||
self.horizontal_lanes(buf, area, &self.phrase)?;
|
||||
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> {
|
||||
let area = Rect {
|
||||
x: area.x + Self::H_KEYS_OFFSET,
|
||||
y: area.y + 1,
|
||||
width: area.width - Self::H_KEYS_OFFSET,
|
||||
height: area.height - 1
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
let src_x = (x + self.time_axis.start) * self.time_axis.scale;
|
||||
let src_y = y + self.note_axis.start;
|
||||
if src_x < self.buffer.area.width && src_y < self.buffer.area.height {
|
||||
let src = self.buffer.get(src_x, src_y);
|
||||
cell.set_symbol(src.symbol());
|
||||
cell.set_fg(src.fg);
|
||||
}
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let area = Rect {
|
||||
x: area.x,
|
||||
y: area.y + 1,
|
||||
width: 2,
|
||||
height: area.height - 2
|
||||
};
|
||||
buffer_update(buf, area, &|cell, x, y|{
|
||||
*cell = self.keys.get(x, y % 6).clone()
|
||||
});
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_quant (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let quant = ppq_to_name(self.time_zoom);
|
||||
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> {
|
||||
let (time, note) = (self.time_cursor, self.note_cursor);
|
||||
if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) {
|
||||
let x = area.x + Self::H_KEYS_OFFSET + 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: &Option<Arc<RwLock<Phrase>>>
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &RwLock<Phrase>
|
||||
) -> Usually<Rect> {
|
||||
if let Some(phrase) = phrase {
|
||||
let phrase = phrase.read().unwrap();
|
||||
let (time0, time_z, now) = (self.time_start, self.time_zoom, self.now % phrase.length);
|
||||
let (time0, time_z, now) = (self.time_axis.start, self.time_axis.scale, self.now % phrase.length);
|
||||
let Rect { x, width, .. } = area;
|
||||
for x in x+Self::H_KEYS_OFFSET..x+width {
|
||||
let step = (time0 + (x-Self::H_KEYS_OFFSET) as usize) * time_z;
|
||||
let next_step = (time0 + (x-Self::H_KEYS_OFFSET) as usize + 1) * time_z;
|
||||
let style = Self::style_timer_step(now, step, next_step);
|
||||
let step = (time0 + (x-Self::H_KEYS_OFFSET)) * time_z;
|
||||
let next_step = (time0 + (x-Self::H_KEYS_OFFSET) + 1) * time_z;
|
||||
let style = Self::style_timer_step(now, step as usize, next_step as usize);
|
||||
"-".blit(buf, x, area.y, Some(style))?;
|
||||
}
|
||||
}
|
||||
return Ok(Rect { x: area.x, y: area.y, width: area.width, height: 1 })
|
||||
}
|
||||
|
||||
fn horizontal_keys (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let note0 = self.note_start;
|
||||
let not_dim = Style::default().not_dim();
|
||||
let Rect { x, y, height, .. } = area;
|
||||
let height = height.min(128);
|
||||
let h = height.saturating_sub(3);
|
||||
for index in 0..h {
|
||||
let y = y + h - index;
|
||||
let key1 = buf.get_mut(x + 1, y);
|
||||
key1.set_char('▄');
|
||||
key1.set_style(not_dim);
|
||||
let (fg, bg) = 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!(); }
|
||||
};
|
||||
key1.set_fg(self.index_to_color(index as usize * 2, fg));
|
||||
key1.set_bg(self.index_to_color(index as usize * 2 + 1, bg));
|
||||
let key2 = buf.get_mut(x + 2, y);
|
||||
key2.set_char('▄');
|
||||
key2.set_style(not_dim);
|
||||
key2.set_fg(self.index_to_color(index as usize * 2, Color::White));
|
||||
key2.set_bg(self.index_to_color(index as usize * 2 + 1, Color::White));
|
||||
//for x in x+Self::H_KEYS_OFFSET..x+width-1 {
|
||||
//let cell = buf.get_mut(x, y);
|
||||
//cell.set_char('░');
|
||||
//cell.set_style(black);
|
||||
//}
|
||||
let note_a = note0 + (index * 2) as usize;
|
||||
if note_a % 12 == 0 {
|
||||
let octave = format!("C{}", (note_a / 12) as i8 - 2);
|
||||
octave.blit(buf, x + 3, y, None)?;
|
||||
continue
|
||||
}
|
||||
let note_b = note0 + (index * 2) as usize;
|
||||
if note_b % 12 == 0 {
|
||||
let octave = format!("C{}", (note_b / 12) as i8 - 2);
|
||||
octave.blit(buf, x + 3, y, None)?;
|
||||
continue
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
}
|
||||
|
||||
fn horizontal_lanes (
|
||||
&self, buf: &mut Buffer, area: Rect, phrase: &Option<Arc<RwLock<Phrase>>>
|
||||
) -> Usually<Rect> {
|
||||
if let Some(phrase) = phrase {
|
||||
let Rect { x, y, width, height } = area;
|
||||
let phrase = phrase.read().unwrap();
|
||||
let now = self.now % phrase.length;
|
||||
let ppq = self.ppq;
|
||||
let time_z = self.time_zoom;
|
||||
let time0 = self.time_start;
|
||||
let note0 = self.note_start;
|
||||
let dim = Style::default().dim();
|
||||
let offset = Self::H_KEYS_OFFSET;
|
||||
let phrase_area = Rect { x: x + offset, y, width: width - offset, height: height - 2 };
|
||||
let mut steps = Vec::with_capacity(phrase_area.width as usize);
|
||||
for x in phrase_area.x .. phrase_area.x + phrase_area.width {
|
||||
let x0 = x.saturating_sub(phrase_area.x) as usize;
|
||||
let step = (0 + time0 + x0) * time_z;
|
||||
let next = (1 + time0 + x0) * time_z;
|
||||
if step >= phrase.length {
|
||||
break
|
||||
}
|
||||
let style = Self::style_timer_step(now, step, next);
|
||||
let cell = buf.get_mut(x, area.y);
|
||||
cell.set_char('-');
|
||||
cell.set_style(style);
|
||||
steps.push((x, step, next));
|
||||
for y in phrase_area.y .. phrase_area.y + phrase_area.height {
|
||||
if y == phrase_area.y {
|
||||
if step % (4 * ppq) == 0 {
|
||||
format!("{}", 1 + step / (4 * ppq)).blit(buf, x, y, None)?;
|
||||
} else if step % ppq == 0 {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
|
||||
cell.set_style(dim);
|
||||
}
|
||||
} else {
|
||||
let cell = buf.get_mut(x, y);
|
||||
cell.set_char(if step % ppq == 0 { '|' } else { '·' });
|
||||
cell.set_style(dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let wh = Style::default().white().not_dim();
|
||||
for index in 0..height-2 {
|
||||
let note_a = note0 + index as usize * 2;
|
||||
let note_b = note0 + index as usize * 2 + 1;
|
||||
for (x, step, next_step) in steps.iter() {
|
||||
let (a, b) = (
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_a as u8), *step, *next_step),
|
||||
phrase.contains_note_on(u7::from_int_lossy(note_b as u8), *step, *next_step),
|
||||
);
|
||||
if let Some(block) = half_block(a, b) {
|
||||
let y = y + height.saturating_sub(index+2) as u16;
|
||||
let cell = buf.get_mut(*x, y);
|
||||
cell.set_char(block);
|
||||
cell.set_style(wh);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(area)
|
||||
} else {
|
||||
return Ok(Rect { x: area.x, y: area.x, width: 0, height: 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||
match (lower, upper) {
|
||||
(true, true) => Some('█'),
|
||||
(true, false) => Some('▄'),
|
||||
(false, true) => Some('▀'),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ render!(TransportToolbar |self, buf, area| {
|
|||
// Quantization
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"QUANT".blit(buf, x, y, Some(not_dim))?;
|
||||
let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
let width = ppq_to_name(*quant as u16).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
let area = Rect { x, y, width: (width + 2).max(10), height: 2 };
|
||||
if self.focused && self.entered && self.selected == TransportFocus::Quant {
|
||||
corners.draw(buf, Rect { x: area.x - 1, ..area })?;
|
||||
|
|
@ -59,7 +59,7 @@ render!(TransportToolbar |self, buf, area| {
|
|||
// Clip launch sync
|
||||
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
|
||||
"SYNC".blit(buf, x, y, Some(not_dim))?;
|
||||
let width = ppq_to_name(*sync).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
let width = ppq_to_name(*sync as u16).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
|
||||
let area = Rect { x, y, width: (width + 2).max(10), height: 2 };
|
||||
if self.focused && self.entered && self.selected == TransportFocus::Sync {
|
||||
corners.draw(buf, Rect { x: area.x - 1, ..area })?;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue