mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
piano keys reappear
This commit is contained in:
parent
9db60c3429
commit
9a50ce6613
15 changed files with 856 additions and 782 deletions
224
src/midi_edit.rs
224
src/midi_edit.rs
|
|
@ -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());
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
|
||||||
});
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
234
src/proj.rs
234
src/proj.rs
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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>),
|
|
||||||
}
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
src/tek.edn
13
src/tek.edn
|
|
@ -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))
|
||||||
|
|
|
||||||
151
src/tek.rs
151
src/tek.rs
|
|
@ -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
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
307
src/tek_view.rs
307
src/tek_view.rs
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue