mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
collect crates/ and deps/
This commit is contained in:
parent
2f8882f6cd
commit
8fa0f8a409
140 changed files with 23 additions and 21 deletions
|
|
@ -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"]
|
||||
|
|
@ -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)))
|
||||
|
|
@ -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)))))
|
||||
201
app/src/api.rs
201
app/src/api.rs
|
|
@ -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))
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -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:?}"); }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -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 {}
|
||||
177
app/src/keys.rs
177
app/src/keys.rs
|
|
@ -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:?}")
|
||||
});
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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
|
||||
},
|
||||
});
|
||||
|
|
@ -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
|
||||
},
|
||||
});
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
@ -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();
|
||||
}
|
||||
126
app/src/model.rs
126
app/src/model.rs
|
|
@ -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);
|
||||
//});
|
||||
|
|
@ -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(&[]);
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
282
app/src/view.rs
282
app/src/view.rs
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "");
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue