piano keys reappear

This commit is contained in:
i do not exist 2026-09-23 16:08:54 +03:00
parent 9db60c3429
commit 9a50ce6613
15 changed files with 856 additions and 782 deletions

View file

@ -115,12 +115,8 @@ impl MidiEditor {
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 (&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) }
@ -140,28 +136,16 @@ impl MidiEditor {
}
}
/// ```
/// use tek::{*, tengri::*};
///
/// struct Test(Option<MidiEditor>);
/// impl_as_ref!(Option<MidiEditor>: |self: Test|&self.0);
/// impl_as_mut!(Option<MidiEditor>: |self: Test|&mut self.0);
///
/// 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();
/// ```
impl <T: NotePoint+TimePoint> MidiPoint for T {}
impl <T: TimeRange+NoteRange> MidiRange for T {}
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
fn redraw (&self);
/// Make sure cursor is within note range
fn autoscroll (&self) {
let note_pos = self.get_note_pos().min(127);
@ -173,6 +157,7 @@ pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
self.note_lo().store((note_lo + note_pos).saturating_sub(note_hi), Relaxed);
}
}
/// Make sure time range is within display
fn autozoom (&self) {
if self.time_lock().load(Relaxed) {
@ -216,6 +201,7 @@ pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
//}
//self.time_zoom().set(time_zoom);
}
}
impl_has!(Sizer: |self: MidiEditor| self.size);
@ -244,59 +230,173 @@ impl MidiViewer for MidiEditor {
}
}
impl App {
/// Toggle MIDI editor.
///
/// ```
/// tek::App::default().toggle_editor(None);
/// ```
pub fn toggle_editor (&mut self, value: Option<bool>) {
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
if value.unwrap_or_else(||!self.editor().is_some()) {
self.toggle_editor_on()
} else {
self.toggle_editor_off()
}
}
/// 12 piano keys, some highlighted.
///
/// ```
/// let keys = tek::OctaveVertical::default();
/// ```
#[derive(Copy, Clone)]
pub struct OctaveVertical {
pub on: [bool; 12],
pub colors: [Color; 3]
}
fn toggle_editor_on (&mut self) {
// Create new clip in pool when entering empty cell
if let Selection::TrackClip { track: track_index, scene } = *self.selection()
&& let Some(scene) = self.project.scenes.get_mut(scene)
&& scene.clips.get(track_index).is_none()
&& let Some(track) = self.project.tracks.get_mut(track_index)
{
let color = track.color.base.mix(scene.color.base, 0.5);
let color = ItemColor::random_near(color, 0.2).into();
let clip: MidiClip = MidiClip { color, ..Default::default() };
let clip = self.project.clips.append(Some(clip));
// autocolor: new clip colors from scene and track color
if let Some(editor) = &mut self.project.editor {
*editor.mode.clip.write() = clip.0.read().clone();
editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color)
.unwrap_or(ItemTheme::G[64]);
}
*scene.clips.slot(track_index).write() = clip.0.read().clone();
//Some(clip)
} else {
//None
}
}
fn toggle_editor_off (&mut self) {
if let Selection::TrackClip { track, scene } = *self.selection()
&& let Some(scene) = self.project.scenes.get_mut(scene)
{
// Remove clip from arrangement when exiting empty clip editor
let mut swapped = None;
if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages())
&& count == 0
{
std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write());
}
if let Some(clip) = swapped {
self.project.clips.remove(&*clip.read());
}
}
impl OctaveVertical {
pub 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_default!(OctaveVertical: Self {
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
});
/// A clip, rendered as a horizontal piano roll.
///
/// Uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
///
/// ```
/// let piano = tek::PianoHorizontal::default();
/// ```
#[derive(Clone, Default)] pub struct PianoHorizontal {
pub clip: ClipSlot,
/// Buffer where the whole clip is rerendered on change
pub buffer: Arc<RwLock<BigBuffer>>,
/// Size of actual notes area
pub size: Sizer,
/// 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,
}
impl_has!(Sizer: |self: PianoHorizontal| self.size);
impl PianoHorizontal {
pub fn new () -> Self {
let size = Sizer::default();
let mut range = MidiSelection::from((12, true));
range.time_axis = size.0.clone();
range.note_axis = size.1.clone();
let piano = Self { keys_width: 5, size, range, ..Default::default() };
piano.redraw();
piano
}
}
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 {
/// Determine the required space to render the clip.
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
let zoom = self.range.time_zoom().load(Relaxed);
let size = if zoom > 0 { clip.length / zoom } else { 0 };
(size, 128)
}
/// Draw the piano roll into the buffer.
fn redraw (&self) {
*self.buffer.write() = self.clip.map_inner(|clip|{
let buf_size = self.buffer_size(&clip);
let mut buffer = BigBuffer::from(buf_size);
let time_zoom = self.get_time_zoom().max(1);
self.time_len().store(clip.length, Relaxed);
let note_len = self.get_note_len();
let note_pos = self.get_note_pos();
let time_pos = self.get_time_pos();
draw_piano_fg(&mut buffer, &clip, time_zoom);
buffer
}).unwrap_or_default()
}
}
impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let buffer = self.buffer.read();
f.debug_struct("PianoHorizontal")
.field("time_zoom", &self.range.time_zoom)
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
.finish()
}
}
//impl App {
///// Toggle MIDI editor.
/////
///// ```
///// tek::App::default().toggle_editor(None);
///// ```
//pub fn toggle_editor (&mut self, value: Option<bool>) {
////FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
//if value.unwrap_or_else(||!self.editor().is_some()) {
//self.toggle_editor_on()
//} else {
//self.toggle_editor_off()
//}
//}
//fn toggle_editor_on (&mut self) {
//// Create new clip in pool when entering empty cell
//if let Selection::TrackClip { track: track_index, scene } = *self.selection()
//&& let Some(scene) = self.project.scenes.get_mut(scene)
//&& scene.clips.get(track_index).is_none()
//&& let Some(track) = self.project.tracks.get_mut(track_index)
//{
//let color = track.color.base.mix(scene.color.base, 0.5);
//let color = ItemColor::random_near(color, 0.2).into();
//let clip: MidiClip = MidiClip { color, ..Default::default() };
//let clip = self.project.clips.append(Some(clip));
//// autocolor: new clip colors from scene and track color
//if let Some(editor) = &mut self.project.editor {
//*editor.mode.clip.write() = clip.0.read().clone();
//editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color)
//.unwrap_or(ItemTheme::G[64]);
//}
//*scene.clips.slot(track_index).write() = clip.0.read().clone();
////Some(clip)
//} else {
////None
//}
//}
//fn toggle_editor_off (&mut self) {
//if let Selection::TrackClip { track, scene } = *self.selection()
//&& let Some(scene) = self.project.scenes.get_mut(scene)
//{
//// Remove clip from arrangement when exiting empty clip editor
//let mut swapped = None;
//if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages())
//&& count == 0
//{
//std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write());
//}
//if let Some(clip) = swapped {
//self.project.clips.remove(&*clip.read());
//}
//}
//}
//}

View file

@ -1,23 +0,0 @@
use crate::*;
/// 12 piano keys, some highlighted.
///
/// ```
/// let keys = tek::OctaveVertical::default();
/// ```
#[derive(Copy, Clone)]
pub struct OctaveVertical {
pub on: [bool; 12],
pub colors: [Color; 3]
}
impl OctaveVertical {
pub 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_default!(OctaveVertical: Self {
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
});

View file

@ -1,86 +0,0 @@
use crate::*;
/// A clip, rendered as a horizontal piano roll.
///
/// ```
/// let piano = tek::PianoHorizontal::default();
/// ```
#[derive(Clone, Default)] pub struct PianoHorizontal {
pub clip: ClipSlot,
/// Buffer where the whole clip is rerendered on change
pub buffer: Arc<RwLock<BigBuffer>>,
/// Size of actual notes area
pub size: Sizer,
/// 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,
}
impl_has!(Sizer: |self: PianoHorizontal| self.size);
impl PianoHorizontal {
pub fn new () -> Self {
let size = Sizer::default();
let mut range = MidiSelection::from((12, true));
range.time_axis = size.0.clone();
range.note_axis = size.1.clone();
let piano = Self { keys_width: 5, size, range, ..Default::default() };
piano.redraw();
piano
}
}
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 {
/// Determine the required space to render the clip.
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
(clip.length / self.range.time_zoom().load(Relaxed), 128)
}
fn redraw (&self) {
*self.buffer.write() = self.clip.map_inner(|clip|{
let buf_size = self.buffer_size(&clip);
let mut buffer = BigBuffer::from(buf_size);
let time_zoom = self.get_time_zoom();
self.time_len().store(clip.length, Relaxed);
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
}).unwrap_or_default()
}
}
impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let buffer = self.buffer.read();
f.debug_struct("PianoHorizontal")
.field("time_zoom", &self.range.time_zoom)
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
.finish()
}
}

View file

@ -48,6 +48,7 @@ use crate::*;
/// Scroll offset of scenes
#[cfg(feature = "scene")] pub scene_scroll: usize,
}
impl Project {
/// Create a new arrangement.
pub fn new (
@ -104,7 +105,7 @@ impl Project {
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
}
}
impl HasJack<'static> for Project { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl_has!(Jack<'static>: |self: Project| self.jack);
impl_has!(Sizer: |self: Project| self.size);
impl_has!(Clock: |self: Project| self.clock);
@ -114,6 +115,13 @@ impl_as_ref_opt!(Scene: |self: Project| self.selected_scene());
impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut self.editor);
impl HasJack<'static> for Project {
fn jack (&self) -> &Jack<'static> {
&self.jack
}
}
impl HasClip for Project {
fn clip_slot (&self) -> Option<Slot<MidiClip>> {
match self.selection {
@ -126,6 +134,25 @@ impl HasClip for Project {
}
}
}
impl HasClipsSize for Project {
fn clips_size (&self) -> &Sizer {
&self.size_inner
}
}
impl ScenesView for Project {
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
fn w_side (&self) -> u16 {
(self.size.w() as u16 * 2 / 10).max(20)
}
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40)
}
}
pub fn swap_value <T: Clone + PartialEq, U> (
target: &mut T, value: &T, returned: impl Fn(T)->U
) -> Perhaps<U> {
@ -137,6 +164,7 @@ pub fn swap_value <T: Clone + PartialEq, U> (
Ok(Some(returned(value)))
}
}
pub fn toggle_bool <U> (
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
) -> Perhaps<U> {
@ -148,6 +176,7 @@ pub fn toggle_bool <U> (
Ok(Some(returned(Some(value))))
}
}
/// Modes for clip pool
#[derive(Debug, Clone)] pub enum PoolMode {
/// Renaming a pattern
@ -159,6 +188,7 @@ pub fn toggle_bool <U> (
/// Save clip to disk
Export(usize, Browse),
}
/// Focused field of `ClipLength`
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
/// Editing the number of bars
@ -168,6 +198,7 @@ pub fn toggle_bool <U> (
/// Editing the number of ticks
Tick,
}
impl ClipLengthFocus {
pub fn next (&mut self) {
use ClipLengthFocus::*;
@ -178,6 +209,7 @@ impl ClipLengthFocus {
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
}
}
/// Displays and edits clip length.
#[derive(Clone, Debug, Default)] pub struct ClipLength {
/// Pulses per beat (quaver)
@ -189,6 +221,7 @@ impl ClipLengthFocus {
/// Selected subdivision
pub focus: Option<ClipLengthFocus>,
}
impl ClipLength {
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
@ -232,6 +265,205 @@ impl HasSceneScroll for Project {
fn scene_scroll (&self) -> usize { self.scene_scroll }
}
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
pub struct Junction<T: JackPort>(T);
impl<T: JackPort> Draw<Tui> for Junction<T> {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
T::KIND.draw(to)
}
}
impl_as_ref_opt!(Track: |self: Project| self.selected_track());
impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut());
impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
#[default]
/// Nothing is selected
Nothing,
/// The whole mix is selected
Mix,
/// A MIDI input is selected.
Input(usize),
/// A MIDI output is selected.
Output(usize),
/// A clip is selected.
Clip(usize),
/// A sample is selected.
Sample(usize),
/// A scene is selected.
#[cfg(feature = "scene")] Scene(usize),
/// A track is selected.
#[cfg(feature = "track")] Track(usize),
/// A clip slot (track × scene) is selected.
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected.
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
/// A track's MIDI output connection is selected.
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
/// A track device slot is selected.
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
}
impl Selection {
pub fn describe (
&self,
#[cfg(feature = "track")] tracks: &[Track],
#[cfg(feature = "scene")] scenes: &[Scene],
) -> Arc<str> {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
#[cfg(feature = "scene")] Scene(s) =>
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
#[cfg(feature = "track")] Track(t) =>
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match &s.clips.get(*track) {
None => format!("T{track} S{scene}: Empty"),
Some(clip) => format!("T{track} S{scene} C{}",
clip.as_ref().map(|clip|clip.read().name.clone()).unwrap_or_default()),
},
_ => format!("T{track} S{scene}: Empty"),
},
_ => todo!()
}).into()
}
}
#[cfg(feature = "scene")]
impl Selection {
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
}
pub fn select_scene (&self, scene_count: usize) -> Self {
use Selection::*;
match self {
Mix | Track(_) => Scene(0),
Scene(s) => Scene((s + 1) % scene_count),
TrackClip { scene, .. } => Track(*scene),
_ => todo!(),
}
}
pub fn select_scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
Track(t) => TrackClip { track: *t, scene: 0 },
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
_ => todo!()
}
}
pub fn select_scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
Scene(s) => Scene(s - 1),
Track(t) => Track(*t),
TrackClip { track, scene: 0 } => Track(*track),
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
_ => todo!()
}
}
}
#[cfg(feature = "track")]
impl Selection {
pub fn track (&self) -> Option<usize> {
use Selection::*;
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
Some(*track)
} else {
None
}
}
pub fn select_track (&self, track_count: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(_) => Mix,
Track(t) => Track((t + 1) % track_count),
TrackClip { track, .. } => Track(*track),
_ => todo!(),
}
}
pub fn select_track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(s) => TrackClip { track: 0, scene: *s },
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
_ => todo!()
}
}
pub fn select_track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
Scene(s) => Scene(*s),
Track(0) => Mix,
Track(t) => Track(t - 1),
TrackClip { track: 0, scene } => Scene(*scene),
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
_ => todo!()
}
}
}
impl<T: HasSelection> SelectionModel for T {}
pub trait SelectionModel: HasSelection {
/// Get the active track
#[cfg(feature = "track")] fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks().get(index)
}
/// Get a mutable reference to the active track
#[cfg(feature = "track")] fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks_mut().get_mut(index)
}
/// Get the active scene
#[cfg(feature = "scene")] fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes().get(index)
}
/// Get a mutable reference to the active scene
#[cfg(feature = "scene")] fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes_mut().get_mut(index)
}
/// Get the active clip
#[cfg(feature = "clip")] fn selected_clip (&self) -> Option<Slot<MidiClip>> where Self: HasScenes + HasTracks {
self.selected_scene()?.clips.read().get(self.selection().track()?).cloned()
}
}
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
//let model = Self::default();
//model.clips.try_write().unwrap().push(clip.clone());

View file

@ -1,46 +0,0 @@
use crate::*;
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
pub struct Junction<T: JackPort>(T);
impl<T: JackPort> Draw<Tui> for Junction<T> {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
T::KIND.draw(to)
}
}
#[tek_proc::command(AudioInput)]
#[tek_proc::keyword(AudioInput)]
#[derive(Debug)]
pub enum AudioInputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(AudioOutput)]
#[tek_proc::keyword(AudioOutput)]
#[derive(Debug)]
pub enum AudioOutputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiInput)]
#[tek_proc::keyword(MidiInput)]
#[derive(Debug)]
pub enum MidiInputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiOutput)]
#[tek_proc::keyword(MidiOutput)]
#[derive(Debug)]
pub enum MidiOutputCmd {
Close,
Connect(Arc<str>),
}

View file

@ -1,185 +0,0 @@
use crate::*;
impl_as_ref_opt!(Track: |self: Project| self.selected_track());
impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut());
impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
/// Represents the current user selection in the arranger
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum Selection {
#[default]
/// Nothing is selected
Nothing,
/// The whole mix is selected
Mix,
/// A MIDI input is selected.
Input(usize),
/// A MIDI output is selected.
Output(usize),
/// A clip is selected.
Clip(usize),
/// A sample is selected.
Sample(usize),
/// A scene is selected.
#[cfg(feature = "scene")] Scene(usize),
/// A track is selected.
#[cfg(feature = "track")] Track(usize),
/// A clip slot (track × scene) is selected.
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected.
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
/// A track's MIDI output connection is selected.
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
/// A track device slot is selected.
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
}
impl Selection {
pub fn describe (
&self,
#[cfg(feature = "track")] tracks: &[Track],
#[cfg(feature = "scene")] scenes: &[Scene],
) -> Arc<str> {
use Selection::*;
format!("{}", match self {
Mix => "Everything".to_string(),
#[cfg(feature = "scene")] Scene(s) =>
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
#[cfg(feature = "track")] Track(t) =>
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match &s.clips.get(*track) {
None => format!("T{track} S{scene}: Empty"),
Some(clip) => format!("T{track} S{scene} C{}",
clip.as_ref().map(|clip|clip.read().name.clone()).unwrap_or_default()),
},
_ => format!("T{track} S{scene}: Empty"),
},
_ => todo!()
}).into()
}
}
#[cfg(feature = "scene")]
impl Selection {
pub fn scene (&self) -> Option<usize> {
use Selection::*;
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
}
pub fn select_scene (&self, scene_count: usize) -> Self {
use Selection::*;
match self {
Mix | Track(_) => Scene(0),
Scene(s) => Scene((s + 1) % scene_count),
TrackClip { scene, .. } => Track(*scene),
_ => todo!(),
}
}
pub fn select_scene_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Scene(0),
Track(t) => TrackClip { track: *t, scene: 0 },
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
_ => todo!()
}
}
pub fn select_scene_prev (&self) -> Self {
use Selection::*;
match self {
Mix | Scene(0) => Mix,
Scene(s) => Scene(s - 1),
Track(t) => Track(*t),
TrackClip { track, scene: 0 } => Track(*track),
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
_ => todo!()
}
}
}
#[cfg(feature = "track")]
impl Selection {
pub fn track (&self) -> Option<usize> {
use Selection::*;
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
Some(*track)
} else {
None
}
}
pub fn select_track (&self, track_count: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(_) => Mix,
Track(t) => Track((t + 1) % track_count),
TrackClip { track, .. } => Track(*track),
_ => todo!(),
}
}
pub fn select_track_next (&self, len: usize) -> Self {
use Selection::*;
match self {
Mix => Track(0),
Scene(s) => TrackClip { track: 0, scene: *s },
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
_ => todo!()
}
}
pub fn select_track_prev (&self) -> Self {
use Selection::*;
match self {
Mix => Mix,
Scene(s) => Scene(*s),
Track(0) => Mix,
Track(t) => Track(t - 1),
TrackClip { track: 0, scene } => Scene(*scene),
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
_ => todo!()
}
}
}
impl<T: HasSelection> SelectionModel for T {}
pub trait SelectionModel: HasSelection {
/// Get the active track
#[cfg(feature = "track")] fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks().get(index)
}
/// Get a mutable reference to the active track
#[cfg(feature = "track")] fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
let index = self.selection().track()?;
self.tracks_mut().get_mut(index)
}
/// Get the active scene
#[cfg(feature = "scene")] fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes().get(index)
}
/// Get a mutable reference to the active scene
#[cfg(feature = "scene")] fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
let index = self.selection().scene()?;
self.scenes_mut().get_mut(index)
}
/// Get the active clip
#[cfg(feature = "clip")] fn selected_clip (&self) -> Option<Slot<MidiClip>> where Self: HasScenes + HasTracks {
self.selected_scene()?.clips.read().get(self.selection().track()?).cloned()
}
}

View file

@ -6,16 +6,21 @@
(bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Nobody Will Help! ))))))))))
(mode Quit
(keys (@y Quit))
(view (bg (g 32) (fg (g 64) (fill/xy
(bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Are you sure? ))))))))))
(mode Save
(view (bg (g 32) (fg (g 64) (fill/xy
(bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Select save path ))))))))))
(bg (g 48) (fg (g 224) (align/c (bar/s 1
(expand/wh 2 2 (text Save to: ))
(text <file browser here>))))))))))
(mode Load
(view (bg (g 32) (fg (g 64) (fill/xy
(bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Select load path ))))))))))
(bg (g 48) (fg (g 224) (align/c (bar/s 1
(expand/wh 2 2 (text Load from: ))
(text <file browser here>))))))))))
(mode History
(view (bg (g 32) (fg (g 64) (fill/xy
@ -67,8 +72,8 @@
(view GrooveSequence (bsp/e (fill/y (align/n (bsp/s Status/v EditorStatus))) (bsp/e SamplesKeys Editor)))
(view Transport (bg (g 0) (pad/x 1 (bar/e 4 PlayPause (bar/e 16 Bpm (bar/e 16 Beat Time))))))
(view StatusBar (bar/e 24 (bg (g 50) (full/x (pad/x 1 (bar/s 1 (text Selected) Selected))))
(bar/e 24 (bg (g 40) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList))))
(bg (g 30) (full/x (pad/x 1 (bar/s 1 (text History) HistoryCount)))))))
(bar/e 24 (bg (g 42) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList))))
(bg (g 34) (full/x (pad/x 1 (bar/s 1 (text History) HistoryCount)))))))
(keys Global (@escape leave)
(@f1 (enter Help))

View file

@ -1,27 +1,5 @@
#![allow(clippy::unit_arg)]
mod deps; pub use self::deps::*;
mod clock; pub use self::clock::*;
mod midi_octave; pub use self::midi_octave::*;
mod midi_piano; pub use self::midi_piano::*;
mod midi_point; pub use self::midi_point::*;
mod midi_range; pub use self::midi_range::*;
mod smpl; pub use self::smpl::*;
mod smpl_sample; pub use self::smpl_sample::*;
mod smpl_add; pub use self::smpl_add::*;
mod smpl_kit; pub use self::smpl_kit::*;
mod smpl_voice; pub use self::smpl_voice::*;
mod midi_clip; pub use self::midi_clip::*;
mod midi_edit; pub use self::midi_edit::*;
mod midi_mon; pub use self::midi_mon::*;
mod midi_play; pub use self::midi_play::*;
mod midi_rec; pub use self::midi_rec::*;
mod midi_seq; pub use self::midi_seq::*;
/// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => {
@ -30,26 +8,46 @@ macro_rules! def_sizes_iter {
}
}
mod proj; pub use self::proj::*;
mod proj_device; pub use self::proj_device::*;
mod proj_mixer; pub use self::proj_mixer::*;
mod proj_port; pub use self::proj_port::*;
mod proj_scene; pub use self::proj_scene::*;
mod proj_select; pub use self::proj_select::*;
mod proj_track; pub use self::proj_track::*;
mod deps; pub use self::deps::*;
mod clock; pub use self::clock::*;
mod proj; pub use self::proj::*;
mod device; pub use self::device::*;
mod scene; pub use self::scene::*;
mod track; pub use self::track::*;
mod mixer; pub use self::mixer::*;
mod tek_bind; pub use self::tek_bind::*;
mod tek_cli; pub use self::tek_cli::*;
mod tek_conf; pub use self::tek_conf::*;
mod tek_ctrl; pub use self::tek_ctrl::*;
mod tek_has; pub use self::tek_has::*;
mod tek_mode; pub use self::tek_mode::*;
mod tek_pool; pub use self::tek_pool::*;
mod tek_view; pub use self::tek_view::*;
mod midi_clip; pub use self::midi_clip::*;
mod midi_edit; pub use self::midi_edit::*;
mod midi_mon; pub use self::midi_mon::*;
mod midi_play; pub use self::midi_play::*;
mod midi_point; pub use self::midi_point::*;
mod midi_range; pub use self::midi_range::*;
mod midi_rec; pub use self::midi_rec::*;
mod midi_seq; pub use self::midi_seq::*;
#[cfg(feature = "lv2")] mod device_lv2; #[cfg(feature = "lv2")] pub use self::device_lv2::*;
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*;
#[cfg(feature = "vst2")] mod device_vst2; #[cfg(feature = "vst2")] pub use self::device_vst2::*;
mod smpl; pub use self::smpl::*;
mod smpl_add; pub use self::smpl_add::*;
mod smpl_kit; pub use self::smpl_kit::*;
mod smpl_sample; pub use self::smpl_sample::*;
mod smpl_voice; pub use self::smpl_voice::*;
mod tek_bind; pub use self::tek_bind::*;
mod tek_cli; pub use self::tek_cli::*;
mod tek_conf; pub use self::tek_conf::*;
mod tek_ctrl; pub use self::tek_ctrl::*;
mod tek_has; pub use self::tek_has::*;
mod tek_mode; pub use self::tek_mode::*;
mod tek_pool; pub use self::tek_pool::*;
mod tek_view; pub use self::tek_view::*;
#[cfg(feature = "lv2")] mod device_lv2;
#[cfg(feature = "lv2")] pub use self::device_lv2::*;
#[cfg(feature = "lv2_gui")] mod device_lv2_gui;
#[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*;
#[cfg(feature = "vst2")] mod device_vst2;
#[cfg(feature = "vst2")] pub use self::device_vst2::*;
/// Command-line entrypoint.
#[allow(unused)]
@ -370,12 +368,27 @@ impl_as_ref!(Option<Project>: |self: App|todo!());
impl_as_mut!(Option<Project>: |self: App|todo!());
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
impl HasClip for App {
fn clip_slot (&self) -> Option<Slot<MidiClip>> {
self.project.clip_slot()
}
}
impl HasClipsSize for App {
fn clips_size (&self) -> &Sizer {
&self.project.size_inner
}
}
impl ScenesView for App {
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(self.w_side())
}
fn w_side (&self) -> u16 {
20
}
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
}
tui_keys!(self: App, input {
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
@ -536,16 +549,10 @@ impl Dialog {
]),
MenuItem::new(" Load session ", [
AppCmd::Dialog {
dialog: Dialog::Browse(
BrowseTarget::LoadProject,
Browse::new(None).unwrap().into()
)
//dialog: Dialog::Browse {
//title: "Load session".into(),
//filter: "\\.tek$",
//}
}
AppCmd::Dialog { dialog: Dialog::Browse(
BrowseTarget::LoadProject,
Browse::new(None).unwrap().into()
) }
]),
MenuItem::new(" Exit ", [
@ -642,21 +649,23 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
/// let item: tek::MenuItem = Default::default();
/// ```
#[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>);
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
impl_default!(MenuItem: Self::new("", []));
impl PartialEq for MenuItem { fn eq (&self, other: &Self) -> bool { self.0 == other.0 } }
impl PartialEq for MenuItem {
fn eq (&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl MenuItem {
pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCmd; N]) -> Self {
Self(name.as_ref().into(), Arc::new(actions))
}
}
//impl AsRef<Arc<[MenuItem]>> for MenuItems {
//fn as_ref (&self) -> &Arc<[MenuItem]> {
//&self.0
//}
//}
/// Browses for files to load/save.
///
/// ```
@ -672,14 +681,6 @@ impl MenuItem {
pub size: Sizer,
}
//pub(crate) struct EntriesIterator<'a, S: Screen> {
//pub browser: &'a Browse,
//pub offset: usize,
//pub length: usize,
//pub index: usize,
//_screen: std::marker::PhantomData<S>
//}
#[derive(Clone, Debug)] pub enum BrowseTarget {
SaveProject,
LoadProject,
@ -688,17 +689,6 @@ impl MenuItem {
ImportClip(Arc<RwLock<Option<MidiClip>>>),
ExportClip(Arc<RwLock<Option<MidiClip>>>),
}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
impl Browse {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
@ -794,5 +784,32 @@ impl HasTrackScroll for App {
}
impl HasSceneScroll for App {
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
fn scene_scroll (&self) -> usize {
self.project.scene_scroll()
}
}
//pub(crate) struct EntriesIterator<'a, S: Screen> {
//pub browser: &'a Browse,
//pub offset: usize,
//pub length: usize,
//pub index: usize,
//_screen: std::marker::PhantomData<S>
//}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
//impl AsRef<Arc<[MenuItem]>> for MenuItems {
//fn as_ref (&self) -> &Arc<[MenuItem]> {
//&self.0
//}
//}

View file

@ -87,13 +87,13 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x });
#[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
#[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps<AppCmd> {
#[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into))
}
#[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps<AppCmd> {
@ -534,13 +534,23 @@ impl<T: HasClip + HasEditor
*self.editor_mut() = None;
} else if let Some(clip) = self.clip() {
let editor = MidiEditor {
mode: Default::default(),
mode: PianoHorizontal {
keys_width: 5,
range: MidiSelection {
time_len: Arc::new(96usize.into()),
time_zoom: Arc::new(1usize.into()),
note_axis: Arc::new(12usize.into()),
..Default::default()
},
..Default::default()
},
size: Sizer(
Arc::new(0usize.into()),
Arc::new(0usize.into()),
Arc::new(16usize.into()),
Arc::new(6usize.into()),
),
};
*editor.mode.clip.0.write() = clip.clone();
editor.redraw();
*self.editor_mut() = Some(editor);
}
Ok(Some(MidiEditCmd::Toggle))
@ -627,7 +637,10 @@ impl<T> MidiClipControl for T where T: HasClip
&mut self
) -> Perhaps<MidiClipCmd> {
Ok(self.clip_mut().map(|mut clip|if clip.is_none() {
*clip = Some(Default::default());
*clip = Some(Arc::new(RwLock::new(MidiClip {
ppq: 24, length: 96,
..Default::default()
})));
None // TODO undo
} else {
None
@ -698,6 +711,38 @@ impl<T> BrowseControl for T where T:
}
}
#[tek_proc::command(AudioInput)]
#[tek_proc::keyword(AudioInput)]
#[derive(Debug)]
pub enum AudioInputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(AudioOutput)]
#[tek_proc::keyword(AudioOutput)]
#[derive(Debug)]
pub enum AudioOutputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiInput)]
#[tek_proc::keyword(MidiInput)]
#[derive(Debug)]
pub enum MidiInputCmd {
Close,
Connect(Arc<str>),
}
#[tek_proc::command(MidiOutput)]
#[tek_proc::keyword(MidiOutput)]
#[derive(Debug)]
pub enum MidiOutputCmd {
Close,
Connect(Arc<str>),
}
//impl<T: HasPool
//+ Namespace<bool>
//+ Namespace<usize>

View file

@ -5,6 +5,20 @@ pub trait HasModeStack {
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>>;
}
/// ```
/// use tek::{*, tengri::*};
///
/// struct Test(Option<MidiEditor>);
/// impl_as_ref!(Option<MidiEditor>: |self: Test|&self.0);
/// impl_as_mut!(Option<MidiEditor>: |self: Test|&mut self.0);
///
/// 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();
/// ```
impl<T> HasEditor for T where T:
AsRef<Option<MidiEditor>> + AsMut<Option<MidiEditor>> {}
pub trait HasEditor:

View file

@ -8,75 +8,73 @@ use crate::*;
impl Draw<Tui> for App {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
#[cfg(feature = "prof2")] profiling::scope!("App::draw");
//self.perf.cycle(&mut |_|{
self.draw_error(to)?;
self.draw_modes(to)?;
self.draw_debug(to)?;
self.perf.cycle(&mut |_|{
draw_error(to, self.error.as_ref())?;
draw_mode_stack(to, &self)?;
view_debug(&self.perf, &self.size).draw(to)?;
#[cfg(feature = "prof2")] profiling::finish_frame!();
Ok(Some(to.area().into()))
//})
})
}
}
impl App {
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
Split::Above.bar(0,
Split::West.bar(16,
format!("{}/{} {} ",
self.perf.used.load(Relaxed),
self.perf.window.load(Relaxed),
self.perf.clock.raw() / 10000000).align_e(),
format!("{}x{} ",
self.size.0.load(Relaxed),
self.size.1.load(Relaxed)).align_w(),
).align_se(),
()
//Split::East.bar(16,
//format!("{}", self.modes.read()[0]),
//format!("{}", self.modes.read()[0])
//).align_sw()
).draw(to)
pub fn draw_error (to: &mut Tui, error: &RwLock<Option<Arc<str>>>) -> Usually<()> {
if let Some(e) = error.read().as_ref() {
e.as_ref().align_c().draw(to)?;
}
Ok(())
}
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
if let Some(e) = self.error.try_read().unwrap().as_ref() {
e.as_ref().align_c().draw(to)?;
}
Ok(())
}
pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw<Tui> {
Split::Above.bar(0,
Split::West.bar(16,
format!("{}/{} {} ",
perf.used.load(Relaxed),
perf.window.load(Relaxed),
perf.clock.raw() / 10000000).align_e(),
format!("{}x{} ",
size.0.load(Relaxed),
size.1.load(Relaxed)).align_w(),
).align_se(),
()
//Split::East.bar(16,
//format!("{}", self.modes.read()[0]),
//format!("{}", self.modes.read()[0])
//).align_sw()
)
}
fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
for name in self.modes.read().iter() {
let mut error = false;
if let Some(mode) = self.config.get_mode(name).as_ref().map(Arc::clone) {
for (index, view) in mode.view.iter().enumerate() {
match (view.render)(self, to) {
Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed);
},
Err(e) => {
let message = format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&mode.name,
&view.source
);
*self.error.try_write().unwrap() = Some(message.into());
error = true;
break;
}
pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
for name in state.modes.read().iter() {
let mut error = false;
if let Some(mode) = state.config.get_mode(name).as_ref().map(Arc::clone) {
for (index, view) in mode.view.iter().enumerate() {
match (view.render)(state, to) {
Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => {
state.size.0.store(w as usize, Relaxed);
state.size.1.store(h as usize, Relaxed);
},
Err(e) => {
let message = format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&mode.name,
&view.source
);
*state.error.try_write().unwrap() = Some(message.into());
error = true;
break;
}
}
if error {
break
} else {
*self.error.try_write().unwrap() = None;
}
}
if error {
break
} else {
*state.error.try_write().unwrap() = None;
}
}
Ok(())
}
Ok(())
}
/// Collection of custom view definitions.
@ -365,58 +363,48 @@ impl View<Tui, App> {
fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
macro_rules! draw {
(|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))};
($const:expr)=>{Self::boxed(move|_, to|$const.draw(to))};
()=>{Self::boxed(move|_, __|Ok(None))};
macro_rules! view {
(|$state:ident|$body:expr)=>{Box::new(move|$state, to|$body.draw(to))};
($const:expr)=>{Box::new(move|_, to|$const.draw(to))};
()=>{Box::new(move|_, __|Ok(None))};
}
Ok(Arc::new(match word.split("/").next() {
Some("Beat") => draw!(),
Some("Bpm") => draw!(),
Some("BrowseTitle") => draw!(|app|view_browse_title(app)),
Some("Debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some("Device") => draw!(|app|view_device(app)),
Some("Menu") => draw!(|app|{
if let Dialog::Menu(selected, items) = &app.dialog {
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
fg_bg(f, b, item).exact_h(2).align_x()
})).exact_w(20).align_x())
} else {
None
}
}),
Some("Editor") => draw!("TODO editor"),
Some("MetersIn") => draw!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
Some("MetersOut") => draw!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
Some("ModeList") => draw!(|app|view_modes(app)),
Some("HistoryCount") => draw!(|app|app.history.len()),
Some("ScenesClips") => draw!(|app|view_scenes_clips(app)),
Some("ScenesNames") => draw!(|app|app.view_scenes_names()),
Some("Sessions") => draw!(|___|view_sessions()),
Some("Selected") => draw!(|app|view_selection(app)),
Some("Templates") => draw!(|app|view_templates(app)),
Some("Time") => draw!(),
Some("Tracks") => draw!("TODO tracks"),
Some("TracksDevices") => draw!(|app|app.project.view_track_devices(app.color.clone())),
Some("TracksDevicesAdd") => draw!(|app|app.project.view_track_device_add()),
Some("TracksDevicesCells") => draw!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
Some("TracksDevicesCount") => draw!(|app|app.project.view_track_device_count()),
Some("TracksInputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())),
Some("TracksInputsAdd") => draw!(|app|app.project.view_track_input_add()),
Some("TracksInputsCells") => draw!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
Some("TracksInputsCount") => draw!(|app|app.project.view_track_input_count()),
Some("TracksNames") => draw!(|app|app.project.view_track_names(app.color.clone())),
Some("TracksNamesAdd") => draw!(|app|app.project.view_track_names_add()),
Some("TracksNamesCells") => draw!(|app|app.project.view_track_names_cells()),
Some("TracksNamesCount") => draw!(|app|app.project.view_track_names_count()),
Some("TracksOutputs") => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
Some("TracksOutputsAdd") => draw!(|app|app.project.view_track_output_add()),
Some("TracksOutputsCells") => draw!(|app|app.project.view_track_output_cells()),
Some("TracksOutputsCount") => draw!(|app|app.project.view_track_output_count()),
Some(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) {
Some("Beat") => view!(),
Some("Bpm") => view!(),
Some("BrowseTitle") => view!(|app|view_browse_title(app)),
Some("Debug") => Box::new(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some("Device") => view!(|app|view_device(app)),
Some("Menu") => view!(|app|view_menu(app)),
Some("Editor") => view!(|app|app.editor()),
Some("MetersIn") => view!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
Some("MetersOut") => view!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
Some("ModeList") => view!(|app|view_modes(app)),
Some("HistoryCount") => view!(|app|app.history.len()),
Some("ScenesClips") => view!(|app|view_scenes_clips(app)),
Some("ScenesNames") => view!(|app|app.view_scenes_names()),
Some("Sessions") => view!(|___|view_sessions()),
Some("Selected") => view!(|app|view_selection(app)),
Some("Templates") => view!(|app|view_templates(app)),
Some("Time") => view!(),
Some("Tracks") => view!("TODO tracks"),
Some("TracksDevices") => view!(|app|app.project.view_track_devices(app.color.clone())),
Some("TracksDevicesAdd") => view!(|app|app.project.view_track_device_add()),
Some("TracksDevicesCells") => view!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
Some("TracksDevicesCount") => view!(|app|app.project.view_track_device_count()),
Some("TracksInputs") => view!(|app|app.project.view_track_inputs(app.color.clone())),
Some("TracksInputsAdd") => view!(|app|app.project.view_track_input_add()),
Some("TracksInputsCells") => view!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
Some("TracksInputsCount") => view!(|app|app.project.view_track_input_count()),
Some("TracksNames") => view!(|app|app.project.view_track_names(app.color.clone())),
Some("TracksNamesAdd") => view!(|app|app.project.view_track_names_add()),
Some("TracksNamesCells") => view!(|app|app.project.view_track_names_cells()),
Some("TracksNamesCount") => view!(|app|app.project.view_track_names_count()),
Some("TracksOutputs") => view!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()),
Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()),
Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()),
Some(_) => Box::new(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) {
(view.render)(state, to)
} else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
@ -426,6 +414,18 @@ impl View<Tui, App> {
}
}
pub fn view_menu (app: &App) -> impl Draw<Tui> {
if let Dialog::Menu(selected, items) = &app.dialog {
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
fg_bg(f, b, item).exact_h(2).align_x()
})).exact_w(20).align_x())
} else {
None
}
}
/// ```
/// let _ = tek::button_play_pause(true, true);
/// let _ = tek::button_play_pause(true, false);
@ -489,7 +489,9 @@ pub fn view_piano_h_notes (state: &PianoHorizontal) -> impl Draw<Tui> {
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.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) {
if let Some(cell) = to.cell_mut(
ratatui::prelude::Position::from((screen_x, screen_y))
) {
*cell = source_cell.clone();
}
}
@ -605,47 +607,43 @@ impl OctaveVertical {
}
}
impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } }
impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } }
pub fn view_editor_clip_status (editor: &MidiEditor) -> impl Draw<Tui> + '_ {
let (
_color, name, length, looped
) = if let Some(clip) = editor.mode.clip.read().as_ref().map(|p|p.read()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else {
(ItemTheme::G[64], String::new().into(), 0, false)
};
south!(
east(button_2("f2", "name ", false), fg(Rgb(255, 255, 255), format!("{name} "))
.origin_e().full_w()).origin_w().full_w(),
east(button_2("l", "ength ", false), fg(Rgb(255, 255, 255), format!("{length} ")).origin_e().full_w())
.origin_w().full_w(),
east(button_2("r", "epeat ", false), fg(Rgb(255, 255, 255), format!("{looped} ")).origin_e().full_w())
.origin_w().full_w(),
).exact_w(20)
}
impl MidiEditor {
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
let (
_color, name, length, looped
) = if let Some(clip) = self.mode.clip.read().as_ref().map(|p|p.read()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else {
(ItemTheme::G[64], String::new().into(), 0, false)
};
south!(
east(button_2("f2", "name ", false), fg(Rgb(255, 255, 255), format!("{name} "))
.origin_e().full_w()).origin_w().full_w(),
east(button_2("l", "ength ", false), fg(Rgb(255, 255, 255), format!("{length} ")).origin_e().full_w())
.origin_w().full_w(),
east(button_2("r", "epeat ", false), fg(Rgb(255, 255, 255), format!("{looped} ")).origin_e().full_w())
.origin_w().full_w(),
).exact_w(20)
}
pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
let (_color, length) = if let Some(clip) = self.mode.clip.read().as_ref().map(|p|p.read()) {
(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());
south!(
east(button_2("t", "ime ", false),
fg(Rgb(255, 255, 255), format!("{length} /{time_zoom} +{time_pos} ")).origin_e().full_w()).origin_w().full_w(),
east(button_2("z", "lock ", false),
fg(Rgb(255, 255, 255), format!("{time_lock}")).origin_e().full_w()).origin_w().full_w(),
east(button_2("x", "note ", false),
fg(Rgb(255, 255, 255), format!("{note_name} {note_pos} {note_len}")).origin_e().full_w()).origin_w().full_w(),
).exact_w(20)
}
pub fn view_editor_edit_status (editor: &MidiEditor) -> impl Draw<Tui> + '_ {
let (_color, length) = if let Some(clip) = editor.mode.clip.read().as_ref().map(|p|p.read()) {
(clip.color, clip.length)
} else { (ItemTheme::G[64], 0) };
let time_pos = editor.get_time_pos();
let time_zoom = editor.get_time_zoom();
let time_lock = if editor.get_time_lock() { "[lock]" } else { " " };
let note_pos = editor.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}", editor.get_note_len());
south!(
east(button_2("t", "ime ", false),
fg(Rgb(255, 255, 255), format!("{length} /{time_zoom} +{time_pos} ")).origin_e().full_w()).origin_w().full_w(),
east(button_2("z", "lock ", false),
fg(Rgb(255, 255, 255), format!("{time_lock}")).origin_e().full_w()).origin_w().full_w(),
east(button_2("x", "note ", false),
fg(Rgb(255, 255, 255), format!("{note_name} {note_pos} {note_len}")).origin_e().full_w()).origin_w().full_w(),
).exact_w(20)
}
impl Draw<Tui> for MidiEditor {
@ -660,28 +658,31 @@ impl<T> TracksView for T where T:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{}
fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where
T: HasTracks + HasEditor + HasSelection + HasClipsSize + ?Sized
{
let _editor_width = state.editor().as_ref().map(|e|e.size.w());
let _active_track = state.selection().track();
let mut x = 0;
let w = state.clips_size().w() as usize;
state.tracks().iter().enumerate().map_while(move |(index, track)|{
let width = track.width.max(8);
if x + width < w {
let data = (index, track, x, x + width);
x += width;
Some(data)
} else {
None
}
})
}
pub trait TracksView:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{
/// Spacing between tracks.
const TRACK_SPACING: usize = 0;
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let _editor_width = self.editor().as_ref().map(|e|e.size.w());
let _active_track = self.selection().track();
let mut x = 0;
let w = self.clips_size().w() as usize;
self.tracks().iter().enumerate().map_while(move |(index, track)|{
let width = track.width.max(8);
if x + width < w {
let data = (index, track, x, x + width);
x += width + Self::TRACK_SPACING;
Some(data)
} else {
None
}
})
iter_tracks_with_sizes(self)
}
fn view_track_column <'t, S, D, F> (&'t self, f: F) -> impl Draw<S> where
@ -830,7 +831,13 @@ pub trait TracksView:
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.outputs_height();
let list = south(
let list = self.view_outputs_list(height);
view_track_row_section(theme, list, self.view_track_output_add(),
bg(theme.darker.term, self.view_outputs_grid().align_w().full_w())).exact_h(height)
}
fn view_outputs_list (&self, height: u16) -> impl Draw<Tui> {
south(
button_3("o", "utput", self.midi_outs().len(), false).align_w().full_w().exact_h(1),
bg(Color::Rgb(0, 0, 0), draw(|to: &mut Tui|{
for (_index, port) in self.midi_outs().iter().enumerate() {
@ -844,26 +851,28 @@ pub trait TracksView:
}
}
todo!();
})).align_nw().full_wh().exact_h(height - 1).min_h(1)
);
view_track_row_section(theme, list, self.view_track_output_add(),
bg(theme.darker.term, draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let _ = draw(|to: &mut Tui|{
east(either(true, fg(Green, "play "), "play "),
either(false, fg(Yellow, "solo "), "solo ")).align_w().exact_h(1).draw(to)?;
for (_index, port) in self.midi_outs().iter().enumerate() {
east(either(true, fg(Green, " ● "), " · "),
either(false, fg(Yellow, " ● "), " · ")).align_w().exact_h(1).draw(to)?;
for (_index, _conn) in port.connections.iter().enumerate() {
"".full_w().exact_h(1).draw(to)?;
}
})).align_nw().full_wh().exact_h(height.minus(1)).min_h(1)
)
}
fn view_outputs_grid (&self) -> impl Draw<Tui> {
draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let _ = draw(|to: &mut Tui|{
east(either(true, fg(Green, "play "), "play "),
either(false, fg(Yellow, "solo "), "solo ")).align_w().exact_h(1).draw(to)?;
for (_index, port) in self.midi_outs().iter().enumerate() {
east(either(true, fg(Green, " ● "), " · "),
either(false, fg(Yellow, " ● "), " · ")).align_w().exact_h(1).draw(to)?;
for (_index, _conn) in port.connections.iter().enumerate() {
"".full_w().exact_h(1).draw(to)?;
}
todo!()
}).exact_w(track_width(index, track)).draw(to)?;
}
todo!()
}).align_w().full_w())).exact_h(height)
}
todo!()
}).exact_w(track_width(index, track)).draw(to)?;
}
todo!()
})
}
}
@ -876,7 +885,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16;
fn w_mid (&self) -> u16;
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
iter_scenes_with_sizes(self)
}
fn view_scenes_names (&self) -> impl Draw<Tui> where Self: Sized {
let select = self.selection();
let editor = self.editor();
@ -885,50 +896,33 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
view_scene_name(select, editor.as_ref(), index, scene, editing)
}))
}
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
let mut y = 0;
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
let height = if self.selection().scene() == Some(s)
&& let Some(editor) = self.editor()
{
editor.size.h() as usize
} else {
Scene::DEFAULT_HEIGHT
};
if y + height <= self.clips_size().h() as usize {
let data = (s, scene, y, y + height);
y += height;
Some(data)
} else {
None
}
})
}
}
impl ScenesView for App {
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(self.w_side())
}
fn w_side (&self) -> u16 {
20
}
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
}
fn iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T:
HasScenes + HasSceneScroll + HasEditor + HasSelection + HasClipsSize + ?Sized
{
let mut y = 0;
let skip = state.scene_scroll();
let selected = state.selection();
let editor = state.editor();
let clips_size = state.clips_size();
state.scenes().iter().enumerate().skip(skip).map_while(move|(s, scene)|{
let height = if selected.scene() == Some(s)
&& let Some(editor) = editor
{
editor.size.h() as usize
} else {
Scene::DEFAULT_HEIGHT
};
impl ScenesView for Project {
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
fn w_side (&self) -> u16 {
(self.size.w() as u16 * 2 / 10).max(20)
}
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40)
}
if y + height <= clips_size.h() as usize {
let data = (s, scene, y, y + height);
y += height;
Some(data)
} else {
None
}
})
}
fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
@ -968,7 +962,7 @@ fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui>
let is_sel = view_scene_sel(select, track_index, scene_index, editing);
below(
Outer(true, Style::default().fg(o)).full_wh(),
below(
above(
below(
fg_bg(o, b, "".full_wh()),
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
@ -1204,9 +1198,11 @@ pub fn view_scene_name <'a> (
Scene::DEFAULT_HEIGHT as u16
};
let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w();
let b = when(select.scene() == Some(index) && editing, south(
editor.as_ref().map(|e|e.clip_status()),
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
let s = select.scene() == Some(index) && editing;
let b = when(s, south(
editor.map(view_editor_clip_status),
editor.map(view_editor_edit_status)
).full_wh().align_se());
let c = if select.scene() == Some(index) {
scene.color.light.term
} else {
@ -1462,62 +1458,67 @@ pub fn view_wrap <'a> (bg: Color, fg: Color, content: impl Draw<Tui>)
east(left, west(right, fg_bg(fg, bg, content)))
}
impl PianoHorizontal {
/// Draw the piano roll background.
///
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
pub 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 background.
pub fn draw_piano_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: █▄ █▄ █▄
pub 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);
}
/// Draw the piano roll foreground.
pub fn draw_piano_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() {
}
let time_end = time_start + zoom;
for time in time_start..time_end.min(clip.length) {
if let Some(notes) = clip.notes.get(time) {
for event in notes.iter() {
match event {
MidiMessage::NoteOn { key, .. } => {
let note = key.as_int() as usize;