tek/crates/app/src/model.rs

730 lines
24 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::*;
#[derive(Default, Debug)]
pub struct Tek {
/// Must not be dropped for the duration of the process
pub jack: Jack,
/// Source of time
pub clock: Clock,
/// Theme
pub color: ItemTheme,
/// Contains all clips in the project
pub pool: Option<MidiPool>,
/// Contains the currently edited MIDI clip
pub editor: Option<MidiEditor>,
/// Contains a render of the project arrangement, redrawn on update.
pub arranger: Arc<RwLock<Buffer>>,
/// List of global midi inputs
pub midi_ins: Vec<JackMidiIn>,
/// List of global midi outputs
pub midi_outs: Vec<JackMidiOut>,
/// List of global audio inputs
pub audio_ins: Vec<JackAudioIn>,
/// List of global audio outputs
pub audio_outs: Vec<JackAudioOut>,
/// Buffer for writing a midi event
pub note_buf: Vec<u8>,
/// Buffer for writing a chunk of midi events
pub midi_buf: Vec<Vec<Vec<u8>>>,
/// List of tracks
pub tracks: Vec<Track>,
/// Scroll offset of tracks
pub track_scroll: usize,
/// List of scenes
pub scenes: Vec<Scene>,
/// Scroll offset of scenes
pub scene_scroll: usize,
/// Selected UI element
pub selected: Selection,
/// Display size
pub size: Measure<TuiOut>,
/// Performance counter
pub perf: PerfModel,
/// Whether in edit mode
pub editing: AtomicBool,
/// Undo history
pub history: Vec<TekCommand>,
/// Port handles
pub ports: std::collections::BTreeMap<u32, Port<Unowned>>,
/// View definition
pub view: SourceIter<'static>,
// Cache of formatted strings
pub view_cache: Arc<RwLock<ViewCache>>,
// Modal overlay
pub modal: Option<Modal>,
// Input keymap
pub keys: InputMap<'static, Self, TekCommand, TuiIn, TokenIter<'static>>
}
impl Tek {
/// Add multiple tracks
pub fn tracks_add (
&mut self,
count: usize,
width: Option<usize>,
mins: &[PortConnect],
mouts: &[PortConnect],
) -> Usually<()> {
let jack = self.jack().clone();
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 mut 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: &[PortConnect],
mouts: &[PortConnect],
) -> Usually<(usize, &mut Track)> {
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
let mut track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
player: MidiPlayer::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]))
}
/// Add and focus a track
pub(crate) fn track_add_focus (&mut self) -> Usually<usize> {
let index = self.track_add(None, None, &[], &[])?.0;
self.selected = match self.selected {
Selection::Track(t) => Selection::Track(index),
Selection::Clip(t, s) => Selection::Clip(index, s),
_ => self.selected
};
Ok(index)
}
/// Delete a track
pub fn track_del (&mut self, index: usize) {
self.tracks_mut().remove(index);
for scene in self.scenes_mut().iter_mut() {
scene.clips.remove(index);
}
}
/// Add multiple scenes
pub fn scenes_add (&mut self, n: usize) -> Usually<()> {
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
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
-> Usually<(usize, &mut Scene)>
{
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]))
}
/// Add and focus an empty scene
pub fn scene_add_focus (&mut self) -> Usually<usize> {
let index = self.scene_add(None, None)?.0;
self.selected = match self.selected {
Selection::Scene(s) => Selection::Scene(index),
Selection::Clip(t, s) => Selection::Clip(t, index),
_ => self.selected
};
Ok(index)
}
/// Enqueue clips from a scene across all tracks
pub fn scene_enqueue (&mut self, scene: usize) {
for track in 0..self.tracks.len() {
self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref());
}
}
pub fn toggle_editor (&mut self, value: Option<bool>) {
let editing = self.is_editing();
let value = value.unwrap_or_else(||!self.is_editing());
self.editing.store(value, Relaxed);
if value {
self.clip_auto_create();
} else {
self.clip_auto_remove();
}
}
pub fn toggle_modal (&mut self, modal: Option<Modal>) {
self.modal = if self.modal == modal {
None
} else {
modal
}
}
// Create new clip in pool when entering empty cell
pub fn clip_auto_create (&mut self) {
if let Some(ref pool) = self.pool
&& let Selection::Clip(t, s) = self.selected
&& let Some(scene) = self.scenes.get_mut(s)
&& let Some(slot) = scene.clips.get_mut(t)
&& slot.is_none()
{
let (index, mut clip) = pool.add_new_clip();
// autocolor: new clip colors from scene and track color
clip.write().unwrap().color = ItemColor::random_near(
self.tracks[t].color.base.mix(
scene.color.base,
0.5
),
0.2
).into();
if let Some(ref mut editor) = self.editor {
editor.set_clip(Some(&clip));
}
*slot = Some(clip);
}
}
// Remove clip from arrangement when exiting empty clip editor
pub fn clip_auto_remove (&mut self) {
if let Some(ref mut pool) = self.pool
&& let Selection::Clip(t, s) = self.selected
&& let Some(scene) = self.scenes.get_mut(s)
&& let Some(slot) = scene.clips.get_mut(t)
&& let Some(clip) = slot.as_mut()
{
let mut swapped = None;
if clip.read().unwrap().count_midi_messages() == 0 {
std::mem::swap(&mut swapped, slot);
}
if let Some(clip) = swapped {
pool.delete_clip(&clip.read().unwrap());
}
}
}
/// Put a clip in a slot
pub(crate) 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(crate) 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
})
}
/// Get the clip pool, if present
pub(crate) fn pool (&self) -> Option<&MidiPool> {
self.pool.as_ref()
}
/// Get the active clip
pub(crate) fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.scene()?.clips.get(self.selected().track()?)?.clone()
}
/// Get the active editor
pub(crate) fn editor (&self) -> Option<&MidiEditor> {
self.editor.as_ref()
}
/// Toggle looping for the active clip
pub(crate) fn toggle_loop (&mut self) {
if let Some(clip) = self.clip() {
clip.write().unwrap().toggle_loop()
}
}
/// Set the selection
pub(crate) fn select (&mut self, s: Selection) {
self.selected = s;
// autoedit: load focused clip in editor.
if let Some(ref mut editor) = self.editor {
editor.set_clip(match self.selected {
Selection::Clip(t, s) if let Some(Some(Some(clip))) = self
.scenes.get(s).map(|s|s.clips.get(t)) => Some(clip),
_ => None
});
}
}
/// Stop all playing clips
pub(crate) fn stop_all (&mut self) {
for track in 0..self.tracks.len() {
self.tracks[track].player.enqueue_next(None);
}
}
/// Launch a clip or scene
pub(crate) fn launch (&mut self) {
match self.selected {
Selection::Track(t) => {
self.tracks[t].player.enqueue_next(None)
},
Selection::Clip(t, s) => {
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref())
},
Selection::Scene(s) => {
for t in 0..self.tracks.len() {
self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref())
}
},
_ => {}
};
}
/// Get the first sampler of the active track
pub fn sampler (&self) -> Option<&Sampler> {
self.track().map(|t|t.sampler(0)).flatten()
}
/// Get the first sampler of the active track
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
self.track_mut().map(|t|t.sampler_mut(0)).flatten()
}
/// Set the color of the selected entity
pub fn set_color (&mut self, palette: Option<ItemTheme>) -> Option<ItemTheme> {
let palette = palette.unwrap_or_else(||ItemTheme::random());
Some(match self.selected {
Selection::Mix => {
let old = self.color;
self.color = palette;
old
},
Selection::Track(t) => {
let old = self.tracks[t].color;
self.tracks[t].color = palette;
old
}
Selection::Scene(s) => {
let old = self.scenes[s].color;
self.scenes[s].color = palette;
old
}
Selection::Clip(t, s) => {
if let Some(ref clip) = self.scenes[s].clips[t] {
let mut clip = clip.write().unwrap();
let old = clip.color;
clip.color = palette;
old
} else {
return None
}
}
})
}
pub(crate) fn add_midi_in (&mut self) -> Usually<()> {
self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?);
Ok(())
}
pub(crate) fn add_midi_out (&mut self) -> Usually<()> {
self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?);
Ok(())
}
}
has_size!(<TuiOut>|self: Tek|&self.size);
has_clock!(|self: Tek|self.clock);
has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips);
has_editor!(|self: Tek|{
editor = self.editor;
editor_w = {
let size = self.size.w();
let editor = self.editor.as_ref().expect("missing editor");
let time_len = editor.time_len().get();
let time_zoom = editor.time_zoom().get().max(1);
(5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16)
};
editor_h = 15;
is_editing = self.editing.load(Relaxed);
});
pub trait HasSelection {
fn selected (&self) -> &Selection;
fn selected_mut (&mut self) -> &mut Selection;
}
/// Various possible modal overlays
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Modal {
Help,
Menu,
}
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
/// The whole mix is selected
#[default] Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl Selection {
pub fn is_mix (&self) -> bool {
matches!(self, Self::Mix)
}
pub fn is_track (&self) -> bool {
matches!(self, Self::Track(_))
}
pub fn is_scene (&self) -> bool {
matches!(self, Self::Scene(_))
}
pub fn is_clip (&self) -> bool {
matches!(self, Self::Clip(_, _))
}
pub fn track (&self) -> Option<usize> {
use Selection::*;
match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None }
}
pub fn track_next (&self, len: usize) -> Self {
match self {
Selection::Mix => Selection::Track(0),
Selection::Track(t) if t + 1 < len => Selection::Track(t + 1),
Selection::Track(t) => Selection::Mix,
Selection::Scene(s) => Selection::Clip(0, *s),
Selection::Clip(t, s) if t + 1 < len => Selection::Clip(t + 1, *s),
Selection::Clip(t, s) => Selection::Scene(*s),
}
}
pub fn track_prev (&self) -> Self {
match self {
Selection::Mix => Selection::Mix,
Selection::Scene(s) => Selection::Scene(*s),
Selection::Track(0) => Selection::Mix,
Selection::Track(t) => Selection::Track(t - 1),
Selection::Clip(0, s) => Selection::Scene(*s),
Selection::Clip(t, s) => Selection::Clip(t - 1, *s),
}
}
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None }
}
pub fn scene_next (&self, len: usize) -> Self {
match self {
Selection::Mix => Selection::Scene(0),
Selection::Track(t) => Selection::Clip(*t, 0),
Selection::Scene(s) if s + 1 < len => Selection::Scene(s + 1),
Selection::Scene(s) => Selection::Mix,
Selection::Clip(t, s) if s + 1 < len => Selection::Clip(*t, s + 1),
Selection::Clip(t, s) => Selection::Track(*t),
}
}
pub fn scene_prev (&self) -> Self {
match self {
Selection::Mix => Selection::Mix,
Selection::Track(t) => Selection::Track(*t),
Selection::Scene(0) => Selection::Mix,
Selection::Scene(s) => Selection::Scene(s - 1),
Selection::Clip(t, 0) => Selection::Track(*t),
Selection::Clip(t, s) => Selection::Clip(*t, s - 1),
}
}
pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc<str> {
format!("{}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
.unwrap_or_else(||"T??".into()),
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
.unwrap_or_else(||"S??".into()),
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
(Some(_), Some(scene)) => match scene.clip(*t) {
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
None => format!("T{t} S{s}: Empty")
},
_ => format!("T{t} S{s}: Empty"),
}
}).into()
}
}
impl HasSelection for Tek {
fn selected (&self) -> &Selection { &self.selected }
fn selected_mut (&mut self) -> &mut Selection { &mut self.selected }
}
pub trait HasScenes: HasSelection + HasEditor + Send + Sync {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
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<str> {
format!("Sc{:3>}", self.scenes().len() + 1).into()
}
}
#[derive(Debug, Default)] pub struct Scene {
/// Name of scene
pub name: Arc<str>,
/// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// 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<RwLock<MidiClip>>> {
match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}
impl HasScenes for Tek {
fn scenes (&self) -> &Vec<Scene> { &self.scenes }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { &mut self.scenes }
}
pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync {
fn midi_ins (&self) -> &Vec<JackMidiIn>;
fn midi_outs (&self) -> &Vec<JackMidiOut>;
fn tracks (&self) -> &Vec<Track>;
fn tracks_mut (&mut self) -> &mut Vec<Track>;
fn track_longest (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
const WIDTH_OFFSET: usize = 1;
fn track_next_name (&self) -> Arc<str> {
format!("Track{:02}", self.tracks().len() + 1).into()
}
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<str>,
/// 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<Device>,
/// Inputs of 1st device
pub audio_ins: Vec<JackAudioIn>,
/// Outputs of last device
pub audio_outs: Vec<JackAudioOut>,
}
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<Self> {
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<Self> {
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<JackMidiIn> { &self.midi_ins }
fn midi_outs (&self) -> &Vec<JackMidiOut> { &self.midi_outs }
fn tracks (&self) -> &Vec<Track> { &self.tracks }
fn tracks_mut (&mut self) -> &mut Vec<Track> { &mut self.tracks }
}
#[derive(Debug)]
pub enum Device {
Sequencer(MidiPlayer),
Sampler(Sampler),
#[cfg(feature="host")]
Plugin(Plugin),
}