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 { fn note_range_prev (&self) -> usize {
self.get_note_axis().saturating_sub(1) self.get_note_axis().saturating_sub(1)
} }
fn time_zoom (&self) -> usize { fn time_zoom (&self) -> usize { self.get_time_zoom() }
self.get_time_zoom() fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 }
}
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_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 (&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_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: NotePoint+TimePoint> MidiPoint for T {}
impl <T: TimeRange+NoteRange> MidiRange for T {} impl <T: TimeRange+NoteRange> MidiRange for T {}
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync { pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize); fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
fn redraw (&self); fn redraw (&self);
/// Make sure cursor is within note range /// Make sure cursor is within note range
fn autoscroll (&self) { fn autoscroll (&self) {
let note_pos = self.get_note_pos().min(127); 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); self.note_lo().store((note_lo + note_pos).saturating_sub(note_hi), Relaxed);
} }
} }
/// Make sure time range is within display /// Make sure time range is within display
fn autozoom (&self) { fn autozoom (&self) {
if self.time_lock().load(Relaxed) { if self.time_lock().load(Relaxed) {
@ -216,6 +201,7 @@ pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
//} //}
//self.time_zoom().set(time_zoom); //self.time_zoom().set(time_zoom);
} }
} }
impl_has!(Sizer: |self: MidiEditor| self.size); impl_has!(Sizer: |self: MidiEditor| self.size);
@ -244,59 +230,173 @@ impl MidiViewer for MidiEditor {
} }
} }
impl App { /// 12 piano keys, some highlighted.
/// Toggle MIDI editor.
/// ///
/// ``` /// ```
/// tek::App::default().toggle_editor(None); /// let keys = tek::OctaveVertical::default();
/// ``` /// ```
pub fn toggle_editor (&mut self, value: Option<bool>) { #[derive(Copy, Clone)]
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); pub struct OctaveVertical {
if value.unwrap_or_else(||!self.editor().is_some()) { pub on: [bool; 12],
self.toggle_editor_on() pub colors: [Color; 3]
} else { }
self.toggle_editor_off()
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 } }]
} }
} }
fn toggle_editor_on (&mut self) { impl_default!(OctaveVertical: Self {
// Create new clip in pool when entering empty cell on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
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() /// A clip, rendered as a horizontal piano roll.
&& let Some(track) = self.project.tracks.get_mut(track_index) ///
{ /// Uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
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 piano = tek::PianoHorizontal::default();
let clip = self.project.clips.append(Some(clip)); /// ```
// autocolor: new clip colors from scene and track color #[derive(Clone, Default)] pub struct PianoHorizontal {
if let Some(editor) = &mut self.project.editor { pub clip: ClipSlot,
*editor.mode.clip.write() = clip.0.read().clone(); /// Buffer where the whole clip is rerendered on change
editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color) pub buffer: Arc<RwLock<BigBuffer>>,
.unwrap_or(ItemTheme::G[64]); /// 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,
} }
*scene.clips.slot(track_index).write() = clip.0.read().clone();
//Some(clip) impl_has!(Sizer: |self: PianoHorizontal| self.size);
} else {
//None 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
} }
} }
fn toggle_editor_off (&mut self) { impl TimeRange for PianoHorizontal {
if let Selection::TrackClip { track, scene } = *self.selection() fn time_len (&self) -> &AtomicUsize { self.range.time_len() }
&& let Some(scene) = self.project.scenes.get_mut(scene) fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() }
{ fn time_lock (&self) -> &AtomicBool { self.range.time_lock() }
// Remove clip from arrangement when exiting empty clip editor fn time_start (&self) -> &AtomicUsize { self.range.time_start() }
let mut swapped = None; fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() }
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 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 /// Scroll offset of scenes
#[cfg(feature = "scene")] pub scene_scroll: usize, #[cfg(feature = "scene")] pub scene_scroll: usize,
} }
impl Project { impl Project {
/// Create a new arrangement. /// Create a new arrangement.
pub fn new ( pub fn new (
@ -104,7 +105,7 @@ impl Project {
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) //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!(Jack<'static>: |self: Project| self.jack);
impl_has!(Sizer: |self: Project| self.size); impl_has!(Sizer: |self: Project| self.size);
impl_has!(Clock: |self: Project| self.clock); 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_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor); impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut 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 { impl HasClip for Project {
fn clip_slot (&self) -> Option<Slot<MidiClip>> { fn clip_slot (&self) -> Option<Slot<MidiClip>> {
match self.selection { 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> ( pub fn swap_value <T: Clone + PartialEq, U> (
target: &mut T, value: &T, returned: impl Fn(T)->U target: &mut T, value: &T, returned: impl Fn(T)->U
) -> Perhaps<U> { ) -> Perhaps<U> {
@ -137,6 +164,7 @@ pub fn swap_value <T: Clone + PartialEq, U> (
Ok(Some(returned(value))) Ok(Some(returned(value)))
} }
} }
pub fn toggle_bool <U> ( pub fn toggle_bool <U> (
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
) -> Perhaps<U> { ) -> Perhaps<U> {
@ -148,6 +176,7 @@ pub fn toggle_bool <U> (
Ok(Some(returned(Some(value)))) Ok(Some(returned(Some(value))))
} }
} }
/// Modes for clip pool /// Modes for clip pool
#[derive(Debug, Clone)] pub enum PoolMode { #[derive(Debug, Clone)] pub enum PoolMode {
/// Renaming a pattern /// Renaming a pattern
@ -159,6 +188,7 @@ pub fn toggle_bool <U> (
/// Save clip to disk /// Save clip to disk
Export(usize, Browse), Export(usize, Browse),
} }
/// Focused field of `ClipLength` /// Focused field of `ClipLength`
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { #[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
/// Editing the number of bars /// Editing the number of bars
@ -168,6 +198,7 @@ pub fn toggle_bool <U> (
/// Editing the number of ticks /// Editing the number of ticks
Tick, Tick,
} }
impl ClipLengthFocus { impl ClipLengthFocus {
pub fn next (&mut self) { pub fn next (&mut self) {
use ClipLengthFocus::*; use ClipLengthFocus::*;
@ -178,6 +209,7 @@ impl ClipLengthFocus {
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
} }
} }
/// Displays and edits clip length. /// Displays and edits clip length.
#[derive(Clone, Debug, Default)] pub struct ClipLength { #[derive(Clone, Debug, Default)] pub struct ClipLength {
/// Pulses per beat (quaver) /// Pulses per beat (quaver)
@ -189,6 +221,7 @@ impl ClipLengthFocus {
/// Selected subdivision /// Selected subdivision
pub focus: Option<ClipLengthFocus>, pub focus: Option<ClipLengthFocus>,
} }
impl ClipLength { impl ClipLength {
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self { pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus } Self { ppq: PPQ, bpb: 4, pulses, focus }
@ -232,6 +265,205 @@ impl HasSceneScroll for Project {
fn scene_scroll (&self) -> usize { self.scene_scroll } 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>>|{ //impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
//let model = Self::default(); //let model = Self::default();
//model.clips.try_write().unwrap().push(clip.clone()); //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! )))))))))) (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Nobody Will Help! ))))))))))
(mode Quit (mode Quit
(keys (@y Quit))
(view (bg (g 32) (fg (g 64) (fill/xy (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? )))))))))) (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Are you sure? ))))))))))
(mode Save (mode Save
(view (bg (g 32) (fg (g 64) (fill/xy (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 (mode Load
(view (bg (g 32) (fg (g 64) (fill/xy (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 (mode History
(view (bg (g 32) (fg (g 64) (fill/xy (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 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 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)))) (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)))) (bar/e 24 (bg (g 42) (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))))))) (bg (g 34) (full/x (pad/x 1 (bar/s 1 (text History) HistoryCount)))))))
(keys Global (@escape leave) (keys Global (@escape leave)
(@f1 (enter Help)) (@f1 (enter Help))

View file

@ -1,27 +1,5 @@
#![allow(clippy::unit_arg)] #![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). /// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter { macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => { ($Type:ident => $($Item:ty),+) => {
@ -30,13 +8,28 @@ macro_rules! def_sizes_iter {
} }
} }
mod deps; pub use self::deps::*;
mod clock; pub use self::clock::*;
mod proj; pub use self::proj::*; mod proj; pub use self::proj::*;
mod proj_device; pub use self::proj_device::*; mod device; pub use self::device::*;
mod proj_mixer; pub use self::proj_mixer::*; mod scene; pub use self::scene::*;
mod proj_port; pub use self::proj_port::*; mod track; pub use self::track::*;
mod proj_scene; pub use self::proj_scene::*; mod mixer; pub use self::mixer::*;
mod proj_select; pub use self::proj_select::*;
mod proj_track; pub use self::proj_track::*; 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::*;
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_bind; pub use self::tek_bind::*;
mod tek_cli; pub use self::tek_cli::*; mod tek_cli; pub use self::tek_cli::*;
@ -47,9 +40,14 @@ mod tek_mode; pub use self::tek_mode::*;
mod tek_pool; pub use self::tek_pool::*; mod tek_pool; pub use self::tek_pool::*;
mod tek_view; pub use self::tek_view::*; 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")] mod device_lv2;
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*; #[cfg(feature = "lv2")] pub use self::device_lv2::*;
#[cfg(feature = "vst2")] mod device_vst2; #[cfg(feature = "vst2")] pub use self::device_vst2::*;
#[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. /// Command-line entrypoint.
#[allow(unused)] #[allow(unused)]
@ -370,12 +368,27 @@ impl_as_ref!(Option<Project>: |self: App|todo!());
impl_as_mut!(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_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut()); impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
impl HasClip for App { impl HasClip for App {
fn clip_slot (&self) -> Option<Slot<MidiClip>> { fn clip_slot (&self) -> Option<Slot<MidiClip>> {
self.project.clip_slot() 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 { tui_keys!(self: App, input {
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!"); #[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
@ -536,16 +549,10 @@ impl Dialog {
]), ]),
MenuItem::new(" Load session ", [ MenuItem::new(" Load session ", [
AppCmd::Dialog { AppCmd::Dialog { dialog: Dialog::Browse(
dialog: Dialog::Browse(
BrowseTarget::LoadProject, BrowseTarget::LoadProject,
Browse::new(None).unwrap().into() Browse::new(None).unwrap().into()
) ) }
//dialog: Dialog::Browse {
//title: "Load session".into(),
//filter: "\\.tek$",
//}
}
]), ]),
MenuItem::new(" Exit ", [ MenuItem::new(" Exit ", [
@ -642,21 +649,23 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
/// let item: tek::MenuItem = Default::default(); /// let item: tek::MenuItem = Default::default();
/// ``` /// ```
#[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>); #[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>);
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) }); impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
impl_default!(MenuItem: Self::new("", [])); 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 { impl MenuItem {
pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCmd; N]) -> Self { pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCmd; N]) -> Self {
Self(name.as_ref().into(), Arc::new(actions)) 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. /// Browses for files to load/save.
/// ///
/// ``` /// ```
@ -672,14 +681,6 @@ impl MenuItem {
pub size: Sizer, 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 { #[derive(Clone, Debug)] pub enum BrowseTarget {
SaveProject, SaveProject,
LoadProject, LoadProject,
@ -688,17 +689,6 @@ impl MenuItem {
ImportClip(Arc<RwLock<Option<MidiClip>>>), ImportClip(Arc<RwLock<Option<MidiClip>>>),
ExportClip(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 { impl Browse {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> { pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? }; 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 { 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

@ -534,13 +534,23 @@ impl<T: HasClip + HasEditor
*self.editor_mut() = None; *self.editor_mut() = None;
} else if let Some(clip) = self.clip() { } else if let Some(clip) = self.clip() {
let editor = MidiEditor { 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( size: Sizer(
Arc::new(0usize.into()), Arc::new(16usize.into()),
Arc::new(0usize.into()), Arc::new(6usize.into()),
), ),
}; };
*editor.mode.clip.0.write() = clip.clone(); *editor.mode.clip.0.write() = clip.clone();
editor.redraw();
*self.editor_mut() = Some(editor); *self.editor_mut() = Some(editor);
} }
Ok(Some(MidiEditCmd::Toggle)) Ok(Some(MidiEditCmd::Toggle))
@ -627,7 +637,10 @@ impl<T> MidiClipControl for T where T: HasClip
&mut self &mut self
) -> Perhaps<MidiClipCmd> { ) -> Perhaps<MidiClipCmd> {
Ok(self.clip_mut().map(|mut clip|if clip.is_none() { 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 None // TODO undo
} else { } else {
None 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 //impl<T: HasPool
//+ Namespace<bool> //+ Namespace<bool>
//+ Namespace<usize> //+ Namespace<usize>

View file

@ -5,6 +5,20 @@ pub trait HasModeStack {
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>>; 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: impl<T> HasEditor for T where T:
AsRef<Option<MidiEditor>> + AsMut<Option<MidiEditor>> {} AsRef<Option<MidiEditor>> + AsMut<Option<MidiEditor>> {}
pub trait HasEditor: pub trait HasEditor:

View file

@ -8,53 +8,52 @@ use crate::*;
impl Draw<Tui> for App { impl Draw<Tui> for App {
fn draw (&self, to: &mut Tui) -> Drawn<u16> { fn draw (&self, to: &mut Tui) -> Drawn<u16> {
#[cfg(feature = "prof2")] profiling::scope!("App::draw"); #[cfg(feature = "prof2")] profiling::scope!("App::draw");
//self.perf.cycle(&mut |_|{ self.perf.cycle(&mut |_|{
self.draw_error(to)?; draw_error(to, self.error.as_ref())?;
self.draw_modes(to)?; draw_mode_stack(to, &self)?;
self.draw_debug(to)?; view_debug(&self.perf, &self.size).draw(to)?;
#[cfg(feature = "prof2")] profiling::finish_frame!(); #[cfg(feature = "prof2")] profiling::finish_frame!();
Ok(Some(to.area().into())) Ok(Some(to.area().into()))
//}) })
} }
} }
impl App { pub fn draw_error (to: &mut Tui, error: &RwLock<Option<Arc<str>>>) -> Usually<()> {
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> { if let Some(e) = error.read().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::Above.bar(0,
Split::West.bar(16, Split::West.bar(16,
format!("{}/{} {} ", format!("{}/{} {} ",
self.perf.used.load(Relaxed), perf.used.load(Relaxed),
self.perf.window.load(Relaxed), perf.window.load(Relaxed),
self.perf.clock.raw() / 10000000).align_e(), perf.clock.raw() / 10000000).align_e(),
format!("{}x{} ", format!("{}x{} ",
self.size.0.load(Relaxed), size.0.load(Relaxed),
self.size.1.load(Relaxed)).align_w(), size.1.load(Relaxed)).align_w(),
).align_se(), ).align_se(),
() ()
//Split::East.bar(16, //Split::East.bar(16,
//format!("{}", self.modes.read()[0]), //format!("{}", self.modes.read()[0]),
//format!("{}", self.modes.read()[0]) //format!("{}", self.modes.read()[0])
//).align_sw() //).align_sw()
).draw(to) )
} }
fn draw_error (&self, to: &mut Tui) -> Usually<()> { pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
if let Some(e) = self.error.try_read().unwrap().as_ref() { for name in state.modes.read().iter() {
e.as_ref().align_c().draw(to)?;
}
Ok(())
}
fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
for name in self.modes.read().iter() {
let mut error = false; let mut error = false;
if let Some(mode) = self.config.get_mode(name).as_ref().map(Arc::clone) { if let Some(mode) = state.config.get_mode(name).as_ref().map(Arc::clone) {
for (index, view) in mode.view.iter().enumerate() { for (index, view) in mode.view.iter().enumerate() {
match (view.render)(self, to) { match (view.render)(state, to) {
Ok(None) => {}, Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => { Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed); state.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed); state.size.1.store(h as usize, Relaxed);
}, },
Err(e) => { Err(e) => {
let message = format!( let message = format!(
@ -62,7 +61,7 @@ impl App {
&mode.name, &mode.name,
&view.source &view.source
); );
*self.error.try_write().unwrap() = Some(message.into()); *state.error.try_write().unwrap() = Some(message.into());
error = true; error = true;
break; break;
} }
@ -71,13 +70,12 @@ impl App {
if error { if error {
break break
} else { } else {
*self.error.try_write().unwrap() = None; *state.error.try_write().unwrap() = None;
} }
} }
} }
Ok(()) Ok(())
} }
}
/// Collection of custom view definitions. /// Collection of custom view definitions.
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug)]
@ -365,19 +363,58 @@ impl View<Tui, App> {
fn compile_word (word: Arc<str>) fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>> -> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{ {
macro_rules! draw { macro_rules! view {
(|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))}; (|$state:ident|$body:expr)=>{Box::new(move|$state, to|$body.draw(to))};
($const:expr)=>{Self::boxed(move|_, to|$const.draw(to))}; ($const:expr)=>{Box::new(move|_, to|$const.draw(to))};
()=>{Self::boxed(move|_, __|Ok(None))}; ()=>{Box::new(move|_, __|Ok(None))};
} }
Ok(Arc::new(match word.split("/").next() { Ok(Arc::new(match word.split("/").next() {
Some("Beat") => draw!(), Some("Beat") => view!(),
Some("Bpm") => draw!(), Some("Bpm") => view!(),
Some("BrowseTitle") => draw!(|app|view_browse_title(app)), Some("BrowseTitle") => view!(|app|view_browse_title(app)),
Some("Debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)), Some("Debug") => Box::new(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some("Device") => draw!(|app|view_device(app)), Some("Device") => view!(|app|view_device(app)),
Some("Menu") => draw!(|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)
}),
_ => unreachable!()
}))
}
}
pub fn view_menu (app: &App) -> impl Draw<Tui> {
if let Dialog::Menu(selected, items) = &app.dialog { if let Dialog::Menu(selected, items) = &app.dialog {
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{ 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 f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
@ -387,43 +424,6 @@ impl View<Tui, App> {
} else { } else {
None 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()) {
(view.render)(state, to)
} else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
}),
_ => unreachable!()
}))
}
} }
/// ``` /// ```
@ -489,7 +489,9 @@ pub fn view_piano_h_notes (state: &PianoHorizontal) -> impl Draw<Tui> {
let is_in_y = source_y < source.height; let is_in_y = source_y < source.height;
if is_in_x && is_in_y { if is_in_x && is_in_y {
if let Some(source_cell) = source.get(source_x, source_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(); *cell = source_cell.clone();
} }
} }
@ -605,14 +607,10 @@ impl OctaveVertical {
} }
} }
impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } } pub fn view_editor_clip_status (editor: &MidiEditor) -> impl Draw<Tui> + '_ {
impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } }
impl MidiEditor {
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
let ( let (
_color, name, length, looped _color, name, length, looped
) = if let Some(clip) = self.mode.clip.read().as_ref().map(|p|p.read()) { ) = if let Some(clip) = editor.mode.clip.read().as_ref().map(|p|p.read()) {
(clip.color, clip.name.clone(), clip.length, clip.looped) (clip.color, clip.name.clone(), clip.length, clip.looped)
} else { } else {
(ItemTheme::G[64], String::new().into(), 0, false) (ItemTheme::G[64], String::new().into(), 0, false)
@ -626,17 +624,18 @@ impl MidiEditor {
.origin_w().full_w(), .origin_w().full_w(),
).exact_w(20) ).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()) { 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) (clip.color, clip.length)
} else { (ItemTheme::G[64], 0) }; } else { (ItemTheme::G[64], 0) };
let time_pos = self.get_time_pos(); let time_pos = editor.get_time_pos();
let time_zoom = self.get_time_zoom(); let time_zoom = editor.get_time_zoom();
let time_lock = if self.get_time_lock() { "[lock]" } else { " " }; let time_lock = if editor.get_time_lock() { "[lock]" } else { " " };
let note_pos = self.get_note_pos(); let note_pos = editor.get_note_pos();
let note_name = format!("{:4}", note_pitch_to_name(note_pos)); let note_name = format!("{:4}", note_pitch_to_name(note_pos));
let note_pos = format!("{:>3}", note_pos); let note_pos = format!("{:>3}", note_pos);
let note_len = format!("{:>4}", self.get_note_len()); let note_len = format!("{:>4}", editor.get_note_len());
south!( south!(
east(button_2("t", "ime ", false), 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(), fg(Rgb(255, 255, 255), format!("{length} /{time_zoom} +{time_pos} ")).origin_e().full_w()).origin_w().full_w(),
@ -646,7 +645,6 @@ impl MidiEditor {
fg(Rgb(255, 255, 255), format!("{note_name} {note_pos} {note_len}")).origin_e().full_w()).origin_w().full_w(), fg(Rgb(255, 255, 255), format!("{note_name} {note_pos} {note_len}")).origin_e().full_w()).origin_w().full_w(),
).exact_w(20) ).exact_w(20)
} }
}
impl Draw<Tui> for MidiEditor { impl Draw<Tui> for MidiEditor {
fn draw (&self, to: &mut Tui) -> Drawn<u16> { fn draw (&self, to: &mut Tui) -> Drawn<u16> {
@ -660,23 +658,18 @@ impl<T> TracksView for T where T:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{} {}
pub trait TracksView: fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack T: HasTracks + HasEditor + HasSelection + HasClipsSize + ?Sized
{ {
/// Spacing between tracks. let _editor_width = state.editor().as_ref().map(|e|e.size.w());
const TRACK_SPACING: usize = 0; let _active_track = state.selection().track();
/// 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 mut x = 0;
let w = self.clips_size().w() as usize; let w = state.clips_size().w() as usize;
self.tracks().iter().enumerate().map_while(move |(index, track)|{ state.tracks().iter().enumerate().map_while(move |(index, track)|{
let width = track.width.max(8); let width = track.width.max(8);
if x + width < w { if x + width < w {
let data = (index, track, x, x + width); let data = (index, track, x, x + width);
x += width + Self::TRACK_SPACING; x += width;
Some(data) Some(data)
} else { } else {
None None
@ -684,6 +677,14 @@ pub trait TracksView:
}) })
} }
pub trait TracksView:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
iter_tracks_with_sizes(self)
}
fn view_track_column <'t, S, D, F> (&'t self, f: F) -> impl Draw<S> where fn view_track_column <'t, S, D, F> (&'t self, f: F) -> impl Draw<S> where
S: Screen, S: Screen,
D: Draw<S> + 't, D: Draw<S> + 't,
@ -830,7 +831,13 @@ pub trait TracksView:
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> { fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.outputs_height(); 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), 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|{ bg(Color::Rgb(0, 0, 0), draw(|to: &mut Tui|{
for (_index, port) in self.midi_outs().iter().enumerate() { for (_index, port) in self.midi_outs().iter().enumerate() {
@ -844,10 +851,12 @@ pub trait TracksView:
} }
} }
todo!(); todo!();
})).align_nw().full_wh().exact_h(height - 1).min_h(1) })).align_nw().full_wh().exact_h(height.minus(1)).min_h(1)
); )
view_track_row_section(theme, list, self.view_track_output_add(), }
bg(theme.darker.term, draw(|to: &mut Tui|{
fn view_outputs_grid (&self) -> impl Draw<Tui> {
draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() { for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let _ = draw(|to: &mut Tui|{ let _ = draw(|to: &mut Tui|{
east(either(true, fg(Green, "play "), "play "), east(either(true, fg(Green, "play "), "play "),
@ -863,7 +872,7 @@ pub trait TracksView:
}).exact_w(track_width(index, track)).draw(to)?; }).exact_w(track_width(index, track)).draw(to)?;
} }
todo!() todo!()
}).align_w().full_w())).exact_h(height) })
} }
} }
@ -876,7 +885,9 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
fn h_scenes (&self) -> u16; fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16; fn w_side (&self) -> u16;
fn w_mid (&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 { fn view_scenes_names (&self) -> impl Draw<Tui> where Self: Sized {
let select = self.selection(); let select = self.selection();
let editor = self.editor(); let editor = self.editor();
@ -885,18 +896,26 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
view_scene_name(select, editor.as_ref(), index, scene, editing) view_scene_name(select, editor.as_ref(), index, scene, editing)
})) }))
} }
}
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { 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 mut y = 0;
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let skip = state.scene_scroll();
let height = if self.selection().scene() == Some(s) let selected = state.selection();
&& let Some(editor) = self.editor() 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 editor.size.h() as usize
} else { } else {
Scene::DEFAULT_HEIGHT Scene::DEFAULT_HEIGHT
}; };
if y + height <= self.clips_size().h() as usize {
if y + height <= clips_size.h() as usize {
let data = (s, scene, y, y + height); let data = (s, scene, y, y + height);
y += height; y += height;
Some(data) Some(data)
@ -905,31 +924,6 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
} }
}) })
} }
}
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)
}
}
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)
}
}
fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a { fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
draw(move|to: &mut Tui|{ draw(move|to: &mut Tui|{
@ -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); let is_sel = view_scene_sel(select, track_index, scene_index, editing);
below( below(
Outer(true, Style::default().fg(o)).full_wh(), Outer(true, Style::default().fg(o)).full_wh(),
below( above(
below( below(
fg_bg(o, b, "".full_wh()), fg_bg(o, b, "".full_wh()),
fg_bg(f, b, bold(true, name)).align_nw().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 Scene::DEFAULT_HEIGHT as u16
}; };
let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w(); 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( let s = select.scene() == Some(index) && editing;
editor.as_ref().map(|e|e.clip_status()), let b = when(s, south(
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh()); editor.map(view_editor_clip_status),
editor.map(view_editor_edit_status)
).full_wh().align_se());
let c = if select.scene() == Some(index) { let c = if select.scene() == Some(index) {
scene.color.light.term scene.color.light.term
} else { } else {
@ -1462,12 +1458,14 @@ pub fn view_wrap <'a> (bg: Color, fg: Color, content: impl Draw<Tui>)
east(left, west(right, fg_bg(fg, bg, content))) east(left, west(right, fg_bg(fg, bg, content)))
} }
impl PianoHorizontal {
/// Draw the piano roll background. /// Draw the piano roll background.
/// pub fn draw_piano_bg (
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ buf: &mut BigBuffer,
pub fn draw_bg ( clip: &MidiClip,
buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize, note_point: usize, time_point: usize zoom: usize,
note_len: usize,
note_point: usize,
time_point: usize
) { ) {
for (y, note) in (0..=127).rev().enumerate() { for (y, note) in (0..=127).rev().enumerate() {
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
@ -1501,9 +1499,11 @@ impl PianoHorizontal {
} }
/// Draw the piano roll foreground. /// Draw the piano roll foreground.
/// pub fn draw_piano_fg (
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ buf: &mut BigBuffer,
pub fn draw_fg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize) { clip: &MidiClip,
zoom: usize
) {
let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0)); let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0));
let mut notes_on = [false;128]; let mut notes_on = [false;128];
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() { for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
@ -1517,7 +1517,8 @@ impl PianoHorizontal {
} }
let time_end = time_start + zoom; let time_end = time_start + zoom;
for time in time_start..time_end.min(clip.length) { for time in time_start..time_end.min(clip.length) {
for event in clip.notes[time].iter() { if let Some(notes) = clip.notes.get(time) {
for event in notes.iter() {
match event { match event {
MidiMessage::NoteOn { key, .. } => { MidiMessage::NoteOn { key, .. } => {
let note = key.as_int() as usize; let note = key.as_int() as usize;