mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
749 lines
24 KiB
Rust
749 lines
24 KiB
Rust
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>>>,
|
||
/// Last track number (to avoid duplicate port names)
|
||
pub track_last: usize,
|
||
/// 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)> {
|
||
self.track_last += 1;
|
||
let name: Arc<str> = name.map_or_else(
|
||
||format!("Track{:02}", self.track_last).into(),
|
||
|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) -> Usually<()> {
|
||
let exists = self.tracks().get(index).is_some();
|
||
if exists {
|
||
let track = self.tracks_mut().remove(index);
|
||
let Track { player: MidiPlayer { midi_ins, midi_outs, .. }, .. } = track;
|
||
for port in midi_ins.into_iter() {
|
||
port.close()?;
|
||
}
|
||
for port in midi_outs.into_iter() {
|
||
port.close()?;
|
||
}
|
||
for scene in self.scenes_mut().iter_mut() {
|
||
scene.clips.remove(index);
|
||
}
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 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 (&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),
|
||
}
|