tek/src/track.rs
2026-09-19 10:11:55 +03:00

743 lines
25 KiB
Rust

use crate::*;
/// 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()
}
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a)
) -> impl Draw<Tui> {
iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{
fg_bg(
track.color.lightest.term,
track.color.base.term,
callback(index, track)
).exact_w((x2 - x1) as u16)
}))
}
pub fn stop (&mut self) -> Perhaps<TrackCommand> {
self.sequencer.enqueue_next(None);
Ok(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; }
}
}
impl<T: AsRefOpt<Track> + AsMutOpt<Track>> HasTrack for T {}
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()
}
}
impl<T: HasTrack
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
+ Namespace<Option<bool>>
> TrackController for T {}
#[tek_proc::commands(TrackCommand)]
pub trait TrackController: HasTrack
+ Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
+ Namespace<Option<bool>>
{
#[command(Stop = "track/stop")]
fn track_stop (&mut self) -> Perhaps<TrackCommand> {
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
Ok(None)
}
#[command(SetMute = "track/mute")]
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCommand> {
todo!()
}
#[command(SetSolo = "track/solo")]
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCommand> {
todo!()
}
#[command(SetSize = "track/size")]
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCommand> {
todo!()
}
#[command(SetZoom = "track/zoom")]
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCommand> {
todo!()
}
#[command(SetName = "track/name")]
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCommand> {
self.track_mut()
.map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name }))
.transpose()
.map(Option::flatten)
}
#[command(SetColor = "track/color")]
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCommand> {
self.track_mut()
.map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color }))
.transpose()
.map(Option::flatten)
}
#[command(SetRec = "track/rec")]
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCommand> {
self.track_mut()
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec }))
.transpose()
.map(Option::flatten)
}
#[command(SetMon = "track/mon")]
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCommand> {
self.track_mut()
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon }))
.transpose()
.map(Option::flatten)
}
}
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
track.width as u16
}
def_sizes_iter!(TracksSizes => Track);
impl_has!(Vec<Track>: |self: Project| self.tracks);
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
impl_has!(Sequencer: |self: Track| self.sequencer);
impl<T: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll> HasTracks for T {}
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll {
/// Spacing between tracks.
const TRACK_SPACING: usize = 0;
/// Read-only reference to collection of [Track]s.
fn tracks (&self) -> &Vec<Track> {
self.as_ref()
}
/// Mutable reference to collection of [Track]s.
fn tracks_mut (&mut self) -> &mut Vec<Track> {
self.as_mut()
}
/// Run audio callbacks for every track and every device
fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control
where Self: HasJack<'static>
{
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
}
/// Add multiple tracks
fn tracks_add_many (
&mut self,
count: usize,
width: Option<usize>,
mins: Arc<[Connect]>,
mouts: Arc<[Connect]>,
) -> Usually<()>
where Self: HasJack<'static> + HasScenes
{
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.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1;
if let Some(width) = width {
track.width = width;
}
}
Ok(())
}
/// Add a track
fn tracks_add_one (
&mut self,
name: Option<Arc<str>>,
color: Option<ItemTheme>,
mins: Arc<[Connect]>,
mouts: Arc<[Connect]>,
) -> Usually<(usize, &mut Track)>
where Self: HasJack<'static> + HasScenes
{
let name: Arc<str> = name.map_or_else(
||format!("trk{}", self.tracks_last()).into(),
|x|x.to_string().into()
);
*self.tracks_last_mut() += 1;
let track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(
&name,
self.jack(),
Some(self.clock()),
None,
mins.as_ref(),
mouts.as_ref()
)?,
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]))
}
fn track_longest_name (&self) -> usize {
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
}
}
impl<T: HasTracks
+ HasScenes
+ HasSelection
+ HasJack<'static>
+ Namespace<usize>
+ Namespace<Arc<[Connect]>>
+ Namespace<Option<usize>>
+ Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
> TracksController for T {}
#[tek_proc::commands(TracksCommand)]
pub trait TracksController: HasTracks
+ HasScenes
+ HasSelection
+ HasJack<'static>
+ Namespace<usize>
+ Namespace<Arc<[Connect]>>
+ Namespace<Option<usize>>
+ Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
{
#[command(Stop = "stop")]
/// Stop all playing clips
fn tracks_stop_all (&mut self) -> Perhaps<TracksCommand> {
for track in self.tracks_mut().iter_mut() {
track.sequencer.enqueue_next(None);
}
Ok(None)
}
#[command(Launch = "launch")]
/// Launch multiple clips
fn tracks_launch (
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
) -> Perhaps<TracksCommand> {
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);
}
}
Ok(None)
}
#[command(Add = "add")]
fn tracks_add (&mut self) -> Perhaps<TracksCommand>
where Self: HasScenes + HasJack<'static>
{
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
Ok(None)
}
#[command(Select = "select")]
fn track_select (&mut self) -> Perhaps<TracksCommand> {
let tracks = self.tracks().len();
let select = self.selection_mut().select_track(tracks);
*self.selection_mut() = select;
Ok(None)
}
#[command(Next = "next")]
fn track_select_next (&mut self) -> Perhaps<TracksCommand> {
let tracks = self.tracks().len();
let select = self.selection().select_track_next(tracks);
*self.selection_mut() = select;
Ok(None)
}
#[command(Prev = "prev")]
fn track_select_prev (&mut self) -> Perhaps<TracksCommand> {
let select = self.selection_mut().select_track_prev();
*self.selection_mut() = select;
Ok(None)
}
}
impl<
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
> TracksView for T {}
pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack {
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let _editor_width = self.editor().as_ref().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
}
})
}
/// Draw name of each track
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_names_count(),
self.view_track_names_add(),
self.view_track_names_cells()
)
}
fn view_track_names_add (&self) -> impl Draw<Tui> {
south(self.view_track_add(), self.view_scene_add())
}
fn view_track_names_count (&self) -> impl Draw<Tui> {
south(self.view_track_count(), self.view_scene_count())
}
fn view_track_names_cells <'a> (&'a self) -> impl Draw<Tui> {
iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{
let b = if self.selection().track() == Some(index) {
track.color.light.term
} else {
track.color.base.term
};
bg(b, south(
east!(
"·t",
index,
" ",
fg(Rgb(255, 255, 255), bold(true, &track.name))
)
.align_nw()
.full_w(),
""
))
.exact_w(track_width(index, track))
.exact_h(2)
}))
}
/// Draw outputs per track
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_output_count(),
self.view_track_output_add(),
self.view_track_output_cells()
)
}
fn view_track_output_cells <'a> (&'a self) -> impl Draw<Tui> {
iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{
let f = Rgb(255, 255, 255);
let b = track.color.dark.term;
iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{
fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1))
}))
.full_h()
.align_nw()
.exact_w(track_width(index, track))
}))
}
fn view_track_device_count (&self) -> impl Draw<Tui> {
button_3("d", "evice", self.track().map(|t|t.devices.len()).unwrap_or(0), false).min_h(2)
}
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.tracks_devices_height();
let btn1 = self.view_track_device_count();
let btn2 = self.view_track_device_add();
view_track_row_section(theme, btn1, btn2, self.view_track_device_cells(height))
}
fn view_track_device_cells (&self, height: u16) -> impl Draw<Tui> {
iter_east(move||self.tracks_with_sizes()
.enumerate()
.map(move|(index, (_, track, _x1, _x2))|bg(
track.color.dark.term,
iter_south(move||(0..height).map(|_|fg_bg(
ItemTheme::G[32].lightest.term,
ItemTheme::G[32].dark.term,
" · --".align_nw()
).exact_wh(track.width as u16, 2)))
.align_nw()
)
.exact_wh(
Some(track_width(index, track)),
Some(height + 1),
)))
}
fn tracks_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 view_inputs (&self) -> impl Draw<Tui> + '_ {
let title_1 = button_3("i", "nput ", self.midi_ins().len(), false).align_w().exact_wh(20, 1);
let title_2 = self.view_track_input_add().exact_wh(4, 1);
east(title_1, west(title_2, self.view_input_cells()))
}
fn tracks_inputs_height (&self) -> u16 {
let mut height = 0u16;
for track in self.tracks().iter() {
height = height.max(track.sequencer.midi_ins.len() as u16);
}
height
}
/// Draw inputs per track
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_input_count(),
self.view_track_input_add(),
self.view_track_input_cells(self.tracks_inputs_height())
)
}
fn view_track_input_cells (&self, height: u16) -> impl Draw<Tui> {
iter_east(move||self
.tracks_with_sizes()
.map(move|(index, track, _x1, _x2)|{
south(
bg(track.color.base.term,
east!(
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
).align_w().full_w()),
iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg(
Rgb(255, 255, 255),
track.color.dark.term,
east!("·i", index, " ", port.port_name()).align_w().full_w()))))
.align_nw()
.exact_wh(track_width(index, track), height + 1)
})).align_w()
}
fn view_input_cells (&self) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
for (_index, track, x1, _x2) in self.tracks_with_sizes() {
let _ = south(
bg(track.color.dark.term, east!(
either(track.sequencer.monitoring, fg(Green, "mon "), "mon "),
either(track.sequencer.recording, fg(Red, "rec "), "rec "),
either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "),
).exact_w(track.width as u16)).align_w().push_x(x1 as u16),
draw(move |to: &mut Tui|{
for (index, port) in self.midi_ins().as_slice().iter().enumerate() {
let _ = east(
east(
" ● ",
bold(true, fg(Rgb(255,255,255), port.port_name()))
).align_w().exact_w(20),
west(
().exact_w(4),
bg(track.color.darker.term, east!(
either(track.sequencer.monitoring, fg(Green, " ● "), " · "),
either(track.sequencer.recording, fg(Red, " ● "), " · "),
either(track.sequencer.overdub, fg(Yellow, " ● "), " · "),
).exact_w(track.width as u16).align_w())
)
).push_x(index as u16 * 10).exact_h(1).draw(to)?;
}
Ok(None)
})
).draw(to)?;
}
Ok(Some(to.area()))
})
}
fn outputs_height (&self) -> u16 {
let mut h = 1;
for output in self.midi_outs().iter() {
h += 1 + output.connections.len();
}
h as u16
}
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.outputs_height();
let list = south(
button_3("o", "utput", self.midi_outs().len(), false).align_w().full_w().exact_h(1),
bg(Color::Rgb(0, 0, 0), draw(|to: &mut Tui|{
for (_index, port) in self.midi_outs().iter().enumerate() {
east(
east(
" ● ",
fg(Rgb(255,255,255), bold(true, port.port_name()))
).align_w(),
east!(
port.port().get_connections().len(),
"/",
port.connections.len(),
" "
).align_e().full_w().exact_h(1)).full_w().draw(to)?;
for (index, conn) in port.connections.iter().enumerate() {
east!(
" c",
index,
conn.info()
).align_w().full_w().exact_h(1).draw(to)?;
}
}
todo!();
})).align_nw().full_wh().exact_h(height - 1).min_h(1)
);
view_track_row_section(theme, list, self.view_track_output_add(),
bg(theme.darker.term, draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let _ = draw(|to: &mut Tui|{
east(
either(true, fg(Green, "play "), "play "),
either(false, fg(Yellow, "solo "), "solo "),
).align_w().exact_h(1).draw(to)?;
for (_index, port) in self.midi_outs().iter().enumerate() {
east(
either(true, fg(Green, " ● "), " · "),
either(false, fg(Yellow, " ● "), " · "),
).align_w().exact_h(1).draw(to)?;
for (_index, _conn) in port.connections.iter().enumerate() {
"".full_w().exact_h(1).draw(to)?;
}
}
todo!()
}).exact_w(track_width(index, track)).draw(to)?;
}
todo!()
}).align_w().full_w())).exact_h(height)
}
fn view_track_count (&self) -> impl Draw<Tui> {
button_3("t", "rack ", east(
self.selection().track().map(|track|east(track, "/")),
self.tracks().len()
), false)
}
fn view_scene_count (&self) -> impl Draw<Tui> {
button_3("s", "cene ", east(
self.selection().scene().map(|scene|east(scene, "/")),
self.scenes().len()
), false)
}
fn view_track_add (&self) -> impl Draw<Tui> {
button_2("T", "+", false)
}
fn view_scene_add (&self) -> impl Draw<Tui> {
button_2("S", "+", false)
}
fn view_track_input_count (&self) -> impl Draw<Tui> {
button_2("i", "nput", false)
}
fn view_track_output_count (&self) -> impl Draw<Tui> {
button_3("o", "utput", 0, false)
//draw(|to: &mut Tui|{
//for port in self.midi_outs().iter() {
//let _ = port.port_name().align_w().full_w().draw(to)?;
//}
//Ok(Some(XYWH(0, 0, 0, 0)))
//})),
}
fn view_track_output_add (&self) -> impl Draw<Tui> {
button_2("O", "+", false)
}
fn view_track_input_add (&self) -> impl Draw<Tui> {
button_2("I", "+", false)
}
fn view_track_device_add (&self) -> impl Draw<Tui> {
button_2("D", "+", false)
}
}
pub trait HasTrackScroll {
fn track_scroll (&self) -> usize;
fn tracks_last (&self) -> usize;
fn tracks_last_mut (&mut self) -> &mut usize;
}
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 HasTrackScroll for App {
fn track_scroll (&self) -> usize {
self.project.track_scroll()
}
fn tracks_last (&self) -> usize {
self.project.track_last
}
fn tracks_last_mut (&mut self) -> &mut usize {
&mut self.project.track_last
}
}
fn view_track_row_section <'a> (
_theme: ItemTheme,
button: impl Draw<Tui>,
button_add: impl Draw<Tui>,
content: impl Draw<Tui>,
) -> impl Draw<Tui> {
bar(
Split::West,
4u16,
button_add.align_ne().full_wh(),
bar(
Split::East,
20u16,
button.full_wh().align_nw(),
content.align_c().full_wh()
)
)
}
pub fn view_track_header <'a> (theme: ItemTheme, content: impl Draw<Tui>)
-> impl Draw<Tui>
{
bg(theme.darker.term, content.align_e().full_w()).exact_w(12)
}
//pub fn per_track <'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 {
//per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
//}
//pub fn per_track_top <'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 {
//bg(Reset, iter_east(||tracks()
//.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{
//fg_bg(
//track.color.lightest.term,
//track.color.base.term,
//callback(index, track)
//).exact_w((x2 - x1) as u16)
//})).align_x())
//}