mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 15:56:57 +02:00
wip: nermalize
This commit is contained in:
parent
915e13aec8
commit
35197fb826
12 changed files with 4649 additions and 4718 deletions
586
src/arrange.rs
586
src/arrange.rs
|
|
@ -1,44 +1,7 @@
|
|||
|
||||
/// Represents the current user selection in the arranger
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
||||
#[default]
|
||||
/// Nothing is selected
|
||||
Nothing,
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
/// A MIDI input is selected.
|
||||
Input(usize),
|
||||
/// A MIDI output is selected.
|
||||
Output(usize),
|
||||
/// A scene is selected.
|
||||
#[cfg(feature = "scene")] Scene(usize),
|
||||
/// A track is selected.
|
||||
#[cfg(feature = "track")] Track(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||||
/// A track's MIDI input connection is selected.
|
||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||
/// A track's MIDI output connection is selected.
|
||||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||||
/// A track device slot is selected.
|
||||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||||
}
|
||||
|
||||
/// A scene consists of a set of clips to play together.
|
||||
///
|
||||
/// ```
|
||||
/// let scene: tek::Scene = Default::default();
|
||||
/// let _ = scene.pulses();
|
||||
/// let _ = scene.is_playing(&[]);
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Scene {
|
||||
/// Name of scene
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of scene
|
||||
pub color: ItemTheme,
|
||||
/// Clips in scene, one per track
|
||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||
}
|
||||
use ::std::sync::{Arc, RwLock};
|
||||
use ::tengri::{space::east, color::ItemTheme};
|
||||
use ::tengri::{draw::*, term::*};
|
||||
use crate::device::{MidiInput, MidiOutput, AudioInput, AudioOutput};
|
||||
|
||||
/// Arranger.
|
||||
///
|
||||
|
|
@ -155,6 +118,47 @@ pub trait ClipsView: TracksView + ScenesView {
|
|||
|
||||
}
|
||||
|
||||
/// Represents the current user selection in the arranger
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection {
|
||||
#[default]
|
||||
/// Nothing is selected
|
||||
Nothing,
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
/// A MIDI input is selected.
|
||||
Input(usize),
|
||||
/// A MIDI output is selected.
|
||||
Output(usize),
|
||||
/// A scene is selected.
|
||||
#[cfg(feature = "scene")] Scene(usize),
|
||||
/// A track is selected.
|
||||
#[cfg(feature = "track")] Track(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||||
/// A track's MIDI input connection is selected.
|
||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||
/// A track's MIDI output connection is selected.
|
||||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||||
/// A track device slot is selected.
|
||||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||||
}
|
||||
|
||||
/// A scene consists of a set of clips to play together.
|
||||
///
|
||||
/// ```
|
||||
/// let scene: tek::Scene = Default::default();
|
||||
/// let _ = scene.pulses();
|
||||
/// let _ = scene.is_playing(&[]);
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Scene {
|
||||
/// Name of scene
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of scene
|
||||
pub color: ItemTheme,
|
||||
/// Clips in scene, one per track
|
||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||
}
|
||||
|
||||
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll + Measured<Tui> {
|
||||
|
||||
fn tracks_width_available (&self) -> u16 {
|
||||
|
|
@ -414,3 +418,503 @@ pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
|||
self.track().map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
||||
}
|
||||
}
|
||||
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
||||
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
||||
#[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
|
||||
#[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
|
||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||
impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } }
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
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()
|
||||
}
|
||||
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!() }
|
||||
pub fn per <'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 {
|
||||
iter(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
let width = (x2 - x1) as u16;
|
||||
iter_east(x1 as u16, width, w_exact(width, Tui::fg_bg(
|
||||
track.color.lightest.rgb,
|
||||
track.color.base.rgb,
|
||||
callback(index, track))))})
|
||||
}
|
||||
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
||||
#[cfg(feature = "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)
|
||||
}
|
||||
#[cfg(feature = "sampler")] 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
|
||||
}
|
||||
#[cfg(feature = "sampler")] 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 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|h_full(origin_y(callback(index, track))))
|
||||
}
|
||||
|
||||
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 {
|
||||
origin_x(Tui::bg(Reset, iter(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
let width = (x2 - x1) as u16;
|
||||
iter_east(x1 as u16, width, w_exact(width, Tui::fg_bg(
|
||||
track.color.lightest.rgb,
|
||||
track.color.base.rgb,
|
||||
callback(index, track))))})))
|
||||
}
|
||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||
impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } }
|
||||
impl ScenesView for App {
|
||||
fn w_mid (&self) -> u16 { (self.measure_width() as u16).saturating_sub(self.w_side()) }
|
||||
fn w_side (&self) -> u16 { 20 }
|
||||
fn h_scenes (&self) -> u16 { (self.measure_height() as u16).saturating_sub(20) }
|
||||
}
|
||||
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.sequencer().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 }
|
||||
}
|
||||
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 Selection {
|
||||
pub fn describe (
|
||||
&self,
|
||||
#[cfg(feature = "track")] tracks: &[Track],
|
||||
#[cfg(feature = "scene")] scenes: &[Scene],
|
||||
) -> Arc<str> {
|
||||
use Selection::*;
|
||||
format!("{}", match self {
|
||||
Mix => "Everything".to_string(),
|
||||
#[cfg(feature = "scene")] Scene(s) =>
|
||||
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
|
||||
#[cfg(feature = "track")] Track(t) =>
|
||||
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
||||
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
||||
(Some(_), Some(s)) => match s.clip(*track) {
|
||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
|
||||
None => format!("T{track} S{scene}: Empty")
|
||||
},
|
||||
_ => format!("T{track} S{scene}: Empty"),
|
||||
},
|
||||
_ => todo!()
|
||||
}).into()
|
||||
}
|
||||
#[cfg(feature = "scene")] pub fn scene (&self) -> Option<usize> {
|
||||
use Selection::*;
|
||||
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
|
||||
}
|
||||
#[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix | Track(_) => Scene(0),
|
||||
Scene(s) => Scene((s + 1) % scene_count),
|
||||
TrackClip { scene, .. } => Track(*scene),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Scene(0),
|
||||
Track(t) => TrackClip { track: *t, scene: 0 },
|
||||
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
|
||||
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix | Scene(0) => Mix,
|
||||
Scene(s) => Scene(s - 1),
|
||||
Track(t) => Track(*t),
|
||||
TrackClip { track, scene: 0 } => Track(*track),
|
||||
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn track (&self) -> Option<usize> {
|
||||
use Selection::*;
|
||||
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
|
||||
Some(*track)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Track(0),
|
||||
Scene(_) => Mix,
|
||||
Track(t) => Track((t + 1) % track_count),
|
||||
TrackClip { track, .. } => Track(*track),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Track(0),
|
||||
Scene(s) => TrackClip { track: 0, scene: *s },
|
||||
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
|
||||
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Mix,
|
||||
Scene(s) => Scene(*s),
|
||||
Track(0) => Mix,
|
||||
Track(t) => Track(t - 1),
|
||||
TrackClip { track: 0, scene } => Scene(*scene),
|
||||
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
||||
impl_has!(Measure<Tui>: |self: Arrangement| self.size);
|
||||
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
||||
impl_has!(Clock: |self: Arrangement| self.clock);
|
||||
impl_has!(Selection: |self: Arrangement| self.selection);
|
||||
impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref());
|
||||
impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut());
|
||||
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
||||
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
||||
impl Arrangement {
|
||||
/// Create a new arrangement.
|
||||
pub fn new (
|
||||
jack: &Jack<'static>,
|
||||
name: Option<Arc<str>>,
|
||||
clock: Clock,
|
||||
tracks: Vec<Track>,
|
||||
scenes: Vec<Scene>,
|
||||
midi_ins: Vec<MidiInput>,
|
||||
midi_outs: Vec<MidiOutput>,
|
||||
) -> Self {
|
||||
Self {
|
||||
clock, tracks, scenes, midi_ins, midi_outs,
|
||||
jack: jack.clone(),
|
||||
name: name.unwrap_or_default(),
|
||||
color: ItemTheme::random(),
|
||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
/// Width of display
|
||||
pub fn w (&self) -> u16 {
|
||||
self.size.w() as u16
|
||||
}
|
||||
/// Width allocated for sidebar.
|
||||
pub fn w_sidebar (&self, is_editing: bool) -> u16 {
|
||||
self.w() / if is_editing { 16 } else { 8 } as u16
|
||||
}
|
||||
/// Width available to display tracks.
|
||||
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
||||
self.w().saturating_sub(self.w_sidebar(is_editing))
|
||||
}
|
||||
/// Height of display
|
||||
pub fn h (&self) -> u16 {
|
||||
self.size.h() as u16
|
||||
}
|
||||
/// Height taken by visible device slots.
|
||||
pub fn h_devices (&self) -> u16 {
|
||||
2
|
||||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Add multiple tracks
|
||||
#[cfg(feature = "track")] pub fn tracks_add (
|
||||
&mut self,
|
||||
count: usize, width: Option<usize>,
|
||||
mins: &[Connect], mouts: &[Connect],
|
||||
) -> Usually<()> {
|
||||
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.track_add(None, Some(color), mins, mouts)?.1;
|
||||
if let Some(width) = width {
|
||||
track.width = width;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Add a track
|
||||
#[cfg(feature = "track")] pub fn track_add (
|
||||
&mut self,
|
||||
name: Option<&str>, color: Option<ItemTheme>,
|
||||
mins: &[Connect], mouts: &[Connect],
|
||||
) -> Usually<(usize, &mut Track)> {
|
||||
let name: Arc<str> = name.map_or_else(
|
||||
||format!("trk{:02}", self.track_last).into(),
|
||||
|x|x.to_string().into()
|
||||
);
|
||||
self.track_last += 1;
|
||||
let track = Track {
|
||||
width: (name.len() + 2).max(12),
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
sequencer: Sequencer::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]))
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||
south(
|
||||
h_exact(1, self.view_inputs_header()),
|
||||
Thunk::new(|to: &mut Tui|{
|
||||
for (index, port) in self.midi_ins().iter().enumerate() {
|
||||
to.place(&x_push(index as u16 * 10, h_exact(1, self.view_inputs_row(port))))
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "track")] fn view_inputs_header (&self) -> impl Draw<Tui> + '_ {
|
||||
east(w_exact(20, origin_w(button_3("i", "nput ", format!("{}", self.midi_ins.len()), false))),
|
||||
west(w_exact(4, button_2("I", "+", false)), Thunk::new(move|to: &mut Tui|for (_index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||
#[cfg(feature = "track")]
|
||||
to.place(&x_push(x1 as u16, Tui::bg(track.color.dark.rgb, origin_w(w_exact(track.width as u16, east!(
|
||||
either(track.sequencer.monitoring, Tui::fg(Green, "mon "), "mon "),
|
||||
either(track.sequencer.recording, Tui::fg(Red, "rec "), "rec "),
|
||||
either(track.sequencer.overdub, Tui::fg(Yellow, "dub "), "dub "),
|
||||
))))))
|
||||
})))
|
||||
}
|
||||
#[cfg(feature = "track")] fn view_inputs_row (&self, port: &MidiInput) -> impl Draw<Tui> {
|
||||
east(w_exact(20, origin_w(east(" ● ", Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))),
|
||||
west(w_exact(4, ()), Thunk::new(move|to: &mut Tui|for (_index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
#[cfg(feature = "track")]
|
||||
to.place(&Tui::bg(track.color.darker.rgb, origin_w(w_exact(track.width as u16, east!(
|
||||
either(track.sequencer.monitoring, Tui::fg(Green, " ● "), " · "),
|
||||
either(track.sequencer.recording, Tui::fg(Red, " ● "), " · "),
|
||||
either(track.sequencer.overdub, Tui::fg(Yellow, " ● "), " · "),
|
||||
)))))
|
||||
})))
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let mut h = 1;
|
||||
for output in self.midi_outs().iter() {
|
||||
h += 1 + output.connections.len();
|
||||
}
|
||||
let h = h as u16;
|
||||
let list = south(
|
||||
h_exact(1, w_full(origin_w(button_3("o", "utput", format!("{}", self.midi_outs.len()), false)))),
|
||||
h_exact(h - 1, wh_full(origin_nw(Thunk::new(|to: &mut Tui|{
|
||||
for (_index, port) in self.midi_outs().iter().enumerate() {
|
||||
to.place(&h_exact(1,w_full(east(
|
||||
origin_w(east(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.port_name())))),
|
||||
w_full(origin_e(format!("{}/{} ",
|
||||
port.port().get_connections().len(),
|
||||
port.connections.len())))))));
|
||||
for (index, conn) in port.connections.iter().enumerate() {
|
||||
to.place(&h_exact(1, w_full(origin_w(format!(" c{index:02}{}", conn.info())))));
|
||||
}
|
||||
}
|
||||
})))));
|
||||
h_exact(h, view_track_row_section(theme, list, button_2("O", "+", false),
|
||||
Tui::bg(theme.darker.rgb, origin_w(w_full(
|
||||
Thunk::new(|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
to.place(&w_exact(track_width(index, track),
|
||||
Thunk::new(|to: &mut Tui|{
|
||||
to.place(&h_exact(1, origin_w(east(
|
||||
either(true, Tui::fg(Green, "play "), "play "),
|
||||
either(false, Tui::fg(Yellow, "solo "), "solo "),
|
||||
))));
|
||||
for (_index, port) in self.midi_outs().iter().enumerate() {
|
||||
to.place(&h_exact(1, origin_w(east(
|
||||
either(true, Tui::fg(Green, " ● "), " · "),
|
||||
either(false, Tui::fg(Yellow, " ● "), " · "),
|
||||
))));
|
||||
for (_index, _conn) in port.connections.iter().enumerate() {
|
||||
to.place(&h_exact(1, w_full("")));
|
||||
}
|
||||
}})))}}))))))
|
||||
}
|
||||
#[cfg(feature = "track")] pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let mut h = 2u16;
|
||||
for track in self.tracks().iter() {
|
||||
h = h.max(track.devices.len() as u16 * 2);
|
||||
}
|
||||
view_track_row_section(theme,
|
||||
button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false),
|
||||
button_2("D", "+", false),
|
||||
Thunk::new(move|to: &mut Tui|for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
to.place(&wh_exact(track_width(index, track), h + 1,
|
||||
Tui::bg(track.color.dark.rgb, origin_nw(iter_south(2, move||0..h,
|
||||
|_, _index|wh_exact(track.width as u16, 2,
|
||||
Tui::fg_bg(
|
||||
ItemTheme::G[32].lightest.rgb,
|
||||
ItemTheme::G[32].dark.rgb,
|
||||
origin_nw(format!(" · {}", "--")))))))));
|
||||
}))
|
||||
}
|
||||
/// Put a clip in a slot
|
||||
#[cfg(feature = "clip")] pub 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
|
||||
#[cfg(feature = "clip")] pub 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
|
||||
})
|
||||
}
|
||||
/// Toggle looping for the active clip
|
||||
#[cfg(feature = "clip")] pub fn toggle_loop (&mut self) {
|
||||
if let Some(clip) = self.selected_clip() {
|
||||
clip.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
/// Get the first sampler of the active track
|
||||
#[cfg(feature = "sampler")] pub fn sampler (&self) -> Option<&Sampler> {
|
||||
self.selected_track()?.sampler(0)
|
||||
}
|
||||
/// Get the first sampler of the active track
|
||||
#[cfg(feature = "sampler")] pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
|
||||
self.selected_track_mut()?.sampler_mut(0)
|
||||
}
|
||||
}
|
||||
impl ScenesView for Arrangement {
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(self.measure_height() as u16).saturating_sub(20)
|
||||
}
|
||||
fn w_side (&self) -> u16 {
|
||||
(self.measure_width() as u16 * 2 / 10).max(20)
|
||||
}
|
||||
fn w_mid (&self) -> u16 {
|
||||
(self.measure_width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||
}
|
||||
}
|
||||
impl HasClipsSize for Arrangement {
|
||||
fn clips_size (&self) -> &Measure<Tui> { &self.size_inner }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue