wip: more shrunkening
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
i do not exist 2026-09-21 01:44:52 +03:00
parent 186b81f29c
commit ca774cca41
21 changed files with 2160 additions and 2191 deletions

View file

@ -1,340 +0,0 @@
use crate::*;
/// Collection of custom view definitions.
#[derive(Default, Clone, Debug)]
pub struct Views(pub Arc<RwLock<BTreeMap<
Arc<str>,
Arc<View<Tui, App>>
>>>);
impl Views {
/// Load custom view definition.
pub fn add <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
views.0.write().insert(
expr.head()?.ok_or("view: missing name")?.into(),
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
);
Ok(())
}
pub fn clear (&self) {
*self.0.try_write().unwrap() = Default::default();
}
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
self.0.try_read().map(|views|views.get(name.as_ref()).cloned()).flatten()
}
pub fn for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->T) -> () {
for (k, v) in self.0.read().iter() {
cb(k, v);
}
}
}
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
pub struct View<S: Screen, T> {
pub source: Arc<str>,
pub render: Arc<Box<dyn Fn(&T, &mut S)->Drawn<S::Unit> + Send + Sync>>
}
impl_debug!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
impl_display!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
impl View<Tui, App> {
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
Ok(Self {
source: source.as_ref().into(),
render: Self::compile(source)?
})
}
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
{
Box::new(f)
}
fn compile (source: impl AsRef<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
let source = source.as_ref();
let layer = if let Ok(Some(expr)) = source.expr() {
Self::compile_expr(expr.into())?
} else if let Ok(Some(word)) = source.word() {
Self::compile_word(word.into())?
} else {
return err!("not word/expr:\n{source:?}")
};
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
}
fn compile_expr (expr: Arc<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
macro_rules! arg {
($src:expr, $head:expr, $index:expr, $name:expr) => {
$src.nth($index)?
.ok_or_else(||Box::<dyn Error>::from(format!(
"{}: no arg #{} ({}) in {}", $head, $index, $name, $src
)))?
};
}
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
match ns {
"when" => {
let cond = Arc::from(arg!(expr, head, 1, "condition"));
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
Self::boxed(move|state, screen|{
when(
cond(state)?,
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"either" => {
let cond = Arc::from(arg!(expr, head, 1, "condition"));
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
Self::boxed(move|state, screen|{
either(
cond(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen)
})
},
"bsp" | "split" | "stack" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return err!("invalid split: {split:?}")
};
let a = Self::compile(arg!(expr, head, 1, "content A"))?;
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
Self::boxed(move|state, screen|split.stack(
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen))
},
"bar" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return err!("invalid split: {split:?}")
};
let size = Arc::from(arg!(expr, head, 1, "size"));
let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::<dyn Error>::from("bar: no size"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
Self::boxed(move|state, screen|split.bar(
size(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen))
},
"align" => {
let azimuth = head.split('/').skip(1).next();
let azimuth = match azimuth {
Some("n") => Azimuth::N,
Some("s") => Azimuth::S,
Some("e") => Azimuth::E,
Some("w") => Azimuth::W,
Some("ne") => Azimuth::NE,
Some("se") => Azimuth::SE,
Some("nw") => Azimuth::NW,
Some("sw") => Azimuth::SW,
Some("c") => Azimuth::C,
Some("x") => Azimuth::X,
Some("y") => Azimuth::Y,
_ => return err!("invalid azimuth: {azimuth:?}")
};
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
Self::boxed(move|state, screen|{
Align(
Some(azimuth),
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"full" | "fill" => {
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
match head.split('/').skip(1).next() {
Some("w") | Some("x") => Self::boxed(move|state, screen|{
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("h") | Some("y") => Self::boxed(move|state, screen|{
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
}),
_ => unreachable!()
}
},
"exact" | "min" | "max" | "push" | "pull" | "pad" => {
match head.split('/').skip(1).next() {
Some("w") | Some("x") => {
let value = Arc::from(arg!(expr, head, 1, "value"));
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
_ => unreachable!()
}
},
Some("h") | Some("y") => {
let value = Arc::from(arg!(expr, head, 1, "value"));
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
_ => unreachable!()
}
},
Some("wh") | Some("xy") | None => {
let value1 = Arc::from(arg!(expr, head, 1, "value"));
let value1 = move|state: &App|state.namespace(&value1);
let value2 = Arc::from(arg!(expr, head, 2, "value"));
let value2 = move|state: &App|state.namespace(&value2);
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
match ns {
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
_ => unreachable!()
}
},
_ => unreachable!()
}
},
"fg" | "bg" => {
let color = Arc::from(arg!(expr, head, 1, "color"));
let color = move|state: &App|state.namespace(&color);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"fg" => Self::boxed(move|state, screen|fg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
"bg" => Self::boxed(move|state, screen|bg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
_ => unreachable!()
}
},
"text" => {
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
Self::boxed(move|_state, screen|{
text.draw(screen)
})
},
_ => return err!("compile_expr: unexpected: {expr:?}")
}
} else {
return err!("compile_expr: invalid expression: {expr:?}")
}))
}
fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
macro_rules! draw {
(|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))};
($const:expr)=>{Self::boxed(move|_, to|$const.draw(to))};
()=>{Self::boxed(move|_, __|Ok(None))};
}
Ok(Arc::new(match word.split("/").next() {
Some("Beat") => draw!(),
Some("Bpm") => draw!(),
Some("BrowseTitle") => draw!(|app|view_browse_title(app)),
Some("Debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some("Device") => draw!(|app|view_device(app)),
Some("Menu") => draw!(|app|{
if let Dialog::Menu(selected, items) = &app.dialog {
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{
let f = if *selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) };
let b = if *selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) };
fg_bg(f, b, item).exact_h(2).align_x()
})).exact_w(20).align_x())
} else {
None
}
}),
Some("Editor") => draw!("TODO editor"),
Some("MetersIn") => draw!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
Some("MetersOut") => draw!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
Some("ModeList") => draw!(|app|app.modes.read().get(0)), // TODO all
Some("ScenesClips") => draw!(|app|app.view_scenes_clips()),
Some("ScenesNames") => draw!(|app|app.view_scenes_names()),
Some("Sessions") => draw!(|___|view_sessions()),
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!()
}))
}
}

View file

@ -41,6 +41,8 @@ pub(crate) use ::{
RwLock, RwLock,
RwLockReadGuard, RwLockReadGuard,
RwLockWriteGuard, RwLockWriteGuard,
ArcRwLockReadGuard,
ArcRwLockWriteGuard,
RawRwLock, RawRwLock,
} }
}, },

View file

@ -1,18 +1,5 @@
use crate::*; use crate::*;
pub type Clips = Vec<Arc<RwLock<MidiClip>>>;
pub trait HasClips {
/// Active clip slot (immutable). Use this to access or modify current clip.
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
/// Active clip slot (mutable). Use this to place or remove a clip at current position.
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>>;
/// Clip list (immutable). Use this to access or modify more than one clip.
fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips>;
/// Clip list (mutable). Use this to define or delete new clips.
fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips>;
}
/// A MIDI sequence. /// A MIDI sequence.
/// ///
/// ``` /// ```
@ -130,88 +117,3 @@ const MIDI_PANIC: MidiMessage = MidiMessage::Controller {
pub type MidiData = Vec<Vec<MidiMessage>>; pub type MidiData = Vec<Vec<MidiMessage>>;
impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
impl Eq for MidiClip {} impl Eq for MidiClip {}
impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } }
impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } }
pub trait HasClipsSize {
/// Size of clips area.
fn clips_size (&self) -> &Sizer;
/// Draw content annotated with value of [Self::clips_size].
fn with_clips_size (&self, show: bool, content: impl Draw<Tui>) -> impl Draw<Tui> {
let size = self.clips_size();
above(
when(show, fg(Green, east!(size.w() as usize, "x", size.h() as usize)).align_se()),
size.of(content.align_c().full_wh())
)
}
}
impl<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
pub trait ClipsView: TracksView + ScenesView {
/// Draw clips per scene
fn view_scenes_clips (&self) -> impl Draw<Tui> {
let select = self.selection();
let editor = self.editor();
let editing = self.is_editing();
self.with_clips_size(true, iter_east(move||self.tracks_with_sizes()
.map(move|(track_index, track, _, _)|iter_south(move||self.scenes_with_sizes()
.map(move|(scene_index, scene, _, _)|{
let (name, theme): (Arc<str>, ItemTheme) = view_scene_name_theme(scene, track_index);
let f = theme.lightest.term;
let (b, o) = view_scene_bg(theme, select, track_index, scene_index);
let is_selected = view_scene_sel(select, track_index, scene_index, editing);
below(
Outer(true, Style::default().fg(o)).full_wh(),
below(
below(
fg_bg(o, b, "".full_wh()),
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
),
when(is_selected, editor).full_wh()
).full_wh()
).exact_wh(
view_scene_w(track, select, track_index, editor.as_ref()),
view_scene_y(select, scene_index, editor.as_ref()),
)
})
)
.exact_w(track.width as u16))
)).align_c()
}
}
pub fn get_opt_arc_rwlock_midiclip <'a> (state: &App, src: impl Language)
-> Perhaps<Option<Arc<RwLock<MidiClip>>>>
{
eval_word(src, |word|Ok(match word {
":selected" if let Selection::TrackClip {
track, scene
} = state.selection() => {
state.scenes()[*scene].clips[*track].clone()
},
//":selected-or-create" if let Selection::TrackClip {
//track, scene
//} = state.selection() => Some(if let Some(Some(clip)) = state.scenes()[*scene].clips.get(*track) {
//clip.clone()
//} else {
//let clip: Arc<RwLock<MidiClip>> = Arc::new(RwLock::new(Default::default()));
//state.scenes()[*scene].clips[*track] = Some(clip.clone());
//clip
//}),
_ => return err!("not a clip: {word}")
}))
}
pub fn get_opt_vec_opt_arc_rwlock_midiclip <'a> (_: &App, _: impl Language)
-> Perhaps<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
{
todo!()
}

View file

@ -26,9 +26,10 @@ impl<T> HasEditor for T where T: AsRef<Option<MidiEditor>> + AsMut<Option<MidiEd
/// ``` /// ```
/// use std::sync::Arc; /// use std::sync::Arc;
/// use parking_lot::RwLock; /// use parking_lot::RwLock;
/// let clip = tek::MidiClip::stop_all(); /// use tek::{MidiClip, MidiEditor, PianoHorizontal};
/// let mode = tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))); /// let clip = MidiClip::stop_all();
/// let mut editor = tek::MidiEditor { mode, size: Default::default() }; /// let mode = PianoHorizontal::default();
/// let mut editor = MidiEditor { mode, size: Default::default() };
/// ///
/// let _ = editor.put_note(true); /// let _ = editor.put_note(true);
/// let _ = editor.put_note(false); /// let _ = editor.put_note(false);
@ -43,7 +44,7 @@ pub struct MidiEditor {
} }
impl_default!(MidiEditor: Self { impl_default!(MidiEditor: Self {
mode: PianoHorizontal::new(None), mode: Default::default(),
size: Sizer( size: Sizer(
Arc::new(0usize.into()), Arc::new(0usize.into()),
Arc::new(0usize.into()), Arc::new(0usize.into()),
@ -54,14 +55,14 @@ impl MidiEditor {
/// Put note at current position /// Put note at current position
pub fn put_note (&mut self, advance: bool) { pub fn put_note (&mut self, advance: bool) {
let mut redraw = false; let mut redraw = false;
if let Some(clip) = self.clip() { if let Some(clip) = &*self.mode.clip.read() {
let mut clip = clip.try_write().unwrap();
let note_start = self.get_time_pos(); let note_start = self.get_time_pos();
let note_pos = self.get_note_pos(); let note_pos = self.get_note_pos();
let note_len = self.get_note_len(); let note_len = self.get_note_len();
let note_end = note_start + (note_len.saturating_sub(1)); let note_end = note_start + (note_len.saturating_sub(1));
let key: u7 = u7::from(note_pos as u8); let key: u7 = u7::from(note_pos as u8);
let vel: u7 = 100.into(); let vel: u7 = 100.into();
let mut clip = clip.write();
let length = clip.length; let length = clip.length;
let note_end = note_end % length; let note_end = note_end % length;
let note_on = MidiMessage::NoteOn { key, vel }; let note_on = MidiMessage::NoteOn { key, vel };
@ -81,21 +82,51 @@ impl MidiEditor {
self.mode.redraw(); self.mode.redraw();
} }
} }
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1) } fn clip_length (&self) -> usize {
fn note_length (&self) -> usize { self.get_note_len() } self.mode.clip.map_inner(|p|p.length).unwrap_or(1)
fn note_pos (&self) -> usize { self.get_note_pos() } }
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 } fn note_length (&self) -> usize {
fn note_pos_next_octave (&self) -> usize { self.get_note_pos() + 12 } self.get_note_len()
fn note_pos_prev (&self) -> usize { self.get_note_pos().saturating_sub(1) } }
fn note_pos_prev_octave (&self) -> usize { self.get_note_pos().saturating_sub(12) } fn note_pos (&self) -> usize {
fn note_len (&self) -> usize { self.get_note_len() } self.get_note_pos()
fn note_len_next (&self) -> usize { self.get_note_len() + 1 } }
fn note_len_prev (&self) -> usize { self.get_note_len().saturating_sub(1) } fn note_pos_next (&self) -> usize {
fn note_range (&self) -> usize { self.get_note_axis() } self.get_note_pos() + 1
fn note_range_next (&self) -> usize { self.get_note_axis() + 1 } }
fn note_range_prev (&self) -> usize { self.get_note_axis().saturating_sub(1) } fn note_pos_next_octave (&self) -> usize {
fn time_zoom (&self) -> usize { self.get_time_zoom() } self.get_note_pos() + 12
fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 } }
fn note_pos_prev (&self) -> usize {
self.get_note_pos().saturating_sub(1)
}
fn note_pos_prev_octave (&self) -> usize {
self.get_note_pos().saturating_sub(12)
}
fn note_len (&self) -> usize {
self.get_note_len()
}
fn note_len_next (&self) -> usize {
self.get_note_len() + 1
}
fn note_len_prev (&self) -> usize {
self.get_note_len().saturating_sub(1)
}
fn note_range (&self) -> usize {
self.get_note_axis()
}
fn note_range_next (&self) -> usize {
self.get_note_axis() + 1
}
fn note_range_prev (&self) -> usize {
self.get_note_axis().saturating_sub(1)
}
fn time_zoom (&self) -> usize {
self.get_time_zoom()
}
fn time_zoom_next (&self) -> usize {
self.get_time_zoom() + 1
}
fn time_zoom_next_fine (&self) -> usize { self.get_time_zoom() + 1 } fn time_zoom_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) }
@ -113,39 +144,6 @@ impl MidiEditor {
self.get_time_pos().overflowing_sub(1) self.get_time_pos().overflowing_sub(1)
.0.min(self.clip_length().saturating_sub(1)) .0.min(self.clip_length().saturating_sub(1))
} }
pub fn clip_status (&self) -> impl Draw<Tui> + '_ {
let (_color, name, length, looped) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
(clip.color, clip.name.clone(), clip.length, clip.looped)
} else { (ItemTheme::G[64], String::new().into(), 0, false) };
south!(
east(button_2("f2", "name ", false), fg(Rgb(255, 255, 255), format!("{name} "))
.origin_e().full_w()).origin_w().full_w(),
east(button_2("l", "ength ", false), fg(Rgb(255, 255, 255), format!("{length} ")).origin_e().full_w())
.origin_w().full_w(),
east(button_2("r", "epeat ", false), fg(Rgb(255, 255, 255), format!("{looped} ")).origin_e().full_w())
.origin_w().full_w(),
).exact_w(20)
}
pub fn edit_status (&self) -> impl Draw<Tui> + '_ {
let (_color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.try_read().unwrap()) {
(clip.color, clip.length)
} else { (ItemTheme::G[64], 0) };
let time_pos = self.get_time_pos();
let time_zoom = self.get_time_zoom();
let time_lock = if self.get_time_lock() { "[lock]" } else { " " };
let note_pos = self.get_note_pos();
let note_name = format!("{:4}", note_pitch_to_name(note_pos));
let note_pos = format!("{:>3}", note_pos);
let note_len = format!("{:>4}", self.get_note_len());
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)
}
} }
/// ``` /// ```
@ -170,12 +168,6 @@ 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);
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>>;
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
*self.clip_mut() = clip.cloned();
self.redraw();
}
/// Make sure cursor is within note range /// 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);
@ -240,19 +232,6 @@ impl std::fmt::Debug for MidiEditor {
} }
} }
impl_from!(MidiEditor: |clip: &Arc<RwLock<MidiClip>>| {
let model = Self::from(Some(clip.clone()));
model.redraw();
model
});
impl_from!(MidiEditor: |clip: Option<Arc<RwLock<MidiClip>>>| {
let mut model = Self::default();
*model.clip_mut() = clip;
model.redraw();
model
});
impl NotePoint for MidiEditor { impl NotePoint for MidiEditor {
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() } fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() } fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
@ -263,31 +242,15 @@ impl TimePoint for MidiEditor {
} }
impl MidiViewer for MidiEditor { impl MidiViewer for MidiEditor {
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) } fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
fn redraw (&self) { self.mode.redraw() } self.mode.buffer_size(clip)
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { self.mode.clip() }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { self.mode.clip_mut() }
fn set_clip (&mut self, p: Option<&Arc<RwLock<MidiClip>>>) { self.mode.set_clip(p) }
} }
fn redraw (&self) {
impl Draw<Tui> for MidiEditor { self.mode.redraw()
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
self.autoscroll();
/*self.autozoom();*/
self.size.of(&self.mode).draw(to)
} }
} }
impl App { impl App {
/// Is a MIDI editor currently focused?
///
/// ```
/// tek::App::default().editor_focused();
/// ```
pub fn editor_focused (&self) -> bool {
false
}
/// Toggle MIDI editor. /// Toggle MIDI editor.
/// ///
/// ``` /// ```
@ -295,39 +258,50 @@ impl App {
/// ``` /// ```
pub fn toggle_editor (&mut self, value: Option<bool>) { pub fn toggle_editor (&mut self, value: Option<bool>) {
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed); //FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
let value = value.unwrap_or_else(||!self.editor().is_some()); if value.unwrap_or_else(||!self.editor().is_some()) {
if value { self.toggle_editor_on()
// Create new clip in pool when entering empty cell } else {
if let Selection::TrackClip { track, scene } = *self.selection() self.toggle_editor_off()
&& let Some(scene) = self.project.scenes.get_mut(scene)
&& let Some(slot) = scene.clips.get_mut(track)
&& slot.is_none()
&& let Some(track) = self.project.tracks.get_mut(track)
{
let (_index, clip) = self.project.pool.add_new_clip();
// autocolor: new clip colors from scene and track color
let color = track.color.base.mix(scene.color.base, 0.5);
clip.try_write().unwrap().color = ItemColor::random_near(color, 0.2).into();
if let Some(editor) = &mut self.project.editor {
editor.set_clip(Some(&clip));
} }
*slot = Some(clip.clone()); }
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) //Some(clip)
} else { } else {
//None //None
} }
} else if let Selection::TrackClip { track, scene } = *self.selection() }
fn toggle_editor_off (&mut self) {
if let Selection::TrackClip { track, scene } = *self.selection()
&& let Some(scene) = self.project.scenes.get_mut(scene) && let Some(scene) = self.project.scenes.get_mut(scene)
&& let Some(slot) = scene.clips.get_mut(track)
&& let Some(clip) = slot.as_mut()
{ {
// Remove clip from arrangement when exiting empty clip editor // Remove clip from arrangement when exiting empty clip editor
let mut swapped = None; let mut swapped = None;
if clip.try_read().unwrap().count_midi_messages() == 0 { if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages())
std::mem::swap(&mut swapped, slot); && count == 0
{
std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write());
} }
if let Some(clip) = swapped { if let Some(clip) = swapped {
self.project.pool.delete_clip(&clip.try_read().unwrap()); self.project.clips.remove(&*clip.read());
} }
} }
} }

View file

@ -12,25 +12,12 @@ pub struct OctaveVertical {
} }
impl OctaveVertical { impl OctaveVertical {
fn color (&self, pitch: usize) -> Color { pub fn color (&self, pitch: usize) -> Color {
let pitch = pitch % 12; let pitch = pitch % 12;
self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }] self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }]
} }
} }
impl OctaveVertical {
pub fn tui (&self) -> impl Draw<Tui> {
east!(
fg_bg(self.color(0), self.color(1), "▙"),
fg_bg(self.color(2), self.color(3), "▙"),
fg_bg(self.color(4), self.color(5), "▌"),
fg_bg(self.color(6), self.color(7), "▟"),
fg_bg(self.color(8), self.color(9), "▟"),
fg_bg(self.color(10), self.color(11), "▟"),
)
}
}
impl_default!(OctaveVertical: Self { impl_default!(OctaveVertical: Self {
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
}); });

View file

@ -5,9 +5,8 @@ use crate::*;
/// ``` /// ```
/// let piano = tek::PianoHorizontal::default(); /// let piano = tek::PianoHorizontal::default();
/// ``` /// ```
#[derive(Clone, Default)] #[derive(Clone)] pub struct PianoHorizontal {
pub struct PianoHorizontal { pub clip: ClipSlot,
pub clip: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole clip is rerendered on change /// Buffer where the whole clip is rerendered on change
pub buffer: Arc<RwLock<BigBuffer>>, pub buffer: Arc<RwLock<BigBuffer>>,
/// Size of actual notes area /// Size of actual notes area
@ -24,226 +23,16 @@ pub struct PianoHorizontal {
impl_has!(Sizer: |self: PianoHorizontal| self.size); impl_has!(Sizer: |self: PianoHorizontal| self.size);
impl Draw<Tui> for PianoHorizontal { impl Default for PianoHorizontal {
fn draw (&self, to: &mut Tui) -> Drawn<u16> { fn default () -> Self {
south(
east(
format!("{}x{}", self.size.w(), self.size.h()).exact_w(5),
self.timeline()
),
east(
self.keys(),
self.size.of(below(self.notes().full_wh(), self.cursor().full_wh()))
),
).draw(to)
}
}
impl PianoHorizontal {
pub fn new (clip: Option<&Arc<RwLock<MidiClip>>>) -> Self {
let size = Sizer::default(); let size = Sizer::default();
let mut range = MidiSelection::from((12, true)); let mut range = MidiSelection::from((12, true));
range.time_axis = size.0.clone(); range.time_axis = size.0.clone();
range.note_axis = size.1.clone(); range.note_axis = size.1.clone();
let piano = Self { let piano = Self { keys_width: 5, size, range, ..Default::default() };
keys_width: 5,
size,
range,
buffer: RwLock::new(Default::default()).into(),
point: MidiCursor::default(),
clip: clip.cloned(),
color: clip.as_ref().map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]),
};
piano.redraw(); piano.redraw();
piano piano
} }
/// Draw the piano roll background.
///
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_bg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize, note_len: usize, note_point: usize, time_point: usize) {
for (y, note) in (0..=127).rev().enumerate() {
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
let cell = buf.get_mut(x, y).unwrap();
if note == (127-note_point) || time == time_point {
cell.set_bg(Rgb(0,0,0));
} else {
cell.set_bg(clip.color.darkest.term);
}
if time % 384 == 0 {
cell.set_fg(clip.color.darker.term);
cell.set_char('│');
} else if time % 96 == 0 {
cell.set_fg(clip.color.dark.term);
cell.set_char('╎');
} else if time % note_len == 0 {
cell.set_fg(clip.color.darker.term);
cell.set_char('┊');
} else if (127 - note) % 12 == 0 {
cell.set_fg(clip.color.darker.term);
cell.set_char('=');
} else if (127 - note) % 6 == 0 {
cell.set_fg(clip.color.darker.term);
cell.set_char('—');
} else {
cell.set_fg(clip.color.darker.term);
cell.set_char('·');
}
}
}
}
/// Draw the piano roll foreground.
///
/// This mode uses full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_fg (buf: &mut BigBuffer, clip: &MidiClip, zoom: usize) {
let style = Style::default().fg(clip.color.base.term);//.bg(Rgb(0, 0, 0));
let mut notes_on = [false;128];
for (x, time_start) in (0..clip.length).step_by(zoom).enumerate() {
for (_y, note) in (0..=127).rev().enumerate() {
if let Some(cell) = buf.get_mut(x, note) {
if notes_on[note] {
cell.set_char('▂');
cell.set_style(style);
}
}
}
let time_end = time_start + zoom;
for time in time_start..time_end.min(clip.length) {
for event in clip.notes[time].iter() {
match event {
MidiMessage::NoteOn { key, .. } => {
let note = key.as_int() as usize;
if let Some(cell) = buf.get_mut(x, note) {
cell.set_char('█');
cell.set_style(style);
}
notes_on[note] = true
},
MidiMessage::NoteOff { key, .. } => {
notes_on[key.as_int() as usize] = false
},
_ => {}
}
}
}
}
}
fn notes (&self) -> impl Draw<Tui> {
let time_start = self.get_time_start();
let note_lo = self.get_note_lo();
let note_hi = self.get_note_hi();
let buffer = self.buffer.clone();
draw(move|to: &mut Tui|{
let xywh = to.area().into();
to.buffer().map(|to|{
let XYWH(x0, y0, w, _h) = xywh;
let source = buffer.try_read().unwrap();
//if h as usize != note_axis {
//panic!("area height mismatch: {h} <> {note_axis}");
//}
for (area_x, screen_x) in (x0..x0+w).enumerate() {
for (area_y, screen_y, _note) in note_y_iter(note_lo, note_hi, y0) {
let source_x = time_start + area_x;
let source_y = note_hi - area_y;
// TODO: enable loop rollover:
//let source_x = (time_start + area_x) % source.width.max(1);
//let source_y = (note_hi - area_y) % source.height.max(1);
let is_in_x = source_x < source.width;
let is_in_y = source_y < source.height;
if is_in_x && is_in_y {
if let Some(source_cell) = source.get(source_x, source_y) {
if let Some(cell) = to.cell_mut(ratatui::prelude::Position::from((screen_x, screen_y))) {
*cell = source_cell.clone();
}
}
}
}
}
});
Ok(Some(xywh))
})
}
fn cursor (&self) -> impl Draw<Tui> {
let note_hi = self.get_note_hi();
let note_lo = self.get_note_lo();
let note_pos = self.get_note_pos();
let note_len = self.get_note_len();
let time_pos = self.get_time_pos();
let time_start = self.get_time_start();
let time_zoom = self.get_time_zoom();
let style = Some(Style::default().fg(self.color.lightest.term));
draw(move|to: &mut Tui|{
let xywh = to.area().into();
let XYWH(x0, y0, w, _h) = xywh;
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
if note == note_pos {
for x in 0..w {
let screen_x = x0 + x;
let time_1 = time_start + x as usize * time_zoom;
let time_2 = time_1 + time_zoom;
if time_1 <= time_pos && time_pos < time_2 {
to.blit(&"█", screen_x, screen_y, style);
let tail = note_len as u16 / time_zoom as u16;
for x_tail in (screen_x + 1)..(screen_x + tail) {
to.blit(&"▂", x_tail, screen_y, style);
}
break
}
}
break
}
}
Ok(Some(xywh))
})
}
fn keys (&self) -> impl Draw<Tui> {
let state = self;
let color = state.color;
let note_lo = state.get_note_lo();
let note_hi = state.get_note_hi();
let note_pos = state.get_note_pos();
let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0)));
let off_style = Some(Style::default().fg(g(255)));
let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.term).bold());
draw(move|to: &mut Tui|{
let xywh = to.area().into();
let XYWH(x, y0, _w, _h) = xywh;
for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
to.blit(&to_key(note), x, screen_y, key_style);
if note > 127 {
continue
}
if note == note_pos {
to.blit(&format!("{:<5}", note_pitch_to_name(note)), x, screen_y, on_style)
} else {
to.blit(&note_pitch_to_name(note), x, screen_y, off_style)
};
}
Ok(Some(xywh))
}).exact_w(self.keys_width).full_h()
}
fn timeline (&self) -> impl Draw<Tui> + '_ {
draw(move|to: &mut Tui|{
let xywh = to.area().into();
let XYWH(x, y, w, _h) = xywh;
let style = Some(Style::default().dim());
let length = self.clip.as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1);
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
let t = area_x as usize * self.time_zoom().load(Relaxed);
if t < length {
to.blit(&"|", screen_x, y, style);
}
}
Ok(Some(xywh))
}).exact_h(1).full_w()
}
} }
impl TimeRange for PianoHorizontal { impl TimeRange for PianoHorizontal {
@ -269,15 +58,12 @@ impl TimePoint for PianoHorizontal {
} }
impl MidiViewer for PianoHorizontal { impl MidiViewer for PianoHorizontal {
fn clip (&self) -> &Option<Arc<RwLock<MidiClip>>> { &self.clip }
fn clip_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> { &mut self.clip }
/// Determine the required space to render the clip. /// Determine the required space to render the clip.
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
(clip.length / self.range.time_zoom().load(Relaxed), 128) (clip.length / self.range.time_zoom().load(Relaxed), 128)
} }
fn redraw (&self) { fn redraw (&self) {
*self.buffer.try_write().unwrap() = if let Some(clip) = self.clip.as_ref() { *self.buffer.write() = self.clip.map_inner(|clip|{
let clip = clip.try_read().unwrap();
let buf_size = self.buffer_size(&clip); let buf_size = self.buffer_size(&clip);
let mut buffer = BigBuffer::from(buf_size); let mut buffer = BigBuffer::from(buf_size);
let time_zoom = self.get_time_zoom(); let time_zoom = self.get_time_zoom();
@ -285,20 +71,13 @@ impl MidiViewer for PianoHorizontal {
PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom,self.get_note_len(), self.get_note_pos(), self.get_time_pos()); 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); PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom);
buffer buffer
} else { }).unwrap_or_default()
Default::default()
}
}
fn set_clip (&mut self, clip: Option<&Arc<RwLock<MidiClip>>>) {
*self.clip_mut() = clip.cloned();
self.color = clip.map(|p|p.try_read().unwrap().color).unwrap_or(ItemTheme::G[64]);
self.redraw();
} }
} }
impl std::fmt::Debug for PianoHorizontal { impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
let buffer = self.buffer.try_read().unwrap(); let buffer = self.buffer.read();
f.debug_struct("PianoHorizontal") f.debug_struct("PianoHorizontal")
.field("time_zoom", &self.range.time_zoom) .field("time_zoom", &self.range.time_zoom)
.field("buffer", &format!("{}x{}", buffer.width, buffer.height)) .field("buffer", &format!("{}x{}", buffer.width, buffer.height))

View file

@ -19,7 +19,7 @@ pub trait HasPlayClip: HasClock {
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> { fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
if let Some((started, Some(clip))) = self.play_clip().as_ref() { if let Some((started, Some(clip))) = self.play_clip().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = clip.try_read().unwrap().length.max(1); // prevent div0 on empty clip let length = clip.read().length.max(1); // prevent div0 on empty clip
let times = (elapsed as usize / length) as f64; let times = (elapsed as usize / length) as f64;
let elapsed = (elapsed as usize % length) as f64; let elapsed = (elapsed as usize % length) as f64;
return Some((times, elapsed)) return Some((times, elapsed))
@ -34,7 +34,7 @@ pub trait HasPlayClip: HasClock {
fn play_status (&self) -> impl Draw<Tui> { fn play_status (&self) -> impl Draw<Tui> {
let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() { let (name, color): (Arc<str>, ItemTheme) = if let Some((_, Some(clip))) = self.play_clip() {
let MidiClip { ref name, color, .. } = *clip.try_read().unwrap(); let MidiClip { ref name, color, .. } = *clip.read();
(name.clone(), color) (name.clone(), color)
} else { } else {
("".into(), ItemTheme::G[64].into()) ("".into(), ItemTheme::G[64].into())
@ -55,7 +55,7 @@ pub trait HasPlayClip: HasClock {
let mut color = ItemTheme::G[64]; let mut color = ItemTheme::G[64];
let clock = self.clock(); let clock = self.clock();
if let Some((t, Some(clip))) = self.next_clip() { if let Some((t, Some(clip))) = self.next_clip() {
let clip = clip.try_read().unwrap(); let clip = clip.read();
name = clip.name.clone(); name = clip.name.clone();
color = clip.color.clone(); color = clip.color.clone();
time = { time = {
@ -69,7 +69,7 @@ pub trait HasPlayClip: HasClock {
} }
}.into() }.into()
} else if let Some((t, Some(clip))) = self.play_clip() { } else if let Some((t, Some(clip))) = self.play_clip() {
let clip = clip.try_read().unwrap(); let clip = clip.read();
if clip.looped { if clip.looped {
name = clip.name.clone(); name = clip.name.clone();
color = clip.color.clone(); color = clip.color.clone();

View file

@ -3,14 +3,12 @@ impl_as_ref!(Option<Project>: |self: App|todo!());
impl_as_mut!(Option<Project>: |self: App|todo!()); impl_as_mut!(Option<Project>: |self: App|todo!());
pub trait HasProject: AsRef<Option<Project>> + AsMut<Option<Project>> {} pub trait HasProject: AsRef<Option<Project>> + AsMut<Option<Project>> {}
impl<T: AsRef<Option<Project>> + AsMut<Option<Project>>> HasProject for T {} impl<T: AsRef<Option<Project>> + AsMut<Option<Project>>> HasProject for T {}
/// Arranger. /// Arranger.
/// ///
/// ``` /// ```
/// let arranger = tek::Project::default(); /// let arranger = tek::Project::default();
/// ``` /// ```
#[derive(Default, Debug)] #[derive(Default, Debug)] pub struct Project {
pub struct Project {
/// JACK client handle. /// JACK client handle.
pub jack: Jack<'static>, pub jack: Jack<'static>,
/// Project name. /// Project name.
@ -27,7 +25,9 @@ pub struct Project {
/// Selected UI element /// Selected UI element
pub selection: Selection, pub selection: Selection,
/// Contains all recently created clips. /// Contains all recently created clips.
pub pool: Pool, #[cfg(feature = "sequencer")] pub clips: Pool<MidiClip>,
/// Contains all recently created clips.
#[cfg(feature = "sampler")] pub samples: Pool<Sample>,
/// Source of time /// Source of time
#[cfg(feature = "clock")] pub clock: Clock, #[cfg(feature = "clock")] pub clock: Clock,
/// Allows one MIDI clip to be edited /// Allows one MIDI clip to be edited
@ -51,9 +51,7 @@ pub struct Project {
/// 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 (
jack: &Jack<'static>, jack: &Jack<'static>,
@ -109,7 +107,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);
@ -119,16 +117,21 @@ 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 { //impl HasClip for Project {
fn jack (&self) -> &Jack<'static> { &self.jack } //fn clip (&self) -> Option<RwLockReadGuard<'_, Option<Arc<RwLock<MidiClip>>>>> {
} //match self.selection {
impl HasClips for Project { //Selection::Pool(index) =>
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> { todo!() } //self.clips.read(index),
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>> { todo!() } //Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) =>
fn clips (&self) -> RwLockReadGuard<'_, Clips> { todo!() } //scene.clips.read(track),
fn clips_mut (&self) -> RwLockWriteGuard<'_, Clips> { todo!() } //_ =>
} //None
//}
//}
//fn clip_mut (&self) -> RwLockWriteGuard<'_, Option<Arc<RwLock<MidiClip>>>> {
//self.as_ref().0.write()
//}
//}
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> {
@ -140,7 +143,6 @@ 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> {
@ -152,3 +154,238 @@ pub fn toggle_bool <U> (
Ok(Some(returned(Some(value)))) Ok(Some(returned(Some(value))))
} }
} }
#[derive(Default, Debug)] pub struct Slot<T>(pub Arc<RwLock<Option<Arc<RwLock<T>>>>>);
pub type SampleSlot = Slot<Sample>;
pub type ClipSlot = Slot<MidiClip>;
impl<T> Clone for Slot<T> { fn clone (&self) -> Self { Slot(self.0.clone()) } }
impl<T> Slot<T> {
pub fn new (value: impl Into<Option<T>>) -> Self {
Self(Arc::new(RwLock::new(value.into().map(|value|Arc::new(RwLock::new(value))))))
}
pub fn read (&self) -> ArcRwLockReadGuard<RawRwLock, Option<Arc<RwLock<T>>>> {
self.0.read_arc()
}
pub fn write (&self) -> ArcRwLockWriteGuard<RawRwLock, Option<Arc<RwLock<T>>>> {
self.0.write_arc()
}
pub fn set_to (&mut self, value: Self) {
self.0 = value.0;
}
pub fn set (&self, value: Option<T>) -> Option<Arc<RwLock<T>>> {
let mut value = value.map(|value|Arc::new(RwLock::new(value)));
std::mem::swap(&mut*self.0.write(), &mut value);
value
}
pub fn map_inner <U> (&self, cb: impl FnOnce(&T)->U) -> Option<U> {
self.read().as_ref().map(|x|cb(&*x.read()))
}
}
impl Slot<MidiClip> {
pub fn eval <'a> (state: &App, src: impl Language) -> Perhaps<ClipSlot> {
eval_word(src, |word|Ok(match word {
":selected" if let Selection::TrackClip { track, scene } = state.selection() => {
state.scenes()[*scene].clips.slot(*track)
},
//":selected-or-create" if let Selection::TrackClip {
//track, scene
//} = state.selection() => Some(if let Some(Some(clip)) = state.scenes()[*scene].clips.get(*track) {
//clip.clone()
//} else {
//let clip: Arc<RwLock<MidiClip>> = Arc::new(RwLock::new(Default::default()));
//state.scenes()[*scene].clips[*track] = Some(clip.clone());
//clip
//}),
_ => return err!("not a clip: {word}")
}))
}
}
impl Slot<Sample> {
pub fn eval <'a> (_: &App, _: impl Language) -> Perhaps<SampleSlot> {
todo!()
}
}
#[derive(Default, Debug)] pub struct Pool<T>(pub Arc<RwLock<Vec<Slot<T>>>>);
impl<T> Clone for Pool<T> { fn clone (&self) -> Self { Pool(self.0.clone()) } }
pub type SamplePool = Pool<Sample>;
pub type ClipPool = Pool<MidiClip>;
impl<T> Pool<T> {
pub fn slot (&self, index: usize) -> Slot<T> {
let mut state = self.0.write();
while index >= state.len() {
state.push(Slot::new(None));
}
state[index].clone()
}
pub fn delete (&self, index: usize) -> Option<Arc<RwLock<T>>> {
let mut none = None;
let mut slot = self.slot(index).write();
std::mem::swap(&mut none, &mut slot);
none
}
pub fn len (&self) -> usize {
self.0.read().len()
}
pub fn get (&self, index: usize) -> Option<ArcRwLockReadGuard<RawRwLock, Option<Arc<RwLock<T>>>>> {
self.0.read_arc().get(index).map(|clip|clip.read())
}
pub fn set (&self, index: usize, value: Option<T>) {
self.write()[index] = Slot::new(value);
}
pub fn read (&self) -> ArcRwLockReadGuard<RawRwLock, Vec<Slot<T>>> {
self.0.read_arc()
}
pub fn write (&self) -> ArcRwLockWriteGuard<RawRwLock, Vec<Slot<T>>> {
self.0.write_arc()
}
pub fn append (&self, value: Option<T>) -> Slot<T> {
let slot = Slot::new(value);
self.0.write().push(slot.clone());
slot
}
}
impl Pool<MidiClip> {
pub fn eval <'a> (_: &App, _: impl Language) -> Perhaps<ClipPool> {
todo!()
}
pub fn remove (&self, filter: &MidiClip) {
let iter = self.0.write();
iter.retain(|x|if let Some(x) = &*x.0.read() { x!=filter } else { true })
}
}
impl Pool<Sample> {
pub fn eval <'a> (_: &App, _: impl Language) -> Perhaps<SamplePool> {
todo!()
}
}
impl<T> From<Vec<Option<T>>> for Pool<T> {
fn from (values: Vec<Option<T>>) -> Self {
Self(Arc::new(RwLock::new(values.into_iter().map(Slot::new).collect())))
}
}
/// Modes for clip pool
#[derive(Debug, Clone)] pub enum PoolMode {
/// Renaming a pattern
Rename(usize, Arc<str>),
/// Editing the length of a pattern
Length(usize, usize, ClipLengthFocus),
/// Load clip from disk
Import(usize, Browse),
/// Save clip to disk
Export(usize, Browse),
}
/// Focused field of `ClipLength`
#[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
impl ClipLengthFocus {
pub fn next (&mut self) {
use ClipLengthFocus::*;
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
}
pub fn prev (&mut self) {
use ClipLengthFocus::*;
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
}
}
/// Displays and edits clip length.
#[derive(Clone, Debug, Default)] pub struct ClipLength {
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of clip in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<ClipLengthFocus>,
}
impl ClipLength {
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq)
}
pub fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq
}
pub fn ticks (&self) -> usize {
self.pulses % self.ppq
}
}
//impl_from!(Pool<MidiClip>: |clip:&Arc<RwLock<MidiClip>>|{
//let model = Self::default();
//model.clips.try_write().unwrap().push(clip.clone());
//model.clip.store(1, Relaxed);
//model
//});
//impl Pool {
//pub fn clip_index (&self) -> usize {
//self.clip.load(Relaxed)
//}
//pub fn set_clip_index (&self, value: usize) {
//self.clip.store(value, Relaxed);
//}
//pub fn mode (&self) -> &Option<PoolMode> {
//&self.mode
//}
//pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
//&mut self.mode
//}
//pub fn begin_import (&mut self) -> Usually<()> {
//*self.mode_mut() = Some(PoolMode::Import(
//self.clip_index(),
//Browse::new(None)?
//));
//Ok(())
//}
//pub fn begin_export (&mut self) -> Usually<()> {
//*self.mode_mut() = Some(PoolMode::Export(
//self.clip_index(),
//Browse::new(None)?
//));
//Ok(())
//}
//pub fn new_clip (&self) -> MidiClip {
//MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
//}
//pub fn cloned_clip (&self) -> MidiClip {
//let index = self.clip_index();
//let mut clip = self.clips.read()[index].read().duplicate();
//clip.color = ItemTheme::random_near(clip.color, 0.25);
//clip
//}
//pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
//let clip = Arc::new(RwLock::new(self.new_clip()));
//let index = {
//let mut clips = self.clips.try_write().unwrap();
//clips.push(clip.clone());
//clips.len().saturating_sub(1)
//};
//self.clip.store(index, Relaxed);
//(index, clip)
//}
//pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
//let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip);
//if let Some(index) = index {
//self.clips.try_write().unwrap().remove(index);
//return true
//}
//false
//}
//fn _clip_new (&self) -> MidiClip { self.new_clip() }
//fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() }
//fn _clip_index_current (&self) -> usize { 0 }
//fn _clip_index_after (&self) -> usize { 0 }
//fn _clip_index_previous (&self) -> usize { 0 }
//fn _clip_index_next (&self) -> usize { 0 }
//fn _color_random (&self) -> ItemColor { ItemColor::random() }
//}

View file

@ -374,27 +374,6 @@ impl_time_unit!(Pulse);
impl_time_unit!(Bpm); impl_time_unit!(Bpm);
impl_time_unit!(LaunchSync); impl_time_unit!(LaunchSync);
/// ```
/// let _ = tek::button_play_pause(true, true);
/// let _ = tek::button_play_pause(true, false);
/// let _ = tek::button_play_pause(false, true);
/// let _ = tek::button_play_pause(false, false);
/// ```
pub fn button_play_pause <'a> (playing: bool, compact: bool) -> impl Draw<Tui> {
bg(if playing { Rgb(0, 128, 0) } else { Rgb(128, 64, 0) },
either(compact,
draw(move|to: &mut Tui|either(playing,
fg(Rgb(0, 255, 0), " PLAYING "),
fg(Rgb(255, 128, 0), " STOPPED "),
).exact_w(9).draw(to)),
draw(move|to: &mut Tui|either(playing,
fg(Rgb(0, 255, 0), south(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
fg(Rgb(255, 128, 0), south(" ▗▄▖ ", " ▝▀▘ ",)),
).exact_w(5).draw(to)),
)
)
}
/// Contains memoized renders of clock values. /// Contains memoized renders of clock values.
/// ///
/// Performance optimization. /// Performance optimization.

View file

@ -1,236 +0,0 @@
use crate::*;
/// A clip pool.
///
/// ```
/// let pool = tek::Pool::default();
/// ```
#[derive(Debug)] pub struct Pool {
pub visible: bool,
/// Selected clip
pub clip: AtomicUsize,
/// Mode switch
pub mode: Option<PoolMode>,
/// Embedded file browse
#[cfg(feature = "browse")] pub browse: Option<Browse>,
/// Collection of MIDI clips.
#[cfg(feature = "clip")] pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
/// Collection of sound samples.
#[cfg(feature = "sampler")] pub samples: Arc<RwLock<Vec<Arc<RwLock<Sample>>>>>,
}
impl HasClips for Pool {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.clips.read().get(self.clip_index()).map(|c|c.clone())
}
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>> {
todo!()
}
fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips> {
self.clips.read()
}
fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips> {
self.clips.write()
}
}
/// Displays and edits clip length.
#[derive(Clone, Debug, Default)] pub struct ClipLength {
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of clip in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<ClipLengthFocus>,
}
/// Some sort of wrapper again?
pub struct PoolView<'a>(pub &'a Pool);
/// Modes for clip pool
#[derive(Debug, Clone)]
pub enum PoolMode {
/// Renaming a pattern
Rename(usize, Arc<str>),
/// Editing the length of a pattern
Length(usize, usize, ClipLengthFocus),
/// Load clip from disk
Import(usize, Browse),
/// Save clip to disk
Export(usize, Browse),
}
/// Focused field of `ClipLength`
#[derive(Copy, Clone, Debug)]
pub enum ClipLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
impl<T: AsRef<Pool> + AsMut<Pool>> HasPool for T {}
pub trait HasPool: AsRef<Pool> + AsMut<Pool> {
fn pool (&self) -> &Pool {
self.as_ref()
}
fn pool_mut (&mut self) -> &mut Pool {
self.as_mut()
}
}
impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{
let model = Self::default();
model.clips.try_write().unwrap().push(clip.clone());
model.clip.store(1, Relaxed);
model
});
impl_default!(Pool: Self {
browse: None,
clip: 0.into(),
clips: Arc::from(RwLock::from(vec![])),
mode: None,
samples: Arc::from(RwLock::from(vec![])),
visible: true,
});
impl Pool {
pub fn clip_index (&self) -> usize {
self.clip.load(Relaxed)
}
pub fn set_clip_index (&self, value: usize) {
self.clip.store(value, Relaxed);
}
pub fn mode (&self) -> &Option<PoolMode> {
&self.mode
}
pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
&mut self.mode
}
pub fn begin_import (&mut self) -> Usually<()> {
*self.mode_mut() = Some(PoolMode::Import(
self.clip_index(),
Browse::new(None)?
));
Ok(())
}
pub fn begin_export (&mut self) -> Usually<()> {
*self.mode_mut() = Some(PoolMode::Export(
self.clip_index(),
Browse::new(None)?
));
Ok(())
}
pub fn new_clip (&self) -> MidiClip {
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
}
pub fn cloned_clip (&self) -> MidiClip {
let index = self.clip_index();
let mut clip = self.clips()[index].try_read().unwrap().duplicate();
clip.color = ItemTheme::random_near(clip.color, 0.25);
clip
}
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
let clip = Arc::new(RwLock::new(self.new_clip()));
let index = {
let mut clips = self.clips.try_write().unwrap();
clips.push(clip.clone());
clips.len().saturating_sub(1)
};
self.clip.store(index, Relaxed);
(index, clip)
}
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip);
if let Some(index) = index {
self.clips.try_write().unwrap().remove(index);
return true
}
false
}
}
impl ClipLengthFocus {
pub fn next (&mut self) {
use ClipLengthFocus::*;
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
}
pub fn prev (&mut self) {
use ClipLengthFocus::*;
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
}
}
impl ClipLength {
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq)
}
pub fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq
}
pub fn ticks (&self) -> usize {
self.pulses % self.ppq
}
}
impl Pool {
fn _clip_new (&self) -> MidiClip { self.new_clip() }
fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() }
fn _clip_index_current (&self) -> usize { 0 }
fn _clip_index_after (&self) -> usize { 0 }
fn _clip_index_previous (&self) -> usize { 0 }
fn _clip_index_next (&self) -> usize { 0 }
fn _color_random (&self) -> ItemColor { ItemColor::random() }
}
impl<'a> PoolView<'a> {
//fn tui (&self) -> impl Draw<'_, Tui> {
//let Self(pool) = self;
////let color = self.1.clip().map(|c|c.try_read().unwrap().color).unwrap_or_else(||g(32).into());
////let on_bg = |x|x;//below(Repeat(" "), bg(color.darkest.term, x));
////let border = |x|x;//Outer(Style::default().fg(color.dark.term).bg(color.darkest.term)).enclose(x);
////let height = pool.clips.try_read().unwrap().len() as u16;
//iter(
//||pool.clips().clone().into_iter(),
//move|clip: Arc<RwLock<MidiClip>>, i: usize|{
//let MidiClip { ref name, color, length, .. } = *clip.try_read().unwrap();
//let item_height = 1;
//let _item_offset = i as u16 * item_height;
//let selected = i == pool.clip_index();
//let b = if selected { color.light.term } else { color.base.term };
//let f = color.lightest.term;
//let name = if false { format!(" {i:>3}") } else { format!(" {i:>3} {name}") };
//let length = if false { String::default() } else { format!("{length} ") };
//bg(b, below!(
//fg(f, bold(selected, name)).origin_w().full_w(),
//fg(f, bold(selected, length)).origin_e().full_w(),
//when(selected, bold(true, fg(g(255), "▶"))).origin_w().full_w(),
//when(selected, bold(true, fg(g(255), "◀"))).origin_e().full_w(),
//)).exact_h(1)
//}).origin_n().full_h().exact_w(20)
//}
}
impl ClipLength {
//fn tui (&self) -> impl Draw<'_, Tui> {
//use ClipLengthFocus::*;
//let bars = format!("{}", self.bars());
//let beats = format!("{}", self.beats());
//let ticks = format!("{:>02}", self.ticks());
//match self.focus {
//None => east!(" ", bars, ".", beats, ".", ticks),
//Some(Bar) => east!("[", bars, "]", beats, ".", ticks),
//Some(Beat) => east!(" ", bars, "[", beats, "]", ticks),
//Some(Tick) => east!(" ", bars, ".", beats, "[", ticks),
//}
//}
}

View file

@ -13,37 +13,30 @@ use super::*;
/// Identifying color of scene /// Identifying color of scene
pub color: ItemTheme, pub color: ItemTheme,
/// Clips in scene, one per track /// Clips in scene, one per track
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>, pub clips: Pool<MidiClip>,
} }
impl Scene { impl Scene {
pub const DEFAULT_HEIGHT: usize = 2; pub const DEFAULT_HEIGHT: usize = 2;
/// Get currently playing clip, if any
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
if let Some(Some(clip)) = self.clips.get(index) {
Some(clip)
} else {
None
}
}
/// Get pulse length of the longest clip in the scene /// Get pulse length of the longest clip in the scene
pub fn pulses (&self) -> usize { pub fn pulses (&self) -> usize {
self.clips.iter().fold(0, |a, p|{ self.clips.read().iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.try_read().unwrap().length).unwrap_or(0)) a.max(p.read().as_ref().map(|q|q.read().length).unwrap_or(0))
}) })
} }
/// True if all clips in scene are currently playing on given tracks. /// True if all clips in scene are currently playing on given tracks.
pub fn is_playing (&self, tracks: &[Track]) -> bool { pub fn is_playing (&self, tracks: &[Track]) -> bool {
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() // Not "playing" if there are no clips
.all(|(track_index, clip)|match clip { if !self.clips.read().iter().any(|clip|clip.read().is_some()) {
return false
}
// Playing if at least 1 clip is playing
self.clips.read().iter().enumerate().all(|(track_index, clip)|match &*clip.read() {
Some(c) => tracks Some(c) => tracks
.get(track_index) .get(track_index)
.map(|track|{ .map(|track|{
if let Some((_, Some(clip))) = track.sequencer().play_clip() { if let Some((_, Some(clip))) = track.sequencer().play_clip() {
*clip.try_read().unwrap() == *c.try_read().unwrap() *clip.read() == *c.read()
} else { } else {
false false
} }
@ -52,7 +45,6 @@ impl Scene {
None => true None => true
}) })
} }
} }
@ -112,7 +104,7 @@ pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
{ {
let scene = Scene { let scene = Scene {
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()), name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
clips: vec![None;self.tracks().len()], clips: Pool::from(vec![None;self.tracks().len()]),
color: color.unwrap_or_else(ItemTheme::random), color: color.unwrap_or_else(ItemTheme::random),
}; };
self.scenes_mut().push(scene); self.scenes_mut().push(scene);
@ -121,75 +113,6 @@ pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
} }
} }
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; }
fn h_scenes (&self) -> u16; impl HasSceneScroll for Project { fn scene_scroll (&self) -> usize { self.scene_scroll } }
fn w_side (&self) -> u16; impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
fn w_mid (&self) -> u16;
fn view_scenes_names (&self) -> impl Draw<Tui> {
let select = self.selection();
let editor = self.editor();
let editing = self.is_editing();
iter_south(move||self.scenes_with_sizes().map(move|(index, scene, ..)|{
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) && self.editor().is_some() {
8
} else {
Scene::DEFAULT_HEIGHT
};
if y + height <= self.clips_size().h() as usize {
let data = (s, scene, y, y + height);
y += height;
Some(data)
} else {
None
}
})
}
}
impl ScenesView for App {
fn w_mid (&self) -> u16 {
(self.size.w() as u16).saturating_sub(self.w_side())
}
fn w_side (&self) -> u16 {
20
}
fn h_scenes (&self) -> u16 {
(self.size.h() as u16).saturating_sub(20)
}
}
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 trait HasSceneScroll: HasScenes {
fn scene_scroll (&self) -> usize;
}
impl HasSceneScroll for Project {
fn scene_scroll (&self) -> usize {
self.scene_scroll
}
}
impl HasSceneScroll for App {
fn scene_scroll (&self) -> usize {
self.project.scene_scroll()
}
}

View file

@ -13,39 +13,29 @@ pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
fn selection_mut (&mut self) -> &mut Selection { fn selection_mut (&mut self) -> &mut Selection {
self.as_mut() self.as_mut()
} }
/// Get the active track /// Get the active track
#[cfg(feature = "track")] #[cfg(feature = "track")] fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
let index = self.selection().track()?; let index = self.selection().track()?;
self.tracks().get(index) self.tracks().get(index)
} }
/// Get a mutable reference to the active track /// Get a mutable reference to the active track
#[cfg(feature = "track")] #[cfg(feature = "track")] fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
let index = self.selection().track()?; let index = self.selection().track()?;
self.tracks_mut().get_mut(index) self.tracks_mut().get_mut(index)
} }
/// Get the active scene /// Get the active scene
#[cfg(feature = "scene")] #[cfg(feature = "scene")] fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
let index = self.selection().scene()?; let index = self.selection().scene()?;
self.scenes().get(index) self.scenes().get(index)
} }
/// Get a mutable reference to the active scene /// Get a mutable reference to the active scene
#[cfg(feature = "scene")] #[cfg(feature = "scene")] fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
let index = self.selection().scene()?; let index = self.selection().scene()?;
self.scenes_mut().get_mut(index) self.scenes_mut().get_mut(index)
} }
/// Get the active clip /// Get the active clip
#[cfg(feature = "clip")] #[cfg(feature = "clip")] fn selected_clip (&self) -> Option<Slot<MidiClip>> where Self: HasScenes + HasTracks {
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks { self.selected_scene()?.clips.read().get(self.selection().track()?).cloned()
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
} }
} }
@ -65,7 +55,11 @@ pub enum Selection {
#[cfg(feature = "scene")] Scene(usize), #[cfg(feature = "scene")] Scene(usize),
/// A track is selected. /// A track is selected.
#[cfg(feature = "track")] Track(usize), #[cfg(feature = "track")] Track(usize),
/// A clip (track × scene) is selected. /// A clip is selected.
#[cfg(feature = "track")] Clip(usize),
/// A sample is selected.
#[cfg(feature = "track")] Sample(usize),
/// A clip slot (track × scene) is selected.
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, #[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
/// A track's MIDI input connection is selected. /// A track's MIDI input connection is selected.
#[cfg(feature = "track")] TrackInput { track: usize, port: usize }, #[cfg(feature = "track")] TrackInput { track: usize, port: usize },
@ -90,9 +84,10 @@ impl Selection {
#[cfg(feature = "track")] Track(t) => #[cfg(feature = "track")] Track(t) =>
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()), 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)) { TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
(Some(_), Some(s)) => match s.clip(*track) { (Some(_), Some(s)) => match &s.clips.get(*track) {
Some(clip) => format!("T{track} S{scene} C{}", &clip.try_read().unwrap().name), None => format!("T{track} S{scene}: Empty"),
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"), _ => format!("T{track} S{scene}: Empty"),
}, },

View file

@ -40,24 +40,30 @@ impl Track {
..Default::default() ..Default::default()
}) })
} }
pub fn new_added (
jack: &Jack<'static>,
clock: Option<&Clock>,
name: &impl AsRef<str>,
color: Option<ItemTheme>,
clip: Option<&Arc<RwLock<MidiClip>>>,
mins: Arc<[Connect]>,
mouts: Arc<[Connect]>,
) -> Usually<Self> {
let name = name.as_ref();
Ok(Self {
name: name.into(),
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(name, jack, clock, clip, mins.as_ref(), mouts.as_ref())?,
..Default::default()
})
}
pub fn audio_ins (&self) -> &[AudioInput] { pub fn audio_ins (&self) -> &[AudioInput] {
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
} }
pub fn audio_outs (&self) -> &[AudioOutput] { pub fn audio_outs (&self) -> &[AudioOutput] {
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
} }
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
tracks: impl Fn() -> U + Send + Sync + 'a,
callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a)
) -> impl Draw<Tui> {
iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{
fg_bg(
track.color.lightest.term,
track.color.base.term,
callback(index, track)
).exact_w((x2 - x1) as u16)
}))
}
pub fn stop (&mut self) -> Perhaps<TrackCmd> { pub fn stop (&mut self) -> Perhaps<TrackCmd> {
self.sequencer.enqueue_next(None); self.sequencer.enqueue_next(None);
Ok(None) Ok(None)
@ -165,26 +171,13 @@ pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrack
|x|x.to_string().into() |x|x.to_string().into()
); );
*self.tracks_last_mut() += 1; *self.tracks_last_mut() += 1;
let track = Track { let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?;
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(
&name,
self.jack(),
Some(self.clock()),
None,
mins.as_ref(),
mouts.as_ref()
)?,
name,
..Default::default()
};
self.tracks_mut().push(track); self.tracks_mut().push(track);
let len = self.tracks().len(); let len = self.tracks().len();
let index = len - 1; let index = len - 1;
for scene in self.scenes_mut().iter_mut() { for scene in self.scenes_mut().iter_mut() {
while scene.clips.len() < len { while scene.clips.len() < len {
scene.clips.push(None); scene.clips.append(None);
} }
} }
Ok((index, &mut self.tracks_mut()[index])) Ok((index, &mut self.tracks_mut()[index]))
@ -195,316 +188,6 @@ pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrack
} }
} }
impl<
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
> TracksView for T {}
pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack {
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
let _editor_width = self.editor().as_ref().map(|e|e.size.w());
let _active_track = self.selection().track();
let mut x = 0;
let w = self.clips_size().w() as usize;
self.tracks().iter().enumerate().map_while(move |(index, track)|{
let width = track.width.max(8);
if x + width < w {
let data = (index, track, x, x + width);
x += width + Self::TRACK_SPACING;
Some(data)
} else {
None
}
})
}
/// Draw name of each track
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_names_count(),
self.view_track_names_add(),
self.view_track_names_cells()
)
}
fn view_track_names_add (&self) -> impl Draw<Tui> {
south(self.view_track_add(), self.view_scene_add())
}
fn view_track_names_count (&self) -> impl Draw<Tui> {
south(self.view_track_count(), self.view_scene_count())
}
fn view_track_names_cells <'a> (&'a self) -> impl Draw<Tui> {
iter_east(||self.tracks_with_sizes().map(|(index, track, _x1, _x2)|{
let b = if self.selection().track() == Some(index) {
track.color.light.term
} else {
track.color.base.term
};
bg(b, south(
east!(
"·t",
index,
" ",
fg(Rgb(255, 255, 255), bold(true, &track.name))
)
.align_nw()
.full_w(),
""
))
.exact_w(track_width(index, track))
.exact_h(2)
}))
}
/// Draw outputs per track
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_output_count(),
self.view_track_output_add(),
self.view_track_output_cells()
)
}
fn view_track_output_cells <'a> (&'a self) -> impl Draw<Tui> {
iter_east(move||self.tracks_with_sizes().map(move|(index, track, _x1, _x2)|{
let f = Rgb(255, 255, 255);
let b = track.color.dark.term;
iter_south(move||track.sequencer.midi_outs.iter().map(move|port: &MidiOutput|{
fg(f, bg(b, east!("·o", index, " ", port.port_name()).full_w().align_w()).exact_h(1))
}))
.full_h()
.align_nw()
.exact_w(track_width(index, track))
}))
}
fn view_track_device_count (&self) -> impl Draw<Tui> {
button_3("d", "evice", self.track().map(|t|t.devices.len()).unwrap_or(0), false).min_h(2)
}
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.tracks_devices_height();
let btn1 = self.view_track_device_count();
let btn2 = self.view_track_device_add();
view_track_row_section(theme, btn1, btn2, self.view_track_device_cells(height))
}
fn view_track_device_cells (&self, height: u16) -> impl Draw<Tui> {
iter_east(move||self.tracks_with_sizes()
.enumerate()
.map(move|(index, (_, track, _x1, _x2))|bg(
track.color.dark.term,
iter_south(move||(0..height).map(|_|fg_bg(
ItemTheme::G[32].lightest.term,
ItemTheme::G[32].dark.term,
" · --".align_nw()
).exact_wh(track.width as u16, 2)))
.align_nw()
)
.exact_wh(
Some(track_width(index, track)),
Some(height + 1),
)))
}
fn tracks_devices_height (&self) -> u16 {
let mut h = 2;
for track in self.tracks().iter() {
h = h.max(track.devices.len() * 2);
}
h as u16
}
fn view_inputs (&self) -> impl Draw<Tui> + '_ {
let title_1 = button_3("i", "nput ", self.midi_ins().len(), false).align_w().exact_wh(20, 1);
let title_2 = self.view_track_input_add().exact_wh(4, 1);
east(title_1, west(title_2, self.view_input_cells()))
}
fn tracks_inputs_height (&self) -> u16 {
let mut height = 0u16;
for track in self.tracks().iter() {
height = height.max(track.sequencer.midi_ins.len() as u16);
}
height
}
/// Draw inputs per track
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
view_track_row_section(theme,
self.view_track_input_count(),
self.view_track_input_add(),
self.view_track_input_cells(self.tracks_inputs_height())
)
}
fn view_track_input_cells (&self, height: u16) -> impl Draw<Tui> {
iter_east(move||self
.tracks_with_sizes()
.map(move|(index, track, _x1, _x2)|{
south(
bg(track.color.base.term,
east!(
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
).align_w().full_w()),
iter_south(move||track.sequencer.midi_ins.iter().map(move|port|fg_bg(
Rgb(255, 255, 255),
track.color.dark.term,
east!("·i", index, " ", port.port_name()).align_w().full_w()))))
.align_nw()
.exact_wh(track_width(index, track), height + 1)
})).align_w()
}
fn view_input_cells (&self) -> impl Draw<Tui> {
draw(move|to: &mut Tui|{
for (_index, track, x1, _x2) in self.tracks_with_sizes() {
let _ = south(
bg(track.color.dark.term, east!(
either(track.sequencer.monitoring, fg(Green, "mon "), "mon "),
either(track.sequencer.recording, fg(Red, "rec "), "rec "),
either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "),
).exact_w(track.width as u16)).align_w().push_x(x1 as u16),
draw(move |to: &mut Tui|{
for (index, port) in self.midi_ins().as_slice().iter().enumerate() {
let _ = east(
east(
" ● ",
bold(true, fg(Rgb(255,255,255), port.port_name()))
).align_w().exact_w(20),
west(
().exact_w(4),
bg(track.color.darker.term, east!(
either(track.sequencer.monitoring, fg(Green, " ● "), " · "),
either(track.sequencer.recording, fg(Red, " ● "), " · "),
either(track.sequencer.overdub, fg(Yellow, " ● "), " · "),
).exact_w(track.width as u16).align_w())
)
).push_x(index as u16 * 10).exact_h(1).draw(to)?;
}
Ok(None)
})
).draw(to)?;
}
Ok(Some(to.area()))
})
}
fn outputs_height (&self) -> u16 {
let mut h = 1;
for output in self.midi_outs().iter() {
h += 1 + output.connections.len();
}
h as u16
}
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
let height = self.outputs_height();
let list = south(
button_3("o", "utput", self.midi_outs().len(), false).align_w().full_w().exact_h(1),
bg(Color::Rgb(0, 0, 0), draw(|to: &mut Tui|{
for (_index, port) in self.midi_outs().iter().enumerate() {
east(
east(
" ● ",
fg(Rgb(255,255,255), bold(true, port.port_name()))
).align_w(),
east!(
port.port().get_connections().len(),
"/",
port.connections.len(),
" "
).align_e().full_w().exact_h(1)).full_w().draw(to)?;
for (index, conn) in port.connections.iter().enumerate() {
east!(
" c",
index,
conn.info()
).align_w().full_w().exact_h(1).draw(to)?;
}
}
todo!();
})).align_nw().full_wh().exact_h(height - 1).min_h(1)
);
view_track_row_section(theme, list, self.view_track_output_add(),
bg(theme.darker.term, draw(|to: &mut Tui|{
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
let _ = draw(|to: &mut Tui|{
east(
either(true, fg(Green, "play "), "play "),
either(false, fg(Yellow, "solo "), "solo "),
).align_w().exact_h(1).draw(to)?;
for (_index, port) in self.midi_outs().iter().enumerate() {
east(
either(true, fg(Green, " ● "), " · "),
either(false, fg(Yellow, " ● "), " · "),
).align_w().exact_h(1).draw(to)?;
for (_index, _conn) in port.connections.iter().enumerate() {
"".full_w().exact_h(1).draw(to)?;
}
}
todo!()
}).exact_w(track_width(index, track)).draw(to)?;
}
todo!()
}).align_w().full_w())).exact_h(height)
}
fn view_track_count (&self) -> impl Draw<Tui> {
button_3("t", "rack ", east(
self.selection().track().map(|track|east(track, "/")),
self.tracks().len()
), false)
}
fn view_scene_count (&self) -> impl Draw<Tui> {
button_3("s", "cene ", east(
self.selection().scene().map(|scene|east(scene, "/")),
self.scenes().len()
), false)
}
fn view_track_add (&self) -> impl Draw<Tui> {
button_2("T", "+", false)
}
fn view_scene_add (&self) -> impl Draw<Tui> {
button_2("S", "+", false)
}
fn view_track_input_count (&self) -> impl Draw<Tui> {
button_2("i", "nput", false)
}
fn view_track_output_count (&self) -> impl Draw<Tui> {
button_3("o", "utput", 0, false)
//draw(|to: &mut Tui|{
//for port in self.midi_outs().iter() {
//let _ = port.port_name().align_w().full_w().draw(to)?;
//}
//Ok(Some(XYWH(0, 0, 0, 0)))
//})),
}
fn view_track_output_add (&self) -> impl Draw<Tui> {
button_2("O", "+", false)
}
fn view_track_input_add (&self) -> impl Draw<Tui> {
button_2("I", "+", false)
}
fn view_track_device_add (&self) -> impl Draw<Tui> {
button_2("D", "+", false)
}
}
pub trait HasTrackScroll { pub trait HasTrackScroll {
fn track_scroll (&self) -> usize; fn track_scroll (&self) -> usize;
fn tracks_last (&self) -> usize; fn tracks_last (&self) -> usize;
@ -546,24 +229,3 @@ impl HasTrackScroll for App {
&mut self.project.track_last &mut self.project.track_last
} }
} }
//pub fn per_track <'a, T: Draw<'_, Tui> + 'a, U: TracksSizes<'a>> (
//tracks: impl Fn() -> U + Send + Sync + 'a,
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
//) -> impl Draw<'_, Tui> + 'a {
//per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
//}
//pub fn per_track_top <'a, T: Draw<'_, Tui> + 'a, U: TracksSizes<'a>> (
//tracks: impl Fn() -> U + Send + Sync + 'a,
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
//) -> impl Draw<'_, Tui> + 'a {
//bg(Reset, iter_east(||tracks()
//.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{
//fg_bg(
//track.color.lightest.term,
//track.color.base.term,
//callback(index, track)
//).exact_w((x2 - x1) as u16)
//})).align_x())
//}

View file

@ -2,10 +2,6 @@
mod deps; pub use self::deps::*; mod deps; pub use self::deps::*;
mod config; pub use self::config::*;
mod config_bind; pub use self::config_bind::*;
mod config_mode; pub use self::config_mode::*;
mod config_view; pub use self::config_view::*;
mod midi_octave; pub use self::midi_octave::*; mod midi_octave; pub use self::midi_octave::*;
mod midi_piano; pub use self::midi_piano::*; mod midi_piano; pub use self::midi_piano::*;
@ -45,11 +41,12 @@ mod proj_port; pub use self::proj_port::*;
mod proj_scene; pub use self::proj_scene::*; mod proj_scene; pub use self::proj_scene::*;
mod proj_select; pub use self::proj_select::*; mod proj_select; pub use self::proj_select::*;
mod proj_track; pub use self::proj_track::*; mod proj_track; pub use self::proj_track::*;
mod proj_pool; pub use self::proj_pool::*;
mod tek_browse; pub use self::tek_browse::*; mod tek_bind; pub use self::tek_bind::*;
mod tek_cli; pub use self::tek_cli::*; mod tek_cli; pub use self::tek_cli::*;
mod tek_config; pub use self::tek_config::*;
mod tek_ctrl; pub use self::tek_ctrl::*; mod tek_ctrl; pub use self::tek_ctrl::*;
mod tek_mode; pub use self::tek_mode::*;
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")] pub use self::device_lv2::*;
@ -126,16 +123,18 @@ fn run_new_plain (config: Config) -> Usually<()> {
#[namespace(Arc<[Connect]> get_arc_array_connect)] #[namespace(Arc<[Connect]> get_arc_array_connect)]
#[namespace(Arc<str> get_arc_str)] #[namespace(Arc<str> get_arc_str)]
#[namespace(Color get_color)] #[namespace(Color get_color)]
#[namespace(ControlAxis get_axis)] #[namespace(ControlAxis ControlAxis::get_axis)]
#[namespace(Dialog get_dialog)] #[namespace(Dialog Dialog::get_dialog)]
#[namespace(ItemColor)] #[namespace(ItemColor)]
#[namespace(ItemTheme)] #[namespace(ItemTheme)]
#[namespace(MidiClip)] #[namespace(MidiClip)]
#[namespace(ClipSlot ClipSlot::eval)]
#[namespace(ClipPool ClipPool::eval)]
#[namespace(SampleSlot SampleSlot::eval)]
#[namespace(SamplePool SamplePool::eval)]
#[namespace(Option<Arc<[Connect]>> get_opt_arc_array_connect)] #[namespace(Option<Arc<[Connect]>> get_opt_arc_array_connect)]
#[namespace(Option<Arc<str>> get_opt_arc_str)] #[namespace(Option<Arc<str>> get_opt_arc_str)]
#[namespace(Option<ItemTheme> get_opt_itemtheme)] #[namespace(Option<ItemTheme> get_opt_itemtheme)]
#[namespace(Option<Arc<RwLock<MidiClip>>> get_opt_arc_rwlock_midiclip)]
#[namespace(Option<Vec<Option<Arc<RwLock<MidiClip>>>>> get_opt_vec_opt_arc_rwlock_midiclip)]
#[namespace(Option<bool> get_opt_bool)] #[namespace(Option<bool> get_opt_bool)]
#[namespace(Option<u16> get_opt_u16)] #[namespace(Option<u16> get_opt_u16)]
#[namespace(Option<u32> get_opt_u32)] #[namespace(Option<u32> get_opt_u32)]
@ -242,14 +241,14 @@ fn get_bool <'a> (state: &App, src: impl Language) -> Perhaps<bool> {
":focused/message" => matches!(state.dialog, Dialog::Message(..)), ":focused/message" => matches!(state.dialog, Dialog::Message(..)),
":focused/add_device" => matches!(state.dialog, Dialog::Device(..)), ":focused/add_device" => matches!(state.dialog, Dialog::Device(..)),
":focused/browser" => state.dialog.browser().is_some(), ":focused/browser" => state.dialog.browser().is_some(),
":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))), //":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))),
":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))), //":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))),
":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))), //":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))),
":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))), //":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))),
":focused/clip" => !state.editor_focused() && matches!(state.selection(), Selection::TrackClip{..}), ":focused/clip" => matches!(state.selection(), Selection::TrackClip{..}),
":focused/track" => !state.editor_focused() && matches!(state.selection(), Selection::Track(..)), ":focused/track" => matches!(state.selection(), Selection::Track(..)),
":focused/scene" => !state.editor_focused() && matches!(state.selection(), Selection::Scene(..)), ":focused/scene" => matches!(state.selection(), Selection::Scene(..)),
":focused/mix" => !state.editor_focused() && matches!(state.selection(), Selection::Mix), ":focused/mix" => matches!(state.selection(), Selection::Mix),
_ => return err!("not bool: {word}") _ => return err!("not bool: {word}")
})).transpose() })).transpose()
} }
@ -322,17 +321,6 @@ fn get_opt_usize <'a> (state: &App, src: impl Language) -> Perhaps<Option<usize>
})) }))
} }
fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
eval_word(src, |word|Ok(match word {
"X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine,
"Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine,
"Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine,
_ => return err!("unknown axis {word}")
}))
}
fn get_opt_u16 <'a> (_: &App, _src: impl Language) -> Perhaps<Option<u16>> { fn get_opt_u16 <'a> (_: &App, _src: impl Language) -> Perhaps<Option<u16>> {
Ok(None) Ok(None)
@ -370,11 +358,16 @@ fn get_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Vec<Arc<str>>> {
todo!() todo!()
} }
impl_has!(Clock: |self: App|self.project.clock); impl_has!(Sizer: |self: App|self.size);
impl_has!(Dialog: |self: App|self.dialog); impl_has!(Dialog: |self: App|self.dialog);
impl_has!(Jack<'static>: |self: App|self.jack); impl_has!(Jack<'static>: |self: App|self.jack);
impl_has!(Pool: |self: App|self.project.pool);
impl_has!(Clock: |self: App|self.project.clock);
impl_has!(Pool<MidiClip>: |self: App|self.project.clips);
impl_has!(Pool<Sample>: |self: App|self.project.samples);
impl_has!(Selection: |self: App|self.project.selection); impl_has!(Selection: |self: App|self.project.selection);
impl_as_ref!(Slot<MidiClip>: |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());
@ -383,21 +376,6 @@ primitive!(u16: try_to_u16);
primitive!(usize: try_to_usize); primitive!(usize: try_to_usize);
primitive!(isize: try_to_isize); primitive!(isize: try_to_isize);
impl HasClips for App {
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.project.clip()
}
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>> {
self.project.clip_mut()
}
fn clips <'a> (&'a self) -> RwLockReadGuard<'a, Clips> {
self.project.clips()
}
fn clips_mut <'a> (&'a self) -> RwLockWriteGuard<'a, Clips> {
self.project.clips_mut()
}
}
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!");
Self::input_cmd(self, input) Self::input_cmd(self, input)
@ -478,86 +456,20 @@ impl App {
I, IFine, I, IFine,
} }
/// The [Draw] implementation for [App] handles the loaded view, impl ControlAxis {
/// which is defined in terms of [dizzle] DSL. fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
/// eval_word(src, |word|Ok(match word {
/// If there is an error, the error is displayed. FIXME: overlay it
/// Then, every top-level form of the DSL description is rendered.
impl Draw<Tui> for App {
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
#[cfg(feature = "prof2")] profiling::scope!("App::draw");
//self.perf.cycle(&mut |_|{
self.draw_error(to)?;
self.draw_modes(to)?;
self.draw_debug(to)?;
#[cfg(feature = "prof2")] profiling::finish_frame!();
Ok(Some(to.area().into()))
//})
}
}
impl App { "X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine,
fn draw_error (&self, to: &mut Tui) -> Usually<()> { "Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine,
if let Some(e) = self.error.try_read().unwrap().as_ref() { "Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine,
e.as_ref().align_c().draw(to)?;
}
Ok(())
}
fn draw_modes (&self, to: &mut Tui) -> Usually<()> { _ => return err!("unknown axis {word}")
for name in self.modes.read().iter() {
let mut error = false;
if let Some(mode) = self.config.get_mode(name).as_ref().map(Arc::clone) {
for (index, view) in mode.view.iter().enumerate() {
match (view.render)(self, to) {
Ok(None) => {},
Ok(Some(XYWH(.., w, h))) => {
self.size.0.store(w as usize, Relaxed);
self.size.1.store(h as usize, Relaxed);
},
Err(e) => {
let message = format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&mode.name,
&view.source
);
*self.error.try_write().unwrap() = Some(message.into());
error = true;
break;
}
}
}
if error {
break
} else {
*self.error.try_write().unwrap() = None;
}
}
}
Ok(())
}
#[allow(unused)] fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> { }))
Split::Above.bar(0,
Split::West.bar(16,
format!("{}/{} {} ",
self.perf.used.load(Relaxed),
self.perf.window.load(Relaxed),
self.perf.clock.raw() / 10000000).align_e(),
format!("{}x{} ",
self.size.0.load(Relaxed),
self.size.1.load(Relaxed)).align_w(),
).align_se(),
Split::East.bar(16,
format!("{}", self.modes.read()[0]),
format!("{}", self.modes.read()[0])
).align_sw()
).draw(to)
} }
} }
impl_has!(Sizer: |self: App|self.size);
pub trait HasWidth { pub trait HasWidth {
const MIN_WIDTH: usize; const MIN_WIDTH: usize;
/// Increment track width. /// Increment track width.
@ -566,7 +478,24 @@ pub trait HasWidth {
fn width_dec (&mut self); fn width_dec (&mut self);
} }
pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> { /// Various possible dialog modes.
///
/// ```
/// let dialog: tek::Dialog = Default::default();
/// ```
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
#[default] None,
Help(usize),
Menu(usize, Arc<[MenuItem]>),
Device(usize),
Message(Arc<str>),
Browse(BrowseTarget, Arc<Browse>),
Options,
}
impl Dialog {
fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
eval_word(&src, |word|Ok(match word { eval_word(&src, |word|Ok(match word {
@ -606,23 +535,6 @@ pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
} }
/// Various possible dialog modes.
///
/// ```
/// let dialog: tek::Dialog = Default::default();
/// ```
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
#[default] None,
Help(usize),
Menu(usize, Arc<[MenuItem]>),
Device(usize),
Message(Arc<str>),
Browse(BrowseTarget, Arc<Browse>),
Options,
}
impl Dialog {
/// ``` /// ```
/// let _ = tek::Dialog::welcome(); /// let _ = tek::Dialog::welcome();
/// ``` /// ```
@ -758,29 +670,142 @@ 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( #[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>);
pub Arc<str>, impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
pub Arc<[AppCmd]>, impl_default!(MenuItem: Self::new("", []));
); 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_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
impl_default!(MenuItem: Self::new("", []));
impl PartialEq for MenuItem {
fn eq (&self, other: &Self) -> bool {
self.0 == other.0
}
}
//impl AsRef<Arc<[MenuItem]>> for MenuItems { //impl AsRef<Arc<[MenuItem]>> for MenuItems {
//fn as_ref (&self) -> &Arc<[MenuItem]> { //fn as_ref (&self) -> &Arc<[MenuItem]> {
//&self.0 //&self.0
//} //}
//} //}
/// Browses for files to load/save.
///
/// ```
/// let browse = tek::Browse::default();
/// ```
#[derive(Debug, Clone, Default, PartialEq)] pub struct Browse {
pub cwd: PathBuf,
pub dirs: Vec<(OsString, String)>,
pub files: Vec<(OsString, String)>,
pub filter: String,
pub index: usize,
pub scroll: usize,
pub size: Sizer,
}
//pub(crate) struct EntriesIterator<'a, S: Screen> {
//pub browser: &'a Browse,
//pub offset: usize,
//pub length: usize,
//pub index: usize,
//_screen: std::marker::PhantomData<S>
//}
#[derive(Clone, Debug)] pub enum BrowseTarget {
SaveProject,
LoadProject,
ImportSample(Arc<RwLock<Option<Sample>>>),
ExportSample(Arc<RwLock<Option<Sample>>>),
ImportClip(Arc<RwLock<Option<MidiClip>>>),
ExportClip(Arc<RwLock<Option<MidiClip>>>),
}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
impl Browse {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
let mut dirs = vec![];
let mut files = vec![];
for entry in std::fs::read_dir(&cwd)? {
let entry = entry?;
let name = entry.file_name();
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
let meta = entry.metadata()?;
if meta.is_dir() {
dirs.push((name, format!("📁 {decoded}")));
} else if meta.is_file() {
files.push((name, format!("📄 {decoded}")));
}
}
Ok(Self { cwd, dirs, files, ..Default::default() })
}
pub fn to_chdir (&self) -> Usually<Self> { Self::new(Some(self.path())) }
pub fn len (&self) -> usize { self.dirs.len() + self.files.len() }
pub fn is_dir (&self) -> bool { self.index < self.dirs.len() }
pub fn is_file (&self) -> bool { self.index >= self.dirs.len() }
pub fn path (&self) -> PathBuf {
self.cwd.join(if self.is_dir() {
&self.dirs[self.index].0
} else if self.is_file() {
&self.files[self.index - self.dirs.len()].0
} else {
unreachable!()
})
}
//fn tui_entries (&self) -> EntriesIterator<'_, Tui> {
//EntriesIterator {
//offset: 0,
//index: 0,
//length: self.dirs.len() + self.files.len(),
//browser: self,
//_screen: Default::default(),
//}
//}
}
impl PartialEq for BrowseTarget {
fn eq (&self, other: &Self) -> bool {
match self {
Self::ImportSample(_) => false,
Self::ExportSample(_) => false,
Self::ImportClip(_) => false,
Self::ExportClip(_) => false,
#[allow(unused)] t => matches!(other, t)
}
}
}
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
let entry = entry.expect("failed to read drectory entry");
let meta = entry.metadata().expect("failed to read entry metadata");
if meta.is_file() {
files.push(entry.file_name());
} else if meta.is_dir() {
subdirs.push(entry.file_name());
}
(subdirs, files)
});
subdirs.sort();
files.sort();
Ok((subdirs, files))
}
impl App {
/// Return reference to content browser if open.
///
/// ```
/// assert_eq!(tek::App::default().browser(), None);
/// ```
pub fn browser (&self) -> Option<&Browse> {
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
}
}

View file

@ -1,146 +0,0 @@
use crate::*;
/// Browses for files to load/save.
///
/// ```
/// let browse = tek::Browse::default();
/// ```
#[derive(Debug, Clone, Default, PartialEq)] pub struct Browse {
pub cwd: PathBuf,
pub dirs: Vec<(OsString, String)>,
pub files: Vec<(OsString, String)>,
pub filter: String,
pub index: usize,
pub scroll: usize,
pub size: Sizer,
}
//pub(crate) struct EntriesIterator<'a, S: Screen> {
//pub browser: &'a Browse,
//pub offset: usize,
//pub length: usize,
//pub index: usize,
//_screen: std::marker::PhantomData<S>
//}
#[derive(Clone, Debug)] pub enum BrowseTarget {
SaveProject,
LoadProject,
ImportSample(Arc<RwLock<Option<Sample>>>),
ExportSample(Arc<RwLock<Option<Sample>>>),
ImportClip(Arc<RwLock<Option<MidiClip>>>),
ExportClip(Arc<RwLock<Option<MidiClip>>>),
}
// Commands supported by [Browse]
//#[derive(Debug, Clone, PartialEq)]
//pub enum BrowseCommand {
//Begin,
//Cancel,
//Confirm,
//Select(usize),
//Chdir(PathBuf),
//Filter(Arc<str>),
//}
impl Browse {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
let mut dirs = vec![];
let mut files = vec![];
for entry in std::fs::read_dir(&cwd)? {
let entry = entry?;
let name = entry.file_name();
let decoded = name.clone().into_string().unwrap_or_else(|_|"<unreadable>".to_string());
let meta = entry.metadata()?;
if meta.is_dir() {
dirs.push((name, format!("📁 {decoded}")));
} else if meta.is_file() {
files.push((name, format!("📄 {decoded}")));
}
}
Ok(Self { cwd, dirs, files, ..Default::default() })
}
pub fn to_chdir (&self) -> Usually<Self> { Self::new(Some(self.path())) }
pub fn len (&self) -> usize { self.dirs.len() + self.files.len() }
pub fn is_dir (&self) -> bool { self.index < self.dirs.len() }
pub fn is_file (&self) -> bool { self.index >= self.dirs.len() }
pub fn path (&self) -> PathBuf {
self.cwd.join(if self.is_dir() {
&self.dirs[self.index].0
} else if self.is_file() {
&self.files[self.index - self.dirs.len()].0
} else {
unreachable!()
})
}
//fn tui (&self) -> impl Draw<Tui> {
//iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w())
//}
//fn tui_entries (&self) -> EntriesIterator<'_, Tui> {
//EntriesIterator {
//offset: 0,
//index: 0,
//length: self.dirs.len() + self.files.len(),
//browser: self,
//_screen: Default::default(),
//}
//}
}
//impl<'a> Iterator for EntriesIterator<'a, Tui> {
//type Item = impl Draw<Tui>;
//fn next (&mut self) -> Option<Self::Item> {
//let dirs = self.browser.dirs.len();
//let files = self.browser.files.len();
//let index = self.index;
//if self.index < dirs {
//self.index += 1;
//Some(bold(true, self.browser.dirs[index].1.as_str()))
//} else if self.index < dirs + files {
//self.index += 1;
//Some(bold(false, self.browser.files[index - dirs].1.as_str()))
//} else {
//None
//}
//}
//}
impl PartialEq for BrowseTarget {
fn eq (&self, other: &Self) -> bool {
match self {
Self::ImportSample(_) => false,
Self::ExportSample(_) => false,
Self::ImportClip(_) => false,
Self::ExportClip(_) => false,
#[allow(unused)] t => matches!(other, t)
}
}
}
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
let entry = entry.expect("failed to read drectory entry");
let meta = entry.metadata().expect("failed to read entry metadata");
if meta.is_file() {
files.push(entry.file_name());
} else if meta.is_dir() {
subdirs.push(entry.file_name());
}
(subdirs, files)
});
subdirs.sort();
files.sort();
Ok((subdirs, files))
}
impl App {
/// Return reference to content browser if open.
///
/// ```
/// assert_eq!(tek::App::default().browser(), None);
/// ```
pub fn browser (&self) -> Option<&Browse> {
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
}
}

View file

@ -5,7 +5,7 @@ impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x });
impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x });
impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x });
impl_from!(AppCmd: |x: ModeCmd| AppCmd::Mode { command: x }); impl_from!(AppCmd: |x: ModeCmd| AppCmd::Mode { command: x });
impl_from!(AppCmd: |x: PoolCmd| AppCmd::Pool { command: x }); //impl_from!(AppCmd: |x: PoolCmd| AppCmd::Pool { command: x });
impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x }); impl_from!(AppCmd: |x: ProjectCmd| AppCmd::Project { command: x });
impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x }); impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x });
impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x }); impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x });
@ -62,9 +62,9 @@ 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(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps<AppCmd> { //#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> 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))
} }
@ -90,12 +90,11 @@ impl<T: HasProject> ProjectControl for T {}
} }
} }
impl<T: HasClock + Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>> ClockControl for T {} impl<T> ClockControl for T where T: HasClock
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
{}
#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock #[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock
+ Namespace<u32> + Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
+ Namespace<f64>
+ Namespace<Option<u32>>
{ {
#[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCmd> { #[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCmd> {
self.clock().playhead.update_from_usec(usec); self.clock().playhead.update_from_usec(usec);
@ -153,7 +152,7 @@ impl<T: HasTracks
+ Namespace<Option<Arc<str>>> + Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>> + Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>> + Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>> + Namespace<ClipPool>
> TracksControl for T {} > TracksControl for T {}
#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks #[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks
@ -166,7 +165,7 @@ impl<T: HasTracks
+ Namespace<Option<Arc<str>>> + Namespace<Option<Arc<str>>>
+ Namespace<Option<ItemTheme>> + Namespace<Option<ItemTheme>>
+ Namespace<Option<Arc<[Connect]>>> + Namespace<Option<Arc<[Connect]>>>
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>> + Namespace<ClipPool>
{ {
/// Stop all playing clips /// Stop all playing clips
#[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> { #[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> {
@ -176,12 +175,12 @@ impl<T: HasTracks
Ok(None) Ok(None)
} }
/// Launch multiple clips /// Launch multiple clips
#[command(Launch = "launch")] fn tracks_launch ( #[command(Launch = "launch")] fn tracks_launch (&mut self, clips: Option<ClipPool>)
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>> -> Perhaps<TracksCmd>
) -> Perhaps<TracksCmd> { {
if let Some(clips) = clips { if let Some(clips) = clips {
for (clip, track) in clips.iter().zip(self.tracks_mut()) { for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) {
track.sequencer.enqueue_next(clip.as_ref()); track.sequencer.enqueue_next(clip);
} }
} else { } else {
for track in self.tracks_mut().iter_mut() { for track in self.tracks_mut().iter_mut() {
@ -322,15 +321,11 @@ impl<T: HasScenes
} }
impl<T: HasScene impl<T: HasScene
+ Namespace<usize> + Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
> SceneControl for T {} > SceneControl for T {}
#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene #[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene
+ Namespace<usize> + Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
+ Namespace<Arc<str>>
+ Namespace<ItemTheme>
{ {
#[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> { #[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> {
todo!() todo!()
@ -357,23 +352,20 @@ impl<T: HasScene
#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler #[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler
+ Namespace<usize> + Namespace<usize>
{ {
#[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
#[command(RecordToggle = "rec-toggle")]
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
where Self: Sized where Self: Sized
{ {
let sampler = self.sampler_mut(); let sampler = self.sampler_mut();
let recording = sampler.recording.as_ref().map(|x|x.0); let recording = sampler.recording.as_ref().map(|x|x.0);
let _ = SamplerCmd::RecordFinish.dispatch(self)?; let _ = SamplerCmd::RecFinish.dispatch(self)?;
// autoslice: continue recording at next slot // autoslice: continue recording at next slot
if recording != Some(slot) { if recording != Some(slot) {
SamplerCmd::RecordBegin { slot }.dispatch(self) SamplerCmd::RecBegin { slot }.dispatch(self)
} else { } else {
Ok(None) Ok(None)
} }
} }
#[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
#[command(RecordBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
let sampler = self.sampler_mut(); let sampler = self.sampler_mut();
sampler.recording = Some(( sampler.recording = Some((
slot, slot,
@ -383,7 +375,7 @@ impl<T: HasScene
)); ));
Ok(None) Ok(None)
} }
#[command(RecordFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps<SamplerCmd> { #[command(RecFinish = "rec-finish")] fn record_finish (&mut self) -> Perhaps<SamplerCmd> {
let sampler = self.sampler_mut(); let sampler = self.sampler_mut();
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{ let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
std::mem::swap(sample, &mut sampler.samples.0[*index]); std::mem::swap(sample, &mut sampler.samples.0[*index]);
@ -391,14 +383,14 @@ impl<T: HasScene
}); // TODO: undo }); // TODO: undo
Ok(None) Ok(None)
} }
#[command(RecordCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps<SamplerCmd> { #[command(RecCancel = "rec-cancel")] fn record_cancel (&mut self) -> Perhaps<SamplerCmd> {
self.sampler_mut().recording = None; self.sampler_mut().recording = None;
Ok(None) Ok(None)
} }
#[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCmd> { #[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
let sampler = self.sampler_mut(); let sampler = self.sampler_mut();
if let Some(ref sample) = sampler.samples.0[slot] { if let Some(ref sample) = sampler.samples.0[slot] {
sampler.voices.try_write().unwrap().push(Sample::play(sample, 0, &u7::from(128))); sampler.voices.write().push(Sample::play(sample, 0, &u7::from(128)));
} }
Ok(None) Ok(None)
} }
@ -414,7 +406,7 @@ impl<T: HasEditor
+ Namespace<bool> + Namespace<bool>
+ Namespace<usize> + Namespace<usize>
+ Namespace<Option<u32>> + Namespace<Option<u32>>
+ Namespace<Option<Arc<RwLock<MidiClip>>>> + Namespace<ClipSlot>
> MidiEditControl for T {} > MidiEditControl for T {}
#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasEditor #[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasEditor
@ -423,7 +415,7 @@ impl<T: HasEditor
+ Namespace<bool> + Namespace<bool>
+ Namespace<usize> + Namespace<usize>
+ Namespace<Option<u32>> + Namespace<Option<u32>>
+ Namespace<Option<Arc<RwLock<MidiClip>>>> + Namespace<ClipSlot>
{ {
#[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> { #[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> {
if self.is_editing() { if self.is_editing() {
@ -433,9 +425,9 @@ impl<T: HasEditor
} }
Ok(Some(MidiEditCmd::Toggle)) Ok(Some(MidiEditCmd::Toggle))
} }
#[command(Show = "show")] fn edit_show (&mut self, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<MidiEditCmd> { #[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps<MidiEditCmd> {
Ok(self.editor_mut().as_mut().map(|mut editor|{ Ok(self.editor_mut().as_mut().map(|editor|{
editor.set_clip(clip.as_ref()); editor.mode.clip.0 = clip.0;
editor.redraw(); editor.redraw();
None None
}).flatten()) }).flatten())
@ -447,7 +439,7 @@ impl<T: HasEditor
}).flatten()) }).flatten())
} }
#[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCmd> { #[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCmd> {
Ok(self.editor_mut().as_mut().map(|mut editor|{ Ok(self.editor_mut().as_mut().map(|editor|{
editor.put_note(advance); editor.put_note(advance);
editor.redraw(); editor.redraw();
None None
@ -505,230 +497,227 @@ impl<T: HasEditor
// TODO: 1-9 seek markers that by default start every 8th of the clip // TODO: 1-9 seek markers that by default start every 8th of the clip
} }
impl<T: HasPool //impl<T: HasPool
+ Namespace<bool> //+ Namespace<bool>
+ Namespace<usize> //+ Namespace<usize>
+ Namespace<Arc<str>> //+ Namespace<Arc<str>>
+ Namespace<PathBuf> //+ Namespace<PathBuf>
+ Namespace<MidiClip> //+ Namespace<MidiClip>
+ Namespace<ItemColor> //+ Namespace<ItemColor>
+ Namespace<BrowseCmd> //+ Namespace<BrowseCmd>
> PoolControl for T {} //> PoolControl for T {}
#[tek_proc::commands(PoolCmd = "pool")] pub trait PoolControl: HasPool //#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool
+ Namespace<bool> //+ Namespace<bool>
+ Namespace<usize> //+ Namespace<usize>
+ Namespace<Arc<str>> //+ Namespace<Arc<str>>
+ Namespace<PathBuf> //+ Namespace<PathBuf>
+ Namespace<MidiClip> //+ Namespace<MidiClip>
+ Namespace<ItemColor> //+ Namespace<ItemColor>
+ Namespace<BrowseCmd> //+ Namespace<BrowseCmd>
{ //{
/// Toggle visibility of pool ///// Toggle visibility of pool
#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> { //#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
self.pool_mut().visible = visible; //self.pool_mut().visible = visible;
Ok(Some(PoolCmd::Show { visible: !visible })) //Ok(Some(PoolCmd::Show { visible: !visible }))
} //}
/// Set selected clip ///// Set selected clip
#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> { //#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
self.pool_mut().set_clip_index(index); //self.pool_mut().set_clip_index(index);
Ok(None) //Ok(None)
} //}
/// Delete a clip from the pool ///// Delete a clip from the pool
#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps<PoolCmd> { //#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps<PoolCmd> {
let clip = self.pool_mut().clips_mut().remove(index).try_read().unwrap().clone(); //let clip = self.pool_mut().remove(index).read().clone();
Ok(Some(PoolCmd::Add { index, clip })) //Ok(Some(PoolCmd::Add { index, clip }))
} //}
/// Switch places of two clips in pool ///// Switch places of two clips in pool
#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCmd> { //#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCmd> {
self.pool_mut().clips_mut().swap(index, other); //self.pool_mut().swap(index, other);
Ok(Some(PoolCmd::Swap { index, other })) //Ok(Some(PoolCmd::Swap { index, other }))
} //}
/// Add clip to pool ///// Add clip to pool
#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCmd> { //#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCmd> {
let mut index = index; //let mut index = index;
let clip = Arc::new(RwLock::new(clip.clone())); //let clip = Arc::new(RwLock::new(clip.clone()));
let mut clips = self.pool_mut().clips_mut(); //let mut clips = self.pool_mut().clips_mut();
if index >= clips.len() { //if index >= clips.len() {
index = clips.len(); //index = clips.len();
clips.push(clip) //clips.push(clip)
} else { //} else {
clips.insert(index, clip); //clips.insert(index, clip);
} //}
Ok(Some(PoolCmd::Delete { index })) //Ok(Some(PoolCmd::Delete { index }))
} //}
/// Set name of clip ///// Set name of clip
#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCmd> { //#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCmd> {
let clip = &mut self.pool_mut().clips_mut()[index]; //Ok(self.pool_mut().clips.read().get(index).map(|clip|{
let old_name = clip.try_read().unwrap().name.clone(); //let old_name = clip.read().name.clone();
clip.try_write().unwrap().name = name.clone(); //clip.write().name = name.clone();
Ok(Some(PoolCmd::SetName { index, name: old_name })) //PoolCmd::SetName { index, name: old_name }
} //}))
/// Set length of clip //}
#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCmd> { ///// Set length of clip
let clip = &mut self.pool_mut().clips_mut()[index]; //#[command(SetLength = "set-length")] fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCmd> {
let old_len = clip.try_read().unwrap().length; //Ok(self.pool_mut().clips.read().get(index).map(|clip|{
clip.try_write().unwrap().length = length; //let old_len = clip.read().length;
Ok(Some(PoolCmd::SetLength { index, length: old_len })) //clip.write().length = length;
} //PoolCmd::SetLength { index, length: old_len }
/// Set color of clip //}))
#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCmd> { //}
let mut color = ItemTheme::from(color); ///// Set color of clip
std::mem::swap(&mut color, &mut self.pool().clips()[index].try_write().unwrap().color); //#[command(SetColor = "set-color")] fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCmd> {
Ok(Some(PoolCmd::SetColor { index, color: color.base })) //let mut color = ItemTheme::from(color);
} //std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color);
/// Enter crop mode //Ok(Some(PoolCmd::SetColor { index, color: color.base }))
#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps<PoolCmd> { //}
let index = self.pool().clip_index(); ///// Enter crop mode
let length = self.pool().clips()[index].try_read().unwrap().length; //#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps<PoolCmd> {
*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar)); //let index = self.pool().clip_index();
Ok(None) //let length = self.pool().clips.read()[index].read().length;
} //*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
/// Exit crop mode, discard //Ok(None)
#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps<PoolCmd> { //}
if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() { ///// Exit crop mode, discard
*self.pool_mut().mode_mut() = None; //#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps<PoolCmd> {
} //if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() {
Ok(None) //*self.pool_mut().mode_mut() = None;
} //}
#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps<PoolCmd> { //Ok(None)
if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus)) //}
= self.pool_mut().mode_mut().clone() //#[command(CropSet = "crop/set")] fn crop_set (&mut self, _length: usize) -> Perhaps<PoolCmd> {
{ //if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
let old_length; //= self.pool_mut().mode_mut().clone()
{ //{
let clip = self.pool().clips()[clip].clone();//.try_write().unwrap(); //let old_length;
old_length = Some(clip.try_read().unwrap().length); //{
clip.try_write().unwrap().length = *length; //let clip = self.pool().clips.read()[clip].clone();//.write();
} //old_length = Some(clip.read().length);
*self.pool_mut().mode_mut() = None; //clip.write().length = *length;
return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l })) //}
} //*self.pool_mut().mode_mut() = None;
Ok(None) //return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l }))
} //}
#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps<PoolCmd> { //Ok(None)
if let Some(PoolMode::Length( //}
_clip, ref mut _length, ref mut focus //#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps<PoolCmd> {
)) = self.pool_mut().mode_mut().clone() { //if let Some(PoolMode::Length(
focus.next() //_clip, ref mut _length, ref mut focus
}; //)) = self.pool_mut().mode_mut().clone() {
Ok(None) //focus.next()
} //};
#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps<PoolCmd> { //Ok(None)
if let Some(PoolMode::Length( //}
_clip, ref mut _length, ref mut focus //#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps<PoolCmd> {
)) = self.pool_mut().mode_mut().clone() { //if let Some(PoolMode::Length(
focus.prev() //_clip, ref mut _length, ref mut focus
}; //)) = self.pool_mut().mode_mut().clone() {
Ok(None) //focus.prev()
} //};
#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps<PoolCmd> { //Ok(None)
if let Some(PoolMode::Length( //}
_clip, ref mut length, ref mut focus //#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps<PoolCmd> {
)) = self.pool_mut().mode_mut().clone() { //if let Some(PoolMode::Length(
match focus { //_clip, ref mut length, ref mut focus
ClipLengthFocus::Bar => { *length += 4 * PPQ }, //)) = self.pool_mut().mode_mut().clone() {
ClipLengthFocus::Beat => { *length += PPQ }, //match focus {
ClipLengthFocus::Tick => { *length += 1 }, //ClipLengthFocus::Bar => { *length += 4 * PPQ },
} //ClipLengthFocus::Beat => { *length += PPQ },
} //ClipLengthFocus::Tick => { *length += 1 },
Ok(None) //}
} //}
#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps<PoolCmd> { //Ok(None)
if let Some(PoolMode::Length( //}
_clip, ref mut length, ref mut focus //#[command(CropDec = "crop/dec")] fn crop_dec (&mut self) -> Perhaps<PoolCmd> {
)) = self.pool_mut().mode_mut().clone() { //if let Some(PoolMode::Length(
match focus { //_clip, ref mut length, ref mut focus
ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, //)) = self.pool_mut().mode_mut().clone() {
ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, //match focus {
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, //ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) },
} //ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) },
} //ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
Ok(None) //}
} //}
/// Enter rename mode //Ok(None)
#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps<PoolCmd> { //}
let index = self.pool().clip_index(); ///// Enter rename mode
let name = self.pool().clips()[index].try_read().unwrap().name.clone(); //#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps<PoolCmd> {
*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name)); //let index = self.pool().clip_index();
Ok(None) //let name = self.pool().clips.read()[index].read().name.clone();
} //*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
/// Exit rename mode, discard //Ok(None)
#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps<PoolCmd> { //}
if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { ///// Exit rename mode, discard
self.pool().clips()[clip].try_write().unwrap().name = old_name.clone().into(); //#[command(RenameCancel = "rename/cancel")] fn rename_cancel (&mut self) -> Perhaps<PoolCmd> {
} //if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
Ok(None) //self.pool().clips.read()[clip].write().name = old_name.clone().into();
} //}
/// Exit rename mode, update name //Ok(None)
#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps<PoolCmd> { //}
Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() { ///// Exit rename mode, update name
let old_name = old_name.clone(); //#[command(RenameConfirm = "rename/confirm")] fn rename_confirm (&mut self) -> Perhaps<PoolCmd> {
*self.pool_mut().mode_mut() = None; //Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
Some(PoolCmd::RenameSet { value: old_name }) //let old_name = old_name.clone();
} else { //*self.pool_mut().mode_mut() = None;
None //Some(PoolCmd::RenameSet { value: old_name })
})
}
#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCmd> {
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
self.pool().clips()[clip].try_write().unwrap().name = value.clone();
}
Ok(None)
}
///// Import from file
//#[command(Browse = "browse")]
//fn browse (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> {
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
//command.act(browse)?.map(|command|PoolCmd::Browse{command})
//} else { //} else {
//None //None
//}) //})
//} //}
/// Import clip to pool from file //#[command(RenameSet = "rename/set")] fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCmd> {
#[command(Import = "import")] //if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCmd> //self.pool().clips.read()[clip].write().name = value.clone();
where Self: Sized //}
{ //Ok(None)
let bytes = std::fs::read(&path)?; //}
let smf = Smf::parse(bytes.as_slice())?; /////// Import from file
let mut t = 0u32; ////#[command(Browse = "browse")]
let mut events = vec![]; ////fn browse (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> {
for track in smf.tracks.iter() { ////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
for event in track.iter() { ////command.act(browse)?.map(|command|PoolCmd::Browse{command})
t += event.delta.as_int(); ////} else {
if let TrackEventKind::Midi { channel, message } = event.kind { ////None
events.push((t, channel.as_int(), message)); ////})
} ////}
} ///// Import clip to pool from file
} //#[command(Import = "import")]
let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); //fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCmd>
for event in events.iter() { //where Self: Sized
clip.notes[event.0 as usize].push(event.2); //{
} //let bytes = std::fs::read(&path)?;
Ok(PoolCmd::Add { index, clip }.dispatch(self)?) //let smf = Smf::parse(bytes.as_slice())?;
} //let mut t = 0u32;
///// Export to file //let mut events = vec![];
//#[command(Export = "export")] //for track in smf.tracks.iter() {
//fn export (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> { //for event in track.iter() {
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() { //t += event.delta.as_int();
//command.act(browse)?.map(|command|PoolCmd::Export{command}) //if let TrackEventKind::Midi { channel, message } = event.kind {
//} else { //events.push((t, channel.as_int(), message));
//None //}
//}) //}
//}
//let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None);
//for event in events.iter() {
//clip.notes[event.0 as usize].push(event.2);
//}
//Ok(PoolCmd::Add { index, clip }.dispatch(self)?)
//}
/////// Export to file
////#[command(Export = "export")]
////fn export (&mut self, command: BrowseCmd) -> Perhaps<PoolCmd> {
////Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
////command.act(browse)?.map(|command|PoolCmd::Export{command})
////} else {
////None
////})
////}
//} //}
}
impl<T> ModeControl for T where T: HasModeStack impl<T> ModeControl for T where T: HasModeStack
+ Namespace<Vec<Arc<str>>> + Namespace<Vec<Arc<str>>> + Namespace<Arc<str>> + Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<usize>
{} {}
#[tek_proc::commands(ModeCmd)] pub trait ModeControl: HasModeStack #[tek_proc::commands(ModeCmd)] pub trait ModeControl: HasModeStack
+ Namespace<Vec<Arc<str>>> + Namespace<Vec<Arc<str>>> + Namespace<Arc<str>> + Namespace<usize>
+ Namespace<Arc<str>>
+ Namespace<usize>
{ {
#[command(Reset = "reset")] fn mode_reset (&mut self, modes: Vec<Arc<str>>) -> Perhaps<ModeCmd> { #[command(Reset = "reset")] fn mode_reset (&mut self, modes: Vec<Arc<str>>) -> Perhaps<ModeCmd> {
*self.modes_mut() = modes.into(); *self.modes_mut() = modes.into();
@ -744,36 +733,46 @@ impl<T> ModeControl for T where T: HasModeStack
} }
} }
impl<T: HasClips + Namespace<Option<bool>> + Namespace<Option<ItemTheme>> > MidiClipControl for T {} pub trait HasClip {
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClips fn clip (&self) -> RwLockReadGuard<'_, Option<Arc<RwLock<MidiClip>>>>;
+ Namespace<Option<bool>> fn clip_mut (&self) -> RwLockWriteGuard<'_, Option<Arc<RwLock<MidiClip>>>>;
+ Namespace<Option<ItemTheme>> }
impl<T: AsRef<Slot<MidiClip>>> HasClip for T {
fn clip (&self) -> RwLockReadGuard<'_, Option<Arc<RwLock<MidiClip>>>> {
self.as_ref().0.read()
}
fn clip_mut (&self) -> RwLockWriteGuard<'_, Option<Arc<RwLock<MidiClip>>>> {
self.as_ref().0.write()
}
}
impl<T> MidiClipControl for T where T: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>> {}
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClip
+ Namespace<Option<bool>> + Namespace<Option<ItemTheme>>
{ {
/// Create a clip at the current position if it does not already exist. /// Create a clip at the current position if it does not already exist.
#[command(Upsert = "upsert")] fn clip_upsert (&mut self) -> Perhaps<MidiClipCmd> { #[command(Upsert = "upsert")] fn clip_upsert (&mut self) -> Perhaps<MidiClipCmd> {
let clip = self.clip(); if self.clip().is_some() {
//let old = self.scenes[scene].clips[track].clone(); Ok(None)
//self.scenes[scene].clips[track] = clip; } else {
//old *self.clip_mut() = Some(Default::default());
todo!() Ok(None)
}
} }
#[command(SetColor = "color")] fn clip_set_color (&mut self, color: Option<ItemTheme>) -> Perhaps<MidiClipCmd> { #[command(SetColor = "color")] fn clip_set_color (&mut self, color: Option<ItemTheme>) -> Perhaps<MidiClipCmd> {
Ok(self.clip().map(|clip|MidiClipCmd::SetColor { Ok(self.clip().as_ref().map(|clip|MidiClipCmd::SetColor { color: Some(clip.write().set_color(color)) }))
color: Some(clip.write().set_color(color))
}))
} }
/// Toggle looping for the active clip /// Toggle looping for the active clip
#[command(SetLoop = "loop")] fn clip_toggle_loop (&mut self, looping: Option<bool>) -> Perhaps<MidiClipCmd> { #[command(SetLoop = "loop")] fn clip_toggle_loop (&mut self, looping: Option<bool>) -> Perhaps<MidiClipCmd> {
Ok(self.clip().map(|clip|MidiClipCmd::SetLoop { Ok(self.clip().as_ref().map(|clip|MidiClipCmd::SetLoop { looping: Some(clip.write().toggle_loop(looping)) }))
looping: Some(clip.write().toggle_loop(looping))
}))
} }
} }
impl<T: Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>> BrowseControl for T {}
impl<T> BrowseControl for T where T:
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>> {}
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl: #[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
Namespace<usize> + Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
Namespace<PathBuf> +
Namespace<Arc<str>>
{ {
/// Toggle visibility of browser /// Toggle visibility of browser
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> { #[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
@ -793,11 +792,9 @@ impl<T: Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>> BrowseContr
} }
} }
#[tek_proc::commands(AxisCmd)] #[tek_proc::commands(AxisCmd)] impl App {
impl App { /// Increment the given data axis.
/// Increment a given data axis. #[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
#[command(Inc = "inc")]
pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
Ok(match (&self.dialog, axis) { Ok(match (&self.dialog, axis) {
(Dialog::None, _) => todo!(), (Dialog::None, _) => todo!(),
(Dialog::Menu(_, _), ControlAxis::Y) => { (Dialog::Menu(_, _), ControlAxis::Y) => {
@ -807,9 +804,8 @@ impl App {
_ => todo!() _ => todo!()
}) })
} }
/// Decrement a given data axis. /// Decrement the given data axis.
#[command(Dec = "dec")] #[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
Ok(match (&self.dialog, axis) { Ok(match (&self.dialog, axis) {
(Dialog::None, _) => None, (Dialog::None, _) => None,
(Dialog::Menu(_, _), ControlAxis::Y) => { (Dialog::Menu(_, _), ControlAxis::Y) => {

File diff suppressed because it is too large Load diff

2
tengri

@ -1 +1 @@
Subproject commit 79f9cfcbf93db626d031ab86abbbdf5c29d80a48 Subproject commit 7237121a508556ee98714a6af387e325a371e368