mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
534 lines
17 KiB
Rust
534 lines
17 KiB
Rust
use crate::*;
|
||
/// Arranger.
|
||
///
|
||
/// ```
|
||
/// let arranger = tek::Project::default();
|
||
/// ```
|
||
#[namespace(ClipPool ClipPool::eval)]
|
||
#[derive(Default, Debug)] pub struct Project {
|
||
/// JACK client handle.
|
||
pub jack: Jack<'static>,
|
||
/// Project name.
|
||
pub name: Arc<str>,
|
||
/// Base color.
|
||
pub color: ItemTheme,
|
||
/// 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,
|
||
/// Contains all recently created clips.
|
||
#[cfg(feature = "sequencer")] pub clips: Pool<MidiClip>,
|
||
/// Contains all recently created clips.
|
||
#[cfg(feature = "sampler")] pub samples: Pool<Sample>,
|
||
/// 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,
|
||
}
|
||
|
||
impl Project {
|
||
/// 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(),
|
||
size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())),
|
||
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)
|
||
}
|
||
}
|
||
|
||
impl_has!(Jack<'static>: |self: Project| self.jack);
|
||
impl_has!(Sizer: |self: Project| self.size);
|
||
impl_has!(Clock: |self: Project| self.clock);
|
||
impl_has!(Selection: |self: Project| self.selection);
|
||
impl_has!(Vec<Scene>: |self: Project| self.scenes);
|
||
impl_as_ref_opt!(Scene: |self: Project| self.selected_scene());
|
||
impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
|
||
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
|
||
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut self.editor);
|
||
|
||
impl HasJack<'static> for Project {
|
||
fn jack (&self) -> &Jack<'static> {
|
||
&self.jack
|
||
}
|
||
}
|
||
|
||
impl HasClip for Project {
|
||
fn clip_slot (&self) -> Option<Slot<MidiClip>> {
|
||
match self.selection {
|
||
Selection::Clip(index) =>
|
||
Some(self.clips.slot(index)),
|
||
Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) =>
|
||
Some(scene.clips.slot(track)),
|
||
_ =>
|
||
None
|
||
}
|
||
}
|
||
}
|
||
|
||
impl HasClipsSize for Project {
|
||
fn clips_size (&self) -> &Sizer {
|
||
&self.size_inner
|
||
}
|
||
}
|
||
|
||
impl ScenesView for Project {
|
||
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 fn swap_value <T: Clone + PartialEq, U> (
|
||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||
) -> Perhaps<U> {
|
||
if *target == *value {
|
||
Ok(None)
|
||
} else {
|
||
let mut value = value.clone();
|
||
std::mem::swap(target, &mut value);
|
||
Ok(Some(returned(value)))
|
||
}
|
||
}
|
||
|
||
pub fn toggle_bool <U> (
|
||
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
||
) -> Perhaps<U> {
|
||
let mut value = value.unwrap_or(!*target);
|
||
if value == *target {
|
||
Ok(None)
|
||
} else {
|
||
std::mem::swap(target, &mut value);
|
||
Ok(Some(returned(Some(value))))
|
||
}
|
||
}
|
||
|
||
/// Modes for clip pool
|
||
#[derive(Debug, Clone)] pub enum PoolMode {
|
||
/// Renaming a pattern
|
||
Rename(usize, Arc<str>),
|
||
/// 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, 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<ClipLengthFocus>,
|
||
}
|
||
|
||
impl ClipLength {
|
||
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> 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
|
||
}
|
||
}
|
||
|
||
impl HasTrackScroll for &mut Project {
|
||
fn track_scroll (&self) -> usize {
|
||
self.track_scroll
|
||
}
|
||
fn tracks_last (&self) -> usize {
|
||
self.track_last
|
||
}
|
||
fn tracks_last_mut (&mut self) -> &mut usize {
|
||
&mut self.track_last
|
||
}
|
||
}
|
||
|
||
impl HasTrackScroll for Project {
|
||
fn track_scroll (&self) -> usize {
|
||
self.track_scroll
|
||
}
|
||
fn tracks_last (&self) -> usize {
|
||
self.track_last
|
||
}
|
||
fn tracks_last_mut (&mut self) -> &mut usize {
|
||
&mut self.track_last
|
||
}
|
||
}
|
||
|
||
impl HasSceneScroll for Project {
|
||
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
||
}
|
||
|
||
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
||
|
||
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
|
||
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
|
||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||
|
||
pub struct Junction<T: JackPort>(T);
|
||
|
||
impl<T: JackPort> Draw<Tui> for Junction<T> {
|
||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||
T::KIND.draw(to)
|
||
}
|
||
}
|
||
|
||
impl_as_ref_opt!(Track: |self: Project| self.selected_track());
|
||
impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut());
|
||
impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
|
||
impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
|
||
|
||
/// 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 clip is selected.
|
||
Clip(usize),
|
||
/// A sample is selected.
|
||
Sample(usize),
|
||
/// A scene is selected.
|
||
#[cfg(feature = "scene")] Scene(usize),
|
||
/// A track is selected.
|
||
#[cfg(feature = "track")] Track(usize),
|
||
/// A clip slot (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 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.clips.get(*track) {
|
||
None => format!("T{track} S{scene}: Empty"),
|
||
Some(clip) => format!("T{track} S{scene} C{}",
|
||
clip.as_ref().map(|clip|clip.read().name.clone()).unwrap_or_default()),
|
||
},
|
||
_ => format!("T{track} S{scene}: Empty"),
|
||
},
|
||
_ => todo!()
|
||
}).into()
|
||
}
|
||
|
||
}
|
||
|
||
#[cfg(feature = "scene")]
|
||
impl Selection {
|
||
|
||
pub fn scene (&self) -> Option<usize> {
|
||
use Selection::*;
|
||
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
|
||
}
|
||
|
||
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!(),
|
||
}
|
||
}
|
||
|
||
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!()
|
||
}
|
||
}
|
||
|
||
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")]
|
||
impl Selection {
|
||
|
||
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
|
||
}
|
||
}
|
||
|
||
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!(),
|
||
}
|
||
}
|
||
|
||
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!()
|
||
}
|
||
}
|
||
|
||
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: HasSelection> SelectionModel for T {}
|
||
|
||
pub trait SelectionModel: HasSelection {
|
||
/// 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<Slot<MidiClip>> where Self: HasScenes + HasTracks {
|
||
self.selected_scene()?.clips.read().get(self.selection().track()?).cloned()
|
||
}
|
||
}
|
||
|
||
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
|
||
//let model = Self::default();
|
||
//model.clips.try_write().unwrap().push(clip.clone());
|
||
//model.clip.store(1, Relaxed);
|
||
//model
|
||
//});
|
||
//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<PoolMode> {
|
||
//&self.mode
|
||
//}
|
||
//pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
|
||
//&mut self.mode
|
||
//}
|
||
//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.read()[index].read().duplicate();
|
||
//clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||
//clip
|
||
//}
|
||
//pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||
//let clip = Arc::new(RwLock::new(self.new_clip()));
|
||
//let index = {
|
||
//let mut clips = self.clips.try_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.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip);
|
||
//if let Some(index) = index {
|
||
//self.clips.try_write().unwrap().remove(index);
|
||
//return true
|
||
//}
|
||
//false
|
||
//}
|
||
//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() }
|
||
//}
|