diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index 343bc02a..660e0cff 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -2,7 +2,10 @@ use crate::*; mod dialog; pub use self::dialog::*; mod editor; pub use self::editor::*; +mod pool; pub use self::pool::*; mod selection; pub use self::selection::*; +mod track; pub use self::track::*; +mod scene; pub use self::scene::*; #[derive(Default, Debug)] pub struct Tek { @@ -456,426 +459,3 @@ has_editor!(|self: Tek|{ editor_h = 15; is_editing = self.editing.load(Relaxed); }); - -pub trait HasScenes: HasSelection + HasEditor + Send + Sync { - fn scenes (&self) -> &Vec; - fn scenes_mut (&mut self) -> &mut Vec; - fn scene_longest (&self) -> usize { - self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) - } - fn scene (&self) -> Option<&Scene> { - self.selected().scene().and_then(|s|self.scenes().get(s)) - } - fn scene_mut (&mut self) -> Option<&mut Scene> { - self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) - } - fn scene_del (&mut self, index: usize) { - self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index))); - } - /// Set the color of a scene, returning the previous one. - fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { - let scenes = self.scenes_mut(); - let old = scenes[index].color; - scenes[index].color = color; - old - } - /// Generate the default name for a new scene - fn scene_default_name (&self) -> Arc { - format!("Sc{:3>}", self.scenes().len() + 1).into() - } -} - -#[derive(Debug, Default)] pub struct Scene { - /// Name of scene - pub name: Arc, - /// Clips in scene, one per track - pub clips: Vec>>>, - /// Identifying color of scene - pub color: ItemTheme, -} - -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.player().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 } - } -} - -impl HasScenes for Tek { - fn scenes (&self) -> &Vec { &self.scenes } - fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } -} - -pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { - fn midi_ins (&self) -> &Vec; - fn midi_outs (&self) -> &Vec; - fn tracks (&self) -> &Vec; - fn tracks_mut (&mut self) -> &mut Vec; - fn track_longest (&self) -> usize { - self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) - } - const WIDTH_OFFSET: usize = 1; - fn track (&self) -> Option<&Track> { - self.selected().track().and_then(|s|self.tracks().get(s)) - } - fn track_mut (&mut self) -> Option<&mut Track> { - self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) - } - /// Set the color of a track - fn track_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { - let tracks = self.tracks_mut(); - let old = tracks[index].color; - tracks[index].color = color; - old - } - /// Toggle track recording - fn track_toggle_record (&mut self) { - if let Some(t) = self.selected().track() { - let tracks = self.tracks_mut(); - tracks[t-1].player.recording = !tracks[t-1].player.recording; - } - } - /// Toggle track monitoring - fn track_toggle_monitor (&mut self) { - if let Some(t) = self.selected().track() { - let tracks = self.tracks_mut(); - tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring; - } - } -} - -#[derive(Debug, Default)] pub struct Track { - /// Name of track - pub name: Arc, - /// Preferred width of track column - pub width: usize, - /// Identifying color of track - pub color: ItemTheme, - /// MIDI player state - pub player: MidiPlayer, - /// Device chain - pub devices: Vec, - /// Inputs of 1st device - pub audio_ins: Vec, - /// Outputs of last device - pub audio_outs: Vec, -} - -has_clock!(|self: Track|self.player.clock); - -has_player!(|self: Track|self.player); - -impl Track { - pub const MIN_WIDTH: usize = 9; - /// Create a new track containing a sequencer. - pub fn new_sequencer () -> Self { - let mut track = Self::default(); - track.devices.push(Device::Sequencer(MidiPlayer::default())); - track - } - /// Create a new track containing a sequencer and sampler. - pub fn new_groovebox ( - jack: &Jack, - midi_from: &[PortConnect], - audio_from: &[&[PortConnect];2], - audio_to: &[&[PortConnect];2], - ) -> Usually { - let mut track = Self::new_sequencer(); - track.devices.push(Device::Sampler( - Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? - )); - Ok(track) - } - /// Create a new track containing a sampler. - pub fn new_sampler ( - jack: &Jack, - midi_from: &[PortConnect], - audio_from: &[&[PortConnect];2], - audio_to: &[&[PortConnect];2], - ) -> Usually { - let mut track = Self::default(); - track.devices.push(Device::Sampler( - Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? - )); - Ok(track) - } - pub fn width_inc (&mut self) { - self.width += 1; - } - pub fn width_dec (&mut self) { - if self.width > Track::MIN_WIDTH { - self.width -= 1; - } - } - pub fn sequencer (&self, mut nth: usize) -> Option<&MidiPlayer> { - for device in self.devices.iter() { - match device { - Device::Sequencer(s) => if nth == 0 { - return Some(s); - } else { - nth -= 1; - }, - _ => {} - } - } - None - } - 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 HasTracks for Tek { - fn midi_ins (&self) -> &Vec { &self.midi_ins } - fn midi_outs (&self) -> &Vec { &self.midi_outs } - fn tracks (&self) -> &Vec { &self.tracks } - fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } -} - -#[derive(Debug)] -pub struct MidiPool { - pub visible: bool, - /// Collection of clips - pub clips: Arc>>>>, - /// Selected clip - pub clip: AtomicUsize, - /// Mode switch - pub mode: Option, -} - -impl Default for MidiPool { - fn default () -> Self { - use PoolMode::*; - Self { - visible: true, - clips: Arc::from(RwLock::from(vec![])), - clip: 0.into(), - mode: None, - } - } -} - -has_clips!(|self: MidiPool|self.clips); - -has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); - -from!(|clip:&Arc>|MidiPool = { - let model = Self::default(); - model.clips.write().unwrap().push(clip.clone()); - model.clip.store(1, Relaxed); - model -}); - -impl MidiPool { - 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(), - FileBrowser::new(None)? - )); - Ok(()) - } - pub fn begin_export (&mut self) -> Usually<()> { - *self.mode_mut() = Some(PoolMode::Export( - self.clip_index(), - FileBrowser::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, FileBrowser), - /// Save clip to disk - Export(usize, FileBrowser), -} - -/// 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() - } - } - } -} diff --git a/crates/app/src/model/pool.rs b/crates/app/src/model/pool.rs new file mode 100644 index 00000000..5ef5e01b --- /dev/null +++ b/crates/app/src/model/pool.rs @@ -0,0 +1,203 @@ +use crate::*; + +#[derive(Debug)] +pub struct MidiPool { + pub visible: bool, + /// Collection of clips + pub clips: Arc>>>>, + /// Selected clip + pub clip: AtomicUsize, + /// Mode switch + pub mode: Option, +} + +impl Default for MidiPool { + fn default () -> Self { + use PoolMode::*; + Self { + visible: true, + clips: Arc::from(RwLock::from(vec![])), + clip: 0.into(), + mode: None, + } + } +} + +has_clips!(|self: MidiPool|self.clips); + +has_clip!(|self: MidiPool|self.clips().get(self.clip_index()).map(|c|c.clone())); + +from!(|clip:&Arc>|MidiPool = { + let model = Self::default(); + model.clips.write().unwrap().push(clip.clone()); + model.clip.store(1, Relaxed); + model +}); + +impl MidiPool { + 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(), + FileBrowser::new(None)? + )); + Ok(()) + } + pub fn begin_export (&mut self) -> Usually<()> { + *self.mode_mut() = Some(PoolMode::Export( + self.clip_index(), + FileBrowser::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, FileBrowser), + /// Save clip to disk + Export(usize, FileBrowser), +} + +/// 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() + } + } + } +} diff --git a/crates/app/src/model/scene.rs b/crates/app/src/model/scene.rs new file mode 100644 index 00000000..dc501600 --- /dev/null +++ b/crates/app/src/model/scene.rs @@ -0,0 +1,73 @@ +use crate::*; + +#[derive(Debug, Default)] pub struct Scene { + /// Name of scene + pub name: Arc, + /// Clips in scene, one per track + pub clips: Vec>>>, + /// Identifying color of scene + pub color: ItemTheme, +} + +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.player().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 } + } +} + +pub trait HasScenes: HasSelection + HasEditor + Send + Sync { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn scene_longest (&self) -> usize { + self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) + } + fn scene (&self) -> Option<&Scene> { + self.selected().scene().and_then(|s|self.scenes().get(s)) + } + fn scene_mut (&mut self) -> Option<&mut Scene> { + self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) + } + fn scene_del (&mut self, index: usize) { + self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index))); + } + /// Set the color of a scene, returning the previous one. + fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { + let scenes = self.scenes_mut(); + let old = scenes[index].color; + scenes[index].color = color; + old + } + /// Generate the default name for a new scene + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes().len() + 1).into() + } +} + +impl HasScenes for Tek { + fn scenes (&self) -> &Vec { &self.scenes } + fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } +} diff --git a/crates/app/src/model/track.rs b/crates/app/src/model/track.rs new file mode 100644 index 00000000..dc194c1e --- /dev/null +++ b/crates/app/src/model/track.rs @@ -0,0 +1,150 @@ +use crate::*; + +#[derive(Debug, Default)] pub struct Track { + /// Name of track + pub name: Arc, + /// Preferred width of track column + pub width: usize, + /// Identifying color of track + pub color: ItemTheme, + /// MIDI player state + pub player: MidiPlayer, + /// Device chain + pub devices: Vec, + /// Inputs of 1st device + pub audio_ins: Vec, + /// Outputs of last device + pub audio_outs: Vec, +} + +has_clock!(|self: Track|self.player.clock); + +has_player!(|self: Track|self.player); + +impl Track { + pub const MIN_WIDTH: usize = 9; + /// Create a new track containing a sequencer. + pub fn new_sequencer () -> Self { + let mut track = Self::default(); + track.devices.push(Device::Sequencer(MidiPlayer::default())); + track + } + /// Create a new track containing a sequencer and sampler. + pub fn new_groovebox ( + jack: &Jack, + midi_from: &[PortConnect], + audio_from: &[&[PortConnect];2], + audio_to: &[&[PortConnect];2], + ) -> Usually { + let mut track = Self::new_sequencer(); + track.devices.push(Device::Sampler( + Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? + )); + Ok(track) + } + /// Create a new track containing a sampler. + pub fn new_sampler ( + jack: &Jack, + midi_from: &[PortConnect], + audio_from: &[&[PortConnect];2], + audio_to: &[&[PortConnect];2], + ) -> Usually { + let mut track = Self::default(); + track.devices.push(Device::Sampler( + Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? + )); + Ok(track) + } + pub fn width_inc (&mut self) { + self.width += 1; + } + pub fn width_dec (&mut self) { + if self.width > Track::MIN_WIDTH { + self.width -= 1; + } + } + pub fn sequencer (&self, mut nth: usize) -> Option<&MidiPlayer> { + for device in self.devices.iter() { + match device { + Device::Sequencer(s) => if nth == 0 { + return Some(s); + } else { + nth -= 1; + }, + _ => {} + } + } + None + } + 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 + } +} + +pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { + fn midi_ins (&self) -> &Vec; + fn midi_outs (&self) -> &Vec; + fn tracks (&self) -> &Vec; + fn tracks_mut (&mut self) -> &mut Vec; + fn track_longest (&self) -> usize { + self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) + } + const WIDTH_OFFSET: usize = 1; + fn track (&self) -> Option<&Track> { + self.selected().track().and_then(|s|self.tracks().get(s)) + } + fn track_mut (&mut self) -> Option<&mut Track> { + self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) + } + /// Set the color of a track + fn track_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { + let tracks = self.tracks_mut(); + let old = tracks[index].color; + tracks[index].color = color; + old + } + /// Toggle track recording + fn track_toggle_record (&mut self) { + if let Some(t) = self.selected().track() { + let tracks = self.tracks_mut(); + tracks[t-1].player.recording = !tracks[t-1].player.recording; + } + } + /// Toggle track monitoring + fn track_toggle_monitor (&mut self) { + if let Some(t) = self.selected().track() { + let tracks = self.tracks_mut(); + tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring; + } + } +} + +impl HasTracks for Tek { + fn midi_ins (&self) -> &Vec { &self.midi_ins } + fn midi_outs (&self) -> &Vec { &self.midi_outs } + fn tracks (&self) -> &Vec { &self.tracks } + fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } +}