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. ///
/// /// ```
/// ``` /// let keys = tek::OctaveVertical::default();
/// tek::App::default().toggle_editor(None); /// ```
/// ``` #[derive(Copy, Clone)]
pub fn toggle_editor (&mut self, value: Option<bool>) { pub struct OctaveVertical {
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); pub on: [bool; 12],
if value.unwrap_or_else(||!self.editor().is_some()) { pub colors: [Color; 3]
self.toggle_editor_on() }
} else {
self.toggle_editor_off()
}
}
fn toggle_editor_on (&mut self) { impl OctaveVertical {
// Create new clip in pool when entering empty cell pub fn color (&self, pitch: usize) -> Color {
if let Selection::TrackClip { track: track_index, scene } = *self.selection() let pitch = pitch % 12;
&& let Some(scene) = self.project.scenes.get_mut(scene) self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }]
&& 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_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 /// 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,26 +8,46 @@ macro_rules! def_sizes_iter {
} }
} }
mod proj; pub use self::proj::*; mod deps; pub use self::deps::*;
mod proj_device; pub use self::proj_device::*; mod clock; pub use self::clock::*;
mod proj_mixer; pub use self::proj_mixer::*; mod proj; pub use self::proj::*;
mod proj_port; pub use self::proj_port::*; mod device; pub use self::device::*;
mod proj_scene; pub use self::proj_scene::*; mod scene; pub use self::scene::*;
mod proj_select; pub use self::proj_select::*; mod track; pub use self::track::*;
mod proj_track; pub use self::proj_track::*; mod mixer; pub use self::mixer::*;
mod tek_bind; pub use self::tek_bind::*; mod midi_clip; pub use self::midi_clip::*;
mod tek_cli; pub use self::tek_cli::*; mod midi_edit; pub use self::midi_edit::*;
mod tek_conf; pub use self::tek_conf::*; mod midi_mon; pub use self::midi_mon::*;
mod tek_ctrl; pub use self::tek_ctrl::*; mod midi_play; pub use self::midi_play::*;
mod tek_has; pub use self::tek_has::*; mod midi_point; pub use self::midi_point::*;
mod tek_mode; pub use self::tek_mode::*; mod midi_range; pub use self::midi_range::*;
mod tek_pool; pub use self::tek_pool::*; mod midi_rec; pub use self::midi_rec::*;
mod tek_view; pub use self::tek_view::*; mod midi_seq; pub use self::midi_seq::*;
#[cfg(feature = "lv2")] mod device_lv2; #[cfg(feature = "lv2")] pub use self::device_lv2::*; mod smpl; pub use self::smpl::*;
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*; mod smpl_add; pub use self::smpl_add::*;
#[cfg(feature = "vst2")] mod device_vst2; #[cfg(feature = "vst2")] pub use self::device_vst2::*; 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. /// 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

@ -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> { #[command(MidiEdit = "edit")] pub fn cmd_midi_edit (&mut self, command: MidiEditCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into)) 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)) Ok(command.dispatch(self)?.map(Into::into))
} }
#[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> { #[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into)) 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)) Ok(command.dispatch(self)?.map(Into::into))
} }
#[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps<AppCmd> { #[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; *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,75 +8,73 @@ 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() {
Split::Above.bar(0, e.as_ref().align_c().draw(to)?;
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)
} }
Ok(())
}
fn draw_error (&self, to: &mut Tui) -> Usually<()> { pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw<Tui> {
if let Some(e) = self.error.try_read().unwrap().as_ref() { Split::Above.bar(0,
e.as_ref().align_c().draw(to)?; Split::West.bar(16,
} format!("{}/{} {} ",
Ok(()) 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<()> { pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
for name in self.modes.read().iter() { for name in state.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!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&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;
}
} }
} }
if error { }
break if error {
} else { break
*self.error.try_write().unwrap() = None; } else {
} *state.error.try_write().unwrap() = None;
} }
} }
Ok(())
} }
Ok(())
} }
/// Collection of custom view definitions. /// Collection of custom view definitions.
@ -365,58 +363,48 @@ 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)),
if let Dialog::Menu(selected, items) = &app.dialog { Some("Editor") => view!(|app|app.editor()),
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{ Some("MetersIn") => view!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) }; Some("MetersOut") => view!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) }; Some("ModeList") => view!(|app|view_modes(app)),
fg_bg(f, b, item).exact_h(2).align_x() Some("HistoryCount") => view!(|app|app.history.len()),
})).exact_w(20).align_x()) Some("ScenesClips") => view!(|app|view_scenes_clips(app)),
} else { Some("ScenesNames") => view!(|app|app.view_scenes_names()),
None Some("Sessions") => view!(|___|view_sessions()),
} Some("Selected") => view!(|app|view_selection(app)),
}), Some("Templates") => view!(|app|view_templates(app)),
Some("Editor") => draw!("TODO editor"), Some("Time") => view!(),
Some("MetersIn") => draw!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())), Some("Tracks") => view!("TODO tracks"),
Some("MetersOut") => draw!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())), Some("TracksDevices") => view!(|app|app.project.view_track_devices(app.color.clone())),
Some("ModeList") => draw!(|app|view_modes(app)), Some("TracksDevicesAdd") => view!(|app|app.project.view_track_device_add()),
Some("HistoryCount") => draw!(|app|app.history.len()), Some("TracksDevicesCells") => view!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
Some("ScenesClips") => draw!(|app|view_scenes_clips(app)), Some("TracksDevicesCount") => view!(|app|app.project.view_track_device_count()),
Some("ScenesNames") => draw!(|app|app.view_scenes_names()), Some("TracksInputs") => view!(|app|app.project.view_track_inputs(app.color.clone())),
Some("Sessions") => draw!(|___|view_sessions()), Some("TracksInputsAdd") => view!(|app|app.project.view_track_input_add()),
Some("Selected") => draw!(|app|view_selection(app)), Some("TracksInputsCells") => view!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
Some("Templates") => draw!(|app|view_templates(app)), Some("TracksInputsCount") => view!(|app|app.project.view_track_input_count()),
Some("Time") => draw!(), Some("TracksNames") => view!(|app|app.project.view_track_names(app.color.clone())),
Some("Tracks") => draw!("TODO tracks"), Some("TracksNamesAdd") => view!(|app|app.project.view_track_names_add()),
Some("TracksDevices") => draw!(|app|app.project.view_track_devices(app.color.clone())), Some("TracksNamesCells") => view!(|app|app.project.view_track_names_cells()),
Some("TracksDevicesAdd") => draw!(|app|app.project.view_track_device_add()), Some("TracksNamesCount") => view!(|app|app.project.view_track_names_count()),
Some("TracksDevicesCells") => draw!(|app|app.project.view_track_device_cells(app.tracks_devices_height())), Some("TracksOutputs") => view!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
Some("TracksDevicesCount") => draw!(|app|app.project.view_track_device_count()), Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()),
Some("TracksInputs") => draw!(|app|app.project.view_track_inputs(app.color.clone())), Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()),
Some("TracksInputsAdd") => draw!(|app|app.project.view_track_input_add()), Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()),
Some("TracksInputsCells") => draw!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())), Some(_) => Box::new(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) {
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) (view.render)(state, to)
} else { } else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to) 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, true);
/// let _ = tek::button_play_pause(true, false); /// 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; 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,47 +607,43 @@ 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 } } 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 view_editor_edit_status (editor: &MidiEditor) -> impl Draw<Tui> + '_ {
pub fn clip_status (&self) -> impl Draw<Tui> + '_ { let (_color, length) = if let Some(clip) = editor.mode.clip.read().as_ref().map(|p|p.read()) {
let ( (clip.color, clip.length)
_color, name, length, looped } else { (ItemTheme::G[64], 0) };
) = if let Some(clip) = self.mode.clip.read().as_ref().map(|p|p.read()) { let time_pos = editor.get_time_pos();
(clip.color, clip.name.clone(), clip.length, clip.looped) let time_zoom = editor.get_time_zoom();
} else { let time_lock = if editor.get_time_lock() { "[lock]" } else { " " };
(ItemTheme::G[64], String::new().into(), 0, false) let note_pos = editor.get_note_pos();
}; let note_name = format!("{:4}", note_pitch_to_name(note_pos));
south!( let note_pos = format!("{:>3}", note_pos);
east(button_2("f2", "name ", false), fg(Rgb(255, 255, 255), format!("{name} ")) let note_len = format!("{:>4}", editor.get_note_len());
.origin_e().full_w()).origin_w().full_w(), south!(
east(button_2("l", "ength ", false), fg(Rgb(255, 255, 255), format!("{length} ")).origin_e().full_w()) east(button_2("t", "ime ", false),
.origin_w().full_w(), fg(Rgb(255, 255, 255), format!("{length} /{time_zoom} +{time_pos} ")).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()) east(button_2("z", "lock ", false),
.origin_w().full_w(), fg(Rgb(255, 255, 255), format!("{time_lock}")).origin_e().full_w()).origin_w().full_w(),
).exact_w(20) 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(),
pub fn edit_status (&self) -> impl Draw<Tui> + '_ { ).exact_w(20)
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)
}
} }
impl Draw<Tui> for MidiEditor { impl Draw<Tui> for MidiEditor {
@ -660,28 +658,31 @@ impl<T> TracksView for T where T:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack 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: pub trait TracksView:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{ {
/// Spacing between tracks.
const TRACK_SPACING: usize = 0;
/// Iterate over tracks with their corresponding sizes. /// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let _editor_width = self.editor().as_ref().map(|e|e.size.w()); iter_tracks_with_sizes(self)
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
}
})
} }
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
@ -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,26 +851,28 @@ 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|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() { fn view_outputs_grid (&self) -> impl Draw<Tui> {
let _ = draw(|to: &mut Tui|{ draw(|to: &mut Tui|{
east(either(true, fg(Green, "play "), "play "), for (index, track, _x1, _x2) in self.tracks_with_sizes() {
either(false, fg(Yellow, "solo "), "solo ")).align_w().exact_h(1).draw(to)?; let _ = draw(|to: &mut Tui|{
for (_index, port) in self.midi_outs().iter().enumerate() { east(either(true, fg(Green, "play "), "play "),
east(either(true, fg(Green, " ● "), " · "), either(false, fg(Yellow, "solo "), "solo ")).align_w().exact_h(1).draw(to)?;
either(false, fg(Yellow, " ● "), " · ")).align_w().exact_h(1).draw(to)?; for (_index, port) in self.midi_outs().iter().enumerate() {
for (_index, _conn) in port.connections.iter().enumerate() { east(either(true, fg(Green, " ● "), " · "),
"".full_w().exact_h(1).draw(to)?; 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!()
} }).exact_w(track_width(index, track)).draw(to)?;
todo!() }
}).align_w().full_w())).exact_h(height) todo!()
})
} }
} }
@ -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,50 +896,33 @@ 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<'_> {
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 iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T:
fn w_mid (&self) -> u16 { HasScenes + HasSceneScroll + HasEditor + HasSelection + HasClipsSize + ?Sized
(self.size.w() as u16).saturating_sub(self.w_side()) {
} let mut y = 0;
fn w_side (&self) -> u16 { let skip = state.scene_scroll();
20 let selected = state.selection();
} let editor = state.editor();
fn h_scenes (&self) -> u16 { let clips_size = state.clips_size();
(self.size.h() as u16).saturating_sub(20) 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 { if y + height <= clips_size.h() as usize {
fn h_scenes (&self) -> u16 { let data = (s, scene, y, y + height);
(self.size.h() as u16).saturating_sub(20) y += height;
} Some(data)
fn w_side (&self) -> u16 { } else {
(self.size.w() as u16 * 2 / 10).max(20) None
} }
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 {
@ -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,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))) 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 (
/// buf: &mut BigBuffer,
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄ clip: &MidiClip,
pub fn draw_bg ( zoom: usize,
buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize, note_point: usize, time_point: usize note_len: usize,
) { note_point: usize,
for (y, note) in (0..=127).rev().enumerate() { time_point: usize
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { ) {
let cell = buf.get_mut(x, y).unwrap(); for (y, note) in (0..=127).rev().enumerate() {
if note == (127-note_point) || time == time_point { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
cell.set_bg(Rgb(0,0,0)); let cell = buf.get_mut(x, y).unwrap();
} else { if note == (127-note_point) || time == time_point {
cell.set_bg(clip.color.darkest.term); cell.set_bg(Rgb(0,0,0));
} } else {
if time % 384 == 0 { cell.set_bg(clip.color.darkest.term);
cell.set_fg(clip.color.darker.term); }
cell.set_char('│'); if time % 384 == 0 {
} else if time % 96 == 0 { cell.set_fg(clip.color.darker.term);
cell.set_fg(clip.color.dark.term); cell.set_char('│');
cell.set_char('╎'); } else if time % 96 == 0 {
} else if time % note_len == 0 { cell.set_fg(clip.color.dark.term);
cell.set_fg(clip.color.darker.term); cell.set_char('╎');
cell.set_char('┊'); } else if time % note_len == 0 {
} else if (127 - note) % 12 == 0 { cell.set_fg(clip.color.darker.term);
cell.set_fg(clip.color.darker.term); cell.set_char('┊');
cell.set_char('='); } else if (127 - note) % 12 == 0 {
} else if (127 - note) % 6 == 0 { cell.set_fg(clip.color.darker.term);
cell.set_fg(clip.color.darker.term); cell.set_char('=');
cell.set_char('—'); } else if (127 - note) % 6 == 0 {
} else { cell.set_fg(clip.color.darker.term);
cell.set_fg(clip.color.darker.term); cell.set_char('—');
cell.set_char('·'); } else {
} cell.set_fg(clip.color.darker.term);
cell.set_char('·');
} }
} }
} }
}
/// 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,
let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0)); zoom: usize
let mut notes_on = [false;128]; ) {
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() { let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0));
for (_y, note) in (0..=127).rev().enumerate() { let mut notes_on = [false;128];
if let Some(cell) = buf.get_mut(x, note) { for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
if notes_on[note] { for (_y, note) in (0..=127).rev().enumerate() {
cell.set_char('▂'); if let Some(cell) = buf.get_mut(x, note) {
cell.set_style(style); 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) { let time_end = time_start + zoom;
for event in clip.notes[time].iter() { 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 { match event {
MidiMessage::NoteOn { key, .. } => { MidiMessage::NoteOn { key, .. } => {
let note = key.as_int() as usize; let note = key.as_int() as usize;