use crate::*; #[derive(Debug)] pub struct Pool { pub visible: bool, /// Selected clip pub clip: AtomicUsize, /// Mode switch pub mode: Option, /// Collection of clips #[cfg(feature = "clip")] pub clips: Arc>>>>, /// Embedded file browse #[cfg(feature = "browse")] pub browse: Option, } impl Default for Pool { fn default () -> Self { //use PoolMode::*; Self { visible: true, clip: 0.into(), mode: None, clips: Arc::from(RwLock::from(vec![])), browse: None, } } } impl Pool { pub fn clip_index (&self) -> usize { self.clip.load(Relaxed) } pub fn set_clip_index (&self, value: usize) { self.clip.store(value, Relaxed); } pub fn mode (&self) -> &Option { &self.mode } pub fn mode_mut (&mut self) -> &mut Option { &mut self.mode } pub fn begin_clip_length (&mut self) { let length = self.clips()[self.clip_index()].read().unwrap().length; *self.mode_mut() = Some(PoolMode::Length( self.clip_index(), length, ClipLengthFocus::Bar )); } pub fn begin_clip_rename (&mut self) { let name = self.clips()[self.clip_index()].read().unwrap().name.clone(); *self.mode_mut() = Some(PoolMode::Rename( self.clip_index(), name )); } pub fn begin_import (&mut self) -> Usually<()> { *self.mode_mut() = Some(PoolMode::Import( self.clip_index(), Browse::new(None)? )); Ok(()) } pub fn begin_export (&mut self) -> Usually<()> { *self.mode_mut() = Some(PoolMode::Export( self.clip_index(), Browse::new(None)? )); Ok(()) } pub fn new_clip (&self) -> MidiClip { MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) } pub fn cloned_clip (&self) -> MidiClip { let index = self.clip_index(); let mut clip = self.clips()[index].read().unwrap().duplicate(); clip.color = ItemTheme::random_near(clip.color, 0.25); clip } pub fn add_new_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(self.new_clip())); let index = { let mut clips = self.clips.write().unwrap(); clips.push(clip.clone()); clips.len().saturating_sub(1) }; self.clip.store(index, Relaxed); (index, clip) } pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip); if let Some(index) = index { self.clips.write().unwrap().remove(index); return true } false } } /// Modes for clip pool #[derive(Debug, Clone)] pub enum PoolMode { /// Renaming a pattern Rename(usize, Arc), /// Editing the length of a pattern Length(usize, usize, ClipLengthFocus), /// Load clip from disk Import(usize, Browse), /// Save clip to disk Export(usize, Browse), } /// Focused field of `ClipLength` #[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { /// Editing the number of bars Bar, /// Editing the number of beats Beat, /// Editing the number of ticks Tick, } impl ClipLengthFocus { pub fn next (&mut self) { use ClipLengthFocus::*; *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } } pub fn prev (&mut self) { use ClipLengthFocus::*; *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } } } /// Displays and edits clip length. #[derive(Clone)] pub struct ClipLength { /// Pulses per beat (quaver) ppq: usize, /// Beats per bar bpb: usize, /// Length of clip in pulses pulses: usize, /// Selected subdivision pub focus: Option, } impl ClipLength { pub fn _new (pulses: usize, focus: Option) -> Self { Self { ppq: PPQ, bpb: 4, pulses, focus } } pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) } pub fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq } pub fn ticks (&self) -> usize { self.pulses % self.ppq } pub fn bars_string (&self) -> Arc { format!("{}", self.bars()).into() } pub fn beats_string (&self) -> Arc { format!("{}", self.beats()).into() } pub fn ticks_string (&self) -> Arc { format!("{:>02}", self.ticks()).into() } } pub type ClipPool = Vec>>; pub trait HasClips { fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>; fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>; fn add_clip (&self) -> (usize, Arc>) { let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None))); self.clips_mut().push(clip.clone()); (self.clips().len() - 1, clip) } } #[macro_export] macro_rules! has_clips { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { $cb.read().unwrap() } fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { $cb.write().unwrap() } } } } has_clips!(|self: Pool|self.clips); has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone())); from!(|clip:&Arc>|Pool = { let model = Self::default(); model.clips.write().unwrap().push(clip.clone()); model.clip.store(1, Relaxed); model }); 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() } } 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) } }); pub struct PoolView<'a>(pub &'a Pool); impl<'a> HasContent for PoolView<'a> { fn content (&self) -> impl Content { 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;//Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, x)); //let border = |x|x;//Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)).enclose(x); //let height = pool.clips.read().unwrap().len() as u16; Fixed::X(20, Fill::Y(Align::n(Map::new( ||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.rgb } else { color.base.rgb }; let fg = color.lightest.rgb; let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; let length = if false { String::default() } else { format!("{length} ") }; Fixed::Y(1, map_south(item_offset, item_height, Tui::bg(bg, lay!( Fill::X(Align::w(Tui::fg(fg, Tui::bold(selected, name)))), Fill::X(Align::e(Tui::fg(fg, Tui::bold(selected, length)))), Fill::X(Align::w(When::new(selected, Tui::bold(true, Tui::fg(Tui::g(255), "▶"))))), Fill::X(Align::e(When::new(selected, Tui::bold(true, Tui::fg(Tui::g(255), "◀"))))), )))) })))) } } impl HasContent for ClipLength { fn content (&self) -> impl Content + '_ { use ClipLengthFocus::*; let bars = ||self.bars_string(); let beats = ||self.beats_string(); let ticks = ||self.ticks_string(); match self.focus { None => row!(" ", bars(), ".", beats(), ".", ticks()), Some(Bar) => row!("[", bars(), "]", beats(), ".", ticks()), Some(Beat) => row!(" ", bars(), "[", beats(), "]", ticks()), Some(Tick) => row!(" ", bars(), ".", beats(), "[", ticks()), } } } //take!(BrowseCommand |state: Pool, iter|Ok(state.browse.as_ref() //.map(|p|Take::take(p, iter)) //.transpose()? //.flatten()));