mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 10:46:43 +02:00
This commit is contained in:
parent
186b81f29c
commit
ca774cca41
21 changed files with 2160 additions and 2191 deletions
|
|
@ -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!()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,8 @@ pub(crate) use ::{
|
|||
RwLock,
|
||||
RwLockReadGuard,
|
||||
RwLockWriteGuard,
|
||||
ArcRwLockReadGuard,
|
||||
ArcRwLockWriteGuard,
|
||||
RawRwLock,
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,18 +1,5 @@
|
|||
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.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -130,88 +117,3 @@ const MIDI_PANIC: MidiMessage = MidiMessage::Controller {
|
|||
pub type MidiData = Vec<Vec<MidiMessage>>;
|
||||
impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
|
||||
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!()
|
||||
}
|
||||
|
|
|
|||
214
src/midi_edit.rs
214
src/midi_edit.rs
|
|
@ -26,9 +26,10 @@ impl<T> HasEditor for T where T: AsRef<Option<MidiEditor>> + AsMut<Option<MidiEd
|
|||
/// ```
|
||||
/// use std::sync::Arc;
|
||||
/// use parking_lot::RwLock;
|
||||
/// let clip = tek::MidiClip::stop_all();
|
||||
/// let mode = tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip))));
|
||||
/// let mut editor = tek::MidiEditor { mode, size: Default::default() };
|
||||
/// use tek::{MidiClip, MidiEditor, PianoHorizontal};
|
||||
/// let clip = MidiClip::stop_all();
|
||||
/// let mode = PianoHorizontal::default();
|
||||
/// let mut editor = MidiEditor { mode, size: Default::default() };
|
||||
///
|
||||
/// let _ = editor.put_note(true);
|
||||
/// let _ = editor.put_note(false);
|
||||
|
|
@ -43,7 +44,7 @@ pub struct MidiEditor {
|
|||
}
|
||||
|
||||
impl_default!(MidiEditor: Self {
|
||||
mode: PianoHorizontal::new(None),
|
||||
mode: Default::default(),
|
||||
size: Sizer(
|
||||
Arc::new(0usize.into()),
|
||||
Arc::new(0usize.into()),
|
||||
|
|
@ -54,14 +55,14 @@ impl MidiEditor {
|
|||
/// Put note at current position
|
||||
pub fn put_note (&mut self, advance: bool) {
|
||||
let mut redraw = false;
|
||||
if let Some(clip) = self.clip() {
|
||||
let mut clip = clip.try_write().unwrap();
|
||||
if let Some(clip) = &*self.mode.clip.read() {
|
||||
let note_start = self.get_time_pos();
|
||||
let note_pos = self.get_note_pos();
|
||||
let note_len = self.get_note_len();
|
||||
let note_end = note_start + (note_len.saturating_sub(1));
|
||||
let key: u7 = u7::from(note_pos as u8);
|
||||
let vel: u7 = 100.into();
|
||||
let mut clip = clip.write();
|
||||
let length = clip.length;
|
||||
let note_end = note_end % length;
|
||||
let note_on = MidiMessage::NoteOn { key, vel };
|
||||
|
|
@ -81,21 +82,51 @@ impl MidiEditor {
|
|||
self.mode.redraw();
|
||||
}
|
||||
}
|
||||
fn clip_length (&self) -> usize { self.clip().as_ref().map(|p|p.try_read().unwrap().length).unwrap_or(1) }
|
||||
fn note_length (&self) -> usize { self.get_note_len() }
|
||||
fn note_pos (&self) -> usize { self.get_note_pos() }
|
||||
fn note_pos_next (&self) -> usize { self.get_note_pos() + 1 }
|
||||
fn note_pos_next_octave (&self) -> usize { self.get_note_pos() + 12 }
|
||||
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 clip_length (&self) -> usize {
|
||||
self.mode.clip.map_inner(|p|p.length).unwrap_or(1)
|
||||
}
|
||||
fn note_length (&self) -> usize {
|
||||
self.get_note_len()
|
||||
}
|
||||
fn note_pos (&self) -> usize {
|
||||
self.get_note_pos()
|
||||
}
|
||||
fn note_pos_next (&self) -> usize {
|
||||
self.get_note_pos() + 1
|
||||
}
|
||||
fn note_pos_next_octave (&self) -> usize {
|
||||
self.get_note_pos() + 12
|
||||
}
|
||||
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_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) }
|
||||
|
|
@ -113,39 +144,6 @@ impl MidiEditor {
|
|||
self.get_time_pos().overflowing_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 {
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||
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
|
||||
fn autoscroll (&self) {
|
||||
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 {
|
||||
fn note_len (&self) -> &AtomicUsize { self.mode.note_len() }
|
||||
fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() }
|
||||
|
|
@ -263,31 +242,15 @@ impl TimePoint for MidiEditor {
|
|||
}
|
||||
|
||||
impl MidiViewer for MidiEditor {
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) { self.mode.buffer_size(clip) }
|
||||
fn redraw (&self) { self.mode.redraw() }
|
||||
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) }
|
||||
}
|
||||
|
||||
impl Draw<Tui> for MidiEditor {
|
||||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
self.autoscroll();
|
||||
/*self.autozoom();*/
|
||||
self.size.of(&self.mode).draw(to)
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
|
||||
self.mode.buffer_size(clip)
|
||||
}
|
||||
fn redraw (&self) {
|
||||
self.mode.redraw()
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Is a MIDI editor currently focused?
|
||||
///
|
||||
/// ```
|
||||
/// tek::App::default().editor_focused();
|
||||
/// ```
|
||||
pub fn editor_focused (&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Toggle MIDI editor.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -295,39 +258,50 @@ impl App {
|
|||
/// ```
|
||||
pub fn toggle_editor (&mut self, value: Option<bool>) {
|
||||
//FIXME: self.editing.store(value.unwrap_or_else(||!self.is_editing()), Relaxed);
|
||||
let value = value.unwrap_or_else(||!self.editor().is_some());
|
||||
if value {
|
||||
// Create new clip in pool when entering empty cell
|
||||
if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& 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());
|
||||
//Some(clip)
|
||||
} else {
|
||||
//None
|
||||
if value.unwrap_or_else(||!self.editor().is_some()) {
|
||||
self.toggle_editor_on()
|
||||
} else {
|
||||
self.toggle_editor_off()
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_editor_on (&mut self) {
|
||||
// Create new clip in pool when entering empty cell
|
||||
if let Selection::TrackClip { track: track_index, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& scene.clips.get(track_index).is_none()
|
||||
&& let Some(track) = self.project.tracks.get_mut(track_index)
|
||||
{
|
||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||
let color = ItemColor::random_near(color, 0.2).into();
|
||||
let clip: MidiClip = MidiClip { color, ..Default::default() };
|
||||
let clip = self.project.clips.append(Some(clip));
|
||||
// autocolor: new clip colors from scene and track color
|
||||
if let Some(editor) = &mut self.project.editor {
|
||||
*editor.mode.clip.write() = clip.0.read().clone();
|
||||
editor.mode.color = clip.0.read().as_ref().map(|p|p.read().color)
|
||||
.unwrap_or(ItemTheme::G[64]);
|
||||
}
|
||||
} else if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
*scene.clips.slot(track_index).write() = clip.0.read().clone();
|
||||
//Some(clip)
|
||||
} else {
|
||||
//None
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_editor_off (&mut self) {
|
||||
if let Selection::TrackClip { track, scene } = *self.selection()
|
||||
&& let Some(scene) = self.project.scenes.get_mut(scene)
|
||||
&& let Some(slot) = scene.clips.get_mut(track)
|
||||
&& let Some(clip) = slot.as_mut()
|
||||
{
|
||||
// Remove clip from arrangement when exiting empty clip editor
|
||||
let mut swapped = None;
|
||||
if clip.try_read().unwrap().count_midi_messages() == 0 {
|
||||
std::mem::swap(&mut swapped, slot);
|
||||
if let Some(count) = scene.clips.slot(track).read().as_ref().map(|c|c.read().count_midi_messages())
|
||||
&& count == 0
|
||||
{
|
||||
std::mem::swap(&mut swapped, &mut scene.clips.slot(track).write());
|
||||
}
|
||||
if let Some(clip) = swapped {
|
||||
self.project.pool.delete_clip(&clip.try_read().unwrap());
|
||||
self.project.clips.remove(&*clip.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,25 +12,12 @@ pub struct OctaveVertical {
|
|||
}
|
||||
|
||||
impl OctaveVertical {
|
||||
fn color (&self, pitch: usize) -> Color {
|
||||
pub fn color (&self, pitch: usize) -> Color {
|
||||
let pitch = pitch % 12;
|
||||
self.colors[if self.on[pitch] { 2 } else { match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } }]
|
||||
}
|
||||
}
|
||||
|
||||
impl 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 {
|
||||
on: [false; 12], colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,245 +5,34 @@ use crate::*;
|
|||
/// ```
|
||||
/// let piano = tek::PianoHorizontal::default();
|
||||
/// ```
|
||||
#[derive(Clone, Default)]
|
||||
pub struct PianoHorizontal {
|
||||
pub clip: Option<Arc<RwLock<MidiClip>>>,
|
||||
#[derive(Clone)] pub struct PianoHorizontal {
|
||||
pub clip: ClipSlot,
|
||||
/// Buffer where the whole clip is rerendered on change
|
||||
pub buffer: Arc<RwLock<BigBuffer>>,
|
||||
/// Size of actual notes area
|
||||
pub size: Sizer,
|
||||
pub size: Sizer,
|
||||
/// The display window
|
||||
pub range: MidiSelection,
|
||||
pub range: MidiSelection,
|
||||
/// The note cursor
|
||||
pub point: MidiCursor,
|
||||
pub point: MidiCursor,
|
||||
/// The highlight color palette
|
||||
pub color: ItemTheme,
|
||||
pub color: ItemTheme,
|
||||
/// Width of the keyboard
|
||||
pub keys_width: u16,
|
||||
}
|
||||
|
||||
impl_has!(Sizer: |self: PianoHorizontal| self.size);
|
||||
|
||||
impl Draw<Tui> for PianoHorizontal {
|
||||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
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 {
|
||||
impl Default for PianoHorizontal {
|
||||
fn default () -> Self {
|
||||
let size = Sizer::default();
|
||||
let mut range = MidiSelection::from((12, true));
|
||||
range.time_axis = size.0.clone();
|
||||
range.note_axis = size.1.clone();
|
||||
let piano = Self {
|
||||
keys_width: 5,
|
||||
size,
|
||||
range,
|
||||
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]),
|
||||
};
|
||||
let piano = Self { keys_width: 5, size, range, ..Default::default() };
|
||||
piano.redraw();
|
||||
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(¬e_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 {
|
||||
|
|
@ -269,15 +58,12 @@ impl TimePoint 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.
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize) {
|
||||
(clip.length / self.range.time_zoom().load(Relaxed), 128)
|
||||
}
|
||||
fn redraw (&self) {
|
||||
*self.buffer.try_write().unwrap() = if let Some(clip) = self.clip.as_ref() {
|
||||
let clip = clip.try_read().unwrap();
|
||||
*self.buffer.write() = self.clip.map_inner(|clip|{
|
||||
let buf_size = self.buffer_size(&clip);
|
||||
let mut buffer = BigBuffer::from(buf_size);
|
||||
let time_zoom = self.get_time_zoom();
|
||||
|
|
@ -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_fg(&mut buffer, &clip, time_zoom);
|
||||
buffer
|
||||
} else {
|
||||
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();
|
||||
}).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PianoHorizontal {
|
||||
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
let buffer = self.buffer.try_read().unwrap();
|
||||
let buffer = self.buffer.read();
|
||||
f.debug_struct("PianoHorizontal")
|
||||
.field("time_zoom", &self.range.time_zoom)
|
||||
.field("buffer", &format!("{}x{}", buffer.width, buffer.height))
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ pub trait HasPlayClip: HasClock {
|
|||
fn pulses_since_start_looped (&self) -> Option<(f64, f64)> {
|
||||
if let Some((started, Some(clip))) = self.play_clip().as_ref() {
|
||||
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 elapsed = (elapsed as usize % length) as f64;
|
||||
return Some((times, elapsed))
|
||||
|
|
@ -34,7 +34,7 @@ pub trait HasPlayClip: HasClock {
|
|||
|
||||
fn play_status (&self) -> impl Draw<Tui> {
|
||||
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)
|
||||
} else {
|
||||
("".into(), ItemTheme::G[64].into())
|
||||
|
|
@ -55,7 +55,7 @@ pub trait HasPlayClip: HasClock {
|
|||
let mut color = ItemTheme::G[64];
|
||||
let clock = self.clock();
|
||||
if let Some((t, Some(clip))) = self.next_clip() {
|
||||
let clip = clip.try_read().unwrap();
|
||||
let clip = clip.read();
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
time = {
|
||||
|
|
@ -69,7 +69,7 @@ pub trait HasPlayClip: HasClock {
|
|||
}
|
||||
}.into()
|
||||
} else if let Some((t, Some(clip))) = self.play_clip() {
|
||||
let clip = clip.try_read().unwrap();
|
||||
let clip = clip.read();
|
||||
if clip.looped {
|
||||
name = clip.name.clone();
|
||||
color = clip.color.clone();
|
||||
|
|
|
|||
295
src/proj.rs
295
src/proj.rs
|
|
@ -3,14 +3,12 @@ impl_as_ref!(Option<Project>: |self: App|todo!());
|
|||
impl_as_mut!(Option<Project>: |self: App|todo!());
|
||||
pub trait HasProject: AsRef<Option<Project>> + AsMut<Option<Project>> {}
|
||||
impl<T: AsRef<Option<Project>> + AsMut<Option<Project>>> HasProject for T {}
|
||||
|
||||
/// Arranger.
|
||||
///
|
||||
/// ```
|
||||
/// let arranger = tek::Project::default();
|
||||
/// ```
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Project {
|
||||
#[derive(Default, Debug)] pub struct Project {
|
||||
/// JACK client handle.
|
||||
pub jack: Jack<'static>,
|
||||
/// Project name.
|
||||
|
|
@ -27,33 +25,33 @@ pub struct Project {
|
|||
/// Selected UI element
|
||||
pub selection: Selection,
|
||||
/// 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
|
||||
#[cfg(feature = "clock")] pub clock: Clock,
|
||||
#[cfg(feature = "clock")] pub clock: Clock,
|
||||
/// Allows one MIDI clip to be edited
|
||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||
/// List of global midi inputs
|
||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||
/// List of global midi outputs
|
||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||
/// List of global audio inputs
|
||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||
/// List of global audio outputs
|
||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||
/// Last track number (to avoid duplicate port names)
|
||||
#[cfg(feature = "track")] pub track_last: usize,
|
||||
#[cfg(feature = "track")] pub track_last: usize,
|
||||
/// List of tracks
|
||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||
/// Scroll offset of tracks
|
||||
#[cfg(feature = "track")] pub track_scroll: usize,
|
||||
#[cfg(feature = "track")] pub track_scroll: usize,
|
||||
/// List of scenes
|
||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||
/// Scroll offset of scenes
|
||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
|
||||
/// Create a new arrangement.
|
||||
pub fn new (
|
||||
jack: &Jack<'static>,
|
||||
|
|
@ -109,7 +107,7 @@ impl Project {
|
|||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasJack<'static> for Project { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl_has!(Jack<'static>: |self: Project| self.jack);
|
||||
impl_has!(Sizer: |self: Project| self.size);
|
||||
impl_has!(Clock: |self: Project| self.clock);
|
||||
|
|
@ -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_ref!(Option<MidiEditor>: |self: Project| &self.editor);
|
||||
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut self.editor);
|
||||
impl HasJack<'static> for Project {
|
||||
fn jack (&self) -> &Jack<'static> { &self.jack }
|
||||
}
|
||||
impl HasClips for Project {
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> { todo!() }
|
||||
fn clip_mut (&self) -> &mut Option<Arc<RwLock<MidiClip>>> { todo!() }
|
||||
fn clips (&self) -> RwLockReadGuard<'_, Clips> { todo!() }
|
||||
fn clips_mut (&self) -> RwLockWriteGuard<'_, Clips> { todo!() }
|
||||
}
|
||||
|
||||
//impl HasClip for Project {
|
||||
//fn clip (&self) -> Option<RwLockReadGuard<'_, Option<Arc<RwLock<MidiClip>>>>> {
|
||||
//match self.selection {
|
||||
//Selection::Pool(index) =>
|
||||
//self.clips.read(index),
|
||||
//Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) =>
|
||||
//scene.clips.read(track),
|
||||
//_ =>
|
||||
//None
|
||||
//}
|
||||
//}
|
||||
//fn clip_mut (&self) -> RwLockWriteGuard<'_, Option<Arc<RwLock<MidiClip>>>> {
|
||||
//self.as_ref().0.write()
|
||||
//}
|
||||
//}
|
||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||
) -> Perhaps<U> {
|
||||
|
|
@ -140,7 +143,6 @@ pub fn swap_value <T: Clone + PartialEq, U> (
|
|||
Ok(Some(returned(value)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_bool <U> (
|
||||
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
||||
) -> Perhaps<U> {
|
||||
|
|
@ -152,3 +154,238 @@ pub fn toggle_bool <U> (
|
|||
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() }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -374,27 +374,6 @@ impl_time_unit!(Pulse);
|
|||
impl_time_unit!(Bpm);
|
||||
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.
|
||||
///
|
||||
/// Performance optimization.
|
||||
|
|
|
|||
236
src/proj_pool.rs
236
src/proj_pool.rs
|
|
@ -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),
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
|
@ -13,46 +13,38 @@ use super::*;
|
|||
/// Identifying color of scene
|
||||
pub color: ItemTheme,
|
||||
/// Clips in scene, one per track
|
||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||
pub clips: Pool<MidiClip>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
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
|
||||
pub fn pulses (&self) -> usize {
|
||||
self.clips.iter().fold(0, |a, p|{
|
||||
a.max(p.as_ref().map(|q|q.try_read().unwrap().length).unwrap_or(0))
|
||||
self.clips.read().iter().fold(0, |a, p|{
|
||||
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.
|
||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
||||
.all(|(track_index, clip)|match clip {
|
||||
Some(c) => tracks
|
||||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||
*clip.try_read().unwrap() == *c.try_read().unwrap()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
// Not "playing" if there are no clips
|
||||
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
|
||||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||
*clip.read() == *c.read()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -111,8 +103,8 @@ pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
|||
Self: HasTracks
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
|
||||
clips: Pool::from(vec![None;self.tracks().len()]),
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
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 {
|
||||
fn h_scenes (&self) -> u16;
|
||||
fn w_side (&self) -> u16;
|
||||
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()
|
||||
}
|
||||
}
|
||||
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() } }
|
||||
|
|
|
|||
|
|
@ -13,39 +13,29 @@ pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
|||
fn selection_mut (&mut self) -> &mut Selection {
|
||||
self.as_mut()
|
||||
}
|
||||
|
||||
/// Get the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
||||
#[cfg(feature = "track")] fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks().get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
||||
#[cfg(feature = "track")] fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks_mut().get_mut(index)
|
||||
}
|
||||
|
||||
/// Get the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
||||
#[cfg(feature = "scene")] fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes().get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
||||
#[cfg(feature = "scene")] fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes_mut().get_mut(index)
|
||||
}
|
||||
|
||||
/// Get the active clip
|
||||
#[cfg(feature = "clip")]
|
||||
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
|
||||
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||
#[cfg(feature = "clip")] fn selected_clip (&self) -> Option<Slot<MidiClip>> where Self: HasScenes + HasTracks {
|
||||
self.selected_scene()?.clips.read().get(self.selection().track()?).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +55,11 @@ pub enum Selection {
|
|||
#[cfg(feature = "scene")] Scene(usize),
|
||||
/// A track is selected.
|
||||
#[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 },
|
||||
/// A track's MIDI input connection is selected.
|
||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||
|
|
@ -90,9 +84,10 @@ impl Selection {
|
|||
#[cfg(feature = "track")] Track(t) =>
|
||||
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
||||
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
||||
(Some(_), Some(s)) => match s.clip(*track) {
|
||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.try_read().unwrap().name),
|
||||
None => format!("T{track} S{scene}: Empty")
|
||||
(Some(_), Some(s)) => match &s.clips.get(*track) {
|
||||
None => format!("T{track} S{scene}: Empty"),
|
||||
Some(clip) => format!("T{track} S{scene} C{}",
|
||||
clip.as_ref().map(|clip|clip.read().name.clone()).unwrap_or_default()),
|
||||
},
|
||||
_ => format!("T{track} S{scene}: Empty"),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,24 +40,30 @@ impl Track {
|
|||
..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] {
|
||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
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> {
|
||||
self.sequencer.enqueue_next(None);
|
||||
Ok(None)
|
||||
|
|
@ -165,26 +171,13 @@ pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrack
|
|||
|x|x.to_string().into()
|
||||
);
|
||||
*self.tracks_last_mut() += 1;
|
||||
let track = Track {
|
||||
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()
|
||||
};
|
||||
let track = Track::new_added(self.jack(), Some(self.clock()), &name, color, None, mins, mouts)?;
|
||||
self.tracks_mut().push(track);
|
||||
let len = self.tracks().len();
|
||||
let index = len - 1;
|
||||
for scene in self.scenes_mut().iter_mut() {
|
||||
while scene.clips.len() < len {
|
||||
scene.clips.push(None);
|
||||
scene.clips.append(None);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
fn track_scroll (&self) -> usize;
|
||||
fn tracks_last (&self) -> usize;
|
||||
|
|
@ -546,24 +229,3 @@ impl HasTrackScroll for App {
|
|||
&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())
|
||||
//}
|
||||
|
|
|
|||
391
src/tek.rs
391
src/tek.rs
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
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_piano; pub use self::midi_piano::*;
|
||||
|
|
@ -45,12 +41,13 @@ mod proj_port; pub use self::proj_port::*;
|
|||
mod proj_scene; pub use self::proj_scene::*;
|
||||
mod proj_select; pub use self::proj_select::*;
|
||||
mod proj_track; pub use self::proj_track::*;
|
||||
mod proj_pool; pub use self::proj_pool::*;
|
||||
|
||||
mod tek_browse; pub use self::tek_browse::*;
|
||||
mod tek_cli; pub use self::tek_cli::*;
|
||||
mod tek_ctrl; pub use self::tek_ctrl::*;
|
||||
mod tek_view; pub use self::tek_view::*;
|
||||
mod tek_bind; pub use self::tek_bind::*;
|
||||
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_mode; pub use self::tek_mode::*;
|
||||
mod tek_view; pub use self::tek_view::*;
|
||||
|
||||
#[cfg(feature = "lv2")] mod device_lv2; #[cfg(feature = "lv2")] pub use self::device_lv2::*;
|
||||
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*;
|
||||
|
|
@ -126,16 +123,18 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
|||
#[namespace(Arc<[Connect]> get_arc_array_connect)]
|
||||
#[namespace(Arc<str> get_arc_str)]
|
||||
#[namespace(Color get_color)]
|
||||
#[namespace(ControlAxis get_axis)]
|
||||
#[namespace(Dialog get_dialog)]
|
||||
#[namespace(ControlAxis ControlAxis::get_axis)]
|
||||
#[namespace(Dialog Dialog::get_dialog)]
|
||||
#[namespace(ItemColor)]
|
||||
#[namespace(ItemTheme)]
|
||||
#[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<str>> get_opt_arc_str)]
|
||||
#[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<u16> get_opt_u16)]
|
||||
#[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/add_device" => matches!(state.dialog, Dialog::Device(..)),
|
||||
":focused/browser" => state.dialog.browser().is_some(),
|
||||
":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))),
|
||||
":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))),
|
||||
":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))),
|
||||
":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))),
|
||||
":focused/clip" => !state.editor_focused() && matches!(state.selection(), Selection::TrackClip{..}),
|
||||
":focused/track" => !state.editor_focused() && matches!(state.selection(), Selection::Track(..)),
|
||||
":focused/scene" => !state.editor_focused() && matches!(state.selection(), Selection::Scene(..)),
|
||||
":focused/mix" => !state.editor_focused() && matches!(state.selection(), Selection::Mix),
|
||||
//":focused/pool/import" => matches!(state.project.pool.mode, Some(PoolMode::Import(..))),
|
||||
//":focused/pool/export" => matches!(state.project.pool.mode, Some(PoolMode::Export(..))),
|
||||
//":focused/pool/rename" => matches!(state.project.pool.mode, Some(PoolMode::Rename(..))),
|
||||
//":focused/pool/length" => matches!(state.project.pool.mode, Some(PoolMode::Length(..))),
|
||||
":focused/clip" => matches!(state.selection(), Selection::TrackClip{..}),
|
||||
":focused/track" => matches!(state.selection(), Selection::Track(..)),
|
||||
":focused/scene" => matches!(state.selection(), Selection::Scene(..)),
|
||||
":focused/mix" => matches!(state.selection(), Selection::Mix),
|
||||
_ => return err!("not bool: {word}")
|
||||
})).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>> {
|
||||
Ok(None)
|
||||
|
|
@ -370,11 +358,16 @@ fn get_vec_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Vec<Arc<str>>> {
|
|||
todo!()
|
||||
}
|
||||
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Sizer: |self: App|self.size);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Pool: |self: App|self.project.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
|
||||
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_as_ref!(Slot<MidiClip>: |self: App|todo!());
|
||||
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
|
||||
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
|
||||
|
||||
|
|
@ -383,21 +376,6 @@ primitive!(u16: try_to_u16);
|
|||
primitive!(usize: try_to_usize);
|
||||
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 {
|
||||
#[cfg(feature = "prof2")] profiling::scope!("App::tui_keys!");
|
||||
Self::input_cmd(self, input)
|
||||
|
|
@ -478,86 +456,20 @@ impl App {
|
|||
I, IFine,
|
||||
}
|
||||
|
||||
/// The [Draw] implementation for [App] handles the loaded view,
|
||||
/// which is defined in terms of [dizzle] DSL.
|
||||
///
|
||||
/// 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 ControlAxis {
|
||||
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}")
|
||||
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
|
||||
if let Some(e) = self.error.try_read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_modes (&self, to: &mut Tui) -> Usually<()> {
|
||||
for name in self.modes.read().iter() {
|
||||
let mut error = false;
|
||||
if let Some(mode) = self.config.get_mode(name).as_ref().map(Arc::clone) {
|
||||
for (index, view) in mode.view.iter().enumerate() {
|
||||
match (view.render)(self, to) {
|
||||
Ok(None) => {},
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let message = format!(
|
||||
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
|
||||
&mode.name,
|
||||
&view.source
|
||||
);
|
||||
*self.error.try_write().unwrap() = Some(message.into());
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
const MIN_WIDTH: usize;
|
||||
/// Increment track width.
|
||||
|
|
@ -566,46 +478,6 @@ pub trait HasWidth {
|
|||
fn width_dec (&mut self);
|
||||
}
|
||||
|
||||
pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
||||
|
||||
eval_word(&src, |word|Ok(match word {
|
||||
|
||||
":none" => Dialog::None,
|
||||
|
||||
":options" => Dialog::Options,
|
||||
|
||||
":device" => Dialog::Device(0),
|
||||
":device/prev" => Dialog::Device(0),
|
||||
":device/next" => Dialog::Device(0),
|
||||
|
||||
":help" => Dialog::Help(0),
|
||||
":save" => Dialog::Browse(
|
||||
BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":load" => Dialog::Browse(
|
||||
BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/clip" => Dialog::Browse(
|
||||
BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/clip" => Dialog::Browse(
|
||||
BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/sample" => Dialog::Browse(
|
||||
BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/sample" => Dialog::Browse(
|
||||
BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
_ => return Err(format!("not a dialog: {}", src.src()?.unwrap_or("<???>")).into())
|
||||
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
/// Various possible dialog modes.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -623,6 +495,46 @@ pub fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
|||
|
||||
impl Dialog {
|
||||
|
||||
fn get_dialog <'a> (_: &App, src: impl Language) -> Perhaps<Dialog> {
|
||||
|
||||
eval_word(&src, |word|Ok(match word {
|
||||
|
||||
":none" => Dialog::None,
|
||||
|
||||
":options" => Dialog::Options,
|
||||
|
||||
":device" => Dialog::Device(0),
|
||||
":device/prev" => Dialog::Device(0),
|
||||
":device/next" => Dialog::Device(0),
|
||||
|
||||
":help" => Dialog::Help(0),
|
||||
":save" => Dialog::Browse(
|
||||
BrowseTarget::SaveProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
":load" => Dialog::Browse(
|
||||
BrowseTarget::LoadProject,
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/clip" => Dialog::Browse(
|
||||
BrowseTarget::ImportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/clip" => Dialog::Browse(
|
||||
BrowseTarget::ExportClip(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
":import/sample" => Dialog::Browse(
|
||||
BrowseTarget::ImportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
":export/sample" => Dialog::Browse(
|
||||
BrowseTarget::ExportSample(Default::default()),
|
||||
Browse::new(None).unwrap().into()),
|
||||
|
||||
_ => return Err(format!("not a dialog: {}", src.src()?.unwrap_or("<???>")).into())
|
||||
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::Dialog::welcome();
|
||||
/// ```
|
||||
|
|
@ -758,29 +670,142 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
|||
/// ```
|
||||
/// let item: tek::MenuItem = Default::default();
|
||||
/// ```
|
||||
#[derive(Clone)] pub struct MenuItem(
|
||||
pub Arc<str>,
|
||||
pub Arc<[AppCmd]>,
|
||||
);
|
||||
|
||||
#[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>);
|
||||
impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) });
|
||||
impl_default!(MenuItem: Self::new("", []));
|
||||
impl PartialEq for MenuItem { fn eq (&self, other: &Self) -> bool { self.0 == other.0 } }
|
||||
impl MenuItem {
|
||||
pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCmd; N]) -> Self {
|
||||
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 {
|
||||
//fn as_ref (&self) -> &Arc<[MenuItem]> {
|
||||
//&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 }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
554
src/tek_ctrl.rs
554
src/tek_ctrl.rs
|
|
@ -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: MidiEditCmd| AppCmd::MidiEdit { 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: SceneCmd| AppCmd::Scene { 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> {
|
||||
Ok(command.dispatch(self)?.map(Into::into))
|
||||
}
|
||||
#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps<AppCmd> {
|
||||
Ok(command.dispatch(self)?.map(Into::into))
|
||||
}
|
||||
//#[command(Pool = "pool")] pub fn cmd_pool (&mut self, command: PoolCmd) -> Perhaps<AppCmd> {
|
||||
//Ok(command.dispatch(self)?.map(Into::into))
|
||||
//}
|
||||
#[command(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
|
||||
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
|
||||
+ Namespace<u32>
|
||||
+ Namespace<f64>
|
||||
+ Namespace<Option<u32>>
|
||||
+ Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>
|
||||
{
|
||||
#[command(SeekUsec = "usec")] fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCmd> {
|
||||
self.clock().playhead.update_from_usec(usec);
|
||||
|
|
@ -153,7 +152,7 @@ impl<T: HasTracks
|
|||
+ Namespace<Option<Arc<str>>>
|
||||
+ Namespace<Option<ItemTheme>>
|
||||
+ Namespace<Option<Arc<[Connect]>>>
|
||||
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||
+ Namespace<ClipPool>
|
||||
> TracksControl for T {}
|
||||
|
||||
#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks
|
||||
|
|
@ -166,7 +165,7 @@ impl<T: HasTracks
|
|||
+ Namespace<Option<Arc<str>>>
|
||||
+ Namespace<Option<ItemTheme>>
|
||||
+ Namespace<Option<Arc<[Connect]>>>
|
||||
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||
+ Namespace<ClipPool>
|
||||
{
|
||||
/// Stop all playing clips
|
||||
#[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> {
|
||||
|
|
@ -176,12 +175,12 @@ impl<T: HasTracks
|
|||
Ok(None)
|
||||
}
|
||||
/// Launch multiple clips
|
||||
#[command(Launch = "launch")] fn tracks_launch (
|
||||
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
|
||||
) -> Perhaps<TracksCmd> {
|
||||
#[command(Launch = "launch")] fn tracks_launch (&mut self, clips: Option<ClipPool>)
|
||||
-> Perhaps<TracksCmd>
|
||||
{
|
||||
if let Some(clips) = clips {
|
||||
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
||||
track.sequencer.enqueue_next(clip.as_ref());
|
||||
for (clip, track) in clips.0.read().iter().zip(self.tracks_mut()) {
|
||||
track.sequencer.enqueue_next(clip);
|
||||
}
|
||||
} else {
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
|
|
@ -322,15 +321,11 @@ impl<T: HasScenes
|
|||
}
|
||||
|
||||
impl<T: HasScene
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<ItemTheme>
|
||||
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
||||
> SceneControl for T {}
|
||||
|
||||
#[tek_proc::commands(SceneCmd)] pub trait SceneControl: HasScene
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<ItemTheme>
|
||||
+ Namespace<usize> + Namespace<Arc<str>> + Namespace<ItemTheme>
|
||||
{
|
||||
#[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
||||
todo!()
|
||||
|
|
@ -357,23 +352,20 @@ impl<T: HasScene
|
|||
#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler
|
||||
+ Namespace<usize>
|
||||
{
|
||||
|
||||
#[command(RecordToggle = "rec-toggle")]
|
||||
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
|
||||
#[command(RecToggle = "rec-toggle")] fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCmd>
|
||||
where Self: Sized
|
||||
{
|
||||
let sampler = self.sampler_mut();
|
||||
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
|
||||
if recording != Some(slot) {
|
||||
SamplerCmd::RecordBegin { slot }.dispatch(self)
|
||||
SamplerCmd::RecBegin { slot }.dispatch(self)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[command(RecordBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||
#[command(RecBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||
let sampler = self.sampler_mut();
|
||||
sampler.recording = Some((
|
||||
slot,
|
||||
|
|
@ -383,7 +375,7 @@ impl<T: HasScene
|
|||
));
|
||||
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 _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
||||
|
|
@ -391,14 +383,14 @@ impl<T: HasScene
|
|||
}); // TODO: undo
|
||||
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;
|
||||
Ok(None)
|
||||
}
|
||||
#[command(PlaySample = "sample-play")] fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||
let sampler = self.sampler_mut();
|
||||
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)
|
||||
}
|
||||
|
|
@ -414,7 +406,7 @@ impl<T: HasEditor
|
|||
+ Namespace<bool>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Option<u32>>
|
||||
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
||||
+ Namespace<ClipSlot>
|
||||
> MidiEditControl for T {}
|
||||
|
||||
#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasEditor
|
||||
|
|
@ -423,7 +415,7 @@ impl<T: HasEditor
|
|||
+ Namespace<bool>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Option<u32>>
|
||||
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
||||
+ Namespace<ClipSlot>
|
||||
{
|
||||
#[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> {
|
||||
if self.is_editing() {
|
||||
|
|
@ -433,9 +425,9 @@ impl<T: HasEditor
|
|||
}
|
||||
Ok(Some(MidiEditCmd::Toggle))
|
||||
}
|
||||
#[command(Show = "show")] fn edit_show (&mut self, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<MidiEditCmd> {
|
||||
Ok(self.editor_mut().as_mut().map(|mut editor|{
|
||||
editor.set_clip(clip.as_ref());
|
||||
#[command(Show = "show")] fn edit_show (&mut self, clip: ClipSlot) -> Perhaps<MidiEditCmd> {
|
||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||
editor.mode.clip.0 = clip.0;
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
|
|
@ -447,7 +439,7 @@ impl<T: HasEditor
|
|||
}).flatten())
|
||||
}
|
||||
#[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.redraw();
|
||||
None
|
||||
|
|
@ -505,230 +497,227 @@ impl<T: HasEditor
|
|||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||
}
|
||||
|
||||
impl<T: HasPool
|
||||
+ Namespace<bool>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<PathBuf>
|
||||
+ Namespace<MidiClip>
|
||||
+ Namespace<ItemColor>
|
||||
+ Namespace<BrowseCmd>
|
||||
> PoolControl for T {}
|
||||
//impl<T: HasPool
|
||||
//+ Namespace<bool>
|
||||
//+ Namespace<usize>
|
||||
//+ Namespace<Arc<str>>
|
||||
//+ Namespace<PathBuf>
|
||||
//+ Namespace<MidiClip>
|
||||
//+ Namespace<ItemColor>
|
||||
//+ Namespace<BrowseCmd>
|
||||
//> PoolControl for T {}
|
||||
|
||||
#[tek_proc::commands(PoolCmd = "pool")] pub trait PoolControl: HasPool
|
||||
+ Namespace<bool>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<PathBuf>
|
||||
+ Namespace<MidiClip>
|
||||
+ Namespace<ItemColor>
|
||||
+ Namespace<BrowseCmd>
|
||||
{
|
||||
/// Toggle visibility of pool
|
||||
#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
|
||||
self.pool_mut().visible = visible;
|
||||
Ok(Some(PoolCmd::Show { visible: !visible }))
|
||||
}
|
||||
/// Set selected clip
|
||||
#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
||||
self.pool_mut().set_clip_index(index);
|
||||
Ok(None)
|
||||
}
|
||||
/// Delete a clip from the pool
|
||||
#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
||||
let clip = self.pool_mut().clips_mut().remove(index).try_read().unwrap().clone();
|
||||
Ok(Some(PoolCmd::Add { index, clip }))
|
||||
}
|
||||
/// Switch places of two clips in pool
|
||||
#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCmd> {
|
||||
self.pool_mut().clips_mut().swap(index, other);
|
||||
Ok(Some(PoolCmd::Swap { index, other }))
|
||||
}
|
||||
/// Add clip to pool
|
||||
#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCmd> {
|
||||
let mut index = index;
|
||||
let clip = Arc::new(RwLock::new(clip.clone()));
|
||||
let mut clips = self.pool_mut().clips_mut();
|
||||
if index >= clips.len() {
|
||||
index = clips.len();
|
||||
clips.push(clip)
|
||||
} else {
|
||||
clips.insert(index, clip);
|
||||
}
|
||||
Ok(Some(PoolCmd::Delete { index }))
|
||||
}
|
||||
/// Set name of clip
|
||||
#[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];
|
||||
let old_name = clip.try_read().unwrap().name.clone();
|
||||
clip.try_write().unwrap().name = name.clone();
|
||||
Ok(Some(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> {
|
||||
let clip = &mut self.pool_mut().clips_mut()[index];
|
||||
let old_len = clip.try_read().unwrap().length;
|
||||
clip.try_write().unwrap().length = length;
|
||||
Ok(Some(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);
|
||||
std::mem::swap(&mut color, &mut self.pool().clips()[index].try_write().unwrap().color);
|
||||
Ok(Some(PoolCmd::SetColor { index, color: color.base }))
|
||||
}
|
||||
/// Enter crop mode
|
||||
#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps<PoolCmd> {
|
||||
let index = self.pool().clip_index();
|
||||
let length = self.pool().clips()[index].try_read().unwrap().length;
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
|
||||
Ok(None)
|
||||
}
|
||||
/// Exit crop mode, discard
|
||||
#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps<PoolCmd> {
|
||||
if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() {
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
#[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))
|
||||
= self.pool_mut().mode_mut().clone()
|
||||
{
|
||||
let old_length;
|
||||
{
|
||||
let clip = self.pool().clips()[clip].clone();//.try_write().unwrap();
|
||||
old_length = Some(clip.try_read().unwrap().length);
|
||||
clip.try_write().unwrap().length = *length;
|
||||
}
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l }))
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps<PoolCmd> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut _length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
focus.next()
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps<PoolCmd> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut _length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
focus.prev()
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps<PoolCmd> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
match focus {
|
||||
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> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
match focus {
|
||||
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
|
||||
#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps<PoolCmd> {
|
||||
let index = self.pool().clip_index();
|
||||
let name = self.pool().clips()[index].try_read().unwrap().name.clone();
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
|
||||
Ok(None)
|
||||
}
|
||||
/// Exit rename mode, discard
|
||||
#[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() {
|
||||
self.pool().clips()[clip].try_write().unwrap().name = old_name.clone().into();
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
/// Exit rename mode, update name
|
||||
#[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() {
|
||||
let old_name = old_name.clone();
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
Some(PoolCmd::RenameSet { value: old_name })
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
#[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})
|
||||
//#[tek_proc::commands(PolCmd = "pool")] pub trait PoolControl: HasPool
|
||||
//+ Namespace<bool>
|
||||
//+ Namespace<usize>
|
||||
//+ Namespace<Arc<str>>
|
||||
//+ Namespace<PathBuf>
|
||||
//+ Namespace<MidiClip>
|
||||
//+ Namespace<ItemColor>
|
||||
//+ Namespace<BrowseCmd>
|
||||
//{
|
||||
///// Toggle visibility of pool
|
||||
//#[command(Show = "show")] fn show (&mut self, visible: bool) -> Perhaps<PoolCmd> {
|
||||
//self.pool_mut().visible = visible;
|
||||
//Ok(Some(PoolCmd::Show { visible: !visible }))
|
||||
//}
|
||||
///// Set selected clip
|
||||
//#[command(Select = "select")] fn select (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
||||
//self.pool_mut().set_clip_index(index);
|
||||
//Ok(None)
|
||||
//}
|
||||
///// Delete a clip from the pool
|
||||
//#[command(Delete = "delete")] fn delete (&mut self, index: usize) -> Perhaps<PoolCmd> {
|
||||
//let clip = self.pool_mut().remove(index).read().clone();
|
||||
//Ok(Some(PoolCmd::Add { index, clip }))
|
||||
//}
|
||||
///// Switch places of two clips in pool
|
||||
//#[command(Swap = "swap")] fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCmd> {
|
||||
//self.pool_mut().swap(index, other);
|
||||
//Ok(Some(PoolCmd::Swap { index, other }))
|
||||
//}
|
||||
///// Add clip to pool
|
||||
//#[command(Add = "add")] fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCmd> {
|
||||
//let mut index = index;
|
||||
//let clip = Arc::new(RwLock::new(clip.clone()));
|
||||
//let mut clips = self.pool_mut().clips_mut();
|
||||
//if index >= clips.len() {
|
||||
//index = clips.len();
|
||||
//clips.push(clip)
|
||||
//} else {
|
||||
//clips.insert(index, clip);
|
||||
//}
|
||||
//Ok(Some(PoolCmd::Delete { index }))
|
||||
//}
|
||||
///// Set name of clip
|
||||
//#[command(SetName = "set-name")] fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCmd> {
|
||||
//Ok(self.pool_mut().clips.read().get(index).map(|clip|{
|
||||
//let old_name = clip.read().name.clone();
|
||||
//clip.write().name = name.clone();
|
||||
//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> {
|
||||
//Ok(self.pool_mut().clips.read().get(index).map(|clip|{
|
||||
//let old_len = clip.read().length;
|
||||
//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);
|
||||
//std::mem::swap(&mut color, &mut self.pool().clips.read()[index].write().color);
|
||||
//Ok(Some(PoolCmd::SetColor { index, color: color.base }))
|
||||
//}
|
||||
///// Enter crop mode
|
||||
//#[command(CropBegin = "crop/begin")] fn crop_begin (&mut self) -> Perhaps<PoolCmd> {
|
||||
//let index = self.pool().clip_index();
|
||||
//let length = self.pool().clips.read()[index].read().length;
|
||||
//*self.pool_mut().mode_mut() = Some(PoolMode::Length(index, length, ClipLengthFocus::Bar));
|
||||
//Ok(None)
|
||||
//}
|
||||
///// Exit crop mode, discard
|
||||
//#[command(CropCancel = "crop/cancel")] fn crop_cancel (&mut self) -> Perhaps<PoolCmd> {
|
||||
//if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() {
|
||||
//*self.pool_mut().mode_mut() = None;
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
//#[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))
|
||||
//= self.pool_mut().mode_mut().clone()
|
||||
//{
|
||||
//let old_length;
|
||||
//{
|
||||
//let clip = self.pool().clips.read()[clip].clone();//.write();
|
||||
//old_length = Some(clip.read().length);
|
||||
//clip.write().length = *length;
|
||||
//}
|
||||
//*self.pool_mut().mode_mut() = None;
|
||||
//return Ok(old_length.map(|l|PoolCmd::CropSet { _length: l }))
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
//#[command(CropNext = "crop/next")] fn crop_next (&mut self) -> Perhaps<PoolCmd> {
|
||||
//if let Some(PoolMode::Length(
|
||||
//_clip, ref mut _length, ref mut focus
|
||||
//)) = self.pool_mut().mode_mut().clone() {
|
||||
//focus.next()
|
||||
//};
|
||||
//Ok(None)
|
||||
//}
|
||||
//#[command(CropPrev = "crop/prev")] fn crop_prev (&mut self) -> Perhaps<PoolCmd> {
|
||||
//if let Some(PoolMode::Length(
|
||||
//_clip, ref mut _length, ref mut focus
|
||||
//)) = self.pool_mut().mode_mut().clone() {
|
||||
//focus.prev()
|
||||
//};
|
||||
//Ok(None)
|
||||
//}
|
||||
//#[command(CropInc = "crop/inc")] fn crop_inc (&mut self) -> Perhaps<PoolCmd> {
|
||||
//if let Some(PoolMode::Length(
|
||||
//_clip, ref mut length, ref mut focus
|
||||
//)) = self.pool_mut().mode_mut().clone() {
|
||||
//match focus {
|
||||
//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> {
|
||||
//if let Some(PoolMode::Length(
|
||||
//_clip, ref mut length, ref mut focus
|
||||
//)) = self.pool_mut().mode_mut().clone() {
|
||||
//match focus {
|
||||
//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
|
||||
//#[command(RenameBegin = "rename/begin")] fn rename_begin (&mut self) -> Perhaps<PoolCmd> {
|
||||
//let index = self.pool().clip_index();
|
||||
//let name = self.pool().clips.read()[index].read().name.clone();
|
||||
//*self.pool_mut().mode_mut() = Some(PoolMode::Rename(index, name));
|
||||
//Ok(None)
|
||||
//}
|
||||
///// Exit rename mode, discard
|
||||
//#[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() {
|
||||
//self.pool().clips.read()[clip].write().name = old_name.clone().into();
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
///// Exit rename mode, update name
|
||||
//#[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() {
|
||||
//let old_name = old_name.clone();
|
||||
//*self.pool_mut().mode_mut() = None;
|
||||
//Some(PoolCmd::RenameSet { value: old_name })
|
||||
//} else {
|
||||
//None
|
||||
//})
|
||||
//}
|
||||
/// Import clip to pool from file
|
||||
#[command(Import = "import")]
|
||||
fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCmd>
|
||||
where Self: Sized
|
||||
{
|
||||
let bytes = std::fs::read(&path)?;
|
||||
let smf = Smf::parse(bytes.as_slice())?;
|
||||
let mut t = 0u32;
|
||||
let mut events = vec![];
|
||||
for track in smf.tracks.iter() {
|
||||
for event in track.iter() {
|
||||
t += event.delta.as_int();
|
||||
if let TrackEventKind::Midi { channel, message } = event.kind {
|
||||
events.push((t, channel.as_int(), message));
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
//})
|
||||
//#[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.read()[clip].write().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 {
|
||||
////None
|
||||
////})
|
||||
////}
|
||||
///// Import clip to pool from file
|
||||
//#[command(Import = "import")]
|
||||
//fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCmd>
|
||||
//where Self: Sized
|
||||
//{
|
||||
//let bytes = std::fs::read(&path)?;
|
||||
//let smf = Smf::parse(bytes.as_slice())?;
|
||||
//let mut t = 0u32;
|
||||
//let mut events = vec![];
|
||||
//for track in smf.tracks.iter() {
|
||||
//for event in track.iter() {
|
||||
//t += event.delta.as_int();
|
||||
//if let TrackEventKind::Midi { channel, message } = event.kind {
|
||||
//events.push((t, channel.as_int(), message));
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//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
|
||||
+ Namespace<Vec<Arc<str>>>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Vec<Arc<str>>> + Namespace<Arc<str>> + Namespace<usize>
|
||||
{}
|
||||
|
||||
#[tek_proc::commands(ModeCmd)] pub trait ModeControl: HasModeStack
|
||||
+ Namespace<Vec<Arc<str>>>
|
||||
+ Namespace<Arc<str>>
|
||||
+ Namespace<usize>
|
||||
+ Namespace<Vec<Arc<str>>> + Namespace<Arc<str>> + Namespace<usize>
|
||||
{
|
||||
#[command(Reset = "reset")] fn mode_reset (&mut self, modes: Vec<Arc<str>>) -> Perhaps<ModeCmd> {
|
||||
*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 {}
|
||||
#[tek_proc::commands(MidiClipCmd)] pub trait MidiClipControl: HasClips
|
||||
+ Namespace<Option<bool>>
|
||||
+ Namespace<Option<ItemTheme>>
|
||||
pub trait HasClip {
|
||||
fn clip (&self) -> RwLockReadGuard<'_, Option<Arc<RwLock<MidiClip>>>>;
|
||||
fn clip_mut (&self) -> RwLockWriteGuard<'_, Option<Arc<RwLock<MidiClip>>>>;
|
||||
}
|
||||
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.
|
||||
#[command(Upsert = "upsert")] fn clip_upsert (&mut self) -> Perhaps<MidiClipCmd> {
|
||||
let clip = self.clip();
|
||||
//let old = self.scenes[scene].clips[track].clone();
|
||||
//self.scenes[scene].clips[track] = clip;
|
||||
//old
|
||||
todo!()
|
||||
if self.clip().is_some() {
|
||||
Ok(None)
|
||||
} else {
|
||||
*self.clip_mut() = Some(Default::default());
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
#[command(SetColor = "color")] fn clip_set_color (&mut self, color: Option<ItemTheme>) -> Perhaps<MidiClipCmd> {
|
||||
Ok(self.clip().map(|clip|MidiClipCmd::SetColor {
|
||||
color: Some(clip.write().set_color(color))
|
||||
}))
|
||||
Ok(self.clip().as_ref().map(|clip|MidiClipCmd::SetColor { color: Some(clip.write().set_color(color)) }))
|
||||
}
|
||||
/// Toggle looping for the active clip
|
||||
#[command(SetLoop = "loop")] fn clip_toggle_loop (&mut self, looping: Option<bool>) -> Perhaps<MidiClipCmd> {
|
||||
Ok(self.clip().map(|clip|MidiClipCmd::SetLoop {
|
||||
looping: Some(clip.write().toggle_loop(looping))
|
||||
}))
|
||||
Ok(self.clip().as_ref().map(|clip|MidiClipCmd::SetLoop { 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:
|
||||
Namespace<usize> +
|
||||
Namespace<PathBuf> +
|
||||
Namespace<Arc<str>>
|
||||
Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>
|
||||
{
|
||||
/// Toggle visibility of browser
|
||||
#[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)]
|
||||
impl App {
|
||||
/// Increment a given data axis.
|
||||
#[command(Inc = "inc")]
|
||||
pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
||||
#[tek_proc::commands(AxisCmd)] impl App {
|
||||
/// Increment the given data axis.
|
||||
#[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => todo!(),
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
||||
|
|
@ -807,9 +804,8 @@ impl App {
|
|||
_ => todo!()
|
||||
})
|
||||
}
|
||||
/// Decrement a given data axis.
|
||||
#[command(Dec = "dec")]
|
||||
pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
||||
/// Decrement the given data axis.
|
||||
#[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => None,
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
||||
|
|
|
|||
1242
src/tek_view.rs
1242
src/tek_view.rs
File diff suppressed because it is too large
Load diff
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit 79f9cfcbf93db626d031ab86abbbdf5c29d80a48
|
||||
Subproject commit 7237121a508556ee98714a6af387e325a371e368
|
||||
Loading…
Add table
Add a link
Reference in a new issue