#![allow(clippy::unit_arg)] #![feature(adt_const_params, associated_type_defaults, closure_lifetime_binder, if_let_guard, impl_trait_in_assoc_type, trait_alias, type_alias_impl_trait, type_changing_struct_update)] #[cfg(test)] mod tek_test; #[allow(unused)] pub(crate) use ::std::{ cmp::Ord, collections::BTreeMap, error::Error, ffi::OsString, fmt::{Write, Debug, Formatter}, fs::File, ops::{Add, Sub, Mul, Div, Rem}, path::{Path, PathBuf}, sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}}, thread::JoinHandle, }; #[allow(unused)] pub(crate) use ::{ xdg::BaseDirectories, atomic_float::*, tengri::tui::ratatui::{ self, prelude::{Rect, Style, Stylize, Buffer, Modifier, buffer::Cell, Color::{self, *}}, widgets::{Widget, canvas::{Canvas, Line}}, }, tengri::tui::crossterm::{ self, event::{Event, KeyEvent, KeyCode::{self, *}}, }, }; pub extern crate tengri; pub(crate) use tengri::{*, input::*, output::*, tui::*}; pub extern crate tek_device; pub(crate) use tek_device::{*, tek_engine::*}; mod tek_struct; pub use self::tek_struct::*; mod tek_impls; /// Command-line entrypoint. #[cfg(feature = "cli")] pub fn main () -> Usually<()> { use clap::Parser; Cli::parse().run() } /// Create a new application from a backend, project, config, and mode /// /// ``` /// let jack = tek_engine::Jack::new(&"test_tek").expect("failed to connect to jack"); /// let proj = Default::default(); /// let conf = Default::default(); /// let tek = tek::tek(&jack, proj, conf, ""); /// ``` pub fn tek ( jack: &Jack<'static>, project: Arrangement, config: Config, mode: impl AsRef ) -> App { App { color: ItemTheme::random(), dialog: Dialog::welcome(), jack: jack.clone(), mode: config.get_mode(mode).expect("failed to find mode"), config, project, ..Default::default() } } /// Collection of interaction modes. pub type Modes = Arc, Arc>>>>>; /// Collection of input bindings. pub type Binds = Arc, Bind>>>>; /// Collection of view definitions. pub type Views = Arc, Arc>>>; def_command!(AppCommand: |app: App| { Nop => Ok(None), Confirm => tek_confirm(app), Cancel => todo!(), // TODO delegate: Inc { axis: ControlAxis } => tek_inc(app, axis), Dec { axis: ControlAxis } => tek_dec(app, axis), SetDialog { dialog: Dialog } => { swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }) }, }); fn tek_confirm (state: &mut App) -> Perhaps { Ok(match &state.dialog { Dialog::Menu(index, items) => { let callback = items.0[*index].1.clone(); callback(state)?; None }, _ => todo!(), }) } fn tek_inc (state: &mut App, axis: &ControlAxis) -> Perhaps { Ok(match (&state.dialog, axis) { (Dialog::None, _) => todo!(), (Dialog::Menu(_, _), ControlAxis::Y) => AppCommand::SetDialog { dialog: state.dialog.menu_next() } .execute(state)?, _ => todo!() }) } fn tek_dec (state: &mut App, axis: &ControlAxis) -> Perhaps { Ok(match (&state.dialog, axis) { (Dialog::None, _) => None, (Dialog::Menu(_, _), ControlAxis::Y) => AppCommand::SetDialog { dialog: state.dialog.menu_prev() } .execute(state)?, _ => todo!() }) } pub fn load_view (views: &Views, name: &impl AsRef, body: &impl Language) -> Usually<()> { views.write().unwrap().insert(name.as_ref().into(), body.src()?.unwrap_or_default().into()); Ok(()) } pub fn load_mode (modes: &Modes, name: &impl AsRef, body: &impl Language) -> Usually<()> { let mut mode = Mode::default(); body.each(|item|mode.add(item))?; modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); Ok(()) } pub fn load_bind (binds: &Binds, name: &impl AsRef, body: &impl Language) -> Usually<()> { let mut map = Bind::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(()) } /// CLI banner. pub(crate) const HEADER: &'static str = r#" ~ █▀█▀█ █▀▀█ █ █ ~~~ ~ ~ ~~ ~ ~ ~ ~~ ~ ~ ~ ~ █ █▀ █▀▀▄ ~ v0.4.0, 2026 winter (or is it) ~ ~ ▀ █▀▀█ ▀ ▀ ~ ~~~ ~ ~ ~ ~ ~~~ ~~~ ~ ~~ "#; fn view_logo () -> impl Content { 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, "~~~~ ╨ ~ ╙──╜ ╨ ╜ ~~~~~~~~~~~~"), }))) } fn collect_commands ( app: &App, input: &TuiIn ) -> Usually> { let mut commands = vec![]; for id in app.mode.keys.iter() { if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) && let Some(bindings) = event_map.query(input.event()) { for binding in bindings { for command in binding.commands.iter() { if let Some(command) = app.namespace(command)? as Option { commands.push(command) } } } } } Ok(commands) } fn execute_commands ( app: &mut App, commands: Vec ) -> Usually)>> { let mut history = vec![]; for command in commands.into_iter() { let result = command.execute(app); match result { Err(err) => { history.push((command, None)); return Err(err) } Ok(undo) => { history.push((command, undo)); } }; } Ok(history) } pub fn tek_jack_process (app: &mut App, client: &Client, scope: &ProcessScope) -> Control { let t0 = app.perf.get_t0(); app.clock().update_from_scope(scope).unwrap(); let midi_in = app.project.midi_input_collect(scope); if let Some(editor) = &app.editor() { let mut pitch: Option = None; for port in midi_in.iter() { for event in port.iter() { if let (_, Ok(LiveEvent::Midi {message: MidiMessage::NoteOn {key, ..}, ..})) = event { pitch = Some(key.clone()); } } } if let Some(pitch) = pitch { editor.set_note_pos(pitch.as_int() as usize); } } let result = app.project.process_tracks(client, scope); app.perf.update_from_jack_scope(t0, scope); result } pub fn tek_jack_event (app: &mut App, event: JackEvent) { use JackEvent::*; match event { SampleRate(sr) => { app.clock().timebase.sr.set(sr as f64); }, PortRegistration(_id, true) => { //let port = app.jack().port_by_id(id); //println!("\rport add: {id} {port:?}"); //println!("\rport add: {id}"); }, PortRegistration(_id, false) => { /*println!("\rport del: {id}")*/ }, PortsConnected(_a, _b, true) => { /*println!("\rport conn: {a} {b}")*/ }, PortsConnected(_a, _b, false) => { /*println!("\rport disc: {a} {b}")*/ }, ClientRegistration(_id, true) => {}, ClientRegistration(_id, false) => {}, ThreadInit => {}, XRun => {}, GraphReorder => {}, _ => { panic!("{event:?}"); } } } pub fn tek_show_version () { println!("todo version"); } pub fn tek_print_config (config: &Config) { use ::ansi_term::Color::*; println!("{:?}", config.dirs); for (k, v) in config.views.read().unwrap().iter() { println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}"))); } for (k, v) in config.binds.read().unwrap().iter() { println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}"))); for (k, v) in v.0.iter() { print!("{} ", &Yellow.paint(match &k.0 { Event::Key(KeyEvent { modifiers, .. }) => format!("{:>16}", format!("{modifiers}")), _ => unimplemented!() })); print!("{}", &Yellow.bold().paint(match &k.0 { Event::Key(KeyEvent { code, .. }) => format!("{:<10}", format!("{code}")), _ => unimplemented!() })); for v in v.iter() { print!(" => {:?}", v.commands); print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default()); println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default()); //println!(" {:?}", v.source); } } } for (k, v) in config.modes.read().unwrap().iter() { println!(); for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); } for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); } print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}"))); print!("\n{}", Blue.paint("KEYS")); for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); } println!(); for (k, v) in v.modes.read().unwrap().iter() { print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name); print!(" INFO={:?}", v.info); print!(" VIEW={:?}", v.view); println!(" KEYS={:?}", v.keys); } print!("{}", Blue.paint("VIEW")); for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); } println!(); } } pub fn tek_print_status (project: &Arrangement) { println!("Name: {:?}", &project.name); println!("JACK: {:?}", &project.jack); println!("Buffer: {:?}", &project.clock.chunk); println!("Sample rate: {:?}", &project.clock.timebase.sr); println!("MIDI PPQ: {:?}", &project.clock.timebase.ppq); println!("Tempo: {:?}", &project.clock.timebase.bpm); println!("Quantize: {:?}", &project.clock.quant); println!("Launch: {:?}", &project.clock.sync); println!("Playhead: {:?}us", &project.clock.playhead.usec); println!("Playhead: {:?}s", &project.clock.playhead.sample); println!("Playhead: {:?}p", &project.clock.playhead.pulse); println!("Started: {:?}", &project.clock.started); println!("Tracks:"); for (i, t) in project.tracks.iter().enumerate() { println!(" Track {i}: {} {} {:?} {:?}", t.name, t.width, &t.sequencer.play_clip, &t.sequencer.next_clip); } println!("Scenes:"); for (i, t) in project.scenes.iter().enumerate() { println!(" Scene {i}: {} {:?}", &t.name, &t.clips); } println!("MIDI Ins: {:?}", &project.midi_ins); println!("MIDI Outs: {:?}", &project.midi_outs); println!("Audio Ins: {:?}", &project.audio_ins); println!("Audio Outs: {:?}", &project.audio_outs); // TODO git integration // TODO dawvert integration }