collect crates/ and deps/

This commit is contained in:
🪞👃🪞 2025-04-19 01:23:43 +03:00
parent 2f8882f6cd
commit 8fa0f8a409
140 changed files with 23 additions and 21 deletions

View file

@ -1,28 +0,0 @@
[package]
name = "tek"
edition = "2021"
version = "0.2.0"
[dependencies]
tengri = { workspace = true }
tek_jack = { workspace = true }
tek_time = { workspace = true }
tek_midi = { workspace = true }
tek_sampler = { workspace = true }
tek_plugin = { workspace = true, optional = true }
backtrace = { workspace = true }
clap = { workspace = true, optional = true }
palette = { workspace = true }
rand = { workspace = true }
toml = { workspace = true }
[dev-dependencies]
proptest = { workspace = true }
proptest-derive = { workspace = true }
[features]
default = ["cli"]
cli = ["clap"]
host = ["tek_plugin"]

View file

View file

@ -1,12 +0,0 @@
(mixer
(track
(name "Drums")
(sampler
(dir "/home/user/Lab/Music/pak")
(sample (midi 34) (name "808 D") (file "808.wav"))))
(track
(name "Lead")
(lv2
(name "Odin2")
(path "file:///home/user/.lv2/Odin2.lv2"))
(gain 0.0)))

View file

@ -1,18 +0,0 @@
(arranger
(track
(name "Drums")
(phrase
(name "4 kicks")
(beats 4)
(steps 16)
(:00 (36 128))
(:04 (36 100))
(:08 (36 100))
(:12 (36 100))))
(track
(name "Bass")
(phrase
(beats 4)
(steps 16)
(:04 (36 100))
(:12 (36 100)))))

View file

@ -1,201 +0,0 @@
use crate::*;
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
//":inputs" => self.view_inputs().boxed(),
//":outputs" => self.view_outputs().boxed(),
//":scene-add" => self.view_scene_add().boxed(),
//":scenes" => self.view_scenes().boxed(),
//":tracks" => self.view_tracks().boxed(),
":nil" => Box::new("nil"),
":transport" => self.view_transport().boxed(),
":arranger" => ArrangerView::new(self).boxed(),
":editor" => self.editor.as_ref().map(|e|Bsp::e(e.clip_status(), e.edit_status())).boxed(),
":sample" => ().boxed(),//self.view_sample(self.is_editing()).boxed(),
":sampler" => ().boxed(),//self.view_sampler(self.is_editing(), &self.editor).boxed(),
":status" => self.view_status().boxed(),
":pool" => self.pool.as_ref()
.map(|pool|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), pool)))
.boxed(),
});
expose!([self: Tek] {
[bool] => {}
[u16] => {
":h-ins" => self.h_inputs(),
":h-outs" => self.h_outputs(),
":h-sample" => if self.is_editing() { 0 } else { 5 },
":w-samples" => if self.is_editing() { 4 } else { 11 },
":w-sidebar" => self.w_sidebar(),
":y-ins" => (self.size.h() as u16).saturating_sub(self.h_inputs() + 1),
":y-outs" => (self.size.h() as u16).saturating_sub(self.h_outputs() + 1),
":y-samples" => if self.is_editing() { 1 } else { 0 },
}
[usize] => {
":scene-last" => self.scenes.len(),
":track-last" => self.tracks.len(),
}
[isize] => {}
[Option<usize>] => {
":scene" => self.selected.scene(),
":track" => self.selected.track(),
}
[Color] => {}
[Arc<RwLock<MidiClip>>] => {}
[Option<Arc<RwLock<MidiClip>>>] => {
":clip" => match self.selected {
Selection::Clip(t, s) => self.scenes[s].clips[t].clone(),
_ => None
}
}
[Selection] => {
":scene-next" => match self.selected {
Selection::Mix => Selection::Scene(0),
Selection::Track(t) => Selection::Clip(t, 0),
Selection::Scene(s) if s + 1 < self.scenes.len() => Selection::Scene(s + 1),
Selection::Scene(s) => Selection::Mix,
Selection::Clip(t, s) if s + 1 < self.scenes.len() => Selection::Clip(t, s + 1),
Selection::Clip(t, s) => Selection::Track(t),
},
":scene-prev" => match self.selected {
Selection::Mix => Selection::Mix,
Selection::Track(t) => Selection::Track(t),
Selection::Scene(0) => Selection::Mix,
Selection::Scene(s) => Selection::Scene(s - 1),
Selection::Clip(t, 0) => Selection::Track(t),
Selection::Clip(t, s) => Selection::Clip(t, s - 1),
},
":track-next" => match self.selected {
Selection::Mix => Selection::Track(0),
Selection::Track(t) if t + 1 < self.tracks.len() => Selection::Track(t + 1),
Selection::Track(t) => Selection::Mix,
Selection::Scene(s) => Selection::Clip(0, s),
Selection::Clip(t, s) if t + 1 < self.tracks.len() => Selection::Clip(t + 1, s),
Selection::Clip(t, s) => Selection::Scene(s),
},
":track-prev" => match self.selected {
Selection::Mix => Selection::Mix,
Selection::Scene(s) => Selection::Scene(s),
Selection::Track(0) => Selection::Mix,
Selection::Track(t) => Selection::Track(t - 1),
Selection::Clip(0, s) => Selection::Scene(s),
Selection::Clip(t, s) => Selection::Clip(t - 1, s),
},
}
});
impose!([app: Tek] {
TekCommand => {
("stop" []
Some(Self::StopAll))
("undo" [d: usize]
Some(Self::History(-(d.unwrap_or(0)as isize))))
("redo" [d: usize]
Some(Self::History(d.unwrap_or(0) as isize)))
("zoom" [z: usize]
Some(Self::Zoom(z)))
("edit" []
Some(Self::Edit(None)))
("edit" [c: bool]
Some(Self::Edit(c)))
("color" [c: Color]
Some(Self::Color(ItemPalette::random())))
("color" [c: Color]
Some(Self::Color(c.map(ItemPalette::from).expect("no color"))))
("enqueue" [c: Arc<RwLock<MidiClip>>]
Some(Self::Enqueue(c)))
("launch" []
Some(Self::Launch))
("clip" [,..a]
ClipCommand::try_from_expr(app, a).map(Self::Clip))
("clock" [,..a]
ClockCommand::try_from_expr(app.clock(), a).map(Self::Clock))
("editor" [,..a]
MidiEditCommand::try_from_expr(app.editor.as_ref().expect("no editor"), a).map(Self::Editor))
("pool" [,..a]
PoolCommand::try_from_expr(app.pool.as_ref().expect("no pool"), a).map(Self::Pool))
//("sampler" [,..a]
// Self::Sampler( //SamplerCommand::try_from_expr(app.sampler().as_ref().expect("no sampler"), a).expect("invalid command")))
("scene" [,..a]
SceneCommand::try_from_expr(app, a).map(Self::Scene))
("track" [,..a]
TrackCommand::try_from_expr(app, a).map(Self::Track))
("input" [,..a]
InputCommand::try_from_expr(app, a).map(Self::Input))
("output" [,..a]
OutputCommand::try_from_expr(app, a).map(Self::Output))
("select" [t: Selection]
Some(t.map(Self::Select).expect("no selection")))
("select" [t: usize, s: usize]
Some(match (t.expect("no track"), s.expect("no scene")) {
(0, 0) => Self::Select(Selection::Mix),
(t, 0) => Self::Select(Selection::Track(t)),
(0, s) => Self::Select(Selection::Scene(s)),
(t, s) => Self::Select(Selection::Clip(t, s)),
}))
}
ClipCommand => {
("get" [a: usize, b: usize]
Some(Self::Get(a.unwrap(), b.unwrap())))
("put" [a: usize, b: usize, c: Option<Arc<RwLock<MidiClip>>>]
Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
("enqueue" [a: usize, b: usize]
Some(Self::Enqueue(a.unwrap(), b.unwrap())))
("edit" [a: Option<Arc<RwLock<MidiClip>>>]
Some(Self::Edit(a.unwrap())))
("loop" [a: usize, b: usize, c: bool]
Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
("color" [a: usize, b: usize]
Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemPalette::random())))
}
InputCommand => {
("add" [] Some(Self::Add))
}
OutputCommand => {
("add" [] Some(Self::Add))
}
SceneCommand => {
("add" []
Some(Self::Add))
("del" [a: usize]
Some(Self::Del(0)))
("zoom" [a: usize]
Some(Self::SetZoom(a.unwrap())))
("color" [a: usize]
Some(Self::SetColor(a.unwrap(), ItemPalette::G[128])))
("enqueue" [a: usize]
Some(Self::Enqueue(a.unwrap())))
("swap" [a: usize, b: usize]
Some(Self::Swap(a.unwrap(), b.unwrap())))
}
TrackCommand => {
("add" []
Some(Self::Add))
("size" [a: usize]
Some(Self::SetSize(a.unwrap())))
("zoom" [a: usize]
Some(Self::SetZoom(a.unwrap())))
("color" [a: usize]
Some(Self::SetColor(a.unwrap(), ItemPalette::random())))
("del" [a: usize]
Some(Self::Del(a.unwrap())))
("stop" [a: usize]
Some(Self::Stop(a.unwrap())))
("swap" [a: usize, b: usize]
Some(Self::Swap(a.unwrap(), b.unwrap())))
("play" []
Some(Self::TogglePlay))
("solo" []
Some(Self::ToggleSolo))
("rec" []
Some(Self::ToggleRecord))
("mon" []
Some(Self::ToggleMonitor))
}
});

View file

@ -1,95 +0,0 @@
use crate::*;
impl HasJack for Tek { fn jack (&self) -> &Jack { &self.jack } }
audio!(
|self: Tek, client, scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();
// Update transport clock
self.clock().update_from_scope(scope).unwrap();
// Collect MIDI input (TODO preallocate)
let midi_in = self.midi_ins.iter()
.map(|port|port.port().iter(scope)
.map(|RawMidi { time, bytes }|(time, LiveEvent::parse(bytes)))
.collect::<Vec<_>>())
.collect::<Vec<_>>();
// Update standalone MIDI sequencer
//if let Some(player) = self.player.as_mut() {
//if Control::Quit == PlayerAudio(
//player,
//&mut self.note_buf,
//&mut self.midi_buf,
//).process(client, scope) {
//return Control::Quit
//}
//}
// Update standalone sampler
//if let Some(sampler) = self.sampler.as_mut() {
//if Control::Quit == SamplerAudio(sampler).process(client, scope) {
//return Control::Quit
//}
//for port in midi_in.iter() {
//for message in port.iter() {
//match message {
//Ok(M
//}
//}
//}
//}
// TODO move these to editor and sampler?:
//for port in midi_in.iter() {
//for event in port.iter() {
//match event {
//(time, Ok(LiveEvent::Midi {message, ..})) => match message {
//MidiMessage::NoteOn {ref key, ..} if let Some(editor) = self.editor.as_ref() => {
//editor.set_note_pos(key.as_int() as usize);
//},
//MidiMessage::Controller {controller, value} if let (Some(editor), Some(sampler)) = (
//self.editor.as_ref(),
//self.sampler.as_ref(),
//) => {
//// TODO: give sampler its own cursor
//if let Some(sample) = &sampler.mapped[editor.note_pos()] {
//sample.write().unwrap().handle_cc(*controller, *value)
//}
//}
//_ =>{}
//},
//_ =>{}
//}
//}
//}
// Update track sequencers
for track in self.tracks.iter_mut() {
if PlayerAudio(
track.player_mut(), &mut self.note_buf, &mut self.midi_buf
).process(client, scope) == Control::Quit {
return Control::Quit
}
}
// End profiling cycle
self.perf.update_from_jack_scope(t0, scope);
Control::Continue
};
|self, event|{
use JackEvent::*;
match event {
SampleRate(sr) => { self.clock.timebase.sr.set(sr as f64); },
PortRegistration(id, true) => {
//let port = self.jack().port_by_id(id);
//println!("\rport add: {id} {port:?}");
//println!("\rport add: {id}");
},
PortRegistration(id, false) => {
/*println!("\rport del: {id}")*/
},
PortsConnected(a, b, true) => { /*println!("\rport conn: {a} {b}")*/ },
PortsConnected(a, b, false) => { /*println!("\rport disc: {a} {b}")*/ },
ClientRegistration(id, true) => {},
ClientRegistration(id, false) => {},
ThreadInit => {},
XRun => {},
GraphReorder => {},
_ => { panic!("{event:?}"); }
}
}
);

View file

@ -1,10 +0,0 @@
use crate::*;
pub trait Device: Send + Sync + std::fmt::Debug {
fn boxed <'a> (self) -> Box<dyn Device + 'a> where Self: Sized + 'a { Box::new(self) }
}
impl Device for Sampler {}
#[cfg(feature = "host")]
impl Device for Plugin {}

View file

@ -1,177 +0,0 @@
use crate::*;
mod keys_clip; pub use self::keys_clip::*;
mod keys_ins; pub use self::keys_ins::*;
mod keys_outs; pub use self::keys_outs::*;
mod keys_scene; pub use self::keys_scene::*;
mod keys_track; pub use self::keys_track::*;
handle!(TuiIn: |self: Tek, input|Ok({
// If editing, editor keys take priority
if self.is_editing() {
if self.editor.handle(input)? == Some(true) {
return Ok(Some(true))
}
}
// Handle from root keymap
if let Some(command) = self.keys.command::<_, TekCommand, _>(self, input) {
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
return Ok(Some(true))
}
// Handle from selection-dependent keymaps
if let Some(command) = match self.selected() {
Selection::Clip(_, _) => self.keys_clip,
Selection::Track(_) => self.keys_track,
Selection::Scene(_) => self.keys_scene,
Selection::Mix => self.keys_mix,
}.command::<_, TekCommand, _>(self, input) {
if let Some(undo) = command.execute(self)? { self.history.push(undo); }
return Ok(Some(true))
}
None
}));
#[derive(Clone, Debug)] pub enum TekCommand {
Clip(ClipCommand),
Clock(ClockCommand),
Color(ItemPalette),
Edit(Option<bool>),
Editor(MidiEditCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
History(isize),
Input(InputCommand),
Launch,
Output(OutputCommand),
Pool(PoolCommand),
Sampler(SamplerCommand),
Scene(SceneCommand),
Select(Selection),
StopAll,
Track(TrackCommand),
Zoom(Option<usize>),
}
command!(|self: TekCommand, app: Tek|match self {
Self::Zoom(_) => { println!("\n\rtodo: global zoom"); None },
Self::History(delta) => { println!("\n\rtodo: undo/redo"); None },
Self::Select(s) => {
app.selected = s;
// autoedit: load focused clip in editor.
if let Some(ref mut editor) = app.editor {
editor.set_clip(match app.selected {
Selection::Clip(t, s) if let Some(Some(Some(clip))) = app
.scenes.get(s).map(|s|s.clips.get(t)) => Some(clip),
_ => None
});
}
None
},
Self::Edit(value) => {
if let Some(value) = value {
if app.is_editing() != value {
app.editing.store(value, Relaxed);
}
} else {
app.editing.store(!app.is_editing(), Relaxed);
};
// autocreate: create new clip from pool when entering empty cell
if let Some(ref pool) = app.pool {
if app.is_editing() {
if let Selection::Clip(t, s) = app.selected {
if let Some(scene) = app.scenes.get_mut(s) {
if let Some(slot) = scene.clips.get_mut(t) {
if slot.is_none() {
let (index, mut clip) = pool.add_new_clip();
// autocolor: new clip colors from scene and track color
clip.write().unwrap().color = ItemColor::random_near(
app.tracks[t].color.base.mix(
scene.color.base,
0.5
),
0.2
).into();
if let Some(ref mut editor) = app.editor {
editor.set_clip(Some(&clip));
}
*slot = Some(clip);
}
}
}
}
}
}
None
},
Self::Clock(cmd) => cmd.delegate(app, Self::Clock)?,
Self::Scene(cmd) => cmd.delegate(app, Self::Scene)?,
Self::Track(cmd) => cmd.delegate(app, Self::Track)?,
Self::Input(cmd) => cmd.delegate(app, Self::Input)?,
Self::Output(cmd) => cmd.delegate(app, Self::Output)?,
Self::Clip(cmd) => cmd.delegate(app, Self::Clip)?,
Self::Editor(cmd) => app.editor.as_mut()
.map(|editor|cmd.delegate(editor, Self::Editor)).transpose()?.flatten(),
//Self::Sampler(cmd) => app.sampler.as_mut()
//.map(|sampler|cmd.delegate(sampler, Self::Sampler)).transpose()?.flatten(),
//Self::Enqueue(clip) => app.player.as_mut()
//.map(|player|{player.enqueue_next(clip.as_ref());None}).flatten(),
Self::Launch => {
use Selection::*;
match app.selected {
Track(t) => app.tracks[t].player.enqueue_next(None),
Clip(t, s) => app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()),
Scene(s) => {
for t in 0..app.tracks.len() {
app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref())
}
},
_ => {}
};
None
},
Self::Color(palette) => {
use Selection::*;
Some(Self::Color(match app.selected {
Mix => {
let old = app.color;
app.color = palette;
old
},
Track(t) => {
let old = app.tracks[t].color;
app.tracks[t].color = palette;
old
}
Scene(s) => {
let old = app.scenes[s].color;
app.scenes[s].color = palette;
old
}
Clip(t, s) => {
if let Some(ref clip) = app.scenes[s].clips[t] {
let mut clip = clip.write().unwrap();
let old = clip.color;
clip.color = palette;
old
} else {
return Ok(None)
}
}
}))
},
Self::StopAll => {
for track in 0..app.tracks.len(){app.tracks[track].player.enqueue_next(None);}
None
},
Self::Pool(cmd) => if let Some(pool) = app.pool.as_mut() {
let undo = cmd.clone().delegate(pool, Self::Pool)?;
if let Some(editor) = app.editor.as_mut() {
match cmd {
// autoselect: automatically load selected clip in editor
// autocolor: update color in all places simultaneously
PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) =>
editor.set_clip(pool.clip().as_ref()),
_ => {}
}
};
undo
} else {
None
},
_ => todo!("{self:?}")
});

View file

@ -1,31 +0,0 @@
use crate::*;
#[derive(Clone, Debug)] pub enum ClipCommand {
Get(usize, usize),
Put(usize, usize, Option<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize),
Edit(Option<Arc<RwLock<MidiClip>>>),
SetLoop(usize, usize, bool),
SetColor(usize, usize, ItemPalette),
}
command!(|self: ClipCommand, app: Tek|match self {
Self::Get(track, scene) => { todo!() },
Self::Put(track, scene, clip) => {
let old = app.scenes[scene].clips[track].clone();
app.scenes[scene].clips[track] = clip;
Some(Self::Put(track, scene, old))
},
Self::Enqueue(track, scene) => {
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
None
},
Self::SetColor(track, scene, color) => {
app.scenes[scene].clips[track].as_ref().map(|clip|{
let mut clip = clip.write().unwrap();
let old = clip.color.clone();
clip.color = color.clone();
panic!("{color:?} {old:?}");
Self::SetColor(track, scene, old)
})
},
_ => None
});

View file

@ -1,8 +0,0 @@
use crate::*;
#[derive(Clone, Debug)] pub enum InputCommand { Add }
command!(|self: InputCommand, app: Tek|match self {
Self::Add => {
app.midi_ins.push(JackMidiIn::new(&app.jack, &format!("M/{}", app.midi_ins.len()), &[])?);
None
},
});

View file

@ -1,8 +0,0 @@
use crate::*;
#[derive(Clone, Debug)] pub enum OutputCommand { Add }
command!(|self: OutputCommand, app: Tek|match self {
Self::Add => {
app.midi_outs.push(JackMidiOut::new(&app.jack, &format!("{}/M", app.midi_outs.len()), &[])?);
None
},
});

View file

@ -1,35 +0,0 @@
use crate::*;
#[derive(Clone, Debug)] pub enum SceneCommand {
Add,
Del(usize),
Swap(usize, usize),
SetSize(usize),
SetZoom(usize),
SetColor(usize, ItemPalette),
Enqueue(usize),
}
command!(|self: SceneCommand, app: Tek|match self {
Self::Add => {
use Selection::*;
let index = app.scene_add(None, None)?.0;
app.selected = match app.selected {
Scene(s) => Scene(index),
Clip(t, s) => Clip(t, index),
_ => app.selected
};
Some(Self::Del(index))
},
Self::Del(index) => { app.scene_del(index); None },
Self::SetColor(index, color) => {
let old = app.scenes[index].color;
app.scenes[index].color = color;
Some(Self::SetColor(index, old))
},
Self::Enqueue(scene) => {
for track in 0..app.tracks.len() {
app.tracks[track].player.enqueue_next(app.scenes[scene].clips[track].as_ref());
}
None
},
_ => None
});

View file

@ -1,52 +0,0 @@
use crate::*;
#[derive(Clone, Debug)] pub enum TrackCommand {
Add,
Del(usize),
Stop(usize),
Swap(usize, usize),
SetSize(usize),
SetZoom(usize),
SetColor(usize, ItemPalette),
TogglePlay,
ToggleSolo,
ToggleRecord,
ToggleMonitor,
}
command!(|self: TrackCommand, app: Tek|match self {
Self::Add => {
use Selection::*;
let index = app.track_add(None, None, &[], &[])?.0;
app.selected = match app.selected {
Track(t) => Track(index),
Clip(t, s) => Clip(index, s),
_ => app.selected
};
Some(Self::Del(index))
},
Self::Del(index) => { app.track_del(index); None },
Self::Stop(track) => { app.tracks[track].player.enqueue_next(None); None },
Self::SetColor(index, color) => {
let old = app.tracks[index].color;
app.tracks[index].color = color;
Some(Self::SetColor(index, old))
},
Self::TogglePlay => {
Some(Self::TogglePlay)
},
Self::ToggleSolo => {
Some(Self::ToggleSolo)
},
Self::ToggleRecord => {
if let Some(t) = app.selected.track() {
app.tracks[t-1].player.recording = !app.tracks[t-1].player.recording;
}
Some(Self::ToggleRecord)
},
Self::ToggleMonitor => {
if let Some(t) = app.selected.track() {
app.tracks[t-1].player.monitoring = !app.tracks[t-1].player.monitoring;
}
Some(Self::ToggleMonitor)
},
_ => None
});

View file

@ -1,50 +0,0 @@
// ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
//██Let me play the world's tiniest piano for you. ██
//█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀█
//█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙█▙▙█▙▙▙██
//█▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█
//███████████████████████████████████████████████████
//█ ▀ ▀ ▀ █
#![allow(unused)]
#![allow(clippy::unit_arg)]
#![feature(adt_const_params)]
#![feature(associated_type_defaults)]
#![feature(if_let_guard)]
#![feature(impl_trait_in_assoc_type)]
#![feature(type_alias_impl_trait)]
#![feature(trait_alias)]
#![feature(type_changing_struct_update)]
/// Standard result type.
pub type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>;
/// Standard optional result type.
pub type Perhaps<T> = std::result::Result<Option<T>, Box<dyn std::error::Error>>;
pub use ::tek_time::{self, *};
pub use ::tek_jack::{self, *, jack::*};
pub use ::tek_midi::{self, *, midly::{MidiMessage, num::*, live::*}};
pub use ::tek_sampler::{self, *};
#[cfg(feature = "host")] pub use ::tek_plugin::{self, *};
pub use ::tengri::dsl::*;
pub use ::tengri::input::*;
pub use ::tengri::output::*;
pub use ::tengri::tui::*;
pub use ::tengri::tui::ratatui;
pub use ::tengri::tui::ratatui::prelude::buffer::Cell;
pub use ::tengri::tui::ratatui::prelude::Color::{self, *};
pub use ::tengri::tui::ratatui::prelude::{Style, Stylize, Buffer, Modifier};
pub use ::tengri::tui::crossterm;
pub use ::tengri::tui::crossterm::event::{Event, KeyCode::{self, *}};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::Relaxed}};
mod api; pub use self::api::*;
mod audio; pub use self::audio::*;
mod device; pub use self::device::*;
mod keys; pub use self::keys::*;
mod model; pub use self::model::*;
mod view; pub use self::view::*;
#[cfg(test)] #[test] fn test_model () {
let mut tek = Tek::default();
let _ = tek.clip();
let _ = tek.toggle_loop();
let _ = tek.activate();
}

View file

@ -1,126 +0,0 @@
use crate::*;
mod model_track; pub use self::model_track::*;
mod model_scene; pub use self::model_scene::*;
mod model_select; pub use self::model_select::*;
#[derive(Default, Debug)] pub struct Tek {
/// Must not be dropped for the duration of the process
pub jack: Jack,
/// Source of time
pub clock: Clock,
/// Theme
pub color: ItemPalette,
/// Contains all clips in the project
pub pool: Option<MidiPool>,
/// Contains the currently edited MIDI clip
pub editor: Option<MidiEditor>,
/// Contains a render of the project arrangement, redrawn on update.
pub arranger: Arc<RwLock<Buffer>>,
/// List of global midi inputs
pub midi_ins: Vec<JackMidiIn>,
/// List of global midi outputs
pub midi_outs: Vec<JackMidiOut>,
/// List of global audio inputs
pub audio_ins: Vec<JackAudioIn>,
/// List of global audio outputs
pub audio_outs: Vec<JackAudioOut>,
/// Buffer for writing a midi event
pub note_buf: Vec<u8>,
/// Buffer for writing a chunk of midi events
pub midi_buf: Vec<Vec<Vec<u8>>>,
/// List of tracks
pub tracks: Vec<Track>,
/// Scroll offset of tracks
pub track_scroll: usize,
/// List of scenes
pub scenes: Vec<Scene>,
/// Scroll offset of scenes
pub scene_scroll: usize,
/// Selected UI element
pub selected: Selection,
/// Display size
pub size: Measure<TuiOut>,
/// Performance counter
pub perf: PerfModel,
/// Whether in edit mode
pub editing: AtomicBool,
/// Undo history
pub history: Vec<TekCommand>,
/// Port handles
pub ports: std::collections::BTreeMap<u32, Port<Unowned>>,
/// View definition
pub view: SourceIter<'static>,
// Input definitions
pub keys: SourceIter<'static>,
// Input definitions when a clip is focused
pub keys_clip: SourceIter<'static>,
// Input definitions when a track is focused
pub keys_track: SourceIter<'static>,
// Input definitions when a scene is focused
pub keys_scene: SourceIter<'static>,
// Input definitions when the mix is focused
pub keys_mix: SourceIter<'static>,
// Cache of formatted strings
pub view_cache: Arc<RwLock<ViewCache>>,
}
impl Tek {
pub(crate) fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.scene()?.clips.get(self.selected().track()?)?.clone()
}
pub(crate) fn toggle_loop (&mut self) {
if let Some(clip) = self.clip() {
clip.write().unwrap().toggle_loop()
}
}
pub(crate) fn activate (&mut self) -> Usually<()> {
let selected = self.selected().clone();
match selected {
Selection::Scene(s) => {
let mut clips = vec![];
for (t, _) in self.tracks().iter().enumerate() {
clips.push(self.scenes()[s].clips[t].clone());
}
for (t, track) in self.tracks_mut().iter_mut().enumerate() {
if track.player.play_clip.is_some() || clips[t].is_some() {
track.player.enqueue_next(clips[t].as_ref());
}
}
if self.clock().is_stopped() {
self.clock().play_from(Some(0))?;
}
},
Selection::Clip(t, s) => {
let clip = self.scenes()[s].clips[t].clone();
self.tracks_mut()[t].player.enqueue_next(clip.as_ref());
},
_ => {}
}
Ok(())
}
}
has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock);
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
has_editor!(|self: Tek|{
editor = self.editor;
editor_w = {
let size = self.size.w();
let editor = self.editor.as_ref().expect("missing editor");
let time_len = editor.time_len().get();
let time_zoom = editor.time_zoom().get().max(1);
(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
};
editor_h = 15;
is_editing = self.editing.load(Relaxed);
});
//has_sampler!(|self: Tek|{
//sampler = self.sampler;
//index = self.editor.as_ref().map(|e|e.note_pos()).unwrap_or(0);
//});

View file

@ -1,97 +0,0 @@
use crate::*;
impl Tek {
pub fn scenes_add (&mut self, n: usize) -> Usually<()> {
let scene_color_1 = ItemColor::random();
let scene_color_2 = ItemColor::random();
for i in 0..n {
let _ = self.scene_add(None, Some(
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
))?;
}
Ok(())
}
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
-> Usually<(usize, &mut Scene)>
{
let scene = Scene {
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
clips: vec![None;self.tracks().len()],
color: color.unwrap_or_else(ItemPalette::random),
};
self.scenes_mut().push(scene);
let index = self.scenes().len() - 1;
Ok((index, &mut self.scenes_mut()[index]))
}
pub fn scene_default_name (&self) -> Arc<str> {
format!("Sc{:3>}", self.scenes().len() + 1).into()
}
}
pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
fn scene_longest (&self) -> usize {
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
}
fn scene (&self) -> Option<&Scene> {
self.selected().scene().and_then(|s|self.scenes().get(s))
}
fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s))
}
fn scene_del (&mut self, index: usize) {
self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index)));
}
}
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// Identifying color of scene
pub color: ItemPalette,
}
impl Scene {
/// Returns the pulse length of the longest clip in the scene
fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
})
}
/// Returns true if all clips in the scene are
/// currently playing on the given collection of tracks.
fn is_playing (&self, tracks: &[Track]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
.all(|(track_index, clip)|match clip {
Some(c) => tracks
.get(track_index)
.map(|track|{
if let Some((_, Some(clip))) = track.player().play_clip() {
*clip.read().unwrap() == *c.read().unwrap()
} else {
false
}
})
.unwrap_or(false),
None => true
})
}
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}
impl HasScenes for Tek {
fn scenes (&self) -> &Vec<Scene> { &self.scenes }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
}
#[cfg(test)] #[test] fn test_model_scene () {
let mut app = Tek::default();
let _ = app.scene_longest();
let _ = app.scene();
let _ = app.scene_mut();
let _ = app.scene_add(None, None);
app.scene_del(0);
let scene = Scene::default();
let _ = scene.pulses();
let _ = scene.is_playing(&[]);
}

View file

@ -1,51 +0,0 @@
use crate::*;
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl Selection {
fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
}
pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
format!("{}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
(Some(_), Some(scene)) => match scene.clip(*t) {
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
None => format!("T{t} S{s}: Empty")
},
_ => format!("T{t} S{s}: Empty"),
}
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}

View file

@ -1,103 +0,0 @@
use crate::*;
impl Tek {
pub fn tracks_add (
&mut self, count: usize, width: Option<usize>,
midi_from: &[PortConnect], midi_to: &[PortConnect],
) -> Usually<()> {
let jack = self.jack().clone();
let track_color_1 = ItemColor::random();
let track_color_2 = ItemColor::random();
for i in 0..count {
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
let mut track = self.track_add(None, Some(color), midi_from, midi_to)?.1;
if let Some(width) = width {
track.width = width;
}
}
Ok(())
}
pub fn track_add (
&mut self, name: Option<&str>, color: Option<ItemPalette>,
midi_froms: &[PortConnect],
midi_tos: &[PortConnect],
) -> Usually<(usize, &mut Track)> {
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
let mut track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemPalette::random),
player: MidiPlayer::new(
&format!("{name}"),
self.jack(),
Some(self.clock()),
None,
midi_froms,
midi_tos
)?,
name,
..Default::default()
};
self.tracks_mut().push(track);
let len = self.tracks().len();
let index = len - 1;
for scene in self.scenes_mut().iter_mut() {
while scene.clips.len() < len {
scene.clips.push(None);
}
}
Ok((index, &mut self.tracks_mut()[index]))
}
pub fn track_del (&mut self, index: usize) {
self.tracks_mut().remove(index);
for scene in self.scenes_mut().iter_mut() {
scene.clips.remove(index);
}
}
}
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_outs (&self) -> &Vec<JackMidiOut>;
fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn track_longest (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
const WIDTH_OFFSET: usize = 1;
fn track_next_name (&self) -> Arc<str> {
format!("Track{:02}", self.tracks().len() + 1).into()
}
fn track (&self) -> Option<&Track> {
self.selected().track().and_then(|s|self.tracks().get(s))
}
fn track_mut (&mut self) -> Option<&mut Track> {
self.selected().track().and_then(|s|self.tracks_mut().get_mut(s))
}
}
#[derive(Debug, Default)] pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemPalette,
/// MIDI player state
pub player: MidiPlayer,
/// Device chain
pub devices: Vec<Box<dyn Device>>,
/// Inputs of 1st device
pub audio_ins: Vec<JackAudioIn>,
/// Outputs of last device
pub audio_outs: Vec<JackAudioOut>,
}
has_clock!(|self: Track|self.player.clock);
has_player!(|self: Track|self.player);
impl Track {
const MIN_WIDTH: usize = 9;
fn width_inc (&mut self) { self.width += 1; }
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
}
impl HasTracks for Tek {
fn midi_ins (&self) -> &Vec<JackMidiIn> { &self.midi_ins }
fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs }
fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
}

View file

@ -1,282 +0,0 @@
use crate::*;
mod view_clock; pub use self::view_clock::*;
mod view_color; pub use self::view_color::*;
mod view_memo; pub use self::view_memo::*;
mod view_meter; pub use self::view_meter::*;
mod view_track; pub use self::view_track::*;
mod view_ports; pub use self::view_ports::*;
mod view_layout; pub use self::view_layout::*;
pub(crate) use std::fmt::Write;
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
pub(crate) trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
pub(crate) type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemPalette>);
pub(crate) struct ArrangerView<'a> {
app: &'a Tek,
is_editing: bool,
width: u16,
width_mid: u16,
width_side: u16,
inputs_count: usize,
inputs_height: u16,
outputs_count: usize,
outputs_height: u16,
scene_last: usize,
scene_count: usize,
scene_scroll: Fill<Fixed<u16, ScrollbarV>>,
scene_selected: Option<usize>,
scenes_height: u16,
track_scroll: Fill<Fixed<u16, ScrollbarH>>,
track_count: usize,
track_selected: Option<usize>,
tracks_height: u16,
show_debug_info: bool,
}
impl<'a> Content<TuiOut> for ArrangerView<'a> {
fn content (&self) -> impl Render<TuiOut> {
let ins = |x|Bsp::s(self.inputs(), x);
let tracks = |x|Bsp::s(self.tracks(), x);
let outs = |x|Bsp::n(self.outputs(), x);
let bg = |x|Tui::bg(Color::Reset, x);
//let track_scroll = |x|Bsp::s(&self.track_scroll, x);
//let scene_scroll = |x|Bsp::e(&self.scene_scroll, x);
ins(tracks(outs(bg(self.scenes()))))
}
}
impl<'a> ArrangerView<'a> {
pub fn new (app: &'a Tek) -> Self {
Self {
app,
is_editing: app.is_editing(),
width: app.w(),
width_mid: app.w_tracks_area(),
width_side: app.w_sidebar(),
inputs_height: app.h_inputs().saturating_sub(1),
inputs_count: app.midi_ins.len(),
outputs_height: app.h_outputs().saturating_sub(1),
outputs_count: app.midi_outs.len(),
scenes_height: app.h_scenes_area(),
scene_selected: app.selected().scene(),
scene_count: app.scenes.len(),
scene_last: app.scenes.len().saturating_sub(1),
scene_scroll: Fill::y(Fixed::x(1, ScrollbarV {
offset: app.scene_scroll,
length: app.h_scenes_area() as usize,
total: app.h_scenes() as usize,
})),
tracks_height: app.h_tracks_area(),
track_count: app.tracks.len(),
track_selected: app.selected().track(),
track_scroll: Fill::x(Fixed::y(1, ScrollbarH {
offset: app.track_scroll,
length: app.h_tracks_area() as usize,
total: app.h_scenes() as usize,
})),
show_debug_info: false
}
}
pub(crate) fn tracks_with_sizes_scrolled (&'a self)
-> impl TracksSizes<'a>
{
let width = self.width_mid;
self.app.tracks_with_sizes().map_while(move|(t, track, x1, x2)|{
(width > x2 as u16).then_some((t, track, x1, x2))
})
}
pub(crate) fn scenes_with_scene_colors (&self)
-> impl ScenesColors<'_>
{
self.app.scenes_with_sizes(self.is_editing, Tek::H_SCENE, Tek::H_EDITOR).map_while(
move|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height {
None
} else { Some((s, scene, y1, y2, if s == 0 {
None
} else {
Some(self.app.scenes()[s-1].color)
}))
})
}
pub(crate) fn scenes_with_track_colors (&self)
-> impl ScenesColors<'_>
{
self.app.scenes_with_sizes(self.is_editing, Tek::H_SCENE, Tek::H_EDITOR).map_while(
move|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height {
None
} else {
Some((s, scene, y1, y2, if s == 0 {
None
} else {
Some(self.app.scenes[s-1].clips[self.track_selected.unwrap_or(0)].as_ref()
.map(|c|c.read().unwrap().color)
.unwrap_or(ItemPalette::G[32]))
}))
}
)
}
}
impl Tek {
/// Spacing between tracks.
pub(crate) const TRACK_SPACING: usize = 0;
/// Default scene height.
pub(crate) const H_SCENE: usize = 2;
/// Default editor height.
pub(crate) const H_EDITOR: usize = 15;
/// Width of display
pub(crate) fn w (&self) -> u16 {
self.size.w() as u16
}
pub(crate) fn w_sidebar (&self) -> u16 {
self.w() / if self.is_editing() { 16 } else { 8 } as u16
}
/// Width taken by all tracks.
pub(crate) fn w_tracks (&self) -> u16 {
self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0)
}
/// Width available to display tracks.
pub(crate) fn w_tracks_area (&self) -> u16 {
self.w().saturating_sub(2 * self.w_sidebar())
}
/// Height of display
pub(crate) fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height available to display track headers.
pub(crate) fn h_tracks_area (&self) -> u16 {
5
//self.h().saturating_sub(self.h_inputs() + self.h_outputs())
}
/// Height available to display tracks.
pub(crate) fn h_scenes_area (&self) -> u16 {
//15
self.h().saturating_sub(self.h_inputs() + self.h_outputs() + 11)
}
/// Height taken by all inputs.
pub(crate) fn h_inputs (&self) -> u16 {
1 + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all outputs.
pub(crate) fn h_outputs (&self) -> u16 {
1 + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Height taken by all scenes.
pub(crate) fn h_scenes (&self) -> u16 {
self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last()
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
}
pub(crate) fn inputs_with_sizes (&self)
-> impl PortsSizes<'_>
{
let mut y = 0;
self.midi_ins.iter().enumerate().map(move|(i, input)|{
let height = 1 + input.conn().len();
let data = (i, input.name(), input.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn outputs_with_sizes (&self)
-> impl PortsSizes<'_>
{
let mut y = 0;
self.midi_outs.iter().enumerate().map(move|(i, output)|{
let height = 1 + output.conn().len();
let data = (i, output.name(), output.conn(), y, y + height);
y += height;
data
})
}
pub(crate) fn tracks_with_sizes (&self)
-> impl TracksSizes<'_>
{
let mut x = 0;
let editing = self.is_editing();
let active = match self.selected() {
Selection::Track(t) if editing => Some(t),
Selection::Clip(t, _) if editing => Some(t),
_ => None
};
let bigger = self.editor_w();
self.tracks().iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) };
let data = (index, track, x, x + width);
x += width + Tek::TRACK_SPACING;
data
})
}
pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize)
-> impl ScenesSizes<'_>
{
let (selected_track, selected_scene) = match self.selected() {
Selection::Track(t) => (Some(*t), None),
Selection::Scene(s) => (None, Some(*s)),
Selection::Clip(t, s) => (Some(*t), Some(*s)),
_ => (None, None)
};
let mut y = 0;
self.scenes().iter().enumerate().map(move|(s, scene)|{
let active = editing && selected_track.is_some() && selected_scene == Some(s);
let height = if active { larger } else { height };
let data = (s, scene, y, y + height);
y += height;
data
})
}
}
/// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => {
pub(crate) trait $Type<'a> =
Iterator<Item=(usize, $(&'a $Item,)+ usize, usize)> + Send + Sync + 'a;}}
def_sizes_iter!(ScenesSizes => Scene);
def_sizes_iter!(TracksSizes => Track);
def_sizes_iter!(InputsSizes => JackMidiIn);
def_sizes_iter!(OutputsSizes => JackMidiOut);
def_sizes_iter!(PortsSizes => Arc<str>, [PortConnect]);
#[cfg(test)] #[test] fn test_view_iter () {
let mut tek = Tek::default();
tek.editor = Some(Default::default());
let _: Vec<_> = tek.inputs_with_sizes().collect();
let _: Vec<_> = tek.outputs_with_sizes().collect();
let _: Vec<_> = tek.tracks_with_sizes().collect();
let _: Vec<_> = tek.scenes_with_sizes(true, 10, 10).collect();
//let _: Vec<_> = tek.scenes_with_colors(true, 10).collect();
//let _: Vec<_> = tek.scenes_with_track_colors(true, 10, 10).collect();
}
#[cfg(test)] #[test] fn test_view_sizes () {
let app = Tek::default();
let _ = app.w();
let _ = app.w_sidebar();
let _ = app.w_tracks_area();
let _ = app.h();
let _ = app.h_tracks_area();
let _ = app.h_inputs();
let _ = app.h_outputs();
let _ = app.h_scenes();
}

View file

@ -1,88 +0,0 @@
use crate::*;
impl Tek {
fn update_clock (&self) {
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
}
pub(crate) fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_transport(
self.clock.is_rolling(),
cache.bpm.view.clone(),
cache.beat.view.clone(),
cache.time.view.clone(),
)
}
pub(crate) fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
self.update_clock();
let cache = self.view_cache.read().unwrap();
view_status(
self.selected.describe(&self.tracks, &self.scenes),
cache.sr.view.clone(),
cache.buf.view.clone(),
cache.lat.view.clone(),
)
}
}
fn view_transport (
play: bool,
bpm: Arc<RwLock<String>>,
beat: Arc<RwLock<String>>,
time: Arc<RwLock<String>>,
) -> impl Content<TuiOut> {
let theme = ItemPalette::G[96];
Tui::bg(Black, row!(Bsp::a(
Fill::xy(Align::w(button_play_pause(play))),
Fill::xy(Align::e(row!(
FieldH(theme, "BPM", bpm),
FieldH(theme, "Beat", beat),
FieldH(theme, "Time", time),
)))
)))
}
fn view_status (
sel: Arc<str>,
sr: Arc<RwLock<String>>,
buf: Arc<RwLock<String>>,
lat: Arc<RwLock<String>>,
) -> impl Content<TuiOut> {
let theme = ItemPalette::G[96];
Tui::bg(Black, row!(Bsp::a(
Fill::xy(Align::w(FieldH(theme, "Selected", sel))),
Fill::xy(Align::e(row!(
FieldH(theme, "SR", sr),
FieldH(theme, "Buf", buf),
FieldH(theme, "Lat", lat),
)))
)))
}
fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
let compact = true;//self.is_editing();
Tui::bg(
if playing{Rgb(0,128,0)}else{Rgb(128,64,0)},
Either::new(compact,
Thunk::new(move||Fixed::x(9, Either::new(playing,
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
Tui::fg(Rgb(255, 128, 0), " STOPPED ")))
),
Thunk::new(move||Fixed::x(5, Either::new(playing,
Tui::fg(Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
Tui::fg(Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))
)
)
)
}
#[cfg(test)] mod test {
use super::*;
#[test] fn test_view_clock () {
let _ = button_play_pause(true);
let mut app = Tek::default();
let _ = app.view_transport();
let _ = app.view_status();
let _ = app.update_clock();
}
}

View file

@ -1,32 +0,0 @@
use crate::*;
impl Tek {
pub(crate) fn colors (
theme: &ItemPalette,
prev: Option<ItemPalette>,
selected: bool,
neighbor: bool,
is_last: bool,
) -> [Color;4] {
let fg = theme.lightest.rgb;
let bg = if selected { theme.light } else { theme.base }.rgb;
let hi = Self::color_hi(prev, neighbor);
let lo = Self::color_lo(theme, is_last, selected);
[fg, bg, hi, lo]
}
pub(crate) fn color_hi (prev: Option<ItemPalette>, neighbor: bool) -> Color {
prev.map(|prev|if neighbor {
prev.light.rgb
} else {
prev.base.rgb
}).unwrap_or(Reset)
}
pub(crate) fn color_lo (theme: &ItemPalette, is_last: bool, selected: bool) -> Color {
if is_last {
Reset
} else if selected {
theme.light.rgb
} else {
theme.base.rgb
}
}
}

View file

@ -1,178 +0,0 @@
use crate::*;
/// A three-column layout.
pub(crate) struct Tryptich<A, B, C> {
pub top: bool,
pub h: u16,
pub left: (u16, A),
pub middle: (u16, B),
pub right: (u16, C),
}
impl Tryptich<(), (), ()> {
pub fn center (h: u16) -> Self {
Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) }
}
pub fn top (h: u16) -> Self {
Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) }
}
}
impl<A, B, C> Tryptich<A, B, C> {
pub fn left <D> (self, w: u16, content: D) -> Tryptich<D, B, C> {
Tryptich { left: (w, content), ..self }
}
pub fn middle <D> (self, w: u16, content: D) -> Tryptich<A, D, C> {
Tryptich { middle: (w, content), ..self }
}
pub fn right <D> (self, w: u16, content: D) -> Tryptich<A, B, D> {
Tryptich { right: (w, content), ..self }
}
}
impl<A, B, C> Content<TuiOut> for Tryptich<A, B, C>
where A: Content<TuiOut>, B: Content<TuiOut>, C: Content<TuiOut> {
fn content (&self) -> impl Render<TuiOut> {
let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self;
Fixed::y(h, if top {
Bsp::a(
Fill::x(Align::n(Fixed::x(w_b, Align::x(Tui::bg(Reset, b))))),
Bsp::a(
Fill::x(Align::nw(Fixed::x(w_a, Tui::bg(Reset, a)))),
Fill::x(Align::ne(Fixed::x(w_c, Tui::bg(Reset, c)))),
),
)
} else {
Bsp::a(
Fill::xy(Align::c(Fixed::x(w_b, Align::x(Tui::bg(Reset, b))))),
Bsp::a(
Fill::xy(Align::w(Fixed::x(w_a, Tui::bg(Reset, a)))),
Fill::xy(Align::e(Fixed::x(w_c, Tui::bg(Reset, c)))),
),
)
})
}
}
pub(crate) fn wrap (
bg: Color, fg: Color, content: impl Content<TuiOut>
) -> impl Content<TuiOut> {
Bsp::e(Tui::fg_bg(bg, Reset, ""),
Bsp::w(Tui::fg_bg(bg, Reset, ""),
Tui::fg_bg(fg, bg, content)))
}
pub(crate) fn button_2 <'a, K, L> (
key: K, label: L, editing: bool,
) -> impl Content<TuiOut> + 'a where
K: Content<TuiOut> + 'a,
L: Content<TuiOut> + 'a,
{
let key = Tui::fg_bg(Tui::g(0), Tui::orange(), Bsp::e(
Tui::fg_bg(Tui::orange(), Reset, ""),
Bsp::e(key, Tui::fg(Tui::g(96), ""))
));
let label = When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label));
Tui::bold(true, Bsp::e(key, label))
}
pub(crate) fn button_3 <'a, K, L, V> (
key: K,
label: L,
value: V,
editing: bool,
) -> impl Content<TuiOut> + 'a where
K: Content<TuiOut> + 'a,
L: Content<TuiOut> + 'a,
V: Content<TuiOut> + 'a,
{
let key = Tui::fg_bg(Tui::g(0), Tui::orange(),
Bsp::e(Tui::fg_bg(Tui::orange(), Reset, ""), Bsp::e(key, Tui::fg(if editing {
Tui::g(128)
} else {
Tui::g(96)
}, ""))));
let label = Bsp::e(
When::new(!editing, Bsp::e(
Tui::fg_bg(Tui::g(255), Tui::g(96), label),
Tui::fg_bg(Tui::g(128), Tui::g(96), ""),
)),
Bsp::e(
Tui::fg_bg(Tui::g(224), Tui::g(128), value),
Tui::fg_bg(Tui::g(128), Reset, ""),
));
Tui::bold(true, Bsp::e(key, label))
}
pub(crate) fn heading <'a> (
key: &'a str,
label: &'a str,
count: usize,
content: impl Content<TuiOut> + Send + Sync + 'a,
editing: bool,
) -> impl Content<TuiOut> + 'a {
let count = format!("{count}");
Fill::xy(Align::w(Bsp::s(Fill::x(Align::w(button_3(key, label, count, editing))), content)))
}
pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))),
Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
&connect.info)))))))))
}
pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Map::new(iter,
move|(index, name, connections, y, y2): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x(""))))))))))
}
pub(crate) fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
width: u16,
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
Align::x(Tui::bg(Reset, Map::new(tracks,
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
let width = (x2 - x1) as u16;
map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg(
track.color.lightest.rgb,
track.color.base.rgb,
callback(index, track))))})))
}
pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
width: u16,
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
) -> impl Content<TuiOut> + 'a {
per_track_top(
width,
tracks,
move|index, track|Fill::y(Align::y(callback(index, track)))
)
}
#[cfg(test)] mod test {
use super::*;
#[test] fn test_view () {
let _ = button_2("", "", true);
let _ = button_2("", "", false);
let _ = button_3("", "", "", true);
let _ = button_3("", "", "", false);
let _ = heading("", "", 0, "", true);
let _ = heading("", "", 0, "", false);
let _ = wrap(Reset, Reset, "");
}
}

View file

@ -1,120 +0,0 @@
use crate::*;
/// Clear a pre-allocated buffer, then write into it.
#[macro_export] macro_rules! rewrite {
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
}
#[derive(Debug, Default)] pub(crate) struct ViewMemo<T, U> {
pub(crate) value: T,
pub(crate) view: Arc<RwLock<U>>
}
impl<T: PartialEq, U> ViewMemo<T, U> {
fn new (value: T, view: U) -> Self {
Self { value, view: Arc::new(view.into()) }
}
pub(crate) fn update <R> (
&mut self,
newval: T,
render: impl Fn(&mut U, &T, &T)->R
) -> Option<R> {
if newval != self.value {
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
self.value = newval;
return Some(result);
}
None
}
}
#[derive(Debug)] pub struct ViewCache {
pub(crate) sr: ViewMemo<Option<(bool, f64)>, String>,
pub(crate) buf: ViewMemo<Option<f64>, String>,
pub(crate) lat: ViewMemo<Option<f64>, String>,
pub(crate) bpm: ViewMemo<Option<f64>, String>,
pub(crate) beat: ViewMemo<Option<f64>, String>,
pub(crate) time: ViewMemo<Option<f64>, String>,
pub(crate) scns: ViewMemo<Option<(usize, usize)>, String>,
pub(crate) trks: ViewMemo<Option<(usize, usize)>, String>,
pub(crate) stop: Arc<str>,
pub(crate) edit: Arc<str>,
}
impl Default for ViewCache {
fn default () -> Self {
let mut beat = String::with_capacity(16);
write!(beat, "{}", Self::BEAT_EMPTY);
let mut time = String::with_capacity(16);
write!(time, "{}", Self::TIME_EMPTY);
let mut bpm = String::with_capacity(16);
write!(bpm, "{}", Self::BPM_EMPTY);
Self {
beat: ViewMemo::new(None, beat),
time: ViewMemo::new(None, time),
bpm: ViewMemo::new(None, bpm),
sr: ViewMemo::new(None, String::with_capacity(16)),
buf: ViewMemo::new(None, String::with_capacity(16)),
lat: ViewMemo::new(None, String::with_capacity(16)),
scns: ViewMemo::new(None, String::with_capacity(16)),
trks: ViewMemo::new(None, String::with_capacity(16)),
stop: "".into(),
edit: "edit".into(),
}
}
}
impl ViewCache {
pub const BEAT_EMPTY: &'static str = "-.-.--";
pub const TIME_EMPTY: &'static str = "-.---s";
pub const BPM_EMPTY: &'static str = "---.---";
pub(crate) fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
-> Arc<RwLock<String>>
{
let data = (track, tracks);
cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
cache.read().unwrap().trks.view.clone()
}
pub(crate) fn scene_add (cache: &Arc<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
-> impl Content<TuiOut>
{
let data = (scene, scenes);
cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing)
}
pub(crate) fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
let rate = clock.timebase.sr.get();
let chunk = clock.chunk.load(Relaxed) as f64;
let lat = chunk / rate * 1000.;
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
let mut cache = cache.write().unwrap();
cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
cache.sr.update(Some((compact, rate)), |buf,_,_|{
buf.clear();
if compact {
write!(buf, "{:.1}kHz", rate / 1000.)
} else {
write!(buf, "{:.0}Hz", rate)
}
});
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
let pulse = clock.timebase.usecs_to_pulse(now);
let time = now/1000000.;
let bpm = clock.timebase.bpm.get();
cache.beat.update(Some(pulse), |buf, _, _|{
buf.clear();
clock.timebase.format_beats_1_to(buf, pulse)
});
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
} else {
cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY));
cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY));
cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY));
}
}
}

View file

@ -1,48 +0,0 @@
use crate::*;
fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiOut> + 'a {
col!(
FieldH(ItemPalette::G[128], label, format!("{:>+9.3}", value)),
Fixed::xy(if value >= 0.0 { 13 }
else if value >= -1.0 { 12 }
else if value >= -2.0 { 11 }
else if value >= -3.0 { 10 }
else if value >= -4.0 { 9 }
else if value >= -6.0 { 8 }
else if value >= -9.0 { 7 }
else if value >= -12.0 { 6 }
else if value >= -15.0 { 5 }
else if value >= -20.0 { 4 }
else if value >= -25.0 { 3 }
else if value >= -30.0 { 2 }
else if value >= -40.0 { 1 }
else { 0 }, 1, Tui::bg(if value >= 0.0 { Red }
else if value >= -3.0 { Yellow }
else { Green }, ())))
}
fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
Bsp::s(
format!("L/{:>+9.3}", values[0]),
format!("R/{:>+9.3}", values[1]),
)
}
#[cfg(test)] mod test_view_meter {
use super::*;
use proptest::prelude::*;
#[test] fn test_view_meter () {
let _ = view_meter("", 0.0);
let _ = view_meters(&[0.0, 0.0]);
}
proptest! {
#[test] fn proptest_view_meter (
label in "\\PC*", value in f32::MIN..f32::MAX
) {
let _ = view_meter(&label, value);
}
#[test] fn proptest_view_meters (
value1 in f32::MIN..f32::MAX,
value2 in f32::MIN..f32::MAX
) {
let _ = view_meters(&[value1, value2]);
}
}
}

View file

@ -1,140 +0,0 @@
use crate::*;
impl<'a> ArrangerView<'a> {
/// Render input matrix.
pub(crate) fn inputs (&'a self) -> impl Content<TuiOut> + 'a {
Tui::bg(Color::Reset,
Bsp::s(Bsp::s(self.input_routes(), self.input_ports()), self.input_intos()))
}
fn input_routes (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(self.inputs_height)
.left(self.width_side,
io_ports(Tui::g(224), Tui::g(32), ||self.app.inputs_with_sizes()))
.middle(self.width_mid,
per_track_top(
self.width_mid,
||self.app.tracks_with_sizes(),
move|_, &Track { color, .. }|{
io_conns(color.dark.rgb, color.darker.rgb, ||self.app.inputs_with_sizes())
}
))
}
fn input_ports (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(1)
.left(self.width_side,
button_3("i", "midi ins", format!("{}", self.inputs_count), self.is_editing))
.right(self.width_side,
button_2("I", "add midi in", self.is_editing))
.middle(self.width_mid,
per_track_top(
self.width_mid,
||self.app.tracks_with_sizes(),
move|t, track|{
let rec = track.player.recording;
let mon = track.player.monitoring;
let rec = if rec { White } else { track.color.darkest.rgb };
let mon = if mon { White } else { track.color.darkest.rgb };
let bg = if self.track_selected == Some(t) {
track.color.light.rgb
} else {
track.color.base.rgb
};
//let bg2 = if t > 0 { track.color.base.rgb } else { Reset };
wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(
Tui::fg_bg(rec, bg, "Rec "),
Tui::fg_bg(mon, bg, "Mon "),
))))
}))
}
fn input_intos (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(2)
.left(self.width_side,
Bsp::s(Align::e("Input:"), Align::e("Into:")))
.middle(self.width_mid,
per_track_top(
self.width_mid,
||self.app.tracks_with_sizes(),
|_, _|{
Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ ")))
}))
}
/// Render output matrix.
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
Tui::bg(Color::Reset, Align::n(Bsp::s(
Bsp::s(
self.output_nexts(),
self.output_froms(),
),
Bsp::s(
self.output_ports(),
self.output_conns(),
)
)))
}
fn output_nexts (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(2)
.left(self.width_side, Align::ne("From:"))
.middle(self.width_mid, per_track_top(
self.width_mid,
||self.tracks_with_sizes_scrolled(),
|_, _|{
Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default())))
}))
}
fn output_froms (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(2)
.left(self.width_side, Align::ne("Next:"))
.middle(self.width_mid, per_track_top(
self.width_mid,
||self.tracks_with_sizes_scrolled(),
|t, track|Either(
track.player.next_clip.is_some(),
Thunk::new(||Tui::bg(Reset, format!("{:?}",
track.player.next_clip.as_ref()
.map(|(moment, clip)|clip.as_ref()
.map(|clip|clip.read().unwrap().name.clone()))
.flatten().as_ref()))),
Thunk::new(||Tui::bg(Reset, " ------ "))
)))
}
fn output_ports (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(1)
.left(self.width_side,
button_3("o", "midi outs", format!("{}", self.outputs_count), self.is_editing))
.right(self.width_side,
button_2("O", "add midi out", self.is_editing))
.middle(self.width_mid,
per_track_top(
self.width_mid,
||self.tracks_with_sizes_scrolled(),
move|i, t|{
let mute = false;
let solo = false;
let mute = if mute { White } else { t.color.darkest.rgb };
let solo = if solo { White } else { t.color.darkest.rgb };
let bg_1 = if self.track_selected == Some(i) { t.color.light.rgb } else { t.color.base.rgb };
let bg_2 = if i > 0 { t.color.base.rgb } else { Reset };
let mute = Tui::fg_bg(mute, bg_1, "Play ");
let solo = Tui::fg_bg(solo, bg_1, "Solo ");
wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo))))
}))
}
fn output_conns (&'a self) -> impl Content<TuiOut> + 'a {
Tryptich::top(self.outputs_height)
.left(self.width_side,
io_ports(Tui::g(224), Tui::g(32), ||self.app.outputs_with_sizes()))
.middle(self.width_mid, per_track_top(
self.width_mid,
||self.tracks_with_sizes_scrolled(),
|_, t|io_conns(
t.color.dark.rgb,
t.color.darker.rgb,
||self.app.outputs_with_sizes()
)))
}
}

View file

@ -1,147 +0,0 @@
use crate::*;
impl<'a> ArrangerView<'a> {
/// Render track headers
pub(crate) fn tracks (&'a self) -> impl Content<TuiOut> + 'a {
let Self { width_side, width_mid, track_count, track_selected, is_editing, .. } = self;
Tryptich::center(3)
.left(*width_side, button_3("t", "track", format!("{}", *track_count), *is_editing))
.right(*width_side, button_2("T", "add track", *is_editing))
.middle(*width_mid, per_track(*width_mid,
||self.tracks_with_sizes_scrolled(),
|t, track|view_track_header(t, track, *track_selected == Some(t))))
}
/// Render scenes with clips
pub(crate) fn scenes (&'a self) -> impl Content<TuiOut> + 'a {
let Self {
width, width_side, width_mid,
scenes_height, scene_last, scene_selected,
track_selected, is_editing, app: Tek { editor, .. }, ..
} = self;
Tryptich::center(*scenes_height)
.left(*width_side, Map::new(||self.scenes_with_scene_colors(),
move|(index, scene, y1, y2, previous): SceneWithColor, _|view_scene_name(
*width,
(1 + y2 - y1) as u16,
y1 as u16,
index,
scene,
previous,
*scene_last == index,
*scene_selected
)))
.middle(*width_mid, per_track(*width_mid, ||self.tracks_with_sizes_scrolled(),
move|track_index, track|Map::new(||self.scenes_with_track_colors(),
move|(scene_index, scene, y1, y2, prev_scene): SceneWithColor<'a>, _|
view_scene_clip(
*width_mid,
(1 + y2 - y1) as u16,
y1 as u16,
scene,
prev_scene,
scene_index,
track_index,
*is_editing,
*track_selected == Some(track_index),
*scene_selected,
*scene_last == scene_index,
editor
))))
}
fn scene_add (&'a self) -> impl Content<TuiOut> + 'a {
ViewCache::scene_add(
&self.app.view_cache,
self.scene_selected.unwrap_or(0),
self.scene_count,
self.is_editing,
)
}
fn track_counter (&'a self) -> Arc<RwLock<String>> {
ViewCache::track_counter(
&self.app.view_cache,
self.track_selected.unwrap_or(0),
self.track_count,
)
}
}
fn view_track_header <'a> (
index: usize, track: &'a Track, active: bool
) -> impl Content<TuiOut> + use<'a> {
let fg = track.color.lightest.rgb;
let bg = if active { track.color.light.rgb } else { track.color.base.rgb };
let bg2 = Reset;//if index > 0 { self.tracks()[index - 1].color.base.rgb } else { Reset };
wrap(bg, fg, Tui::bold(true, Fill::x(Align::nw(&track.name))))
}
pub(crate) fn view_scene_name (
width: u16,
height: u16,
offset: u16,
index: usize,
scene: &Scene,
prev: Option<ItemPalette>,
last: bool,
select: Option<usize>,
) -> impl Content<TuiOut> {
Fill::x(map_south(offset, height, Fixed::y(height, view_scene_cell(
"", Some(scene.name.clone()), &scene.color, prev, last, select, true, index,
))))
}
pub(crate) fn view_scene_clip <'a> (
width: u16,
height: u16,
offset: u16,
scene: &'a Scene,
prev_bg: Option<ItemPalette>,
scene_index: usize,
track_index: usize,
editing: bool,
same_track: bool,
scene_selected: Option<usize>,
scene_is_last: bool,
editor: &'a Option<MidiEditor>,
) -> impl Content<TuiOut> + use<'a> {
let (name, _fg, bg) = if let Some(clip) = &scene.clips[track_index] {
let clip = clip.read().unwrap();
(Some(clip.name.clone()), clip.color.lightest.rgb, clip.color)
} else {
(None, Tui::g(96), ItemPalette::G[32])
};
let active = editing && same_track && scene_selected == Some(scene_index);
let with_editor = |x|Bsp::b(x, When(active, editor));
map_south(offset, height, with_editor(Fixed::y(height, view_scene_cell(
"", name, &bg, prev_bg,
scene_is_last, scene_selected, same_track, scene_index,
))))
}
pub(crate) fn view_scene_cell <'a> (
icon: &'a str,
name: Option<Arc<str>>,
color: &ItemPalette,
prev_color: Option<ItemPalette>,
is_last: bool,
selected: Option<usize>,
same_track: bool,
scene: usize,
) -> impl Content<TuiOut> + use<'a> {
Phat {
width: 0,
height: 0,
content: Fill::x(Align::w(Tui::bold(true, Bsp::e(icon, name)))),
colors: Tek::colors(
color,
prev_color,
same_track && selected == Some(scene),
same_track && scene > 0 && selected == Some(scene - 1),
is_last
)
}
}