mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-28 04:46:58 +02:00
This commit is contained in:
parent
ae496987c8
commit
7fcab73b04
12 changed files with 811 additions and 559 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
*/target
|
||||||
target/*
|
target/*
|
||||||
!target/.gitkeep
|
!target/.gitkeep
|
||||||
perf.data*
|
perf.data*
|
||||||
|
|
|
||||||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -368,6 +368,12 @@ dependencies = [
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "case"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cassowary"
|
name = "cassowary"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -3158,6 +3164,7 @@ dependencies = [
|
||||||
name = "tek_proc"
|
name = "tek_proc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
|
|
|
||||||
54
proc/Cargo.lock
generated
Normal file
54
proc/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "case"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_proc"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
case = "1.0.0"
|
||||||
proc-macro2 = "1.0.106"
|
proc-macro2 = "1.0.106"
|
||||||
quote = "1.0.46"
|
quote = "1.0.46"
|
||||||
syn = { version = "2.0.119", features = ["full", "extra-traits"] }
|
syn = { version = "2.0.119", features = ["full", "extra-traits"] }
|
||||||
|
|
|
||||||
122
proc/src/lib.rs
122
proc/src/lib.rs
|
|
@ -3,8 +3,12 @@ use proc_macro2::{TokenStream as TokenStream2, Span};
|
||||||
use quote::{quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use std::collections::{HashMap, BTreeMap};
|
use std::collections::{HashMap, BTreeMap};
|
||||||
use syn::{
|
use syn::{
|
||||||
Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp,
|
Error, Path, Lit, Ident, Variant, Fields, BinOp,
|
||||||
parse::{ParseStream, Parse, Result}
|
Expr, ExprPath, ExprBinary, ExprAssign, ExprLit,
|
||||||
|
ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature,
|
||||||
|
MetaList, Type, TypePath, FnArg, PatType,
|
||||||
|
parse::{ParseStream, Parse, Result},
|
||||||
|
spanned::Spanned
|
||||||
};
|
};
|
||||||
|
|
||||||
macro_rules! attribute {
|
macro_rules! attribute {
|
||||||
|
|
@ -22,16 +26,114 @@ macro_rules! attribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attribute!(command {
|
attribute!(commands {
|
||||||
#[derive(Debug, Clone)] pub struct Def(
|
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
|
||||||
pub Meta, pub Item
|
#[derive(Debug, Clone)] pub struct Meta(pub Path, pub Lit);
|
||||||
);
|
|
||||||
#[derive(Debug, Clone)] pub struct Meta(
|
|
||||||
pub Path
|
|
||||||
);
|
|
||||||
#[derive(Debug, Clone)] pub struct Item(
|
#[derive(Debug, Clone)] pub struct Item(
|
||||||
pub ItemEnum, pub HashMap<Ident, (Fields, Expr)>
|
pub Path, pub ItemImpl, pub HashMap<Ident, (Ident, Vec<FnArg>, Lit)>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl Parse for Meta {
|
||||||
|
fn parse (input: ParseStream) -> Result<Self> {
|
||||||
|
let meta = input.parse()?;
|
||||||
|
if let Expr::Assign(ExprAssign { ref left, ref right, .. }) = meta
|
||||||
|
&& let Expr::Path(ExprPath { path, .. }) = &**left
|
||||||
|
&& let Expr::Lit(ExprLit { lit, .. }) = &**right
|
||||||
|
{
|
||||||
|
Ok(Self(path.clone(), lit.clone()))
|
||||||
|
} else {
|
||||||
|
Err(Error::new(meta.span(), format!(
|
||||||
|
"must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Item {
|
||||||
|
fn parse (input: ParseStream) -> Result<Self> {
|
||||||
|
let mut item: ItemImpl = input.parse()?;
|
||||||
|
let path = if let Type::Path(TypePath { path, .. }) = &*item.self_ty {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
return Err(Error::new(item.self_ty.span(), format!("must be path to struct")))
|
||||||
|
};
|
||||||
|
let mut dispatch: HashMap<Ident, (Ident, Vec<FnArg>, Lit)> = Default::default();
|
||||||
|
for item in item.items.iter_mut() {
|
||||||
|
if let ImplItem::Fn(ImplItemFn {
|
||||||
|
attrs, sig: Signature { ident, inputs, .. }, ..
|
||||||
|
}) = item {
|
||||||
|
*attrs = attrs.iter().filter(|attr|{
|
||||||
|
if let syn::Meta::List(MetaList { ref path, ref tokens, .. }) = attr.meta
|
||||||
|
&& path == &Path::from(Ident::new("command", Span::call_site()))
|
||||||
|
&& let Ok(handler) = syn::parse2::<Expr>(tokens.clone())
|
||||||
|
&& let Expr::Assign(ExprAssign { ref left, ref right, .. }) = handler
|
||||||
|
&& let Expr::Lit(ExprLit { lit, .. }) = &**right
|
||||||
|
&& let Expr::Path(ExprPath { path, .. }) = &**left
|
||||||
|
&& path.segments.len() == 1 {
|
||||||
|
dispatch.insert(ident.clone(), (
|
||||||
|
path.segments.first().unwrap().ident.clone(),
|
||||||
|
inputs.iter().cloned().collect(),
|
||||||
|
lit.clone()
|
||||||
|
));
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self(path.clone(), item, dispatch))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Def {
|
||||||
|
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||||
|
let Self(Meta(command, namespace), Item(ident, item, items)) = self;
|
||||||
|
let mut variants = quote! {};
|
||||||
|
let mut dispatch = quote! {};
|
||||||
|
for (ident, (variant, inputs, keyword)) in items.iter() {
|
||||||
|
let mut params = quote! {};
|
||||||
|
let mut args = quote! {};
|
||||||
|
let mut has_args = false;
|
||||||
|
for arg in inputs.iter() {
|
||||||
|
match arg {
|
||||||
|
FnArg::Receiver(_) => {},
|
||||||
|
FnArg::Typed(PatType { pat, ty, .. }) => {
|
||||||
|
has_args = true;
|
||||||
|
append(&mut params, quote! { #pat: #ty, });
|
||||||
|
append(&mut args, quote! { #pat, })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_args {
|
||||||
|
params = quote! { { #params } };
|
||||||
|
args = quote! { { #args } };
|
||||||
|
}
|
||||||
|
append(&mut variants, quote! { #variant #params, });
|
||||||
|
append(&mut dispatch, quote! { #command::#variant #args => { todo!() }, });
|
||||||
|
}
|
||||||
|
append(out, quote! {
|
||||||
|
#item
|
||||||
|
pub enum #command { #variants }
|
||||||
|
impl dizzle::Act<#ident> for #command {
|
||||||
|
fn act (&self, state: &mut #ident) -> Perhaps<Self> {
|
||||||
|
match self {
|
||||||
|
#dispatch
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attribute!(command {
|
||||||
|
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
|
||||||
|
#[derive(Debug, Clone)] pub struct Meta(pub Path);
|
||||||
|
#[derive(Debug, Clone)] pub struct Item(pub ItemEnum, pub HashMap<Ident, (Fields, Expr)>);
|
||||||
impl Parse for Meta {
|
impl Parse for Meta {
|
||||||
fn parse (input: ParseStream) -> Result<Self> {
|
fn parse (input: ParseStream) -> Result<Self> {
|
||||||
Ok(Self(input.parse()?))
|
Ok(Self(input.parse()?))
|
||||||
|
|
|
||||||
|
|
@ -23,29 +23,64 @@ pub struct Arrangement {
|
||||||
/// Selected UI element
|
/// Selected UI element
|
||||||
pub selection: Selection,
|
pub selection: Selection,
|
||||||
/// 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
|
||||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||||
/// List of global midi inputs
|
/// List of global midi inputs
|
||||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||||
/// List of global midi outputs
|
/// List of global midi outputs
|
||||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||||
/// List of global audio inputs
|
/// List of global audio inputs
|
||||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||||
/// List of global audio outputs
|
/// List of global audio outputs
|
||||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||||
/// Last track number (to avoid duplicate port names)
|
/// Last track number (to avoid duplicate port names)
|
||||||
#[cfg(feature = "track")] pub track_last: usize,
|
#[cfg(feature = "track")] pub track_last: usize,
|
||||||
/// List of tracks
|
/// List of tracks
|
||||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||||
/// Scroll offset of tracks
|
/// Scroll offset of tracks
|
||||||
#[cfg(feature = "track")] pub track_scroll: usize,
|
#[cfg(feature = "track")] pub track_scroll: usize,
|
||||||
/// List of scenes
|
/// List of scenes
|
||||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||||
/// Scroll offset of scenes
|
/// Scroll offset of scenes
|
||||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_command!(TrackCommand: |track: Track| {
|
||||||
|
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
||||||
|
SetRec { rec: Option<bool> } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
||||||
|
SetMon { mon: Option<bool> } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
||||||
|
SetMute { mute: Option<bool> } => todo!(),
|
||||||
|
SetSolo { solo: Option<bool> } => todo!(),
|
||||||
|
SetSize { size: usize } => todo!(),
|
||||||
|
SetZoom { zoom: usize } => todo!(),
|
||||||
|
SetName { name: Arc<str> } => swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
||||||
|
SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(SceneCommand: |scene: Scene| {
|
||||||
|
SetSize { size: usize } => { todo!() },
|
||||||
|
SetZoom { size: usize } => { todo!() },
|
||||||
|
SetName { name: Arc<str> } =>
|
||||||
|
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
||||||
|
SetColor { color: ItemTheme } =>
|
||||||
|
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(ClipCommand: |clip: MidiClip| {
|
||||||
|
SetColor { color: Option<ItemTheme> } => {
|
||||||
|
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
||||||
|
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
||||||
|
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
||||||
|
todo!()
|
||||||
|
},
|
||||||
|
SetLoop { looping: Option<bool> } => {
|
||||||
|
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
||||||
|
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/// Represents the current user selection in the arranger
|
/// Represents the current user selection in the arranger
|
||||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||||
pub enum Selection {
|
pub enum Selection {
|
||||||
|
|
@ -333,19 +368,7 @@ impl HasClipsSize for Arrangement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def_command!(ClipCommand: |clip: MidiClip| {
|
|
||||||
SetColor { color: Option<ItemTheme> } => {
|
|
||||||
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
|
||||||
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
|
||||||
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
|
||||||
todo!()
|
|
||||||
},
|
|
||||||
SetLoop { looping: Option<bool> } => {
|
|
||||||
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
|
||||||
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
/// Put a clip in a slot
|
/// Put a clip in a slot
|
||||||
pub fn clip_put (
|
pub fn clip_put (
|
||||||
|
|
@ -528,14 +551,6 @@ impl ScenesView for Arrangement {
|
||||||
pub type SceneWith<'a, T> =
|
pub type SceneWith<'a, T> =
|
||||||
(usize, &'a Scene, usize, usize, T);
|
(usize, &'a Scene, usize, usize, T);
|
||||||
|
|
||||||
def_command!(SceneCommand: |scene: Scene| {
|
|
||||||
SetSize { size: usize } => { todo!() },
|
|
||||||
SetZoom { size: usize } => { todo!() },
|
|
||||||
SetName { name: Arc<str> } =>
|
|
||||||
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
|
||||||
SetColor { color: ItemTheme } =>
|
|
||||||
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
|
||||||
});
|
|
||||||
|
|
||||||
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
#[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
|
||||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());
|
||||||
|
|
@ -847,18 +862,6 @@ impl HasTrackScroll for Arrangement {
|
||||||
fn track_scroll (&self) -> usize { self.track_scroll }
|
fn track_scroll (&self) -> usize { self.track_scroll }
|
||||||
}
|
}
|
||||||
|
|
||||||
def_command!(TrackCommand: |track: Track| {
|
|
||||||
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
|
||||||
SetRec { rec: Option<bool> } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
|
||||||
SetMon { mon: Option<bool> } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
|
||||||
SetMute { mute: Option<bool> } => todo!(),
|
|
||||||
SetSolo { solo: Option<bool> } => todo!(),
|
|
||||||
SetSize { size: usize } => todo!(),
|
|
||||||
SetZoom { zoom: usize } => todo!(),
|
|
||||||
SetName { name: Arc<str> } => swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
|
||||||
SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl<T: AsRef<Vec<Track>>+AsMut<Vec<Track>>> HasTracks for T {}
|
impl<T: AsRef<Vec<Track>>+AsMut<Vec<Track>>> HasTracks for T {}
|
||||||
impl<T: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack for T {}
|
impl<T: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack for T {}
|
||||||
impl<T: ScenesView+HasMidiIns+HasMidiOuts+HasTrackScroll> TracksView for T {}
|
impl<T: ScenesView+HasMidiIns+HasMidiOuts+HasTrackScroll> TracksView for T {}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,30 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(BrowseCommand = "browse")]
|
||||||
|
impl Browse {
|
||||||
|
/// Toggle visibility of browser
|
||||||
|
#[command(Show = "show")]
|
||||||
|
fn show (&mut self) -> Perhaps<BrowseCommand> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
/// Set current directory
|
||||||
|
#[command(SetPath = "set-path")]
|
||||||
|
fn set_path (&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!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def_command!(FileBrowserCommand: |_browse: Browse|{
|
def_command!(FileBrowserCommand: |_browse: Browse|{
|
||||||
//("begin" [] Some(Self::Begin))
|
//("begin" [] Some(Self::Begin))
|
||||||
//("cancel" [] Some(Self::Cancel))
|
//("cancel" [] Some(Self::Cancel))
|
||||||
|
|
@ -52,40 +76,6 @@ pub(crate) struct EntriesIterator<'a, S: Screen> {
|
||||||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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);
|
|
||||||
|
|
||||||
// Commands supported by [Browse]
|
// Commands supported by [Browse]
|
||||||
//#[derive(Debug, Clone, PartialEq)]
|
//#[derive(Debug, Clone, PartialEq)]
|
||||||
//pub enum BrowseCommand {
|
//pub enum BrowseCommand {
|
||||||
|
|
@ -96,198 +86,6 @@ pub struct PoolView<'a>(pub &'a Pool);
|
||||||
//Chdir(PathBuf),
|
//Chdir(PathBuf),
|
||||||
//Filter(Arc<str>),
|
//Filter(Arc<str>),
|
||||||
//}
|
//}
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
|
||||||
impl_has_clips!(|self: Pool|self.clips);
|
|
||||||
impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{
|
|
||||||
let model = Self::default();
|
|
||||||
model.clips.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_clip_length (&mut self) {
|
|
||||||
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
|
||||||
*self.mode_mut() = Some(PoolMode::Length(
|
|
||||||
self.clip_index(),
|
|
||||||
length,
|
|
||||||
ClipLengthFocus::Bar
|
|
||||||
));
|
|
||||||
}
|
|
||||||
pub fn begin_clip_rename (&mut self) {
|
|
||||||
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
|
||||||
*self.mode_mut() = Some(PoolMode::Rename(
|
|
||||||
self.clip_index(),
|
|
||||||
name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
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].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.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.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
|
||||||
if let Some(index) = index {
|
|
||||||
self.clips.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 _todo_usize_ (&self) -> usize { todo!() }
|
|
||||||
fn _todo_bool_ (&self) -> bool { todo!() }
|
|
||||||
fn _todo_clip_ (&self) -> MidiClip { todo!() }
|
|
||||||
fn _todo_path_ (&self) -> PathBuf { todo!() }
|
|
||||||
fn _todo_color_ (&self) -> ItemColor { todo!() }
|
|
||||||
fn _todo_str_ (&self) -> Arc<str> { todo!() }
|
|
||||||
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.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.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.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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Browse {
|
impl Browse {
|
||||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||||
|
|
@ -322,9 +120,6 @@ impl Browse {
|
||||||
fn _todo_stub_path_buf (&self) -> PathBuf { todo!() }
|
fn _todo_stub_path_buf (&self) -> PathBuf { todo!() }
|
||||||
fn _todo_stub_usize (&self) -> usize { todo!() }
|
fn _todo_stub_usize (&self) -> usize { todo!() }
|
||||||
fn _todo_stub_arc_str (&self) -> Arc<str> { todo!() }
|
fn _todo_stub_arc_str (&self) -> Arc<str> { todo!() }
|
||||||
}
|
|
||||||
|
|
||||||
impl Browse {
|
|
||||||
fn tui (&self) -> impl Draw<Tui> {
|
fn tui (&self) -> impl Draw<Tui> {
|
||||||
iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w())
|
iter_south_fixed(1, ||self.tui_entries(), |entry, _index|entry.origin_w().full_w())
|
||||||
}
|
}
|
||||||
|
|
@ -369,188 +164,6 @@ impl PartialEq for BrowseTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def_command!(BrowseCommand: |browse: Browse| {
|
|
||||||
SetVisible => Ok(None),
|
|
||||||
SetPath { address: PathBuf } => Ok(None),
|
|
||||||
SetSearch { filter: Arc<str> } => Ok(None),
|
|
||||||
SetCursor { cursor: usize } => Ok(None),
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(PoolCommand: |pool: Pool| {
|
|
||||||
// Toggle visibility of pool
|
|
||||||
Show { visible: bool } => {
|
|
||||||
pool.visible = *visible;
|
|
||||||
Ok(Some(Self::Show { visible: !visible }))
|
|
||||||
},
|
|
||||||
// Select a clip from the clip pool
|
|
||||||
Select { index: usize } => {
|
|
||||||
pool.set_clip_index(*index);
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
// Update the contents of the clip pool
|
|
||||||
Clip { command: PoolClipCommand } => Ok(
|
|
||||||
command.act(pool)?.map(|command|Self::Clip{command})
|
|
||||||
),
|
|
||||||
// Rename a clip
|
|
||||||
Rename { command: RenameCommand } => Ok(
|
|
||||||
command.act(pool)?.map(|command|Self::Rename{command})
|
|
||||||
),
|
|
||||||
// Change the length of a clip
|
|
||||||
Length { command: CropCommand } => Ok(
|
|
||||||
command.act(pool)?.map(|command|Self::Length{command})
|
|
||||||
),
|
|
||||||
// Import from file
|
|
||||||
Import { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() {
|
|
||||||
command.act(browse)?.map(|command|Self::Import{command})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
// Export to file
|
|
||||||
Export { command: BrowseCommand } => Ok(if let Some(browse) = pool.browse.as_mut() {
|
|
||||||
command.act(browse)?.map(|command|Self::Export{command})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(PoolClipCommand: |pool: Pool| {
|
|
||||||
Delete { index: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
|
|
||||||
Ok(Some(Self::Add { index, clip }))
|
|
||||||
},
|
|
||||||
Swap { index: usize, other: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let other = *other;
|
|
||||||
pool.clips_mut().swap(index, other);
|
|
||||||
Ok(Some(Self::Swap { index, other }))
|
|
||||||
},
|
|
||||||
Export { index: usize, path: PathBuf } => {
|
|
||||||
todo!("export clip to midi file");
|
|
||||||
},
|
|
||||||
Add { index: usize, clip: MidiClip } => {
|
|
||||||
let index = *index;
|
|
||||||
let mut index = index;
|
|
||||||
let clip = Arc::new(RwLock::new(clip.clone()));
|
|
||||||
let mut clips = pool.clips_mut();
|
|
||||||
if index >= clips.len() {
|
|
||||||
index = clips.len();
|
|
||||||
clips.push(clip)
|
|
||||||
} else {
|
|
||||||
clips.insert(index, clip);
|
|
||||||
}
|
|
||||||
Ok(Some(Self::Delete { index }))
|
|
||||||
},
|
|
||||||
Import { index: usize, path: PathBuf } => {
|
|
||||||
let index = *index;
|
|
||||||
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(Self::Add { index, clip }.act(pool)?)
|
|
||||||
},
|
|
||||||
SetName { index: usize, name: Arc<str> } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = &mut pool.clips_mut()[index];
|
|
||||||
let old_name = clip.read().unwrap().name.clone();
|
|
||||||
clip.write().unwrap().name = name.clone();
|
|
||||||
Ok(Some(Self::SetName { index, name: old_name }))
|
|
||||||
},
|
|
||||||
SetLength { index: usize, length: usize } => {
|
|
||||||
let index = *index;
|
|
||||||
let clip = &mut pool.clips_mut()[index];
|
|
||||||
let old_len = clip.read().unwrap().length;
|
|
||||||
clip.write().unwrap().length = *length;
|
|
||||||
Ok(Some(Self::SetLength { index, length: old_len }))
|
|
||||||
},
|
|
||||||
SetColor { index: usize, color: ItemColor } => {
|
|
||||||
let index = *index;
|
|
||||||
let mut color = ItemTheme::from(*color);
|
|
||||||
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
|
|
||||||
Ok(Some(Self::SetColor { index, color: color.base }))
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(RenameCommand: |pool: Pool| {
|
|
||||||
Begin => unreachable!(),
|
|
||||||
Cancel => {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
|
||||||
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Confirm => {
|
|
||||||
if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() {
|
|
||||||
let old_name = old_name.clone(); *pool.mode_mut() = None; return Ok(Some(Self::Set { value: old_name }))
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Set { value: Arc<str> } => {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() {
|
|
||||||
pool.clips()[clip].write().unwrap().name = value.clone();
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
def_command!(CropCommand: |pool: Pool| {
|
|
||||||
Begin => unreachable!(),
|
|
||||||
Cancel => { if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { *pool.mode_mut() = None; } Ok(None) },
|
|
||||||
Set { length: usize } => {
|
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
|
|
||||||
= pool.mode_mut().clone()
|
|
||||||
{
|
|
||||||
let old_length;
|
|
||||||
{
|
|
||||||
let clip = pool.clips()[clip].clone();//.write().unwrap();
|
|
||||||
old_length = Some(clip.read().unwrap().length);
|
|
||||||
clip.write().unwrap().length = *length;
|
|
||||||
}
|
|
||||||
*pool.mode_mut() = None;
|
|
||||||
return Ok(old_length.map(|length|Self::Set { length }))
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Next => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.next() }; Ok(None)
|
|
||||||
},
|
|
||||||
Prev => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = pool.mode_mut().clone() { focus.prev() }; Ok(None)
|
|
||||||
},
|
|
||||||
Inc => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.mode_mut().clone() {
|
|
||||||
match focus {
|
|
||||||
ClipLengthFocus::Bar => { *length += 4 * PPQ },
|
|
||||||
ClipLengthFocus::Beat => { *length += PPQ },
|
|
||||||
ClipLengthFocus::Tick => { *length += 1 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Dec => {
|
|
||||||
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = pool.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)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
||||||
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
||||||
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
|
.fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,32 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_command!(ClockCommand: |clock: Clock| {
|
||||||
|
SeekUsec { usec: f64 } => {
|
||||||
|
clock.playhead.update_from_usec(*usec); Ok(None) },
|
||||||
|
SeekSample { sample: f64 } => {
|
||||||
|
clock.playhead.update_from_sample(*sample); Ok(None) },
|
||||||
|
SeekPulse { pulse: f64 } => {
|
||||||
|
clock.playhead.update_from_pulse(*pulse); Ok(None) },
|
||||||
|
SetBpm { bpm: f64 } => Ok(Some(
|
||||||
|
Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })),
|
||||||
|
SetQuant { quant: f64 } => Ok(Some(
|
||||||
|
Self::SetQuant { quant: clock.quant.set(*quant) })),
|
||||||
|
SetSync { sync: f64 } => Ok(Some(
|
||||||
|
Self::SetSync { sync: clock.sync.set(*sync) })),
|
||||||
|
|
||||||
|
Play { position: Option<u32> } => {
|
||||||
|
clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ },
|
||||||
|
Pause { position: Option<u32> } => {
|
||||||
|
clock.pause_at(*position)?; Ok(None) },
|
||||||
|
|
||||||
|
TogglePlayback { position: u32 } => Ok(if clock.is_rolling() {
|
||||||
|
clock.pause_at(Some(*position))?; None
|
||||||
|
} else {
|
||||||
|
clock.play_from(Some(*position))?; None
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
impl <T: AsRef<Clock>+AsMut<Clock>> HasClock for T {}
|
impl <T: AsRef<Clock>+AsMut<Clock>> HasClock for T {}
|
||||||
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||||
fn clock (&self) -> &Clock { self.as_ref() }
|
fn clock (&self) -> &Clock { self.as_ref() }
|
||||||
|
|
@ -118,32 +144,6 @@ pub const NOTE_NAMES: [&str; 128] = [
|
||||||
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
|
||||||
];
|
];
|
||||||
|
|
||||||
def_command!(ClockCommand: |clock: Clock| {
|
|
||||||
SeekUsec { usec: f64 } => {
|
|
||||||
clock.playhead.update_from_usec(*usec); Ok(None) },
|
|
||||||
SeekSample { sample: f64 } => {
|
|
||||||
clock.playhead.update_from_sample(*sample); Ok(None) },
|
|
||||||
SeekPulse { pulse: f64 } => {
|
|
||||||
clock.playhead.update_from_pulse(*pulse); Ok(None) },
|
|
||||||
SetBpm { bpm: f64 } => Ok(Some(
|
|
||||||
Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })),
|
|
||||||
SetQuant { quant: f64 } => Ok(Some(
|
|
||||||
Self::SetQuant { quant: clock.quant.set(*quant) })),
|
|
||||||
SetSync { sync: f64 } => Ok(Some(
|
|
||||||
Self::SetSync { sync: clock.sync.set(*sync) })),
|
|
||||||
|
|
||||||
Play { position: Option<u32> } => {
|
|
||||||
clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ },
|
|
||||||
Pause { position: Option<u32> } => {
|
|
||||||
clock.pause_at(*position)?; Ok(None) },
|
|
||||||
|
|
||||||
TogglePlayback { position: u32 } => Ok(if clock.is_rolling() {
|
|
||||||
clock.pause_at(Some(*position))?; None
|
|
||||||
} else {
|
|
||||||
clock.play_from(Some(*position))?; None
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
impl LaunchSync {
|
impl LaunchSync {
|
||||||
pub fn next (&self) -> f64 {
|
pub fn next (&self) -> f64 {
|
||||||
note_duration_next(self.get() as usize) as f64
|
note_duration_next(self.get() as usize) as f64
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,30 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
||||||
|
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
||||||
|
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
||||||
|
DeleteNote => {
|
||||||
|
editor.redraw(); todo!() },
|
||||||
|
AppendNote { advance: bool } => {
|
||||||
|
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
||||||
|
SetNotePos { pos: usize } => {
|
||||||
|
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteLen { len: usize } => {
|
||||||
|
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteScroll { scroll: usize } => {
|
||||||
|
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetTimePos { pos: usize } => {
|
||||||
|
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeScroll { scroll: usize } => {
|
||||||
|
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeZoom { zoom: usize } => {
|
||||||
|
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeLock { lock: bool } => {
|
||||||
|
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
||||||
|
// 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.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -213,30 +237,6 @@ impl <T: TimeRange+NoteRange> MidiRange for T {}
|
||||||
|
|
||||||
impl <T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
impl <T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
||||||
|
|
||||||
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
|
||||||
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
|
||||||
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
|
||||||
DeleteNote => {
|
|
||||||
editor.redraw(); todo!() },
|
|
||||||
AppendNote { advance: bool } => {
|
|
||||||
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
|
||||||
SetNotePos { pos: usize } => {
|
|
||||||
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteLen { len: usize } => {
|
|
||||||
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
|
||||||
SetNoteScroll { scroll: usize } => {
|
|
||||||
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
|
||||||
SetTimePos { pos: usize } => {
|
|
||||||
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeScroll { scroll: usize } => {
|
|
||||||
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeZoom { zoom: usize } => {
|
|
||||||
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
|
||||||
SetTimeLock { lock: bool } => {
|
|
||||||
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
||||||
});
|
|
||||||
|
|
||||||
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
||||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||||
fn redraw (&self);
|
fn redraw (&self);
|
||||||
|
|
|
||||||
470
src/device/pool.rs
Normal file
470
src/device/pool.rs
Normal file
|
|
@ -0,0 +1,470 @@
|
||||||
|
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>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(PoolCommand = "pool")]
|
||||||
|
impl Pool {
|
||||||
|
|
||||||
|
#[command(Show = "show")]
|
||||||
|
/// Toggle visibility of pool
|
||||||
|
fn show (&mut self, visible: bool) -> Perhaps<PoolCommand> {
|
||||||
|
self.visible = visible;
|
||||||
|
Ok(Some(PoolCommand::Show { visible: !visible }))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set selected clip
|
||||||
|
#[command(Select = "select")]
|
||||||
|
fn select (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
||||||
|
self.set_clip_index(index);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rename item in pool
|
||||||
|
#[command(Rename = "rename")]
|
||||||
|
fn rename (&mut self, command: RenameCommand) -> Perhaps<PoolCommand> {
|
||||||
|
Ok(command.act(self)?.map(|command|PoolCommand::Rename{command}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change length of item
|
||||||
|
#[command(Length = "length")]
|
||||||
|
fn length (&mut self, command: CropCommand) -> Perhaps<PoolCommand> {
|
||||||
|
Ok(command.act(self)?.map(|command|PoolCommand::Length{command}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import from file
|
||||||
|
#[command(Browse = "browse")]
|
||||||
|
fn browse (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
||||||
|
Ok(if let Some(browse) = self.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> {
|
||||||
|
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 }.act(self)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Export to file
|
||||||
|
#[command(Export = "export")]
|
||||||
|
fn export (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
||||||
|
Ok(if let Some(browse) = self.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.clips_mut().remove(index).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.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.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.clips_mut()[index];
|
||||||
|
let old_name = clip.read().unwrap().name.clone();
|
||||||
|
clip.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.clips_mut()[index];
|
||||||
|
let old_len = clip.read().unwrap().length;
|
||||||
|
clip.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.clips()[index].write().unwrap().color);
|
||||||
|
Ok(Some(PoolCommand::SetColor { index, color: color.base }))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(CropCommand = "crop")]
|
||||||
|
impl Pool {
|
||||||
|
|
||||||
|
/// Enter crop mode
|
||||||
|
#[command(Begin = "begin")]
|
||||||
|
fn crop_begin (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
||||||
|
*self.mode_mut() = Some(PoolMode::Length(
|
||||||
|
self.clip_index(),
|
||||||
|
length,
|
||||||
|
ClipLengthFocus::Bar
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit crop mode, discard
|
||||||
|
#[command(Cancel = "cancel")]
|
||||||
|
fn crop_cancel (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(..)) = self.mode_mut().clone() {
|
||||||
|
*self.mode_mut() = None;
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Set = "set")]
|
||||||
|
fn crop_set (&mut self, length: usize) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
|
||||||
|
= self.mode_mut().clone()
|
||||||
|
{
|
||||||
|
let old_length;
|
||||||
|
{
|
||||||
|
let clip = self.clips()[clip].clone();//.write().unwrap();
|
||||||
|
old_length = Some(clip.read().unwrap().length);
|
||||||
|
clip.write().unwrap().length = *length;
|
||||||
|
}
|
||||||
|
*self.mode_mut() = None;
|
||||||
|
return Ok(old_length.map(|length|CropCommand::Set { length }))
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Next = "next")]
|
||||||
|
fn crop_next (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = self.mode_mut().clone() { focus.next() }; Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Prev = "prev")]
|
||||||
|
fn crop_prev (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(_clip, ref mut _length, ref mut focus)) = self.mode_mut().clone() { focus.prev() }; Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Inc = "inc")]
|
||||||
|
fn crop_inc (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = self.mode_mut().clone() {
|
||||||
|
match focus {
|
||||||
|
ClipLengthFocus::Bar => { *length += 4 * PPQ },
|
||||||
|
ClipLengthFocus::Beat => { *length += PPQ },
|
||||||
|
ClipLengthFocus::Tick => { *length += 1 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Dec = "dec")]
|
||||||
|
fn crop_dec (&mut self) -> Perhaps<CropCommand> {
|
||||||
|
if let Some(PoolMode::Length(_clip, ref mut length, ref mut focus)) = self.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tek_proc::commands(RenameCommand = "rename")]
|
||||||
|
impl Pool {
|
||||||
|
|
||||||
|
/// Enter rename mode
|
||||||
|
#[command(Begin = "begin")]
|
||||||
|
fn rename_begin (&mut self) -> Perhaps<RenameCommand> {
|
||||||
|
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
||||||
|
*self.mode_mut() = Some(PoolMode::Rename(
|
||||||
|
self.clip_index(),
|
||||||
|
name
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit rename mode, discard
|
||||||
|
#[command(Cancel = "cancel")]
|
||||||
|
fn rename_cancel (&mut self) -> Perhaps<RenameCommand> {
|
||||||
|
if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.mode_mut().clone() {
|
||||||
|
self.clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit rename mode, update name
|
||||||
|
#[command(Confirm = "confirm")]
|
||||||
|
fn rename_confirm (&mut self) -> Perhaps<RenameCommand> {
|
||||||
|
Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.mode_mut().clone() {
|
||||||
|
let old_name = old_name.clone();
|
||||||
|
*self.mode_mut() = None;
|
||||||
|
Some(RenameCommand::Set { value: old_name })
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(Set = "set")]
|
||||||
|
fn rename_set (&mut self, value: Arc<str>) -> Perhaps<RenameCommand> {
|
||||||
|
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.mode_mut().clone() {
|
||||||
|
self.clips()[clip].write().unwrap().name = value.clone();
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||||
|
|
||||||
|
impl_has_clips!(|self: Pool|self.clips);
|
||||||
|
|
||||||
|
impl_from!(Pool: |clip:&Arc<RwLock<MidiClip>>|{
|
||||||
|
let model = Self::default();
|
||||||
|
model.clips.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].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.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.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
||||||
|
if let Some(index) = index {
|
||||||
|
self.clips.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 _todo_usize_ (&self) -> usize { todo!() }
|
||||||
|
fn _todo_bool_ (&self) -> bool { todo!() }
|
||||||
|
fn _todo_clip_ (&self) -> MidiClip { todo!() }
|
||||||
|
fn _todo_path_ (&self) -> PathBuf { todo!() }
|
||||||
|
fn _todo_color_ (&self) -> ItemColor { todo!() }
|
||||||
|
fn _todo_str_ (&self) -> Arc<str> { todo!() }
|
||||||
|
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.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.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.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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,53 @@ pub(crate) use symphonia::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
def_command!(SamplerCommand: |sampler: Sampler| {
|
||||||
|
RecordToggle { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
let recording = sampler.recording.as_ref().map(|x|x.0);
|
||||||
|
let _ = Self::RecordFinish.act(sampler)?;
|
||||||
|
// autoslice: continue recording at next slot
|
||||||
|
if recording != Some(slot) {
|
||||||
|
Self::RecordBegin { slot }.act(sampler)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RecordBegin { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
sampler.recording = Some((
|
||||||
|
slot,
|
||||||
|
Some(Arc::new(RwLock::new(Sample::new(
|
||||||
|
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
||||||
|
))))
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
RecordFinish => {
|
||||||
|
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||||
|
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
||||||
|
sample
|
||||||
|
}); // TODO: undo
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
RecordCancel => {
|
||||||
|
sampler.recording = None;
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
PlaySample { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
if let Some(ref sample) = sampler.samples.0[slot] {
|
||||||
|
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
StopSample { slot: usize } => {
|
||||||
|
let _slot = *slot;
|
||||||
|
todo!();
|
||||||
|
//Ok(None)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/// Plays [Voice]s from [Sample]s.
|
/// Plays [Voice]s from [Sample]s.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -354,53 +401,6 @@ fn draw_sample (
|
||||||
Ok(label1.len() + label2.len() + 4)
|
Ok(label1.len() + label2.len() + 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
def_command!(SamplerCommand: |sampler: Sampler| {
|
|
||||||
RecordToggle { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
|
||||||
let _ = Self::RecordFinish.act(sampler)?;
|
|
||||||
// autoslice: continue recording at next slot
|
|
||||||
if recording != Some(slot) {
|
|
||||||
Self::RecordBegin { slot }.act(sampler)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
RecordBegin { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
sampler.recording = Some((
|
|
||||||
slot,
|
|
||||||
Some(Arc::new(RwLock::new(Sample::new(
|
|
||||||
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
|
||||||
))))
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
RecordFinish => {
|
|
||||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
|
||||||
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
|
||||||
sample
|
|
||||||
}); // TODO: undo
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
RecordCancel => {
|
|
||||||
sampler.recording = None;
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
PlaySample { slot: usize } => {
|
|
||||||
let slot = *slot;
|
|
||||||
if let Some(ref sample) = sampler.samples.0[slot] {
|
|
||||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
StopSample { slot: usize } => {
|
|
||||||
let _slot = *slot;
|
|
||||||
todo!();
|
|
||||||
//Ok(None)
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/// A currently playing instance of a sample.
|
/// A currently playing instance of a sample.
|
||||||
#[derive(Default, Debug, Clone)] pub struct Voice {
|
#[derive(Default, Debug, Clone)] pub struct Voice {
|
||||||
pub sample: Arc<RwLock<Sample>>,
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
|
|
||||||
|
|
@ -1312,6 +1312,7 @@ mod device {
|
||||||
|
|
||||||
pub mod arrange; pub use self::arrange::*;
|
pub mod arrange; pub use self::arrange::*;
|
||||||
pub mod browse; pub use self::browse::*;
|
pub mod browse; pub use self::browse::*;
|
||||||
|
pub mod pool; pub use self::pool::*;
|
||||||
pub mod clock; pub use self::clock::*;
|
pub mod clock; pub use self::clock::*;
|
||||||
pub mod dialog; pub use self::dialog::*;
|
pub mod dialog; pub use self::dialog::*;
|
||||||
pub mod editor; pub use self::editor::*;
|
pub mod editor; pub use self::editor::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue