mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
This commit is contained in:
parent
2c3bfe4ebb
commit
ef81b085a0
106 changed files with 6866 additions and 7106 deletions
347
device/track.rs
Normal file
347
device/track.rs
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
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 } }
|
||||
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() }
|
||||
}
|
||||
impl Track {
|
||||
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!() }
|
||||
}
|
||||
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 }),
|
||||
});
|
||||
|
||||
#[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 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 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
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: 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 <T: JackPort> (theme: ItemTheme, title: &str, ports: &[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, |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.content())))
|
||||
}
|
||||
|
||||
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()));
|
||||
Loading…
Add table
Add a link
Reference in a new issue