add GrooveboxStatus and try to autostretch sampler

This commit is contained in:
🪞👃🪞 2024-12-27 18:05:35 +01:00
parent 8d79537edf
commit fc0a398702
9 changed files with 129 additions and 38 deletions

View file

@ -4,14 +4,12 @@ pub use ratatui::prelude::Color;
pub trait HasColor {
fn color (&self) -> ItemColor;
fn color_mut (&mut self) -> &mut ItemColor;
}
#[macro_export] macro_rules! has_color {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
fn color (&$self) -> ItemColor { $cb }
fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb }
}
}
}

View file

@ -121,6 +121,7 @@ pub struct MidiPointModel {
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
impl Default for MidiPointModel {
fn default () -> Self {
Self {
@ -130,20 +131,32 @@ impl Default for MidiPointModel {
}
}
}
pub trait MidiPoint {
pub trait NotePoint {
fn note_len (&self) -> usize;
fn set_note_len (&self, x: usize);
fn note_point (&self) -> 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() }
}
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 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 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 set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
}

View file

@ -3,6 +3,7 @@ use super::*;
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
pub struct GrooveboxTui {
pub size: Measure<Tui>,
pub sequencer: SequencerTui,
pub sampler: SamplerTui,
pub split: u16,
@ -24,6 +25,7 @@ from_jack!(|jack|GrooveboxTui {
sampler: SamplerTui::try_from(jack)?,
split: 16,
focus: GrooveboxFocus::Sequencer,
size: Measure::new(),
}
});
@ -34,13 +36,16 @@ pub enum GrooveboxFocus {
audio!(|self:GrooveboxTui,_client,_process|Control::Continue);
render!(<Tui>|self:GrooveboxTui|Bsp::n(
Fixed::h(2, SequencerStatus::from(&self.sequencer)),
Fill::h(Bsp::s(
Tui::min_y(30, &self.sequencer),
Fill::h(&self.sampler),
)),
));
render!(<Tui>|self:GrooveboxTui|Fill::wh(Bsp::n(
Fixed::h(2, GrooveboxStatus::from(self)),
Fill::h(lay!([
Fill::h(&self.size),
Fill::h(Bsp::s(
Tui::min_y(20, &self.sequencer),
Tui::min_y(20, &self.sampler),
))
])),
)));
pub enum GrooveboxCommand {
Sequencer(SequencerCommand),

View file

@ -24,6 +24,7 @@ pub struct SamplerTui {
pub size: Measure<Tui>,
/// Lowest note displayed
pub note_lo: AtomicUsize,
color: ItemColor
}
render!(<Tui>|self: SamplerTui|{
@ -32,28 +33,18 @@ render!(<Tui>|self: SamplerTui|{
let fg = TuiTheme::g(200);
let bg = TuiTheme::g(50);
let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg)));
let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]);
with_border(Fill::wh(Bsp::s(
"kyp",
let with_border = |x|lay!([
border,
Tui::inset_xy(1, 1, Fill::wh(&x))
]);
Tui::bg(bg, Fill::wh(with_border(Bsp::s(
"Sampler",
Bsp::e(
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{
let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?;
let audio_outs = vec![
@ -66,6 +57,7 @@ from_jack!(|jack|SamplerTui{
mode: None,
size: Measure::new(),
note_lo: 36.into(),
color: ItemColor::default(),
state: Sampler {
jack: jack.clone(),
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));
pub enum SamplerCommand {
Import(FileBrowserCommand),

View file

@ -140,11 +140,14 @@ impl NoteRange for MidiEditorModel {
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl MidiPoint for MidiEditorModel {
fn note_len (&self) -> usize { self.mode.note_len()}
impl NotePoint for MidiEditorModel {
fn note_len (&self) -> usize { self.mode.note_len() }
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
fn note_point (&self) -> usize { self.mode.note_point() }
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 set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
}

View file

@ -141,11 +141,13 @@ impl NoteRange for PianoHorizontal {
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
}
impl MidiPoint for PianoHorizontal {
fn note_len (&self) -> usize { self.point.note_len()}
impl NotePoint for PianoHorizontal {
fn note_len (&self) -> usize { self.point.note_len() }
fn set_note_len (&self, x: usize) { self.point.set_note_len(x) }
fn note_point (&self) -> usize { self.point.note_point() }
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 set_time_point (&self, x: usize) { self.point.set_time_point(x) }
}

View file

@ -2,14 +2,20 @@ use crate::*;
use super::note_y_iter;
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);
impl<'a> NoteRange for PianoHorizontalKeys<'a> {
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() }
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 note_lo = state.note_lo().get();
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 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 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) {
to.blit(&to_key(note), x, screen_y, key_style);
if note > 127 {

View file

@ -1,3 +1,4 @@
mod status_arranger; pub(crate) use self::status_arranger::*;
mod status_edit; pub(crate) use self::status_edit::*;
mod status_sequencer; pub(crate) use self::status_sequencer::*;
mod status_groovebox; pub(crate) use self::status_groovebox::*;

View 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])
}
}