Compare commits

..

No commits in common. "a77536c2346f170f2e7c34f0a7f1b04cf145f112" and "a67481ab044e5abbfef81575a64ad707ecde28c6" have entirely different histories.

10 changed files with 121 additions and 221 deletions

10
Cargo.lock generated
View file

@ -1521,7 +1521,6 @@ dependencies = [
"tek_device", "tek_device",
"tek_engine", "tek_engine",
"tengri", "tengri",
"tengri_proc",
"toml", "toml",
] ]
@ -1602,15 +1601,6 @@ dependencies = [
"tengri_dsl", "tengri_dsl",
] ]
[[package]]
name = "tengri_proc"
version = "0.13.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tengri_tui" name = "tengri_tui"
version = "0.13.0" version = "0.13.0"

View file

@ -23,13 +23,15 @@ lto = false
[workspace.dependencies.tengri] [workspace.dependencies.tengri]
path = "./deps/tengri/tengri" path = "./deps/tengri/tengri"
#git = "https://codeberg.org/unspeaker/tengri"
#rev = "6048d24"
features = [ "tui", "dsl" ] features = [ "tui", "dsl" ]
[workspace.dependencies.tengri_proc]
path = "./deps/tengri/proc"
[workspace.dependencies.jack] [workspace.dependencies.jack]
#git = "https://codeberg.org/unspeaker/rust-jack"
#rev = "a13c1c4"
path = "./deps/rust-jack" path = "./deps/rust-jack"
#default-features = false
[workspace.dependencies] [workspace.dependencies]
tek_device = { path = "./crates/device" } tek_device = { path = "./crates/device" }

View file

@ -11,7 +11,6 @@
:arranger)))))) :arranger))))))
(keys (keys
(layer-if :mode-device-add "./keys_device_add.edn")
(layer-if :mode-pool-import "./keys_pool_file.edn") (layer-if :mode-pool-import "./keys_pool_file.edn")
(layer-if :mode-pool-export "./keys_pool_file.edn") (layer-if :mode-pool-export "./keys_pool_file.edn")
(layer-if :mode-pool-rename "./keys_clip_rename.edn") (layer-if :mode-pool-rename "./keys_clip_rename.edn")

View file

@ -1,12 +1,11 @@
(@c color) (@t select :track 0)
(@q launch) (@tab edit :clip)
(@t select :track 0) (@c color)
(@tab edit :clip) (@q launch)
(@shift-I input add) (@shift-I input add)
(@shift-O output add) (@shift-O output add)
(@shift-S scene add) (@shift-S scene add)
(@shift-T track add) (@shift-T track add)
(@shift-Z device picker)
(@up select :scene-prev) (@up select :scene-prev)
(@w select :scene-prev) (@w select :scene-prev)

View file

@ -1,4 +0,0 @@
(@esc device select-cancel)
(@up device pick :device-kind-prev)
(@down device pick :device-kind-next)
(@enter device add :device-kind)

View file

@ -4,8 +4,7 @@ edition = { workspace = true }
version = { workspace = true } version = { workspace = true }
[dependencies] [dependencies]
tengri = { workspace = true } tengri = { workspace = true }
tengri_proc = { workspace = true }
tek_engine = { workspace = true } tek_engine = { workspace = true }
tek_device = { workspace = true } tek_device = { workspace = true }

View file

@ -7,20 +7,9 @@ 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 }}; }
handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.command(self, input) {
let undo = command.execute(self)?;
if let Some(undo) = undo {
self.history.push(undo);
}
Some(true)
} else {
None
}));
expose!([self: Tek] expose!([self: Tek]
([bool] ([bool]
(":mode-editor" self.is_editing()) (":mode-editor" self.is_editing())
(":mode-device-add" matches!(self.modal, Some(Modal::Device(..))))
(":mode-clip" !self.is_editing() && self.selected.is_clip()) (":mode-clip" !self.is_editing() && self.selected.is_clip())
(":mode-track" !self.is_editing() && self.selected.is_track()) (":mode-track" !self.is_editing() && self.selected.is_track())
(":mode-scene" !self.is_editing() && self.selected.is_scene()) (":mode-scene" !self.is_editing() && self.selected.is_scene())
@ -46,22 +35,7 @@ expose!([self: Tek]
(":w-sidebar" self.w_sidebar())) (":w-sidebar" self.w_sidebar()))
([usize] ([usize]
(":scene-last" self.scenes.len()) (":scene-last" self.scenes.len())
(":track-last" self.tracks.len()) (":track-last" self.tracks.len()))
(":device-kind" if let Some(Modal::Device(index)) = self.modal {
index
} else {
0
})
(":device-kind-prev" if let Some(Modal::Device(index)) = self.modal {
index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1))
} else {
0
})
(":device-kind-next" if let Some(Modal::Device(index)) = self.modal {
(index + 1) % self.device_kinds().len()
} else {
0
}))
([Option<usize>] ([Option<usize>]
(":scene" self.selected.scene()) (":scene" self.selected.scene())
(":track" self.selected.track())) (":track" self.selected.track()))
@ -75,45 +49,58 @@ expose!([self: Tek]
(":scene-prev" self.selected.scene_prev()) (":scene-prev" self.selected.scene_prev())
(":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())));
expose!([self: MidiPool] provide!(bool: |self: MidiPool| {});
([bool]) provide!(MidiClip: |self: MidiPool| {
([PathBuf]) ":new-clip" => self.new_clip(),
([Arc<str>]) ":cloned-clip" => self.cloned_clip(),
([MidiClip] });
(":new-clip" self.new_clip()) provide!(PathBuf: |self: MidiPool| {});
(":cloned-clip" self.cloned_clip())) provide!(Arc<str>: |self: MidiPool| {});
([usize] provide!(usize: |self: MidiPool| {
(":current" 0) ":current" => 0,
(":after" 0) ":after" => 0,
(":previous" 0) ":previous" => 0,
(":next" 0)) ":next" => 0
([ItemColor] });
(":random-color" ItemColor::random()))); provide!(ItemColor: |self: MidiPool| {
expose!([self: MidiEditor] ":random-color" => ItemColor::random()
([bool] });
(":true" true) provide!(bool: |self: MidiEditor| {
(":false" false) ":true" => true,
(":time-lock" self.time_lock().get()) ":false" => false,
(":time-lock-toggle" !self.time_lock().get())) ":time-lock" => self.time_lock().get(),
([usize] ":time-lock-toggle" => !self.time_lock().get(),
(":note-length" self.note_len()) });
(":note-pos" self.note_pos()) provide!(usize: |self: MidiEditor| {
(":note-pos-next" self.note_pos() + 1) ":note-length" => self.note_len(),
(":note-pos-prev" self.note_pos().saturating_sub(1)) ":note-pos" => self.note_pos(),
(":note-pos-next-octave" self.note_pos() + 12) ":note-pos-next" => self.note_pos() + 1,
(":note-pos-prev-octave" self.note_pos().saturating_sub(12)) ":note-pos-prev" => self.note_pos().saturating_sub(1),
(":note-len" self.note_len()) ":note-pos-next-octave" => self.note_pos() + 12,
(":note-len-next" self.note_len() + 1) ":note-pos-prev-octave" => self.note_pos().saturating_sub(12),
(":note-len-prev" self.note_len().saturating_sub(1)) ":note-len" => self.note_len(),
(":note-range" self.note_axis().get()) ":note-len-next" => self.note_len() + 1,
(":note-range-prev" self.note_axis().get() + 1) ":note-len-prev" => self.note_len().saturating_sub(1),
(":note-range-next" self.note_axis().get().saturating_sub(1)) ":note-range" => self.note_axis().get(),
(":time-pos" self.time_pos()) ":note-range-prev" => self.note_axis().get() + 1,
(":time-pos-next" self.time_pos() + self.time_zoom().get()) ":note-range-next" => self.note_axis().get().saturating_sub(1),
(":time-pos-prev" self.time_pos().saturating_sub(self.time_zoom().get())) ":time-pos" => self.time_pos(),
(":time-zoom" self.time_zoom().get()) ":time-pos-next" => self.time_pos() + self.time_zoom().get(),
(":time-zoom-next" self.time_zoom().get() + 1) ":time-pos-prev" => self.time_pos().saturating_sub(self.time_zoom().get()),
(":time-zoom-prev" self.time_zoom().get().saturating_sub(1).max(1)))); ":time-zoom" => self.time_zoom().get(),
":time-zoom-next" => self.time_zoom().get() + 1,
":time-zoom-prev" => self.time_zoom().get().saturating_sub(1).max(1),
});
handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.command(self, input) {
let undo = command.execute(self)?;
if let Some(undo) = undo {
self.history.push(undo);
}
Some(true)
} else {
None
}));
impose!([app: Tek] impose!([app: Tek]
(TekCommand: (TekCommand:
@ -136,7 +123,6 @@ impose!([app: Tek]
("input" [,..a] ns!(InputCommand, app, a, Self::Input)) ("input" [,..a] ns!(InputCommand, app, a, Self::Input))
("output" [,..a] ns!(OutputCommand, app, a, Self::Output)) ("output" [,..a] ns!(OutputCommand, app, a, Self::Output))
("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip)) ("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip))
("device" [,..a] ns!(DeviceCommand, app, a, Self::Device))
("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten()) ("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten())
("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten()) ("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten())
("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten()) ("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten())
@ -161,11 +147,6 @@ impose!([app: Tek]
(OutputCommand: (OutputCommand:
("add" [] Some(Self::Add))) ("add" [] Some(Self::Add)))
(DeviceCommand:
("picker" [] Some(Self::Picker))
("pick" [index: usize] Some(Self::Pick(index.unwrap())))
("add" [index: usize] Some(Self::Add(index.unwrap()))))
(SceneCommand: (SceneCommand:
("add" [] Some(Self::Add)) ("add" [] Some(Self::Add))
("delete" [a: Option<usize>] Some(Self::Del(a.flatten().unwrap()))) ("delete" [a: Option<usize>] Some(Self::Del(a.flatten().unwrap())))
@ -197,7 +178,6 @@ defcom!([self, app: Tek]
(Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?) (Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?)
(Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?) (Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?)
(Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) (Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?)
(Device [cmd: DeviceCommand] cmd.delegate(app, Self::Device)?)
(Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) (Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?)
(Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) (Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?)
(ToggleHelp [] cmd!(app.toggle_modal(Some(Modal::Help)))) (ToggleHelp [] cmd!(app.toggle_modal(Some(Modal::Help))))
@ -212,15 +192,10 @@ defcom!([self, app: Tek]
(StopAll [] cmd!(app.stop_all()))) (StopAll [] cmd!(app.stop_all())))
(InputCommand (InputCommand
(Add [] cmd!(app.midi_in_add()?))) (Add [] cmd!(app.add_midi_in()?)))
(OutputCommand (OutputCommand
(Add [] cmd!(app.midi_out_add()?))) (Add [] cmd!(app.add_midi_out()?)))
(DeviceCommand
(Picker [] cmd!(app.device_picker_show()))
(Pick [i: usize] cmd!(app.device_pick(i)))
(Add [i: usize] cmd!(app.device_add(i))))
(TrackCommand (TrackCommand
(TogglePlay [] Some(Self::TogglePlay)) (TogglePlay [] Some(Self::TogglePlay))

View file

@ -383,50 +383,16 @@ impl Tek {
}) })
} }
pub(crate) fn midi_in_add (&mut self) -> Usually<()> { pub(crate) fn add_midi_in (&mut self) -> Usually<()> {
self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?); self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?);
Ok(()) Ok(())
} }
pub(crate) fn midi_out_add (&mut self) -> Usually<()> { pub(crate) fn add_midi_out (&mut self) -> Usually<()> {
self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?); self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?);
Ok(()) Ok(())
} }
pub(crate) fn device_picker_show (&mut self) {
self.modal = Some(Modal::Device(0));
}
pub(crate) fn device_pick (&mut self, index: usize) {
self.modal = Some(Modal::Device(index));
}
pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> {
match index {
0 => {
let jack = self.jack.clone();
self.track_mut()
.expect("no active track")
.devices
.push({
let sampler = Sampler::new(&jack, &"sampler", &[], &[&[], &[]], &[&[], &[]])?;
Device::Sampler(sampler)
});
self.modal = None;
Ok(())
},
1 => todo!(),
_ => unreachable!(),
}
}
pub(crate) fn device_kinds (&self) -> &'static [&'static str] {
&[
"Sampler",
"Plugin (LV2)",
]
}
} }
has_size!(<TuiOut>|self: Tek|&self.size); has_size!(<TuiOut>|self: Tek|&self.size);
@ -458,7 +424,6 @@ pub trait HasSelection {
pub enum Modal { pub enum Modal {
Help, Help,
Menu, Menu,
Device(usize)
} }
/// Represents the current user selection in the arranger /// Represents the current user selection in the arranger

View file

@ -2,66 +2,26 @@ 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;
#[tengri_proc::view(TuiOut)] view!(TuiOut: |self: Tek| self.size.of(View(self, self.config.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 { impl Tek {
#[tengri::view(":nil")] pub fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
fn view_nil (&self) -> impl Content<TuiOut> + use<'_> {
"nil"
}
#[tengri::view(":status")]
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(),
)
}
#[tengri::view(":transport")]
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(),
)
}
#[tengri::view(":arranger")]
fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
ArrangerView::new(self)
}
#[tengri::view(":pool")]
fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p)))
}
#[tengri::view(":editor")]
fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e))
}
#[tengri::view(":samples-keys")]
fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_list(false, self.editor().unwrap()))
}
#[tengri::view(":samples-grid")]
fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_grid())
}
#[tengri::view(":sample-viewer")]
fn view_sample_viewer (&self) -> impl Content<TuiOut> + use<'_> {
self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos()))
}
#[tengri::view(":modal")]
fn view_modal (&self) -> impl Content<TuiOut> + use<'_> {
When::new(self.modal.is_some(), Bsp::b( When::new(self.modal.is_some(), Bsp::b(
Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")), Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")),
Fixed::xy(30, 15, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b( Fixed::xy(30, 15, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b(
@ -70,7 +30,6 @@ impl Tek {
.enclose(self.modal.map(|modal|match modal { .enclose(self.modal.map(|modal|match modal {
Modal::Menu => self.view_modal_menu().boxed(), Modal::Menu => self.view_modal_menu().boxed(),
Modal::Help => self.view_modal_help().boxed(), Modal::Help => self.view_modal_help().boxed(),
Modal::Device(index) => self.view_modal_device(index).boxed(),
})) }))
))) )))
)) ))
@ -110,14 +69,34 @@ impl Tek {
Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding))) Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1, bindings, binding)))
} }
fn view_modal_device (&self, index: usize) -> impl Content<TuiOut> + use<'_> { pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
let choices = ||self.device_kinds().iter(); self.update_clock();
let choice = move|label, i| let cache = self.view_cache.read().unwrap();
Fill::x(Tui::bg(if i == index { Rgb(64,128,32) } else { Rgb(0,0,0) }, view_status(
Bsp::e(if i == index { "[ " } else { " " }, self.selected.describe(&self.tracks, &self.scenes),
Bsp::w(if i == index { " ]" } else { " " }, cache.sr.view.clone(),
label)))); cache.buf.view.clone(),
Bsp::s(Tui::bold(true, "Add device"), Map::south(1, choices, choice)) 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. /// Spacing between tracks.
@ -221,7 +200,7 @@ impl Tek {
TrackClip { track, .. } if editing => Some(track), TrackClip { track, .. } if editing => Some(track),
_ => None _ => None
}; };
let bigger = self.editor_w(); let bigger = self.editor_w();
self.tracks().iter().enumerate().map(move |(index, track)|{ self.tracks().iter().enumerate().map(move |(index, track)|{
let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) }; let width = if Some(index) == active.copied() { bigger } else { track.width.max(8) };
let data = (index, track, x, x + width); let data = (index, track, x, x + width);
@ -357,8 +336,8 @@ impl<'a> ArrangerView<'a> {
pub(crate) fn devices (&'a self) -> impl Content<TuiOut> + 'a { pub(crate) fn devices (&'a self) -> impl Content<TuiOut> + 'a {
let Self { width_side, width_mid, track_count, track_selected, is_editing, .. } = self; let Self { width_side, width_mid, track_count, track_selected, is_editing, .. } = self;
Tryptich::top(1) Tryptich::top(1)
.left(*width_side, button_3("z", "devices", format!("{}", 0), *is_editing)) .left(*width_side, button_3("x", "devices", format!("{}", 0), *is_editing))
.right(*width_side, button_2("Z", "add device", *is_editing)) .right(*width_side, button_2("X", "add device", *is_editing))
.middle(*width_mid, per_track_top(*width_mid, ||self.tracks_with_sizes_scrolled(), .middle(*width_mid, per_track_top(*width_mid, ||self.tracks_with_sizes_scrolled(),
move|index, track|{ move|index, track|{
wrap(if *track_selected == Some(index) { wrap(if *track_selected == Some(index) {
@ -640,10 +619,6 @@ impl<'a> ArrangerView<'a> {
} }
trait ScenesColors<'a> = Iterator<Item=SceneWithColor<'a>>;
type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option<ItemTheme>);
/// 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),+) => {

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit b543c43e68154f049019da648064f36af1537434 Subproject commit 21f7f6b38afc966b7b45af442935d48c8c5067d3