mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 12:46:42 +01:00
This commit is contained in:
parent
5696cbbebb
commit
2fd7d7b39f
18 changed files with 355 additions and 360 deletions
|
|
@ -1,5 +1,3 @@
|
||||||
(@space clock toggle)
|
|
||||||
(@shift-space clock toggle 0)
|
|
||||||
(@t select :track 0)
|
(@t select :track 0)
|
||||||
(@tab edit :clip)
|
(@tab edit :clip)
|
||||||
(@c color)
|
(@c color)
|
||||||
|
|
|
||||||
2
config/keys_clock.edn
Normal file
2
config/keys_clock.edn
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
(@space clock toggle)
|
||||||
|
(@shift-space clock toggle 0)
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
(@up note/pos :note-pos-next)
|
|
||||||
(@w note/pos :note-pos-next)
|
|
||||||
(@down note/pos :note-pos-prev)
|
|
||||||
(@s note/pos :note-pos-prev)
|
|
||||||
|
|
||||||
(@pgup note/pos :note-pos-next-octave)
|
|
||||||
(@pgdn note/pos :note-pos-prev-octave)
|
|
||||||
|
|
||||||
(@comma note/len :note-len-prev)
|
|
||||||
(@period note/len :note-len-next)
|
|
||||||
(@lt note/len :note-len-prev)
|
|
||||||
(@gt note/len :note-len-next)
|
|
||||||
|
|
||||||
(@plus note/range :note-range-next)
|
|
||||||
(@underscore note/range :note-range-prev)
|
|
||||||
|
|
||||||
(@left time/pos :time-pos-prev)
|
|
||||||
(@a time/pos :time-pos-prev)
|
|
||||||
(@right time/pos :time-pos-next)
|
|
||||||
(@d time/pos :time-pos-next)
|
|
||||||
|
|
||||||
(@equal time/zoom :time-zoom-prev)
|
|
||||||
(@minus time/zoom :time-zoom-next)
|
|
||||||
|
|
||||||
(@z time/lock)
|
|
||||||
|
|
||||||
(@enter note/put)
|
|
||||||
(@shift-enter note/append)
|
|
||||||
(@del note/del)
|
|
||||||
(@shift-del note/del)
|
|
||||||
30
config/keys_editor.edn
Normal file
30
config/keys_editor.edn
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
(@up editor note/pos :note-pos-next)
|
||||||
|
(@w editor note/pos :note-pos-next)
|
||||||
|
(@down editor note/pos :note-pos-prev)
|
||||||
|
(@s editor note/pos :note-pos-prev)
|
||||||
|
|
||||||
|
(@pgup editor note/pos :note-pos-next-octave)
|
||||||
|
(@pgdn editor note/pos :note-pos-prev-octave)
|
||||||
|
|
||||||
|
(@comma editor note/len :note-len-prev)
|
||||||
|
(@period editor note/len :note-len-next)
|
||||||
|
(@lt editor note/len :note-len-prev)
|
||||||
|
(@gt editor note/len :note-len-next)
|
||||||
|
|
||||||
|
(@plus editor note/range :note-range-next)
|
||||||
|
(@underscore editor note/range :note-range-prev)
|
||||||
|
|
||||||
|
(@left editor time/pos :time-pos-prev)
|
||||||
|
(@a editor time/pos :time-pos-prev)
|
||||||
|
(@right editor time/pos :time-pos-next)
|
||||||
|
(@d editor time/pos :time-pos-next)
|
||||||
|
|
||||||
|
(@equal editor time/zoom :time-zoom-prev)
|
||||||
|
(@minus editor time/zoom :time-zoom-next)
|
||||||
|
|
||||||
|
(@z editor time/lock)
|
||||||
|
|
||||||
|
(@enter editor note/put)
|
||||||
|
(@shift-enter editor note/append)
|
||||||
|
(@del editor note/del)
|
||||||
|
(@shift-del editor note/del)
|
||||||
|
|
@ -1,8 +1 @@
|
||||||
(@space clock toggle)
|
|
||||||
(@shift-space clock toggle 0)
|
|
||||||
|
|
||||||
(@c color)
|
|
||||||
|
|
||||||
(@q launch)
|
|
||||||
|
|
||||||
(@r sampler record/begin :pitch)
|
(@r sampler record/begin :pitch)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
(@space clock toggle)
|
(@c color)
|
||||||
(@shift-space clock toggle 0)
|
(@q launch)
|
||||||
(@c color)
|
(@shift-I input add)
|
||||||
(@q launch)
|
(@shift-O output add)
|
||||||
(@shift-I input add)
|
|
||||||
(@shift-O output add)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(bsp/a :modal
|
(bsp/a :modal
|
||||||
(bsp/s (fixed/y 1 :transport)
|
(bsp/s (fixed/y 1 :transport)
|
||||||
(bsp/n (fixed/y 1 :status)
|
(bsp/n (fixed/y 1 :status)
|
||||||
(bsp/n (fixed/y 5 :sample-viewer)
|
(bsp/n (fixed/y 5 :sample-viewer)
|
||||||
(bsp/w (fixed/x :w-sidebar :pool)
|
(bsp/w (fixed/x :w-sidebar :pool)
|
||||||
(bsp/e :samples-keys
|
(bsp/e :samples-keys
|
||||||
(fill/y :editor)))))))
|
(fill/y :editor)))))))
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,6 @@ macro_rules! ns { ($C:ty, $s:expr, $a:expr, $W:expr) => { <$C>::try_from_expr($s
|
||||||
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
|
macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; }
|
||||||
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; }
|
||||||
|
|
||||||
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
|
|
||||||
":nil" => Box::new("nil"),
|
|
||||||
":modal" => self.view_modal(),
|
|
||||||
":status" => self.view_status(),
|
|
||||||
":transport" => self.view_transport(),
|
|
||||||
":arranger" => self.view_arranger(),
|
|
||||||
":pool" => self.view_pool(),
|
|
||||||
":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)),
|
|
||||||
":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())),
|
|
||||||
":samples-grid" => self.sampler().map(|s|s.view_grid()),
|
|
||||||
":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())),
|
|
||||||
});
|
|
||||||
|
|
||||||
expose!([self: Tek]
|
expose!([self: Tek]
|
||||||
([bool])
|
([bool])
|
||||||
([isize])
|
([isize])
|
||||||
|
|
@ -45,6 +32,13 @@ expose!([self: Tek]
|
||||||
(":track-next" self.selected.track_next(self.tracks.len()))
|
(":track-next" self.selected.track_next(self.tracks.len()))
|
||||||
(":track-prev" self.selected.track_prev())));
|
(":track-prev" self.selected.track_prev())));
|
||||||
|
|
||||||
|
handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.keys.command(self, input) {
|
||||||
|
let undo = command.execute(self)?;
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}));
|
||||||
|
|
||||||
impose!([app: Tek]
|
impose!([app: Tek]
|
||||||
(TekCommand:
|
(TekCommand:
|
||||||
("menu" [] Some(Self::ToggleMenu))
|
("menu" [] Some(Self::ToggleMenu))
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
handle!(TuiIn: |self: Tek, input|if let Some(handler) = self.handler {
|
|
||||||
handler(self, input)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn handle_arranger (app: &mut Tek, input: &TuiIn) -> Perhaps<bool> {
|
|
||||||
Ok((app.is_editing() && app.editor.handle(input)? == Some(true) ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_global.edn"))? ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_arranger.edn"))? ||
|
|
||||||
app.selected.is_clip() && layer(app, input, include_str!("../../../config/keys_clip.edn"))? ||
|
|
||||||
app.selected.is_track() && layer(app, input, include_str!("../../../config/keys_track.edn"))? ||
|
|
||||||
app.selected.is_scene() && layer(app, input, include_str!("../../../config/keys_scene.edn"))? ||
|
|
||||||
app.selected.is_mix() && layer(app, input, include_str!("../../../config/keys_mix.edn"))?).then_some(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_sequencer (app: &mut Tek, input: &TuiIn) -> Perhaps<bool> {
|
|
||||||
Ok((app.editor.handle(input)? == Some(true) ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_global.edn"))? ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_sequencer.edn"))?).then_some(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_groovebox (app: &mut Tek, input: &TuiIn) -> Perhaps<bool> {
|
|
||||||
Ok((app.editor.handle(input)? == Some(true) ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_global.edn"))? ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_groovebox.edn"))?).then_some(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_sampler (app: &mut Tek, input: &TuiIn) -> Perhaps<bool> {
|
|
||||||
Ok((layer(app, input, include_str!("../../../config/keys_global.edn"))? ||
|
|
||||||
layer(app, input, include_str!("../../../config/keys_sampler.edn"))?).then_some(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layer (app: &mut Tek, input: &TuiIn, keymap: &str) -> Usually<bool> {
|
|
||||||
if let Some(command) = SourceIter(keymap).command::<_, TekCommand, _>(app, input) {
|
|
||||||
if let Some(undo) = command.execute(app)? {
|
|
||||||
app.history.push(undo);
|
|
||||||
}
|
|
||||||
return Ok(true)
|
|
||||||
}
|
|
||||||
return Ok(false)
|
|
||||||
}
|
|
||||||
|
|
@ -40,13 +40,13 @@ mod api; pub use self::api::*;
|
||||||
mod audio; pub use self::audio::*;
|
mod audio; pub use self::audio::*;
|
||||||
mod model; pub use self::model::*;
|
mod model; pub use self::model::*;
|
||||||
mod view; pub use self::view::*;
|
mod view; pub use self::view::*;
|
||||||
mod keys; pub use self::keys::*;
|
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_model () {
|
#[cfg(test)] #[test] fn test_model () {
|
||||||
let mut tek = Tek::default();
|
let mut tek = Tek::default();
|
||||||
let _ = tek.clip();
|
let _ = tek.clip();
|
||||||
let _ = tek.toggle_loop();
|
let _ = tek.toggle_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_model_scene () {
|
#[cfg(test)] #[test] fn test_model_scene () {
|
||||||
let mut app = Tek::default();
|
let mut app = Tek::default();
|
||||||
let _ = app.scene_longest();
|
let _ = app.scene_longest();
|
||||||
|
|
@ -58,6 +58,7 @@ mod keys; pub use self::keys::*;
|
||||||
let _ = scene.pulses();
|
let _ = scene.pulses();
|
||||||
let _ = scene.is_playing(&[]);
|
let _ = scene.is_playing(&[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_clock () {
|
#[cfg(test)] #[test] fn test_view_clock () {
|
||||||
let _ = button_play_pause(true);
|
let _ = button_play_pause(true);
|
||||||
let mut app = Tek::default();
|
let mut app = Tek::default();
|
||||||
|
|
@ -65,6 +66,7 @@ mod keys; pub use self::keys::*;
|
||||||
let _ = app.view_status();
|
let _ = app.view_status();
|
||||||
let _ = app.update_clock();
|
let _ = app.update_clock();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_layout () {
|
#[cfg(test)] #[test] fn test_view_layout () {
|
||||||
let _ = button_2("", "", true);
|
let _ = button_2("", "", true);
|
||||||
let _ = button_2("", "", false);
|
let _ = button_2("", "", false);
|
||||||
|
|
@ -74,6 +76,7 @@ mod keys; pub use self::keys::*;
|
||||||
let _ = heading("", "", 0, "", false);
|
let _ = heading("", "", 0, "", false);
|
||||||
let _ = wrap(Reset, Reset, "");
|
let _ = wrap(Reset, Reset, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] mod test_view_meter {
|
#[cfg(test)] mod test_view_meter {
|
||||||
use super::*;
|
use super::*;
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
|
|
@ -95,6 +98,7 @@ mod keys; pub use self::keys::*;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_iter () {
|
#[cfg(test)] #[test] fn test_view_iter () {
|
||||||
let mut tek = Tek::default();
|
let mut tek = Tek::default();
|
||||||
tek.editor = Some(Default::default());
|
tek.editor = Some(Default::default());
|
||||||
|
|
@ -105,6 +109,7 @@ mod keys; pub use self::keys::*;
|
||||||
//let _: Vec<_> = tek.scenes_with_colors(true, 10).collect();
|
//let _: Vec<_> = tek.scenes_with_colors(true, 10).collect();
|
||||||
//let _: Vec<_> = tek.scenes_with_track_colors(true, 10, 10).collect();
|
//let _: Vec<_> = tek.scenes_with_track_colors(true, 10, 10).collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_sizes () {
|
#[cfg(test)] #[test] fn test_view_sizes () {
|
||||||
let app = Tek::default();
|
let app = Tek::default();
|
||||||
let _ = app.w();
|
let _ = app.w();
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ pub struct Tek {
|
||||||
pub view: SourceIter<'static>,
|
pub view: SourceIter<'static>,
|
||||||
// Cache of formatted strings
|
// Cache of formatted strings
|
||||||
pub view_cache: Arc<RwLock<ViewCache>>,
|
pub view_cache: Arc<RwLock<ViewCache>>,
|
||||||
// Input handler function
|
|
||||||
pub handler: Option<fn(&mut Self, &TuiIn)->Result<Option<bool>, Box<(dyn std::error::Error + 'static)>>>,
|
|
||||||
// Modal overlay
|
// Modal overlay
|
||||||
pub modal: Option<Modal>
|
pub modal: Option<Modal>,
|
||||||
|
// Input keymap
|
||||||
|
pub keys: InputMap<'static, Self, TekCommand, TuiIn, SourceIter<'static>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tek {
|
impl Tek {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,225 @@ use crate::*;
|
||||||
pub(crate) use std::fmt::Write;
|
pub(crate) use std::fmt::Write;
|
||||||
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
||||||
|
|
||||||
|
view!(TuiOut: |self: Tek| self.size.of(View(self, self.view)); {
|
||||||
|
":nil" => Box::new("nil"),
|
||||||
|
":modal" => self.view_modal(),
|
||||||
|
":status" => self.view_status(),
|
||||||
|
":transport" => self.view_transport(),
|
||||||
|
":arranger" => self.view_arranger(),
|
||||||
|
":pool" => self.view_pool(),
|
||||||
|
":editor" => self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)),
|
||||||
|
":samples-keys" => self.sampler().map(|s|s.view_list(false, self.editor().unwrap())),
|
||||||
|
":samples-grid" => self.sampler().map(|s|s.view_grid()),
|
||||||
|
":sample-viewer" => self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())),
|
||||||
|
});
|
||||||
|
|
||||||
|
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
|
||||||
|
|
||||||
|
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
||||||
|
|
||||||
|
impl Tek {
|
||||||
|
|
||||||
|
pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
When::new(self.modal.is_some(), Bsp::b(
|
||||||
|
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
|
||||||
|
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), Bsp::b(
|
||||||
|
Repeat(" "),
|
||||||
|
Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
|
.enclose(self.modal.map(|modal|match modal {
|
||||||
|
Modal::Menu => self.view_modal_menu().boxed(),
|
||||||
|
Modal::Help => self.view_modal_help().boxed(),
|
||||||
|
}))
|
||||||
|
)))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_modal_menu (&self) -> impl Content<TuiOut> {
|
||||||
|
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
|
||||||
|
let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a));
|
||||||
|
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_modal_help (&self) -> impl Content<TuiOut> {
|
||||||
|
let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn"))
|
||||||
|
.filter_map(|x|if let Value::Exp(_, iter)=x.value{
|
||||||
|
Some(iter)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
});
|
||||||
|
let binding = |mut binding: TokenIter, _|Bsp::e(
|
||||||
|
Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token {
|
||||||
|
value: Value::Sym(key), ..
|
||||||
|
}) = binding.next() {
|
||||||
|
Some(key.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})),
|
||||||
|
Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token {
|
||||||
|
value: Value::Key(command), ..
|
||||||
|
}) = binding.next() {
|
||||||
|
Some(command.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
let layer = Map::south(1, bindings, binding);
|
||||||
|
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", layer))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub 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 fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
ArrangerView::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_clock (&self) {
|
||||||
|
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct ArrangerView<'a> {
|
pub(crate) struct ArrangerView<'a> {
|
||||||
app: &'a Tek,
|
app: &'a Tek,
|
||||||
|
|
||||||
|
|
@ -146,14 +365,8 @@ impl<'a> ArrangerView<'a> {
|
||||||
/// Render output matrix.
|
/// Render output matrix.
|
||||||
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
|
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
|
||||||
Tui::bg(Color::Reset, Align::n(Bsp::s(
|
Tui::bg(Color::Reset, Align::n(Bsp::s(
|
||||||
Bsp::s(
|
Bsp::s(self.output_nexts(), self.output_froms()),
|
||||||
self.output_nexts(),
|
Bsp::s(self.output_ports(), self.output_conns()),
|
||||||
self.output_froms(),
|
|
||||||
),
|
|
||||||
Bsp::s(
|
|
||||||
self.output_ports(),
|
|
||||||
self.output_conns(),
|
|
||||||
)
|
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,160 +582,6 @@ impl<'a> ArrangerView<'a> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
|
|
||||||
|
|
||||||
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
|
|
||||||
|
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_clock (&self) {
|
|
||||||
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub 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 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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
When::new(self.modal.is_some(), Bsp::b(
|
|
||||||
Fill::xy(Tui::fg_bg(Color::Rgb(64,64,64), Color::Rgb(32,32,32), "")),
|
|
||||||
Fixed::xy(30, 15, Tui::fg_bg(Color::Rgb(255,255,255), Color::Rgb(16,16,16), self.modal))
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
ArrangerView::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define a type alias for iterators of sized items (columns).
|
/// Define a type alias for iterators of sized items (columns).
|
||||||
macro_rules! def_sizes_iter {
|
macro_rules! def_sizes_iter {
|
||||||
($Type:ident => $($Item:ty),+) => {
|
($Type:ident => $($Item:ty),+) => {
|
||||||
|
|
@ -577,8 +636,7 @@ fn view_status (
|
||||||
|
|
||||||
pub(crate) fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
|
pub(crate) fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
|
||||||
let compact = true;//self.is_editing();
|
let compact = true;//self.is_editing();
|
||||||
Tui::bg(
|
Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
||||||
if playing{Rgb(0,128,0)}else{Rgb(128,64,0)},
|
|
||||||
Either::new(compact,
|
Either::new(compact,
|
||||||
Thunk::new(move||Fixed::x(9, Either::new(playing,
|
Thunk::new(move||Fixed::x(9, Either::new(playing,
|
||||||
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
Tui::fg(Rgb(0, 255, 0), " PLAYING "),
|
||||||
|
|
@ -614,18 +672,15 @@ pub (crate) fn view_meter <'a> (label: &'a str, value: f32) -> impl Content<TuiO
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
pub(crate) fn view_meters (values: &[f32;2]) -> impl Content<TuiOut> + use<'_> {
|
||||||
Bsp::s(
|
let left = format!("L/{:>+9.3}", values[0]);
|
||||||
format!("L/{:>+9.3}", values[0]),
|
let right = format!("R/{:>+9.3}", values[1]);
|
||||||
format!("R/{:>+9.3}", values[1]),
|
Bsp::s(left, right)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wrap (
|
pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||||
bg: Color, fg: Color, content: impl Content<TuiOut>
|
let left = Tui::fg_bg(bg, Reset, "▐");
|
||||||
) -> impl Content<TuiOut> {
|
let right = Tui::fg_bg(bg, Reset, "▌");
|
||||||
Bsp::e(Tui::fg_bg(bg, Reset, "▐"),
|
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
||||||
Bsp::w(Tui::fg_bg(bg, Reset, "▌"),
|
|
||||||
Tui::fg_bg(fg, bg, content)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn button_2 <'a, K, L> (
|
pub(crate) fn button_2 <'a, K, L> (
|
||||||
|
|
@ -848,44 +903,3 @@ impl ViewCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content<TuiOut> for Modal {
|
|
||||||
fn content (&self) -> impl Render<TuiOut> {
|
|
||||||
Bsp::b(
|
|
||||||
Repeat(" "),
|
|
||||||
Outer(true, Style::default().fg(Tui::g(96))).enclose(match self {
|
|
||||||
Self::Menu => self.view_menu().boxed(),
|
|
||||||
Self::Help => self.view_help().boxed(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Modal {
|
|
||||||
fn view_menu (&self) -> impl Content<TuiOut> {
|
|
||||||
let options = ||["Projects", "Settings", "Help", "Quit"].iter();
|
|
||||||
let option = |a,i|Tui::fg(Color::Rgb(255,255,255), format!("{}", a));
|
|
||||||
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
|
|
||||||
}
|
|
||||||
fn view_help (&self) -> impl Content<TuiOut> {
|
|
||||||
let bindings = ||TokenIter::new(include_str!("../../../config/keys_groovebox.edn"))
|
|
||||||
.filter_map(|x|if let Value::Exp(_, iter)=x.value{
|
|
||||||
Some(iter)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
});
|
|
||||||
let binding = |mut binding: TokenIter, _|Bsp::e(
|
|
||||||
Tui::bold(true, Tui::fg(Color::Rgb(255,192,0), if let Some(Token { value: Value::Sym(key), .. }) = binding.next() {
|
|
||||||
Some(key.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})),
|
|
||||||
Bsp::e(" ", Tui::fg(Color::Rgb(255,255,255), if let Some(Token { value: Value::Key(command), .. }) = binding.next() {
|
|
||||||
Some(command.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,49 @@ impl Cli {
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
clock: Clock::new(jack, self.bpm)?,
|
clock: Clock::new(jack, self.bpm)?,
|
||||||
view: SourceIter(match mode {
|
view: SourceIter(match mode {
|
||||||
LaunchMode::Clock => include_str!("../../config/view_transport.edn"),
|
LaunchMode::Clock =>
|
||||||
LaunchMode::Sequencer => include_str!("../../config/view_sequencer.edn"),
|
include_str!("../../config/view_transport.edn"),
|
||||||
LaunchMode::Groovebox => include_str!("../../config/view_groovebox.edn"),
|
LaunchMode::Sequencer =>
|
||||||
LaunchMode::Arranger { .. } => include_str!("../../config/view_arranger.edn"),
|
include_str!("../../config/view_sequencer.edn"),
|
||||||
LaunchMode::Sampler => include_str!("../../config/view_sampler.edn"),
|
LaunchMode::Groovebox =>
|
||||||
|
include_str!("../../config/view_groovebox.edn"),
|
||||||
|
LaunchMode::Arranger { .. } =>
|
||||||
|
include_str!("../../config/view_arranger.edn"),
|
||||||
|
LaunchMode::Sampler =>
|
||||||
|
include_str!("../../config/view_sampler.edn"),
|
||||||
_ => todo!("{mode:?}"),
|
_ => todo!("{mode:?}"),
|
||||||
}),
|
}),
|
||||||
|
keys: match mode {
|
||||||
|
LaunchMode::Sampler => InputMap::default()
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_global.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_sampler.edn"))),
|
||||||
|
LaunchMode::Clock => InputMap::default()
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_global.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_clock.edn"))),
|
||||||
|
LaunchMode::Sequencer => InputMap::default()
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_global.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_editor.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_clock.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_sequencer.edn"))),
|
||||||
|
LaunchMode::Groovebox => InputMap::default()
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_global.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_editor.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_clock.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_sequencer.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_groovebox.edn"))),
|
||||||
|
LaunchMode::Arranger {..} => InputMap::default()
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_global.edn")))
|
||||||
|
.layer_if(
|
||||||
|
|state: &Tek|state.is_editing(),
|
||||||
|
SourceIter(include_str!("../../config/keys_editor.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_clock.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_arranger.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_clip.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_track.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_scene.edn")))
|
||||||
|
.layer(SourceIter(include_str!("../../config/keys_mix.edn"))),
|
||||||
|
_ => todo!("{mode:?}"),
|
||||||
|
},
|
||||||
pool: match mode {
|
pool: match mode {
|
||||||
LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into),
|
LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into),
|
||||||
LaunchMode::Arranger { .. } => Some(Default::default()),
|
LaunchMode::Arranger { .. } => Some(Default::default()),
|
||||||
|
|
@ -126,16 +162,13 @@ impl Cli {
|
||||||
midi_ins,
|
midi_ins,
|
||||||
midi_outs,
|
midi_outs,
|
||||||
midi_buf: match mode {
|
midi_buf: match mode {
|
||||||
LaunchMode::Clock | LaunchMode::Sampler => vec![],
|
LaunchMode::Clock
|
||||||
LaunchMode::Sequencer | LaunchMode::Groovebox | LaunchMode::Arranger {..} => vec![vec![];65536],
|
| LaunchMode::Sampler => vec![],
|
||||||
|
LaunchMode::Sequencer
|
||||||
|
| LaunchMode::Groovebox
|
||||||
|
| LaunchMode::Arranger {..} => vec![vec![];65536],
|
||||||
_ => todo!("{mode:?}"),
|
_ => todo!("{mode:?}"),
|
||||||
},
|
},
|
||||||
handler: Some(match mode {
|
|
||||||
LaunchMode::Sequencer => handle_sequencer,
|
|
||||||
LaunchMode::Groovebox => handle_groovebox,
|
|
||||||
LaunchMode::Sampler => handle_sampler,
|
|
||||||
_ => handle_arranger,
|
|
||||||
}),
|
|
||||||
tracks: match mode {
|
tracks: match mode {
|
||||||
LaunchMode::Sequencer => vec![
|
LaunchMode::Sequencer => vec![
|
||||||
Track::new_sequencer()
|
Track::new_sequencer()
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@ use crate::*;
|
||||||
|
|
||||||
/// Contains state for viewing and editing a clip
|
/// Contains state for viewing and editing a clip
|
||||||
pub struct MidiEditor {
|
pub struct MidiEditor {
|
||||||
pub mode: PianoHorizontal,
|
/// Size of editor on screen
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
pub keys: InputMap<'static, Self, MidiEditCommand, TuiIn>
|
/// View mode and state of editor
|
||||||
|
pub mode: PianoHorizontal,
|
||||||
|
/// Input keymap
|
||||||
|
pub keys: InputMap<'static, Self, MidiEditCommand, TuiIn, SourceIter<'static>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for MidiEditor {
|
impl std::fmt::Debug for MidiEditor {
|
||||||
|
|
@ -19,10 +22,9 @@ impl std::fmt::Debug for MidiEditor {
|
||||||
impl Default for MidiEditor {
|
impl Default for MidiEditor {
|
||||||
fn default () -> Self {
|
fn default () -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: PianoHorizontal::new(None),
|
|
||||||
size: Measure::new(),
|
size: Measure::new(),
|
||||||
keys: InputMap::new()
|
mode: PianoHorizontal::new(None),
|
||||||
.layer(SourceIter(include_str!("../../../../config/keys_edit.edn"))),
|
keys: InputMap::new(SourceIter(include_str!("../../../../config/keys_editor.edn"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -206,14 +208,12 @@ atom_command!(MidiEditCommand: |state: MidiEditor| {
|
||||||
Show(Option<Arc<RwLock<MidiClip>>>),
|
Show(Option<Arc<RwLock<MidiClip>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
handle!(TuiIn: |self: MidiEditor, input|{
|
handle!(TuiIn: |self: MidiEditor, input|Ok(if let Some(command) = self.keys.command(self, input) {
|
||||||
Ok(if let Some(command) = self.keys.command(self, input) {
|
command.execute(self)?;
|
||||||
let _undo = command.execute(self)?;
|
Some(true)
|
||||||
Some(true)
|
} else {
|
||||||
} else {
|
None
|
||||||
None
|
}));
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
impl Command<MidiEditor> for MidiEditCommand {
|
impl Command<MidiEditor> for MidiEditCommand {
|
||||||
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
|
fn execute (self, state: &mut MidiEditor) -> Perhaps<Self> {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::*;
|
||||||
use Color::*;
|
use Color::*;
|
||||||
|
|
||||||
/// A clip, rendered as a horizontal piano roll.
|
/// A clip, rendered as a horizontal piano roll.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct PianoHorizontal {
|
pub struct PianoHorizontal {
|
||||||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||||||
/// Buffer where the whole clip is rerendered on change
|
/// Buffer where the whole clip is rerendered on change
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
//#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MidiPool {
|
pub struct MidiPool {
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
/// Collection of clips
|
/// Collection of clips
|
||||||
|
|
@ -10,7 +10,7 @@ pub struct MidiPool {
|
||||||
/// Mode switch
|
/// Mode switch
|
||||||
pub mode: Option<PoolMode>,
|
pub mode: Option<PoolMode>,
|
||||||
|
|
||||||
pub keys: InputMap<'static, Self, PoolCommand, TuiIn>,
|
pub keys: InputMap<'static, Self, PoolCommand, TuiIn, SourceIter<'static>>,
|
||||||
//pub keys: SourceIter<'static>,
|
//pub keys: SourceIter<'static>,
|
||||||
//pub keys_rename: SourceIter<'static>,
|
//pub keys_rename: SourceIter<'static>,
|
||||||
//pub keys_length: SourceIter<'static>,
|
//pub keys_length: SourceIter<'static>,
|
||||||
|
|
@ -25,9 +25,7 @@ impl Default for MidiPool {
|
||||||
clips: Arc::from(RwLock::from(vec![])),
|
clips: Arc::from(RwLock::from(vec![])),
|
||||||
clip: 0.into(),
|
clip: 0.into(),
|
||||||
mode: None,
|
mode: None,
|
||||||
keys: InputMap::new()
|
keys: InputMap::new(SourceIter(include_str!("../../../../config/keys_pool.edn")))
|
||||||
.layer(
|
|
||||||
SourceIter(include_str!("../../../../config/keys_edit.edn")))
|
|
||||||
.layer_if(|pool: &Self|matches!(pool.mode, Some(Import(..))|Some(Export(..))),
|
.layer_if(|pool: &Self|matches!(pool.mode, Some(Import(..))|Some(Export(..))),
|
||||||
SourceIter(include_str!("../../../../config/keys_pool_file.edn")))
|
SourceIter(include_str!("../../../../config/keys_pool_file.edn")))
|
||||||
.layer_if(|pool: &Self|matches!(pool.mode, Some(Rename(..))),
|
.layer_if(|pool: &Self|matches!(pool.mode, Some(Rename(..))),
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@ use crate::*;
|
||||||
/// Trait for thing that may output MIDI.
|
/// Trait for thing that may output MIDI.
|
||||||
pub trait HasMidiOuts {
|
pub trait HasMidiOuts {
|
||||||
fn midi_outs (&self) -> &Vec<JackMidiOut>;
|
fn midi_outs (&self) -> &Vec<JackMidiOut>;
|
||||||
|
|
||||||
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut>;
|
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut>;
|
||||||
|
|
||||||
fn has_midi_outs (&self) -> bool {
|
fn has_midi_outs (&self) -> bool {
|
||||||
!self.midi_outs().is_empty()
|
!self.midi_outs().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Buffer for serializing a MIDI event. FIXME rename
|
/// Buffer for serializing a MIDI event. FIXME rename
|
||||||
fn midi_note (&mut self) -> &mut Vec<u8>;
|
fn midi_note (&mut self) -> &mut Vec<u8>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 35ad37120554611197c1e30bbed657f310d332c3
|
Subproject commit 4ec51d5b694c14ccf617ec4538da04089b17ab92
|
||||||
Loading…
Add table
Add a link
Reference in a new issue