mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
Compare commits
13 commits
4f6cb7cb8e
...
3f1a2fee80
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f1a2fee80 | |||
| 48603e4812 | |||
| b663c53b0a | |||
| a9288cb0c2 | |||
| 0d9bb709a5 | |||
| a7f37e52cf | |||
| e9b4a2ca78 | |||
| 4d0868add8 | |||
| b5326b578c | |||
| b6d1978a55 | |||
| 17abb3d971 | |||
| e312e442fa | |||
| 0733742685 |
21 changed files with 1022 additions and 953 deletions
|
|
@ -21,11 +21,10 @@
|
||||||
(bsp/s
|
(bsp/s
|
||||||
(fixed/y 8 (bsp/e
|
(fixed/y 8 (bsp/e
|
||||||
(fixed/x 20 (fill/y (align/n (bsp/s :view-status-v
|
(fixed/x 20 (fill/y (align/n (bsp/s :view-status-v
|
||||||
(bsp/s (bsp/s :view-audio-ins-status :view-audio-outs-status)
|
(bsp/s :view-audio-ins-status :view-audio-outs-status)))))
|
||||||
:view-editor-status)))))
|
|
||||||
(fill/xy (align/n (bsp/s :view-arranger-track-names
|
(fill/xy (align/n (bsp/s :view-arranger-track-names
|
||||||
(bsp/s :view-arranger-track-outputs
|
(bsp/s :view-arranger-track-outputs
|
||||||
(bsp/s :view-arranger-track-devices :view-arranger-track-inputs)))))))
|
(bsp/s :view-arranger-track-devices :view-arranger-track-inputs)))))))
|
||||||
(fill/xy (align/w (bsp/e
|
(fill/xy (bsp/e
|
||||||
(align/w (fixed/x 20 (fill/xy (align/nw :view-arranger-scenes-names))))
|
(bsp/n (max/y 9 :view-editor-status) (fixed/x 20 (align/nw :view-arranger-scenes-names)))
|
||||||
(align/w (fill/xy (align/nw :view-arranger-scenes-clips)))))))))
|
:view-arranger-scenes-clips)))))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(@c color)
|
(@c color)
|
||||||
(@q launch)
|
(@q launch)
|
||||||
(@t select :select-track-header)
|
(@t select :select-track-header)
|
||||||
(@tab edit :clip)
|
(@tab edit :clip-selected)
|
||||||
(@shift-I input add)
|
(@shift-I input add)
|
||||||
(@shift-O output add)
|
(@shift-O output add)
|
||||||
(@shift-S scene add)
|
(@shift-S scene add)
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@
|
||||||
(@p sampler play-sample :sample-selected)
|
(@p sampler play-sample :sample-selected)
|
||||||
(@P sampler stop-sample :sample-selected)
|
(@P sampler stop-sample :sample-selected)
|
||||||
|
|
||||||
(@shift-f6 dialog :dialog-import-sample)
|
(@shift-f6 dialog :dialog-export-sample)
|
||||||
(@shift-f9 dialog :dialog-export-sample)
|
(@shift-f9 dialog :dialog-import-sample)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,20 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm
|
||||||
}));
|
}));
|
||||||
|
|
||||||
#[tengri_proc::command(App)] impl AppCommand {
|
#[tengri_proc::command(App)] impl AppCommand {
|
||||||
|
fn edit (app: &mut App) -> Perhaps<Self> {
|
||||||
|
let selection = app.selection().clone();
|
||||||
|
Ok(match selection {
|
||||||
|
Selection::TrackClip { track, scene } => {
|
||||||
|
let clip = &mut app.scenes_mut()[scene].clips[track];
|
||||||
|
if clip.is_none() {
|
||||||
|
*clip = Some(Default::default());
|
||||||
|
}
|
||||||
|
app.editor = clip.as_ref().map(|c|c.into());
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
})
|
||||||
|
}
|
||||||
fn dialog (app: &mut App, dialog: Option<Dialog>) -> Perhaps<Self> {
|
fn dialog (app: &mut App, dialog: Option<Dialog>) -> Perhaps<Self> {
|
||||||
app.toggle_dialog(dialog);
|
app.toggle_dialog(dialog);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@
|
||||||
#![feature(closure_lifetime_binder)]
|
#![feature(closure_lifetime_binder)]
|
||||||
pub use ::tek_engine:: *;
|
pub use ::tek_engine:: *;
|
||||||
pub use ::tek_device::{self, *};
|
pub use ::tek_device::{self, *};
|
||||||
pub use ::tengri::{Usually, Perhaps, Has};
|
pub use ::tengri::{Usually, Perhaps, Has, MaybeHas};
|
||||||
|
pub use ::tengri::{has, maybe_has};
|
||||||
pub use ::tengri::dsl::*;
|
pub use ::tengri::dsl::*;
|
||||||
pub use ::tengri::input::*;
|
pub use ::tengri::input::*;
|
||||||
pub use ::tengri::output::*;
|
pub use ::tengri::output::*;
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ pub struct App {
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
/// Performance counter
|
/// Performance counter
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
|
|
||||||
// View and input definition
|
// View and input definition
|
||||||
pub config: Configuration,
|
pub config: Configuration,
|
||||||
|
/// Contains all recently created clips.
|
||||||
|
pub pool: Pool,
|
||||||
/// Contains the currently edited musical arrangement
|
/// Contains the currently edited musical arrangement
|
||||||
pub project: Arrangement,
|
pub project: Arrangement,
|
||||||
/// Undo history
|
/// Undo history
|
||||||
|
|
@ -21,7 +22,6 @@ pub struct App {
|
||||||
pub dialog: Option<Dialog>,
|
pub dialog: Option<Dialog>,
|
||||||
/// Contains the currently edited MIDI clip
|
/// Contains the currently edited MIDI clip
|
||||||
pub editor: Option<MidiEditor>,
|
pub editor: Option<MidiEditor>,
|
||||||
|
|
||||||
// Cache of formatted strings
|
// Cache of formatted strings
|
||||||
pub view_cache: Arc<RwLock<ViewCache>>,
|
pub view_cache: Arc<RwLock<ViewCache>>,
|
||||||
/// Base color.
|
/// Base color.
|
||||||
|
|
@ -29,6 +29,7 @@ pub struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
has!(Jack: |self: App|self.jack);
|
has!(Jack: |self: App|self.jack);
|
||||||
|
has!(Pool: |self: App|self.pool);
|
||||||
has!(Clock: |self: App|self.project.clock);
|
has!(Clock: |self: App|self.project.clock);
|
||||||
has!(Selection: |self: App|self.project.selection);
|
has!(Selection: |self: App|self.project.selection);
|
||||||
has!(Vec<JackMidiIn>: |self: App|self.project.midi_ins);
|
has!(Vec<JackMidiIn>: |self: App|self.project.midi_ins);
|
||||||
|
|
@ -36,7 +37,18 @@ has!(Vec<JackMidiOut>: |self: App|self.project.midi_outs);
|
||||||
has!(Vec<Scene>: |self: App|self.project.scenes);
|
has!(Vec<Scene>: |self: App|self.project.scenes);
|
||||||
has!(Vec<Track>: |self: App|self.project.tracks);
|
has!(Vec<Track>: |self: App|self.project.tracks);
|
||||||
has!(Measure<TuiOut>: |self: App|self.size);
|
has!(Measure<TuiOut>: |self: App|self.size);
|
||||||
|
maybe_has!(Track: |self: App|
|
||||||
|
{ MaybeHas::<Track>::get(&self.project) };
|
||||||
|
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
||||||
|
impl HasTrackScroll for App {
|
||||||
|
fn track_scroll (&self) -> usize { self.project.track_scroll() }
|
||||||
|
}
|
||||||
|
maybe_has!(Scene: |self: App|
|
||||||
|
{ MaybeHas::<Scene>::get(&self.project) };
|
||||||
|
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||||
|
impl HasSceneScroll for App {
|
||||||
|
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
||||||
|
}
|
||||||
has_clips!(|self: App|self.project.pool.clips);
|
has_clips!(|self: App|self.project.pool.clips);
|
||||||
has_editor!(|self: App|{
|
has_editor!(|self: App|{
|
||||||
editor = self.editor;
|
editor = self.editor;
|
||||||
|
|
@ -300,7 +312,7 @@ impl App {
|
||||||
fn select_track_prev (&self) -> Selection {
|
fn select_track_prev (&self) -> Selection {
|
||||||
self.selection().track_prev()
|
self.selection().track_prev()
|
||||||
}
|
}
|
||||||
fn clip_selection (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
fn clip_selected (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
match self.selection() {
|
match self.selection() {
|
||||||
Selection::TrackClip { track, scene } => self.scenes()[*scene].clips[*track].clone(),
|
Selection::TrackClip { track, scene } => self.scenes()[*scene].clips[*track].clone(),
|
||||||
_ => None
|
_ => None
|
||||||
|
|
|
||||||
|
|
@ -61,164 +61,41 @@ impl App {
|
||||||
self.editor()
|
self.editor()
|
||||||
}
|
}
|
||||||
pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_editor_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.editor().map(|e|Bsp::s(e.clip_status(), e.edit_status()))
|
self.editor().map(|e|Fixed::x(20, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fill::y(Align::n(Bsp::s(e.clip_status(), e.edit_status()))))))
|
||||||
}
|
}
|
||||||
pub fn view_midi_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_midi_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.get_track().map(|track|{
|
self.project.view_midi_ins_status(self.color)
|
||||||
let ins = track.sequencer.midi_ins.len() as u16;
|
|
||||||
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
Fixed::xy(20, 1 + ins, FieldV(self.color, format!("MIDI ins: "),
|
|
||||||
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn view_midi_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_midi_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.get_track().map(|track|{
|
self.project.view_midi_outs_status(self.color)
|
||||||
let outs = track.sequencer.midi_outs.len() as u16;
|
|
||||||
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
Fixed::xy(20, 1 + outs, FieldV(self.color, format!("MIDI outs: "),
|
|
||||||
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pub fn view_audio_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_audio_ins_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.get_track()
|
self.project.view_audio_ins_status(self.color)
|
||||||
.and_then(|track|track.devices.get(0))
|
|
||||||
.map(|device|{
|
|
||||||
let ins = device.audio_ins().len() as u16;
|
|
||||||
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
Fixed::xy(20, 1 + ins, FieldV(self.color, format!("Audio ins: "),
|
|
||||||
Map::south(1, ||device.audio_ins().iter(),
|
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|
||||||
}
|
}
|
||||||
pub fn view_audio_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_audio_outs_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.get_track()
|
self.project.view_audio_outs_status(self.color)
|
||||||
.and_then(|track|track.devices.last())
|
|
||||||
.map(|device|{
|
|
||||||
let outs = device.audio_outs().len() as u16;
|
|
||||||
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
|
||||||
Fixed::xy(20, 1 + outs, FieldV(self.color, format!("Audio outs: "),
|
|
||||||
Map::south(1, ||device.audio_outs().iter(),
|
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_arranger (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
ArrangerView::new(&self.project, self.editor.as_ref())
|
&self.project
|
||||||
}
|
|
||||||
pub fn view_arranger_scenes (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
self.scenes_view(&self.editor)
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_scenes_names (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_arranger_scenes_names (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.scenes_names()
|
self.project.view_scenes_names()
|
||||||
}
|
}
|
||||||
pub fn view_arranger_scenes_clips (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_arranger_scenes_clips (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.scenes_clips(&self.editor)
|
self.project.view_scenes_clips(&self.editor)
|
||||||
}
|
|
||||||
pub fn view_arranger_scene_names <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
let h = self.project.scenes.len() as u16 * 2;
|
|
||||||
let bg = self.color.darker.rgb;
|
|
||||||
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.scenes.iter().skip(self.project.scene_scroll),
|
|
||||||
move|scene: &Scene, index|
|
|
||||||
Push::y(index as u16 * 2u16, Fixed::xy(20, 2,
|
|
||||||
Tui::bg(scene.color.dark.rgb, Align::nw(Bsp::e(
|
|
||||||
format!(" {index:2} "),
|
|
||||||
Tui::fg(Rgb(255, 255, 255),
|
|
||||||
Tui::bold(true, format!("{}", scene.name)))))))))))))
|
|
||||||
}
|
|
||||||
pub fn view_arranger_scene_clips <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
|
||||||
let h = self.project.scenes.len() as u16 * 2;
|
|
||||||
let bg = self.color.darker.rgb;
|
|
||||||
Fixed::y(h, Tui::bg(bg, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.scenes.iter().skip(self.project.scene_scroll),
|
|
||||||
move|scene: &'a Scene, index1|
|
|
||||||
Push::y(index1 as u16 * 2u16, Fixed::xy(20, 2,
|
|
||||||
Map::new(
|
|
||||||
move||scene.clips.iter().skip(self.project.track_scroll),
|
|
||||||
move|clip: &'a Option<Arc<RwLock<MidiClip>>>, index2|{
|
|
||||||
let (theme, text) = if let Some(clip) = clip {
|
|
||||||
let clip = clip.read().unwrap();
|
|
||||||
(clip.color, clip.name.clone())
|
|
||||||
} else {
|
|
||||||
(scene.color, Default::default())
|
|
||||||
};
|
|
||||||
Push::x(index2 as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e(
|
|
||||||
format!(" {index1:2} {index2:2} "),
|
|
||||||
Tui::fg(Rgb(255, 255, 255),
|
|
||||||
Tui::bold(true, format!("{}", text))))))
|
|
||||||
}))))))))
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_track_names (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_arranger_track_names (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
let mut max_outputs = 0u16;
|
self.project.view_track_names(self.color)
|
||||||
for track in self.project.tracks.iter() {
|
|
||||||
max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16);
|
|
||||||
}
|
|
||||||
Bsp::w(
|
|
||||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
|
||||||
col!(Tui::bold(true, "[t]rack"), "[T] Add"))),
|
|
||||||
Align::w(Fixed::y(max_outputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
|
||||||
.skip(self.project.track_scroll),
|
|
||||||
move|(index, track, x1, x2): (usize, &Track, usize, usize), _|
|
|
||||||
Push::x(index as u16 * 14, Fixed::xy(track.width as u16, max_outputs + 1,
|
|
||||||
Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s(
|
|
||||||
Tui::fg(Rgb(255, 255, 255), Tui::bold(true, format!("{}", track.name))),
|
|
||||||
format!("{index} {x1} {x2}")))))))))))))
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_track_outputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
pub fn view_arranger_track_outputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
let mut max_outputs = 0u16;
|
self.project.view_track_outputs(self.color)
|
||||||
for track in self.project.tracks.iter() {
|
|
||||||
max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16);
|
|
||||||
}
|
|
||||||
Bsp::w(
|
|
||||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
|
||||||
col!(Tui::bold(true, "[o]utput"), "[O] Add"))),
|
|
||||||
Align::w(Fixed::y(max_outputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
|
||||||
.skip(self.project.track_scroll),
|
|
||||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
|
||||||
Push::x(x2 as u16, Tui::bg(track.color.dark.rgb, Fixed::xy(
|
|
||||||
track.width as u16,
|
|
||||||
max_outputs + 1,
|
|
||||||
Align::nw(Bsp::s(
|
|
||||||
format!("[mut] [sol]"),
|
|
||||||
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
|
||||||
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|
||||||
format!("{index}: {}", port.name())))))))))))))))
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_track_inputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
pub fn view_arranger_track_inputs <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
let mut max_inputs = 0u16;
|
self.project.view_track_inputs(self.color)
|
||||||
for track in self.project.tracks.iter() {
|
|
||||||
max_inputs = max_inputs.max(track.sequencer.midi_ins.len() as u16);
|
|
||||||
}
|
|
||||||
Bsp::w(
|
|
||||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
|
||||||
col!(Tui::bold(true, "[i]nputs"), "[I] Add"))),
|
|
||||||
Fill::x(Align::w(Fixed::y(max_inputs + 1, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
|
||||||
.skip(self.project.track_scroll),
|
|
||||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
|
||||||
Push::x(x2 as u16, Fixed::xy(track.width as u16, max_inputs + 1,
|
|
||||||
Tui::bg(track.color.dark.rgb, Align::nw(Bsp::s(
|
|
||||||
format!("[rec] [mon]"),
|
|
||||||
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
|
||||||
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|
||||||
format!("{index}: {}", port.name()))))))))))))))))
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_track_devices <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
pub fn view_arranger_track_devices <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
let mut max_devices = 2u16;
|
self.project.view_track_devices(self.color)
|
||||||
for track in self.project.tracks.iter() {
|
|
||||||
max_devices = max_devices.max(track.devices.len() as u16);
|
|
||||||
}
|
|
||||||
Bsp::w(
|
|
||||||
Fixed::x(20, Tui::bg(self.color.darkest.rgb,
|
|
||||||
col!(Tui::bold(true, "[d]evice"), "[D] Add"))),
|
|
||||||
Fixed::y(max_devices, Tui::bg(self.color.darker.rgb, Align::w(Fill::x(Map::new(
|
|
||||||
||self.project.tracks_with_sizes(&self.project.selection, None)
|
|
||||||
.skip(self.project.track_scroll),
|
|
||||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|
|
|
||||||
Push::x(x2 as u16, Fixed::xy(track.width as u16, max_devices + 1,
|
|
||||||
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..max_devices,
|
|
||||||
|_, index|format!("{index}: {}", "--------"))))))))))))
|
|
||||||
}
|
}
|
||||||
pub fn view_arranger_track_scenes <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
pub fn view_arranger_track_scenes <'a> (&'a self) -> impl Content<TuiOut> + use<'a> {
|
||||||
let mut max_devices = 0u16;
|
let mut max_devices = 0u16;
|
||||||
|
|
@ -255,7 +132,8 @@ impl App {
|
||||||
self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos()))
|
self.project.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos()))
|
||||||
}
|
}
|
||||||
pub fn view_sample_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_sample_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.sampler().map(|s|s.view_sample_status(self.editor().unwrap().get_note_pos()))
|
self.project.sampler().map(|s|Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fill::y(Align::n(s.view_sample_status(self.editor().unwrap().get_note_pos())))))
|
||||||
}
|
}
|
||||||
pub fn view_meters_input (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_meters_input (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.project.sampler().map(|s|s.view_meters_input())
|
self.project.sampler().map(|s|s.view_meters_input())
|
||||||
|
|
@ -361,7 +239,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArrangerSceneRows for App {
|
impl ScenesView for App {
|
||||||
fn arrangement (&self) -> &Arrangement {
|
fn arrangement (&self) -> &Arrangement {
|
||||||
&self.project
|
&self.project
|
||||||
}
|
}
|
||||||
|
|
@ -377,15 +255,9 @@ impl ArrangerSceneRows for App {
|
||||||
fn scene_selected (&self) -> Option<usize> {
|
fn scene_selected (&self) -> Option<usize> {
|
||||||
self.project.selection.scene()
|
self.project.selection.scene()
|
||||||
}
|
}
|
||||||
fn scene_last (&self) -> usize {
|
|
||||||
self.project.scenes.len().saturating_sub(1)
|
|
||||||
}
|
|
||||||
fn track_selected (&self) -> Option<usize> {
|
fn track_selected (&self) -> Option<usize> {
|
||||||
self.project.selection.track()
|
self.project.selection.track()
|
||||||
}
|
}
|
||||||
fn is_editing (&self) -> bool {
|
|
||||||
self.editor.is_some()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn heading <'a> (
|
pub(crate) fn heading <'a> (
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,8 @@ macro_rules! def_sizes_iter {
|
||||||
mod arranger_api; pub use self::arranger_api::*;
|
mod arranger_api; pub use self::arranger_api::*;
|
||||||
mod arranger_clip; pub use self::arranger_clip::*;
|
mod arranger_clip; pub use self::arranger_clip::*;
|
||||||
mod arranger_model; pub use self::arranger_model::*;
|
mod arranger_model; pub use self::arranger_model::*;
|
||||||
mod arranger_port; pub use self::arranger_port::*;
|
|
||||||
mod arranger_scene; pub use self::arranger_scene::*;
|
|
||||||
mod arranger_scenes; pub use self::arranger_scenes::*;
|
mod arranger_scenes; pub use self::arranger_scenes::*;
|
||||||
mod arranger_select; pub use self::arranger_select::*;
|
mod arranger_select; pub use self::arranger_select::*;
|
||||||
mod arranger_track; pub use self::arranger_track::*;
|
|
||||||
mod arranger_tracks; pub use self::arranger_tracks::*;
|
mod arranger_tracks; pub use self::arranger_tracks::*;
|
||||||
mod arranger_view; pub use self::arranger_view::*;
|
mod arranger_view; pub use self::arranger_view::*;
|
||||||
|
|
||||||
|
|
@ -30,48 +27,3 @@ pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl
|
||||||
let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌")));
|
let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌")));
|
||||||
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
||||||
}
|
}
|
||||||
|
|
||||||
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 [PortConnect], usize, usize), _|
|
|
||||||
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
||||||
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" ", name))))),
|
|
||||||
Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1,
|
|
||||||
Fill::x(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: impl Fn()->T + Send + Sync + 'a
|
|
||||||
) -> impl Content<TuiOut> + 'a {
|
|
||||||
Map::new(iter, move|(
|
|
||||||
index, name, connections, y, y2
|
|
||||||
): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
|
|
||||||
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
|
||||||
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
|
|
||||||
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
|
|
||||||
Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x(""))))))))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasWidth {
|
|
||||||
const MIN_WIDTH: usize;
|
|
||||||
/// Increment track width.
|
|
||||||
fn width_inc (&mut self);
|
|
||||||
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
|
||||||
fn width_dec (&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,12 @@ impl ArrangementCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'state> Context<'state, TrackCommand> for Arrangement {
|
||||||
|
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<TrackCommand> {
|
||||||
|
Context::get(&self, iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'state> Context<'state, MidiInputCommand> for Arrangement {
|
impl<'state> Context<'state, MidiInputCommand> for Arrangement {
|
||||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<MidiInputCommand> {
|
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<MidiInputCommand> {
|
||||||
Context::get(&self, iter)
|
Context::get(&self, iter)
|
||||||
|
|
@ -209,3 +215,15 @@ impl<'state> Context<'state, DeviceCommand> for Arrangement {
|
||||||
Context::get(&self, iter)
|
Context::get(&self, iter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'state> Context<'state, SceneCommand> for Arrangement {
|
||||||
|
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SceneCommand> {
|
||||||
|
Context::get(&self, iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'state> Context<'state, ClipCommand> for Arrangement {
|
||||||
|
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClipCommand> {
|
||||||
|
Context::get(&self, iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
impl<'state> Context<'state, ClipCommand> for Arrangement {
|
|
||||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<ClipCommand> {
|
|
||||||
Context::get(&self, iter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
#[tengri_proc::expose]
|
||||||
impl MidiClip {
|
impl MidiClip {
|
||||||
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
fn _todo_opt_bool_stub_ (&self) -> Option<bool> { todo!() }
|
||||||
|
|
@ -30,7 +24,3 @@ impl ClipCommand {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arrangement {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,12 @@ has!(Vec<JackMidiOut>: |self: Arrangement|self.midi_outs);
|
||||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||||
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
||||||
|
maybe_has!(Track: |self: Arrangement|
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
||||||
|
maybe_has!(Scene: |self: Arrangement|
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
||||||
|
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
/// Width of display
|
/// Width of display
|
||||||
|
|
@ -70,18 +76,6 @@ impl Arrangement {
|
||||||
pub fn h (&self) -> u16 {
|
pub fn h (&self) -> u16 {
|
||||||
self.size.h() as u16
|
self.size.h() as u16
|
||||||
}
|
}
|
||||||
/// Height taken by all scenes.
|
|
||||||
pub fn h_scenes (&self, is_editing: bool) -> u16 {
|
|
||||||
self.scenes_with_sizes(
|
|
||||||
is_editing,
|
|
||||||
ArrangerView::H_SCENE,
|
|
||||||
ArrangerView::H_EDITOR,
|
|
||||||
self.selection().track(),
|
|
||||||
self.selection().scene(),
|
|
||||||
)
|
|
||||||
.last()
|
|
||||||
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
|
|
||||||
}
|
|
||||||
/// Height taken by all inputs.
|
/// Height taken by all inputs.
|
||||||
pub fn h_inputs (&self) -> u16 {
|
pub fn h_inputs (&self) -> u16 {
|
||||||
self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||||
|
|
@ -145,6 +139,64 @@ impl Arrangement {
|
||||||
clip.write().unwrap().toggle_loop()
|
clip.write().unwrap().toggle_loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// 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)> {
|
||||||
|
let name: Arc<str> = name.map_or_else(
|
||||||
|
||format!("t{:02}", self.track_last).into(),
|
||||||
|
|x|x.to_string().into()
|
||||||
|
);
|
||||||
|
self.track_last += 1;
|
||||||
|
let mut 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 = "sampler")]
|
#[cfg(feature = "sampler")]
|
||||||
|
|
@ -158,3 +210,24 @@ impl Arrangement {
|
||||||
self.get_track_mut()?.sampler_mut(0)
|
self.get_track_mut()?.sampler_mut(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ScenesView for Arrangement {
|
||||||
|
fn arrangement (&self) -> &Arrangement {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn scenes_height (&self) -> u16 {
|
||||||
|
(self.height() as u16).saturating_sub(20)
|
||||||
|
}
|
||||||
|
fn width_side (&self) -> u16 {
|
||||||
|
(self.width() as u16 * 2 / 10).max(20)
|
||||||
|
}
|
||||||
|
fn width_mid (&self) -> u16 {
|
||||||
|
(self.width() as u16).saturating_sub(2 * self.width_side()).max(40)
|
||||||
|
}
|
||||||
|
fn scene_selected (&self) -> Option<usize> {
|
||||||
|
self.selection().scene()
|
||||||
|
}
|
||||||
|
fn track_selected (&self) -> Option<usize> {
|
||||||
|
self.selection().track()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
|
||||||
|
|
||||||
pub(crate) fn input_routes (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::top(self.arrangement.h_inputs())
|
|
||||||
.left(self.width_side,
|
|
||||||
io_ports(Tui::g(224), Tui::g(32), ||self.arrangement.midi_ins_with_sizes()))
|
|
||||||
.middle(self.width_mid,
|
|
||||||
per_track_top(||self.tracks_with_sizes_scrolled(),
|
|
||||||
move|_, &Track { color, .. }|io_conns(
|
|
||||||
color.dark.rgb,
|
|
||||||
color.darker.rgb,
|
|
||||||
||self.arrangement.midi_ins_with_sizes()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn input_ports (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::top(1)
|
|
||||||
.left(self.width_side,
|
|
||||||
button_3("i", "midi ins", format!("{}", self.arrangement.midi_ins().len()), self.is_editing))
|
|
||||||
.right(self.width_side,
|
|
||||||
button_2("I", "add midi in", self.is_editing))
|
|
||||||
.middle(self.width_mid,
|
|
||||||
per_track_top(||self.tracks_with_sizes_scrolled(),
|
|
||||||
move|t, track|{
|
|
||||||
let rec = track.sequencer.recording;
|
|
||||||
let mon = track.sequencer.monitoring;
|
|
||||||
let rec = if rec { White } else { track.color.darkest.rgb };
|
|
||||||
let mon = if mon { White } else { track.color.darkest.rgb };
|
|
||||||
let bg = if self.track_selected == Some(t) {
|
|
||||||
track.color.light.rgb
|
|
||||||
} else {
|
|
||||||
track.color.base.rgb
|
|
||||||
};
|
|
||||||
//let bg2 = if t > 0 { track.color.base.rgb } else { Reset };
|
|
||||||
wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(
|
|
||||||
Tui::fg_bg(rec, bg, "Rec "),
|
|
||||||
Tui::fg_bg(mon, bg, "Mon "),
|
|
||||||
))))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn input_intos (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::top(2)
|
|
||||||
.left(self.width_side,
|
|
||||||
Bsp::s(Align::e("Input:"), Align::e("Into clip:")))
|
|
||||||
.middle(self.width_mid,
|
|
||||||
per_track_top(||self.tracks_with_sizes_scrolled(),
|
|
||||||
|_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ ")))))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
|
||||||
|
|
||||||
pub(crate) fn output_nexts (&self) -> impl Content<TuiOut> + '_ {
|
|
||||||
Tryptich::top(2)
|
|
||||||
.left(self.width_side, Align::ne("From clip:"))
|
|
||||||
.middle(self.width_mid, per_track_top(||self.tracks_with_sizes_scrolled(),
|
|
||||||
|_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default())))))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_froms (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
let label = Align::ne("Next clip:");
|
|
||||||
Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top(
|
|
||||||
||self.tracks_with_sizes_scrolled(), |t, track|{
|
|
||||||
let queued = track.sequencer.next_clip.is_some();
|
|
||||||
let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ "));
|
|
||||||
let queued_clip = Thunk::new(||{
|
|
||||||
Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() {
|
|
||||||
if let Some(clip) = clip {
|
|
||||||
clip.read().unwrap().name.clone()
|
|
||||||
} else {
|
|
||||||
"Stop".into()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
"".into()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
Either(queued, queued_clip, queued_blank)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_ports (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::top(1)
|
|
||||||
.left(self.width_side, self.output_count())
|
|
||||||
.right(self.width_side, self.output_add())
|
|
||||||
.middle(self.width_mid, self.output_map())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_count (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
button_3(
|
|
||||||
"o",
|
|
||||||
"midi outs",
|
|
||||||
format!("{}", self.arrangement.midi_outs().len()),
|
|
||||||
self.is_editing
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_add (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
button_2("O", "add midi out", self.is_editing)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_map (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{
|
|
||||||
let mute = false;
|
|
||||||
let solo = false;
|
|
||||||
let mute = if mute { White } else { t.color.darkest.rgb };
|
|
||||||
let solo = if solo { White } else { t.color.darkest.rgb };
|
|
||||||
let bg_1 = if self.track_selected == Some(i) {
|
|
||||||
t.color.light.rgb
|
|
||||||
} else {
|
|
||||||
t.color.base.rgb
|
|
||||||
};
|
|
||||||
let bg_2 = if i > 0 { t.color.base.rgb } else { Reset };
|
|
||||||
let mute = Tui::fg_bg(mute, bg_1, "Play ");
|
|
||||||
let solo = Tui::fg_bg(solo, bg_1, "Solo ");
|
|
||||||
wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo))))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn output_conns (&'a self) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::top(self.arrangement.h_outputs())
|
|
||||||
.left(self.width_side, io_ports(
|
|
||||||
Tui::g(224), Tui::g(32), ||self.arrangement.midi_outs_with_sizes()))
|
|
||||||
.middle(self.width_mid, per_track_top(||self.tracks_with_sizes_scrolled(),
|
|
||||||
|_, t|io_conns(
|
|
||||||
t.color.dark.rgb,
|
|
||||||
t.color.darker.rgb,
|
|
||||||
||self.arrangement.midi_outs_with_sizes()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl<'state> Context<'state, SceneCommand> for Arrangement {
|
|
||||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<SceneCommand> {
|
|
||||||
Context::get(&self, iter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Scene {
|
|
||||||
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!() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::command(Scene)]
|
|
||||||
impl SceneCommand {
|
|
||||||
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
|
|
||||||
std::mem::swap(&mut scene.name, &mut name);
|
|
||||||
Ok(Some(Self::SetName { name }))
|
|
||||||
}
|
|
||||||
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
|
|
||||||
std::mem::swap(&mut scene.color, &mut color);
|
|
||||||
Ok(Some(Self::SetColor { color }))
|
|
||||||
}
|
|
||||||
fn set_size (scene: &mut Scene, size: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
|
||||||
|
|
||||||
pub trait HasScene: Has<Option<Scene>> + Send + Sync {
|
|
||||||
fn scene (&self) -> Option<&Scene> {
|
|
||||||
Has::<Option<Scene>>::get(self).as_ref()
|
|
||||||
}
|
|
||||||
fn scene_mut (&mut self) -> &mut Option<Scene> {
|
|
||||||
Has::<Option<Scene>>::get_mut(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,162 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub type SceneWith<'a, T: Send + Sync> = (usize, &'a Scene, usize, usize, T);
|
|
||||||
|
|
||||||
pub trait ArrangerSceneRows: Send + Sync {
|
|
||||||
/// Default scene height.
|
|
||||||
const H_SCENE: usize = 2;
|
|
||||||
/// Default editor height.
|
|
||||||
const H_EDITOR: usize = 15;
|
|
||||||
/// Render scenes with clips
|
|
||||||
fn scenes_view <'a> (&'a self, editor: &'a Option<MidiEditor>) -> impl Content<TuiOut> + 'a {
|
|
||||||
Tryptich::center(self.scenes_height())
|
|
||||||
.left(self.width_side(), self.scenes_names())
|
|
||||||
.middle(self.width_mid(), self.scenes_clips(editor))
|
|
||||||
}
|
|
||||||
fn is_editing (&self) -> bool;
|
|
||||||
fn arrangement (&self) -> &Arrangement;
|
|
||||||
fn scene_last (&self) -> usize;
|
|
||||||
fn scene_selected (&self) -> Option<usize>;
|
|
||||||
fn track_selected (&self) -> Option<usize>;
|
|
||||||
fn scenes_height (&self) -> u16;
|
|
||||||
fn width_side (&self) -> u16;
|
|
||||||
fn width_mid (&self) -> u16;
|
|
||||||
fn scenes_names (&self) -> impl Content<TuiOut> {
|
|
||||||
let h = self.scenes_with_prev_color().last().map(|(_,_,_,h,_)|h as u16).unwrap_or(0);
|
|
||||||
Fixed::y(h, Map::new(move||self.scenes_with_prev_color(),
|
|
||||||
move|(s, scene, y1, y2, previous): SceneWith<'_, Option<ItemTheme>>, _|{
|
|
||||||
let height = (1 + y2 - y1) as u16;
|
|
||||||
let name = Some(scene.name.clone());
|
|
||||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name))));
|
|
||||||
let selected = self.scene_selected() == Some(s);
|
|
||||||
let neighbor = s > 0 && self.scene_selected() == Some(s - 1);
|
|
||||||
let is_last = self.scene_last() == s;
|
|
||||||
let theme = scene.color;
|
|
||||||
let fg = theme.lightest.rgb;
|
|
||||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
|
||||||
let hi = if let Some(previous) = previous {
|
|
||||||
if neighbor { previous.light.rgb } else { previous.base.rgb }
|
|
||||||
} else {
|
|
||||||
Reset
|
|
||||||
};
|
|
||||||
let lo = if is_last { Reset } else if selected {
|
|
||||||
theme.light.rgb
|
|
||||||
} else {
|
|
||||||
theme.base.rgb
|
|
||||||
};
|
|
||||||
Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
|
|
||||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
|
||||||
})))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
fn scenes_with_prev_color (&self) -> impl Iterator<Item=SceneWith<Option<ItemTheme>>> + Send + Sync {
|
|
||||||
self.scenes_iter().map(|(s, scene, y1, y2)|(s, scene, y1, y2,
|
|
||||||
(s>0).then(||self.arrangement().scenes()[s-1].color)))
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
|
||||||
Tui::fg_bg(track.color.lightest.rgb, track.color.darker.rgb,
|
|
||||||
map_east(x1 as u16, (x2 - x1) as u16, callback(index, track))) })
|
|
||||||
}
|
|
||||||
fn scenes_clips <'a> (&'a self, editor: &'a Option<MidiEditor>)
|
|
||||||
-> impl Content<TuiOut> + 'a
|
|
||||||
{
|
|
||||||
let h = self.scenes_with_prev_color().last().map(|(_,_,_,h,_)|h as u16).unwrap_or(0);
|
|
||||||
Fixed::y(h, Self::per_track(||self.tracks_with_sizes_scrolled(),
|
|
||||||
move|track_index, track|Map::new(move||self.scenes_with_clip(track_index),
|
|
||||||
move|(s, scene, y1, y2, previous): SceneWith<'_, Option<ItemTheme>>, _|{
|
|
||||||
let (name, theme) = if let Some(clip) = &scene.clips[track_index] {
|
|
||||||
let clip = clip.read().unwrap();
|
|
||||||
(Some(clip.name.clone()), clip.color)
|
|
||||||
} else {
|
|
||||||
(None, ItemTheme::G[32])
|
|
||||||
};
|
|
||||||
let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
|
||||||
let same_track = self.track_selected() == Some(track_index);
|
|
||||||
let selected = same_track && self.scene_selected() == Some(s);
|
|
||||||
let neighbor = same_track && s > 0 && self.scene_selected() == Some(s - 1);
|
|
||||||
let is_last = self.scene_last() == s;
|
|
||||||
let fg = theme.lightest.rgb;
|
|
||||||
let bg = if selected { theme.light } else { theme.base }.rgb;
|
|
||||||
let hi = if let Some(previous) = previous {
|
|
||||||
if neighbor { previous.light.rgb } else { previous.base.rgb }
|
|
||||||
} else {
|
|
||||||
Reset
|
|
||||||
};
|
|
||||||
let lo = if is_last {
|
|
||||||
Reset
|
|
||||||
} else if selected {
|
|
||||||
theme.light.rgb
|
|
||||||
} else {
|
|
||||||
theme.base.rgb
|
|
||||||
};
|
|
||||||
let height = (1 + y2 - y1) as u16;
|
|
||||||
map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat {
|
|
||||||
width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
|
||||||
}), When(
|
|
||||||
self.is_editing() && same_track && self.scene_selected() == Some(s),
|
|
||||||
editor
|
|
||||||
)))
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
fn scenes_with_clip (&self, track_index: usize) -> impl Iterator<Item=SceneWith<'_, Option<ItemTheme>>> + Send + Sync {
|
|
||||||
self.scenes_iter().map(move|(s, scene, y1, y2)|(s, scene, y1, y2,
|
|
||||||
(s>0).then(||self.arrangement().scenes()[s-1].clips[track_index].as_ref()
|
|
||||||
.map(|c|c.read().unwrap().color)
|
|
||||||
.unwrap_or(ItemTheme::G[32]))))
|
|
||||||
}
|
|
||||||
/// A scene with size and color.
|
|
||||||
fn scenes_iter (&self) -> impl Iterator<Item=(usize, &Scene, usize, usize,)> + Send + Sync {
|
|
||||||
let selection = Has::<Selection>::get(self.arrangement());
|
|
||||||
self.arrangement().scenes_with_sizes(
|
|
||||||
self.is_editing(),
|
|
||||||
Self::H_SCENE, Self::H_EDITOR,
|
|
||||||
selection.track(), selection.scene(),
|
|
||||||
).map_while(|(s, scene, y1, y2)|(y2<=self.scenes_height() as usize)
|
|
||||||
.then_some((s, scene, y1, y2)))
|
|
||||||
}
|
|
||||||
fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> {
|
|
||||||
self.arrangement()
|
|
||||||
.tracks_with_sizes(
|
|
||||||
&self.arrangement().selection(),
|
|
||||||
self.is_editing().then_some(20/*FIXME*/)
|
|
||||||
)
|
|
||||||
.map_while(move|(t, track, x1, x2)|{
|
|
||||||
(self.width_mid() > x2 as u16).then_some((t, track, x1, x2))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ArrangerSceneRows for ArrangerView<'a> {
|
|
||||||
fn arrangement (&self) -> &Arrangement {
|
|
||||||
self.arrangement
|
|
||||||
}
|
|
||||||
fn scenes_height (&self) -> u16 {
|
|
||||||
self.scenes_height
|
|
||||||
}
|
|
||||||
fn width_side (&self) -> u16 {
|
|
||||||
self.width_side
|
|
||||||
}
|
|
||||||
fn width_mid (&self) -> u16 {
|
|
||||||
self.width_mid
|
|
||||||
}
|
|
||||||
fn scene_selected (&self) -> Option<usize> {
|
|
||||||
self.arrangement.selection.scene()
|
|
||||||
}
|
|
||||||
fn scene_last (&self) -> usize {
|
|
||||||
self.scene_last
|
|
||||||
}
|
|
||||||
fn track_selected (&self) -> Option<usize> {
|
|
||||||
self.arrangement.selection.track()
|
|
||||||
}
|
|
||||||
fn is_editing (&self) -> bool {
|
|
||||||
self.is_editing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
|
impl<T: Has<Vec<Scene>> + Send + Sync> HasScenes for T {}
|
||||||
|
|
||||||
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||||
|
|
@ -185,13 +28,22 @@ pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||||
}
|
}
|
||||||
/// Generate the default name for a new scene
|
/// Generate the default name for a new scene
|
||||||
fn scene_default_name (&self) -> Arc<str> {
|
fn scene_default_name (&self) -> Arc<str> {
|
||||||
format!("Sc{:3>}", self.scenes().len() + 1).into()
|
format!("s{:3>}", self.scenes().len() + 1).into()
|
||||||
}
|
}
|
||||||
fn scene_longest_name (&self) -> usize {
|
fn scene_longest_name (&self) -> usize {
|
||||||
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HasSceneScroll: HasScenes {
|
||||||
|
fn scene_scroll (&self) -> usize;
|
||||||
|
}
|
||||||
|
impl HasSceneScroll for Arrangement {
|
||||||
|
fn scene_scroll (&self) -> usize { self.scene_scroll }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SceneWith<'a, T: Send + Sync> = (usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
impl<T: HasScenes + HasTracks> AddScene for T {}
|
impl<T: HasScenes + HasTracks> AddScene for T {}
|
||||||
|
|
||||||
pub trait AddScene: HasScenes + HasTracks {
|
pub trait AddScene: HasScenes + HasTracks {
|
||||||
|
|
@ -220,3 +72,85 @@ pub trait AddScene: HasScenes + HasTracks {
|
||||||
Ok((index, &mut self.scenes_mut()[index]))
|
Ok((index, &mut self.scenes_mut()[index]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tengri_proc::expose]
|
||||||
|
impl Scene {
|
||||||
|
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!() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tengri_proc::command(Scene)]
|
||||||
|
impl SceneCommand {
|
||||||
|
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
|
||||||
|
std::mem::swap(&mut scene.name, &mut name);
|
||||||
|
Ok(Some(Self::SetName { name }))
|
||||||
|
}
|
||||||
|
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
|
||||||
|
std::mem::swap(&mut scene.color, &mut color);
|
||||||
|
Ok(Some(Self::SetColor { color }))
|
||||||
|
}
|
||||||
|
fn set_size (scene: &mut Scene, size: usize) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
||||||
|
|
||||||
|
pub trait HasScene: Has<Option<Scene>> + Send + Sync {
|
||||||
|
fn scene (&self) -> Option<&Scene> {
|
||||||
|
Has::<Option<Scene>>::get(self).as_ref()
|
||||||
|
}
|
||||||
|
fn scene_mut (&mut self) -> &mut Option<Scene> {
|
||||||
|
Has::<Option<Scene>>::get_mut(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//scene_scroll: Fill::y(Fixed::x(1, ScrollbarV {
|
||||||
|
//offset: arrangement.scene_scroll,
|
||||||
|
//length: h_scenes_area as usize,
|
||||||
|
//total: h_scenes as usize,
|
||||||
|
//})),
|
||||||
|
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl<'state> Context<'state, TrackCommand> for Arrangement {
|
|
||||||
fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option<TrackCommand> {
|
|
||||||
Context::get(&self, iter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
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!() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::command(Track)]
|
|
||||||
impl TrackCommand {
|
|
||||||
fn set_name (track: &mut Track, mut name: Arc<str>) -> Perhaps<Self> {
|
|
||||||
std::mem::swap(&mut name, &mut track.name);
|
|
||||||
Ok(Some(Self::SetName { name }))
|
|
||||||
}
|
|
||||||
fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps<Self> {
|
|
||||||
std::mem::swap(&mut color, &mut track.color);
|
|
||||||
Ok(Some(Self::SetColor { color }))
|
|
||||||
}
|
|
||||||
fn set_mute (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_solo (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_rec (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
let current = track.sequencer.recording;
|
|
||||||
let value = value.unwrap_or(!current);
|
|
||||||
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
|
||||||
}
|
|
||||||
fn set_mon (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
let current = track.sequencer.monitoring;
|
|
||||||
let value = value.unwrap_or(!current);
|
|
||||||
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
|
||||||
}
|
|
||||||
fn set_size (track: &mut Track, size: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_zoom (track: &mut Track, zoom: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn stop (track: &mut Track) -> Perhaps<Self> {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
midi_from: &[PortConnect],
|
|
||||||
midi_to: &[PortConnect],
|
|
||||||
) -> 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()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
|
||||||
pub fn new_with_sampler (
|
|
||||||
name: &impl AsRef<str>,
|
|
||||||
color: Option<ItemTheme>,
|
|
||||||
jack: &Jack,
|
|
||||||
clock: Option<&Clock>,
|
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
||||||
midi_from: &[PortConnect],
|
|
||||||
midi_to: &[PortConnect],
|
|
||||||
audio_from: &[&[PortConnect];2],
|
|
||||||
audio_to: &[&[PortConnect];2],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
|
||||||
track.devices.push(Device::Sampler(Sampler::new(
|
|
||||||
jack,
|
|
||||||
&format!("{}/sampler", name.as_ref()),
|
|
||||||
&[PortConnect::exact(format!("{}:{}",
|
|
||||||
jack.with_client(|c|c.name().to_string()),
|
|
||||||
track.sequencer.midi_outs[0].name()
|
|
||||||
))],
|
|
||||||
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 trait HasTrack {
|
|
||||||
fn track (&self) -> Option<&Track>;
|
|
||||||
fn track_mut (&mut self) -> Option<&mut Track>;
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<T: Has<Option<Track>>> HasTrack for T {
|
|
||||||
//fn track (&self) -> Option<&Track> {
|
|
||||||
//self.get().as_ref()
|
|
||||||
//}
|
|
||||||
//fn track_mut (&mut self) -> Option<&mut Track> {
|
|
||||||
//self.get_mut().as_mut()
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
impl<T: Has<Vec<Track>> + Has<Selection>> HasTrack for T {
|
|
||||||
fn track (&self) -> Option<&Track> {
|
|
||||||
let index = Has::<Selection>::get(self).track()?;
|
|
||||||
Has::<Vec<Track>>::get(self).get(index)
|
|
||||||
}
|
|
||||||
fn track_mut (&mut self) -> Option<&mut Track> {
|
|
||||||
let index = Has::<Selection>::get(self).track()?;
|
|
||||||
Has::<Vec<Track>>::get_mut(self).get_mut(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -45,11 +45,9 @@ pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Iterate over tracks with their corresponding sizes.
|
/// Iterate over tracks with their corresponding sizes.
|
||||||
fn tracks_with_sizes (
|
fn tracks_with_sizes (&self, selection: &Selection, editor_width: Option<usize>)
|
||||||
&self,
|
-> impl TracksSizes<'_>
|
||||||
selection: &Selection,
|
{
|
||||||
editor_width: Option<usize>
|
|
||||||
) -> impl TracksSizes<'_> {
|
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
let active_track = if let Some(width) = editor_width {
|
let active_track = if let Some(width) = editor_width {
|
||||||
selection.track()
|
selection.track()
|
||||||
|
|
@ -69,63 +67,263 @@ pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
||||||
const TRACK_SPACING: usize = 0;
|
const TRACK_SPACING: usize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Arrangement {
|
pub trait HasTrackScroll: HasTracks {
|
||||||
/// Add multiple tracks
|
fn track_scroll (&self) -> usize;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
impl HasTrackScroll for Arrangement {
|
||||||
Ok(())
|
fn track_scroll (&self) -> usize { self.track_scroll }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a track
|
impl<T: MaybeHas<Track>> HasTrack for T {
|
||||||
pub fn track_add (
|
fn track (&self) -> Option<&Track> {
|
||||||
&mut self,
|
self.get()
|
||||||
name: Option<&str>,
|
}
|
||||||
|
fn track_mut (&mut self) -> Option<&mut Track> {
|
||||||
|
self.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tengri_proc::expose]
|
||||||
|
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!() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tengri_proc::command(Track)]
|
||||||
|
impl TrackCommand {
|
||||||
|
fn set_name (track: &mut Track, mut name: Arc<str>) -> Perhaps<Self> {
|
||||||
|
std::mem::swap(&mut name, &mut track.name);
|
||||||
|
Ok(Some(Self::SetName { name }))
|
||||||
|
}
|
||||||
|
fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps<Self> {
|
||||||
|
std::mem::swap(&mut color, &mut track.color);
|
||||||
|
Ok(Some(Self::SetColor { color }))
|
||||||
|
}
|
||||||
|
fn set_mute (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn set_solo (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn set_rec (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
||||||
|
let current = track.sequencer.recording;
|
||||||
|
let value = value.unwrap_or(!current);
|
||||||
|
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
||||||
|
}
|
||||||
|
fn set_mon (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
||||||
|
let current = track.sequencer.monitoring;
|
||||||
|
let value = value.unwrap_or(!current);
|
||||||
|
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
||||||
|
}
|
||||||
|
fn set_size (track: &mut Track, size: usize) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn set_zoom (track: &mut Track, zoom: usize) -> Perhaps<Self> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn stop (track: &mut Track) -> Perhaps<Self> {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>,
|
color: Option<ItemTheme>,
|
||||||
mins: &[PortConnect],
|
jack: &Jack,
|
||||||
mouts: &[PortConnect],
|
clock: Option<&Clock>,
|
||||||
) -> Usually<(usize, &mut Track)> {
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
self.track_last += 1;
|
midi_from: &[PortConnect],
|
||||||
let name: Arc<str> = name.map_or_else(
|
midi_to: &[PortConnect],
|
||||||
||format!("Track{:02}", self.track_last).into(),
|
) -> Usually<Self> {
|
||||||
|x|x.to_string().into()
|
Ok(Self {
|
||||||
);
|
name: name.as_ref().into(),
|
||||||
let mut track = Track {
|
color: color.unwrap_or_default(),
|
||||||
width: (name.len() + 2).max(12),
|
|
||||||
color: color.unwrap_or_else(ItemTheme::random),
|
|
||||||
sequencer: Sequencer::new(
|
sequencer: Sequencer::new(
|
||||||
&format!("{name}"),
|
format!("{}/sequencer", name.as_ref()),
|
||||||
self.jack(),
|
jack,
|
||||||
Some(self.clock()),
|
clock,
|
||||||
None,
|
clip,
|
||||||
mins,
|
midi_from,
|
||||||
mouts
|
midi_to
|
||||||
)?,
|
)?,
|
||||||
name,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
})
|
||||||
self.tracks_mut().push(track);
|
}
|
||||||
let len = self.tracks().len();
|
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
||||||
let index = len - 1;
|
pub fn new_with_sampler (
|
||||||
for scene in self.scenes_mut().iter_mut() {
|
name: &impl AsRef<str>,
|
||||||
while scene.clips.len() < len {
|
color: Option<ItemTheme>,
|
||||||
scene.clips.push(None);
|
jack: &Jack,
|
||||||
|
clock: Option<&Clock>,
|
||||||
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
|
midi_from: &[PortConnect],
|
||||||
|
midi_to: &[PortConnect],
|
||||||
|
audio_from: &[&[PortConnect];2],
|
||||||
|
audio_to: &[&[PortConnect];2],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
||||||
|
track.devices.push(Device::Sampler(Sampler::new(
|
||||||
|
jack,
|
||||||
|
&format!("{}/sampler", name.as_ref()),
|
||||||
|
&[PortConnect::exact(format!("{}:{}",
|
||||||
|
jack.with_client(|c|c.name().to_string()),
|
||||||
|
track.sequencer.midi_outs[0].name()
|
||||||
|
))],
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((index, &mut self.tracks_mut()[index]))
|
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 trait HasTrack {
|
||||||
|
fn track (&self) -> Option<&Track>;
|
||||||
|
fn track_mut (&mut self) -> Option<&mut Track>;
|
||||||
|
fn view_midi_ins_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
self.track().map(|track|{
|
||||||
|
let ins = track.sequencer.midi_ins.len() as u16;
|
||||||
|
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "),
|
||||||
|
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
||||||
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
||||||
|
}
|
||||||
|
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
self.track().map(|track|{
|
||||||
|
let outs = track.sequencer.midi_outs.len() as u16;
|
||||||
|
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "),
|
||||||
|
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
||||||
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
||||||
|
}
|
||||||
|
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
self.track().and_then(|track|track.devices.get(0)).map(|device|{
|
||||||
|
let ins = device.audio_ins().len() as u16;
|
||||||
|
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "),
|
||||||
|
Map::south(1, ||device.audio_ins().iter(),
|
||||||
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
||||||
|
}
|
||||||
|
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
self.track().and_then(|track|track.devices.last()).map(|device|{
|
||||||
|
let outs = device.audio_outs().len() as u16;
|
||||||
|
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "),
|
||||||
|
Map::south(1, ||device.audio_outs().iter(),
|
||||||
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [PortConnect], usize, usize), _|
|
||||||
|
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
||||||
|
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" ", name))))),
|
||||||
|
Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1,
|
||||||
|
Fill::x(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: impl Fn()->T + Send + Sync + 'a
|
||||||
|
) -> impl Content<TuiOut> + 'a {
|
||||||
|
Map::new(iter, move|(
|
||||||
|
index, name, connections, y, y2
|
||||||
|
): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
|
||||||
|
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
||||||
|
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
|
||||||
|
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
|
||||||
|
Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x(""))))))))))
|
||||||
|
}
|
||||||
|
//track_scroll: Fill::x(Fixed::y(1, ScrollbarH {
|
||||||
|
//offset: arrangement.track_scroll,
|
||||||
|
//length: h_tracks_area as usize,
|
||||||
|
//total: h_scenes as usize,
|
||||||
|
//})),
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,159 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct ArrangerView<'a> {
|
impl Content<TuiOut> for Arrangement {
|
||||||
pub arrangement: &'a Arrangement,
|
|
||||||
pub is_editing: bool,
|
|
||||||
pub width: u16,
|
|
||||||
pub width_mid: u16,
|
|
||||||
pub width_side: u16,
|
|
||||||
pub scene_last: usize,
|
|
||||||
pub scene_scroll: Fill<Fixed<u16, ScrollbarV>>,
|
|
||||||
pub scene_selected: Option<usize>,
|
|
||||||
/// Height available to display scene/track content.
|
|
||||||
pub scenes_height: u16,
|
|
||||||
pub track_scroll: Fill<Fixed<u16, ScrollbarH>>,
|
|
||||||
pub track_selected: Option<usize>,
|
|
||||||
/// Height available to display track headers.
|
|
||||||
pub tracks_height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
|
||||||
pub fn new (
|
|
||||||
arrangement: &'a Arrangement,
|
|
||||||
editor: Option<&'a MidiEditor>
|
|
||||||
) -> Self {
|
|
||||||
let is_editing = editor.is_some();
|
|
||||||
let h_tracks_area = 5;
|
|
||||||
let h_scenes_area = (arrangement.height() as u16).saturating_sub(20);
|
|
||||||
let h_scenes = arrangement.h_scenes(is_editing);
|
|
||||||
Self {
|
|
||||||
arrangement,
|
|
||||||
is_editing,
|
|
||||||
width: arrangement.w_tracks_area(is_editing),
|
|
||||||
width_mid: arrangement.w_tracks_area(is_editing).saturating_sub(20),
|
|
||||||
width_side: 20,
|
|
||||||
scenes_height: h_scenes_area,
|
|
||||||
scene_selected: arrangement.selection().scene(),
|
|
||||||
scene_last: arrangement.scenes.len().saturating_sub(1),
|
|
||||||
scene_scroll: Fill::y(Fixed::x(1, ScrollbarV {
|
|
||||||
offset: arrangement.scene_scroll,
|
|
||||||
length: h_scenes_area as usize,
|
|
||||||
total: h_scenes as usize,
|
|
||||||
})),
|
|
||||||
tracks_height: h_tracks_area,
|
|
||||||
track_selected: arrangement.selection().track(),
|
|
||||||
track_scroll: Fill::x(Fixed::y(1, ScrollbarH {
|
|
||||||
offset: arrangement.track_scroll,
|
|
||||||
length: h_tracks_area as usize,
|
|
||||||
total: h_scenes as usize,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Content<TuiOut> for ArrangerView<'a> {
|
|
||||||
fn content (&self) -> impl Render<TuiOut> {
|
fn content (&self) -> impl Render<TuiOut> {
|
||||||
let ins = |x|Bsp::n(self.inputs(), x);
|
let ins = |x|Bsp::n(self.view_inputs_0(), x);
|
||||||
let tracks = |x|Bsp::s(self.tracks(), x);
|
let tracks = |x|Bsp::s(self.view_tracks_0(), x);
|
||||||
let devices = |x|Bsp::s(self.devices(), x);
|
let devices = |x|Bsp::s(self.view_devices_0(), x);
|
||||||
let outs = |x|Bsp::s(self.outputs(), x);
|
let outs = |x|Bsp::s(self.view_outputs_0(), x);
|
||||||
let bg = |x|Tui::bg(Reset, x);
|
let bg = |x|Tui::bg(Reset, x);
|
||||||
//let track_scroll = |x|Bsp::s(&self.track_scroll, x);
|
//let track_scroll = |x|Bsp::s(&self.track_scroll, x);
|
||||||
//let scene_scroll = |x|Bsp::e(&self.scene_scroll, x);
|
//let scene_scroll = |x|Bsp::e(&self.scene_scroll, x);
|
||||||
self.arrangement.size.of(outs(tracks(devices(ins(bg(self.scenes_view(&None)))))))
|
self.size.of(outs(tracks(devices(ins(bg(self.view_scenes_clips(&None)))))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ArrangerView<'a> {
|
impl Arrangement {
|
||||||
/// Render input matrix.
|
/// Render input matrix.
|
||||||
pub(crate) fn inputs (&'a self) -> impl Content<TuiOut> + 'a {
|
fn view_inputs_0 (&self) -> impl Content<TuiOut> + '_ {
|
||||||
Tui::bg(Reset, Bsp::s(
|
Tui::bg(Reset, Bsp::s(
|
||||||
self.input_intos(),
|
self.view_input_intos(),
|
||||||
Bsp::s(self.input_routes(), self.input_ports()),
|
Bsp::s(self.view_input_routes(), self.view_input_ports()),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
fn view_input_ports (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
let is_editing = false; //FIXME
|
||||||
|
Tryptich::top(1)
|
||||||
|
.left(20, button_3("i", "midi ins", format!("{}",
|
||||||
|
self.midi_ins().len()), is_editing))
|
||||||
|
.right(20, button_2("I", "add midi in", is_editing))
|
||||||
|
.middle(self.width().saturating_sub(40) as u16,
|
||||||
|
per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
|
move|t, track|{
|
||||||
|
let rec = track.sequencer.recording;
|
||||||
|
let mon = track.sequencer.monitoring;
|
||||||
|
let rec = if rec { White } else { track.color.darkest.rgb };
|
||||||
|
let mon = if mon { White } else { track.color.darkest.rgb };
|
||||||
|
let bg = if self.track_selected() == Some(t) {
|
||||||
|
track.color.light.rgb
|
||||||
|
} else {
|
||||||
|
track.color.base.rgb
|
||||||
|
};
|
||||||
|
//let bg2 = if t > 0 { track.color.base.rgb } else { Reset };
|
||||||
|
wrap(bg, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(
|
||||||
|
Tui::fg_bg(rec, bg, "Rec "),
|
||||||
|
Tui::fg_bg(mon, bg, "Mon "),
|
||||||
|
))))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
fn view_input_routes (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
Tryptich::top(self.h_inputs())
|
||||||
|
.left(self.width_side(),
|
||||||
|
io_ports(Tui::g(224), Tui::g(32), ||self.midi_ins_with_sizes()))
|
||||||
|
.middle(self.width_mid(),
|
||||||
|
per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
|
move|_, &Track { color, .. }|io_conns(
|
||||||
|
color.dark.rgb, color.darker.rgb, ||self.midi_ins_with_sizes())))
|
||||||
|
}
|
||||||
|
fn view_input_intos (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
Tryptich::top(2)
|
||||||
|
.left(self.width_side(),
|
||||||
|
Bsp::s(Align::e("Input:"), Align::e("Into clip:")))
|
||||||
|
.middle(self.width_mid(),
|
||||||
|
per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
|
|_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ ")))))
|
||||||
|
}
|
||||||
/// Render output matrix.
|
/// Render output matrix.
|
||||||
pub(crate) fn outputs (&'a self) -> impl Content<TuiOut> + 'a {
|
fn view_outputs_0 (&self) -> impl Content<TuiOut> + '_ {
|
||||||
Tui::bg(Reset, Align::n(Bsp::s(
|
Tui::bg(Reset, Align::n(Bsp::s(
|
||||||
Bsp::s(self.output_ports(), self.output_conns()),
|
Bsp::s(self.view_output_ports(), self.view_output_conns()),
|
||||||
Bsp::s(self.output_nexts(), self.output_froms()),
|
Bsp::s(self.view_output_nexts(), self.view_output_froms()),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
fn view_output_ports (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
Tryptich::top(1)
|
||||||
|
.left(self.width_side(), self.view_output_count())
|
||||||
|
.right(self.width_side(), self.view_output_add())
|
||||||
|
.middle(self.width_mid(), self.view_output_map())
|
||||||
|
}
|
||||||
|
fn view_output_count (&self) -> impl Content<TuiOut> {
|
||||||
|
button_3(
|
||||||
|
"o",
|
||||||
|
"midi outs",
|
||||||
|
format!("{}", self.midi_outs().len()),
|
||||||
|
false // self.is_editing()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn view_output_add (&self) -> impl Content<TuiOut> {
|
||||||
|
button_2("O", "add midi out", false /* is_editing */)
|
||||||
|
}
|
||||||
|
fn view_output_map (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
per_track_top(||self.tracks_with_sizes_scrolled(), move|i, t|{
|
||||||
|
let mute = false;
|
||||||
|
let solo = false;
|
||||||
|
let mute = if mute { White } else { t.color.darkest.rgb };
|
||||||
|
let solo = if solo { White } else { t.color.darkest.rgb };
|
||||||
|
let bg_1 = if self.track_selected() == Some(i) {
|
||||||
|
t.color.light.rgb
|
||||||
|
} else {
|
||||||
|
t.color.base.rgb
|
||||||
|
};
|
||||||
|
let bg_2 = if i > 0 { t.color.base.rgb } else { Reset };
|
||||||
|
let mute = Tui::fg_bg(mute, bg_1, "Play ");
|
||||||
|
let solo = Tui::fg_bg(solo, bg_1, "Solo ");
|
||||||
|
wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo))))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn view_output_conns (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
Tryptich::top(self.h_outputs())
|
||||||
|
.left(self.width_side(), io_ports(
|
||||||
|
Tui::g(224), Tui::g(32), ||self.midi_outs_with_sizes()))
|
||||||
|
.middle(self.width_mid(), per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
|
|_, t|io_conns(
|
||||||
|
t.color.dark.rgb,
|
||||||
|
t.color.darker.rgb,
|
||||||
|
||self.midi_outs_with_sizes()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn view_output_nexts (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
Tryptich::top(2).left(self.width_side(), Align::ne("From clip:"))
|
||||||
|
.middle(self.width_mid(), per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
|
|_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default())))))
|
||||||
|
}
|
||||||
|
fn view_output_froms (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
let label = Align::ne("Next clip:");
|
||||||
|
Tryptich::top(2).left(self.width_side(), label)
|
||||||
|
.middle(self.width_mid(), per_track_top(
|
||||||
|
||self.tracks_with_sizes_scrolled(), |t, track|{
|
||||||
|
let queued = track.sequencer.next_clip.is_some();
|
||||||
|
let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ "));
|
||||||
|
let queued_clip = Thunk::new(||{
|
||||||
|
Tui::bg(Reset, if let Some((_, clip)) = track.sequencer.next_clip.as_ref() {
|
||||||
|
if let Some(clip) = clip {
|
||||||
|
clip.read().unwrap().name.clone()
|
||||||
|
} else {
|
||||||
|
"Stop".into()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
Either(queued, queued_clip, queued_blank)
|
||||||
|
}))
|
||||||
|
}
|
||||||
/// Render track headers
|
/// Render track headers
|
||||||
pub(crate) fn tracks (&'a self) -> impl Content<TuiOut> + 'a {
|
fn view_tracks_0 (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let Self { width_side, width_mid, track_selected, is_editing, .. } = self;
|
let width_side = self.width_side();
|
||||||
|
let width_mid = self.width_mid();
|
||||||
|
let is_editing = false; // FIXME
|
||||||
|
let track_selected = self.track_selected();
|
||||||
Tryptich::center(3)
|
Tryptich::center(3)
|
||||||
.left(*width_side,
|
.left(width_side,
|
||||||
button_3("t", "track", format!("{}", self.arrangement.tracks.len()), *is_editing))
|
button_3("t", "track", format!("{}", self.tracks().len()), is_editing))
|
||||||
.right(*width_side, button_2("T", "add track", *is_editing))
|
.right(width_side, button_2("T", "add track", is_editing))
|
||||||
.middle(*width_mid, per_track(||self.tracks_with_sizes_scrolled(),
|
.middle(width_mid, per_track(||self.tracks_with_sizes_scrolled(),
|
||||||
|index, track|wrap(
|
move|index, track|wrap(
|
||||||
if *track_selected == Some(index) {
|
if track_selected == Some(index) {
|
||||||
track.color.light
|
track.color.light
|
||||||
} else {
|
} else {
|
||||||
track.color.base
|
track.color.base
|
||||||
|
|
@ -98,14 +163,17 @@ impl<'a> ArrangerView<'a> {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
/// Render device switches.
|
/// Render device switches.
|
||||||
pub(crate) fn devices (&'a self) -> impl Content<TuiOut> + 'a {
|
fn view_devices_0 (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let Self { width_side, width_mid, track_selected, is_editing, .. } = self;
|
let width_side = self.width_side();
|
||||||
|
let width_mid = self.width_mid();
|
||||||
|
let is_editing = false; // FIXME
|
||||||
|
let track_selected = self.track_selected();
|
||||||
Tryptich::top(1)
|
Tryptich::top(1)
|
||||||
.left(*width_side, button_3("d", "devices", format!("{}", 0), *is_editing))
|
.left(width_side, button_3("d", "devices", format!("{}", 0), is_editing))
|
||||||
.right(*width_side, button_2("D", "add device", *is_editing))
|
.right(width_side, button_2("D", "add device", is_editing))
|
||||||
.middle(*width_mid, per_track_top(||self.tracks_with_sizes_scrolled(),
|
.middle(width_mid, per_track_top(||self.tracks_with_sizes_scrolled(),
|
||||||
move|index, track|{
|
move|index, track|{
|
||||||
let bg = if *track_selected == Some(index) {
|
let bg = if track_selected == Some(index) {
|
||||||
track.color.light
|
track.color.light
|
||||||
} else {
|
} else {
|
||||||
track.color.base
|
track.color.base
|
||||||
|
|
@ -116,22 +184,363 @@ impl<'a> ArrangerView<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
impl<T> TracksView for T
|
||||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
where T: HasSize<TuiOut> + HasTrackScroll + HasSelection + HasMidiIns {}
|
||||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
|
||||||
) -> impl Content<TuiOut> + 'a {
|
impl ClipsView for Arrangement {}
|
||||||
Align::x(Tui::bg(Reset, Map::new(tracks,
|
|
||||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
pub trait TracksView: HasSize<TuiOut> + HasTrackScroll + HasSelection + HasMidiIns {
|
||||||
let width = (x2 - x1) as u16;
|
fn is_editing (&self) -> bool { false }
|
||||||
map_east(x1 as u16, width, Fixed::x(width, Tui::fg_bg(
|
fn tracks_width_available (&self) -> u16 {
|
||||||
track.color.lightest.rgb,
|
(self.width() as u16).saturating_sub(40)
|
||||||
track.color.base.rgb,
|
}
|
||||||
callback(index, track))))})))
|
fn tracks_with_sizes_scrolled <'t> (&'t self) -> impl TracksSizes<'t> {
|
||||||
|
self.tracks_with_sizes(&self.selection(), self.is_editing().then_some(20/*FIXME*/))
|
||||||
|
.map_while(move|(t, track, x1, x2)|
|
||||||
|
((x2 as u16) < self.tracks_width_available())
|
||||||
|
.then_some((t, track, x1, x2)))
|
||||||
|
}
|
||||||
|
fn view_track_names (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
let content = Fixed::y(1, Align::w(Tui::bg(theme.darker.rgb, Align::w(Fill::x(
|
||||||
|
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (index, track, x1, x2) in self
|
||||||
|
.tracks_with_sizes(&self.selection(), None)
|
||||||
|
.skip(self.track_scroll())
|
||||||
|
{
|
||||||
|
add(&Fixed::x(track.width as u16,
|
||||||
|
Tui::bg(if self.selection().track() == Some(index) {
|
||||||
|
track.color.light.rgb
|
||||||
|
} else {
|
||||||
|
track.color.base.rgb
|
||||||
|
}, Align::nw(Tui::fg(
|
||||||
|
Rgb(255, 255, 255), Tui::bold(true,
|
||||||
|
format!("{}", track.name)))))));
|
||||||
|
}
|
||||||
|
}))))));
|
||||||
|
Bsp::w(
|
||||||
|
self.view_track_header(theme, row!(
|
||||||
|
Tui::bold(true, button_2("t", "rack ", false)),
|
||||||
|
button_2("T", "+", false)
|
||||||
|
)),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
let mut max_outputs = 0u16;
|
||||||
|
for track in self.tracks().iter() {
|
||||||
|
max_outputs = max_outputs.max(track.sequencer.midi_outs.len() as u16);
|
||||||
|
}
|
||||||
|
let content = Align::w(Fixed::y(max_outputs + 1,
|
||||||
|
Tui::bg(theme.darker.rgb, Align::w(Fill::x(
|
||||||
|
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (index, track, x1, x2) in self
|
||||||
|
.tracks_with_sizes(&self.selection(), None)
|
||||||
|
.skip(self.track_scroll())
|
||||||
|
{
|
||||||
|
(add)(&Fixed::x(track.width as u16, Align::nw(Bsp::s(
|
||||||
|
Tui::bg(track.color.base.rgb, Fill::x(Align::w(format!("[mut] [sol]")))),
|
||||||
|
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
||||||
|
|port, index|Tui::fg(Rgb(255, 255, 255),
|
||||||
|
Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
|
||||||
|
format!("{index}: {}", port.name()))))))))));
|
||||||
|
}
|
||||||
|
}))))));
|
||||||
|
Bsp::w(
|
||||||
|
self.view_track_header(theme, row!(
|
||||||
|
Tui::bold(true, button_2("o", "utput", false)),
|
||||||
|
button_2("O", "+", false)
|
||||||
|
)),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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 = Tui::bg(theme.darker.rgb, Align::w(Fill::x(
|
||||||
|
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (index, track, x1, x2) in self
|
||||||
|
.tracks_with_sizes(&self.selection(), None)
|
||||||
|
.skip(self.track_scroll())
|
||||||
|
{
|
||||||
|
add(&Fixed::xy(track.width as u16, h + 1,
|
||||||
|
Align::nw(Bsp::s(
|
||||||
|
Tui::bg(track.color.base.rgb,
|
||||||
|
Fill::x(Align::w(format!("[rec] [mon]")))),
|
||||||
|
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!("{index}: {}", port.name())))))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
}))));
|
||||||
|
Bsp::w(
|
||||||
|
self.view_track_header(theme, row!(
|
||||||
|
Tui::bold(true, button_2("i", "nputs", false)),
|
||||||
|
button_2("I", "+", false)
|
||||||
|
)),
|
||||||
|
Fixed::y(h, Fill::x(Align::w(Fixed::y(h + 1, content)))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn view_track_devices <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
|
let mut h = 2u16;
|
||||||
|
for track in self.tracks().iter() {
|
||||||
|
h = h.max(track.devices.len() as u16);
|
||||||
|
}
|
||||||
|
Bsp::w(
|
||||||
|
self.view_track_header(theme, row!(
|
||||||
|
Tui::bold(true, button_2("d", "evice", false)),
|
||||||
|
button_2("D", "+", false)
|
||||||
|
)),
|
||||||
|
Fixed::y(h, Tui::bg(theme.darker.rgb, Align::w(Fill::x(Stack::east(
|
||||||
|
move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (index, track, x1, x2) in self
|
||||||
|
.tracks_with_sizes(&self.selection(), None)
|
||||||
|
.skip(self.track_scroll())
|
||||||
|
{
|
||||||
|
add(&Fixed::xy(track.width as u16, h + 1,
|
||||||
|
Tui::bg(track.color.dark.rgb, Align::nw(Map::south(1, move||0..h,
|
||||||
|
|_, index|format!("{index}: {}", "--------"))))));
|
||||||
|
}
|
||||||
|
}))))))
|
||||||
|
}
|
||||||
|
fn view_track_header <'a, T: Content<TuiOut>> (
|
||||||
|
&'a self, theme: ItemTheme, content: T
|
||||||
|
) -> impl Content<TuiOut> {
|
||||||
|
Fixed::x(20, Tui::bg(theme.darker.rgb, Fill::x(Align::e(content))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ScenesView: HasSelection + HasSceneScroll + Send + Sync {
|
||||||
|
/// Default scene height.
|
||||||
|
const H_SCENE: usize = 2;
|
||||||
|
/// Default editor height.
|
||||||
|
const H_EDITOR: usize = 15;
|
||||||
|
fn arrangement (&self) -> &Arrangement;
|
||||||
|
fn scene_selected (&self) -> Option<usize>;
|
||||||
|
fn track_selected (&self) -> Option<usize>;
|
||||||
|
fn scenes_height (&self) -> u16;
|
||||||
|
fn width_side (&self) -> u16;
|
||||||
|
fn width_mid (&self) -> u16;
|
||||||
|
fn view_scenes_names (&self) -> impl Content<TuiOut> {
|
||||||
|
Stack::south(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) {
|
||||||
|
add(&self.view_scene_name(index, scene));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn view_scene_name (&self, index: usize, scene: &Scene) -> impl Content<TuiOut> {
|
||||||
|
Fixed::xy(20, 2, Tui::bg(if self.selection().scene() == Some(index) {
|
||||||
|
scene.color.light.rgb
|
||||||
|
} else {
|
||||||
|
scene.color.base.rgb
|
||||||
|
}, Align::nw(Bsp::e(
|
||||||
|
format!(" {index:2} "),
|
||||||
|
Tui::fg(Rgb(255, 255, 255),
|
||||||
|
Tui::bold(true, format!("{}", scene.name)))))))
|
||||||
|
//let height = (1 + y2 - y1) as u16;
|
||||||
|
//let name = Some(scene.name.clone());
|
||||||
|
//let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name))));
|
||||||
|
//let selected = self.scene_selected() == Some(s);
|
||||||
|
//let neighbor = s > 0 && self.scene_selected() == Some(s - 1);
|
||||||
|
//let is_last = self.scenes().len().saturating_sub(1) == s;
|
||||||
|
//let theme = scene.color;
|
||||||
|
//let fg = theme.lightest.rgb;
|
||||||
|
//let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||||
|
//let hi = if let Some(previous) = previous {
|
||||||
|
//if neighbor { previous.light.rgb } else { previous.base.rgb }
|
||||||
|
//} else {
|
||||||
|
//Reset
|
||||||
|
//};
|
||||||
|
//let lo = if is_last { Reset } else if selected {
|
||||||
|
//theme.light.rgb
|
||||||
|
//} else {
|
||||||
|
//theme.base.rgb
|
||||||
|
//};
|
||||||
|
//add(&Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat {
|
||||||
|
//width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||||
|
//}))))
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
fn scenes_with_prev_color (&self) -> impl Iterator<Item=SceneWith<Option<ItemTheme>>> + Send + Sync {
|
||||||
|
self.scenes_iter().map(|(s, scene, y1, y2)|(s, scene, y1, y2,
|
||||||
|
(s>0).then(||self.arrangement().scenes()[s-1].color)))
|
||||||
|
}
|
||||||
|
fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
||||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||||
) -> impl Content<TuiOut> + 'a {
|
) -> impl Content<TuiOut> + 'a {
|
||||||
per_track_top(tracks, move|index, track|Fill::y(Align::y(callback(index, track))))
|
Map::new(tracks, move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||||
|
Tui::fg_bg(track.color.lightest.rgb, track.color.darker.rgb,
|
||||||
|
map_east(x1 as u16, (x2 - x1) as u16, callback(index, track))) })
|
||||||
|
}
|
||||||
|
fn scenes_with_clip (&self, track_index: usize) -> impl Iterator<Item=SceneWith<'_, Option<ItemTheme>>> + Send + Sync {
|
||||||
|
self.scenes_iter().map(move|(s, scene, y1, y2)|(s, scene, y1, y2,
|
||||||
|
(s>0).then(||self.arrangement().scenes()[s-1].clips[track_index].as_ref()
|
||||||
|
.map(|c|c.read().unwrap().color)
|
||||||
|
.unwrap_or(ItemTheme::G[32]))))
|
||||||
|
}
|
||||||
|
/// A scene with size and color.
|
||||||
|
fn scenes_iter (&self) -> impl Iterator<Item=(usize, &Scene, usize, usize,)> + Send + Sync {
|
||||||
|
let selection = Has::<Selection>::get(self.arrangement());
|
||||||
|
self.arrangement().scenes_with_sizes(
|
||||||
|
false, // FIXME self.is_editing(),
|
||||||
|
Self::H_SCENE, Self::H_EDITOR,
|
||||||
|
selection.track(), selection.scene(),
|
||||||
|
).map_while(|(s, scene, y1, y2)|(y2<=self.scenes_height() as usize)
|
||||||
|
.then_some((s, scene, y1, y2)))
|
||||||
|
}
|
||||||
|
/// Height required to display all scenes.
|
||||||
|
fn scenes_height_total (&self, is_editing: bool) -> u16 {
|
||||||
|
self.scenes_with_sizes(
|
||||||
|
is_editing,
|
||||||
|
Self::H_SCENE,
|
||||||
|
Self::H_EDITOR,
|
||||||
|
self.selection().track(),
|
||||||
|
self.selection().scene(),
|
||||||
|
)
|
||||||
|
.last()
|
||||||
|
.map(|(_, _, _, y)|y as u16).unwrap_or(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ClipsView: TracksView + ScenesView + Send + Sync {
|
||||||
|
fn view_scenes_clips <'a> (&'a self, editor: &'a Option<MidiEditor>)
|
||||||
|
-> impl Content<TuiOut> + 'a
|
||||||
|
{
|
||||||
|
Fill::xy(Stack::<TuiOut, _>::east(move|column: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (track_index, track, _, _) in self
|
||||||
|
.tracks_with_sizes(&self.selection(), None)
|
||||||
|
.skip(self.track_scroll())
|
||||||
|
{
|
||||||
|
//column(&Fixed::x(5, Fill::xy(Tui::bg(Green, "kyp"))));
|
||||||
|
column(&Fixed::x(
|
||||||
|
track.width as u16,
|
||||||
|
Fill::y(self.view_track_clips(track_index, track))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_track_clips <'a> (&'a self, track_index: usize, track: &Track) -> impl Content<TuiOut> {
|
||||||
|
Stack::south(move|cell: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
|
for (scene_index, scene) in self.scenes().iter().enumerate().skip(self.scene_scroll()) {
|
||||||
|
let (name, theme) = if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||||
|
let clip = clip.read().unwrap();
|
||||||
|
(Some(clip.name.clone()), clip.color)
|
||||||
|
} else {
|
||||||
|
(None, ItemTheme::G[32])
|
||||||
|
};
|
||||||
|
let fg = theme.lightest.rgb;
|
||||||
|
let mut outline = theme.base.rgb;
|
||||||
|
let bg = if self.selection().track() == Some(track_index)
|
||||||
|
&& self.selection().scene() == Some(scene_index)
|
||||||
|
{
|
||||||
|
outline = theme.lightest.rgb;
|
||||||
|
theme.light.rgb
|
||||||
|
} else if self.selection().track() == Some(track_index)
|
||||||
|
|| self.selection().scene() == Some(scene_index)
|
||||||
|
{
|
||||||
|
outline = theme.darkest.rgb;
|
||||||
|
theme.base.rgb
|
||||||
|
} else {
|
||||||
|
theme.dark.rgb
|
||||||
|
};
|
||||||
|
cell(&Fixed::xy(track.width as u16, 2, Bsp::b(
|
||||||
|
Fill::xy(Outer(true, Style::default().fg(outline))),
|
||||||
|
Fill::xy(Align::nw(Tui::fg_bg(fg, bg, Align::nw(name.unwrap_or(" ---- ".into()))))))));
|
||||||
|
//let (name, theme) = if let Some(clip) = &scene.clips.get(track_index).flatten() {
|
||||||
|
//let clip = clip.read().unwrap();
|
||||||
|
//(Some(clip.name.clone()), clip.color)
|
||||||
|
//} else {
|
||||||
|
//(None, ItemTheme::G[32])
|
||||||
|
//};
|
||||||
|
//let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name))));
|
||||||
|
//let same_track = self.track_selected() == Some(track_index);
|
||||||
|
//let selected = same_track && self.scene_selected() == Some(s);
|
||||||
|
//let neighbor = same_track && s > 0 && self.scene_selected() == Some(s - 1);
|
||||||
|
//let is_last = self.scenes().len().saturating_sub(1) == s;
|
||||||
|
//let fg = theme.lightest.rgb;
|
||||||
|
//let bg = if selected { theme.light } else { theme.base }.rgb;
|
||||||
|
//let hi = if let Some(previous) = previous {
|
||||||
|
//if neighbor { previous.light.rgb } else { previous.base.rgb }
|
||||||
|
//} else {
|
||||||
|
//Reset
|
||||||
|
//};
|
||||||
|
//let lo = if is_last {
|
||||||
|
//Reset
|
||||||
|
//} else if selected {
|
||||||
|
//theme.light.rgb
|
||||||
|
//} else {
|
||||||
|
//theme.base.rgb
|
||||||
|
//};
|
||||||
|
//let height = (1 + y2 - y1) as u16;
|
||||||
|
//let is_editing = false; //FIXME
|
||||||
|
//let editor = (); //FIXME
|
||||||
|
//cell(&Fixed::xy(track.width as u16, 2, Bsp::b(Fixed::y(height, Phat {
|
||||||
|
//width: 0, height: 0, content, colors: [fg, bg, hi, lo]
|
||||||
|
//}), When(
|
||||||
|
//is_editing && same_track && self.scene_selected() == Some(s),
|
||||||
|
//editor
|
||||||
|
//))))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scenes_clips_2 <'a> (
|
||||||
|
&'a self,
|
||||||
|
theme: ItemTheme
|
||||||
|
) -> impl Content<TuiOut> + 'a {
|
||||||
|
Fixed::y(self.scenes().len() as u16 * 2, Tui::bg(theme.darker.rgb,
|
||||||
|
Align::w(Fill::x(Map::new(||self.scenes().iter().skip(self.scene_scroll()),
|
||||||
|
move|scene: &'a Scene, index|self.track_scenes(index, scene))))))
|
||||||
|
}
|
||||||
|
fn track_scenes <'a> (
|
||||||
|
&'a self,
|
||||||
|
scene_index: usize,
|
||||||
|
scene: &'a Scene
|
||||||
|
) -> impl Content<TuiOut> + 'a {
|
||||||
|
Push::y(scene_index as u16 * 2u16, Fixed::xy(20, 2, Map::new(
|
||||||
|
move||scene.clips.iter().skip(self.track_scroll()),
|
||||||
|
move|clip: &'a Option<Arc<RwLock<MidiClip>>>, track_index|
|
||||||
|
self.track_scene_clip(scene_index, scene, track_index, clip))))
|
||||||
|
}
|
||||||
|
fn track_scene_clip (
|
||||||
|
&self,
|
||||||
|
scene_index: usize,
|
||||||
|
scene: &Scene,
|
||||||
|
track_index: usize,
|
||||||
|
clip: &Option<Arc<RwLock<MidiClip>>>
|
||||||
|
) -> impl Content<TuiOut> {
|
||||||
|
let (theme, text) = if let Some(clip) = clip {
|
||||||
|
let clip = clip.read().unwrap();
|
||||||
|
(clip.color, clip.name.clone())
|
||||||
|
} else {
|
||||||
|
(scene.color, Default::default())
|
||||||
|
};
|
||||||
|
Push::x(track_index as u16 * 14, Tui::bg(theme.dark.rgb, Bsp::e(
|
||||||
|
format!(" {scene_index:2} {track_index:2} "),
|
||||||
|
Tui::fg(Rgb(255, 255, 255),
|
||||||
|
Tui::bold(true, format!("{}", text))))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HasWidth {
|
||||||
|
const MIN_WIDTH: usize;
|
||||||
|
/// Increment track width.
|
||||||
|
fn width_inc (&mut self);
|
||||||
|
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
||||||
|
fn width_dec (&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub(crate) use std::path::PathBuf;
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
pub(crate) use std::ffi::OsString;
|
pub(crate) use std::ffi::OsString;
|
||||||
|
|
||||||
pub(crate) use ::tengri::{from, Usually, Perhaps, Has};
|
pub(crate) use ::tengri::{from, has, maybe_has, Usually, Perhaps, Has, MaybeHas};
|
||||||
pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}};
|
pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}};
|
||||||
pub(crate) use ::tek_engine::*;
|
pub(crate) use ::tek_engine::*;
|
||||||
pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage};
|
pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage};
|
||||||
|
|
|
||||||
|
|
@ -119,13 +119,13 @@ impl Sampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_sample_status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_sample_status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fixed::x(20, Outer(true, Style::default().fg(Tui::g(96))).enclose(draw_info_v(if let Some((_, sample)) = &self.recording {
|
Fixed::x(20, draw_info_v(if let Some((_, sample)) = &self.recording {
|
||||||
Some(sample)
|
Some(sample)
|
||||||
} else if let Some(sample) = &self.mapped[note_pt] {
|
} else if let Some(sample) = &self.mapped[note_pt] {
|
||||||
Some(sample)
|
Some(sample)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_status (&self, index: usize) -> impl Content<TuiOut> {
|
pub fn view_status (&self, index: usize) -> impl Content<TuiOut> {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Rela
|
||||||
pub(crate) use std::fmt::Debug;
|
pub(crate) use std::fmt::Debug;
|
||||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||||
|
|
||||||
pub(crate) use ::tengri::{from, Usually, Perhaps, Has, tui::*};
|
pub(crate) use ::tengri::{from, Usually, Perhaps, Has, has, maybe_has, tui::*};
|
||||||
|
|
||||||
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
pub use ::atomic_float; pub(crate) use atomic_float::*;
|
||||||
|
|
||||||
|
|
@ -23,15 +23,6 @@ pub use ::atomic_float; pub(crate) use atomic_float::*;
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
#[macro_export] macro_rules! has {
|
|
||||||
($T:ty: |$self:ident : $S:ty| $x:expr) => {
|
|
||||||
impl Has<$T> for $S {
|
|
||||||
fn get (&$self) -> &$T { &$x }
|
|
||||||
fn get_mut (&mut $self) -> &mut $T { &mut $x }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! as_ref {
|
#[macro_export] macro_rules! as_ref {
|
||||||
($T:ty: |$self:ident : $S:ty| $x:expr) => {
|
($T:ty: |$self:ident : $S:ty| $x:expr) => {
|
||||||
impl AsRef<$T> for $S {
|
impl AsRef<$T> for $S {
|
||||||
|
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4ff4ea81735548f808302c61b619ad7804e1eec0
|
Subproject commit a55e84c29f51606e0996f7f88b7664ca0d37365b
|
||||||
Loading…
Add table
Add a link
Reference in a new issue