mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-01-31 16:36:40 +01:00
Compare commits
No commits in common. "6ec445aab7604e886ce20eb9a1e15bf824436e0d" and "dcde588c7ba26cf582d0f406b6b64587dd3df8cb" have entirely different histories.
6ec445aab7
...
dcde588c7b
17 changed files with 1442 additions and 1498 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -8,6 +8,3 @@
|
||||||
[submodule "deps/rust-jack"]
|
[submodule "deps/rust-jack"]
|
||||||
path = deps/rust-jack
|
path = deps/rust-jack
|
||||||
url = https://codeberg.org/unspeaker/rust-jack
|
url = https://codeberg.org/unspeaker/rust-jack
|
||||||
[submodule "deps/dizzle"]
|
|
||||||
path = deps/dizzle
|
|
||||||
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
|
||||||
|
|
|
||||||
47
Cargo.lock
generated
47
Cargo.lock
generated
|
|
@ -79,15 +79,6 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ansi_term"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.20"
|
version = "0.6.20"
|
||||||
|
|
@ -655,16 +646,6 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dizzle"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"const_panic",
|
|
||||||
"itertools 0.14.0",
|
|
||||||
"konst",
|
|
||||||
"thiserror 2.0.16",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlib"
|
name = "dlib"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -2402,7 +2383,6 @@ dependencies = [
|
||||||
name = "tek"
|
name = "tek"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -2478,24 +2458,41 @@ dependencies = [
|
||||||
name = "tengri"
|
name = "tengri"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dizzle",
|
"tengri_core",
|
||||||
|
"tengri_dsl",
|
||||||
"tengri_input",
|
"tengri_input",
|
||||||
"tengri_output",
|
"tengri_output",
|
||||||
"tengri_tui",
|
"tengri_tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tengri_core"
|
||||||
|
version = "0.14.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tengri_dsl"
|
||||||
|
version = "0.14.0"
|
||||||
|
dependencies = [
|
||||||
|
"const_panic",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"konst",
|
||||||
|
"tengri_core",
|
||||||
|
"thiserror 2.0.16",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tengri_input"
|
name = "tengri_input"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dizzle",
|
"tengri_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tengri_output"
|
name = "tengri_output"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dizzle",
|
"tengri_core",
|
||||||
|
"tengri_dsl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2505,11 +2502,13 @@ dependencies = [
|
||||||
"atomic_float",
|
"atomic_float",
|
||||||
"better-panic",
|
"better-panic",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"dizzle",
|
"konst",
|
||||||
"palette",
|
"palette",
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"tengri_core",
|
||||||
|
"tengri_dsl",
|
||||||
"tengri_input",
|
"tengri_input",
|
||||||
"tengri_output",
|
"tengri_output",
|
||||||
"unicode-width 0.2.0",
|
"unicode-width 0.2.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "./app", "./engine", "./device" ]
|
members = [ "./app", "./engine", "./device" ]
|
||||||
exclude = [ "./deps/tengri", "./deps/dizzle" ]
|
exclude = [ "./deps/tengri" ]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ path = "tek.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tek"
|
name = "tek"
|
||||||
path = "tek.rs"
|
path = "tek_cli.rs"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")']
|
[target.'cfg(target_os = "linux")']
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
@ -32,7 +32,6 @@ uuid = { workspace = true, optional = true }
|
||||||
wavers = { workspace = true, optional = true }
|
wavers = { workspace = true, optional = true }
|
||||||
winit = { workspace = true, optional = true }
|
winit = { workspace = true, optional = true }
|
||||||
xdg = { workspace = true }
|
xdg = { workspace = true }
|
||||||
ansi_term = "0.12.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
|
|
|
||||||
10
app/tek.edn
10
app/tek.edn
|
|
@ -38,6 +38,12 @@
|
||||||
:ports/out
|
:ports/out
|
||||||
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill :dialog/menu)))))))
|
(bsp/n :ports/in (bg (g 30) (bsp/s (fixed/y 7 :logo) (fill :dialog/menu)))))))
|
||||||
|
|
||||||
|
(view :menu (bsp/s
|
||||||
|
(push/y 4 (fixed/xy 20 2 (bg (g 0) :debug)))
|
||||||
|
(fixed 20 2 (bg (g 20) (push/x 2 :debug)))))
|
||||||
|
|
||||||
|
(view :menu (bsp/s (fixed/y 4 :debug) :debug))
|
||||||
|
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
(view :ports/out (fill/x (fixed/y 3 (bsp/a
|
||||||
(fill/x (align/w (text L-AUDIO-OUT)))
|
(fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT-R))))))))
|
||||||
|
|
@ -105,7 +111,9 @@
|
||||||
(mode :add-device (keys :add-device)) (mode :browse (keys :browse)) (mode :rename (keys :input))
|
(mode :add-device (keys :add-device)) (mode :browse (keys :browse)) (mode :rename (keys :input))
|
||||||
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
(mode :length (keys :rename)) (mode :clip (keys :clip)) (mode :track (keys :track))
|
||||||
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
(mode :scene (keys :scene)) (mode :mix (keys :mix))
|
||||||
(keys :clock :arranger :global) (bsp/n
|
(keys :clock :arranger :global) :arranger)
|
||||||
|
|
||||||
|
(view :arranger (bsp/n
|
||||||
:status
|
:status
|
||||||
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
(bsp/w :meters/output (bsp/e :meters/input :arrangement))))
|
||||||
|
|
||||||
|
|
|
||||||
1703
app/tek.rs
1703
app/tek.rs
File diff suppressed because it is too large
Load diff
325
app/tek_bind.rs
Normal file
325
app/tek_bind.rs
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>;
|
||||||
|
|
||||||
|
/// A collection of input bindings.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EventMap<E, C>(
|
||||||
|
/// Map of each event (e.g. key combination) to
|
||||||
|
/// all command expressions bound to it by
|
||||||
|
/// all loaded input layers.
|
||||||
|
pub BTreeMap<E, Vec<Binding<C>>>
|
||||||
|
);
|
||||||
|
|
||||||
|
/// An input binding.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Binding<C> {
|
||||||
|
pub commands: Arc<[C]>,
|
||||||
|
pub condition: Option<Condition>,
|
||||||
|
pub description: Option<Arc<str>>,
|
||||||
|
pub source: Option<Arc<PathBuf>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input bindings are only returned if this evaluates to true
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
|
||||||
|
|
||||||
|
/// Default is always empty map regardless if `E` and `C` implement [Default].
|
||||||
|
impl<E, C> Default for EventMap<E, C> {
|
||||||
|
fn default () -> Self { Self(Default::default()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Clone + Ord, C> EventMap<E, C> {
|
||||||
|
/// Create a new event map
|
||||||
|
pub fn new () -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
/// Add a binding to an owned event map.
|
||||||
|
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
||||||
|
self.add(event, binding);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Add a binding to an event map.
|
||||||
|
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
|
||||||
|
if !self.0.contains_key(&event) {
|
||||||
|
self.0.insert(event.clone(), Default::default());
|
||||||
|
}
|
||||||
|
self.0.get_mut(&event).unwrap().push(binding);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Return the binding(s) that correspond to an event.
|
||||||
|
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
|
||||||
|
self.0.get(event).map(|x|x.as_slice())
|
||||||
|
}
|
||||||
|
/// Return the first binding that corresponds to an event, considering conditions.
|
||||||
|
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
|
||||||
|
self.query(event)
|
||||||
|
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventMap<TuiEvent, Arc<str>> {
|
||||||
|
pub fn load_into (binds: &Binds, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
||||||
|
println!("EventMap::load_into: {}: {body:?}", name.as_ref());
|
||||||
|
let mut map = Self::new();
|
||||||
|
body.each(|item|if item.expr().head() == Ok(Some("see")) {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
||||||
|
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
|
||||||
|
map.add(key, Binding {
|
||||||
|
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
||||||
|
condition: None,
|
||||||
|
description: None,
|
||||||
|
source: None
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else if Some(":char") == item.expr()?.head()? {
|
||||||
|
// TODO
|
||||||
|
return Ok(())
|
||||||
|
} else {
|
||||||
|
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
||||||
|
})?;
|
||||||
|
binds.write().unwrap().insert(name.as_ref().into(), map);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> Binding<C> {
|
||||||
|
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
||||||
|
let command: Option<C> = None;
|
||||||
|
let condition: Option<Condition> = None;
|
||||||
|
let description: Option<Arc<str>> = None;
|
||||||
|
let source: Option<Arc<PathBuf>> = None;
|
||||||
|
if let Some(command) = command {
|
||||||
|
Ok(Self { commands: [command].into(), condition, description, source })
|
||||||
|
} else {
|
||||||
|
Err(format!("no command in {dsl:?}").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_debug!(Condition |self, w| { write!(w, "*") });
|
||||||
|
|
||||||
|
handle!(TuiIn:|self: App, input|{
|
||||||
|
let mut commands = vec![];
|
||||||
|
for id in self.mode.keys.iter() {
|
||||||
|
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
||||||
|
if let Some(bindings) = event_map.query(input.event()) {
|
||||||
|
for binding in bindings {
|
||||||
|
for command in binding.commands.iter() {
|
||||||
|
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
||||||
|
commands.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for command in commands.into_iter() {
|
||||||
|
let result = command.execute(self);
|
||||||
|
match result {
|
||||||
|
Ok(undo) => {
|
||||||
|
self.history.push((command, undo));
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.history.push((command, None));
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Axis { X, Y, Z, I }
|
||||||
|
|
||||||
|
impl<'a> DslNs<'a, AppCommand> for App {}
|
||||||
|
impl<'a> DslNsExprs<'a, AppCommand> for App {}
|
||||||
|
impl<'a> DslNsWords<'a, AppCommand> for App {
|
||||||
|
dsl_words!('a |app| -> AppCommand {
|
||||||
|
"x/inc" => AppCommand::Inc { axis: Axis::X },
|
||||||
|
"x/dec" => AppCommand::Dec { axis: Axis::X },
|
||||||
|
"y/inc" => AppCommand::Inc { axis: Axis::Y },
|
||||||
|
"y/dec" => AppCommand::Dec { axis: Axis::Y },
|
||||||
|
"confirm" => AppCommand::Confirm,
|
||||||
|
"cancel" => AppCommand::Cancel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
||||||
|
|
||||||
|
def_command!(AppCommand: |app: App| {
|
||||||
|
Nop => Ok(None),
|
||||||
|
Confirm => Ok(match &app.dialog {
|
||||||
|
Dialog::Menu(index, items) => {
|
||||||
|
let callback = items.0[*index].1.clone();
|
||||||
|
callback(app)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!(),
|
||||||
|
}),
|
||||||
|
Cancel => todo!(), // TODO delegate:
|
||||||
|
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
|
(Dialog::None, _) => todo!(),
|
||||||
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
||||||
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
Dec { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
|
(Dialog::None, _) => None,
|
||||||
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_prev() }
|
||||||
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
SetDialog { dialog: Dialog } => {
|
||||||
|
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog })
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//AppCommand => {
|
||||||
|
//("x/inc" /
|
||||||
|
//("stop-all") => todo!(),//app.project.stop_all(),
|
||||||
|
//("enqueue", clip: Option<Arc<RwLock<MidiClip>>>) => todo!(),
|
||||||
|
//("history", delta: isize) => todo!(),
|
||||||
|
//("zoom", zoom: usize) => todo!(),
|
||||||
|
//("select", selection: Selection) => todo!(),
|
||||||
|
//("dialog" / command: DialogCommand) => todo!(),
|
||||||
|
//("project" / command: ArrangementCommand) => todo!(),
|
||||||
|
//("clock" / command: ClockCommand) => todo!(),
|
||||||
|
//("sampler" / command: SamplerCommand) => todo!(),
|
||||||
|
//("pool" / command: PoolCommand) => todo!(),
|
||||||
|
//("edit" / editor: MidiEditCommand) => todo!(),
|
||||||
|
//};
|
||||||
|
|
||||||
|
//DialogCommand;
|
||||||
|
|
||||||
|
//ArrangementCommand;
|
||||||
|
|
||||||
|
//ClockCommand;
|
||||||
|
|
||||||
|
//SamplerCommand;
|
||||||
|
|
||||||
|
//PoolCommand;
|
||||||
|
|
||||||
|
//MidiEditCommand;
|
||||||
|
|
||||||
|
|
||||||
|
//take!(DialogCommand |state: App, iter|Take::take(&state.dialog, iter));
|
||||||
|
//#[derive(Clone, Debug)]
|
||||||
|
//pub enum DialogCommand {
|
||||||
|
//Open { dialog: Dialog },
|
||||||
|
//Close
|
||||||
|
//}
|
||||||
|
|
||||||
|
//impl Command<Option<Dialog>> for DialogCommand {
|
||||||
|
//fn execute (self, state: &mut Option<Dialog>) -> Perhaps<Self> {
|
||||||
|
//match self {
|
||||||
|
//Self::Open { dialog } => {
|
||||||
|
//*state = Some(dialog);
|
||||||
|
//},
|
||||||
|
//Self::Close => {
|
||||||
|
//*state = None;
|
||||||
|
//}
|
||||||
|
//};
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//dsl!(DialogCommand: |self: Dialog, iter|todo!());
|
||||||
|
//Dsl::take(&mut self.dialog, iter));
|
||||||
|
|
||||||
|
//#[tengri_proc::command(Option<Dialog>)]//Nope.
|
||||||
|
//impl DialogCommand {
|
||||||
|
//fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
||||||
|
//*dialog = Some(new);
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
||||||
|
//*dialog = None;
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//dsl_bind!(AppCommand: App {
|
||||||
|
//enqueue = |app, clip: Option<Arc<RwLock<MidiClip>>>| { todo!() };
|
||||||
|
//history = |app, delta: isize| { todo!() };
|
||||||
|
//zoom = |app, zoom: usize| { todo!() };
|
||||||
|
//stop_all = |app| { app.tracks_stop_all(); Ok(None) };
|
||||||
|
////dialog = |app, command: DialogCommand|
|
||||||
|
////Ok(command.delegate(&mut app.dialog, |c|Self::Dialog{command: c})?);
|
||||||
|
//project = |app, command: ArrangementCommand|
|
||||||
|
//Ok(command.delegate(&mut app.project, |c|Self::Project{command: c})?);
|
||||||
|
//clock = |app, command: ClockCommand|
|
||||||
|
//Ok(command.execute(app.clock_mut())?.map(|c|Self::Clock{command: c}));
|
||||||
|
//sampler = |app, command: SamplerCommand|
|
||||||
|
//Ok(app.project.sampler_mut().map(|s|command.delegate(s, |command|Self::Sampler{command}))
|
||||||
|
//.transpose()?.flatten());
|
||||||
|
//pool = |app, command: PoolCommand| {
|
||||||
|
//let undo = command.clone().delegate(&mut app.pool, |command|AppCommand::Pool{command})?;
|
||||||
|
//// update linked editor after pool action
|
||||||
|
//match command {
|
||||||
|
//// autoselect: automatically load selected clip in editor
|
||||||
|
//PoolCommand::Select { .. } |
|
||||||
|
//// autocolor: update color in all places simultaneously
|
||||||
|
//PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => {
|
||||||
|
//let clip = app.pool.clip().clone();
|
||||||
|
//app.editor_mut().map(|editor|editor.set_clip(clip.as_ref()))
|
||||||
|
//},
|
||||||
|
//_ => None
|
||||||
|
//};
|
||||||
|
//Ok(undo)
|
||||||
|
//};
|
||||||
|
//select = |app, selection: Selection| {
|
||||||
|
//*app.project.selection_mut() = selection;
|
||||||
|
////todo!
|
||||||
|
////if let Some(ref mut editor) = app.editor_mut() {
|
||||||
|
////editor.set_clip(match selection {
|
||||||
|
////Selection::TrackClip { track, scene } if let Some(Some(Some(clip))) = app
|
||||||
|
////.project
|
||||||
|
////.scenes.get(scene)
|
||||||
|
////.map(|s|s.clips.get(track))
|
||||||
|
////=>
|
||||||
|
////Some(clip),
|
||||||
|
////_ =>
|
||||||
|
////None
|
||||||
|
////});
|
||||||
|
////}
|
||||||
|
//Ok(None)
|
||||||
|
////("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::TrackClip { track: t, scene: s }) })))
|
||||||
|
//// autoedit: load focused clip in editor.
|
||||||
|
//};
|
||||||
|
////fn color (app: &mut App, theme: ItemTheme) -> Perhaps<Self> {
|
||||||
|
////Ok(app.set_color(Some(theme)).map(|theme|Self::Color{theme}))
|
||||||
|
////}
|
||||||
|
////fn launch (app: &mut App) -> Perhaps<Self> {
|
||||||
|
////app.project.launch();
|
||||||
|
////Ok(None)
|
||||||
|
////}
|
||||||
|
//toggle_editor = |app, value: bool|{ app.toggle_editor(Some(value)); Ok(None) };
|
||||||
|
//editor = |app, command: MidiEditCommand| Ok(if let Some(editor) = app.editor_mut() {
|
||||||
|
//let undo = command.clone().delegate(editor, |command|AppCommand::Editor{command})?;
|
||||||
|
//// update linked sampler after editor action
|
||||||
|
//app.project.sampler_mut().map(|sampler|match command {
|
||||||
|
//// autoselect: automatically select sample in sampler
|
||||||
|
//MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); },
|
||||||
|
//_ => {}
|
||||||
|
//});
|
||||||
|
//undo
|
||||||
|
//} else {
|
||||||
|
//None
|
||||||
|
//});
|
||||||
|
//});
|
||||||
|
//take!(ClockCommand |state: App, iter|Take::take(state.clock(), iter));
|
||||||
|
//take!(MidiEditCommand |state: App, iter|Ok(state.editor().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
|
//take!(PoolCommand |state: App, iter|Take::take(&state.pool, iter));
|
||||||
|
//take!(SamplerCommand |state: App, iter|Ok(state.project.sampler().map(|x|Take::take(x, iter)).transpose()?.flatten()));
|
||||||
|
//take!(ArrangementCommand |state: App, iter|Take::take(&state.project, iter));
|
||||||
65
app/tek_cfg.rs
Normal file
65
app/tek_cfg.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Configuration.
|
||||||
|
///
|
||||||
|
/// Contains mode, view, and bind definitions.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub dirs: BaseDirectories,
|
||||||
|
pub modes: Modes,
|
||||||
|
pub views: Views,
|
||||||
|
pub binds: Binds,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
const CONFIG: &'static str = "tek.edn";
|
||||||
|
const DEFAULTS: &'static str = include_str!("./tek.edn");
|
||||||
|
|
||||||
|
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||||
|
Self {
|
||||||
|
dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn init (&mut self) -> Usually<()> {
|
||||||
|
self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(&dsl))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn init_file (
|
||||||
|
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||||
|
) -> Usually<()> {
|
||||||
|
if self.dirs.find_config_file(path).is_none() {
|
||||||
|
println!("Creating {path:?}");
|
||||||
|
std::fs::write(self.dirs.place_config_file(path)?, defaults)?;
|
||||||
|
}
|
||||||
|
Ok(if let Some(path) = self.dirs.find_config_file(path) {
|
||||||
|
println!("Loading {path:?}");
|
||||||
|
let src = std::fs::read_to_string(&path)?;
|
||||||
|
src.as_str().each(move|item|each(self, item))?;
|
||||||
|
} else {
|
||||||
|
return Err(format!("{path}: not found").into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> {
|
||||||
|
dsl.each(|item|if let Some(expr) = item.expr()? {
|
||||||
|
let head = expr.head()?;
|
||||||
|
let tail = expr.tail()?;
|
||||||
|
let name = tail.head()?;
|
||||||
|
let body = tail.tail()?;
|
||||||
|
println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||||
|
match head {
|
||||||
|
Some("mode") if let Some(name) = name =>
|
||||||
|
Mode::<Arc<str>>::load_into(&self.modes, &name, &body)?,
|
||||||
|
Some("keys") if let Some(name) = name =>
|
||||||
|
EventMap::<TuiEvent, Arc<str>>::load_into(&self.binds, &name, &body)?,
|
||||||
|
Some("view") if let Some(name) = name => {
|
||||||
|
self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into());
|
||||||
|
},
|
||||||
|
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
138
app/tek_cli.rs
Normal file
138
app/tek_cli.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
pub(crate) use tek::*;
|
||||||
|
pub(crate) use tek_device::*;
|
||||||
|
pub(crate) use tek_engine::*;
|
||||||
|
pub(crate) use tengri::{*, tui::*};
|
||||||
|
pub(crate) use clap::{self, Parser, Subcommand};
|
||||||
|
|
||||||
|
/// Application entrypoint.
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
Cli::parse().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CLI header
|
||||||
|
const HEADER: &'static str = r#"
|
||||||
|
~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ ~~~~~~ ~ ~~~
|
||||||
|
~~ ║ ~ ╟─╌ ~╟─< ~ v0.3.0, 2025 sum(m)er @ the nose of the cat. ~
|
||||||
|
~~~ ╨ ~ ╙──╜ ╨ ╜ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ ~~ ~~ ~ ~~
|
||||||
|
On first run, Tek will create configuration and state dirs:
|
||||||
|
* [x] ~/.config/tek - config
|
||||||
|
* [ ] ~/.local/share/tek - projects
|
||||||
|
* [ ] ~/.local/lib/tek - plugins
|
||||||
|
* [ ] ~/.cache/tek - cache
|
||||||
|
~"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(name = "tek", version, about = Some(HEADER), long_about = Some(HEADER))]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Pre-defined configuration modes.
|
||||||
|
///
|
||||||
|
/// TODO: Replace these with scripted configurations.
|
||||||
|
#[command(subcommand)] mode: Option<LaunchMode>,
|
||||||
|
/// Name of JACK client
|
||||||
|
#[arg(short='n', long)] name: Option<String>,
|
||||||
|
/// Whether to attempt to become transport master
|
||||||
|
#[arg(short='S', long, default_value_t = false)] sync_lead: bool,
|
||||||
|
/// Whether to sync to external transport master
|
||||||
|
#[arg(short='s', long, default_value_t = true)] sync_follow: bool,
|
||||||
|
/// Initial tempo in beats per minute
|
||||||
|
#[arg(short='b', long, default_value = None)] bpm: Option<f64>,
|
||||||
|
/// Whether to include a transport toolbar (default: true)
|
||||||
|
#[arg(short='t', long, default_value_t = true)] show_clock: bool,
|
||||||
|
/// MIDI outs to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='I', long)] midi_from: Vec<String>,
|
||||||
|
/// MIDI outs to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='i', long)] midi_from_re: Vec<String>,
|
||||||
|
/// MIDI ins to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='O', long)] midi_to: Vec<String>,
|
||||||
|
/// MIDI ins to connect to (multiple instances accepted)
|
||||||
|
#[arg(short='o', long)] midi_to_re: Vec<String>,
|
||||||
|
/// Audio outs to connect to left input
|
||||||
|
#[arg(short='l', long)] left_from: Vec<String>,
|
||||||
|
/// Audio outs to connect to right input
|
||||||
|
#[arg(short='r', long)] right_from: Vec<String>,
|
||||||
|
/// Audio ins to connect from left output
|
||||||
|
#[arg(short='L', long)] left_to: Vec<String>,
|
||||||
|
/// Audio ins to connect from right output
|
||||||
|
#[arg(short='R', long)] right_to: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Application modes
|
||||||
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
|
pub enum LaunchMode {
|
||||||
|
/// Create a new session instead of loading the previous one.
|
||||||
|
New,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
fn midi_froms (&self) -> Vec<Connect> {
|
||||||
|
Connect::collect(&self.midi_from, &[] as &[&str], &self.midi_from_re)
|
||||||
|
}
|
||||||
|
fn midi_tos (&self) -> Vec<Connect> {
|
||||||
|
Connect::collect(&self.midi_to, &[] as &[&str], &self.midi_to_re)
|
||||||
|
}
|
||||||
|
pub fn run (&self) -> Usually<()> {
|
||||||
|
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
|
||||||
|
let tracks = vec![];
|
||||||
|
let scenes = vec![];
|
||||||
|
let empty = &[] as &[&str];
|
||||||
|
let left_froms = Connect::collect(&self.left_from, empty, empty);
|
||||||
|
let left_tos = Connect::collect(&self.left_to, empty, empty);
|
||||||
|
let right_froms = Connect::collect(&self.right_from, empty, empty);
|
||||||
|
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
||||||
|
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||||
|
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
||||||
|
let mut config = Config::new(None);
|
||||||
|
config.init()?;
|
||||||
|
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
||||||
|
let midi_ins = {
|
||||||
|
let mut midi_ins = vec![];
|
||||||
|
for (index, connect) in self.midi_froms().iter().enumerate() {
|
||||||
|
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
||||||
|
}
|
||||||
|
midi_ins
|
||||||
|
};
|
||||||
|
let midi_outs = {
|
||||||
|
let mut midi_outs = vec![];
|
||||||
|
for (index, connect) in self.midi_tos().iter().enumerate() {
|
||||||
|
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
||||||
|
};
|
||||||
|
midi_outs
|
||||||
|
};
|
||||||
|
let project = Arrangement {
|
||||||
|
name: Default::default(),
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
clock: Clock::new(&jack, self.bpm)?,
|
||||||
|
tracks,
|
||||||
|
scenes,
|
||||||
|
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||||
|
midi_ins,
|
||||||
|
midi_outs,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let app = App {
|
||||||
|
jack: jack.clone(),
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
dialog: Dialog::welcome(),
|
||||||
|
mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(),
|
||||||
|
config,
|
||||||
|
project,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
jack.sync_lead(self.sync_lead, |mut state|{
|
||||||
|
let clock = app.clock();
|
||||||
|
clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
state.position.bbt = Some(clock.bbt());
|
||||||
|
state.position
|
||||||
|
})?;
|
||||||
|
jack.sync_follow(self.sync_follow)?;
|
||||||
|
Ok(app)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] #[test] fn test_cli () {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
Cli::command().debug_assert();
|
||||||
|
//let jack = Jack::default();
|
||||||
|
}
|
||||||
39
app/tek_deps.rs
Normal file
39
app/tek_deps.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) use ::{
|
||||||
|
tek_device::{*, tek_engine::*},
|
||||||
|
tengri::{
|
||||||
|
Usually, Perhaps, Has, MaybeHas, has, maybe_has, impl_debug, from,
|
||||||
|
wrap_inc, wrap_dec,
|
||||||
|
dsl::*,
|
||||||
|
input::*,
|
||||||
|
output::*,
|
||||||
|
tui::{
|
||||||
|
*,
|
||||||
|
ratatui::{
|
||||||
|
self,
|
||||||
|
prelude::{Rect, Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}},
|
||||||
|
widgets::{Widget, canvas::{Canvas, Line}},
|
||||||
|
},
|
||||||
|
crossterm::{
|
||||||
|
self,
|
||||||
|
event::{Event, KeyCode::{self, *}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed},
|
||||||
|
error::Error,
|
||||||
|
collections::BTreeMap,
|
||||||
|
fmt::Write,
|
||||||
|
cmp::Ord,
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
fs::File,
|
||||||
|
ops::{Add, Sub, Mul, Div, Rem},
|
||||||
|
thread::JoinHandle
|
||||||
|
},
|
||||||
|
xdg::BaseDirectories,
|
||||||
|
atomic_float::*
|
||||||
|
};
|
||||||
61
app/tek_mode.rs
Normal file
61
app/tek_mode.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||||
|
|
||||||
|
/// A set of currently active view and keys definitions,
|
||||||
|
/// with optional name and description.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Mode<D: Dsl + Ord> {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub name: Vec<D>,
|
||||||
|
pub info: Vec<D>,
|
||||||
|
pub view: Vec<D>,
|
||||||
|
pub keys: Vec<D>,
|
||||||
|
pub modes: Modes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: Dsl + Ord> Draw<TuiOut> for Mode<D> {
|
||||||
|
fn draw (&self, _to: &mut TuiOut) {
|
||||||
|
//self.content().draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode<Arc<str>> {
|
||||||
|
|
||||||
|
pub fn load_into (modes: &Modes, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
||||||
|
let mut mode = Self::default();
|
||||||
|
println!("Mode::load_into: {}: {body:?}", name.as_ref());
|
||||||
|
body.each(|item|mode.load_one(item))?;
|
||||||
|
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> {
|
||||||
|
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
|
||||||
|
println!("Mode::load_one: {head} {:?}", expr.tail());
|
||||||
|
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||||
|
match head {
|
||||||
|
"name" => self.name.push(tail.into()),
|
||||||
|
"info" => self.info.push(tail.into()),
|
||||||
|
"view" => self.view.push(tail.into()),
|
||||||
|
"keys" => tail.each(|expr|{
|
||||||
|
self.keys.push(expr.trim().into());
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
"mode" => if let Some(id) = tail.head()? {
|
||||||
|
Self::load_into(&self.modes, &id, &tail.tail())?;
|
||||||
|
} else {
|
||||||
|
return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into());
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if let Ok(Some(word)) = dsl.word() {
|
||||||
|
self.view.push(word.into());
|
||||||
|
} else {
|
||||||
|
return Err(format!("Mode::load_one: unexpected: {dsl:?}").into());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_cli () {
|
|
||||||
use clap::CommandFactory;
|
|
||||||
cli::Cli::command().debug_assert();
|
|
||||||
//let jack = Jack::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
#[cfg(test)] #[test] fn test_app () -> Usually<()> {
|
||||||
let mut app = model::App::default();
|
let mut app = App::default();
|
||||||
let _ = app.scene_add(None, None)?;
|
let _ = app.scene_add(None, None)?;
|
||||||
let _ = app.update_clock();
|
let _ = app.update_clock();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -59,7 +53,7 @@ use crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_iter () {
|
#[cfg(test)] #[test] fn test_view_iter () {
|
||||||
let mut app = model::App::default();
|
let mut app = App::default();
|
||||||
app.project.editor = Some(Default::default());
|
app.project.editor = Some(Default::default());
|
||||||
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||||
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
//let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||||
|
|
@ -70,7 +64,7 @@ use crate::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] #[test] fn test_view_sizes () {
|
#[cfg(test)] #[test] fn test_view_sizes () {
|
||||||
let app = model::App::default();
|
let app = App::default();
|
||||||
let _ = app.project.w();
|
let _ = app.project.w();
|
||||||
//let _ = app.project.w_sidebar();
|
//let _ = app.project.w_sidebar();
|
||||||
//let _ = app.project.w_tracks_area();
|
//let _ = app.project.w_tracks_area();
|
||||||
|
|
|
||||||
353
app/tek_view.rs
Normal file
353
app/tek_view.rs
Normal file
|
|
@ -0,0 +1,353 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||||
|
|
||||||
|
impl Draw<TuiOut> for App {
|
||||||
|
fn draw (&self, to: &mut TuiOut) {
|
||||||
|
for (index, dsl) in self.mode.view.iter().enumerate() {
|
||||||
|
if let Err(e) = self.view(to, dsl) {
|
||||||
|
panic!("render #{index} failed ({e}): {dsl}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View<TuiOut, ()> for App {
|
||||||
|
fn view_expr <'a> (&'a self, to: &mut TuiOut, expr: &'a impl DslExpr) -> Usually<()> {
|
||||||
|
if evaluate_output_expression(self, to, expr)?
|
||||||
|
|| evaluate_output_expression_tui(self, to, expr)? {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("App::view_expr: unexpected: {expr:?}").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn view_word <'a> (&'a self, to: &mut TuiOut, dsl: &'a impl DslExpr) -> Usually<()> {
|
||||||
|
let mut frags = dsl.src()?.unwrap().split("/");
|
||||||
|
match frags.next() {
|
||||||
|
Some(":logo") => to.place(&view_logo()),
|
||||||
|
Some(":status") => to.place(&Fixed::Y(1, "TODO: Status Bar")),
|
||||||
|
Some(":meters") => match frags.next() {
|
||||||
|
Some("input") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::Y(Align::s("Input Meters")))),
|
||||||
|
Some("output") => to.place(&Tui::bg(Rgb(30, 30, 30), Fill::Y(Align::s("Output Meters")))),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":tracks") => match frags.next() {
|
||||||
|
None => to.place(&"TODO tracks"),
|
||||||
|
Some("names") => to.place(&self.project.view_track_names(self.color.clone())),//Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Names")))),
|
||||||
|
Some("inputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Inputs")))),
|
||||||
|
Some("devices") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Devices")))),
|
||||||
|
Some("outputs") => to.place(&Tui::bg(Rgb(40, 40, 40), Fill::X(Align::w("Track Outputs")))),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":scenes") => match frags.next() {
|
||||||
|
None => to.place(&"TODO scenes"),
|
||||||
|
Some(":scenes/names") => to.place(&"TODO Scene Names"),
|
||||||
|
_ => panic!()
|
||||||
|
},
|
||||||
|
Some(":editor") => to.place(&"TODO Editor"),
|
||||||
|
Some(":dialog") => match frags.next() {
|
||||||
|
Some("menu") => to.place(&if let Dialog::Menu(selected, items) = &self.dialog {
|
||||||
|
let items = items.clone();
|
||||||
|
let selected = selected;
|
||||||
|
Some(Fill::XY(Thunk::new(move|to: &mut TuiOut|{
|
||||||
|
for (index, MenuItem(item, _)) in items.0.iter().enumerate() {
|
||||||
|
to.place(&Push::Y((2 * index) as u16,
|
||||||
|
Tui::fg_bg(
|
||||||
|
if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
||||||
|
if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
||||||
|
Fixed::Y(2, Align::n(Fill::X(item)))
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}),
|
||||||
|
_ => unimplemented!("App::view_word: {dsl:?} ({frags:?})"),
|
||||||
|
},
|
||||||
|
Some(":templates") => to.place(&{
|
||||||
|
let modes = self.config.modes.clone();
|
||||||
|
let height = (modes.read().unwrap().len() * 2) as u16;
|
||||||
|
Fixed::Y(height, Min::X(30, Thunk::new(move |to: &mut TuiOut|{
|
||||||
|
for (index, (id, profile)) in modes.read().unwrap().iter().enumerate() {
|
||||||
|
let bg = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
||||||
|
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||||
|
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||||
|
let fg1 = Rgb(224, 192, 128);
|
||||||
|
let fg2 = Rgb(224, 128, 32);
|
||||||
|
let field_name = Fill::X(Align::w(Tui::fg(fg1, name)));
|
||||||
|
let field_id = Fill::X(Align::e(Tui::fg(fg2, id)));
|
||||||
|
let field_info = Fill::X(Align::w(info));
|
||||||
|
to.place(&Push::Y((2 * index) as u16,
|
||||||
|
Fixed::Y(2, Fill::X(Tui::bg(bg, Bsp::s(
|
||||||
|
Bsp::a(field_name, field_id), field_info))))));
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}),
|
||||||
|
Some(":sessions") => to.place(&Fixed::Y(6, Min::X(30, Thunk::new(|to: &mut TuiOut|{
|
||||||
|
let fg = Rgb(224, 192, 128);
|
||||||
|
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||||
|
let bg = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||||
|
to.place(&Push::Y((2 * index) as u16,
|
||||||
|
&Fixed::Y(2, Fill::X(Tui::bg(bg, Align::w(Tui::fg(fg, name)))))));
|
||||||
|
}
|
||||||
|
})))),
|
||||||
|
Some(":browse/title") => to.place(&Fill::X(Align::w(FieldV(ItemColor::default(),
|
||||||
|
match self.dialog.browser_target().unwrap() {
|
||||||
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
|
}, Shrink::X(3, Fixed::Y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
||||||
|
Some(":device") => {
|
||||||
|
let selected = self.dialog.device_kind().unwrap();
|
||||||
|
to.place(&Bsp::s(Tui::bold(true, "Add device"), Map::south(1,
|
||||||
|
move||device_kinds().iter(),
|
||||||
|
move|_label: &&'static str, i|{
|
||||||
|
let bg = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||||
|
let lb = if i == selected { "[ " } else { " " };
|
||||||
|
let rb = if i == selected { " ]" } else { " " };
|
||||||
|
Fill::X(Tui::bg(bg, Bsp::e(lb, Bsp::w(rb, "FIXME device name")))) })))
|
||||||
|
},
|
||||||
|
Some(":debug") => to.place(&Fixed::Y(1, format!("[{:?}]", to.area()))),
|
||||||
|
Some(_) => {
|
||||||
|
let views = self.config.views.read().unwrap();
|
||||||
|
if let Some(dsl) = views.get(dsl.src()?.unwrap()) {
|
||||||
|
let dsl = dsl.clone();
|
||||||
|
std::mem::drop(views);
|
||||||
|
self.view(to, &dsl)?
|
||||||
|
} else {
|
||||||
|
unimplemented!("{dsl:?}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_logo () -> impl Content<TuiOut> {
|
||||||
|
Fixed::XY(32, 7, Tui::bold(true, Tui::fg(Rgb(240,200,180), col!{
|
||||||
|
Fixed::Y(1, ""),
|
||||||
|
Fixed::Y(1, ""),
|
||||||
|
Fixed::Y(1, "~~ ╓─╥─╖ ╓──╖ ╥ ╖ ~~~~~~~~~~~~"),
|
||||||
|
Fixed::Y(1, Bsp::e("~~~~ ║ ~ ╟─╌ ~╟─< ~~ ", Bsp::e(Tui::fg(Rgb(230,100,40), "v0.3.0"), " ~~"))),
|
||||||
|
Fixed::Y(1, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub fn view_nil (_: &App) -> TuiCb {
|
||||||
|
//|to|to.place(&Fill::XY("·"))
|
||||||
|
//}
|
||||||
|
|
||||||
|
//Bsp::s("",
|
||||||
|
//Map::south(1,
|
||||||
|
//move||app.config.binds.layers.iter()
|
||||||
|
//.filter_map(|a|(a.0)(app).then_some(a.1))
|
||||||
|
//.flat_map(|a|a)
|
||||||
|
//.filter_map(|x|if let Value::Exp(_, iter)=x.value{ Some(iter) } else { None })
|
||||||
|
//.skip(offset)
|
||||||
|
//.take(20),
|
||||||
|
//|mut b,i|Fixed::X(60, Align::w(Bsp::e("(", Bsp::e(
|
||||||
|
//b.next().map(|t|Fixed::X(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))),
|
||||||
|
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
|
||||||
|
|
||||||
|
//Dialog::Browse(BrowseTarget::Load, browser) => {
|
||||||
|
//"bobcat".boxed()
|
||||||
|
////Bsp::s(
|
||||||
|
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
||||||
|
////Tui::bold(true, " Load project: "),
|
||||||
|
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
||||||
|
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
|
////.enclose(Fill::XY(browser)))
|
||||||
|
//},
|
||||||
|
//Dialog::Browse(BrowseTarget::Export, browser) => {
|
||||||
|
//"bobcat".boxed()
|
||||||
|
////Bsp::s(
|
||||||
|
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
||||||
|
////Tui::bold(true, " Export: "),
|
||||||
|
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
||||||
|
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
|
////.enclose(Fill::XY(browser)))
|
||||||
|
//},
|
||||||
|
//Dialog::Browse(BrowseTarget::Import, browser) => {
|
||||||
|
//"bobcat".boxed()
|
||||||
|
////Bsp::s(
|
||||||
|
////Fill::X(Align::w(Margin::XY(1, 1, Bsp::e(
|
||||||
|
////Tui::bold(true, " Import: "),
|
||||||
|
////Shrink::X(3, Fixed::Y(1, RepeatH("🭻"))))))),
|
||||||
|
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
|
////.enclose(Fill::XY(browser)))
|
||||||
|
//},
|
||||||
|
//
|
||||||
|
//pub fn view_history (&self) -> impl Content<TuiOut> {
|
||||||
|
//Fixed::Y(1, Fill::X(Align::w(FieldH(self.color,
|
||||||
|
//format!("History ({})", self.history.len()),
|
||||||
|
//self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))))
|
||||||
|
//}
|
||||||
|
//pub fn view_status_h2 (&self) -> impl Content<TuiOut> {
|
||||||
|
//self.update_clock();
|
||||||
|
//let theme = self.color;
|
||||||
|
//let clock = self.clock();
|
||||||
|
//let playing = clock.is_rolling();
|
||||||
|
//let cache = clock.view_cache.clone();
|
||||||
|
////let selection = self.selection().describe(self.tracks(), self.scenes());
|
||||||
|
//let hist_len = self.history.len();
|
||||||
|
//let hist_last = self.history.last();
|
||||||
|
//Fixed::Y(2, Stack::east(move|add: &mut dyn FnMut(&dyn Draw<TuiOut>)|{
|
||||||
|
//add(&Fixed::X(5, Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
||||||
|
//Either::new(false, // TODO
|
||||||
|
//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(" ▗▄▖ ", " ▝▀▘ ",))))
|
||||||
|
//)
|
||||||
|
//)
|
||||||
|
//)));
|
||||||
|
//add(&" ");
|
||||||
|
//{
|
||||||
|
//let cache = cache.read().unwrap();
|
||||||
|
//add(&Fixed::X(15, Align::w(Bsp::s(
|
||||||
|
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||||
|
//FieldH(theme, "Time", cache.time.view.clone()),
|
||||||
|
//))));
|
||||||
|
//add(&Fixed::X(13, Align::w(Bsp::s(
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||||
|
//))));
|
||||||
|
//add(&Fixed::X(12, Align::w(Bsp::s(
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
||||||
|
//))));
|
||||||
|
////add(&Bsp::s(
|
||||||
|
//////Fill::X(Align::w(FieldH(theme, "Selected", Align::w(selection)))),
|
||||||
|
////Fill::X(Align::w(FieldH(theme, format!("History ({})", hist_len),
|
||||||
|
////hist_last.map(|last|Fill::X(Align::w(format!("{:?}", last.0))))))),
|
||||||
|
////""
|
||||||
|
////));
|
||||||
|
//////if let Some(last) = self.history.last() {
|
||||||
|
//////add(&FieldV(theme, format!("History ({})", self.history.len()),
|
||||||
|
//////Fill::X(Align::w(format!("{:?}", last.0)))));
|
||||||
|
//////}
|
||||||
|
//}
|
||||||
|
//}))
|
||||||
|
//}
|
||||||
|
//pub fn view_status_v (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.update_clock();
|
||||||
|
//let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
|
//let theme = self.color;
|
||||||
|
//let playing = self.clock().is_rolling();
|
||||||
|
//Tui::bg(theme.darker.rgb, Fixed::XY(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
//col!(
|
||||||
|
//Fill::X(Align::w(Bsp::e(
|
||||||
|
//Align::w(Tui::bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
|
||||||
|
//Either::new(false, // TODO
|
||||||
|
//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(" ▗▄▖ ", " ▝▀▘ ",))))
|
||||||
|
//)
|
||||||
|
//)
|
||||||
|
//)),
|
||||||
|
//Bsp::s(
|
||||||
|
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||||
|
//FieldH(theme, "Time", cache.time.view.clone()),
|
||||||
|
//),
|
||||||
|
//))),
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||||
|
//Fill::X(Align::w(FieldH(theme, "Buf", Bsp::e(cache.buf.view.clone(), Bsp::e(" = ", cache.lat.view.clone()))))),
|
||||||
|
//))))
|
||||||
|
//}
|
||||||
|
//pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.update_clock();
|
||||||
|
//let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
|
//view_status(Some(self.project.selection.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.project.clock.view_cache.read().unwrap();
|
||||||
|
//view_transport(self.project.clock.is_rolling(),
|
||||||
|
//cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone())
|
||||||
|
//}
|
||||||
|
//pub fn view_editor (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//let bg = self.editor()
|
||||||
|
//.and_then(|editor|editor.clip().clone())
|
||||||
|
//.map(|clip|clip.read().unwrap().color.darker)
|
||||||
|
//.unwrap_or(self.color.darker);
|
||||||
|
//Fill::XY(Tui::bg(bg.rgb, self.editor()))
|
||||||
|
//}
|
||||||
|
//pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.editor().map(|e|Fixed::X(20, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
//Fill::Y(Align::n(Bsp::s(e.clip_status(), e.edit_status()))))))
|
||||||
|
//}
|
||||||
|
//pub fn view_midi_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_midi_ins_status(self.color)
|
||||||
|
//}
|
||||||
|
//pub fn view_midi_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_midi_outs_status(self.color)
|
||||||
|
//}
|
||||||
|
//pub fn view_audio_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_audio_ins_status(self.color)
|
||||||
|
//}
|
||||||
|
//pub fn view_audio_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_audio_outs_status(self.color)
|
||||||
|
//}
|
||||||
|
//pub fn view_scenes (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//Bsp::e(
|
||||||
|
//Fixed::X(20, Align::nw(self.project.view_scenes_names())),
|
||||||
|
//self.project.view_scenes_clips(),
|
||||||
|
//)
|
||||||
|
//}
|
||||||
|
//pub fn view_scenes_names (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_scenes_names()
|
||||||
|
//}
|
||||||
|
//pub fn view_scenes_clips (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.view_scenes_clips()
|
||||||
|
//}
|
||||||
|
//pub fn view_tracks_inputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
|
//Fixed::Y(1 + self.project.midi_ins.len() as u16,
|
||||||
|
//self.project.view_inputs(self.color))
|
||||||
|
//}
|
||||||
|
//pub fn view_tracks_outputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
|
//self.project.view_outputs(self.color)
|
||||||
|
//}
|
||||||
|
//pub fn view_tracks_devices <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
|
//Fixed::Y(4, self.project.view_track_devices(self.color))
|
||||||
|
//}
|
||||||
|
//pub fn view_tracks_names <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
|
//Fixed::Y(2, self.project.view_track_names(self.color))
|
||||||
|
//}
|
||||||
|
//pub fn view_pool (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//Fixed::X(20, Bsp::s(
|
||||||
|
//Fill::X(Align::w(FieldH(self.color, "Clip pool:", ""))),
|
||||||
|
//Fill::Y(Align::n(Tui::bg(Rgb(0, 0, 0), Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
|
//.enclose(PoolView(&self.pool)))))))
|
||||||
|
//}
|
||||||
|
//pub fn view_samples_keys (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.sampler().map(|s|s.view_list(true, self.editor().unwrap()))
|
||||||
|
//}
|
||||||
|
//pub fn view_samples_grid (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.sampler().map(|s|s.view_grid())
|
||||||
|
//}
|
||||||
|
//pub fn view_sample_viewer (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.sampler().map(|s|s.view_sample(self.editor().unwrap().get_note_pos()))
|
||||||
|
//}
|
||||||
|
//pub fn view_sample_info (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos()))
|
||||||
|
//}
|
||||||
|
//pub fn view_sample_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
//self.project.sampler().map(|s|Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
//Fill::Y(Align::n(s.view_sample_status(self.editor().unwrap().get_note_pos())))))
|
||||||
|
//}
|
||||||
|
////let options = ||["Projects", "Settings", "Help", "Quit"].iter();
|
||||||
|
////let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
|
||||||
|
////Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
|
||||||
1
deps/dizzle
vendored
1
deps/dizzle
vendored
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 1ce18223c60eac427da617d948ad18808d2f5b38
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit a933cbe2857613a439761e38a9634cf85dc17461
|
Subproject commit 8c54510f630e8a81b7d7bdca0a51a69cdb9dffcc
|
||||||
|
|
@ -74,25 +74,6 @@ impl Arrangement {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
/// Create a new arrangement.
|
|
||||||
pub fn new (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: Option<Arc<str>>,
|
|
||||||
clock: Clock,
|
|
||||||
tracks: Vec<Track>,
|
|
||||||
scenes: Vec<Scene>,
|
|
||||||
midi_ins: Vec<MidiInput>,
|
|
||||||
midi_outs: Vec<MidiOutput>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
clock, tracks, scenes, midi_ins, midi_outs,
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.unwrap_or_default(),
|
|
||||||
color: ItemTheme::random(),
|
|
||||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Width of display
|
/// Width of display
|
||||||
pub fn w (&self) -> u16 {
|
pub fn w (&self) -> u16 {
|
||||||
self.size.w() as u16
|
self.size.w() as u16
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
name = "tek";
|
name = "tek";
|
||||||
stdenv = pkgs.clang19Stdenv;
|
stdenv = pkgs.clang19Stdenv;
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pkgs.bacon
|
|
||||||
pkgs.pkg-config
|
pkgs.pkg-config
|
||||||
pkgs.freetype
|
pkgs.freetype
|
||||||
pkgs.libclang
|
pkgs.libclang
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue