tek/src/device/arrange.rs
2026-07-26 21:50:13 +03:00

900 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use crate::*;
/// Arranger.
///
/// ```
/// let arranger = tek::Arrangement::default();
/// ```
#[derive(Default, Debug)]
pub struct Arrangement {
/// Project name.
pub name: Arc<str>,
/// Base color.
pub color: ItemTheme,
/// JACK client handle.
pub jack: Jack<'static>,
/// FIXME a render of the project arrangement, redrawn on update.
/// TODO rename to "render_cache" or smth
pub arranger: Arc<RwLock<Buffer>>,
/// Display size
pub size: Sizer,
/// Display size of clips area
pub size_inner: Sizer,
/// Selected UI element
pub selection: Selection,
/// Source of time
#[cfg(feature = "clock")] pub clock: Clock,
/// Allows one MIDI clip to be edited
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
/// List of global midi inputs
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
/// List of global midi outputs
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
/// List of global audio inputs
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
/// List of global audio outputs
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
/// Last track number (to avoid duplicate port names)
#[cfg(feature = "track")] pub track_last: usize,
/// List of tracks
#[cfg(feature = "track")] pub tracks: Vec<Track>,
/// Scroll offset of tracks
#[cfg(feature = "track")] pub track_scroll: usize,
/// List of scenes
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
/// Scroll offset of scenes
#[cfg(feature = "scene")] pub scene_scroll: usize,
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
#[default]
/// Nothing is selected
Nothing,
/// The whole mix is selected
Mix,
/// A MIDI input is selected.
Input(usize),
/// A MIDI output is selected.
Output(usize),
/// A scene is selected.
#[cfg(feature = "scene")] Scene(usize),
/// A track is selected.
#[cfg(feature = "track")] Track(usize),
/// A clip (track × scene) is selected.
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected.
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
/// A track's MIDI output connection is selected.
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
/// A track device slot is selected.
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
}
impl Arrangement {
/// Create a new arrangement.
pub fn new (
jack: &Jack<'static>,
name: Arc<str>,
clock: Clock,
tracks: impl Iterator<Item = Track>,
scenes: impl Iterator<Item = Scene>,
midi_ins: impl Iterator<Item = MidiInput>,
midi_outs: impl Iterator<Item = MidiOutput>,
audio_ins: impl Iterator<Item = AudioInput>,
audio_outs: impl Iterator<Item = AudioOutput>,
) -> Self {
Self {
jack: jack.clone(),
color: ItemTheme::random(),
selection: Selection::TrackClip { track: 0, scene: 0 },
tracks: tracks.collect(),
scenes: scenes.collect(),
midi_ins: midi_ins.collect(),
midi_outs: midi_outs.collect(),
audio_ins: audio_ins.collect(),
audio_outs: audio_outs.collect(),
clock,
name,
..Default::default()
}
}
/// Width of display
pub fn w (&self) -> u16 {
self.size.w() as u16
}
/// Width allocated for sidebar.
pub fn w_sidebar (&self, is_editing: bool) -> u16 {
self.w() / if is_editing { 16 } else { 8 } as u16
}
/// Width available to display tracks.
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
self.w().saturating_sub(self.w_sidebar(is_editing))
}
/// Height of display
pub fn h (&self) -> u16 {
self.size.h() as u16
}
/// Height taken by visible device slots.
pub fn h_devices (&self) -> u16 {
2
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
/// Get the first sampler of the active track
#[cfg(feature = "sampler")]
pub fn sampler (&self) -> Option<&Sampler> {
self.selected_track()?.sampler(0)
}
/// Get the first sampler of the active track
#[cfg(feature = "sampler")]
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
self.selected_track_mut()?.sampler_mut(0)
}
}
impl Selection {
pub fn describe (
&self,
#[cfg(feature = "track")] tracks: &[Track],
#[cfg(feature = "scene")] scenes: &[Scene],
) -> Arc<str> {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
#[cfg(feature = "scene")] Scene(s) =>
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
#[cfg(feature = "track")] Track(t) =>
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) {
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
None => format!("T{track} S{scene}: Empty")
},
_ => format!("T{track} S{scene}: Empty"),
},
_ => todo!()
}).into()
}
#[cfg(feature = "scene")] pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
}
#[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self {
use Selection::*;
match self {
Mix | Track(_) => Scene(0),
Scene(s) => Scene((s + 1) % scene_count),
TrackClip { scene, .. } => Track(*scene),
_ => todo!(),
}
}
#[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
Track(t) => TrackClip { track: *t, scene: 0 },
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
_ => todo!()
}
}
#[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
Scene(s) => Scene(s - 1),
Track(t) => Track(*t),
TrackClip { track, scene: 0 } => Track(*track),
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
_ => todo!()
}
}
#[cfg(feature = "track")] pub fn track (&self) -> Option<usize> {
use Selection::*;
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
Some(*track)
} else {
None
}
}
#[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(_) => Mix,
Track(t) => Track((t + 1) % track_count),
TrackClip { track, .. } => Track(*track),
_ => todo!(),
}
}
#[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(s) => TrackClip { track: 0, scene: *s },
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
_ => todo!()
}
}
#[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
Scene(s) => Scene(*s),
Track(0) => Mix,
Track(t) => Track(t - 1),
TrackClip { track: 0, scene } => Scene(*scene),
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
_ => todo!()
}
}
}
impl <T: AsRef<Selection>+AsMut<Selection>> HasSelection for T {}
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
fn selection (&self) -> &Selection { self.as_ref() }
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
/// Get the active track
#[cfg(feature = "track")]
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks().get(index)
}
/// Get a mutable reference to the active track
#[cfg(feature = "track")]
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks_mut().get_mut(index)
}
/// Get the active scene
#[cfg(feature = "scene")]
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes().get(index)
}
/// Get a mutable reference to the active scene
#[cfg(feature = "scene")]
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes_mut().get_mut(index)
}
/// Get the active clip
#[cfg(feature = "clip")]
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
}
}
impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
impl_has!(Sizer: |self: Arrangement| self.size);
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
impl_has!(Clock: |self: Arrangement| self.clock);
impl_has!(Selection: |self: Arrangement| self.selection);
impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
pub trait HasClipsSize {
fn clips_size (&self) -> &Sizer;
}
pub trait ClipsView: TracksView + ScenesView {
/// Draw clips per scene
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
view_scenes_clips(
||self.scenes_with_sizes(),
self.tracks_with_sizes(),
self.selection(),
self.editor(),
self.clips_size(),
self.is_editing(),
)
}
}
impl HasClipsSize for App {
fn clips_size (&self) -> &Sizer { &self.project.size_inner }
}
impl HasClipsSize for Arrangement {
fn clips_size (&self) -> &Sizer { &self.size_inner }
}
/// TODO: Preserve the generic passthru syntax;
/// remove this macro (only used twice) and potentially the trait.
#[macro_export] macro_rules! impl_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()
}
}
}
}
#[macro_export] macro_rules! has_clip {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
}
}
}
def_command!(ClipCommand: |clip: MidiClip| {
SetColor { color: Option<ItemTheme> } => {
//(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<bool> } => {
//(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 (
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
) -> Option<Arc<RwLock<MidiClip>>> {
let old = self.scenes[scene].clips[track].clone();
self.scenes[scene].clips[track] = clip;
old
}
/// Change the color of a clip, returning the previous one
pub fn clip_set_color (
&self, track: usize, scene: usize, color: ItemTheme
) -> Option<ItemTheme> {
self.scenes[scene].clips[track].as_ref().map(|clip|{
let mut clip = clip.write().unwrap();
let old = clip.color.clone();
clip.color = color.clone();
panic!("{color:?} {old:?}");
//old
})
}
/// Toggle looping for the active clip
pub fn toggle_loop (&mut self) {
if let Some(clip) = self.selected_clip() {
clip.write().unwrap().toggle_loop()
}
}
}
/// Default scene height.
pub const H_SCENE: usize = 2;
/// Default editor height.
pub const H_EDITOR: usize = 15;
/// A scene consists of a set of clips to play together.
///
/// ```
/// let scene: tek::Scene = Default::default();
/// let _ = scene.pulses();
/// let _ = scene.is_playing(&[]);
/// ```
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Identifying color of scene
pub color: ItemTheme,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
}
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.sequencer().play_clip() {
*clip.read().unwrap() == *c.read().unwrap()
} else {
false
}
})
.unwrap_or(false),
None => true
})
}
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
fn _todo_usize_stub_ (&self) -> usize { todo!() }
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
}
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16;
fn w_mid (&self) -> u16;
fn view_scenes_names (&self) -> impl Draw<Tui> {
view_scenes_names(
self.scenes_with_sizes(), self.selection(), self.editor(), self.is_editing()
)
}
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
let mut y = 0;
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
let height = if self.selection().scene() == Some(s) && self.editor().is_some() {
8
} else {
H_SCENE
};
if y + height <= self.clips_size().h() as usize {
let data = (s, scene, y, y + height);
y += height;
Some(data)
} else {
None
}
})
}
}
pub trait HasSceneScroll: HasScenes {
fn scene_scroll (&self) -> usize;
}
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() }
fn scene (&self) -> Option<&Scene> { self.as_ref_opt() }
}
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
fn scenes (&self) -> &Vec<Scene> { self.as_ref() }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
/// Generate the default name for a new scene
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) }
/// Add multiple scenes
fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks {
let scene_color_1 = ItemColor::random();
let scene_color_2 = ItemColor::random();
for i in 0..n {
let _ = self.scene_add(None, Some(
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
))?;
}
Ok(())
}
/// Add a scene
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
-> Usually<(usize, &mut Scene)> where Self: HasTracks
{
let scene = Scene {
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
clips: vec![None;self.tracks().len()],
color: color.unwrap_or_else(ItemTheme::random),
};
self.scenes_mut().push(scene);
let index = self.scenes().len() - 1;
Ok((index, &mut self.scenes_mut()[index]))
}
}
impl HasSceneScroll for Arrangement {
fn scene_scroll (&self) -> usize { self.scene_scroll }
}
impl HasSceneScroll for App {
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
}
impl ScenesView for Arrangement {
fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) }
fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) }
fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) }
}
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<str> } =>
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());
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
impl<T: TracksView+ScenesView+Send+Sync> ClipsView for T {}
impl<T: AsRef<Vec<Scene>>+AsMut<Vec<Scene>>> HasScenes for T {}
impl<T: AsRefOpt<Scene>+AsMutOpt<Scene>+Send+Sync> HasScene for T {}
impl ScenesView for App {
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(self.w_side())
}
fn w_side (&self) -> u16 {
20
}
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
}
/// A track consists of a sequencer and zero or more devices chained after it.
///
/// ```
/// let track: tek::Track = Default::default();
/// ```
#[derive(Debug, Default)] pub struct Track {
/// Name of track
pub name: Arc<str>,
/// Identifying color of track
pub color: ItemTheme,
/// Preferred width of track column
pub width: usize,
/// MIDI sequencer state
pub sequencer: Sequencer,
/// Device chain
pub devices: Vec<Device>,
}
impl Track {
/// Create a new track with only the default [Sequencer].
pub fn new (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[Connect],
midi_to: &[Connect],
) -> Usually<Self> {
Ok(Self {
name: name.as_ref().into(),
color: color.unwrap_or_default(),
sequencer: Sequencer::new(
format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to
)?,
..Default::default()
})
}
pub fn audio_ins (&self) -> &[AudioInput] {
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
}
pub fn audio_outs (&self) -> &[AudioOutput] {
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
}
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
fn _todo_usize_stub_ (&self) -> usize { todo!() }
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
) -> impl Draw<Tui> + 'a {
view_track_per(tracks, callback)
}
}
#[cfg(feature = "sampler")]
impl Track {
/// Create a new track connecting the [Sequencer] to a [Sampler].
pub fn new_with_sampler (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[Connect],
midi_to: &[Connect],
audio_from: &[&[Connect];2],
audio_to: &[&[Connect];2],
) -> Usually<Self> {
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
let client_name = jack.with_client(|c|c.name().to_string());
let port_name = track.sequencer.midi_outs[0].port_name();
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
track.devices.push(Device::Sampler(Sampler::new(
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
)?));
Ok(track)
}
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 HasWidth for Track {
const MIN_WIDTH: usize = 9;
fn width_inc (&mut self) { self.width += 1; }
fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } }
}
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> {
fn tracks (&self) -> &Vec<Track> { self.as_ref() }
fn tracks_mut (&mut self) -> &mut Vec<Track> { self.as_mut() }
/// Run audio callbacks for every track and every device
fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control {
for track in self.tracks_mut().iter_mut() {
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
return Control::Quit
}
for device in track.devices.iter_mut() {
if Control::Quit == DeviceAudio(device).process(client, scope) {
return Control::Quit
}
}
}
Control::Continue
}
fn track_longest_name (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
/// Stop all playing clips
fn tracks_stop_all (&mut self) {
for track in self.tracks_mut().iter_mut() {
track.sequencer.enqueue_next(None);
}
}
/// Stop all playing clips
fn tracks_launch (&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>) {
if let Some(clips) = clips {
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
track.sequencer.enqueue_next(clip.as_ref());
}
} else {
for track in self.tracks_mut().iter_mut() {
track.sequencer.enqueue_next(None);
}
}
}
/// Spacing between tracks.
const TRACK_SPACING: usize = 0;
}
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
fn track (&self) -> Option<&Track> { self.as_ref_opt() }
fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() }
#[cfg(feature = "port")]
fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> + 'a {
view_midi_ins_status(theme, self.track())
}
#[cfg(feature = "port")]
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> + '_ {
view_midi_outs_status(theme, self.track())
}
#[cfg(feature = "port")]
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_audio_ins_status(theme, self.track())
}
#[cfg(feature = "port")]
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_audio_outs_status(theme, self.track())
}
}
pub trait HasTrackScroll: HasTracks {
fn track_scroll (&self) -> usize;
}
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
/// Draw name of each track
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_names(
theme, self.tracks_with_sizes(), self.tracks().len(), self.scenes().len(), self.selection()
)
}
/// Draw outputs per track
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
view_track_outputs(
theme, self.tracks_with_sizes(), self.midi_outs().iter()
)
}
/// Draw inputs per track
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
let mut h = 0u16;
for track in self.tracks().iter() {
h = h.max(track.sequencer.midi_ins.len() as u16);
}
view_track_inputs(theme, self.tracks_with_sizes(), h)
}
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let _editor_width = self.editor().map(|e|e.size.w());
let _active_track = self.selection().track();
let mut x = 0;
let w = self.clips_size().w() as usize;
self.tracks().iter().enumerate().map_while(move |(index, track)|{
let width = track.width.max(8);
if x + width < w {
let data = (index, track, x, x + width);
x += width + Self::TRACK_SPACING;
Some(data)
} else {
None
}
})
}
}
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<bool> } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
SetMon { mon: Option<bool> } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
SetMute { mute: Option<bool> } => todo!(),
SetSolo { solo: Option<bool> } => todo!(),
SetSize { size: usize } => todo!(),
SetZoom { zoom: usize } => todo!(),
SetName { name: Arc<str> } => swap_value(&mut track.name, name, |name|Self::SetName { name }),
SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }),
});
impl<T: AsRef<Vec<Track>>+AsMut<Vec<Track>>> HasTracks for T {}
impl<T: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack for T {}
impl<T: ScenesView+HasMidiIns+HasMidiOuts+HasTrackScroll> TracksView for T {}
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
impl Arrangement {
pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
view_inputs(
self.tracks_with_sizes(), self.midi_ins().as_slice()
)
}
pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_outputs(
theme, self.tracks_with_sizes(), self.midi_outs(), self.outputs_height()
)
}
pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_devices(
theme, self.tracks_with_sizes(), self.track(), self.devices_height()
)
}
fn devices_height (&self) -> u16 {
let mut h = 2;
for track in self.tracks().iter() {
h = h.max(track.devices.len() * 2);
}
h as u16
}
fn outputs_height (&self) -> u16 {
let mut h = 1;
for output in self.midi_outs().iter() {
h += 1 + output.connections.len();
}
h as u16
}
/// Add multiple tracks
pub fn tracks_add (
&mut self, count: usize, width: Option<usize>, mins: &[Connect], mouts: &[Connect],
) -> Usually<()> {
let track_color_1 = ItemColor::random();
let track_color_2 = ItemColor::random();
for i in 0..count {
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
let track = self.track_add(None, Some(color), mins, mouts)?.1;
if let Some(width) = width {
track.width = width;
}
}
Ok(())
}
/// Add a track
pub fn track_add (
&mut self,
name: Option<&str>,
color: Option<ItemTheme>,
mins: &[Connect],
mouts: &[Connect],
) -> Usually<(usize, &mut Track)> {
let name: Arc<str> = name.map_or_else(
||format!("trk{:02}", self.track_last).into(),
|x|x.to_string().into()
);
self.track_last += 1;
let track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(
&format!("{name}"),
self.jack(),
Some(self.clock()),
None,
mins,
mouts
)?,
name,
..Default::default()
};
self.tracks_mut().push(track);
let len = self.tracks().len();
let index = len - 1;
for scene in self.scenes_mut().iter_mut() {
while scene.clips.len() < len {
scene.clips.push(None);
}
}
Ok((index, &mut self.tracks_mut()[index]))
}
}
impl HasTrackScroll for App {
fn track_scroll (&self) -> usize {
self.project.track_scroll()
}
}
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
track.width as u16
}