start implementing edn loader; remove PhantomData from some tek_layout constructs

This commit is contained in:
🪞👃🪞 2025-01-03 22:50:58 +01:00
parent f359768ba2
commit 2b07e7963e
20 changed files with 239 additions and 222 deletions

View file

@ -20,7 +20,7 @@ impl<'a> ArrangerVClips<'a> {
impl<'a> Content<Tui> for ArrangerVClips<'a> {
fn content (&self) -> impl Content<Tui> {
let iter = ||self.scenes.iter().zip(self.rows.iter().map(|row|row.0));
let col = Tui::map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses));
let col = Map(iter, |(scene, pulses), i|Self::format_scene(self.tracks, scene, pulses));
Fill::xy(col)
}
}
@ -37,7 +37,7 @@ impl<'a> ArrangerVClips<'a> {
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone()))
);
let clips = Tui::map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _|
let clips = Map(||ArrangerTrack::with_widths(tracks), move|(index, track, x1, x2), _|
Push::x((x2 - x1) as u16, Self::format_clip(scene, index, track, (x2 - x1) as u16, height))
);
Fixed::y(height, row!(icon, name, clips))

View file

@ -20,7 +20,7 @@ render!(Tui: (self: ArrangerVHead<'a>) => {
row!(Tui::fg(color.light.rgb, ""), Tui::fg(color.lightest.rgb, field))
}
Some(Push::x(self.scenes_w,
Tui::map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| {
Map(||ArrangerTrack::with_widths(self.tracks), |(_, track, x1, x2), i| {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
let input = Self::format_input(track);

View file

@ -74,11 +74,11 @@ render!(Tui: (self: TransportView<'a>) => Outer(
pub struct PlayPause { pub compact: bool, pub playing: bool }
render!(Tui: (self: PlayPause) => Tui::bg(
if self.playing{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)},
Tui::either(self.compact,
Thunk::new(||Fixed::x(9, Tui::either(self.playing,
Either(self.compact,
Thunk::new(||Fixed::x(9, Either(self.playing,
Tui::fg(Color::Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Color::Rgb(255, 128, 0), " STOPPED ")))),
Thunk::new(||Fixed::x(5, Tui::either(self.playing,
Thunk::new(||Fixed::x(5, Either(self.playing,
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
@ -95,7 +95,7 @@ impl BeatStats {
Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time }
}
}
render!(Tui: (self: BeatStats) => Tui::either(self.compact,
render!(Tui: (self: BeatStats) => Either(self.compact,
row!(
FieldV(TuiTheme::g(128).into(), "BPM", &self.bpm),
FieldV(TuiTheme::g(128).into(), "Beat", &self.beat),
@ -124,7 +124,7 @@ impl OutputStats {
}
}
}
render!(Tui: (self: OutputStats) => Tui::either(self.compact,
render!(Tui: (self: OutputStats) => Either(self.compact,
row!(
FieldV(TuiTheme::g(128).into(), "SR", &self.sample_rate),
FieldV(TuiTheme::g(128).into(), "Buf", &self.buffer_size),

View file

@ -1,37 +1,4 @@
use crate::*;
use std::sync::{Arc, RwLock};
use std::collections::BTreeMap;
pub use clojure_reader::edn::Edn;
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
/// EDN parsing helper.
#[macro_export] macro_rules! edn {
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
match $edn { $($pat => $expr),* }
};
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
for $edn in $args {
edn!($edn { $($pat => $expr),* })
}
};
}
pub trait FromEdn<C>: Sized {
const ID: &'static str;
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
}
/// Implements the [FromEdn] trait.
#[macro_export] macro_rules! from_edn {
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
impl FromEdn<$Context> for $T {
const ID: &'static str = $id;
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
$body
}
}
}
}
from_edn!("sampler" => |jack: &Arc<RwLock<JackConnection>>, args| -> crate::Sampler {
let mut name = String::new();

View file

@ -12,6 +12,7 @@ pub(crate) use ::tek_layout::{
Output, Content, Thunk, render,
Input, Handle, handle,
kexp, kpat,
edn::*,
tui::{
Tui,
TuiIn, key, ctrl, shift, alt,

View file

@ -15,12 +15,13 @@ pub trait HasPlayPhrase: HasClock {
None
}
}
fn pulses_since_start_looped (&self) -> Option<f64> {
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase
let times = (elapsed as usize / length) as f64;
let elapsed = (elapsed as usize % length) as f64;
Some(elapsed)
Some((times, elapsed))
} else {
None
}

View file

@ -25,7 +25,9 @@ impl ClipSelected {
name,
color,
time: state.pulses_since_start_looped()
.map(|elapsed|format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)))
.map(|(times, time)|format!("{:>3}x {:>}",
times+1.0,
state.clock().timebase.format_beats_1(time)))
.unwrap_or_else(||String::from(" "))
}
}
@ -44,7 +46,7 @@ impl ClipSelected {
let current = state.clock().playhead.pulse.get();
if target > current {
let remaining = target - current;
format!("-{:>}", state.clock().timebase.format_beats_0(remaining))
format!("-{:>}", state.clock().timebase.format_beats_1(remaining))
} else {
String::new()
}

View file

@ -7,7 +7,7 @@ render!(Tui: (self: PoolView<'a>) => {
let color = self.1.phrase().read().unwrap().color;
Outer(
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
).enclose(Tui::map(||model.phrases().iter(), |clip, i|{
).enclose(Map(||model.phrases().iter(), |clip, i|{
let item_height = 1;
let item_offset = i as u16 * item_height;
let selected = i == model.phrase_index();
@ -20,8 +20,8 @@ render!(Tui: (self: PoolView<'a>) => {
offset(Tui::bg(if selected { color.light.rgb } else { color.base.rgb }, lay!(
Align::w(Tui::fg(color.lightest.rgb, Tui::bold(selected, name))),
Align::e(Tui::fg(color.lightest.rgb, Tui::bold(selected, length))),
Align::w(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
Align::e(Tui::when(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "")))),
)))
}))
});

View file

@ -17,7 +17,7 @@ render!(Tui: (self: SampleList<'a>) => {
let note_lo = editor.note_lo().load(Relaxed);
let note_pt = editor.note_point();
let note_hi = editor.note_hi();
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Tui::map(move||(note_lo..=note_hi).rev(), move|note, i| {
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map(move||(note_lo..=note_hi).rev(), move|note, i| {
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));

View file

@ -47,38 +47,27 @@ from_jack!(|jack|SequencerTui {
clock,
}
});
render!(Tui: (self: SequencerTui) => {
let w =
self.size.w();
let phrase_w =
if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let color = self.player.play_phrase().as_ref().map(|(_,p)|
p.as_ref().map(|p|p.read().unwrap().color)
).flatten().clone();
let toolbar = Tui::when(self.transport,
TransportView::new(true, &self.clock));
let selectors = Tui::when(self.selectors,
Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
let pool_w =
if self.pool.visible { phrase_w } else { 0 };
let pool =
Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
let edit_clip =
MidiEditClip(&self.editor);
self.size.of(Bsp::s(
toolbar,
Bsp::s(
lay!(Align::w(edit_clip), Align::e(selectors)),
Bsp::n(
Align::x(Fixed::y(1, MidiEditStatus(&self.editor))),
Bsp::w(
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool)))),
Fill::xy(&self.editor),
),
)
)
))
});
render!(Tui: (self: SequencerTui) => self.size.of(
Bsp::s(self.toolbar_view(),
Bsp::n(self.status_view(),
Bsp::w(self.pool_view(), Fill::xy(&self.editor))))));
impl SequencerTui {
fn toolbar_view (&self) -> impl Content<Tui> + use<'_> {
self.transport.then(||TransportView::new(true, &self.clock))
}
fn status_view (&self) -> impl Content<Tui> + use<'_> {
let edit_clip = MidiEditClip(&self.editor);
let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
row!(selectors, edit_clip, MidiEditStatus(&self.editor))
}
fn pool_view (&self) -> impl Content<Tui> + use<'_> {
let w = self.size.w();
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let pool_w = if self.pool.visible { phrase_w } else { 0 };
let pool = Pull::y(1, Fill::y(Align::e(PoolView(self.pool.visible, &self.pool))));
Fixed::x(pool_w, Align::e(Fill::y(PoolView(self.compact, &self.pool))))
}
}
audio!(|self:SequencerTui, client, scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();