mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
add GrooveboxStatus and try to autostretch sampler
This commit is contained in:
parent
8d79537edf
commit
fc0a398702
9 changed files with 129 additions and 38 deletions
|
|
@ -4,14 +4,12 @@ pub use ratatui::prelude::Color;
|
||||||
|
|
||||||
pub trait HasColor {
|
pub trait HasColor {
|
||||||
fn color (&self) -> ItemColor;
|
fn color (&self) -> ItemColor;
|
||||||
fn color_mut (&mut self) -> &mut ItemColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_color {
|
#[macro_export] macro_rules! has_color {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
||||||
fn color (&$self) -> ItemColor { $cb }
|
fn color (&$self) -> ItemColor { $cb }
|
||||||
fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,7 @@ pub struct MidiPointModel {
|
||||||
/// Length of note that will be inserted, in pulses
|
/// Length of note that will be inserted, in pulses
|
||||||
pub note_len: Arc<AtomicUsize>,
|
pub note_len: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MidiPointModel {
|
impl Default for MidiPointModel {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -130,20 +131,32 @@ impl Default for MidiPointModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub trait MidiPoint {
|
|
||||||
|
pub trait NotePoint {
|
||||||
fn note_len (&self) -> usize;
|
fn note_len (&self) -> usize;
|
||||||
fn set_note_len (&self, x: usize);
|
fn set_note_len (&self, x: usize);
|
||||||
fn note_point (&self) -> usize;
|
fn note_point (&self) -> usize;
|
||||||
fn set_note_point (&self, x: usize);
|
fn set_note_point (&self, x: usize);
|
||||||
fn time_point (&self) -> usize;
|
|
||||||
fn set_time_point (&self, x: usize);
|
|
||||||
fn note_end (&self) -> usize { self.note_point() + self.note_len() }
|
fn note_end (&self) -> usize { self.note_point() + self.note_len() }
|
||||||
}
|
}
|
||||||
impl MidiPoint for MidiPointModel {
|
|
||||||
|
pub trait TimePoint {
|
||||||
|
fn time_point (&self) -> usize;
|
||||||
|
fn set_time_point (&self, x: usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiPoint: NotePoint + TimePoint {}
|
||||||
|
|
||||||
|
impl<T: NotePoint + TimePoint> MidiPoint for T {}
|
||||||
|
|
||||||
|
impl NotePoint for MidiPointModel {
|
||||||
fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
|
fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
|
||||||
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
|
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
|
||||||
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) }
|
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) }
|
||||||
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) }
|
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePoint for MidiPointModel {
|
||||||
fn time_point (&self) -> usize { self.time_point.load(Relaxed) }
|
fn time_point (&self) -> usize { self.time_point.load(Relaxed) }
|
||||||
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
|
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use super::*;
|
||||||
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
|
||||||
|
|
||||||
pub struct GrooveboxTui {
|
pub struct GrooveboxTui {
|
||||||
|
pub size: Measure<Tui>,
|
||||||
pub sequencer: SequencerTui,
|
pub sequencer: SequencerTui,
|
||||||
pub sampler: SamplerTui,
|
pub sampler: SamplerTui,
|
||||||
pub split: u16,
|
pub split: u16,
|
||||||
|
|
@ -24,6 +25,7 @@ from_jack!(|jack|GrooveboxTui {
|
||||||
sampler: SamplerTui::try_from(jack)?,
|
sampler: SamplerTui::try_from(jack)?,
|
||||||
split: 16,
|
split: 16,
|
||||||
focus: GrooveboxFocus::Sequencer,
|
focus: GrooveboxFocus::Sequencer,
|
||||||
|
size: Measure::new(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -34,13 +36,16 @@ pub enum GrooveboxFocus {
|
||||||
|
|
||||||
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
|
||||||
|
|
||||||
render!(<Tui>|self:GrooveboxTui|Bsp::n(
|
render!(<Tui>|self:GrooveboxTui|Fill::wh(Bsp::n(
|
||||||
Fixed::h(2, SequencerStatus::from(&self.sequencer)),
|
Fixed::h(2, GrooveboxStatus::from(self)),
|
||||||
Fill::h(Bsp::s(
|
Fill::h(lay!([
|
||||||
Tui::min_y(30, &self.sequencer),
|
Fill::h(&self.size),
|
||||||
Fill::h(&self.sampler),
|
Fill::h(Bsp::s(
|
||||||
)),
|
Tui::min_y(20, &self.sequencer),
|
||||||
));
|
Tui::min_y(20, &self.sampler),
|
||||||
|
))
|
||||||
|
])),
|
||||||
|
)));
|
||||||
|
|
||||||
pub enum GrooveboxCommand {
|
pub enum GrooveboxCommand {
|
||||||
Sequencer(SequencerCommand),
|
Sequencer(SequencerCommand),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ pub struct SamplerTui {
|
||||||
pub size: Measure<Tui>,
|
pub size: Measure<Tui>,
|
||||||
/// Lowest note displayed
|
/// Lowest note displayed
|
||||||
pub note_lo: AtomicUsize,
|
pub note_lo: AtomicUsize,
|
||||||
|
color: ItemColor
|
||||||
}
|
}
|
||||||
|
|
||||||
render!(<Tui>|self: SamplerTui|{
|
render!(<Tui>|self: SamplerTui|{
|
||||||
|
|
@ -32,28 +33,18 @@ render!(<Tui>|self: SamplerTui|{
|
||||||
let fg = TuiTheme::g(200);
|
let fg = TuiTheme::g(200);
|
||||||
let bg = TuiTheme::g(50);
|
let bg = TuiTheme::g(50);
|
||||||
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
|
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
|
||||||
let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]);
|
let with_border = |x|lay!([
|
||||||
with_border(Fill::wh(Bsp::s(
|
border,
|
||||||
"kyp",
|
Tui::inset_xy(1, 1, Fill::wh(&x))
|
||||||
|
]);
|
||||||
|
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
|
||||||
|
"Sampler",
|
||||||
Bsp::e(
|
Bsp::e(
|
||||||
Fixed::w(keys_width, keys()),
|
Fixed::w(keys_width, keys()),
|
||||||
Fill::wh(lay!([&self.size, Fill::wh("nymka")])),
|
Fill::wh(lay!([&self.size, Fill::wh("Sample")])),
|
||||||
),
|
),
|
||||||
)))
|
))))
|
||||||
});
|
});
|
||||||
|
|
||||||
struct SamplerKeys<'a>(&'a SamplerTui);
|
|
||||||
has_color!(|self: SamplerKeys<'a>|ItemColor::default());
|
|
||||||
render!(<Tui>|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v::<Tui, Self>(to, self))));
|
|
||||||
impl<'a> NoteRange for SamplerKeys<'a> {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo }
|
|
||||||
fn note_axis (&self) -> &AtomicUsize { &self.0.size.y }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum SamplerMode {
|
|
||||||
// Load sample from path
|
|
||||||
Import(usize, FileBrowser),
|
|
||||||
}
|
|
||||||
from_jack!(|jack|SamplerTui{
|
from_jack!(|jack|SamplerTui{
|
||||||
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
|
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
|
||||||
let audio_outs = vec![
|
let audio_outs = vec![
|
||||||
|
|
@ -66,6 +57,7 @@ from_jack!(|jack|SamplerTui{
|
||||||
mode: None,
|
mode: None,
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
note_lo: 36.into(),
|
note_lo: 36.into(),
|
||||||
|
color: ItemColor::default(),
|
||||||
state: Sampler {
|
state: Sampler {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
name: "Sampler".into(),
|
name: "Sampler".into(),
|
||||||
|
|
@ -79,6 +71,25 @@ from_jack!(|jack|SamplerTui{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct SamplerKeys<'a>(&'a SamplerTui);
|
||||||
|
has_color!(|self: SamplerKeys<'a>|self.0.color);
|
||||||
|
render!(<Tui>|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
|
||||||
|
impl<'a> NoteRange for SamplerKeys<'a> {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo }
|
||||||
|
fn note_axis (&self) -> &AtomicUsize { &self.0.size.y }
|
||||||
|
}
|
||||||
|
impl<'a> NotePoint for SamplerKeys<'a> {
|
||||||
|
fn note_len (&self) -> usize {0/*TODO*/}
|
||||||
|
fn set_note_len (&self, x: usize) {}
|
||||||
|
fn note_point (&self) -> usize {0/*TODO*/}
|
||||||
|
fn set_note_point (&self, x: usize) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SamplerMode {
|
||||||
|
// Load sample from path
|
||||||
|
Import(usize, FileBrowser),
|
||||||
|
}
|
||||||
handle!(<Tui>|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input));
|
handle!(<Tui>|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input));
|
||||||
pub enum SamplerCommand {
|
pub enum SamplerCommand {
|
||||||
Import(FileBrowserCommand),
|
Import(FileBrowserCommand),
|
||||||
|
|
|
||||||
|
|
@ -140,11 +140,14 @@ impl NoteRange for MidiEditorModel {
|
||||||
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiPoint for MidiEditorModel {
|
impl NotePoint for MidiEditorModel {
|
||||||
fn note_len (&self) -> usize { self.mode.note_len()}
|
fn note_len (&self) -> usize { self.mode.note_len() }
|
||||||
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
|
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
|
||||||
fn note_point (&self) -> usize { self.mode.note_point() }
|
fn note_point (&self) -> usize { self.mode.note_point() }
|
||||||
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) }
|
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePoint for MidiEditorModel {
|
||||||
fn time_point (&self) -> usize { self.mode.time_point() }
|
fn time_point (&self) -> usize { self.mode.time_point() }
|
||||||
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
|
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,11 +141,13 @@ impl NoteRange for PianoHorizontal {
|
||||||
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
|
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
|
||||||
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
|
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
|
||||||
}
|
}
|
||||||
impl MidiPoint for PianoHorizontal {
|
impl NotePoint for PianoHorizontal {
|
||||||
fn note_len (&self) -> usize { self.point.note_len()}
|
fn note_len (&self) -> usize { self.point.note_len() }
|
||||||
fn set_note_len (&self, x: usize) { self.point.set_note_len(x) }
|
fn set_note_len (&self, x: usize) { self.point.set_note_len(x) }
|
||||||
fn note_point (&self) -> usize { self.point.note_point() }
|
fn note_point (&self) -> usize { self.point.note_point() }
|
||||||
fn set_note_point (&self, x: usize) { self.point.set_note_point(x) }
|
fn set_note_point (&self, x: usize) { self.point.set_note_point(x) }
|
||||||
|
}
|
||||||
|
impl TimePoint for PianoHorizontal {
|
||||||
fn time_point (&self) -> usize { self.point.time_point() }
|
fn time_point (&self) -> usize { self.point.time_point() }
|
||||||
fn set_time_point (&self, x: usize) { self.point.set_time_point(x) }
|
fn set_time_point (&self, x: usize) { self.point.set_time_point(x) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,20 @@ use crate::*;
|
||||||
use super::note_y_iter;
|
use super::note_y_iter;
|
||||||
|
|
||||||
pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal);
|
pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal);
|
||||||
render!(<Tui>|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v::<Tui, Self>(to, self))));
|
render!(<Tui>|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
|
||||||
has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base);
|
has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base);
|
||||||
impl<'a> NoteRange for PianoHorizontalKeys<'a> {
|
impl<'a> NoteRange for PianoHorizontalKeys<'a> {
|
||||||
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() }
|
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() }
|
||||||
fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() }
|
fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() }
|
||||||
}
|
}
|
||||||
|
impl<'a> NotePoint for PianoHorizontalKeys<'a> {
|
||||||
|
fn note_len (&self) -> usize { self.0.note_len() }
|
||||||
|
fn set_note_len (&self, x: usize) { self.0.set_note_len(x) }
|
||||||
|
fn note_point (&self) -> usize { self.0.note_point() }
|
||||||
|
fn set_note_point (&self, x: usize) { self.0.set_note_point(x) }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_keys_v <E: Engine, T: HasColor + NoteRange> (to: &mut E::Output, state: &T) {
|
pub fn render_keys_v <T: HasColor + NoteRange + NotePoint> (to: &mut TuiOutput, state: &T) {
|
||||||
let color = state.color();
|
let color = state.color();
|
||||||
let note_lo = state.note_lo().get();
|
let note_lo = state.note_lo().get();
|
||||||
let note_hi = state.note_hi();
|
let note_hi = state.note_hi();
|
||||||
|
|
@ -17,7 +23,7 @@ pub fn render_keys_v <E: Engine, T: HasColor + NoteRange> (to: &mut E::Output, s
|
||||||
let [x, y0, w, h] = to.area().xywh();
|
let [x, y0, w, h] = to.area().xywh();
|
||||||
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
|
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
|
||||||
let off_style = Some(Style::default().fg(TuiTheme::g(160)));
|
let off_style = Some(Style::default().fg(TuiTheme::g(160)));
|
||||||
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold());
|
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold());
|
||||||
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||||
to.blit(&to_key(note), x, screen_y, key_style);
|
to.blit(&to_key(note), x, screen_y, key_style);
|
||||||
if note > 127 {
|
if note > 127 {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
mod status_arranger; pub(crate) use self::status_arranger::*;
|
mod status_arranger; pub(crate) use self::status_arranger::*;
|
||||||
mod status_edit; pub(crate) use self::status_edit::*;
|
mod status_edit; pub(crate) use self::status_edit::*;
|
||||||
mod status_sequencer; pub(crate) use self::status_sequencer::*;
|
mod status_sequencer; pub(crate) use self::status_sequencer::*;
|
||||||
|
mod status_groovebox; pub(crate) use self::status_groovebox::*;
|
||||||
|
|
|
||||||
52
crates/tek/src/tui/status/status_groovebox.rs
Normal file
52
crates/tek/src/tui/status/status_groovebox.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Status bar for sequencer app
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GrooveboxStatus {
|
||||||
|
pub(crate) width: usize,
|
||||||
|
pub(crate) cpu: Option<String>,
|
||||||
|
pub(crate) size: String,
|
||||||
|
pub(crate) res: String,
|
||||||
|
pub(crate) playing: bool,
|
||||||
|
}
|
||||||
|
from!(|state:&GrooveboxTui|GrooveboxStatus = {
|
||||||
|
let samples = state.sequencer.clock.chunk.load(Relaxed);
|
||||||
|
let rate = state.sequencer.clock.timebase.sr.get();
|
||||||
|
let buffer = samples as f64 / rate;
|
||||||
|
let width = state.size.w();
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
playing: state.sequencer.clock.is_rolling(),
|
||||||
|
cpu: state.sequencer.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||||
|
size: format!("{}x{}│", width, state.size.h()),
|
||||||
|
res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
render!(<Tui>|self: GrooveboxStatus|Fixed::h(2, lay!([
|
||||||
|
Self::help(),
|
||||||
|
Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||||
|
])));
|
||||||
|
impl GrooveboxStatus {
|
||||||
|
fn help () -> impl Render<Tui> {
|
||||||
|
let single = |binding, command|row!([" ", col!([
|
||||||
|
Tui::fg(TuiTheme::yellow(), binding),
|
||||||
|
command
|
||||||
|
])]);
|
||||||
|
let double = |(b1, c1), (b2, c2)|col!([
|
||||||
|
row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]),
|
||||||
|
row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]),
|
||||||
|
]);
|
||||||
|
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([
|
||||||
|
single("SPACE", "play/pause"),
|
||||||
|
double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ),
|
||||||
|
double(("a", "append"), ("s", "set note"),),
|
||||||
|
double((",.", "length"), ("<>", "triplet"), ),
|
||||||
|
double(("[]", "phrase"), ("{}", "order"), ),
|
||||||
|
double(("q", "enqueue"), ("e", "edit"), ),
|
||||||
|
double(("c", "color"), ("", ""),),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
fn stats (&self) -> impl Render<Tui> + use<'_> {
|
||||||
|
row!([&self.cpu, &self.res, &self.size])
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue