mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-28 04:46:58 +02:00
This commit is contained in:
parent
a4931d8e4f
commit
def7a1b210
17 changed files with 2209 additions and 1790 deletions
292
proc/src/lib.rs
292
proc/src/lib.rs
|
|
@ -5,18 +5,20 @@ use std::collections::{HashMap, BTreeMap};
|
|||
use syn::{
|
||||
Error, Path, Ident, Variant, Fields, BinOp,
|
||||
Expr, ExprPath, ExprBinary, ExprAssign, ExprLit, Lit, LitStr,
|
||||
ItemEnum, ItemImpl, ImplItem, ImplItemFn, Signature,
|
||||
MetaList, Type, TypePath, FnArg, PatType,
|
||||
Item, ItemEnum, ItemImpl, ImplItem, ImplItemFn, ItemTrait, TraitItem, TraitItemFn,
|
||||
Signature, MetaList, Type, TypePath, FnArg, PatType, Attribute,
|
||||
parse::{ParseStream, Parse, Result},
|
||||
spanned::Spanned
|
||||
spanned::Spanned,
|
||||
punctuated::Punctuated,
|
||||
token::Comma
|
||||
};
|
||||
|
||||
macro_rules! attribute {
|
||||
($name:ident { $($body:tt)* }) => {
|
||||
#[proc_macro_attribute] pub fn $name (meta: TokenStream, item: TokenStream) -> TokenStream {
|
||||
write(self::$name::Def(
|
||||
syn::parse_macro_input!(meta as self::$name::Meta),
|
||||
syn::parse_macro_input!(item as self::$name::Item),
|
||||
write(self::$name::CustomAttribute(
|
||||
syn::parse_macro_input!(meta as self::$name::CustomAttributeMeta),
|
||||
syn::parse_macro_input!(item as self::$name::CustomAttributeItem),
|
||||
))
|
||||
}
|
||||
mod $name {
|
||||
|
|
@ -27,70 +29,139 @@ macro_rules! attribute {
|
|||
}
|
||||
|
||||
attribute!(commands {
|
||||
#[derive(Debug, Clone)] pub struct Def(pub Meta, pub Item);
|
||||
#[derive(Debug, Clone)] pub struct Meta(pub Path, pub LitStr);
|
||||
#[derive(Debug, Clone)] pub struct Item(
|
||||
pub Path, pub ItemImpl, pub HashMap<Ident, (Ident, Vec<FnArg>, LitStr)>
|
||||
#[derive(Debug, Clone)] pub struct CustomAttribute(
|
||||
pub CustomAttributeMeta,
|
||||
pub CustomAttributeItem,
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeMeta(
|
||||
pub Path,
|
||||
pub Option<LitStr>
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeItem(
|
||||
pub Path,
|
||||
pub CustomAttributeItemDispatch,
|
||||
pub Item
|
||||
);
|
||||
|
||||
impl Parse for Meta {
|
||||
pub type CustomAttributeItemDispatch = HashMap<Ident, (Ident, Vec<FnArg>, LitStr)>;
|
||||
|
||||
impl Parse for CustomAttributeMeta {
|
||||
/// Parse contents of `#[command(...)]` attribute tag.
|
||||
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: Lit::Str(lit), .. }) = &**right
|
||||
{
|
||||
Ok(Self(path.clone(), lit.clone()))
|
||||
} else {
|
||||
Err(Error::new(meta.span(), format!(
|
||||
"must be: #[tek_proc::commands(Struct = \"struct\")], got: {meta:?}"
|
||||
let meta: Expr = input.parse()?;
|
||||
Ok(match meta {
|
||||
// Struct name only
|
||||
Expr::Path(ExprPath { path, .. }) => Self(path, None),
|
||||
|
||||
// Struct name with namespace prefix
|
||||
Expr::Assign(ExprAssign { ref left, ref right, .. })
|
||||
if let Expr::Path(ExprPath { path, .. }) = &**left
|
||||
&& let Expr::Lit(ExprLit { lit: Lit::Str(lit), .. }) = &**right
|
||||
=> Self(path.clone(), Some(lit.clone())),
|
||||
|
||||
// All other variants invalid
|
||||
_ => return Err(Error::new(meta.span(), format!(
|
||||
"must be: #[commands(Struct)] or #[commands(Struct = \"struct\")], got: {meta:?}"
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Item {
|
||||
impl Parse for CustomAttributeItem {
|
||||
/// Parse contents of `trait` or `impl` block annotated with `#[command(...)]`
|
||||
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>, LitStr)> = 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: Lit::Str(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))
|
||||
let mut item: Item = input.parse()?;
|
||||
Ok(Self(
|
||||
parse_custom_attribute_item_path(&item)?,
|
||||
parse_custom_attribute_item_dispatch(&mut item)?,
|
||||
item,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Def {
|
||||
fn parse_custom_attribute_item_path (item: &Item) -> Result<Path> {
|
||||
Ok(match item {
|
||||
Item::Trait(ItemTrait { ident, .. }) => Path {
|
||||
leading_colon: None,
|
||||
segments: syn::punctuated::Punctuated::from_iter([
|
||||
syn::PathSegment {
|
||||
ident: ident.clone(),
|
||||
arguments: syn::PathArguments::None // TODO support generics
|
||||
}
|
||||
])
|
||||
},
|
||||
|
||||
Item::Impl(ItemImpl { self_ty, .. }) if let Type::Path(TypePath {
|
||||
path, ..
|
||||
}) = &**self_ty => path.clone(),
|
||||
|
||||
_ => return Err(
|
||||
Error::new(item.span(), format!("#[commands] works on trait or inherent impl"))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Pick out annotated functions from the `trait` or `impl` block,
|
||||
/// adding them to the [CustomAttributeItemDispatch] collection.
|
||||
fn parse_custom_attribute_item_dispatch (item: &mut Item)
|
||||
-> Result<CustomAttributeItemDispatch>
|
||||
{
|
||||
let mut dispatch: CustomAttributeItemDispatch = Default::default();
|
||||
let mut dispatch_attrs = |
|
||||
attrs: &Vec<Attribute>, ident: &Ident, inputs: &Punctuated<FnArg, Comma>
|
||||
| {
|
||||
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: Lit::Str(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()
|
||||
};
|
||||
|
||||
match item {
|
||||
Item::Trait(ItemTrait { items, .. }) => {
|
||||
for item in items.iter_mut() {
|
||||
if let TraitItem::Fn(TraitItemFn {
|
||||
attrs, sig: Signature { ident, inputs, .. }, ..
|
||||
}) = item {
|
||||
*attrs = dispatch_attrs(attrs, ident, inputs);
|
||||
}
|
||||
}
|
||||
},
|
||||
Item::Impl(ItemImpl { items, .. }) => {
|
||||
for item in items.iter_mut() {
|
||||
if let ImplItem::Fn(ImplItemFn {
|
||||
attrs, sig: Signature { ident, inputs, .. }, ..
|
||||
}) = item {
|
||||
*attrs = dispatch_attrs(attrs, ident, inputs);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => return Err(
|
||||
Error::new(item.span(), format!("#[commands] works on trait or inherent impl"))
|
||||
)
|
||||
}
|
||||
Ok(dispatch)
|
||||
}
|
||||
|
||||
impl ToTokens for CustomAttribute {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(Meta(command, namespace), Item(state, item, items)) = self;
|
||||
let Self(
|
||||
CustomAttributeMeta(command, namespace),
|
||||
CustomAttributeItem(state, items, item),
|
||||
) = self;
|
||||
let mut variants = quote! {};
|
||||
let mut dispatch = quote! {};
|
||||
let mut keywords = quote! {};
|
||||
|
|
@ -116,7 +187,11 @@ attribute!(commands {
|
|||
} else {
|
||||
quote! { #command::#variant => state.#ident(), }
|
||||
});
|
||||
let keyword = format!("{}/{}", namespace.value(), keyword.value());
|
||||
let keyword = if let Some(namespace) = namespace {
|
||||
format!("{}/{}", namespace.value(), keyword.value())
|
||||
} else {
|
||||
keyword.value()
|
||||
};
|
||||
if has_args {
|
||||
append(&mut expressions, quote! {
|
||||
#keyword (#typed) => { #command::#variant { #params } },
|
||||
|
|
@ -127,24 +202,40 @@ attribute!(commands {
|
|||
});
|
||||
}
|
||||
}
|
||||
append(out, quote! {
|
||||
#[derive(Debug, Clone)] pub enum #command { #variants }
|
||||
|
||||
impl<'a> dizzle::Namespace<'a, #command> for #state {
|
||||
symbols!('a |state| -> #command {
|
||||
#keywords
|
||||
});
|
||||
expressions!('a |state| -> #command {
|
||||
#expressions
|
||||
});
|
||||
}
|
||||
|
||||
impl #command {
|
||||
pub fn act (self, state: &mut #state) -> Perhaps<Self> {
|
||||
match self { #dispatch _ => unreachable!() }
|
||||
let impls = match item {
|
||||
Item::Impl(ItemImpl { generics, .. }) => {
|
||||
let lts = Punctuated::<_, Comma>::from_iter(generics.lifetimes());
|
||||
let tys = Punctuated::<_, Comma>::from_iter(generics.type_params());
|
||||
let cns = Punctuated::<_, Comma>::from_iter(generics.const_params());
|
||||
quote! {
|
||||
impl<'n, #tys> dizzle::Namespace<'n, #command> for #state {
|
||||
symbols!('n |state: Self| -> #command { #keywords });
|
||||
expressions!('n |state: Self| -> #command { #expressions });
|
||||
}
|
||||
impl #generics #command {
|
||||
pub fn act (self, state: &mut #state) -> Perhaps<Self> {
|
||||
match self { #dispatch _ => unreachable!() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
Item::Trait { .. } => quote! {
|
||||
impl<'n, T: #state + 'n> dizzle::Namespaced<'n, T> for #command {
|
||||
symbols!('n |state: T| -> Self { #keywords });
|
||||
expressions!('n |state: T| -> Self { #expressions });
|
||||
}
|
||||
impl #command {
|
||||
pub fn act <'n, T: #state + 'n> (self, state: &mut T) -> Perhaps<Self> {
|
||||
match self { #dispatch _ => unreachable!() }
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("trait or inherent impl needed for #[commands]")
|
||||
};
|
||||
append(out, quote! {
|
||||
/// Command variants
|
||||
#[derive(Debug, Clone)] pub enum #command { #variants }
|
||||
#impls
|
||||
#item
|
||||
})
|
||||
}
|
||||
|
|
@ -152,15 +243,22 @@ attribute!(commands {
|
|||
});
|
||||
|
||||
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 {
|
||||
#[derive(Debug, Clone)] pub struct CustomAttribute(
|
||||
pub CustomAttributeMeta,
|
||||
pub CustomAttributeItem
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeMeta(
|
||||
pub Path
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeItem(
|
||||
pub ItemEnum, pub HashMap<Ident, (Fields, Expr)>
|
||||
);
|
||||
impl Parse for CustomAttributeMeta {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self(input.parse()?))
|
||||
}
|
||||
}
|
||||
impl Parse for Item {
|
||||
impl Parse for CustomAttributeItem {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let mut item: ItemEnum = input.parse()?;
|
||||
let mut branches: HashMap<Ident, (Fields, Expr)> = Default::default();
|
||||
|
|
@ -187,9 +285,12 @@ attribute!(command {
|
|||
Ok(Self(item, branches))
|
||||
}
|
||||
}
|
||||
impl ToTokens for Def {
|
||||
impl ToTokens for CustomAttribute {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(Meta(state), Item(item, branches)) = self;
|
||||
let Self(
|
||||
CustomAttributeMeta(state),
|
||||
CustomAttributeItem(item, branches)
|
||||
) = self;
|
||||
let ident = &item.ident;
|
||||
let mut body = quote! {};
|
||||
for (variant, (fields, handler)) in branches.iter() {
|
||||
|
|
@ -249,21 +350,21 @@ attribute!(command {
|
|||
});
|
||||
|
||||
attribute!(keyword {
|
||||
#[derive(Debug, Clone)] pub struct Def(
|
||||
pub Meta, pub Item
|
||||
#[derive(Debug, Clone)] pub struct CustomAttribute(
|
||||
pub CustomAttributeMeta, pub CustomAttributeItem
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct Meta(
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeMeta(
|
||||
pub Path
|
||||
);
|
||||
#[derive(Debug, Clone)] pub struct Item(
|
||||
#[derive(Debug, Clone)] pub struct CustomAttributeItem(
|
||||
pub ItemEnum, pub BTreeMap<Ident, Vec<(Literal, BTreeMap<Ident, Type>)>>
|
||||
);
|
||||
impl Parse for Meta {
|
||||
impl Parse for CustomAttributeMeta {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
Ok(Self(input.parse()?))
|
||||
}
|
||||
}
|
||||
impl Parse for Item {
|
||||
impl Parse for CustomAttributeItem {
|
||||
fn parse (input: ParseStream) -> Result<Self> {
|
||||
let mut item: ItemEnum = input.parse()?;
|
||||
for Variant { attrs, ident, fields, discriminant } in item.variants.iter_mut() {
|
||||
|
|
@ -281,18 +382,19 @@ attribute!(keyword {
|
|||
Ok(Self(item, Default::default()))
|
||||
}
|
||||
}
|
||||
impl ToTokens for Def {
|
||||
impl ToTokens for CustomAttribute {
|
||||
fn to_tokens (&self, out: &mut TokenStream2) {
|
||||
let Self(Meta(state), Item(item, variants)) = self;
|
||||
let Self(
|
||||
CustomAttributeMeta(state),
|
||||
CustomAttributeItem(item, variants)
|
||||
) = self;
|
||||
let ident = &item.ident;
|
||||
let body = quote! {};
|
||||
append(out, quote! {
|
||||
#item
|
||||
|
||||
impl<'a> Namespace<'a, #ident> for #state {
|
||||
symbols!('a |_state| -> #ident {
|
||||
#body,
|
||||
});
|
||||
impl<'n> Namespace<'n, #ident> for #state {
|
||||
symbols!('n |_state: #state| -> #ident { #body, });
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
165
src/device/arrange/clip.rs
Normal file
165
src/device/arrange/clip.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use crate::*;
|
||||
|
||||
/// TODO: Preserve the generic passthru syntax;
|
||||
/// remove this macro (only used twice) and potentially the trait.
|
||||
#[macro_export] macro_rules! impl_has_clips {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||
$cb.read().unwrap()
|
||||
}
|
||||
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
||||
$cb.write().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clip {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clip (&$self) -> Option<Arc<RwLock<MidiClip>>> { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arrangement {
|
||||
/// Toggle looping for the active clip
|
||||
pub fn toggle_loop (&mut self) {
|
||||
if let Some(clip) = self.selected_clip() {
|
||||
clip.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
|
||||
/// Put a clip in a slot
|
||||
pub fn clip_put (
|
||||
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
||||
) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
let old = self.scenes[scene].clips[track].clone();
|
||||
self.scenes[scene].clips[track] = clip;
|
||||
old
|
||||
}
|
||||
|
||||
/// Change the color of a clip, returning the previous one
|
||||
pub fn clip_set_color (
|
||||
&self, track: usize, scene: usize, color: ItemTheme
|
||||
) -> Option<ItemTheme> {
|
||||
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
||||
let mut clip = clip.write().unwrap();
|
||||
let old = clip.color.clone();
|
||||
clip.color = color.clone();
|
||||
panic!("{color:?} {old:?}");
|
||||
//old
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
|
||||
|
||||
pub trait HasClipsSize {
|
||||
fn clips_size (&self) -> &Sizer;
|
||||
}
|
||||
|
||||
impl HasClipsSize for App {
|
||||
fn clips_size (&self) -> &Sizer { &self.project.size_inner }
|
||||
}
|
||||
|
||||
impl HasClipsSize for Arrangement {
|
||||
fn clips_size (&self) -> &Sizer { &self.size_inner }
|
||||
}
|
||||
|
||||
pub trait ClipsView: TracksView + ScenesView {
|
||||
/// Draw clips per scene
|
||||
fn view_scenes_clips <'a> (&'a self) -> impl Draw<Tui> + 'a {
|
||||
view_scenes_clips(
|
||||
||self.scenes_with_sizes(),
|
||||
self.tracks_with_sizes(),
|
||||
self.selection(),
|
||||
self.editor(),
|
||||
self.clips_size(),
|
||||
self.is_editing(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> (
|
||||
scenes: impl Fn()->S,
|
||||
tracks: impl TracksSizes<'a>,
|
||||
select: &Selection,
|
||||
editor: Option<&MidiEditor>,
|
||||
size: &Sizer,
|
||||
editing: bool,
|
||||
) -> impl Draw<Tui> {
|
||||
let status = fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh();
|
||||
let tracks = iter_once(tracks, move|(track_index, track, _, _), _| {
|
||||
let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| {
|
||||
let (name, theme): (Arc<str>, ItemTheme) = scene_name_theme(scene, track_index);
|
||||
let f = theme.lightest.term;
|
||||
let (b, o) = scene_bg(theme, select, track_index, scene_index);
|
||||
let w = scene_w(track, select, track_index, editor);
|
||||
let y = scene_y(select, scene_index, editor);
|
||||
let is_selected = scene_sel(select, track_index, scene_index, editing);
|
||||
below(
|
||||
Outer(true, Style::default().fg(o)).full_wh(),
|
||||
below(
|
||||
below(
|
||||
fg_bg(o, b, "".full_wh()),
|
||||
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
|
||||
),
|
||||
when(is_selected, editor.map(|e|e.view())).full_wh()
|
||||
).full_wh()
|
||||
).exact_wh(w, y)
|
||||
});
|
||||
scenes.full_h().exact_w(track.width as u16)
|
||||
});
|
||||
|
||||
return size.of(above(status, tracks).full_wh());
|
||||
|
||||
fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
||||
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||
let clip = clip.read().unwrap();
|
||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||
} else {
|
||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_bg (
|
||||
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
||||
) -> (Color, Color) {
|
||||
let mut outline = theme.base.term;
|
||||
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
||||
outline = theme.lighter.term;
|
||||
theme.light.term
|
||||
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
||||
outline = theme.darkest.term;
|
||||
theme.base.term
|
||||
} else {
|
||||
theme.dark.term
|
||||
}, outline)
|
||||
}
|
||||
|
||||
fn scene_w (
|
||||
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
||||
) -> u16 {
|
||||
if select.track() == Some(track_index) && let Some(editor) = editor {
|
||||
(editor.size.w() as usize).max(24).max(track.width) as u16
|
||||
} else {
|
||||
track.width as u16
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_y (
|
||||
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
||||
) -> u16 {
|
||||
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
||||
editor.size.h().max(12)
|
||||
} else {
|
||||
Scene::DEFAULT_HEIGHT as u16
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
||||
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
||||
}
|
||||
}
|
||||
87
src/device/arrange/port.rs
Normal file
87
src/device/arrange/port.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use crate::*;
|
||||
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
|
||||
impl_has!(Vec<MidiInput>: |self: Arrangement| self.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: Arrangement| self.midi_outs);
|
||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
|
||||
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
||||
}
|
||||
|
||||
pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
||||
}
|
||||
|
||||
pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
||||
}
|
||||
|
||||
pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
||||
}
|
||||
|
||||
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
||||
-> impl Draw<Tui> + use<'a, T>
|
||||
{
|
||||
let ins = ports.len() as u16;
|
||||
let frame = Outer(true, Style::default().fg(g(96)));
|
||||
let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
|
||||
" {index} {}", port.port_name()
|
||||
).align_w().full_h()));
|
||||
let field = field_v(theme, title, names);
|
||||
border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
||||
}
|
||||
|
||||
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
||||
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
|
||||
iter(items,
|
||||
move|(_index, name, connections, y, y2): Item<'a>, _| south(
|
||||
bold(true, fg_bg(fg, bg, east(" ", name).align_w())).full_h(),
|
||||
iter(||connections.iter(), move|connect: &'a Connect, index|{
|
||||
bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16)
|
||||
})
|
||||
).exact_h((y2 - y) as u16).push_y(y as u16))
|
||||
}
|
||||
|
||||
pub struct Junction<T: JackPort>(T);
|
||||
|
||||
impl<T: JackPort> View<Tui> for Junction<T> {
|
||||
fn view (&self) -> impl Draw<Tui> {
|
||||
T::KIND
|
||||
}
|
||||
}
|
||||
|
||||
#[tek_proc::command(AudioInput)]
|
||||
#[tek_proc::keyword(AudioInput)]
|
||||
#[derive(Debug)]
|
||||
pub enum AudioInputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(AudioOutput)]
|
||||
#[tek_proc::keyword(AudioOutput)]
|
||||
#[derive(Debug)]
|
||||
pub enum AudioOutputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(MidiInput)]
|
||||
#[tek_proc::keyword(MidiInput)]
|
||||
#[derive(Debug)]
|
||||
pub enum MidiInputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(MidiOutput)]
|
||||
#[tek_proc::keyword(MidiOutput)]
|
||||
#[derive(Debug)]
|
||||
pub enum MidiOutputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
289
src/device/arrange/scene.rs
Normal file
289
src/device/arrange/scene.rs
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
/// A scene consists of a set of clips to play together.
|
||||
///
|
||||
/// ```
|
||||
/// let scene: tek::Scene = Default::default();
|
||||
/// let _ = scene.pulses();
|
||||
/// let _ = scene.is_playing(&[]);
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub struct Scene {
|
||||
/// Name of scene
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of scene
|
||||
pub color: ItemTheme,
|
||||
/// Clips in scene, one per track
|
||||
pub clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
pub const DEFAULT_HEIGHT: usize = 2;
|
||||
|
||||
/// Get currently playing clip, if any
|
||||
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
|
||||
if let Some(Some(clip)) = self.clips.get(index) {
|
||||
Some(clip)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get pulse length of the longest clip in the scene
|
||||
pub fn pulses (&self) -> usize {
|
||||
self.clips.iter().fold(0, |a, p|{
|
||||
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
/// True if all clips in scene are currently playing on given tracks.
|
||||
pub fn is_playing (&self, tracks: &[Track]) -> bool {
|
||||
self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate()
|
||||
.all(|(track_index, clip)|match clip {
|
||||
Some(c) => tracks
|
||||
.get(track_index)
|
||||
.map(|track|{
|
||||
if let Some((_, Some(clip))) = track.sequencer().play_clip() {
|
||||
*clip.read().unwrap() == *c.read().unwrap()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.unwrap_or(false),
|
||||
None => true
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[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_ref_opt!(Scene: |self: Arrangement| self.selected_scene());
|
||||
#[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut());
|
||||
|
||||
impl<T: AsRefOpt<Scene> + AsMutOpt<Scene> + Send + Sync> HasScene for T {}
|
||||
|
||||
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
||||
fn scene (&self) -> Option<&Scene> {
|
||||
self.as_ref_opt()
|
||||
}
|
||||
fn scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
self.as_mut_opt()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasScene
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, ItemTheme>
|
||||
> SceneController for T {}
|
||||
|
||||
#[tek_proc::commands(SceneCommand)]
|
||||
pub trait SceneController: HasScene
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, ItemTheme>
|
||||
{
|
||||
#[command(SetSize = "scene/size")]
|
||||
fn scene_set_size (&mut self, size: usize) -> Perhaps<SceneCommand>
|
||||
where Self: for<'a> Namespace<'a, usize>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
#[command(SetZoom = "scene/zoom")]
|
||||
fn scene_set_zoom (&mut self, size: usize) -> Perhaps<SceneCommand>
|
||||
where Self: for<'a> Namespace<'a, usize>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
#[command(SetName = "scene/name")]
|
||||
fn scene_set_name (&mut self, name: Arc<str>) -> Perhaps<SceneCommand>
|
||||
where Self: for<'a> Namespace<'a, Arc<str>>
|
||||
{
|
||||
Ok(self.scene_mut().map(|scene|swap_value(
|
||||
&mut scene.name,
|
||||
&name,
|
||||
|name|SceneCommand::SetName { name }
|
||||
)).transpose()?.flatten())
|
||||
}
|
||||
#[command(SetColor = "scene/color")]
|
||||
fn scene_set_color (&mut self, color: ItemTheme) -> Perhaps<SceneCommand>
|
||||
where Self: for<'a> Namespace<'a, ItemTheme>
|
||||
{
|
||||
Ok(self.scene_mut().map(|scene|swap_value(
|
||||
&mut scene.color,
|
||||
&color,
|
||||
|color|SceneCommand::SetColor { color }
|
||||
)).transpose()?.flatten())
|
||||
}
|
||||
}
|
||||
|
||||
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
|
||||
|
||||
def_sizes_iter!(ScenesSizes => Scene);
|
||||
impl_has!(Vec<Scene>: |self: Arrangement| self.scenes);
|
||||
impl_as_ref!(Vec<Scene>: |self: App| self.project.as_ref());
|
||||
impl_as_mut!(Vec<Scene>: |self: App| self.project.as_mut());
|
||||
|
||||
impl<T: AsRef<Vec<Scene>> + AsMut<Vec<Scene>>> HasScenes for T {}
|
||||
|
||||
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
||||
fn scenes (&self) -> &Vec<Scene> {
|
||||
self.as_ref()
|
||||
}
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
|
||||
self.as_mut()
|
||||
}
|
||||
/// Generate the default name for a new scene
|
||||
fn scenes_default_name (&self) -> Arc<str> {
|
||||
format!("s{:3>}", self.scenes().len() + 1).into()
|
||||
}
|
||||
fn scenes_longest_name (&self) -> usize {
|
||||
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||
}
|
||||
/// Add multiple scenes
|
||||
fn scenes_add_many (&mut self, n: usize)
|
||||
-> Usually<()> where Self: HasTracks
|
||||
{
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _ = self.scenes_add_one(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Add a scene
|
||||
fn scenes_add_one (&mut self, name: Option<Arc<str>>, color: Option<ItemTheme>)
|
||||
-> Usually<(usize, &mut Scene)>
|
||||
where
|
||||
Self: HasTracks
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scenes_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
self.scenes_mut().push(scene);
|
||||
let index = self.scenes().len() - 1;
|
||||
Ok((index, &mut self.scenes_mut()[index]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasScenes
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<str>>>
|
||||
> ScenesController for T {}
|
||||
|
||||
#[tek_proc::commands(ScenesCommand)]
|
||||
pub trait ScenesController: HasScenes
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<str>>>
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
|
||||
fn h_scenes (&self) -> u16;
|
||||
fn w_side (&self) -> u16;
|
||||
fn w_mid (&self) -> u16;
|
||||
|
||||
fn view_scenes_names (&self) -> impl Draw<Tui> {
|
||||
let select = self.selection();
|
||||
let editor = self.editor();
|
||||
let editing = self.is_editing();
|
||||
draw(move |to: &mut Tui|{
|
||||
for (index, scene, ..) in self.scenes_with_sizes() {
|
||||
view_scene_name(select, editor, index, scene, editing).draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(1, 1, 1, 1)))
|
||||
})
|
||||
.exact_w(20)
|
||||
}
|
||||
|
||||
fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> {
|
||||
let mut y = 0;
|
||||
self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{
|
||||
let height = if self.selection().scene() == Some(s) && self.editor().is_some() {
|
||||
8
|
||||
} else {
|
||||
Scene::DEFAULT_HEIGHT
|
||||
};
|
||||
if y + height <= self.clips_size().h() as usize {
|
||||
let data = (s, scene, y, y + height);
|
||||
y += height;
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ScenesView for App {
|
||||
fn w_mid (&self) -> u16 {
|
||||
(self.size.w() as u16).saturating_sub(self.w_side())
|
||||
}
|
||||
fn w_side (&self) -> u16 {
|
||||
20
|
||||
}
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(self.size.h() as u16).saturating_sub(20)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScenesView for Arrangement {
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(self.size.h() as u16).saturating_sub(20)
|
||||
}
|
||||
fn w_side (&self) -> u16 {
|
||||
(self.size.w() as u16 * 2 / 10).max(20)
|
||||
}
|
||||
fn w_mid (&self) -> u16 {
|
||||
(self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasSceneScroll: HasScenes {
|
||||
fn scene_scroll (&self) -> usize;
|
||||
}
|
||||
|
||||
impl HasSceneScroll for Arrangement {
|
||||
fn scene_scroll (&self) -> usize {
|
||||
self.scene_scroll
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSceneScroll for App {
|
||||
fn scene_scroll (&self) -> usize {
|
||||
self.project.scene_scroll()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_scene_name (
|
||||
select: &Selection,
|
||||
editor: Option<&MidiEditor>,
|
||||
index: usize,
|
||||
scene: &Scene,
|
||||
editing: bool
|
||||
) -> impl Draw<Tui> {
|
||||
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
||||
7
|
||||
} else {
|
||||
Scene::DEFAULT_HEIGHT as u16
|
||||
};
|
||||
let a = east(format!("·s{index:02} "),
|
||||
fg(g(255), bold(true, &scene.name))).align_w().full_w();
|
||||
let b = when(select.scene() == Some(index) && editing, south(
|
||||
editor.as_ref().map(|e|e.clip_status()),
|
||||
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
|
||||
let c = if select.scene() == Some(index) {
|
||||
scene.color.light.term
|
||||
} else {
|
||||
scene.color.base.term
|
||||
};
|
||||
bg(c, south(a, b).align_nw()).exact_wh(20, h)
|
||||
}
|
||||
195
src/device/arrange/select.rs
Normal file
195
src/device/arrange/select.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
use crate::*;
|
||||
|
||||
impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track());
|
||||
impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut());
|
||||
impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt());
|
||||
impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt());
|
||||
impl <T: AsRef<Selection>+AsMut<Selection>> HasSelection for T {}
|
||||
|
||||
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
||||
fn selection (&self) -> &Selection {
|
||||
self.as_ref()
|
||||
}
|
||||
fn selection_mut (&mut self) -> &mut Selection {
|
||||
self.as_mut()
|
||||
}
|
||||
|
||||
/// Get the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks().get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks_mut().get_mut(index)
|
||||
}
|
||||
|
||||
/// Get the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes().get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes_mut().get_mut(index)
|
||||
}
|
||||
|
||||
/// Get the active clip
|
||||
#[cfg(feature = "clip")]
|
||||
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
|
||||
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the current user selection in the arranger
|
||||
#[derive(PartialEq, Clone, Copy, Debug, Default)]
|
||||
pub enum Selection {
|
||||
#[default]
|
||||
/// Nothing is selected
|
||||
Nothing,
|
||||
/// The whole mix is selected
|
||||
Mix,
|
||||
/// A MIDI input is selected.
|
||||
Input(usize),
|
||||
/// A MIDI output is selected.
|
||||
Output(usize),
|
||||
/// A scene is selected.
|
||||
#[cfg(feature = "scene")] Scene(usize),
|
||||
/// A track is selected.
|
||||
#[cfg(feature = "track")] Track(usize),
|
||||
/// A clip (track × scene) is selected.
|
||||
#[cfg(feature = "track")] TrackClip { track: usize, scene: usize },
|
||||
/// A track's MIDI input connection is selected.
|
||||
#[cfg(feature = "track")] TrackInput { track: usize, port: usize },
|
||||
/// A track's MIDI output connection is selected.
|
||||
#[cfg(feature = "track")] TrackOutput { track: usize, port: usize },
|
||||
/// A track device slot is selected.
|
||||
#[cfg(feature = "track")] TrackDevice { track: usize, device: usize },
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
|
||||
pub fn describe (
|
||||
&self,
|
||||
#[cfg(feature = "track")] tracks: &[Track],
|
||||
#[cfg(feature = "scene")] scenes: &[Scene],
|
||||
) -> Arc<str> {
|
||||
use Selection::*;
|
||||
format!("{}", match self {
|
||||
Mix => "Everything".to_string(),
|
||||
#[cfg(feature = "scene")] Scene(s) =>
|
||||
scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()),
|
||||
#[cfg(feature = "track")] Track(t) =>
|
||||
tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)).unwrap_or_else(||"T??".into()),
|
||||
TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) {
|
||||
(Some(_), Some(s)) => match s.clip(*track) {
|
||||
Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name),
|
||||
None => format!("T{track} S{scene}: Empty")
|
||||
},
|
||||
_ => format!("T{track} S{scene}: Empty"),
|
||||
},
|
||||
_ => todo!()
|
||||
}).into()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "scene")]
|
||||
impl Selection {
|
||||
|
||||
pub fn scene (&self) -> Option<usize> {
|
||||
use Selection::*;
|
||||
match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None }
|
||||
}
|
||||
|
||||
pub fn select_scene (&self, scene_count: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix | Track(_) => Scene(0),
|
||||
Scene(s) => Scene((s + 1) % scene_count),
|
||||
TrackClip { scene, .. } => Track(*scene),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_scene_next (&self, len: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Scene(0),
|
||||
Track(t) => TrackClip { track: *t, scene: 0 },
|
||||
Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix },
|
||||
TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_scene_prev (&self) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix | Scene(0) => Mix,
|
||||
Scene(s) => Scene(s - 1),
|
||||
Track(t) => Track(*t),
|
||||
TrackClip { track, scene: 0 } => Track(*track),
|
||||
TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "track")]
|
||||
impl Selection {
|
||||
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
use Selection::*;
|
||||
if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self {
|
||||
Some(*track)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_track (&self, track_count: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Track(0),
|
||||
Scene(_) => Mix,
|
||||
Track(t) => Track((t + 1) % track_count),
|
||||
TrackClip { track, .. } => Track(*track),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_track_next (&self, len: usize) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Track(0),
|
||||
Scene(s) => TrackClip { track: 0, scene: *s },
|
||||
Track(t) => if t + 1 < len { Track(t + 1) } else { Mix },
|
||||
TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_track_prev (&self) -> Self {
|
||||
use Selection::*;
|
||||
match self {
|
||||
Mix => Mix,
|
||||
Scene(s) => Scene(*s),
|
||||
Track(0) => Mix,
|
||||
Track(t) => Track(t - 1),
|
||||
TrackClip { track: 0, scene } => Scene(*scene),
|
||||
TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene },
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
588
src/device/arrange/track.rs
Normal file
588
src/device/arrange/track.rs
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
use crate::{*, arrange::*};
|
||||
|
||||
/// A track consists of a sequencer and zero or more devices chained after it.
|
||||
///
|
||||
/// ```
|
||||
/// let track: tek::Track = Default::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Track {
|
||||
/// Name of track
|
||||
pub name: Arc<str>,
|
||||
/// Identifying color of track
|
||||
pub color: ItemTheme,
|
||||
/// Preferred width of track column
|
||||
pub width: usize,
|
||||
/// MIDI sequencer state
|
||||
pub sequencer: Sequencer,
|
||||
/// Device chain
|
||||
pub devices: Vec<Device>,
|
||||
}
|
||||
|
||||
impl Track {
|
||||
/// Create a new track with only the default [Sequencer].
|
||||
pub fn new (
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
midi_from: &[Connect],
|
||||
midi_to: &[Connect],
|
||||
) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.as_ref().into(),
|
||||
color: color.unwrap_or_default(),
|
||||
sequencer: Sequencer::new(
|
||||
format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to
|
||||
)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
||||
}
|
||||
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a)
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{
|
||||
fg_bg(
|
||||
track.color.lightest.term,
|
||||
track.color.base.term,
|
||||
callback(index, track)
|
||||
).exact_w((x2 - x1) as u16)
|
||||
}))
|
||||
}
|
||||
pub fn stop (&mut self) -> Perhaps<TrackCommand> {
|
||||
self.sequencer.enqueue_next(None);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasWidth for Track {
|
||||
const MIN_WIDTH: usize = 9;
|
||||
fn width_inc (&mut self) {
|
||||
self.width += 1;
|
||||
}
|
||||
fn width_dec (&mut self) {
|
||||
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRefOpt<Track> + AsMutOpt<Track>> HasTrack for T {}
|
||||
|
||||
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||
fn track (&self) -> Option<&Track> {
|
||||
self.as_ref_opt()
|
||||
}
|
||||
fn track_mut (&mut self) -> Option<&mut Track> {
|
||||
self.as_mut_opt()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasTrack
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, ItemTheme>
|
||||
+ for<'a> Namespace<'a, Option<bool>>
|
||||
> TrackController for T {}
|
||||
|
||||
#[tek_proc::commands(TrackCommand)]
|
||||
pub trait TrackController: HasTrack
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, ItemTheme>
|
||||
+ for<'a> Namespace<'a, Option<bool>>
|
||||
{
|
||||
#[command(Stop = "track/stop")]
|
||||
fn track_stop (&mut self) -> Perhaps<TrackCommand> {
|
||||
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
||||
Ok(None)
|
||||
}
|
||||
#[command(SetMute = "track/mute")]
|
||||
fn track_set_mute (&mut self, mute: Option<bool>) -> Perhaps<TrackCommand> {
|
||||
todo!()
|
||||
}
|
||||
#[command(SetSolo = "track/solo")]
|
||||
fn track_set_solo (&mut self, solo: Option<bool>) -> Perhaps<TrackCommand> {
|
||||
todo!()
|
||||
}
|
||||
#[command(SetSize = "track/size")]
|
||||
fn track_set_size (&mut self, size: usize) -> Perhaps<TrackCommand> {
|
||||
todo!()
|
||||
}
|
||||
#[command(SetZoom = "track/zoom")]
|
||||
fn track_set_zoom (&mut self, zoom: usize) -> Perhaps<TrackCommand> {
|
||||
todo!()
|
||||
}
|
||||
#[command(SetName = "track/name")]
|
||||
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCommand> {
|
||||
self.track_mut()
|
||||
.map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name }))
|
||||
.transpose()
|
||||
.map(Option::flatten)
|
||||
}
|
||||
#[command(SetColor = "track/color")]
|
||||
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCommand> {
|
||||
self.track_mut()
|
||||
.map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color }))
|
||||
.transpose()
|
||||
.map(Option::flatten)
|
||||
}
|
||||
#[command(SetRec = "track/rec")]
|
||||
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCommand> {
|
||||
self.track_mut()
|
||||
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec }))
|
||||
.transpose()
|
||||
.map(Option::flatten)
|
||||
}
|
||||
#[command(SetMon = "track/mon")]
|
||||
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCommand> {
|
||||
self.track_mut()
|
||||
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon }))
|
||||
.transpose()
|
||||
.map(Option::flatten)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
||||
track.width as u16
|
||||
}
|
||||
|
||||
def_sizes_iter!(TracksSizes => Track);
|
||||
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
||||
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
||||
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
||||
|
||||
impl<T: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll> HasTracks for T {}
|
||||
|
||||
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll {
|
||||
/// Spacing between tracks.
|
||||
const TRACK_SPACING: usize = 0;
|
||||
|
||||
/// Read-only reference to collection of [Track]s.
|
||||
fn tracks (&self) -> &Vec<Track> {
|
||||
self.as_ref()
|
||||
}
|
||||
|
||||
/// Mutable reference to collection of [Track]s.
|
||||
fn tracks_mut (&mut self) -> &mut Vec<Track> {
|
||||
self.as_mut()
|
||||
}
|
||||
|
||||
/// Run audio callbacks for every track and every device
|
||||
fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control
|
||||
where Self: HasJack<'static>
|
||||
{
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
for device in track.devices.iter_mut() {
|
||||
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
||||
return Control::Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
/// Add multiple tracks
|
||||
fn tracks_add_many (
|
||||
&mut self,
|
||||
count: usize,
|
||||
width: Option<usize>,
|
||||
mins: Arc<[Connect]>,
|
||||
mouts: Arc<[Connect]>,
|
||||
) -> Usually<()>
|
||||
where Self: HasJack<'static> + HasScenes
|
||||
{
|
||||
let track_color_1 = ItemColor::random();
|
||||
let track_color_2 = ItemColor::random();
|
||||
for i in 0..count {
|
||||
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
||||
let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1;
|
||||
if let Some(width) = width {
|
||||
track.width = width;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a track
|
||||
fn tracks_add_one (
|
||||
&mut self,
|
||||
name: Option<Arc<str>>,
|
||||
color: Option<ItemTheme>,
|
||||
mins: Arc<[Connect]>,
|
||||
mouts: Arc<[Connect]>,
|
||||
) -> Usually<(usize, &mut Track)>
|
||||
where Self: HasJack<'static> + HasScenes
|
||||
{
|
||||
let name: Arc<str> = name.map_or_else(
|
||||
||format!("trk{:02}", self.tracks_last()).into(),
|
||||
|x|x.to_string().into()
|
||||
);
|
||||
*self.tracks_last_mut() += 1;
|
||||
let track = Track {
|
||||
width: (name.len() + 2).max(12),
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
sequencer: Sequencer::new(
|
||||
&format!("{name}"),
|
||||
self.jack(),
|
||||
Some(self.clock()),
|
||||
None,
|
||||
mins.as_ref(),
|
||||
mouts.as_ref()
|
||||
)?,
|
||||
name,
|
||||
..Default::default()
|
||||
};
|
||||
self.tracks_mut().push(track);
|
||||
let len = self.tracks().len();
|
||||
let index = len - 1;
|
||||
for scene in self.scenes_mut().iter_mut() {
|
||||
while scene.clips.len() < len {
|
||||
scene.clips.push(None);
|
||||
}
|
||||
}
|
||||
Ok((index, &mut self.tracks_mut()[index]))
|
||||
}
|
||||
|
||||
fn track_longest_name (&self) -> usize {
|
||||
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasTracks
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<[Connect]>>
|
||||
+ for<'a> Namespace<'a, Option<usize>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<str>>>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<[Connect]>>>
|
||||
+ for<'a> Namespace<'a, Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||
> TracksController for T {}
|
||||
|
||||
#[tek_proc::commands(TracksCommand)]
|
||||
pub trait TracksController: HasTracks
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<[Connect]>>
|
||||
+ for<'a> Namespace<'a, Option<usize>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<str>>>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<[Connect]>>>
|
||||
+ for<'a> Namespace<'a, Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
||||
{
|
||||
|
||||
#[command(Stop = "tracks/stop")]
|
||||
/// Stop all playing clips
|
||||
fn tracks_stop_all (&mut self) -> Perhaps<TracksCommand> {
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
track.sequencer.enqueue_next(None);
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(Launch = "tracks/launch")]
|
||||
/// Launch multiple clips
|
||||
fn tracks_launch (
|
||||
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
|
||||
) -> Perhaps<TracksCommand> {
|
||||
if let Some(clips) = clips {
|
||||
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
||||
track.sequencer.enqueue_next(clip.as_ref());
|
||||
}
|
||||
} else {
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
track.sequencer.enqueue_next(None);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
|
||||
> TracksView for T {}
|
||||
|
||||
pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack {
|
||||
|
||||
/// Iterate over tracks with their corresponding sizes.
|
||||
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
||||
let _editor_width = self.editor().map(|e|e.size.w());
|
||||
let _active_track = self.selection().track();
|
||||
let mut x = 0;
|
||||
let w = self.clips_size().w() as usize;
|
||||
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
||||
let width = track.width.max(8);
|
||||
if x + width < w {
|
||||
let data = (index, track, x, x + width);
|
||||
x += width + Self::TRACK_SPACING;
|
||||
Some(data)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw name of each track
|
||||
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let track_count = self.tracks().len();
|
||||
let scene_count = self.scenes().len();
|
||||
let selected = self.selection();
|
||||
let button = south(
|
||||
button_3("t", "rack ", format!("{}{track_count}", selected.track()
|
||||
.map(|track|format!("{track}/")).unwrap_or_default()), false),
|
||||
button_3("s", "cene ", format!("{}{scene_count}", selected.scene()
|
||||
.map(|scene|format!("{scene}/")).unwrap_or_default()), false));
|
||||
let button_2 = south(
|
||||
button_2("T", "+", false),
|
||||
button_2("S", "+", false));
|
||||
view_track_row_section(theme, button, button_2, bg(theme.darker.term,
|
||||
draw(|to: &mut Tui|{
|
||||
for (index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||
let b = if selected.track() == Some(index) {
|
||||
track.color.light.term
|
||||
} else {
|
||||
track.color.base.term
|
||||
};
|
||||
bg(b, south(east(
|
||||
format!("·t{index:02} "),
|
||||
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
||||
).align_nw().full_w(), ""))
|
||||
.exact_w(track_width(index, track))
|
||||
.push_x(x1 as u16)
|
||||
.draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||
}).exact_h(2)))
|
||||
}
|
||||
|
||||
/// Draw outputs per track
|
||||
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
||||
view_track_row_section(theme,
|
||||
south(button_2("o", "utput", false).align_w().full_w(),
|
||||
draw(|to: &mut Tui|{
|
||||
for port in self.midi_outs().iter() {
|
||||
let _ = port.port_name().align_w().full_w().draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||
})),
|
||||
button_2("O", "+", false),
|
||||
bg(theme.darker.term, draw(|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
let f = Rgb(255, 255, 255);
|
||||
let b = track.color.dark.term;
|
||||
iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{
|
||||
fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1))
|
||||
}))
|
||||
.full_h().align_nw().exact_w(track_width(index, track)).draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||
}).align_w()))
|
||||
}
|
||||
|
||||
/// Draw inputs per track
|
||||
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let mut height = 0u16;
|
||||
for track in self.tracks().iter() {
|
||||
height = height.max(track.sequencer.midi_ins.len() as u16);
|
||||
}
|
||||
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
||||
bg(theme.darker.term, draw(move|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
south(
|
||||
bg(track.color.base.term,
|
||||
east!(
|
||||
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
|
||||
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
||||
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
||||
).align_w().full_w()),
|
||||
iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
||||
format!("·i{index:02} {}", port.port_name()).align_w().full_w())))
|
||||
).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?;
|
||||
}
|
||||
Ok(Some(XYWH(0, 0, 0, 0)))
|
||||
}).align_w()))
|
||||
}
|
||||
|
||||
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let height = self.tracks_devices_height();
|
||||
let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false);
|
||||
let btn2 = button_2("D", "+", false);
|
||||
view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes()
|
||||
.enumerate()
|
||||
.map(move|(index, (_, track, _x1, _x2))|bg(
|
||||
track.color.dark.term,
|
||||
iter_south(move||(0..height).map(|_|fg_bg(
|
||||
ItemTheme::G[32].lightest.term,
|
||||
ItemTheme::G[32].dark.term,
|
||||
format!(" · {}", "--").align_nw()
|
||||
).exact_wh(track.width as u16, 2)))
|
||||
.align_nw()
|
||||
)
|
||||
.exact_wh(
|
||||
Some(track_width(index, track)),
|
||||
Some(height + 1),
|
||||
))))
|
||||
}
|
||||
|
||||
fn tracks_devices_height (&self) -> u16 {
|
||||
let mut h = 2;
|
||||
for track in self.tracks().iter() {
|
||||
h = h.max(track.devices.len() * 2);
|
||||
}
|
||||
h as u16
|
||||
}
|
||||
|
||||
fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
||||
let title_1 = button_3("i", "nput ", format!("{}", self.midi_ins().len()), false).align_w().exact_wh(20, 1);
|
||||
let title_2 = button_2("I", "+", false).exact_wh(4, 1);
|
||||
east(title_1, west(title_2, draw(move|to: &mut Tui|{
|
||||
for (_index, track, x1, _x2) in self.tracks_with_sizes() {
|
||||
let _ = south(
|
||||
bg(track.color.dark.term, east!(
|
||||
either(track.sequencer.monitoring, fg(Green, "mon "), "mon "),
|
||||
either(track.sequencer.recording, fg(Red, "rec "), "rec "),
|
||||
either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "),
|
||||
).exact_w(track.width as u16)).align_w().push_x(x1 as u16),
|
||||
draw(move |to: &mut Tui|{
|
||||
for (index, port) in self.midi_ins().as_slice().iter().enumerate() {
|
||||
let _ = east(
|
||||
east(
|
||||
" ● ",
|
||||
bold(true, fg(Rgb(255,255,255), port.port_name()))
|
||||
).align_w().exact_w(20),
|
||||
west(
|
||||
().exact_w(4),
|
||||
bg(track.color.darker.term, east!(
|
||||
either(track.sequencer.monitoring, fg(Green, " ● "), " · "),
|
||||
either(track.sequencer.recording, fg(Red, " ● "), " · "),
|
||||
either(track.sequencer.overdub, fg(Yellow, " ● "), " · "),
|
||||
).exact_w(track.width as u16).align_w())
|
||||
)
|
||||
).push_x(index as u16 * 10).exact_h(1).draw(to)?;
|
||||
}
|
||||
todo!()
|
||||
})
|
||||
).draw(to)?;
|
||||
}
|
||||
Ok(Some(to.area()))
|
||||
})))
|
||||
}
|
||||
|
||||
fn outputs_height (&self) -> u16 {
|
||||
let mut h = 1;
|
||||
for output in self.midi_outs().iter() {
|
||||
h += 1 + output.connections.len();
|
||||
}
|
||||
h as u16
|
||||
}
|
||||
|
||||
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
||||
let height = self.outputs_height();
|
||||
let list = south(
|
||||
button_3(
|
||||
"o", "utput", format!("{}", self.midi_outs().len()), false
|
||||
).align_w().full_w().exact_h(1),
|
||||
draw(|to: &mut Tui|{
|
||||
for (_index, port) in self.midi_outs().iter().enumerate() {
|
||||
east(
|
||||
east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name()))).align_w(),
|
||||
format!("{}/{} ",
|
||||
port.port().get_connections().len(),
|
||||
port.connections.len()).align_e().full_w().exact_h(1)).full_w().draw(to)?;
|
||||
for (index, conn) in port.connections.iter().enumerate() {
|
||||
format!(" c{index:02}{}", conn.info()).align_w().full_w().exact_h(1).draw(to)?;
|
||||
}
|
||||
}
|
||||
todo!();
|
||||
}).align_nw().full_wh().exact_h(height - 1)
|
||||
);
|
||||
view_track_row_section(theme, list, button_2("O", "+", false),
|
||||
bg(theme.darker.term, draw(|to: &mut Tui|{
|
||||
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
||||
let _ = draw(|to: &mut Tui|{
|
||||
east(
|
||||
either(true, fg(Green, "play "), "play "),
|
||||
either(false, fg(Yellow, "solo "), "solo "),
|
||||
).align_w().exact_h(1).draw(to)?;
|
||||
for (_index, port) in self.midi_outs().iter().enumerate() {
|
||||
east(
|
||||
either(true, fg(Green, " ● "), " · "),
|
||||
either(false, fg(Yellow, " ● "), " · "),
|
||||
).align_w().exact_h(1).draw(to)?;
|
||||
for (_index, _conn) in port.connections.iter().enumerate() {
|
||||
"".full_w().exact_h(1).draw(to)?;
|
||||
}
|
||||
}
|
||||
todo!()
|
||||
}).exact_w(track_width(index, track)).draw(to)?;
|
||||
}
|
||||
todo!()
|
||||
}).align_w().full_w())).exact_h(height)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub trait HasTrackScroll {
|
||||
fn track_scroll (&self) -> usize;
|
||||
fn tracks_last (&self) -> usize;
|
||||
fn tracks_last_mut (&mut self) -> &mut usize;
|
||||
}
|
||||
|
||||
impl HasTrackScroll for &mut Arrangement {
|
||||
fn track_scroll (&self) -> usize {
|
||||
self.track_scroll
|
||||
}
|
||||
fn tracks_last (&self) -> usize {
|
||||
self.track_last
|
||||
}
|
||||
fn tracks_last_mut (&mut self) -> &mut usize {
|
||||
&mut self.track_last
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTrackScroll for Arrangement {
|
||||
fn track_scroll (&self) -> usize {
|
||||
self.track_scroll
|
||||
}
|
||||
fn tracks_last (&self) -> usize {
|
||||
self.track_last
|
||||
}
|
||||
fn tracks_last_mut (&mut self) -> &mut usize {
|
||||
&mut self.track_last
|
||||
}
|
||||
}
|
||||
|
||||
impl HasTrackScroll for App {
|
||||
fn track_scroll (&self) -> usize {
|
||||
self.project.track_scroll()
|
||||
}
|
||||
fn tracks_last (&self) -> usize {
|
||||
self.project.track_last
|
||||
}
|
||||
fn tracks_last_mut (&mut self) -> &mut usize {
|
||||
&mut self.project.track_last
|
||||
}
|
||||
}
|
||||
|
||||
fn view_track_row_section (
|
||||
_theme: ItemTheme,
|
||||
button: impl Draw<Tui>,
|
||||
button_add: impl Draw<Tui>,
|
||||
content: impl Draw<Tui>,
|
||||
) -> impl Draw<Tui> {
|
||||
west(
|
||||
button_add.align_nw().exact_w(4).full_h(),
|
||||
east(
|
||||
button.align_nw().full_h().exact_w(20),
|
||||
content.align_c().full_wh()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -12,7 +12,11 @@ impl App {
|
|||
}
|
||||
|
||||
#[tek_proc::commands(BrowseCommand = "browse")]
|
||||
impl Browse {
|
||||
pub trait BrowseController:
|
||||
for<'a> Namespace<'a, usize> +
|
||||
for<'a> Namespace<'a, PathBuf> +
|
||||
for<'a> Namespace<'a, Arc<str>>
|
||||
{
|
||||
/// Toggle visibility of browser
|
||||
#[command(Show = "show")]
|
||||
fn show (&mut self) -> Perhaps<BrowseCommand> {
|
||||
|
|
@ -171,3 +175,14 @@ pub fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
|
|||
files.sort();
|
||||
Ok((subdirs, files))
|
||||
}
|
||||
|
||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,63 +20,6 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
#[tek_proc::commands(ClockCommand = "clock")]
|
||||
impl Clock {
|
||||
#[command(SeekUsec = "usec")]
|
||||
fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCommand> {
|
||||
self.playhead.update_from_usec(usec);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SeekSample = "sample")]
|
||||
fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCommand> {
|
||||
self.playhead.update_from_sample(sample);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SeekPulse = "pulse")]
|
||||
fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCommand> {
|
||||
self.playhead.update_from_pulse(pulse);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SetBpm = "bpm")]
|
||||
fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetBpm { bpm: self.timebase().bpm.set(bpm) }))
|
||||
}
|
||||
|
||||
#[command(SetQuant = "quant")]
|
||||
fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetQuant { quant: self.quant.set(quant) }))
|
||||
}
|
||||
|
||||
#[command(SetSync = "sync")]
|
||||
fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetSync { sync: self.sync.set(sync) }))
|
||||
}
|
||||
|
||||
#[command(Play = "play")]
|
||||
fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
||||
self.play_from(position)?;
|
||||
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
||||
}
|
||||
|
||||
#[command(Pause = "pause")]
|
||||
fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
||||
self.pause_at(position)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(TogglePlayback = "toggle")]
|
||||
fn toggle (&mut self, position: u32) -> Perhaps<ClockCommand> {
|
||||
Ok(if self.is_rolling() {
|
||||
self.pause_at(Some(position))?; None
|
||||
} else {
|
||||
self.play_from(Some(position))?; None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The source of time.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -84,23 +27,23 @@ impl Clock {
|
|||
/// ```
|
||||
#[derive(Clone, Default)] pub struct Clock {
|
||||
/// JACK transport handle.
|
||||
pub transport: Arc<Option<Transport>>,
|
||||
pub transport: Arc<Option<Transport>>,
|
||||
/// Global temporal resolution (shared by [Moment] fields)
|
||||
pub timebase: Arc<Timebase>,
|
||||
pub timebase: Arc<Timebase>,
|
||||
/// Current global sample and usec (monotonic from JACK clock)
|
||||
pub global: Arc<Moment>,
|
||||
pub global: Arc<Moment>,
|
||||
/// Global sample and usec at which playback started
|
||||
pub started: Arc<RwLock<Option<Moment>>>,
|
||||
pub started: Arc<RwLock<Option<Moment>>>,
|
||||
/// Playback offset (when playing not from start)
|
||||
pub offset: Arc<Moment>,
|
||||
pub offset: Arc<Moment>,
|
||||
/// Current playhead position
|
||||
pub playhead: Arc<Moment>,
|
||||
pub playhead: Arc<Moment>,
|
||||
/// Note quantization factor
|
||||
pub quant: Arc<Quantize>,
|
||||
pub quant: Arc<Quantize>,
|
||||
/// Launch quantization factor
|
||||
pub sync: Arc<LaunchSync>,
|
||||
pub sync: Arc<LaunchSync>,
|
||||
/// Size of buffer in samples
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
// Cache of formatted strings
|
||||
pub view_cache: Arc<RwLock<ClockView>>,
|
||||
/// For syncing the clock to an external source
|
||||
|
|
@ -111,10 +54,10 @@ impl Clock {
|
|||
#[cfg(feature = "port")] pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||||
}
|
||||
|
||||
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> {
|
||||
fn clock (&self) -> &Clock {
|
||||
fn clock (&self) -> &Clock {
|
||||
self.as_ref()
|
||||
}
|
||||
fn clock_mut (&mut self) -> &mut Clock {
|
||||
|
|
@ -122,12 +65,87 @@ pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: HasClock> Act<T> for ClockCommand {
|
||||
fn act (&self, state: &mut T) -> Perhaps<Self> {
|
||||
self.act(state.clock_mut()) // awesome
|
||||
impl<T: HasClock
|
||||
+ for<'a> Namespace<'a, u32>
|
||||
+ for<'a> Namespace<'a, f64>
|
||||
+ for<'a> Namespace<'a, Option<u32>>
|
||||
> ClockController for T {}
|
||||
|
||||
#[tek_proc::commands(ClockCommand = "clock")]
|
||||
pub trait ClockController: HasClock
|
||||
+ for<'a> Namespace<'a, u32>
|
||||
+ for<'a> Namespace<'a, f64>
|
||||
+ for<'a> Namespace<'a, Option<u32>>
|
||||
{
|
||||
#[command(SeekUsec = "usec")]
|
||||
fn seek_usec (&mut self, usec: f64) -> Perhaps<ClockCommand> {
|
||||
self.clock().playhead.update_from_usec(usec);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SeekSample = "sample")]
|
||||
fn seek_sample (&mut self, sample: f64) -> Perhaps<ClockCommand> {
|
||||
self.clock().playhead.update_from_sample(sample);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SeekPulse = "pulse")]
|
||||
fn seek_pulse (&mut self, pulse: f64) -> Perhaps<ClockCommand> {
|
||||
self.clock().playhead.update_from_pulse(pulse);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(SetBpm = "bpm")]
|
||||
fn set_bpm (&mut self, bpm: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetBpm {
|
||||
bpm: self.clock().timebase().bpm.set(bpm)
|
||||
}))
|
||||
}
|
||||
|
||||
#[command(SetQuant = "quant")]
|
||||
fn set_quant (&mut self, quant: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetQuant {
|
||||
quant: self.clock().quant.set(quant)
|
||||
}))
|
||||
}
|
||||
|
||||
#[command(SetSync = "sync")]
|
||||
fn set_sync (&mut self, sync: f64) -> Perhaps<ClockCommand> {
|
||||
Ok(Some(ClockCommand::SetSync {
|
||||
sync: self.clock().sync.set(sync)
|
||||
}))
|
||||
}
|
||||
|
||||
#[command(Play = "play")]
|
||||
fn play (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
||||
self.clock().play_from(position)?;
|
||||
Ok(None) /* TODO Some(Pause(previousPosition)) */
|
||||
}
|
||||
|
||||
#[command(Pause = "pause")]
|
||||
fn pause (&mut self, position: Option<u32>) -> Perhaps<ClockCommand> {
|
||||
self.clock().pause_at(position)?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(TogglePlayback = "toggle")]
|
||||
fn toggle (&mut self, position: u32) -> Perhaps<ClockCommand> {
|
||||
Ok(if self.clock().is_rolling() {
|
||||
self.clock().pause_at(Some(position))?;
|
||||
None
|
||||
} else {
|
||||
self.clock().play_from(Some(position))?;
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//impl<T: HasClock> Act<T> for ClockCommand {
|
||||
//fn act (&self, state: &mut T) -> Perhaps<Self> {
|
||||
//self.act(state.clock_mut()) // awesome
|
||||
//}
|
||||
//}
|
||||
|
||||
/// Quantization setting for launching clips
|
||||
///
|
||||
/// ```
|
||||
|
|
|
|||
|
|
@ -66,45 +66,6 @@ impl App {
|
|||
})).transpose()
|
||||
}
|
||||
|
||||
/// Set currently active modal dialog.
|
||||
///
|
||||
/// ```
|
||||
/// let previous: tek::Dialog = tek::App::default().set_dialog(&tek::Dialog::welcome());
|
||||
/// ```
|
||||
pub fn set_dialog (&mut self, dialog: &Dialog) -> Dialog {
|
||||
let mut dialog = dialog.clone();
|
||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||
dialog
|
||||
}
|
||||
|
||||
pub fn inc (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => todo!(),
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog(self.dialog.menu_next()).act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dec (&mut self, axis: &ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => None,
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog(self.dialog.menu_prev()).act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
||||
Ok(match &self.dialog {
|
||||
Dialog::Menu(index, items) => {
|
||||
let callback = items.0[*index].1.clone();
|
||||
callback(self)?;
|
||||
None
|
||||
},
|
||||
_ => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Various possible dialog modes.
|
||||
|
|
|
|||
|
|
@ -56,66 +56,121 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
||||
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
||||
fn is_editing (&self) -> bool { self.editor().is_some() }
|
||||
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
||||
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
||||
}
|
||||
|
||||
impl<T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
||||
|
||||
impl<T: HasEditor
|
||||
+ for<'a> Namespace<'a, u32>
|
||||
+ for<'a> Namespace<'a, f64>
|
||||
+ for<'a> Namespace<'a, bool>
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Option<u32>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<RwLock<MidiClip>>>>
|
||||
> MidiEditController for T {}
|
||||
|
||||
#[tek_proc::commands(MidiEditCommand = "edit")]
|
||||
impl MidiEditor {
|
||||
pub trait MidiEditController: HasEditor
|
||||
+ for<'a> Namespace<'a, u32>
|
||||
+ for<'a> Namespace<'a, f64>
|
||||
+ for<'a> Namespace<'a, bool>
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Option<u32>>
|
||||
+ for<'a> Namespace<'a, Option<Arc<RwLock<MidiClip>>>>
|
||||
{
|
||||
#[command(Show = "show")]
|
||||
fn show (&mut self, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<MidiEditCommand> {
|
||||
self.set_clip(clip.as_ref());
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_clip(clip.as_ref());
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(DeleteNote = "delete")]
|
||||
fn note_delete (&mut self) -> Perhaps<MidiEditCommand> {
|
||||
self.redraw();
|
||||
todo!()
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.redraw();
|
||||
todo!()
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(AppendNote = "append")]
|
||||
fn note_append (&mut self, advance: bool) -> Perhaps<MidiEditCommand> {
|
||||
self.put_note(advance);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.put_note(advance);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetNotePos = "note-pos")]
|
||||
fn note_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_note_pos((pos).min(127));
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_note_pos((pos).min(127));
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetNoteLen = "note-len")]
|
||||
fn note_set_len (&mut self, len: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_note_len(len);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_note_len(len);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetNoteScroll = "note-scroll")]
|
||||
fn note_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_note_lo((scroll).min(127));
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_note_lo((scroll).min(127));
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetTimePos = "time-pos")]
|
||||
fn time_set_pos (&mut self, pos: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_time_pos(pos);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_time_pos(pos);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetTimeScroll = "time-scroll")]
|
||||
fn time_set_scroll (&mut self, scroll: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_time_start(scroll);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_time_start(scroll);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetTimeZoom = "time-zoom")]
|
||||
fn time_set_zoom (&mut self, zoom: usize) -> Perhaps<MidiEditCommand> {
|
||||
self.set_time_zoom(zoom);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_time_zoom(zoom);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
|
||||
#[command(SetTimeLock = "time-lock")]
|
||||
fn time_set_lock (&mut self, lock: bool) -> Perhaps<MidiEditCommand> {
|
||||
self.set_time_lock(lock);
|
||||
self.redraw();
|
||||
Ok(None)
|
||||
Ok(self.editor_mut().map(|editor|{
|
||||
editor.set_time_lock(lock);
|
||||
editor.redraw();
|
||||
None
|
||||
}).flatten())
|
||||
}
|
||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||
}
|
||||
|
|
@ -263,20 +318,11 @@ impl MidiEditor {
|
|||
/// let _ = host.editor_w();
|
||||
/// let _ = host.editor_h();
|
||||
/// ```
|
||||
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
||||
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
||||
fn is_editing (&self) -> bool { self.editor().is_some() }
|
||||
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
||||
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
||||
}
|
||||
|
||||
impl <T: NotePoint+TimePoint> MidiPoint for T {}
|
||||
|
||||
impl <T: TimeRange+NoteRange> MidiRange for T {}
|
||||
|
||||
impl <T: AsRefOpt<MidiEditor>+AsMutOpt<MidiEditor>> HasEditor for T {}
|
||||
|
||||
pub trait MidiViewer: MidiRange + MidiPoint + Debug + Send + Sync {
|
||||
fn buffer_size (&self, clip: &MidiClip) -> (usize, usize);
|
||||
fn redraw (&self);
|
||||
|
|
|
|||
|
|
@ -58,48 +58,71 @@ pub enum ClipLengthFocus {
|
|||
Tick,
|
||||
}
|
||||
|
||||
impl<T: AsRef<Pool> + AsMut<Pool>> HasPool for T {}
|
||||
|
||||
pub trait HasPool: AsRef<Pool> + AsMut<Pool> {
|
||||
fn pool (&self) -> &Pool {
|
||||
self.as_ref()
|
||||
}
|
||||
fn pool_mut (&mut self) -> &mut Pool {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasPool
|
||||
+ for<'a> Namespace<'a, bool>
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, PathBuf>
|
||||
+ for<'a> Namespace<'a, MidiClip>
|
||||
+ for<'a> Namespace<'a, ItemColor>
|
||||
+ for<'a> Namespace<'a, PoolCommand>
|
||||
+ for<'a> Namespace<'a, PoolCommand>
|
||||
+ for<'a> Namespace<'a, BrowseCommand>
|
||||
> PoolController for T {}
|
||||
|
||||
#[tek_proc::commands(PoolCommand = "pool")]
|
||||
impl Pool {
|
||||
pub trait PoolController: HasPool
|
||||
+ for<'a> Namespace<'a, bool>
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
+ for<'a> Namespace<'a, Arc<str>>
|
||||
+ for<'a> Namespace<'a, PathBuf>
|
||||
+ for<'a> Namespace<'a, MidiClip>
|
||||
+ for<'a> Namespace<'a, ItemColor>
|
||||
+ for<'a> Namespace<'a, PoolCommand>
|
||||
+ for<'a> Namespace<'a, PoolCommand>
|
||||
+ for<'a> Namespace<'a, BrowseCommand>
|
||||
{
|
||||
|
||||
#[command(Show = "show")]
|
||||
/// Toggle visibility of pool
|
||||
fn show (&mut self, visible: bool) -> Perhaps<PoolCommand> {
|
||||
self.visible = visible;
|
||||
self.pool_mut().visible = visible;
|
||||
Ok(Some(PoolCommand::Show { visible: !visible }))
|
||||
}
|
||||
|
||||
/// Set selected clip
|
||||
#[command(Select = "select")]
|
||||
fn select (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
||||
self.set_clip_index(index);
|
||||
self.pool_mut().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 from file
|
||||
//#[command(Browse = "browse")]
|
||||
//fn browse (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
||||
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
||||
//command.act(browse)?.map(|command|PoolCommand::Browse{command})
|
||||
//} else {
|
||||
//None
|
||||
//})
|
||||
//}
|
||||
|
||||
/// Import clip to pool from file
|
||||
#[command(Import = "import")]
|
||||
fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCommand> {
|
||||
fn clip_import (&mut self, index: usize, path: PathBuf) -> Perhaps<PoolCommand>
|
||||
where Self: Sized
|
||||
{
|
||||
let bytes = std::fs::read(&path)?;
|
||||
let smf = Smf::parse(bytes.as_slice())?;
|
||||
let mut t = 0u32;
|
||||
|
|
@ -119,27 +142,27 @@ impl Pool {
|
|||
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
|
||||
})
|
||||
}
|
||||
///// Export to file
|
||||
//#[command(Export = "export")]
|
||||
//fn export (&mut self, command: BrowseCommand) -> Perhaps<PoolCommand> {
|
||||
//Ok(if let Some(browse) = self.pool_mut().browse.as_mut() {
|
||||
//command.act(browse)?.map(|command|PoolCommand::Export{command})
|
||||
//} else {
|
||||
//None
|
||||
//})
|
||||
//}
|
||||
|
||||
/// Delete a clip from the pool
|
||||
#[command(Delete = "delete")]
|
||||
fn delete (&mut self, index: usize) -> Perhaps<PoolCommand> {
|
||||
let clip = self.clips_mut().remove(index).read().unwrap().clone();
|
||||
let clip = self.pool_mut().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);
|
||||
self.pool_mut().clips_mut().swap(index, other);
|
||||
Ok(Some(PoolCommand::Swap { index, other }))
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +171,7 @@ impl Pool {
|
|||
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();
|
||||
let mut clips = self.pool_mut().clips_mut();
|
||||
if index >= clips.len() {
|
||||
index = clips.len();
|
||||
clips.push(clip)
|
||||
|
|
@ -161,7 +184,7 @@ impl Pool {
|
|||
/// 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 clip = &mut self.pool_mut().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 }))
|
||||
|
|
@ -170,7 +193,7 @@ impl Pool {
|
|||
/// 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 clip = &mut self.pool_mut().clips_mut()[index];
|
||||
let old_len = clip.read().unwrap().length;
|
||||
clip.write().unwrap().length = length;
|
||||
Ok(Some(PoolCommand::SetLength { index, length: old_len }))
|
||||
|
|
@ -180,66 +203,70 @@ impl Pool {
|
|||
#[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);
|
||||
std::mem::swap(&mut color, &mut self.pool().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
|
||||
));
|
||||
#[command(CropBegin = "crop/begin")]
|
||||
fn crop_begin (&mut self) -> Perhaps<PoolCommand> {
|
||||
let index = self.pool().clip_index();
|
||||
let length = self.pool().clips()[index].read().unwrap().length;
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Length(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;
|
||||
#[command(CropCancel = "crop/cancel")]
|
||||
fn crop_cancel (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(..)) = self.pool_mut().mode_mut().clone() {
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(Set = "set")]
|
||||
fn crop_set (&mut self, length: usize) -> Perhaps<CropCommand> {
|
||||
#[command(CropSet = "crop/set")]
|
||||
fn crop_set (&mut self, length: usize) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut _focus))
|
||||
= self.mode_mut().clone()
|
||||
= self.pool_mut().mode_mut().clone()
|
||||
{
|
||||
let old_length;
|
||||
{
|
||||
let clip = self.clips()[clip].clone();//.write().unwrap();
|
||||
let clip = self.pool().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 }))
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
return Ok(old_length.map(|length|PoolCommand::CropSet { 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(CropNext = "crop/next")]
|
||||
fn crop_next (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut _length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
focus.next()
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(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(CropPrev = "crop/prev")]
|
||||
fn crop_prev (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut _length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
focus.prev()
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(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() {
|
||||
#[command(CropInc = "crop/inc")]
|
||||
fn crop_inc (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
match focus {
|
||||
ClipLengthFocus::Bar => { *length += 4 * PPQ },
|
||||
ClipLengthFocus::Beat => { *length += PPQ },
|
||||
|
|
@ -249,9 +276,11 @@ impl Pool {
|
|||
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() {
|
||||
#[command(CropDec = "crop/dec")]
|
||||
fn crop_dec (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Length(
|
||||
_clip, ref mut length, ref mut focus
|
||||
)) = self.pool_mut().mode_mut().clone() {
|
||||
match focus {
|
||||
ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) },
|
||||
ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) },
|
||||
|
|
@ -261,47 +290,40 @@ impl Pool {
|
|||
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
|
||||
));
|
||||
#[command(RenameBegin = "rename/begin")]
|
||||
fn rename_begin (&mut self) -> Perhaps<PoolCommand> {
|
||||
let index = self.pool().clip_index();
|
||||
let name = self.pool().clips()[index].read().unwrap().name.clone();
|
||||
*self.pool_mut().mode_mut() = Some(PoolMode::Rename(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();
|
||||
#[command(RenameCancel = "rename/cancel")]
|
||||
fn rename_cancel (&mut self) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
|
||||
self.pool().clips()[clip].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() {
|
||||
#[command(RenameConfirm = "rename/confirm")]
|
||||
fn rename_confirm (&mut self) -> Perhaps<PoolCommand> {
|
||||
Ok(if let Some(PoolMode::Rename(_clip, ref mut old_name)) = self.pool_mut().mode_mut().clone() {
|
||||
let old_name = old_name.clone();
|
||||
*self.mode_mut() = None;
|
||||
Some(RenameCommand::Set { value: old_name })
|
||||
*self.pool_mut().mode_mut() = None;
|
||||
Some(PoolCommand::RenameSet { 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();
|
||||
#[command(RenameSet = "rename/set")]
|
||||
fn rename_set (&mut self, value: Arc<str>) -> Perhaps<PoolCommand> {
|
||||
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = self.pool_mut().mode_mut().clone() {
|
||||
self.pool().clips()[clip].write().unwrap().name = value.clone();
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,28 @@ pub(crate) use symphonia::{
|
|||
},
|
||||
};
|
||||
|
||||
pub trait HasSampler: AsRef<Sampler> + AsMut<Sampler> {
|
||||
fn sampler (&self) -> &Sampler {
|
||||
self.as_ref()
|
||||
}
|
||||
fn sampler_mut (&mut self) -> &mut Sampler {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<Sampler> + AsMut<Sampler>> HasSampler for T {}
|
||||
|
||||
#[tek_proc::commands(SamplerCommand = "sampler")]
|
||||
impl Sampler {
|
||||
pub trait SamplerController: HasSampler
|
||||
+ for<'a> Namespace<'a, usize>
|
||||
{
|
||||
|
||||
#[command(RecordToggle = "rec-toggle")]
|
||||
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
||||
let recording = self.recording.as_ref().map(|x|x.0);
|
||||
fn record_toggle (&mut self, slot: usize) -> Perhaps<SamplerCommand>
|
||||
where Self: Sized
|
||||
{
|
||||
let sampler = self.sampler_mut();
|
||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
||||
let _ = SamplerCommand::RecordFinish.act(self)?;
|
||||
// autoslice: continue recording at next slot
|
||||
if recording != Some(slot) {
|
||||
|
|
@ -25,10 +41,11 @@ impl Sampler {
|
|||
|
||||
#[command(RecordBegin = "rec-begin")]
|
||||
fn record_begin (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
||||
self.recording = Some((
|
||||
let sampler = self.sampler_mut();
|
||||
sampler.recording = Some((
|
||||
slot,
|
||||
Some(Arc::new(RwLock::new(Sample::new(
|
||||
"Sample", 0, 0, vec![vec![];self.audio_ins.len()]
|
||||
"Sample", 0, 0, vec![vec![]; sampler.audio_ins.len()]
|
||||
))))
|
||||
));
|
||||
Ok(None)
|
||||
|
|
@ -36,8 +53,9 @@ impl Sampler {
|
|||
|
||||
#[command(RecordFinish = "rec-finish")]
|
||||
fn record_finish (&mut self) -> Perhaps<SamplerCommand> {
|
||||
let _prev_sample = self.recording.as_mut().map(|(index, sample)|{
|
||||
std::mem::swap(sample, &mut self.samples.0[*index]);
|
||||
let sampler = self.sampler_mut();
|
||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||
std::mem::swap(sample, &mut sampler.samples.0[*index]);
|
||||
sample
|
||||
}); // TODO: undo
|
||||
Ok(None)
|
||||
|
|
@ -45,14 +63,15 @@ impl Sampler {
|
|||
|
||||
#[command(RecordCancel = "rec-cancel")]
|
||||
fn record_cancel (&mut self) -> Perhaps<SamplerCommand> {
|
||||
self.recording = None;
|
||||
self.sampler_mut().recording = None;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[command(PlaySample = "sample-play")]
|
||||
fn sample_play (&mut self, slot: usize) -> Perhaps<SamplerCommand> {
|
||||
if let Some(ref sample) = self.samples.0[slot] {
|
||||
self.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||
let sampler = self.sampler_mut();
|
||||
if let Some(ref sample) = sampler.samples.0[slot] {
|
||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -730,3 +749,95 @@ impl SampleAdd {
|
|||
fn read_sample_data (_: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
when(sample.is_some(), draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
east!(
|
||||
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||
field_h(theme, "Start", format!("{:<8}", sample.start)),
|
||||
field_h(theme, "End", format!("{:<8}", sample.end)),
|
||||
field_h(theme, "Trans", "0"),
|
||||
field_h(theme, "Gain", format!("{}", sample.gain)),
|
||||
).draw(to)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
let a = draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
south!(
|
||||
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
|
||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
|
||||
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
|
||||
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
|
||||
field_h(theme, "Trans ", "0") .align_w().full_w(),
|
||||
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
|
||||
).exact_w(20).draw(to)
|
||||
});
|
||||
|
||||
let b = draw(|to: &mut Tui|fg(Red, south!(
|
||||
bold(true, "× No sample."),
|
||||
"[r] record",
|
||||
"[Shift-F9] import",
|
||||
)).draw(to));
|
||||
|
||||
either(sample.is_some(), a, b)
|
||||
}
|
||||
|
||||
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
||||
bold(true, fg(g(224), sample
|
||||
.map(|sample|{
|
||||
let sample = sample.read().unwrap();
|
||||
format!("Sample {}-{}", sample.start, sample.end)
|
||||
})
|
||||
.unwrap_or_else(||"No sample".to_string())))
|
||||
}
|
||||
|
||||
#[cfg(feature = "track")]
|
||||
impl Track {
|
||||
/// Create a new track connecting the [Sequencer] to a [Sampler].
|
||||
pub fn new_with_sampler (
|
||||
name: &impl AsRef<str>,
|
||||
color: Option<ItemTheme>,
|
||||
jack: &Jack<'static>,
|
||||
clock: Option<&Clock>,
|
||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||
midi_from: &[Connect],
|
||||
midi_to: &[Connect],
|
||||
audio_from: &[&[Connect];2],
|
||||
audio_to: &[&[Connect];2],
|
||||
) -> Usually<Self> {
|
||||
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
||||
let client_name = jack.with_client(|c|c.name().to_string());
|
||||
let port_name = track.sequencer.midi_outs[0].port_name();
|
||||
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
|
||||
track.devices.push(Device::Sampler(Sampler::new(
|
||||
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
|
||||
)?));
|
||||
Ok(track)
|
||||
}
|
||||
|
||||
pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> {
|
||||
for device in self.devices.iter() {
|
||||
match device {
|
||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> {
|
||||
for device in self.devices.iter_mut() {
|
||||
match device {
|
||||
Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; },
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ pub trait MidiRecord: MidiMonitor + HasClock + HasPlayClip {
|
|||
}
|
||||
|
||||
pub type MidiData = Vec<Vec<MidiMessage>>;
|
||||
|
||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||
|
||||
pub trait HasClips {
|
||||
|
|
@ -244,6 +245,34 @@ pub trait HasMidiClip {
|
|||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||
}
|
||||
|
||||
impl<T: HasMidiClip
|
||||
+ for<'a> Namespace<'a, Option<bool>>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
> MidiClipController for T {}
|
||||
|
||||
#[tek_proc::commands(MidiClipCommand)]
|
||||
pub trait MidiClipController: HasMidiClip
|
||||
+ for<'a> Namespace<'a, Option<bool>>
|
||||
+ for<'a> Namespace<'a, Option<ItemTheme>>
|
||||
{
|
||||
|
||||
#[command(SetColor = "clip/color")]
|
||||
fn clip_set_color (&mut self, color: Option<ItemTheme>) -> Perhaps<MidiClipCommand> {
|
||||
//(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!()
|
||||
}
|
||||
|
||||
#[command(SetLoop = "clip/loop")]
|
||||
fn clip_toggle_loop (&mut self, looping: Option<bool>) -> Perhaps<MidiClipCommand> {
|
||||
//(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!()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
||||
fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() }
|
||||
fn sequencer (&self) -> &Sequencer { self.as_ref() }
|
||||
|
|
|
|||
20
src/tek.edn
20
src/tek.edn
|
|
@ -88,16 +88,16 @@
|
|||
|
||||
(keys :back (@escape back))
|
||||
(keys :confirm (@enter confirm))
|
||||
(keys :axis/x (@left x/dec) (@right x/inc))
|
||||
(keys :axis/x2 (@shift/left x2/dec) (@shift/right x2/inc))
|
||||
(keys :axis/y (@up y/dec) (@down y/inc))
|
||||
(keys :axis/y2 (@shift/up y2/dec) (@shift/down y2/inc))
|
||||
(keys :axis/z (@minus z/dec) (@equal z/inc))
|
||||
(keys :axis/z2 (@underscore z2/dec) (@plus z2/inc))
|
||||
(keys :axis/i (@comma i/dec) (@period z/inc))
|
||||
(keys :axis/i2 (@lt i2/dec) (@gt z2/inc))
|
||||
(keys :axis/w (@openbracket w/dec) (@closebracket w/inc))
|
||||
(keys :axis/w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
||||
(keys :axis/x (@left app/dec :x) (@right app/inc :x))
|
||||
(keys :axis/x2 (@shift/left app/dec :x2) (@shift/right app/inc :x2))
|
||||
(keys :axis/y (@up app/dec :y) (@down app/inc :y))
|
||||
(keys :axis/y2 (@shift/up app/dec :y2) (@shift/down app/inc :y2))
|
||||
(keys :axis/z (@minus app/dec :z) (@equal app/inc :z))
|
||||
(keys :axis/z2 (@underscore app/dec :z2) (@plus app/inc :z2))
|
||||
(keys :axis/i (@comma app/dec :i) (@period app/inc :z))
|
||||
(keys :axis/i2 (@lt app/dec :i2) (@gt app/inc :z2))
|
||||
(keys :axis/w (@openbracket app/dec :w) (@closebracket app/inc :w))
|
||||
(keys :axis/w2 (@openbrace app/dec :w2) (@closebrace app/inc :w2))
|
||||
(keys :focus)
|
||||
(keys :editor (see :axis/i :axis/i2 :axis/y
|
||||
:page :editor/view :editor/add :editor/del))
|
||||
|
|
|
|||
623
src/tek.rs
623
src/tek.rs
|
|
@ -239,8 +239,8 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
|||
connect_audio_outs(&jack, &"R".to_string(), &right_to, None)?.into_iter()
|
||||
));
|
||||
//&jack, Clock::new(&jack, *bpm)?, &lf, <, &rf, &rt, &mf, &mt, &mfr, &mtr)?;
|
||||
proj.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
||||
proj.scenes_add(scenes.unwrap_or(0))?;
|
||||
proj.tracks_add_many(tracks.unwrap_or(0), None, [].into(), [].into())?;
|
||||
proj.scenes_add_many(scenes.unwrap_or(0))?;
|
||||
//if matches!(self, Action::Status) {
|
||||
//// Show status and exit
|
||||
//tek_print_status(&proj);
|
||||
|
|
@ -298,49 +298,6 @@ mod config {
|
|||
/// Collection of custom view definitions.
|
||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||
|
||||
/// Collection of input bindings.
|
||||
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
|
||||
|
||||
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
|
||||
///
|
||||
/// ```
|
||||
/// let lang = "(@x (nop)) (@y (nop) (nop))";
|
||||
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
|
||||
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
|
||||
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Bind<E, C>(
|
||||
/// Map of each event (e.g. key combination) to
|
||||
/// all command expressions bound to it by
|
||||
/// all loaded input layers.
|
||||
pub BTreeMap<E, Vec<Binding<C>>>
|
||||
);
|
||||
|
||||
/// A sequence of zero or more commands (e.g. [AppCommand]),
|
||||
/// optionally filtered by [Condition] to form layers.
|
||||
///
|
||||
/// ```
|
||||
/// //FIXME: Why does it overflow?
|
||||
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
|
||||
/// ```
|
||||
#[derive(Debug, Clone)] pub struct Binding<C> {
|
||||
pub commands: Arc<[C]>,
|
||||
pub condition: Option<Condition>,
|
||||
pub description: Option<Arc<str>>,
|
||||
pub source: Option<Arc<PathBuf>>,
|
||||
}
|
||||
|
||||
/// Condition that must evaluate to true in order to enable an input layer.
|
||||
///
|
||||
/// ```
|
||||
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Condition(
|
||||
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
|
||||
);
|
||||
|
||||
/// Collection of UI modes.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -651,14 +608,10 @@ mod app {
|
|||
primitive!(usize: try_to_usize);
|
||||
primitive!(isize: try_to_isize);
|
||||
impl_has!(Clock: |self: App|self.project.clock);
|
||||
impl_has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
impl_has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
impl_has!(Dialog: |self: App|self.dialog);
|
||||
impl_has!(Jack<'static>: |self: App|self.jack);
|
||||
impl_has!(Pool: |self: App|self.pool);
|
||||
impl_has!(Selection: |self: App|self.project.selection);
|
||||
impl_as_ref!(Vec<Scene>: |self: App|self.project.as_ref());
|
||||
impl_as_mut!(Vec<Scene>: |self: App|self.project.as_mut());
|
||||
impl_as_ref_opt!(MidiEditor: |self: App|self.project.as_ref_opt());
|
||||
impl_as_mut_opt!(MidiEditor: |self: App|self.project.as_mut_opt());
|
||||
impl_has_clips!( |self: App|self.pool.clips);
|
||||
|
|
@ -700,6 +653,7 @@ mod app {
|
|||
#[namespace(Option<usize> App::get_opt_usize)]
|
||||
#[namespace(Option<Arc<RwLock<MidiClip>>> App::get_clip)]
|
||||
#[namespace(Dialog App::get_dialog)]
|
||||
#[namespace(ControlAxis App::get_axis)]
|
||||
pub struct App {
|
||||
/// Exit flag
|
||||
pub exit: Exit,
|
||||
|
|
@ -815,7 +769,8 @@ mod app {
|
|||
if let Some(expr) = src.expr()? {
|
||||
match (expr.head()?, expr.tail()?) {
|
||||
(Some("g"), Some(tail)) => {
|
||||
let n = try_to_u8(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
|
||||
let n = try_to_u8(tail.head().map_err(Into::into))?
|
||||
.ok_or(LanguageError::Domain("not gray"))?;
|
||||
Ok(Some(Color::Rgb(n, n, n)))
|
||||
},
|
||||
(Some("rgb"), Some(tail)) => {
|
||||
|
|
@ -870,6 +825,14 @@ mod app {
|
|||
})).transpose()
|
||||
}
|
||||
|
||||
fn get_axis (&self, src: impl Language) -> Perhaps<ControlAxis> {
|
||||
Ok(src.word()?.map(|word|Ok(match word {
|
||||
"x" => ControlAxis::X,
|
||||
"y" => ControlAxis::Y,
|
||||
_ => return Err(format!("unknown axis {word}"))
|
||||
})).transpose()?)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -877,6 +840,49 @@ pub use self::bind::*;
|
|||
mod bind {
|
||||
use crate::*;
|
||||
|
||||
/// Collection of input bindings.
|
||||
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
|
||||
|
||||
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
|
||||
///
|
||||
/// ```
|
||||
/// let lang = "(@x (nop)) (@y (nop) (nop))";
|
||||
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
|
||||
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
|
||||
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Bind<E, C>(
|
||||
/// Map of each event (e.g. key combination) to
|
||||
/// all command expressions bound to it by
|
||||
/// all loaded input layers.
|
||||
pub BTreeMap<E, Vec<Binding<C>>>
|
||||
);
|
||||
|
||||
/// A sequence of zero or more commands (e.g. [AppCommand]),
|
||||
/// optionally filtered by [Condition] to form layers.
|
||||
///
|
||||
/// ```
|
||||
/// //FIXME: Why does it overflow?
|
||||
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
|
||||
/// ```
|
||||
#[derive(Debug, Clone)] pub struct Binding<C> {
|
||||
pub commands: Arc<[C]>,
|
||||
pub condition: Option<Condition>,
|
||||
pub description: Option<Arc<str>>,
|
||||
pub source: Option<Arc<PathBuf>>,
|
||||
}
|
||||
|
||||
/// Condition that must evaluate to true in order to enable an input layer.
|
||||
///
|
||||
/// ```
|
||||
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct Condition(
|
||||
pub Arc<Box<dyn Fn()->bool + Send + Sync>>
|
||||
);
|
||||
|
||||
tui_keys!(self: App, input {
|
||||
let commands = collect_commands(self, input)?;
|
||||
let results = execute_commands(self, commands)?;
|
||||
|
|
@ -888,24 +894,21 @@ mod bind {
|
|||
-> Usually<Vec<AppCommand>>
|
||||
{
|
||||
let mut commands = vec![];
|
||||
app.mode
|
||||
.as_ref()
|
||||
.and_then(|m|app.config.get_mode(m))
|
||||
.map(|mode|{
|
||||
for id in mode.keys.iter() {
|
||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||
commands.push(command)
|
||||
}
|
||||
app.mode.as_ref().and_then(|m|app.config.get_mode(m)).map(|mode|{
|
||||
for id in mode.keys.iter() {
|
||||
if let Some(event_map) = app.config.binds.clone().read().unwrap().get(id.as_ref())
|
||||
&& let Some(bindings) = event_map.query(input) {
|
||||
for binding in bindings {
|
||||
for command in binding.commands.iter() {
|
||||
if let Some(command) = app.namespace(command)? as Option<AppCommand> {
|
||||
commands.push(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok::<_, Box<dyn Error>>(())
|
||||
}).transpose()?;
|
||||
}
|
||||
Ok::<_, Box<dyn Error>>(())
|
||||
}).transpose()?;
|
||||
Ok(commands)
|
||||
}
|
||||
|
||||
|
|
@ -914,9 +917,16 @@ mod bind {
|
|||
{
|
||||
let mut history = vec![];
|
||||
for command in commands.into_iter() {
|
||||
let result = command.act(app);
|
||||
match result { Err(err) => { history.push((command, None)); return Err(err) }
|
||||
Ok(undo) => { history.push((command, undo)); } };
|
||||
let result = command.clone().act(app);
|
||||
match result {
|
||||
Err(err) => {
|
||||
history.push((command, None));
|
||||
return Err(err)
|
||||
},
|
||||
Ok(undo) => {
|
||||
history.push((command, undo));
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(history)
|
||||
}
|
||||
|
|
@ -1002,45 +1012,62 @@ mod bind {
|
|||
|
||||
impl_debug!(Condition |self, w| { write!(w, "*") });
|
||||
|
||||
#[tek_proc::command(App)]
|
||||
#[tek_proc::keyword(App)]
|
||||
#[derive(Debug, Default)]
|
||||
pub enum AppCommand {
|
||||
#[default]
|
||||
#[command(App::nop)]
|
||||
#[keyword("nop")]
|
||||
Nop,
|
||||
|
||||
#[command(App::cancel)]
|
||||
#[keyword("cancel")]
|
||||
Cancel,
|
||||
|
||||
#[command(App::confirm)]
|
||||
#[keyword("confirm")]
|
||||
Confirm,
|
||||
|
||||
#[command(App::inc)]
|
||||
#[keyword("x/inc", ControlAxis::X)]
|
||||
#[keyword("y/inc", ControlAxis::Y)]
|
||||
Inc(ControlAxis),
|
||||
|
||||
#[command(App::dec)]
|
||||
#[keyword("x/dec", ControlAxis::X)]
|
||||
#[keyword("y/dec", ControlAxis::Y)]
|
||||
Dec(ControlAxis),
|
||||
|
||||
#[command(App::set_dialog | Self::SetDialog)]
|
||||
#[keyword("dialog")]
|
||||
SetDialog(Dialog),
|
||||
}
|
||||
|
||||
#[tek_proc::commands(AppCommand = "app")]
|
||||
impl App {
|
||||
fn nop (&mut self) -> Perhaps<AppCommand> {
|
||||
|
||||
#[command(Nop = "nop")]
|
||||
pub fn nop (&mut self) -> Perhaps<AppCommand> {
|
||||
Ok(None)
|
||||
}
|
||||
fn cancel (&mut self) -> Perhaps<AppCommand> {
|
||||
|
||||
#[command(Cancel = "cancel")]
|
||||
pub fn cancel (&mut self) -> Perhaps<AppCommand> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[command(Inc = "inc")]
|
||||
pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => todo!(),
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_next() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
#[command(Dec = "dec")]
|
||||
pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AppCommand> {
|
||||
Ok(match (&self.dialog, axis) {
|
||||
(Dialog::None, _) => None,
|
||||
(Dialog::Menu(_, _), ControlAxis::Y) =>
|
||||
AppCommand::SetDialog { dialog: self.dialog.menu_prev() }.act(self)?,
|
||||
_ => todo!()
|
||||
})
|
||||
}
|
||||
|
||||
#[command(Confirm = "confirm")]
|
||||
pub fn confirm (&mut self) -> Perhaps<AppCommand> {
|
||||
Ok(match &self.dialog {
|
||||
Dialog::Menu(index, items) => {
|
||||
let callback = items.0[*index].1.clone();
|
||||
callback(self)?;
|
||||
None
|
||||
},
|
||||
_ => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Swap currently active modal dialog.
|
||||
///
|
||||
/// ```
|
||||
/// let _ = tek::App::default().set_dialog(tek::Dialog::welcome());
|
||||
/// ```
|
||||
#[command(SetDialog = "dialog")]
|
||||
pub fn set_dialog (&mut self, dialog: Dialog) -> Perhaps<AppCommand> {
|
||||
let mut dialog = dialog.clone();
|
||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||
Ok(Some(AppCommand::SetDialog { dialog }))
|
||||
}
|
||||
}
|
||||
|
||||
//impl<'a> Namespace<'a, AppCommand> for App {
|
||||
|
|
@ -1135,7 +1162,7 @@ mod device {
|
|||
editor.set_note_pos(pitch.as_int() as usize);
|
||||
}
|
||||
}
|
||||
let result = state.project.process_tracks(client, scope);
|
||||
let result = state.project.tracks_jack_process(client, scope);
|
||||
state.perf.update_from_jack_scope(t0, scope);
|
||||
result
|
||||
}
|
||||
|
|
@ -1279,46 +1306,6 @@ mod device {
|
|||
|
||||
#[cfg(feature = "plugin")] pub mod plugin;
|
||||
#[cfg(feature = "plugin")] pub use self::plugin::*;
|
||||
|
||||
pub struct Junction<T: JackPort>(T);
|
||||
|
||||
impl<T: JackPort> View<Tui> for Junction<T> {
|
||||
fn view (&self) -> impl Draw<Tui> {
|
||||
T::KIND
|
||||
}
|
||||
}
|
||||
|
||||
#[tek_proc::command(AudioInput)]
|
||||
#[tek_proc::keyword(AudioInput)]
|
||||
#[derive(Debug)]
|
||||
pub enum AudioInputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(AudioOutput)]
|
||||
#[tek_proc::keyword(AudioOutput)]
|
||||
#[derive(Debug)]
|
||||
pub enum AudioOutputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(MidiInput)]
|
||||
#[tek_proc::keyword(MidiInput)]
|
||||
#[derive(Debug)]
|
||||
pub enum MidiInputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
|
||||
#[tek_proc::command(MidiOutput)]
|
||||
#[tek_proc::keyword(MidiOutput)]
|
||||
#[derive(Debug)]
|
||||
pub enum MidiOutputCommand {
|
||||
Close,
|
||||
Connect(Arc<str>),
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn tui (
|
||||
|
|
@ -1374,57 +1361,61 @@ mod draw {
|
|||
fn view (&self) -> impl Draw<Tui> {
|
||||
self.perf.cycle(&mut |_|{
|
||||
draw(|to: &mut Tui|{
|
||||
let xywh = to.area().into();
|
||||
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
|
||||
self.mode
|
||||
.as_ref()
|
||||
.and_then(|m|self.config.get_mode(m))
|
||||
.map(|mode|{
|
||||
|
||||
//south!(
|
||||
//format!("Mode: {:?}", self.mode.as_ref()),
|
||||
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
||||
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
||||
//format!("View #{index} {line}")
|
||||
//}))
|
||||
//).align_nw().draw(to)?;
|
||||
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
match self.interpret(to, dsl) {
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
return Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
*self.error.write().unwrap() = None;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
east(
|
||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000),
|
||||
).align_se().draw(to)?;
|
||||
|
||||
Ok(Some(xywh))
|
||||
self.draw_error(to)?;
|
||||
self.draw_mode(to)?;
|
||||
self.draw_debug(to)?;
|
||||
Ok(Some(to.area().into()))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn draw_error (&self, to: &mut Tui) -> Usually<()> {
|
||||
if let Some(e) = self.error.read().unwrap().as_ref() {
|
||||
e.as_ref().align_c().draw(to)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_mode (&self, to: &mut Tui) -> Usually<()> {
|
||||
self.mode.as_ref().and_then(|m|self.config.get_mode(m)).map(|mode|{
|
||||
//south!(
|
||||
//format!("Mode: {:?}", self.mode.as_ref()),
|
||||
//format!("Time: {}", self.config.stamp.load(Relaxed)),
|
||||
//iter_south(||mode.view.iter().enumerate().map(|(index, line)|{
|
||||
//format!("View #{index} {line}")
|
||||
//}))
|
||||
//).align_nw().draw(to)?;
|
||||
for (index, dsl) in mode.view.iter().enumerate() {
|
||||
match self.interpret(to, dsl) {
|
||||
Ok(Some(XYWH(.., w, h))) => {
|
||||
self.size.0.store(w as usize, Relaxed);
|
||||
self.size.1.store(h as usize, Relaxed);
|
||||
},
|
||||
Err(e) => {
|
||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||
let message = format!("Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &src);
|
||||
*self.error.write().unwrap() = Some(message.into());
|
||||
return Err(e);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
*self.error.write().unwrap() = None;
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
|
||||
east(
|
||||
format!("{}x{} ", self.size.0.load(Relaxed), self.size.1.load(Relaxed)),
|
||||
format!("{}% {} ", self.perf.percentage().unwrap_or_default(), self.perf.clock.raw() / 1000000000),
|
||||
).align_se().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret<Tui, Option<XYWH<u16>>> for App {
|
||||
fn interpret_expr <'a> (&'a self, to: &mut Tui, lang: &'a impl Expression) -> Drawn<u16> {
|
||||
self.keyword(to, lang)
|
||||
|
|
@ -1433,12 +1424,12 @@ mod draw {
|
|||
let mut frags = lang.src()?.unwrap().split("/");
|
||||
match frags.next() {
|
||||
//Some(":logo") => view_logo().draw(to),
|
||||
Some(":meters") => match frags.next() {
|
||||
Some(":meters") => match frags.next() {
|
||||
Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to),
|
||||
Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":tracks") => match frags.next() {
|
||||
Some(":tracks") => match frags.next() {
|
||||
None => "TODO tracks".draw(to),
|
||||
Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))),
|
||||
Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to),
|
||||
|
|
@ -1446,7 +1437,7 @@ mod draw {
|
|||
Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to),
|
||||
_ => panic!()
|
||||
},
|
||||
Some(":scenes") => match frags.next() {
|
||||
Some(":scenes") => match frags.next() {
|
||||
None => "TODO Scenes".draw(to),
|
||||
Some(":scenes/names") => "TODO Scene Names".draw(to),
|
||||
_ => panic!()
|
||||
|
|
@ -1506,27 +1497,6 @@ mod draw {
|
|||
}).min_w(30).exact_h(height)
|
||||
}
|
||||
|
||||
//pub fn per_track <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
//tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
//) -> impl Draw<Tui> + 'a {
|
||||
//per_track_top(tracks, move|index, track|callback(index, track).full_h().align_y())
|
||||
//}
|
||||
|
||||
//pub fn per_track_top <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
//tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
//) -> impl Draw<Tui> + 'a {
|
||||
//bg(Reset, iter_east(||tracks()
|
||||
//.map(move|(index, track, x1, x2): (usize, &'a Track, usize, usize)|{
|
||||
//fg_bg(
|
||||
//track.color.lightest.term,
|
||||
//track.color.base.term,
|
||||
//callback(index, track)
|
||||
//).exact_w((x2 - x1) as u16)
|
||||
//})).align_x())
|
||||
//}
|
||||
|
||||
pub fn field_h <T: Screen> (
|
||||
_theme: ItemTheme, _head: impl Draw<T>, _body: impl Draw<T>
|
||||
) -> impl Draw<T> {
|
||||
|
|
@ -1552,17 +1522,6 @@ mod draw {
|
|||
}).min_w(w).exact_h(h)
|
||||
}
|
||||
|
||||
pub fn view_browse_title (state: &App) -> impl Draw<Tui> {
|
||||
field_v(ItemTheme::default(), match state.dialog.browser_target().unwrap() {
|
||||
BrowseTarget::SaveProject => "Save project:",
|
||||
BrowseTarget::LoadProject => "Load project:",
|
||||
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||
}, fg(g(96), x_repeat("🭻")).exact_h(1)).align_w().full_w()
|
||||
}
|
||||
|
||||
pub fn view_device (state: &App) -> impl Draw<Tui> {
|
||||
let selected = state.dialog.device_kind().unwrap();
|
||||
south(
|
||||
|
|
@ -1629,21 +1588,6 @@ mod draw {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "track")] pub fn view_track_row_section (
|
||||
_theme: ItemTheme,
|
||||
button: impl Draw<Tui>,
|
||||
button_add: impl Draw<Tui>,
|
||||
content: impl Draw<Tui>,
|
||||
) -> impl Draw<Tui> {
|
||||
west(
|
||||
button_add.align_nw().exact_w(4).full_h(),
|
||||
east(
|
||||
button.align_nw().full_h().exact_w(20),
|
||||
content.align_c().full_wh()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// let bg = tengri::ratatui::style::Color::Red;
|
||||
/// let fg = tengri::ratatui::style::Color::Green;
|
||||
|
|
@ -1687,211 +1631,10 @@ mod draw {
|
|||
south(left, right)
|
||||
}
|
||||
|
||||
pub fn view_sample_info (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
when(sample.is_some(), draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
east!(
|
||||
field_h(theme, "Name", format!("{:<10}", sample.name.clone())),
|
||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())),
|
||||
field_h(theme, "Start", format!("{:<8}", sample.start)),
|
||||
field_h(theme, "End", format!("{:<8}", sample.end)),
|
||||
field_h(theme, "Trans", "0"),
|
||||
field_h(theme, "Gain", format!("{}", sample.gain)),
|
||||
).draw(to)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn view_sample_info_v (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> + use<'_> {
|
||||
let a = draw(move|to: &mut Tui|{
|
||||
let sample = sample.unwrap().read().unwrap();
|
||||
let theme = sample.color;
|
||||
south!(
|
||||
field_h(theme, "Name ", format!("{:<10}", sample.name.clone())) .align_w().full_w(),
|
||||
field_h(theme, "Length", format!("{:<8}", sample.channels[0].len())).align_w().full_w(),
|
||||
field_h(theme, "Start ", format!("{:<8}", sample.start)) .align_w().full_w(),
|
||||
field_h(theme, "End ", format!("{:<8}", sample.end)) .align_w().full_w(),
|
||||
field_h(theme, "Trans ", "0") .align_w().full_w(),
|
||||
field_h(theme, "Gain ", format!("{}", sample.gain)) .align_w().full_w(),
|
||||
).exact_w(20).draw(to)
|
||||
});
|
||||
|
||||
let b = draw(|to: &mut Tui|fg(Red, south!(
|
||||
bold(true, "× No sample."),
|
||||
"[r] record",
|
||||
"[Shift-F9] import",
|
||||
)).draw(to));
|
||||
|
||||
either(sample.is_some(), a, b)
|
||||
}
|
||||
|
||||
pub fn view_sample_status (sample: Option<&Arc<RwLock<Sample>>>) -> impl Draw<Tui> {
|
||||
bold(true, fg(g(224), sample
|
||||
.map(|sample|{
|
||||
let sample = sample.read().unwrap();
|
||||
format!("Sample {}-{}", sample.start, sample.end)
|
||||
})
|
||||
.unwrap_or_else(||"No sample".to_string())))
|
||||
}
|
||||
|
||||
pub fn view_track_header (theme: ItemTheme, content: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
pub fn view_track_header (theme: ItemTheme, content: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
bg(theme.darker.term, content.align_e().full_w()).exact_w(12)
|
||||
}
|
||||
|
||||
pub fn view_ports_status <'a, T: JackPort> (theme: ItemTheme, title: &'a str, ports: &'a [T])
|
||||
-> impl Draw<Tui> + use<'a, T>
|
||||
{
|
||||
let ins = ports.len() as u16;
|
||||
let frame = Outer(true, Style::default().fg(g(96)));
|
||||
let names = iter_south(move||ports.iter().enumerate().map(|(index, port)|format!(
|
||||
" {index} {}", port.port_name()
|
||||
).align_w().full_h()));
|
||||
let field = field_v(theme, title, names);
|
||||
border(true, frame, field.exact_wh(20, 1 + ins)).exact_wh(20, 1 + ins)
|
||||
}
|
||||
|
||||
pub fn view_io_ports <'a, T: PortsSizes<'a>> (
|
||||
fg: Color, bg: Color, items: impl Fn()->T + Send + Sync + 'a
|
||||
) -> impl Draw<Tui> + 'a {
|
||||
type Item<'a> = (usize, &'a Arc<str>, &'a [Connect], usize, usize);
|
||||
iter(items,
|
||||
move|(_index, name, connections, y, y2): Item<'a>, _| south(
|
||||
bold(true, fg_bg(fg, bg, east(" ", name).align_w())).full_h(),
|
||||
iter(||connections.iter(), move|connect: &'a Connect, index|{
|
||||
bold(false, fg_bg(fg, bg, &connect.info)).exact_h(1).align_w().push_y(index as u16)
|
||||
})
|
||||
).exact_h((y2 - y) as u16).push_y(y as u16))
|
||||
}
|
||||
|
||||
pub fn view_scenes_clips <'a, S: ScenesSizes<'a>> (
|
||||
scenes: impl Fn()->S,
|
||||
tracks: impl TracksSizes<'a>,
|
||||
select: &Selection,
|
||||
editor: Option<&MidiEditor>,
|
||||
size: &Sizer,
|
||||
editing: bool,
|
||||
) -> impl Draw<Tui> {
|
||||
let status = fg(Green, format!("{}x{}", size.w(), size.h())).align_se().full_wh();
|
||||
let tracks = iter_once(tracks, move|(track_index, track, _, _), _| {
|
||||
let scenes = iter_once(scenes(), move|(scene_index, scene, _, _), _| {
|
||||
let (name, theme): (Arc<str>, ItemTheme) = scene_name_theme(scene, track_index);
|
||||
let f = theme.lightest.term;
|
||||
let (b, o) = scene_bg(theme, select, track_index, scene_index);
|
||||
let w = scene_w(track, select, track_index, editor);
|
||||
let y = scene_y(select, scene_index, editor);
|
||||
let is_selected = scene_sel(select, track_index, scene_index, editing);
|
||||
below(
|
||||
Outer(true, Style::default().fg(o)).full_wh(),
|
||||
below(
|
||||
below(
|
||||
fg_bg(o, b, "".full_wh()),
|
||||
fg_bg(f, b, bold(true, name)).align_nw().full_wh(),
|
||||
),
|
||||
when(is_selected, editor.map(|e|e.view())).full_wh()
|
||||
).full_wh()
|
||||
).exact_wh(w, y)
|
||||
});
|
||||
scenes.full_h().exact_w(track.width as u16)
|
||||
});
|
||||
|
||||
return size.of(above(status, tracks).full_wh());
|
||||
|
||||
fn scene_name_theme (scene: &Scene, track_index: usize) -> (Arc<str>, ItemTheme) {
|
||||
if let Some(Some(clip)) = &scene.clips.get(track_index) {
|
||||
let clip = clip.read().unwrap();
|
||||
(format!(" ⏹ {}", &clip.name).into(), clip.color)
|
||||
} else {
|
||||
(" ⏹ -- ".into(), ItemTheme::G[32])
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_bg (
|
||||
theme: ItemTheme, select: &Selection, track_index: usize, scene_index: usize
|
||||
) -> (Color, Color) {
|
||||
let mut outline = theme.base.term;
|
||||
(if select.track() == Some(track_index) && select.scene() == Some(scene_index) {
|
||||
outline = theme.lighter.term;
|
||||
theme.light.term
|
||||
} else if select.track() == Some(track_index) || select.scene() == Some(scene_index) {
|
||||
outline = theme.darkest.term;
|
||||
theme.base.term
|
||||
} else {
|
||||
theme.dark.term
|
||||
}, outline)
|
||||
}
|
||||
|
||||
fn scene_w (
|
||||
track: &Track, select: &Selection, track_index: usize, editor: Option<&MidiEditor>
|
||||
) -> u16 {
|
||||
if select.track() == Some(track_index) && let Some(editor) = editor {
|
||||
(editor.size.w() as usize).max(24).max(track.width) as u16
|
||||
} else {
|
||||
track.width as u16
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_y (
|
||||
select: &Selection, scene_index: usize, editor: Option<&MidiEditor>
|
||||
) -> u16 {
|
||||
if select.scene() == Some(scene_index) && let Some(editor) = editor {
|
||||
editor.size.h().max(12)
|
||||
} else {
|
||||
H_SCENE as u16
|
||||
}
|
||||
}
|
||||
|
||||
fn scene_sel (select: &Selection, track_index: usize, scene_index: usize, editing: bool) -> bool {
|
||||
editing && select.track() == Some(track_index) && select.scene() == Some(scene_index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_scene_name (
|
||||
select: &Selection,
|
||||
editor: Option<&MidiEditor>,
|
||||
index: usize,
|
||||
scene: &Scene,
|
||||
editing: bool
|
||||
) -> impl Draw<Tui> {
|
||||
let h = if select.scene() == Some(index) && let Some(_editor) = editor {
|
||||
7
|
||||
} else {
|
||||
H_SCENE as u16
|
||||
};
|
||||
let a = east(format!("·s{index:02} "),
|
||||
fg(g(255), bold(true, &scene.name))).align_w().full_w();
|
||||
let b = when(select.scene() == Some(index) && editing, south(
|
||||
editor.as_ref().map(|e|e.clip_status()),
|
||||
editor.as_ref().map(|e|e.edit_status())).align_nw().full_wh());
|
||||
let c = if select.scene() == Some(index) {
|
||||
scene.color.light.term
|
||||
} else {
|
||||
scene.color.base.term
|
||||
};
|
||||
bg(c, south(a, b).align_nw()).exact_wh(20, h)
|
||||
}
|
||||
|
||||
pub fn view_midi_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
||||
}
|
||||
|
||||
pub fn view_midi_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "MIDI outs: ", &track.sequencer.midi_outs))
|
||||
}
|
||||
|
||||
pub fn view_audio_ins_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio ins: ", &track.audio_ins()))
|
||||
}
|
||||
|
||||
pub fn view_audio_outs_status (theme: ItemTheme, track: Option<&Track>) -> impl Draw<Tui> {
|
||||
track.map(move|track|view_ports_status(theme, "Audio outs:", &track.audio_outs()))
|
||||
}
|
||||
|
||||
//pub fn view_track_per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
||||
//tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
//callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
//) -> impl Draw<Tui> {
|
||||
//}
|
||||
|
||||
/// ```
|
||||
/// let _ = tek::button_2("", "", true);
|
||||
/// let _ = tek::button_2("", "", false);
|
||||
|
|
|
|||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit 8a6eb19e279afefc126008c27b38cc66645e1c96
|
||||
Subproject commit b7f4d55e1d67d3481ecee14f693ca3e0a9426a6c
|
||||
Loading…
Add table
Add a link
Reference in a new issue