mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
collect tek_view, tek_ctrl
This commit is contained in:
parent
c5d6ce24b2
commit
186b81f29c
47 changed files with 2665 additions and 2893 deletions
636
src/app.rs
636
src/app.rs
|
|
@ -1,636 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Total application state.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tek::{HasTracks, HasScenes, TracksView, ScenesView};
|
|
||||||
/// let mut app = tek::App::default();
|
|
||||||
/// let _ = app.scenes_add_one(None, None).unwrap();
|
|
||||||
/// let _ = app.update_clock();
|
|
||||||
/// app.project.editor = Some(Default::default());
|
|
||||||
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
|
||||||
/// //let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
|
||||||
/// let _: Vec<_> = app.project.tracks_with_sizes().collect();
|
|
||||||
/// //let _: Vec<_> = app.project.scenes_with_sizes(true, 10, 10).collect();
|
|
||||||
/// //let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
|
||||||
/// //let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
|
||||||
/// let _ = app.project.w();
|
|
||||||
/// //let _ = app.project.w_sidebar();
|
|
||||||
/// //let _ = app.project.w_tracks_area();
|
|
||||||
/// let _ = app.project.h();
|
|
||||||
/// //let _ = app.project.h_tracks_area();
|
|
||||||
/// //let _ = app.project.h_inputs();
|
|
||||||
/// //let _ = app.project.h_outputs();
|
|
||||||
/// let _ = app.project.h_scenes();
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
#[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(ItemColor)]
|
|
||||||
#[namespace(ItemTheme)]
|
|
||||||
#[namespace(MidiClip)]
|
|
||||||
#[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)]
|
|
||||||
#[namespace(Option<usize> get_opt_usize)]
|
|
||||||
#[namespace(PathBuf get_path_buf)]
|
|
||||||
#[namespace(Vec<Arc<str>> get_vec_arc_str)]
|
|
||||||
#[namespace(bool get_bool)]
|
|
||||||
#[namespace(f64 get_f64)]
|
|
||||||
#[namespace(isize)]
|
|
||||||
#[namespace(u16 get_u16)]
|
|
||||||
#[namespace(u32)]
|
|
||||||
#[namespace(u8)]
|
|
||||||
#[namespace(usize get_usize)]
|
|
||||||
pub struct App {
|
|
||||||
/// Exit flag
|
|
||||||
pub exit: Exit,
|
|
||||||
/// Base color.
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Must not be dropped for the duration of the process
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Display size
|
|
||||||
pub size: Sizer,
|
|
||||||
/// Performance counter
|
|
||||||
pub perf: PerfModel,
|
|
||||||
/// Available view modes and input bindings
|
|
||||||
pub config: Arc<Config>,
|
|
||||||
/// Currently selected mode
|
|
||||||
pub modes: Arc<RwLock<Vec<Arc<str>>>>,
|
|
||||||
/// Undo history
|
|
||||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
|
||||||
/// Dialog overlay
|
|
||||||
pub dialog: Dialog,
|
|
||||||
/// Contains all recently created clips.
|
|
||||||
pub pool: Pool,
|
|
||||||
/// Contains the currently edited musical arrangement
|
|
||||||
pub project: Project,
|
|
||||||
/// Error, if any
|
|
||||||
pub error: Arc<RwLock<Option<Arc<str>>>>,
|
|
||||||
#[cfg(feature = "prof2")]
|
|
||||||
/// Tracing guard
|
|
||||||
pub guard: Option<tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
/// Create a new application instance from a backend, project, config, and mode
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let mut proj = tek::Project::default();
|
|
||||||
/// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
|
|
||||||
/// let mut conf = tek::Config::load(tek::Config::default(), "(mode hello)").unwrap();
|
|
||||||
/// let tek = tek::App::new(None, proj, conf.into(), "hello");
|
|
||||||
/// ```
|
|
||||||
pub fn new (
|
|
||||||
exit: Option<Exit>,
|
|
||||||
project: Project,
|
|
||||||
config: Arc<Config>,
|
|
||||||
mode: impl AsRef<str>
|
|
||||||
) -> Self {
|
|
||||||
App {
|
|
||||||
exit: exit.unwrap_or_default(),
|
|
||||||
jack: project.jack.clone(),
|
|
||||||
color: ItemTheme::random(),
|
|
||||||
dialog: Dialog::welcome(),
|
|
||||||
modes: Arc::new(RwLock::new(vec![mode.as_ref().into()])),
|
|
||||||
config,
|
|
||||||
project,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
|
||||||
Ok(src.src()?.map(|x|x.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_u16 <'a> (state: &App, src: impl Language) -> Perhaps<u16> {
|
|
||||||
Ok(Some(match src.word()? {
|
|
||||||
Some(":w/sidebar") => state.project.w_sidebar(state.editor().is_some()),
|
|
||||||
Some(":h/sample-detail") => 6.max(state.size.h() as u16 * 3 / 9),
|
|
||||||
_ => return try_to_u16(src)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_usize <'a> (state: &App, src: impl Language) -> Perhaps<usize> {
|
|
||||||
Ok(Some(match src.word()? {
|
|
||||||
Some(":scene-count") => state.scenes().len(),
|
|
||||||
Some(":track-count") => state.tracks().len(),
|
|
||||||
Some(":device-kind") => state.dialog.device_kind().unwrap_or(0),
|
|
||||||
Some(":device-kind/next") => state.dialog.device_kind_next().unwrap_or(0),
|
|
||||||
Some(":device-kind/prev") => state.dialog.device_kind_prev().unwrap_or(0),
|
|
||||||
_ => return try_to_usize(src)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_f64 <'a> (_state: &App, _src: impl Language) -> Perhaps<f64> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bool <'a> (state: &App, src: impl Language) -> Perhaps<bool> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
"Y" => true,
|
|
||||||
"N" => false,
|
|
||||||
":mode/editor" => state.project.editor.is_some(),
|
|
||||||
":focused/dialog" => !matches!(state.dialog, Dialog::None),
|
|
||||||
":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.pool.mode, Some(PoolMode::Import(..))),
|
|
||||||
":focused/pool/export" => matches!(state.pool.mode, Some(PoolMode::Export(..))),
|
|
||||||
":focused/pool/rename" => matches!(state.pool.mode, Some(PoolMode::Rename(..))),
|
|
||||||
":focused/pool/length" => matches!(state.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),
|
|
||||||
_ => return err!("not bool: {word}")
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
fn get_selection <'a> (state: &App, src: impl Language) -> Perhaps<Selection> {
|
|
||||||
src.word()?.map(|word|Ok(match word {
|
|
||||||
":select/scene" => state.selection().select_scene(state.tracks().len()),
|
|
||||||
":select/scene/next" => state.selection().select_scene_next(state.scenes().len()),
|
|
||||||
":select/scene/prev" => state.selection().select_scene_prev(),
|
|
||||||
":select/track" => state.selection().select_track(state.tracks().len()),
|
|
||||||
":select/track/next" => state.selection().select_track_next(state.tracks().len()),
|
|
||||||
":select/track/prev" => state.selection().select_track_prev(),
|
|
||||||
_ => return err!("not selection: {word}")
|
|
||||||
})).transpose()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_color <'a> (_: &App, src: impl Language) -> Perhaps<Color> {
|
|
||||||
if let Some(expr) = src.expr()? {
|
|
||||||
match (expr.head()?, expr.tail()?) {
|
|
||||||
(Some("g"), Some(tail)) => {
|
|
||||||
let n = try_to_u8(tail.head().map_err(Into::into))?
|
|
||||||
.ok_or(LanguageError::domain("not gray"))?;
|
|
||||||
Ok(Some(Color::Rgb(n, n, n)))
|
|
||||||
},
|
|
||||||
(Some("rgb"), Some(tail)) => {
|
|
||||||
let r = try_to_u8(tail.head().map_err(Into::into))?
|
|
||||||
.ok_or(LanguageError::domain("not red"))?;
|
|
||||||
let g = try_to_u8(tail.tail().head().map_err(Into::into))?
|
|
||||||
.ok_or(LanguageError::domain("not green"))?;
|
|
||||||
let b = try_to_u8(tail.tail().tail().head().map_err(Into::into))?
|
|
||||||
.ok_or(LanguageError::domain("not blue"))?;
|
|
||||||
Ok(Some(Color::Rgb(r, g, b)))
|
|
||||||
},
|
|
||||||
(Some(_), _) => return err!("not a color expression: {expr}"),
|
|
||||||
(None, _) => return err!("not a color expression: {expr}"),
|
|
||||||
}
|
|
||||||
} else if let Ok(Some(sym)) = src.word() {
|
|
||||||
Ok(match sym {
|
|
||||||
":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
|
||||||
":color/fg" => Some(Color::Rgb(98, 92, 96)),
|
|
||||||
_ => return err!("not a color: {sym}")
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return err!("not a color: {:?}", src.src()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
fn get_opt_u7 <'a> (state: &App, src: impl Language) -> Perhaps<Option<u7>> {
|
|
||||||
eval_word(src, |word|Ok(match word {
|
|
||||||
|
|
||||||
":editor/pitch" => Some((
|
|
||||||
state.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8
|
|
||||||
).into()),
|
|
||||||
|
|
||||||
_ => return err!("unknown midi note: {word}")
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_usize <'a> (state: &App, src: impl Language) -> Perhaps<Option<usize>> {
|
|
||||||
eval_word(src, |word|Ok(match word {
|
|
||||||
|
|
||||||
":selected/scene" => state.selection().scene(),
|
|
||||||
":selected/track" => state.selection().track(),
|
|
||||||
|
|
||||||
_ => return err!("unknown opt<usize>: {word}")
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_u32 <'a> (_: &App, _src: impl Language) -> Perhaps<Option<u32>> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_itemtheme <'a> (_: &App, _: impl Language) -> Perhaps<Option<ItemTheme>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_arc_array_connect <'a> (_: &App, _: impl Language) -> Perhaps<Option<Arc<[Connect]>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_arc_array_connect <'a> (_: &App, _: impl Language) -> Perhaps<Arc<[Connect]>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Option<Arc<str>>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_opt_bool <'a> (_: &App, _: impl Language) -> Perhaps<Option<bool>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_path_buf <'a> (_: &App, _: impl Language) -> Perhaps<PathBuf> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
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!(Jack<'static>: |self: App|self.jack);
|
|
||||||
impl_has!(Pool: |self: App|self.pool);
|
|
||||||
impl_has!(Selection: |self: App|self.project.selection);
|
|
||||||
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
|
|
||||||
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
|
|
||||||
|
|
||||||
primitive!(u8: try_to_u8);
|
|
||||||
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_command(self, input)
|
|
||||||
});
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
|
|
||||||
fn input_command (state: &mut Self, input: &TuiEvent) -> Usually<()> {
|
|
||||||
if let Some(bindings) = Self::input_command_find(state, input) {
|
|
||||||
return Self::input_command_exec(state, bindings)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_command_find (state: &mut Self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
|
|
||||||
for name in state.modes.read().iter().rev() {
|
|
||||||
let mode = state.config.get_mode(name).as_ref().map(Arc::clone);
|
|
||||||
if let Some(mode) = mode {
|
|
||||||
let binds = state.config.binds.clone();
|
|
||||||
for id in mode.keys.iter() {
|
|
||||||
if let Some(event_map) = binds.find(id)
|
|
||||||
&& let Some(bindings) = event_map.query(input) {
|
|
||||||
return Some(bindings.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn input_command_exec (state: &mut Self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
|
|
||||||
for binding in bindings.iter() {
|
|
||||||
for command in binding.commands.iter() {
|
|
||||||
if Self::command::<AppCommand>(state, command)? {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command <
|
|
||||||
T: Dispatch<Self> + Clone + Into<AppCommand>
|
|
||||||
> (state: &mut Self, src: &str) -> Usually<bool> where
|
|
||||||
Self: Namespace<T>,
|
|
||||||
{
|
|
||||||
if let Some(command) = Namespace::<T>::namespace(state, &src)? {
|
|
||||||
return Self::command_dispatch(state, command.into())
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_dispatch (state: &mut Self, command: AppCommand) -> Usually<bool> where
|
|
||||||
Self: Namespace<AppCommand>,
|
|
||||||
{
|
|
||||||
match command.clone().dispatch(state) {
|
|
||||||
Err(err) => {
|
|
||||||
state.history.push((command.into(), None));
|
|
||||||
return Err(err)
|
|
||||||
},
|
|
||||||
Ok(undo) => {
|
|
||||||
state.history.push((command.into(), undo.map(Into::into)));
|
|
||||||
return Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::commands(AppCommand)]
|
|
||||||
impl App {
|
|
||||||
|
|
||||||
/// Exit the application.
|
|
||||||
#[command(Exit = "exit")]
|
|
||||||
pub fn command_exit (&mut self) -> Perhaps<AppCommand> {
|
|
||||||
self.exit.exit();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancel current selection.
|
|
||||||
#[command(Cancel = "cancel")]
|
|
||||||
pub fn command_cancel (&mut self) -> Perhaps<AppCommand> {
|
|
||||||
todo!("cancel")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Confirm current selection.
|
|
||||||
#[command(Confirm = "confirm")]
|
|
||||||
pub fn command_confirm (&mut self) -> Perhaps<AppCommand> {
|
|
||||||
Ok(match self.dialog.clone() {
|
|
||||||
Dialog::Menu(index, items) => {
|
|
||||||
for command in items[index].1.iter() {
|
|
||||||
App::command_dispatch(self, command.clone())?;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Swap currently active modal dialog.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::App::default().command_dialog(tek::Dialog::welcome());
|
|
||||||
/// ```
|
|
||||||
#[command(Dialog = "dialog")]
|
|
||||||
pub fn command_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCommand> {
|
|
||||||
let mut dialog = dialog.clone();
|
|
||||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
|
||||||
Ok(Some(AppCommand::Dialog { dialog }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Mode = "mode")]
|
|
||||||
pub fn command_mode (&mut self, command: ModeCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Project = "session")]
|
|
||||||
pub fn command_session (&mut self, command: ProjectCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Axis = "axis")]
|
|
||||||
pub fn command_axis (&mut self, command: AxisCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Browse = "browse")]
|
|
||||||
pub fn command_browse (&mut self, command: BrowseCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(MidiClip = "clip")]
|
|
||||||
pub fn command_midi_clip (&mut self, command: MidiClipCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(MidiEdit = "edit")]
|
|
||||||
pub fn command_midi_edit (&mut self, command: MidiEditCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Pool = "pool")]
|
|
||||||
pub fn command_pool (&mut self, command: PoolCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Scene = "scene")]
|
|
||||||
pub fn command_scene (&mut self, command: SceneCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Scenes = "scenes")]
|
|
||||||
pub fn command_scenes (&mut self, command: ScenesCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Track = "track")]
|
|
||||||
pub fn command_track (&mut self, command: TrackCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Tracks = "tracks")]
|
|
||||||
pub fn command_tracks (&mut self, command: TracksCommand) -> Perhaps<AppCommand> {
|
|
||||||
Ok(command.dispatch(self)?.map(Into::into))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A control axis.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let axis = tek::ControlAxis::X;
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
|
||||||
X, XFine,
|
|
||||||
Y, YFine,
|
|
||||||
Z, ZFine,
|
|
||||||
I, IFine,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::commands(AxisCommand)]
|
|
||||||
impl App {
|
|
||||||
/// Increment a given data axis.
|
|
||||||
#[command(Inc = "inc")]
|
|
||||||
pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCommand> {
|
|
||||||
Ok(match (&self.dialog, axis) {
|
|
||||||
(Dialog::None, _) => todo!(),
|
|
||||||
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
|
||||||
AppCommand::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Decrement a given data axis.
|
|
||||||
#[command(Dec = "dec")]
|
|
||||||
pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCommand> {
|
|
||||||
Ok(match (&self.dialog, axis) {
|
|
||||||
(Dialog::None, _) => None,
|
|
||||||
(Dialog::Menu(_, _), ControlAxis::Y) => {
|
|
||||||
AppCommand::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
|
|
||||||
None
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_from!(AppCommand: |x: AxisCommand| AppCommand::Axis { command: x });
|
|
||||||
impl_from!(AppCommand: |x: BrowseCommand| AppCommand::Browse { command: x });
|
|
||||||
impl_from!(AppCommand: |x: MidiClipCommand| AppCommand::MidiClip { command: x });
|
|
||||||
impl_from!(AppCommand: |x: MidiEditCommand| AppCommand::MidiEdit { command: x });
|
|
||||||
impl_from!(AppCommand: |x: ModeCommand| AppCommand::Mode { command: x });
|
|
||||||
impl_from!(AppCommand: |x: PoolCommand| AppCommand::Pool { command: x });
|
|
||||||
impl_from!(AppCommand: |x: ProjectCommand| AppCommand::Project { command: x });
|
|
||||||
impl_from!(AppCommand: |x: SceneCommand| AppCommand::Scene { command: x });
|
|
||||||
impl_from!(AppCommand: |x: ScenesCommand| AppCommand::Scenes { command: x });
|
|
||||||
impl_from!(AppCommand: |x: TrackCommand| AppCommand::Track { command: x });
|
|
||||||
impl_from!(AppCommand: |x: TracksCommand| AppCommand::Tracks { command: x });
|
|
||||||
|
|
||||||
/// 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 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.
|
|
||||||
fn width_inc (&mut self);
|
|
||||||
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
|
||||||
fn width_dec (&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
|
|
||||||
let height = (state.config.modes.len() * 2) as u16;
|
|
||||||
draw(move |to: &mut Tui|{
|
|
||||||
let mut index = 0;
|
|
||||||
state.config.modes.for_each(&mut |id: &str, profile: &Mode| {
|
|
||||||
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
|
||||||
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
|
||||||
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
|
||||||
let fg1 = Rgb(224, 192, 128);
|
|
||||||
let fg2 = Rgb(224, 128, 32);
|
|
||||||
let field_name = fg(fg1, name).align_w().full_w();
|
|
||||||
let field_id = fg(fg2, id).align_e().full_w();
|
|
||||||
let field_info = info.align_w().full_w();
|
|
||||||
let _ = bg(b, south(above(field_name, field_id), field_info))
|
|
||||||
.full_w().exact_h(2).push_y((2 * index) as u16).draw(to);
|
|
||||||
index += 1;
|
|
||||||
});
|
|
||||||
Ok(Some(to.area().into()))
|
|
||||||
}).min_w(30).exact_h(height)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
|
|
||||||
bar(Split::South, 1u16,
|
|
||||||
dim(true, "Mode"),
|
|
||||||
bold(true, state.modes.read().get(0))
|
|
||||||
).push_x(1).draw(to)
|
|
||||||
}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// Contains memoized renders of clock values.
|
|
||||||
///
|
|
||||||
/// Performance optimization.
|
|
||||||
#[derive(Debug)] pub struct ClockView {
|
|
||||||
pub sr: Memo<Option<(bool, f64)>, String>,
|
|
||||||
pub buf: Memo<Option<f64>, String>,
|
|
||||||
pub lat: Memo<Option<f64>, String>,
|
|
||||||
pub bpm: Memo<Option<f64>, String>,
|
|
||||||
pub beat: Memo<Option<f64>, String>,
|
|
||||||
pub time: Memo<Option<f64>, String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_default!(ClockView: {
|
|
||||||
let mut beat = String::with_capacity(16);
|
|
||||||
let _ = write!(beat, "{}", Self::BEAT_EMPTY);
|
|
||||||
let mut time = String::with_capacity(16);
|
|
||||||
let _ = write!(time, "{}", Self::TIME_EMPTY);
|
|
||||||
let mut bpm = String::with_capacity(16);
|
|
||||||
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
|
|
||||||
Self {
|
|
||||||
beat: Memo::new(None, beat),
|
|
||||||
time: Memo::new(None, time),
|
|
||||||
bpm: Memo::new(None, bpm),
|
|
||||||
sr: Memo::new(None, String::with_capacity(16)),
|
|
||||||
buf: Memo::new(None, String::with_capacity(16)),
|
|
||||||
lat: Memo::new(None, String::with_capacity(16)),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
impl ClockView {
|
|
||||||
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
|
||||||
pub const TIME_EMPTY: &'static str = "-.---s";
|
|
||||||
pub const BPM_EMPTY: &'static str = "---.---";
|
|
||||||
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
|
||||||
let rate = clock.timebase.sr.get();
|
|
||||||
let chunk = clock.chunk.load(Relaxed) as f64;
|
|
||||||
let lat = chunk / rate * 1000.;
|
|
||||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
|
||||||
let mut cache = cache.try_write().unwrap();
|
|
||||||
|
|
||||||
cache.buf.update(
|
|
||||||
Some(chunk), rewrite!(buf, "{chunk}")
|
|
||||||
);
|
|
||||||
|
|
||||||
cache.lat.update(
|
|
||||||
Some(lat), rewrite!(buf, "{lat:.1}ms")
|
|
||||||
);
|
|
||||||
|
|
||||||
cache.sr.update(
|
|
||||||
Some((compact, rate)), |buf: &mut String,_,_|{
|
|
||||||
buf.clear();
|
|
||||||
if compact {
|
|
||||||
write!(buf, "{:.1}kHz", rate / 1000.)
|
|
||||||
} else {
|
|
||||||
write!(buf, "{:.0}Hz", rate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) {
|
|
||||||
let pulse = clock.timebase.usecs_to_pulse(now);
|
|
||||||
let time = now/1000000.;
|
|
||||||
let bpm = clock.timebase.bpm.get();
|
|
||||||
cache.beat.update(Some(pulse), |buf, _, _|{
|
|
||||||
buf.clear();
|
|
||||||
clock.timebase.format_beats_1_to(buf, pulse)
|
|
||||||
});
|
|
||||||
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
|
||||||
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
|
||||||
} else {
|
|
||||||
cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY));
|
|
||||||
cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY));
|
|
||||||
cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,13 +7,10 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify
|
||||||
/// ```
|
/// ```
|
||||||
/// use ::{std::sync::Arc, tek::{Config, Modes, Mode}};
|
/// use ::{std::sync::Arc, tek::{Config, Modes, Mode}};
|
||||||
/// let config: Config = Default::default();
|
/// let config: Config = Default::default();
|
||||||
/// Modes::add(&config.modes, stringify!(
|
/// let mode = stringify!(mode :menu (name Menu) (info Mode selector.) (keys Y Confirm)
|
||||||
/// (mode :menu (name Menu)
|
/// (view (bg (g 0) (bsp/s PortsOut (bsp/n PortsIn
|
||||||
/// (info Mode selector.) (keys :axis/y :confirm)
|
/// (bg (g 30) (bsp/s (exact/y 7 Logo) (fill Dialog))))))));
|
||||||
/// (view (bg (g 0) (bsp/s :ports/out
|
/// Modes::add(&config.modes, mode).unwrap();
|
||||||
/// (bsp/n :ports/in
|
|
||||||
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
|
|
||||||
/// (fill :dialog/menu)))))))))).unwrap();
|
|
||||||
/// let mode: Arc<Mode> = config.get_mode(":menu").unwrap();
|
/// let mode: Arc<Mode> = config.get_mode(":menu").unwrap();
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|
@ -60,7 +57,7 @@ impl Config {
|
||||||
if let Some(expr) = src.expr()? {
|
if let Some(expr) = src.expr()? {
|
||||||
match expr.head()? {
|
match expr.head()? {
|
||||||
Some("mode") => Modes::add(&config.as_ref().modes, expr.tail()),
|
Some("mode") => Modes::add(&config.as_ref().modes, expr.tail()),
|
||||||
Some("keys") => Binds::add(&config.as_ref().binds, expr.tail()),
|
Some("keys") => config.as_ref().binds.add(expr.tail()),
|
||||||
Some("view") => Views::add(&config.as_ref().views, expr.tail()),
|
Some("view") => Views::add(&config.as_ref().views, expr.tail()),
|
||||||
_ => return Err(format!("Config::load: expected view/keys/mode expr, got: {src:?}").into())
|
_ => return Err(format!("Config::load: expected view/keys/mode expr, got: {src:?}").into())
|
||||||
}?;
|
}?;
|
||||||
|
|
|
||||||
|
|
@ -8,49 +8,13 @@ pub struct Binds(pub Arc<RwLock<BTreeMap<
|
||||||
>>>);
|
>>>);
|
||||||
|
|
||||||
impl Binds {
|
impl Binds {
|
||||||
pub fn add <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> {
|
pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
|
||||||
let name = expr.head()?.ok_or("bind: missing name")?;
|
let name = expr.head()?.ok_or("bind: missing name")?;
|
||||||
let body = expr.tail()?.unwrap_or("");
|
let body = expr.tail()?.unwrap_or("");
|
||||||
binds.0.try_write().unwrap().insert(name.into(), {
|
self.0.try_write().unwrap().insert(name.into(), Bind::load(body)?.into());
|
||||||
let mut map: Bind<TuiEvent, Arc<str>> = Bind::new();
|
|
||||||
body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) {
|
|
||||||
Binds::add_ref(&mut map, item)
|
|
||||||
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
|
||||||
Binds::add_def(&mut map, item)
|
|
||||||
} else {
|
|
||||||
return err!("Binds::add: unexpected: {item:?}")
|
|
||||||
})?;
|
|
||||||
map.into()
|
|
||||||
});
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_ref (_map: &mut Bind<TuiEvent, Arc<str>>, _item: &str) -> Usually<()> {
|
|
||||||
// TODO
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_def (map: &mut Bind<TuiEvent, Arc<str>>, item: &str) -> Usually<()> {
|
|
||||||
let expr = item.expr()?;
|
|
||||||
let head = expr.head()?;
|
|
||||||
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
|
|
||||||
let mut commands: Vec<Arc<str>> = vec![];
|
|
||||||
item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?;
|
|
||||||
map.add(TuiEvent(event), Binding {
|
|
||||||
commands: commands.into(),
|
|
||||||
condition: None,
|
|
||||||
description: None,
|
|
||||||
source: None
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
} else if Some(":char") == item.expr()?.head()? {
|
|
||||||
// TODO
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
err!("Binds::add: invalid key: {:?}", item.expr()?.head()?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear (&self) {
|
pub fn clear (&self) {
|
||||||
*self.0.write() = Default::default();
|
*self.0.write() = Default::default();
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +50,7 @@ pub struct Bind<E, C>(
|
||||||
pub BTreeMap<E, Vec<Binding<C>>>
|
pub BTreeMap<E, Vec<Binding<C>>>
|
||||||
);
|
);
|
||||||
|
|
||||||
/// A sequence of zero or more commands (e.g. [AppCommand]),
|
/// A sequence of zero or more commands (e.g. [AppCmd]),
|
||||||
/// optionally filtered by [Condition] to form layers.
|
/// optionally filtered by [Condition] to form layers.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -124,7 +88,48 @@ impl<C: Default> Default for Binding<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Bind<TuiEvent, Arc<str>> {
|
||||||
|
pub fn load (body: impl Language) -> Usually<Self> {
|
||||||
|
let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new();
|
||||||
|
body.each((), |_, item: &str|if matches!(item.expr().head(), Ok(Some("see"))) {
|
||||||
|
bind.add_ref(item)
|
||||||
|
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
||||||
|
bind.add_def(item)
|
||||||
|
} else {
|
||||||
|
return err!("Binds::add: unexpected: {item:?}")
|
||||||
|
})?;
|
||||||
|
Ok(bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_ref (&mut self, _item: &str) -> Usually<()> {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_def (&mut self, item: &str) -> Usually<()> {
|
||||||
|
let expr = item.expr()?;
|
||||||
|
let head = expr.head()?;
|
||||||
|
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
|
||||||
|
let mut commands: Vec<Arc<str>> = vec![];
|
||||||
|
item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?;
|
||||||
|
self.add(TuiEvent(event), Binding {
|
||||||
|
commands: commands.into(),
|
||||||
|
condition: None,
|
||||||
|
description: None,
|
||||||
|
source: None
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else if Some(":char") == item.expr()?.head()? {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
err!("Binds::add: invalid key: {:?}", item.expr()?.head()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E: Clone + Ord, C> Bind<E, C> {
|
impl<E: Clone + Ord, C> Bind<E, C> {
|
||||||
|
|
||||||
/// Create a new event map
|
/// Create a new event map
|
||||||
pub fn new () -> Self {
|
pub fn new () -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
|
|
@ -156,5 +161,5 @@ impl<E: Clone + Ord, C> Bind<E, C> {
|
||||||
|
|
||||||
impl_debug!(Condition |self, w| { write!(w, "*") });
|
impl_debug!(Condition |self, w| { write!(w, "*") });
|
||||||
|
|
||||||
//take!(DeviceCommand|state: Project, iter|state.selected_device().as_ref()
|
//take!(DeviceCmd|state: Project, iter|state.selected_device().as_ref()
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::*;
|
||||||
/// ```
|
/// ```
|
||||||
/// let mut modes = tek::Modes::default();
|
/// let mut modes = tek::Modes::default();
|
||||||
/// assert_eq!(modes.len(), 0);
|
/// assert_eq!(modes.len(), 0);
|
||||||
/// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))");
|
/// let _ = modes.add(&"mode Foo (name Foo) (info Bar)").unwrap();
|
||||||
/// assert_eq!(modes.len(), 1);
|
/// assert_eq!(modes.len(), 1);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
|
|
@ -22,7 +22,7 @@ impl Modes {
|
||||||
let body = expr.tail()?.ok_or("mode: missing body")?;
|
let body = expr.tail()?.ok_or("mode: missing body")?;
|
||||||
let mode = Mode::default();
|
let mode = Mode::default();
|
||||||
let mode = body.each(mode, |c, s|Mode::add(c, s))?;
|
let mode = body.each(mode, |c, s|Mode::add(c, s))?;
|
||||||
self.0.try_write().unwrap().insert(name.into(), Arc::new(mode));
|
self.0.write().insert(name.into(), Arc::new(mode));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,14 +33,14 @@ impl Modes {
|
||||||
|
|
||||||
/// Run something for each mode.
|
/// Run something for each mode.
|
||||||
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
|
||||||
for (k, v) in self.0.try_read().unwrap().iter() {
|
for (k, v) in self.0.read().iter() {
|
||||||
let _ = ator(k.as_ref(), v.as_ref());
|
let _ = ator(k.as_ref(), v.as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Count modes.
|
/// Count modes.
|
||||||
pub fn len (&self) -> usize {
|
pub fn len (&self) -> usize {
|
||||||
self.0.try_read().unwrap().len()
|
self.0.read().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ impl Mode {
|
||||||
let submode = Mode::default();
|
let submode = Mode::default();
|
||||||
let submode = body.each(submode, |c,s|Mode::add(c,s))?;
|
let submode = body.each(submode, |c,s|Mode::add(c,s))?;
|
||||||
let modes = mode.as_mut().modes.clone();
|
let modes = mode.as_mut().modes.clone();
|
||||||
modes.0.try_write().unwrap().insert(name.into(), Arc::new(submode));
|
modes.0.write().insert(name.into(), Arc::new(submode));
|
||||||
mode
|
mode
|
||||||
},
|
},
|
||||||
"keys" => {
|
"keys" => {
|
||||||
|
|
@ -123,40 +123,9 @@ pub trait HasModeStack {
|
||||||
|
|
||||||
impl HasModeStack for App {
|
impl HasModeStack for App {
|
||||||
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>> {
|
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>> {
|
||||||
self.modes.try_read().unwrap()
|
self.modes.read()
|
||||||
}
|
}
|
||||||
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>> {
|
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>> {
|
||||||
self.modes.try_write().unwrap()
|
self.modes.write()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ModeController for T where T: HasModeStack
|
|
||||||
+ Namespace<Vec<Arc<str>>>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<usize>
|
|
||||||
{}
|
|
||||||
|
|
||||||
#[tek_proc::commands(ModeCommand)]
|
|
||||||
pub trait ModeController: HasModeStack
|
|
||||||
+ Namespace<Vec<Arc<str>>>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<usize>
|
|
||||||
{
|
|
||||||
#[command(Reset = "reset")]
|
|
||||||
fn mode_reset (&mut self, modes: Vec<Arc<str>>) -> Perhaps<ModeCommand> {
|
|
||||||
*self.modes_mut() = modes.into();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Push = "push")]
|
|
||||||
fn mode_push (&mut self, mode: Arc<str>) -> Perhaps<ModeCommand> {
|
|
||||||
self.modes_mut().push(mode);
|
|
||||||
Ok(Some(ModeCommand::Pop))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Pop = "pop")]
|
|
||||||
fn mode_pop (&mut self) -> Perhaps<ModeCommand> {
|
|
||||||
let mode = self.modes_mut().pop();
|
|
||||||
Ok(mode.map(|mode|ModeCommand::Push { mode }))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,18 +155,6 @@ impl Draw<'_, Tui> for Lv2 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draw_header(self, to, x, y, width);
|
draw_lv2_header(self, to, x, y, width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn draw_header (state: &Lv2, to: &mut Tui, x: u16, y: u16, w: u16) {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
|
||||||
if let Some(ref path) = state.path {
|
|
||||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
|
||||||
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
|
||||||
}
|
|
||||||
//Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
188
src/dialog.rs
188
src/dialog.rs
|
|
@ -1,188 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
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.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let dialog: tek::Dialog = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
|
|
||||||
#[default] None,
|
|
||||||
Help(usize),
|
|
||||||
Menu(usize, Arc<[MenuItem]>),
|
|
||||||
Device(usize),
|
|
||||||
Message(Arc<str>),
|
|
||||||
Browse(BrowseTarget, Arc<Browse>),
|
|
||||||
Options,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dialog {
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome();
|
|
||||||
/// ```
|
|
||||||
pub fn welcome () -> Self {
|
|
||||||
Self::Menu(1, Arc::new([
|
|
||||||
|
|
||||||
MenuItem::new(" Resume session ", [
|
|
||||||
AppCommand::Project {
|
|
||||||
command: ProjectCommand::LoadLast
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
|
|
||||||
MenuItem::new(" Create session ", [
|
|
||||||
AppCommand::Dialog {
|
|
||||||
dialog: Dialog::None
|
|
||||||
},
|
|
||||||
AppCommand::Mode {
|
|
||||||
command: ModeCommand::Reset {
|
|
||||||
modes: vec!["Arranger".into()]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AppCommand::Tracks {
|
|
||||||
command: TracksCommand::Add
|
|
||||||
},
|
|
||||||
AppCommand::Scenes {
|
|
||||||
command: ScenesCommand::Add
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
|
|
||||||
MenuItem::new(" Load session ", [
|
|
||||||
AppCommand::Dialog {
|
|
||||||
dialog: Dialog::Browse(
|
|
||||||
BrowseTarget::LoadProject,
|
|
||||||
Browse::new(None).unwrap().into()
|
|
||||||
)
|
|
||||||
//dialog: Dialog::Browse {
|
|
||||||
//title: "Load session".into(),
|
|
||||||
//filter: "\\.tek$",
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
|
|
||||||
MenuItem::new(" Exit ", [
|
|
||||||
AppCommand::Exit
|
|
||||||
]),
|
|
||||||
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().menu_selected();
|
|
||||||
/// ```
|
|
||||||
pub fn menu_selected (&self) -> Option<usize> {
|
|
||||||
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().menu_next();
|
|
||||||
/// ```
|
|
||||||
pub fn menu_next (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().menu_prev();
|
|
||||||
/// ```
|
|
||||||
pub fn menu_prev (&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()),
|
|
||||||
_ => Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().device_kind();
|
|
||||||
/// ```
|
|
||||||
pub fn device_kind (&self) -> Option<usize> {
|
|
||||||
if let Self::Device(index) = self { Some(*index) } else { None }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().device_kind_next();
|
|
||||||
/// ```
|
|
||||||
pub fn device_kind_next (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: generalize
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::Dialog::welcome().device_kind_prev();
|
|
||||||
/// ```
|
|
||||||
pub fn device_kind_prev (&self) -> Option<usize> {
|
|
||||||
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// FIXME: implement
|
|
||||||
pub fn message (&self) -> Option<&str> { todo!() }
|
|
||||||
|
|
||||||
/// FIXME: implement
|
|
||||||
pub fn browser (&self) -> Option<&Arc<Browse>> { todo!() }
|
|
||||||
|
|
||||||
/// FIXME: implement
|
|
||||||
pub fn browser_target (&self) -> Option<&BrowseTarget> { todo!() }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increment a wrapping counter.
|
|
||||||
pub const fn wrap_inc (index: usize, count: usize) -> usize {
|
|
||||||
if count > 0 { (index + 1) % count } else { 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrement a wrapping counter.
|
|
||||||
pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
|
||||||
if count > 0 {
|
|
||||||
let a = index.overflowing_sub(1).0;
|
|
||||||
let b = count.saturating_sub(1);
|
|
||||||
if a < b { a } else { b }
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
81
src/field.rs
81
src/field.rs
|
|
@ -1,81 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub fn field_h <'a, T: Screen> (
|
|
||||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
|
||||||
) -> impl Draw<T> {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn field_v <'a, T: Screen> (
|
|
||||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
|
||||||
) -> impl Draw<T> {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_sessions <'a> () -> impl Draw<Tui> {
|
|
||||||
let h = 6;
|
|
||||||
let w = Some(30);
|
|
||||||
let f = Rgb(224, 192, 128);
|
|
||||||
draw(move |to: &mut Tui|{
|
|
||||||
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
|
||||||
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
|
||||||
let y = (2 * index) as u16;
|
|
||||||
let h = 2;
|
|
||||||
bg(b, fg(f, *name).align_w()).full_w().exact_h(h).push_y(y).draw(to)?;
|
|
||||||
}
|
|
||||||
Ok(Some(to.area().into()))
|
|
||||||
}).min_w(w).exact_h(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let bg = tengri::ratatui::style::Color::Red;
|
|
||||||
/// let fg = tengri::ratatui::style::Color::Green;
|
|
||||||
/// let _ = tek::view_wrap(bg, fg, "and then blue, too!");
|
|
||||||
/// ```
|
|
||||||
pub fn view_wrap <'a> (bg: Color, fg: Color, content: impl Draw<Tui>)
|
|
||||||
-> impl Draw<Tui>
|
|
||||||
{
|
|
||||||
let left = fg_bg(bg, Reset, y_repeat("▐").exact_w(1));
|
|
||||||
let right = fg_bg(bg, Reset, y_repeat("▌").exact_w(1));
|
|
||||||
east(left, west(right, fg_bg(fg, bg, content)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::button_2("", "", true);
|
|
||||||
/// let _ = tek::button_2("", "", false);
|
|
||||||
/// ```
|
|
||||||
pub fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool)
|
|
||||||
-> impl Draw<Tui>
|
|
||||||
{
|
|
||||||
let c1 = tui_orange();
|
|
||||||
let c2 = tui_g(0);
|
|
||||||
let c3 = tui_g(96);
|
|
||||||
let c4 = tui_g(255);
|
|
||||||
bold(true, fg_bg(c1, c2,
|
|
||||||
bar(Split::East, 1u16, fg(c2, "▐"),
|
|
||||||
bar(Split::East, 1u16, key,
|
|
||||||
bar(Split::East, 1u16, fg(c3, "▐"),
|
|
||||||
when(!hide, fg_bg(c4, c3, label)))))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::button_3("", "", "", true);
|
|
||||||
/// let _ = tek::button_3("", "", "", false);
|
|
||||||
/// ```
|
|
||||||
pub fn button_3 <'a> (
|
|
||||||
key: impl Draw<Tui>,
|
|
||||||
label: impl Draw<Tui>,
|
|
||||||
value: impl Draw<Tui>,
|
|
||||||
editing: bool,
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
let c1 = tui_orange();
|
|
||||||
let c2 = tui_g(0);
|
|
||||||
let c3 = tui_g(96);
|
|
||||||
let c4 = tui_g(255);
|
|
||||||
let c5 = tui_g(128);
|
|
||||||
bold(true, bar(Split::East, 3u16,
|
|
||||||
fg_bg(c1, c2,
|
|
||||||
bar(Split::East, 1u16, fg(c2, "▐"),
|
|
||||||
bar(Split::East, 1u16, key, fg(if editing { c5 } else { c3 }, "▐")))),
|
|
||||||
east(
|
|
||||||
when(!editing, east(fg_bg(c4, c3, label), fg_bg(c5, c3, "▐"),)),
|
|
||||||
east(fg_bg(tui_g(224), c5, value), fg_bg(c5, Reset, "▌"), ))))
|
|
||||||
}
|
|
||||||
54
src/memo.rs
54
src/memo.rs
|
|
@ -1,54 +0,0 @@
|
||||||
#![allow(unused)]
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! rewrite {
|
|
||||||
($buf:ident, $($rest:tt)*) => {
|
|
||||||
|$buf,_,_|{
|
|
||||||
$buf.clear();
|
|
||||||
write!($buf, $($rest)*)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Memo<T, U> {
|
|
||||||
value: T,
|
|
||||||
view: U,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> Memo<T, U> {
|
|
||||||
pub fn new (value: T, view: U) -> Self {
|
|
||||||
Self { value, view }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq + Debug, U: Debug> Debug for Memo<T, U> {
|
|
||||||
fn fmt (&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq, U> Memo<T, U> {
|
|
||||||
pub fn update <R> (&mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
|
||||||
if newval != self.value {
|
|
||||||
let result = render(&mut self.view, &newval, &self.value);
|
|
||||||
self.value = newval;
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Gettable<T> {
|
|
||||||
/// Returns current value
|
|
||||||
fn get (&self) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Mutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&mut self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait InteriorMutable<T>: Gettable<T> {
|
|
||||||
/// Sets new value, returns old
|
|
||||||
fn set (&self, value: T) -> T;
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// An item of a menu.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let item: tek::MenuItem = Default::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Clone)] pub struct MenuItem(
|
|
||||||
pub Arc<str>,
|
|
||||||
pub Arc<[AppCommand]>,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl MenuItem {
|
|
||||||
pub fn new <const N: usize> (name: impl AsRef<str>, actions: [AppCommand; 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
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
115
src/meter.rs
115
src/meter.rs
|
|
@ -1,115 +0,0 @@
|
||||||
#![allow(unused)]
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub enum MeteringMode {
|
|
||||||
#[default] Rms,
|
|
||||||
Log10,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct Log10Meter(pub f32);
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub struct RmsMeter(pub f32);
|
|
||||||
|
|
||||||
impl_draw!(|self: RmsMeter, to: Tui|{
|
|
||||||
let XYWH(x, y, w, h) = to.area().into();
|
|
||||||
let signal = f32::max(0.0, f32::min(100.0, self.0.abs()));
|
|
||||||
let v = (signal * h as f32).ceil() as u16;
|
|
||||||
let y2 = y + h;
|
|
||||||
//to.blit(&format!("\r{v} {} {signal}", self.0), x * 30, y, Some(Style::default()));
|
|
||||||
for y in y..(y + v) {
|
|
||||||
for x in x..(x + w) {
|
|
||||||
to.blit(&"▌", x, y2.saturating_sub(y), Some(Style::default().green()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(to.area().into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
impl_draw!(|self: Log10Meter, to: Tui| {
|
|
||||||
let XYWH(x, y, w, h) = to.area().into();
|
|
||||||
let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs()));
|
|
||||||
let v = (signal * h as f32 / 100.0).ceil() as u16;
|
|
||||||
let y2 = y + h;
|
|
||||||
//to.blit(&format!("\r{v} {} {signal}", self.0), x * 20, y, None);
|
|
||||||
for y in y..(y + v) {
|
|
||||||
for x in x..(x + w) {
|
|
||||||
to.blit(&"▌", x, y2 - y, Some(Style::default().green()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(to.area().into()))
|
|
||||||
});
|
|
||||||
|
|
||||||
fn draw_meters (meters: &[f32]) -> impl Draw<Tui> + use<'_> {
|
|
||||||
bg(Black, iter_east_fixed(1, ||meters.iter(), |value, _index|{
|
|
||||||
RmsMeter(*value).full_h()
|
|
||||||
}).exact_w(2))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_log10 (samples: &[f32]) -> f32 {
|
|
||||||
let total: f32 = samples.iter().map(|x|x.abs()).sum();
|
|
||||||
let count = samples.len() as f32;
|
|
||||||
10. * (total / count).log10()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_rms (samples: &[f32]) -> f32 {
|
|
||||||
let sum = samples.iter()
|
|
||||||
.map(|s|*s)
|
|
||||||
.reduce(|sum, sample|sum + sample.abs())
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
(sum / samples.len() as f32).sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let _ = tek::view_meter("", 0.0);
|
|
||||||
/// let _ = tek::view_meters(&[0.0, 0.0]);
|
|
||||||
/// ```
|
|
||||||
pub fn view_meter <'a> (label: &'a str, value: f32)
|
|
||||||
-> impl Draw<Tui>
|
|
||||||
{
|
|
||||||
let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value));
|
|
||||||
let w = if value >= 0.0 { 13 }
|
|
||||||
else if value >= -1.0 { 12 }
|
|
||||||
else if value >= -2.0 { 11 }
|
|
||||||
else if value >= -3.0 { 10 }
|
|
||||||
else if value >= -4.0 { 9 }
|
|
||||||
else if value >= -6.0 { 8 }
|
|
||||||
else if value >= -9.0 { 7 }
|
|
||||||
else if value >= -12.0 { 6 }
|
|
||||||
else if value >= -15.0 { 5 }
|
|
||||||
else if value >= -20.0 { 4 }
|
|
||||||
else if value >= -25.0 { 3 }
|
|
||||||
else if value >= -30.0 { 2 }
|
|
||||||
else if value >= -40.0 { 1 }
|
|
||||||
else { 0 };
|
|
||||||
let c = if value >= 0.0 { Red }
|
|
||||||
else if value >= -3.0 { Yellow }
|
|
||||||
else { Green };
|
|
||||||
south!(f, bg(c, ()).exact_wh(w, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_meters (values: &[f32;2]) -> impl Draw<Tui> + use<'_> {
|
|
||||||
let left = format!("L/{:>+9.3}", values[0]);
|
|
||||||
let right = format!("R/{:>+9.3}", values[1]);
|
|
||||||
south(left, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)] mod test_view_meter {
|
|
||||||
use super::*;
|
|
||||||
use proptest::prelude::*;
|
|
||||||
proptest! {
|
|
||||||
|
|
||||||
#[test] fn proptest_view_meter (
|
|
||||||
label in "\\PC*", value in f32::MIN..f32::MAX
|
|
||||||
) {
|
|
||||||
let _ = view_meter(&label, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test] fn proptest_view_meters (
|
|
||||||
value1 in f32::MIN..f32::MAX,
|
|
||||||
value2 in f32::MIN..f32::MAX
|
|
||||||
) {
|
|
||||||
let _ = view_meters(&[value1, value2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -131,50 +131,6 @@ pub type MidiData = Vec<Vec<MidiMessage>>;
|
||||||
impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
|
impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } }
|
||||||
impl Eq for MidiClip {}
|
impl Eq for MidiClip {}
|
||||||
|
|
||||||
impl HasMidiClip for Project {
|
|
||||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> { None }
|
|
||||||
}
|
|
||||||
impl HasMidiClip for App {
|
|
||||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>> { self.project.clip() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: HasMidiClip
|
|
||||||
+ Namespace<Option<bool>>
|
|
||||||
+ Namespace<Option<ItemTheme>>
|
|
||||||
> MidiClipController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(MidiClipCommand)]
|
|
||||||
pub trait MidiClipController: HasMidiClip
|
|
||||||
+ 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<MidiClipCommand> {
|
|
||||||
let clip = self.clip();
|
|
||||||
//let old = self.scenes[scene].clips[track].clone();
|
|
||||||
//self.scenes[scene].clips[track] = clip;
|
|
||||||
//old
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetColor = "color")]
|
|
||||||
fn clip_set_color (&mut self, color: Option<ItemTheme>) -> Perhaps<MidiClipCommand> {
|
|
||||||
Ok(self.clip().map(|clip|MidiClipCommand::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<MidiClipCommand> {
|
|
||||||
Ok(self.clip().map(|clip|MidiClipCommand::SetLoop {
|
|
||||||
looping: Some(clip.write().toggle_loop(looping))
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } }
|
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 } }
|
impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } }
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
143
src/midi_edit.rs
143
src/midi_edit.rs
|
|
@ -19,140 +19,17 @@ pub trait HasEditor: AsRef<Option<MidiEditor>> + AsMut<Option<MidiEditor>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> HasEditor for T where T:
|
impl<T> HasEditor for T where T: AsRef<Option<MidiEditor>> + AsMut<Option<MidiEditor>> {}
|
||||||
AsRef<Option<MidiEditor>> +
|
|
||||||
AsMut<Option<MidiEditor>> {}
|
|
||||||
|
|
||||||
impl<T: HasEditor
|
|
||||||
+ Namespace<u32>
|
|
||||||
+ Namespace<f64>
|
|
||||||
+ Namespace<bool>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Option<u32>>
|
|
||||||
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
|
||||||
> MidiEditController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(MidiEditCommand)]
|
|
||||||
pub trait MidiEditController: HasEditor
|
|
||||||
+ Namespace<u32>
|
|
||||||
+ Namespace<f64>
|
|
||||||
+ Namespace<bool>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Option<u32>>
|
|
||||||
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
|
||||||
{
|
|
||||||
#[command(Toggle = "toggle")]
|
|
||||||
fn edit_toggle (&mut self) -> Perhaps<MidiEditCommand> {
|
|
||||||
if self.is_editing() {
|
|
||||||
*self.editor_mut() = None;
|
|
||||||
} else {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
Ok(Some(MidiEditCommand::Toggle))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Show = "show")]
|
|
||||||
fn edit_show (&mut self, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|mut editor|{
|
|
||||||
editor.set_clip(clip.as_ref());
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(DeleteNote = "delete")]
|
|
||||||
fn edit_note_delete (&mut self) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.redraw();
|
|
||||||
todo!()
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(AppendNote = "append")]
|
|
||||||
fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|mut editor|{
|
|
||||||
editor.put_note(advance);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetNotePos = "note-pos")]
|
|
||||||
fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_note_pos((pos).min(127));
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetNoteLen = "note-len")]
|
|
||||||
fn edit_note_set_len (&mut self, len: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_note_len(len);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetNoteScroll = "note-scroll")]
|
|
||||||
fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_note_lo((scroll).min(127));
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetTimePos = "time-pos")]
|
|
||||||
fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_time_pos(pos);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetTimeScroll = "time-scroll")]
|
|
||||||
fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_time_start(scroll);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetTimeZoom = "time-zoom")]
|
|
||||||
fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_time_zoom(zoom);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetTimeLock = "time-lock")]
|
|
||||||
fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps<MidiEditCommand> {
|
|
||||||
Ok(self.editor_mut().as_mut().map(|editor|{
|
|
||||||
editor.set_time_lock(lock);
|
|
||||||
editor.redraw();
|
|
||||||
None
|
|
||||||
}).flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Contains state for viewing and editing a clip.
|
/// Contains state for viewing and editing a clip.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use std::sync::{Arc, RwLock};
|
/// use std::sync::Arc;
|
||||||
|
/// use parking_lot::RwLock;
|
||||||
/// let clip = tek::MidiClip::stop_all();
|
/// let clip = tek::MidiClip::stop_all();
|
||||||
/// let mut editor = tek::MidiEditor {
|
/// let mode = tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip))));
|
||||||
/// mode: tek::PianoHorizontal::new(Some(&Arc::new(RwLock::new(clip)))),
|
/// let mut editor = tek::MidiEditor { mode, size: Default::default() };
|
||||||
/// size: Default::default(),
|
///
|
||||||
/// //keys: Default::default(),
|
|
||||||
/// };
|
|
||||||
/// let _ = editor.put_note(true);
|
/// let _ = editor.put_note(true);
|
||||||
/// let _ = editor.put_note(false);
|
/// let _ = editor.put_note(false);
|
||||||
/// let _ = editor.clip_status();
|
/// let _ = editor.clip_status();
|
||||||
|
|
@ -275,8 +152,8 @@ impl MidiEditor {
|
||||||
/// use tek::{*, tengri::*};
|
/// use tek::{*, tengri::*};
|
||||||
///
|
///
|
||||||
/// struct Test(Option<MidiEditor>);
|
/// struct Test(Option<MidiEditor>);
|
||||||
/// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref());
|
/// impl_as_ref!(Option<MidiEditor>: |self: Test|&self.0);
|
||||||
/// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut());
|
/// impl_as_mut!(Option<MidiEditor>: |self: Test|&mut self.0);
|
||||||
///
|
///
|
||||||
/// let mut host = Test(Some(MidiEditor::default()));
|
/// let mut host = Test(Some(MidiEditor::default()));
|
||||||
/// let _ = host.editor();
|
/// let _ = host.editor();
|
||||||
|
|
@ -427,7 +304,7 @@ impl App {
|
||||||
&& slot.is_none()
|
&& slot.is_none()
|
||||||
&& let Some(track) = self.project.tracks.get_mut(track)
|
&& let Some(track) = self.project.tracks.get_mut(track)
|
||||||
{
|
{
|
||||||
let (_index, clip) = self.pool.add_new_clip();
|
let (_index, clip) = self.project.pool.add_new_clip();
|
||||||
// autocolor: new clip colors from scene and track color
|
// autocolor: new clip colors from scene and track color
|
||||||
let color = track.color.base.mix(scene.color.base, 0.5);
|
let color = track.color.base.mix(scene.color.base, 0.5);
|
||||||
clip.try_write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
clip.try_write().unwrap().color = ItemColor::random_near(color, 0.2).into();
|
||||||
|
|
@ -450,7 +327,7 @@ impl App {
|
||||||
std::mem::swap(&mut swapped, slot);
|
std::mem::swap(&mut swapped, slot);
|
||||||
}
|
}
|
||||||
if let Some(clip) = swapped {
|
if let Some(clip) = swapped {
|
||||||
self.pool.delete_clip(&clip.try_read().unwrap());
|
self.project.pool.delete_clip(&clip.try_read().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
||||||
///
|
///
|
||||||
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
/// let mut clip = tek::MidiClip::new("clip", true, 1, None, None);
|
||||||
/// clip.set_length(96);
|
/// clip.set_length(96);
|
||||||
/// clip.toggle_loop();
|
/// clip.toggle_loop(None);
|
||||||
/// clip.record_event(12, ::tengri::midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
/// clip.record_event(12, ::tengri::midly::MidiMessage::NoteOn { key: 36.into(), vel: 100.into() });
|
||||||
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
/// assert!(clip.contains_note_on(36.into(), 6, 18));
|
||||||
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
/// assert_eq!(&clip.notes, &clip.duplicate().notes);
|
||||||
|
|
|
||||||
42
src/mix.rs
42
src/mix.rs
|
|
@ -1,42 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub enum MixingMode {
|
|
||||||
#[default] Summing,
|
|
||||||
Average,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mix_summing <const N: usize> (
|
|
||||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
|
||||||
) -> bool {
|
|
||||||
let channels = buffer.len();
|
|
||||||
for index in 0..frames {
|
|
||||||
if let Some(frame) = next() {
|
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
|
||||||
let channel = channel % channels;
|
|
||||||
buffer[channel][index] += sample * gain;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mix_average <const N: usize> (
|
|
||||||
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
|
||||||
) -> bool {
|
|
||||||
let channels = buffer.len();
|
|
||||||
for index in 0..frames {
|
|
||||||
if let Some(frame) = next() {
|
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
|
||||||
let channel = channel % channels;
|
|
||||||
let value = buffer[channel][index];
|
|
||||||
buffer[channel][index] = (value + sample * gain) / 2.0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
493
src/pool.rs
493
src/pool.rs
|
|
@ -1,493 +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<T: HasPool
|
|
||||||
+ Namespace<bool>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<PathBuf>
|
|
||||||
+ Namespace<MidiClip>
|
|
||||||
+ Namespace<ItemColor>
|
|
||||||
+ Namespace<BrowseCommand>
|
|
||||||
> PoolController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(PoolCommand = "pool")]
|
|
||||||
pub trait PoolController: HasPool
|
|
||||||
+ Namespace<bool>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<PathBuf>
|
|
||||||
+ Namespace<MidiClip>
|
|
||||||
+ Namespace<ItemColor>
|
|
||||||
+ Namespace<BrowseCommand>
|
|
||||||
{
|
|
||||||
|
|
||||||
#[command(Show = "show")]
|
|
||||||
/// Toggle visibility of pool
|
|
||||||
fn show (&mut self, visible: bool) -> Perhaps<PoolCommand> {
|
|
||||||
self.pool_mut().visible = visible;
|
|
||||||
Ok(Some(PoolCommand::Show { visible: !visible }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set selected clip
|
|
||||||
#[command(Select = "select")]
|
|
||||||
fn select (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
|
||||||
self.pool_mut().set_clip_index(index);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
///// Import from file
|
|
||||||
//#[command(Browse = "browse")]
|
|
||||||
//fn browse (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
|
||||||
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
|
||||||
//command.act(browse)?.map(|command|PoolCommand::Browse{command})
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Import clip to pool from file
|
|
||||||
#[command(Import = "import")]
|
|
||||||
fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCommand>
|
|
||||||
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(PoolCommand::Add { index, clip }.dispatch(self)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
///// Export to file
|
|
||||||
//#[command(Export = "export")]
|
|
||||||
//fn export (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
|
||||||
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
|
||||||
//command.act(browse)?.map(|command|PoolCommand::Export{command})
|
|
||||||
//} else {
|
|
||||||
//None
|
|
||||||
//})
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Delete a clip from the pool
|
|
||||||
#[command(Delete = "delete")]
|
|
||||||
fn delete (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
|
||||||
let clip = self.pool_mut().clips_mut().remove(index).try_read().unwrap().clone();
|
|
||||||
Ok(Some(PoolCommand::Add { index, clip }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Switch places of two clips in pool
|
|
||||||
#[command(Swap = "swap")]
|
|
||||||
fn swap (&mut self, index: usize, other: usize) -> Perhaps<PoolCommand> {
|
|
||||||
self.pool_mut().clips_mut().swap(index, other);
|
|
||||||
Ok(Some(PoolCommand::Swap { index, other }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add clip to pool
|
|
||||||
#[command(Add = "add")]
|
|
||||||
fn clip_add (&mut self, index: usize, clip: MidiClip) -> Perhaps<PoolCommand> {
|
|
||||||
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(PoolCommand::Delete { index }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set name of clip
|
|
||||||
#[command(SetName = "set-name")]
|
|
||||||
fn clip_set_name (&mut self, index: usize, name: Arc<str>) -> Perhaps<PoolCommand> {
|
|
||||||
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(PoolCommand::SetName { index, name: old_name }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set length of clip
|
|
||||||
#[command(SetLength = "set-length")]
|
|
||||||
fn clip_set_length (&mut self, index: usize, length: usize) -> Perhaps<PoolCommand> {
|
|
||||||
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(PoolCommand::SetLength { index, length: old_len }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set color of clip
|
|
||||||
#[command(SetColor = "set-color")]
|
|
||||||
fn clip_set_color (&mut self, index: usize, color: ItemColor) -> Perhaps<PoolCommand> {
|
|
||||||
let mut color = ItemTheme::from(color);
|
|
||||||
std::mem::swap(&mut color, &mut self.pool().clips()[index].try_write().unwrap().color);
|
|
||||||
Ok(Some(PoolCommand::SetColor { index, color: color.base }))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enter crop mode
|
|
||||||
#[command(CropBegin = "crop/begin")]
|
|
||||||
fn crop_begin (&mut self) -> Perhaps<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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|PoolCommand::CropSet { _length: l }))
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(CropNext = "crop/next")]
|
|
||||||
fn crop_next (&mut self) -> Perhaps<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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<PoolCommand> {
|
|
||||||
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(PoolCommand::RenameSet { value: old_name })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(RenameSet = "rename/set")]
|
|
||||||
fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCommand> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
87
src/port.rs
87
src/port.rs
|
|
@ -1,87 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
|
||||||
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
|
|
||||||
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
|
|
||||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
|
||||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
|
||||||
|
|
||||||
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
|
||||||
track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
|
||||||
track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
|
||||||
track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
|
||||||
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
|
||||||
-> impl Draw<Tui> + use<'a, T>
|
|
||||||
{
|
|
||||||
let ins = ports.len() as u16;
|
|
||||||
let frame = Outer(true, Style::default().fg(g(96)));
|
|
||||||
border(true, frame, field_v(theme, title, iter_south({
|
|
||||||
move||ports.iter().enumerate().map(|(index, port)|{
|
|
||||||
east!(" ", index, " ", port.port_name()).align_w().full_h()
|
|
||||||
})
|
|
||||||
})).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
|
||||||
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
|
|
||||||
) -> impl Draw<Tui> + 'a {
|
|
||||||
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
|
|
||||||
iter(items,
|
|
||||||
move|(_index, name, connections, y, y2): Item<'a>, _| south(
|
|
||||||
bold(true, fg_bg(fg, bg, east(" ", name).align_w())).full_h(),
|
|
||||||
iter(||connections.iter(), move|connect: &'a Connect, index|{
|
|
||||||
bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16)
|
|
||||||
})
|
|
||||||
).exact_h((y2 - y) as u16).push_y(y as u16))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Junction<T: JackPort>(T);
|
|
||||||
|
|
||||||
impl<T: JackPort> Draw<Tui> for Junction<T> {
|
|
||||||
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
|
||||||
T::KIND.draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::command(AudioInput)]
|
|
||||||
#[tek_proc::keyword(AudioInput)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AudioInputCommand {
|
|
||||||
Close,
|
|
||||||
Connect(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::command(AudioOutput)]
|
|
||||||
#[tek_proc::keyword(AudioOutput)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AudioOutputCommand {
|
|
||||||
Close,
|
|
||||||
Connect(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::command(MidiInput)]
|
|
||||||
#[tek_proc::keyword(MidiInput)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MidiInputCommand {
|
|
||||||
Close,
|
|
||||||
Connect(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tek_proc::command(MidiOutput)]
|
|
||||||
#[tek_proc::keyword(MidiOutput)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MidiOutputCommand {
|
|
||||||
Close,
|
|
||||||
Connect(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
impl_as_ref!(Option<Project>: |self: App|todo!());
|
||||||
impl AsRef<Option<Project>> for App {
|
impl_as_mut!(Option<Project>: |self: App|todo!());
|
||||||
fn as_ref (&self) -> &Option<Project> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsMut<Option<Project>> for App {
|
|
||||||
fn as_mut (&mut self) -> &mut Option<Project> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasProject: AsRef<Option<Project>> + AsMut<Option<Project>> {}
|
pub trait HasProject: AsRef<Option<Project>> + AsMut<Option<Project>> {}
|
||||||
|
|
||||||
impl<T: AsRef<Option<Project>> + AsMut<Option<Project>>> HasProject for T {}
|
impl<T: AsRef<Option<Project>> + AsMut<Option<Project>>> HasProject for T {}
|
||||||
|
|
||||||
impl<T: HasProject> ProjectController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(ProjectCommand)]
|
|
||||||
pub trait ProjectController: HasProject {
|
|
||||||
|
|
||||||
#[command(LoadLast = "load-last")]
|
|
||||||
fn load_last (&mut self) -> Perhaps<ProjectCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Load = "load")]
|
|
||||||
fn load (&mut self) -> Perhaps<ProjectCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Arranger.
|
/// Arranger.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -55,6 +26,8 @@ pub struct Project {
|
||||||
pub size_inner: Sizer,
|
pub size_inner: Sizer,
|
||||||
/// Selected UI element
|
/// Selected UI element
|
||||||
pub selection: Selection,
|
pub selection: Selection,
|
||||||
|
/// Contains all recently created clips.
|
||||||
|
pub pool: Pool,
|
||||||
/// Source of time
|
/// Source of time
|
||||||
#[cfg(feature = "clock")] pub clock: Clock,
|
#[cfg(feature = "clock")] pub clock: Clock,
|
||||||
/// Allows one MIDI clip to be edited
|
/// Allows one MIDI clip to be edited
|
||||||
|
|
@ -137,7 +110,6 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasJack<'static> for Project { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
|
||||||
impl_has!(Jack<'static>: |self: Project| self.jack);
|
impl_has!(Jack<'static>: |self: Project| self.jack);
|
||||||
impl_has!(Sizer: |self: Project| self.size);
|
impl_has!(Sizer: |self: Project| self.size);
|
||||||
impl_has!(Clock: |self: Project| self.clock);
|
impl_has!(Clock: |self: Project| self.clock);
|
||||||
|
|
@ -147,6 +119,15 @@ impl_as_ref_opt!(Scene: |self: Project| self.selected_scene());
|
||||||
impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
|
impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
|
||||||
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
|
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
|
||||||
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut self.editor);
|
impl_as_mut!(Option<MidiEditor>: |self: Project| &mut self.editor);
|
||||||
|
impl HasJack<'static> for Project {
|
||||||
|
fn jack (&self) -> &Jack<'static> { &self.jack }
|
||||||
|
}
|
||||||
|
impl 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!() }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn swap_value <T: Clone + PartialEq, U> (
|
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||||
target: &mut T, value: &T, returned: impl Fn(T)->U
|
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||||
|
|
@ -57,82 +57,7 @@ pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasClock
|
//impl<T: HasClock> Act<T> for ClockCmd {
|
||||||
+ Namespace<u32>
|
|
||||||
+ Namespace<f64>
|
|
||||||
+ Namespace<Option<u32>>
|
|
||||||
> ClockController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(ClockCommand = "clock")]
|
|
||||||
pub trait ClockController: HasClock
|
|
||||||
+ Namespace<u32>
|
|
||||||
+ Namespace<f64>
|
|
||||||
+ Namespace<Option<u32>>
|
|
||||||
{
|
|
||||||
#[command(SeekUsec = "usec")]
|
|
||||||
fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCommand> {
|
|
||||||
self.clock().playhead.update_from_usec(usec);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SeekSample = "sample")]
|
|
||||||
fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCommand> {
|
|
||||||
self.clock().playhead.update_from_sample(sample);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SeekPulse = "pulse")]
|
|
||||||
fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCommand> {
|
|
||||||
self.clock().playhead.update_from_pulse(pulse);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetBpm = "bpm")]
|
|
||||||
fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCommand> {
|
|
||||||
Ok(Some(ClockCommand::SetBpm {
|
|
||||||
bpm: self.clock().timebase().bpm.set(bpm)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetQuant = "quant")]
|
|
||||||
fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCommand> {
|
|
||||||
Ok(Some(ClockCommand::SetQuant {
|
|
||||||
quant: self.clock().quant.set(quant)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetSync = "sync")]
|
|
||||||
fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCommand> {
|
|
||||||
Ok(Some(ClockCommand::SetSync {
|
|
||||||
sync: self.clock().sync.set(sync)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Play = "play")]
|
|
||||||
fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
|
||||||
self.clock().play_from(position)?;
|
|
||||||
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Pause = "pause")]
|
|
||||||
fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
|
||||||
self.clock().pause_at(position)?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(TogglePlayback = "toggle")]
|
|
||||||
fn toggle (&mut self, position: u32) -> Perhaps<ClockCommand> {
|
|
||||||
Ok(if self.clock().is_rolling() {
|
|
||||||
self.clock().pause_at(Some(position))?;
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
self.clock().play_from(Some(position))?;
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<T: HasClock> Act<T> for ClockCommand {
|
|
||||||
//fn act (&self, state: &mut T) -> Perhaps<Self> {
|
//fn act (&self, state: &mut T) -> Perhaps<Self> {
|
||||||
//self.act(state.clock_mut()) // awesome
|
//self.act(state.clock_mut()) // awesome
|
||||||
//}
|
//}
|
||||||
|
|
@ -449,22 +374,6 @@ impl_time_unit!(Pulse);
|
||||||
impl_time_unit!(Bpm);
|
impl_time_unit!(Bpm);
|
||||||
impl_time_unit!(LaunchSync);
|
impl_time_unit!(LaunchSync);
|
||||||
|
|
||||||
/// ```
|
|
||||||
/// let x = "";
|
|
||||||
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
|
||||||
/// ```
|
|
||||||
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
|
|
||||||
let theme = ItemTheme::G[96];
|
|
||||||
let sr = field_h(theme, "SR", sr);
|
|
||||||
let buf = field_h(theme, "Buf", buf);
|
|
||||||
let lat = field_h(theme, "Lat", lat);
|
|
||||||
bg(Black, east!(above(
|
|
||||||
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
|
|
||||||
east!(sr, buf, lat).align_e().full_wh(),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ```
|
/// ```
|
||||||
/// let _ = tek::button_play_pause(true, true);
|
/// let _ = tek::button_play_pause(true, true);
|
||||||
/// let _ = tek::button_play_pause(true, false);
|
/// let _ = tek::button_play_pause(true, false);
|
||||||
|
|
@ -485,3 +394,80 @@ pub fn button_play_pause <'a> (playing: bool, compact: bool) -> impl Draw<Tui> {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contains memoized renders of clock values.
|
||||||
|
///
|
||||||
|
/// Performance optimization.
|
||||||
|
#[derive(Debug)] pub struct ClockView {
|
||||||
|
pub sr: Memo<Option<(bool, f64)>, String>,
|
||||||
|
pub buf: Memo<Option<f64>, String>,
|
||||||
|
pub lat: Memo<Option<f64>, String>,
|
||||||
|
pub bpm: Memo<Option<f64>, String>,
|
||||||
|
pub beat: Memo<Option<f64>, String>,
|
||||||
|
pub time: Memo<Option<f64>, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_default!(ClockView: {
|
||||||
|
let mut beat = String::with_capacity(16);
|
||||||
|
let _ = write!(beat, "{}", Self::BEAT_EMPTY);
|
||||||
|
let mut time = String::with_capacity(16);
|
||||||
|
let _ = write!(time, "{}", Self::TIME_EMPTY);
|
||||||
|
let mut bpm = String::with_capacity(16);
|
||||||
|
let _ = write!(bpm, "{}", Self::BPM_EMPTY);
|
||||||
|
Self {
|
||||||
|
beat: Memo::new(None, beat),
|
||||||
|
time: Memo::new(None, time),
|
||||||
|
bpm: Memo::new(None, bpm),
|
||||||
|
sr: Memo::new(None, String::with_capacity(16)),
|
||||||
|
buf: Memo::new(None, String::with_capacity(16)),
|
||||||
|
lat: Memo::new(None, String::with_capacity(16)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
impl ClockView {
|
||||||
|
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
||||||
|
pub const TIME_EMPTY: &'static str = "-.---s";
|
||||||
|
pub const BPM_EMPTY: &'static str = "---.---";
|
||||||
|
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
||||||
|
let rate = clock.timebase.sr.get();
|
||||||
|
let chunk = clock.chunk.load(Relaxed) as f64;
|
||||||
|
let lat = chunk / rate * 1000.;
|
||||||
|
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
||||||
|
let mut cache = cache.try_write().unwrap();
|
||||||
|
|
||||||
|
cache.buf.update(
|
||||||
|
Some(chunk), rewrite!(buf, "{chunk}")
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.lat.update(
|
||||||
|
Some(lat), rewrite!(buf, "{lat:.1}ms")
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.sr.update(
|
||||||
|
Some((compact, rate)), |buf: &mut String,_,_|{
|
||||||
|
buf.clear();
|
||||||
|
if compact {
|
||||||
|
write!(buf, "{:.1}kHz", rate / 1000.)
|
||||||
|
} else {
|
||||||
|
write!(buf, "{:.0}Hz", rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(now) = clock.started.try_read().unwrap().as_ref().map(delta) {
|
||||||
|
let pulse = clock.timebase.usecs_to_pulse(now);
|
||||||
|
let time = now/1000000.;
|
||||||
|
let bpm = clock.timebase.bpm.get();
|
||||||
|
cache.beat.update(Some(pulse), |buf, _, _|{
|
||||||
|
buf.clear();
|
||||||
|
clock.timebase.format_beats_1_to(buf, pulse)
|
||||||
|
});
|
||||||
|
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
||||||
|
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
||||||
|
} else {
|
||||||
|
cache.beat.update(None, rewrite!(buf, "{}", ClockView::BEAT_EMPTY));
|
||||||
|
cache.time.update(None, rewrite!(buf, "{}", ClockView::TIME_EMPTY));
|
||||||
|
cache.bpm.update(None, rewrite!(buf, "{}", ClockView::BPM_EMPTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -99,7 +99,7 @@ fn tek_jack_event (state: &mut App, event: JackEvent) {
|
||||||
#[tek_proc::command(Device)]
|
#[tek_proc::command(Device)]
|
||||||
#[tek_proc::keyword(Device)]
|
#[tek_proc::keyword(Device)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DeviceCommand {}
|
pub enum DeviceCmd {}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
pub fn name (&self) -> &str {
|
pub fn name (&self) -> &str {
|
||||||
|
|
@ -198,15 +198,3 @@ pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||||
self.as_mut()
|
self.as_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_device <'a> (state: &'a App) -> impl Draw<Tui> {
|
|
||||||
let selected = state.dialog.device_kind().unwrap();
|
|
||||||
south(
|
|
||||||
bold(true, "Add device"),
|
|
||||||
iter_south(move||device_kinds().iter().enumerate().map(move|(i, _label)|{
|
|
||||||
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
|
||||||
let l = if i == selected { "[ " } else { " " };
|
|
||||||
let r = if i == selected { " ]" } else { " " };
|
|
||||||
bg(b, east(l, west(r, "FIXME device name"))).full_w()
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
95
src/proj_mixer.rs
Normal file
95
src/proj_mixer.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub enum MixingMode {
|
||||||
|
#[default] Summing,
|
||||||
|
Average,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mix_summing <const N: usize> (
|
||||||
|
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
||||||
|
) -> bool {
|
||||||
|
let channels = buffer.len();
|
||||||
|
for index in 0..frames {
|
||||||
|
if let Some(frame) = next() {
|
||||||
|
for (channel, sample) in frame.iter().enumerate() {
|
||||||
|
let channel = channel % channels;
|
||||||
|
buffer[channel][index] += sample * gain;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mix_average <const N: usize> (
|
||||||
|
buffer: &mut [Vec<f32>], gain: f32, frames: usize, mut next: impl FnMut()->Option<[f32;N]>,
|
||||||
|
) -> bool {
|
||||||
|
let channels = buffer.len();
|
||||||
|
for index in 0..frames {
|
||||||
|
if let Some(frame) = next() {
|
||||||
|
for (channel, sample) in frame.iter().enumerate() {
|
||||||
|
let channel = channel % channels;
|
||||||
|
let value = buffer[channel][index];
|
||||||
|
buffer[channel][index] = (value + sample * gain) / 2.0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)] pub enum MeteringMode {
|
||||||
|
#[default] Rms,
|
||||||
|
Log10,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct Log10Meter(pub f32);
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct RmsMeter(pub f32);
|
||||||
|
|
||||||
|
impl_draw!(|self: RmsMeter, to: Tui|{
|
||||||
|
let XYWH(x, y, w, h) = to.area().into();
|
||||||
|
let signal = f32::max(0.0, f32::min(100.0, self.0.abs()));
|
||||||
|
let v = (signal * h as f32).ceil() as u16;
|
||||||
|
let y2 = y + h;
|
||||||
|
//to.blit(&format!("\r{v} {} {signal}", self.0), x * 30, y, Some(Style::default()));
|
||||||
|
for y in y..(y + v) {
|
||||||
|
for x in x..(x + w) {
|
||||||
|
to.blit(&"▌", x, y2.saturating_sub(y), Some(Style::default().green()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(to.area().into()))
|
||||||
|
});
|
||||||
|
|
||||||
|
impl_draw!(|self: Log10Meter, to: Tui| {
|
||||||
|
let XYWH(x, y, w, h) = to.area().into();
|
||||||
|
let signal = 100.0 - f32::max(0.0, f32::min(100.0, self.0.abs()));
|
||||||
|
let v = (signal * h as f32 / 100.0).ceil() as u16;
|
||||||
|
let y2 = y + h;
|
||||||
|
//to.blit(&format!("\r{v} {} {signal}", self.0), x * 20, y, None);
|
||||||
|
for y in y..(y + v) {
|
||||||
|
for x in x..(x + w) {
|
||||||
|
to.blit(&"▌", x, y2 - y, Some(Style::default().green()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(to.area().into()))
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn to_log10 (samples: &[f32]) -> f32 {
|
||||||
|
let total: f32 = samples.iter().map(|x|x.abs()).sum();
|
||||||
|
let count = samples.len() as f32;
|
||||||
|
10. * (total / count).log10()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_rms (samples: &[f32]) -> f32 {
|
||||||
|
let sum = samples.iter()
|
||||||
|
.map(|s|*s)
|
||||||
|
.reduce(|sum, sample|sum + sample.abs())
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
(sum / samples.len() as f32).sqrt()
|
||||||
|
}
|
||||||
236
src/proj_pool.rs
Normal file
236
src/proj_pool.rs
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
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),
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
}
|
||||||
46
src/proj_port.rs
Normal file
46
src/proj_port.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::*;
|
||||||
|
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
||||||
|
impl_has!(Vec<MidiInput>: |self: Project| self.midi_ins);
|
||||||
|
impl_has!(Vec<MidiOutput>: |self: Project| self.midi_outs);
|
||||||
|
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||||
|
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||||
|
|
||||||
|
pub struct Junction<T: JackPort>(T);
|
||||||
|
|
||||||
|
impl<T: JackPort> Draw<Tui> for Junction<T> {
|
||||||
|
fn draw (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
T::KIND.draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(AudioInput)]
|
||||||
|
#[tek_proc::keyword(AudioInput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AudioInputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(AudioOutput)]
|
||||||
|
#[tek_proc::keyword(AudioOutput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AudioOutputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(MidiInput)]
|
||||||
|
#[tek_proc::keyword(MidiInput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MidiInputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::command(MidiOutput)]
|
||||||
|
#[tek_proc::keyword(MidiOutput)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum MidiOutputCmd {
|
||||||
|
Close,
|
||||||
|
Connect(Arc<str>),
|
||||||
|
}
|
||||||
|
|
@ -67,55 +67,6 @@ pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasScene
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<ItemTheme>
|
|
||||||
> SceneController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(SceneCommand)]
|
|
||||||
pub trait SceneController: HasScene
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<ItemTheme>
|
|
||||||
{
|
|
||||||
#[command(SetSize = "size")]
|
|
||||||
fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCommand>
|
|
||||||
where Self: Namespace<usize>
|
|
||||||
{
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetZoom = "zoom")]
|
|
||||||
fn scene_set_zoom (&mut self, _size: usize) -> Perhaps<SceneCommand>
|
|
||||||
where Self: Namespace<usize>
|
|
||||||
{
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetName = "name")]
|
|
||||||
fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCommand>
|
|
||||||
where Self: Namespace<Arc<str>>
|
|
||||||
{
|
|
||||||
Ok(self.scene_mut().map(|scene|swap_value(
|
|
||||||
&mut scene.name,
|
|
||||||
&name,
|
|
||||||
|name|SceneCommand::SetName { name }
|
|
||||||
)).transpose()?.flatten())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(SetColor = "color")]
|
|
||||||
fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCommand>
|
|
||||||
where Self: Namespace<ItemTheme>
|
|
||||||
{
|
|
||||||
Ok(self.scene_mut().map(|scene|swap_value(
|
|
||||||
&mut scene.color,
|
|
||||||
&color,
|
|
||||||
|color|SceneCommand::SetColor { color }
|
|
||||||
)).transpose()?.flatten())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
def_sizes_iter!(ScenesSizes => Scene);
|
def_sizes_iter!(ScenesSizes => Scene);
|
||||||
|
|
@ -170,59 +121,6 @@ pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasScenes
|
|
||||||
+ HasTracks
|
|
||||||
+ HasTrackScroll
|
|
||||||
+ HasSelection
|
|
||||||
+ HasJack<'static>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Option<ItemTheme>>
|
|
||||||
+ Namespace<Option<Arc<str>>>
|
|
||||||
> ScenesController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(ScenesCommand)]
|
|
||||||
pub trait ScenesController: HasScenes
|
|
||||||
+ HasTracks
|
|
||||||
+ HasTrackScroll
|
|
||||||
+ HasSelection
|
|
||||||
+ HasJack<'static>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Option<ItemTheme>>
|
|
||||||
+ Namespace<Option<Arc<str>>>
|
|
||||||
{
|
|
||||||
#[command(Add = "add")]
|
|
||||||
fn scenes_add (&mut self) -> Perhaps<ScenesCommand>
|
|
||||||
where Self: HasScenes + HasJack<'static>
|
|
||||||
{
|
|
||||||
let (_index, _) = self.scenes_add_one(None, None)?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
#[command(Select = "select")]
|
|
||||||
fn scene_select (&mut self) -> Perhaps<ScenesCommand> {
|
|
||||||
let scenes = self.scenes().len();
|
|
||||||
let select = self.selection_mut().select_scene(scenes);
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Next = "next")]
|
|
||||||
fn scene_select_next (&mut self) -> Perhaps<ScenesCommand> {
|
|
||||||
let scenes = self.scenes().len();
|
|
||||||
let select = self.selection().select_scene_next(scenes);
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Prev = "prev")]
|
|
||||||
fn scene_select_prev (&mut self) -> Perhaps<ScenesCommand> {
|
|
||||||
let select = self.selection_mut().select_scene_prev();
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
||||||
fn h_scenes (&self) -> u16;
|
fn h_scenes (&self) -> u16;
|
||||||
fn w_side (&self) -> u16;
|
fn w_side (&self) -> u16;
|
||||||
|
|
@ -280,30 +178,6 @@ impl ScenesView for Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_scene_name <'a> (
|
|
||||||
select: &Selection,
|
|
||||||
editor: Option<&'a MidiEditor>,
|
|
||||||
index: usize,
|
|
||||||
scene: &Scene,
|
|
||||||
editing: bool
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
|
||||||
7
|
|
||||||
} else {
|
|
||||||
Scene::DEFAULT_HEIGHT as u16
|
|
||||||
};
|
|
||||||
let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w();
|
|
||||||
let b = when(select.scene() == Some(index) && editing, south(
|
|
||||||
editor.as_ref().map(|e|e.clip_status()),
|
|
||||||
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
|
|
||||||
let c = if select.scene() == Some(index) {
|
|
||||||
scene.color.light.term
|
|
||||||
} else {
|
|
||||||
scene.color.base.term
|
|
||||||
};
|
|
||||||
bg(c, south(a, b).align_nw().exact_h(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait HasSceneScroll: HasScenes {
|
pub trait HasSceneScroll: HasScenes {
|
||||||
fn scene_scroll (&self) -> usize;
|
fn scene_scroll (&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
@ -319,51 +193,3 @@ impl HasSceneScroll for App {
|
||||||
self.project.scene_scroll()
|
self.project.scene_scroll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
|
||||||
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
|
||||||
let clip = clip.try_read().unwrap();
|
|
||||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
|
||||||
} else {
|
|
||||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_scene_bg (
|
|
||||||
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
|
||||||
) -> (Color, Color) {
|
|
||||||
let mut outline = theme.base.term;
|
|
||||||
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
|
||||||
outline = theme.lighter.term;
|
|
||||||
theme.light.term
|
|
||||||
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
|
||||||
outline = theme.darkest.term;
|
|
||||||
theme.base.term
|
|
||||||
} else {
|
|
||||||
theme.dark.term
|
|
||||||
}, outline)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_scene_w (
|
|
||||||
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
|
||||||
) -> u16 {
|
|
||||||
if select.track() == Some(track_index) && let Some(editor) = editor {
|
|
||||||
(editor.size.w() as usize).max(24).max(track.width) as u16
|
|
||||||
} else {
|
|
||||||
track.width as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_scene_y (
|
|
||||||
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
|
||||||
) -> u16 {
|
|
||||||
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
|
||||||
editor.size.h().max(12)
|
|
||||||
} else {
|
|
||||||
Scene::DEFAULT_HEIGHT as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
|
||||||
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
|
||||||
}
|
|
||||||
|
|
@ -58,7 +58,7 @@ impl Track {
|
||||||
).exact_w((x2 - x1) as u16)
|
).exact_w((x2 - x1) as u16)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
pub fn stop (&mut self) -> Perhaps<TrackCommand> {
|
pub fn stop (&mut self) -> Perhaps<TrackCmd> {
|
||||||
self.sequencer.enqueue_next(None);
|
self.sequencer.enqueue_next(None);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -85,71 +85,6 @@ pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasTrack
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<ItemTheme>
|
|
||||||
+ Namespace<Option<bool>>
|
|
||||||
> TrackController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(TrackCommand)]
|
|
||||||
pub trait TrackController: HasTrack
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<str>>
|
|
||||||
+ Namespace<ItemTheme>
|
|
||||||
+ Namespace<Option<bool>>
|
|
||||||
{
|
|
||||||
#[command(Stop = "track/stop")]
|
|
||||||
fn track_stop (&mut self) -> Perhaps<TrackCommand> {
|
|
||||||
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
#[command(SetMute = "track/mute")]
|
|
||||||
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
#[command(SetSolo = "track/solo")]
|
|
||||||
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
#[command(SetSize = "track/size")]
|
|
||||||
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
#[command(SetZoom = "track/zoom")]
|
|
||||||
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
#[command(SetName = "track/name")]
|
|
||||||
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCommand> {
|
|
||||||
self.track_mut()
|
|
||||||
.map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name }))
|
|
||||||
.transpose()
|
|
||||||
.map(Option::flatten)
|
|
||||||
}
|
|
||||||
#[command(SetColor = "track/color")]
|
|
||||||
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCommand> {
|
|
||||||
self.track_mut()
|
|
||||||
.map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color }))
|
|
||||||
.transpose()
|
|
||||||
.map(Option::flatten)
|
|
||||||
}
|
|
||||||
#[command(SetRec = "track/rec")]
|
|
||||||
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCommand> {
|
|
||||||
self.track_mut()
|
|
||||||
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec }))
|
|
||||||
.transpose()
|
|
||||||
.map(Option::flatten)
|
|
||||||
}
|
|
||||||
#[command(SetMon = "track/mon")]
|
|
||||||
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCommand> {
|
|
||||||
self.track_mut()
|
|
||||||
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon }))
|
|
||||||
.transpose()
|
|
||||||
.map(Option::flatten)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
||||||
track.width as u16
|
track.width as u16
|
||||||
}
|
}
|
||||||
|
|
@ -260,90 +195,6 @@ pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasTracks
|
|
||||||
+ HasScenes
|
|
||||||
+ HasSelection
|
|
||||||
+ HasJack<'static>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<[Connect]>>
|
|
||||||
+ Namespace<Option<usize>>
|
|
||||||
+ Namespace<Option<Arc<str>>>
|
|
||||||
+ Namespace<Option<ItemTheme>>
|
|
||||||
+ Namespace<Option<Arc<[Connect]>>>
|
|
||||||
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
|
||||||
> TracksController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(TracksCommand)]
|
|
||||||
pub trait TracksController: HasTracks
|
|
||||||
+ HasScenes
|
|
||||||
+ HasSelection
|
|
||||||
+ HasJack<'static>
|
|
||||||
+ Namespace<usize>
|
|
||||||
+ Namespace<Arc<[Connect]>>
|
|
||||||
+ Namespace<Option<usize>>
|
|
||||||
+ Namespace<Option<Arc<str>>>
|
|
||||||
+ Namespace<Option<ItemTheme>>
|
|
||||||
+ Namespace<Option<Arc<[Connect]>>>
|
|
||||||
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
|
||||||
{
|
|
||||||
#[command(Stop = "stop")]
|
|
||||||
/// Stop all playing clips
|
|
||||||
fn tracks_stop_all (&mut self) -> Perhaps<TracksCommand> {
|
|
||||||
for track in self.tracks_mut().iter_mut() {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Launch = "launch")]
|
|
||||||
/// Launch multiple clips
|
|
||||||
fn tracks_launch (
|
|
||||||
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
|
|
||||||
) -> Perhaps<TracksCommand> {
|
|
||||||
if let Some(clips) = clips {
|
|
||||||
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
|
||||||
track.sequencer.enqueue_next(clip.as_ref());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for track in self.tracks_mut().iter_mut() {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Add = "add")]
|
|
||||||
fn tracks_add (&mut self) -> Perhaps<TracksCommand>
|
|
||||||
where Self: HasScenes + HasJack<'static>
|
|
||||||
{
|
|
||||||
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Select = "select")]
|
|
||||||
fn track_select (&mut self) -> Perhaps<TracksCommand> {
|
|
||||||
let tracks = self.tracks().len();
|
|
||||||
let select = self.selection_mut().select_track(tracks);
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Next = "next")]
|
|
||||||
fn track_select_next (&mut self) -> Perhaps<TracksCommand> {
|
|
||||||
let tracks = self.tracks().len();
|
|
||||||
let select = self.selection().select_track_next(tracks);
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(Prev = "prev")]
|
|
||||||
fn track_select_prev (&mut self) -> Perhaps<TracksCommand> {
|
|
||||||
let select = self.selection_mut().select_track_prev();
|
|
||||||
*self.selection_mut() = select;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
|
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
|
||||||
> TracksView for T {}
|
> TracksView for T {}
|
||||||
|
|
@ -696,31 +547,6 @@ impl HasTrackScroll for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_track_row_section <'a> (
|
|
||||||
_theme: ItemTheme,
|
|
||||||
button: impl Draw<Tui>,
|
|
||||||
button_add: impl Draw<Tui>,
|
|
||||||
content: impl Draw<Tui>,
|
|
||||||
) -> impl Draw<Tui> {
|
|
||||||
bar(
|
|
||||||
Split::West,
|
|
||||||
4u16,
|
|
||||||
button_add.align_ne().full_wh(),
|
|
||||||
bar(
|
|
||||||
Split::East,
|
|
||||||
20u16,
|
|
||||||
button.full_wh().align_nw(),
|
|
||||||
content.align_c().full_wh()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_track_header <'a> (theme: ItemTheme, content: impl Draw<Tui>)
|
|
||||||
-> impl Draw<Tui>
|
|
||||||
{
|
|
||||||
bg(theme.darker.term, content.align_e().full_w()).exact_w(12)
|
|
||||||
}
|
|
||||||
|
|
||||||
//pub fn per_track <'a, T: Draw<'_, Tui> + 'a, U: TracksSizes<'a>> (
|
//pub fn per_track <'a, T: Draw<'_, Tui> + 'a, U: TracksSizes<'a>> (
|
||||||
//tracks: impl Fn() -> U + Send + Sync + 'a,
|
//tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||||
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||||
|
|
@ -8,81 +8,6 @@ pub(crate) use symphonia::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait HasSampler: AsRef<Sampler> + AsMut<Sampler> {
|
|
||||||
fn sampler (&self) -> &Sampler {
|
|
||||||
self.as_ref()
|
|
||||||
}
|
|
||||||
fn sampler_mut (&mut self) -> &mut Sampler {
|
|
||||||
self.as_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(SamplerCommand = "sampler")]
|
|
||||||
pub trait SamplerController: HasSampler
|
|
||||||
+ Namespace<usize>
|
|
||||||
{
|
|
||||||
|
|
||||||
#[command(RecordToggle = "rec-toggle")]
|
|
||||||
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCommand>
|
|
||||||
where Self: Sized
|
|
||||||
{
|
|
||||||
let sampler = self.sampler_mut();
|
|
||||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
|
||||||
let _ = SamplerCommand::RecordFinish.dispatch(self)?;
|
|
||||||
// autoslice: continue recording at next slot
|
|
||||||
if recording != Some(slot) {
|
|
||||||
SamplerCommand::RecordBegin { slot }.dispatch(self)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(RecordBegin = "rec-begin")]
|
|
||||||
fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
|
||||||
let sampler = self.sampler_mut();
|
|
||||||
sampler.recording = Some((
|
|
||||||
slot,
|
|
||||||
Some(Arc::new(RwLock::new(Sample::new(
|
|
||||||
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
|
|
||||||
))))
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(RecordFinish = "rec-finish")]
|
|
||||||
fn record_finish (&mut self) -> Perhaps<SamplerCommand> {
|
|
||||||
let sampler = self.sampler_mut();
|
|
||||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
|
||||||
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
|
||||||
sample
|
|
||||||
}); // TODO: undo
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(RecordCancel = "rec-cancel")]
|
|
||||||
fn record_cancel (&mut self) -> Perhaps<SamplerCommand> {
|
|
||||||
self.sampler_mut().recording = None;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(PlaySample = "sample-play")]
|
|
||||||
fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
|
||||||
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)));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[command(StopSample = "sample-stop")]
|
|
||||||
fn sample_stop (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
|
||||||
todo!();
|
|
||||||
//Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Plays [Voice]s from [Sample]s.
|
/// Plays [Voice]s from [Sample]s.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -133,6 +58,17 @@ pub trait SamplerController: HasSampler
|
||||||
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
#[cfg(feature = "meter")] pub output_meters: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait HasSampler: AsRef<Sampler> + AsMut<Sampler> {
|
||||||
|
fn sampler (&self) -> &Sampler {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
fn sampler_mut (&mut self) -> &mut Sampler {
|
||||||
|
self.as_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
|
||||||
|
|
||||||
impl_audio!(Sampler: sampler_jack_process);
|
impl_audio!(Sampler: sampler_jack_process);
|
||||||
|
|
||||||
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
|
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
|
@ -314,84 +250,6 @@ impl Sampler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
|
||||||
if let Some(sample) = sample {
|
|
||||||
let sample = sample.try_read().unwrap();
|
|
||||||
format!("{:8}", sample.name)
|
|
||||||
//format!("{:8} {:3} {:6}-{:6}/{:6}",
|
|
||||||
//sample.name,
|
|
||||||
//sample.gain,
|
|
||||||
//sample.start,
|
|
||||||
//sample.end,
|
|
||||||
//sample.channels[0].len()
|
|
||||||
//)
|
|
||||||
} else {
|
|
||||||
String::from("........")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
|
||||||
let min_db = -64.0;
|
|
||||||
draw(move|to: &mut Tui|{
|
|
||||||
let xywh = to.area().into();
|
|
||||||
let XYWH(x, y, width, height) = xywh;
|
|
||||||
let area = Rect { x, y, width, height };
|
|
||||||
if let Some(sample) = &sample {
|
|
||||||
let sample = sample.try_read().unwrap();
|
|
||||||
let start = sample.start as f64;
|
|
||||||
let end = sample.end as f64;
|
|
||||||
let length = end - start;
|
|
||||||
let step = length / width as f64;
|
|
||||||
let mut t = start;
|
|
||||||
let mut lines = vec![];
|
|
||||||
while t < end {
|
|
||||||
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
|
|
||||||
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
|
|
||||||
let count = chunk.len() as f32;
|
|
||||||
let meter = 10. * (total / count).log10();
|
|
||||||
let x = t as f64;
|
|
||||||
let y = meter as f64;
|
|
||||||
lines.push(Line::new(x, min_db, x, y, Color::Green));
|
|
||||||
t += step / 2.;
|
|
||||||
}
|
|
||||||
to.buffer().map(|buf|{
|
|
||||||
Canvas::default()
|
|
||||||
.x_bounds([sample.start as f64, sample.end as f64])
|
|
||||||
.y_bounds([min_db, 0.])
|
|
||||||
.paint(|ctx| {
|
|
||||||
for line in lines.iter() {
|
|
||||||
ctx.draw(line);
|
|
||||||
}
|
|
||||||
//FIXME: proportions
|
|
||||||
//let text = "press record to finish sampling";
|
|
||||||
//ctx.print(
|
|
||||||
//(width - text.len() as u16) as f64 / 2.0,
|
|
||||||
//height as f64 / 2.0,
|
|
||||||
//text.red()
|
|
||||||
//);
|
|
||||||
})
|
|
||||||
.render(area, buf);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
to.buffer().map(|buf|{
|
|
||||||
Canvas::default()
|
|
||||||
.x_bounds([0.0, width as f64])
|
|
||||||
.y_bounds([0.0, height as f64])
|
|
||||||
.paint(|_ctx| {
|
|
||||||
//let text = "press record to begin sampling";
|
|
||||||
//ctx.print(
|
|
||||||
//(width - text.len() as u16) as f64 / 2.0,
|
|
||||||
//height as f64 / 2.0,
|
|
||||||
//text.red()
|
|
||||||
//);
|
|
||||||
})
|
|
||||||
.render(area, buf);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(Some(xywh))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||||
fn sampler_midi_in (
|
fn sampler_midi_in (
|
||||||
samples: &SampleKit<128>, voices: &Arc<RwLock<Vec<Voice>>>, RawMidi { time, bytes }: RawMidi
|
samples: &SampleKit<128>, voices: &Arc<RwLock<Vec<Voice>>>, RawMidi { time, bytes }: RawMidi
|
||||||
|
|
@ -411,71 +269,6 @@ fn sampler_midi_in (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_sample (
|
|
||||||
to: &mut Tui, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
|
|
||||||
) -> Usually<usize> {
|
|
||||||
let style = if focus { Style::default().green() } else { Style::default() };
|
|
||||||
if focus {
|
|
||||||
to.blit(&"🬴", x+1, y, Some(style.bold()));
|
|
||||||
}
|
|
||||||
let label1 = format!("{:3} {:12}",
|
|
||||||
note.map(|n|n.to_string()).unwrap_or(String::default()),
|
|
||||||
sample.name);
|
|
||||||
let label2 = format!("{:>6} {:>6} +0.0",
|
|
||||||
sample.start,
|
|
||||||
sample.end);
|
|
||||||
to.blit(&label1, x+2, y, Some(style.bold()));
|
|
||||||
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
|
|
||||||
Ok(label1.len() + label2.len() + 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
|
||||||
when(sample.is_some(), draw(move|to: &mut Tui|{
|
|
||||||
let sample = sample.unwrap().try_read().unwrap();
|
|
||||||
let theme = sample.color;
|
|
||||||
east!(
|
|
||||||
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
|
||||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
|
||||||
field_h(theme, "Start", format!("{:<8}", sample.start)),
|
|
||||||
field_h(theme, "End", format!("{:<8}", sample.end)),
|
|
||||||
field_h(theme, "Trans", "0"),
|
|
||||||
field_h(theme, "Gain", format!("{}", sample.gain)),
|
|
||||||
).draw(to)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
|
||||||
let a = draw(move|to: &mut Tui|{
|
|
||||||
let sample = sample.unwrap().try_read().unwrap();
|
|
||||||
let theme = sample.color;
|
|
||||||
south!(
|
|
||||||
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
|
|
||||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
|
|
||||||
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
|
|
||||||
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
|
|
||||||
field_h(theme, "Trans ", "0") .align_w().full_w(),
|
|
||||||
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
|
|
||||||
).exact_w(20).draw(to)
|
|
||||||
});
|
|
||||||
|
|
||||||
let b = draw(|to: &mut Tui|fg(Red, south!(
|
|
||||||
bold(true, "× No sample."),
|
|
||||||
"[r] record",
|
|
||||||
"[Shift-F9] import",
|
|
||||||
)).draw(to));
|
|
||||||
|
|
||||||
either(sample.is_some(), a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
|
||||||
bold(true, fg(g(224), sample
|
|
||||||
.map(|sample|{
|
|
||||||
let sample = sample.try_read().unwrap();
|
|
||||||
format!("Sample {}-{}", sample.start, sample.end)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(||"No sample".to_string())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the first sampler of the active track
|
/// Get the first sampler of the active track
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn sampler (&self) -> Option<&Sampler> {
|
pub fn sampler (&self) -> Option<&Sampler> {
|
||||||
780
src/tek.rs
780
src/tek.rs
|
|
@ -1,5 +1,34 @@
|
||||||
#![allow(clippy::unit_arg)]
|
#![allow(clippy::unit_arg)]
|
||||||
|
|
||||||
|
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::*;
|
||||||
|
mod midi_point; pub use self::midi_point::*;
|
||||||
|
mod midi_range; pub use self::midi_range::*;
|
||||||
|
|
||||||
|
mod smpl; pub use self::smpl::*;
|
||||||
|
mod smpl_sample; pub use self::smpl_sample::*;
|
||||||
|
mod smpl_add; pub use self::smpl_add::*;
|
||||||
|
mod smpl_kit; pub use self::smpl_kit::*;
|
||||||
|
mod smpl_voice; pub use self::smpl_voice::*;
|
||||||
|
|
||||||
|
mod midi_clip; pub use self::midi_clip::*;
|
||||||
|
mod midi_edit; pub use self::midi_edit::*;
|
||||||
|
mod midi_mon; pub use self::midi_mon::*;
|
||||||
|
mod midi_play; pub use self::midi_play::*;
|
||||||
|
mod midi_rec; pub use self::midi_rec::*;
|
||||||
|
mod midi_seq; pub use self::midi_seq::*;
|
||||||
|
|
||||||
|
mod time_base; pub use self::time_base::*;
|
||||||
|
mod time_moment; pub use self::time_moment::*;
|
||||||
|
mod time_ticker; pub use self::time_ticker::*;
|
||||||
|
|
||||||
/// Define a type alias for iterators of sized items (columns).
|
/// Define a type alias for iterators of sized items (columns).
|
||||||
macro_rules! def_sizes_iter {
|
macro_rules! def_sizes_iter {
|
||||||
($Type:ident => $($Item:ty),+) => {
|
($Type:ident => $($Item:ty),+) => {
|
||||||
|
|
@ -8,57 +37,24 @@ macro_rules! def_sizes_iter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod app; pub use self::app::*;
|
mod proj; pub use self::proj::*;
|
||||||
mod browse; pub use self::browse::*;
|
mod proj_clock; pub use self::proj_clock::*;
|
||||||
mod cli; pub use self::cli::*;
|
mod proj_device; pub use self::proj_device::*;
|
||||||
mod clock; pub use self::clock::*;
|
mod proj_mixer; pub use self::proj_mixer::*;
|
||||||
mod clock_view; pub use self::clock_view::*;
|
mod proj_port; pub use self::proj_port::*;
|
||||||
mod config; pub use self::config::*;
|
mod proj_scene; pub use self::proj_scene::*;
|
||||||
mod config_bind; pub use self::config_bind::*;
|
mod proj_select; pub use self::proj_select::*;
|
||||||
mod config_mode; pub use self::config_mode::*;
|
mod proj_track; pub use self::proj_track::*;
|
||||||
mod config_view; pub use self::config_view::*;
|
mod proj_pool; pub use self::proj_pool::*;
|
||||||
mod deps; pub use self::deps::*;
|
|
||||||
mod device; pub use self::device::*;
|
|
||||||
mod dialog; pub use self::dialog::*;
|
|
||||||
mod field; pub use self::field::*;
|
|
||||||
mod memo; pub use self::memo::*;
|
|
||||||
mod menu_item; pub use self::menu_item::*;
|
|
||||||
mod meter; pub use self::meter::*;
|
|
||||||
mod mix; pub use self::mix::*;
|
|
||||||
mod moment; pub use self::moment::*;
|
|
||||||
mod octave; pub use self::octave::*;
|
|
||||||
mod piano; pub use self::piano::*;
|
|
||||||
mod point; pub use self::point::*;
|
|
||||||
mod pool; pub use self::pool::*;
|
|
||||||
mod port; pub use self::port::*;
|
|
||||||
mod project; pub use self::project::*;
|
|
||||||
mod range; pub use self::range::*;
|
|
||||||
mod sampler; pub use self::sampler::*;
|
|
||||||
mod sample; pub use self::sample::*;
|
|
||||||
mod sample_add; pub use self::sample_add::*;
|
|
||||||
mod sample_kit; pub use self::sample_kit::*;
|
|
||||||
mod sample_voice; pub use self::sample_voice::*;
|
|
||||||
mod scene; pub use self::scene::*;
|
|
||||||
mod select; pub use self::select::*;
|
|
||||||
mod midi_clip; pub use self::midi_clip::*;
|
|
||||||
mod midi_clips; pub use self::midi_clips::*;
|
|
||||||
mod midi_edit; pub use self::midi_edit::*;
|
|
||||||
mod midi_mon; pub use self::midi_mon::*;
|
|
||||||
mod midi_play; pub use self::midi_play::*;
|
|
||||||
mod midi_rec; pub use self::midi_rec::*;
|
|
||||||
mod midi_seq; pub use self::midi_seq::*;
|
|
||||||
mod ticker; pub use self::ticker::*;
|
|
||||||
mod timebase; pub use self::timebase::*;
|
|
||||||
mod track; pub use self::track::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "lv2")] mod lv2;
|
mod tek_browse; pub use self::tek_browse::*;
|
||||||
#[cfg(feature = "lv2")] pub use self::lv2::*;
|
mod tek_cli; pub use self::tek_cli::*;
|
||||||
|
mod tek_ctrl; pub use self::tek_ctrl::*;
|
||||||
|
mod tek_view; pub use self::tek_view::*;
|
||||||
|
|
||||||
#[cfg(feature = "lv2_gui")] mod lv2_gui;
|
#[cfg(feature = "lv2")] mod device_lv2; #[cfg(feature = "lv2")] pub use self::device_lv2::*;
|
||||||
#[cfg(feature = "lv2_gui")] pub use self::lv2_gui::*;
|
#[cfg(feature = "lv2_gui")] mod device_lv2_gui; #[cfg(feature = "lv2_gui")] pub use self::device_lv2_gui::*;
|
||||||
|
#[cfg(feature = "vst2")] mod device_vst2; #[cfg(feature = "vst2")] pub use self::device_vst2::*;
|
||||||
#[cfg(feature = "vst2")] mod vst2;
|
|
||||||
#[cfg(feature = "vst2")] pub use self::vst2::*;
|
|
||||||
|
|
||||||
/// Command-line entrypoint.
|
/// Command-line entrypoint.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
@ -102,3 +98,689 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
||||||
Ok(app)
|
Ok(app)
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Total application state.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tek::{HasTracks, HasScenes, TracksView, ScenesView};
|
||||||
|
/// let mut app = tek::App::default();
|
||||||
|
/// let _ = app.scenes_add_one(None, None).unwrap();
|
||||||
|
/// let _ = app.update_clock();
|
||||||
|
/// app.project.editor = Some(Default::default());
|
||||||
|
/// //let _: Vec<_> = app.project.inputs_with_sizes().collect();
|
||||||
|
/// //let _: Vec<_> = app.project.outputs_with_sizes().collect();
|
||||||
|
/// let _: Vec<_> = app.project.tracks_with_sizes().collect();
|
||||||
|
/// //let _: Vec<_> = app.project.scenes_with_sizes(true, 10, 10).collect();
|
||||||
|
/// //let _: Vec<_> = app.scenes_with_colors(true, 10).collect();
|
||||||
|
/// //let _: Vec<_> = app.scenes_with_track_colors(true, 10, 10).collect();
|
||||||
|
/// let _ = app.project.w();
|
||||||
|
/// //let _ = app.project.w_sidebar();
|
||||||
|
/// //let _ = app.project.w_tracks_area();
|
||||||
|
/// let _ = app.project.h();
|
||||||
|
/// //let _ = app.project.h_tracks_area();
|
||||||
|
/// //let _ = app.project.h_inputs();
|
||||||
|
/// //let _ = app.project.h_outputs();
|
||||||
|
/// let _ = app.project.h_scenes();
|
||||||
|
/// ```
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
#[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(ItemColor)]
|
||||||
|
#[namespace(ItemTheme)]
|
||||||
|
#[namespace(MidiClip)]
|
||||||
|
#[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)]
|
||||||
|
#[namespace(Option<usize> get_opt_usize)]
|
||||||
|
#[namespace(PathBuf get_path_buf)]
|
||||||
|
#[namespace(Vec<Arc<str>> get_vec_arc_str)]
|
||||||
|
#[namespace(bool get_bool)]
|
||||||
|
#[namespace(f64 get_f64)]
|
||||||
|
#[namespace(isize)]
|
||||||
|
#[namespace(u16 get_u16)]
|
||||||
|
#[namespace(u32)]
|
||||||
|
#[namespace(u8)]
|
||||||
|
#[namespace(usize get_usize)]
|
||||||
|
pub struct App {
|
||||||
|
/// Exit flag
|
||||||
|
pub exit: Exit,
|
||||||
|
/// Base color.
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Must not be dropped for the duration of the process
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Display size
|
||||||
|
pub size: Sizer,
|
||||||
|
/// Performance counter
|
||||||
|
pub perf: PerfModel,
|
||||||
|
/// Available view modes and input bindings
|
||||||
|
pub config: Arc<Config>,
|
||||||
|
/// Currently selected mode
|
||||||
|
pub modes: Arc<RwLock<Vec<Arc<str>>>>,
|
||||||
|
/// Undo history
|
||||||
|
pub history: Vec<(AppCmd, Option<AppCmd>)>,
|
||||||
|
/// Dialog overlay
|
||||||
|
pub dialog: Dialog,
|
||||||
|
/// Contains the currently edited musical arrangement
|
||||||
|
pub project: Project,
|
||||||
|
/// Error, if any
|
||||||
|
pub error: Arc<RwLock<Option<Arc<str>>>>,
|
||||||
|
#[cfg(feature = "prof2")]
|
||||||
|
/// Tracing guard
|
||||||
|
pub guard: Option<tracing_flame::FlushGuard<std::io::BufWriter<std::fs::File>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// Create a new application instance from a backend, project, config, and mode
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let mut proj = tek::Project::default();
|
||||||
|
/// proj.jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
|
||||||
|
/// let config = "(mode hi (name Hello) (info World))";
|
||||||
|
/// let mut conf = tek::Config::load(tek::Config::default(), config).unwrap();
|
||||||
|
/// let tek = tek::App::new(None, proj, conf.into(), "hello");
|
||||||
|
/// ```
|
||||||
|
pub fn new (
|
||||||
|
exit: Option<Exit>,
|
||||||
|
project: Project,
|
||||||
|
config: Arc<Config>,
|
||||||
|
mode: impl AsRef<str>
|
||||||
|
) -> Self {
|
||||||
|
App {
|
||||||
|
exit: exit.unwrap_or_default(),
|
||||||
|
jack: project.jack.clone(),
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
dialog: Dialog::welcome(),
|
||||||
|
modes: Arc::new(RwLock::new(vec![mode.as_ref().into()])),
|
||||||
|
config,
|
||||||
|
project,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_arc_str <'a> (_: &App, src: impl Language) -> Perhaps<Arc<str>> {
|
||||||
|
Ok(src.src()?.map(|x|x.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_u16 <'a> (state: &App, src: impl Language) -> Perhaps<u16> {
|
||||||
|
Ok(Some(match src.word()? {
|
||||||
|
Some(":w/sidebar") => state.project.w_sidebar(state.editor().is_some()),
|
||||||
|
Some(":h/sample-detail") => 6.max(state.size.h() as u16 * 3 / 9),
|
||||||
|
_ => return try_to_u16(src)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_usize <'a> (state: &App, src: impl Language) -> Perhaps<usize> {
|
||||||
|
Ok(Some(match src.word()? {
|
||||||
|
Some(":scene-count") => state.scenes().len(),
|
||||||
|
Some(":track-count") => state.tracks().len(),
|
||||||
|
Some(":device-kind") => state.dialog.device_kind().unwrap_or(0),
|
||||||
|
Some(":device-kind/next") => state.dialog.device_kind_next().unwrap_or(0),
|
||||||
|
Some(":device-kind/prev") => state.dialog.device_kind_prev().unwrap_or(0),
|
||||||
|
_ => return try_to_usize(src)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_f64 <'a> (_state: &App, _src: impl Language) -> Perhaps<f64> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_bool <'a> (state: &App, src: impl Language) -> Perhaps<bool> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
"Y" => true,
|
||||||
|
"N" => false,
|
||||||
|
":mode/editor" => state.project.editor.is_some(),
|
||||||
|
":focused/dialog" => !matches!(state.dialog, Dialog::None),
|
||||||
|
":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),
|
||||||
|
_ => return err!("not bool: {word}")
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn get_selection <'a> (state: &App, src: impl Language) -> Perhaps<Selection> {
|
||||||
|
src.word()?.map(|word|Ok(match word {
|
||||||
|
":select/scene" => state.selection().select_scene(state.tracks().len()),
|
||||||
|
":select/scene/next" => state.selection().select_scene_next(state.scenes().len()),
|
||||||
|
":select/scene/prev" => state.selection().select_scene_prev(),
|
||||||
|
":select/track" => state.selection().select_track(state.tracks().len()),
|
||||||
|
":select/track/next" => state.selection().select_track_next(state.tracks().len()),
|
||||||
|
":select/track/prev" => state.selection().select_track_prev(),
|
||||||
|
_ => return err!("not selection: {word}")
|
||||||
|
})).transpose()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color <'a> (_: &App, src: impl Language) -> Perhaps<Color> {
|
||||||
|
if let Some(expr) = src.expr()? {
|
||||||
|
match (expr.head()?, expr.tail()?) {
|
||||||
|
(Some("g"), Some(tail)) => {
|
||||||
|
let n = try_to_u8(tail.head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::domain("not gray"))?;
|
||||||
|
Ok(Some(Color::Rgb(n, n, n)))
|
||||||
|
},
|
||||||
|
(Some("rgb"), Some(tail)) => {
|
||||||
|
let r = try_to_u8(tail.head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::domain("not red"))?;
|
||||||
|
let g = try_to_u8(tail.tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::domain("not green"))?;
|
||||||
|
let b = try_to_u8(tail.tail().tail().head().map_err(Into::into))?
|
||||||
|
.ok_or(LanguageError::domain("not blue"))?;
|
||||||
|
Ok(Some(Color::Rgb(r, g, b)))
|
||||||
|
},
|
||||||
|
(Some(_), _) => return err!("not a color expression: {expr}"),
|
||||||
|
(None, _) => return err!("not a color expression: {expr}"),
|
||||||
|
}
|
||||||
|
} else if let Ok(Some(sym)) = src.word() {
|
||||||
|
Ok(match sym {
|
||||||
|
":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
||||||
|
":color/fg" => Some(Color::Rgb(98, 92, 96)),
|
||||||
|
_ => return err!("not a color: {sym}")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return err!("not a color: {:?}", src.src()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn get_opt_u7 <'a> (state: &App, src: impl Language) -> Perhaps<Option<u7>> {
|
||||||
|
eval_word(src, |word|Ok(match word {
|
||||||
|
|
||||||
|
":editor/pitch" => Some((
|
||||||
|
state.editor().as_ref().map(|e|e.get_note_pos()).unwrap() as u8
|
||||||
|
).into()),
|
||||||
|
|
||||||
|
_ => return err!("unknown midi note: {word}")
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_usize <'a> (state: &App, src: impl Language) -> Perhaps<Option<usize>> {
|
||||||
|
eval_word(src, |word|Ok(match word {
|
||||||
|
|
||||||
|
":selected/scene" => state.selection().scene(),
|
||||||
|
":selected/track" => state.selection().track(),
|
||||||
|
|
||||||
|
_ => return err!("unknown opt<usize>: {word}")
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_u32 <'a> (_: &App, _src: impl Language) -> Perhaps<Option<u32>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_itemtheme <'a> (_: &App, _: impl Language) -> Perhaps<Option<ItemTheme>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_arc_array_connect <'a> (_: &App, _: impl Language) -> Perhaps<Option<Arc<[Connect]>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_arc_array_connect <'a> (_: &App, _: impl Language) -> Perhaps<Arc<[Connect]>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_arc_str <'a> (_: &App, _: impl Language) -> Perhaps<Option<Arc<str>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_opt_bool <'a> (_: &App, _: impl Language) -> Perhaps<Option<bool>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_path_buf <'a> (_: &App, _: impl Language) -> Perhaps<PathBuf> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
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!(Jack<'static>: |self: App|self.jack);
|
||||||
|
impl_has!(Pool: |self: App|self.project.pool);
|
||||||
|
impl_has!(Selection: |self: App|self.project.selection);
|
||||||
|
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
|
||||||
|
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
|
||||||
|
|
||||||
|
primitive!(u8: try_to_u8);
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
|
||||||
|
fn input_cmd (state: &mut Self, input: &TuiEvent) -> Usually<()> {
|
||||||
|
if let Some(bindings) = Self::input_cmd_find(state, input) {
|
||||||
|
return Self::input_cmd_exec(state, bindings)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_cmd_find (state: &mut Self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
|
||||||
|
for name in state.modes.read().iter().rev() {
|
||||||
|
let mode = state.config.get_mode(name).as_ref().map(Arc::clone);
|
||||||
|
if let Some(mode) = mode {
|
||||||
|
let binds = state.config.binds.clone();
|
||||||
|
for id in mode.keys.iter() {
|
||||||
|
if let Some(event_map) = binds.find(id)
|
||||||
|
&& let Some(bindings) = event_map.query(input) {
|
||||||
|
return Some(bindings.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_cmd_exec (state: &mut Self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
|
||||||
|
for binding in bindings.iter() {
|
||||||
|
for cmd in binding.commands.iter() {
|
||||||
|
if Self::cmd::<AppCmd>(state, cmd)? {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd <
|
||||||
|
T: Dispatch<Self> + Clone + Into<AppCmd>
|
||||||
|
> (state: &mut Self, src: &str) -> Usually<bool> where
|
||||||
|
Self: Namespace<T>,
|
||||||
|
{
|
||||||
|
if let Some(cmd) = Namespace::<T>::namespace(state, &src)? {
|
||||||
|
return Self::cmd_dispatch(state, cmd.into())
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_dispatch (state: &mut Self, cmd: AppCmd) -> Usually<bool> where
|
||||||
|
Self: Namespace<AppCmd>,
|
||||||
|
{
|
||||||
|
match cmd.clone().dispatch(state) {
|
||||||
|
Err(err) => {
|
||||||
|
state.history.push((cmd.into(), None));
|
||||||
|
return Err(err)
|
||||||
|
},
|
||||||
|
Ok(undo) => {
|
||||||
|
state.history.push((cmd.into(), undo.map(Into::into)));
|
||||||
|
return Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A control axis.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let axis = tek::ControlAxis::X;
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
|
||||||
|
X, XFine,
|
||||||
|
Y, YFine,
|
||||||
|
Z, ZFine,
|
||||||
|
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 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.
|
||||||
|
fn width_inc (&mut self);
|
||||||
|
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let dialog: tek::Dialog = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)] pub enum Dialog {
|
||||||
|
#[default] None,
|
||||||
|
Help(usize),
|
||||||
|
Menu(usize, Arc<[MenuItem]>),
|
||||||
|
Device(usize),
|
||||||
|
Message(Arc<str>),
|
||||||
|
Browse(BrowseTarget, Arc<Browse>),
|
||||||
|
Options,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dialog {
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome();
|
||||||
|
/// ```
|
||||||
|
pub fn welcome () -> Self {
|
||||||
|
Self::Menu(1, Arc::new([
|
||||||
|
|
||||||
|
MenuItem::new(" Resume session ", [
|
||||||
|
AppCmd::Project {
|
||||||
|
command: ProjectCmd::LoadLast
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
|
||||||
|
MenuItem::new(" Create session ", [
|
||||||
|
AppCmd::Dialog {
|
||||||
|
dialog: Dialog::None
|
||||||
|
},
|
||||||
|
AppCmd::Mode {
|
||||||
|
command: ModeCmd::Reset {
|
||||||
|
modes: vec!["Arranger".into()]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AppCmd::Tracks {
|
||||||
|
command: TracksCmd::Add
|
||||||
|
},
|
||||||
|
AppCmd::Scenes {
|
||||||
|
command: ScenesCmd::Add
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
|
||||||
|
MenuItem::new(" Load session ", [
|
||||||
|
AppCmd::Dialog {
|
||||||
|
dialog: Dialog::Browse(
|
||||||
|
BrowseTarget::LoadProject,
|
||||||
|
Browse::new(None).unwrap().into()
|
||||||
|
)
|
||||||
|
//dialog: Dialog::Browse {
|
||||||
|
//title: "Load session".into(),
|
||||||
|
//filter: "\\.tek$",
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
|
||||||
|
MenuItem::new(" Exit ", [
|
||||||
|
AppCmd::Exit
|
||||||
|
]),
|
||||||
|
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().menu_selected();
|
||||||
|
/// ```
|
||||||
|
pub fn menu_selected (&self) -> Option<usize> {
|
||||||
|
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().menu_next();
|
||||||
|
/// ```
|
||||||
|
pub fn menu_next (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().menu_prev();
|
||||||
|
/// ```
|
||||||
|
pub fn menu_prev (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().device_kind();
|
||||||
|
/// ```
|
||||||
|
pub fn device_kind (&self) -> Option<usize> {
|
||||||
|
if let Self::Device(index) = self { Some(*index) } else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().device_kind_next();
|
||||||
|
/// ```
|
||||||
|
pub fn device_kind_next (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|(index + 1) % device_kinds().len())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: generalize
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::Dialog::welcome().device_kind_prev();
|
||||||
|
/// ```
|
||||||
|
pub fn device_kind_prev (&self) -> Option<usize> {
|
||||||
|
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// FIXME: implement
|
||||||
|
pub fn message (&self) -> Option<&str> { todo!() }
|
||||||
|
|
||||||
|
/// FIXME: implement
|
||||||
|
pub fn browser (&self) -> Option<&Arc<Browse>> { todo!() }
|
||||||
|
|
||||||
|
/// FIXME: implement
|
||||||
|
pub fn browser_target (&self) -> Option<&BrowseTarget> { todo!() }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increment a wrapping counter.
|
||||||
|
pub const fn wrap_inc (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { (index + 1) % count } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrement a wrapping counter.
|
||||||
|
pub const fn wrap_dec (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 {
|
||||||
|
let a = index.overflowing_sub(1).0;
|
||||||
|
let b = count.saturating_sub(1);
|
||||||
|
if a < b { a } else { b }
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An item of a menu.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let item: tek::MenuItem = Default::default();
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)] pub struct MenuItem(
|
||||||
|
pub Arc<str>,
|
||||||
|
pub Arc<[AppCmd]>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T:
|
|
||||||
Namespace<usize> +
|
|
||||||
Namespace<PathBuf> +
|
|
||||||
Namespace<Arc<str>>
|
|
||||||
> BrowseController for T {}
|
|
||||||
|
|
||||||
#[tek_proc::commands(BrowseCommand = "browse")]
|
|
||||||
pub trait BrowseController:
|
|
||||||
Namespace<usize> +
|
|
||||||
Namespace<PathBuf> +
|
|
||||||
Namespace<Arc<str>>
|
|
||||||
{
|
|
||||||
/// Toggle visibility of browser
|
|
||||||
#[command(Show = "show")]
|
|
||||||
fn show (&mut self) -> Perhaps<BrowseCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
/// Set current directory
|
|
||||||
#[command(Chdir = "chdir")]
|
|
||||||
fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
/// Set current filter
|
|
||||||
#[command(SetSearch = "set-search")]
|
|
||||||
fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
/// Set selected item
|
|
||||||
#[command(SetCursor = "set-cursor")]
|
|
||||||
fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCommand> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Browses for files to load/save.
|
/// Browses for files to load/save.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -179,13 +134,13 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||||
Ok((subdirs, files))
|
Ok((subdirs, files))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
impl App {
|
||||||
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
/// Return reference to content browser if open.
|
||||||
BrowseTarget::SaveProject => "Save project:",
|
///
|
||||||
BrowseTarget::LoadProject => "Load project:",
|
/// ```
|
||||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
/// assert_eq!(tek::App::default().browser(), None);
|
||||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
/// ```
|
||||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
pub fn browser (&self) -> Option<&Browse> {
|
||||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
}
|
||||||
}
|
}
|
||||||
822
src/tek_ctrl.rs
Normal file
822
src/tek_ctrl.rs
Normal file
|
|
@ -0,0 +1,822 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl_from!(AppCmd: |x: AxisCmd| AppCmd::Axis { command: x });
|
||||||
|
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: ProjectCmd| AppCmd::Project { command: x });
|
||||||
|
impl_from!(AppCmd: |x: SceneCmd| AppCmd::Scene { command: x });
|
||||||
|
impl_from!(AppCmd: |x: ScenesCmd| AppCmd::Scenes { command: x });
|
||||||
|
impl_from!(AppCmd: |x: TrackCmd| AppCmd::Track { command: x });
|
||||||
|
impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x });
|
||||||
|
|
||||||
|
#[tek_proc::commands(AppCmd)] impl App {
|
||||||
|
/// Exit the application.
|
||||||
|
#[command(Exit = "exit")] pub fn cmd_exit (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
self.exit.exit();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Cancel current selection.
|
||||||
|
#[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
todo!("cancel")
|
||||||
|
}
|
||||||
|
/// Confirm current selection.
|
||||||
|
#[command(Confirm = "confirm")] pub fn cmd_confirm (&mut self) -> Perhaps<AppCmd> {
|
||||||
|
Ok(match self.dialog.clone() {
|
||||||
|
Dialog::Menu(index, items) => {
|
||||||
|
for command in items[index].1.iter() {
|
||||||
|
App::cmd_dispatch(self, command.clone())?;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Swap currently active modal dialog.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::App::default().cmd_dialog(tek::Dialog::welcome());
|
||||||
|
/// ```
|
||||||
|
#[command(Dialog = "dialog")] pub fn cmd_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCmd> {
|
||||||
|
let mut dialog = dialog.clone();
|
||||||
|
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||||
|
Ok(Some(AppCmd::Dialog { dialog }))
|
||||||
|
}
|
||||||
|
#[command(Mode = "mode")] pub fn cmd_mode (&mut self, command: ModeCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Project = "session")] pub fn cmd_session (&mut self, command: ProjectCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Browse = "browse")] pub fn cmd_browse (&mut self, command: BrowseCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(MidiClip = "clip")] pub fn cmd_midi_clip (&mut self, command: MidiClipCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[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(Scene = "scene")] pub fn cmd_scene (&mut self, command: SceneCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Scenes = "scenes")] pub fn cmd_scenes (&mut self, command: ScenesCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Track = "track")] pub fn cmd_track (&mut self, command: TrackCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
#[command(Tracks = "tracks")] pub fn cmd_tracks (&mut self, command: TracksCmd) -> Perhaps<AppCmd> {
|
||||||
|
Ok(command.dispatch(self)?.map(Into::into))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasProject> ProjectControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ProjectCmd)] pub trait ProjectControl: HasProject {
|
||||||
|
#[command(LoadLast = "load-last")] fn load_last (&mut self) -> Perhaps<ProjectCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(Load = "load")] fn load (&mut self) -> Perhaps<ProjectCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasClock + Namespace<u32> + Namespace<f64> + Namespace<Option<u32>>> ClockControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ClockCmd = "clock")] pub trait ClockControl: HasClock
|
||||||
|
+ 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);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SeekSample = "sample")] fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().playhead.update_from_sample(sample);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SeekPulse = "pulse")] fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().playhead.update_from_pulse(pulse);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SetBpm = "bpm")] fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetBpm {
|
||||||
|
bpm: self.clock().timebase().bpm.set(bpm)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(SetQuant = "quant")] fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetQuant {
|
||||||
|
quant: self.clock().quant.set(quant)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(SetSync = "sync")] fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(Some(ClockCmd::SetSync {
|
||||||
|
sync: self.clock().sync.set(sync)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
#[command(Play = "play")] fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().play_from(position)?;
|
||||||
|
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
||||||
|
}
|
||||||
|
#[command(Pause = "pause")] fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCmd> {
|
||||||
|
self.clock().pause_at(position)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(TogglePlayback = "toggle")] fn toggle (&mut self, position: u32) -> Perhaps<ClockCmd> {
|
||||||
|
Ok(if self.clock().is_rolling() {
|
||||||
|
self.clock().pause_at(Some(position))?;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
self.clock().play_from(Some(position))?;
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasTracks
|
||||||
|
+ HasScenes
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<[Connect]>>
|
||||||
|
+ Namespace<Option<usize>>
|
||||||
|
+ Namespace<Option<Arc<str>>>
|
||||||
|
+ Namespace<Option<ItemTheme>>
|
||||||
|
+ Namespace<Option<Arc<[Connect]>>>
|
||||||
|
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||||
|
> TracksControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(TracksCmd)] pub trait TracksControl: HasTracks
|
||||||
|
+ HasScenes
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<[Connect]>>
|
||||||
|
+ Namespace<Option<usize>>
|
||||||
|
+ Namespace<Option<Arc<str>>>
|
||||||
|
+ Namespace<Option<ItemTheme>>
|
||||||
|
+ Namespace<Option<Arc<[Connect]>>>
|
||||||
|
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||||
|
{
|
||||||
|
/// Stop all playing clips
|
||||||
|
#[command(Stop = "stop")] fn tracks_stop_all (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
/// Launch multiple clips
|
||||||
|
#[command(Launch = "launch")] fn tracks_launch (
|
||||||
|
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
|
||||||
|
) -> Perhaps<TracksCmd> {
|
||||||
|
if let Some(clips) = clips {
|
||||||
|
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
||||||
|
track.sequencer.enqueue_next(clip.as_ref());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for track in self.tracks_mut().iter_mut() {
|
||||||
|
track.sequencer.enqueue_next(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Add = "add")] fn tracks_add (&mut self) -> Perhaps<TracksCmd>
|
||||||
|
where Self: HasScenes + HasJack<'static>
|
||||||
|
{
|
||||||
|
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Select = "select")] fn track_select (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let tracks = self.tracks().len();
|
||||||
|
let select = self.selection_mut().select_track(tracks);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Next = "next")] fn track_select_next (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let tracks = self.tracks().len();
|
||||||
|
let select = self.selection().select_track_next(tracks);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Prev = "prev")] fn track_select_prev (&mut self) -> Perhaps<TracksCmd> {
|
||||||
|
let select = self.selection_mut().select_track_prev();
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasTrack
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<str>>
|
||||||
|
+ Namespace<ItemTheme>
|
||||||
|
+ Namespace<Option<bool>>
|
||||||
|
> TrackControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(TrackCmd)] pub trait TrackControl: HasTrack
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Arc<str>>
|
||||||
|
+ Namespace<ItemTheme>
|
||||||
|
+ Namespace<Option<bool>>
|
||||||
|
{
|
||||||
|
#[command(Stop = "track/stop")]
|
||||||
|
fn track_stop (&mut self) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(SetMute = "track/mute")]
|
||||||
|
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetSolo = "track/solo")]
|
||||||
|
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetSize = "track/size")]
|
||||||
|
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetZoom = "track/zoom")]
|
||||||
|
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetName = "track/name")]
|
||||||
|
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|swap_value(&mut track.name, &name, |name|TrackCmd::SetName { name }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetColor = "track/color")]
|
||||||
|
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|swap_value(&mut track.color, &color, |color|TrackCmd::SetColor { color }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetRec = "track/rec")]
|
||||||
|
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCmd::SetRec { rec }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
#[command(SetMon = "track/mon")]
|
||||||
|
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCmd> {
|
||||||
|
self.track_mut()
|
||||||
|
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCmd::SetMon { mon }))
|
||||||
|
.transpose()
|
||||||
|
.map(Option::flatten)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasScenes
|
||||||
|
+ HasTracks
|
||||||
|
+ HasTrackScroll
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<ItemTheme>>
|
||||||
|
+ Namespace<Option<Arc<str>>>
|
||||||
|
> ScenesControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ScenesCmd)] pub trait ScenesControl: HasScenes
|
||||||
|
+ HasTracks
|
||||||
|
+ HasTrackScroll
|
||||||
|
+ HasSelection
|
||||||
|
+ HasJack<'static>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<ItemTheme>>
|
||||||
|
+ Namespace<Option<Arc<str>>>
|
||||||
|
{
|
||||||
|
#[command(Add = "add")] fn scenes_add (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let (_index, _) = self.scenes_add_one(None, None)?;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Select = "select")] fn scene_select (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let scenes = self.scenes().len();
|
||||||
|
let select = self.selection_mut().select_scene(scenes);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Next = "next")] fn scene_select_next (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let scenes = self.scenes().len();
|
||||||
|
let select = self.selection().select_scene_next(scenes);
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Prev = "prev")] fn scene_select_prev (&mut self) -> Perhaps<ScenesCmd> {
|
||||||
|
let select = self.selection_mut().select_scene_prev();
|
||||||
|
*self.selection_mut() = select;
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasScene
|
||||||
|
+ 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>
|
||||||
|
{
|
||||||
|
#[command(SetSize = "size")] fn scene_set_size (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetZoom = "zoom")] fn scene_set_zoom (&mut self, _size: usize) -> Perhaps<SceneCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
#[command(SetName = "name")] fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCmd> {
|
||||||
|
Ok(self.scene_mut().map(|scene|swap_value(
|
||||||
|
&mut scene.name,
|
||||||
|
&name,
|
||||||
|
|name|SceneCmd::SetName { name }
|
||||||
|
)).transpose()?.flatten())
|
||||||
|
}
|
||||||
|
#[command(SetColor = "color")] fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCmd> {
|
||||||
|
Ok(self.scene_mut().map(|scene|swap_value(
|
||||||
|
&mut scene.color,
|
||||||
|
&color,
|
||||||
|
|color|SceneCmd::SetColor { color }
|
||||||
|
)).transpose()?.flatten())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(SamplerCmd = "sampler")] pub trait SamplerControl: HasSampler
|
||||||
|
+ Namespace<usize>
|
||||||
|
{
|
||||||
|
|
||||||
|
#[command(RecordToggle = "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)?;
|
||||||
|
// autoslice: continue recording at next slot
|
||||||
|
if recording != Some(slot) {
|
||||||
|
SamplerCmd::RecordBegin { slot }.dispatch(self)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(RecordBegin = "rec-begin")] fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||||
|
let sampler = self.sampler_mut();
|
||||||
|
sampler.recording = Some((
|
||||||
|
slot,
|
||||||
|
Some(Arc::new(RwLock::new(Sample::new(
|
||||||
|
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
|
||||||
|
))))
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(RecordFinish = "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]);
|
||||||
|
sample
|
||||||
|
}); // TODO: undo
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(RecordCancel = "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)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(StopSample = "sample-stop")] fn sample_stop (&mut self, slot: usize) -> Perhaps<SamplerCmd> {
|
||||||
|
todo!();
|
||||||
|
//Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HasEditor
|
||||||
|
+ Namespace<u32>
|
||||||
|
+ Namespace<f64>
|
||||||
|
+ Namespace<bool>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<u32>>
|
||||||
|
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
||||||
|
> MidiEditControl for T {}
|
||||||
|
|
||||||
|
#[tek_proc::commands(MidiEditCmd)] pub trait MidiEditControl: HasEditor
|
||||||
|
+ Namespace<u32>
|
||||||
|
+ Namespace<f64>
|
||||||
|
+ Namespace<bool>
|
||||||
|
+ Namespace<usize>
|
||||||
|
+ Namespace<Option<u32>>
|
||||||
|
+ Namespace<Option<Arc<RwLock<MidiClip>>>>
|
||||||
|
{
|
||||||
|
#[command(Toggle = "toggle")] fn edit_toggle (&mut self) -> Perhaps<MidiEditCmd> {
|
||||||
|
if self.is_editing() {
|
||||||
|
*self.editor_mut() = None;
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(DeleteNote = "delete")] fn edit_note_delete (&mut self) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.redraw();
|
||||||
|
todo!()
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(AppendNote = "append")] fn edit_note_append (&mut self, advance: bool) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|mut editor|{
|
||||||
|
editor.put_note(advance);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNotePos = "note-pos")] fn edit_note_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_pos((pos).min(127));
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNoteLen = "note-len")] fn edit_note_set_len (&mut self, len: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_len(len);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetNoteScroll = "note-scroll")] fn edit_note_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_note_lo((scroll).min(127));
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimePos = "time-pos")] fn edit_time_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_pos(pos);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeScroll = "time-scroll")] fn edit_time_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_start(scroll);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeZoom = "time-zoom")] fn edit_time_set_zoom (&mut self, zoom: usize) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_zoom(zoom);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
#[command(SetTimeLock = "time-lock")] fn edit_time_set_lock (&mut self, lock: bool) -> Perhaps<MidiEditCmd> {
|
||||||
|
Ok(self.editor_mut().as_mut().map(|editor|{
|
||||||
|
editor.set_time_lock(lock);
|
||||||
|
editor.redraw();
|
||||||
|
None
|
||||||
|
}).flatten())
|
||||||
|
}
|
||||||
|
// 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 {}
|
||||||
|
|
||||||
|
#[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})
|
||||||
|
//} 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>
|
||||||
|
{}
|
||||||
|
|
||||||
|
#[tek_proc::commands(ModeCmd)] pub trait ModeControl: HasModeStack
|
||||||
|
+ 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();
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
#[command(Push = "push")] fn mode_push (&mut self, mode: Arc<str>) -> Perhaps<ModeCmd> {
|
||||||
|
self.modes_mut().push(mode);
|
||||||
|
Ok(Some(ModeCmd::Pop))
|
||||||
|
}
|
||||||
|
#[command(Pop = "pop")] fn mode_pop (&mut self) -> Perhaps<ModeCmd> {
|
||||||
|
let mode = self.modes_mut().pop();
|
||||||
|
Ok(mode.map(|mode|ModeCmd::Push { mode }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>>
|
||||||
|
{
|
||||||
|
/// 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!()
|
||||||
|
}
|
||||||
|
#[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))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/// 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))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: Namespace<usize> + Namespace<PathBuf> + Namespace<Arc<str>>> BrowseControl for T {}
|
||||||
|
#[tek_proc::commands(BrowseCmd = "browse")] pub trait BrowseControl:
|
||||||
|
Namespace<usize> +
|
||||||
|
Namespace<PathBuf> +
|
||||||
|
Namespace<Arc<str>>
|
||||||
|
{
|
||||||
|
/// Toggle visibility of browser
|
||||||
|
#[command(Show = "show")] fn show (&mut self) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set current directory
|
||||||
|
#[command(Chdir = "chdir")] fn chdir (&mut self, _path: PathBuf) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set current filter
|
||||||
|
#[command(SetSearch = "set-search")] fn set_search (&mut self, _filter: Arc<str>) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set selected item
|
||||||
|
#[command(SetCursor = "set-cursor")] fn set_cursor (&mut self, _index: usize) -> Perhaps<BrowseCmd> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(AxisCmd)]
|
||||||
|
impl App {
|
||||||
|
/// Increment a 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) => {
|
||||||
|
AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Decrement a 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) => {
|
||||||
|
AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
|
||||||
|
None
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
553
src/tek_view.rs
Normal file
553
src/tek_view.rs
Normal file
|
|
@ -0,0 +1,553 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub fn draw_sample (
|
||||||
|
to: &mut Tui, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
|
||||||
|
) -> Usually<usize> {
|
||||||
|
let style = if focus { Style::default().green() } else { Style::default() };
|
||||||
|
if focus {
|
||||||
|
to.blit(&"🬴", x+1, y, Some(style.bold()));
|
||||||
|
}
|
||||||
|
let label1 = format!("{:3} {:12}",
|
||||||
|
note.map(|n|n.to_string()).unwrap_or(String::default()),
|
||||||
|
sample.name);
|
||||||
|
let label2 = format!("{:>6} {:>6} +0.0",
|
||||||
|
sample.start,
|
||||||
|
sample.end);
|
||||||
|
to.blit(&label1, x+2, y, Some(style.bold()));
|
||||||
|
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
|
||||||
|
Ok(label1.len() + label2.len() + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_sample_list_item (sample: &Option<Arc<RwLock<Sample>>>) -> String {
|
||||||
|
if let Some(sample) = sample {
|
||||||
|
let sample = sample.try_read().unwrap();
|
||||||
|
format!("{:8}", sample.name)
|
||||||
|
//format!("{:8} {:3} {:6}-{:6}/{:6}",
|
||||||
|
//sample.name,
|
||||||
|
//sample.gain,
|
||||||
|
//sample.start,
|
||||||
|
//sample.end,
|
||||||
|
//sample.channels[0].len()
|
||||||
|
//)
|
||||||
|
} else {
|
||||||
|
String::from("........")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_sample_viewer (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||||
|
let min_db = -64.0;
|
||||||
|
draw(move|to: &mut Tui|{
|
||||||
|
let xywh = to.area().into();
|
||||||
|
let XYWH(x, y, width, height) = xywh;
|
||||||
|
let area = Rect { x, y, width, height };
|
||||||
|
if let Some(sample) = &sample {
|
||||||
|
let sample = sample.try_read().unwrap();
|
||||||
|
let start = sample.start as f64;
|
||||||
|
let end = sample.end as f64;
|
||||||
|
let length = end - start;
|
||||||
|
let step = length / width as f64;
|
||||||
|
let mut t = start;
|
||||||
|
let mut lines = vec![];
|
||||||
|
while t < end {
|
||||||
|
let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)];
|
||||||
|
let total: f32 = chunk.iter().map(|x|x.abs()).sum();
|
||||||
|
let count = chunk.len() as f32;
|
||||||
|
let meter = 10. * (total / count).log10();
|
||||||
|
let x = t as f64;
|
||||||
|
let y = meter as f64;
|
||||||
|
lines.push(Line::new(x, min_db, x, y, Color::Green));
|
||||||
|
t += step / 2.;
|
||||||
|
}
|
||||||
|
to.buffer().map(|buf|{
|
||||||
|
Canvas::default()
|
||||||
|
.x_bounds([sample.start as f64, sample.end as f64])
|
||||||
|
.y_bounds([min_db, 0.])
|
||||||
|
.paint(|ctx| {
|
||||||
|
for line in lines.iter() {
|
||||||
|
ctx.draw(line);
|
||||||
|
}
|
||||||
|
//FIXME: proportions
|
||||||
|
//let text = "press record to finish sampling";
|
||||||
|
//ctx.print(
|
||||||
|
//(width - text.len() as u16) as f64 / 2.0,
|
||||||
|
//height as f64 / 2.0,
|
||||||
|
//text.red()
|
||||||
|
//);
|
||||||
|
})
|
||||||
|
.render(area, buf);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
to.buffer().map(|buf|{
|
||||||
|
Canvas::default()
|
||||||
|
.x_bounds([0.0, width as f64])
|
||||||
|
.y_bounds([0.0, height as f64])
|
||||||
|
.paint(|_ctx| {
|
||||||
|
//let text = "press record to begin sampling";
|
||||||
|
//ctx.print(
|
||||||
|
//(width - text.len() as u16) as f64 / 2.0,
|
||||||
|
//height as f64 / 2.0,
|
||||||
|
//text.red()
|
||||||
|
//);
|
||||||
|
})
|
||||||
|
.render(area, buf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Some(xywh))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_meters (meters: &[f32]) -> impl Draw<Tui> + use<'_> {
|
||||||
|
bg(Black, iter_east_fixed(1, ||meters.iter(), |value, _index|{
|
||||||
|
RmsMeter(*value).full_h()
|
||||||
|
}).exact_w(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "lv2")]
|
||||||
|
pub fn draw_lv2_header (state: &Lv2, to: &mut Tui, x: u16, y: u16, w: u16) {
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let label1 = format!(" {}", state.name);
|
||||||
|
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
||||||
|
if let Some(ref path) = state.path {
|
||||||
|
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
||||||
|
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
||||||
|
}
|
||||||
|
//Ok(Rect { x, y, width: w, height: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
|
||||||
|
bar(Split::South, 1u16,
|
||||||
|
dim(true, "Mode"),
|
||||||
|
bold(true, state.modes.read().get(0))
|
||||||
|
).push_x(1).draw(to)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let x = "";
|
||||||
|
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
|
||||||
|
/// ```
|
||||||
|
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
|
||||||
|
let theme = ItemTheme::G[96];
|
||||||
|
let sr = field_h(theme, "SR", sr);
|
||||||
|
let buf = field_h(theme, "Buf", buf);
|
||||||
|
let lat = field_h(theme, "Lat", lat);
|
||||||
|
bg(Black, east!(above(
|
||||||
|
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
|
||||||
|
east!(sr, buf, lat).align_e().full_wh(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_device <'a> (state: &'a App) -> impl Draw<Tui> {
|
||||||
|
let selected = state.dialog.device_kind().unwrap();
|
||||||
|
south(
|
||||||
|
bold(true, "Add device"),
|
||||||
|
iter_south(move||device_kinds().iter().enumerate().map(move|(i, _label)|{
|
||||||
|
let b = if i == selected { Rgb(64,128,32) } else { Rgb(0,0,0) };
|
||||||
|
let l = if i == selected { "[ " } else { " " };
|
||||||
|
let r = if i == selected { " ]" } else { " " };
|
||||||
|
bg(b, east(l, west(r, "FIXME device name"))).full_w()
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_track_row_section <'a> (
|
||||||
|
_theme: ItemTheme,
|
||||||
|
button: impl Draw<Tui>,
|
||||||
|
button_add: impl Draw<Tui>,
|
||||||
|
content: impl Draw<Tui>,
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
bar(
|
||||||
|
Split::West,
|
||||||
|
4u16,
|
||||||
|
button_add.align_ne().full_wh(),
|
||||||
|
bar(
|
||||||
|
Split::East,
|
||||||
|
20u16,
|
||||||
|
button.full_wh().align_nw(),
|
||||||
|
content.align_c().full_wh()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_track_header <'a> (theme: ItemTheme, content: impl Draw<Tui>)
|
||||||
|
-> impl Draw<Tui>
|
||||||
|
{
|
||||||
|
bg(theme.darker.term, content.align_e().full_w()).exact_w(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_name <'a> (
|
||||||
|
select: &Selection,
|
||||||
|
editor: Option<&'a MidiEditor>,
|
||||||
|
index: usize,
|
||||||
|
scene: &Scene,
|
||||||
|
editing: bool
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
||||||
|
7
|
||||||
|
} else {
|
||||||
|
Scene::DEFAULT_HEIGHT as u16
|
||||||
|
};
|
||||||
|
let a = east!("·s", index, " ", fg(g(255), bold(true, &scene.name))).align_w().full_w();
|
||||||
|
let b = when(select.scene() == Some(index) && editing, south(
|
||||||
|
editor.as_ref().map(|e|e.clip_status()),
|
||||||
|
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
|
||||||
|
let c = if select.scene() == Some(index) {
|
||||||
|
scene.color.light.term
|
||||||
|
} else {
|
||||||
|
scene.color.base.term
|
||||||
|
};
|
||||||
|
bg(c, south(a, b).align_nw().exact_h(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
||||||
|
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||||
|
let clip = clip.try_read().unwrap();
|
||||||
|
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||||
|
} else {
|
||||||
|
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_bg (
|
||||||
|
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
||||||
|
) -> (Color, Color) {
|
||||||
|
let mut outline = theme.base.term;
|
||||||
|
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
||||||
|
outline = theme.lighter.term;
|
||||||
|
theme.light.term
|
||||||
|
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
||||||
|
outline = theme.darkest.term;
|
||||||
|
theme.base.term
|
||||||
|
} else {
|
||||||
|
theme.dark.term
|
||||||
|
}, outline)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_w (
|
||||||
|
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
||||||
|
) -> u16 {
|
||||||
|
if select.track() == Some(track_index) && let Some(editor) = editor {
|
||||||
|
(editor.size.w() as usize).max(24).max(track.width) as u16
|
||||||
|
} else {
|
||||||
|
track.width as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_y (
|
||||||
|
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
||||||
|
) -> u16 {
|
||||||
|
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
||||||
|
editor.size.h().max(12)
|
||||||
|
} else {
|
||||||
|
Scene::DEFAULT_HEIGHT as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
||||||
|
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
|
||||||
|
let height = (state.config.modes.len() * 2) as u16;
|
||||||
|
draw(move |to: &mut Tui|{
|
||||||
|
let mut index = 0;
|
||||||
|
state.config.modes.for_each(&mut |id: &str, profile: &Mode| {
|
||||||
|
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
|
||||||
|
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||||
|
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||||
|
let fg1 = Rgb(224, 192, 128);
|
||||||
|
let fg2 = Rgb(224, 128, 32);
|
||||||
|
let field_name = fg(fg1, name).align_w().full_w();
|
||||||
|
let field_id = fg(fg2, id).align_e().full_w();
|
||||||
|
let field_info = info.align_w().full_w();
|
||||||
|
let _ = bg(b, south(above(field_name, field_id), field_info))
|
||||||
|
.full_w().exact_h(2).push_y((2 * index) as u16).draw(to);
|
||||||
|
index += 1;
|
||||||
|
});
|
||||||
|
Ok(Some(to.area().into()))
|
||||||
|
}).min_w(30).exact_h(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||||
|
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
||||||
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
|
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::view_meter("", 0.0);
|
||||||
|
/// let _ = tek::view_meters(&[0.0, 0.0]);
|
||||||
|
/// ```
|
||||||
|
pub fn view_meter <'a> (label: &'a str, value: f32)
|
||||||
|
-> impl Draw<Tui>
|
||||||
|
{
|
||||||
|
let f = field_h(ItemTheme::G[128], label, format!("{:>+9.3}", value));
|
||||||
|
let w = if value >= 0.0 { 13 }
|
||||||
|
else if value >= -1.0 { 12 }
|
||||||
|
else if value >= -2.0 { 11 }
|
||||||
|
else if value >= -3.0 { 10 }
|
||||||
|
else if value >= -4.0 { 9 }
|
||||||
|
else if value >= -6.0 { 8 }
|
||||||
|
else if value >= -9.0 { 7 }
|
||||||
|
else if value >= -12.0 { 6 }
|
||||||
|
else if value >= -15.0 { 5 }
|
||||||
|
else if value >= -20.0 { 4 }
|
||||||
|
else if value >= -25.0 { 3 }
|
||||||
|
else if value >= -30.0 { 2 }
|
||||||
|
else if value >= -40.0 { 1 }
|
||||||
|
else { 0 };
|
||||||
|
let c = if value >= 0.0 { Red }
|
||||||
|
else if value >= -3.0 { Yellow }
|
||||||
|
else { Green };
|
||||||
|
south!(f, bg(c, ()).exact_wh(w, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_meters (values: &[f32;2]) -> impl Draw<Tui> + use<'_> {
|
||||||
|
let left = format!("L/{:>+9.3}", values[0]);
|
||||||
|
let right = format!("R/{:>+9.3}", values[1]);
|
||||||
|
south(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||||
|
track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||||
|
track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||||
|
track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||||
|
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
||||||
|
-> impl Draw<Tui> + use<'a, T>
|
||||||
|
{
|
||||||
|
let ins = ports.len() as u16;
|
||||||
|
let frame = Outer(true, Style::default().fg(g(96)));
|
||||||
|
border(true, frame, field_v(theme, title, iter_south({
|
||||||
|
move||ports.iter().enumerate().map(|(index, port)|{
|
||||||
|
east!(" ", index, " ", port.port_name()).align_w().full_h()
|
||||||
|
})
|
||||||
|
})).exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
||||||
|
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
|
||||||
|
) -> impl Draw<Tui> + 'a {
|
||||||
|
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
|
||||||
|
iter(items,
|
||||||
|
move|(_index, name, connections, y, y2): Item<'a>, _| south(
|
||||||
|
bold(true, fg_bg(fg, bg, east(" ", name).align_w())).full_h(),
|
||||||
|
iter(||connections.iter(), move|connect: &'a Connect, index|{
|
||||||
|
bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16)
|
||||||
|
})
|
||||||
|
).exact_h((y2 - y) as u16).push_y(y as u16))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||||
|
when(sample.is_some(), draw(move|to: &mut Tui|{
|
||||||
|
let sample = sample.unwrap().try_read().unwrap();
|
||||||
|
let theme = sample.color;
|
||||||
|
east!(
|
||||||
|
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||||
|
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||||
|
field_h(theme, "Start", format!("{:<8}", sample.start)),
|
||||||
|
field_h(theme, "End", format!("{:<8}", sample.end)),
|
||||||
|
field_h(theme, "Trans", "0"),
|
||||||
|
field_h(theme, "Gain", format!("{}", sample.gain)),
|
||||||
|
).draw(to)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||||
|
let a = draw(move|to: &mut Tui|{
|
||||||
|
let sample = sample.unwrap().try_read().unwrap();
|
||||||
|
let theme = sample.color;
|
||||||
|
south!(
|
||||||
|
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
|
||||||
|
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
|
||||||
|
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
|
||||||
|
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
|
||||||
|
field_h(theme, "Trans ", "0") .align_w().full_w(),
|
||||||
|
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
|
||||||
|
).exact_w(20).draw(to)
|
||||||
|
});
|
||||||
|
|
||||||
|
let b = draw(|to: &mut Tui|fg(Red, south!(
|
||||||
|
bold(true, "× No sample."),
|
||||||
|
"[r] record",
|
||||||
|
"[Shift-F9] import",
|
||||||
|
)).draw(to));
|
||||||
|
|
||||||
|
either(sample.is_some(), a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
||||||
|
bold(true, fg(g(224), sample
|
||||||
|
.map(|sample|{
|
||||||
|
let sample = sample.try_read().unwrap();
|
||||||
|
format!("Sample {}-{}", sample.start, sample.end)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(||"No sample".to_string())))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::button_2("", "", true);
|
||||||
|
/// let _ = tek::button_2("", "", false);
|
||||||
|
/// ```
|
||||||
|
pub fn button_2 <'a> (key: impl Draw<Tui>, label: impl Draw<Tui>, hide: bool)
|
||||||
|
-> impl Draw<Tui>
|
||||||
|
{
|
||||||
|
let c1 = tui_orange();
|
||||||
|
let c2 = tui_g(0);
|
||||||
|
let c3 = tui_g(96);
|
||||||
|
let c4 = tui_g(255);
|
||||||
|
bold(true, fg_bg(c1, c2,
|
||||||
|
bar(Split::East, 1u16, fg(c2, "▐"),
|
||||||
|
bar(Split::East, 1u16, key,
|
||||||
|
bar(Split::East, 1u16, fg(c3, "▐"),
|
||||||
|
when(!hide, fg_bg(c4, c3, label)))))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let _ = tek::button_3("", "", "", true);
|
||||||
|
/// let _ = tek::button_3("", "", "", false);
|
||||||
|
/// ```
|
||||||
|
pub fn button_3 <'a> (
|
||||||
|
key: impl Draw<Tui>,
|
||||||
|
label: impl Draw<Tui>,
|
||||||
|
value: impl Draw<Tui>,
|
||||||
|
editing: bool,
|
||||||
|
) -> impl Draw<Tui> {
|
||||||
|
let c1 = tui_orange();
|
||||||
|
let c2 = tui_g(0);
|
||||||
|
let c3 = tui_g(96);
|
||||||
|
let c4 = tui_g(255);
|
||||||
|
let c5 = tui_g(128);
|
||||||
|
bold(true, bar(Split::East, 3u16,
|
||||||
|
fg_bg(c1, c2,
|
||||||
|
bar(Split::East, 1u16, fg(c2, "▐"),
|
||||||
|
bar(Split::East, 1u16, key, fg(if editing { c5 } else { c3 }, "▐")))),
|
||||||
|
east(
|
||||||
|
when(!editing, east(fg_bg(c4, c3, label), fg_bg(c5, c3, "▐"),)),
|
||||||
|
east(fg_bg(tui_g(224), c5, value), fg_bg(c5, Reset, "▌"), ))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_h <'a, T: Screen> (
|
||||||
|
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||||
|
) -> impl Draw<T> {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_v <'a, T: Screen> (
|
||||||
|
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||||
|
) -> impl Draw<T> {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sessions <'a> () -> impl Draw<Tui> {
|
||||||
|
let h = 6;
|
||||||
|
let w = Some(30);
|
||||||
|
let f = Rgb(224, 192, 128);
|
||||||
|
draw(move |to: &mut Tui|{
|
||||||
|
for (index, name) in ["session1", "session2", "session3"].iter().enumerate() {
|
||||||
|
let b = if index == 0 { Rgb(50,50,50) } else { Rgb(40,40,40) };
|
||||||
|
let y = (2 * index) as u16;
|
||||||
|
let h = 2;
|
||||||
|
bg(b, fg(f, *name).align_w()).full_w().exact_h(h).push_y(y).draw(to)?;
|
||||||
|
}
|
||||||
|
Ok(Some(to.area().into()))
|
||||||
|
}).min_w(w).exact_h(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ```
|
||||||
|
/// let bg = tengri::ratatui::style::Color::Red;
|
||||||
|
/// let fg = tengri::ratatui::style::Color::Green;
|
||||||
|
/// let _ = tek::view_wrap(bg, fg, "and then blue, too!");
|
||||||
|
/// ```
|
||||||
|
pub fn view_wrap <'a> (bg: Color, fg: Color, content: impl Draw<Tui>)
|
||||||
|
-> impl Draw<Tui>
|
||||||
|
{
|
||||||
|
let left = fg_bg(bg, Reset, y_repeat("▐").exact_w(1));
|
||||||
|
let right = fg_bg(bg, Reset, y_repeat("▌").exact_w(1));
|
||||||
|
east(left, west(right, fg_bg(fg, bg, content)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! rewrite {
|
||||||
|
($buf:ident, $($rest:tt)*) => {
|
||||||
|
|$buf,_,_|{
|
||||||
|
$buf.clear();
|
||||||
|
write!($buf, $($rest)*)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Memo<T, U> {
|
||||||
|
value: T,
|
||||||
|
view: U,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Memo<T, U> {
|
||||||
|
pub fn new (value: T, view: U) -> Self {
|
||||||
|
Self { value, view }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq + Debug, U: Debug> Debug for Memo<T, U> {
|
||||||
|
fn fmt (&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq, U> Memo<T, U> {
|
||||||
|
pub fn update <R> (&mut self, newval: T, render: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
||||||
|
if newval != self.value {
|
||||||
|
let result = render(&mut self.view, &newval, &self.value);
|
||||||
|
self.value = newval;
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Gettable<T> {
|
||||||
|
/// Returns current value
|
||||||
|
fn get (&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Mutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&mut self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InteriorMutable<T>: Gettable<T> {
|
||||||
|
/// Sets new value, returns old
|
||||||
|
fn set (&self, value: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)] mod test_view {
|
||||||
|
use super::*;
|
||||||
|
use proptest::prelude::*;
|
||||||
|
proptest! {
|
||||||
|
|
||||||
|
#[test] fn proptest_view_meter (
|
||||||
|
label in "\\PC*", value in f32::MIN..f32::MAX
|
||||||
|
) {
|
||||||
|
let _ = view_meter(&label, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test] fn proptest_view_meters (
|
||||||
|
value1 in f32::MIN..f32::MAX,
|
||||||
|
value2 in f32::MIN..f32::MAX
|
||||||
|
) {
|
||||||
|
let _ = view_meters(&[value1, value2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue