diff --git a/crates/app/app.rs b/crates/app/app.rs index bdd705c7..f399769f 100644 --- a/crates/app/app.rs +++ b/crates/app/app.rs @@ -10,6 +10,8 @@ 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 @@ -20,26 +22,24 @@ 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, - /// Base color. - pub color: ItemTheme, + /// Contains all recently created clips. + pub pool: Pool, + /// Contains the currently edited musical arrangement + pub project: Arrangement, } /// Various possible dialog modes. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog { #[default] None, Help(usize), - Menu(usize), + Menu(usize, usize), Device(usize), Message(Arc), - Browser(BrowserTarget, Arc), + Browse(BrowseTarget, Arc), Options, } has!(Jack<'static>: |self: App|self.jack); @@ -61,68 +61,9 @@ 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 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 } @@ -136,10 +77,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<&BrowserTarget> { + pub fn browser_target (&self) -> Option<&BrowseTarget> { todo!() } } @@ -189,8 +130,8 @@ impl App { } } } - pub fn browser (&self) -> Option<&Browser> { - if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None } + pub fn browser (&self) -> Option<&Browse> { + if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None } } pub fn device_pick (&mut self, index: usize) { self.dialog = Dialog::Device(index); diff --git a/crates/app/app_bind.rs b/crates/app/app_bind.rs index 2a13994b..cf11ba45 100644 --- a/crates/app/app_bind.rs +++ b/crates/app/app_bind.rs @@ -1,30 +1,62 @@ 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) +}); + impl<'t> DslNs<'t, AppCommand> for App { dsl_exprs!(|app| -> AppCommand { /* TODO */ }); dsl_words!(|app| -> AppCommand { - "x/inc" => todo!(), - "x/dec" => todo!(), - "y/inc" => todo!(), - "y/dec" => todo!(), + "y/inc" => match app.dialog { + Dialog::Menu(index, count) => AppCommand::SetDialog { + dialog: Dialog::Menu(if count > 0 { + (index + 1) % count + } else { 0 }, count) + }, + _ => todo!(), + }, + "y/dec" => match app.dialog { + Dialog::Menu(index, count) => AppCommand::SetDialog { + dialog: Dialog::Menu(if count > 0 { + index.overflowing_sub(1).0.min(count.saturating_sub(1)) + } else { 0 }, count) + }, + _ => todo!(), + }, "confirm" => todo!(), }); } -#[derive(Debug)] -pub enum AppCommand { /* TODO */ } - -#[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) - } -} +def_command!(AppCommand: |app: App| { + SetDialog { dialog: Dialog } => + swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }), +}); //AppCommand => { //("x/inc" / diff --git a/crates/app/app_data.rs b/crates/app/app_data.rs index e44ada9f..8c25c39d 100644 --- a/crates/app/app_data.rs +++ b/crates/app/app_data.rs @@ -43,19 +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/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()), + ":dialog/menu" => Dialog::Menu(0, 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()), }); } diff --git a/crates/app/app_view.rs b/crates/app/app_view.rs index 6d7faef1..99ad7854 100644 --- a/crates/app/app_view.rs +++ b/crates/app/app_view.rs @@ -1,5 +1,49 @@ use crate::*; +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("▀")))), + }) +} + impl<'t> DslNs<'t, Box>> for App { dsl_exprs!(|app| -> Box> { "text" (tail: Arc) => Box::new(tail), @@ -71,12 +115,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() { - BrowserTarget::SaveProject => "Save project:", - BrowserTarget::LoadProject => "Load project:", - BrowserTarget::ImportSample(_) => "Import sample:", - BrowserTarget::ExportSample(_) => "Export sample:", - BrowserTarget::ImportClip(_) => "Import clip:", - BrowserTarget::ExportClip(_) => "Export clip:", + BrowseTarget::SaveProject => "Save project:", + BrowseTarget::LoadProject => "Load project:", + BrowseTarget::ImportSample(_) => "Import sample:", + BrowseTarget::ExportSample(_) => "Export sample:", + BrowseTarget::ImportClip(_) => "Import clip:", + BrowseTarget::ExportClip(_) => "Export clip:", }, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))), ":device" => { let selected = app.dialog.device_kind().unwrap(); @@ -130,7 +174,7 @@ impl<'t> DslNs<'t, Box>> for App { //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::Browser(BrowserTarget::Load, browser) => { + //Dialog::Browse(BrowseTarget::Load, browser) => { //"bobcat".boxed() ////Bsp::s( ////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( @@ -139,7 +183,7 @@ impl<'t> DslNs<'t, Box>> for App { ////Outer(true, Style::default().fg(Tui::g(96))) ////.enclose(Fill::xy(browser))) //}, - //Dialog::Browser(BrowserTarget::Export, browser) => { + //Dialog::Browse(BrowseTarget::Export, browser) => { //"bobcat".boxed() ////Bsp::s( ////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e( @@ -148,7 +192,7 @@ impl<'t> DslNs<'t, Box>> for App { ////Outer(true, Style::default().fg(Tui::g(96))) ////.enclose(Fill::xy(browser))) //}, - //Dialog::Browser(BrowserTarget::Import, browser) => { + //Dialog::Browse(BrowseTarget::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 a9e80a31..8a719729 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -50,56 +50,51 @@ pub enum LaunchMode { impl Cli { pub fn run (&self) -> Usually<()> { - 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()]; + let name = self.name.as_ref().map_or("tek", |x|x.as_str()); + let tracks = vec![]; + let scenes = vec![]; + let empty = &[] as &[&str]; + let left_froms = Connect::collect(&self.left_from, empty, empty); + let left_tos = Connect::collect(&self.left_to, empty, empty); + let right_froms = Connect::collect(&self.right_from, empty, empty); + let right_tos = Connect::collect(&self.right_to, empty, empty); + let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()]; + let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()]; + let mut config = Config::new(None); + config.init()?; Tui::new()?.run(&Jack::new_run(&name, move|jack|{ - 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::Menu(0), - mode, + dialog: Dialog::Menu(0, 0), + mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(), config, project: Arrangement { name: Default::default(), color: ItemTheme::random(), jack: jack.clone(), - clock, + clock: Clock::new(&jack, self.bpm)?, tracks, scenes, selection: Selection::TrackClip { track: 0, scene: 0 }, - midi_ins, - midi_outs, + 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 + }, ..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); @@ -110,6 +105,12 @@ 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 a44e955f..1206750e 100644 --- a/crates/config/config.rs +++ b/crates/config/config.rs @@ -20,11 +20,15 @@ use ::{ #[derive(Default, Debug)] pub struct Config { pub dirs: BaseDirectories, - pub modes: Arc, Arc>>>>>, - pub views: Arc, Arc>>>, - pub binds: Arc, EventMap>>>>, + pub modes: Modes, + pub views: Views, + pub binds: Binds, } +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)] @@ -34,7 +38,7 @@ pub struct Mode { pub info: Vec, pub view: Vec, pub keys: Vec, - pub modes: BTreeMap>, + pub modes: Modes, } /// A collection of input bindings. @@ -62,11 +66,16 @@ pub struct Condition(Arcbool + Send + Sync>>); impl Config { const CONFIG: &'static str = "tek.edn"; const DEFAULTS: &'static str = include_str!("../../tek.edn"); - 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 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_file ( &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> @@ -89,74 +98,59 @@ impl Config { let tail = expr.tail()?; let name = tail.head()?; let body = tail.tail()?; - println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); + println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { - 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), + 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()); + }, _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } + Ok(()) } else { return Err(format!("Config::load: expected expr, got: {item:?}").into()) }) } - pub fn load_view (&mut self, id: Arc, dsl: impl Dsl) -> Usually<()> { - self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().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)); Ok(()) } - 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!("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<()> { + pub fn load_one (&mut self, 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()), + "name" => { + self.name.push(item.tail()?.map(|x|x.trim()).unwrap_or("").into()) + }, + "info" => { + self.info.push(item.tail()?.map(|x|x.trim()).unwrap_or("").into()) + }, "keys" => { - item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?; + item.tail()?.each(|item|{ + self.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); + "self" => if let Some(id) = item.tail()?.head()? { + Self::load_into(&self.modes, &id, &item.tail().tail())?; } else { - return Err(format!("load_mode_one: incomplete: {item:?}").into()); + return Err(format!("Mode::load_one: self: incomplete: {item:?}").into()); }, - _ => mode.view.push(item.expr()?.unwrap().into()), - } + _ if let Some(src) = item.src()? => self.view.push(src.into()), + _ => {}, + }; } else if let Ok(Some(word)) = item.word() { - mode.view.push(word.into()); + self.view.push(word.into()); } else { - return Err(format!("load_mode_one: unexpected: {item:?}").into()); + return Err(format!("Mode::load_one: unexpected: {item:?}").into()); }) } } @@ -196,6 +190,36 @@ 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 d0e66b23..0a3b6dd2 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" ] -browser = [] +browse = [] clap = [] clock = [] editor = [] @@ -28,7 +28,7 @@ meter = [] mixer = [] pool = [] port = [] -sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ] +sampler = [ "port", "meter", "mixer", "browse", "symphonia", "wavers" ] sequencer = [ "port", "clock", "uuid", "pool" ] sf2 = [] vst2 = [] diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 11305006..56ac336b 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -2,7 +2,6 @@ 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::*; @@ -19,3 +18,187 @@ 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 3e73941d..75a1d91d 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -1,11 +1,10 @@ use crate::*; -#[tengri_proc::command(Arrangement)] -impl ArrangementCommand { - fn home (arranger: &mut Arrangement) -> Perhaps { - arranger.editor = None; - Ok(None) - } - fn edit (arranger: &mut Arrangement) -> Perhaps { + +def_command!(ArrangementCommand: |arranger: Arrangement| { + + Home => { arranger.editor = None; Ok(None) }, + + Edit => { let selection = arranger.selection().clone(); arranger.editor = if arranger.editor.is_some() { None @@ -36,14 +35,13 @@ impl ArrangementCommand { } } Ok(None) - } - /// 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 { + }, + + //// Set the selection + Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) }, + + //// Launch the selected clip or scene + Launch => { match *arranger.selection() { Selection::Track(t) => { arranger.tracks[t].sequencer.enqueue_next(None) @@ -59,9 +57,10 @@ impl ArrangementCommand { _ => {} }; Ok(None) - } - /// Set the color of the selected entity - fn set_color (arranger: &mut Arrangement, palette: Option) -> Perhaps { + }, + + //// Set the color of the selected entity + SetColor { palette: Option } => { let mut palette = palette.unwrap_or_else(||ItemTheme::random()); let selection = *arranger.selection(); Ok(Some(Self::SetColor { palette: Some(match selection { @@ -88,11 +87,11 @@ impl ArrangementCommand { }, _ => todo!() }) })) - } - fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps { - todo!("delegate") - } - fn track_add (arranger: &mut Arrangement) -> Perhaps { + }, + + Track { track: TrackCommand } => { todo!("delegate") }, + + TrackAdd => { let index = arranger.track_add(None, None, &[], &[])?.0; *arranger.selection_mut() = match arranger.selection() { Selection::Track(_) => Selection::Track(index), @@ -102,12 +101,16 @@ impl ArrangementCommand { _ => *arranger.selection() }; Ok(Some(Self::TrackDelete { index })) - } - fn track_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps { - todo!(); + }, + + TrackSwap { index: usize, other: usize } => { + let index = *index; + let other = *other; Ok(Some(Self::TrackSwap { index, other })) - } - fn track_delete (arranger: &mut Arrangement, index: usize) -> Perhaps { + }, + + TrackDelete { index: usize } => { + let index = *index; let exists = arranger.tracks().get(index).is_some(); if exists { let track = arranger.tracks_mut().remove(index); @@ -124,52 +127,61 @@ impl ArrangementCommand { } Ok(None) //TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) }) - } - fn midi_in (_arranger: &mut Arrangement, _input: MidiInputCommand) -> Perhaps { - todo!("delegate"); - Ok(None) - } - fn midi_in_add (arranger: &mut Arrangement) -> Perhaps { + }, + + MidiIn { input: MidiInputCommand } => { + todo!("delegate"); Ok(None) + }, + + MidiInAdd => { arranger.midi_in_add()?; Ok(None) - } - fn midi_out (_arranger: &mut Arrangement, _input: MidiOutputCommand) -> Perhaps { + }, + + MidiOut { output: MidiOutputCommand } => { todo!("delegate"); Ok(None) - } - fn midi_out_add (arranger: &mut Arrangement) -> Perhaps { + }, + + MidiOutAdd => { arranger.midi_out_add()?; Ok(None) - } - fn device (_arranger: &mut Arrangement, _input: DeviceCommand) -> Perhaps { + }, + + Device { command: DeviceCommand } => { todo!("delegate"); Ok(None) - } - fn device_add (_arranger: &mut Arrangement, _i: usize) -> Perhaps { + }, + + DeviceAdd { index: usize } => { todo!("delegate"); Ok(None) - } - fn scene (arranger: &mut Arrangement, scene: SceneCommand) -> Perhaps { + }, + + Scene { scene: SceneCommand } => { todo!("delegate"); Ok(None) - } - fn output_add (arranger: &mut Arrangement) -> Perhaps { + }, + + OutputAdd => { arranger.midi_outs.push(MidiOutput::new( arranger.jack(), &format!("/M{}", arranger.midi_outs.len() + 1), &[] )?); Ok(None) - } - fn input_add (arranger: &mut Arrangement) -> Perhaps { + }, + + InputAdd => { arranger.midi_ins.push(MidiInput::new( arranger.jack(), &format!("M{}/", arranger.midi_ins.len() + 1), &[] )?); Ok(None) - } - fn scene_add (arranger: &mut Arrangement) -> Perhaps { + }, + + SceneAdd => { let index = arranger.scene_add(None, None)?.0; *arranger.selection_mut() = match arranger.selection() { Selection::Scene(_) => Selection::Scene(index), @@ -180,12 +192,16 @@ impl ArrangementCommand { _ => *arranger.selection() }; Ok(None) // TODO - } - fn scene_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps { - todo!(); + }, + + SceneSwap { index: usize, other: usize } => { + let index = *index; + let other = *other; Ok(Some(Self::SceneSwap { index, other })) - } - fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps { + }, + + SceneDelete { index: usize } => { + let index = *index; let scenes = arranger.scenes_mut(); Ok(if scenes.get(index).is_some() { let _scene = scenes.remove(index); @@ -193,41 +209,50 @@ impl ArrangementCommand { } else { None }) - } - fn scene_launch (arranger: &mut Arrangement, index: usize) -> Perhaps { + }, + + SceneLaunch { index: usize } => { + let index = *index; 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) - } - fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps { + }, + + Clip { scene: ClipCommand } => { todo!("delegate") - } - fn clip_get (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { + }, + + ClipGet { a: usize, b: usize } => { //(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!() - } - fn clip_put (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { + }, + + ClipPut { a: usize, b: usize } => { //(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!() - } - fn clip_del (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { + }, + + ClipDel { a: usize, b: usize } => { //("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None)))) todo!() - } - fn clip_enqueue (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { + }, + + ClipEnqueue { a: usize, b: usize } => { //(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!() - } - fn clip_edit (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps { + }, + + ClipSwap { a: usize, b: usize }=> { //(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 a856c47b..e85e6839 100644 --- a/crates/device/src/arranger/arranger_clip.rs +++ b/crates/device/src/arranger/arranger_clip.rs @@ -10,17 +10,19 @@ impl MidiClip { fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } } -#[tengri_proc::command(MidiClip)] -impl ClipCommand { - fn set_color (clip: &mut MidiClip, color: Option) -> Perhaps { +def_command!(ClipCommand: |clip: MidiClip| { + + SetColor { color: Option } => { //(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!() - } - fn set_loop (clip: &mut MidiClip, looping: Option) -> Perhaps { + }, + + SetLoop { looping: Option } => { //(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 deleted file mode 100644 index 35f5b296..00000000 --- a/crates/device/src/arranger/arranger_model.rs +++ /dev/null @@ -1,184 +0,0 @@ -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 1c48531d..61e2841d 100644 --- a/crates/device/src/arranger/arranger_scenes.rs +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -64,23 +64,14 @@ impl Scene { fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } -#[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!() - } -} +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}), +}); 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 cf483faa..364ffab8 100644 --- a/crates/device/src/arranger/arranger_tracks.rs +++ b/crates/device/src/arranger/arranger_tracks.rs @@ -129,43 +129,21 @@ impl Track { fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } -#[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) - } -} +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 }), +}); #[derive(Debug, Default)] pub struct Track { diff --git a/crates/device/src/browser/browser_model.rs b/crates/device/src/browse.rs similarity index 79% rename from crates/device/src/browser/browser_model.rs rename to crates/device/src/browse.rs index fe0a0645..c9ecd55f 100644 --- a/crates/device/src/browser/browser_model.rs +++ b/crates/device/src/browse.rs @@ -1,9 +1,11 @@ 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 BrowserTarget { +pub enum BrowseTarget { SaveProject, LoadProject, ImportSample(Arc>>), @@ -12,9 +14,21 @@ pub enum BrowserTarget { 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)] -pub struct Browser { +#[derive(Debug, Clone, Default, PartialEq)] +pub struct Browse { pub cwd: PathBuf, pub dirs: Vec<(OsString, String)>, pub files: Vec<(OsString, String)>, @@ -24,7 +38,7 @@ pub struct Browser { pub size: Measure, } -impl Browser { +impl Browse { 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_api.rs b/crates/device/src/browse/browse_api.rs new file mode 100644 index 00000000..21f78b1e --- /dev/null +++ b/crates/device/src/browse/browse_api.rs @@ -0,0 +1,93 @@ +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/browser_view.rs b/crates/device/src/browse/browse_view.rs similarity index 90% rename from crates/device/src/browser/browser_view.rs rename to crates/device/src/browse/browse_view.rs index af84ba3a..4d73b0bd 100644 --- a/crates/device/src/browser/browser_view.rs +++ b/crates/device/src/browse/browse_view.rs @@ -1,6 +1,6 @@ use crate::*; -content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator { +content!(TuiOut: |self: Browse|Map::south(1, ||EntriesIterator { offset: 0, index: 0, length: self.dirs.len() + self.files.len(), @@ -8,7 +8,7 @@ content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator { }, |entry, _index|Fill::x(Align::w(entry)))); struct EntriesIterator<'a> { - browser: &'a Browser, + browser: &'a Browse, offset: usize, length: usize, index: usize, diff --git a/crates/device/src/browser.rs b/crates/device/src/browser.rs deleted file mode 100644 index 5a83e797..00000000 --- a/crates/device/src/browser.rs +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 1f80e1f8..00000000 --- a/crates/device/src/browser/browser_api.rs +++ /dev/null @@ -1,102 +0,0 @@ -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/clock/clock_api.rs b/crates/device/src/clock/clock_api.rs index e8544525..9235869a 100644 --- a/crates/device/src/clock/clock_api.rs +++ b/crates/device/src/clock/clock_api.rs @@ -14,48 +14,33 @@ 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 } } -#[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) })) - } -} +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 + }), +}); diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs index 9de535cd..e6667d17 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -84,6 +84,4 @@ audio!(|self: DeviceAudio<'a>, client, scope|{ } }); -#[tengri_proc::command(Device)] -impl DeviceCommand { -} +def_command!(DeviceCommand: |device: Device| {}); diff --git a/crates/device/src/editor/editor_api.rs b/crates/device/src/editor/editor_api.rs index 8a588208..9f22a6d9 100644 --- a/crates/device/src/editor/editor_api.rs +++ b/crates/device/src/editor/editor_api.rs @@ -1,5 +1,29 @@ 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!() @@ -91,56 +115,3 @@ use crate::*; !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 0ccf3724..22edce2e 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 = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*; +#[cfg(feature = "browse")] mod browse; #[cfg(feature = "browse")] pub use self::browse::*; #[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,3 +85,27 @@ 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 eaf9a753..90e7e490 100644 --- a/crates/device/src/pool.rs +++ b/crates/device/src/pool.rs @@ -1,3 +1,213 @@ -mod pool_api; pub use self::pool_api::*; -mod pool_model; pub use self::pool_model::*; -mod pool_view; pub use self::pool_view::*; +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 +}); diff --git a/crates/device/src/pool/pool_api.rs b/crates/device/src/pool/pool_api.rs index ffce5802..24e435b6 100644 --- a/crates/device/src/pool/pool_api.rs +++ b/crates/device/src/pool/pool_api.rs @@ -8,85 +8,65 @@ 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() } } -#[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})? +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})? } else { - 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})? + None }) }, + // Export to file + Export { command: BrowseCommand } => { + Ok(if let Some(browse) = pool.browse.as_mut() { + command.delegate(browse, |command|Self::Export{command})? } else { - None - }) - } + None }) }, +}); - /// Update the contents of the clip pool - fn clip (pool: &mut Pool, command: PoolClipCommand) -> Perhaps { - Ok(command.execute(pool)?.map(|command|Self::Clip{command})) - } +def_command!(PoolClipCommand: |pool: Pool| { -} + Delete { index: usize } => { + let index = *index; + let clip = pool.clips_mut().remove(index).read().unwrap().clone(); + Ok(Some(Self::Add { index, clip })) }, -#[tengri_proc::command(Pool)] -impl PoolClipCommand { + Swap { index: usize, other: usize } => { + let index = *index; + let other = *other; + pool.clips_mut().swap(index, other); + Ok(Some(Self::Swap { index, other })) }, - fn add (pool: &mut Pool, index: usize, clip: MidiClip) -> Perhaps { + Export { index: usize, path: PathBuf } => { + todo!("export clip to midi file"); }, + + Add { index: usize, clip: MidiClip } => { + let index = *index; let mut index = index; - let clip = Arc::new(RwLock::new(clip)); + let clip = Arc::new(RwLock::new(clip.clone())); let mut clips = pool.clips_mut(); if index >= clips.len() { index = clips.len(); @@ -94,20 +74,10 @@ impl PoolClipCommand { } else { clips.insert(index, clip); } - Ok(Some(Self::Delete { index })) - } + Ok(Some(Self::Delete { 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 { + Import { index: usize, path: PathBuf } => { + let index = *index; let bytes = std::fs::read(&path)?; let smf = Smf::parse(bytes.as_slice())?; let mut t = 0u32; @@ -124,74 +94,64 @@ impl PoolClipCommand { 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)?) }, - 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 { + SetName { index: usize, name: Arc } => { + let index = *index; let clip = &mut pool.clips_mut()[index]; let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name; - Ok(Some(Self::SetName { index, name: old_name })) - } + clip.write().unwrap().name = name.clone(); + Ok(Some(Self::SetName { index, name: old_name })) }, - fn set_length (pool: &mut Pool, index: usize, length: usize) -> Perhaps { + SetLength { index: usize, length: usize } => { + let index = *index; 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 })) }, - fn set_color (pool: &mut Pool, index: usize, color: ItemColor) -> Perhaps { - let mut color = ItemTheme::from(color); + SetColor { index: usize, color: ItemColor } => { + let index = *index; + 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 })) }, -} +}); -#[tengri_proc::command(Pool)] -impl RenameCommand { - fn begin (_pool: &mut Pool) -> Perhaps { - unreachable!(); - } - fn cancel (pool: &mut Pool) -> Perhaps { +def_command!(RenameCommand: |pool: Pool| { + Begin => unreachable!(), + + Cancel => { if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { pool.clips()[clip].write().unwrap().name = old_name.clone().into(); } - return Ok(None) - } - fn confirm (pool: &mut Pool) -> Perhaps { + Ok(None) }, + + Confirm => { 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 })) } - 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; - } - return Ok(None) - } -} + Ok(None) }, -#[tengri_proc::command(Pool)] -impl CropCommand { - fn begin (_pool: &mut Pool) -> Perhaps { - unreachable!() - } - fn cancel (pool: &mut Pool) -> Perhaps { + Set { value: Arc } => { + if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() { + pool.clips()[clip].write().unwrap().name = value.clone(); + } + Ok(None) }, +}); + +def_command!(CropCommand: |pool: Pool| { + Begin => unreachable!(), + + Cancel => { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } - Ok(None) - } - fn set (pool: &mut Pool, length: usize) -> Perhaps { + Ok(None) }, + + Set { length: usize } => { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -204,25 +164,25 @@ impl CropCommand { *pool.mode_mut() = None; return Ok(old_length.map(|length|Self::Set { length })) } - Ok(None) - } - fn next (pool: &mut Pool) -> Perhaps { + Ok(None) }, + + Next => { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { focus.next() } - Ok(None) - } - fn prev (pool: &mut Pool) -> Perhaps { + Ok(None) }, + + Prev => { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() } - Ok(None) - } - fn inc (pool: &mut Pool) -> Perhaps { + Ok(None) }, + + Inc => { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -232,9 +192,9 @@ impl CropCommand { ClipLengthFocus::Tick => { *length += 1 }, } } - Ok(None) - } - fn dec (pool: &mut Pool) -> Perhaps { + Ok(None) }, + + Dec => { if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { @@ -244,6 +204,5 @@ impl CropCommand { 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 deleted file mode 100644 index 166c4cbe..00000000 --- a/crates/device/src/pool/pool_model.rs +++ /dev/null @@ -1,209 +0,0 @@ -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 f75959ea..edfae329 100644 --- a/crates/device/src/port.rs +++ b/crates/device/src/port.rs @@ -1,5 +1,7 @@ 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::*; @@ -8,6 +10,58 @@ 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; @@ -33,184 +87,3 @@ 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 new file mode 100644 index 00000000..7742d743 --- /dev/null +++ b/crates/device/src/port/port_api.rs @@ -0,0 +1,17 @@ +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 7edd0755..1c36d2fd 100644 --- a/crates/device/src/port/port_audio_in.rs +++ b/crates/device/src/port/port_audio_in.rs @@ -1,20 +1,7 @@ use crate::*; -#[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 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 fda9d606..451eb3c8 100644 --- a/crates/device/src/port/port_audio_out.rs +++ b/crates/device/src/port/port_audio_out.rs @@ -1,20 +1,7 @@ use crate::*; -#[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 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 new file mode 100644 index 00000000..d8bb6ad7 --- /dev/null +++ b/crates/device/src/port/port_connect.rs @@ -0,0 +1,183 @@ +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 b92f0c95..a66ba049 100644 --- a/crates/device/src/port/port_midi_in.rs +++ b/crates/device/src/port/port_midi_in.rs @@ -1,24 +1,7 @@ use crate::*; -//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::(n)); +impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } } -#[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; @@ -51,17 +34,13 @@ 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 d32cafef..62a70c41 100644 --- a/crates/device/src/port/port_midi_out.rs +++ b/crates/device/src/port/port_midi_out.rs @@ -1,26 +1,7 @@ use crate::*; -#[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 HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } } + impl JackPort for MidiOutput { type Port = MidiOut; type Pair = MidiIn; @@ -59,6 +40,7 @@ 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. @@ -99,10 +81,6 @@ 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 47ac6366..be89a841 100644 --- a/crates/device/src/sampler.rs +++ b/crates/device/src/sampler.rs @@ -1,3 +1,5 @@ +use crate::*; + pub(crate) use symphonia::{ core::{ formats::Packet, @@ -14,7 +16,6 @@ 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; @@ -23,3 +24,177 @@ 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 1d30e44e..f3f8ee05 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -1,7 +1,69 @@ 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!() //} @@ -40,82 +102,34 @@ 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() - } } -#[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 { + //select (&self, state: &mut Sampler, i: usize) -> Option { //Self::Select(state.set_note_pos(i)) //} ///// Assign sample to slot - //fn set (&self, slot: u7, sample: Option>>) -> Option { + //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)) //} - //fn set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option { + //set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option { //todo!() //} - //fn set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option { + //set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option { //todo!() //} - //fn note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option { + //note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option { //todo!() //} - //fn note_off (&self, state: &mut Sampler, slot: u7) -> Option { + //note_off (&self, state: &mut Sampler, slot: u7) -> Option { //todo!() //} - //fn set_sample (&self, state: &mut Sampler, slot: u7, s: Option>>) -> Option { + //set_sample (&self, state: &mut Sampler, slot: u7, s: Option>>) -> Option { //Some(Self::SetSample(p, state.set_sample(p, s))) //} - //fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { + //import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { //match c { //FileBrowserCommand::Begin => { ////let voices = &state.state.voices; @@ -170,14 +184,3 @@ impl SamplerCommand { ////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 deleted file mode 100644 index 3983e1c0..00000000 --- a/crates/device/src/sampler/sampler_model.rs +++ /dev/null @@ -1,175 +0,0 @@ -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 e3e3c163..446ec7a7 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit e3e3c163da02165e77a259eb715749b7f0097498 +Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0 diff --git a/tek.edn b/tek.edn index 14b11ae6..6fb3ebe8 100644 --- a/tek.edn +++ b/tek.edn @@ -1,4 +1,4 @@ -(mode :menu (keys :x :y :confirm) :menu) +(mode :menu (keys :y :confirm) :menu) (keys :x (@left x/dec) (@right x/inc)) (keys :y (@up y/dec) (@down y/inc)) (keys :confirm (@enter confirm))