diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 7e8b2213..fcd4ff7e 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -5,18 +5,20 @@ use std::collections::{HashMap, BTreeMap}; use syn::{ Error, Path, Ident, Variant, Fields, BinOp, Expr, ExprPath, ExprBinary, ExprAssign, ExprLit, Lit, LitStr, - ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature, - MetaList, Type, TypePath, FnArg, PatType, + Item, ItemEnum, ItemImpl, ImplItem, ImplItemFn, ItemTrait, TraitItem, TraitItemFn, + Signature, MetaList, Type, TypePath, FnArg, PatType, Attribute, parse::{ParseStream, Parse, Result}, - spanned::Spanned + spanned::Spanned, + punctuated::Punctuated, + token::Comma }; macro_rules! attribute { ($name:ident { $($body:tt)* }) => { #[proc_macro_attribute] pub fn $name (meta: TokenStream, item: TokenStream) -> TokenStream { - write(self::$name::Def( - syn::parse_macro_input!(meta as self::$name::Meta), - syn::parse_macro_input!(item as self::$name::Item), + write(self::$name::CustomAttribute( + syn::parse_macro_input!(meta as self::$name::CustomAttributeMeta), + syn::parse_macro_input!(item as self::$name::CustomAttributeItem), )) } mod $name { @@ -27,70 +29,139 @@ macro_rules! attribute { } attribute!(commands { - #[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item); - #[derive(Debug, Clone)] pub struct Meta(pub Path, pub LitStr); - #[derive(Debug, Clone)] pub struct Item( - pub Path, pub ItemImpl, pub HashMap, LitStr)> + #[derive(Debug, Clone)] pub struct CustomAttribute( + pub CustomAttributeMeta, + pub CustomAttributeItem, + ); + #[derive(Debug, Clone)] pub struct CustomAttributeMeta( + pub Path, + pub Option + ); + #[derive(Debug, Clone)] pub struct CustomAttributeItem( + pub Path, + pub CustomAttributeItemDispatch, + pub Item ); - impl Parse for Meta { + pub type CustomAttributeItemDispatch = HashMap, LitStr)>; + + impl Parse for CustomAttributeMeta { + /// Parse contents of `#[command(...)]` attribute tag. fn parse (input: ParseStream) -> Result { - let meta = input.parse()?; - if let Expr::Assign(ExprAssign { ref left, ref right, .. }) = meta - && let Expr::Path(ExprPath { path, .. }) = &**left - && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right - { - Ok(Self(path.clone(), lit.clone())) - } else { - Err(Error::new(meta.span(), format!( - "must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}" + let meta: Expr = input.parse()?; + Ok(match meta { + // Struct name only + Expr::Path(ExprPath { path, .. }) => Self(path, None), + + // Struct name with namespace prefix + Expr::Assign(ExprAssign { ref left, ref right, .. }) + if let Expr::Path(ExprPath { path, .. }) = &**left + && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right + => Self(path.clone(), Some(lit.clone())), + + // All other variants invalid + _ => return Err(Error::new(meta.span(), format!( + "must be: #[commands(Struct)] or #[commands(Struct = \"struct\")], got: {meta:?}" ))) - } + }) } } - impl Parse for Item { + impl Parse for CustomAttributeItem { + /// Parse contents of `trait` or `impl` block annotated with `#[command(...)]` fn parse (input: ParseStream) -> Result { - let mut item: ItemImpl = input.parse()?; - let path = if let Type::Path(TypePath { path, .. }) = &*item.self_ty { - path - } else { - return Err(Error::new(item.self_ty.span(), format!("must be path to struct"))) - }; - let mut dispatch: HashMap, LitStr)> = Default::default(); - for item in item.items.iter_mut() { - if let ImplItem::Fn(ImplItemFn { - attrs, sig: Signature { ident, inputs, .. }, .. - }) = item { - *attrs = attrs.iter().filter(|attr|{ - if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta - && path == &Path::from(Ident::new("command", Span::call_site())) - && let Ok(handler) = syn::parse2::(tokens.clone()) - && let Expr::Assign(ExprAssign { ref left, ref right, .. }) = handler - && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right - && let Expr::Path(ExprPath { path, .. }) = &**left - && path.segments.len() == 1 { - dispatch.insert(ident.clone(), ( - path.segments.first().unwrap().ident.clone(), - inputs.iter().cloned().collect(), - lit.clone() - )); - false - } else { - true - } - }) - .cloned() - .collect(); - } - } - Ok(Self(path.clone(), item, dispatch)) + let mut item: Item = input.parse()?; + Ok(Self( + parse_custom_attribute_item_path(&item)?, + parse_custom_attribute_item_dispatch(&mut item)?, + item, + )) } } - impl ToTokens for Def { + fn parse_custom_attribute_item_path (item: &Item) -> Result { + Ok(match item { + Item::Trait(ItemTrait { ident, .. }) => Path { + leading_colon: None, + segments: syn::punctuated::Punctuated::from_iter([ + syn::PathSegment { + ident: ident.clone(), + arguments: syn::PathArguments::None // TODO support generics + } + ]) + }, + + Item::Impl(ItemImpl { self_ty, .. }) if let Type::Path(TypePath { + path, .. + }) = &**self_ty => path.clone(), + + _ => return Err( + Error::new(item.span(), format!("#[commands] works on trait or inherent impl")) + ) + }) + } + + /// Pick out annotated functions from the `trait` or `impl` block, + /// adding them to the [CustomAttributeItemDispatch] collection. + fn parse_custom_attribute_item_dispatch (item: &mut Item) + -> Result + { + let mut dispatch: CustomAttributeItemDispatch = Default::default(); + let mut dispatch_attrs = | + attrs: &Vec, ident: &Ident, inputs: &Punctuated + | { + attrs.iter().filter(|attr|{ + if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta + && path == &Path::from(Ident::new("command", Span::call_site())) + && let Ok(handler) = syn::parse2::(tokens.clone()) + && let Expr::Assign(ExprAssign { ref left, ref right, .. }) = handler + && let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right + && let Expr::Path(ExprPath { path, .. }) = &**left + && path.segments.len() == 1 { + dispatch.insert(ident.clone(), ( + path.segments.first().unwrap().ident.clone(), + inputs.iter().cloned().collect(), + lit.clone() + )); + false + } else { + true + } + }).cloned().collect() + }; + + match item { + Item::Trait(ItemTrait { items, .. }) => { + for item in items.iter_mut() { + if let TraitItem::Fn(TraitItemFn { + attrs, sig: Signature { ident, inputs, .. }, .. + }) = item { + *attrs = dispatch_attrs(attrs, ident, inputs); + } + } + }, + Item::Impl(ItemImpl { items, .. }) => { + for item in items.iter_mut() { + if let ImplItem::Fn(ImplItemFn { + attrs, sig: Signature { ident, inputs, .. }, .. + }) = item { + *attrs = dispatch_attrs(attrs, ident, inputs); + } + } + }, + _ => return Err( + Error::new(item.span(), format!("#[commands] works on trait or inherent impl")) + ) + } + Ok(dispatch) + } + + impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(Meta(command, namespace), Item(state, item, items)) = self; + let Self( + CustomAttributeMeta(command, namespace), + CustomAttributeItem(state, items, item), + ) = self; let mut variants = quote! {}; let mut dispatch = quote! {}; let mut keywords = quote! {}; @@ -116,7 +187,11 @@ attribute!(commands { } else { quote! { #command::#variant => state.#ident(), } }); - let keyword = format!("{}/{}", namespace.value(), keyword.value()); + let keyword = if let Some(namespace) = namespace { + format!("{}/{}", namespace.value(), keyword.value()) + } else { + keyword.value() + }; if has_args { append(&mut expressions, quote! { #keyword (#typed) => { #command::#variant { #params } }, @@ -127,24 +202,40 @@ attribute!(commands { }); } } - append(out, quote! { - #[derive(Debug, Clone)] pub enum #command { #variants } - - impl<'a> dizzle::Namespace<'a, #command> for #state { - symbols!('a |state| -> #command { - #keywords - }); - expressions!('a |state| -> #command { - #expressions - }); - } - - impl #command { - pub fn act (self, state: &mut #state) -> Perhaps { - match self { #dispatch _ => unreachable!() } + let impls = match item { + Item::Impl(ItemImpl { generics, .. }) => { + let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes()); + let tys = Punctuated::<_, Comma>::from_iter(generics.type_params()); + let cns = Punctuated::<_, Comma>::from_iter(generics.const_params()); + quote! { + impl<'n, #tys> dizzle::Namespace<'n, #command> for #state { + symbols!('n |state: Self| -> #command { #keywords }); + expressions!('n |state: Self| -> #command { #expressions }); + } + impl #generics #command { + pub fn act (self, state: &mut #state) -> Perhaps { + match self { #dispatch _ => unreachable!() } + } + } } - } - + }, + Item::Trait { .. } => quote! { + impl<'n, T: #state + 'n> dizzle::Namespaced<'n, T> for #command { + symbols!('n |state: T| -> Self { #keywords }); + expressions!('n |state: T| -> Self { #expressions }); + } + impl #command { + pub fn act <'n, T: #state + 'n> (self, state: &mut T) -> Perhaps { + match self { #dispatch _ => unreachable!() } + } + } + }, + _ => panic!("trait or inherent impl needed for #[commands]") + }; + append(out, quote! { + /// Command variants + #[derive(Debug, Clone)] pub enum #command { #variants } + #impls #item }) } @@ -152,15 +243,22 @@ attribute!(commands { }); attribute!(command { - #[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item); - #[derive(Debug, Clone)] pub struct Meta(pub Path); - #[derive(Debug, Clone)] pub struct Item(pub ItemEnum, pub HashMap); - impl Parse for Meta { + #[derive(Debug, Clone)] pub struct CustomAttribute( + pub CustomAttributeMeta, + pub CustomAttributeItem + ); + #[derive(Debug, Clone)] pub struct CustomAttributeMeta( + pub Path + ); + #[derive(Debug, Clone)] pub struct CustomAttributeItem( + pub ItemEnum, pub HashMap + ); + impl Parse for CustomAttributeMeta { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse()?)) } } - impl Parse for Item { + impl Parse for CustomAttributeItem { fn parse (input: ParseStream) -> Result { let mut item: ItemEnum = input.parse()?; let mut branches: HashMap = Default::default(); @@ -187,9 +285,12 @@ attribute!(command { Ok(Self(item, branches)) } } - impl ToTokens for Def { + impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(Meta(state), Item(item, branches)) = self; + let Self( + CustomAttributeMeta(state), + CustomAttributeItem(item, branches) + ) = self; let ident = &item.ident; let mut body = quote! {}; for (variant, (fields, handler)) in branches.iter() { @@ -249,21 +350,21 @@ attribute!(command { }); attribute!(keyword { - #[derive(Debug, Clone)] pub struct Def( - pub Meta, pub Item + #[derive(Debug, Clone)] pub struct CustomAttribute( + pub CustomAttributeMeta, pub CustomAttributeItem ); - #[derive(Debug, Clone)] pub struct Meta( + #[derive(Debug, Clone)] pub struct CustomAttributeMeta( pub Path ); - #[derive(Debug, Clone)] pub struct Item( + #[derive(Debug, Clone)] pub struct CustomAttributeItem( pub ItemEnum, pub BTreeMap)>> ); - impl Parse for Meta { + impl Parse for CustomAttributeMeta { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse()?)) } } - impl Parse for Item { + impl Parse for CustomAttributeItem { fn parse (input: ParseStream) -> Result { let mut item: ItemEnum = input.parse()?; for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() { @@ -281,18 +382,19 @@ attribute!(keyword { Ok(Self(item, Default::default())) } } - impl ToTokens for Def { + impl ToTokens for CustomAttribute { fn to_tokens (&self, out: &mut TokenStream2) { - let Self(Meta(state), Item(item, variants)) = self; + let Self( + CustomAttributeMeta(state), + CustomAttributeItem(item, variants) + ) = self; let ident = &item.ident; let body = quote! {}; append(out, quote! { #item - impl<'a> Namespace<'a, #ident> for #state { - symbols!('a |_state| -> #ident { - #body, - }); + impl<'n> Namespace<'n, #ident> for #state { + symbols!('n |_state: #state| -> #ident { #body, }); } }) } diff --git a/src/device/arrange.rs b/src/device/arrange.rs index 3b19c891..9699ab44 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -46,110 +46,6 @@ pub struct Arrangement { #[cfg(feature = "scene")] pub scene_scroll: usize, } -#[tek_proc::commands(TrackCommand = "track")] -impl Track { - #[command(Stop = "stop")] - fn stop (&mut self) -> Perhaps { - self.sequencer.enqueue_next(None); - Ok(None) - } - #[command(SetRec = "rec")] - fn set_rec (&mut self, rec: Option) -> Perhaps { - toggle_bool(&mut self.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec }) - } - #[command(SetMon = "mon")] - fn set_mon (&mut self, mon: Option) -> Perhaps { - toggle_bool(&mut self.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon }) - } - #[command(SetMute = "mute")] - fn set_mute (&mut self, mute: Option) -> Perhaps { - todo!() - } - #[command(SetSolo = "solo")] - fn set_solo (&mut self, solo: Option) -> Perhaps { - todo!() - } - #[command(SetSize = "size")] - fn set_size (&mut self, size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "zoom")] - fn set_zoom (&mut self, zoom: usize) -> Perhaps { - todo!() - } - #[command(SetName = "name")] - fn set_name (&mut self, name: Arc) -> Perhaps { - swap_value(&mut self.name, &name, |name|TrackCommand::SetName { name }) - } - #[command(SetColor = "color")] - fn set_color (&mut self, color: ItemTheme) -> Perhaps { - swap_value(&mut self.color, &color, |color|TrackCommand::SetColor { color }) - } -} - -#[tek_proc::commands(SceneCommand = "scene")] -impl Scene { - #[command(SetSize = "size")] - fn set_size (&mut self, size: usize) -> Perhaps { - todo!() - } - #[command(SetZoom = "zoom")] - fn set_zoom (&mut self, size: usize) -> Perhaps { - todo!() - } - #[command(SetName = "name")] - fn set_name (&mut self, name: Arc) -> Perhaps { - swap_value(&mut self.name, &name, |name|SceneCommand::SetName{name}) - } - #[command(SetColor = "color")] - fn set_color (&mut self, color: ItemTheme) -> Perhaps { - swap_value(&mut self.color, &color, |color|SceneCommand::SetColor{color}) - } -} - -#[tek_proc::commands(ClipCommand = "clip")] -impl MidiClip { - #[command(SetColor = "color")] - fn set_color (&mut self, color: Option) -> Perhaps { - //(SetColor [t: usize, s: usize, c: ItemTheme] - //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - todo!() - } - #[command(SetLoop = "loop")] - fn set_loop (&mut self, looping: Option) -> Perhaps { - //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - todo!() - } -} - -/// Represents the current user selection in the arranger -#[derive(PartialEq, Clone, Copy, Debug, Default)] -pub enum Selection { - #[default] - /// Nothing is selected - Nothing, - /// The whole mix is selected - Mix, - /// A MIDI input is selected. - Input(usize), - /// A MIDI output is selected. - Output(usize), - /// A scene is selected. - #[cfg(feature = "scene")] Scene(usize), - /// A track is selected. - #[cfg(feature = "track")] Track(usize), - /// A clip (track × scene) is selected. - #[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, - /// A track's MIDI input connection is selected. - #[cfg(feature = "track")] TrackInput { track: usize, port: usize }, - /// A track's MIDI output connection is selected. - #[cfg(feature = "track")] TrackOutput { track: usize, port: usize }, - /// A track device slot is selected. - #[cfg(feature = "track")] TrackDevice { track: usize, device: usize }, -} - impl Arrangement { /// Create a new arrangement. @@ -205,9 +101,11 @@ impl Arrangement { 2 //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) } +} - /// Get the first sampler of the active track - #[cfg(feature = "sampler")] +/// Get the first sampler of the active track +#[cfg(feature = "sampler")] +impl Arrangement { pub fn sampler (&self) -> Option<&Sampler> { self.selected_track()?.sampler(0) } @@ -217,889 +115,16 @@ impl Arrangement { pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { self.selected_track_mut()?.sampler_mut(0) } - -} - -impl Selection { - pub fn describe ( - &self, - #[cfg(feature = "track")] tracks: &[Track], - #[cfg(feature = "scene")] scenes: &[Scene], - ) -> Arc { - use Selection::*; - format!("{}", match self { - Mix => "Everything".to_string(), - #[cfg(feature = "scene")] Scene(s) => - scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()), - #[cfg(feature = "track")] Track(t) => - tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), - TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { - (Some(_), Some(s)) => match s.clip(*track) { - Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), - None => format!("T{track} S{scene}: Empty") - }, - _ => format!("T{track} S{scene}: Empty"), - }, - _ => todo!() - }).into() - } - #[cfg(feature = "scene")] pub fn scene (&self) -> Option { - use Selection::*; - match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } - } - #[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self { - use Selection::*; - match self { - Mix | Track(_) => Scene(0), - Scene(s) => Scene((s + 1) % scene_count), - TrackClip { scene, .. } => Track(*scene), - _ => todo!(), - } - } - #[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self { - use Selection::*; - match self { - Mix => Scene(0), - Track(t) => TrackClip { track: *t, scene: 0 }, - Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix }, - TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) }, - _ => todo!() - } - } - #[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self { - use Selection::*; - match self { - Mix | Scene(0) => Mix, - Scene(s) => Scene(s - 1), - Track(t) => Track(*t), - TrackClip { track, scene: 0 } => Track(*track), - TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, - _ => todo!() - } - } - #[cfg(feature = "track")] pub fn track (&self) -> Option { - use Selection::*; - if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { - Some(*track) - } else { - None - } - } - #[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self { - use Selection::*; - match self { - Mix => Track(0), - Scene(_) => Mix, - Track(t) => Track((t + 1) % track_count), - TrackClip { track, .. } => Track(*track), - _ => todo!(), - } - } - #[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self { - use Selection::*; - match self { - Mix => Track(0), - Scene(s) => TrackClip { track: 0, scene: *s }, - Track(t) => if t + 1 < len { Track(t + 1) } else { Mix }, - TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) }, - _ => todo!() - } - } - #[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self { - use Selection::*; - match self { - Mix => Mix, - Scene(s) => Scene(*s), - Track(0) => Mix, - Track(t) => Track(t - 1), - TrackClip { track: 0, scene } => Scene(*scene), - TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, - _ => todo!() - } - } -} - -impl +AsMut> HasSelection for T {} - -pub trait HasSelection: AsRef + AsMut { - fn selection (&self) -> &Selection { self.as_ref() } - fn selection_mut (&mut self) -> &mut Selection { self.as_mut() } - /// Get the active track - #[cfg(feature = "track")] - fn selected_track (&self) -> Option<&Track> where Self: HasTracks { - let index = self.selection().track()?; - self.tracks().get(index) - } - /// Get a mutable reference to the active track - #[cfg(feature = "track")] - fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks { - let index = self.selection().track()?; - self.tracks_mut().get_mut(index) - } - /// Get the active scene - #[cfg(feature = "scene")] - fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes { - let index = self.selection().scene()?; - self.scenes().get(index) - } - /// Get a mutable reference to the active scene - #[cfg(feature = "scene")] - fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes { - let index = self.selection().scene()?; - self.scenes_mut().get_mut(index) - } - /// Get the active clip - #[cfg(feature = "clip")] - fn selected_clip (&self) -> Option>> where Self: HasScenes + HasTracks { - self.selected_scene()?.clips.get(self.selection().track()?)?.clone() - } } impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } } impl_has!(Jack<'static>: |self: Arrangement| self.jack); impl_has!(Sizer: |self: Arrangement| self.size); -impl_has!(Vec: |self: Arrangement| self.midi_ins); -impl_has!(Vec: |self: Arrangement| self.midi_outs); impl_has!(Clock: |self: Arrangement| self.clock); impl_has!(Selection: |self: Arrangement| self.selection); impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); -pub trait HasClipsSize { - fn clips_size (&self) -> &Sizer; -} -pub trait ClipsView: TracksView + ScenesView { - /// Draw clips per scene - fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { - view_scenes_clips( - ||self.scenes_with_sizes(), - self.tracks_with_sizes(), - self.selection(), - self.editor(), - self.clips_size(), - self.is_editing(), - ) - } -} - -impl HasClipsSize for App { - fn clips_size (&self) -> &Sizer { &self.project.size_inner } -} - -impl HasClipsSize for Arrangement { - fn clips_size (&self) -> &Sizer { &self.size_inner } -} - -/// TODO: Preserve the generic passthru syntax; -/// remove this macro (only used twice) and potentially the trait. -#[macro_export] macro_rules! impl_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() - } - } - } -} -#[macro_export] macro_rules! has_clip { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { - fn clip (&$self) -> Option>> { $cb } - } - } -} - -impl Arrangement { - /// 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.selected_clip() { - clip.write().unwrap().toggle_loop() - } - } -} - -/// Default scene height. -pub const H_SCENE: usize = 2; - -/// Default editor height. -pub const H_EDITOR: usize = 15; - -/// A scene consists of a set of clips to play together. -/// -/// ``` -/// let scene: tek::Scene = Default::default(); -/// let _ = scene.pulses(); -/// let _ = scene.is_playing(&[]); -/// ``` -#[derive(Debug, Default)] pub struct Scene { - /// Name of scene - pub name: Arc, - /// Identifying color of scene - pub color: ItemTheme, - /// Clips in scene, one per track - pub clips: Vec>>>, -} - -impl Scene { - /// Returns the pulse length of the longest clip in the scene - pub fn pulses (&self) -> usize { - self.clips.iter().fold(0, |a, p|{ - a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) - }) - } - /// Returns true if all clips in the scene are - /// currently playing on the given collection of tracks. - pub fn is_playing (&self, tracks: &[Track]) -> bool { - self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() - .all(|(track_index, clip)|match clip { - Some(c) => tracks - .get(track_index) - .map(|track|{ - if let Some((_, Some(clip))) = track.sequencer().play_clip() { - *clip.read().unwrap() == *c.read().unwrap() - } else { - false - } - }) - .unwrap_or(false), - None => true - }) - } - pub fn clip (&self, index: usize) -> Option<&Arc>> { - match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } - } - fn _todo_opt_bool_stub_ (&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!() } -} - -pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { - fn h_scenes (&self) -> u16; - fn w_side (&self) -> u16; - fn w_mid (&self) -> u16; - - fn view_scenes_names (&self) -> impl Draw { - let select = self.selection(); - let editor = self.editor(); - let editing = self.is_editing(); - draw(move |to: &mut Tui|{ - for (index, scene, ..) in self.scenes_with_sizes() { - view_scene_name(select, editor, index, scene, editing).draw(to)?; - } - Ok(Some(XYWH(1, 1, 1, 1))) - }) - .exact_w(20) - } - - fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { - let mut y = 0; - self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ - let height = if self.selection().scene() == Some(s) && self.editor().is_some() { - 8 - } else { - H_SCENE - }; - if y + height <= self.clips_size().h() as usize { - let data = (s, scene, y, y + height); - y += height; - Some(data) - } else { - None - } - }) - } -} - -pub trait HasSceneScroll: HasScenes { - fn scene_scroll (&self) -> usize; -} - -pub trait HasScene: AsRefOpt + AsMutOpt { - fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() } - fn scene (&self) -> Option<&Scene> { self.as_ref_opt() } -} - -pub trait HasScenes: AsRef> + AsMut> { - fn scenes (&self) -> &Vec { self.as_ref() } - fn scenes_mut (&mut self) -> &mut Vec { self.as_mut() } - /// Generate the default name for a new scene - fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } - fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } - /// Add multiple scenes - fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks { - let scene_color_1 = ItemColor::random(); - let scene_color_2 = ItemColor::random(); - for i in 0..n { - let _ = self.scene_add(None, Some( - scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() - ))?; - } - Ok(()) - } - /// Add a scene - fn scene_add (&mut self, name: Option<&str>, color: Option) - -> Usually<(usize, &mut Scene)> where Self: HasTracks - { - let scene = Scene { - name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(ItemTheme::random), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok((index, &mut self.scenes_mut()[index])) - } -} - -impl HasSceneScroll for Arrangement { - fn scene_scroll (&self) -> usize { self.scene_scroll } -} - -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { self.project.scene_scroll() } -} - -impl ScenesView for Arrangement { - fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } - fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } - fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) } -} - -pub type SceneWith<'a, T> = - (usize, &'a Scene, usize, usize, T); - - -#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); -#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); -#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); -#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); -impl_has!(Vec: |self: Arrangement| self.scenes); -impl ClipsView for T {} -impl>+AsMut>> HasScenes for T {} -impl+AsMutOpt+Send+Sync> HasScene for T {} - -impl ScenesView for App { - fn w_mid (&self) -> u16 { - (self.size.w() as u16).saturating_sub(self.w_side()) - } - fn w_side (&self) -> u16 { - 20 - } - fn h_scenes (&self) -> u16 { - (self.size.h() as u16).saturating_sub(20) - } -} - -/// A track consists of a sequencer and zero or more devices chained after it. -/// -/// ``` -/// let track: tek::Track = Default::default(); -/// ``` -#[derive(Debug, Default)] pub struct Track { - /// Name of track - pub name: Arc, - /// Identifying color of track - pub color: ItemTheme, - /// Preferred width of track column - pub width: usize, - /// MIDI sequencer state - pub sequencer: Sequencer, - /// Device chain - pub devices: Vec, -} - -impl Track { - /// Create a new track with only the default [Sequencer]. - pub fn new ( - name: &impl AsRef, - color: Option, - jack: &Jack<'static>, - clock: Option<&Clock>, - clip: Option<&Arc>>, - midi_from: &[Connect], - midi_to: &[Connect], - ) -> Usually { - Ok(Self { - name: name.as_ref().into(), - color: color.unwrap_or_default(), - sequencer: Sequencer::new( - format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to - )?, - ..Default::default() - }) - } - pub fn audio_ins (&self) -> &[AudioInput] { - self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() - } - pub fn audio_outs (&self) -> &[AudioOutput] { - self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() - } - fn _todo_opt_bool_stub_ (&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!() } - - pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - tracks: impl Fn() -> U + Send + Sync + 'a, - callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a) - ) -> impl Draw + 'a { - iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{ - fg_bg( - track.color.lightest.term, - track.color.base.term, - callback(index, track) - ).exact_w((x2 - x1) as u16) - })) - } -} - -#[cfg(feature = "sampler")] -impl Track { - - /// Create a new track connecting the [Sequencer] to a [Sampler]. - pub fn new_with_sampler ( - name: &impl AsRef, - color: Option, - jack: &Jack<'static>, - clock: Option<&Clock>, - clip: Option<&Arc>>, - midi_from: &[Connect], - midi_to: &[Connect], - audio_from: &[&[Connect];2], - audio_to: &[&[Connect];2], - ) -> Usually { - let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; - let client_name = jack.with_client(|c|c.name().to_string()); - let port_name = track.sequencer.midi_outs[0].port_name(); - let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; - track.devices.push(Device::Sampler(Sampler::new( - jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to - )?)); - Ok(track) - } - - pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { - for device in self.devices.iter() { - match device { - Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, - _ => {} - } - } - None - } - - pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { - for device in self.devices.iter_mut() { - match device { - Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, - _ => {} - } - } - None - } -} - -impl HasWidth for Track { - const MIN_WIDTH: usize = 9; - fn width_inc (&mut self) { self.width += 1; } - fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } -} - -pub trait HasTracks: AsRef> + AsMut> { - fn tracks (&self) -> &Vec { self.as_ref() } - fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } - /// Run audio callbacks for every track and every device - fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.tracks_mut().iter_mut() { - if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { - return Control::Quit - } - for device in track.devices.iter_mut() { - if Control::Quit == DeviceAudio(device).process(client, scope) { - return Control::Quit - } - } - } - Control::Continue - } - fn track_longest_name (&self) -> usize { - self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) - } - /// Stop all playing clips - fn tracks_stop_all (&mut self) { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - } - /// Stop all playing clips - fn tracks_launch (&mut self, clips: Option>>>>) { - if let Some(clips) = clips { - for (clip, track) in clips.iter().zip(self.tracks_mut()) { - track.sequencer.enqueue_next(clip.as_ref()); - } - } else { - for track in self.tracks_mut().iter_mut() { - track.sequencer.enqueue_next(None); - } - } - } - /// Spacing between tracks. - const TRACK_SPACING: usize = 0; -} - -pub trait HasTrack: AsRefOpt + AsMutOpt { - fn track (&self) -> Option<&Track> { self.as_ref_opt() } - fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } - - #[cfg(feature = "port")] - fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + 'a { - view_midi_ins_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw + '_ { - view_midi_outs_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { - view_audio_ins_status(theme, self.track()) - } - - #[cfg(feature = "port")] - fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { - view_audio_outs_status(theme, self.track()) - } -} - -pub trait HasTrackScroll: HasTracks { - fn track_scroll (&self) -> usize; -} - -pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { - /// Draw name of each track - fn view_track_names (&self, theme: ItemTheme) -> impl Draw { - let track_count = self.tracks().len(); - let scene_count = self.scenes().len(); - let selected = self.selection(); - let button = south( - button_3("t", "rack ", format!("{}{track_count}", selected.track() - .map(|track|format!("{track}/")).unwrap_or_default()), false), - button_3("s", "cene ", format!("{}{scene_count}", selected.scene() - .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); - let button_2 = south( - button_2("T", "+", false), - button_2("S", "+", false)); - view_track_row_section(theme, button, button_2, bg(theme.darker.term, - draw(|to: &mut Tui|{ - for (index, track, x1, _x2) in self.tracks_with_sizes() { - let b = if selected.track() == Some(index) { - track.color.light.term - } else { - track.color.base.term - }; - bg(b, south(east( - format!("·t{index:02} "), - fg(Rgb(255, 255, 255), bold(true, &track.name)) - ).align_nw().full_w(), "")) - .exact_w(track_width(index, track)) - .push_x(x1 as u16) - .draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - }).exact_h(2))) - } - /// Draw outputs per track - fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { - view_track_row_section(theme, - south(button_2("o", "utput", false).align_w().full_w(), - draw(|to: &mut Tui|{ - for port in self.midi_outs().iter() { - let _ = port.port_name().align_w().full_w().draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - })), - button_2("O", "+", false), - bg(theme.darker.term, draw(|to: &mut Tui|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - let f = Rgb(255, 255, 255); - let b = track.color.dark.term; - iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{ - fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1)) - })) - .full_h().align_nw().exact_w(track_width(index, track)).draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - }).align_w())) - } - /// Draw inputs per track - fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { - let mut height = 0u16; - for track in self.tracks().iter() { - height = height.max(track.sequencer.midi_ins.len() as u16); - } - view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), - bg(theme.darker.term, draw(move|to: &mut Tui|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - south( - bg(track.color.base.term, - east!( - either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), - either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), - either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), - ).align_w().full_w()), - iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term, - format!("·i{index:02} {}", port.port_name()).align_w().full_w()))) - ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; - } - Ok(Some(XYWH(0, 0, 0, 0))) - }).align_w())) - } - /// Iterate over tracks with their corresponding sizes. - fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { - let _editor_width = self.editor().map(|e|e.size.w()); - let _active_track = self.selection().track(); - let mut x = 0; - let w = self.clips_size().w() as usize; - self.tracks().iter().enumerate().map_while(move |(index, track)|{ - let width = track.width.max(8); - if x + width < w { - let data = (index, track, x, x + width); - x += width + Self::TRACK_SPACING; - Some(data) - } else { - None - } - }) - } - -} - -impl HasTrackScroll for Arrangement { - fn track_scroll (&self) -> usize { self.track_scroll } -} - -impl>+AsMut>> HasTracks for T {} -impl+AsMutOpt+Send+Sync> HasTrack for T {} -impl TracksView for T {} -impl_has!(Vec: |self: Arrangement| self.tracks); -impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); -impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); -impl_as_ref!(Vec: |self: App| self.project.as_ref()); -impl_as_mut!(Vec: |self: App| self.project.as_mut()); -#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); -#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); - -impl Arrangement { - - pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { - let title_1 = button_3("i", "nput ", format!("{}", self.midi_ins().len()), false).align_w().exact_wh(20, 1); - let title_2 = button_2("I", "+", false).exact_wh(4, 1); - east(title_1, west(title_2, draw(move|to: &mut Tui|{ - for (_index, track, x1, _x2) in self.tracks_with_sizes() { - let _ = south( - bg(track.color.dark.term, east!( - either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), - either(track.sequencer.recording, fg(Red, "rec "), "rec "), - either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), - ).exact_w(track.width as u16)).align_w().push_x(x1 as u16), - draw(move |to: &mut Tui|{ - for (index, port) in self.midi_ins().as_slice().iter().enumerate() { - let _ = east( - east( - " ● ", - bold(true, fg(Rgb(255,255,255), port.port_name())) - ).align_w().exact_w(20), - west( - ().exact_w(4), - bg(track.color.darker.term, east!( - either(track.sequencer.monitoring, fg(Green, " ● "), " · "), - either(track.sequencer.recording, fg(Red, " ● "), " · "), - either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), - ).exact_w(track.width as u16).align_w()) - ) - ).push_x(index as u16 * 10).exact_h(1).draw(to)?; - } - todo!() - }) - ).draw(to)?; - } - Ok(Some(to.area())) - }))) - } - - pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { - let height = self.outputs_height(); - let list = south( - button_3( - "o", "utput", format!("{}", self.midi_outs().len()), false - ).align_w().full_w().exact_h(1), - draw(|to: &mut Tui|{ - for (_index, port) in self.midi_outs().iter().enumerate() { - east( - east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name()))).align_w(), - format!("{}/{} ", - port.port().get_connections().len(), - port.connections.len()).align_e().full_w().exact_h(1)).full_w().draw(to)?; - for (index, conn) in port.connections.iter().enumerate() { - format!(" c{index:02}{}", conn.info()).align_w().full_w().exact_h(1).draw(to)?; - } - } - todo!(); - }).align_nw().full_wh().exact_h(height - 1) - ); - view_track_row_section(theme, list, button_2("O", "+", false), - bg(theme.darker.term, draw(|to: &mut Tui|{ - for (index, track, _x1, _x2) in self.tracks_with_sizes() { - let _ = draw(|to: &mut Tui|{ - east( - either(true, fg(Green, "play "), "play "), - either(false, fg(Yellow, "solo "), "solo "), - ).align_w().exact_h(1).draw(to)?; - for (_index, port) in self.midi_outs().iter().enumerate() { - east( - either(true, fg(Green, " ● "), " · "), - either(false, fg(Yellow, " ● "), " · "), - ).align_w().exact_h(1).draw(to)?; - for (_index, _conn) in port.connections.iter().enumerate() { - "".full_w().exact_h(1).draw(to)?; - } - } - todo!() - }).exact_w(track_width(index, track)).draw(to)?; - } - todo!() - }).align_w().full_w())).exact_h(height) - } - - pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { - let height = self.devices_height(); - let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false); - let btn2 = button_2("D", "+", false); - view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes() - .enumerate() - .map(move|(index, (_, track, _x1, _x2))|bg( - track.color.dark.term, - iter_south(move||(0..height).map(|_|fg_bg( - ItemTheme::G[32].lightest.term, - ItemTheme::G[32].dark.term, - format!(" · {}", "--").align_nw() - ).exact_wh(track.width as u16, 2))) - .align_nw() - ) - .exact_wh( - Some(track_width(index, track)), - Some(height + 1), - )))) - } - - fn devices_height (&self) -> u16 { - let mut h = 2; - for track in self.tracks().iter() { - h = h.max(track.devices.len() * 2); - } - h as u16 - } - - fn outputs_height (&self) -> u16 { - let mut h = 1; - for output in self.midi_outs().iter() { - h += 1 + output.connections.len(); - } - h as u16 - } - - /// Add multiple tracks - pub fn tracks_add ( - &mut self, count: usize, width: Option, mins: &[Connect], mouts: &[Connect], - ) -> Usually<()> { - let track_color_1 = ItemColor::random(); - let track_color_2 = ItemColor::random(); - for i in 0..count { - let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); - let track = self.track_add(None, Some(color), mins, mouts)?.1; - if let Some(width) = width { - track.width = width; - } - } - Ok(()) - } - - /// Add a track - pub fn track_add ( - &mut self, - name: Option<&str>, - color: Option, - mins: &[Connect], - mouts: &[Connect], - ) -> Usually<(usize, &mut Track)> { - let name: Arc = name.map_or_else( - ||format!("trk{:02}", self.track_last).into(), - |x|x.to_string().into() - ); - self.track_last += 1; - let track = Track { - width: (name.len() + 2).max(12), - color: color.unwrap_or_else(ItemTheme::random), - sequencer: Sequencer::new( - &format!("{name}"), - self.jack(), - Some(self.clock()), - None, - mins, - mouts - )?, - name, - ..Default::default() - }; - self.tracks_mut().push(track); - let len = self.tracks().len(); - let index = len - 1; - for scene in self.scenes_mut().iter_mut() { - while scene.clips.len() < len { - scene.clips.push(None); - } - } - Ok((index, &mut self.tracks_mut()[index])) - } -} - -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { - self.project.track_scroll() - } -} - -pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { - track.width as u16 -} - /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { @@ -1108,11 +133,13 @@ macro_rules! def_sizes_iter { } } -def_sizes_iter!(PortsSizes => Arc, [Connect]); -def_sizes_iter!(ScenesSizes => Scene); -def_sizes_iter!(TracksSizes => Track); +mod clip; pub use self::clip::*; +mod scene; pub use self::scene::*; +mod select; pub use self::select::*; +mod port; pub use self::port::*; +mod track; pub use self::track::*; -fn swap_value ( +pub(self) fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { if *target == *value { @@ -1124,7 +151,7 @@ fn swap_value ( } } -fn toggle_bool ( +pub(self) fn toggle_bool ( target: &mut bool, value: &Option, returned: impl Fn(Option)->U ) -> Perhaps { let mut value = value.unwrap_or(!*target); @@ -1135,3 +162,24 @@ fn toggle_bool ( Ok(Some(returned(Some(value)))) } } + + //pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + //tracks: impl Fn() -> U + Send + Sync + 'a, + //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //) -> impl Draw + 'a { + //per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) + //} + + //pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + //tracks: impl Fn() -> U + Send + Sync + 'a, + //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a + //) -> impl Draw + 'a { + //bg(Reset, iter_east(||tracks() + //.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{ + //fg_bg( + //track.color.lightest.term, + //track.color.base.term, + //callback(index, track) + //).exact_w((x2 - x1) as u16) + //})).align_x()) + //} diff --git a/src/device/arrange/clip.rs b/src/device/arrange/clip.rs new file mode 100644 index 00000000..207ad15d --- /dev/null +++ b/src/device/arrange/clip.rs @@ -0,0 +1,165 @@ +use crate::*; + +/// TODO: Preserve the generic passthru syntax; +/// remove this macro (only used twice) and potentially the trait. +#[macro_export] macro_rules! impl_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() + } + } + } +} + +#[macro_export] macro_rules! has_clip { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { + fn clip (&$self) -> Option>> { $cb } + } + } +} + +impl Arrangement { + /// Toggle looping for the active clip + pub fn toggle_loop (&mut self) { + if let Some(clip) = self.selected_clip() { + clip.write().unwrap().toggle_loop() + } + } + + /// 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 + }) + } +} + +impl ClipsView for T {} + +pub trait HasClipsSize { + fn clips_size (&self) -> &Sizer; +} + +impl HasClipsSize for App { + fn clips_size (&self) -> &Sizer { &self.project.size_inner } +} + +impl HasClipsSize for Arrangement { + fn clips_size (&self) -> &Sizer { &self.size_inner } +} + +pub trait ClipsView: TracksView + ScenesView { + /// Draw clips per scene + fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { + view_scenes_clips( + ||self.scenes_with_sizes(), + self.tracks_with_sizes(), + self.selection(), + self.editor(), + self.clips_size(), + self.is_editing(), + ) + } +} + +pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> ( + scenes: impl Fn()->S, + tracks: impl TracksSizes<'a>, + select: &Selection, + editor: Option<&MidiEditor>, + size: &Sizer, + editing: bool, +) -> impl Draw { + let status = fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(); + let tracks = iter_once(tracks, move|(track_index, track, _, _), _| { + let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| { + let (name, theme): (Arc, ItemTheme) = scene_name_theme(scene, track_index); + let f = theme.lightest.term; + let (b, o) = scene_bg(theme, select, track_index, scene_index); + let w = scene_w(track, select, track_index, editor); + let y = scene_y(select, scene_index, editor); + let is_selected = scene_sel(select, track_index, scene_index, editing); + below( + Outer(true, Style::default().fg(o)).full_wh(), + below( + below( + fg_bg(o, b, "".full_wh()), + fg_bg(f, b, bold(true, name)).align_nw().full_wh(), + ), + when(is_selected, editor.map(|e|e.view())).full_wh() + ).full_wh() + ).exact_wh(w, y) + }); + scenes.full_h().exact_w(track.width as u16) + }); + + return size.of(above(status, tracks).full_wh()); + + fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { + if let Some(Some(clip)) = &scene.clips.get(track_index) { + let clip = clip.read().unwrap(); + (format!(" ⏹ {}", &clip.name).into(), clip.color) + } else { + (" ⏹ -- ".into(), ItemTheme::G[32]) + } + } + + fn scene_bg ( + theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize + ) -> (Color, Color) { + let mut outline = theme.base.term; + (if select.track() == Some(track_index) && select.scene() == Some(scene_index) { + outline = theme.lighter.term; + theme.light.term + } else if select.track() == Some(track_index) || select.scene() == Some(scene_index) { + outline = theme.darkest.term; + theme.base.term + } else { + theme.dark.term + }, outline) + } + + fn scene_w ( + track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor> + ) -> u16 { + if select.track() == Some(track_index) && let Some(editor) = editor { + (editor.size.w() as usize).max(24).max(track.width) as u16 + } else { + track.width as u16 + } + } + + fn scene_y ( + select: &Selection, scene_index: usize, editor: Option<&MidiEditor> + ) -> u16 { + if select.scene() == Some(scene_index) && let Some(editor) = editor { + editor.size.h().max(12) + } else { + Scene::DEFAULT_HEIGHT as u16 + } + } + + fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool { + editing && select.track() == Some(track_index) && select.scene() == Some(scene_index) + } +} diff --git a/src/device/arrange/port.rs b/src/device/arrange/port.rs new file mode 100644 index 00000000..7f63b5f6 --- /dev/null +++ b/src/device/arrange/port.rs @@ -0,0 +1,87 @@ +use crate::*; +def_sizes_iter!(PortsSizes => Arc, [Connect]); +impl_has!(Vec: |self: Arrangement| self.midi_ins); +impl_has!(Vec: |self: Arrangement| self.midi_outs); +impl_has!(Vec: |self: App|self.project.midi_ins); +impl_has!(Vec: |self: App|self.project.midi_outs); + +pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) +} + +pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) +} + +pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) +} + +pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { + track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) +} + +pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) + -> impl Draw + use<'a, T> +{ + let ins = ports.len() as u16; + let frame = Outer(true, Style::default().fg(g(96))); + let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!( + " {index} {}", port.port_name() + ).align_w().full_h())); + let field = field_v(theme, title, names); + border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) +} + +pub fn view_io_ports <'a, T: PortsSizes<'a>> ( + fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a +) -> impl Draw + 'a { + type Item<'a> = (usize, &'a Arc, &'a [Connect], usize, usize); + iter(items, + move|(_index, name, connections, y, y2): Item<'a>, _| south( + bold(true, fg_bg(fg, bg, east(" 󰣲 ", name).align_w())).full_h(), + iter(||connections.iter(), move|connect: &'a Connect, index|{ + bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16) + }) + ).exact_h((y2 - y) as u16).push_y(y as u16)) +} + +pub struct Junction(T); + +impl View for Junction { + fn view (&self) -> impl Draw { + T::KIND + } +} + +#[tek_proc::command(AudioInput)] +#[tek_proc::keyword(AudioInput)] +#[derive(Debug)] +pub enum AudioInputCommand { + Close, + Connect(Arc), +} + +#[tek_proc::command(AudioOutput)] +#[tek_proc::keyword(AudioOutput)] +#[derive(Debug)] +pub enum AudioOutputCommand { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiInput)] +#[tek_proc::keyword(MidiInput)] +#[derive(Debug)] +pub enum MidiInputCommand { + Close, + Connect(Arc), +} + +#[tek_proc::command(MidiOutput)] +#[tek_proc::keyword(MidiOutput)] +#[derive(Debug)] +pub enum MidiOutputCommand { + Close, + Connect(Arc), +} diff --git a/src/device/arrange/scene.rs b/src/device/arrange/scene.rs new file mode 100644 index 00000000..eae2e854 --- /dev/null +++ b/src/device/arrange/scene.rs @@ -0,0 +1,289 @@ +use crate::*; +use super::*; + +/// A scene consists of a set of clips to play together. +/// +/// ``` +/// let scene: tek::Scene = Default::default(); +/// let _ = scene.pulses(); +/// let _ = scene.is_playing(&[]); +/// ``` +#[derive(Debug, Default)] pub struct Scene { + /// Name of scene + pub name: Arc, + /// Identifying color of scene + pub color: ItemTheme, + /// Clips in scene, one per track + pub clips: Vec>>>, +} + +impl Scene { + pub const DEFAULT_HEIGHT: usize = 2; + + /// Get currently playing clip, if any + pub fn clip (&self, index: usize) -> Option<&Arc>> { + if let Some(Some(clip)) = self.clips.get(index) { + Some(clip) + } else { + None + } + } + + /// Get pulse length of the longest clip in the scene + pub fn pulses (&self) -> usize { + self.clips.iter().fold(0, |a, p|{ + a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) + }) + } + + /// True if all clips in scene are currently playing on given tracks. + pub fn is_playing (&self, tracks: &[Track]) -> bool { + self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() + .all(|(track_index, clip)|match clip { + Some(c) => tracks + .get(track_index) + .map(|track|{ + if let Some((_, Some(clip))) = track.sequencer().play_clip() { + *clip.read().unwrap() == *c.read().unwrap() + } else { + false + } + }) + .unwrap_or(false), + None => true + }) + } + +} + +#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); +#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); +#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); +#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); + +impl + AsMutOpt + Send + Sync> HasScene for T {} + +pub trait HasScene: AsRefOpt + AsMutOpt { + fn scene (&self) -> Option<&Scene> { + self.as_ref_opt() + } + fn scene_mut (&mut self) -> Option<&mut Scene> { + self.as_mut_opt() + } +} + +impl Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, ItemTheme> +> SceneController for T {} + +#[tek_proc::commands(SceneCommand)] +pub trait SceneController: HasScene + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, ItemTheme> +{ + #[command(SetSize = "scene/size")] + fn scene_set_size (&mut self, size: usize) -> Perhaps + where Self: for<'a> Namespace<'a, usize> + { + todo!() + } + #[command(SetZoom = "scene/zoom")] + fn scene_set_zoom (&mut self, size: usize) -> Perhaps + where Self: for<'a> Namespace<'a, usize> + { + todo!() + } + #[command(SetName = "scene/name")] + fn scene_set_name (&mut self, name: Arc) -> Perhaps + where Self: for<'a> Namespace<'a, Arc> + { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.name, + &name, + |name|SceneCommand::SetName { name } + )).transpose()?.flatten()) + } + #[command(SetColor = "scene/color")] + fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps + where Self: for<'a> Namespace<'a, ItemTheme> + { + Ok(self.scene_mut().map(|scene|swap_value( + &mut scene.color, + &color, + |color|SceneCommand::SetColor { color } + )).transpose()?.flatten()) + } +} + +pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); + +def_sizes_iter!(ScenesSizes => Scene); +impl_has!(Vec: |self: Arrangement| self.scenes); +impl_as_ref!(Vec: |self: App| self.project.as_ref()); +impl_as_mut!(Vec: |self: App| self.project.as_mut()); + +impl> + AsMut>> HasScenes for T {} + +pub trait HasScenes: AsRef> + AsMut> { + fn scenes (&self) -> &Vec { + self.as_ref() + } + fn scenes_mut (&mut self) -> &mut Vec { + self.as_mut() + } + /// Generate the default name for a new scene + fn scenes_default_name (&self) -> Arc { + format!("s{:3>}", self.scenes().len() + 1).into() + } + fn scenes_longest_name (&self) -> usize { + self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) + } + /// Add multiple scenes + fn scenes_add_many (&mut self, n: usize) + -> Usually<()> where Self: HasTracks + { + let scene_color_1 = ItemColor::random(); + let scene_color_2 = ItemColor::random(); + for i in 0..n { + let _ = self.scenes_add_one(None, Some( + scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() + ))?; + } + Ok(()) + } + /// Add a scene + fn scenes_add_one (&mut self, name: Option>, color: Option) + -> Usually<(usize, &mut Scene)> + where + Self: HasTracks + { + let scene = Scene { + name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(ItemTheme::random), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok((index, &mut self.scenes_mut()[index])) + } +} + +impl Namespace<'a, usize> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> +> ScenesController for T {} + +#[tek_proc::commands(ScenesCommand)] +pub trait ScenesController: HasScenes + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> +{ + // TODO +} + +pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { + fn h_scenes (&self) -> u16; + fn w_side (&self) -> u16; + fn w_mid (&self) -> u16; + + fn view_scenes_names (&self) -> impl Draw { + let select = self.selection(); + let editor = self.editor(); + let editing = self.is_editing(); + draw(move |to: &mut Tui|{ + for (index, scene, ..) in self.scenes_with_sizes() { + view_scene_name(select, editor, index, scene, editing).draw(to)?; + } + Ok(Some(XYWH(1, 1, 1, 1))) + }) + .exact_w(20) + } + + fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { + let mut y = 0; + self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ + let height = if self.selection().scene() == Some(s) && self.editor().is_some() { + 8 + } else { + Scene::DEFAULT_HEIGHT + }; + if y + height <= self.clips_size().h() as usize { + let data = (s, scene, y, y + height); + y += height; + Some(data) + } else { + None + } + }) + } +} + +impl ScenesView for App { + fn w_mid (&self) -> u16 { + (self.size.w() as u16).saturating_sub(self.w_side()) + } + fn w_side (&self) -> u16 { + 20 + } + fn h_scenes (&self) -> u16 { + (self.size.h() as u16).saturating_sub(20) + } +} + +impl ScenesView for Arrangement { + fn h_scenes (&self) -> u16 { + (self.size.h() as u16).saturating_sub(20) + } + fn w_side (&self) -> u16 { + (self.size.w() as u16 * 2 / 10).max(20) + } + fn w_mid (&self) -> u16 { + (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) + } +} + +pub trait HasSceneScroll: HasScenes { + fn scene_scroll (&self) -> usize; +} + +impl HasSceneScroll for Arrangement { + fn scene_scroll (&self) -> usize { + self.scene_scroll + } +} + +impl HasSceneScroll for App { + fn scene_scroll (&self) -> usize { + self.project.scene_scroll() + } +} + + pub fn view_scene_name ( + select: &Selection, + editor: Option<&MidiEditor>, + index: usize, + scene: &Scene, + editing: bool + ) -> impl Draw { + let h = if select.scene() == Some(index) && let Some(_editor) = editor { + 7 + } else { + Scene::DEFAULT_HEIGHT as u16 + }; + let a = east(format!("·s{index:02} "), + fg(g(255), bold(true, &scene.name))).align_w().full_w(); + let b = when(select.scene() == Some(index) && editing, south( + editor.as_ref().map(|e|e.clip_status()), + editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); + let c = if select.scene() == Some(index) { + scene.color.light.term + } else { + scene.color.base.term + }; + bg(c, south(a, b).align_nw()).exact_wh(20, h) + } diff --git a/src/device/arrange/select.rs b/src/device/arrange/select.rs new file mode 100644 index 00000000..af7a45a0 --- /dev/null +++ b/src/device/arrange/select.rs @@ -0,0 +1,195 @@ +use crate::*; + +impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); +impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); +impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); +impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); +impl +AsMut> HasSelection for T {} + +pub trait HasSelection: AsRef + AsMut { + fn selection (&self) -> &Selection { + self.as_ref() + } + fn selection_mut (&mut self) -> &mut Selection { + self.as_mut() + } + + /// Get the active track + #[cfg(feature = "track")] + fn selected_track (&self) -> Option<&Track> where Self: HasTracks { + let index = self.selection().track()?; + self.tracks().get(index) + } + + /// Get a mutable reference to the active track + #[cfg(feature = "track")] + fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks { + let index = self.selection().track()?; + self.tracks_mut().get_mut(index) + } + + /// Get the active scene + #[cfg(feature = "scene")] + fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes { + let index = self.selection().scene()?; + self.scenes().get(index) + } + + /// Get a mutable reference to the active scene + #[cfg(feature = "scene")] + fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes { + let index = self.selection().scene()?; + self.scenes_mut().get_mut(index) + } + + /// Get the active clip + #[cfg(feature = "clip")] + fn selected_clip (&self) -> Option>> where Self: HasScenes + HasTracks { + self.selected_scene()?.clips.get(self.selection().track()?)?.clone() + } +} + +/// Represents the current user selection in the arranger +#[derive(PartialEq, Clone, Copy, Debug, Default)] +pub enum Selection { + #[default] + /// Nothing is selected + Nothing, + /// The whole mix is selected + Mix, + /// A MIDI input is selected. + Input(usize), + /// A MIDI output is selected. + Output(usize), + /// A scene is selected. + #[cfg(feature = "scene")] Scene(usize), + /// A track is selected. + #[cfg(feature = "track")] Track(usize), + /// A clip (track × scene) is selected. + #[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, + /// A track's MIDI input connection is selected. + #[cfg(feature = "track")] TrackInput { track: usize, port: usize }, + /// A track's MIDI output connection is selected. + #[cfg(feature = "track")] TrackOutput { track: usize, port: usize }, + /// A track device slot is selected. + #[cfg(feature = "track")] TrackDevice { track: usize, device: usize }, +} + +impl Selection { + + pub fn describe ( + &self, + #[cfg(feature = "track")] tracks: &[Track], + #[cfg(feature = "scene")] scenes: &[Scene], + ) -> Arc { + use Selection::*; + format!("{}", match self { + Mix => "Everything".to_string(), + #[cfg(feature = "scene")] Scene(s) => + scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()), + #[cfg(feature = "track")] Track(t) => + tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), + TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { + (Some(_), Some(s)) => match s.clip(*track) { + Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), + None => format!("T{track} S{scene}: Empty") + }, + _ => format!("T{track} S{scene}: Empty"), + }, + _ => todo!() + }).into() + } + +} + +#[cfg(feature = "scene")] +impl Selection { + + pub fn scene (&self) -> Option { + use Selection::*; + match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } + } + + pub fn select_scene (&self, scene_count: usize) -> Self { + use Selection::*; + match self { + Mix | Track(_) => Scene(0), + Scene(s) => Scene((s + 1) % scene_count), + TrackClip { scene, .. } => Track(*scene), + _ => todo!(), + } + } + + pub fn select_scene_next (&self, len: usize) -> Self { + use Selection::*; + match self { + Mix => Scene(0), + Track(t) => TrackClip { track: *t, scene: 0 }, + Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix }, + TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) }, + _ => todo!() + } + } + + pub fn select_scene_prev (&self) -> Self { + use Selection::*; + match self { + Mix | Scene(0) => Mix, + Scene(s) => Scene(s - 1), + Track(t) => Track(*t), + TrackClip { track, scene: 0 } => Track(*track), + TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, + _ => todo!() + } + } + +} + +#[cfg(feature = "track")] +impl Selection { + + pub fn track (&self) -> Option { + use Selection::*; + if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { + Some(*track) + } else { + None + } + } + + pub fn select_track (&self, track_count: usize) -> Self { + use Selection::*; + match self { + Mix => Track(0), + Scene(_) => Mix, + Track(t) => Track((t + 1) % track_count), + TrackClip { track, .. } => Track(*track), + _ => todo!(), + } + } + + pub fn select_track_next (&self, len: usize) -> Self { + use Selection::*; + match self { + Mix => Track(0), + Scene(s) => TrackClip { track: 0, scene: *s }, + Track(t) => if t + 1 < len { Track(t + 1) } else { Mix }, + TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) }, + _ => todo!() + } + } + + pub fn select_track_prev (&self) -> Self { + use Selection::*; + match self { + Mix => Mix, + Scene(s) => Scene(*s), + Track(0) => Mix, + Track(t) => Track(t - 1), + TrackClip { track: 0, scene } => Scene(*scene), + TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, + _ => todo!() + } + } + +} diff --git a/src/device/arrange/track.rs b/src/device/arrange/track.rs new file mode 100644 index 00000000..2aa64c92 --- /dev/null +++ b/src/device/arrange/track.rs @@ -0,0 +1,588 @@ +use crate::{*, arrange::*}; + +/// A track consists of a sequencer and zero or more devices chained after it. +/// +/// ``` +/// let track: tek::Track = Default::default(); +/// ``` +#[derive(Debug, Default)] +pub struct Track { + /// Name of track + pub name: Arc, + /// Identifying color of track + pub color: ItemTheme, + /// Preferred width of track column + pub width: usize, + /// MIDI sequencer state + pub sequencer: Sequencer, + /// Device chain + pub devices: Vec, +} + +impl Track { + /// Create a new track with only the default [Sequencer]. + pub fn new ( + name: &impl AsRef, + color: Option, + jack: &Jack<'static>, + clock: Option<&Clock>, + clip: Option<&Arc>>, + midi_from: &[Connect], + midi_to: &[Connect], + ) -> Usually { + Ok(Self { + name: name.as_ref().into(), + color: color.unwrap_or_default(), + sequencer: Sequencer::new( + format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to + )?, + ..Default::default() + }) + } + pub fn audio_ins (&self) -> &[AudioInput] { + self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() + } + pub fn audio_outs (&self) -> &[AudioOutput] { + self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() + } + pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( + tracks: impl Fn() -> U + Send + Sync + 'a, + callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a) + ) -> impl Draw + 'a { + iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{ + fg_bg( + track.color.lightest.term, + track.color.base.term, + callback(index, track) + ).exact_w((x2 - x1) as u16) + })) + } + pub fn stop (&mut self) -> Perhaps { + self.sequencer.enqueue_next(None); + Ok(None) + } +} + +impl HasWidth for Track { + const MIN_WIDTH: usize = 9; + fn width_inc (&mut self) { + self.width += 1; + } + fn width_dec (&mut self) { + if self.width > Track::MIN_WIDTH { self.width -= 1; } + } +} + +impl + AsMutOpt> HasTrack for T {} + +pub trait HasTrack: AsRefOpt + AsMutOpt { + fn track (&self) -> Option<&Track> { + self.as_ref_opt() + } + fn track_mut (&mut self) -> Option<&mut Track> { + self.as_mut_opt() + } +} + +impl Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, ItemTheme> + + for<'a> Namespace<'a, Option> +> TrackController for T {} + +#[tek_proc::commands(TrackCommand)] +pub trait TrackController: HasTrack + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, ItemTheme> + + for<'a> Namespace<'a, Option> +{ + #[command(Stop = "track/stop")] + fn track_stop (&mut self) -> Perhaps { + self.track_mut().map(|track|track.sequencer.enqueue_next(None)); + Ok(None) + } + #[command(SetMute = "track/mute")] + fn track_set_mute (&mut self, mute: Option) -> Perhaps { + todo!() + } + #[command(SetSolo = "track/solo")] + fn track_set_solo (&mut self, solo: Option) -> Perhaps { + todo!() + } + #[command(SetSize = "track/size")] + fn track_set_size (&mut self, size: usize) -> Perhaps { + todo!() + } + #[command(SetZoom = "track/zoom")] + fn track_set_zoom (&mut self, zoom: usize) -> Perhaps { + todo!() + } + #[command(SetName = "track/name")] + fn track_set_name (&mut self, name: Arc) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name })) + .transpose() + .map(Option::flatten) + } + #[command(SetColor = "track/color")] + fn track_set_color (&mut self, color: ItemTheme) -> Perhaps { + self.track_mut() + .map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color })) + .transpose() + .map(Option::flatten) + } + #[command(SetRec = "track/rec")] + fn track_set_rec (&mut self, rec: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec })) + .transpose() + .map(Option::flatten) + } + #[command(SetMon = "track/mon")] + fn track_set_mon (&mut self, mon: Option) -> Perhaps { + self.track_mut() + .map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon })) + .transpose() + .map(Option::flatten) + } +} + +pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { + track.width as u16 +} + +def_sizes_iter!(TracksSizes => Track); +impl_has!(Vec: |self: Arrangement| self.tracks); +impl_as_ref!(Vec: |self: App| self.project.as_ref()); +impl_as_mut!(Vec: |self: App| self.project.as_mut()); + +impl> + AsMut> + HasClock + HasTrackScroll> HasTracks for T {} + +pub trait HasTracks: AsRef> + AsMut> + HasClock + HasTrackScroll { + /// Spacing between tracks. + const TRACK_SPACING: usize = 0; + + /// Read-only reference to collection of [Track]s. + fn tracks (&self) -> &Vec { + self.as_ref() + } + + /// Mutable reference to collection of [Track]s. + fn tracks_mut (&mut self) -> &mut Vec { + self.as_mut() + } + + /// Run audio callbacks for every track and every device + fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control + where Self: HasJack<'static> + { + for track in self.tracks_mut().iter_mut() { + if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { + return Control::Quit + } + for device in track.devices.iter_mut() { + if Control::Quit == DeviceAudio(device).process(client, scope) { + return Control::Quit + } + } + } + Control::Continue + } + + /// Add multiple tracks + fn tracks_add_many ( + &mut self, + count: usize, + width: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<()> + where Self: HasJack<'static> + HasScenes + { + let track_color_1 = ItemColor::random(); + let track_color_2 = ItemColor::random(); + for i in 0..count { + let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); + let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1; + if let Some(width) = width { + track.width = width; + } + } + Ok(()) + } + + /// Add a track + fn tracks_add_one ( + &mut self, + name: Option>, + color: Option, + mins: Arc<[Connect]>, + mouts: Arc<[Connect]>, + ) -> Usually<(usize, &mut Track)> + where Self: HasJack<'static> + HasScenes + { + let name: Arc = name.map_or_else( + ||format!("trk{:02}", self.tracks_last()).into(), + |x|x.to_string().into() + ); + *self.tracks_last_mut() += 1; + let track = Track { + width: (name.len() + 2).max(12), + color: color.unwrap_or_else(ItemTheme::random), + sequencer: Sequencer::new( + &format!("{name}"), + self.jack(), + Some(self.clock()), + None, + mins.as_ref(), + mouts.as_ref() + )?, + name, + ..Default::default() + }; + self.tracks_mut().push(track); + let len = self.tracks().len(); + let index = len - 1; + for scene in self.scenes_mut().iter_mut() { + while scene.clips.len() < len { + scene.clips.push(None); + } + } + Ok((index, &mut self.tracks_mut()[index])) + } + + fn track_longest_name (&self) -> usize { + self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) + } +} + +impl Namespace<'a, usize> + + for<'a> Namespace<'a, Arc<[Connect]>> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> + + for<'a> Namespace<'a, Option>>>>> +> TracksController for T {} + +#[tek_proc::commands(TracksCommand)] +pub trait TracksController: HasTracks + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Arc<[Connect]>> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>> + + for<'a> Namespace<'a, Option>>>>> +{ + + #[command(Stop = "tracks/stop")] + /// Stop all playing clips + fn tracks_stop_all (&mut self) -> Perhaps { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + Ok(None) + } + + #[command(Launch = "tracks/launch")] + /// Launch multiple clips + fn tracks_launch ( + &mut self, clips: Option>>>> + ) -> Perhaps { + if let Some(clips) = clips { + for (clip, track) in clips.iter().zip(self.tracks_mut()) { + track.sequencer.enqueue_next(clip.as_ref()); + } + } else { + for track in self.tracks_mut().iter_mut() { + track.sequencer.enqueue_next(None); + } + } + Ok(None) + } +} + +impl< + T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack +> TracksView for T {} + +pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack { + + /// Iterate over tracks with their corresponding sizes. + fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { + let _editor_width = self.editor().map(|e|e.size.w()); + let _active_track = self.selection().track(); + let mut x = 0; + let w = self.clips_size().w() as usize; + self.tracks().iter().enumerate().map_while(move |(index, track)|{ + let width = track.width.max(8); + if x + width < w { + let data = (index, track, x, x + width); + x += width + Self::TRACK_SPACING; + Some(data) + } else { + None + } + }) + } + + /// Draw name of each track + fn view_track_names (&self, theme: ItemTheme) -> impl Draw { + let track_count = self.tracks().len(); + let scene_count = self.scenes().len(); + let selected = self.selection(); + let button = south( + button_3("t", "rack ", format!("{}{track_count}", selected.track() + .map(|track|format!("{track}/")).unwrap_or_default()), false), + button_3("s", "cene ", format!("{}{scene_count}", selected.scene() + .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); + let button_2 = south( + button_2("T", "+", false), + button_2("S", "+", false)); + view_track_row_section(theme, button, button_2, bg(theme.darker.term, + draw(|to: &mut Tui|{ + for (index, track, x1, _x2) in self.tracks_with_sizes() { + let b = if selected.track() == Some(index) { + track.color.light.term + } else { + track.color.base.term + }; + bg(b, south(east( + format!("·t{index:02} "), + fg(Rgb(255, 255, 255), bold(true, &track.name)) + ).align_nw().full_w(), "")) + .exact_w(track_width(index, track)) + .push_x(x1 as u16) + .draw(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + }).exact_h(2))) + } + + /// Draw outputs per track + fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { + view_track_row_section(theme, + south(button_2("o", "utput", false).align_w().full_w(), + draw(|to: &mut Tui|{ + for port in self.midi_outs().iter() { + let _ = port.port_name().align_w().full_w().draw(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + })), + button_2("O", "+", false), + bg(theme.darker.term, draw(|to: &mut Tui|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + let f = Rgb(255, 255, 255); + let b = track.color.dark.term; + iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{ + fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1)) + })) + .full_h().align_nw().exact_w(track_width(index, track)).draw(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + }).align_w())) + } + + /// Draw inputs per track + fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { + let mut height = 0u16; + for track in self.tracks().iter() { + height = height.max(track.sequencer.midi_ins.len() as u16); + } + view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), + bg(theme.darker.term, draw(move|to: &mut Tui|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + south( + bg(track.color.base.term, + east!( + either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), + either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), + either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), + ).align_w().full_w()), + iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term, + format!("·i{index:02} {}", port.port_name()).align_w().full_w()))) + ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; + } + Ok(Some(XYWH(0, 0, 0, 0))) + }).align_w())) + } + + fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { + let height = self.tracks_devices_height(); + let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false); + let btn2 = button_2("D", "+", false); + view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes() + .enumerate() + .map(move|(index, (_, track, _x1, _x2))|bg( + track.color.dark.term, + iter_south(move||(0..height).map(|_|fg_bg( + ItemTheme::G[32].lightest.term, + ItemTheme::G[32].dark.term, + format!(" · {}", "--").align_nw() + ).exact_wh(track.width as u16, 2))) + .align_nw() + ) + .exact_wh( + Some(track_width(index, track)), + Some(height + 1), + )))) + } + + fn tracks_devices_height (&self) -> u16 { + let mut h = 2; + for track in self.tracks().iter() { + h = h.max(track.devices.len() * 2); + } + h as u16 + } + + fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { + let title_1 = button_3("i", "nput ", format!("{}", self.midi_ins().len()), false).align_w().exact_wh(20, 1); + let title_2 = button_2("I", "+", false).exact_wh(4, 1); + east(title_1, west(title_2, draw(move|to: &mut Tui|{ + for (_index, track, x1, _x2) in self.tracks_with_sizes() { + let _ = south( + bg(track.color.dark.term, east!( + either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), + either(track.sequencer.recording, fg(Red, "rec "), "rec "), + either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), + ).exact_w(track.width as u16)).align_w().push_x(x1 as u16), + draw(move |to: &mut Tui|{ + for (index, port) in self.midi_ins().as_slice().iter().enumerate() { + let _ = east( + east( + " ● ", + bold(true, fg(Rgb(255,255,255), port.port_name())) + ).align_w().exact_w(20), + west( + ().exact_w(4), + bg(track.color.darker.term, east!( + either(track.sequencer.monitoring, fg(Green, " ● "), " · "), + either(track.sequencer.recording, fg(Red, " ● "), " · "), + either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), + ).exact_w(track.width as u16).align_w()) + ) + ).push_x(index as u16 * 10).exact_h(1).draw(to)?; + } + todo!() + }) + ).draw(to)?; + } + Ok(Some(to.area())) + }))) + } + + fn outputs_height (&self) -> u16 { + let mut h = 1; + for output in self.midi_outs().iter() { + h += 1 + output.connections.len(); + } + h as u16 + } + + fn view_outputs (&self, theme: ItemTheme) -> impl Draw { + let height = self.outputs_height(); + let list = south( + button_3( + "o", "utput", format!("{}", self.midi_outs().len()), false + ).align_w().full_w().exact_h(1), + draw(|to: &mut Tui|{ + for (_index, port) in self.midi_outs().iter().enumerate() { + east( + east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name()))).align_w(), + format!("{}/{} ", + port.port().get_connections().len(), + port.connections.len()).align_e().full_w().exact_h(1)).full_w().draw(to)?; + for (index, conn) in port.connections.iter().enumerate() { + format!(" c{index:02}{}", conn.info()).align_w().full_w().exact_h(1).draw(to)?; + } + } + todo!(); + }).align_nw().full_wh().exact_h(height - 1) + ); + view_track_row_section(theme, list, button_2("O", "+", false), + bg(theme.darker.term, draw(|to: &mut Tui|{ + for (index, track, _x1, _x2) in self.tracks_with_sizes() { + let _ = draw(|to: &mut Tui|{ + east( + either(true, fg(Green, "play "), "play "), + either(false, fg(Yellow, "solo "), "solo "), + ).align_w().exact_h(1).draw(to)?; + for (_index, port) in self.midi_outs().iter().enumerate() { + east( + either(true, fg(Green, " ● "), " · "), + either(false, fg(Yellow, " ● "), " · "), + ).align_w().exact_h(1).draw(to)?; + for (_index, _conn) in port.connections.iter().enumerate() { + "".full_w().exact_h(1).draw(to)?; + } + } + todo!() + }).exact_w(track_width(index, track)).draw(to)?; + } + todo!() + }).align_w().full_w())).exact_h(height) + } + +} + +pub trait HasTrackScroll { + fn track_scroll (&self) -> usize; + fn tracks_last (&self) -> usize; + fn tracks_last_mut (&mut self) -> &mut usize; +} + +impl HasTrackScroll for &mut Arrangement { + fn track_scroll (&self) -> usize { + self.track_scroll + } + fn tracks_last (&self) -> usize { + self.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.track_last + } +} + +impl HasTrackScroll for Arrangement { + fn track_scroll (&self) -> usize { + self.track_scroll + } + fn tracks_last (&self) -> usize { + self.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.track_last + } +} + +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } + fn tracks_last (&self) -> usize { + self.project.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.project.track_last + } +} + +fn view_track_row_section ( + _theme: ItemTheme, + button: impl Draw, + button_add: impl Draw, + content: impl Draw, +) -> impl Draw { + west( + button_add.align_nw().exact_w(4).full_h(), + east( + button.align_nw().full_h().exact_w(20), + content.align_c().full_wh() + ) + ) +} diff --git a/src/device/browse.rs b/src/device/browse.rs index 82be27c1..f4498964 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -12,7 +12,11 @@ impl App { } #[tek_proc::commands(BrowseCommand = "browse")] -impl Browse { +pub trait BrowseController: + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, PathBuf> + + for<'a> Namespace<'a, Arc> +{ /// Toggle visibility of browser #[command(Show = "show")] fn show (&mut self) -> Perhaps { @@ -171,3 +175,14 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { files.sort(); Ok((subdirs, files)) } + +pub fn view_browse_title (state: &App) -> impl Draw { + field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() { + BrowseTarget::SaveProject => "Save project:", + BrowseTarget::LoadProject => "Load project:", + BrowseTarget::ImportSample(_) => "Import sample:", + BrowseTarget::ExportSample(_) => "Export sample:", + BrowseTarget::ImportClip(_) => "Import clip:", + BrowseTarget::ExportClip(_) => "Export clip:", + }, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w() +} diff --git a/src/device/clock.rs b/src/device/clock.rs index a59d179e..a8e78b56 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -20,63 +20,6 @@ impl App { } } -#[tek_proc::commands(ClockCommand = "clock")] -impl Clock { - #[command(SeekUsec = "usec")] - fn seek_usec (&mut self, usec: f64) -> Perhaps { - self.playhead.update_from_usec(usec); - Ok(None) - } - - #[command(SeekSample = "sample")] - fn seek_sample (&mut self, sample: f64) -> Perhaps { - self.playhead.update_from_sample(sample); - Ok(None) - } - - #[command(SeekPulse = "pulse")] - fn seek_pulse (&mut self, pulse: f64) -> Perhaps { - self.playhead.update_from_pulse(pulse); - Ok(None) - } - - #[command(SetBpm = "bpm")] - fn set_bpm (&mut self, bpm: f64) -> Perhaps { - Ok(Some(ClockCommand::SetBpm { bpm: self.timebase().bpm.set(bpm) })) - } - - #[command(SetQuant = "quant")] - fn set_quant (&mut self, quant: f64) -> Perhaps { - Ok(Some(ClockCommand::SetQuant { quant: self.quant.set(quant) })) - } - - #[command(SetSync = "sync")] - fn set_sync (&mut self, sync: f64) -> Perhaps { - Ok(Some(ClockCommand::SetSync { sync: self.sync.set(sync) })) - } - - #[command(Play = "play")] - fn play (&mut self, position: Option) -> Perhaps { - self.play_from(position)?; - Ok(None) /* TODO Some(Pause(previousPosition)) */ - } - - #[command(Pause = "pause")] - fn pause (&mut self, position: Option) -> Perhaps { - self.pause_at(position)?; - Ok(None) - } - - #[command(TogglePlayback = "toggle")] - fn toggle (&mut self, position: u32) -> Perhaps { - Ok(if self.is_rolling() { - self.pause_at(Some(position))?; None - } else { - self.play_from(Some(position))?; None - }) - } -} - /// The source of time. /// /// ``` @@ -84,23 +27,23 @@ impl Clock { /// ``` #[derive(Clone, Default)] pub struct Clock { /// JACK transport handle. - pub transport: Arc>, + pub transport: Arc>, /// Global temporal resolution (shared by [Moment] fields) - pub timebase: Arc, + pub timebase: Arc, /// Current global sample and usec (monotonic from JACK clock) - pub global: Arc, + pub global: Arc, /// Global sample and usec at which playback started - pub started: Arc>>, + pub started: Arc>>, /// Playback offset (when playing not from start) - pub offset: Arc, + pub offset: Arc, /// Current playhead position - pub playhead: Arc, + pub playhead: Arc, /// Note quantization factor - pub quant: Arc, + pub quant: Arc, /// Launch quantization factor - pub sync: Arc, + pub sync: Arc, /// Size of buffer in samples - pub chunk: Arc, + pub chunk: Arc, // Cache of formatted strings pub view_cache: Arc>, /// For syncing the clock to an external source @@ -111,10 +54,10 @@ impl Clock { #[cfg(feature = "port")] pub click_out: Arc>>, } -impl +AsMut> HasClock for T {} +impl + AsMut> HasClock for T {} pub trait HasClock: AsRef + AsMut { - fn clock (&self) -> &Clock { + fn clock (&self) -> &Clock { self.as_ref() } fn clock_mut (&mut self) -> &mut Clock { @@ -122,12 +65,87 @@ pub trait HasClock: AsRef + AsMut { } } -impl Act for ClockCommand { - fn act (&self, state: &mut T) -> Perhaps { - self.act(state.clock_mut()) // awesome +impl Namespace<'a, u32> + + for<'a> Namespace<'a, f64> + + for<'a> Namespace<'a, Option> +> ClockController for T {} + +#[tek_proc::commands(ClockCommand = "clock")] +pub trait ClockController: HasClock + + for<'a> Namespace<'a, u32> + + for<'a> Namespace<'a, f64> + + for<'a> Namespace<'a, Option> +{ + #[command(SeekUsec = "usec")] + fn seek_usec (&mut self, usec: f64) -> Perhaps { + self.clock().playhead.update_from_usec(usec); + Ok(None) + } + + #[command(SeekSample = "sample")] + fn seek_sample (&mut self, sample: f64) -> Perhaps { + self.clock().playhead.update_from_sample(sample); + Ok(None) + } + + #[command(SeekPulse = "pulse")] + fn seek_pulse (&mut self, pulse: f64) -> Perhaps { + self.clock().playhead.update_from_pulse(pulse); + Ok(None) + } + + #[command(SetBpm = "bpm")] + fn set_bpm (&mut self, bpm: f64) -> Perhaps { + Ok(Some(ClockCommand::SetBpm { + bpm: self.clock().timebase().bpm.set(bpm) + })) + } + + #[command(SetQuant = "quant")] + fn set_quant (&mut self, quant: f64) -> Perhaps { + Ok(Some(ClockCommand::SetQuant { + quant: self.clock().quant.set(quant) + })) + } + + #[command(SetSync = "sync")] + fn set_sync (&mut self, sync: f64) -> Perhaps { + Ok(Some(ClockCommand::SetSync { + sync: self.clock().sync.set(sync) + })) + } + + #[command(Play = "play")] + fn play (&mut self, position: Option) -> Perhaps { + self.clock().play_from(position)?; + Ok(None) /* TODO Some(Pause(previousPosition)) */ + } + + #[command(Pause = "pause")] + fn pause (&mut self, position: Option) -> Perhaps { + self.clock().pause_at(position)?; + Ok(None) + } + + #[command(TogglePlayback = "toggle")] + fn toggle (&mut self, position: u32) -> Perhaps { + Ok(if self.clock().is_rolling() { + self.clock().pause_at(Some(position))?; + None + } else { + self.clock().play_from(Some(position))?; + None + }) } } +//impl Act for ClockCommand { + //fn act (&self, state: &mut T) -> Perhaps { + //self.act(state.clock_mut()) // awesome + //} +//} + /// Quantization setting for launching clips /// /// ``` diff --git a/src/device/dialog.rs b/src/device/dialog.rs index c54e5dea..06834327 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -66,45 +66,6 @@ impl App { })).transpose() } - /// Set currently active modal dialog. - /// - /// ``` - /// let previous: tek::Dialog = tek::App::default().set_dialog(&tek::Dialog::welcome()); - /// ``` - pub fn set_dialog (&mut self, dialog: &Dialog) -> Dialog { - let mut dialog = dialog.clone(); - std::mem::swap(&mut self.dialog, &mut dialog); - dialog - } - - pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => todo!(), - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog(self.dialog.menu_next()).act(self)?, - _ => todo!() - }) - } - - pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps { - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => None, - (Dialog::Menu(_, _), ControlAxis::Y) => - AppCommand::SetDialog(self.dialog.menu_prev()).act(self)?, - _ => todo!() - }) - } - - pub fn confirm (&mut self) -> Perhaps { - Ok(match &self.dialog { - Dialog::Menu(index, items) => { - let callback = items.0[*index].1.clone(); - callback(self)?; - None - }, - _ => todo!(), - }) - } } /// Various possible dialog modes. diff --git a/src/device/editor.rs b/src/device/editor.rs index 27354cfe..9cee7a03 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -56,66 +56,121 @@ impl App { } } +pub trait HasEditor: AsRefOpt + AsMutOpt { + fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() } + fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() } + fn is_editing (&self) -> bool { self.editor().is_some() } + fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize } + fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize } +} + +impl+AsMutOpt> HasEditor for T {} + +impl Namespace<'a, u32> + + for<'a> Namespace<'a, f64> + + for<'a> Namespace<'a, bool> + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>>> +> MidiEditController for T {} + #[tek_proc::commands(MidiEditCommand = "edit")] -impl MidiEditor { +pub trait MidiEditController: HasEditor + + for<'a> Namespace<'a, u32> + + for<'a> Namespace<'a, f64> + + for<'a> Namespace<'a, bool> + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option>>> +{ #[command(Show = "show")] fn show (&mut self, clip: Option>>) -> Perhaps { - self.set_clip(clip.as_ref()); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_clip(clip.as_ref()); + editor.redraw(); + None + }).flatten()) } + #[command(DeleteNote = "delete")] fn note_delete (&mut self) -> Perhaps { - self.redraw(); - todo!() + Ok(self.editor_mut().map(|editor|{ + editor.redraw(); + todo!() + }).flatten()) } + #[command(AppendNote = "append")] fn note_append (&mut self, advance: bool) -> Perhaps { - self.put_note(advance); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.put_note(advance); + editor.redraw(); + None + }).flatten()) } + #[command(SetNotePos = "note-pos")] fn note_set_pos (&mut self, pos: usize) -> Perhaps { - self.set_note_pos((pos).min(127)); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_note_pos((pos).min(127)); + editor.redraw(); + None + }).flatten()) } + #[command(SetNoteLen = "note-len")] fn note_set_len (&mut self, len: usize) -> Perhaps { - self.set_note_len(len); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_note_len(len); + editor.redraw(); + None + }).flatten()) } + #[command(SetNoteScroll = "note-scroll")] fn note_set_scroll (&mut self, scroll: usize) -> Perhaps { - self.set_note_lo((scroll).min(127)); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_note_lo((scroll).min(127)); + editor.redraw(); + None + }).flatten()) } + #[command(SetTimePos = "time-pos")] fn time_set_pos (&mut self, pos: usize) -> Perhaps { - self.set_time_pos(pos); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_time_pos(pos); + editor.redraw(); + None + }).flatten()) } + #[command(SetTimeScroll = "time-scroll")] fn time_set_scroll (&mut self, scroll: usize) -> Perhaps { - self.set_time_start(scroll); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_time_start(scroll); + editor.redraw(); + None + }).flatten()) } + #[command(SetTimeZoom = "time-zoom")] fn time_set_zoom (&mut self, zoom: usize) -> Perhaps { - self.set_time_zoom(zoom); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_time_zoom(zoom); + editor.redraw(); + None + }).flatten()) } + #[command(SetTimeLock = "time-lock")] fn time_set_lock (&mut self, lock: bool) -> Perhaps { - self.set_time_lock(lock); - self.redraw(); - Ok(None) + Ok(self.editor_mut().map(|editor|{ + editor.set_time_lock(lock); + editor.redraw(); + None + }).flatten()) } // TODO: 1-9 seek markers that by default start every 8th of the clip } @@ -263,20 +318,11 @@ impl MidiEditor { /// let _ = host.editor_w(); /// let _ = host.editor_h(); /// ``` -pub trait HasEditor: AsRefOpt + AsMutOpt { - fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() } - fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() } - fn is_editing (&self) -> bool { self.editor().is_some() } - fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize } - fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize } -} impl MidiPoint for T {} impl MidiRange for T {} -impl +AsMutOpt> HasEditor for T {} - pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn redraw (&self); diff --git a/src/device/pool.rs b/src/device/pool.rs index 63a24f03..2ae3c95b 100644 --- a/src/device/pool.rs +++ b/src/device/pool.rs @@ -58,48 +58,71 @@ pub enum ClipLengthFocus { Tick, } +impl + AsMut> HasPool for T {} + +pub trait HasPool: AsRef + AsMut { + fn pool (&self) -> &Pool { + self.as_ref() + } + fn pool_mut (&mut self) -> &mut Pool { + self.as_mut() + } +} + +impl Namespace<'a, bool> + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, PathBuf> + + for<'a> Namespace<'a, MidiClip> + + for<'a> Namespace<'a, ItemColor> + + for<'a> Namespace<'a, PoolCommand> + + for<'a> Namespace<'a, PoolCommand> + + for<'a> Namespace<'a, BrowseCommand> +> PoolController for T {} + #[tek_proc::commands(PoolCommand = "pool")] -impl Pool { +pub trait PoolController: HasPool + + for<'a> Namespace<'a, bool> + + for<'a> Namespace<'a, usize> + + for<'a> Namespace<'a, Arc> + + for<'a> Namespace<'a, PathBuf> + + for<'a> Namespace<'a, MidiClip> + + for<'a> Namespace<'a, ItemColor> + + for<'a> Namespace<'a, PoolCommand> + + for<'a> Namespace<'a, PoolCommand> + + for<'a> Namespace<'a, BrowseCommand> +{ #[command(Show = "show")] /// Toggle visibility of pool fn show (&mut self, visible: bool) -> Perhaps { - self.visible = visible; + self.pool_mut().visible = visible; Ok(Some(PoolCommand::Show { visible: !visible })) } /// Set selected clip #[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps { - self.set_clip_index(index); + self.pool_mut().set_clip_index(index); Ok(None) } - /// Rename item in pool - #[command(Rename = "rename")] - fn rename (&mut self, command: RenameCommand) -> Perhaps { - Ok(command.act(self)?.map(|command|PoolCommand::Rename{command})) - } - - /// Change length of item - #[command(Length = "length")] - fn length (&mut self, command: CropCommand) -> Perhaps { - Ok(command.act(self)?.map(|command|PoolCommand::Length{command})) - } - - /// Import from file - #[command(Browse = "browse")] - fn browse (&mut self, command: BrowseCommand) -> Perhaps { - Ok(if let Some(browse) = self.browse.as_mut() { - command.act(browse)?.map(|command|PoolCommand::Browse{command}) - } else { - None - }) - } + ///// Import from file + //#[command(Browse = "browse")] + //fn browse (&mut self, command: BrowseCommand) -> Perhaps { + //Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + //command.act(browse)?.map(|command|PoolCommand::Browse{command}) + //} else { + //None + //}) + //} /// Import clip to pool from file #[command(Import = "import")] - fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps { + fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps + where Self: Sized + { let bytes = std::fs::read(&path)?; let smf = Smf::parse(bytes.as_slice())?; let mut t = 0u32; @@ -119,27 +142,27 @@ impl Pool { Ok(PoolCommand::Add { index, clip }.act(self)?) } - /// Export to file - #[command(Export = "export")] - fn export (&mut self, command: BrowseCommand) -> Perhaps { - Ok(if let Some(browse) = self.browse.as_mut() { - command.act(browse)?.map(|command|PoolCommand::Export{command}) - } else { - None - }) - } + ///// Export to file + //#[command(Export = "export")] + //fn export (&mut self, command: BrowseCommand) -> Perhaps { + //Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { + //command.act(browse)?.map(|command|PoolCommand::Export{command}) + //} else { + //None + //}) + //} /// Delete a clip from the pool #[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps { - let clip = self.clips_mut().remove(index).read().unwrap().clone(); + let clip = self.pool_mut().clips_mut().remove(index).read().unwrap().clone(); Ok(Some(PoolCommand::Add { index, clip })) } /// Switch places of two clips in pool #[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps { - self.clips_mut().swap(index, other); + self.pool_mut().clips_mut().swap(index, other); Ok(Some(PoolCommand::Swap { index, other })) } @@ -148,7 +171,7 @@ impl Pool { fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps { let mut index = index; let clip = Arc::new(RwLock::new(clip.clone())); - let mut clips = self.clips_mut(); + let mut clips = self.pool_mut().clips_mut(); if index >= clips.len() { index = clips.len(); clips.push(clip) @@ -161,7 +184,7 @@ impl Pool { /// Set name of clip #[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc) -> Perhaps { - let clip = &mut self.clips_mut()[index]; + let clip = &mut self.pool_mut().clips_mut()[index]; let old_name = clip.read().unwrap().name.clone(); clip.write().unwrap().name = name.clone(); Ok(Some(PoolCommand::SetName { index, name: old_name })) @@ -170,7 +193,7 @@ impl Pool { /// Set length of clip #[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps { - let clip = &mut self.clips_mut()[index]; + let clip = &mut self.pool_mut().clips_mut()[index]; let old_len = clip.read().unwrap().length; clip.write().unwrap().length = length; Ok(Some(PoolCommand::SetLength { index, length: old_len })) @@ -180,66 +203,70 @@ impl Pool { #[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps { let mut color = ItemTheme::from(color); - std::mem::swap(&mut color, &mut self.clips()[index].write().unwrap().color); + std::mem::swap(&mut color, &mut self.pool().clips()[index].write().unwrap().color); Ok(Some(PoolCommand::SetColor { index, color: color.base })) } -} - -#[tek_proc::commands(CropCommand = "crop")] -impl Pool { - /// Enter crop mode - #[command(Begin = "begin")] - fn crop_begin (&mut self) -> Perhaps { - let length = self.clips()[self.clip_index()].read().unwrap().length; - *self.mode_mut() = Some(PoolMode::Length( - self.clip_index(), - length, - ClipLengthFocus::Bar - )); + #[command(CropBegin = "crop/begin")] + fn crop_begin (&mut self) -> Perhaps { + let index = self.pool().clip_index(); + let length = self.pool().clips()[index].read().unwrap().length; + *self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); Ok(None) } /// Exit crop mode, discard - #[command(Cancel = "cancel")] - fn crop_cancel (&mut self) -> Perhaps { - if let Some(PoolMode::Length(..)) = self.mode_mut().clone() { - *self.mode_mut() = None; + #[command(CropCancel = "crop/cancel")] + fn crop_cancel (&mut self) -> Perhaps { + if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { + *self.pool_mut().mode_mut() = None; } Ok(None) } - #[command(Set = "set")] - fn crop_set (&mut self, length: usize) -> Perhaps { + #[command(CropSet = "crop/set")] + fn crop_set (&mut self, length: usize) -> Perhaps { if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) - = self.mode_mut().clone() + = self.pool_mut().mode_mut().clone() { let old_length; { - let clip = self.clips()[clip].clone();//.write().unwrap(); + let clip = self.pool().clips()[clip].clone();//.write().unwrap(); old_length = Some(clip.read().unwrap().length); clip.write().unwrap().length = *length; } - *self.mode_mut() = None; - return Ok(old_length.map(|length|CropCommand::Set { length })) + *self.pool_mut().mode_mut() = None; + return Ok(old_length.map(|length|PoolCommand::CropSet { length })) } Ok(None) } - #[command(Next = "next")] - fn crop_next (&mut self) -> Perhaps { - if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = self.mode_mut().clone() { focus.next() }; Ok(None) + #[command(CropNext = "crop/next")] + fn crop_next (&mut self) -> Perhaps { + if let Some(PoolMode::Length( + _clip, ref mut _length, ref mut focus + )) = self.pool_mut().mode_mut().clone() { + focus.next() + }; + Ok(None) } - #[command(Prev = "prev")] - fn crop_prev (&mut self) -> Perhaps { - if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = self.mode_mut().clone() { focus.prev() }; Ok(None) + #[command(CropPrev = "crop/prev")] + fn crop_prev (&mut self) -> Perhaps { + if let Some(PoolMode::Length( + _clip, ref mut _length, ref mut focus + )) = self.pool_mut().mode_mut().clone() { + focus.prev() + }; + Ok(None) } - #[command(Inc = "inc")] - fn crop_inc (&mut self) -> Perhaps { - if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = self.mode_mut().clone() { + #[command(CropInc = "crop/inc")] + fn crop_inc (&mut self) -> Perhaps { + if let Some(PoolMode::Length( + _clip, ref mut length, ref mut focus + )) = self.pool_mut().mode_mut().clone() { match focus { ClipLengthFocus::Bar => { *length += 4 * PPQ }, ClipLengthFocus::Beat => { *length += PPQ }, @@ -249,9 +276,11 @@ impl Pool { Ok(None) } - #[command(Dec = "dec")] - fn crop_dec (&mut self) -> Perhaps { - if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = self.mode_mut().clone() { + #[command(CropDec = "crop/dec")] + fn crop_dec (&mut self) -> Perhaps { + if let Some(PoolMode::Length( + _clip, ref mut length, ref mut focus + )) = self.pool_mut().mode_mut().clone() { match focus { ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, @@ -261,47 +290,40 @@ impl Pool { Ok(None) } -} - -#[tek_proc::commands(RenameCommand = "rename")] -impl Pool { - /// Enter rename mode - #[command(Begin = "begin")] - fn rename_begin (&mut self) -> Perhaps { - let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); - *self.mode_mut() = Some(PoolMode::Rename( - self.clip_index(), - name - )); + #[command(RenameBegin = "rename/begin")] + fn rename_begin (&mut self) -> Perhaps { + let index = self.pool().clip_index(); + let name = self.pool().clips()[index].read().unwrap().name.clone(); + *self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); Ok(None) } /// Exit rename mode, discard - #[command(Cancel = "cancel")] - fn rename_cancel (&mut self) -> Perhaps { - if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.mode_mut().clone() { - self.clips()[clip].write().unwrap().name = old_name.clone().into(); + #[command(RenameCancel = "rename/cancel")] + fn rename_cancel (&mut self) -> Perhaps { + if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { + self.pool().clips()[clip].write().unwrap().name = old_name.clone().into(); } Ok(None) } /// Exit rename mode, update name - #[command(Confirm = "confirm")] - fn rename_confirm (&mut self) -> Perhaps { - Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.mode_mut().clone() { + #[command(RenameConfirm = "rename/confirm")] + fn rename_confirm (&mut self) -> Perhaps { + Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { let old_name = old_name.clone(); - *self.mode_mut() = None; - Some(RenameCommand::Set { value: old_name }) + *self.pool_mut().mode_mut() = None; + Some(PoolCommand::RenameSet { value: old_name }) } else { None }) } - #[command(Set = "set")] - fn rename_set (&mut self, value: Arc) -> Perhaps { - if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.mode_mut().clone() { - self.clips()[clip].write().unwrap().name = value.clone(); + #[command(RenameSet = "rename/set")] + fn rename_set (&mut self, value: Arc) -> Perhaps { + if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() { + self.pool().clips()[clip].write().unwrap().name = value.clone(); } Ok(None) } diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 206a8efa..b2808ac7 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -8,12 +8,28 @@ pub(crate) use symphonia::{ }, }; +pub trait HasSampler: AsRef + AsMut { + fn sampler (&self) -> &Sampler { + self.as_ref() + } + fn sampler_mut (&mut self) -> &mut Sampler { + self.as_mut() + } +} + +impl + AsMut> HasSampler for T {} + #[tek_proc::commands(SamplerCommand = "sampler")] -impl Sampler { +pub trait SamplerController: HasSampler + + for<'a> Namespace<'a, usize> +{ #[command(RecordToggle = "rec-toggle")] - fn record_toggle (&mut self, slot: usize) -> Perhaps { - let recording = self.recording.as_ref().map(|x|x.0); + fn record_toggle (&mut self, slot: usize) -> Perhaps + where Self: Sized + { + let sampler = self.sampler_mut(); + let recording = sampler.recording.as_ref().map(|x|x.0); let _ = SamplerCommand::RecordFinish.act(self)?; // autoslice: continue recording at next slot if recording != Some(slot) { @@ -25,10 +41,11 @@ impl Sampler { #[command(RecordBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps { - self.recording = Some(( + let sampler = self.sampler_mut(); + sampler.recording = Some(( slot, Some(Arc::new(RwLock::new(Sample::new( - "Sample", 0, 0, vec![vec![];self.audio_ins.len()] + "Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()] )))) )); Ok(None) @@ -36,8 +53,9 @@ impl Sampler { #[command(RecordFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps { - let _prev_sample = self.recording.as_mut().map(|(index, sample)|{ - std::mem::swap(sample, &mut self.samples.0[*index]); + let sampler = self.sampler_mut(); + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.samples.0[*index]); sample }); // TODO: undo Ok(None) @@ -45,14 +63,15 @@ impl Sampler { #[command(RecordCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps { - self.recording = None; + self.sampler_mut().recording = None; Ok(None) } #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps { - if let Some(ref sample) = self.samples.0[slot] { - self.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); + let sampler = self.sampler_mut(); + if let Some(ref sample) = sampler.samples.0[slot] { + sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); } Ok(None) } @@ -730,3 +749,95 @@ impl SampleAdd { fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { todo!(); } + +pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { + when(sample.is_some(), draw(move|to: &mut Tui|{ + let sample = sample.unwrap().read().unwrap(); + let theme = sample.color; + east!( + field_h(theme, "Name", format!("{:<10}", sample.name.clone())), + field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())), + field_h(theme, "Start", format!("{:<8}", sample.start)), + field_h(theme, "End", format!("{:<8}", sample.end)), + field_h(theme, "Trans", "0"), + field_h(theme, "Gain", format!("{}", sample.gain)), + ).draw(to) + })) +} + +pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { + let a = draw(move|to: &mut Tui|{ + let sample = sample.unwrap().read().unwrap(); + let theme = sample.color; + south!( + field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(), + field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(), + field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(), + field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(), + field_h(theme, "Trans ", "0") .align_w().full_w(), + field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(), + ).exact_w(20).draw(to) + }); + + let b = draw(|to: &mut Tui|fg(Red, south!( + bold(true, "× No sample."), + "[r] record", + "[Shift-F9] import", + )).draw(to)); + + either(sample.is_some(), a, b) +} + +pub fn view_sample_status (sample: Option<&Arc>>) -> impl Draw { + bold(true, fg(g(224), sample + .map(|sample|{ + let sample = sample.read().unwrap(); + format!("Sample {}-{}", sample.start, sample.end) + }) + .unwrap_or_else(||"No sample".to_string()))) +} + +#[cfg(feature = "track")] +impl Track { + /// Create a new track connecting the [Sequencer] to a [Sampler]. + pub fn new_with_sampler ( + name: &impl AsRef, + color: Option, + jack: &Jack<'static>, + clock: Option<&Clock>, + clip: Option<&Arc>>, + midi_from: &[Connect], + midi_to: &[Connect], + audio_from: &[&[Connect];2], + audio_to: &[&[Connect];2], + ) -> Usually { + let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; + let client_name = jack.with_client(|c|c.name().to_string()); + let port_name = track.sequencer.midi_outs[0].port_name(); + let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; + track.devices.push(Device::Sampler(Sampler::new( + jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to + )?)); + Ok(track) + } + + pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { + for device in self.devices.iter() { + match device { + Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, + _ => {} + } + } + None + } + + pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { + for device in self.devices.iter_mut() { + match device { + Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, + _ => {} + } + } + None + } +} diff --git a/src/device/sequence.rs b/src/device/sequence.rs index 573ea4c3..cb60604f 100644 --- a/src/device/sequence.rs +++ b/src/device/sequence.rs @@ -228,6 +228,7 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip { } pub type MidiData = Vec>; + pub type ClipPool = Vec>>; pub trait HasClips { @@ -244,6 +245,34 @@ pub trait HasMidiClip { fn clip (&self) -> Option>>; } +impl Namespace<'a, Option> + + for<'a> Namespace<'a, Option> +> MidiClipController for T {} + +#[tek_proc::commands(MidiClipCommand)] +pub trait MidiClipController: HasMidiClip + + for<'a> Namespace<'a, Option> + + for<'a> Namespace<'a, Option> +{ + + #[command(SetColor = "clip/color")] + fn clip_set_color (&mut self, color: Option) -> Perhaps { + //(SetColor [t: usize, s: usize, c: ItemTheme] + //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + todo!() + } + + #[command(SetLoop = "clip/loop")] + fn clip_toggle_loop (&mut self, looping: Option) -> Perhaps { + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } + +} + pub trait HasSequencer: AsRef + AsMut { fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() } fn sequencer (&self) -> &Sequencer { self.as_ref() } diff --git a/src/tek.edn b/src/tek.edn index ef4b6566..198f95b9 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -88,16 +88,16 @@ (keys :back (@escape back)) (keys :confirm (@enter confirm)) -(keys :axis/x (@left x/dec) (@right x/inc)) -(keys :axis/x2 (@shift/left x2/dec) (@shift/right x2/inc)) -(keys :axis/y (@up y/dec) (@down y/inc)) -(keys :axis/y2 (@shift/up y2/dec) (@shift/down y2/inc)) -(keys :axis/z (@minus z/dec) (@equal z/inc)) -(keys :axis/z2 (@underscore z2/dec) (@plus z2/inc)) -(keys :axis/i (@comma i/dec) (@period z/inc)) -(keys :axis/i2 (@lt i2/dec) (@gt z2/inc)) -(keys :axis/w (@openbracket w/dec) (@closebracket w/inc)) -(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc)) +(keys :axis/x (@left app/dec :x) (@right app/inc :x)) +(keys :axis/x2 (@shift/left app/dec :x2) (@shift/right app/inc :x2)) +(keys :axis/y (@up app/dec :y) (@down app/inc :y)) +(keys :axis/y2 (@shift/up app/dec :y2) (@shift/down app/inc :y2)) +(keys :axis/z (@minus app/dec :z) (@equal app/inc :z)) +(keys :axis/z2 (@underscore app/dec :z2) (@plus app/inc :z2)) +(keys :axis/i (@comma app/dec :i) (@period app/inc :z)) +(keys :axis/i2 (@lt app/dec :i2) (@gt app/inc :z2)) +(keys :axis/w (@openbracket app/dec :w) (@closebracket app/inc :w)) +(keys :axis/w2 (@openbrace app/dec :w2) (@closebrace app/inc :w2)) (keys :focus) (keys :editor (see :axis/i :axis/i2 :axis/y :page :editor/view :editor/add :editor/del)) diff --git a/src/tek.rs b/src/tek.rs index 5bac9752..fd2fb982 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -239,8 +239,8 @@ fn run_new_plain (config: Config) -> Usually<()> { connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter() )); //&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?; - proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?; - proj.scenes_add(scenes.unwrap_or(0))?; + proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?; + proj.scenes_add_many(scenes.unwrap_or(0))?; //if matches!(self, Action::Status) { //// Show status and exit //tek_print_status(&proj); @@ -298,49 +298,6 @@ mod config { /// Collection of custom view definitions. pub type Views = Arc, Arc>>>; - /// Collection of input bindings. - pub type Binds = Arc, Bind>>>>; - - /// An map of input events (e.g. [TuiEvent]) to [Binding]s. - /// - /// ``` - /// let lang = "(@x (nop)) (@y (nop) (nop))"; - /// let bind = tek::Bind::>::load(&lang).unwrap(); - /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); - /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); - /// ``` - #[derive(Debug)] - pub struct Bind( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub BTreeMap>> - ); - - /// A sequence of zero or more commands (e.g. [AppCommand]), - /// optionally filtered by [Condition] to form layers. - /// - /// ``` - /// //FIXME: Why does it overflow? - /// //let binding: Binding<()> = tek::Binding { ..Default::default() }; - /// ``` - #[derive(Debug, Clone)] pub struct Binding { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, - } - - /// Condition that must evaluate to true in order to enable an input layer. - /// - /// ``` - /// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); - /// ``` - #[derive(Clone)] - pub struct Condition( - pub Arcbool + Send + Sync>> - ); - /// Collection of UI modes. /// /// ``` @@ -651,14 +608,10 @@ mod app { primitive!(usize: try_to_usize); primitive!(isize: try_to_isize); impl_has!(Clock: |self: App|self.project.clock); - impl_has!(Vec: |self: App|self.project.midi_ins); - impl_has!(Vec: |self: App|self.project.midi_outs); impl_has!(Dialog: |self: App|self.dialog); impl_has!(Jack<'static>: |self: App|self.jack); impl_has!(Pool: |self: App|self.pool); impl_has!(Selection: |self: App|self.project.selection); - impl_as_ref!(Vec: |self: App|self.project.as_ref()); - impl_as_mut!(Vec: |self: App|self.project.as_mut()); impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt()); impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt()); impl_has_clips!( |self: App|self.pool.clips); @@ -700,6 +653,7 @@ mod app { #[namespace(Option App::get_opt_usize)] #[namespace(Option>> App::get_clip)] #[namespace(Dialog App::get_dialog)] + #[namespace(ControlAxis App::get_axis)] pub struct App { /// Exit flag pub exit: Exit, @@ -815,7 +769,8 @@ mod app { if let Some(expr) = src.expr()? { match (expr.head()?, expr.tail()?) { (Some("g"), Some(tail)) => { - let n = try_to_u8(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?; + let n = try_to_u8(tail.head().map_err(Into::into))? + .ok_or(LanguageError::Domain("not gray"))?; Ok(Some(Color::Rgb(n, n, n))) }, (Some("rgb"), Some(tail)) => { @@ -870,6 +825,14 @@ mod app { })).transpose() } + fn get_axis (&self, src: impl Language) -> Perhaps { + Ok(src.word()?.map(|word|Ok(match word { + "x" => ControlAxis::X, + "y" => ControlAxis::Y, + _ => return Err(format!("unknown axis {word}")) + })).transpose()?) + } + } } @@ -877,6 +840,49 @@ pub use self::bind::*; mod bind { use crate::*; + /// Collection of input bindings. + pub type Binds = Arc, Bind>>>>; + + /// An map of input events (e.g. [TuiEvent]) to [Binding]s. + /// + /// ``` + /// let lang = "(@x (nop)) (@y (nop) (nop))"; + /// let bind = tek::Bind::>::load(&lang).unwrap(); + /// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); + /// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); + /// ``` + #[derive(Debug)] + pub struct Bind( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> + ); + + /// A sequence of zero or more commands (e.g. [AppCommand]), + /// optionally filtered by [Condition] to form layers. + /// + /// ``` + /// //FIXME: Why does it overflow? + /// //let binding: Binding<()> = tek::Binding { ..Default::default() }; + /// ``` + #[derive(Debug, Clone)] pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, + } + + /// Condition that must evaluate to true in order to enable an input layer. + /// + /// ``` + /// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); + /// ``` + #[derive(Clone)] + pub struct Condition( + pub Arcbool + Send + Sync>> + ); + tui_keys!(self: App, input { let commands = collect_commands(self, input)?; let results = execute_commands(self, commands)?; @@ -888,24 +894,21 @@ mod bind { -> Usually> { let mut commands = vec![]; - app.mode - .as_ref() - .and_then(|m|app.config.get_mode(m)) - .map(|mode|{ - for id in mode.keys.iter() { - if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) - && let Some(bindings) = event_map.query(input) { - for binding in bindings { - for command in binding.commands.iter() { - if let Some(command) = app.namespace(command)? as Option { - commands.push(command) - } + app.mode.as_ref().and_then(|m|app.config.get_mode(m)).map(|mode|{ + for id in mode.keys.iter() { + if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref()) + && let Some(bindings) = event_map.query(input) { + for binding in bindings { + for command in binding.commands.iter() { + if let Some(command) = app.namespace(command)? as Option { + commands.push(command) } } } } - Ok::<_, Box>(()) - }).transpose()?; + } + Ok::<_, Box>(()) + }).transpose()?; Ok(commands) } @@ -914,9 +917,16 @@ mod bind { { let mut history = vec![]; for command in commands.into_iter() { - let result = command.act(app); - match result { Err(err) => { history.push((command, None)); return Err(err) } - Ok(undo) => { history.push((command, undo)); } }; + let result = command.clone().act(app); + match result { + Err(err) => { + history.push((command, None)); + return Err(err) + }, + Ok(undo) => { + history.push((command, undo)); + } + }; } Ok(history) } @@ -1002,45 +1012,62 @@ mod bind { impl_debug!(Condition |self, w| { write!(w, "*") }); - #[tek_proc::command(App)] - #[tek_proc::keyword(App)] - #[derive(Debug, Default)] - pub enum AppCommand { - #[default] - #[command(App::nop)] - #[keyword("nop")] - Nop, - - #[command(App::cancel)] - #[keyword("cancel")] - Cancel, - - #[command(App::confirm)] - #[keyword("confirm")] - Confirm, - - #[command(App::inc)] - #[keyword("x/inc", ControlAxis::X)] - #[keyword("y/inc", ControlAxis::Y)] - Inc(ControlAxis), - - #[command(App::dec)] - #[keyword("x/dec", ControlAxis::X)] - #[keyword("y/dec", ControlAxis::Y)] - Dec(ControlAxis), - - #[command(App::set_dialog | Self::SetDialog)] - #[keyword("dialog")] - SetDialog(Dialog), - } - + #[tek_proc::commands(AppCommand = "app")] impl App { - fn nop (&mut self) -> Perhaps { + + #[command(Nop = "nop")] + pub fn nop (&mut self) -> Perhaps { Ok(None) } - fn cancel (&mut self) -> Perhaps { + + #[command(Cancel = "cancel")] + pub fn cancel (&mut self) -> Perhaps { todo!() } + + #[command(Inc = "inc")] + pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => todo!(), + (Dialog::Menu(_, _), ControlAxis::Y) => + AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?, + _ => todo!() + }) + } + + #[command(Dec = "dec")] + pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { + Ok(match (&self.dialog, axis) { + (Dialog::None, _) => None, + (Dialog::Menu(_, _), ControlAxis::Y) => + AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?, + _ => todo!() + }) + } + + #[command(Confirm = "confirm")] + pub fn confirm (&mut self) -> Perhaps { + Ok(match &self.dialog { + Dialog::Menu(index, items) => { + let callback = items.0[*index].1.clone(); + callback(self)?; + None + }, + _ => todo!(), + }) + } + + /// Swap currently active modal dialog. + /// + /// ``` + /// let _ = tek::App::default().set_dialog(tek::Dialog::welcome()); + /// ``` + #[command(SetDialog = "dialog")] + pub fn set_dialog (&mut self, dialog: Dialog) -> Perhaps { + let mut dialog = dialog.clone(); + std::mem::swap(&mut self.dialog, &mut dialog); + Ok(Some(AppCommand::SetDialog { dialog })) + } } //impl<'a> Namespace<'a, AppCommand> for App { @@ -1135,7 +1162,7 @@ mod device { editor.set_note_pos(pitch.as_int() as usize); } } - let result = state.project.process_tracks(client, scope); + let result = state.project.tracks_jack_process(client, scope); state.perf.update_from_jack_scope(t0, scope); result } @@ -1279,46 +1306,6 @@ mod device { #[cfg(feature = "plugin")] pub mod plugin; #[cfg(feature = "plugin")] pub use self::plugin::*; - - pub struct Junction(T); - - impl View for Junction { - fn view (&self) -> impl Draw { - T::KIND - } - } - - #[tek_proc::command(AudioInput)] - #[tek_proc::keyword(AudioInput)] - #[derive(Debug)] - pub enum AudioInputCommand { - Close, - Connect(Arc), - } - - #[tek_proc::command(AudioOutput)] - #[tek_proc::keyword(AudioOutput)] - #[derive(Debug)] - pub enum AudioOutputCommand { - Close, - Connect(Arc), - } - - #[tek_proc::command(MidiInput)] - #[tek_proc::keyword(MidiInput)] - #[derive(Debug)] - pub enum MidiInputCommand { - Close, - Connect(Arc), - } - - #[tek_proc::command(MidiOutput)] - #[tek_proc::keyword(MidiOutput)] - #[derive(Debug)] - pub enum MidiOutputCommand { - Close, - Connect(Arc), - } } //pub fn tui ( @@ -1374,57 +1361,61 @@ mod draw { fn view (&self) -> impl Draw { self.perf.cycle(&mut |_|{ draw(|to: &mut Tui|{ - let xywh = to.area().into(); - - if let Some(e) = self.error.read().unwrap().as_ref() { - e.as_ref().align_c().draw(to)?; - } - - self.mode - .as_ref() - .and_then(|m|self.config.get_mode(m)) - .map(|mode|{ - - //south!( - //format!("Mode: {:?}", self.mode.as_ref()), - //format!("Time: {}", self.config.stamp.load(Relaxed)), - //iter_south(||mode.view.iter().enumerate().map(|(index, line)|{ - //format!("View #{index} {line}") - //})) - //).align_nw().draw(to)?; - - for (index, dsl) in mode.view.iter().enumerate() { - match self.interpret(to, dsl) { - Ok(Some(XYWH(.., w, h))) => { - self.size.0.store(w as usize, Relaxed); - self.size.1.store(h as usize, Relaxed); - }, - Err(e) => { - let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); - let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src); - *self.error.write().unwrap() = Some(message.into()); - return Err(e); - }, - _ => {} - } - } - - *self.error.write().unwrap() = None; - - Ok(()) - }); - - east( - format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)), - format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000), - ).align_se().draw(to)?; - - Ok(Some(xywh)) + self.draw_error(to)?; + self.draw_mode(to)?; + self.draw_debug(to)?; + Ok(Some(to.area().into())) }) }) } } + impl App { + fn draw_error (&self, to: &mut Tui) -> Usually<()> { + if let Some(e) = self.error.read().unwrap().as_ref() { + e.as_ref().align_c().draw(to)?; + } + Ok(()) + } + + fn draw_mode (&self, to: &mut Tui) -> Usually<()> { + self.mode.as_ref().and_then(|m|self.config.get_mode(m)).map(|mode|{ + //south!( + //format!("Mode: {:?}", self.mode.as_ref()), + //format!("Time: {}", self.config.stamp.load(Relaxed)), + //iter_south(||mode.view.iter().enumerate().map(|(index, line)|{ + //format!("View #{index} {line}") + //})) + //).align_nw().draw(to)?; + for (index, dsl) in mode.view.iter().enumerate() { + match self.interpret(to, dsl) { + Ok(Some(XYWH(.., w, h))) => { + self.size.0.store(w as usize, Relaxed); + self.size.1.store(h as usize, Relaxed); + }, + Err(e) => { + let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); + let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src); + *self.error.write().unwrap() = Some(message.into()); + return Err(e); + }, + _ => {} + } + } + *self.error.write().unwrap() = None; + Ok(()) + }); + Ok(()) + } + + fn draw_debug (&self, to: &mut Tui) -> Drawn { + east( + format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)), + format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000), + ).align_se().draw(to) + } + } + impl Interpret>> for App { fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Drawn { self.keyword(to, lang) @@ -1433,12 +1424,12 @@ mod draw { let mut frags = lang.src()?.unwrap().split("/"); match frags.next() { //Some(":logo") => view_logo().draw(to), - Some(":meters") => match frags.next() { + Some(":meters") => match frags.next() { Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), _ => panic!() }, - Some(":tracks") => match frags.next() { + Some(":tracks") => match frags.next() { None => "TODO tracks".draw(to), Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), @@ -1446,7 +1437,7 @@ mod draw { Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to), _ => panic!() }, - Some(":scenes") => match frags.next() { + Some(":scenes") => match frags.next() { None => "TODO Scenes".draw(to), Some(":scenes/names") => "TODO Scene Names".draw(to), _ => panic!() @@ -1506,27 +1497,6 @@ mod draw { }).min_w(30).exact_h(height) } - //pub fn per_track <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw + 'a { - //per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y()) - //} - - //pub fn per_track_top <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw + 'a { - //bg(Reset, iter_east(||tracks() - //.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{ - //fg_bg( - //track.color.lightest.term, - //track.color.base.term, - //callback(index, track) - //).exact_w((x2 - x1) as u16) - //})).align_x()) - //} - pub fn field_h ( _theme: ItemTheme, _head: impl Draw, _body: impl Draw ) -> impl Draw { @@ -1552,17 +1522,6 @@ mod draw { }).min_w(w).exact_h(h) } - pub fn view_browse_title (state: &App) -> impl Draw { - field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() { - BrowseTarget::SaveProject => "Save project:", - BrowseTarget::LoadProject => "Load project:", - BrowseTarget::ImportSample(_) => "Import sample:", - BrowseTarget::ExportSample(_) => "Export sample:", - BrowseTarget::ImportClip(_) => "Import clip:", - BrowseTarget::ExportClip(_) => "Export clip:", - }, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w() - } - pub fn view_device (state: &App) -> impl Draw { let selected = state.dialog.device_kind().unwrap(); south( @@ -1629,21 +1588,6 @@ mod draw { ) } - #[cfg(feature = "track")] pub fn view_track_row_section ( - _theme: ItemTheme, - button: impl Draw, - button_add: impl Draw, - content: impl Draw, - ) -> impl Draw { - west( - button_add.align_nw().exact_w(4).full_h(), - east( - button.align_nw().full_h().exact_w(20), - content.align_c().full_wh() - ) - ) - } - /// ``` /// let bg = tengri::ratatui::style::Color::Red; /// let fg = tengri::ratatui::style::Color::Green; @@ -1687,211 +1631,10 @@ mod draw { south(left, right) } - pub fn view_sample_info (sample: Option<&Arc>>) -> impl Draw + use<'_> { - when(sample.is_some(), draw(move|to: &mut Tui|{ - let sample = sample.unwrap().read().unwrap(); - let theme = sample.color; - east!( - field_h(theme, "Name", format!("{:<10}", sample.name.clone())), - field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())), - field_h(theme, "Start", format!("{:<8}", sample.start)), - field_h(theme, "End", format!("{:<8}", sample.end)), - field_h(theme, "Trans", "0"), - field_h(theme, "Gain", format!("{}", sample.gain)), - ).draw(to) - })) - } - - pub fn view_sample_info_v (sample: Option<&Arc>>) -> impl Draw + use<'_> { - let a = draw(move|to: &mut Tui|{ - let sample = sample.unwrap().read().unwrap(); - let theme = sample.color; - south!( - field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(), - field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(), - field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(), - field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(), - field_h(theme, "Trans ", "0") .align_w().full_w(), - field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(), - ).exact_w(20).draw(to) - }); - - let b = draw(|to: &mut Tui|fg(Red, south!( - bold(true, "× No sample."), - "[r] record", - "[Shift-F9] import", - )).draw(to)); - - either(sample.is_some(), a, b) - } - - pub fn view_sample_status (sample: Option<&Arc>>) -> impl Draw { - bold(true, fg(g(224), sample - .map(|sample|{ - let sample = sample.read().unwrap(); - format!("Sample {}-{}", sample.start, sample.end) - }) - .unwrap_or_else(||"No sample".to_string()))) - } - - pub fn view_track_header (theme: ItemTheme, content: impl Draw) -> impl Draw { + pub fn view_track_header (theme: ItemTheme, content: impl Draw) -> impl Draw { bg(theme.darker.term, content.align_e().full_w()).exact_w(12) } - pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T]) - -> impl Draw + use<'a, T> - { - let ins = ports.len() as u16; - let frame = Outer(true, Style::default().fg(g(96))); - let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!( - " {index} {}", port.port_name() - ).align_w().full_h())); - let field = field_v(theme, title, names); - border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins) - } - - pub fn view_io_ports <'a, T: PortsSizes<'a>> ( - fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a - ) -> impl Draw + 'a { - type Item<'a> = (usize, &'a Arc, &'a [Connect], usize, usize); - iter(items, - move|(_index, name, connections, y, y2): Item<'a>, _| south( - bold(true, fg_bg(fg, bg, east(" 󰣲 ", name).align_w())).full_h(), - iter(||connections.iter(), move|connect: &'a Connect, index|{ - bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16) - }) - ).exact_h((y2 - y) as u16).push_y(y as u16)) - } - - pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> ( - scenes: impl Fn()->S, - tracks: impl TracksSizes<'a>, - select: &Selection, - editor: Option<&MidiEditor>, - size: &Sizer, - editing: bool, - ) -> impl Draw { - let status = fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh(); - let tracks = iter_once(tracks, move|(track_index, track, _, _), _| { - let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| { - let (name, theme): (Arc, ItemTheme) = scene_name_theme(scene, track_index); - let f = theme.lightest.term; - let (b, o) = scene_bg(theme, select, track_index, scene_index); - let w = scene_w(track, select, track_index, editor); - let y = scene_y(select, scene_index, editor); - let is_selected = scene_sel(select, track_index, scene_index, editing); - below( - Outer(true, Style::default().fg(o)).full_wh(), - below( - below( - fg_bg(o, b, "".full_wh()), - fg_bg(f, b, bold(true, name)).align_nw().full_wh(), - ), - when(is_selected, editor.map(|e|e.view())).full_wh() - ).full_wh() - ).exact_wh(w, y) - }); - scenes.full_h().exact_w(track.width as u16) - }); - - return size.of(above(status, tracks).full_wh()); - - fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc, ItemTheme) { - if let Some(Some(clip)) = &scene.clips.get(track_index) { - let clip = clip.read().unwrap(); - (format!(" ⏹ {}", &clip.name).into(), clip.color) - } else { - (" ⏹ -- ".into(), ItemTheme::G[32]) - } - } - - fn scene_bg ( - theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize - ) -> (Color, Color) { - let mut outline = theme.base.term; - (if select.track() == Some(track_index) && select.scene() == Some(scene_index) { - outline = theme.lighter.term; - theme.light.term - } else if select.track() == Some(track_index) || select.scene() == Some(scene_index) { - outline = theme.darkest.term; - theme.base.term - } else { - theme.dark.term - }, outline) - } - - fn scene_w ( - track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor> - ) -> u16 { - if select.track() == Some(track_index) && let Some(editor) = editor { - (editor.size.w() as usize).max(24).max(track.width) as u16 - } else { - track.width as u16 - } - } - - fn scene_y ( - select: &Selection, scene_index: usize, editor: Option<&MidiEditor> - ) -> u16 { - if select.scene() == Some(scene_index) && let Some(editor) = editor { - editor.size.h().max(12) - } else { - H_SCENE as u16 - } - } - - fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool { - editing && select.track() == Some(track_index) && select.scene() == Some(scene_index) - } - } - - pub fn view_scene_name ( - select: &Selection, - editor: Option<&MidiEditor>, - index: usize, - scene: &Scene, - editing: bool - ) -> impl Draw { - let h = if select.scene() == Some(index) && let Some(_editor) = editor { - 7 - } else { - H_SCENE as u16 - }; - let a = east(format!("·s{index:02} "), - fg(g(255), bold(true, &scene.name))).align_w().full_w(); - let b = when(select.scene() == Some(index) && editing, south( - editor.as_ref().map(|e|e.clip_status()), - editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); - let c = if select.scene() == Some(index) { - scene.color.light.term - } else { - scene.color.base.term - }; - bg(c, south(a, b).align_nw()).exact_wh(20, h) - } - - pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins)) - } - - pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs)) - } - - pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins())) - } - - pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw { - track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs())) - } - - //pub fn view_track_per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( - //tracks: impl Fn() -> U + Send + Sync + 'a, - //callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a - //) -> impl Draw { - //} - /// ``` /// let _ = tek::button_2("", "", true); /// let _ = tek::button_2("", "", false); diff --git a/tengri b/tengri index 8a6eb19e..b7f4d55e 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 8a6eb19e279afefc126008c27b38cc66645e1c96 +Subproject commit b7f4d55e1d67d3481ecee14f693ca3e0a9426a6c