diff --git a/src/core/device.rs b/src/core/device.rs index 18f9d6ba..10b48f81 100644 --- a/src/core/device.rs +++ b/src/core/device.rs @@ -16,7 +16,7 @@ pub struct DynamicDevice { pub render: MutexUsually + Send>>, pub handle: ArcUsually + Send>>>, pub process: ArcControl + Send>>>, - pub client: Option + pub client: Option } impl Handle for DynamicDevice { @@ -46,10 +46,6 @@ impl PortList for DynamicDevice { } } -type DynamicAsyncClient = AsyncClient; -type DynamicNotifications = Notifications>; -type DynamicProcessHandler = ClosureProcessHandler; - impl DynamicDevice { pub fn new <'a, R, H, P> (render: R, handle: H, process: P, state: T) -> Self where R: FnMut(&T, &mut Buffer, Rect) -> Usually + Send + 'static, diff --git a/src/core/jack.rs b/src/core/jack.rs index 8ae5ae62..71dbec7e 100644 --- a/src/core/jack.rs +++ b/src/core/jack.rs @@ -1,13 +1,37 @@ use crate::core::*; -pub type BoxedNotificationHandler = - Box; +pub trait Process { + fn process (&mut self, c: &Client, s: &ProcessScope) -> Control; +} + +pub type DynamicAsyncClient = + AsyncClient; + +pub type DynamicNotifications = + Notifications>; + +pub type DynamicProcessHandler = + ClosureProcessHandler; pub type BoxedProcessHandler = Box Control + Send>; -pub type Jack = - AsyncClient>; +pub fn jack_run (name: &str, app: &Arc>) -> Usually + where T: Handle + Process + Send + 'static +{ + let options = ClientOptions::NO_START_SERVER; + let (client, _status) = Client::new(name, options)?; + Ok(client.activate_async( + Notifications(Box::new({ + let app = app.clone(); + move|event|{app.lock().unwrap().handle(&event).unwrap();} + }) as Box), + ClosureProcessHandler::new(Box::new({ + let app = app.clone(); + move|c: &Client, s: &ProcessScope|{app.lock().unwrap().process(c, s)} + }) as BoxedProcessHandler) + )?) +} pub use ::jack::{ AsyncClient, diff --git a/src/core/mod.rs b/src/core/mod.rs index afeffb8a..2332b938 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,33 +1,20 @@ -pub type Usually = Result>; - -macro_rules! submod { - ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; -} - -submod!( device handle jack keymap port render run time ); - pub use std::error::Error; pub use std::io::{stdout, Stdout, Write}; pub use std::thread::{spawn, JoinHandle}; pub use std::time::Duration; pub use std::collections::BTreeMap; -pub use std::sync::{ - Arc, Mutex, MutexGuard, - atomic::{Ordering, AtomicBool, AtomicUsize}, - mpsc::{self, channel, Sender, Receiver} -}; +pub use std::sync::{Arc, Mutex, MutexGuard}; +pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize}; +pub use std::sync::mpsc::{channel, Sender, Receiver}; +pub use ratatui::prelude::*; +pub use midly::{MidiMessage, live::LiveEvent, num::u7}; +pub use crossterm::{ExecutableCommand, QueueableCommand}; +pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers}; -pub use ::crossterm::{ - ExecutableCommand, QueueableCommand, - event::{Event, KeyEvent, KeyCode, KeyModifiers}, - terminal::{ - self, - Clear, ClearType, - EnterAlternateScreen, LeaveAlternateScreen, - enable_raw_mode, disable_raw_mode - }, -}; +macro_rules! submod { ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; } + +submod!( device handle jack keymap port render run time ); + +pub type Usually = Result>; -pub use ::ratatui::prelude::*; -pub use ::midly::{MidiMessage, live::LiveEvent, num::u7}; pub use crate::{key, keymap}; diff --git a/src/core/render.rs b/src/core/render.rs index d3ba4293..7ec278fa 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -21,6 +21,16 @@ pub trait Render { fn max_height (&self) -> u16 { u16::MAX } + + //fn boxed (self) -> Box where Self: Sized + 'static { + //Box::new(self) + //} +} + +impl Usually> Render for T { + fn render (&self, b: &mut Buffer, a: Rect) -> Usually { + (*self).render(b, a) + } } impl Render for Box { diff --git a/src/core/run.rs b/src/core/run.rs index 3a1f84bb..39ecdb80 100644 --- a/src/core/run.rs +++ b/src/core/run.rs @@ -1,13 +1,21 @@ use crate::core::*; +use crossterm::terminal::{ + EnterAlternateScreen, LeaveAlternateScreen, + enable_raw_mode, disable_raw_mode +}; pub trait Run: Render + Handle + Send + Sized + 'static { - fn run (self) -> Usually<()> { + fn run (self, callback: Option>)->Usually<()>>) -> Usually<()> { let device = Arc::new(Mutex::new(self)); let exited = Arc::new(AtomicBool::new(false)); let _input_thread = input_thread(&exited, &device); terminal_setup()?; panic_hook_setup(); - main_thread(&exited, &device)?; + let main_thread = main_thread(&exited, &device)?; + if let Some(callback) = callback { + callback(device); + } + main_thread.join(); terminal_teardown()?; Ok(()) } @@ -67,12 +75,13 @@ pub fn panic_hook_setup () { /// Main thread render loop pub fn main_thread ( exited: &Arc, - device: &Arc> -) -> Usually<()> { + device: &Arc> +) -> Usually> { let exited = exited.clone(); + let device = device.clone(); let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; let sleep = std::time::Duration::from_millis(16); - loop { + Ok(spawn(move || loop { terminal.draw(|frame|{ let area = frame.size(); @@ -86,8 +95,7 @@ pub fn main_thread ( std::thread::sleep(sleep); - } - Ok(()) + })) } /// Cleanup pub fn terminal_teardown () -> Usually<()> { diff --git a/src/core/time.rs b/src/core/time.rs index 7dd99bf6..da54d441 100644 --- a/src/core/time.rs +++ b/src/core/time.rs @@ -1,5 +1,6 @@ use crate::core::*; +#[derive(Default)] pub struct Timebase { /// Frames per second pub rate: ::atomic_float::AtomicF64, diff --git a/src/device/launcher/grid.rs b/src/device/launcher/grid.rs index 6a42b045..935173be 100644 --- a/src/device/launcher/grid.rs +++ b/src/device/launcher/grid.rs @@ -1,17 +1,36 @@ use crate::core::*; use super::*; -pub struct LauncherGrid<'a> { - state: &'a Launcher, - buf: &'a mut Buffer, - area: Rect, - focused: bool, +pub struct SceneGrid<'a> { + pub buf: &'a mut Buffer, + pub area: Rect, + pub name: &'a str, + pub focused: bool, + pub scenes: &'a[Scene], + pub tracks: &'a[Track], + pub cursor: &'a(usize, usize), } -impl<'a> LauncherGrid<'a> { - pub fn new (state: &'a Launcher, buf: &'a mut Buffer, area: Rect, focused: bool) -> Self { - Self { state, buf, area, focused } +impl<'a> SceneGrid<'a> { + pub fn new ( + buf: &'a mut Buffer, + area: Rect, + name: &'a str, + focused: bool, + scenes: &'a[Scene], + tracks: &'a[Track], + cursor: &'a(usize, usize), + ) -> Self { + Self { + buf, + area, + name, + focused, + scenes, + tracks, + cursor, + } } pub fn draw (&mut self) -> Usually { - self.area.height = self.state.scenes.len() as u16 + 2; + self.area.height = self.scenes.len() as u16 + 2; let style = Some(Style::default().green().dim()); if self.focused { let Rect { x, y, width, height } = self.area; @@ -24,9 +43,9 @@ impl<'a> LauncherGrid<'a> { if x >= self.area.x + self.area.width { break } - self.separator_v(x, i == self.state.cursor.0); + self.separator_v(x, i == self.cursor.0); x = x + w; - self.separator_v(x, i == self.state.cursor.0); + self.separator_v(x, i == self.cursor.0); } let (mut x, y) = (self.area.x, self.area.y); for (i, title) in columns.iter().enumerate() { @@ -34,7 +53,7 @@ impl<'a> LauncherGrid<'a> { break } title.blit( - self.buf, x+1, y, Some(self.highlight(i == self.state.cursor.0).bold()) + self.buf, x+1, y, Some(self.highlight(i == self.cursor.0).bold()) ); if i == 0 { self.scenes(x+1, y + 1); @@ -49,8 +68,8 @@ impl<'a> LauncherGrid<'a> { } fn column_names (&self) -> Vec<&'a str> { - let mut column_names = vec![self.state.name.as_str()]; - for track in self.state.tracks.iter() { + let mut column_names = vec![self.name]; + for track in self.tracks.iter() { column_names.push(track.name.as_str()); } column_names @@ -63,23 +82,23 @@ impl<'a> LauncherGrid<'a> { fn scenes (&mut self, x: u16, y: u16) -> u16 { let mut index = 0usize; loop { - if index >= self.state.scenes.len() { + if index >= self.scenes.len() { break } if y + index as u16 >= self.area.height { break } - if let Some(scene) = self.state.scenes.get(index) { + if let Some(scene) = self.scenes.get(index) { let style = Some(self.highlight( - (0 == self.state.cursor.0) && (index + 1 == self.state.cursor.1) + (0 == self.cursor.0) && (index + 1 == self.cursor.1) ).bold()); "⯈".blit(self.buf, x, y + index as u16, style); scene.name.blit(self.buf, x+1, y + index as u16, style); } index = index + 1; } - let hi = (0 == self.state.cursor.0) && - (self.state.scenes.len() + 1 == self.state.cursor.1); + let hi = (0 == self.cursor.0) && + (self.scenes.len() + 1 == self.cursor.1); "+Add scene…".blit(self.buf, x, y + index as u16, Some(if hi { self.highlight(true) } else { @@ -91,20 +110,20 @@ impl<'a> LauncherGrid<'a> { fn clips (&mut self, x: u16, y: u16, track: usize) -> u16 { let mut index = 0; loop { - if index >= self.state.scenes.len() { + if index >= self.scenes.len() { break } if y + index as u16 >= self.area.height { break } - if let Some(scene) = self.state.scenes.get(index) { - let hi = (track + 1 == self.state.cursor.0) && - (index + 1 == self.state.cursor.1); + if let Some(scene) = self.scenes.get(index) { + let hi = (track + 1 == self.cursor.0) && + (index + 1 == self.cursor.1); let style = Some(self.highlight(hi)); let clip = scene.clips.get(track); let index = index as u16; let label = if let Some(Some(clip)) = clip { - let track = self.state.tracks[track].sequencer.state(); + let track = self.tracks[track].sequencer.state(); let phrase = track.phrases.get(*clip); if let Some(phrase) = phrase { format!("⯈{}", phrase.name) @@ -118,8 +137,8 @@ impl<'a> LauncherGrid<'a> { } index = index + 1; } - let hi = (track + 1 == self.state.cursor.0) && - (self.state.scenes.len() + 1 == self.state.cursor.1); + let hi = (track + 1 == self.cursor.0) && + (self.scenes.len() + 1 == self.cursor.1); " + Add clip".blit(self.buf, x, y + index as u16, Some(if hi { self.highlight(true) } else { diff --git a/src/device/launcher/mod.rs b/src/device/launcher/mod.rs index c922af20..21119ec0 100644 --- a/src/device/launcher/mod.rs +++ b/src/device/launcher/mod.rs @@ -1,9 +1,9 @@ use crate::core::*; use crate::layout::*; use crate::device::*; -mod grid; -pub use self::grid::*; mod handle; -pub use self::handle::*; +mod grid; pub use self::grid::*; +mod handle; pub use self::handle::*; +mod scene; pub use self::scene::*; pub struct Launcher { name: String, timebase: Arc, @@ -20,28 +20,6 @@ pub struct Launcher { view: LauncherView, modal: Option>>, } -pub enum LauncherView { - Tracks, - Sequencer, - Chains -} -impl LauncherView { - fn is_tracks (&self) -> bool { - match self { Self::Tracks => true, _ => false } - } -} -pub struct Scene { - name: String, - clips: Vec>, -} -impl Scene { - pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { - Self { - name: name.as_ref().into(), - clips: clips.as_ref().iter().map(|x|x.clone()).collect() - } - } -} impl Launcher { pub fn new ( name: &str, @@ -144,7 +122,7 @@ impl Launcher { } } impl DynamicDevice { - pub fn connect (self, midi_in: &str, audio_outs: &[&str]) -> Usually { + pub fn connect (&self, midi_in: &str, audio_outs: &[&str]) -> Usually<&Self> { { let state = &self.state(); let (client, _status) = Client::new( @@ -197,16 +175,26 @@ pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually Us } fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let style = Some(Style::default().green().dim()); - match state.view { - LauncherView::Chains => { - let Rect { x, y, width, height} = area; - lozenge_left(buf, x, y, height, style); - lozenge_right(buf, x + width - 1, y, height, style); - }, - _ => {}, - }; + if state.view.is_chains() { + let Rect { x, y, width, height} = area; + lozenge_left(buf, x, y, height, style); + lozenge_right(buf, x + width - 1, y, height, style); + } let chain = state.chain(); let _ = if let Some(chain) = &chain { let (_, plugins) = crate::device::chain::draw_as_row( @@ -281,3 +266,16 @@ fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usual }; Ok(area) } +pub enum LauncherView { + Tracks, + Sequencer, + Chains +} +impl LauncherView { + fn is_chains (&self) -> bool { + match self { Self::Chains => true, _ => false } + } + fn is_tracks (&self) -> bool { + match self { Self::Tracks => true, _ => false } + } +} diff --git a/src/device/launcher/scene.rs b/src/device/launcher/scene.rs new file mode 100644 index 00000000..bbb65582 --- /dev/null +++ b/src/device/launcher/scene.rs @@ -0,0 +1,13 @@ +pub struct Scene { + pub name: String, + pub clips: Vec>, +} + +impl Scene { + pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { + Self { + name: name.as_ref().into(), + clips: clips.as_ref().iter().map(|x|x.clone()).collect() + } + } +} diff --git a/src/device/sequencer/horizontal.rs b/src/device/sequencer/horizontal.rs index 97731873..b0a18851 100644 --- a/src/device/sequencer/horizontal.rs +++ b/src/device/sequencer/horizontal.rs @@ -127,8 +127,8 @@ pub fn lanes ( phrase.contains_note_on(u7::from_int_lossy(note_b as u8), a, b), ) { (true, true) => ("█", wh), - (false, true) => ("▄", wh), - (true, false) => ("▀", wh), + (false, true) => ("▀", wh), + (true, false) => ("▄", wh), (false, false) => ("·", bw), }; let y = y + height.saturating_sub(index+2) as u16; diff --git a/src/layout/collect.rs b/src/layout/collect.rs new file mode 100644 index 00000000..3a302869 --- /dev/null +++ b/src/layout/collect.rs @@ -0,0 +1,42 @@ + +pub enum Collected<'a> { + Box(Box), + Ref(&'a (dyn Render + 'a)), + None +} + +impl<'a> Render for Collected<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + match self { + Self::Box(item) => (*item).render(buf, area), + Self::Ref(item) => (*item).render(buf, area), + Self::None => Ok(area), + } + } +} + +pub struct Collector<'a>(pub Vec>); + +impl<'a, R: Render + 'a> FnOnce<(R)> for Collector<'a> { + type Output = (); + extern "rust-call" fn call_once (self, (device, ): (R,)) -> Self::Output { + self.add(widget.into_collected()); + } +} + +impl<'a> Collector<'a> { + pub fn collect (collect: impl Fn(&mut Collector<'a>)) -> Self { + let mut items = Self(vec![]); + collect(&mut items); + items + } + fn add (mut self, widget: Collected<'a>) -> Self { + self.0.push(widget); + self + } +} + +pub trait Collection<'a, T, U> { + fn add (self, widget: impl Render + 'a) -> Self; +} + diff --git a/src/layout/container.rs b/src/layout/container.rs index 4a463d65..257839a4 100644 --- a/src/layout/container.rs +++ b/src/layout/container.rs @@ -1,5 +1,17 @@ use crate::core::*; +pub struct Stack<'a>(pub &'a[Box]); + +impl<'a> Render for Stack<'a> { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + let mut area2 = area.clone(); + for layer in self.0.iter() { + area2 = layer.render(buf, area2)?; + } + Ok(area) + } +} + pub struct Column(pub Vec>); pub struct Row(pub Vec>); diff --git a/src/main.rs b/src/main.rs index adada051..2ba4316d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +//#![feature(fn_traits)] +//#![feature(unboxed_closures)] #![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] extern crate clap; @@ -18,23 +20,59 @@ use crate::device::*; mod new { use crate::core::*; - type Phrase = (String, usize, BTreeMap>>); - type Scene = (String, Option, Vec>); + use crate::layout::Stack; + type Phrase = (String, usize, BTreeMap>); + type Scene = (String, Option, Vec>); type Track = (String, usize); #[derive(Default)] - struct App { - phrases: BTreeMap>>, - scenes: BTreeMap>>, - tracks: BTreeMap>>, + pub struct App { + client: Option, + phrases: BTreeMap, + scenes: BTreeMap, + tracks: BTreeMap, + frame: usize, + scene: Vec, + timebase: Arc, } - fn main () { - App::default().run() - } - impl App { - fn run (self) { - panic_hook_setup(); + struct SceneGrid {} + impl Render for SceneGrid {} + struct Chains {} + impl Render for Chains {} + struct Sequencer {} + impl Render for Sequencer {} + impl Render for App { + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + crate::device::Transport { + timebase: &self.timebase, + playing: TransportState::Stopped, + record: false, + overdub: false, + monitor: false, + frame: 0, + }.render(buf, area)?; + SceneGrid { + }.render(buf, area)?; + Chains {}.render(buf, area)?; + Sequencer {}.render(buf, area)?; + Ok(area) } } + impl Handle for App { + fn handle (&mut self, _e: &AppEvent) -> Usually { + Ok(true) + } + } + impl Process for App { + fn process (&mut self, _c: &Client, _s: &ProcessScope) -> Control { + Control::Continue + } + } + pub fn main () -> Usually<()> { + App::default().run(Some(|app: Arc>|{ + app.lock().unwrap().client = Some(jack_run("tek", &app)?); + Ok(()) + })) + } } macro_rules! sample { @@ -61,8 +99,6 @@ fn main () -> Usually<()> { let xdg = microxdg::XdgApp::new("tek")?; crate::config::create_dirs(&xdg)?; //run(Sampler::new("Sampler#000")?) - let input = ".*nanoKEY.*"; - let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"]; let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?; let timebase = Arc::new(Timebase::new(client.sample_rate() as f64, 125.0, 96.0)); let ppq = timebase.ppq() as usize; @@ -213,6 +249,13 @@ fn main () -> Usually<()> { ]) )? - .connect(input, &output)? - .run() + .run(Some(init)) +} + +fn init (state: Arc>>) -> Usually<()> { + let input = ".*nanoKEY.*"; + let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"]; + let state = state.lock().unwrap(); + state.connect(input, &output)?; + Ok(()) }