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/.gitkeep
|
||||
perf.data*
|
||||
|
|
|
|||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -368,6 +368,12 @@ dependencies = [
|
|||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "case"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
|
|
@ -3158,6 +3164,7 @@ dependencies = [
|
|||
name = "tek_proc"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"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]
|
||||
proc-macro = true
|
||||
[dependencies]
|
||||
case = "1.0.0"
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.46"
|
||||
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 std::collections::{HashMap, BTreeMap};
|
||||
use syn::{
|
||||
Path, ItemEnum, Ident, Type, Variant, MetaList, Expr, ExprPath, ExprBinary, Fields, BinOp,
|
||||
parse::{ParseStream, Parse, Result}
|
||||
Error, Path, Lit, Ident, Variant, Fields, BinOp,
|
||||
Expr, ExprPath, ExprBinary, ExprAssign, ExprLit,
|
||||
ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature,
|
||||
MetaList, Type, TypePath, FnArg, PatType,
|
||||
parse::{ParseStream, Parse, Result},
|
||||
spanned::Spanned
|
||||
};
|
||||
|
||||
macro_rules! attribute {
|
||||
|
|
@ -22,16 +26,114 @@ macro_rules! attribute {
|
|||
}
|
||||
}
|
||||
|
||||
attribute!(command {
|
||||
#[derive(Debug, Clone)] pub struct Def(
|
||||
pub Meta, pub Item
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct Meta(
|
||||
pub Path
|
||||
);
|
||||
attribute!(commands {
|
||||
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
|
||||
#[derive(Debug, Clone)] pub struct Meta(pub Path, pub Lit);
|
||||
#[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 {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self(input.parse()?))
|
||||
|
|
|
|||
|
|
@ -23,29 +23,64 @@ pub struct Arrangement {
|
|||
/// Selected UI element
|
||||
pub selection: Selection,
|
||||
/// Source of time
|
||||
#[cfg(feature = "clock")] pub clock: Clock,
|
||||
#[cfg(feature = "clock")] pub clock: Clock,
|
||||
/// Allows one MIDI clip to be edited
|
||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||
#[cfg(feature = "editor")] pub editor: Option<MidiEditor>,
|
||||
/// List of global midi inputs
|
||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||
#[cfg(feature = "port")] pub midi_ins: Vec<MidiInput>,
|
||||
/// List of global midi outputs
|
||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||
#[cfg(feature = "port")] pub midi_outs: Vec<MidiOutput>,
|
||||
/// List of global audio inputs
|
||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||
#[cfg(feature = "port")] pub audio_ins: Vec<AudioInput>,
|
||||
/// List of global audio outputs
|
||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||
#[cfg(feature = "port")] pub audio_outs: Vec<AudioOutput>,
|
||||
/// Last track number (to avoid duplicate port names)
|
||||
#[cfg(feature = "track")] pub track_last: usize,
|
||||
/// List of tracks
|
||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||
#[cfg(feature = "track")] pub tracks: Vec<Track>,
|
||||
/// Scroll offset of tracks
|
||||
#[cfg(feature = "track")] pub track_scroll: usize,
|
||||
/// List of scenes
|
||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
|
||||
/// Scroll offset of scenes
|
||||
#[cfg(feature = "scene")] pub scene_scroll: usize,
|
||||
}
|
||||
|
||||
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
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||
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 {
|
||||
/// Put a clip in a slot
|
||||
pub fn clip_put (
|
||||
|
|
@ -528,14 +551,6 @@ impl ScenesView for Arrangement {
|
|||
pub type SceneWith<'a, 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_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 }
|
||||
}
|
||||
|
||||
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: AsRefOpt<Track>+AsMutOpt<Track>+Send+Sync> HasTrack 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|{
|
||||
//("begin" [] Some(Self::Begin))
|
||||
//("cancel" [] Some(Self::Cancel))
|
||||
|
|
@ -52,40 +76,6 @@ pub(crate) struct EntriesIterator<'a, S: Screen> {
|
|||
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]
|
||||
//#[derive(Debug, Clone, PartialEq)]
|
||||
//pub enum BrowseCommand {
|
||||
|
|
@ -96,198 +86,6 @@ pub struct PoolView<'a>(pub &'a Pool);
|
|||
//Chdir(PathBuf),
|
||||
//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 {
|
||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||
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_usize (&self) -> usize { todo!() }
|
||||
fn _todo_stub_arc_str (&self) -> Arc<str> { todo!() }
|
||||
}
|
||||
|
||||
impl Browse {
|
||||
fn tui (&self) -> impl Draw<Tui> {
|
||||
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>)> {
|
||||
let (mut subdirs, mut files) = std::fs::read_dir(dir)?
|
||||
.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 {}
|
||||
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||
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",
|
||||
];
|
||||
|
||||
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 {
|
||||
pub fn next (&self) -> 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.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -213,30 +237,6 @@ impl <T: TimeRange+NoteRange> MidiRange 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 {
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||
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.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -354,53 +401,6 @@ fn draw_sample (
|
|||
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.
|
||||
#[derive(Default, Debug, Clone)] pub struct Voice {
|
||||
pub sample: Arc<RwLock<Sample>>,
|
||||
|
|
|
|||
|
|
@ -1312,6 +1312,7 @@ mod device {
|
|||
|
||||
pub mod arrange; pub use self::arrange::*;
|
||||
pub mod browse; pub use self::browse::*;
|
||||
pub mod pool; pub use self::pool::*;
|
||||
pub mod clock; pub use self::clock::*;
|
||||
pub mod dialog; pub use self::dialog::*;
|
||||
pub mod editor; pub use self::editor::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue