mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-07-17 07:46:57 +02:00
'197e' is as much as i can tell ya
This commit is contained in:
parent
dcca74a649
commit
5ffa9472e1
7 changed files with 975 additions and 948 deletions
186
src/arrange.rs
186
src/arrange.rs
|
|
@ -1,8 +1,7 @@
|
||||||
use ::std::sync::{Arc, RwLock};
|
use ::std::sync::{Arc, RwLock};
|
||||||
use ::tengri::{space::east, color::ItemTheme};
|
use ::tengri::{space::east, color::ItemTheme};
|
||||||
use ::tengri::{draw::*, term::*};
|
use ::tengri::{draw::*, term::*};
|
||||||
use crate::{*, device::*, sequence::*, clock::*, select::*, sample::*};
|
use crate::{*, device::*, editor::*, sequence::*, clock::*, select::*, sample::*};
|
||||||
impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
|
|
||||||
/// Arranger.
|
/// Arranger.
|
||||||
///
|
///
|
||||||
|
|
@ -20,9 +19,9 @@ impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &sel
|
||||||
/// TODO rename to "render_cache" or smth
|
/// TODO rename to "render_cache" or smth
|
||||||
pub arranger: Arc<RwLock<Buffer>>,
|
pub arranger: Arc<RwLock<Buffer>>,
|
||||||
/// Display size
|
/// Display size
|
||||||
pub size: [AtomicUsize; 2],
|
pub size: Size,
|
||||||
/// Display size of clips area
|
/// Display size of clips area
|
||||||
pub size_inner: [AtomicUsize; 2],
|
pub size_inner: Size,
|
||||||
/// Source of time
|
/// Source of time
|
||||||
#[cfg(feature = "clock")] pub clock: Clock,
|
#[cfg(feature = "clock")] pub clock: Clock,
|
||||||
/// Allows one MIDI clip to be edited
|
/// Allows one MIDI clip to be edited
|
||||||
|
|
@ -84,7 +83,11 @@ impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &sel
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
impl_has!(Jack<'static>: |self: Arrangement| self.jack);
|
||||||
impl_has!([AtomicUsize; 2]: |self: Arrangement| self.size);
|
impl HasJack<'static> for Arrangement {
|
||||||
|
fn jack (&self) -> &Jack<'static> { &self.jack }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_has!(Size: |self: Arrangement| self.size);
|
||||||
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
||||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||||
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||||
|
|
@ -110,67 +113,69 @@ pub trait ClipsView: TracksView + ScenesView {
|
||||||
{
|
{
|
||||||
self.clips_size().of(wh_full(above(
|
self.clips_size().of(wh_full(above(
|
||||||
wh_full(origin_se(fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))),
|
wh_full(origin_se(fg(Green, format!("{}x{}", self.clips_size().w(), self.clips_size().h())))),
|
||||||
thunk(|to: &mut Tui|for (
|
thunk(|to: &mut Tui|{
|
||||||
track_index, track, _, _
|
for (track_index, track, _, _) in self.tracks_with_sizes() {
|
||||||
) in self.tracks_with_sizes() {
|
to.place(&w_exact(track.width as u16,
|
||||||
to.place(&w_exact(track.width as u16,
|
h_full(self.view_track_clips(track_index, track))))
|
||||||
h_full(self.view_track_clips(track_index, track))))
|
}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
}))))
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Draw<Tui> + 'a {
|
fn view_track_clips <'a> (&'a self, track_index: usize, track: &'a Track) -> impl Draw<Tui> + 'a {
|
||||||
thunk(move|to: &mut Tui|for (
|
thunk(move|to: &mut Tui|{
|
||||||
scene_index, scene, ..
|
for (scene_index, scene, ..) in self.scenes_with_sizes() {
|
||||||
) in self.scenes_with_sizes() {
|
let (name, theme): (Arc<str>, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||||
let (name, theme): (Arc<str>, ItemTheme) = if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
let clip = clip.read().unwrap();
|
||||||
let clip = clip.read().unwrap();
|
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
} else {
|
||||||
} else {
|
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
};
|
||||||
};
|
let fg = theme.lightest.term;
|
||||||
let fg = theme.lightest.term;
|
let mut outline = theme.base.term;
|
||||||
let mut outline = theme.base.term;
|
let bg = if self.selection().track() == Some(track_index)
|
||||||
let bg = if self.selection().track() == Some(track_index)
|
&& self.selection().scene() == Some(scene_index)
|
||||||
&& self.selection().scene() == Some(scene_index)
|
{
|
||||||
{
|
outline = theme.lighter.term;
|
||||||
outline = theme.lighter.term;
|
theme.light.term
|
||||||
theme.light.term
|
} else if self.selection().track() == Some(track_index)
|
||||||
} else if self.selection().track() == Some(track_index)
|
|| self.selection().scene() == Some(scene_index)
|
||||||
|| self.selection().scene() == Some(scene_index)
|
{
|
||||||
{
|
outline = theme.darkest.term;
|
||||||
outline = theme.darkest.term;
|
theme.base.term
|
||||||
theme.base.term
|
} else {
|
||||||
} else {
|
theme.dark.term
|
||||||
theme.dark.term
|
};
|
||||||
};
|
let w = if self.selection().track() == Some(track_index)
|
||||||
let w = if self.selection().track() == Some(track_index)
|
&& let Some(editor) = self.editor ()
|
||||||
&& let Some(editor) = self.editor ()
|
{
|
||||||
{
|
(editor.size.w() as usize).max(24).max(track.width)
|
||||||
(editor.measure_width() as usize).max(24).max(track.width)
|
} else {
|
||||||
} else {
|
track.width
|
||||||
track.width
|
} as u16;
|
||||||
} as u16;
|
let y = if self.selection().scene() == Some(scene_index)
|
||||||
let y = if self.selection().scene() == Some(scene_index)
|
&& let Some(editor) = self.editor ()
|
||||||
&& let Some(editor) = self.editor ()
|
{
|
||||||
{
|
(editor.size.h() as usize).max(12)
|
||||||
(editor.measure_height() as usize).max(12)
|
} else {
|
||||||
} else {
|
Self::H_SCENE as usize
|
||||||
Self::H_SCENE as usize
|
} as u16;
|
||||||
} as u16;
|
|
||||||
|
|
||||||
let is_selected =
|
let is_selected =
|
||||||
self.selection().track() == Some(track_index) &&
|
self.selection().track() == Some(track_index) &&
|
||||||
self.selection().scene() == Some(scene_index) &&
|
self.selection().scene() == Some(scene_index) &&
|
||||||
self.is_editing();
|
self.is_editing();
|
||||||
|
|
||||||
to.place(&wh_exact(w, y, below(
|
to.place(&wh_exact(Some(w), Some(y), below(
|
||||||
wh_full(Outer(true, Style::default().fg(outline))),
|
wh_full(Outer(true, Style::default().fg(outline))),
|
||||||
wh_full(below(
|
wh_full(below(
|
||||||
below(
|
below(
|
||||||
fg_bg(outline, bg, wh_full("")),
|
fg_bg(outline, bg, wh_full("")),
|
||||||
wh_full(origin_nw(fg_bg(fg, bg, bold(true, name)))),
|
wh_full(origin_nw(fg_bg(fg, bg, bold(true, &name)))),
|
||||||
),
|
),
|
||||||
wh_full(when(is_selected, self.editor().view())))))));
|
wh_full(when(is_selected, self.editor().map(|e|e.view()))))))));
|
||||||
|
}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,12 +184,12 @@ pub trait ClipsView: TracksView + ScenesView {
|
||||||
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
|
|
||||||
fn tracks_width_available (&self) -> u16 {
|
fn tracks_width_available (&self) -> u16 {
|
||||||
(self.measure_width() as u16).saturating_sub(40)
|
(self.size.width() as u16).saturating_sub(40)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over tracks with their corresponding sizes.
|
/// Iterate over tracks with their corresponding sizes.
|
||||||
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
||||||
let _editor_width = self.editor().map(|e|e.measure_width());
|
let _editor_width = self.editor().map(|e|e.size.w());
|
||||||
let _active_track = self.selection().track();
|
let _active_track = self.selection().track();
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
||||||
|
|
@ -222,23 +227,31 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
}, south(w_full(origin_nw(east(
|
}, south(w_full(origin_nw(east(
|
||||||
format!("·t{index:02} "),
|
format!("·t{index:02} "),
|
||||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||||
))), ""))) ));}}))))
|
))), ""))) ));}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
||||||
view_track_row_section(theme,
|
view_track_row_section(theme,
|
||||||
south(w_full(origin_w(button_2("o", "utput", false))),
|
south(w_full(origin_w(button_2("o", "utput", false))),
|
||||||
thunk(|to: &mut Tui|for port in self.midi_outs().iter() {
|
thunk(|to: &mut Tui|{
|
||||||
to.place(&w_full(origin_w(port.port_name())));
|
for port in self.midi_outs().iter() {
|
||||||
|
to.place(&w_full(origin_w(port.port_name())));
|
||||||
|
}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
})),
|
})),
|
||||||
button_2("O", "+", false),
|
button_2("O", "+", false),
|
||||||
bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{
|
bg(theme.darker.term, origin_w(thunk(|to: &mut Tui|{
|
||||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||||
to.place(&w_exact(track_width(index, track),
|
to.place(&w_exact(track_width(index, track),
|
||||||
origin_nw(h_full(iter_south(1, ||track.sequencer.midi_outs.iter(),
|
origin_nw(h_full(iter_south(||track.sequencer.midi_outs.iter(),
|
||||||
|port, index|fg(Rgb(255, 255, 255),
|
|port, index|fg(Rgb(255, 255, 255),
|
||||||
h_exact(1, bg(track.color.dark.term, w_full(origin_w(
|
h_exact(1, bg(track.color.dark.term, w_full(origin_w(
|
||||||
format!("·o{index:02} {}", port.port_name())))))))))));}}))))
|
format!("·o{index:02} {}", port.port_name())))))))))));
|
||||||
|
}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||||
|
|
@ -246,18 +259,21 @@ pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll {
|
||||||
for track in self.tracks().iter() {
|
for track in self.tracks().iter() {
|
||||||
h = h.max(track.sequencer.midi_ins.len() as u16);
|
h = h.max(track.sequencer.midi_ins.len() as u16);
|
||||||
}
|
}
|
||||||
let content = thunk(move|to: &mut Tui|for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
let content = thunk(move|to: &mut Tui|{
|
||||||
to.place(&wh_exact(track_width(index, track), h + 1,
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||||
origin_nw(south(
|
to.place(&wh_exact(track_width(index, track), h + 1,
|
||||||
bg(track.color.base.term,
|
origin_nw(south(
|
||||||
w_full(origin_w(east!(
|
bg(track.color.base.term,
|
||||||
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
|
w_full(origin_w(east!(
|
||||||
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
|
||||||
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
||||||
)))),
|
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
||||||
iter_south(1, ||track.sequencer.midi_ins.iter(),
|
)))),
|
||||||
|port, index|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
iter_south(1, ||track.sequencer.midi_ins.iter(),
|
||||||
w_full(origin_w(format!("·i{index:02} {}", port.port_name())))))))));
|
|port, index|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
||||||
|
w_full(origin_w(format!("·i{index:02} {}", port.port_name())))))))));
|
||||||
|
}
|
||||||
|
Ok(XYWH(0, 0, 0, 0))
|
||||||
});
|
});
|
||||||
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
||||||
bg(theme.darker.term, origin_w(content)))
|
bg(theme.darker.term, origin_w(content)))
|
||||||
|
|
@ -522,9 +538,9 @@ pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||||
impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } }
|
impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } }
|
||||||
impl ScenesView for App {
|
impl ScenesView for App {
|
||||||
fn w_mid (&self) -> u16 { (self.measure_width() as u16).saturating_sub(self.w_side()) }
|
fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(self.w_side()) }
|
||||||
fn w_side (&self) -> u16 { 20 }
|
fn w_side (&self) -> u16 { 20 }
|
||||||
fn h_scenes (&self) -> u16 { (self.measure_height() as u16).saturating_sub(20) }
|
fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) }
|
||||||
}
|
}
|
||||||
impl Scene {
|
impl Scene {
|
||||||
/// Returns the pulse length of the longest clip in the scene
|
/// Returns the pulse length of the longest clip in the scene
|
||||||
|
|
@ -780,17 +796,17 @@ impl Arrangement {
|
||||||
}
|
}
|
||||||
impl ScenesView for Arrangement {
|
impl ScenesView for Arrangement {
|
||||||
fn h_scenes (&self) -> u16 {
|
fn h_scenes (&self) -> u16 {
|
||||||
(self.measure_height() as u16).saturating_sub(20)
|
(self.size.h() as u16).saturating_sub(20)
|
||||||
}
|
}
|
||||||
fn w_side (&self) -> u16 {
|
fn w_side (&self) -> u16 {
|
||||||
(self.measure_width() as u16 * 2 / 10).max(20)
|
(self.size.w() as u16 * 2 / 10).max(20)
|
||||||
}
|
}
|
||||||
fn w_mid (&self) -> u16 {
|
fn w_mid (&self) -> u16 {
|
||||||
(self.measure_width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
(self.size.width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl HasClipsSize for Arrangement {
|
impl HasClipsSize for Arrangement {
|
||||||
fn clips_size (&self) -> &[AtomicUsize; 2] { &self.size_inner }
|
fn clips_size (&self) -> &Size { &self.size_inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SceneWith<'a, T> =
|
pub type SceneWith<'a, T> =
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ def_command!(FileBrowserCommand: |sampler: Sampler|{
|
||||||
pub filter: String,
|
pub filter: String,
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub scroll: usize,
|
pub scroll: usize,
|
||||||
pub size: [AtomicUsize; 2],
|
pub size: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct EntriesIterator<'a> {
|
pub(crate) struct EntriesIterator<'a> {
|
||||||
|
|
@ -330,7 +330,7 @@ impl Browse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Iterator for EntriesIterator<'a> {
|
impl<'a> Iterator for EntriesIterator<'a> {
|
||||||
type Item = Modify<&'a str>;
|
type Item = ();
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
fn next (&mut self) -> Option<Self::Item> {
|
||||||
let dirs = self.browser.dirs.len();
|
let dirs = self.browser.dirs.len();
|
||||||
let files = self.browser.files.len();
|
let files = self.browser.files.len();
|
||||||
|
|
|
||||||
818
src/editor.rs
Normal file
818
src/editor.rs
Normal file
|
|
@ -0,0 +1,818 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Contains state for viewing and editing a clip.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::sync::{Arc, RwLock};
|
||||||
|
/// let clip = tek::MidiClip::stop_all();
|
||||||
|
/// let mut editor = tek::MidiEditor {
|
||||||
|
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
||||||
|
/// size: Default::default(),
|
||||||
|
/// //keys: Default::default(),
|
||||||
|
/// };
|
||||||
|
/// let _ = editor.put_note(true);
|
||||||
|
/// let _ = editor.put_note(false);
|
||||||
|
/// let _ = editor.clip_status();
|
||||||
|
/// let _ = editor.edit_status();
|
||||||
|
/// ```
|
||||||
|
pub struct MidiEditor {
|
||||||
|
/// Size of editor on screen
|
||||||
|
pub size: Size,
|
||||||
|
/// View mode and state of editor
|
||||||
|
pub mode: PianoHorizontal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A clip, rendered as a horizontal piano roll.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let piano = tek::PianoHorizontal::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct PianoHorizontal {
|
||||||
|
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||||||
|
/// Buffer where the whole clip is rerendered on change
|
||||||
|
pub buffer: Arc<RwLock<BigBuffer>>,
|
||||||
|
/// Size of actual notes area
|
||||||
|
pub size: Size,
|
||||||
|
/// The display window
|
||||||
|
pub range: MidiSelection,
|
||||||
|
/// The note cursor
|
||||||
|
pub point: MidiCursor,
|
||||||
|
/// The highlight color palette
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Width of the keyboard
|
||||||
|
pub keys_width: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 12 piano keys, some highlighted.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let keys = tek::OctaveVertical::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct OctaveVertical {
|
||||||
|
pub on: [bool; 12],
|
||||||
|
pub colors: [Color; 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::MidiCursor::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)] pub struct MidiCursor {
|
||||||
|
/// Time coordinate of cursor
|
||||||
|
pub time_pos: Arc<AtomicUsize>,
|
||||||
|
/// Note coordinate of cursor
|
||||||
|
pub note_pos: Arc<AtomicUsize>,
|
||||||
|
/// Length of note that will be inserted, in pulses
|
||||||
|
pub note_len: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tek::{TimeRange, NoteRange};
|
||||||
|
/// let model = tek::MidiSelection::from((1, false));
|
||||||
|
///
|
||||||
|
/// let _ = model.get_time_len();
|
||||||
|
/// let _ = model.get_time_zoom();
|
||||||
|
/// let _ = model.get_time_lock();
|
||||||
|
/// let _ = model.get_time_start();
|
||||||
|
/// let _ = model.get_time_axis();
|
||||||
|
/// let _ = model.get_time_end();
|
||||||
|
///
|
||||||
|
/// let _ = model.get_note_lo();
|
||||||
|
/// let _ = model.get_note_axis();
|
||||||
|
/// let _ = model.get_note_hi();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
||||||
|
pub time_len: Arc<AtomicUsize>,
|
||||||
|
/// Length of visible time axis
|
||||||
|
pub time_axis: Arc<AtomicUsize>,
|
||||||
|
/// Earliest time displayed
|
||||||
|
pub time_start: Arc<AtomicUsize>,
|
||||||
|
/// Time step
|
||||||
|
pub time_zoom: Arc<AtomicUsize>,
|
||||||
|
/// Auto rezoom to fit in time axis
|
||||||
|
pub time_lock: Arc<AtomicBool>,
|
||||||
|
/// Length of visible note axis
|
||||||
|
pub note_axis: Arc<AtomicUsize>,
|
||||||
|
// Lowest note displayed
|
||||||
|
pub note_lo: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// use tek::{*, tengri::*};
|
||||||
|
///
|
||||||
|
/// struct Test(Option<MidiEditor>);
|
||||||
|
/// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref());
|
||||||
|
/// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut());
|
||||||
|
///
|
||||||
|
/// let mut host = Test(Some(MidiEditor::default()));
|
||||||
|
/// let _ = host.editor();
|
||||||
|
/// let _ = host.editor_mut();
|
||||||
|
/// let _ = host.is_editing();
|
||||||
|
/// let _ = host.editor_w();
|
||||||
|
/// let _ = host.editor_h();
|
||||||
|
/// ```
|
||||||
|
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
||||||
|
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
||||||
|
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
||||||
|
fn is_editing (&self) -> bool { self.editor().is_some() }
|
||||||
|
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
||||||
|
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl <T: NotePoint+TimePoint> MidiPoint for T {}
|
||||||
|
|
||||||
|
impl <T: TimeRange+NoteRange> MidiRange for T {}
|
||||||
|
|
||||||
|
impl <T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
||||||
|
|
||||||
|
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
||||||
|
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
||||||
|
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
||||||
|
DeleteNote => {
|
||||||
|
editor.redraw(); todo!() },
|
||||||
|
AppendNote { advance: bool } => {
|
||||||
|
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
||||||
|
SetNotePos { pos: usize } => {
|
||||||
|
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteLen { len: usize } => {
|
||||||
|
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteScroll { scroll: usize } => {
|
||||||
|
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetTimePos { pos: usize } => {
|
||||||
|
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeScroll { scroll: usize } => {
|
||||||
|
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeZoom { zoom: usize } => {
|
||||||
|
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeLock { lock: bool } => {
|
||||||
|
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||||
|
});
|
||||||
|
|
||||||
|
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
||||||
|
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||||
|
fn redraw (&self);
|
||||||
|
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>>;
|
||||||
|
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
|
||||||
|
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||||
|
*self.clip_mut() = clip.cloned();
|
||||||
|
self.redraw();
|
||||||
|
}
|
||||||
|
/// Make sure cursor is within note range
|
||||||
|
fn autoscroll (&self) {
|
||||||
|
let note_pos = self.get_note_pos().min(127);
|
||||||
|
let note_lo = self.get_note_lo();
|
||||||
|
let note_hi = self.get_note_hi();
|
||||||
|
if note_pos < note_lo {
|
||||||
|
self.note_lo().set(note_pos);
|
||||||
|
} else if note_pos > note_hi {
|
||||||
|
self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Make sure time range is within display
|
||||||
|
fn autozoom (&self) {
|
||||||
|
if self.time_lock().get() {
|
||||||
|
let time_len = self.get_time_len();
|
||||||
|
let time_axis = self.get_time_axis();
|
||||||
|
let time_zoom = self.get_time_zoom();
|
||||||
|
loop {
|
||||||
|
let time_zoom = self.time_zoom().get();
|
||||||
|
let time_area = time_axis * time_zoom;
|
||||||
|
if time_area > time_len {
|
||||||
|
let next_time_zoom = note_duration_prev(time_zoom);
|
||||||
|
if next_time_zoom <= 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let next_time_area = time_axis * next_time_zoom;
|
||||||
|
if next_time_area >= time_len {
|
||||||
|
self.time_zoom().set(next_time_zoom);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if time_area < time_len {
|
||||||
|
let prev_time_zoom = note_duration_next(time_zoom);
|
||||||
|
if prev_time_zoom > 384 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let prev_time_area = time_axis * prev_time_zoom;
|
||||||
|
if prev_time_area <= time_len {
|
||||||
|
self.time_zoom().set(prev_time_zoom);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time_zoom != self.time_zoom().get() {
|
||||||
|
self.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//while time_len.div_ceil(time_zoom) > time_axis {
|
||||||
|
//println!("\r{time_len} {time_zoom} {time_axis}");
|
||||||
|
//time_zoom = Note::next(time_zoom);
|
||||||
|
//}
|
||||||
|
//self.time_zoom().set(time_zoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NotePoint {
|
||||||
|
fn note_len (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current length of the note cursor.
|
||||||
|
fn get_note_len (&self) -> usize {
|
||||||
|
self.note_len().load(Relaxed)
|
||||||
|
}
|
||||||
|
/// Set the length of the note cursor, returning the previous value.
|
||||||
|
fn set_note_len (&self, x: usize) -> usize {
|
||||||
|
self.note_len().swap(x, Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_pos (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current pitch of the note cursor.
|
||||||
|
fn get_note_pos (&self) -> usize {
|
||||||
|
self.note_pos().load(Relaxed).min(127)
|
||||||
|
}
|
||||||
|
/// Set the current pitch fo the note cursor, returning the previous value.
|
||||||
|
fn set_note_pos (&self, x: usize) -> usize {
|
||||||
|
self.note_pos().swap(x.min(127), Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TimePoint {
|
||||||
|
fn time_pos (&self) -> &AtomicUsize;
|
||||||
|
/// Get the current time position of the note cursor.
|
||||||
|
fn get_time_pos (&self) -> usize {
|
||||||
|
self.time_pos().load(Relaxed)
|
||||||
|
}
|
||||||
|
/// Set the current time position of the note cursor, returning the previous value.
|
||||||
|
fn set_time_pos (&self, x: usize) -> usize {
|
||||||
|
self.time_pos().swap(x, Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiPoint: NotePoint + TimePoint {
|
||||||
|
/// Get the current end of the note cursor.
|
||||||
|
fn get_note_end (&self) -> usize {
|
||||||
|
self.get_time_pos() + self.get_note_len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TimeRange {
|
||||||
|
fn time_len (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_len (&self) -> usize {
|
||||||
|
self.time_len().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn time_zoom (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_zoom (&self) -> usize {
|
||||||
|
self.time_zoom().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_zoom (&self, value: usize) -> usize {
|
||||||
|
self.time_zoom().swap(value, Relaxed)
|
||||||
|
}
|
||||||
|
fn time_lock (&self) -> &AtomicBool;
|
||||||
|
fn get_time_lock (&self) -> bool {
|
||||||
|
self.time_lock().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_lock (&self, value: bool) -> bool {
|
||||||
|
self.time_lock().swap(value, Relaxed)
|
||||||
|
}
|
||||||
|
fn time_start (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_start (&self) -> usize {
|
||||||
|
self.time_start().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn set_time_start (&self, value: usize) -> usize {
|
||||||
|
self.time_start().swap(value, Relaxed)
|
||||||
|
}
|
||||||
|
fn time_axis (&self) -> &AtomicUsize;
|
||||||
|
fn get_time_axis (&self) -> usize {
|
||||||
|
self.time_axis().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn get_time_end (&self) -> usize {
|
||||||
|
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NoteRange {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize;
|
||||||
|
fn note_axis (&self) -> &AtomicUsize;
|
||||||
|
|
||||||
|
fn get_note_lo (&self) -> usize {
|
||||||
|
self.note_lo().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn set_note_lo (&self, x: usize) -> usize {
|
||||||
|
self.note_lo().swap(x, Relaxed)
|
||||||
|
}
|
||||||
|
fn get_note_axis (&self) -> usize {
|
||||||
|
self.note_axis().load(Relaxed)
|
||||||
|
}
|
||||||
|
fn get_note_hi (&self) -> usize {
|
||||||
|
(self.get_note_lo() + self.get_note_axis().saturating_sub(1)).min(127)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MidiRange: TimeRange + NoteRange {}
|
||||||
|
|
||||||
|
impl_has!(Size: |self: MidiEditor| self.size);
|
||||||
|
impl_has!(Size: |self: PianoHorizontal| self.size);
|
||||||
|
|
||||||
|
impl Draw<Tui> for MidiEditor {
|
||||||
|
fn draw(self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
self.tui().draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Draw<Tui> for PianoHorizontal {
|
||||||
|
fn draw(self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
||||||
|
self.tui().draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Debug for MidiEditor {
|
||||||
|
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("MidiEditor").field("mode", &self.mode).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_from!(MidiEditor: |clip: &Arc<RwLock<MidiClip>>| {
|
||||||
|
let model = Self::from(Some(clip.clone()));
|
||||||
|
model.redraw();
|
||||||
|
model
|
||||||
|
});
|
||||||
|
impl_from!(MidiEditor: |clip: Option<Arc<RwLock<MidiClip>>>| {
|
||||||
|
let mut model = Self::default();
|
||||||
|
*model.clip_mut() = clip;
|
||||||
|
model.redraw();
|
||||||
|
model
|
||||||
|
});
|
||||||
|
impl_default!(MidiEditor: Self {
|
||||||
|
size: [0, 0].into(), mode: PianoHorizontal::new(None)
|
||||||
|
});
|
||||||
|
impl_default!(OctaveVertical: Self {
|
||||||
|
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
|
||||||
|
});
|
||||||
|
impl MidiEditor {
|
||||||
|
/// Put note at current position
|
||||||
|
pub fn put_note (&mut self, advance: bool) {
|
||||||
|
let mut redraw = false;
|
||||||
|
if let Some(clip) = self.clip() {
|
||||||
|
let mut clip = clip.write().unwrap();
|
||||||
|
let note_start = self.get_time_pos();
|
||||||
|
let note_pos = self.get_note_pos();
|
||||||
|
let note_len = self.get_note_len();
|
||||||
|
let note_end = note_start + (note_len.saturating_sub(1));
|
||||||
|
let key: u7 = u7::from(note_pos as u8);
|
||||||
|
let vel: u7 = 100.into();
|
||||||
|
let length = clip.length;
|
||||||
|
let note_end = note_end % length;
|
||||||
|
let note_on = MidiMessage::NoteOn { key, vel };
|
||||||
|
if !clip.notes[note_start].iter().any(|msg|*msg == note_on) {
|
||||||
|
clip.notes[note_start].push(note_on);
|
||||||
|
}
|
||||||
|
let note_off = MidiMessage::NoteOff { key, vel };
|
||||||
|
if !clip.notes[note_end].iter().any(|msg|*msg == note_off) {
|
||||||
|
clip.notes[note_end].push(note_off);
|
||||||
|
}
|
||||||
|
if advance {
|
||||||
|
self.set_time_pos((note_end + 1) % clip.length);
|
||||||
|
}
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
if redraw {
|
||||||
|
self.mode.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> { todo!() }
|
||||||
|
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) }
|
||||||
|
fn note_length (&self) -> usize { self.get_note_len() }
|
||||||
|
fn note_pos (&self) -> usize { self.get_note_pos() }
|
||||||
|
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 }
|
||||||
|
fn note_pos_next_octave (&self) -> usize { self.get_note_pos() + 12 }
|
||||||
|
fn note_pos_prev (&self) -> usize { self.get_note_pos().saturating_sub(1) }
|
||||||
|
fn note_pos_prev_octave (&self) -> usize { self.get_note_pos().saturating_sub(12) }
|
||||||
|
fn note_len (&self) -> usize { self.get_note_len() }
|
||||||
|
fn note_len_next (&self) -> usize { self.get_note_len() + 1 }
|
||||||
|
fn note_len_prev (&self) -> usize { self.get_note_len().saturating_sub(1) }
|
||||||
|
fn note_range (&self) -> usize { self.get_note_axis() }
|
||||||
|
fn note_range_next (&self) -> usize { self.get_note_axis() + 1 }
|
||||||
|
fn note_range_prev (&self) -> usize { self.get_note_axis().saturating_sub(1) }
|
||||||
|
fn time_zoom (&self) -> usize { self.get_time_zoom() }
|
||||||
|
fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 }
|
||||||
|
fn time_zoom_next_fine (&self) -> usize { self.get_time_zoom() + 1 }
|
||||||
|
fn time_zoom_prev (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) }
|
||||||
|
fn time_zoom_prev_fine (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) }
|
||||||
|
fn time_lock (&self) -> bool { self.get_time_lock() }
|
||||||
|
fn time_lock_toggled (&self) -> bool { !self.get_time_lock() }
|
||||||
|
fn time_pos (&self) -> usize { self.get_time_pos() }
|
||||||
|
fn time_pos_next (&self) -> usize { (self.get_time_pos() + self.get_note_len()) % self.clip_length() }
|
||||||
|
fn time_pos_next_fine (&self) -> usize { (self.get_time_pos() + 1) % self.clip_length() }
|
||||||
|
fn time_pos_prev (&self) -> usize {
|
||||||
|
let step = self.get_note_len();
|
||||||
|
self.get_time_pos().overflowing_sub(step)
|
||||||
|
.0.min(self.clip_length().saturating_sub(step))
|
||||||
|
}
|
||||||
|
fn time_pos_prev_fine (&self) -> usize {
|
||||||
|
self.get_time_pos().overflowing_sub(1)
|
||||||
|
.0.min(self.clip_length().saturating_sub(1))
|
||||||
|
}
|
||||||
|
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
|
||||||
|
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||||
|
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
||||||
|
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
|
||||||
|
w_exact(20, south!(
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("f2", "name ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{name} "))))))),
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("l", "ength ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{length} "))))))),
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("r", "epeat ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{looped} "))))))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
|
||||||
|
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
||||||
|
(clip.color, clip.length)
|
||||||
|
} else { (ItemTheme::G[64], 0) };
|
||||||
|
let time_pos = self.get_time_pos();
|
||||||
|
let time_zoom = self.get_time_zoom();
|
||||||
|
let time_lock = if self.get_time_lock() { "[lock]" } else { " " };
|
||||||
|
let note_pos = self.get_note_pos();
|
||||||
|
let note_name = format!("{:4}", note_pitch_to_name(note_pos));
|
||||||
|
let note_pos = format!("{:>3}", note_pos);
|
||||||
|
let note_len = format!("{:>4}", self.get_note_len());
|
||||||
|
w_exact(20, south!(
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("t", "ime ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
||||||
|
format!("{length} /{time_zoom} +{time_pos} "))))))),
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("z", "lock ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
||||||
|
format!("{time_lock}"))))))),
|
||||||
|
w_full(origin_w(east(
|
||||||
|
button_2("x", "note ", false),
|
||||||
|
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
||||||
|
format!("{note_name} {note_pos} {note_len}"))))))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeRange for MidiEditor {
|
||||||
|
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
||||||
|
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
|
||||||
|
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
|
||||||
|
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
|
||||||
|
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteRange for MidiEditor {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
|
||||||
|
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotePoint for MidiEditor {
|
||||||
|
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
|
||||||
|
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePoint for MidiEditor {
|
||||||
|
fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiViewer for MidiEditor {
|
||||||
|
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
|
||||||
|
fn redraw (&self) { self.mode.redraw() }
|
||||||
|
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
|
||||||
|
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
|
||||||
|
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View<Tui> for MidiEditor {
|
||||||
|
fn view (&self) -> impl Draw<Tui> {
|
||||||
|
self.autoscroll();
|
||||||
|
/*self.autozoom();*/
|
||||||
|
self.size.of(&self.mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl PianoHorizontal {
|
||||||
|
pub fn new (clip: Option<&Arc<RwLock<MidiClip>>>) -> Self {
|
||||||
|
let size = [0, 0].into();
|
||||||
|
let mut range = MidiSelection::from((12, true));
|
||||||
|
range.time_axis = size.x.clone();
|
||||||
|
range.note_axis = size.y.clone();
|
||||||
|
let piano = Self {
|
||||||
|
keys_width: 5,
|
||||||
|
size,
|
||||||
|
range,
|
||||||
|
buffer: RwLock::new(Default::default()).into(),
|
||||||
|
point: MidiCursor::default(),
|
||||||
|
clip: clip.cloned(),
|
||||||
|
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]),
|
||||||
|
};
|
||||||
|
piano.redraw();
|
||||||
|
piano
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PianoHorizontal {
|
||||||
|
fn tui (&self) -> impl Draw<Tui> {
|
||||||
|
south(
|
||||||
|
east(w_exact(5, format!("{}x{}", self.size.w(), self.size.h())), self.timeline()),
|
||||||
|
east(self.keys(), self.size.of(below(wh_full(self.notes()), wh_full(self.cursor())))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PianoHorizontal {
|
||||||
|
/// Draw the piano roll background.
|
||||||
|
///
|
||||||
|
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||||
|
fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize, note_point: usize, time_point: usize) {
|
||||||
|
for (y, note) in (0..=127).rev().enumerate() {
|
||||||
|
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
|
||||||
|
let cell = buf.get_mut(x, y).unwrap();
|
||||||
|
if note == (127-note_point) || time == time_point {
|
||||||
|
cell.set_bg(Rgb(0,0,0));
|
||||||
|
} else {
|
||||||
|
cell.set_bg(clip.color.darkest.term);
|
||||||
|
}
|
||||||
|
if time % 384 == 0 {
|
||||||
|
cell.set_fg(clip.color.darker.term);
|
||||||
|
cell.set_char('│');
|
||||||
|
} else if time % 96 == 0 {
|
||||||
|
cell.set_fg(clip.color.dark.term);
|
||||||
|
cell.set_char('╎');
|
||||||
|
} else if time % note_len == 0 {
|
||||||
|
cell.set_fg(clip.color.darker.term);
|
||||||
|
cell.set_char('┊');
|
||||||
|
} else if (127 - note) % 12 == 0 {
|
||||||
|
cell.set_fg(clip.color.darker.term);
|
||||||
|
cell.set_char('=');
|
||||||
|
} else if (127 - note) % 6 == 0 {
|
||||||
|
cell.set_fg(clip.color.darker.term);
|
||||||
|
cell.set_char('—');
|
||||||
|
} else {
|
||||||
|
cell.set_fg(clip.color.darker.term);
|
||||||
|
cell.set_char('·');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Draw the piano roll foreground.
|
||||||
|
///
|
||||||
|
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
||||||
|
fn draw_fg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize) {
|
||||||
|
let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0));
|
||||||
|
let mut notes_on = [false;128];
|
||||||
|
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
|
||||||
|
for (_y, note) in (0..=127).rev().enumerate() {
|
||||||
|
if let Some(cell) = buf.get_mut(x, note) {
|
||||||
|
if notes_on[note] {
|
||||||
|
cell.set_char('▂');
|
||||||
|
cell.set_style(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let time_end = time_start + zoom;
|
||||||
|
for time in time_start..time_end.min(clip.length) {
|
||||||
|
for event in clip.notes[time].iter() {
|
||||||
|
match event {
|
||||||
|
MidiMessage::NoteOn { key, .. } => {
|
||||||
|
let note = key.as_int() as usize;
|
||||||
|
if let Some(cell) = buf.get_mut(x, note) {
|
||||||
|
cell.set_char('█');
|
||||||
|
cell.set_style(style);
|
||||||
|
}
|
||||||
|
notes_on[note] = true
|
||||||
|
},
|
||||||
|
MidiMessage::NoteOff { key, .. } => {
|
||||||
|
notes_on[key.as_int() as usize] = false
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn notes (&self) -> impl Draw<Tui> {
|
||||||
|
let time_start = self.get_time_start();
|
||||||
|
let note_lo = self.get_note_lo();
|
||||||
|
let note_hi = self.get_note_hi();
|
||||||
|
let buffer = self.buffer.clone();
|
||||||
|
Thunk::new(move|to: &mut Tui|{
|
||||||
|
let source = buffer.read().unwrap();
|
||||||
|
let XYWH(x0, y0, w, _h) = to.area();
|
||||||
|
//if h as usize != note_axis {
|
||||||
|
//panic!("area height mismatch: {h} <> {note_axis}");
|
||||||
|
//}
|
||||||
|
for (area_x, screen_x) in (x0..x0+w).enumerate() {
|
||||||
|
for (area_y, screen_y, _note) in note_y_iter(note_lo, note_hi, y0) {
|
||||||
|
let source_x = time_start + area_x;
|
||||||
|
let source_y = note_hi - area_y;
|
||||||
|
// TODO: enable loop rollover:
|
||||||
|
//let source_x = (time_start + area_x) % source.width.max(1);
|
||||||
|
//let source_y = (note_hi - area_y) % source.height.max(1);
|
||||||
|
let is_in_x = source_x < source.width;
|
||||||
|
let is_in_y = source_y < source.height;
|
||||||
|
if is_in_x && is_in_y {
|
||||||
|
if let Some(source_cell) = source.get(source_x, source_y) {
|
||||||
|
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) {
|
||||||
|
*cell = source_cell.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn cursor (&self) -> impl Draw<Tui> {
|
||||||
|
let note_hi = self.get_note_hi();
|
||||||
|
let note_lo = self.get_note_lo();
|
||||||
|
let note_pos = self.get_note_pos();
|
||||||
|
let note_len = self.get_note_len();
|
||||||
|
let time_pos = self.get_time_pos();
|
||||||
|
let time_start = self.get_time_start();
|
||||||
|
let time_zoom = self.get_time_zoom();
|
||||||
|
let style = Some(Style::default().fg(self.color.lightest.term));
|
||||||
|
Thunk::new(move|to: &mut Tui|{
|
||||||
|
let XYWH(x0, y0, w, _) = to.area();
|
||||||
|
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||||
|
if note == note_pos {
|
||||||
|
for x in 0..w {
|
||||||
|
let screen_x = x0 + x;
|
||||||
|
let time_1 = time_start + x as usize * time_zoom;
|
||||||
|
let time_2 = time_1 + time_zoom;
|
||||||
|
if time_1 <= time_pos && time_pos < time_2 {
|
||||||
|
to.blit(&"█", screen_x, screen_y, style);
|
||||||
|
let tail = note_len as u16 / time_zoom as u16;
|
||||||
|
for x_tail in (screen_x + 1)..(screen_x + tail) {
|
||||||
|
to.blit(&"▂", x_tail, screen_y, style);
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn keys (&self) -> impl Draw<Tui> {
|
||||||
|
let state = self;
|
||||||
|
let color = state.color;
|
||||||
|
let note_lo = state.get_note_lo();
|
||||||
|
let note_hi = state.get_note_hi();
|
||||||
|
let note_pos = state.get_note_pos();
|
||||||
|
let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0)));
|
||||||
|
let off_style = Some(Style::default().fg(Tui::g(255)));
|
||||||
|
let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.term).bold());
|
||||||
|
h_full(w_exact(self.keys_width, Thunk::new(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y0, _w, _h) = to.area();
|
||||||
|
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
||||||
|
to.blit(&to_key(note), x, screen_y, key_style);
|
||||||
|
if note > 127 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if note == note_pos {
|
||||||
|
to.blit(&format!("{:<5}", note_pitch_to_name(note)), x, screen_y, on_style)
|
||||||
|
} else {
|
||||||
|
to.blit(¬e_pitch_to_name(note), x, screen_y, off_style)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
fn timeline (&self) -> impl Draw<Tui> + '_ {
|
||||||
|
w_full(h_exact(1, Thunk::new(move|to: &mut Tui|{
|
||||||
|
let XYWH(x, y, w, _h) = to.area();
|
||||||
|
let style = Some(Style::default().dim());
|
||||||
|
let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
||||||
|
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
|
||||||
|
let t = area_x as usize * self.time_zoom().get();
|
||||||
|
if t < length {
|
||||||
|
to.blit(&"|", screen_x, y, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeRange for PianoHorizontal {
|
||||||
|
fn time_len (&self) -> &AtomicUsize { self.range.time_len() }
|
||||||
|
fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() }
|
||||||
|
fn time_lock (&self) -> &AtomicBool { self.range.time_lock() }
|
||||||
|
fn time_start (&self) -> &AtomicUsize { self.range.time_start() }
|
||||||
|
fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteRange for PianoHorizontal {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
|
||||||
|
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotePoint for PianoHorizontal {
|
||||||
|
fn note_len (&self) -> &AtomicUsize { self.point.note_len() }
|
||||||
|
fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePoint for PianoHorizontal {
|
||||||
|
fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiViewer for PianoHorizontal {
|
||||||
|
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { &self.clip }
|
||||||
|
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { &mut self.clip }
|
||||||
|
/// Determine the required space to render the clip.
|
||||||
|
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
|
||||||
|
(clip.length / self.range.time_zoom().get(), 128)
|
||||||
|
}
|
||||||
|
fn redraw (&self) {
|
||||||
|
*self.buffer.write().unwrap() = if let Some(clip) = self.clip.as_ref() {
|
||||||
|
let clip = clip.read().unwrap();
|
||||||
|
let buf_size = self.buffer_size(&clip);
|
||||||
|
let mut buffer = BigBuffer::from(buf_size);
|
||||||
|
let time_zoom = self.get_time_zoom();
|
||||||
|
self.time_len().set(clip.length);
|
||||||
|
PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom,self.get_note_len(), self.get_note_pos(), self.get_time_pos());
|
||||||
|
PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom);
|
||||||
|
buffer
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
||||||
|
*self.clip_mut() = clip.cloned();
|
||||||
|
self.color = clip.map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]);
|
||||||
|
self.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PianoHorizontal {
|
||||||
|
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
let buffer = self.buffer.read().unwrap();
|
||||||
|
f.debug_struct("PianoHorizontal")
|
||||||
|
.field("time_zoom", &self.range.time_zoom)
|
||||||
|
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl OctaveVertical {
|
||||||
|
fn color (&self, pitch: usize) -> Color {
|
||||||
|
let pitch = pitch % 12;
|
||||||
|
self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl OctaveVertical {
|
||||||
|
fn tui (&self) -> impl Draw<Tui> {
|
||||||
|
east!(
|
||||||
|
Tui::fg_bg(self.color(0), self.color(1), "▙"),
|
||||||
|
Tui::fg_bg(self.color(2), self.color(3), "▙"),
|
||||||
|
Tui::fg_bg(self.color(4), self.color(5), "▌"),
|
||||||
|
Tui::fg_bg(self.color(6), self.color(7), "▟"),
|
||||||
|
Tui::fg_bg(self.color(8), self.color(9), "▟"),
|
||||||
|
Tui::fg_bg(self.color(10), self.color(11), "▟"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_from!(MidiSelection: |data:(usize, bool)| Self {
|
||||||
|
time_len: Arc::new(0.into()),
|
||||||
|
note_axis: Arc::new(0.into()),
|
||||||
|
note_lo: Arc::new(0.into()),
|
||||||
|
time_axis: Arc::new(0.into()),
|
||||||
|
time_start: Arc::new(0.into()),
|
||||||
|
time_zoom: Arc::new(data.0.into()),
|
||||||
|
time_lock: Arc::new(data.1.into()),
|
||||||
|
});
|
||||||
|
impl_default!(MidiCursor: Self {
|
||||||
|
time_pos: Arc::new(0.into()),
|
||||||
|
note_pos: Arc::new(36.into()),
|
||||||
|
note_len: Arc::new(24.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
impl NotePoint for MidiCursor {
|
||||||
|
fn note_len (&self) -> &AtomicUsize {
|
||||||
|
&self.note_len
|
||||||
|
}
|
||||||
|
fn note_pos (&self) -> &AtomicUsize {
|
||||||
|
&self.note_pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimePoint for MidiCursor {
|
||||||
|
fn time_pos (&self) -> &AtomicUsize {
|
||||||
|
self.time_pos.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeRange for MidiSelection {
|
||||||
|
fn time_len (&self) -> &AtomicUsize { &self.time_len }
|
||||||
|
fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom }
|
||||||
|
fn time_lock (&self) -> &AtomicBool { &self.time_lock }
|
||||||
|
fn time_start (&self) -> &AtomicUsize { &self.time_start }
|
||||||
|
fn time_axis (&self) -> &AtomicUsize { &self.time_axis }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteRange for MidiSelection {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
||||||
|
fn note_axis (&self) -> &AtomicUsize { &self.note_axis }
|
||||||
|
}
|
||||||
|
|
@ -78,7 +78,7 @@ def_command!(SamplerCommand: |sampler: Sampler| {
|
||||||
/// Currently active modal, if any.
|
/// Currently active modal, if any.
|
||||||
pub mode: Option<SamplerMode>,
|
pub mode: Option<SamplerMode>,
|
||||||
/// Size of rendered sampler.
|
/// Size of rendered sampler.
|
||||||
pub size: [AtomicUsize;2],
|
pub size: Size,
|
||||||
/// Lowest note displayed.
|
/// Lowest note displayed.
|
||||||
pub note_lo: AtomicUsize,
|
pub note_lo: AtomicUsize,
|
||||||
/// Currently selected note.
|
/// Currently selected note.
|
||||||
|
|
|
||||||
824
src/sequence.rs
824
src/sequence.rs
|
|
@ -1,85 +1,7 @@
|
||||||
use crate::{*, clock::*, device::*};
|
use crate::{*, clock::*, device::*};
|
||||||
|
|
||||||
impl <T: AsRef<Sequencer>+AsMut<Sequencer>> HasSequencer for T {}
|
impl <T: AsRef<Sequencer>+AsMut<Sequencer>> HasSequencer for T {}
|
||||||
impl <T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
|
||||||
impl <T: NotePoint+TimePoint> MidiPoint for T {}
|
|
||||||
impl <T: TimeRange+NoteRange> MidiRange for T {}
|
|
||||||
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
|
||||||
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
|
||||||
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
|
||||||
DeleteNote => {
|
|
||||||
editor.redraw(); todo!() },
|
|
||||||
AppendNote { advance: bool } => {
|
|
||||||
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
|
||||||
SetNotePos { pos: usize } => {
|
|
||||||
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteLen { len: usize } => {
|
|
||||||
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteScroll { scroll: usize } => {
|
|
||||||
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetTimePos { pos: usize } => {
|
|
||||||
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeScroll { scroll: usize } => {
|
|
||||||
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeZoom { zoom: usize } => {
|
|
||||||
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeLock { lock: bool } => {
|
|
||||||
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Contains state for viewing and editing a clip.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use std::sync::{Arc, RwLock};
|
|
||||||
/// let clip = tek::MidiClip::stop_all();
|
|
||||||
/// let mut editor = tek::MidiEditor {
|
|
||||||
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
|
||||||
/// size: Default::default(),
|
|
||||||
/// //keys: Default::default(),
|
|
||||||
/// };
|
|
||||||
/// let _ = editor.put_note(true);
|
|
||||||
/// let _ = editor.put_note(false);
|
|
||||||
/// let _ = editor.clip_status();
|
|
||||||
/// let _ = editor.edit_status();
|
|
||||||
/// ```
|
|
||||||
pub struct MidiEditor {
|
|
||||||
/// Size of editor on screen
|
|
||||||
pub size: [AtomicUsize; 2],
|
|
||||||
/// View mode and state of editor
|
|
||||||
pub mode: PianoHorizontal,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A clip, rendered as a horizontal piano roll.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let piano = tek::PianoHorizontal::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone, Default)] pub struct PianoHorizontal {
|
|
||||||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
|
||||||
/// Buffer where the whole clip is rerendered on change
|
|
||||||
pub buffer: Arc<RwLock<BigBuffer>>,
|
|
||||||
/// Size of actual notes area
|
|
||||||
pub size: [AtomicUsize; 2],
|
|
||||||
/// The display window
|
|
||||||
pub range: MidiSelection,
|
|
||||||
/// The note cursor
|
|
||||||
pub point: MidiCursor,
|
|
||||||
/// The highlight color palette
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Width of the keyboard
|
|
||||||
pub keys_width: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 12 piano keys, some highlighted.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let keys = tek::OctaveVertical::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Copy, Clone)] pub struct OctaveVertical {
|
|
||||||
pub on: [bool; 12],
|
|
||||||
pub colors: [Color; 3]
|
|
||||||
}
|
|
||||||
/// A MIDI sequence.
|
/// A MIDI sequence.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -160,17 +82,11 @@ pub struct Sequencer {
|
||||||
|
|
||||||
|
|
||||||
pub trait HasPlayClip: HasClock {
|
pub trait HasPlayClip: HasClock {
|
||||||
|
|
||||||
fn reset (&self) -> bool;
|
fn reset (&self) -> bool;
|
||||||
|
|
||||||
fn reset_mut (&mut self) -> &mut bool;
|
fn reset_mut (&mut self) -> &mut bool;
|
||||||
|
|
||||||
fn play_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
fn play_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||||
|
|
||||||
fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||||
|
|
||||||
fn next_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
fn next_clip (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||||
|
|
||||||
fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
|
||||||
|
|
||||||
fn pulses_since_start (&self) -> Option<f64> {
|
fn pulses_since_start (&self) -> Option<f64> {
|
||||||
|
|
@ -307,76 +223,9 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
pub type MidiData = Vec<Vec<MidiMessage>>;
|
||||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||||
fn redraw (&self);
|
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||||
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>>;
|
|
||||||
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
|
|
||||||
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
|
||||||
*self.clip_mut() = clip.cloned();
|
|
||||||
self.redraw();
|
|
||||||
}
|
|
||||||
/// Make sure cursor is within note range
|
|
||||||
fn autoscroll (&self) {
|
|
||||||
let note_pos = self.get_note_pos().min(127);
|
|
||||||
let note_lo = self.get_note_lo();
|
|
||||||
let note_hi = self.get_note_hi();
|
|
||||||
if note_pos < note_lo {
|
|
||||||
self.note_lo().set(note_pos);
|
|
||||||
} else if note_pos > note_hi {
|
|
||||||
self.note_lo().set((note_lo + note_pos).saturating_sub(note_hi));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Make sure time range is within display
|
|
||||||
fn autozoom (&self) {
|
|
||||||
if self.time_lock().get() {
|
|
||||||
let time_len = self.get_time_len();
|
|
||||||
let time_axis = self.get_time_axis();
|
|
||||||
let time_zoom = self.get_time_zoom();
|
|
||||||
loop {
|
|
||||||
let time_zoom = self.time_zoom().get();
|
|
||||||
let time_area = time_axis * time_zoom;
|
|
||||||
if time_area > time_len {
|
|
||||||
let next_time_zoom = note_duration_prev(time_zoom);
|
|
||||||
if next_time_zoom <= 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let next_time_area = time_axis * next_time_zoom;
|
|
||||||
if next_time_area >= time_len {
|
|
||||||
self.time_zoom().set(next_time_zoom);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if time_area < time_len {
|
|
||||||
let prev_time_zoom = note_duration_next(time_zoom);
|
|
||||||
if prev_time_zoom > 384 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let prev_time_area = time_axis * prev_time_zoom;
|
|
||||||
if prev_time_area <= time_len {
|
|
||||||
self.time_zoom().set(prev_time_zoom);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if time_zoom != self.time_zoom().get() {
|
|
||||||
self.redraw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//while time_len.div_ceil(time_zoom) > time_axis {
|
|
||||||
//println!("\r{time_len} {time_zoom} {time_axis}");
|
|
||||||
//time_zoom = Note::next(time_zoom);
|
|
||||||
//}
|
|
||||||
//self.time_zoom().set(time_zoom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub type MidiData =
|
|
||||||
Vec<Vec<MidiMessage>>;
|
|
||||||
pub type ClipPool =
|
|
||||||
Vec<Arc<RwLock<MidiClip>>>;
|
|
||||||
pub type CollectedMidiInput<'a> =
|
|
||||||
Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
|
||||||
|
|
||||||
pub trait HasClips {
|
pub trait HasClips {
|
||||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||||
|
|
@ -387,180 +236,21 @@ pub trait HasClips {
|
||||||
(self.clips().len() - 1, clip)
|
(self.clips().len() - 1, clip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// ```
|
|
||||||
/// use tek::{*, tengri::*};
|
|
||||||
///
|
|
||||||
/// struct Test(Option<MidiEditor>);
|
|
||||||
/// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref());
|
|
||||||
/// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut());
|
|
||||||
///
|
|
||||||
/// let mut host = Test(Some(MidiEditor::default()));
|
|
||||||
/// let _ = host.editor();
|
|
||||||
/// let _ = host.editor_mut();
|
|
||||||
/// let _ = host.is_editing();
|
|
||||||
/// let _ = host.editor_w();
|
|
||||||
/// let _ = host.editor_h();
|
|
||||||
/// ```
|
|
||||||
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
|
||||||
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
|
||||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
|
||||||
fn is_editing (&self) -> bool { self.editor().is_some() }
|
|
||||||
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
|
||||||
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
|
||||||
}
|
|
||||||
pub trait HasMidiClip {
|
pub trait HasMidiClip {
|
||||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
||||||
fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() }
|
fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() }
|
||||||
fn sequencer (&self) -> &Sequencer { self.as_ref() }
|
fn sequencer (&self) -> &Sequencer { self.as_ref() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HasMidiBuffers {
|
pub trait HasMidiBuffers {
|
||||||
fn note_buf_mut (&mut self) -> &mut Vec<u8>;
|
fn note_buf_mut (&mut self) -> &mut Vec<u8>;
|
||||||
fn midi_buf_mut (&mut self) -> &mut Vec<Vec<Vec<u8>>>;
|
fn midi_buf_mut (&mut self) -> &mut Vec<Vec<Vec<u8>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub trait NotePoint {
|
|
||||||
fn note_len (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current length of the note cursor.
|
|
||||||
fn get_note_len (&self) -> usize {
|
|
||||||
self.note_len().load(Relaxed)
|
|
||||||
}
|
|
||||||
/// Set the length of the note cursor, returning the previous value.
|
|
||||||
fn set_note_len (&self, x: usize) -> usize {
|
|
||||||
self.note_len().swap(x, Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn note_pos (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current pitch of the note cursor.
|
|
||||||
fn get_note_pos (&self) -> usize {
|
|
||||||
self.note_pos().load(Relaxed).min(127)
|
|
||||||
}
|
|
||||||
/// Set the current pitch fo the note cursor, returning the previous value.
|
|
||||||
fn set_note_pos (&self, x: usize) -> usize {
|
|
||||||
self.note_pos().swap(x.min(127), Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TimePoint {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize;
|
|
||||||
/// Get the current time position of the note cursor.
|
|
||||||
fn get_time_pos (&self) -> usize {
|
|
||||||
self.time_pos().load(Relaxed)
|
|
||||||
}
|
|
||||||
/// Set the current time position of the note cursor, returning the previous value.
|
|
||||||
fn set_time_pos (&self, x: usize) -> usize {
|
|
||||||
self.time_pos().swap(x, Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiPoint: NotePoint + TimePoint {
|
|
||||||
/// Get the current end of the note cursor.
|
|
||||||
fn get_note_end (&self) -> usize {
|
|
||||||
self.get_time_pos() + self.get_note_len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TimeRange {
|
|
||||||
fn time_len (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_len (&self) -> usize {
|
|
||||||
self.time_len().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_zoom (&self) -> usize {
|
|
||||||
self.time_zoom().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_zoom (&self, value: usize) -> usize {
|
|
||||||
self.time_zoom().swap(value, Relaxed)
|
|
||||||
}
|
|
||||||
fn time_lock (&self) -> &AtomicBool;
|
|
||||||
fn get_time_lock (&self) -> bool {
|
|
||||||
self.time_lock().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_lock (&self, value: bool) -> bool {
|
|
||||||
self.time_lock().swap(value, Relaxed)
|
|
||||||
}
|
|
||||||
fn time_start (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_start (&self) -> usize {
|
|
||||||
self.time_start().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn set_time_start (&self, value: usize) -> usize {
|
|
||||||
self.time_start().swap(value, Relaxed)
|
|
||||||
}
|
|
||||||
fn time_axis (&self) -> &AtomicUsize;
|
|
||||||
fn get_time_axis (&self) -> usize {
|
|
||||||
self.time_axis().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn get_time_end (&self) -> usize {
|
|
||||||
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NoteRange {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize;
|
|
||||||
fn get_note_lo (&self) -> usize {
|
|
||||||
self.note_lo().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn set_note_lo (&self, x: usize) -> usize {
|
|
||||||
self.note_lo().swap(x, Relaxed)
|
|
||||||
}
|
|
||||||
fn note_axis (&self) -> &AtomicUsize;
|
|
||||||
fn get_note_axis (&self) -> usize {
|
|
||||||
self.note_axis().load(Relaxed)
|
|
||||||
}
|
|
||||||
fn get_note_hi (&self) -> usize {
|
|
||||||
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MidiRange: TimeRange + NoteRange {}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::MidiCursor::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone)] pub struct MidiCursor {
|
|
||||||
/// Time coordinate of cursor
|
|
||||||
pub time_pos: Arc<AtomicUsize>,
|
|
||||||
/// Note coordinate of cursor
|
|
||||||
pub note_pos: Arc<AtomicUsize>,
|
|
||||||
/// Length of note that will be inserted, in pulses
|
|
||||||
pub note_len: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tek::{TimeRange, NoteRange};
|
|
||||||
/// let model = tek::MidiSelection::from((1, false));
|
|
||||||
///
|
|
||||||
/// let _ = model.get_time_len();
|
|
||||||
/// let _ = model.get_time_zoom();
|
|
||||||
/// let _ = model.get_time_lock();
|
|
||||||
/// let _ = model.get_time_start();
|
|
||||||
/// let _ = model.get_time_axis();
|
|
||||||
/// let _ = model.get_time_end();
|
|
||||||
///
|
|
||||||
/// let _ = model.get_note_lo();
|
|
||||||
/// let _ = model.get_note_axis();
|
|
||||||
/// let _ = model.get_note_hi();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default)] pub struct MidiSelection {
|
|
||||||
pub time_len: Arc<AtomicUsize>,
|
|
||||||
/// Length of visible time axis
|
|
||||||
pub time_axis: Arc<AtomicUsize>,
|
|
||||||
/// Earliest time displayed
|
|
||||||
pub time_start: Arc<AtomicUsize>,
|
|
||||||
/// Time step
|
|
||||||
pub time_zoom: Arc<AtomicUsize>,
|
|
||||||
/// Auto rezoom to fit in time axis
|
|
||||||
pub time_lock: Arc<AtomicBool>,
|
|
||||||
/// Length of visible note axis
|
|
||||||
pub note_axis: Arc<AtomicUsize>,
|
|
||||||
// Lowest note displayed
|
|
||||||
pub note_lo: Arc<AtomicUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiClip {
|
impl MidiClip {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
|
|
@ -646,8 +336,6 @@ impl_has!(Sequencer: |self: Track| self.sequencer);
|
||||||
impl_has!(Clock: |self: Sequencer| self.clock);
|
impl_has!(Clock: |self: Sequencer| self.clock);
|
||||||
impl_has!(Vec<MidiInput>: |self: Sequencer| self.midi_ins);
|
impl_has!(Vec<MidiInput>: |self: Sequencer| self.midi_ins);
|
||||||
impl_has!(Vec<MidiOutput>: |self: Sequencer| self.midi_outs);
|
impl_has!(Vec<MidiOutput>: |self: Sequencer| self.midi_outs);
|
||||||
impl_has!([AtomicUsize; 2]: |self: MidiEditor| self.size);
|
|
||||||
impl_has!([AtomicUsize; 2]: |self: PianoHorizontal| self.size);
|
|
||||||
impl_default!(Sequencer: Self {
|
impl_default!(Sequencer: Self {
|
||||||
clock: Clock::default(),
|
clock: Clock::default(),
|
||||||
play_clip: None,
|
play_clip: None,
|
||||||
|
|
@ -863,502 +551,6 @@ impl Audio for Sequencer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Draw<Tui> for MidiEditor {
|
|
||||||
fn draw(self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
|
||||||
self.tui().draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Draw<Tui> for PianoHorizontal {
|
|
||||||
fn draw(self, to: &mut Tui) -> Usually<XYWH<u16>> {
|
|
||||||
self.tui().draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl std::fmt::Debug for MidiEditor {
|
|
||||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
f.debug_struct("MidiEditor").field("mode", &self.mode).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl_from!(MidiEditor: |clip: &Arc<RwLock<MidiClip>>| {
|
|
||||||
let model = Self::from(Some(clip.clone()));
|
|
||||||
model.redraw();
|
|
||||||
model
|
|
||||||
});
|
|
||||||
impl_from!(MidiEditor: |clip: Option<Arc<RwLock<MidiClip>>>| {
|
|
||||||
let mut model = Self::default();
|
|
||||||
*model.clip_mut() = clip;
|
|
||||||
model.redraw();
|
|
||||||
model
|
|
||||||
});
|
|
||||||
impl_default!(MidiEditor: Self {
|
|
||||||
size: [0, 0].into(), mode: PianoHorizontal::new(None)
|
|
||||||
});
|
|
||||||
impl_default!(OctaveVertical: Self {
|
|
||||||
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
|
|
||||||
});
|
|
||||||
impl MidiEditor {
|
|
||||||
/// Put note at current position
|
|
||||||
pub fn put_note (&mut self, advance: bool) {
|
|
||||||
let mut redraw = false;
|
|
||||||
if let Some(clip) = self.clip() {
|
|
||||||
let mut clip = clip.write().unwrap();
|
|
||||||
let note_start = self.get_time_pos();
|
|
||||||
let note_pos = self.get_note_pos();
|
|
||||||
let note_len = self.get_note_len();
|
|
||||||
let note_end = note_start + (note_len.saturating_sub(1));
|
|
||||||
let key: u7 = u7::from(note_pos as u8);
|
|
||||||
let vel: u7 = 100.into();
|
|
||||||
let length = clip.length;
|
|
||||||
let note_end = note_end % length;
|
|
||||||
let note_on = MidiMessage::NoteOn { key, vel };
|
|
||||||
if !clip.notes[note_start].iter().any(|msg|*msg == note_on) {
|
|
||||||
clip.notes[note_start].push(note_on);
|
|
||||||
}
|
|
||||||
let note_off = MidiMessage::NoteOff { key, vel };
|
|
||||||
if !clip.notes[note_end].iter().any(|msg|*msg == note_off) {
|
|
||||||
clip.notes[note_end].push(note_off);
|
|
||||||
}
|
|
||||||
if advance {
|
|
||||||
self.set_time_pos((note_end + 1) % clip.length);
|
|
||||||
}
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
if redraw {
|
|
||||||
self.mode.redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> { todo!() }
|
|
||||||
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) }
|
|
||||||
fn note_length (&self) -> usize { self.get_note_len() }
|
|
||||||
fn note_pos (&self) -> usize { self.get_note_pos() }
|
|
||||||
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 }
|
|
||||||
fn note_pos_next_octave (&self) -> usize { self.get_note_pos() + 12 }
|
|
||||||
fn note_pos_prev (&self) -> usize { self.get_note_pos().saturating_sub(1) }
|
|
||||||
fn note_pos_prev_octave (&self) -> usize { self.get_note_pos().saturating_sub(12) }
|
|
||||||
fn note_len (&self) -> usize { self.get_note_len() }
|
|
||||||
fn note_len_next (&self) -> usize { self.get_note_len() + 1 }
|
|
||||||
fn note_len_prev (&self) -> usize { self.get_note_len().saturating_sub(1) }
|
|
||||||
fn note_range (&self) -> usize { self.get_note_axis() }
|
|
||||||
fn note_range_next (&self) -> usize { self.get_note_axis() + 1 }
|
|
||||||
fn note_range_prev (&self) -> usize { self.get_note_axis().saturating_sub(1) }
|
|
||||||
fn time_zoom (&self) -> usize { self.get_time_zoom() }
|
|
||||||
fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 }
|
|
||||||
fn time_zoom_next_fine (&self) -> usize { self.get_time_zoom() + 1 }
|
|
||||||
fn time_zoom_prev (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) }
|
|
||||||
fn time_zoom_prev_fine (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) }
|
|
||||||
fn time_lock (&self) -> bool { self.get_time_lock() }
|
|
||||||
fn time_lock_toggled (&self) -> bool { !self.get_time_lock() }
|
|
||||||
fn time_pos (&self) -> usize { self.get_time_pos() }
|
|
||||||
fn time_pos_next (&self) -> usize { (self.get_time_pos() + self.get_note_len()) % self.clip_length() }
|
|
||||||
fn time_pos_next_fine (&self) -> usize { (self.get_time_pos() + 1) % self.clip_length() }
|
|
||||||
fn time_pos_prev (&self) -> usize {
|
|
||||||
let step = self.get_note_len();
|
|
||||||
self.get_time_pos().overflowing_sub(step)
|
|
||||||
.0.min(self.clip_length().saturating_sub(step))
|
|
||||||
}
|
|
||||||
fn time_pos_prev_fine (&self) -> usize {
|
|
||||||
self.get_time_pos().overflowing_sub(1)
|
|
||||||
.0.min(self.clip_length().saturating_sub(1))
|
|
||||||
}
|
|
||||||
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
|
|
||||||
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
|
||||||
(clip.color, clip.name.clone(), clip.length, clip.looped)
|
|
||||||
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
|
|
||||||
w_exact(20, south!(
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("f2", "name ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{name} "))))))),
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("l", "ength ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{length} "))))))),
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("r", "epeat ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255), format!("{looped} "))))))),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
|
|
||||||
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) {
|
|
||||||
(clip.color, clip.length)
|
|
||||||
} else { (ItemTheme::G[64], 0) };
|
|
||||||
let time_pos = self.get_time_pos();
|
|
||||||
let time_zoom = self.get_time_zoom();
|
|
||||||
let time_lock = if self.get_time_lock() { "[lock]" } else { " " };
|
|
||||||
let note_pos = self.get_note_pos();
|
|
||||||
let note_name = format!("{:4}", note_pitch_to_name(note_pos));
|
|
||||||
let note_pos = format!("{:>3}", note_pos);
|
|
||||||
let note_len = format!("{:>4}", self.get_note_len());
|
|
||||||
w_exact(20, south!(
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("t", "ime ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
|
||||||
format!("{length} /{time_zoom} +{time_pos} "))))))),
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("z", "lock ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
|
||||||
format!("{time_lock}"))))))),
|
|
||||||
w_full(origin_w(east(
|
|
||||||
button_2("x", "note ", false),
|
|
||||||
w_full(origin_e(Tui::fg(Rgb(255, 255, 255),
|
|
||||||
format!("{note_name} {note_pos} {note_len}"))))))),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeRange for MidiEditor {
|
|
||||||
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
|
|
||||||
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
|
|
||||||
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
|
|
||||||
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteRange for MidiEditor {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
|
|
||||||
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotePoint for MidiEditor {
|
|
||||||
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
|
|
||||||
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimePoint for MidiEditor {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiViewer for MidiEditor {
|
|
||||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
|
|
||||||
fn redraw (&self) { self.mode.redraw() }
|
|
||||||
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
|
|
||||||
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
|
|
||||||
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiEditor {
|
|
||||||
fn tui (&self) -> impl Draw<Tui> { self.autoscroll(); /*self.autozoom();*/ self.size.of(&self.mode) }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl PianoHorizontal {
|
|
||||||
pub fn new (clip: Option<&Arc<RwLock<MidiClip>>>) -> Self {
|
|
||||||
let size = [0, 0].into();
|
|
||||||
let mut range = MidiSelection::from((12, true));
|
|
||||||
range.time_axis = size.x.clone();
|
|
||||||
range.note_axis = size.y.clone();
|
|
||||||
let piano = Self {
|
|
||||||
keys_width: 5,
|
|
||||||
size,
|
|
||||||
range,
|
|
||||||
buffer: RwLock::new(Default::default()).into(),
|
|
||||||
point: MidiCursor::default(),
|
|
||||||
clip: clip.cloned(),
|
|
||||||
color: clip.as_ref().map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]),
|
|
||||||
};
|
|
||||||
piano.redraw();
|
|
||||||
piano
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PianoHorizontal {
|
|
||||||
fn tui (&self) -> impl Draw<Tui> {
|
|
||||||
south(
|
|
||||||
east(w_exact(5, format!("{}x{}", self.size.w(), self.size.h())), self.timeline()),
|
|
||||||
east(self.keys(), self.size.of(below(wh_full(self.notes()), wh_full(self.cursor())))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PianoHorizontal {
|
|
||||||
/// Draw the piano roll background.
|
|
||||||
///
|
|
||||||
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
|
||||||
fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize, note_point: usize, time_point: usize) {
|
|
||||||
for (y, note) in (0..=127).rev().enumerate() {
|
|
||||||
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
|
|
||||||
let cell = buf.get_mut(x, y).unwrap();
|
|
||||||
if note == (127-note_point) || time == time_point {
|
|
||||||
cell.set_bg(Rgb(0,0,0));
|
|
||||||
} else {
|
|
||||||
cell.set_bg(clip.color.darkest.term);
|
|
||||||
}
|
|
||||||
if time % 384 == 0 {
|
|
||||||
cell.set_fg(clip.color.darker.term);
|
|
||||||
cell.set_char('│');
|
|
||||||
} else if time % 96 == 0 {
|
|
||||||
cell.set_fg(clip.color.dark.term);
|
|
||||||
cell.set_char('╎');
|
|
||||||
} else if time % note_len == 0 {
|
|
||||||
cell.set_fg(clip.color.darker.term);
|
|
||||||
cell.set_char('┊');
|
|
||||||
} else if (127 - note) % 12 == 0 {
|
|
||||||
cell.set_fg(clip.color.darker.term);
|
|
||||||
cell.set_char('=');
|
|
||||||
} else if (127 - note) % 6 == 0 {
|
|
||||||
cell.set_fg(clip.color.darker.term);
|
|
||||||
cell.set_char('—');
|
|
||||||
} else {
|
|
||||||
cell.set_fg(clip.color.darker.term);
|
|
||||||
cell.set_char('·');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Draw the piano roll foreground.
|
|
||||||
///
|
|
||||||
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
|
|
||||||
fn draw_fg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize) {
|
|
||||||
let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0));
|
|
||||||
let mut notes_on = [false;128];
|
|
||||||
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
|
|
||||||
for (_y, note) in (0..=127).rev().enumerate() {
|
|
||||||
if let Some(cell) = buf.get_mut(x, note) {
|
|
||||||
if notes_on[note] {
|
|
||||||
cell.set_char('▂');
|
|
||||||
cell.set_style(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let time_end = time_start + zoom;
|
|
||||||
for time in time_start..time_end.min(clip.length) {
|
|
||||||
for event in clip.notes[time].iter() {
|
|
||||||
match event {
|
|
||||||
MidiMessage::NoteOn { key, .. } => {
|
|
||||||
let note = key.as_int() as usize;
|
|
||||||
if let Some(cell) = buf.get_mut(x, note) {
|
|
||||||
cell.set_char('█');
|
|
||||||
cell.set_style(style);
|
|
||||||
}
|
|
||||||
notes_on[note] = true
|
|
||||||
},
|
|
||||||
MidiMessage::NoteOff { key, .. } => {
|
|
||||||
notes_on[key.as_int() as usize] = false
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn notes (&self) -> impl Draw<Tui> {
|
|
||||||
let time_start = self.get_time_start();
|
|
||||||
let note_lo = self.get_note_lo();
|
|
||||||
let note_hi = self.get_note_hi();
|
|
||||||
let buffer = self.buffer.clone();
|
|
||||||
Thunk::new(move|to: &mut Tui|{
|
|
||||||
let source = buffer.read().unwrap();
|
|
||||||
let XYWH(x0, y0, w, _h) = to.area();
|
|
||||||
//if h as usize != note_axis {
|
|
||||||
//panic!("area height mismatch: {h} <> {note_axis}");
|
|
||||||
//}
|
|
||||||
for (area_x, screen_x) in (x0..x0+w).enumerate() {
|
|
||||||
for (area_y, screen_y, _note) in note_y_iter(note_lo, note_hi, y0) {
|
|
||||||
let source_x = time_start + area_x;
|
|
||||||
let source_y = note_hi - area_y;
|
|
||||||
// TODO: enable loop rollover:
|
|
||||||
//let source_x = (time_start + area_x) % source.width.max(1);
|
|
||||||
//let source_y = (note_hi - area_y) % source.height.max(1);
|
|
||||||
let is_in_x = source_x < source.width;
|
|
||||||
let is_in_y = source_y < source.height;
|
|
||||||
if is_in_x && is_in_y {
|
|
||||||
if let Some(source_cell) = source.get(source_x, source_y) {
|
|
||||||
if let Some(cell) = to.buffer.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) {
|
|
||||||
*cell = source_cell.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn cursor (&self) -> impl Draw<Tui> {
|
|
||||||
let note_hi = self.get_note_hi();
|
|
||||||
let note_lo = self.get_note_lo();
|
|
||||||
let note_pos = self.get_note_pos();
|
|
||||||
let note_len = self.get_note_len();
|
|
||||||
let time_pos = self.get_time_pos();
|
|
||||||
let time_start = self.get_time_start();
|
|
||||||
let time_zoom = self.get_time_zoom();
|
|
||||||
let style = Some(Style::default().fg(self.color.lightest.term));
|
|
||||||
Thunk::new(move|to: &mut Tui|{
|
|
||||||
let XYWH(x0, y0, w, _) = to.area();
|
|
||||||
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
|
||||||
if note == note_pos {
|
|
||||||
for x in 0..w {
|
|
||||||
let screen_x = x0 + x;
|
|
||||||
let time_1 = time_start + x as usize * time_zoom;
|
|
||||||
let time_2 = time_1 + time_zoom;
|
|
||||||
if time_1 <= time_pos && time_pos < time_2 {
|
|
||||||
to.blit(&"█", screen_x, screen_y, style);
|
|
||||||
let tail = note_len as u16 / time_zoom as u16;
|
|
||||||
for x_tail in (screen_x + 1)..(screen_x + tail) {
|
|
||||||
to.blit(&"▂", x_tail, screen_y, style);
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn keys (&self) -> impl Draw<Tui> {
|
|
||||||
let state = self;
|
|
||||||
let color = state.color;
|
|
||||||
let note_lo = state.get_note_lo();
|
|
||||||
let note_hi = state.get_note_hi();
|
|
||||||
let note_pos = state.get_note_pos();
|
|
||||||
let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0)));
|
|
||||||
let off_style = Some(Style::default().fg(Tui::g(255)));
|
|
||||||
let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.term).bold());
|
|
||||||
h_full(w_exact(self.keys_width, Thunk::new(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y0, _w, _h) = to.area();
|
|
||||||
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
|
|
||||||
to.blit(&to_key(note), x, screen_y, key_style);
|
|
||||||
if note > 127 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if note == note_pos {
|
|
||||||
to.blit(&format!("{:<5}", note_pitch_to_name(note)), x, screen_y, on_style)
|
|
||||||
} else {
|
|
||||||
to.blit(¬e_pitch_to_name(note), x, screen_y, off_style)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
fn timeline (&self) -> impl Draw<Tui> + '_ {
|
|
||||||
w_full(h_exact(1, Thunk::new(move|to: &mut Tui|{
|
|
||||||
let XYWH(x, y, w, _h) = to.area();
|
|
||||||
let style = Some(Style::default().dim());
|
|
||||||
let length = self.clip.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
|
|
||||||
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
|
|
||||||
let t = area_x as usize * self.time_zoom().get();
|
|
||||||
if t < length {
|
|
||||||
to.blit(&"|", screen_x, y, style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeRange for PianoHorizontal {
|
|
||||||
fn time_len (&self) -> &AtomicUsize { self.range.time_len() }
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() }
|
|
||||||
fn time_lock (&self) -> &AtomicBool { self.range.time_lock() }
|
|
||||||
fn time_start (&self) -> &AtomicUsize { self.range.time_start() }
|
|
||||||
fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteRange for PianoHorizontal {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
|
|
||||||
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotePoint for PianoHorizontal {
|
|
||||||
fn note_len (&self) -> &AtomicUsize { self.point.note_len() }
|
|
||||||
fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimePoint for PianoHorizontal {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MidiViewer for PianoHorizontal {
|
|
||||||
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { &self.clip }
|
|
||||||
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { &mut self.clip }
|
|
||||||
/// Determine the required space to render the clip.
|
|
||||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
|
|
||||||
(clip.length / self.range.time_zoom().get(), 128)
|
|
||||||
}
|
|
||||||
fn redraw (&self) {
|
|
||||||
*self.buffer.write().unwrap() = if let Some(clip) = self.clip.as_ref() {
|
|
||||||
let clip = clip.read().unwrap();
|
|
||||||
let buf_size = self.buffer_size(&clip);
|
|
||||||
let mut buffer = BigBuffer::from(buf_size);
|
|
||||||
let time_zoom = self.get_time_zoom();
|
|
||||||
self.time_len().set(clip.length);
|
|
||||||
PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom,self.get_note_len(), self.get_note_pos(), self.get_time_pos());
|
|
||||||
PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom);
|
|
||||||
buffer
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
|
|
||||||
*self.clip_mut() = clip.cloned();
|
|
||||||
self.color = clip.map(|p|p.read().unwrap().color).unwrap_or(ItemTheme::G[64]);
|
|
||||||
self.redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for PianoHorizontal {
|
|
||||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
||||||
let buffer = self.buffer.read().unwrap();
|
|
||||||
f.debug_struct("PianoHorizontal")
|
|
||||||
.field("time_zoom", &self.range.time_zoom)
|
|
||||||
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl OctaveVertical {
|
|
||||||
fn color (&self, pitch: usize) -> Color {
|
|
||||||
let pitch = pitch % 12;
|
|
||||||
self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl OctaveVertical {
|
|
||||||
fn tui (&self) -> impl Draw<Tui> {
|
|
||||||
east!(
|
|
||||||
Tui::fg_bg(self.color(0), self.color(1), "▙"),
|
|
||||||
Tui::fg_bg(self.color(2), self.color(3), "▙"),
|
|
||||||
Tui::fg_bg(self.color(4), self.color(5), "▌"),
|
|
||||||
Tui::fg_bg(self.color(6), self.color(7), "▟"),
|
|
||||||
Tui::fg_bg(self.color(8), self.color(9), "▟"),
|
|
||||||
Tui::fg_bg(self.color(10), self.color(11), "▟"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl_from!(MidiSelection: |data:(usize, bool)| Self {
|
|
||||||
time_len: Arc::new(0.into()),
|
|
||||||
note_axis: Arc::new(0.into()),
|
|
||||||
note_lo: Arc::new(0.into()),
|
|
||||||
time_axis: Arc::new(0.into()),
|
|
||||||
time_start: Arc::new(0.into()),
|
|
||||||
time_zoom: Arc::new(data.0.into()),
|
|
||||||
time_lock: Arc::new(data.1.into()),
|
|
||||||
});
|
|
||||||
impl_default!(MidiCursor: Self {
|
|
||||||
time_pos: Arc::new(0.into()),
|
|
||||||
note_pos: Arc::new(36.into()),
|
|
||||||
note_len: Arc::new(24.into()),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl NotePoint for MidiCursor {
|
|
||||||
fn note_len (&self) -> &AtomicUsize {
|
|
||||||
&self.note_len
|
|
||||||
}
|
|
||||||
fn note_pos (&self) -> &AtomicUsize {
|
|
||||||
&self.note_pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimePoint for MidiCursor {
|
|
||||||
fn time_pos (&self) -> &AtomicUsize {
|
|
||||||
self.time_pos.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeRange for MidiSelection {
|
|
||||||
fn time_len (&self) -> &AtomicUsize { &self.time_len }
|
|
||||||
fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom }
|
|
||||||
fn time_lock (&self) -> &AtomicBool { &self.time_lock }
|
|
||||||
fn time_start (&self) -> &AtomicUsize { &self.time_start }
|
|
||||||
fn time_axis (&self) -> &AtomicUsize { &self.time_axis }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteRange for MidiSelection {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
|
|
||||||
fn note_axis (&self) -> &AtomicUsize { &self.note_axis }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Ticker {
|
impl Iterator for Ticker {
|
||||||
type Item = (usize, usize);
|
type Item = (usize, usize);
|
||||||
|
|
@ -1382,7 +574,7 @@ impl Iterator for Ticker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_key (note: usize) -> &'static str {
|
pub fn to_key (note: usize) -> &'static str {
|
||||||
match note % 12 {
|
match note % 12 {
|
||||||
11 | 9 | 7 | 5 | 4 | 2 | 0 => "████▌",
|
11 | 9 | 7 | 5 | 4 | 2 | 0 => "████▌",
|
||||||
10 | 8 | 6 | 3 | 1 => " ",
|
10 | 8 | 6 | 3 | 1 => " ",
|
||||||
|
|
|
||||||
87
src/tek.rs
87
src/tek.rs
|
|
@ -46,6 +46,7 @@ pub mod clock;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
pub mod editor;
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
pub mod mix;
|
pub mod mix;
|
||||||
pub mod mode;
|
pub mod mode;
|
||||||
|
|
@ -57,16 +58,17 @@ pub mod view;
|
||||||
#[cfg(feature = "plugin")] pub mod plugin;
|
#[cfg(feature = "plugin")] pub mod plugin;
|
||||||
|
|
||||||
use clap::{self, Parser, Subcommand};
|
use clap::{self, Parser, Subcommand};
|
||||||
use builder_pattern::Builder;
|
|
||||||
use self::{
|
use self::{
|
||||||
arrange::*, clock::*, dialog::*, browse::*, select::*, sequence::*, device::*,
|
config::*, mode::*, view::*, bind::*,
|
||||||
config::*, mode::*, view::*, bind::*, sample::*, menu::*
|
dialog::*, browse::*, menu::*,
|
||||||
|
clock::*, sequence::*, editor::*,
|
||||||
|
arrange::*, select::*, device::*, sample::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern crate xdg;
|
extern crate xdg;
|
||||||
pub(crate) use ::xdg::BaseDirectories;
|
pub(crate) use ::xdg::BaseDirectories;
|
||||||
pub extern crate atomic_float;
|
pub extern crate atomic_float;
|
||||||
pub(crate) use atomic_float::AtomicF64;
|
//pub(crate) use atomic_float::AtomicF64;
|
||||||
//pub extern crate jack;
|
//pub extern crate jack;
|
||||||
//pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
//pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
pub extern crate midly;
|
pub extern crate midly;
|
||||||
|
|
@ -198,34 +200,39 @@ fn tek_dec (state: &mut App, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_commands (app: &App, input: &TuiIn) -> Usually<Vec<AppCommand>> {
|
//impl_handle!(TuiIn: |self: App, input|{
|
||||||
let mut commands = vec![];
|
//let commands = tek_collect_commands(self, input)?;
|
||||||
for id in app.mode.keys.iter() {
|
//let history = tek_execute_commands(self, commands)?;
|
||||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
//self.history.extend(history.into_iter());
|
||||||
&& let Some(bindings) = event_map.query(input.event()) {
|
//Ok(None)
|
||||||
for binding in bindings {
|
//});
|
||||||
for command in binding.commands.iter() {
|
//fn tek_collect_commands (app: &App, input: &TuiIn) -> Usually<Vec<AppCommand>> {
|
||||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
//let mut commands = vec![];
|
||||||
commands.push(command)
|
//for id in app.mode.keys.iter() {
|
||||||
}
|
//if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||||
}
|
//&& let Some(bindings) = event_map.query(input.event()) {
|
||||||
}
|
//for binding in bindings {
|
||||||
}
|
//for command in binding.commands.iter() {
|
||||||
}
|
//if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||||
Ok(commands)
|
//commands.push(command)
|
||||||
}
|
//}
|
||||||
|
//}
|
||||||
fn execute_commands (
|
//}
|
||||||
app: &mut App, commands: Vec<AppCommand>
|
//}
|
||||||
) -> Usually<Vec<(AppCommand, Option<AppCommand>)>> {
|
//}
|
||||||
let mut history = vec![];
|
//Ok(commands)
|
||||||
for command in commands.into_iter() {
|
//}
|
||||||
let result = command.act(app);
|
//fn tek_execute_commands (
|
||||||
match result { Err(err) => { history.push((command, None)); return Err(err) }
|
//app: &mut App, commands: Vec<AppCommand>
|
||||||
Ok(undo) => { history.push((command, undo)); } };
|
//) -> Usually<Vec<(AppCommand, Option<AppCommand>)>> {
|
||||||
}
|
//let mut history = vec![];
|
||||||
Ok(history)
|
//for command in commands.into_iter() {
|
||||||
}
|
//let result = command.act(app);
|
||||||
|
//match result { Err(err) => { history.push((command, None)); return Err(err) }
|
||||||
|
//Ok(undo) => { history.push((command, undo)); } };
|
||||||
|
//}
|
||||||
|
//Ok(history)
|
||||||
|
//}
|
||||||
|
|
||||||
pub fn tek_jack_process (app: &mut App, client: &Client, scope: &ProcessScope) -> Control {
|
pub fn tek_jack_process (app: &mut App, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
let t0 = app.perf.get_t0();
|
let t0 = app.perf.get_t0();
|
||||||
|
|
@ -590,7 +597,7 @@ pub(crate) const HEADER: &'static str = r#"
|
||||||
/// Must not be dropped for the duration of the process
|
/// Must not be dropped for the duration of the process
|
||||||
pub jack: Jack<'static>,
|
pub jack: Jack<'static>,
|
||||||
/// Display size
|
/// Display size
|
||||||
pub size: [AtomicUsize;2],
|
pub size: Size,
|
||||||
/// Performance counter
|
/// Performance counter
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
/// Available view modes and input bindings
|
/// Available view modes and input bindings
|
||||||
|
|
@ -613,7 +620,7 @@ impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||||
impl_has!(Dialog: |self: App|self.dialog);
|
impl_has!(Dialog: |self: App|self.dialog);
|
||||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||||
impl_has!([AtomicUsize;2]: |self: App|self.size);
|
impl_has!(Size: |self: App|self.size);
|
||||||
impl_has!(Pool: |self: App|self.pool);
|
impl_has!(Pool: |self: App|self.pool);
|
||||||
impl_has!(Selection: |self: App|self.project.selection);
|
impl_has!(Selection: |self: App|self.project.selection);
|
||||||
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
||||||
|
|
@ -622,17 +629,11 @@ impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||||
impl_has_clips!( |self: App|self.pool.clips);
|
impl_has_clips!( |self: App|self.pool.clips);
|
||||||
impl_audio!(App: tek_jack_process, tek_jack_event);
|
impl_audio!(App: tek_jack_process, tek_jack_event);
|
||||||
impl_handle!(TuiIn: |self: App, input|{
|
|
||||||
let commands = collect_commands(self, input)?;
|
|
||||||
let history = execute_commands(self, commands)?;
|
|
||||||
self.history.extend(history.into_iter());
|
|
||||||
Ok(None)
|
|
||||||
});
|
|
||||||
namespace!(App: Arc<str> { literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); });
|
namespace!(App: Arc<str> { literal = |dsl|Ok(dsl.src()?.map(|x|x.into())); });
|
||||||
namespace!(App: u8 { literal = |dsl|try_to_u8(dsl); });
|
namespace!(App: u8 { literal = |dsl|try_to_u8(dsl); });
|
||||||
namespace!(App: u16 { literal = |dsl|try_to_u16(dsl); symbol = |app| {
|
namespace!(App: u16 { literal = |dsl|try_to_u16(dsl); symbol = |app| {
|
||||||
":w/sidebar" => app.project.w_sidebar(app.editor().is_some()),
|
":w/sidebar" => app.project.w_sidebar(app.editor().is_some()),
|
||||||
":h/sample-detail" => 6.max(app.measure_height() as u16 * 3 / 9), }; });
|
":h/sample-detail" => 6.max(app.size.h() as u16 * 3 / 9), }; });
|
||||||
namespace!(App: isize { literal = |dsl|try_to_isize(dsl); });
|
namespace!(App: isize { literal = |dsl|try_to_isize(dsl); });
|
||||||
namespace!(App: usize { literal = |dsl|try_to_usize(dsl); symbol = |app| {
|
namespace!(App: usize { literal = |dsl|try_to_usize(dsl); symbol = |app| {
|
||||||
":scene-count" => app.scenes().len(),
|
":scene-count" => app.scenes().len(),
|
||||||
|
|
@ -690,7 +691,7 @@ namespace!(App: Option<Arc<RwLock<MidiClip>>> {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
pub trait HasClipsSize { fn clips_size (&self) -> &[AtomicUsize;2]; }
|
pub trait HasClipsSize { fn clips_size (&self) -> &Size; }
|
||||||
|
|
||||||
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||||
fn devices (&self) -> &Vec<Device> { self.as_ref() }
|
fn devices (&self) -> &Vec<Device> { self.as_ref() }
|
||||||
|
|
@ -966,7 +967,7 @@ impl Draw<Tui> for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &[AtomicUsize;2] { &self.project.size_inner } }
|
impl HasClipsSize for App { fn clips_size (&self) -> &Size { &self.project.size_inner } }
|
||||||
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
impl_default!(AppCommand: Self::Nop);
|
impl_default!(AppCommand: Self::Nop);
|
||||||
primitive!(u8: try_to_u8);
|
primitive!(u8: try_to_u8);
|
||||||
|
|
|
||||||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
||||||
Subproject commit a06ea2ac139d09ab9eba5931455c43a3a75f4151
|
Subproject commit a93fe92a596b8a9bcec898e228fca909c0e2e9f4
|
||||||
Loading…
Add table
Add a link
Reference in a new issue