mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56: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 {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
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(30, &self.sequencer),
|
||||
Fill::h(&self.sampler),
|
||||
)),
|
||||
));
|
||||
Tui::min_y(20, &self.sequencer),
|
||||
Tui::min_y(20, &self.sampler),
|
||||
))
|
||||
])),
|
||||
)));
|
||||
|
||||
pub enum GrooveboxCommand {
|
||||
Sequencer(SequencerCommand),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -140,11 +140,14 @@ impl NoteRange for MidiEditorModel {
|
|||
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 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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
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