mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: fix phrase editor
This commit is contained in:
parent
e92677d50c
commit
3995ec0f03
9 changed files with 131 additions and 95 deletions
8
Justfile
8
Justfile
|
|
@ -17,18 +17,26 @@ ftpush:
|
|||
git push --tags -fu codeberg
|
||||
git push --tags -fu origin
|
||||
transport:
|
||||
reset
|
||||
cargo run --bin tek_transport
|
||||
arranger:
|
||||
reset
|
||||
cargo run --bin tek_arranger
|
||||
sequencer:
|
||||
reset
|
||||
cargo run --bin tek_sequencer
|
||||
sequencer-release:
|
||||
reset
|
||||
cargo run --release --bin tek_sequencer
|
||||
mixer:
|
||||
reset
|
||||
cargo run --bin tek_mixer
|
||||
track:
|
||||
reset
|
||||
cargo run --bin tek_track
|
||||
sampler:
|
||||
reset
|
||||
cargo run --bin tek_sampler
|
||||
plugin:
|
||||
reset
|
||||
cargo run --bin tek_plugin
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
mod align; pub(crate) use align::*;
|
||||
mod bsp; pub(crate) use bsp::*;
|
||||
mod cond; pub(crate) use cond::*;
|
||||
mod debug; pub(crate) use debug::*;
|
||||
mod fill; pub(crate) use fill::*;
|
||||
mod fixed; pub(crate) use fixed::*;
|
||||
mod inset_outset; pub(crate) use inset_outset::*;
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
impl<E: Engine, W: Render<E>> LayoutDebug<E> for W {}
|
||||
|
||||
pub trait LayoutDebug<E: Engine>: Render<E> + Sized {
|
||||
fn debug (self) -> DebugOverlay<E, Self> {
|
||||
DebugOverlay(Default::default(), self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugOverlay<E: Engine, W: Render<E>>(PhantomData<E>, pub W);
|
||||
|
|
@ -1,5 +1,15 @@
|
|||
use crate::*;
|
||||
|
||||
impl<E: Engine> LayoutDebug<E> for E {}
|
||||
|
||||
pub trait LayoutDebug<E: Engine> {
|
||||
fn debug <W: Render<E>> (other: W) -> DebugOverlay<E, W> {
|
||||
DebugOverlay(Default::default(), other)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugOverlay<E: Engine, W: Render<E>>(PhantomData<E>, pub W);
|
||||
|
||||
/// A widget that tracks its render width and height
|
||||
#[derive(Default)]
|
||||
pub struct Measure<E: Engine> {
|
||||
|
|
@ -57,13 +67,12 @@ impl Render<Tui> for Measure<Tui> {
|
|||
|
||||
impl Measure<Tui> {
|
||||
pub fn debug (&self) -> ShowMeasure {
|
||||
let measure: Measure<Tui> = (*self).clone();
|
||||
ShowMeasure(measure)
|
||||
ShowMeasure(&self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShowMeasure(Measure<Tui>);
|
||||
render!(|self: ShowMeasure|render(|to|Ok({
|
||||
pub struct ShowMeasure<'a>(&'a Measure<Tui>);
|
||||
render!(|self: ShowMeasure<'a>|render(|to|Ok({
|
||||
let w = self.0.w();
|
||||
let h = self.0.h();
|
||||
to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some(
|
||||
|
|
|
|||
|
|
@ -1228,6 +1228,7 @@ fn to_arranger_command (state: &ArrangerTui, input: &TuiInput) -> Option<Arrange
|
|||
key!(Char('e')) => Cmd::Editor(PhraseCommand::Show(Some(
|
||||
state.phrases.phrases[state.phrases.phrase.load(Ordering::Relaxed)].clone()
|
||||
))),
|
||||
// WSAD navigation, Q launches, E edits, PgUp/Down pool, Arrows editor
|
||||
_ => match state.focused() {
|
||||
ArrangerFocus::Transport(_) => {
|
||||
match to_transport_command(state, input)? {
|
||||
|
|
|
|||
|
|
@ -196,14 +196,14 @@ render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
|
|||
col!([
|
||||
Tui::fixed_y(2, TransportView::from((
|
||||
self,
|
||||
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten(),
|
||||
self.player.play_phrase().as_ref().map(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)).flatten().clone(),
|
||||
if let SequencerFocus::Transport(_) = self.focus {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
))),
|
||||
//self.editor
|
||||
Tui::fill_xy(&self.editor)
|
||||
]),
|
||||
)
|
||||
)]));
|
||||
|
|
|
|||
|
|
@ -28,20 +28,39 @@ impl Input<Tui> for TuiInput {
|
|||
}
|
||||
}
|
||||
|
||||
//#[macro_export] macro_rules! key_pat {
|
||||
//}
|
||||
//#[macro_export] macro_rules! key_expr {
|
||||
//}
|
||||
|
||||
/// Define key pattern in key match statement
|
||||
#[macro_export] macro_rules! key {
|
||||
(Ctrl-Alt-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
})) };
|
||||
(Ctrl-Alt-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL | KeyModifiers::ALT,
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Ctrl-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Ctrl-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::CONTROL, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Alt-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
(Alt-$code:expr) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::ALT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
||||
(Shift-$code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { code: $code,
|
||||
modifiers: KeyModifiers::SHIFT, kind: KeyEventKind::Press, state: KeyEventState::NONE
|
||||
})) };
|
||||
|
|
|
|||
|
|
@ -54,21 +54,23 @@ impl InputToCommand<Tui, PhraseEditorModel> for PhraseCommand {
|
|||
key!(Char('>')) => SetNoteLength(next_note_length(note_len)),
|
||||
// TODO: '/' set triplet, '?' set dotted
|
||||
_ => match from.event() {
|
||||
key!(Up) => SetNoteCursor(note_point + 1),
|
||||
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
||||
key!(PageUp) => SetNoteCursor(note_point + 3),
|
||||
key!(PageDown) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||
key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
|
||||
key!(Right) => SetTimeCursor((time_point + note_len) % length),
|
||||
key!(Shift-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||
key!(Shift-Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||
key!(Up) => SetNoteCursor(note_point + 1),
|
||||
key!(Down) => SetNoteCursor(note_point.saturating_sub(1)),
|
||||
key!(Left) => SetTimeCursor(time_point.saturating_sub(note_len)),
|
||||
key!(Right) => SetTimeCursor((time_point + note_len) % length),
|
||||
key!(Alt-Up) => SetNoteCursor(note_point + 3),
|
||||
key!(Alt-Down) => SetNoteCursor(note_point.saturating_sub(3)),
|
||||
key!(Alt-Left) => SetTimeCursor(time_point.saturating_sub(time_zoom)),
|
||||
key!(Alt-Right) => SetTimeCursor((time_point + time_zoom) % length),
|
||||
|
||||
key!(Ctrl-Up) => SetNoteScroll(note_lo + 1),
|
||||
key!(Ctrl-Down) => SetNoteScroll(note_lo.saturating_sub(1)),
|
||||
key!(Ctrl-PageUp) => SetNoteScroll(note_point + 3),
|
||||
key!(Ctrl-PageDown) => SetNoteScroll(note_point.saturating_sub(3)),
|
||||
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
||||
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
||||
key!(Ctrl-Up) => SetNoteScroll(note_lo + 1),
|
||||
key!(Ctrl-Down) => SetNoteScroll(note_lo.saturating_sub(1)),
|
||||
key!(Ctrl-Left) => SetTimeScroll(time_start.saturating_sub(note_len)),
|
||||
key!(Ctrl-Right) => SetTimeScroll(time_start + note_len),
|
||||
key!(Ctrl-Alt-Up) => SetNoteScroll(note_point + 3),
|
||||
key!(Ctrl-Alt-Down) => SetNoteScroll(note_point.saturating_sub(3)),
|
||||
key!(Ctrl-Alt-Left) => SetTimeScroll(time_point.saturating_sub(time_zoom)),
|
||||
key!(Ctrl-Alt-Right) => SetTimeScroll((time_point + time_zoom) % length),
|
||||
_ => return None
|
||||
},
|
||||
})
|
||||
|
|
@ -151,6 +153,9 @@ impl PhraseViewMode for PhraseEditorModel {
|
|||
fn phrase (&self) -> &Arc<RwLock<Option<Arc<RwLock<Phrase>>>>> {
|
||||
self.mode.phrase()
|
||||
}
|
||||
fn set_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
self.mode.set_phrase(phrase)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -194,12 +199,24 @@ impl PhraseEditorRange {
|
|||
pub fn set_time_lock (&self, x: bool) {
|
||||
self.time_lock.store(x, Relaxed);
|
||||
}
|
||||
pub fn time_start (&self) -> usize {
|
||||
self.time_start.load(Relaxed)
|
||||
}
|
||||
pub fn set_time_start (&self, x: usize) {
|
||||
self.time_start.store(x, Relaxed);
|
||||
}
|
||||
pub fn set_note_lo (&self, x: usize) {
|
||||
self.note_lo.store(x, Relaxed);
|
||||
}
|
||||
pub fn note_lo (&self) -> usize {
|
||||
self.note_lo.load(Relaxed)
|
||||
}
|
||||
pub fn note_axis (&self) -> usize {
|
||||
self.note_lo.load(Relaxed)
|
||||
}
|
||||
pub fn note_hi (&self) -> usize {
|
||||
self.note_lo() + self.note_axis()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -228,6 +245,9 @@ impl PhraseEditorPoint {
|
|||
pub fn set_note_len (&self, x: usize) {
|
||||
self.note_len.store(x, Relaxed)
|
||||
}
|
||||
pub fn note_point (&self) -> usize {
|
||||
self.note_point.load(Relaxed)
|
||||
}
|
||||
pub fn time_point (&self) -> usize {
|
||||
self.time_point.load(Relaxed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use super::*;
|
|||
/// A phrase, rendered as a horizontal piano roll.
|
||||
pub struct PianoHorizontal {
|
||||
phrase: Arc<RwLock<Option<Arc<RwLock<Phrase>>>>>,
|
||||
/// Buffer where the whole phrase is rerendered on change
|
||||
buffer: BigBuffer,
|
||||
/// Width and height of notes area at last render
|
||||
size: Measure<Tui>,
|
||||
|
|
@ -11,6 +12,8 @@ pub struct PianoHorizontal {
|
|||
range: PhraseEditorRange,
|
||||
/// The note cursor
|
||||
point: PhraseEditorPoint,
|
||||
/// The highlight color palette
|
||||
color: ItemPalette,
|
||||
}
|
||||
|
||||
impl PianoHorizontal {
|
||||
|
|
@ -19,56 +22,69 @@ impl PianoHorizontal {
|
|||
let mut range = PhraseEditorRange::default();
|
||||
range.time_axis = size.x.clone();
|
||||
range.note_axis = size.y.clone();
|
||||
let phrase = phrase.clone();
|
||||
let color = phrase.read().unwrap().as_ref()
|
||||
.map(|p|p.read().unwrap().color)
|
||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
||||
Self {
|
||||
buffer: Default::default(),
|
||||
phrase: phrase.clone(),
|
||||
point: PhraseEditorPoint::default(),
|
||||
range,
|
||||
size,
|
||||
range,
|
||||
phrase,
|
||||
color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render!(|self: PianoHorizontal|{
|
||||
let bg = TuiTheme::g(32);
|
||||
let fg = self.phrase().read().unwrap()
|
||||
.as_ref().map(|p|p.read().unwrap().color)
|
||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
||||
let bg = TuiTheme::g(32);
|
||||
let fg = self.color;
|
||||
let note_lo = self.range.note_lo();
|
||||
let note_hi = self.range.note_hi();
|
||||
let time_lock = self.range.time_lock();
|
||||
let time_start = self.range.time_start();
|
||||
let time_zoom = self.range.time_zoom();
|
||||
let time_point = self.point.time_point();
|
||||
let note_point = self.point.note_point();
|
||||
let note_len = self.point.note_len();
|
||||
Tui::bg(bg, Tui::split_down(false, 1,
|
||||
Tui::bg(fg.dark.rgb, PianoHorizontalTimeline {
|
||||
start: "|0".into()
|
||||
}),
|
||||
Split::right(false, 5, PianoHorizontalKeys {
|
||||
color: ItemPalette::random(),
|
||||
note_lo: 0,
|
||||
note_hi: 0,
|
||||
note_point: None
|
||||
}, lay!([
|
||||
//self.size,
|
||||
Tui::debug(Tui::fill_x(Tui::push_x(5, Tui::bg(fg.darkest.rgb, Tui::fg(fg.lightest.rgb,
|
||||
PianoHorizontalTimeline {
|
||||
time_start,
|
||||
time_zoom,
|
||||
}
|
||||
))))),
|
||||
Tui::fill_xy(Split::right(true, 5, Tui::debug(lay!([
|
||||
self.size,
|
||||
PianoHorizontalNotes {
|
||||
source: &self.buffer,
|
||||
time_start: 0,
|
||||
note_hi: 0,
|
||||
source: &self.buffer,
|
||||
time_start,
|
||||
note_hi,
|
||||
},
|
||||
PianoHorizontalCursor {
|
||||
time_zoom: 0,
|
||||
time_point: 0,
|
||||
time_start: 0,
|
||||
note_point: 0,
|
||||
note_len: 0,
|
||||
note_hi: 0,
|
||||
note_lo: 0,
|
||||
time_zoom,
|
||||
time_point,
|
||||
time_start,
|
||||
note_point: note_point,
|
||||
note_len: note_len,
|
||||
note_hi: note_hi,
|
||||
note_lo: note_lo,
|
||||
},
|
||||
])),
|
||||
])), PianoHorizontalKeys {
|
||||
color: self.color,
|
||||
note_lo,
|
||||
note_hi,
|
||||
note_point: Some(note_point),
|
||||
}))
|
||||
))
|
||||
});
|
||||
|
||||
pub struct PianoHorizontalTimeline {
|
||||
start: String
|
||||
time_start: usize,
|
||||
time_zoom: usize,
|
||||
}
|
||||
render!(|self: PianoHorizontalTimeline|{
|
||||
Tui::fg(TuiTheme::g(224), Tui::push_x(5, self.start.as_str()))
|
||||
});
|
||||
render!(|self: PianoHorizontalTimeline|format!("{}*{}", self.time_start, self.time_zoom).as_str());
|
||||
|
||||
pub struct PianoHorizontalKeys {
|
||||
color: ItemPalette,
|
||||
|
|
@ -253,6 +269,13 @@ impl PhraseViewMode for PianoHorizontal {
|
|||
};
|
||||
self.buffer = buffer
|
||||
}
|
||||
fn set_phrase (&mut self, phrase: Option<Arc<RwLock<Phrase>>>) {
|
||||
*self.phrase().write().unwrap() = phrase;
|
||||
self.color = self.phrase.read().unwrap().as_ref()
|
||||
.map(|p|p.read().unwrap().color)
|
||||
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
|
||||
self.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PianoHorizontal {
|
||||
|
|
@ -263,38 +286,6 @@ impl std::fmt::Debug for PianoHorizontal {
|
|||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
//fn render_cursor (
|
||||
//&self,
|
||||
//to: &mut TuiOutput,
|
||||
//time_point: usize,
|
||||
//time_start: usize,
|
||||
//note_point: usize,
|
||||
//note_len: usize,
|
||||
//note_hi: usize,
|
||||
//note_lo: usize,
|
||||
//) {
|
||||
//let time_zoom = self.time_zoom;
|
||||
//let [x0, y0, w, h] = to.area().xywh();
|
||||
//let style = Some(Style::default().fg(Color::Rgb(0,255,0)));
|
||||
//for (y, note) in (note_lo..=note_hi).rev().enumerate() {
|
||||
//if note == note_point {
|
||||
//for x in 0..w {
|
||||
//let time_1 = time_start + x as usize * time_zoom;
|
||||
//let time_2 = time_1 + time_zoom;
|
||||
//if time_1 <= time_point && time_point < time_2 {
|
||||
//to.blit(&"█", x0 + x as u16, y0 + y as u16, style);
|
||||
//let tail = note_len as u16 / time_zoom as u16;
|
||||
//for x_tail in (x0 + x + 1)..(x0 + x + tail) {
|
||||
//to.blit(&"▂", x_tail, y0 + y as u16, style);
|
||||
//}
|
||||
//break
|
||||
//}
|
||||
//}
|
||||
//break
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
// Update sequencer playhead indicator
|
||||
//self.now().set(0.);
|
||||
//if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue