diff --git a/crates/app/app.rs b/crates/app/app.rs index ad4e0a47..bdd705c7 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -10,8 +10,6 @@ mod app_view; pub use self::app_view::*; /// Total state #[derive(Default, Debug)] pub struct App { - /// Base color. - pub color: ItemTheme, /// Must not be dropped for the duration of the process pub jack: Jack<'static>, /// Display size @@ -22,30 +20,26 @@ pub struct App { pub config: Config, /// Currently selected mode pub mode: Arc>>, + /// Contains the currently edited musical arrangement + pub project: Arrangement, + /// Contains all recently created clips. + pub pool: Pool, /// Undo history pub history: Vec<(AppCommand, Option)>, /// Dialog overlay pub dialog: Dialog, - /// Contains all recently created clips. - pub pool: Pool, - /// Contains the currently edited musical arrangement - pub project: Arrangement, -} -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Axis { - min: usize, - max: usize, - step: usize, + /// Base color. + pub color: ItemTheme, } /// Various possible dialog modes. -#[derive(Debug, Clone, Default, PartialEq)] +#[derive(Debug, Clone, Default)] pub enum Dialog { #[default] None, Help(usize), - Menu(usize, Arc<[Arc]>), + Menu(usize), Device(usize), Message(Arc), - Browse(BrowseTarget, Arc), + Browser(BrowserTarget, Arc), Options, } has!(Jack<'static>: |self: App|self.jack); @@ -67,28 +61,68 @@ maybe_has!(Scene: |self: App| { MaybeHas::::get(&self.project) }; impl HasClipsSize for App { fn clips_size (&self) -> &Measure { &self.project.inner_size } } impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } +pub fn view_nil (_: &App) -> Box> { + Box::new(Fill::xy("·")) +} +content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{ + for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); } +}))); +impl App { + fn view (&self, index: D) -> Box> { + match index.src() { + Ok(Some(src)) => render_dsl(self, src), + Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")), + Err(e) => Box::new(format!("{e}")), + } + } + pub fn update_clock (&self) { + ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) + } +} +fn render_dsl <'t> ( + state: &'t impl DslNs<'t, Box>>, + src: &str +) -> Box> { + let err: Option> = match state.from(&src) { + Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), + }; + let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); + let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0)); + let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0)); + let bg = Color::Rgb(24, 0, 0); + Box::new(col! { + Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))), + Tui::bg(bg, col! { + Fill::x(Bsp::e( + Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")), + Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))), + )), + Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))), + }), + Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))), + }) +} +handle!(TuiIn:|self: App, input|{ + for id in self.mode.keys.iter() { + if let Some(event_map) = self.config.binds.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() { + let command: Option = self.from(command)?; + if let Some(command) = command { + panic!("{command:?}"); + } + } + panic!("{binding:?}"); + } + } + } + } + Ok(None) +}); impl Dialog { - pub fn welcome () -> Self { - Self::Menu(0, [ - "Continue session".into(), - "Load old session".into(), - "Begin new session".into(), - ].into()) - } - pub fn menu_next (&self) -> Self { - match self { - Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()), - _ => Self::None - } - } - pub fn menu_prev (&self) -> Self { - match self { - Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()), - _ => Self::None - } - } pub fn menu_selected (&self) -> Option { - if let Self::Menu(selected, _) = self { Some(*selected) } else { None } + if let Self::Menu(selected) = self { Some(*selected) } else { None } } pub fn device_kind (&self) -> Option { if let Self::Device(index) = self { Some(*index) } else { None } @@ -102,10 +136,10 @@ impl Dialog { pub fn message (&self) -> Option<&str> { todo!() } - pub fn browser (&self) -> Option<&Arc> { + pub fn browser (&self) -> Option<&Arc> { todo!() } - pub fn browser_target (&self) -> Option<&BrowseTarget> { + pub fn browser_target (&self) -> Option<&BrowserTarget> { todo!() } } @@ -155,8 +189,8 @@ impl App { } } } - pub fn browser (&self) -> Option<&Browse> { - if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } + pub fn browser (&self) -> Option<&Browser> { + if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None } } pub fn device_pick (&mut self, index: usize) { self.dialog = Dialog::Device(index); @@ -191,6 +225,7 @@ impl App { } } + fn wrap_dialog (dialog: impl Content) -> impl Content { Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b( Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))).enclose(dialog)))) diff --git a/crates/app/app_bind.rs b/crates/app/app_bind.rs index 6abdd449..2a13994b 100644 --- a/crates/app/app_bind.rs +++ b/crates/app/app_bind.rs @@ -1,83 +1,30 @@ use crate::*; -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 { - 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<'t> DslNs<'t, AppCommand> for App { dsl_exprs!(|app| -> AppCommand { /* TODO */ }); dsl_words!(|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, + "x/inc" => todo!(), + "x/dec" => todo!(), + "y/inc" => todo!(), + "y/dec" => todo!(), + "confirm" => todo!(), }); } +#[derive(Debug)] +pub enum AppCommand { /* TODO */ } -impl Default for AppCommand { fn default () -> Self { Self::Nop } } - -def_command!(AppCommand: |app: App| { - Nop => Ok(None), - Confirm => 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 }) - }, -}); - -pub fn wrap_inc (index: usize, count: usize) -> usize { - if count > 0 { (index + 1) % count } else { 0 } +#[tengri_proc::command(Option)] +impl DialogCommand { + fn open (dialog: &mut Option, new: Dialog) -> Perhaps { + *dialog = Some(new); + Ok(None) + } + fn close (dialog: &mut Option) -> Perhaps { + *dialog = None; + Ok(None) + } } -pub fn wrap_dec (index: usize, count: usize) -> usize { - if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 } -} - -impl Dialog { -} //AppCommand => { //("x/inc" / diff --git a/crates/app/app_data.rs b/crates/app/app_data.rs index cfb8feb3..e44ada9f 100644 --- a/crates/app/app_data.rs +++ b/crates/app/app_data.rs @@ -43,18 +43,19 @@ impl<'t> DslNs<'t, Dialog> for App { ":dialog/device/prev" => Dialog::Device(0), ":dialog/device/next" => Dialog::Device(0), ":dialog/help" => Dialog::Help(0), - ":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject, - Browse::new(None).unwrap().into()), - ":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject, - Browse::new(None).unwrap().into()), - ":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()), - Browse::new(None).unwrap().into()), - ":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()), - Browse::new(None).unwrap().into()), + ":dialog/menu" => Dialog::Menu(0), + ":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject, + Browser::new(None).unwrap().into()), + ":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject, + Browser::new(None).unwrap().into()), + ":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()), + Browser::new(None).unwrap().into()), + ":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()), + Browser::new(None).unwrap().into()), + ":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()), + Browser::new(None).unwrap().into()), + ":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()), + Browser::new(None).unwrap().into()), }); } diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index 43ac442f..6d7faef1 100644 --- a/crates/app/app_view.rs +++ b/crates/app/app_view.rs @@ -1,46 +1,5 @@ use crate::*; -content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{ - for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); } -}))); - -impl App { - fn view (&self, index: D) -> Box> { - match index.src() { - Ok(Some(src)) => render_dsl(self, src), - Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")), - Err(e) => Box::new(format!("{e}")), - } - } - pub fn update_clock (&self) { - ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80) - } -} - -fn render_dsl <'t> ( - state: &'t impl DslNs<'t, Box>>, - src: &str -) -> Box> { - let err: Option> = match state.from(&src) { - Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e), - }; - let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0)); - let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0)); - let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0)); - let bg = Color::Rgb(24, 0, 0); - Box::new(col! { - Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))), - Tui::bg(bg, col! { - Fill::x(Bsp::e( - Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")), - Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))), - )), - Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))), - }), - Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))), - }) -} - impl<'t> DslNs<'t, Box>> for App { dsl_exprs!(|app| -> Box> { "text" (tail: Arc) => Box::new(tail), @@ -83,21 +42,6 @@ impl<'t> DslNs<'t, Box>> for App { "max/xy" (x: u16, y: u16, c: Box>) => Box::new(Max::xy(x, y, c)), }); dsl_words!(|app| -> Box> { - ":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog { - let items = items.clone(); - let selected = *selected; - Some(Fill::xy(Align::c(Tui::bg(Red, Fill::x(Stack::south(move|add|{ - for (index, item) in items.iter().enumerate() { - add(&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 - }), ":templates" => Box::new({ let modes = app.config.modes.clone(); let height = (modes.read().unwrap().len() * 2) as u16; @@ -127,12 +71,12 @@ impl<'t> DslNs<'t, Box>> for App { )))), ":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(), match app.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:", + BrowserTarget::SaveProject => "Save project:", + BrowserTarget::LoadProject => "Load project:", + BrowserTarget::ImportSample(_) => "Import sample:", + BrowserTarget::ExportSample(_) => "Export sample:", + BrowserTarget::ImportClip(_) => "Import clip:", + BrowserTarget::ExportClip(_) => "Export clip:", }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), ":device" => { let selected = app.dialog.device_kind().unwrap(); @@ -174,10 +118,6 @@ impl<'t> DslNs<'t, Box>> for App { } } -pub fn view_nil (_: &App) -> Box> { - Box::new(Fill::xy("·")) -} - //Bsp::s("", //Map::south(1, //move||app.config.binds.layers.iter() @@ -190,7 +130,7 @@ pub fn view_nil (_: &App) -> Box> { //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) => { + //Dialog::Browser(BrowserTarget::Load, browser) => { //"bobcat".boxed() ////Bsp::s( ////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( @@ -199,7 +139,7 @@ pub fn view_nil (_: &App) -> Box> { ////Outer(true, Style::default().fg(Tui::g(96))) ////.enclose(Fill::xy(browser))) //}, - //Dialog::Browse(BrowseTarget::Export, browser) => { + //Dialog::Browser(BrowserTarget::Export, browser) => { //"bobcat".boxed() ////Bsp::s( ////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( @@ -208,7 +148,7 @@ pub fn view_nil (_: &App) -> Box> { ////Outer(true, Style::default().fg(Tui::g(96))) ////.enclose(Fill::xy(browser))) //}, - //Dialog::Browse(BrowseTarget::Import, browser) => { + //Dialog::Browser(BrowserTarget::Import, browser) => { //"bobcat".boxed() ////Bsp::s( ////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index 255a8d8c..a9e80a31 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -50,51 +50,56 @@ pub enum LaunchMode { impl Cli { 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()?; + let name = self.name.as_ref().map_or("tek", |x|x.as_str()); + let config = Config::init()?; + let empty = &[] as &[&str]; + let mut midi_ins = vec![]; + let mut midi_outs = vec![]; + let tracks = vec![]; + let scenes = vec![]; + let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re); + let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re); + 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()]; Tui::new()?.run(&Jack::new_run(&name, move|jack|{ + for (index, connect) in midi_froms.iter().enumerate() { + midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?); + } + for (index, connect) in midi_tos.iter().enumerate() { + midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?); + }; + let clock = Clock::new(&jack, self.bpm)?; + let mode = config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(); let app = App { jack: jack.clone(), color: ItemTheme::random(), - dialog: Dialog::welcome(), - mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(), + dialog: Dialog::Menu(0), + mode, config, project: Arrangement { name: Default::default(), color: ItemTheme::random(), jack: jack.clone(), - clock: Clock::new(&jack, self.bpm)?, + clock, tracks, scenes, selection: Selection::TrackClip { track: 0, scene: 0 }, - 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 - }, - 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 - }, + midi_ins, + midi_outs, ..Default::default() }, ..Default::default() }; + //if let LaunchMode::Arranger { scenes, tracks, track_width, .. } = self.mode { + //app.project.arranger = Default::default(); + //app.project.selection = Selection::TrackClip { track: 1, scene: 1 }; + //app.project.scenes_add(scenes)?; + //app.project.tracks_add(tracks, Some(track_width), &[], &[])?; + //} jack.sync_lead(self.sync_lead, |mut state|{ let clock = app.clock(); clock.playhead.update_from_sample(state.position.frame() as f64); @@ -105,12 +110,6 @@ impl Cli { Ok(app) })?) } - fn midi_froms (&self) -> Vec { - Connect::collect(&self.midi_from, &[] as &[&str], &self.midi_from_re) - } - fn midi_tos (&self) -> Vec { - Connect::collect(&self.midi_to, &[] as &[&str], &self.midi_to_re) - } } /// CLI header diff --git a/crates/config/config.rs b/crates/config/config.rs index d48482bb..a44e955f 100644 --- a/crates/config/config.rs +++ b/crates/config/config.rs @@ -20,15 +20,11 @@ use ::{ #[derive(Default, Debug)] pub struct Config { pub dirs: BaseDirectories, - pub modes: Modes, - pub views: Views, - pub binds: Binds, + pub modes: Arc, Arc>>>>>, + pub views: Arc, Arc>>>, + pub binds: Arc, EventMap>>>>, } -type Modes = Arc, Arc>>>>>; -type Binds = Arc, EventMap>>>>; -type Views = Arc, Arc>>>; - /// A set of currently active view and keys definitions, /// with optional name and description. #[derive(Default, Debug)] @@ -38,7 +34,7 @@ pub struct Mode { pub info: Vec, pub view: Vec, pub keys: Vec, - pub modes: Modes, + pub modes: BTreeMap>, } /// A collection of input bindings. @@ -66,16 +62,11 @@ pub struct Condition(Arcbool + Send + Sync>>); impl Config { const CONFIG: &'static str = "tek.edn"; const DEFAULTS: &'static str = include_str!("../../tek.edn"); - - pub fn new (dirs: Option) -> 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 () -> Usually { + let mut cfgs: Self = Default::default(); + cfgs.dirs = BaseDirectories::with_profile("tek", "v0"); + cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; + Ok(cfgs) } pub fn init_file ( &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> @@ -98,54 +89,74 @@ impl Config { 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()); + println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { - Some("mode") if let Some(name) = name => - Mode::>::load_into(&self.modes, &name, &body)?, - Some("keys") if let Some(name) = name => - EventMap::>::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()); - }, + Some("view") if let Some(name) = name => self.load_view(name.into(), body), + Some("keys") if let Some(name) = name => self.load_bind(name.into(), body), + Some("mode") if let Some(name) = name => self.load_mode(name.into(), body), _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } - Ok(()) } else { return Err(format!("Config::load: expected expr, got: {item:?}").into()) }) } -} - -impl Mode> { - pub fn load_into (modes: &Modes, name: &impl AsRef, 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)); + pub fn load_view (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().into()); Ok(()) } - pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> { - Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(key)) = expr.head() { - println!("Mode::load_one: {key} {:?}", expr.tail()?); - let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); - match key { - "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: {key:?} {tail:?}").into()) - }, - }; - } else if let Ok(Some(word)) = dsl.word() { - self.view.push(word.into()); + pub fn load_bind (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + let mut map = EventMap::new(); + dsl.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!("Mode::load_one: unexpected: {dsl:?}").into()); + return Err(format!("Config::load_bind: unexpected: {item:?}").into()) + })?; + self.binds.write().unwrap().insert(id, map); + Ok(()) + } + pub fn load_mode (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { + let mut mode = Mode::default(); + dsl.each(|item|Self::load_mode_one(&mut mode, item))?; + self.modes.write().unwrap().insert(id.into(), Arc::new(mode)); + Ok(()) + } + pub fn load_mode_one (mode: &mut Mode>, item: impl Dsl) -> Usually<()> { + Ok(if let Ok(Some(key)) = item.expr().head() { + match key { + "name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()), + "keys" => { + item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?; + }, + "mode" => if let Some(id) = item.expr()?.tail()?.head()? { + let mut submode = Mode::default(); + Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?; + mode.modes.insert(id.into(), submode); + } else { + return Err(format!("load_mode_one: incomplete: {item:?}").into()); + }, + _ => mode.view.push(item.expr()?.unwrap().into()), + } + } else if let Ok(Some(word)) = item.word() { + mode.view.push(word.into()); + } else { + return Err(format!("load_mode_one: unexpected: {item:?}").into()); }) } } @@ -185,36 +196,6 @@ impl EventMap { } } -impl EventMap> { - pub fn load_into (binds: &Binds, name: &impl AsRef, 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 Binding { pub fn from_dsl (dsl: impl Dsl) -> Usually { let command: Option = None; diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml index 0a3b6dd2..d0e66b23 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -19,7 +19,7 @@ winit = { workspace = true, optional = true } default = [ "arranger", "sampler", "lv2" ] arranger = [ "port", "editor", "sequencer", "editor" ] -browse = [] +browser = [] clap = [] clock = [] editor = [] @@ -28,7 +28,7 @@ meter = [] mixer = [] pool = [] port = [] -sampler = [ "port", "meter", "mixer", "browse", "symphonia", "wavers" ] +sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ] sequencer = [ "port", "clock", "uuid", "pool" ] sf2 = [] vst2 = [] diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 56ac336b..11305006 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -2,6 +2,7 @@ use crate::*; mod arranger_api; pub use self::arranger_api::*; mod arranger_clip; pub use self::arranger_clip::*; +mod arranger_model; pub use self::arranger_model::*; mod arranger_scenes; pub use self::arranger_scenes::*; mod arranger_select; pub use self::arranger_select::*; mod arranger_tracks; pub use self::arranger_tracks::*; @@ -18,187 +19,3 @@ pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content) -> impl let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌"))); Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content))) } - -#[derive(Default, Debug)] pub struct Arrangement { - /// Project name. - pub name: Arc, - /// Base color. - pub color: ItemTheme, - /// Jack client handle - pub jack: Jack<'static>, - /// Source of time - pub clock: Clock, - /// Allows one MIDI clip to be edited - pub editor: Option, - /// List of global midi inputs - pub midi_ins: Vec, - /// List of global midi outputs - pub midi_outs: Vec, - /// List of global audio inputs - pub audio_ins: Vec, - /// List of global audio outputs - pub audio_outs: Vec, - /// Last track number (to avoid duplicate port names) - pub track_last: usize, - /// List of tracks - pub tracks: Vec, - /// Scroll offset of tracks - pub track_scroll: usize, - /// List of scenes - pub scenes: Vec, - /// Scroll offset of scenes - pub scene_scroll: usize, - /// Selected UI element - pub selection: Selection, - /// Contains a render of the project arrangement, redrawn on update. - /// TODO rename to "render_cache" or smth - pub arranger: Arc>, - /// Display size - pub size: Measure, - /// Display size of clips area - pub inner_size: Measure, -} -impl HasJack<'static> for Arrangement { - fn jack (&self) -> &Jack<'static> { - &self.jack - } -} -has!(Jack<'static>: |self: Arrangement|self.jack); -has!(Clock: |self: Arrangement|self.clock); -has!(Selection: |self: Arrangement|self.selection); -has!(Vec: |self: Arrangement|self.midi_ins); -has!(Vec: |self: Arrangement|self.midi_outs); -has!(Vec: |self: Arrangement|self.scenes); -has!(Vec: |self: Arrangement|self.tracks); -has!(Measure: |self: Arrangement|self.size); -has!(Option: |self: Arrangement|self.editor); -maybe_has!(Track: |self: Arrangement| - { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; - { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); -maybe_has!(Scene: |self: Arrangement| - { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; - { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); -//take!(MidiInputCommand |state: Arrangement, iter|state.selected_midi_in().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -//take!(MidiOutputCommand |state: Arrangement, iter|state.selected_midi_out().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() - //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); -#[tengri_proc::expose] impl Arrangement { - fn selected_midi_in (&self) -> Option { todo!() } - fn selected_midi_out (&self) -> Option { todo!() } - fn selected_device (&self) -> Option { todo!() } - fn selected_track (&self) -> Option { todo!() } - fn selected_scene (&self) -> Option { todo!() } - fn selected_clip (&self) -> Option { todo!() } - fn _todo_usize_stub_ (&self) -> usize { todo!() } - fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } - fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } - fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } - fn select_nothing (&self) -> Selection { - Selection::Nothing - } -} -impl Arrangement { - /// Width of display - pub fn w (&self) -> u16 { - self.size.w() as u16 - } - /// Width allocated for sidebar. - pub fn w_sidebar (&self, is_editing: bool) -> u16 { - self.w() / if is_editing { 16 } else { 8 } as u16 - } - /// Width available to display tracks. - pub fn w_tracks_area (&self, is_editing: bool) -> u16 { - self.w().saturating_sub(self.w_sidebar(is_editing)) - } - /// Height of display - pub fn h (&self) -> u16 { - self.size.h() as u16 - } - /// Height taken by visible device slots. - pub fn h_devices (&self) -> u16 { - 2 - //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Get the active track - pub fn get_track (&self) -> Option<&Track> { - let index = self.selection().track()?; - Has::>::get(self).get(index) - } - /// Get a mutable reference to the active track - pub fn get_track_mut (&mut self) -> Option<&mut Track> { - let index = self.selection().track()?; - Has::>::get_mut(self).get_mut(index) - } - /// Get the active scene - pub fn get_scene (&self) -> Option<&Scene> { - let index = self.selection().scene()?; - Has::>::get(self).get(index) - } - /// Get a mutable reference to the active scene - pub fn get_scene_mut (&mut self) -> Option<&mut Scene> { - let index = self.selection().scene()?; - Has::>::get_mut(self).get_mut(index) - } - /// Get the active clip - pub fn get_clip (&self) -> Option>> { - self.get_scene()?.clips.get(self.selection().track()?)?.clone() - } - /// Put a clip in a slot - pub fn clip_put ( - &mut self, track: usize, scene: usize, clip: Option>> - ) -> Option>> { - let old = self.scenes[scene].clips[track].clone(); - self.scenes[scene].clips[track] = clip; - old - } - /// Change the color of a clip, returning the previous one - pub fn clip_set_color (&self, track: usize, scene: usize, color: ItemTheme) - -> Option - { - self.scenes[scene].clips[track].as_ref().map(|clip|{ - let mut clip = clip.write().unwrap(); - let old = clip.color.clone(); - clip.color = color.clone(); - panic!("{color:?} {old:?}"); - old - }) - } - /// Toggle looping for the active clip - pub fn toggle_loop (&mut self) { - if let Some(clip) = self.get_clip() { - clip.write().unwrap().toggle_loop() - } - } -} - -#[cfg(feature = "sampler")] -impl Arrangement { - /// Get the first sampler of the active track - pub fn sampler (&self) -> Option<&Sampler> { - self.get_track()?.sampler(0) - } - /// Get the first sampler of the active track - pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { - self.get_track_mut()?.sampler_mut(0) - } -} - -impl ScenesView for Arrangement { - fn h_scenes (&self) -> u16 { - (self.height() as u16).saturating_sub(20) - } - fn w_side (&self) -> u16 { - (self.width() as u16 * 2 / 10).max(20) - } - fn w_mid (&self) -> u16 { - (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) - } -} diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index 75a1d91d..3e73941d 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -1,10 +1,11 @@ use crate::*; - -def_command!(ArrangementCommand: |arranger: Arrangement| { - - Home => { arranger.editor = None; Ok(None) }, - - Edit => { +#[tengri_proc::command(Arrangement)] +impl ArrangementCommand { + fn home (arranger: &mut Arrangement) -> Perhaps { + arranger.editor = None; + Ok(None) + } + fn edit (arranger: &mut Arrangement) -> Perhaps { let selection = arranger.selection().clone(); arranger.editor = if arranger.editor.is_some() { None @@ -35,13 +36,14 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { } } Ok(None) - }, - - //// Set the selection - Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) }, - - //// Launch the selected clip or scene - Launch => { + } + /// Set the selection + fn select (arranger: &mut Arrangement, s: Selection) -> Perhaps { + *arranger.selection_mut() = s; + Ok(None) + } + /// Launch a clip or scene + fn launch (arranger: &mut Arrangement) -> Perhaps { match *arranger.selection() { Selection::Track(t) => { arranger.tracks[t].sequencer.enqueue_next(None) @@ -57,10 +59,9 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { _ => {} }; Ok(None) - }, - - //// Set the color of the selected entity - SetColor { palette: Option } => { + } + /// Set the color of the selected entity + fn set_color (arranger: &mut Arrangement, palette: Option) -> Perhaps { let mut palette = palette.unwrap_or_else(||ItemTheme::random()); let selection = *arranger.selection(); Ok(Some(Self::SetColor { palette: Some(match selection { @@ -87,11 +88,11 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { }, _ => todo!() }) })) - }, - - Track { track: TrackCommand } => { todo!("delegate") }, - - TrackAdd => { + } + fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps { + todo!("delegate") + } + fn track_add (arranger: &mut Arrangement) -> Perhaps { let index = arranger.track_add(None, None, &[], &[])?.0; *arranger.selection_mut() = match arranger.selection() { Selection::Track(_) => Selection::Track(index), @@ -101,16 +102,12 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { _ => *arranger.selection() }; Ok(Some(Self::TrackDelete { index })) - }, - - TrackSwap { index: usize, other: usize } => { - let index = *index; - let other = *other; + } + fn track_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps { + todo!(); Ok(Some(Self::TrackSwap { index, other })) - }, - - TrackDelete { index: usize } => { - let index = *index; + } + fn track_delete (arranger: &mut Arrangement, index: usize) -> Perhaps { let exists = arranger.tracks().get(index).is_some(); if exists { let track = arranger.tracks_mut().remove(index); @@ -127,61 +124,52 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { } Ok(None) //TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) }) - }, - - MidiIn { input: MidiInputCommand } => { - todo!("delegate"); Ok(None) - }, - - MidiInAdd => { + } + fn midi_in (_arranger: &mut Arrangement, _input: MidiInputCommand) -> Perhaps { + todo!("delegate"); + Ok(None) + } + fn midi_in_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_in_add()?; Ok(None) - }, - - MidiOut { output: MidiOutputCommand } => { + } + fn midi_out (_arranger: &mut Arrangement, _input: MidiOutputCommand) -> Perhaps { todo!("delegate"); Ok(None) - }, - - MidiOutAdd => { + } + fn midi_out_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_out_add()?; Ok(None) - }, - - Device { command: DeviceCommand } => { + } + fn device (_arranger: &mut Arrangement, _input: DeviceCommand) -> Perhaps { todo!("delegate"); Ok(None) - }, - - DeviceAdd { index: usize } => { + } + fn device_add (_arranger: &mut Arrangement, _i: usize) -> Perhaps { todo!("delegate"); Ok(None) - }, - - Scene { scene: SceneCommand } => { + } + fn scene (arranger: &mut Arrangement, scene: SceneCommand) -> Perhaps { todo!("delegate"); Ok(None) - }, - - OutputAdd => { + } + fn output_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_outs.push(MidiOutput::new( arranger.jack(), &format!("/M{}", arranger.midi_outs.len() + 1), &[] )?); Ok(None) - }, - - InputAdd => { + } + fn input_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_ins.push(MidiInput::new( arranger.jack(), &format!("M{}/", arranger.midi_ins.len() + 1), &[] )?); Ok(None) - }, - - SceneAdd => { + } + fn scene_add (arranger: &mut Arrangement) -> Perhaps { let index = arranger.scene_add(None, None)?.0; *arranger.selection_mut() = match arranger.selection() { Selection::Scene(_) => Selection::Scene(index), @@ -192,16 +180,12 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { _ => *arranger.selection() }; Ok(None) // TODO - }, - - SceneSwap { index: usize, other: usize } => { - let index = *index; - let other = *other; + } + fn scene_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps { + todo!(); Ok(Some(Self::SceneSwap { index, other })) - }, - - SceneDelete { index: usize } => { - let index = *index; + } + fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps { let scenes = arranger.scenes_mut(); Ok(if scenes.get(index).is_some() { let _scene = scenes.remove(index); @@ -209,50 +193,41 @@ def_command!(ArrangementCommand: |arranger: Arrangement| { } else { None }) - }, - - SceneLaunch { index: usize } => { - let index = *index; + } + fn scene_launch (arranger: &mut Arrangement, index: usize) -> Perhaps { for track in 0..arranger.tracks.len() { let clip = arranger.scenes[index].clips[track].as_ref(); arranger.tracks[track].sequencer.enqueue_next(clip); } Ok(None) - }, - - Clip { scene: ClipCommand } => { + } + fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps { todo!("delegate") - }, - - ClipGet { a: usize, b: usize } => { + } + fn clip_get (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { //(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}")) //("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap()))) todo!() - }, - - ClipPut { a: usize, b: usize } => { + } + fn clip_put (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { //(Put [t: usize, s: usize, c: MaybeClip] //Some(Self::Put(t, s, arranger.clip_put(t, s, c)))) //("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) todo!() - }, - - ClipDel { a: usize, b: usize } => { + } + fn clip_del (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { //("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None)))) todo!() - }, - - ClipEnqueue { a: usize, b: usize } => { + } + fn clip_enqueue (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { //(Enqueue [t: usize, s: usize] //cmd!(arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref()))) //("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) todo!() - }, - - ClipSwap { a: usize, b: usize }=> { + } + fn clip_edit (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { //(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}")) //("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) todo!() - }, - -}); + } +} diff --git a/crates/device/src/arranger/arranger_clip.rs b/crates/device/src/arranger/arranger_clip.rs index e85e6839..a856c47b 100644 --- a/crates/device/src/arranger/arranger_clip.rs +++ b/crates/device/src/arranger/arranger_clip.rs @@ -10,19 +10,17 @@ impl MidiClip { fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } } -def_command!(ClipCommand: |clip: MidiClip| { - - SetColor { color: Option } => { +#[tengri_proc::command(MidiClip)] +impl ClipCommand { + fn set_color (clip: &mut MidiClip, color: Option) -> Perhaps { //(SetColor [t: usize, s: usize, c: ItemTheme] //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) todo!() - }, - - SetLoop { looping: Option } => { + } + fn set_loop (clip: &mut MidiClip, looping: Option) -> Perhaps { //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) todo!() } - -}); +} diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs new file mode 100644 index 00000000..35f5b296 --- /dev/null +++ b/crates/device/src/arranger/arranger_model.rs @@ -0,0 +1,184 @@ +use crate::*; +#[derive(Default, Debug)] pub struct Arrangement { + /// Project name. + pub name: Arc, + /// Base color. + pub color: ItemTheme, + /// Jack client handle + pub jack: Jack<'static>, + /// Source of time + pub clock: Clock, + /// Allows one MIDI clip to be edited + pub editor: Option, + /// List of global midi inputs + pub midi_ins: Vec, + /// List of global midi outputs + pub midi_outs: Vec, + /// List of global audio inputs + pub audio_ins: Vec, + /// List of global audio outputs + pub audio_outs: Vec, + /// Last track number (to avoid duplicate port names) + pub track_last: usize, + /// List of tracks + pub tracks: Vec, + /// Scroll offset of tracks + pub track_scroll: usize, + /// List of scenes + pub scenes: Vec, + /// Scroll offset of scenes + pub scene_scroll: usize, + /// Selected UI element + pub selection: Selection, + /// Contains a render of the project arrangement, redrawn on update. + /// TODO rename to "render_cache" or smth + pub arranger: Arc>, + /// Display size + pub size: Measure, + /// Display size of clips area + pub inner_size: Measure, +} +impl HasJack<'static> for Arrangement { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} +has!(Jack<'static>: |self: Arrangement|self.jack); +has!(Clock: |self: Arrangement|self.clock); +has!(Selection: |self: Arrangement|self.selection); +has!(Vec: |self: Arrangement|self.midi_ins); +has!(Vec: |self: Arrangement|self.midi_outs); +has!(Vec: |self: Arrangement|self.scenes); +has!(Vec: |self: Arrangement|self.tracks); +has!(Measure: |self: Arrangement|self.size); +has!(Option: |self: Arrangement|self.editor); +maybe_has!(Track: |self: Arrangement| + { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; + { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); +maybe_has!(Scene: |self: Arrangement| + { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; + { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); +//take!(MidiInputCommand |state: Arrangement, iter|state.selected_midi_in().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +//take!(MidiOutputCommand |state: Arrangement, iter|state.selected_midi_out().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref() + //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); +#[tengri_proc::expose] impl Arrangement { + fn selected_midi_in (&self) -> Option { todo!() } + fn selected_midi_out (&self) -> Option { todo!() } + fn selected_device (&self) -> Option { todo!() } + fn selected_track (&self) -> Option { todo!() } + fn selected_scene (&self) -> Option { todo!() } + fn selected_clip (&self) -> Option { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } + fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } + fn select_nothing (&self) -> Selection { + Selection::Nothing + } +} +impl Arrangement { + /// Width of display + pub fn w (&self) -> u16 { + self.size.w() as u16 + } + /// Width allocated for sidebar. + pub fn w_sidebar (&self, is_editing: bool) -> u16 { + self.w() / if is_editing { 16 } else { 8 } as u16 + } + /// Width available to display tracks. + pub fn w_tracks_area (&self, is_editing: bool) -> u16 { + self.w().saturating_sub(self.w_sidebar(is_editing)) + } + /// Height of display + pub fn h (&self) -> u16 { + self.size.h() as u16 + } + /// Height taken by visible device slots. + pub fn h_devices (&self) -> u16 { + 2 + //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Get the active track + pub fn get_track (&self) -> Option<&Track> { + let index = self.selection().track()?; + Has::>::get(self).get(index) + } + /// Get a mutable reference to the active track + pub fn get_track_mut (&mut self) -> Option<&mut Track> { + let index = self.selection().track()?; + Has::>::get_mut(self).get_mut(index) + } + /// Get the active scene + pub fn get_scene (&self) -> Option<&Scene> { + let index = self.selection().scene()?; + Has::>::get(self).get(index) + } + /// Get a mutable reference to the active scene + pub fn get_scene_mut (&mut self) -> Option<&mut Scene> { + let index = self.selection().scene()?; + Has::>::get_mut(self).get_mut(index) + } + /// Get the active clip + pub fn get_clip (&self) -> Option>> { + self.get_scene()?.clips.get(self.selection().track()?)?.clone() + } + /// Put a clip in a slot + pub fn clip_put ( + &mut self, track: usize, scene: usize, clip: Option>> + ) -> Option>> { + let old = self.scenes[scene].clips[track].clone(); + self.scenes[scene].clips[track] = clip; + old + } + /// Change the color of a clip, returning the previous one + pub fn clip_set_color (&self, track: usize, scene: usize, color: ItemTheme) + -> Option + { + self.scenes[scene].clips[track].as_ref().map(|clip|{ + let mut clip = clip.write().unwrap(); + let old = clip.color.clone(); + clip.color = color.clone(); + panic!("{color:?} {old:?}"); + old + }) + } + /// Toggle looping for the active clip + pub fn toggle_loop (&mut self) { + if let Some(clip) = self.get_clip() { + clip.write().unwrap().toggle_loop() + } + } +} + +#[cfg(feature = "sampler")] +impl Arrangement { + /// Get the first sampler of the active track + pub fn sampler (&self) -> Option<&Sampler> { + self.get_track()?.sampler(0) + } + /// Get the first sampler of the active track + pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { + self.get_track_mut()?.sampler_mut(0) + } +} + +impl ScenesView for Arrangement { + fn h_scenes (&self) -> u16 { + (self.height() as u16).saturating_sub(20) + } + fn w_side (&self) -> u16 { + (self.width() as u16 * 2 / 10).max(20) + } + fn w_mid (&self) -> u16 { + (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) + } +} diff --git a/crates/device/src/arranger/arranger_scenes.rs b/crates/device/src/arranger/arranger_scenes.rs index 61e2841d..1c48531d 100644 --- a/crates/device/src/arranger/arranger_scenes.rs +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -64,14 +64,23 @@ impl Scene { fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } -def_command!(SceneCommand: |scene: Scene| { - SetSize { size: usize } => { todo!() }, - SetZoom { size: usize } => { todo!() }, - SetName { name: Arc } => - swap_value(&mut scene.name, name, |name|Self::SetName{name}), - SetColor { color: ItemTheme } => - swap_value(&mut scene.color, color, |color|Self::SetColor{color}), -}); +#[tengri_proc::command(Scene)] +impl SceneCommand { + fn set_name (scene: &mut Scene, mut name: Arc) -> Perhaps { + std::mem::swap(&mut scene.name, &mut name); + Ok(Some(Self::SetName { name })) + } + fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps { + std::mem::swap(&mut scene.color, &mut color); + Ok(Some(Self::SetColor { color })) + } + fn set_size (scene: &mut Scene, size: usize) -> Perhaps { + todo!() + } + fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps { + todo!() + } +} impl> + Send + Sync> HasScene for T {} diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs index 364ffab8..cf483faa 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -129,21 +129,43 @@ impl Track { fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } -def_command!(TrackCommand: |track: Track| { - Stop => { track.sequencer.enqueue_next(None); Ok(None) }, - SetMute { mute: Option } => todo!(), - SetSolo { solo: Option } => todo!(), - SetSize { size: usize } => todo!(), - SetZoom { zoom: usize } => todo!(), - SetName { name: Arc } => - swap_value(&mut track.name, name, |name|Self::SetName { name }), - SetColor { color: ItemTheme } => - swap_value(&mut track.color, color, |color|Self::SetColor { color }), - SetRec { rec: Option } => - toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), - SetMon { mon: Option } => - toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), -}); +#[tengri_proc::command(Track)] +impl TrackCommand { + fn set_name (track: &mut Track, mut name: Arc) -> Perhaps { + std::mem::swap(&mut name, &mut track.name); + Ok(Some(Self::SetName { name })) + } + fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps { + std::mem::swap(&mut color, &mut track.color); + Ok(Some(Self::SetColor { color })) + } + fn set_mute (_track: &mut Track, _value: Option) -> Perhaps { + todo!() + } + fn set_solo (_track: &mut Track, _value: Option) -> Perhaps { + todo!() + } + fn set_rec (track: &mut Track, value: Option) -> Perhaps { + let current = track.sequencer.recording; + let value = value.unwrap_or(!current); + Ok((value != current).then_some(Self::SetRec { value: Some(current) })) + } + fn set_mon (track: &mut Track, value: Option) -> Perhaps { + let current = track.sequencer.monitoring; + let value = value.unwrap_or(!current); + Ok((value != current).then_some(Self::SetRec { value: Some(current) })) + } + fn set_size (_track: &mut Track, _size: usize) -> Perhaps { + todo!() + } + fn set_zoom (_track: &mut Track, _zoom: usize) -> Perhaps { + todo!() + } + fn stop (track: &mut Track) -> Perhaps { + track.sequencer.enqueue_next(None); + Ok(None) + } +} #[derive(Debug, Default)] pub struct Track { diff --git a/crates/device/src/browse/browse_api.rs b/crates/device/src/browse/browse_api.rs deleted file mode 100644 index 21f78b1e..00000000 --- a/crates/device/src/browse/browse_api.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::*; - -#[tengri_proc::expose] -impl Browse { - fn _todo_stub_path_buf (&self) -> PathBuf { - todo!() - } - fn _todo_stub_usize (&self) -> usize { - todo!() - } - fn _todo_stub_arc_str (&self) -> Arc { - todo!() - } -} - -def_command!(BrowseCommand: |browse: Browse| { - SetVisible => Ok(None), - SetPath { address: PathBuf } => Ok(None), - SetSearch { filter: Arc } => Ok(None), - SetCursor { cursor: usize } => Ok(None), -}); - -// Commands supported by [Browse] -//#[derive(Debug, Clone, PartialEq)] -//pub enum BrowseCommand { - //Begin, - //Cancel, - //Confirm, - //Select(usize), - //Chdir(PathBuf), - //Filter(Arc), -//} - //fn begin (browse: &mut Browse) => { - //unreachable!(); - //} - //fn cancel (browse: &mut Browse) => { - //todo!() - ////browse.mode = None; - ////Ok(None) - //} - //fn confirm (browse: &mut Browse) => { - //todo!() - ////Ok(match browse.mode { - ////Some(PoolMode::Import(index, ref mut browse)) => { - ////if browse.is_file() { - ////let path = browse.path(); - ////browse.mode = None; - ////let _undo = PoolClipCommand::import(browse, index, path)?; - ////None - ////} else if browse.is_dir() { - ////browse.mode = Some(PoolMode::Import(index, browse.chdir()?)); - ////None - ////} else { - ////None - ////} - ////}, - ////Some(PoolMode::Export(index, ref mut browse)) => { - ////todo!() - ////}, - ////_ => unreachable!(), - ////}) - //} - //fn select (browse: &mut Browse, index: usize) => { - //todo!() - ////Ok(match browse.mode { - ////Some(PoolMode::Import(index, ref mut browse)) => { - ////browse.index = index; - ////None - ////}, - ////Some(PoolMode::Export(index, ref mut browse)) => { - ////browse.index = index; - ////None - ////}, - ////_ => unreachable!(), - ////}) - //} - //fn chdir (browse: &mut Browse, dir: PathBuf) => { - //todo!() - ////Ok(match browse.mode { - ////Some(PoolMode::Import(index, ref mut browse)) => { - ////browse.mode = Some(PoolMode::Import(index, Browse::new(Some(dir))?)); - ////None - ////}, - ////Some(PoolMode::Export(index, ref mut browse)) => { - ////browse.mode = Some(PoolMode::Export(index, Browse::new(Some(dir))?)); - ////None - ////}, - ////_ => unreachable!(), - ////}) - //} - //fn filter (browse: &mut Browse, filter: Arc) => { - //todo!() - //} diff --git a/crates/device/src/browser.rs b/crates/device/src/browser.rs new file mode 100644 index 00000000..5a83e797 --- /dev/null +++ b/crates/device/src/browser.rs @@ -0,0 +1,3 @@ +mod browser_api; pub use self::browser_api::*; +mod browser_model; pub use self::browser_model::*; +mod browser_view; //pub use self::browser_view::*; diff --git a/crates/device/src/browser/browser_api.rs b/crates/device/src/browser/browser_api.rs new file mode 100644 index 00000000..1f80e1f8 --- /dev/null +++ b/crates/device/src/browser/browser_api.rs @@ -0,0 +1,102 @@ +use crate::*; + +#[tengri_proc::expose] +impl Browser { + fn _todo_stub_path_buf (&self) -> PathBuf { + todo!() + } + fn _todo_stub_usize (&self) -> usize { + todo!() + } + fn _todo_stub_arc_str (&self) -> Arc { + todo!() + } +} + +#[tengri_proc::command(Browser)] +impl BrowserCommand { + fn set_visible (browser: &mut Browser) -> Perhaps { + Ok(None) + } + fn set_path (browser: &mut Browser, address: PathBuf) -> Perhaps { + Ok(None) + } + fn set_search (browser: &mut Browser, filter: Arc) -> Perhaps { + Ok(None) + } + fn set_cursor (browser: &mut Browser, cursor: usize) -> Perhaps { + Ok(None) + } +} + +// Commands supported by [Browser] +//#[derive(Debug, Clone, PartialEq)] +//pub enum BrowserCommand { + //Begin, + //Cancel, + //Confirm, + //Select(usize), + //Chdir(PathBuf), + //Filter(Arc), +//} + //fn begin (browser: &mut Browser) -> Perhaps { + //unreachable!(); + //} + //fn cancel (browser: &mut Browser) -> Perhaps { + //todo!() + ////browser.mode = None; + ////Ok(None) + //} + //fn confirm (browser: &mut Browser) -> Perhaps { + //todo!() + ////Ok(match browser.mode { + ////Some(PoolMode::Import(index, ref mut browser)) => { + ////if browser.is_file() { + ////let path = browser.path(); + ////browser.mode = None; + ////let _undo = PoolClipCommand::import(browser, index, path)?; + ////None + ////} else if browser.is_dir() { + ////browser.mode = Some(PoolMode::Import(index, browser.chdir()?)); + ////None + ////} else { + ////None + ////} + ////}, + ////Some(PoolMode::Export(index, ref mut browser)) => { + ////todo!() + ////}, + ////_ => unreachable!(), + ////}) + //} + //fn select (browser: &mut Browser, index: usize) -> Perhaps { + //todo!() + ////Ok(match browser.mode { + ////Some(PoolMode::Import(index, ref mut browser)) => { + ////browser.index = index; + ////None + ////}, + ////Some(PoolMode::Export(index, ref mut browser)) => { + ////browser.index = index; + ////None + ////}, + ////_ => unreachable!(), + ////}) + //} + //fn chdir (browser: &mut Browser, dir: PathBuf) -> Perhaps { + //todo!() + ////Ok(match browser.mode { + ////Some(PoolMode::Import(index, ref mut browser)) => { + ////browser.mode = Some(PoolMode::Import(index, Browser::new(Some(dir))?)); + ////None + ////}, + ////Some(PoolMode::Export(index, ref mut browser)) => { + ////browser.mode = Some(PoolMode::Export(index, Browser::new(Some(dir))?)); + ////None + ////}, + ////_ => unreachable!(), + ////}) + //} + //fn filter (browser: &mut Browser, filter: Arc) -> Perhaps { + //todo!() + //} diff --git a/crates/device/src/browse.rs b/crates/device/src/browser/browser_model.rs similarity index 79% rename from crates/device/src/browse.rs rename to crates/device/src/browser/browser_model.rs index c9ecd55f..fe0a0645 100644 --- a/crates/device/src/browse.rs +++ b/crates/device/src/browser/browser_model.rs @@ -1,11 +1,9 @@ use crate::*; use std::path::PathBuf; use std::ffi::OsString; -mod browse_api; pub use self::browse_api::*; -mod browse_view; //pub use self::browse_view::*; #[derive(Clone, Debug)] -pub enum BrowseTarget { +pub enum BrowserTarget { SaveProject, LoadProject, ImportSample(Arc>>), @@ -14,21 +12,9 @@ pub enum BrowseTarget { ExportClip(Arc>>), } -impl PartialEq for BrowseTarget { - fn eq (&self, other: &Self) -> bool { - match self { - Self::ImportSample(_) => false, - Self::ExportSample(_) => false, - Self::ImportClip(_) => false, - Self::ExportClip(_) => false, - t => matches!(other, t) - } - } -} - /// Browses for phrase to import/export -#[derive(Debug, Clone, Default, PartialEq)] -pub struct Browse { +#[derive(Debug, Clone, Default)] +pub struct Browser { pub cwd: PathBuf, pub dirs: Vec<(OsString, String)>, pub files: Vec<(OsString, String)>, @@ -38,7 +24,7 @@ pub struct Browse { pub size: Measure, } -impl Browse { +impl Browser { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; diff --git a/crates/device/src/browse/browse_view.rs b/crates/device/src/browser/browser_view.rs similarity index 90% rename from crates/device/src/browse/browse_view.rs rename to crates/device/src/browser/browser_view.rs index 4d73b0bd..af84ba3a 100644 --- a/crates/device/src/browse/browse_view.rs +++ b/crates/device/src/browser/browser_view.rs @@ -1,6 +1,6 @@ use crate::*; -content!(TuiOut: |self: Browse|Map::south(1, ||EntriesIterator { +content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator { offset: 0, index: 0, length: self.dirs.len() + self.files.len(), @@ -8,7 +8,7 @@ content!(TuiOut: |self: Browse|Map::south(1, ||EntriesIterator { }, |entry, _index|Fill::x(Align::w(entry)))); struct EntriesIterator<'a> { - browser: &'a Browse, + browser: &'a Browser, offset: usize, length: usize, index: usize, diff --git a/crates/device/src/clock/clock_api.rs b/crates/device/src/clock/clock_api.rs index 9235869a..e8544525 100644 --- a/crates/device/src/clock/clock_api.rs +++ b/crates/device/src/clock/clock_api.rs @@ -14,33 +14,48 @@ impl Clock { } impl Command for ClockCommand { - fn execute (&self, state: &mut T) -> Perhaps { + fn execute (self, state: &mut T) -> Perhaps { self.execute(state.clock_mut()) // awesome } } -def_command!(ClockCommand: |clock: Clock| { - SeekUsec { usec: f64 } => { - clock.playhead.update_from_usec(*usec); Ok(None) }, - SeekSample { sample: f64 } => { - clock.playhead.update_from_sample(*sample); Ok(None) }, - SeekPulse { pulse: f64 } => { - clock.playhead.update_from_pulse(*pulse); Ok(None) }, - SetBpm { bpm: f64 } => Ok(Some( - Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })), - SetQuant { quant: f64 } => Ok(Some( - Self::SetQuant { quant: clock.quant.set(*quant) })), - SetSync { sync: f64 } => Ok(Some( - Self::SetSync { sync: clock.sync.set(*sync) })), - - Play { position: Option } => { - clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ }, - Pause { position: Option } => { - clock.pause_at(*position)?; Ok(None) }, - - TogglePlayback { position: u32 } => Ok(if clock.is_rolling() { - clock.pause_at(Some(*position))?; None - } else { - clock.play_from(Some(*position))?; None - }), -}); +#[tengri_proc::command(Clock)] +impl ClockCommand { + fn play (state: &mut Clock, position: Option) -> Perhaps { + state.play_from(position)?; + Ok(None) // TODO Some(Pause(previousPosition)) + } + fn pause (state: &mut Clock, position: Option) -> Perhaps { + state.pause_at(position)?; + Ok(None) + } + fn toggle_playback (state: &mut Clock, position: u32) -> Perhaps { + if state.is_rolling() { + state.pause_at(Some(position))?; + } else { + state.play_from(Some(position))?; + } + Ok(None) + } + fn seek_usec (state: &mut Clock, usec: f64) -> Perhaps { + state.playhead.update_from_usec(usec); + Ok(None) + } + fn seek_sample (state: &mut Clock, sample: f64) -> Perhaps { + state.playhead.update_from_sample(sample); + Ok(None) + } + fn seek_pulse (state: &mut Clock, pulse: f64) -> Perhaps { + state.playhead.update_from_pulse(pulse); + Ok(None) + } + fn set_bpm (state: &mut Clock, bpm: f64) -> Perhaps { + Ok(Some(Self::SetBpm { bpm: state.timebase().bpm.set(bpm) })) + } + fn set_quant (state: &mut Clock, quant: f64) -> Perhaps { + Ok(Some(Self::SetQuant { quant: state.quant.set(quant) })) + } + fn set_sync (state: &mut Clock, sync: f64) -> Perhaps { + Ok(Some(Self::SetSync { sync: state.sync.set(sync) })) + } +} diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs index e6667d17..9de535cd 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -84,4 +84,6 @@ audio!(|self: DeviceAudio<'a>, client, scope|{ } }); -def_command!(DeviceCommand: |device: Device| {}); +#[tengri_proc::command(Device)] +impl DeviceCommand { +} diff --git a/crates/device/src/editor/editor_api.rs b/crates/device/src/editor/editor_api.rs index 9f22a6d9..8a588208 100644 --- a/crates/device/src/editor/editor_api.rs +++ b/crates/device/src/editor/editor_api.rs @@ -1,29 +1,5 @@ use crate::*; -def_command!(MidiEditCommand: |editor: MidiEditor| { - Show { clip: Option>> } => { - editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) }, - DeleteNote => { - editor.redraw(); todo!() }, - AppendNote { advance: bool } => { - editor.put_note(*advance); editor.redraw(); Ok(None) }, - SetNotePos { pos: usize } => { - editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) }, - SetNoteLen { len: usize } => { - editor.set_note_len(*len); editor.redraw(); Ok(None) }, - SetNoteScroll { scroll: usize } => { - editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) }, - SetTimePos { pos: usize } => { - editor.set_time_pos(*pos); editor.redraw(); Ok(None) }, - SetTimeScroll { scroll: usize } => { - editor.set_time_start(*scroll); editor.redraw(); Ok(None) }, - SetTimeZoom { zoom: usize } => { - editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) }, - SetTimeLock { lock: bool } => { - editor.set_time_lock(*lock); editor.redraw(); Ok(None) }, - // TODO: 1-9 seek markers that by default start every 8th of the clip -}); - #[tengri_proc::expose] impl MidiEditor { fn _todo_opt_clip_stub (&self) -> Option>> { todo!() @@ -115,3 +91,56 @@ def_command!(MidiEditCommand: |editor: MidiEditor| { !self.get_time_lock() } } + +#[tengri_proc::command(MidiEditor)] impl MidiEditCommand { + fn append_note (editor: &mut MidiEditor, advance: bool) -> Perhaps { + editor.put_note(advance); + editor.redraw(); + Ok(None) + } + fn delete_note (editor: &mut MidiEditor) -> Perhaps { + editor.redraw(); + todo!() + } + fn set_note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps { + editor.set_note_pos(pos.min(127)); + editor.redraw(); + Ok(None) + } + fn set_note_len (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_note_len(value); + editor.redraw(); + Ok(None) + } + fn set_note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_note_lo(value.min(127)); + editor.redraw(); + Ok(None) + } + fn set_time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_pos(value); + editor.redraw(); + Ok(None) + } + fn set_time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_start(value); + editor.redraw(); + Ok(None) + } + fn set_time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_zoom(value); + editor.redraw(); + Ok(None) + } + fn set_time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps { + editor.set_time_lock(value); + editor.redraw(); + Ok(None) + } + fn show (editor: &mut MidiEditor, clip: Option>>) -> Perhaps { + editor.set_clip(clip.as_ref()); + editor.redraw(); + Ok(None) + } + // TODO: 1-9 seek markers that by default start every 8th of the clip +} diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index 22edce2e..0ccf3724 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -32,7 +32,7 @@ macro_rules! def_sizes_iter { } #[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*; -#[cfg(feature = "browse")] mod browse; #[cfg(feature = "browse")] pub use self::browse::*; +#[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*; #[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*; #[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*; #[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*; @@ -85,27 +85,3 @@ pub fn button_3 <'a, K, L, V> ( )); Tui::bold(true, Bsp::e(key, label)) } - -pub fn swap_value ( - target: &mut T, value: &T, returned: impl Fn(T)->U -) -> Perhaps { - if *target == *value { - Ok(None) - } else { - let mut value = value.clone(); - std::mem::swap(target, &mut value); - Ok(Some(returned(value))) - } -} - -pub fn toggle_bool ( - target: &mut bool, value: &Option, returned: impl Fn(Option)->U -) -> Perhaps { - let mut value = value.unwrap_or(!*target); - if value == *target { - Ok(None) - } else { - std::mem::swap(target, &mut value); - Ok(Some(returned(Some(value)))) - } -} diff --git a/crates/device/src/pool.rs b/crates/device/src/pool.rs index 90e7e490..eaf9a753 100644 --- a/crates/device/src/pool.rs +++ b/crates/device/src/pool.rs @@ -1,213 +1,3 @@ -use crate::*; -mod pool_api; pub use self::pool_api::*; -mod pool_view; pub use self::pool_view::*; - -#[derive(Debug)] -pub struct Pool { - pub visible: bool, - /// Collection of clips - pub clips: Arc>>>>, - /// Selected clip - pub clip: AtomicUsize, - /// Mode switch - pub mode: Option, - /// Embedded file browse - pub browse: Option, -} - -//take!(BrowseCommand |state: Pool, iter|Ok(state.browse.as_ref() - //.map(|p|Take::take(p, iter)) - //.transpose()? - //.flatten())); - -impl Default for Pool { - fn default () -> Self { - //use PoolMode::*; - Self { - visible: true, - clips: Arc::from(RwLock::from(vec![])), - clip: 0.into(), - mode: None, - browse: None, - } - } -} - -impl Pool { - pub fn clip_index (&self) -> usize { - self.clip.load(Relaxed) - } - pub fn set_clip_index (&self, value: usize) { - self.clip.store(value, Relaxed); - } - pub fn mode (&self) -> &Option { - &self.mode - } - pub fn mode_mut (&mut self) -> &mut Option { - &mut self.mode - } - pub fn begin_clip_length (&mut self) { - let length = self.clips()[self.clip_index()].read().unwrap().length; - *self.mode_mut() = Some(PoolMode::Length( - self.clip_index(), - length, - ClipLengthFocus::Bar - )); - } - pub fn begin_clip_rename (&mut self) { - let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); - *self.mode_mut() = Some(PoolMode::Rename( - self.clip_index(), - name - )); - } - pub fn begin_import (&mut self) -> Usually<()> { - *self.mode_mut() = Some(PoolMode::Import( - self.clip_index(), - Browse::new(None)? - )); - Ok(()) - } - pub fn begin_export (&mut self) -> Usually<()> { - *self.mode_mut() = Some(PoolMode::Export( - self.clip_index(), - Browse::new(None)? - )); - Ok(()) - } - pub fn new_clip (&self) -> MidiClip { - MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) - } - pub fn cloned_clip (&self) -> MidiClip { - let index = self.clip_index(); - let mut clip = self.clips()[index].read().unwrap().duplicate(); - clip.color = ItemTheme::random_near(clip.color, 0.25); - clip - } - pub fn add_new_clip (&self) -> (usize, Arc>) { - let clip = Arc::new(RwLock::new(self.new_clip())); - let index = { - let mut clips = self.clips.write().unwrap(); - clips.push(clip.clone()); - clips.len().saturating_sub(1) - }; - self.clip.store(index, Relaxed); - (index, clip) - } - pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { - let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); - if let Some(index) = index { - self.clips.write().unwrap().remove(index); - return true - } - false - } -} - -/// Modes for clip pool -#[derive(Debug, Clone)] -pub enum PoolMode { - /// Renaming a pattern - Rename(usize, Arc), - /// Editing the length of a pattern - Length(usize, usize, ClipLengthFocus), - /// Load clip from disk - Import(usize, Browse), - /// Save clip to disk - Export(usize, Browse), -} - -/// Focused field of `ClipLength` -#[derive(Copy, Clone, Debug)] -pub enum ClipLengthFocus { - /// Editing the number of bars - Bar, - /// Editing the number of beats - Beat, - /// Editing the number of ticks - Tick, -} - -impl ClipLengthFocus { - pub fn next (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } - } - pub fn prev (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } - } -} - -/// Displays and edits clip length. -#[derive(Clone)] -pub struct ClipLength { - /// Pulses per beat (quaver) - ppq: usize, - /// Beats per bar - bpb: usize, - /// Length of clip in pulses - pulses: usize, - /// Selected subdivision - pub focus: Option, -} - -impl ClipLength { - pub fn _new (pulses: usize, focus: Option) -> Self { - Self { ppq: PPQ, bpb: 4, pulses, focus } - } - pub fn bars (&self) -> usize { - self.pulses / (self.bpb * self.ppq) - } - pub fn beats (&self) -> usize { - (self.pulses % (self.bpb * self.ppq)) / self.ppq - } - pub fn ticks (&self) -> usize { - self.pulses % self.ppq - } - pub fn bars_string (&self) -> Arc { - format!("{}", self.bars()).into() - } - pub fn beats_string (&self) -> Arc { - format!("{}", self.beats()).into() - } - pub fn ticks_string (&self) -> Arc { - format!("{:>02}", self.ticks()).into() - } -} - -pub type ClipPool = Vec>>; - -pub trait HasClips { - fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; - fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; - fn add_clip (&self) -> (usize, Arc>) { - let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); - self.clips_mut().push(clip.clone()); - (self.clips().len() - 1, clip) - } -} - -#[macro_export] macro_rules! has_clips { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { - fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { - $cb.read().unwrap() - } - fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { - $cb.write().unwrap() - } - } - } -} - -has_clips!(|self: Pool|self.clips); - -has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); - -from!(|clip:&Arc>|Pool = { - let model = Self::default(); - model.clips.write().unwrap().push(clip.clone()); - model.clip.store(1, Relaxed); - model -}); +mod pool_api; pub use self::pool_api::*; +mod pool_model; pub use self::pool_model::*; +mod pool_view; pub use self::pool_view::*; diff --git a/crates/device/src/pool/pool_api.rs b/crates/device/src/pool/pool_api.rs index 24e435b6..ffce5802 100644 --- a/crates/device/src/pool/pool_api.rs +++ b/crates/device/src/pool/pool_api.rs @@ -8,65 +8,85 @@ impl Pool { fn _todo_path_ (&self) -> PathBuf { todo!() } fn _todo_color_ (&self) -> ItemColor { todo!() } fn _todo_str_ (&self) -> Arc { todo!() } - fn clip_new (&self) -> MidiClip { self.new_clip() } - fn clip_cloned (&self) -> MidiClip { self.cloned_clip() } - fn clip_index_current (&self) -> usize { 0 } - fn clip_index_after (&self) -> usize { 0 } - fn clip_index_previous (&self) -> usize { 0 } - fn clip_index_next (&self) -> usize { 0 } - fn color_random (&self) -> ItemColor { ItemColor::random() } + fn clip_new (&self) -> MidiClip { + self.new_clip() + } + fn clip_cloned (&self) -> MidiClip { + self.cloned_clip() + } + fn clip_index_current (&self) -> usize { + 0 + } + fn clip_index_after (&self) -> usize { + 0 + } + fn clip_index_previous (&self) -> usize { + 0 + } + fn clip_index_next (&self) -> usize { + 0 + } + fn color_random (&self) -> ItemColor { + ItemColor::random() + } } -def_command!(PoolCommand: |pool: Pool| { - // Toggle visibility of pool - Show { visible: bool } => { - pool.visible = *visible; Ok(Some(Self::Show { visible: !visible })) }, - // Select a clip from the clip pool - Select { index: usize } => { - pool.set_clip_index(*index); Ok(None) }, - // Update the contents of the clip pool - Clip { command: PoolClipCommand } => - Ok(command.execute(pool)?.map(|command|Self::Clip{command})), - // Rename a clip - Rename { command: RenameCommand } => - Ok(command.delegate(pool, |command|Self::Rename{command})?), - // Change the length of a clip - Length { command: CropCommand } => - Ok(command.delegate(pool, |command|Self::Length{command})?), - // Import from file - Import { command: BrowseCommand } => { - Ok(if let Some(browse) = pool.browse.as_mut() { - command.delegate(browse, |command|Self::Import{command})? +#[tengri_proc::command(Pool)] +impl PoolCommand { + + /// Toggle visibility of pool + fn show (pool: &mut Pool, visible: bool) -> Perhaps { + pool.visible = visible; + Ok(Some(Self::Show { visible: !visible })) + } + + /// Select a clip from the clip pool + fn select (pool: &mut Pool, index: usize) -> Perhaps { + pool.set_clip_index(index); + Ok(None) + } + + /// Rename a clip + fn rename (pool: &mut Pool, command: RenameCommand) -> Perhaps { + Ok(command.delegate(pool, |command|Self::Rename{command})?) + } + + /// Change the length of a clip + fn length (pool: &mut Pool, command: CropCommand) -> Perhaps { + Ok(command.delegate(pool, |command|Self::Length{command})?) + } + + /// Import from file + fn import (pool: &mut Pool, command: BrowserCommand) -> Perhaps { + Ok(if let Some(browser) = pool.browser.as_mut() { + command.delegate(browser, |command|Self::Import{command})? } else { - None }) }, - // Export to file - Export { command: BrowseCommand } => { - Ok(if let Some(browse) = pool.browse.as_mut() { - command.delegate(browse, |command|Self::Export{command})? + None + }) + } + + /// Export to file + fn export (pool: &mut Pool, command: BrowserCommand) -> Perhaps { + Ok(if let Some(browser) = pool.browser.as_mut() { + command.delegate(browser, |command|Self::Export{command})? } else { - None }) }, -}); + None + }) + } -def_command!(PoolClipCommand: |pool: Pool| { + /// Update the contents of the clip pool + fn clip (pool: &mut Pool, command: PoolClipCommand) -> Perhaps { + Ok(command.execute(pool)?.map(|command|Self::Clip{command})) + } - Delete { index: usize } => { - let index = *index; - let clip = pool.clips_mut().remove(index).read().unwrap().clone(); - Ok(Some(Self::Add { index, clip })) }, +} - Swap { index: usize, other: usize } => { - let index = *index; - let other = *other; - pool.clips_mut().swap(index, other); - Ok(Some(Self::Swap { index, other })) }, +#[tengri_proc::command(Pool)] +impl PoolClipCommand { - Export { index: usize, path: PathBuf } => { - todo!("export clip to midi file"); }, - - Add { index: usize, clip: MidiClip } => { - let index = *index; + fn add (pool: &mut Pool, index: usize, clip: MidiClip) -> Perhaps { let mut index = index; - let clip = Arc::new(RwLock::new(clip.clone())); + let clip = Arc::new(RwLock::new(clip)); let mut clips = pool.clips_mut(); if index >= clips.len() { index = clips.len(); @@ -74,10 +94,20 @@ def_command!(PoolClipCommand: |pool: Pool| { } else { clips.insert(index, clip); } - Ok(Some(Self::Delete { index })) }, + Ok(Some(Self::Delete { index })) + } - Import { index: usize, path: PathBuf } => { - let index = *index; + fn delete (pool: &mut Pool, index: usize) -> Perhaps { + let clip = pool.clips_mut().remove(index).read().unwrap().clone(); + Ok(Some(Self::Add { index, clip })) + } + + fn swap (pool: &mut Pool, index: usize, other: usize) -> Perhaps { + pool.clips_mut().swap(index, other); + Ok(Some(Self::Swap { index, other })) + } + + fn import (pool: &mut Pool, index: usize, path: PathBuf) -> Perhaps { let bytes = std::fs::read(&path)?; let smf = Smf::parse(bytes.as_slice())?; let mut t = 0u32; @@ -94,64 +124,74 @@ def_command!(PoolClipCommand: |pool: Pool| { for event in events.iter() { clip.notes[event.0 as usize].push(event.2); } - Ok(Self::Add { index, clip }.execute(pool)?) }, + Ok(Self::Add { index, clip }.execute(pool)?) + } - SetName { index: usize, name: Arc } => { - let index = *index; + fn export (_pool: &mut Pool, _index: usize, _path: PathBuf) -> Perhaps { + todo!("export clip to midi file"); + } + + fn set_name (pool: &mut Pool, index: usize, name: Arc) -> Perhaps { let clip = &mut pool.clips_mut()[index]; let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name.clone(); - Ok(Some(Self::SetName { index, name: old_name })) }, + clip.write().unwrap().name = name; + Ok(Some(Self::SetName { index, name: old_name })) + } - SetLength { index: usize, length: usize } => { - let index = *index; + fn set_length (pool: &mut Pool, index: usize, length: usize) -> Perhaps { let clip = &mut pool.clips_mut()[index]; let old_len = clip.read().unwrap().length; - clip.write().unwrap().length = *length; - Ok(Some(Self::SetLength { index, length: old_len })) }, + clip.write().unwrap().length = length; + Ok(Some(Self::SetLength { index, length: old_len })) + } - SetColor { index: usize, color: ItemColor } => { - let index = *index; - let mut color = ItemTheme::from(*color); + fn set_color (pool: &mut Pool, index: usize, color: ItemColor) -> Perhaps { + let mut color = ItemTheme::from(color); std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color); - Ok(Some(Self::SetColor { index, color: color.base })) }, + Ok(Some(Self::SetColor { index, color: color.base })) + } -}); +} -def_command!(RenameCommand: |pool: Pool| { - Begin => unreachable!(), - - Cancel => { +#[tengri_proc::command(Pool)] +impl RenameCommand { + fn begin (_pool: &mut Pool) -> Perhaps { + unreachable!(); + } + fn cancel (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { pool.clips()[clip].write().unwrap().name = old_name.clone().into(); } - Ok(None) }, - - Confirm => { + return Ok(None) + } + fn confirm (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() { let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name })) } - Ok(None) }, - - Set { value: Arc } => { + return Ok(None) + } + fn set (pool: &mut Pool, value: Arc) -> Perhaps { if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() { - pool.clips()[clip].write().unwrap().name = value.clone(); + pool.clips()[clip].write().unwrap().name = value; } - Ok(None) }, -}); + return Ok(None) + } +} -def_command!(CropCommand: |pool: Pool| { - Begin => unreachable!(), - - Cancel => { +#[tengri_proc::command(Pool)] +impl CropCommand { + fn begin (_pool: &mut Pool) -> Perhaps { + unreachable!() + } + fn cancel (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } - Ok(None) }, - - Set { length: usize } => { + Ok(None) + } + fn set (pool: &mut Pool, length: usize) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -164,25 +204,25 @@ def_command!(CropCommand: |pool: Pool| { *pool.mode_mut() = None; return Ok(old_length.map(|length|Self::Set { length })) } - Ok(None) }, - - Next => { + Ok(None) + } + fn next (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { focus.next() } - Ok(None) }, - - Prev => { + Ok(None) + } + fn prev (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() } - Ok(None) }, - - Inc => { + Ok(None) + } + fn inc (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -192,9 +232,9 @@ def_command!(CropCommand: |pool: Pool| { ClipLengthFocus::Tick => { *length += 1 }, } } - Ok(None) }, - - Dec => { + Ok(None) + } + fn dec (pool: &mut Pool) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -204,5 +244,6 @@ def_command!(CropCommand: |pool: Pool| { ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, } } - Ok(None) }, -}); + Ok(None) + } +} diff --git a/crates/device/src/pool/pool_model.rs b/crates/device/src/pool/pool_model.rs new file mode 100644 index 00000000..166c4cbe --- /dev/null +++ b/crates/device/src/pool/pool_model.rs @@ -0,0 +1,209 @@ +use crate::*; + +#[derive(Debug)] +pub struct Pool { + pub visible: bool, + /// Collection of clips + pub clips: Arc>>>>, + /// Selected clip + pub clip: AtomicUsize, + /// Mode switch + pub mode: Option, + /// Embedded file browser + pub browser: Option, +} +//take!(BrowserCommand |state: Pool, iter|Ok(state.browser.as_ref() + //.map(|p|Take::take(p, iter)) + //.transpose()? + //.flatten())); +impl Default for Pool { + fn default () -> Self { + //use PoolMode::*; + Self { + visible: true, + clips: Arc::from(RwLock::from(vec![])), + clip: 0.into(), + mode: None, + browser: None, + } + } +} + +impl Pool { + pub fn clip_index (&self) -> usize { + self.clip.load(Relaxed) + } + pub fn set_clip_index (&self, value: usize) { + self.clip.store(value, Relaxed); + } + pub fn mode (&self) -> &Option { + &self.mode + } + pub fn mode_mut (&mut self) -> &mut Option { + &mut self.mode + } + pub fn begin_clip_length (&mut self) { + let length = self.clips()[self.clip_index()].read().unwrap().length; + *self.mode_mut() = Some(PoolMode::Length( + self.clip_index(), + length, + ClipLengthFocus::Bar + )); + } + pub fn begin_clip_rename (&mut self) { + let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); + *self.mode_mut() = Some(PoolMode::Rename( + self.clip_index(), + name + )); + } + pub fn begin_import (&mut self) -> Usually<()> { + *self.mode_mut() = Some(PoolMode::Import( + self.clip_index(), + Browser::new(None)? + )); + Ok(()) + } + pub fn begin_export (&mut self) -> Usually<()> { + *self.mode_mut() = Some(PoolMode::Export( + self.clip_index(), + Browser::new(None)? + )); + Ok(()) + } + pub fn new_clip (&self) -> MidiClip { + MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) + } + pub fn cloned_clip (&self) -> MidiClip { + let index = self.clip_index(); + let mut clip = self.clips()[index].read().unwrap().duplicate(); + clip.color = ItemTheme::random_near(clip.color, 0.25); + clip + } + pub fn add_new_clip (&self) -> (usize, Arc>) { + let clip = Arc::new(RwLock::new(self.new_clip())); + let index = { + let mut clips = self.clips.write().unwrap(); + clips.push(clip.clone()); + clips.len().saturating_sub(1) + }; + self.clip.store(index, Relaxed); + (index, clip) + } + pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { + let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); + if let Some(index) = index { + self.clips.write().unwrap().remove(index); + return true + } + false + } +} + +/// Modes for clip pool +#[derive(Debug, Clone)] +pub enum PoolMode { + /// Renaming a pattern + Rename(usize, Arc), + /// Editing the length of a pattern + Length(usize, usize, ClipLengthFocus), + /// Load clip from disk + Import(usize, Browser), + /// Save clip to disk + Export(usize, Browser), +} + +/// Focused field of `ClipLength` +#[derive(Copy, Clone, Debug)] +pub enum ClipLengthFocus { + /// Editing the number of bars + Bar, + /// Editing the number of beats + Beat, + /// Editing the number of ticks + Tick, +} + +impl ClipLengthFocus { + pub fn next (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } + } + pub fn prev (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } + } +} + +/// Displays and edits clip length. +#[derive(Clone)] +pub struct ClipLength { + /// Pulses per beat (quaver) + ppq: usize, + /// Beats per bar + bpb: usize, + /// Length of clip in pulses + pulses: usize, + /// Selected subdivision + pub focus: Option, +} + +impl ClipLength { + pub fn _new (pulses: usize, focus: Option) -> Self { + Self { ppq: PPQ, bpb: 4, pulses, focus } + } + pub fn bars (&self) -> usize { + self.pulses / (self.bpb * self.ppq) + } + pub fn beats (&self) -> usize { + (self.pulses % (self.bpb * self.ppq)) / self.ppq + } + pub fn ticks (&self) -> usize { + self.pulses % self.ppq + } + pub fn bars_string (&self) -> Arc { + format!("{}", self.bars()).into() + } + pub fn beats_string (&self) -> Arc { + format!("{}", self.beats()).into() + } + pub fn ticks_string (&self) -> Arc { + format!("{:>02}", self.ticks()).into() + } +} + +pub type ClipPool = Vec>>; + +pub trait HasClips { + fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; + fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; + fn add_clip (&self) -> (usize, Arc>) { + let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); + self.clips_mut().push(clip.clone()); + (self.clips().len() - 1, clip) + } +} + +#[macro_export] macro_rules! has_clips { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { + fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { + $cb.read().unwrap() + } + fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { + $cb.write().unwrap() + } + } + } +} + +has_clips!(|self: Pool|self.clips); + +has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); + +from!(|clip:&Arc>|Pool = { + let model = Self::default(); + model.clips.write().unwrap().push(clip.clone()); + model.clip.store(1, Relaxed); + model +}); diff --git a/crates/device/src/port.rs b/crates/device/src/port.rs index edfae329..f75959ea 100644 --- a/crates/device/src/port.rs +++ b/crates/device/src/port.rs @@ -1,7 +1,5 @@ use crate::*; -mod port_api; pub use self::port_api::*; -mod port_connect; pub use self::port_connect::*; mod port_audio_out; pub use self::port_audio_out::*; mod port_audio_in; pub use self::port_audio_in::*; mod port_midi_out; pub use self::port_midi_out::*; @@ -10,58 +8,6 @@ pub(crate) use ConnectName::*; pub(crate) use ConnectScope::*; pub(crate) use ConnectStatus::*; -#[derive(Debug)] pub struct AudioInput { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'static>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of ports to connect to. - pub connections: Vec, -} - -#[derive(Debug)] pub struct AudioOutput { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'static>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of ports to connect to. - pub connections: Vec, -} - -#[derive(Debug)] pub struct MidiInput { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'static>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of currently held notes. - held: Arc>, - /// List of ports to connect to. - pub connections: Vec, -} - -#[derive(Debug)] pub struct MidiOutput { - /// Handle to JACK client, for receiving reconnect events. - jack: Jack<'static>, - /// Port name - name: Arc, - /// Port handle. - port: Port, - /// List of ports to connect to. - pub connections: Vec, - /// List of currently held notes. - held: Arc>, - /// Buffer - note_buffer: Vec, - /// Buffer - output_buffer: Vec>>, -} - pub trait RegisterPorts: HasJack<'static> { /// Register a MIDI input port. fn midi_in (&self, name: &impl AsRef, connect: &[Connect]) -> Usually; @@ -87,3 +33,184 @@ impl> RegisterPorts for J { AudioOutput::new(self.jack(), name, connect) } } + +pub trait JackPort: HasJack<'static> { + type Port: PortSpec + Default; + type Pair: PortSpec + Default; + fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) + -> Usually where Self: Sized; + fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { + jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) + .map_err(|e|e.into()) + } + fn port_name (&self) -> &Arc; + fn connections (&self) -> &[Connect]; + fn port (&self) -> &Port; + fn port_mut (&mut self) -> &mut Port; + fn into_port (self) -> Port where Self: Sized; + fn close (self) -> Usually<()> where Self: Sized { + let jack = self.jack().clone(); + Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) + } + fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { + self.with_client(|c|c.ports(re_name, re_type, flags)) + } + fn port_by_id (&self, id: u32) -> Option> { + self.with_client(|c|c.port_by_id(id)) + } + fn port_by_name (&self, name: impl AsRef) -> Option> { + self.with_client(|c|c.port_by_name(name.as_ref())) + } + fn connect_to_matching <'k> (&'k self) -> Usually<()> { + for connect in self.connections().iter() { + //panic!("{connect:?}"); + let status = match &connect.name { + Exact(name) => self.connect_exact(name), + RegExp(re) => self.connect_regexp(re, connect.scope), + }?; + *connect.status.write().unwrap() = status; + } + Ok(()) + } + fn connect_exact <'k> (&'k self, name: &str) -> + Usually, Arc, ConnectStatus)>> + { + self.with_client(move|c|{ + let mut status = vec![]; + for port in c.ports(None, None, PortFlags::empty()).iter() { + if port.as_str() == &*name { + if let Some(port) = c.port_by_name(port.as_str()) { + let port_status = self.connect_to_unowned(&port)?; + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected { + break + } + } + } + } + Ok(status) + }) + } + fn connect_regexp <'k> ( + &'k self, re: &str, scope: ConnectScope + ) -> Usually, Arc, ConnectStatus)>> { + self.with_client(move|c|{ + let mut status = vec![]; + let ports = c.ports(Some(&re), None, PortFlags::empty()); + for port in ports.iter() { + if let Some(port) = c.port_by_name(port.as_str()) { + let port_status = self.connect_to_unowned(&port)?; + let name = port.name()?.into(); + status.push((port, name, port_status)); + if port_status == Connected && scope == One { + break + } + } + } + Ok(status) + }) + } + /** Connect to a matching port by name. */ + fn connect_to_name (&self, name: impl AsRef) -> Usually { + self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { + self.connect_to_unowned(port) + } else { + Ok(Missing) + }) + } + /** Connect to a matching port by reference. */ + fn connect_to_unowned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } + /** Connect to an owned matching port by reference. */ + fn connect_to_owned (&self, port: &Port) -> Usually { + self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { + Connected + } else if let Ok(_) = c.connect_ports(port, self.port()) { + Connected + } else { + Mismatch + })) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ConnectName { + /** Exact match */ + Exact(Arc), + /** Match regular expression */ + RegExp(Arc), +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { + One, + All +} + +#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { + Missing, + Disconnected, + Connected, + Mismatch, +} + +#[derive(Clone, Debug)] pub struct Connect { + pub name: ConnectName, + pub scope: ConnectScope, + pub status: Arc, Arc, ConnectStatus)>>>, + pub info: Arc, +} + +impl Connect { + pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) + -> Vec + { + let mut connections = vec![]; + for port in exact.iter() { connections.push(Self::exact(port)) } + for port in re.iter() { connections.push(Self::regexp(port)) } + for port in re_all.iter() { connections.push(Self::regexp_all(port)) } + connections + } + /// Connect to this exact port + pub fn exact (name: impl AsRef) -> Self { + let info = format!("=:{}", name.as_ref()).into(); + let name = Exact(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn regexp (name: impl AsRef) -> Self { + let info = format!("~:{}", name.as_ref()).into(); + let name = RegExp(name.as_ref().into()); + Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn regexp_all (name: impl AsRef) -> Self { + let info = format!("+:{}", name.as_ref()).into(); + let name = RegExp(name.as_ref().into()); + Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info } + } + pub fn info (&self) -> Arc { + let status = { + let status = self.status.read().unwrap(); + let mut ok = 0; + for (_, _, state) in status.iter() { + if *state == Connected { + ok += 1 + } + } + format!("{ok}/{}", status.len()) + }; + let scope = match self.scope { + One => " ", All => "*", + }; + let name = match &self.name { + Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"), + }; + format!(" ({}) {} {}", status, scope, name).into() + } +} diff --git a/crates/device/src/port/port_api.rs b/crates/device/src/port/port_api.rs deleted file mode 100644 index 7742d743..00000000 --- a/crates/device/src/port/port_api.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::*; -def_command!(MidiInputCommand: |port: MidiInput| { - Close => todo!(), - Connect { midi_out: Arc } => todo!(), -}); -def_command!(MidiOutputCommand: |port: MidiOutput| { - Close => todo!(), - Connect { midi_in: Arc } => todo!(), -}); -def_command!(AudioInputCommand: |port: AudioInput| { - Close => todo!(), - Connect { audio_out: Arc } => todo!(), -}); -def_command!(AudioOutputCommand: |port: AudioOutput| { - Close => todo!(), - Connect { audio_in: Arc } => todo!(), -}); diff --git a/crates/device/src/port/port_audio_in.rs b/crates/device/src/port/port_audio_in.rs index 1c36d2fd..7edd0755 100644 --- a/crates/device/src/port/port_audio_in.rs +++ b/crates/device/src/port/port_audio_in.rs @@ -1,7 +1,20 @@ use crate::*; -impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } } - +#[derive(Debug)] pub struct AudioInput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of ports to connect to. + pub connections: Vec, +} +impl HasJack<'static> for AudioInput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for AudioInput { type Port = AudioIn; type Pair = AudioOut; diff --git a/crates/device/src/port/port_audio_out.rs b/crates/device/src/port/port_audio_out.rs index 451eb3c8..fda9d606 100644 --- a/crates/device/src/port/port_audio_out.rs +++ b/crates/device/src/port/port_audio_out.rs @@ -1,7 +1,20 @@ use crate::*; -impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } } - +#[derive(Debug)] pub struct AudioOutput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of ports to connect to. + pub connections: Vec, +} +impl HasJack<'static> for AudioOutput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for AudioOutput { type Port = AudioOut; type Pair = AudioIn; diff --git a/crates/device/src/port/port_connect.rs b/crates/device/src/port/port_connect.rs deleted file mode 100644 index d8bb6ad7..00000000 --- a/crates/device/src/port/port_connect.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::*; - -pub trait JackPort: HasJack<'static> { - type Port: PortSpec + Default; - type Pair: PortSpec + Default; - fn new (jack: &Jack<'static>, name: &impl AsRef, connect: &[Connect]) - -> Usually where Self: Sized; - fn register (jack: &Jack<'static>, name: &impl AsRef) -> Usually> { - jack.with_client(|c|c.register_port::(name.as_ref(), Default::default())) - .map_err(|e|e.into()) - } - fn port_name (&self) -> &Arc; - fn connections (&self) -> &[Connect]; - fn port (&self) -> &Port; - fn port_mut (&mut self) -> &mut Port; - fn into_port (self) -> Port where Self: Sized; - fn close (self) -> Usually<()> where Self: Sized { - let jack = self.jack().clone(); - Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?) - } - fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec { - self.with_client(|c|c.ports(re_name, re_type, flags)) - } - fn port_by_id (&self, id: u32) -> Option> { - self.with_client(|c|c.port_by_id(id)) - } - fn port_by_name (&self, name: impl AsRef) -> Option> { - self.with_client(|c|c.port_by_name(name.as_ref())) - } - fn connect_to_matching <'k> (&'k self) -> Usually<()> { - for connect in self.connections().iter() { - //panic!("{connect:?}"); - let status = match &connect.name { - Exact(name) => self.connect_exact(name), - RegExp(re) => self.connect_regexp(re, connect.scope), - }?; - *connect.status.write().unwrap() = status; - } - Ok(()) - } - fn connect_exact <'k> (&'k self, name: &str) -> - Usually, Arc, ConnectStatus)>> - { - self.with_client(move|c|{ - let mut status = vec![]; - for port in c.ports(None, None, PortFlags::empty()).iter() { - if port.as_str() == &*name { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected { - break - } - } - } - } - Ok(status) - }) - } - fn connect_regexp <'k> ( - &'k self, re: &str, scope: ConnectScope - ) -> Usually, Arc, ConnectStatus)>> { - self.with_client(move|c|{ - let mut status = vec![]; - let ports = c.ports(Some(&re), None, PortFlags::empty()); - for port in ports.iter() { - if let Some(port) = c.port_by_name(port.as_str()) { - let port_status = self.connect_to_unowned(&port)?; - let name = port.name()?.into(); - status.push((port, name, port_status)); - if port_status == Connected && scope == One { - break - } - } - } - Ok(status) - }) - } - /** Connect to a matching port by name. */ - fn connect_to_name (&self, name: impl AsRef) -> Usually { - self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) { - self.connect_to_unowned(port) - } else { - Ok(Missing) - }) - } - /** Connect to a matching port by reference. */ - fn connect_to_unowned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } - /** Connect to an owned matching port by reference. */ - fn connect_to_owned (&self, port: &Port) -> Usually { - self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) { - Connected - } else if let Ok(_) = c.connect_ports(port, self.port()) { - Connected - } else { - Mismatch - })) - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ConnectName { - /** Exact match */ - Exact(Arc), - /** Match regular expression */ - RegExp(Arc), -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope { - One, - All -} - -#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus { - Missing, - Disconnected, - Connected, - Mismatch, -} - -#[derive(Clone, Debug)] pub struct Connect { - pub name: ConnectName, - pub scope: ConnectScope, - pub status: Arc, Arc, ConnectStatus)>>>, - pub info: Arc, -} - -impl Connect { - pub fn collect (exact: &[impl AsRef], re: &[impl AsRef], re_all: &[impl AsRef]) - -> Vec - { - let mut connections = vec![]; - for port in exact.iter() { connections.push(Self::exact(port)) } - for port in re.iter() { connections.push(Self::regexp(port)) } - for port in re_all.iter() { connections.push(Self::regexp_all(port)) } - connections - } - /// Connect to this exact port - pub fn exact (name: impl AsRef) -> Self { - let info = format!("=:{}", name.as_ref()).into(); - let name = Exact(name.as_ref().into()); - Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp (name: impl AsRef) -> Self { - let info = format!("~:{}", name.as_ref()).into(); - let name = RegExp(name.as_ref().into()); - Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn regexp_all (name: impl AsRef) -> Self { - let info = format!("+:{}", name.as_ref()).into(); - let name = RegExp(name.as_ref().into()); - Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info } - } - pub fn info (&self) -> Arc { - let status = { - let status = self.status.read().unwrap(); - let mut ok = 0; - for (_, _, state) in status.iter() { - if *state == Connected { - ok += 1 - } - } - format!("{ok}/{}", status.len()) - }; - let scope = match self.scope { - One => " ", All => "*", - }; - let name = match &self.name { - Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"), - }; - format!(" ({}) {} {}", status, scope, name).into() - } -} - diff --git a/crates/device/src/port/port_midi_in.rs b/crates/device/src/port/port_midi_in.rs index a66ba049..b92f0c95 100644 --- a/crates/device/src/port/port_midi_in.rs +++ b/crates/device/src/port/port_midi_in.rs @@ -1,7 +1,24 @@ use crate::*; -impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } } +//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::(n)); +#[derive(Debug)] pub struct MidiInput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of currently held notes. + held: Arc>, + /// List of ports to connect to. + pub connections: Vec, +} +impl HasJack<'static> for MidiInput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for MidiInput { type Port = MidiIn; type Pair = MidiOut; @@ -34,13 +51,17 @@ impl JackPort for MidiInput { Ok(port) } } - impl MidiInput { pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator, &'a [u8])> { parse_midi_input(self.port().iter(scope)) } } +#[tengri_proc::command(MidiInput)] +impl MidiInputCommand { + //fn _todo_ (_port: &mut MidiInput) -> Perhaps { Ok(None) } +} + impl>> HasMidiIns for T { fn midi_ins (&self) -> &Vec { self.get() diff --git a/crates/device/src/port/port_midi_out.rs b/crates/device/src/port/port_midi_out.rs index 62a70c41..d32cafef 100644 --- a/crates/device/src/port/port_midi_out.rs +++ b/crates/device/src/port/port_midi_out.rs @@ -1,7 +1,26 @@ use crate::*; -impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } } - +#[derive(Debug)] pub struct MidiOutput { + /// Handle to JACK client, for receiving reconnect events. + jack: Jack<'static>, + /// Port name + name: Arc, + /// Port handle. + port: Port, + /// List of ports to connect to. + pub connections: Vec, + /// List of currently held notes. + held: Arc>, + /// Buffer + note_buffer: Vec, + /// Buffer + output_buffer: Vec>>, +} +impl HasJack<'static> for MidiOutput { + fn jack (&self) -> &Jack<'static> { + &self.jack + } +} impl JackPort for MidiOutput { type Port = MidiOut; type Pair = MidiIn; @@ -40,7 +59,6 @@ impl JackPort for MidiOutput { Ok(port) } } - impl MidiOutput { /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. @@ -81,6 +99,10 @@ impl MidiOutput { } } +#[tengri_proc::command(MidiOutput)] +impl MidiOutputCommand { + fn _todo_ (_port: &mut MidiOutput) -> Perhaps { Ok(None) } +} impl>> HasMidiOuts for T { fn midi_outs (&self) -> &Vec { diff --git a/crates/device/src/sampler.rs b/crates/device/src/sampler.rs index be89a841..47ac6366 100644 --- a/crates/device/src/sampler.rs +++ b/crates/device/src/sampler.rs @@ -1,5 +1,3 @@ -use crate::*; - pub(crate) use symphonia::{ core::{ formats::Packet, @@ -16,6 +14,7 @@ mod sampler_api; pub use self::sampler_api::*; mod sampler_audio; mod sampler_browse; pub use self::sampler_browse::*; mod sampler_midi; pub use self::sampler_midi::*; +mod sampler_model; pub use self::sampler_model::*; mod sampler_data; mod sampler_view; @@ -24,177 +23,3 @@ mod sampler_view; // TODO! let sample = Sample::new("test", 0, 0, vec![]); } - -/// The sampler device plays sounds in response to MIDI notes. -#[derive(Debug)] -pub struct Sampler { - /// Name of sampler. - pub name: Arc, - /// Device color. - pub color: ItemTheme, - /// Audio input ports. Samples get recorded here. - pub audio_ins: Vec, - /// Audio input meters. - pub input_meters: Vec, - /// Sample currently being recorded. - pub recording: Option<(usize, Option>>)>, - /// Recording buffer. - pub buffer: Vec>, - /// Samples mapped to MIDI notes. - pub mapped: [Option>>;128], - /// Samples that are not mapped to MIDI notes. - pub unmapped: Vec>>, - /// Sample currently being edited. - pub editing: Option>>, - /// MIDI input port. Triggers sample playback. - pub midi_in: MidiInput, - /// Collection of currently playing instances of samples. - pub voices: Arc>>, - /// Audio output ports. Voices get played here. - pub audio_outs: Vec, - /// Audio output meters. - pub output_meters: Vec, - /// How to mix the voices. - pub mixing_mode: MixingMode, - /// How to meter the inputs and outputs. - pub metering_mode: MeteringMode, - /// Fixed gain applied to all output. - pub output_gain: f32, - /// Currently active modal, if any. - pub mode: Option, - /// Size of rendered sampler. - pub size: Measure, - /// Lowest note displayed. - pub note_lo: AtomicUsize, - /// Currently selected note. - pub note_pt: AtomicUsize, - /// Selected note as row/col. - pub cursor: (AtomicUsize, AtomicUsize), -} - -impl Sampler { - pub fn new ( - jack: &Jack<'static>, - name: impl AsRef, - midi_from: &[Connect], - audio_from: &[&[Connect];2], - audio_to: &[&[Connect];2], - ) -> Usually { - let name = name.as_ref(); - Ok(Self { - name: name.into(), - midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?, - audio_ins: vec![ - AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?, - AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?, - ], - audio_outs: vec![ - AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?, - AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?, - ], - input_meters: vec![0.0;2], - output_meters: vec![0.0;2], - mapped: [const { None };128], - unmapped: vec![], - voices: Arc::new(RwLock::new(vec![])), - buffer: vec![vec![0.0;16384];2], - output_gain: 1., - recording: None, - mode: None, - editing: None, - size: Default::default(), - note_lo: 0.into(), - note_pt: 0.into(), - cursor: (0.into(), 0.into()), - color: Default::default(), - mixing_mode: Default::default(), - metering_mode: Default::default(), - }) - } - /// Value of cursor - pub fn cursor (&self) -> (usize, usize) { - (self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed)) - } -} - -impl NoteRange for Sampler { - fn note_lo (&self) -> &AtomicUsize { - &self.note_lo - } - fn note_axis (&self) -> &AtomicUsize { - &self.size.y - } -} - -impl NotePoint for Sampler { - fn note_len (&self) -> &AtomicUsize { - unreachable!(); - } - fn get_note_len (&self) -> usize { - 0 - } - fn set_note_len (&self, x: usize) -> usize { - 0 /*TODO?*/ - } - fn note_pos (&self) -> &AtomicUsize { - &self.note_pt - } - fn get_note_pos (&self) -> usize { - self.note_pt.load(Relaxed) - } - fn set_note_pos (&self, x: usize) -> usize { - let old = self.note_pt.swap(x, Relaxed); - self.cursor.0.store(x % 8, Relaxed); - self.cursor.1.store(x / 8, Relaxed); - old - } -} - -/// A sound sample. -#[derive(Default, Debug)] -pub struct Sample { - pub name: Arc, - pub start: usize, - pub end: usize, - pub channels: Vec>, - pub rate: Option, - pub gain: f32, - pub color: ItemTheme, -} - -impl Sample { - pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { - Self { - name: name.as_ref().into(), - start, - end, - channels, - rate: None, - gain: 1.0, - color: ItemTheme::random(), - } - } - pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { - Voice { - sample: sample.clone(), - after, - position: sample.read().unwrap().start, - velocity: velocity.as_int() as f32 / 127.0, - } - } -} - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] -pub struct Voice { - pub sample: Arc>, - pub after: usize, - pub position: usize, - pub velocity: f32, -} - -#[derive(Debug)] -pub enum SamplerMode { - // Load sample from path - Import(usize, Browse), -} diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs index f3f8ee05..1d30e44e 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -1,69 +1,7 @@ use crate::*; -def_command!(SamplerCommand: |sampler: Sampler| { - RecordToggle { slot: usize } => { - let slot = *slot; - let recording = sampler.recording.as_ref().map(|x|x.0); - let _ = Self::RecordFinish.execute(sampler)?; - // autoslice: continue recording at next slot - if recording != Some(slot) { - Self::RecordBegin { slot }.execute(sampler) - } else { - Ok(None) - } - }, - RecordBegin { slot: usize } => { - let slot = *slot; - sampler.recording = Some(( - slot, - Some(Arc::new(RwLock::new(Sample::new( - "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] - )))) - )); - Ok(None) - }, - RecordFinish => { - let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ - std::mem::swap(sample, &mut sampler.mapped[*index]); - sample - }); // TODO: undo - Ok(None) - }, - RecordCancel => { - sampler.recording = None; - Ok(None) - }, - PlaySample { slot: usize } => { - let slot = *slot; - if let Some(ref sample) = sampler.mapped[slot] { - sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); - } - Ok(None) - }, - StopSample { slot: usize } => { - let slot = *slot; - todo!(); - Ok(None) - }, -}); - -def_command!(FileBrowserCommand: |sampler: Sampler|{ - //("begin" [] Some(Self::Begin)) - //("cancel" [] Some(Self::Cancel)) - //("confirm" [] Some(Self::Confirm)) - //("select" [i: usize] Some(Self::Select(i.expect("no index")))) - //("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) - //("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) -}); - #[tengri_proc::expose] impl Sampler { - fn sample_selected (&self) -> usize { - (self.get_note_pos() as u8).into() - } - fn sample_selected_pitch (&self) -> u7 { - (self.get_note_pos() as u8).into() - } //fn file_browser_filter (&self) -> Arc { //todo!() //} @@ -102,34 +40,82 @@ impl Sampler { //fn selected_pitch () -> u7 { //(self.note_pos() as u8).into() // TODO //} + fn sample_selected (&self) -> usize { + (self.get_note_pos() as u8).into() + } + fn sample_selected_pitch (&self) -> u7 { + (self.get_note_pos() as u8).into() + } } - //select (&self, state: &mut Sampler, i: usize) -> Option { +#[tengri_proc::command(Sampler)] +impl SamplerCommand { + fn record_toggle (sampler: &mut Sampler, slot: usize) -> Perhaps { + let recording = sampler.recording.as_ref().map(|x|x.0); + Self::record_finish(sampler)?; + // autoslice: continue recording at next slot + if recording != Some(slot) { + Self::record_begin(sampler, slot) + } else { + Ok(None) + } + } + fn record_begin (sampler: &mut Sampler, slot: usize) -> Perhaps { + sampler.recording = Some(( + slot, + Some(Arc::new(RwLock::new(Sample::new( + "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] + )))) + )); + Ok(None) + } + fn record_finish (sampler: &mut Sampler) -> Perhaps { + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.mapped[*index]); + sample + }); // TODO: undo + Ok(None) + } + fn record_cancel (sampler: &mut Sampler) -> Perhaps { + sampler.recording = None; + Ok(None) + } + fn play_sample (sampler: &mut Sampler, slot: usize) -> Perhaps { + if let Some(ref sample) = sampler.mapped[slot] { + sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); + } + Ok(None) + } + fn stop_sample (sampler: &mut Sampler, slot: usize) -> Perhaps { + todo!(); + Ok(None) + } + //fn select (&self, state: &mut Sampler, i: usize) -> Option { //Self::Select(state.set_note_pos(i)) //} ///// Assign sample to slot - //set (&self, slot: u7, sample: Option>>) -> Option { + //fn set (&self, slot: u7, sample: Option>>) -> Option { //let i = slot.as_int() as usize; //let old = self.mapped[i].clone(); //self.mapped[i] = sample; //Some(Self::Set(old)) //} - //set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option { + //fn set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option { //todo!() //} - //set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option { + //fn set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option { //todo!() //} - //note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option { + //fn note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option { //todo!() //} - //note_off (&self, state: &mut Sampler, slot: u7) -> Option { + //fn note_off (&self, state: &mut Sampler, slot: u7) -> Option { //todo!() //} - //set_sample (&self, state: &mut Sampler, slot: u7, s: Option>>) -> Option { + //fn set_sample (&self, state: &mut Sampler, slot: u7, s: Option>>) -> Option { //Some(Self::SetSample(p, state.set_sample(p, s))) //} - //import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { + //fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { //match c { //FileBrowserCommand::Begin => { ////let voices = &state.state.voices; @@ -184,3 +170,14 @@ impl Sampler { ////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity")))) ////("note/off" [p: u7] ////Some(Self::NoteOff(p.expect("no slot")))))); +} + +#[tengri_proc::command(Sampler)] +impl FileBrowserCommand { + //("begin" [] Some(Self::Begin)) + //("cancel" [] Some(Self::Cancel)) + //("confirm" [] Some(Self::Confirm)) + //("select" [i: usize] Some(Self::Select(i.expect("no index")))) + //("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) + //("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) +} diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs new file mode 100644 index 00000000..3983e1c0 --- /dev/null +++ b/crates/device/src/sampler/sampler_model.rs @@ -0,0 +1,175 @@ +use crate::*; + +/// The sampler device plays sounds in response to MIDI notes. +#[derive(Debug)] +pub struct Sampler { + /// Name of sampler. + pub name: Arc, + /// Device color. + pub color: ItemTheme, + /// Audio input ports. Samples get recorded here. + pub audio_ins: Vec, + /// Audio input meters. + pub input_meters: Vec, + /// Sample currently being recorded. + pub recording: Option<(usize, Option>>)>, + /// Recording buffer. + pub buffer: Vec>, + /// Samples mapped to MIDI notes. + pub mapped: [Option>>;128], + /// Samples that are not mapped to MIDI notes. + pub unmapped: Vec>>, + /// Sample currently being edited. + pub editing: Option>>, + /// MIDI input port. Triggers sample playback. + pub midi_in: MidiInput, + /// Collection of currently playing instances of samples. + pub voices: Arc>>, + /// Audio output ports. Voices get played here. + pub audio_outs: Vec, + /// Audio output meters. + pub output_meters: Vec, + /// How to mix the voices. + pub mixing_mode: MixingMode, + /// How to meter the inputs and outputs. + pub metering_mode: MeteringMode, + /// Fixed gain applied to all output. + pub output_gain: f32, + /// Currently active modal, if any. + pub mode: Option, + /// Size of rendered sampler. + pub size: Measure, + /// Lowest note displayed. + pub note_lo: AtomicUsize, + /// Currently selected note. + pub note_pt: AtomicUsize, + /// Selected note as row/col. + pub cursor: (AtomicUsize, AtomicUsize), +} + +impl Sampler { + pub fn new ( + jack: &Jack<'static>, + name: impl AsRef, + midi_from: &[Connect], + audio_from: &[&[Connect];2], + audio_to: &[&[Connect];2], + ) -> Usually { + let name = name.as_ref(); + Ok(Self { + name: name.into(), + midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?, + audio_ins: vec![ + AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?, + AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?, + ], + audio_outs: vec![ + AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?, + AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?, + ], + input_meters: vec![0.0;2], + output_meters: vec![0.0;2], + mapped: [const { None };128], + unmapped: vec![], + voices: Arc::new(RwLock::new(vec![])), + buffer: vec![vec![0.0;16384];2], + output_gain: 1., + recording: None, + mode: None, + editing: None, + size: Default::default(), + note_lo: 0.into(), + note_pt: 0.into(), + cursor: (0.into(), 0.into()), + color: Default::default(), + mixing_mode: Default::default(), + metering_mode: Default::default(), + }) + } + /// Value of cursor + pub fn cursor (&self) -> (usize, usize) { + (self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed)) + } +} + +impl NoteRange for Sampler { + fn note_lo (&self) -> &AtomicUsize { + &self.note_lo + } + fn note_axis (&self) -> &AtomicUsize { + &self.size.y + } +} + +impl NotePoint for Sampler { + fn note_len (&self) -> &AtomicUsize { + unreachable!(); + } + fn get_note_len (&self) -> usize { + 0 + } + fn set_note_len (&self, x: usize) -> usize { + 0 /*TODO?*/ + } + fn note_pos (&self) -> &AtomicUsize { + &self.note_pt + } + fn get_note_pos (&self) -> usize { + self.note_pt.load(Relaxed) + } + fn set_note_pos (&self, x: usize) -> usize { + let old = self.note_pt.swap(x, Relaxed); + self.cursor.0.store(x % 8, Relaxed); + self.cursor.1.store(x / 8, Relaxed); + old + } +} + +/// A sound sample. +#[derive(Default, Debug)] +pub struct Sample { + pub name: Arc, + pub start: usize, + pub end: usize, + pub channels: Vec>, + pub rate: Option, + pub gain: f32, + pub color: ItemTheme, +} + +impl Sample { + pub fn new (name: impl AsRef, start: usize, end: usize, channels: Vec>) -> Self { + Self { + name: name.as_ref().into(), + start, + end, + channels, + rate: None, + gain: 1.0, + color: ItemTheme::random(), + } + } + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { + Voice { + sample: sample.clone(), + after, + position: sample.read().unwrap().start, + velocity: velocity.as_int() as f32 / 127.0, + } + } +} + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] +pub struct Voice { + pub sample: Arc>, + pub after: usize, + pub position: usize, + pub velocity: f32, +} + +#[derive(Debug)] +pub enum SamplerMode { + // Load sample from path + Import(usize, Browser), +} diff --git a/deps/tengri b/deps/tengri index 446ec7a7..e3e3c163 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0 +Subproject commit e3e3c163da02165e77a259eb715749b7f0097498 diff --git a/tek.edn b/tek.edn index 5066d1b1..14b11ae6 100644 --- a/tek.edn +++ b/tek.edn @@ -1,18 +1,12 @@ -(keys :axis/x (@left x/dec) (@right x/inc)) -(keys :axis/x2 (@shift/left x2/dec) (@shift/right x2/inc)) -(keys :axis/y (@up y/dec) (@down y/inc)) -(keys :axis/y2 (@shift/up y2/dec) (@shift/down y2/inc)) -(keys :axis/z (@minus z/dec) (@equal z/inc)) -(keys :axis/z2 (@underscore z2/dec) (@plus z2/inc)) -(keys :axis/i (@comma i/dec) (@period z/inc)) -(keys :axis/i2 (@lt i2/dec) (@gt z2/inc)) -(keys :axis/w (@openbracket w/dec) (@closebracket w/inc)) -(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc)) - -(mode :menu (keys :axis/y :confirm) :menu) +(mode :menu (keys :x :y :confirm) :menu) +(keys :x (@left x/dec) (@right x/inc)) +(keys :y (@up y/dec) (@down y/inc)) (keys :confirm (@enter confirm)) (view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in - (bg (g 30) (fill/xy (align/c :dialog/menu))))))) + (bg (g 30) (fill/xy (align/c (bg (g 40) (bsp/s + (text CONTINUE-SESSION) + (bsp/s (text LOAD-OTHER-SESSION) + (text BEGIN-NEW-SESSION))))))))))) (view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT))) (bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R)))))))) (view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN))) @@ -22,10 +16,18 @@ (keys :help (@f1 dialog :help)) (keys :back (@escape back)) (keys :page (@pgup page/up) (@pgdn page/down)) +(keys :x2 (@shift/left x2/dec) (@shift/right x2/inc)) +(keys :y2 (@shift/up y2/dec) (@shift/down y2/inc)) +(keys :z (@minus z/dec) (@equal z/inc)) +(keys :z2 (@underscore z2/dec) (@plus z2/inc)) +(keys :i (@comma i/dec) (@period z/inc)) +(keys :i2 (@lt i2/dec) (@gt z2/inc)) +(keys :w (@openbracket w/dec) (@closebracket w/inc)) +(keys :w2 (@openbrace w2/dec) (@closebrace w2/inc)) (keys :delete (@delete delete) (@backspace delete/back)) -(keys :input (see :axis/x :delete) (:char input)) -(keys :list (see :axis/y :confirm)) -(keys :length (see :axis/x :axis/y :confirm)) +(keys :input (see :x :delete) (:char input)) +(keys :list (see :y :confirm)) +(keys :length (see :x :y :confirm)) (keys :browse (see :list :input :focus)) (keys :history (@u undo 1) (@r redo 1)) (keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0)) @@ -78,15 +80,15 @@ (@up select :select/scene/dec) (@down select :select/scene/inc)) -(keys :track (see :color :launch :axis/z :axis/z2 :delete) +(keys :track (see :color :launch :z :z2 :delete) (@r toggle :rec) (@m toggle :mon) (@p toggle :play) (@P toggle :solo)) -(keys :scene (see :color :launch :axis/z :axis/z2 :delete)) +(keys :scene (see :color :launch :z :z2 :delete)) -(keys :clip (see :color :launch :axis/z :axis/z2 :delete) +(keys :clip (see :color :launch :z :z2 :delete) (@l toggle :loop)) (mode :groovebox @@ -152,13 +154,13 @@ (keys :sequencer (see :color :launch) (@shift/I input/add) (@shift/O output/add)) -(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete) +(keys :pool (see :axis-y :axis-w :z2 :color :delete) (@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin) (@shift/A clip/add :after :new/clip) (@shift/D clip/add :after :cloned/clip)) (keys :editor (see :editor/view :editor/note)) -(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2) +(keys :editor/view (see :x :x2 :z :z2) (@z toggle :lock)) -(keys :editor/note (see :axis/i :axis/i2 :axis/y :page) +(keys :editor/note (see :i :i2 :y :page) (@a editor/append :true) (@enter editor/append :false) (@del editor/delete/note) (@shift/del editor/delete/note))