use crate::*; 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() } } } } #[derive(Debug)] pub struct MidiPool { pub visible: bool, /// Collection of clips pub clips: Arc>>>>, /// Selected clip pub clip: AtomicUsize, /// Mode switch pub mode: Option, } /// 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, FileBrowser), /// Save clip to disk Export(usize, FileBrowser), } impl Default for MidiPool { fn default () -> Self { Self { visible: true, clips: Arc::from(RwLock::from(vec![])), clip: 0.into(), mode: None, } } } from!(|clip:&Arc>|MidiPool = { let model = Self::default(); model.clips.write().unwrap().push(clip.clone()); model.clip.store(1, Relaxed); model }); has_clips!(|self: MidiPool|self.clips); has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); impl MidiPool { pub(crate) fn clip_index (&self) -> usize { self.clip.load(Relaxed) } pub(crate) fn set_clip_index (&self, value: usize) { self.clip.store(value, Relaxed); } pub(crate) fn mode (&self) -> &Option { &self.mode } pub(crate) fn mode_mut (&mut self) -> &mut Option { &mut self.mode } pub fn file_picker (&self) -> Option<&FileBrowser> { match self.mode { Some(PoolMode::Import(_, ref file_picker)) => Some(file_picker), Some(PoolMode::Export(_, ref file_picker)) => Some(file_picker), _ => None } } 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(), FileBrowser::new(None)? )); Ok(()) } pub fn begin_export (&mut self) -> Usually<()> { *self.mode_mut() = Some(PoolMode::Export( self.clip_index(), FileBrowser::new(None)? )); Ok(()) } } /// Displays and edits clip length. #[derive(Clone)] 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, } 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() } } /// 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) { *self = match self { Self::Bar => Self::Beat, Self::Beat => Self::Tick, Self::Tick => Self::Bar, } } pub fn prev (&mut self) { *self = match self { Self::Bar => Self::Tick, Self::Beat => Self::Bar, Self::Tick => Self::Beat, } } } pub struct PoolView<'a>(pub bool, pub &'a MidiPool); content!(TuiOut: |self: PoolView<'a>| { let Self(compact, model) = self; let MidiPool { clips, mode, .. } = self.1; let color = self.1.clip().map(|c|c.read().unwrap().color).unwrap_or_else(||TuiTheme::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 iter = | |model.clips().clone().into_iter(); Tui::bg(Color::Reset, Fixed::y(clips.read().unwrap().len() as u16, on_bg(border(Map::new(iter, move|clip, i|{ let item_height = 1; let item_offset = i as u16 * item_height; let selected = i == model.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 *compact { format!(" {i:>3}") } else { format!(" {i:>3} {name}") }; let length = if *compact { 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(TuiTheme::g(255), "▶"))))), Fill::x(Align::e(When::new(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀"))))), )))) }))))) }); content!(TuiOut: |self: ClipLength| { let bars = ||self.bars_string(); let beats = ||self.beats_string(); let ticks = ||self.ticks_string(); match self.focus { None => row!(" ", bars(), ".", beats(), ".", ticks()), Some(ClipLengthFocus::Bar) => row!("[", bars(), "]", beats(), ".", ticks()), Some(ClipLengthFocus::Beat) => row!(" ", bars(), "[", beats(), "]", ticks()), Some(ClipLengthFocus::Tick) => row!(" ", bars(), ".", beats(), "[", ticks()), } });