diff --git a/.gitignore b/.gitignore index e5790860..1c54c9b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*/target target/* !target/.gitkeep perf.data* diff --git a/Cargo.lock b/Cargo.lock index 10dda47d..45ccc393 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,6 +368,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + [[package]] name = "cassowary" version = "0.3.0" @@ -3158,6 +3164,7 @@ dependencies = [ name = "tek_proc" version = "0.1.0" dependencies = [ + "case", "proc-macro2", "quote", "syn 2.0.119", diff --git a/proc/Cargo.lock b/proc/Cargo.lock new file mode 100644 index 00000000..dbbc6aa7 --- /dev/null +++ b/proc/Cargo.lock @@ -0,0 +1,54 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tek_proc" +version = "0.1.0" +dependencies = [ + "case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/proc/Cargo.toml b/proc/Cargo.toml index bea4fa22..30d6ccf8 100644 --- a/proc/Cargo.toml +++ b/proc/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [lib] proc-macro = true [dependencies] +case = "1.0.0" proc-macro2 = "1.0.106" quote = "1.0.46" syn = { version = "2.0.119", features = ["full", "extra-traits"] } diff --git a/proc/src/lib.rs b/proc/src/lib.rs index 9ef4169a..602e8f69 100644 --- a/proc/src/lib.rs +++ b/proc/src/lib.rs @@ -3,8 +3,12 @@ use proc_macro2::{TokenStream as TokenStream2, Span}; use quote::{quote, ToTokens, TokenStreamExt}; use std::collections::{HashMap, BTreeMap}; use syn::{ - Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp, - parse::{ParseStream, Parse, Result} + Error, Path, Lit, Ident, Variant, Fields, BinOp, + Expr, ExprPath, ExprBinary, ExprAssign, ExprLit, + ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature, + MetaList, Type, TypePath, FnArg, PatType, + parse::{ParseStream, Parse, Result}, + spanned::Spanned }; macro_rules! attribute { @@ -22,16 +26,114 @@ macro_rules! attribute { } } -attribute!(command { - #[derive(Debug, Clone)] pub struct Def( - pub Meta, pub Item - ); - #[derive(Debug, Clone)] pub struct Meta( - pub Path - ); +attribute!(commands { + #[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item); + #[derive(Debug, Clone)] pub struct Meta(pub Path, pub Lit); #[derive(Debug, Clone)] pub struct Item( - pub ItemEnum, pub HashMap + pub Path, pub ItemImpl, pub HashMap, Lit)> ); + + impl Parse for Meta { + 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, .. }) = &**right + { + Ok(Self(path.clone(), lit.clone())) + } else { + Err(Error::new(meta.span(), format!( + "must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}" + ))) + } + } + } + + impl Parse for Item { + 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, Lit)> = 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, .. }) = &**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)) + } + } + + impl ToTokens for Def { + fn to_tokens (&self, out: &mut TokenStream2) { + let Self(Meta(command, namespace), Item(ident, item, items)) = self; + let mut variants = quote! {}; + let mut dispatch = quote! {}; + for (ident, (variant, inputs, keyword)) in items.iter() { + let mut params = quote! {}; + let mut args = quote! {}; + let mut has_args = false; + for arg in inputs.iter() { + match arg { + FnArg::Receiver(_) => {}, + FnArg::Typed(PatType { pat, ty, .. }) => { + has_args = true; + append(&mut params, quote! { #pat: #ty, }); + append(&mut args, quote! { #pat, }) + } + } + } + if has_args { + params = quote! { { #params } }; + args = quote! { { #args } }; + } + append(&mut variants, quote! { #variant #params, }); + append(&mut dispatch, quote! { #command::#variant #args => { todo!() }, }); + } + append(out, quote! { + #item + pub enum #command { #variants } + impl dizzle::Act<#ident> for #command { + fn act (&self, state: &mut #ident) -> Perhaps { + match self { + #dispatch + _ => unreachable!() + } + } + } + }) + } + } +}); + +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 { fn parse (input: ParseStream) -> Result { Ok(Self(input.parse()?)) diff --git a/src/device/arrange.rs b/src/device/arrange.rs index e86227c6..fa481c92 100644 --- a/src/device/arrange.rs +++ b/src/device/arrange.rs @@ -23,29 +23,64 @@ pub struct Arrangement { /// Selected UI element pub selection: Selection, /// Source of time - #[cfg(feature = "clock")] pub clock: Clock, + #[cfg(feature = "clock")] pub clock: Clock, /// Allows one MIDI clip to be edited - #[cfg(feature = "editor")] pub editor: Option, + #[cfg(feature = "editor")] pub editor: Option, /// List of global midi inputs - #[cfg(feature = "port")] pub midi_ins: Vec, + #[cfg(feature = "port")] pub midi_ins: Vec, /// List of global midi outputs - #[cfg(feature = "port")] pub midi_outs: Vec, + #[cfg(feature = "port")] pub midi_outs: Vec, /// List of global audio inputs - #[cfg(feature = "port")] pub audio_ins: Vec, + #[cfg(feature = "port")] pub audio_ins: Vec, /// List of global audio outputs - #[cfg(feature = "port")] pub audio_outs: Vec, + #[cfg(feature = "port")] pub audio_outs: Vec, /// Last track number (to avoid duplicate port names) #[cfg(feature = "track")] pub track_last: usize, /// List of tracks - #[cfg(feature = "track")] pub tracks: Vec, + #[cfg(feature = "track")] pub tracks: Vec, /// Scroll offset of tracks #[cfg(feature = "track")] pub track_scroll: usize, /// List of scenes - #[cfg(feature = "scene")] pub scenes: Vec, + #[cfg(feature = "scene")] pub scenes: Vec, /// Scroll offset of scenes #[cfg(feature = "scene")] pub scene_scroll: usize, } +def_command!(TrackCommand: |track: Track| { + Stop => { track.sequencer.enqueue_next(None); Ok(None) }, + SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), + SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), + SetMute { mute: Option } => todo!(), + SetSolo { solo: Option } => todo!(), + SetSize { size: usize } => todo!(), + SetZoom { zoom: usize } => todo!(), + SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), + SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), +}); + +def_command!(SceneCommand: |scene: Scene| { + SetSize { size: usize } => { todo!() }, + SetZoom { size: usize } => { todo!() }, + SetName { name: Arc } => + swap_value(&mut scene.name, name, |name|Self::SetName{name}), + SetColor { color: ItemTheme } => + swap_value(&mut scene.color, color, |color|Self::SetColor{color}), +}); + +def_command!(ClipCommand: |clip: MidiClip| { + SetColor { color: Option } => { + //(SetColor [t: usize, s: usize, c: ItemTheme] + //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + todo!() + }, + SetLoop { looping: Option } => { + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } +}); + /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { @@ -333,19 +368,7 @@ impl HasClipsSize for Arrangement { } } } -def_command!(ClipCommand: |clip: MidiClip| { - SetColor { color: Option } => { - //(SetColor [t: usize, s: usize, c: ItemTheme] - //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - todo!() - }, - SetLoop { looping: Option } => { - //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - todo!() - } -}); + impl Arrangement { /// Put a clip in a slot pub fn clip_put ( @@ -528,14 +551,6 @@ impl ScenesView for Arrangement { pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); -def_command!(SceneCommand: |scene: Scene| { - SetSize { size: usize } => { todo!() }, - SetZoom { size: usize } => { todo!() }, - SetName { name: Arc } => - swap_value(&mut scene.name, name, |name|Self::SetName{name}), - SetColor { color: ItemTheme } => - swap_value(&mut scene.color, color, |color|Self::SetColor{color}), -}); #[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()); @@ -847,18 +862,6 @@ impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } } -def_command!(TrackCommand: |track: Track| { - Stop => { track.sequencer.enqueue_next(None); Ok(None) }, - SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), - SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), - SetMute { mute: Option } => todo!(), - SetSolo { solo: Option } => todo!(), - SetSize { size: usize } => todo!(), - SetZoom { zoom: usize } => todo!(), - SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), - SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), -}); - impl>+AsMut>> HasTracks for T {} impl+AsMutOpt+Send+Sync> HasTrack for T {} impl TracksView for T {} diff --git a/src/device/browse.rs b/src/device/browse.rs index 985a98a7..e9bed1cf 100644 --- a/src/device/browse.rs +++ b/src/device/browse.rs @@ -11,6 +11,30 @@ impl App { } } +#[tek_proc::commands(BrowseCommand = "browse")] +impl Browse { + /// Toggle visibility of browser + #[command(Show = "show")] + fn show (&mut self) -> Perhaps { + todo!() + } + /// Set current directory + #[command(SetPath = "set-path")] + fn set_path (&mut self, _path: PathBuf) -> Perhaps { + todo!() + } + /// Set current filter + #[command(SetSearch = "set-search")] + fn set_search (&mut self, _filter: Arc) -> Perhaps { + todo!() + } + /// Set selected item + #[command(SetCursor = "set-cursor")] + fn set_cursor (&mut self, _index: usize) -> Perhaps { + todo!() + } +} + def_command!(FileBrowserCommand: |_browse: Browse|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) @@ -52,40 +76,6 @@ pub(crate) struct EntriesIterator<'a, S: Screen> { ExportClip(Arc>>), } -/// A clip pool. -/// -/// ``` -/// let pool = tek::Pool::default(); -/// ``` -#[derive(Debug)] pub struct Pool { - pub visible: bool, - /// Selected clip - pub clip: AtomicUsize, - /// Mode switch - pub mode: Option, - /// Embedded file browse - #[cfg(feature = "browse")] pub browse: Option, - /// Collection of MIDI clips. - #[cfg(feature = "clip")] pub clips: Arc>>>>, - /// Collection of sound samples. - #[cfg(feature = "sampler")] pub samples: Arc>>>>, -} - -/// Displays and edits clip length. -#[derive(Clone, Debug, Default)] pub struct ClipLength { - /// Pulses per beat (quaver) - pub ppq: usize, - /// Beats per bar - pub bpb: usize, - /// Length of clip in pulses - pub pulses: usize, - /// Selected subdivision - pub focus: Option, -} - -/// Some sort of wrapper again? -pub struct PoolView<'a>(pub &'a Pool); - // Commands supported by [Browse] //#[derive(Debug, Clone, PartialEq)] //pub enum BrowseCommand { @@ -96,198 +86,6 @@ pub struct PoolView<'a>(pub &'a Pool); //Chdir(PathBuf), //Filter(Arc), //} - -/// Modes for clip pool -#[derive(Debug, Clone)] pub enum PoolMode { - /// Renaming a pattern - Rename(usize, Arc), - /// Editing the length of a pattern - Length(usize, usize, ClipLengthFocus), - /// Load clip from disk - Import(usize, Browse), - /// Save clip to disk - Export(usize, Browse), -} - -/// Focused field of `ClipLength` -#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { - /// Editing the number of bars - Bar, - /// Editing the number of beats - Beat, - /// Editing the number of ticks - Tick, -} -has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); -impl_has_clips!(|self: Pool|self.clips); -impl_from!(Pool: |clip:&Arc>|{ - let model = Self::default(); - model.clips.write().unwrap().push(clip.clone()); - model.clip.store(1, Relaxed); - model -}); -impl_default!(Pool: Self { - browse: None, - clip: 0.into(), - clips: Arc::from(RwLock::from(vec![])), - mode: None, - samples: Arc::from(RwLock::from(vec![])), - visible: true, -}); -impl Pool { - pub fn clip_index (&self) -> usize { - self.clip.load(Relaxed) - } - pub fn set_clip_index (&self, value: usize) { - self.clip.store(value, Relaxed); - } - pub fn mode (&self) -> &Option { - &self.mode - } - pub fn mode_mut (&mut self) -> &mut Option { - &mut self.mode - } - pub fn begin_clip_length (&mut self) { - let length = self.clips()[self.clip_index()].read().unwrap().length; - *self.mode_mut() = Some(PoolMode::Length( - self.clip_index(), - length, - ClipLengthFocus::Bar - )); - } - pub fn begin_clip_rename (&mut self) { - let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); - *self.mode_mut() = Some(PoolMode::Rename( - self.clip_index(), - name - )); - } - pub fn begin_import (&mut self) -> Usually<()> { - *self.mode_mut() = Some(PoolMode::Import( - self.clip_index(), - Browse::new(None)? - )); - Ok(()) - } - pub fn begin_export (&mut self) -> Usually<()> { - *self.mode_mut() = Some(PoolMode::Export( - self.clip_index(), - Browse::new(None)? - )); - Ok(()) - } - pub fn new_clip (&self) -> MidiClip { - MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) - } - pub fn cloned_clip (&self) -> MidiClip { - let index = self.clip_index(); - let mut clip = self.clips()[index].read().unwrap().duplicate(); - clip.color = ItemTheme::random_near(clip.color, 0.25); - clip - } - pub fn add_new_clip (&self) -> (usize, Arc>) { - let clip = Arc::new(RwLock::new(self.new_clip())); - let index = { - let mut clips = self.clips.write().unwrap(); - clips.push(clip.clone()); - clips.len().saturating_sub(1) - }; - self.clip.store(index, Relaxed); - (index, clip) - } - pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { - let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); - if let Some(index) = index { - self.clips.write().unwrap().remove(index); - return true - } - false - } -} -impl ClipLengthFocus { - pub fn next (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } - } - pub fn prev (&mut self) { - use ClipLengthFocus::*; - *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } - } -} -impl ClipLength { - pub fn _new (pulses: usize, focus: Option) -> Self { - Self { ppq: PPQ, bpb: 4, pulses, focus } - } - pub fn bars (&self) -> usize { - self.pulses / (self.bpb * self.ppq) - } - pub fn beats (&self) -> usize { - (self.pulses % (self.bpb * self.ppq)) / self.ppq - } - pub fn ticks (&self) -> usize { - self.pulses % self.ppq - } -} - -impl Pool { - fn _todo_usize_ (&self) -> usize { todo!() } - fn _todo_bool_ (&self) -> bool { todo!() } - fn _todo_clip_ (&self) -> MidiClip { todo!() } - fn _todo_path_ (&self) -> PathBuf { todo!() } - fn _todo_color_ (&self) -> ItemColor { todo!() } - fn _todo_str_ (&self) -> Arc { todo!() } - fn _clip_new (&self) -> MidiClip { self.new_clip() } - fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } - fn _clip_index_current (&self) -> usize { 0 } - fn _clip_index_after (&self) -> usize { 0 } - fn _clip_index_previous (&self) -> usize { 0 } - fn _clip_index_next (&self) -> usize { 0 } - fn _color_random (&self) -> ItemColor { ItemColor::random() } -} - -impl<'a> PoolView<'a> { - fn tui (&self) -> impl Draw { - let Self(pool) = self; - //let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into()); - //let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); - //let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); - //let height = pool.clips.read().unwrap().len() as u16; - iter( - ||pool.clips().clone().into_iter(), - move|clip: Arc>, i: usize|{ - let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); - let item_height = 1; - let _item_offset = i as u16 * item_height; - let selected = i == pool.clip_index(); - let b = if selected { color.light.term } else { color.base.term }; - let f = color.lightest.term; - let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; - let length = if false { String::default() } else { format!("{length} ") }; - bg(b, below!( - fg(f, bold(selected, name)).origin_w().full_w(), - fg(f, bold(selected, length)).origin_e().full_w(), - when(selected, bold(true, fg(g(255), "▶"))).origin_w().full_w(), - when(selected, bold(true, fg(g(255), "◀"))).origin_e().full_w(), - )).exact_h(1) - }).origin_n().full_h().exact_w(20) - } -} - -impl ClipLength { - fn tui (&self) -> impl Draw { - use ClipLengthFocus::*; - let bars = format!("{}", self.bars()); - let beats = format!("{}", self.beats()); - let ticks = format!("{:>02}", self.ticks()); - match self.focus { - None => east!(" ", bars, ".", beats, ".", ticks), - Some(Bar) => east!("[", bars, "]", beats, ".", ticks), - Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), - Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), - } - } -} - impl Browse { pub fn new (cwd: Option) -> Usually { let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; @@ -322,9 +120,6 @@ impl Browse { fn _todo_stub_path_buf (&self) -> PathBuf { todo!() } fn _todo_stub_usize (&self) -> usize { todo!() } fn _todo_stub_arc_str (&self) -> Arc { todo!() } -} - -impl Browse { fn tui (&self) -> impl Draw { iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w()) } @@ -369,188 +164,6 @@ impl PartialEq for BrowseTarget { } } -def_command!(BrowseCommand: |browse: Browse| { - SetVisible => Ok(None), - SetPath { address: PathBuf } => Ok(None), - SetSearch { filter: Arc } => Ok(None), - SetCursor { cursor: usize } => Ok(None), -}); - -def_command!(PoolCommand: |pool: Pool| { - // Toggle visibility of pool - Show { visible: bool } => { - pool.visible = *visible; - Ok(Some(Self::Show { visible: !visible })) - }, - // Select a clip from the clip pool - Select { index: usize } => { - pool.set_clip_index(*index); - Ok(None) - }, - // Update the contents of the clip pool - Clip { command: PoolClipCommand } => Ok( - command.act(pool)?.map(|command|Self::Clip{command}) - ), - // Rename a clip - Rename { command: RenameCommand } => Ok( - command.act(pool)?.map(|command|Self::Rename{command}) - ), - // Change the length of a clip - Length { command: CropCommand } => Ok( - command.act(pool)?.map(|command|Self::Length{command}) - ), - // Import from file - Import { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { - command.act(browse)?.map(|command|Self::Import{command}) - } else { - None - }), - // Export to file - Export { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { - command.act(browse)?.map(|command|Self::Export{command}) - } else { - None - }), -}); - -def_command!(PoolClipCommand: |pool: Pool| { - Delete { index: usize } => { - let index = *index; - let clip = pool.clips_mut().remove(index).read().unwrap().clone(); - Ok(Some(Self::Add { index, clip })) - }, - Swap { index: usize, other: usize } => { - let index = *index; - let other = *other; - pool.clips_mut().swap(index, other); - Ok(Some(Self::Swap { index, other })) - }, - Export { index: usize, path: PathBuf } => { - todo!("export clip to midi file"); - }, - Add { index: usize, clip: MidiClip } => { - let index = *index; - let mut index = index; - let clip = Arc::new(RwLock::new(clip.clone())); - let mut clips = pool.clips_mut(); - if index >= clips.len() { - index = clips.len(); - clips.push(clip) - } else { - clips.insert(index, clip); - } - Ok(Some(Self::Delete { index })) - }, - Import { index: usize, path: PathBuf } => { - let index = *index; - let bytes = std::fs::read(&path)?; - let smf = Smf::parse(bytes.as_slice())?; - let mut t = 0u32; - let mut events = vec![]; - for track in smf.tracks.iter() { - for event in track.iter() { - t += event.delta.as_int(); - if let TrackEventKind::Midi { channel, message } = event.kind { - events.push((t, channel.as_int(), message)); - } - } - } - let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); - for event in events.iter() { - clip.notes[event.0 as usize].push(event.2); - } - Ok(Self::Add { index, clip }.act(pool)?) - }, - SetName { index: usize, name: Arc } => { - let index = *index; - let clip = &mut pool.clips_mut()[index]; - let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name.clone(); - Ok(Some(Self::SetName { index, name: old_name })) - }, - SetLength { index: usize, length: usize } => { - let index = *index; - let clip = &mut pool.clips_mut()[index]; - let old_len = clip.read().unwrap().length; - clip.write().unwrap().length = *length; - Ok(Some(Self::SetLength { index, length: old_len })) - }, - SetColor { index: usize, color: ItemColor } => { - let index = *index; - let mut color = ItemTheme::from(*color); - std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color); - Ok(Some(Self::SetColor { index, color: color.base })) - }, -}); - -def_command!(RenameCommand: |pool: Pool| { - Begin => unreachable!(), - Cancel => { - if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { - pool.clips()[clip].write().unwrap().name = old_name.clone().into(); - } - Ok(None) - }, - Confirm => { - if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() { - let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name })) - } - Ok(None) - }, - Set { value: Arc } => { - if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() { - pool.clips()[clip].write().unwrap().name = value.clone(); - } - Ok(None) - }, -}); - -def_command!(CropCommand: |pool: Pool| { - Begin => unreachable!(), - Cancel => { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } Ok(None) }, - Set { length: usize } => { - if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) - = pool.mode_mut().clone() - { - let old_length; - { - let clip = pool.clips()[clip].clone();//.write().unwrap(); - old_length = Some(clip.read().unwrap().length); - clip.write().unwrap().length = *length; - } - *pool.mode_mut() = None; - return Ok(old_length.map(|length|Self::Set { length })) - } - Ok(None) - }, - Next => { - if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.next() }; Ok(None) - }, - Prev => { - if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() }; Ok(None) - }, - Inc => { - if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { - match focus { - ClipLengthFocus::Bar => { *length += 4 * PPQ }, - ClipLengthFocus::Beat => { *length += PPQ }, - ClipLengthFocus::Tick => { *length += 1 }, - } - } - Ok(None) - }, - Dec => { - if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() { - match focus { - ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, - ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, - ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, - } - } - Ok(None) - } -}); - pub fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { let (mut subdirs, mut files) = std::fs::read_dir(dir)? .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ diff --git a/src/device/clock.rs b/src/device/clock.rs index 5cf352b7..e34ae47c 100644 --- a/src/device/clock.rs +++ b/src/device/clock.rs @@ -20,6 +20,32 @@ impl App { } } +def_command!(ClockCommand: |clock: Clock| { + SeekUsec { usec: f64 } => { + clock.playhead.update_from_usec(*usec); Ok(None) }, + SeekSample { sample: f64 } => { + clock.playhead.update_from_sample(*sample); Ok(None) }, + SeekPulse { pulse: f64 } => { + clock.playhead.update_from_pulse(*pulse); Ok(None) }, + SetBpm { bpm: f64 } => Ok(Some( + Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })), + SetQuant { quant: f64 } => Ok(Some( + Self::SetQuant { quant: clock.quant.set(*quant) })), + SetSync { sync: f64 } => Ok(Some( + Self::SetSync { sync: clock.sync.set(*sync) })), + + Play { position: Option } => { + clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ }, + Pause { position: Option } => { + clock.pause_at(*position)?; Ok(None) }, + + TogglePlayback { position: u32 } => Ok(if clock.is_rolling() { + clock.pause_at(Some(*position))?; None + } else { + clock.play_from(Some(*position))?; None + }), +}); + impl +AsMut> HasClock for T {} pub trait HasClock: AsRef + AsMut { fn clock (&self) -> &Clock { self.as_ref() } @@ -118,32 +144,6 @@ pub const NOTE_NAMES: [&str; 128] = [ "C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10", ]; -def_command!(ClockCommand: |clock: Clock| { - SeekUsec { usec: f64 } => { - clock.playhead.update_from_usec(*usec); Ok(None) }, - SeekSample { sample: f64 } => { - clock.playhead.update_from_sample(*sample); Ok(None) }, - SeekPulse { pulse: f64 } => { - clock.playhead.update_from_pulse(*pulse); Ok(None) }, - SetBpm { bpm: f64 } => Ok(Some( - Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })), - SetQuant { quant: f64 } => Ok(Some( - Self::SetQuant { quant: clock.quant.set(*quant) })), - SetSync { sync: f64 } => Ok(Some( - Self::SetSync { sync: clock.sync.set(*sync) })), - - Play { position: Option } => { - clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ }, - Pause { position: Option } => { - clock.pause_at(*position)?; Ok(None) }, - - TogglePlayback { position: u32 } => Ok(if clock.is_rolling() { - clock.pause_at(Some(*position))?; None - } else { - clock.play_from(Some(*position))?; None - }), -}); - impl LaunchSync { pub fn next (&self) -> f64 { note_duration_next(self.get() as usize) as f64 diff --git a/src/device/editor.rs b/src/device/editor.rs index 5b51d7b3..44b50020 100644 --- a/src/device/editor.rs +++ b/src/device/editor.rs @@ -56,6 +56,30 @@ impl App { } } +def_command!(MidiEditCommand: |editor: MidiEditor| { + Show { clip: Option>> } => { + editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) }, + DeleteNote => { + editor.redraw(); todo!() }, + AppendNote { advance: bool } => { + editor.put_note(*advance); editor.redraw(); Ok(None) }, + SetNotePos { pos: usize } => { + editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) }, + SetNoteLen { len: usize } => { + editor.set_note_len(*len); editor.redraw(); Ok(None) }, + SetNoteScroll { scroll: usize } => { + editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) }, + SetTimePos { pos: usize } => { + editor.set_time_pos(*pos); editor.redraw(); Ok(None) }, + SetTimeScroll { scroll: usize } => { + editor.set_time_start(*scroll); editor.redraw(); Ok(None) }, + SetTimeZoom { zoom: usize } => { + editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) }, + SetTimeLock { lock: bool } => { + editor.set_time_lock(*lock); editor.redraw(); Ok(None) }, + // TODO: 1-9 seek markers that by default start every 8th of the clip +}); + /// Contains state for viewing and editing a clip. /// /// ``` @@ -213,30 +237,6 @@ impl MidiRange for T {} impl +AsMutOpt> HasEditor for T {} -def_command!(MidiEditCommand: |editor: MidiEditor| { - Show { clip: Option>> } => { - editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) }, - DeleteNote => { - editor.redraw(); todo!() }, - AppendNote { advance: bool } => { - editor.put_note(*advance); editor.redraw(); Ok(None) }, - SetNotePos { pos: usize } => { - editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) }, - SetNoteLen { len: usize } => { - editor.set_note_len(*len); editor.redraw(); Ok(None) }, - SetNoteScroll { scroll: usize } => { - editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) }, - SetTimePos { pos: usize } => { - editor.set_time_pos(*pos); editor.redraw(); Ok(None) }, - SetTimeScroll { scroll: usize } => { - editor.set_time_start(*scroll); editor.redraw(); Ok(None) }, - SetTimeZoom { zoom: usize } => { - editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) }, - SetTimeLock { lock: bool } => { - editor.set_time_lock(*lock); editor.redraw(); Ok(None) }, - // TODO: 1-9 seek markers that by default start every 8th of the clip -}); - 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 new file mode 100644 index 00000000..63a24f03 --- /dev/null +++ b/src/device/pool.rs @@ -0,0 +1,470 @@ +use crate::*; + +/// A clip pool. +/// +/// ``` +/// let pool = tek::Pool::default(); +/// ``` +#[derive(Debug)] pub struct Pool { + pub visible: bool, + /// Selected clip + pub clip: AtomicUsize, + /// Mode switch + pub mode: Option, + /// Embedded file browse + #[cfg(feature = "browse")] pub browse: Option, + /// Collection of MIDI clips. + #[cfg(feature = "clip")] pub clips: Arc>>>>, + /// Collection of sound samples. + #[cfg(feature = "sampler")] pub samples: Arc>>>>, +} + +/// Displays and edits clip length. +#[derive(Clone, Debug, Default)] pub struct ClipLength { + /// Pulses per beat (quaver) + pub ppq: usize, + /// Beats per bar + pub bpb: usize, + /// Length of clip in pulses + pub pulses: usize, + /// Selected subdivision + pub focus: Option, +} + +/// Some sort of wrapper again? +pub struct PoolView<'a>(pub &'a Pool); + +/// Modes for clip pool +#[derive(Debug, Clone)] +pub enum PoolMode { + /// Renaming a pattern + Rename(usize, Arc), + /// Editing the length of a pattern + Length(usize, usize, ClipLengthFocus), + /// Load clip from disk + Import(usize, Browse), + /// Save clip to disk + Export(usize, Browse), +} + +/// Focused field of `ClipLength` +#[derive(Copy, Clone, Debug)] +pub enum ClipLengthFocus { + /// Editing the number of bars + Bar, + /// Editing the number of beats + Beat, + /// Editing the number of ticks + Tick, +} + +#[tek_proc::commands(PoolCommand = "pool")] +impl Pool { + + #[command(Show = "show")] + /// Toggle visibility of pool + fn show (&mut self, visible: bool) -> Perhaps { + self.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); + 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 clip to pool from file + #[command(Import = "import")] + fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps { + let bytes = std::fs::read(&path)?; + let smf = Smf::parse(bytes.as_slice())?; + let mut t = 0u32; + let mut events = vec![]; + for track in smf.tracks.iter() { + for event in track.iter() { + t += event.delta.as_int(); + if let TrackEventKind::Midi { channel, message } = event.kind { + events.push((t, channel.as_int(), message)); + } + } + } + let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); + for event in events.iter() { + clip.notes[event.0 as usize].push(event.2); + } + 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 + }) + } + + /// 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(); + 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); + Ok(Some(PoolCommand::Swap { index, other })) + } + + /// Add clip to pool + #[command(Add = "add")] + 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(); + if index >= clips.len() { + index = clips.len(); + clips.push(clip) + } else { + clips.insert(index, clip); + } + Ok(Some(PoolCommand::Delete { index })) + } + + /// 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 old_name = clip.read().unwrap().name.clone(); + clip.write().unwrap().name = name.clone(); + Ok(Some(PoolCommand::SetName { index, name: old_name })) + } + + /// 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 old_len = clip.read().unwrap().length; + clip.write().unwrap().length = length; + Ok(Some(PoolCommand::SetLength { index, length: old_len })) + } + + /// Set color of clip + #[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); + 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 + )); + 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; + } + Ok(None) + } + + #[command(Set = "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() + { + let old_length; + { + let clip = self.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 })) + } + 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(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(Inc = "inc")] + fn crop_inc (&mut self) -> Perhaps { + if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = self.mode_mut().clone() { + match focus { + ClipLengthFocus::Bar => { *length += 4 * PPQ }, + ClipLengthFocus::Beat => { *length += PPQ }, + ClipLengthFocus::Tick => { *length += 1 }, + } + } + 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() { + match focus { + ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, + ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, + ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, + } + } + 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 + )); + 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(); + } + 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() { + let old_name = old_name.clone(); + *self.mode_mut() = None; + Some(RenameCommand::Set { 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(); + } + Ok(None) + } + +} + +has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); + +impl_has_clips!(|self: Pool|self.clips); + +impl_from!(Pool: |clip:&Arc>|{ + let model = Self::default(); + model.clips.write().unwrap().push(clip.clone()); + model.clip.store(1, Relaxed); + model +}); + +impl_default!(Pool: Self { + browse: None, + clip: 0.into(), + clips: Arc::from(RwLock::from(vec![])), + mode: None, + samples: Arc::from(RwLock::from(vec![])), + visible: true, +}); + +impl Pool { + pub fn clip_index (&self) -> usize { + self.clip.load(Relaxed) + } + pub fn set_clip_index (&self, value: usize) { + self.clip.store(value, Relaxed); + } + pub fn mode (&self) -> &Option { + &self.mode + } + pub fn mode_mut (&mut self) -> &mut Option { + &mut self.mode + } + pub fn begin_import (&mut self) -> Usually<()> { + *self.mode_mut() = Some(PoolMode::Import( + self.clip_index(), + Browse::new(None)? + )); + Ok(()) + } + pub fn begin_export (&mut self) -> Usually<()> { + *self.mode_mut() = Some(PoolMode::Export( + self.clip_index(), + Browse::new(None)? + )); + Ok(()) + } + pub fn new_clip (&self) -> MidiClip { + MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) + } + pub fn cloned_clip (&self) -> MidiClip { + let index = self.clip_index(); + let mut clip = self.clips()[index].read().unwrap().duplicate(); + clip.color = ItemTheme::random_near(clip.color, 0.25); + clip + } + pub fn add_new_clip (&self) -> (usize, Arc>) { + let clip = Arc::new(RwLock::new(self.new_clip())); + let index = { + let mut clips = self.clips.write().unwrap(); + clips.push(clip.clone()); + clips.len().saturating_sub(1) + }; + self.clip.store(index, Relaxed); + (index, clip) + } + pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { + let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); + if let Some(index) = index { + self.clips.write().unwrap().remove(index); + return true + } + false + } +} + +impl ClipLengthFocus { + pub fn next (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } + } + pub fn prev (&mut self) { + use ClipLengthFocus::*; + *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } + } +} + +impl ClipLength { + pub fn _new (pulses: usize, focus: Option) -> Self { + Self { ppq: PPQ, bpb: 4, pulses, focus } + } + pub fn bars (&self) -> usize { + self.pulses / (self.bpb * self.ppq) + } + pub fn beats (&self) -> usize { + (self.pulses % (self.bpb * self.ppq)) / self.ppq + } + pub fn ticks (&self) -> usize { + self.pulses % self.ppq + } +} + +impl Pool { + fn _todo_usize_ (&self) -> usize { todo!() } + fn _todo_bool_ (&self) -> bool { todo!() } + fn _todo_clip_ (&self) -> MidiClip { todo!() } + fn _todo_path_ (&self) -> PathBuf { todo!() } + fn _todo_color_ (&self) -> ItemColor { todo!() } + fn _todo_str_ (&self) -> Arc { todo!() } + fn _clip_new (&self) -> MidiClip { self.new_clip() } + fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } + fn _clip_index_current (&self) -> usize { 0 } + fn _clip_index_after (&self) -> usize { 0 } + fn _clip_index_previous (&self) -> usize { 0 } + fn _clip_index_next (&self) -> usize { 0 } + fn _color_random (&self) -> ItemColor { ItemColor::random() } +} + +impl<'a> PoolView<'a> { + fn tui (&self) -> impl Draw { + let Self(pool) = self; + //let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||g(32).into()); + //let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x)); + //let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x); + //let height = pool.clips.read().unwrap().len() as u16; + iter( + ||pool.clips().clone().into_iter(), + move|clip: Arc>, i: usize|{ + let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); + let item_height = 1; + let _item_offset = i as u16 * item_height; + let selected = i == pool.clip_index(); + let b = if selected { color.light.term } else { color.base.term }; + let f = color.lightest.term; + let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; + let length = if false { String::default() } else { format!("{length} ") }; + bg(b, below!( + fg(f, bold(selected, name)).origin_w().full_w(), + fg(f, bold(selected, length)).origin_e().full_w(), + when(selected, bold(true, fg(g(255), "▶"))).origin_w().full_w(), + when(selected, bold(true, fg(g(255), "◀"))).origin_e().full_w(), + )).exact_h(1) + }).origin_n().full_h().exact_w(20) + } +} + +impl ClipLength { + fn tui (&self) -> impl Draw { + use ClipLengthFocus::*; + let bars = format!("{}", self.bars()); + let beats = format!("{}", self.beats()); + let ticks = format!("{:>02}", self.ticks()); + match self.focus { + None => east!(" ", bars, ".", beats, ".", ticks), + Some(Bar) => east!("[", bars, "]", beats, ".", ticks), + Some(Beat) => east!(" ", bars, "[", beats, "]", ticks), + Some(Tick) => east!(" ", bars, ".", beats, "[", ticks), + } + } +} diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 6e8fa0a8..7da83c1d 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -8,6 +8,53 @@ pub(crate) use symphonia::{ }, }; +def_command!(SamplerCommand: |sampler: Sampler| { + RecordToggle { slot: usize } => { + let slot = *slot; + let recording = sampler.recording.as_ref().map(|x|x.0); + let _ = Self::RecordFinish.act(sampler)?; + // autoslice: continue recording at next slot + if recording != Some(slot) { + Self::RecordBegin { slot }.act(sampler) + } else { + Ok(None) + } + }, + RecordBegin { slot: usize } => { + let slot = *slot; + sampler.recording = Some(( + slot, + Some(Arc::new(RwLock::new(Sample::new( + "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] + )))) + )); + Ok(None) + }, + RecordFinish => { + let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ + std::mem::swap(sample, &mut sampler.samples.0[*index]); + sample + }); // TODO: undo + Ok(None) + }, + RecordCancel => { + sampler.recording = None; + Ok(None) + }, + PlaySample { slot: usize } => { + let slot = *slot; + if let Some(ref sample) = sampler.samples.0[slot] { + sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); + } + Ok(None) + }, + StopSample { slot: usize } => { + let _slot = *slot; + todo!(); + //Ok(None) + }, +}); + /// Plays [Voice]s from [Sample]s. /// /// ``` @@ -354,53 +401,6 @@ fn draw_sample ( Ok(label1.len() + label2.len() + 4) } -def_command!(SamplerCommand: |sampler: Sampler| { - RecordToggle { slot: usize } => { - let slot = *slot; - let recording = sampler.recording.as_ref().map(|x|x.0); - let _ = Self::RecordFinish.act(sampler)?; - // autoslice: continue recording at next slot - if recording != Some(slot) { - Self::RecordBegin { slot }.act(sampler) - } else { - Ok(None) - } - }, - RecordBegin { slot: usize } => { - let slot = *slot; - sampler.recording = Some(( - slot, - Some(Arc::new(RwLock::new(Sample::new( - "Sample", 0, 0, vec![vec![];sampler.audio_ins.len()] - )))) - )); - Ok(None) - }, - RecordFinish => { - let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ - std::mem::swap(sample, &mut sampler.samples.0[*index]); - sample - }); // TODO: undo - Ok(None) - }, - RecordCancel => { - sampler.recording = None; - Ok(None) - }, - PlaySample { slot: usize } => { - let slot = *slot; - if let Some(ref sample) = sampler.samples.0[slot] { - sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); - } - Ok(None) - }, - StopSample { slot: usize } => { - let _slot = *slot; - todo!(); - //Ok(None) - }, -}); - /// A currently playing instance of a sample. #[derive(Default, Debug, Clone)] pub struct Voice { pub sample: Arc>, diff --git a/src/tek.rs b/src/tek.rs index 8dcc90c8..f0f7fedb 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -1312,6 +1312,7 @@ mod device { pub mod arrange; pub use self::arrange::*; pub mod browse; pub use self::browse::*; + pub mod pool; pub use self::pool::*; pub mod clock; pub use self::clock::*; pub mod dialog; pub use self::dialog::*; pub mod editor; pub use self::editor::*;