mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 03:36:41 +01:00
Some checks failed
/ build (push) Has been cancelled
the long-standing architectural issues around how the Draw, Layout, and Content traits from Tengri should, well, actually work - that has subsided for now. somewhat. going to amend this commit with fixes to the remaining import not founds, and then... what?
365 lines
16 KiB
Rust
365 lines
16 KiB
Rust
use crate::*;
|
|
|
|
def_sizes_iter!(TracksSizes => Track);
|
|
|
|
impl<T: Has<Vec<Track>> + Send + Sync> HasTracks for T {}
|
|
|
|
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
|
fn tracks (&self) -> &Vec<Track> { Has::<Vec<Track>>::get(self) }
|
|
fn tracks_mut (&mut self) -> &mut Vec<Track> { Has::<Vec<Track>>::get_mut(self) }
|
|
/// 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 HasTrackScroll: HasTracks {
|
|
fn track_scroll (&self) -> usize;
|
|
}
|
|
|
|
impl HasTrackScroll for Arrangement {
|
|
fn track_scroll (&self) -> usize {
|
|
self.track_scroll
|
|
}
|
|
}
|
|
|
|
pub trait HasTrack {
|
|
fn track (&self) -> Option<&Track>;
|
|
fn track_mut (&mut self) -> Option<&mut Track>;
|
|
#[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
|
self.track().map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
|
}
|
|
#[cfg(feature = "port")] fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> + '_ {
|
|
self.track().map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
|
}
|
|
#[cfg(feature = "port")] fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
|
self.track().map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
|
}
|
|
#[cfg(feature = "port")] fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
|
self.track().map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
|
}
|
|
}
|
|
|
|
impl<T: MaybeHas<Track>> HasTrack for T {
|
|
fn track (&self) -> Option<&Track> {
|
|
self.get()
|
|
}
|
|
fn track_mut (&mut self) -> Option<&mut Track> {
|
|
self.get_mut()
|
|
}
|
|
}
|
|
|
|
#[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>,
|
|
}
|
|
|
|
has!(Clock: |self: Track|self.sequencer.clock);
|
|
has!(Sequencer: |self: Track|self.sequencer);
|
|
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()
|
|
})
|
|
}
|
|
fn audio_ins (&self) -> &[AudioInput] {
|
|
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
|
}
|
|
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!() }
|
|
}
|
|
|
|
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; } }
|
|
}
|
|
|
|
#[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
|
|
}
|
|
}
|
|
def_command!(TrackCommand: |track: Track| {
|
|
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
|
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 }),
|
|
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 }),
|
|
});
|
|
|
|
impl<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
|
|
|
|
pub trait TracksView:
|
|
ScenesView +
|
|
HasMidiIns +
|
|
HasMidiOuts +
|
|
HasSize<TuiOut> +
|
|
HasTrackScroll +
|
|
HasSelection +
|
|
HasEditor +
|
|
HasClipsSize
|
|
{
|
|
fn tracks_width_available (&self) -> u16 {
|
|
(self.width() as u16).saturating_sub(40)
|
|
}
|
|
/// Iterate over tracks with their corresponding sizes.
|
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
|
let _editor_width = self.editor().map(|e|e.width());
|
|
let _active_track = self.selection().track();
|
|
let mut x = 0;
|
|
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
|
let width = track.width.max(8);
|
|
if x + width < self.clips_size().w() {
|
|
let data = (index, track, x, x + width);
|
|
x += width + Self::TRACK_SPACING;
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
fn view_track_names (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
|
let track_count = self.tracks().len();
|
|
let scene_count = self.scenes().len();
|
|
let selected = self.selection();
|
|
let button = Bsp::s(
|
|
button_3("t", "rack ", format!("{}{track_count}", selected.track()
|
|
.map(|track|format!("{track}/")).unwrap_or_default()), false),
|
|
button_3("s", "cene ", format!("{}{scene_count}", selected.scene()
|
|
.map(|scene|format!("{scene}/")).unwrap_or_default()), false));
|
|
let button_2 = Bsp::s(
|
|
button_2("T", "+", false),
|
|
button_2("S", "+", false));
|
|
view_track_row_section(theme, button, button_2, Tui::bg(theme.darker.rgb,
|
|
Fixed::Y(2, Thunk::new(|to: &mut TuiOut|{
|
|
for (index, track, x1, _x2) in self.tracks_with_sizes() {
|
|
to.place(&Push::X(x1 as u16, Fixed::X(track_width(index, track),
|
|
Tui::bg(if selected.track() == Some(index) {
|
|
track.color.light.rgb
|
|
} else {
|
|
track.color.base.rgb
|
|
}, Bsp::s(Fill::X(Align::nw(Bsp::e(
|
|
format!("·t{index:02} "),
|
|
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, &track.name))
|
|
))), ""))) ));}}))))
|
|
}
|
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Content<TuiOut> {
|
|
view_track_row_section(theme,
|
|
Bsp::s(Fill::X(Align::w(button_2("o", "utput", false))),
|
|
Thunk::new(|to: &mut TuiOut|for port in self.midi_outs().iter() {
|
|
to.place(&Fill::X(Align::w(port.port_name())));
|
|
})),
|
|
button_2("O", "+", false),
|
|
Tui::bg(theme.darker.rgb, Align::w(Thunk::new(|to: &mut TuiOut|{
|
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
|
to.place(&Fixed::X(track_width(index, track),
|
|
Align::nw(Fill::Y(Map::south(1, ||track.sequencer.midi_outs.iter(),
|
|
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|
Fixed::Y(1, Tui::bg(track.color.dark.rgb, Fill::X(Align::w(
|
|
format!("·o{index:02} {}", port.port_name())))))))))));}}))))
|
|
}
|
|
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
|
|
let mut h = 0u16;
|
|
for track in self.tracks().iter() {
|
|
h = h.max(track.sequencer.midi_ins.len() as u16);
|
|
}
|
|
let content = Thunk::new(move|to: &mut TuiOut|for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
|
to.place(&Fixed::XY(track_width(index, track), h + 1,
|
|
Align::nw(Bsp::s(
|
|
Tui::bg(track.color.base.rgb,
|
|
Fill::X(Align::w(row!(
|
|
Either::new(track.sequencer.monitoring, Tui::fg(Green, "●mon "), "·mon "),
|
|
Either::new(track.sequencer.recording, Tui::fg(Red, "●rec "), "·rec "),
|
|
Either::new(track.sequencer.overdub, Tui::fg(Yellow, "●dub "), "·dub "),
|
|
)))),
|
|
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
|
|port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb,
|
|
Fill::X(Align::w(format!("·i{index:02} {}", port.port_name())))))))));
|
|
});
|
|
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
|
Tui::bg(theme.darker.rgb, Align::w(content)))
|
|
}
|
|
}
|
|
|
|
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
|
track.width as u16
|
|
}
|
|
|
|
fn view_track_header (theme: ItemTheme, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
|
Fixed::X(12, Tui::bg(theme.darker.rgb, Fill::X(Align::e(content))))
|
|
}
|
|
|
|
fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
|
-> impl Content<TuiOut> + use<'a, T>
|
|
{
|
|
let ins = ports.len() as u16;
|
|
let frame = Outer(true, Style::default().fg(Tui::g(96)));
|
|
let iter = move||ports.iter();
|
|
let names = Map::south(1, iter, move|port, index|Fill::Y(Align::w(format!(" {index} {}", port.port_name()))));
|
|
let field = FieldV(theme, title, names);
|
|
Fixed::XY(20, 1 + ins, frame.enclose(Fixed::XY(20, 1 + ins, field)))
|
|
}
|
|
|
|
impl Track {
|
|
pub fn per <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
Map::new(tracks,
|
|
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
|
let width = (x2 - x1) as u16;
|
|
map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg(
|
|
track.color.lightest.rgb,
|
|
track.color.base.rgb,
|
|
callback(index, track))))})
|
|
}
|
|
}
|
|
|
|
pub(crate) fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
Align::x(Tui::bg(Reset, Map::new(tracks,
|
|
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
|
let width = (x2 - x1) as u16;
|
|
map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg(
|
|
track.color.lightest.rgb,
|
|
track.color.base.rgb,
|
|
callback(index, track))))})))
|
|
}
|
|
|
|
pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
per_track_top(tracks, move|index, track|Fill::Y(Align::y(callback(index, track))))
|
|
}
|
|
|
|
pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
|
|
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
|
|
) -> impl Content<TuiOut> + 'a {
|
|
Map::new(iter, move|(
|
|
_index, name, connections, y, y2
|
|
): (usize, &'a Arc<str>, &'a [Connect], usize, usize), _|
|
|
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
Fill::Y(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(&" ", name))))),
|
|
Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1,
|
|
Fill::Y(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
|
|
&connect.info)))))))))
|
|
}
|
|
|
|
//pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
|
|
//fg: Color, bg: Color, iter: &mut impl Iterator<Item = (usize, &'a Arc<str>, &'a [Connect], usize, usize)>
|
|
//) -> impl Content<TuiOut> + 'a {
|
|
//Fill::XY(Thunk::new(move|to: &mut TuiOut|for (_, _, connections, y, y2) in &mut *iter {
|
|
//to.place(&map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
//Fill::Y(Tui::bold(true, wrap(bg, fg, Fill::Y(Align::w(&"▞▞▞▞ ▞▞▞▞"))))),
|
|
//Thunk::new(|to: &mut TuiOut|for (index, _connection) in connections.iter().enumerate() {
|
|
//to.place(&map_south(index as u16, 1, Fill::Y(Align::w(Tui::bold(false,
|
|
//wrap(bg, fg, Fill::Y(&"")))))))
|
|
//})
|
|
//)))
|
|
//}))
|
|
//}
|
|
//track_scroll: Fill::Y(Fixed::Y(1, ScrollbarH {
|
|
//offset: arrangement.track_scroll,
|
|
//length: h_tracks_area as usize,
|
|
//total: h_scenes as usize,
|
|
//})),
|
|
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|