use crate::{*, clock::*, sequence::*, sample::*}; def_command!(FileBrowserCommand: |sampler: Sampler|{ //("begin" [] Some(Self::Begin)) //("cancel" [] Some(Self::Cancel)) //("confirm" [] Some(Self::Confirm)) //("select" [i: usize] Some(Self::Select(i.expect("no index")))) //("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) //("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) }); /// Browses for files to load/save. /// /// ``` /// let browse = tek::Browse::default(); /// ``` #[derive(Debug, Clone, Default, PartialEq)] pub struct Browse { pub cwd: PathBuf, pub dirs: Vec<(OsString, String)>, pub files: Vec<(OsString, String)>, pub filter: String, pub index: usize, pub scroll: usize, pub size: [AtomicUsize; 2], } pub(crate) struct EntriesIterator<'a> { pub browser: &'a Browse, pub offset: usize, pub length: usize, pub index: usize, } #[derive(Clone, Debug)] pub enum BrowseTarget { SaveProject, LoadProject, ImportSample(Arc>>), ExportSample(Arc>>), ImportClip(Arc>>), 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 { //Begin, //Cancel, //Confirm, //Select(usize), //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 } pub fn bars_string (&self) -> Arc { format!("{}", self.bars()).into() } pub fn beats_string (&self) -> Arc { format!("{}", self.beats()).into() } pub fn ticks_string (&self) -> Arc { format!("{:>02}", self.ticks()).into() } } 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(||Tui::g(32).into()); //let on_bg = |x|x;//below(Repeat(" "), Tui::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; w_exact(20, h_full(origin_n(iter( ||pool.clips().clone().into_iter(), move|clip: Arc>, i: usize|{ let item_height = 1; let item_offset = i as u16 * item_height; let selected = i == pool.clip_index(); let MidiClip { ref name, color, length, .. } = *clip.read().unwrap(); let bg = if selected { color.light.term } else { color.base.term }; let fg = color.lightest.term; let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; let length = if false { String::default() } else { format!("{length} ") }; h_exact(1, iter_south(item_offset, item_height, Tui::bg(bg, below!( w_full(origin_w(Tui::fg(fg, Tui::bold(selected, name)))), w_full(origin_e(Tui::fg(fg, Tui::bold(selected, length)))), w_full(origin_w(When::new(selected, Tui::bold(true, Tui::fg(Tui::g(255), "▶"))))), w_full(origin_e(When::new(selected, Tui::bold(true, Tui::fg(Tui::g(255), "◀"))))), )))) })))) } } impl ClipLength { fn tui (&self) -> impl Draw { use ClipLengthFocus::*; let bars = ||self.bars_string(); let beats = ||self.beats_string(); let ticks = ||self.ticks_string(); 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()? }; let mut dirs = vec![]; let mut files = vec![]; for entry in std::fs::read_dir(&cwd)? { let entry = entry?; let name = entry.file_name(); let decoded = name.clone().into_string().unwrap_or_else(|_|"".to_string()); let meta = entry.metadata()?; if meta.is_dir() { dirs.push((name, format!("📁 {decoded}"))); } else if meta.is_file() { files.push((name, format!("📄 {decoded}"))); } } Ok(Self { cwd, dirs, files, ..Default::default() }) } pub fn chdir (&self) -> Usually { Self::new(Some(self.path())) } pub fn len (&self) -> usize { self.dirs.len() + self.files.len() } pub fn is_dir (&self) -> bool { self.index < self.dirs.len() } pub fn is_file (&self) -> bool { self.index >= self.dirs.len() } pub fn path (&self) -> PathBuf { self.cwd.join(if self.is_dir() { &self.dirs[self.index].0 } else if self.is_file() { &self.files[self.index - self.dirs.len()].0 } else { unreachable!() }) } 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(1, ||EntriesIterator { offset: 0, index: 0, length: self.dirs.len() + self.files.len(), browser: self, }, |entry, _index|w_full(origin_w(entry))) } } impl<'a> Iterator for EntriesIterator<'a> { type Item = Modify<&'a str>; fn next (&mut self) -> Option { let dirs = self.browser.dirs.len(); let files = self.browser.files.len(); let index = self.index; if self.index < dirs { self.index += 1; Some(Tui::bold(true, self.browser.dirs[index].1.as_str())) } else if self.index < dirs + files { self.index += 1; Some(Tui::bold(false, self.browser.files[index - dirs].1.as_str())) } else { None } } } impl PartialEq for BrowseTarget { fn eq (&self, other: &Self) -> bool { match self { Self::ImportSample(_) => false, Self::ExportSample(_) => false, Self::ImportClip(_) => false, Self::ExportClip(_) => false, #[allow(unused)] t => matches!(other, t) } } } 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.execute(pool)?.map(|command|Self::Clip{command})), // Rename a clip Rename { command: RenameCommand } => Ok(command.delegate(pool, |command|Self::Rename{command})?), // Change the length of a clip Length { command: CropCommand } => Ok(command.delegate(pool, |command|Self::Length{command})?), // Import from file Import { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { command.delegate(browse, |command|Self::Import{command})? } else { None }), // Export to file Export { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() { command.delegate(browse, |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 }.execute(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) } });