mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-01-31 16:36:40 +01:00
Compare commits
2 commits
5ccbb9719f
...
559d2fc4a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 559d2fc4a1 | |||
| cfd19062fd |
37 changed files with 1658 additions and 1621 deletions
|
|
@ -10,6 +10,8 @@ mod app_view; pub use self::app_view::*;
|
||||||
/// Total state
|
/// Total state
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
/// Base color.
|
||||||
|
pub color: ItemTheme,
|
||||||
/// Must not be dropped for the duration of the process
|
/// Must not be dropped for the duration of the process
|
||||||
pub jack: Jack<'static>,
|
pub jack: Jack<'static>,
|
||||||
/// Display size
|
/// Display size
|
||||||
|
|
@ -20,26 +22,30 @@ pub struct App {
|
||||||
pub config: Config,
|
pub config: Config,
|
||||||
/// Currently selected mode
|
/// Currently selected mode
|
||||||
pub mode: Arc<Mode<Arc<str>>>,
|
pub mode: Arc<Mode<Arc<str>>>,
|
||||||
/// Contains the currently edited musical arrangement
|
|
||||||
pub project: Arrangement,
|
|
||||||
/// Contains all recently created clips.
|
|
||||||
pub pool: Pool,
|
|
||||||
/// Undo history
|
/// Undo history
|
||||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||||
/// Dialog overlay
|
/// Dialog overlay
|
||||||
pub dialog: Dialog,
|
pub dialog: Dialog,
|
||||||
/// Base color.
|
/// Contains all recently created clips.
|
||||||
pub color: ItemTheme,
|
pub pool: Pool,
|
||||||
|
/// Contains the currently edited musical arrangement
|
||||||
|
pub project: Arrangement,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
|
pub struct Axis {
|
||||||
|
min: usize,
|
||||||
|
max: usize,
|
||||||
|
step: usize,
|
||||||
}
|
}
|
||||||
/// Various possible dialog modes.
|
/// Various possible dialog modes.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub enum Dialog {
|
pub enum Dialog {
|
||||||
#[default] None,
|
#[default] None,
|
||||||
Help(usize),
|
Help(usize),
|
||||||
Menu(usize),
|
Menu(usize, Arc<[Arc<str>]>),
|
||||||
Device(usize),
|
Device(usize),
|
||||||
Message(Arc<str>),
|
Message(Arc<str>),
|
||||||
Browser(BrowserTarget, Arc<Browser>),
|
Browse(BrowseTarget, Arc<Browse>),
|
||||||
Options,
|
Options,
|
||||||
}
|
}
|
||||||
has!(Jack<'static>: |self: App|self.jack);
|
has!(Jack<'static>: |self: App|self.jack);
|
||||||
|
|
@ -61,68 +67,28 @@ maybe_has!(Scene: |self: App| { MaybeHas::<Scene>::get(&self.project) };
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||||
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||||
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||||
pub fn view_nil (_: &App) -> Box<dyn Render<TuiOut>> {
|
|
||||||
Box::new(Fill::xy("·"))
|
|
||||||
}
|
|
||||||
content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{
|
|
||||||
for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); }
|
|
||||||
})));
|
|
||||||
impl App {
|
|
||||||
fn view <D: Dsl> (&self, index: D) -> Box<dyn Render<TuiOut>> {
|
|
||||||
match index.src() {
|
|
||||||
Ok(Some(src)) => render_dsl(self, src),
|
|
||||||
Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")),
|
|
||||||
Err(e) => Box::new(format!("{e}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn update_clock (&self) {
|
|
||||||
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn render_dsl <'t> (
|
|
||||||
state: &'t impl DslNs<'t, Box<dyn Render<TuiOut>>>,
|
|
||||||
src: &str
|
|
||||||
) -> Box<dyn Render<TuiOut>> {
|
|
||||||
let err: Option<Box<dyn Error>> = match state.from(&src) {
|
|
||||||
Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e),
|
|
||||||
};
|
|
||||||
let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0));
|
|
||||||
let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0));
|
|
||||||
let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0));
|
|
||||||
let bg = Color::Rgb(24, 0, 0);
|
|
||||||
Box::new(col! {
|
|
||||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))),
|
|
||||||
Tui::bg(bg, col! {
|
|
||||||
Fill::x(Bsp::e(
|
|
||||||
Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")),
|
|
||||||
Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))),
|
|
||||||
)),
|
|
||||||
Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))),
|
|
||||||
}),
|
|
||||||
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
handle!(TuiIn:|self: App, input|{
|
|
||||||
for id in self.mode.keys.iter() {
|
|
||||||
if let Some(event_map) = self.config.binds.read().unwrap().get(id.as_ref()) {
|
|
||||||
if let Some(bindings) = event_map.query(input.event()) {
|
|
||||||
for binding in bindings {
|
|
||||||
for command in binding.commands.iter() {
|
|
||||||
let command: Option<AppCommand> = self.from(command)?;
|
|
||||||
if let Some(command) = command {
|
|
||||||
panic!("{command:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!("{binding:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
});
|
|
||||||
impl Dialog {
|
impl Dialog {
|
||||||
|
pub fn welcome () -> Self {
|
||||||
|
Self::Menu(0, [
|
||||||
|
"Continue session".into(),
|
||||||
|
"Load old session".into(),
|
||||||
|
"Begin new session".into(),
|
||||||
|
].into())
|
||||||
|
}
|
||||||
|
pub fn menu_next (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn menu_prev (&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.len()), items.clone()),
|
||||||
|
_ => Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn menu_selected (&self) -> Option<usize> {
|
pub fn menu_selected (&self) -> Option<usize> {
|
||||||
if let Self::Menu(selected) = self { Some(*selected) } else { None }
|
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
|
||||||
}
|
}
|
||||||
pub fn device_kind (&self) -> Option<usize> {
|
pub fn device_kind (&self) -> Option<usize> {
|
||||||
if let Self::Device(index) = self { Some(*index) } else { None }
|
if let Self::Device(index) = self { Some(*index) } else { None }
|
||||||
|
|
@ -136,10 +102,10 @@ impl Dialog {
|
||||||
pub fn message (&self) -> Option<&str> {
|
pub fn message (&self) -> Option<&str> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
pub fn browser (&self) -> Option<&Arc<Browser>> {
|
pub fn browser (&self) -> Option<&Arc<Browse>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
pub fn browser_target (&self) -> Option<&BrowserTarget> {
|
pub fn browser_target (&self) -> Option<&BrowseTarget> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,8 +155,8 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn browser (&self) -> Option<&Browser> {
|
pub fn browser (&self) -> Option<&Browse> {
|
||||||
if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None }
|
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
|
||||||
}
|
}
|
||||||
pub fn device_pick (&mut self, index: usize) {
|
pub fn device_pick (&mut self, index: usize) {
|
||||||
self.dialog = Dialog::Device(index);
|
self.dialog = Dialog::Device(index);
|
||||||
|
|
@ -225,7 +191,6 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn wrap_dialog (dialog: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
fn wrap_dialog (dialog: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||||
Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b(
|
Fixed::xy(70, 23, Tui::fg_bg(Rgb(255,255,255), Rgb(16,16,16), Bsp::b(
|
||||||
Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))).enclose(dialog))))
|
Repeat(" "), Outer(true, Style::default().fg(Tui::g(96))).enclose(dialog))))
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,83 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
handle!(TuiIn:|self: App, input|{
|
||||||
|
let mut commands = vec![];
|
||||||
|
for id in self.mode.keys.iter() {
|
||||||
|
if let Some(event_map) = self.config.binds.clone().read().unwrap().get(id.as_ref()) {
|
||||||
|
if let Some(bindings) = event_map.query(input.event()) {
|
||||||
|
for binding in bindings {
|
||||||
|
for command in binding.commands.iter() {
|
||||||
|
if let Some(command) = self.from(command)? as Option<AppCommand> {
|
||||||
|
commands.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for command in commands.into_iter() {
|
||||||
|
let result = command.execute(self);
|
||||||
|
match result {
|
||||||
|
Ok(undo) => {
|
||||||
|
self.history.push((command, undo));
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
self.history.push((command, None));
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum Axis { X, Y, Z, I }
|
||||||
|
|
||||||
impl<'t> DslNs<'t, AppCommand> for App {
|
impl<'t> DslNs<'t, AppCommand> for App {
|
||||||
dsl_exprs!(|app| -> AppCommand { /* TODO */ });
|
dsl_exprs!(|app| -> AppCommand { /* TODO */ });
|
||||||
dsl_words!(|app| -> AppCommand {
|
dsl_words!(|app| -> AppCommand {
|
||||||
"x/inc" => todo!(),
|
"x/inc" => AppCommand::Inc { axis: Axis::X },
|
||||||
"x/dec" => todo!(),
|
"x/dec" => AppCommand::Dec { axis: Axis::X },
|
||||||
"y/inc" => todo!(),
|
"y/inc" => AppCommand::Inc { axis: Axis::Y },
|
||||||
"y/dec" => todo!(),
|
"y/dec" => AppCommand::Dec { axis: Axis::Y },
|
||||||
"confirm" => todo!(),
|
"confirm" => AppCommand::Confirm,
|
||||||
|
"cancel" => AppCommand::Cancel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AppCommand { /* TODO */ }
|
|
||||||
|
|
||||||
#[tengri_proc::command(Option<Dialog>)]
|
impl Default for AppCommand { fn default () -> Self { Self::Nop } }
|
||||||
impl DialogCommand {
|
|
||||||
fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
|
def_command!(AppCommand: |app: App| {
|
||||||
*dialog = Some(new);
|
Nop => Ok(None),
|
||||||
Ok(None)
|
Confirm => todo!(),
|
||||||
}
|
Cancel => todo!(), // TODO delegate:
|
||||||
fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
|
Inc { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
*dialog = None;
|
(Dialog::None, _) => todo!(),
|
||||||
Ok(None)
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_next() }
|
||||||
}
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
Dec { axis: Axis } => Ok(match (&app.dialog, axis) {
|
||||||
|
(Dialog::None, _) => None,
|
||||||
|
(Dialog::Menu(_, _), Axis::Y) => AppCommand::SetDialog { dialog: app.dialog.menu_prev() }
|
||||||
|
.execute(app)?,
|
||||||
|
_ => todo!()
|
||||||
|
}),
|
||||||
|
SetDialog { dialog: Dialog } => {
|
||||||
|
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog })
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn wrap_inc (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { (index + 1) % count } else { 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wrap_dec (index: usize, count: usize) -> usize {
|
||||||
|
if count > 0 { index.overflowing_sub(1).0.min(count.saturating_sub(1)) } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dialog {
|
||||||
|
}
|
||||||
|
|
||||||
//AppCommand => {
|
//AppCommand => {
|
||||||
//("x/inc" /
|
//("x/inc" /
|
||||||
|
|
|
||||||
|
|
@ -43,19 +43,18 @@ impl<'t> DslNs<'t, Dialog> for App {
|
||||||
":dialog/device/prev" => Dialog::Device(0),
|
":dialog/device/prev" => Dialog::Device(0),
|
||||||
":dialog/device/next" => Dialog::Device(0),
|
":dialog/device/next" => Dialog::Device(0),
|
||||||
":dialog/help" => Dialog::Help(0),
|
":dialog/help" => Dialog::Help(0),
|
||||||
":dialog/menu" => Dialog::Menu(0),
|
":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject,
|
||||||
":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject,
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject,
|
||||||
":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject,
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()),
|
||||||
":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()),
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()),
|
||||||
":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()),
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()),
|
||||||
":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()),
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()),
|
||||||
":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()),
|
Browse::new(None).unwrap().into()),
|
||||||
Browser::new(None).unwrap().into()),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,46 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
content!(TuiOut:|self: App|Fill::xy(Stack::above(|add|{
|
||||||
|
for dsl in self.mode.view.iter() { add(&Fill::xy(self.view(dsl.as_ref()))); }
|
||||||
|
})));
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn view <D: Dsl> (&self, index: D) -> Box<dyn Render<TuiOut>> {
|
||||||
|
match index.src() {
|
||||||
|
Ok(Some(src)) => render_dsl(self, src),
|
||||||
|
Ok(None) => Box::new(Tui::fg(Color::Rgb(192, 192, 192), "empty view")),
|
||||||
|
Err(e) => Box::new(format!("{e}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn update_clock (&self) {
|
||||||
|
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_dsl <'t> (
|
||||||
|
state: &'t impl DslNs<'t, Box<dyn Render<TuiOut>>>,
|
||||||
|
src: &str
|
||||||
|
) -> Box<dyn Render<TuiOut>> {
|
||||||
|
let err: Option<Box<dyn Error>> = match state.from(&src) {
|
||||||
|
Ok(Some(value)) => return value, Ok(None) => None, Err(e) => Some(e),
|
||||||
|
};
|
||||||
|
let (fg_1, bg_1) = (Color::Rgb(240, 160, 100), Color::Rgb(48, 0, 0));
|
||||||
|
let (fg_2, bg_2) = (Color::Rgb(250, 200, 180), Color::Rgb(48, 0, 0));
|
||||||
|
let (fg_3, bg_3) = (Color::Rgb(250, 200, 120), Color::Rgb(0, 0, 0));
|
||||||
|
let bg = Color::Rgb(24, 0, 0);
|
||||||
|
Box::new(col! {
|
||||||
|
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▄")))),
|
||||||
|
Tui::bg(bg, col! {
|
||||||
|
Fill::x(Bsp::e(
|
||||||
|
Tui::bold(true, Tui::fg_bg(fg_1, bg_1, " Render error: ")),
|
||||||
|
Tui::fg_bg(fg_2, bg_2, err.map(|e|format!(" {e} "))),
|
||||||
|
)),
|
||||||
|
Fill::x(Align::x(Tui::fg_bg(fg_3, bg_3, format!(" {src} ")))),
|
||||||
|
}),
|
||||||
|
Tui::fg(bg, Fixed::y(1, Fill::x(RepeatH("▀")))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
dsl_exprs!(|app| -> Box<dyn Render<TuiOut>> {
|
dsl_exprs!(|app| -> Box<dyn Render<TuiOut>> {
|
||||||
"text" (tail: Arc<str>) => Box::new(tail),
|
"text" (tail: Arc<str>) => Box::new(tail),
|
||||||
|
|
@ -42,6 +83,21 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
"max/xy" (x: u16, y: u16, c: Box<dyn Render<TuiOut>>) => Box::new(Max::xy(x, y, c)),
|
"max/xy" (x: u16, y: u16, c: Box<dyn Render<TuiOut>>) => Box::new(Max::xy(x, y, c)),
|
||||||
});
|
});
|
||||||
dsl_words!(|app| -> Box<dyn Render<TuiOut>> {
|
dsl_words!(|app| -> Box<dyn Render<TuiOut>> {
|
||||||
|
":dialog/menu" => Box::new(if let Dialog::Menu(selected, items) = &app.dialog {
|
||||||
|
let items = items.clone();
|
||||||
|
let selected = *selected;
|
||||||
|
Some(Fill::xy(Align::c(Tui::bg(Red, Fill::x(Stack::south(move|add|{
|
||||||
|
for (index, item) in items.iter().enumerate() {
|
||||||
|
add(&Tui::fg_bg(
|
||||||
|
if selected == index { Rgb(240,200,180) } else { Rgb(200, 200, 200) },
|
||||||
|
if selected == index { Rgb(80, 80, 50) } else { Rgb(30, 30, 30) },
|
||||||
|
Fixed::y(2, Align::n(Fill::x(item)))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}))))))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}),
|
||||||
":templates" => Box::new({
|
":templates" => Box::new({
|
||||||
let modes = app.config.modes.clone();
|
let modes = app.config.modes.clone();
|
||||||
let height = (modes.read().unwrap().len() * 2) as u16;
|
let height = (modes.read().unwrap().len() * 2) as u16;
|
||||||
|
|
@ -71,12 +127,12 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
)))),
|
)))),
|
||||||
":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(),
|
":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(),
|
||||||
match app.dialog.browser_target().unwrap() {
|
match app.dialog.browser_target().unwrap() {
|
||||||
BrowserTarget::SaveProject => "Save project:",
|
BrowseTarget::SaveProject => "Save project:",
|
||||||
BrowserTarget::LoadProject => "Load project:",
|
BrowseTarget::LoadProject => "Load project:",
|
||||||
BrowserTarget::ImportSample(_) => "Import sample:",
|
BrowseTarget::ImportSample(_) => "Import sample:",
|
||||||
BrowserTarget::ExportSample(_) => "Export sample:",
|
BrowseTarget::ExportSample(_) => "Export sample:",
|
||||||
BrowserTarget::ImportClip(_) => "Import clip:",
|
BrowseTarget::ImportClip(_) => "Import clip:",
|
||||||
BrowserTarget::ExportClip(_) => "Export clip:",
|
BrowseTarget::ExportClip(_) => "Export clip:",
|
||||||
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
|
||||||
":device" => {
|
":device" => {
|
||||||
let selected = app.dialog.device_kind().unwrap();
|
let selected = app.dialog.device_kind().unwrap();
|
||||||
|
|
@ -118,6 +174,10 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view_nil (_: &App) -> Box<dyn Render<TuiOut>> {
|
||||||
|
Box::new(Fill::xy("·"))
|
||||||
|
}
|
||||||
|
|
||||||
//Bsp::s("",
|
//Bsp::s("",
|
||||||
//Map::south(1,
|
//Map::south(1,
|
||||||
//move||app.config.binds.layers.iter()
|
//move||app.config.binds.layers.iter()
|
||||||
|
|
@ -130,7 +190,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
//b.next().map(|t|Fixed::x(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))),
|
//b.next().map(|t|Fixed::x(16, Align::w(Tui::fg(Rgb(64,224,0), format!("{}", t.value))))),
|
||||||
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
|
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
|
||||||
|
|
||||||
//Dialog::Browser(BrowserTarget::Load, browser) => {
|
//Dialog::Browse(BrowseTarget::Load, browser) => {
|
||||||
//"bobcat".boxed()
|
//"bobcat".boxed()
|
||||||
////Bsp::s(
|
////Bsp::s(
|
||||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||||
|
|
@ -139,7 +199,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
////Outer(true, Style::default().fg(Tui::g(96)))
|
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
////.enclose(Fill::xy(browser)))
|
////.enclose(Fill::xy(browser)))
|
||||||
//},
|
//},
|
||||||
//Dialog::Browser(BrowserTarget::Export, browser) => {
|
//Dialog::Browse(BrowseTarget::Export, browser) => {
|
||||||
//"bobcat".boxed()
|
//"bobcat".boxed()
|
||||||
////Bsp::s(
|
////Bsp::s(
|
||||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||||
|
|
@ -148,7 +208,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
|
||||||
////Outer(true, Style::default().fg(Tui::g(96)))
|
////Outer(true, Style::default().fg(Tui::g(96)))
|
||||||
////.enclose(Fill::xy(browser)))
|
////.enclose(Fill::xy(browser)))
|
||||||
//},
|
//},
|
||||||
//Dialog::Browser(BrowserTarget::Import, browser) => {
|
//Dialog::Browse(BrowseTarget::Import, browser) => {
|
||||||
//"bobcat".boxed()
|
//"bobcat".boxed()
|
||||||
////Bsp::s(
|
////Bsp::s(
|
||||||
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
|
||||||
|
|
|
||||||
|
|
@ -50,56 +50,51 @@ pub enum LaunchMode {
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn run (&self) -> Usually<()> {
|
pub fn run (&self) -> Usually<()> {
|
||||||
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
|
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
|
||||||
let config = Config::init()?;
|
let tracks = vec![];
|
||||||
let empty = &[] as &[&str];
|
let scenes = vec![];
|
||||||
let mut midi_ins = vec![];
|
let empty = &[] as &[&str];
|
||||||
let mut midi_outs = vec![];
|
let left_froms = Connect::collect(&self.left_from, empty, empty);
|
||||||
let tracks = vec![];
|
let left_tos = Connect::collect(&self.left_to, empty, empty);
|
||||||
let scenes = vec![];
|
let right_froms = Connect::collect(&self.right_from, empty, empty);
|
||||||
let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re);
|
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
||||||
let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re);
|
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||||
let left_froms = Connect::collect(&self.left_from, empty, empty);
|
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
||||||
let left_tos = Connect::collect(&self.left_to, empty, empty);
|
let mut config = Config::new(None);
|
||||||
let right_froms = Connect::collect(&self.right_from, empty, empty);
|
config.init()?;
|
||||||
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
|
||||||
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
|
||||||
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
|
||||||
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
||||||
for (index, connect) in midi_froms.iter().enumerate() {
|
|
||||||
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
|
||||||
}
|
|
||||||
for (index, connect) in midi_tos.iter().enumerate() {
|
|
||||||
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
|
||||||
};
|
|
||||||
let clock = Clock::new(&jack, self.bpm)?;
|
|
||||||
let mode = config.modes.clone().read().unwrap().get(":menu").cloned().unwrap();
|
|
||||||
let app = App {
|
let app = App {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
dialog: Dialog::Menu(0),
|
dialog: Dialog::welcome(),
|
||||||
mode,
|
mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(),
|
||||||
config,
|
config,
|
||||||
project: Arrangement {
|
project: Arrangement {
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
color: ItemTheme::random(),
|
color: ItemTheme::random(),
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
clock,
|
clock: Clock::new(&jack, self.bpm)?,
|
||||||
tracks,
|
tracks,
|
||||||
scenes,
|
scenes,
|
||||||
selection: Selection::TrackClip { track: 0, scene: 0 },
|
selection: Selection::TrackClip { track: 0, scene: 0 },
|
||||||
midi_ins,
|
midi_ins: {
|
||||||
midi_outs,
|
let mut midi_ins = vec![];
|
||||||
|
for (index, connect) in self.midi_froms().iter().enumerate() {
|
||||||
|
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
||||||
|
}
|
||||||
|
midi_ins
|
||||||
|
},
|
||||||
|
midi_outs: {
|
||||||
|
let mut midi_outs = vec![];
|
||||||
|
for (index, connect) in self.midi_tos().iter().enumerate() {
|
||||||
|
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
||||||
|
};
|
||||||
|
midi_outs
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
//if let LaunchMode::Arranger { scenes, tracks, track_width, .. } = self.mode {
|
|
||||||
//app.project.arranger = Default::default();
|
|
||||||
//app.project.selection = Selection::TrackClip { track: 1, scene: 1 };
|
|
||||||
//app.project.scenes_add(scenes)?;
|
|
||||||
//app.project.tracks_add(tracks, Some(track_width), &[], &[])?;
|
|
||||||
//}
|
|
||||||
jack.sync_lead(self.sync_lead, |mut state|{
|
jack.sync_lead(self.sync_lead, |mut state|{
|
||||||
let clock = app.clock();
|
let clock = app.clock();
|
||||||
clock.playhead.update_from_sample(state.position.frame() as f64);
|
clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||||
|
|
@ -110,6 +105,12 @@ impl Cli {
|
||||||
Ok(app)
|
Ok(app)
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
fn midi_froms (&self) -> Vec<Connect> {
|
||||||
|
Connect::collect(&self.midi_from, &[] as &[&str], &self.midi_from_re)
|
||||||
|
}
|
||||||
|
fn midi_tos (&self) -> Vec<Connect> {
|
||||||
|
Connect::collect(&self.midi_to, &[] as &[&str], &self.midi_to_re)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CLI header
|
/// CLI header
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,15 @@ use ::{
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub dirs: BaseDirectories,
|
pub dirs: BaseDirectories,
|
||||||
pub modes: Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>,
|
pub modes: Modes,
|
||||||
pub views: Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>,
|
pub views: Views,
|
||||||
pub binds: Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>,
|
pub binds: Binds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Modes = Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>;
|
||||||
|
type Binds = Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>;
|
||||||
|
type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
||||||
|
|
||||||
/// A set of currently active view and keys definitions,
|
/// A set of currently active view and keys definitions,
|
||||||
/// with optional name and description.
|
/// with optional name and description.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|
@ -34,7 +38,7 @@ pub struct Mode<D: Dsl + Ord> {
|
||||||
pub info: Vec<D>,
|
pub info: Vec<D>,
|
||||||
pub view: Vec<D>,
|
pub view: Vec<D>,
|
||||||
pub keys: Vec<D>,
|
pub keys: Vec<D>,
|
||||||
pub modes: BTreeMap<D, Mode<D>>,
|
pub modes: Modes,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of input bindings.
|
/// A collection of input bindings.
|
||||||
|
|
@ -62,11 +66,16 @@ pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
|
||||||
impl Config {
|
impl Config {
|
||||||
const CONFIG: &'static str = "tek.edn";
|
const CONFIG: &'static str = "tek.edn";
|
||||||
const DEFAULTS: &'static str = include_str!("../../tek.edn");
|
const DEFAULTS: &'static str = include_str!("../../tek.edn");
|
||||||
pub fn init () -> Usually<Self> {
|
|
||||||
let mut cfgs: Self = Default::default();
|
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||||
cfgs.dirs = BaseDirectories::with_profile("tek", "v0");
|
Self {
|
||||||
cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?;
|
dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")),
|
||||||
Ok(cfgs)
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn init (&mut self) -> Usually<()> {
|
||||||
|
self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn init_file (
|
pub fn init_file (
|
||||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||||
|
|
@ -89,74 +98,54 @@ impl Config {
|
||||||
let tail = expr.tail()?;
|
let tail = expr.tail()?;
|
||||||
let name = tail.head()?;
|
let name = tail.head()?;
|
||||||
let body = tail.tail()?;
|
let body = tail.tail()?;
|
||||||
println!("{} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||||
match head {
|
match head {
|
||||||
Some("view") if let Some(name) = name => self.load_view(name.into(), body),
|
Some("mode") if let Some(name) = name =>
|
||||||
Some("keys") if let Some(name) = name => self.load_bind(name.into(), body),
|
Mode::<Arc<str>>::load_into(&self.modes, &name, &body)?,
|
||||||
Some("mode") if let Some(name) = name => self.load_mode(name.into(), body),
|
Some("keys") if let Some(name) = name =>
|
||||||
|
EventMap::<TuiEvent, Arc<str>>::load_into(&self.binds, &name, &body)?,
|
||||||
|
Some("view") if let Some(name) = name => {
|
||||||
|
self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into());
|
||||||
|
},
|
||||||
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn load_view (&mut self, id: Arc<str>, dsl: impl Dsl) -> Usually<()> {
|
}
|
||||||
self.views.write().unwrap().insert(id, dsl.src()?.unwrap_or_default().into());
|
|
||||||
|
impl Mode<Arc<str>> {
|
||||||
|
pub fn load_into (modes: &Modes, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
||||||
|
let mut mode = Self::default();
|
||||||
|
println!("Mode::load_into: {}: {body:?}", name.as_ref());
|
||||||
|
body.each(|item|mode.load_one(item))?;
|
||||||
|
modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn load_bind (&mut self, id: Arc<str>, dsl: impl Dsl) -> Usually<()> {
|
pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> {
|
||||||
let mut map = EventMap::new();
|
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(key)) = expr.head() {
|
||||||
dsl.each(|item|if item.expr().head() == Ok(Some("see")) {
|
println!("Mode::load_one: {key} {:?}", expr.tail()?);
|
||||||
// TODO
|
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||||
Ok(())
|
|
||||||
} else if let Ok(Some(word)) = item.expr().head().word() {
|
|
||||||
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
|
|
||||||
map.add(key, Binding {
|
|
||||||
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
|
||||||
condition: None,
|
|
||||||
description: None,
|
|
||||||
source: None
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
} else if Some(":char") == item.expr()?.head()? {
|
|
||||||
// TODO
|
|
||||||
return Ok(())
|
|
||||||
} else {
|
|
||||||
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
|
||||||
})?;
|
|
||||||
self.binds.write().unwrap().insert(id, map);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn load_mode (&mut self, id: Arc<str>, dsl: impl Dsl) -> Usually<()> {
|
|
||||||
let mut mode = Mode::default();
|
|
||||||
dsl.each(|item|Self::load_mode_one(&mut mode, item))?;
|
|
||||||
self.modes.write().unwrap().insert(id.into(), Arc::new(mode));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn load_mode_one (mode: &mut Mode<Arc<str>>, item: impl Dsl) -> Usually<()> {
|
|
||||||
Ok(if let Ok(Some(key)) = item.expr().head() {
|
|
||||||
match key {
|
match key {
|
||||||
"name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()),
|
"name" => self.name.push(tail.into()),
|
||||||
"info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()),
|
"info" => self.info.push(tail.into()),
|
||||||
"keys" => {
|
"view" => self.view.push(tail.into()),
|
||||||
item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?;
|
"keys" => tail.each(|expr|{self.keys.push(expr.trim().into()); Ok(())})?,
|
||||||
},
|
"mode" => if let Some(id) = tail.head()? {
|
||||||
"mode" => if let Some(id) = item.expr()?.tail()?.head()? {
|
Self::load_into(&self.modes, &id, &tail.tail())?;
|
||||||
let mut submode = Mode::default();
|
|
||||||
Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?;
|
|
||||||
mode.modes.insert(id.into(), submode);
|
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("load_mode_one: incomplete: {item:?}").into());
|
return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into());
|
||||||
},
|
},
|
||||||
_ => mode.view.push(item.expr()?.unwrap().into()),
|
_ => {
|
||||||
}
|
return Err(format!("Mode::load_one: unexpected expr: {key:?} {tail:?}").into())
|
||||||
} else if let Ok(Some(word)) = item.word() {
|
},
|
||||||
mode.view.push(word.into());
|
};
|
||||||
|
} else if let Ok(Some(word)) = dsl.word() {
|
||||||
|
self.view.push(word.into());
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("load_mode_one: unexpected: {item:?}").into());
|
return Err(format!("Mode::load_one: unexpected: {dsl:?}").into());
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +185,36 @@ impl<E: Clone + Ord, C> EventMap<E, C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventMap<TuiEvent, Arc<str>> {
|
||||||
|
pub fn load_into (binds: &Binds, name: &impl AsRef<str>, body: &impl Dsl) -> Usually<()> {
|
||||||
|
println!("EventMap::load_into: {}: {body:?}", name.as_ref());
|
||||||
|
let mut map = Self::new();
|
||||||
|
body.each(|item|if item.expr().head() == Ok(Some("see")) {
|
||||||
|
// TODO
|
||||||
|
Ok(())
|
||||||
|
} else if let Ok(Some(word)) = item.expr().head().word() {
|
||||||
|
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
|
||||||
|
map.add(key, Binding {
|
||||||
|
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
||||||
|
condition: None,
|
||||||
|
description: None,
|
||||||
|
source: None
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else if Some(":char") == item.expr()?.head()? {
|
||||||
|
// TODO
|
||||||
|
return Ok(())
|
||||||
|
} else {
|
||||||
|
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
||||||
|
})?;
|
||||||
|
binds.write().unwrap().insert(name.as_ref().into(), map);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<C> Binding<C> {
|
impl<C> Binding<C> {
|
||||||
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
|
||||||
let command: Option<C> = None;
|
let command: Option<C> = None;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ winit = { workspace = true, optional = true }
|
||||||
default = [ "arranger", "sampler", "lv2" ]
|
default = [ "arranger", "sampler", "lv2" ]
|
||||||
|
|
||||||
arranger = [ "port", "editor", "sequencer", "editor" ]
|
arranger = [ "port", "editor", "sequencer", "editor" ]
|
||||||
browser = []
|
browse = []
|
||||||
clap = []
|
clap = []
|
||||||
clock = []
|
clock = []
|
||||||
editor = []
|
editor = []
|
||||||
|
|
@ -28,7 +28,7 @@ meter = []
|
||||||
mixer = []
|
mixer = []
|
||||||
pool = []
|
pool = []
|
||||||
port = []
|
port = []
|
||||||
sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ]
|
sampler = [ "port", "meter", "mixer", "browse", "symphonia", "wavers" ]
|
||||||
sequencer = [ "port", "clock", "uuid", "pool" ]
|
sequencer = [ "port", "clock", "uuid", "pool" ]
|
||||||
sf2 = []
|
sf2 = []
|
||||||
vst2 = []
|
vst2 = []
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use crate::*;
|
||||||
|
|
||||||
mod arranger_api; pub use self::arranger_api::*;
|
mod arranger_api; pub use self::arranger_api::*;
|
||||||
mod arranger_clip; pub use self::arranger_clip::*;
|
mod arranger_clip; pub use self::arranger_clip::*;
|
||||||
mod arranger_model; pub use self::arranger_model::*;
|
|
||||||
mod arranger_scenes; pub use self::arranger_scenes::*;
|
mod arranger_scenes; pub use self::arranger_scenes::*;
|
||||||
mod arranger_select; pub use self::arranger_select::*;
|
mod arranger_select; pub use self::arranger_select::*;
|
||||||
mod arranger_tracks; pub use self::arranger_tracks::*;
|
mod arranger_tracks; pub use self::arranger_tracks::*;
|
||||||
|
|
@ -19,3 +18,187 @@ pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl
|
||||||
let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌")));
|
let right = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▌")));
|
||||||
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
Bsp::e(left, Bsp::w(right, Tui::fg_bg(fg, bg, content)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)] pub struct Arrangement {
|
||||||
|
/// Project name.
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Base color.
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Jack client handle
|
||||||
|
pub jack: Jack<'static>,
|
||||||
|
/// Source of time
|
||||||
|
pub clock: Clock,
|
||||||
|
/// Allows one MIDI clip to be edited
|
||||||
|
pub editor: Option<MidiEditor>,
|
||||||
|
/// List of global midi inputs
|
||||||
|
pub midi_ins: Vec<MidiInput>,
|
||||||
|
/// List of global midi outputs
|
||||||
|
pub midi_outs: Vec<MidiOutput>,
|
||||||
|
/// List of global audio inputs
|
||||||
|
pub audio_ins: Vec<AudioInput>,
|
||||||
|
/// List of global audio outputs
|
||||||
|
pub audio_outs: Vec<AudioOutput>,
|
||||||
|
/// Last track number (to avoid duplicate port names)
|
||||||
|
pub track_last: usize,
|
||||||
|
/// List of tracks
|
||||||
|
pub tracks: Vec<Track>,
|
||||||
|
/// Scroll offset of tracks
|
||||||
|
pub track_scroll: usize,
|
||||||
|
/// List of scenes
|
||||||
|
pub scenes: Vec<Scene>,
|
||||||
|
/// Scroll offset of scenes
|
||||||
|
pub scene_scroll: usize,
|
||||||
|
/// Selected UI element
|
||||||
|
pub selection: Selection,
|
||||||
|
/// Contains a render of the project arrangement, redrawn on update.
|
||||||
|
/// TODO rename to "render_cache" or smth
|
||||||
|
pub arranger: Arc<RwLock<Buffer>>,
|
||||||
|
/// Display size
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// Display size of clips area
|
||||||
|
pub inner_size: Measure<TuiOut>,
|
||||||
|
}
|
||||||
|
impl HasJack<'static> for Arrangement {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
has!(Jack<'static>: |self: Arrangement|self.jack);
|
||||||
|
has!(Clock: |self: Arrangement|self.clock);
|
||||||
|
has!(Selection: |self: Arrangement|self.selection);
|
||||||
|
has!(Vec<MidiInput>: |self: Arrangement|self.midi_ins);
|
||||||
|
has!(Vec<MidiOutput>: |self: Arrangement|self.midi_outs);
|
||||||
|
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||||
|
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||||
|
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
||||||
|
has!(Option<MidiEditor>: |self: Arrangement|self.editor);
|
||||||
|
maybe_has!(Track: |self: Arrangement|
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
||||||
|
maybe_has!(Scene: |self: Arrangement|
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
||||||
|
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
||||||
|
//take!(MidiInputCommand |state: Arrangement, iter|state.selected_midi_in().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
//take!(MidiOutputCommand |state: Arrangement, iter|state.selected_midi_out().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||||
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
#[tengri_proc::expose] impl Arrangement {
|
||||||
|
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
||||||
|
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
||||||
|
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||||
|
fn selected_track (&self) -> Option<Track> { todo!() }
|
||||||
|
fn selected_scene (&self) -> Option<Scene> { todo!() }
|
||||||
|
fn selected_clip (&self) -> Option<MidiClip> { todo!() }
|
||||||
|
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||||
|
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||||
|
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||||
|
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||||
|
fn select_nothing (&self) -> Selection {
|
||||||
|
Selection::Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Arrangement {
|
||||||
|
/// Width of display
|
||||||
|
pub fn w (&self) -> u16 {
|
||||||
|
self.size.w() as u16
|
||||||
|
}
|
||||||
|
/// Width allocated for sidebar.
|
||||||
|
pub fn w_sidebar (&self, is_editing: bool) -> u16 {
|
||||||
|
self.w() / if is_editing { 16 } else { 8 } as u16
|
||||||
|
}
|
||||||
|
/// Width available to display tracks.
|
||||||
|
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
||||||
|
self.w().saturating_sub(self.w_sidebar(is_editing))
|
||||||
|
}
|
||||||
|
/// Height of display
|
||||||
|
pub fn h (&self) -> u16 {
|
||||||
|
self.size.h() as u16
|
||||||
|
}
|
||||||
|
/// Height taken by visible device slots.
|
||||||
|
pub fn h_devices (&self) -> u16 {
|
||||||
|
2
|
||||||
|
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||||
|
}
|
||||||
|
/// Get the active track
|
||||||
|
pub fn get_track (&self) -> Option<&Track> {
|
||||||
|
let index = self.selection().track()?;
|
||||||
|
Has::<Vec<Track>>::get(self).get(index)
|
||||||
|
}
|
||||||
|
/// Get a mutable reference to the active track
|
||||||
|
pub fn get_track_mut (&mut self) -> Option<&mut Track> {
|
||||||
|
let index = self.selection().track()?;
|
||||||
|
Has::<Vec<Track>>::get_mut(self).get_mut(index)
|
||||||
|
}
|
||||||
|
/// Get the active scene
|
||||||
|
pub fn get_scene (&self) -> Option<&Scene> {
|
||||||
|
let index = self.selection().scene()?;
|
||||||
|
Has::<Vec<Scene>>::get(self).get(index)
|
||||||
|
}
|
||||||
|
/// Get a mutable reference to the active scene
|
||||||
|
pub fn get_scene_mut (&mut self) -> Option<&mut Scene> {
|
||||||
|
let index = self.selection().scene()?;
|
||||||
|
Has::<Vec<Scene>>::get_mut(self).get_mut(index)
|
||||||
|
}
|
||||||
|
/// Get the active clip
|
||||||
|
pub fn get_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
|
self.get_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||||
|
}
|
||||||
|
/// 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Toggle looping for the active clip
|
||||||
|
pub fn toggle_loop (&mut self) {
|
||||||
|
if let Some(clip) = self.get_clip() {
|
||||||
|
clip.write().unwrap().toggle_loop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "sampler")]
|
||||||
|
impl Arrangement {
|
||||||
|
/// Get the first sampler of the active track
|
||||||
|
pub fn sampler (&self) -> Option<&Sampler> {
|
||||||
|
self.get_track()?.sampler(0)
|
||||||
|
}
|
||||||
|
/// Get the first sampler of the active track
|
||||||
|
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
|
||||||
|
self.get_track_mut()?.sampler_mut(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScenesView for Arrangement {
|
||||||
|
fn h_scenes (&self) -> u16 {
|
||||||
|
(self.height() as u16).saturating_sub(20)
|
||||||
|
}
|
||||||
|
fn w_side (&self) -> u16 {
|
||||||
|
(self.width() as u16 * 2 / 10).max(20)
|
||||||
|
}
|
||||||
|
fn w_mid (&self) -> u16 {
|
||||||
|
(self.width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
#[tengri_proc::command(Arrangement)]
|
|
||||||
impl ArrangementCommand {
|
def_command!(ArrangementCommand: |arranger: Arrangement| {
|
||||||
fn home (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
arranger.editor = None;
|
Home => { arranger.editor = None; Ok(None) },
|
||||||
Ok(None)
|
|
||||||
}
|
Edit => {
|
||||||
fn edit (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
let selection = arranger.selection().clone();
|
let selection = arranger.selection().clone();
|
||||||
arranger.editor = if arranger.editor.is_some() {
|
arranger.editor = if arranger.editor.is_some() {
|
||||||
None
|
None
|
||||||
|
|
@ -36,14 +35,13 @@ impl ArrangementCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
/// Set the selection
|
|
||||||
fn select (arranger: &mut Arrangement, s: Selection) -> Perhaps<Self> {
|
//// Set the selection
|
||||||
*arranger.selection_mut() = s;
|
Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) },
|
||||||
Ok(None)
|
|
||||||
}
|
//// Launch the selected clip or scene
|
||||||
/// Launch a clip or scene
|
Launch => {
|
||||||
fn launch (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
match *arranger.selection() {
|
match *arranger.selection() {
|
||||||
Selection::Track(t) => {
|
Selection::Track(t) => {
|
||||||
arranger.tracks[t].sequencer.enqueue_next(None)
|
arranger.tracks[t].sequencer.enqueue_next(None)
|
||||||
|
|
@ -59,9 +57,10 @@ impl ArrangementCommand {
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
/// Set the color of the selected entity
|
|
||||||
fn set_color (arranger: &mut Arrangement, palette: Option<ItemTheme>) -> Perhaps<Self> {
|
//// Set the color of the selected entity
|
||||||
|
SetColor { palette: Option<ItemTheme> } => {
|
||||||
let mut palette = palette.unwrap_or_else(||ItemTheme::random());
|
let mut palette = palette.unwrap_or_else(||ItemTheme::random());
|
||||||
let selection = *arranger.selection();
|
let selection = *arranger.selection();
|
||||||
Ok(Some(Self::SetColor { palette: Some(match selection {
|
Ok(Some(Self::SetColor { palette: Some(match selection {
|
||||||
|
|
@ -88,11 +87,11 @@ impl ArrangementCommand {
|
||||||
},
|
},
|
||||||
_ => todo!()
|
_ => todo!()
|
||||||
}) }))
|
}) }))
|
||||||
}
|
},
|
||||||
fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps<Self> {
|
|
||||||
todo!("delegate")
|
Track { track: TrackCommand } => { todo!("delegate") },
|
||||||
}
|
|
||||||
fn track_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
TrackAdd => {
|
||||||
let index = arranger.track_add(None, None, &[], &[])?.0;
|
let index = arranger.track_add(None, None, &[], &[])?.0;
|
||||||
*arranger.selection_mut() = match arranger.selection() {
|
*arranger.selection_mut() = match arranger.selection() {
|
||||||
Selection::Track(_) => Selection::Track(index),
|
Selection::Track(_) => Selection::Track(index),
|
||||||
|
|
@ -102,12 +101,16 @@ impl ArrangementCommand {
|
||||||
_ => *arranger.selection()
|
_ => *arranger.selection()
|
||||||
};
|
};
|
||||||
Ok(Some(Self::TrackDelete { index }))
|
Ok(Some(Self::TrackDelete { index }))
|
||||||
}
|
},
|
||||||
fn track_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps<Self> {
|
|
||||||
todo!();
|
TrackSwap { index: usize, other: usize } => {
|
||||||
|
let index = *index;
|
||||||
|
let other = *other;
|
||||||
Ok(Some(Self::TrackSwap { index, other }))
|
Ok(Some(Self::TrackSwap { index, other }))
|
||||||
}
|
},
|
||||||
fn track_delete (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
|
|
||||||
|
TrackDelete { index: usize } => {
|
||||||
|
let index = *index;
|
||||||
let exists = arranger.tracks().get(index).is_some();
|
let exists = arranger.tracks().get(index).is_some();
|
||||||
if exists {
|
if exists {
|
||||||
let track = arranger.tracks_mut().remove(index);
|
let track = arranger.tracks_mut().remove(index);
|
||||||
|
|
@ -124,52 +127,61 @@ impl ArrangementCommand {
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
//TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
|
//TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
|
||||||
}
|
},
|
||||||
fn midi_in (_arranger: &mut Arrangement, _input: MidiInputCommand) -> Perhaps<Self> {
|
|
||||||
todo!("delegate");
|
MidiIn { input: MidiInputCommand } => {
|
||||||
Ok(None)
|
todo!("delegate"); Ok(None)
|
||||||
}
|
},
|
||||||
fn midi_in_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
|
MidiInAdd => {
|
||||||
arranger.midi_in_add()?;
|
arranger.midi_in_add()?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn midi_out (_arranger: &mut Arrangement, _input: MidiOutputCommand) -> Perhaps<Self> {
|
|
||||||
|
MidiOut { output: MidiOutputCommand } => {
|
||||||
todo!("delegate");
|
todo!("delegate");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn midi_out_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
|
MidiOutAdd => {
|
||||||
arranger.midi_out_add()?;
|
arranger.midi_out_add()?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn device (_arranger: &mut Arrangement, _input: DeviceCommand) -> Perhaps<Self> {
|
|
||||||
|
Device { command: DeviceCommand } => {
|
||||||
todo!("delegate");
|
todo!("delegate");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn device_add (_arranger: &mut Arrangement, _i: usize) -> Perhaps<Self> {
|
|
||||||
|
DeviceAdd { index: usize } => {
|
||||||
todo!("delegate");
|
todo!("delegate");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn scene (arranger: &mut Arrangement, scene: SceneCommand) -> Perhaps<Self> {
|
|
||||||
|
Scene { scene: SceneCommand } => {
|
||||||
todo!("delegate");
|
todo!("delegate");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
|
OutputAdd => {
|
||||||
arranger.midi_outs.push(MidiOutput::new(
|
arranger.midi_outs.push(MidiOutput::new(
|
||||||
arranger.jack(),
|
arranger.jack(),
|
||||||
&format!("/M{}", arranger.midi_outs.len() + 1),
|
&format!("/M{}", arranger.midi_outs.len() + 1),
|
||||||
&[]
|
&[]
|
||||||
)?);
|
)?);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
|
InputAdd => {
|
||||||
arranger.midi_ins.push(MidiInput::new(
|
arranger.midi_ins.push(MidiInput::new(
|
||||||
arranger.jack(),
|
arranger.jack(),
|
||||||
&format!("M{}/", arranger.midi_ins.len() + 1),
|
&format!("M{}/", arranger.midi_ins.len() + 1),
|
||||||
&[]
|
&[]
|
||||||
)?);
|
)?);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn scene_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
|
||||||
|
SceneAdd => {
|
||||||
let index = arranger.scene_add(None, None)?.0;
|
let index = arranger.scene_add(None, None)?.0;
|
||||||
*arranger.selection_mut() = match arranger.selection() {
|
*arranger.selection_mut() = match arranger.selection() {
|
||||||
Selection::Scene(_) => Selection::Scene(index),
|
Selection::Scene(_) => Selection::Scene(index),
|
||||||
|
|
@ -180,12 +192,16 @@ impl ArrangementCommand {
|
||||||
_ => *arranger.selection()
|
_ => *arranger.selection()
|
||||||
};
|
};
|
||||||
Ok(None) // TODO
|
Ok(None) // TODO
|
||||||
}
|
},
|
||||||
fn scene_swap (arranger: &mut Arrangement, index: usize, other: usize) -> Perhaps<Self> {
|
|
||||||
todo!();
|
SceneSwap { index: usize, other: usize } => {
|
||||||
|
let index = *index;
|
||||||
|
let other = *other;
|
||||||
Ok(Some(Self::SceneSwap { index, other }))
|
Ok(Some(Self::SceneSwap { index, other }))
|
||||||
}
|
},
|
||||||
fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
|
|
||||||
|
SceneDelete { index: usize } => {
|
||||||
|
let index = *index;
|
||||||
let scenes = arranger.scenes_mut();
|
let scenes = arranger.scenes_mut();
|
||||||
Ok(if scenes.get(index).is_some() {
|
Ok(if scenes.get(index).is_some() {
|
||||||
let _scene = scenes.remove(index);
|
let _scene = scenes.remove(index);
|
||||||
|
|
@ -193,41 +209,50 @@ impl ArrangementCommand {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
fn scene_launch (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
|
|
||||||
|
SceneLaunch { index: usize } => {
|
||||||
|
let index = *index;
|
||||||
for track in 0..arranger.tracks.len() {
|
for track in 0..arranger.tracks.len() {
|
||||||
let clip = arranger.scenes[index].clips[track].as_ref();
|
let clip = arranger.scenes[index].clips[track].as_ref();
|
||||||
arranger.tracks[track].sequencer.enqueue_next(clip);
|
arranger.tracks[track].sequencer.enqueue_next(clip);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
},
|
||||||
fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps<Self> {
|
|
||||||
|
Clip { scene: ClipCommand } => {
|
||||||
todo!("delegate")
|
todo!("delegate")
|
||||||
}
|
},
|
||||||
fn clip_get (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
|
|
||||||
|
ClipGet { a: usize, b: usize } => {
|
||||||
//(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
//(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}"))
|
||||||
//("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
//("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
fn clip_put (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
|
|
||||||
|
ClipPut { a: usize, b: usize } => {
|
||||||
//(Put [t: usize, s: usize, c: MaybeClip]
|
//(Put [t: usize, s: usize, c: MaybeClip]
|
||||||
//Some(Self::Put(t, s, arranger.clip_put(t, s, c))))
|
//Some(Self::Put(t, s, arranger.clip_put(t, s, c))))
|
||||||
//("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
//("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
fn clip_del (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
|
|
||||||
|
ClipDel { a: usize, b: usize } => {
|
||||||
//("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
|
//("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None))))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
fn clip_enqueue (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
|
|
||||||
|
ClipEnqueue { a: usize, b: usize } => {
|
||||||
//(Enqueue [t: usize, s: usize]
|
//(Enqueue [t: usize, s: usize]
|
||||||
//cmd!(arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())))
|
//cmd!(arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref())))
|
||||||
//("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
//("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
fn clip_edit (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
|
|
||||||
|
ClipSwap { a: usize, b: usize }=> {
|
||||||
//(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
//(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}"))
|
||||||
//("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
//("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,19 @@ impl MidiClip {
|
||||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(MidiClip)]
|
def_command!(ClipCommand: |clip: MidiClip| {
|
||||||
impl ClipCommand {
|
|
||||||
fn set_color (clip: &mut MidiClip, color: Option<ItemTheme>) -> Perhaps<Self> {
|
SetColor { color: Option<ItemTheme> } => {
|
||||||
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
//(SetColor [t: usize, s: usize, c: ItemTheme]
|
||||||
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
|
//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())))
|
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
},
|
||||||
fn set_loop (clip: &mut MidiClip, looping: Option<bool>) -> Perhaps<Self> {
|
|
||||||
|
SetLoop { looping: Option<bool> } => {
|
||||||
//(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}"))
|
//(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())))
|
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,184 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
#[derive(Default, Debug)] pub struct Arrangement {
|
|
||||||
/// Project name.
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Base color.
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Jack client handle
|
|
||||||
pub jack: Jack<'static>,
|
|
||||||
/// Source of time
|
|
||||||
pub clock: Clock,
|
|
||||||
/// Allows one MIDI clip to be edited
|
|
||||||
pub editor: Option<MidiEditor>,
|
|
||||||
/// List of global midi inputs
|
|
||||||
pub midi_ins: Vec<MidiInput>,
|
|
||||||
/// List of global midi outputs
|
|
||||||
pub midi_outs: Vec<MidiOutput>,
|
|
||||||
/// List of global audio inputs
|
|
||||||
pub audio_ins: Vec<AudioInput>,
|
|
||||||
/// List of global audio outputs
|
|
||||||
pub audio_outs: Vec<AudioOutput>,
|
|
||||||
/// Last track number (to avoid duplicate port names)
|
|
||||||
pub track_last: usize,
|
|
||||||
/// List of tracks
|
|
||||||
pub tracks: Vec<Track>,
|
|
||||||
/// Scroll offset of tracks
|
|
||||||
pub track_scroll: usize,
|
|
||||||
/// List of scenes
|
|
||||||
pub scenes: Vec<Scene>,
|
|
||||||
/// Scroll offset of scenes
|
|
||||||
pub scene_scroll: usize,
|
|
||||||
/// Selected UI element
|
|
||||||
pub selection: Selection,
|
|
||||||
/// Contains a render of the project arrangement, redrawn on update.
|
|
||||||
/// TODO rename to "render_cache" or smth
|
|
||||||
pub arranger: Arc<RwLock<Buffer>>,
|
|
||||||
/// Display size
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// Display size of clips area
|
|
||||||
pub inner_size: Measure<TuiOut>,
|
|
||||||
}
|
|
||||||
impl HasJack<'static> for Arrangement {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
has!(Jack<'static>: |self: Arrangement|self.jack);
|
|
||||||
has!(Clock: |self: Arrangement|self.clock);
|
|
||||||
has!(Selection: |self: Arrangement|self.selection);
|
|
||||||
has!(Vec<MidiInput>: |self: Arrangement|self.midi_ins);
|
|
||||||
has!(Vec<MidiOutput>: |self: Arrangement|self.midi_outs);
|
|
||||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
|
||||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
|
||||||
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
|
||||||
has!(Option<MidiEditor>: |self: Arrangement|self.editor);
|
|
||||||
maybe_has!(Track: |self: Arrangement|
|
|
||||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
|
||||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
|
||||||
maybe_has!(Scene: |self: Arrangement|
|
|
||||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
|
||||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
|
||||||
//take!(MidiInputCommand |state: Arrangement, iter|state.selected_midi_in().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
//take!(MidiOutputCommand |state: Arrangement, iter|state.selected_midi_out().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
|
||||||
#[tengri_proc::expose] impl Arrangement {
|
|
||||||
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
|
||||||
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
|
||||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
|
||||||
fn selected_track (&self) -> Option<Track> { todo!() }
|
|
||||||
fn selected_scene (&self) -> Option<Scene> { todo!() }
|
|
||||||
fn selected_clip (&self) -> Option<MidiClip> { todo!() }
|
|
||||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
|
||||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
|
||||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
|
||||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
|
||||||
fn select_nothing (&self) -> Selection {
|
|
||||||
Selection::Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Arrangement {
|
|
||||||
/// Width of display
|
|
||||||
pub fn w (&self) -> u16 {
|
|
||||||
self.size.w() as u16
|
|
||||||
}
|
|
||||||
/// Width allocated for sidebar.
|
|
||||||
pub fn w_sidebar (&self, is_editing: bool) -> u16 {
|
|
||||||
self.w() / if is_editing { 16 } else { 8 } as u16
|
|
||||||
}
|
|
||||||
/// Width available to display tracks.
|
|
||||||
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
|
||||||
self.w().saturating_sub(self.w_sidebar(is_editing))
|
|
||||||
}
|
|
||||||
/// Height of display
|
|
||||||
pub fn h (&self) -> u16 {
|
|
||||||
self.size.h() as u16
|
|
||||||
}
|
|
||||||
/// Height taken by visible device slots.
|
|
||||||
pub fn h_devices (&self) -> u16 {
|
|
||||||
2
|
|
||||||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
|
||||||
}
|
|
||||||
/// Get the active track
|
|
||||||
pub fn get_track (&self) -> Option<&Track> {
|
|
||||||
let index = self.selection().track()?;
|
|
||||||
Has::<Vec<Track>>::get(self).get(index)
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the active track
|
|
||||||
pub fn get_track_mut (&mut self) -> Option<&mut Track> {
|
|
||||||
let index = self.selection().track()?;
|
|
||||||
Has::<Vec<Track>>::get_mut(self).get_mut(index)
|
|
||||||
}
|
|
||||||
/// Get the active scene
|
|
||||||
pub fn get_scene (&self) -> Option<&Scene> {
|
|
||||||
let index = self.selection().scene()?;
|
|
||||||
Has::<Vec<Scene>>::get(self).get(index)
|
|
||||||
}
|
|
||||||
/// Get a mutable reference to the active scene
|
|
||||||
pub fn get_scene_mut (&mut self) -> Option<&mut Scene> {
|
|
||||||
let index = self.selection().scene()?;
|
|
||||||
Has::<Vec<Scene>>::get_mut(self).get_mut(index)
|
|
||||||
}
|
|
||||||
/// Get the active clip
|
|
||||||
pub fn get_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
|
||||||
self.get_scene()?.clips.get(self.selection().track()?)?.clone()
|
|
||||||
}
|
|
||||||
/// 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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Toggle looping for the active clip
|
|
||||||
pub fn toggle_loop (&mut self) {
|
|
||||||
if let Some(clip) = self.get_clip() {
|
|
||||||
clip.write().unwrap().toggle_loop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sampler")]
|
|
||||||
impl Arrangement {
|
|
||||||
/// Get the first sampler of the active track
|
|
||||||
pub fn sampler (&self) -> Option<&Sampler> {
|
|
||||||
self.get_track()?.sampler(0)
|
|
||||||
}
|
|
||||||
/// Get the first sampler of the active track
|
|
||||||
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
|
|
||||||
self.get_track_mut()?.sampler_mut(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScenesView for Arrangement {
|
|
||||||
fn h_scenes (&self) -> u16 {
|
|
||||||
(self.height() as u16).saturating_sub(20)
|
|
||||||
}
|
|
||||||
fn w_side (&self) -> u16 {
|
|
||||||
(self.width() as u16 * 2 / 10).max(20)
|
|
||||||
}
|
|
||||||
fn w_mid (&self) -> u16 {
|
|
||||||
(self.width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -64,23 +64,14 @@ impl Scene {
|
||||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(Scene)]
|
def_command!(SceneCommand: |scene: Scene| {
|
||||||
impl SceneCommand {
|
SetSize { size: usize } => { todo!() },
|
||||||
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
|
SetZoom { size: usize } => { todo!() },
|
||||||
std::mem::swap(&mut scene.name, &mut name);
|
SetName { name: Arc<str> } =>
|
||||||
Ok(Some(Self::SetName { name }))
|
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
|
||||||
}
|
SetColor { color: ItemTheme } =>
|
||||||
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
|
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
|
||||||
std::mem::swap(&mut scene.color, &mut color);
|
});
|
||||||
Ok(Some(Self::SetColor { color }))
|
|
||||||
}
|
|
||||||
fn set_size (scene: &mut Scene, size: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,43 +129,21 @@ impl Track {
|
||||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(Track)]
|
def_command!(TrackCommand: |track: Track| {
|
||||||
impl TrackCommand {
|
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
|
||||||
fn set_name (track: &mut Track, mut name: Arc<str>) -> Perhaps<Self> {
|
SetMute { mute: Option<bool> } => todo!(),
|
||||||
std::mem::swap(&mut name, &mut track.name);
|
SetSolo { solo: Option<bool> } => todo!(),
|
||||||
Ok(Some(Self::SetName { name }))
|
SetSize { size: usize } => todo!(),
|
||||||
}
|
SetZoom { zoom: usize } => todo!(),
|
||||||
fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps<Self> {
|
SetName { name: Arc<str> } =>
|
||||||
std::mem::swap(&mut color, &mut track.color);
|
swap_value(&mut track.name, name, |name|Self::SetName { name }),
|
||||||
Ok(Some(Self::SetColor { color }))
|
SetColor { color: ItemTheme } =>
|
||||||
}
|
swap_value(&mut track.color, color, |color|Self::SetColor { color }),
|
||||||
fn set_mute (_track: &mut Track, _value: Option<bool>) -> Perhaps<Self> {
|
SetRec { rec: Option<bool> } =>
|
||||||
todo!()
|
toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
|
||||||
}
|
SetMon { mon: Option<bool> } =>
|
||||||
fn set_solo (_track: &mut Track, _value: Option<bool>) -> Perhaps<Self> {
|
toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
|
||||||
todo!()
|
});
|
||||||
}
|
|
||||||
fn set_rec (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
let current = track.sequencer.recording;
|
|
||||||
let value = value.unwrap_or(!current);
|
|
||||||
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
|
||||||
}
|
|
||||||
fn set_mon (track: &mut Track, value: Option<bool>) -> Perhaps<Self> {
|
|
||||||
let current = track.sequencer.monitoring;
|
|
||||||
let value = value.unwrap_or(!current);
|
|
||||||
Ok((value != current).then_some(Self::SetRec { value: Some(current) }))
|
|
||||||
}
|
|
||||||
fn set_size (_track: &mut Track, _size: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_zoom (_track: &mut Track, _zoom: usize) -> Perhaps<Self> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn stop (track: &mut Track) -> Perhaps<Self> {
|
|
||||||
track.sequencer.enqueue_next(None);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
mod browse_api; pub use self::browse_api::*;
|
||||||
|
mod browse_view; //pub use self::browse_view::*;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BrowserTarget {
|
pub enum BrowseTarget {
|
||||||
SaveProject,
|
SaveProject,
|
||||||
LoadProject,
|
LoadProject,
|
||||||
ImportSample(Arc<RwLock<Option<Sample>>>),
|
ImportSample(Arc<RwLock<Option<Sample>>>),
|
||||||
|
|
@ -12,9 +14,21 @@ pub enum BrowserTarget {
|
||||||
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
ExportClip(Arc<RwLock<Option<MidiClip>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for BrowseTarget {
|
||||||
|
fn eq (&self, other: &Self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::ImportSample(_) => false,
|
||||||
|
Self::ExportSample(_) => false,
|
||||||
|
Self::ImportClip(_) => false,
|
||||||
|
Self::ExportClip(_) => false,
|
||||||
|
t => matches!(other, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Browses for phrase to import/export
|
/// Browses for phrase to import/export
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
pub struct Browser {
|
pub struct Browse {
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
pub dirs: Vec<(OsString, String)>,
|
pub dirs: Vec<(OsString, String)>,
|
||||||
pub files: Vec<(OsString, String)>,
|
pub files: Vec<(OsString, String)>,
|
||||||
|
|
@ -24,7 +38,7 @@ pub struct Browser {
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Browser {
|
impl Browse {
|
||||||
|
|
||||||
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
|
||||||
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };
|
||||||
93
crates/device/src/browse/browse_api.rs
Normal file
93
crates/device/src/browse/browse_api.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[tengri_proc::expose]
|
||||||
|
impl Browse {
|
||||||
|
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn _todo_stub_usize (&self) -> usize {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn _todo_stub_arc_str (&self) -> Arc<str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def_command!(BrowseCommand: |browse: Browse| {
|
||||||
|
SetVisible => Ok(None),
|
||||||
|
SetPath { address: PathBuf } => Ok(None),
|
||||||
|
SetSearch { filter: Arc<str> } => Ok(None),
|
||||||
|
SetCursor { cursor: usize } => Ok(None),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Commands supported by [Browse]
|
||||||
|
//#[derive(Debug, Clone, PartialEq)]
|
||||||
|
//pub enum BrowseCommand {
|
||||||
|
//Begin,
|
||||||
|
//Cancel,
|
||||||
|
//Confirm,
|
||||||
|
//Select(usize),
|
||||||
|
//Chdir(PathBuf),
|
||||||
|
//Filter(Arc<str>),
|
||||||
|
//}
|
||||||
|
//fn begin (browse: &mut Browse) => {
|
||||||
|
//unreachable!();
|
||||||
|
//}
|
||||||
|
//fn cancel (browse: &mut Browse) => {
|
||||||
|
//todo!()
|
||||||
|
////browse.mode = None;
|
||||||
|
////Ok(None)
|
||||||
|
//}
|
||||||
|
//fn confirm (browse: &mut Browse) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////if browse.is_file() {
|
||||||
|
////let path = browse.path();
|
||||||
|
////browse.mode = None;
|
||||||
|
////let _undo = PoolClipCommand::import(browse, index, path)?;
|
||||||
|
////None
|
||||||
|
////} else if browse.is_dir() {
|
||||||
|
////browse.mode = Some(PoolMode::Import(index, browse.chdir()?));
|
||||||
|
////None
|
||||||
|
////} else {
|
||||||
|
////None
|
||||||
|
////}
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////todo!()
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn select (browse: &mut Browse, index: usize) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////browse.index = index;
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////browse.index = index;
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn chdir (browse: &mut Browse, dir: PathBuf) => {
|
||||||
|
//todo!()
|
||||||
|
////Ok(match browse.mode {
|
||||||
|
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||||
|
////browse.mode = Some(PoolMode::Import(index, Browse::new(Some(dir))?));
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||||
|
////browse.mode = Some(PoolMode::Export(index, Browse::new(Some(dir))?));
|
||||||
|
////None
|
||||||
|
////},
|
||||||
|
////_ => unreachable!(),
|
||||||
|
////})
|
||||||
|
//}
|
||||||
|
//fn filter (browse: &mut Browse, filter: Arc<str>) => {
|
||||||
|
//todo!()
|
||||||
|
//}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator {
|
content!(TuiOut: |self: Browse|Map::south(1, ||EntriesIterator {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
index: 0,
|
index: 0,
|
||||||
length: self.dirs.len() + self.files.len(),
|
length: self.dirs.len() + self.files.len(),
|
||||||
|
|
@ -8,7 +8,7 @@ content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator {
|
||||||
}, |entry, _index|Fill::x(Align::w(entry))));
|
}, |entry, _index|Fill::x(Align::w(entry))));
|
||||||
|
|
||||||
struct EntriesIterator<'a> {
|
struct EntriesIterator<'a> {
|
||||||
browser: &'a Browser,
|
browser: &'a Browse,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
length: usize,
|
length: usize,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
mod browser_api; pub use self::browser_api::*;
|
|
||||||
mod browser_model; pub use self::browser_model::*;
|
|
||||||
mod browser_view; //pub use self::browser_view::*;
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
|
||||||
impl Browser {
|
|
||||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn _todo_stub_usize (&self) -> usize {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn _todo_stub_arc_str (&self) -> Arc<str> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::command(Browser)]
|
|
||||||
impl BrowserCommand {
|
|
||||||
fn set_visible (browser: &mut Browser) -> Perhaps<Self> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_path (browser: &mut Browser, address: PathBuf) -> Perhaps<Self> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_search (browser: &mut Browser, filter: Arc<str>) -> Perhaps<Self> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_cursor (browser: &mut Browser, cursor: usize) -> Perhaps<Self> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands supported by [Browser]
|
|
||||||
//#[derive(Debug, Clone, PartialEq)]
|
|
||||||
//pub enum BrowserCommand {
|
|
||||||
//Begin,
|
|
||||||
//Cancel,
|
|
||||||
//Confirm,
|
|
||||||
//Select(usize),
|
|
||||||
//Chdir(PathBuf),
|
|
||||||
//Filter(Arc<str>),
|
|
||||||
//}
|
|
||||||
//fn begin (browser: &mut Browser) -> Perhaps<Self> {
|
|
||||||
//unreachable!();
|
|
||||||
//}
|
|
||||||
//fn cancel (browser: &mut Browser) -> Perhaps<Self> {
|
|
||||||
//todo!()
|
|
||||||
////browser.mode = None;
|
|
||||||
////Ok(None)
|
|
||||||
//}
|
|
||||||
//fn confirm (browser: &mut Browser) -> Perhaps<Self> {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browser.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
|
||||||
////if browser.is_file() {
|
|
||||||
////let path = browser.path();
|
|
||||||
////browser.mode = None;
|
|
||||||
////let _undo = PoolClipCommand::import(browser, index, path)?;
|
|
||||||
////None
|
|
||||||
////} else if browser.is_dir() {
|
|
||||||
////browser.mode = Some(PoolMode::Import(index, browser.chdir()?));
|
|
||||||
////None
|
|
||||||
////} else {
|
|
||||||
////None
|
|
||||||
////}
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
|
||||||
////todo!()
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn select (browser: &mut Browser, index: usize) -> Perhaps<Self> {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browser.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
|
||||||
////browser.index = index;
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
|
||||||
////browser.index = index;
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn chdir (browser: &mut Browser, dir: PathBuf) -> Perhaps<Self> {
|
|
||||||
//todo!()
|
|
||||||
////Ok(match browser.mode {
|
|
||||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
|
||||||
////browser.mode = Some(PoolMode::Import(index, Browser::new(Some(dir))?));
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
|
||||||
////browser.mode = Some(PoolMode::Export(index, Browser::new(Some(dir))?));
|
|
||||||
////None
|
|
||||||
////},
|
|
||||||
////_ => unreachable!(),
|
|
||||||
////})
|
|
||||||
//}
|
|
||||||
//fn filter (browser: &mut Browser, filter: Arc<str>) -> Perhaps<Self> {
|
|
||||||
//todo!()
|
|
||||||
//}
|
|
||||||
|
|
@ -14,48 +14,33 @@ impl Clock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: HasClock> Command<T> for ClockCommand {
|
impl<T: HasClock> Command<T> for ClockCommand {
|
||||||
fn execute (self, state: &mut T) -> Perhaps<Self> {
|
fn execute (&self, state: &mut T) -> Perhaps<Self> {
|
||||||
self.execute(state.clock_mut()) // awesome
|
self.execute(state.clock_mut()) // awesome
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(Clock)]
|
def_command!(ClockCommand: |clock: Clock| {
|
||||||
impl ClockCommand {
|
SeekUsec { usec: f64 } => {
|
||||||
fn play (state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
|
clock.playhead.update_from_usec(*usec); Ok(None) },
|
||||||
state.play_from(position)?;
|
SeekSample { sample: f64 } => {
|
||||||
Ok(None) // TODO Some(Pause(previousPosition))
|
clock.playhead.update_from_sample(*sample); Ok(None) },
|
||||||
}
|
SeekPulse { pulse: f64 } => {
|
||||||
fn pause (state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
|
clock.playhead.update_from_pulse(*pulse); Ok(None) },
|
||||||
state.pause_at(position)?;
|
SetBpm { bpm: f64 } => Ok(Some(
|
||||||
Ok(None)
|
Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })),
|
||||||
}
|
SetQuant { quant: f64 } => Ok(Some(
|
||||||
fn toggle_playback (state: &mut Clock, position: u32) -> Perhaps<Self> {
|
Self::SetQuant { quant: clock.quant.set(*quant) })),
|
||||||
if state.is_rolling() {
|
SetSync { sync: f64 } => Ok(Some(
|
||||||
state.pause_at(Some(position))?;
|
Self::SetSync { sync: clock.sync.set(*sync) })),
|
||||||
} else {
|
|
||||||
state.play_from(Some(position))?;
|
Play { position: Option<u32> } => {
|
||||||
}
|
clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ },
|
||||||
Ok(None)
|
Pause { position: Option<u32> } => {
|
||||||
}
|
clock.pause_at(*position)?; Ok(None) },
|
||||||
fn seek_usec (state: &mut Clock, usec: f64) -> Perhaps<Self> {
|
|
||||||
state.playhead.update_from_usec(usec);
|
TogglePlayback { position: u32 } => Ok(if clock.is_rolling() {
|
||||||
Ok(None)
|
clock.pause_at(Some(*position))?; None
|
||||||
}
|
} else {
|
||||||
fn seek_sample (state: &mut Clock, sample: f64) -> Perhaps<Self> {
|
clock.play_from(Some(*position))?; None
|
||||||
state.playhead.update_from_sample(sample);
|
}),
|
||||||
Ok(None)
|
});
|
||||||
}
|
|
||||||
fn seek_pulse (state: &mut Clock, pulse: f64) -> Perhaps<Self> {
|
|
||||||
state.playhead.update_from_pulse(pulse);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_bpm (state: &mut Clock, bpm: f64) -> Perhaps<Self> {
|
|
||||||
Ok(Some(Self::SetBpm { bpm: state.timebase().bpm.set(bpm) }))
|
|
||||||
}
|
|
||||||
fn set_quant (state: &mut Clock, quant: f64) -> Perhaps<Self> {
|
|
||||||
Ok(Some(Self::SetQuant { quant: state.quant.set(quant) }))
|
|
||||||
}
|
|
||||||
fn set_sync (state: &mut Clock, sync: f64) -> Perhaps<Self> {
|
|
||||||
Ok(Some(Self::SetSync { sync: state.sync.set(sync) }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,4 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[tengri_proc::command(Device)]
|
def_command!(DeviceCommand: |device: Device| {});
|
||||||
impl DeviceCommand {
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,29 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
def_command!(MidiEditCommand: |editor: MidiEditor| {
|
||||||
|
Show { clip: Option<Arc<RwLock<MidiClip>>> } => {
|
||||||
|
editor.set_clip(clip.as_ref()); editor.redraw(); Ok(None) },
|
||||||
|
DeleteNote => {
|
||||||
|
editor.redraw(); todo!() },
|
||||||
|
AppendNote { advance: bool } => {
|
||||||
|
editor.put_note(*advance); editor.redraw(); Ok(None) },
|
||||||
|
SetNotePos { pos: usize } => {
|
||||||
|
editor.set_note_pos((*pos).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteLen { len: usize } => {
|
||||||
|
editor.set_note_len(*len); editor.redraw(); Ok(None) },
|
||||||
|
SetNoteScroll { scroll: usize } => {
|
||||||
|
editor.set_note_lo((*scroll).min(127)); editor.redraw(); Ok(None) },
|
||||||
|
SetTimePos { pos: usize } => {
|
||||||
|
editor.set_time_pos(*pos); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeScroll { scroll: usize } => {
|
||||||
|
editor.set_time_start(*scroll); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeZoom { zoom: usize } => {
|
||||||
|
editor.set_time_zoom(*zoom); editor.redraw(); Ok(None) },
|
||||||
|
SetTimeLock { lock: bool } => {
|
||||||
|
editor.set_time_lock(*lock); editor.redraw(); Ok(None) },
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
||||||
|
});
|
||||||
|
|
||||||
#[tengri_proc::expose] impl MidiEditor {
|
#[tengri_proc::expose] impl MidiEditor {
|
||||||
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||||
todo!()
|
todo!()
|
||||||
|
|
@ -91,56 +115,3 @@ use crate::*;
|
||||||
!self.get_time_lock()
|
!self.get_time_lock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(MidiEditor)] impl MidiEditCommand {
|
|
||||||
fn append_note (editor: &mut MidiEditor, advance: bool) -> Perhaps<Self> {
|
|
||||||
editor.put_note(advance);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn delete_note (editor: &mut MidiEditor) -> Perhaps<Self> {
|
|
||||||
editor.redraw();
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn set_note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_note_pos(pos.min(127));
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_note_len (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_note_len(value);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_note_lo(value.min(127));
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_time_pos(value);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_time_start(value);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps<Self> {
|
|
||||||
editor.set_time_zoom(value);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn set_time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps<Self> {
|
|
||||||
editor.set_time_lock(value);
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn show (editor: &mut MidiEditor, clip: Option<Arc<RwLock<MidiClip>>>) -> Perhaps<Self> {
|
|
||||||
editor.set_clip(clip.as_ref());
|
|
||||||
editor.redraw();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
// TODO: 1-9 seek markers that by default start every 8th of the clip
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ macro_rules! def_sizes_iter {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*;
|
#[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*;
|
||||||
#[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*;
|
#[cfg(feature = "browse")] mod browse; #[cfg(feature = "browse")] pub use self::browse::*;
|
||||||
#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*;
|
#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*;
|
||||||
#[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*;
|
#[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*;
|
||||||
#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*;
|
#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*;
|
||||||
|
|
@ -85,3 +85,27 @@ pub fn button_3 <'a, K, L, V> (
|
||||||
));
|
));
|
||||||
Tui::bold(true, Bsp::e(key, label))
|
Tui::bold(true, Bsp::e(key, label))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn swap_value <T: Clone + PartialEq, U> (
|
||||||
|
target: &mut T, value: &T, returned: impl Fn(T)->U
|
||||||
|
) -> Perhaps<U> {
|
||||||
|
if *target == *value {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let mut value = value.clone();
|
||||||
|
std::mem::swap(target, &mut value);
|
||||||
|
Ok(Some(returned(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_bool <U> (
|
||||||
|
target: &mut bool, value: &Option<bool>, returned: impl Fn(Option<bool>)->U
|
||||||
|
) -> Perhaps<U> {
|
||||||
|
let mut value = value.unwrap_or(!*target);
|
||||||
|
if value == *target {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
std::mem::swap(target, &mut value);
|
||||||
|
Ok(Some(returned(Some(value))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,213 @@
|
||||||
mod pool_api; pub use self::pool_api::*;
|
use crate::*;
|
||||||
mod pool_model; pub use self::pool_model::*;
|
mod pool_api; pub use self::pool_api::*;
|
||||||
mod pool_view; pub use self::pool_view::*;
|
mod pool_view; pub use self::pool_view::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Pool {
|
||||||
|
pub visible: bool,
|
||||||
|
/// Collection of clips
|
||||||
|
pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||||
|
/// Selected clip
|
||||||
|
pub clip: AtomicUsize,
|
||||||
|
/// Mode switch
|
||||||
|
pub mode: Option<PoolMode>,
|
||||||
|
/// Embedded file browse
|
||||||
|
pub browse: Option<Browse>,
|
||||||
|
}
|
||||||
|
|
||||||
|
//take!(BrowseCommand |state: Pool, iter|Ok(state.browse.as_ref()
|
||||||
|
//.map(|p|Take::take(p, iter))
|
||||||
|
//.transpose()?
|
||||||
|
//.flatten()));
|
||||||
|
|
||||||
|
impl Default for Pool {
|
||||||
|
fn default () -> Self {
|
||||||
|
//use PoolMode::*;
|
||||||
|
Self {
|
||||||
|
visible: true,
|
||||||
|
clips: Arc::from(RwLock::from(vec![])),
|
||||||
|
clip: 0.into(),
|
||||||
|
mode: None,
|
||||||
|
browse: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pool {
|
||||||
|
pub fn clip_index (&self) -> usize {
|
||||||
|
self.clip.load(Relaxed)
|
||||||
|
}
|
||||||
|
pub fn set_clip_index (&self, value: usize) {
|
||||||
|
self.clip.store(value, Relaxed);
|
||||||
|
}
|
||||||
|
pub fn mode (&self) -> &Option<PoolMode> {
|
||||||
|
&self.mode
|
||||||
|
}
|
||||||
|
pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
|
||||||
|
&mut self.mode
|
||||||
|
}
|
||||||
|
pub fn begin_clip_length (&mut self) {
|
||||||
|
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
||||||
|
*self.mode_mut() = Some(PoolMode::Length(
|
||||||
|
self.clip_index(),
|
||||||
|
length,
|
||||||
|
ClipLengthFocus::Bar
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pub fn begin_clip_rename (&mut self) {
|
||||||
|
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
||||||
|
*self.mode_mut() = Some(PoolMode::Rename(
|
||||||
|
self.clip_index(),
|
||||||
|
name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pub fn begin_import (&mut self) -> Usually<()> {
|
||||||
|
*self.mode_mut() = Some(PoolMode::Import(
|
||||||
|
self.clip_index(),
|
||||||
|
Browse::new(None)?
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn begin_export (&mut self) -> Usually<()> {
|
||||||
|
*self.mode_mut() = Some(PoolMode::Export(
|
||||||
|
self.clip_index(),
|
||||||
|
Browse::new(None)?
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn new_clip (&self) -> MidiClip {
|
||||||
|
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
|
||||||
|
}
|
||||||
|
pub fn cloned_clip (&self) -> MidiClip {
|
||||||
|
let index = self.clip_index();
|
||||||
|
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
||||||
|
clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||||||
|
clip
|
||||||
|
}
|
||||||
|
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||||
|
let clip = Arc::new(RwLock::new(self.new_clip()));
|
||||||
|
let index = {
|
||||||
|
let mut clips = self.clips.write().unwrap();
|
||||||
|
clips.push(clip.clone());
|
||||||
|
clips.len().saturating_sub(1)
|
||||||
|
};
|
||||||
|
self.clip.store(index, Relaxed);
|
||||||
|
(index, clip)
|
||||||
|
}
|
||||||
|
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
|
||||||
|
let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
||||||
|
if let Some(index) = index {
|
||||||
|
self.clips.write().unwrap().remove(index);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modes for clip pool
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum PoolMode {
|
||||||
|
/// Renaming a pattern
|
||||||
|
Rename(usize, Arc<str>),
|
||||||
|
/// Editing the length of a pattern
|
||||||
|
Length(usize, usize, ClipLengthFocus),
|
||||||
|
/// Load clip from disk
|
||||||
|
Import(usize, Browse),
|
||||||
|
/// Save clip to disk
|
||||||
|
Export(usize, Browse),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Focused field of `ClipLength`
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ClipLengthFocus {
|
||||||
|
/// Editing the number of bars
|
||||||
|
Bar,
|
||||||
|
/// Editing the number of beats
|
||||||
|
Beat,
|
||||||
|
/// Editing the number of ticks
|
||||||
|
Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipLengthFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
use ClipLengthFocus::*;
|
||||||
|
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
use ClipLengthFocus::*;
|
||||||
|
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays and edits clip length.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ClipLength {
|
||||||
|
/// Pulses per beat (quaver)
|
||||||
|
ppq: usize,
|
||||||
|
/// Beats per bar
|
||||||
|
bpb: usize,
|
||||||
|
/// Length of clip in pulses
|
||||||
|
pulses: usize,
|
||||||
|
/// Selected subdivision
|
||||||
|
pub focus: Option<ClipLengthFocus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipLength {
|
||||||
|
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
|
||||||
|
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
||||||
|
}
|
||||||
|
pub fn bars (&self) -> usize {
|
||||||
|
self.pulses / (self.bpb * self.ppq)
|
||||||
|
}
|
||||||
|
pub fn beats (&self) -> usize {
|
||||||
|
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
||||||
|
}
|
||||||
|
pub fn ticks (&self) -> usize {
|
||||||
|
self.pulses % self.ppq
|
||||||
|
}
|
||||||
|
pub fn bars_string (&self) -> Arc<str> {
|
||||||
|
format!("{}", self.bars()).into()
|
||||||
|
}
|
||||||
|
pub fn beats_string (&self) -> Arc<str> {
|
||||||
|
format!("{}", self.beats()).into()
|
||||||
|
}
|
||||||
|
pub fn ticks_string (&self) -> Arc<str> {
|
||||||
|
format!("{:>02}", self.ticks()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||||
|
|
||||||
|
pub trait HasClips {
|
||||||
|
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||||
|
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||||
|
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||||
|
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
|
||||||
|
self.clips_mut().push(clip.clone());
|
||||||
|
(self.clips().len() - 1, clip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
has_clips!(|self: Pool|self.clips);
|
||||||
|
|
||||||
|
has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||||
|
|
||||||
|
from!(|clip:&Arc<RwLock<MidiClip>>|Pool = {
|
||||||
|
let model = Self::default();
|
||||||
|
model.clips.write().unwrap().push(clip.clone());
|
||||||
|
model.clip.store(1, Relaxed);
|
||||||
|
model
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,85 +8,65 @@ impl Pool {
|
||||||
fn _todo_path_ (&self) -> PathBuf { todo!() }
|
fn _todo_path_ (&self) -> PathBuf { todo!() }
|
||||||
fn _todo_color_ (&self) -> ItemColor { todo!() }
|
fn _todo_color_ (&self) -> ItemColor { todo!() }
|
||||||
fn _todo_str_ (&self) -> Arc<str> { todo!() }
|
fn _todo_str_ (&self) -> Arc<str> { todo!() }
|
||||||
fn clip_new (&self) -> MidiClip {
|
fn clip_new (&self) -> MidiClip { self.new_clip() }
|
||||||
self.new_clip()
|
fn clip_cloned (&self) -> MidiClip { self.cloned_clip() }
|
||||||
}
|
fn clip_index_current (&self) -> usize { 0 }
|
||||||
fn clip_cloned (&self) -> MidiClip {
|
fn clip_index_after (&self) -> usize { 0 }
|
||||||
self.cloned_clip()
|
fn clip_index_previous (&self) -> usize { 0 }
|
||||||
}
|
fn clip_index_next (&self) -> usize { 0 }
|
||||||
fn clip_index_current (&self) -> usize {
|
fn color_random (&self) -> ItemColor { ItemColor::random() }
|
||||||
0
|
|
||||||
}
|
|
||||||
fn clip_index_after (&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn clip_index_previous (&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn clip_index_next (&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn color_random (&self) -> ItemColor {
|
|
||||||
ItemColor::random()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(Pool)]
|
def_command!(PoolCommand: |pool: Pool| {
|
||||||
impl PoolCommand {
|
// Toggle visibility of pool
|
||||||
|
Show { visible: bool } => {
|
||||||
/// Toggle visibility of pool
|
pool.visible = *visible; Ok(Some(Self::Show { visible: !visible })) },
|
||||||
fn show (pool: &mut Pool, visible: bool) -> Perhaps<Self> {
|
// Select a clip from the clip pool
|
||||||
pool.visible = visible;
|
Select { index: usize } => {
|
||||||
Ok(Some(Self::Show { visible: !visible }))
|
pool.set_clip_index(*index); Ok(None) },
|
||||||
}
|
// Update the contents of the clip pool
|
||||||
|
Clip { command: PoolClipCommand } =>
|
||||||
/// Select a clip from the clip pool
|
Ok(command.execute(pool)?.map(|command|Self::Clip{command})),
|
||||||
fn select (pool: &mut Pool, index: usize) -> Perhaps<Self> {
|
// Rename a clip
|
||||||
pool.set_clip_index(index);
|
Rename { command: RenameCommand } =>
|
||||||
Ok(None)
|
Ok(command.delegate(pool, |command|Self::Rename{command})?),
|
||||||
}
|
// Change the length of a clip
|
||||||
|
Length { command: CropCommand } =>
|
||||||
/// Rename a clip
|
Ok(command.delegate(pool, |command|Self::Length{command})?),
|
||||||
fn rename (pool: &mut Pool, command: RenameCommand) -> Perhaps<Self> {
|
// Import from file
|
||||||
Ok(command.delegate(pool, |command|Self::Rename{command})?)
|
Import { command: BrowseCommand } => {
|
||||||
}
|
Ok(if let Some(browse) = pool.browse.as_mut() {
|
||||||
|
command.delegate(browse, |command|Self::Import{command})?
|
||||||
/// Change the length of a clip
|
|
||||||
fn length (pool: &mut Pool, command: CropCommand) -> Perhaps<Self> {
|
|
||||||
Ok(command.delegate(pool, |command|Self::Length{command})?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Import from file
|
|
||||||
fn import (pool: &mut Pool, command: BrowserCommand) -> Perhaps<Self> {
|
|
||||||
Ok(if let Some(browser) = pool.browser.as_mut() {
|
|
||||||
command.delegate(browser, |command|Self::Import{command})?
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None }) },
|
||||||
})
|
// Export to file
|
||||||
}
|
Export { command: BrowseCommand } => {
|
||||||
|
Ok(if let Some(browse) = pool.browse.as_mut() {
|
||||||
/// Export to file
|
command.delegate(browse, |command|Self::Export{command})?
|
||||||
fn export (pool: &mut Pool, command: BrowserCommand) -> Perhaps<Self> {
|
|
||||||
Ok(if let Some(browser) = pool.browser.as_mut() {
|
|
||||||
command.delegate(browser, |command|Self::Export{command})?
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None }) },
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the contents of the clip pool
|
def_command!(PoolClipCommand: |pool: Pool| {
|
||||||
fn clip (pool: &mut Pool, command: PoolClipCommand) -> Perhaps<Self> {
|
|
||||||
Ok(command.execute(pool)?.map(|command|Self::Clip{command}))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
Delete { index: usize } => {
|
||||||
|
let index = *index;
|
||||||
|
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
|
||||||
|
Ok(Some(Self::Add { index, clip })) },
|
||||||
|
|
||||||
#[tengri_proc::command(Pool)]
|
Swap { index: usize, other: usize } => {
|
||||||
impl PoolClipCommand {
|
let index = *index;
|
||||||
|
let other = *other;
|
||||||
|
pool.clips_mut().swap(index, other);
|
||||||
|
Ok(Some(Self::Swap { index, other })) },
|
||||||
|
|
||||||
fn add (pool: &mut Pool, index: usize, clip: MidiClip) -> Perhaps<Self> {
|
Export { index: usize, path: PathBuf } => {
|
||||||
|
todo!("export clip to midi file"); },
|
||||||
|
|
||||||
|
Add { index: usize, clip: MidiClip } => {
|
||||||
|
let index = *index;
|
||||||
let mut index = index;
|
let mut index = index;
|
||||||
let clip = Arc::new(RwLock::new(clip));
|
let clip = Arc::new(RwLock::new(clip.clone()));
|
||||||
let mut clips = pool.clips_mut();
|
let mut clips = pool.clips_mut();
|
||||||
if index >= clips.len() {
|
if index >= clips.len() {
|
||||||
index = clips.len();
|
index = clips.len();
|
||||||
|
|
@ -94,20 +74,10 @@ impl PoolClipCommand {
|
||||||
} else {
|
} else {
|
||||||
clips.insert(index, clip);
|
clips.insert(index, clip);
|
||||||
}
|
}
|
||||||
Ok(Some(Self::Delete { index }))
|
Ok(Some(Self::Delete { index })) },
|
||||||
}
|
|
||||||
|
|
||||||
fn delete (pool: &mut Pool, index: usize) -> Perhaps<Self> {
|
Import { index: usize, path: PathBuf } => {
|
||||||
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
|
let index = *index;
|
||||||
Ok(Some(Self::Add { index, clip }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn swap (pool: &mut Pool, index: usize, other: usize) -> Perhaps<Self> {
|
|
||||||
pool.clips_mut().swap(index, other);
|
|
||||||
Ok(Some(Self::Swap { index, other }))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn import (pool: &mut Pool, index: usize, path: PathBuf) -> Perhaps<Self> {
|
|
||||||
let bytes = std::fs::read(&path)?;
|
let bytes = std::fs::read(&path)?;
|
||||||
let smf = Smf::parse(bytes.as_slice())?;
|
let smf = Smf::parse(bytes.as_slice())?;
|
||||||
let mut t = 0u32;
|
let mut t = 0u32;
|
||||||
|
|
@ -124,74 +94,64 @@ impl PoolClipCommand {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
clip.notes[event.0 as usize].push(event.2);
|
clip.notes[event.0 as usize].push(event.2);
|
||||||
}
|
}
|
||||||
Ok(Self::Add { index, clip }.execute(pool)?)
|
Ok(Self::Add { index, clip }.execute(pool)?) },
|
||||||
}
|
|
||||||
|
|
||||||
fn export (_pool: &mut Pool, _index: usize, _path: PathBuf) -> Perhaps<Self> {
|
SetName { index: usize, name: Arc<str> } => {
|
||||||
todo!("export clip to midi file");
|
let index = *index;
|
||||||
}
|
|
||||||
|
|
||||||
fn set_name (pool: &mut Pool, index: usize, name: Arc<str>) -> Perhaps<Self> {
|
|
||||||
let clip = &mut pool.clips_mut()[index];
|
let clip = &mut pool.clips_mut()[index];
|
||||||
let old_name = clip.read().unwrap().name.clone();
|
let old_name = clip.read().unwrap().name.clone();
|
||||||
clip.write().unwrap().name = name;
|
clip.write().unwrap().name = name.clone();
|
||||||
Ok(Some(Self::SetName { index, name: old_name }))
|
Ok(Some(Self::SetName { index, name: old_name })) },
|
||||||
}
|
|
||||||
|
|
||||||
fn set_length (pool: &mut Pool, index: usize, length: usize) -> Perhaps<Self> {
|
SetLength { index: usize, length: usize } => {
|
||||||
|
let index = *index;
|
||||||
let clip = &mut pool.clips_mut()[index];
|
let clip = &mut pool.clips_mut()[index];
|
||||||
let old_len = clip.read().unwrap().length;
|
let old_len = clip.read().unwrap().length;
|
||||||
clip.write().unwrap().length = length;
|
clip.write().unwrap().length = *length;
|
||||||
Ok(Some(Self::SetLength { index, length: old_len }))
|
Ok(Some(Self::SetLength { index, length: old_len })) },
|
||||||
}
|
|
||||||
|
|
||||||
fn set_color (pool: &mut Pool, index: usize, color: ItemColor) -> Perhaps<Self> {
|
SetColor { index: usize, color: ItemColor } => {
|
||||||
let mut color = ItemTheme::from(color);
|
let index = *index;
|
||||||
|
let mut color = ItemTheme::from(*color);
|
||||||
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
|
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
|
||||||
Ok(Some(Self::SetColor { index, color: color.base }))
|
Ok(Some(Self::SetColor { index, color: color.base })) },
|
||||||
}
|
|
||||||
|
|
||||||
}
|
});
|
||||||
|
|
||||||
#[tengri_proc::command(Pool)]
|
def_command!(RenameCommand: |pool: Pool| {
|
||||||
impl RenameCommand {
|
Begin => unreachable!(),
|
||||||
fn begin (_pool: &mut Pool) -> Perhaps<Self> {
|
|
||||||
unreachable!();
|
Cancel => {
|
||||||
}
|
|
||||||
fn cancel (pool: &mut Pool) -> Perhaps<Self> {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
|
||||||
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
|
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
|
||||||
}
|
}
|
||||||
return Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn confirm (pool: &mut Pool) -> Perhaps<Self> {
|
Confirm => {
|
||||||
if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() {
|
if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() {
|
||||||
let old_name = old_name.clone();
|
let old_name = old_name.clone();
|
||||||
*pool.mode_mut() = None;
|
*pool.mode_mut() = None;
|
||||||
return Ok(Some(Self::Set { value: old_name }))
|
return Ok(Some(Self::Set { value: old_name }))
|
||||||
}
|
}
|
||||||
return Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn set (pool: &mut Pool, value: Arc<str>) -> Perhaps<Self> {
|
|
||||||
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() {
|
|
||||||
pool.clips()[clip].write().unwrap().name = value;
|
|
||||||
}
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::command(Pool)]
|
Set { value: Arc<str> } => {
|
||||||
impl CropCommand {
|
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() {
|
||||||
fn begin (_pool: &mut Pool) -> Perhaps<Self> {
|
pool.clips()[clip].write().unwrap().name = value.clone();
|
||||||
unreachable!()
|
}
|
||||||
}
|
Ok(None) },
|
||||||
fn cancel (pool: &mut Pool) -> Perhaps<Self> {
|
});
|
||||||
|
|
||||||
|
def_command!(CropCommand: |pool: Pool| {
|
||||||
|
Begin => unreachable!(),
|
||||||
|
|
||||||
|
Cancel => {
|
||||||
if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() {
|
if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() {
|
||||||
*pool.mode_mut() = None;
|
*pool.mode_mut() = None;
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn set (pool: &mut Pool, length: usize) -> Perhaps<Self> {
|
Set { length: usize } => {
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||||
= pool.mode_mut().clone()
|
= pool.mode_mut().clone()
|
||||||
{
|
{
|
||||||
|
|
@ -204,25 +164,25 @@ impl CropCommand {
|
||||||
*pool.mode_mut() = None;
|
*pool.mode_mut() = None;
|
||||||
return Ok(old_length.map(|length|Self::Set { length }))
|
return Ok(old_length.map(|length|Self::Set { length }))
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn next (pool: &mut Pool) -> Perhaps<Self> {
|
Next => {
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||||
= pool.mode_mut().clone()
|
= pool.mode_mut().clone()
|
||||||
{
|
{
|
||||||
focus.next()
|
focus.next()
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn prev (pool: &mut Pool) -> Perhaps<Self> {
|
Prev => {
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||||
= pool.mode_mut().clone()
|
= pool.mode_mut().clone()
|
||||||
{
|
{
|
||||||
focus.prev()
|
focus.prev()
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn inc (pool: &mut Pool) -> Perhaps<Self> {
|
Inc => {
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||||
= pool.mode_mut().clone()
|
= pool.mode_mut().clone()
|
||||||
{
|
{
|
||||||
|
|
@ -232,9 +192,9 @@ impl CropCommand {
|
||||||
ClipLengthFocus::Tick => { *length += 1 },
|
ClipLengthFocus::Tick => { *length += 1 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
|
||||||
fn dec (pool: &mut Pool) -> Perhaps<Self> {
|
Dec => {
|
||||||
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
|
||||||
= pool.mode_mut().clone()
|
= pool.mode_mut().clone()
|
||||||
{
|
{
|
||||||
|
|
@ -244,6 +204,5 @@ impl CropCommand {
|
||||||
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
|
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None) },
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Pool {
|
|
||||||
pub visible: bool,
|
|
||||||
/// Collection of clips
|
|
||||||
pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
|
||||||
/// Selected clip
|
|
||||||
pub clip: AtomicUsize,
|
|
||||||
/// Mode switch
|
|
||||||
pub mode: Option<PoolMode>,
|
|
||||||
/// Embedded file browser
|
|
||||||
pub browser: Option<Browser>,
|
|
||||||
}
|
|
||||||
//take!(BrowserCommand |state: Pool, iter|Ok(state.browser.as_ref()
|
|
||||||
//.map(|p|Take::take(p, iter))
|
|
||||||
//.transpose()?
|
|
||||||
//.flatten()));
|
|
||||||
impl Default for Pool {
|
|
||||||
fn default () -> Self {
|
|
||||||
//use PoolMode::*;
|
|
||||||
Self {
|
|
||||||
visible: true,
|
|
||||||
clips: Arc::from(RwLock::from(vec![])),
|
|
||||||
clip: 0.into(),
|
|
||||||
mode: None,
|
|
||||||
browser: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pool {
|
|
||||||
pub fn clip_index (&self) -> usize {
|
|
||||||
self.clip.load(Relaxed)
|
|
||||||
}
|
|
||||||
pub fn set_clip_index (&self, value: usize) {
|
|
||||||
self.clip.store(value, Relaxed);
|
|
||||||
}
|
|
||||||
pub fn mode (&self) -> &Option<PoolMode> {
|
|
||||||
&self.mode
|
|
||||||
}
|
|
||||||
pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
|
|
||||||
&mut self.mode
|
|
||||||
}
|
|
||||||
pub fn begin_clip_length (&mut self) {
|
|
||||||
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
|
||||||
*self.mode_mut() = Some(PoolMode::Length(
|
|
||||||
self.clip_index(),
|
|
||||||
length,
|
|
||||||
ClipLengthFocus::Bar
|
|
||||||
));
|
|
||||||
}
|
|
||||||
pub fn begin_clip_rename (&mut self) {
|
|
||||||
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
|
||||||
*self.mode_mut() = Some(PoolMode::Rename(
|
|
||||||
self.clip_index(),
|
|
||||||
name
|
|
||||||
));
|
|
||||||
}
|
|
||||||
pub fn begin_import (&mut self) -> Usually<()> {
|
|
||||||
*self.mode_mut() = Some(PoolMode::Import(
|
|
||||||
self.clip_index(),
|
|
||||||
Browser::new(None)?
|
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn begin_export (&mut self) -> Usually<()> {
|
|
||||||
*self.mode_mut() = Some(PoolMode::Export(
|
|
||||||
self.clip_index(),
|
|
||||||
Browser::new(None)?
|
|
||||||
));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn new_clip (&self) -> MidiClip {
|
|
||||||
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
|
|
||||||
}
|
|
||||||
pub fn cloned_clip (&self) -> MidiClip {
|
|
||||||
let index = self.clip_index();
|
|
||||||
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
|
||||||
clip.color = ItemTheme::random_near(clip.color, 0.25);
|
|
||||||
clip
|
|
||||||
}
|
|
||||||
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
|
||||||
let clip = Arc::new(RwLock::new(self.new_clip()));
|
|
||||||
let index = {
|
|
||||||
let mut clips = self.clips.write().unwrap();
|
|
||||||
clips.push(clip.clone());
|
|
||||||
clips.len().saturating_sub(1)
|
|
||||||
};
|
|
||||||
self.clip.store(index, Relaxed);
|
|
||||||
(index, clip)
|
|
||||||
}
|
|
||||||
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
|
|
||||||
let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
|
||||||
if let Some(index) = index {
|
|
||||||
self.clips.write().unwrap().remove(index);
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modes for clip pool
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum PoolMode {
|
|
||||||
/// Renaming a pattern
|
|
||||||
Rename(usize, Arc<str>),
|
|
||||||
/// Editing the length of a pattern
|
|
||||||
Length(usize, usize, ClipLengthFocus),
|
|
||||||
/// Load clip from disk
|
|
||||||
Import(usize, Browser),
|
|
||||||
/// Save clip to disk
|
|
||||||
Export(usize, Browser),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Focused field of `ClipLength`
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum ClipLengthFocus {
|
|
||||||
/// Editing the number of bars
|
|
||||||
Bar,
|
|
||||||
/// Editing the number of beats
|
|
||||||
Beat,
|
|
||||||
/// Editing the number of ticks
|
|
||||||
Tick,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClipLengthFocus {
|
|
||||||
pub fn next (&mut self) {
|
|
||||||
use ClipLengthFocus::*;
|
|
||||||
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
|
|
||||||
}
|
|
||||||
pub fn prev (&mut self) {
|
|
||||||
use ClipLengthFocus::*;
|
|
||||||
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Displays and edits clip length.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ClipLength {
|
|
||||||
/// Pulses per beat (quaver)
|
|
||||||
ppq: usize,
|
|
||||||
/// Beats per bar
|
|
||||||
bpb: usize,
|
|
||||||
/// Length of clip in pulses
|
|
||||||
pulses: usize,
|
|
||||||
/// Selected subdivision
|
|
||||||
pub focus: Option<ClipLengthFocus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClipLength {
|
|
||||||
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
|
|
||||||
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
|
||||||
}
|
|
||||||
pub fn bars (&self) -> usize {
|
|
||||||
self.pulses / (self.bpb * self.ppq)
|
|
||||||
}
|
|
||||||
pub fn beats (&self) -> usize {
|
|
||||||
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
|
||||||
}
|
|
||||||
pub fn ticks (&self) -> usize {
|
|
||||||
self.pulses % self.ppq
|
|
||||||
}
|
|
||||||
pub fn bars_string (&self) -> Arc<str> {
|
|
||||||
format!("{}", self.bars()).into()
|
|
||||||
}
|
|
||||||
pub fn beats_string (&self) -> Arc<str> {
|
|
||||||
format!("{}", self.beats()).into()
|
|
||||||
}
|
|
||||||
pub fn ticks_string (&self) -> Arc<str> {
|
|
||||||
format!("{:>02}", self.ticks()).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
|
||||||
|
|
||||||
pub trait HasClips {
|
|
||||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
|
||||||
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
|
||||||
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
|
||||||
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
|
|
||||||
self.clips_mut().push(clip.clone());
|
|
||||||
(self.clips().len() - 1, clip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
has_clips!(|self: Pool|self.clips);
|
|
||||||
|
|
||||||
has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
|
||||||
|
|
||||||
from!(|clip:&Arc<RwLock<MidiClip>>|Pool = {
|
|
||||||
let model = Self::default();
|
|
||||||
model.clips.write().unwrap().push(clip.clone());
|
|
||||||
model.clip.store(1, Relaxed);
|
|
||||||
model
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
mod port_api; pub use self::port_api::*;
|
||||||
|
mod port_connect; pub use self::port_connect::*;
|
||||||
mod port_audio_out; pub use self::port_audio_out::*;
|
mod port_audio_out; pub use self::port_audio_out::*;
|
||||||
mod port_audio_in; pub use self::port_audio_in::*;
|
mod port_audio_in; pub use self::port_audio_in::*;
|
||||||
mod port_midi_out; pub use self::port_midi_out::*;
|
mod port_midi_out; pub use self::port_midi_out::*;
|
||||||
|
|
@ -8,6 +10,58 @@ pub(crate) use ConnectName::*;
|
||||||
pub(crate) use ConnectScope::*;
|
pub(crate) use ConnectScope::*;
|
||||||
pub(crate) use ConnectStatus::*;
|
pub(crate) use ConnectStatus::*;
|
||||||
|
|
||||||
|
#[derive(Debug)] pub struct AudioInput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
port: Port<AudioIn>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)] pub struct AudioOutput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
port: Port<AudioOut>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)] pub struct MidiInput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
port: Port<MidiIn>,
|
||||||
|
/// List of currently held notes.
|
||||||
|
held: Arc<RwLock<[bool;128]>>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)] pub struct MidiOutput {
|
||||||
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
|
jack: Jack<'static>,
|
||||||
|
/// Port name
|
||||||
|
name: Arc<str>,
|
||||||
|
/// Port handle.
|
||||||
|
port: Port<MidiOut>,
|
||||||
|
/// List of ports to connect to.
|
||||||
|
pub connections: Vec<Connect>,
|
||||||
|
/// List of currently held notes.
|
||||||
|
held: Arc<RwLock<[bool;128]>>,
|
||||||
|
/// Buffer
|
||||||
|
note_buffer: Vec<u8>,
|
||||||
|
/// Buffer
|
||||||
|
output_buffer: Vec<Vec<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait RegisterPorts: HasJack<'static> {
|
pub trait RegisterPorts: HasJack<'static> {
|
||||||
/// Register a MIDI input port.
|
/// Register a MIDI input port.
|
||||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
||||||
|
|
@ -33,184 +87,3 @@ impl<J: HasJack<'static>> RegisterPorts for J {
|
||||||
AudioOutput::new(self.jack(), name, connect)
|
AudioOutput::new(self.jack(), name, connect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait JackPort: HasJack<'static> {
|
|
||||||
type Port: PortSpec + Default;
|
|
||||||
type Pair: PortSpec + Default;
|
|
||||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
|
||||||
-> Usually<Self> where Self: Sized;
|
|
||||||
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
|
||||||
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
|
||||||
.map_err(|e|e.into())
|
|
||||||
}
|
|
||||||
fn port_name (&self) -> &Arc<str>;
|
|
||||||
fn connections (&self) -> &[Connect];
|
|
||||||
fn port (&self) -> &Port<Self::Port>;
|
|
||||||
fn port_mut (&mut self) -> &mut Port<Self::Port>;
|
|
||||||
fn into_port (self) -> Port<Self::Port> where Self: Sized;
|
|
||||||
fn close (self) -> Usually<()> where Self: Sized {
|
|
||||||
let jack = self.jack().clone();
|
|
||||||
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
|
|
||||||
}
|
|
||||||
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
|
||||||
self.with_client(|c|c.ports(re_name, re_type, flags))
|
|
||||||
}
|
|
||||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_id(id))
|
|
||||||
}
|
|
||||||
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
|
|
||||||
self.with_client(|c|c.port_by_name(name.as_ref()))
|
|
||||||
}
|
|
||||||
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
|
||||||
for connect in self.connections().iter() {
|
|
||||||
//panic!("{connect:?}");
|
|
||||||
let status = match &connect.name {
|
|
||||||
Exact(name) => self.connect_exact(name),
|
|
||||||
RegExp(re) => self.connect_regexp(re, connect.scope),
|
|
||||||
}?;
|
|
||||||
*connect.status.write().unwrap() = status;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn connect_exact <'k> (&'k self, name: &str) ->
|
|
||||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
|
||||||
{
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
|
||||||
if port.as_str() == &*name {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn connect_regexp <'k> (
|
|
||||||
&'k self, re: &str, scope: ConnectScope
|
|
||||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
|
||||||
self.with_client(move|c|{
|
|
||||||
let mut status = vec![];
|
|
||||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
|
||||||
for port in ports.iter() {
|
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
|
||||||
let port_status = self.connect_to_unowned(&port)?;
|
|
||||||
let name = port.name()?.into();
|
|
||||||
status.push((port, name, port_status));
|
|
||||||
if port_status == Connected && scope == One {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(status)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Connect to a matching port by name. */
|
|
||||||
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
|
|
||||||
self.connect_to_unowned(port)
|
|
||||||
} else {
|
|
||||||
Ok(Missing)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/** Connect to a matching port by reference. */
|
|
||||||
fn connect_to_unowned (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/** Connect to an owned matching port by reference. */
|
|
||||||
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> Usually<ConnectStatus> {
|
|
||||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
|
||||||
Connected
|
|
||||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
|
||||||
Connected
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum ConnectName {
|
|
||||||
/** Exact match */
|
|
||||||
Exact(Arc<str>),
|
|
||||||
/** Match regular expression */
|
|
||||||
RegExp(Arc<str>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
|
||||||
One,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
|
||||||
Missing,
|
|
||||||
Disconnected,
|
|
||||||
Connected,
|
|
||||||
Mismatch,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)] pub struct Connect {
|
|
||||||
pub name: ConnectName,
|
|
||||||
pub scope: ConnectScope,
|
|
||||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
|
||||||
pub info: Arc<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connect {
|
|
||||||
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
|
||||||
-> Vec<Self>
|
|
||||||
{
|
|
||||||
let mut connections = vec![];
|
|
||||||
for port in exact.iter() { connections.push(Self::exact(port)) }
|
|
||||||
for port in re.iter() { connections.push(Self::regexp(port)) }
|
|
||||||
for port in re_all.iter() { connections.push(Self::regexp_all(port)) }
|
|
||||||
connections
|
|
||||||
}
|
|
||||||
/// Connect to this exact port
|
|
||||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("=:{}", name.as_ref()).into();
|
|
||||||
let name = Exact(name.as_ref().into());
|
|
||||||
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn regexp (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("~:{}", name.as_ref()).into();
|
|
||||||
let name = RegExp(name.as_ref().into());
|
|
||||||
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
|
||||||
let info = format!("+:{}", name.as_ref()).into();
|
|
||||||
let name = RegExp(name.as_ref().into());
|
|
||||||
Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info }
|
|
||||||
}
|
|
||||||
pub fn info (&self) -> Arc<str> {
|
|
||||||
let status = {
|
|
||||||
let status = self.status.read().unwrap();
|
|
||||||
let mut ok = 0;
|
|
||||||
for (_, _, state) in status.iter() {
|
|
||||||
if *state == Connected {
|
|
||||||
ok += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{ok}/{}", status.len())
|
|
||||||
};
|
|
||||||
let scope = match self.scope {
|
|
||||||
One => " ", All => "*",
|
|
||||||
};
|
|
||||||
let name = match &self.name {
|
|
||||||
Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"),
|
|
||||||
};
|
|
||||||
format!(" ({}) {} {}", status, scope, name).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
17
crates/device/src/port/port_api.rs
Normal file
17
crates/device/src/port/port_api.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use crate::*;
|
||||||
|
def_command!(MidiInputCommand: |port: MidiInput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { midi_out: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { midi_in: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
def_command!(AudioInputCommand: |port: AudioInput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { audio_out: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
||||||
|
Close => todo!(),
|
||||||
|
Connect { audio_in: Arc<str> } => todo!(),
|
||||||
|
});
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug)] pub struct AudioInput {
|
impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
port: Port<AudioIn>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
impl HasJack<'static> for AudioInput {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackPort for AudioInput {
|
impl JackPort for AudioInput {
|
||||||
type Port = AudioIn;
|
type Port = AudioIn;
|
||||||
type Pair = AudioOut;
|
type Pair = AudioOut;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug)] pub struct AudioOutput {
|
impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
port: Port<AudioOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
impl HasJack<'static> for AudioOutput {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackPort for AudioOutput {
|
impl JackPort for AudioOutput {
|
||||||
type Port = AudioOut;
|
type Port = AudioOut;
|
||||||
type Pair = AudioIn;
|
type Pair = AudioIn;
|
||||||
|
|
|
||||||
183
crates/device/src/port/port_connect.rs
Normal file
183
crates/device/src/port/port_connect.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub trait JackPort: HasJack<'static> {
|
||||||
|
type Port: PortSpec + Default;
|
||||||
|
type Pair: PortSpec + Default;
|
||||||
|
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
||||||
|
-> Usually<Self> where Self: Sized;
|
||||||
|
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
||||||
|
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
||||||
|
.map_err(|e|e.into())
|
||||||
|
}
|
||||||
|
fn port_name (&self) -> &Arc<str>;
|
||||||
|
fn connections (&self) -> &[Connect];
|
||||||
|
fn port (&self) -> &Port<Self::Port>;
|
||||||
|
fn port_mut (&mut self) -> &mut Port<Self::Port>;
|
||||||
|
fn into_port (self) -> Port<Self::Port> where Self: Sized;
|
||||||
|
fn close (self) -> Usually<()> where Self: Sized {
|
||||||
|
let jack = self.jack().clone();
|
||||||
|
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
|
||||||
|
}
|
||||||
|
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
||||||
|
self.with_client(|c|c.ports(re_name, re_type, flags))
|
||||||
|
}
|
||||||
|
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_id(id))
|
||||||
|
}
|
||||||
|
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_name(name.as_ref()))
|
||||||
|
}
|
||||||
|
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
||||||
|
for connect in self.connections().iter() {
|
||||||
|
//panic!("{connect:?}");
|
||||||
|
let status = match &connect.name {
|
||||||
|
Exact(name) => self.connect_exact(name),
|
||||||
|
RegExp(re) => self.connect_regexp(re, connect.scope),
|
||||||
|
}?;
|
||||||
|
*connect.status.write().unwrap() = status;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn connect_exact <'k> (&'k self, name: &str) ->
|
||||||
|
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
||||||
|
{
|
||||||
|
self.with_client(move|c|{
|
||||||
|
let mut status = vec![];
|
||||||
|
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
||||||
|
if port.as_str() == &*name {
|
||||||
|
if let Some(port) = c.port_by_name(port.as_str()) {
|
||||||
|
let port_status = self.connect_to_unowned(&port)?;
|
||||||
|
let name = port.name()?.into();
|
||||||
|
status.push((port, name, port_status));
|
||||||
|
if port_status == Connected {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn connect_regexp <'k> (
|
||||||
|
&'k self, re: &str, scope: ConnectScope
|
||||||
|
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
||||||
|
self.with_client(move|c|{
|
||||||
|
let mut status = vec![];
|
||||||
|
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
||||||
|
for port in ports.iter() {
|
||||||
|
if let Some(port) = c.port_by_name(port.as_str()) {
|
||||||
|
let port_status = self.connect_to_unowned(&port)?;
|
||||||
|
let name = port.name()?.into();
|
||||||
|
status.push((port, name, port_status));
|
||||||
|
if port_status == Connected && scope == One {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** Connect to a matching port by name. */
|
||||||
|
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
|
||||||
|
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
|
||||||
|
self.connect_to_unowned(port)
|
||||||
|
} else {
|
||||||
|
Ok(Missing)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/** Connect to a matching port by reference. */
|
||||||
|
fn connect_to_unowned (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
||||||
|
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
||||||
|
Connected
|
||||||
|
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
||||||
|
Connected
|
||||||
|
} else {
|
||||||
|
Mismatch
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/** Connect to an owned matching port by reference. */
|
||||||
|
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> Usually<ConnectStatus> {
|
||||||
|
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
||||||
|
Connected
|
||||||
|
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
||||||
|
Connected
|
||||||
|
} else {
|
||||||
|
Mismatch
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ConnectName {
|
||||||
|
/** Exact match */
|
||||||
|
Exact(Arc<str>),
|
||||||
|
/** Match regular expression */
|
||||||
|
RegExp(Arc<str>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
||||||
|
One,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
||||||
|
Missing,
|
||||||
|
Disconnected,
|
||||||
|
Connected,
|
||||||
|
Mismatch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)] pub struct Connect {
|
||||||
|
pub name: ConnectName,
|
||||||
|
pub scope: ConnectScope,
|
||||||
|
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
||||||
|
pub info: Arc<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connect {
|
||||||
|
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
||||||
|
-> Vec<Self>
|
||||||
|
{
|
||||||
|
let mut connections = vec![];
|
||||||
|
for port in exact.iter() { connections.push(Self::exact(port)) }
|
||||||
|
for port in re.iter() { connections.push(Self::regexp(port)) }
|
||||||
|
for port in re_all.iter() { connections.push(Self::regexp_all(port)) }
|
||||||
|
connections
|
||||||
|
}
|
||||||
|
/// Connect to this exact port
|
||||||
|
pub fn exact (name: impl AsRef<str>) -> Self {
|
||||||
|
let info = format!("=:{}", name.as_ref()).into();
|
||||||
|
let name = Exact(name.as_ref().into());
|
||||||
|
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
||||||
|
}
|
||||||
|
pub fn regexp (name: impl AsRef<str>) -> Self {
|
||||||
|
let info = format!("~:{}", name.as_ref()).into();
|
||||||
|
let name = RegExp(name.as_ref().into());
|
||||||
|
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
||||||
|
}
|
||||||
|
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
||||||
|
let info = format!("+:{}", name.as_ref()).into();
|
||||||
|
let name = RegExp(name.as_ref().into());
|
||||||
|
Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info }
|
||||||
|
}
|
||||||
|
pub fn info (&self) -> Arc<str> {
|
||||||
|
let status = {
|
||||||
|
let status = self.status.read().unwrap();
|
||||||
|
let mut ok = 0;
|
||||||
|
for (_, _, state) in status.iter() {
|
||||||
|
if *state == Connected {
|
||||||
|
ok += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format!("{ok}/{}", status.len())
|
||||||
|
};
|
||||||
|
let scope = match self.scope {
|
||||||
|
One => " ", All => "*",
|
||||||
|
};
|
||||||
|
let name = match &self.name {
|
||||||
|
Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"),
|
||||||
|
};
|
||||||
|
format!(" ({}) {} {}", status, scope, name).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,24 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::<MidiOut>(n));
|
impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
|
|
||||||
#[derive(Debug)] pub struct MidiInput {
|
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
port: Port<MidiIn>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
}
|
|
||||||
impl HasJack<'static> for MidiInput {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackPort for MidiInput {
|
impl JackPort for MidiInput {
|
||||||
type Port = MidiIn;
|
type Port = MidiIn;
|
||||||
type Pair = MidiOut;
|
type Pair = MidiOut;
|
||||||
|
|
@ -51,17 +34,13 @@ impl JackPort for MidiInput {
|
||||||
Ok(port)
|
Ok(port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiInput {
|
impl MidiInput {
|
||||||
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
||||||
parse_midi_input(self.port().iter(scope))
|
parse_midi_input(self.port().iter(scope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(MidiInput)]
|
|
||||||
impl MidiInputCommand {
|
|
||||||
//fn _todo_ (_port: &mut MidiInput) -> Perhaps<Self> { Ok(None) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Vec<MidiInput>>> HasMidiIns for T {
|
impl<T: Has<Vec<MidiInput>>> HasMidiIns for T {
|
||||||
fn midi_ins (&self) -> &Vec<MidiInput> {
|
fn midi_ins (&self) -> &Vec<MidiInput> {
|
||||||
self.get()
|
self.get()
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug)] pub struct MidiOutput {
|
impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
/// Handle to JACK client, for receiving reconnect events.
|
|
||||||
jack: Jack<'static>,
|
|
||||||
/// Port name
|
|
||||||
name: Arc<str>,
|
|
||||||
/// Port handle.
|
|
||||||
port: Port<MidiOut>,
|
|
||||||
/// List of ports to connect to.
|
|
||||||
pub connections: Vec<Connect>,
|
|
||||||
/// List of currently held notes.
|
|
||||||
held: Arc<RwLock<[bool;128]>>,
|
|
||||||
/// Buffer
|
|
||||||
note_buffer: Vec<u8>,
|
|
||||||
/// Buffer
|
|
||||||
output_buffer: Vec<Vec<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
impl HasJack<'static> for MidiOutput {
|
|
||||||
fn jack (&self) -> &Jack<'static> {
|
|
||||||
&self.jack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl JackPort for MidiOutput {
|
impl JackPort for MidiOutput {
|
||||||
type Port = MidiOut;
|
type Port = MidiOut;
|
||||||
type Pair = MidiIn;
|
type Pair = MidiIn;
|
||||||
|
|
@ -59,6 +40,7 @@ impl JackPort for MidiOutput {
|
||||||
Ok(port)
|
Ok(port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MidiOutput {
|
impl MidiOutput {
|
||||||
/// Clear the section of the output buffer that we will be using,
|
/// Clear the section of the output buffer that we will be using,
|
||||||
/// emitting "all notes off" at start of buffer if requested.
|
/// emitting "all notes off" at start of buffer if requested.
|
||||||
|
|
@ -99,10 +81,6 @@ impl MidiOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(MidiOutput)]
|
|
||||||
impl MidiOutputCommand {
|
|
||||||
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Has<Vec<MidiOutput>>> HasMidiOuts for T {
|
impl<T: Has<Vec<MidiOutput>>> HasMidiOuts for T {
|
||||||
fn midi_outs (&self) -> &Vec<MidiOutput> {
|
fn midi_outs (&self) -> &Vec<MidiOutput> {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
pub(crate) use symphonia::{
|
pub(crate) use symphonia::{
|
||||||
core::{
|
core::{
|
||||||
formats::Packet,
|
formats::Packet,
|
||||||
|
|
@ -14,7 +16,6 @@ mod sampler_api; pub use self::sampler_api::*;
|
||||||
mod sampler_audio;
|
mod sampler_audio;
|
||||||
mod sampler_browse; pub use self::sampler_browse::*;
|
mod sampler_browse; pub use self::sampler_browse::*;
|
||||||
mod sampler_midi; pub use self::sampler_midi::*;
|
mod sampler_midi; pub use self::sampler_midi::*;
|
||||||
mod sampler_model; pub use self::sampler_model::*;
|
|
||||||
|
|
||||||
mod sampler_data;
|
mod sampler_data;
|
||||||
mod sampler_view;
|
mod sampler_view;
|
||||||
|
|
@ -23,3 +24,177 @@ mod sampler_view;
|
||||||
// TODO!
|
// TODO!
|
||||||
let sample = Sample::new("test", 0, 0, vec![]);
|
let sample = Sample::new("test", 0, 0, vec![]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sampler device plays sounds in response to MIDI notes.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sampler {
|
||||||
|
/// Name of sampler.
|
||||||
|
pub name: Arc<str>,
|
||||||
|
/// Device color.
|
||||||
|
pub color: ItemTheme,
|
||||||
|
/// Audio input ports. Samples get recorded here.
|
||||||
|
pub audio_ins: Vec<AudioInput>,
|
||||||
|
/// Audio input meters.
|
||||||
|
pub input_meters: Vec<f32>,
|
||||||
|
/// Sample currently being recorded.
|
||||||
|
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||||||
|
/// Recording buffer.
|
||||||
|
pub buffer: Vec<Vec<f32>>,
|
||||||
|
/// Samples mapped to MIDI notes.
|
||||||
|
pub mapped: [Option<Arc<RwLock<Sample>>>;128],
|
||||||
|
/// Samples that are not mapped to MIDI notes.
|
||||||
|
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||||
|
/// Sample currently being edited.
|
||||||
|
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||||
|
/// MIDI input port. Triggers sample playback.
|
||||||
|
pub midi_in: MidiInput,
|
||||||
|
/// Collection of currently playing instances of samples.
|
||||||
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||||
|
/// Audio output ports. Voices get played here.
|
||||||
|
pub audio_outs: Vec<AudioOutput>,
|
||||||
|
/// Audio output meters.
|
||||||
|
pub output_meters: Vec<f32>,
|
||||||
|
/// How to mix the voices.
|
||||||
|
pub mixing_mode: MixingMode,
|
||||||
|
/// How to meter the inputs and outputs.
|
||||||
|
pub metering_mode: MeteringMode,
|
||||||
|
/// Fixed gain applied to all output.
|
||||||
|
pub output_gain: f32,
|
||||||
|
/// Currently active modal, if any.
|
||||||
|
pub mode: Option<SamplerMode>,
|
||||||
|
/// Size of rendered sampler.
|
||||||
|
pub size: Measure<TuiOut>,
|
||||||
|
/// Lowest note displayed.
|
||||||
|
pub note_lo: AtomicUsize,
|
||||||
|
/// Currently selected note.
|
||||||
|
pub note_pt: AtomicUsize,
|
||||||
|
/// Selected note as row/col.
|
||||||
|
pub cursor: (AtomicUsize, AtomicUsize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
pub fn new (
|
||||||
|
jack: &Jack<'static>,
|
||||||
|
name: impl AsRef<str>,
|
||||||
|
midi_from: &[Connect],
|
||||||
|
audio_from: &[&[Connect];2],
|
||||||
|
audio_to: &[&[Connect];2],
|
||||||
|
) -> Usually<Self> {
|
||||||
|
let name = name.as_ref();
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?,
|
||||||
|
audio_ins: vec![
|
||||||
|
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||||
|
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||||
|
],
|
||||||
|
audio_outs: vec![
|
||||||
|
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
|
||||||
|
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
|
||||||
|
],
|
||||||
|
input_meters: vec![0.0;2],
|
||||||
|
output_meters: vec![0.0;2],
|
||||||
|
mapped: [const { None };128],
|
||||||
|
unmapped: vec![],
|
||||||
|
voices: Arc::new(RwLock::new(vec![])),
|
||||||
|
buffer: vec![vec![0.0;16384];2],
|
||||||
|
output_gain: 1.,
|
||||||
|
recording: None,
|
||||||
|
mode: None,
|
||||||
|
editing: None,
|
||||||
|
size: Default::default(),
|
||||||
|
note_lo: 0.into(),
|
||||||
|
note_pt: 0.into(),
|
||||||
|
cursor: (0.into(), 0.into()),
|
||||||
|
color: Default::default(),
|
||||||
|
mixing_mode: Default::default(),
|
||||||
|
metering_mode: Default::default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/// Value of cursor
|
||||||
|
pub fn cursor (&self) -> (usize, usize) {
|
||||||
|
(self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteRange for Sampler {
|
||||||
|
fn note_lo (&self) -> &AtomicUsize {
|
||||||
|
&self.note_lo
|
||||||
|
}
|
||||||
|
fn note_axis (&self) -> &AtomicUsize {
|
||||||
|
&self.size.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotePoint for Sampler {
|
||||||
|
fn note_len (&self) -> &AtomicUsize {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
fn get_note_len (&self) -> usize {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn set_note_len (&self, x: usize) -> usize {
|
||||||
|
0 /*TODO?*/
|
||||||
|
}
|
||||||
|
fn note_pos (&self) -> &AtomicUsize {
|
||||||
|
&self.note_pt
|
||||||
|
}
|
||||||
|
fn get_note_pos (&self) -> usize {
|
||||||
|
self.note_pt.load(Relaxed)
|
||||||
|
}
|
||||||
|
fn set_note_pos (&self, x: usize) -> usize {
|
||||||
|
let old = self.note_pt.swap(x, Relaxed);
|
||||||
|
self.cursor.0.store(x % 8, Relaxed);
|
||||||
|
self.cursor.1.store(x / 8, Relaxed);
|
||||||
|
old
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sound sample.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Sample {
|
||||||
|
pub name: Arc<str>,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
pub rate: Option<usize>,
|
||||||
|
pub gain: f32,
|
||||||
|
pub color: ItemTheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.as_ref().into(),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
channels,
|
||||||
|
rate: None,
|
||||||
|
gain: 1.0,
|
||||||
|
color: ItemTheme::random(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
||||||
|
Voice {
|
||||||
|
sample: sample.clone(),
|
||||||
|
after,
|
||||||
|
position: sample.read().unwrap().start,
|
||||||
|
velocity: velocity.as_int() as f32 / 127.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A currently playing instance of a sample.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Voice {
|
||||||
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
pub after: usize,
|
||||||
|
pub position: usize,
|
||||||
|
pub velocity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SamplerMode {
|
||||||
|
// Load sample from path
|
||||||
|
Import(usize, Browse),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,69 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
def_command!(SamplerCommand: |sampler: Sampler| {
|
||||||
|
RecordToggle { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
let recording = sampler.recording.as_ref().map(|x|x.0);
|
||||||
|
let _ = Self::RecordFinish.execute(sampler)?;
|
||||||
|
// autoslice: continue recording at next slot
|
||||||
|
if recording != Some(slot) {
|
||||||
|
Self::RecordBegin { slot }.execute(sampler)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RecordBegin { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
sampler.recording = Some((
|
||||||
|
slot,
|
||||||
|
Some(Arc::new(RwLock::new(Sample::new(
|
||||||
|
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
||||||
|
))))
|
||||||
|
));
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
RecordFinish => {
|
||||||
|
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||||
|
std::mem::swap(sample, &mut sampler.mapped[*index]);
|
||||||
|
sample
|
||||||
|
}); // TODO: undo
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
RecordCancel => {
|
||||||
|
sampler.recording = None;
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
PlaySample { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
if let Some(ref sample) = sampler.mapped[slot] {
|
||||||
|
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
StopSample { slot: usize } => {
|
||||||
|
let slot = *slot;
|
||||||
|
todo!();
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
def_command!(FileBrowserCommand: |sampler: Sampler|{
|
||||||
|
//("begin" [] Some(Self::Begin))
|
||||||
|
//("cancel" [] Some(Self::Cancel))
|
||||||
|
//("confirm" [] Some(Self::Confirm))
|
||||||
|
//("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
||||||
|
//("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
|
||||||
|
//("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
||||||
|
});
|
||||||
|
|
||||||
#[tengri_proc::expose]
|
#[tengri_proc::expose]
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
|
fn sample_selected (&self) -> usize {
|
||||||
|
(self.get_note_pos() as u8).into()
|
||||||
|
}
|
||||||
|
fn sample_selected_pitch (&self) -> u7 {
|
||||||
|
(self.get_note_pos() as u8).into()
|
||||||
|
}
|
||||||
//fn file_browser_filter (&self) -> Arc<str> {
|
//fn file_browser_filter (&self) -> Arc<str> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
|
|
@ -40,82 +102,34 @@ impl Sampler {
|
||||||
//fn selected_pitch () -> u7 {
|
//fn selected_pitch () -> u7 {
|
||||||
//(self.note_pos() as u8).into() // TODO
|
//(self.note_pos() as u8).into() // TODO
|
||||||
//}
|
//}
|
||||||
fn sample_selected (&self) -> usize {
|
|
||||||
(self.get_note_pos() as u8).into()
|
|
||||||
}
|
|
||||||
fn sample_selected_pitch (&self) -> u7 {
|
|
||||||
(self.get_note_pos() as u8).into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tengri_proc::command(Sampler)]
|
//select (&self, state: &mut Sampler, i: usize) -> Option<Self> {
|
||||||
impl SamplerCommand {
|
|
||||||
fn record_toggle (sampler: &mut Sampler, slot: usize) -> Perhaps<Self> {
|
|
||||||
let recording = sampler.recording.as_ref().map(|x|x.0);
|
|
||||||
Self::record_finish(sampler)?;
|
|
||||||
// autoslice: continue recording at next slot
|
|
||||||
if recording != Some(slot) {
|
|
||||||
Self::record_begin(sampler, slot)
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn record_begin (sampler: &mut Sampler, slot: usize) -> Perhaps<Self> {
|
|
||||||
sampler.recording = Some((
|
|
||||||
slot,
|
|
||||||
Some(Arc::new(RwLock::new(Sample::new(
|
|
||||||
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
|
||||||
))))
|
|
||||||
));
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn record_finish (sampler: &mut Sampler) -> Perhaps<Self> {
|
|
||||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
|
||||||
std::mem::swap(sample, &mut sampler.mapped[*index]);
|
|
||||||
sample
|
|
||||||
}); // TODO: undo
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn record_cancel (sampler: &mut Sampler) -> Perhaps<Self> {
|
|
||||||
sampler.recording = None;
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn play_sample (sampler: &mut Sampler, slot: usize) -> Perhaps<Self> {
|
|
||||||
if let Some(ref sample) = sampler.mapped[slot] {
|
|
||||||
sampler.voices.write().unwrap().push(Sample::play(sample, 0, &u7::from(128)));
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
fn stop_sample (sampler: &mut Sampler, slot: usize) -> Perhaps<Self> {
|
|
||||||
todo!();
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
//fn select (&self, state: &mut Sampler, i: usize) -> Option<Self> {
|
|
||||||
//Self::Select(state.set_note_pos(i))
|
//Self::Select(state.set_note_pos(i))
|
||||||
//}
|
//}
|
||||||
///// Assign sample to slot
|
///// Assign sample to slot
|
||||||
//fn set (&self, slot: u7, sample: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
//set (&self, slot: u7, sample: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||||
//let i = slot.as_int() as usize;
|
//let i = slot.as_int() as usize;
|
||||||
//let old = self.mapped[i].clone();
|
//let old = self.mapped[i].clone();
|
||||||
//self.mapped[i] = sample;
|
//self.mapped[i] = sample;
|
||||||
//Some(Self::Set(old))
|
//Some(Self::Set(old))
|
||||||
//}
|
//}
|
||||||
//fn set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option<Self> {
|
//set_start (&self, state: &mut Sampler, slot: u7, frame: usize) -> Option<Self> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
//fn set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option<Self> {
|
//set_gain (&self, state: &mut Sampler, slot: u7, g: f32) -> Option<Self> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
//fn note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option<Self> {
|
//note_on (&self, state: &mut Sampler, slot: u7, v: u7) -> Option<Self> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
//fn note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
|
//note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
|
||||||
//todo!()
|
//todo!()
|
||||||
//}
|
//}
|
||||||
//fn set_sample (&self, state: &mut Sampler, slot: u7, s: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
//set_sample (&self, state: &mut Sampler, slot: u7, s: Option<Arc<RwLock<Sample>>>) -> Option<Self> {
|
||||||
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
//Some(Self::SetSample(p, state.set_sample(p, s)))
|
||||||
//}
|
//}
|
||||||
//fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
//import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
|
||||||
//match c {
|
//match c {
|
||||||
//FileBrowserCommand::Begin => {
|
//FileBrowserCommand::Begin => {
|
||||||
////let voices = &state.state.voices;
|
////let voices = &state.state.voices;
|
||||||
|
|
@ -170,14 +184,3 @@ impl SamplerCommand {
|
||||||
////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity"))))
|
////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity"))))
|
||||||
////("note/off" [p: u7]
|
////("note/off" [p: u7]
|
||||||
////Some(Self::NoteOff(p.expect("no slot"))))));
|
////Some(Self::NoteOff(p.expect("no slot"))))));
|
||||||
}
|
|
||||||
|
|
||||||
#[tengri_proc::command(Sampler)]
|
|
||||||
impl FileBrowserCommand {
|
|
||||||
//("begin" [] Some(Self::Begin))
|
|
||||||
//("cancel" [] Some(Self::Cancel))
|
|
||||||
//("confirm" [] Some(Self::Confirm))
|
|
||||||
//("select" [i: usize] Some(Self::Select(i.expect("no index"))))
|
|
||||||
//("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path"))))
|
|
||||||
//("filter" [f: Arc<str>] Some(Self::Filter(f.expect("no filter")))))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// The sampler device plays sounds in response to MIDI notes.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Sampler {
|
|
||||||
/// Name of sampler.
|
|
||||||
pub name: Arc<str>,
|
|
||||||
/// Device color.
|
|
||||||
pub color: ItemTheme,
|
|
||||||
/// Audio input ports. Samples get recorded here.
|
|
||||||
pub audio_ins: Vec<AudioInput>,
|
|
||||||
/// Audio input meters.
|
|
||||||
pub input_meters: Vec<f32>,
|
|
||||||
/// Sample currently being recorded.
|
|
||||||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
|
||||||
/// Recording buffer.
|
|
||||||
pub buffer: Vec<Vec<f32>>,
|
|
||||||
/// Samples mapped to MIDI notes.
|
|
||||||
pub mapped: [Option<Arc<RwLock<Sample>>>;128],
|
|
||||||
/// Samples that are not mapped to MIDI notes.
|
|
||||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
|
||||||
/// Sample currently being edited.
|
|
||||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
|
||||||
/// MIDI input port. Triggers sample playback.
|
|
||||||
pub midi_in: MidiInput,
|
|
||||||
/// Collection of currently playing instances of samples.
|
|
||||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
||||||
/// Audio output ports. Voices get played here.
|
|
||||||
pub audio_outs: Vec<AudioOutput>,
|
|
||||||
/// Audio output meters.
|
|
||||||
pub output_meters: Vec<f32>,
|
|
||||||
/// How to mix the voices.
|
|
||||||
pub mixing_mode: MixingMode,
|
|
||||||
/// How to meter the inputs and outputs.
|
|
||||||
pub metering_mode: MeteringMode,
|
|
||||||
/// Fixed gain applied to all output.
|
|
||||||
pub output_gain: f32,
|
|
||||||
/// Currently active modal, if any.
|
|
||||||
pub mode: Option<SamplerMode>,
|
|
||||||
/// Size of rendered sampler.
|
|
||||||
pub size: Measure<TuiOut>,
|
|
||||||
/// Lowest note displayed.
|
|
||||||
pub note_lo: AtomicUsize,
|
|
||||||
/// Currently selected note.
|
|
||||||
pub note_pt: AtomicUsize,
|
|
||||||
/// Selected note as row/col.
|
|
||||||
pub cursor: (AtomicUsize, AtomicUsize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sampler {
|
|
||||||
pub fn new (
|
|
||||||
jack: &Jack<'static>,
|
|
||||||
name: impl AsRef<str>,
|
|
||||||
midi_from: &[Connect],
|
|
||||||
audio_from: &[&[Connect];2],
|
|
||||||
audio_to: &[&[Connect];2],
|
|
||||||
) -> Usually<Self> {
|
|
||||||
let name = name.as_ref();
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?,
|
|
||||||
audio_ins: vec![
|
|
||||||
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
|
||||||
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
|
||||||
],
|
|
||||||
audio_outs: vec![
|
|
||||||
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
|
|
||||||
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
|
|
||||||
],
|
|
||||||
input_meters: vec![0.0;2],
|
|
||||||
output_meters: vec![0.0;2],
|
|
||||||
mapped: [const { None };128],
|
|
||||||
unmapped: vec![],
|
|
||||||
voices: Arc::new(RwLock::new(vec![])),
|
|
||||||
buffer: vec![vec![0.0;16384];2],
|
|
||||||
output_gain: 1.,
|
|
||||||
recording: None,
|
|
||||||
mode: None,
|
|
||||||
editing: None,
|
|
||||||
size: Default::default(),
|
|
||||||
note_lo: 0.into(),
|
|
||||||
note_pt: 0.into(),
|
|
||||||
cursor: (0.into(), 0.into()),
|
|
||||||
color: Default::default(),
|
|
||||||
mixing_mode: Default::default(),
|
|
||||||
metering_mode: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/// Value of cursor
|
|
||||||
pub fn cursor (&self) -> (usize, usize) {
|
|
||||||
(self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NoteRange for Sampler {
|
|
||||||
fn note_lo (&self) -> &AtomicUsize {
|
|
||||||
&self.note_lo
|
|
||||||
}
|
|
||||||
fn note_axis (&self) -> &AtomicUsize {
|
|
||||||
&self.size.y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotePoint for Sampler {
|
|
||||||
fn note_len (&self) -> &AtomicUsize {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
fn get_note_len (&self) -> usize {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn set_note_len (&self, x: usize) -> usize {
|
|
||||||
0 /*TODO?*/
|
|
||||||
}
|
|
||||||
fn note_pos (&self) -> &AtomicUsize {
|
|
||||||
&self.note_pt
|
|
||||||
}
|
|
||||||
fn get_note_pos (&self) -> usize {
|
|
||||||
self.note_pt.load(Relaxed)
|
|
||||||
}
|
|
||||||
fn set_note_pos (&self, x: usize) -> usize {
|
|
||||||
let old = self.note_pt.swap(x, Relaxed);
|
|
||||||
self.cursor.0.store(x % 8, Relaxed);
|
|
||||||
self.cursor.1.store(x / 8, Relaxed);
|
|
||||||
old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sound sample.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Sample {
|
|
||||||
pub name: Arc<str>,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
pub channels: Vec<Vec<f32>>,
|
|
||||||
pub rate: Option<usize>,
|
|
||||||
pub gain: f32,
|
|
||||||
pub color: ItemTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sample {
|
|
||||||
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
|
||||||
Self {
|
|
||||||
name: name.as_ref().into(),
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
channels,
|
|
||||||
rate: None,
|
|
||||||
gain: 1.0,
|
|
||||||
color: ItemTheme::random(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
|
||||||
Voice {
|
|
||||||
sample: sample.clone(),
|
|
||||||
after,
|
|
||||||
position: sample.read().unwrap().start,
|
|
||||||
velocity: velocity.as_int() as f32 / 127.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A currently playing instance of a sample.
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct Voice {
|
|
||||||
pub sample: Arc<RwLock<Sample>>,
|
|
||||||
pub after: usize,
|
|
||||||
pub position: usize,
|
|
||||||
pub velocity: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum SamplerMode {
|
|
||||||
// Load sample from path
|
|
||||||
Import(usize, Browser),
|
|
||||||
}
|
|
||||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit e3e3c163da02165e77a259eb715749b7f0097498
|
Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0
|
||||||
46
tek.edn
46
tek.edn
|
|
@ -1,12 +1,18 @@
|
||||||
(mode :menu (keys :x :y :confirm) :menu)
|
(keys :axis/x (@left x/dec) (@right x/inc))
|
||||||
(keys :x (@left x/dec) (@right x/inc))
|
(keys :axis/x2 (@shift/left x2/dec) (@shift/right x2/inc))
|
||||||
(keys :y (@up y/dec) (@down y/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))
|
||||||
|
|
||||||
|
(mode :menu (keys :axis/y :confirm) :menu)
|
||||||
(keys :confirm (@enter confirm))
|
(keys :confirm (@enter confirm))
|
||||||
(view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in
|
(view :menu (bg (g 40) (bsp/s :ports/out (bsp/n :ports/in
|
||||||
(bg (g 30) (fill/xy (align/c (bg (g 40) (bsp/s
|
(bg (g 30) (fill/xy (align/c :dialog/menu)))))))
|
||||||
(text CONTINUE-SESSION)
|
|
||||||
(bsp/s (text LOAD-OTHER-SESSION)
|
|
||||||
(text BEGIN-NEW-SESSION)))))))))))
|
|
||||||
(view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT)))
|
(view :ports/out (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-OUT)))
|
||||||
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R))))))))
|
(bsp/a (text MIDI-OUT) (fill/x (align/e (text AUDIO-OUT R))))))))
|
||||||
(view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN)))
|
(view :ports/in (fill/x (fixed/y 3 (bsp/a (fill/x (align/w (text L-AUDIO-IN)))
|
||||||
|
|
@ -16,18 +22,10 @@
|
||||||
(keys :help (@f1 dialog :help))
|
(keys :help (@f1 dialog :help))
|
||||||
(keys :back (@escape back))
|
(keys :back (@escape back))
|
||||||
(keys :page (@pgup page/up) (@pgdn page/down))
|
(keys :page (@pgup page/up) (@pgdn page/down))
|
||||||
(keys :x2 (@shift/left x2/dec) (@shift/right x2/inc))
|
|
||||||
(keys :y2 (@shift/up y2/dec) (@shift/down y2/inc))
|
|
||||||
(keys :z (@minus z/dec) (@equal z/inc))
|
|
||||||
(keys :z2 (@underscore z2/dec) (@plus z2/inc))
|
|
||||||
(keys :i (@comma i/dec) (@period z/inc))
|
|
||||||
(keys :i2 (@lt i2/dec) (@gt z2/inc))
|
|
||||||
(keys :w (@openbracket w/dec) (@closebracket w/inc))
|
|
||||||
(keys :w2 (@openbrace w2/dec) (@closebrace w2/inc))
|
|
||||||
(keys :delete (@delete delete) (@backspace delete/back))
|
(keys :delete (@delete delete) (@backspace delete/back))
|
||||||
(keys :input (see :x :delete) (:char input))
|
(keys :input (see :axis/x :delete) (:char input))
|
||||||
(keys :list (see :y :confirm))
|
(keys :list (see :axis/y :confirm))
|
||||||
(keys :length (see :x :y :confirm))
|
(keys :length (see :axis/x :axis/y :confirm))
|
||||||
(keys :browse (see :list :input :focus))
|
(keys :browse (see :list :input :focus))
|
||||||
(keys :history (@u undo 1) (@r redo 1))
|
(keys :history (@u undo 1) (@r redo 1))
|
||||||
(keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0))
|
(keys :clock (@space clock/toggle 0) (@shift/space clock/toggle 0))
|
||||||
|
|
@ -80,15 +78,15 @@
|
||||||
(@up select :select/scene/dec)
|
(@up select :select/scene/dec)
|
||||||
(@down select :select/scene/inc))
|
(@down select :select/scene/inc))
|
||||||
|
|
||||||
(keys :track (see :color :launch :z :z2 :delete)
|
(keys :track (see :color :launch :axis/z :axis/z2 :delete)
|
||||||
(@r toggle :rec)
|
(@r toggle :rec)
|
||||||
(@m toggle :mon)
|
(@m toggle :mon)
|
||||||
(@p toggle :play)
|
(@p toggle :play)
|
||||||
(@P toggle :solo))
|
(@P toggle :solo))
|
||||||
|
|
||||||
(keys :scene (see :color :launch :z :z2 :delete))
|
(keys :scene (see :color :launch :axis/z :axis/z2 :delete))
|
||||||
|
|
||||||
(keys :clip (see :color :launch :z :z2 :delete)
|
(keys :clip (see :color :launch :axis/z :axis/z2 :delete)
|
||||||
(@l toggle :loop))
|
(@l toggle :loop))
|
||||||
|
|
||||||
(mode :groovebox
|
(mode :groovebox
|
||||||
|
|
@ -154,13 +152,13 @@
|
||||||
(keys :sequencer (see :color :launch)
|
(keys :sequencer (see :color :launch)
|
||||||
(@shift/I input/add)
|
(@shift/I input/add)
|
||||||
(@shift/O output/add))
|
(@shift/O output/add))
|
||||||
(keys :pool (see :axis-y :axis-w :z2 :color :delete)
|
(keys :pool (see :axis-y :axis-w :axis/z2 :color :delete)
|
||||||
(@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin)
|
(@n rename/begin) (@t length/begin) (@m import/begin) (@x export/begin)
|
||||||
(@shift/A clip/add :after :new/clip)
|
(@shift/A clip/add :after :new/clip)
|
||||||
(@shift/D clip/add :after :cloned/clip))
|
(@shift/D clip/add :after :cloned/clip))
|
||||||
(keys :editor (see :editor/view :editor/note))
|
(keys :editor (see :editor/view :editor/note))
|
||||||
(keys :editor/view (see :x :x2 :z :z2)
|
(keys :editor/view (see :axis/x :axis/x2 :axis/z :axis/z2)
|
||||||
(@z toggle :lock))
|
(@z toggle :lock))
|
||||||
(keys :editor/note (see :i :i2 :y :page)
|
(keys :editor/note (see :axis/i :axis/i2 :axis/y :page)
|
||||||
(@a editor/append :true) (@enter editor/append :false)
|
(@a editor/append :true) (@enter editor/append :false)
|
||||||
(@del editor/delete/note) (@shift/del editor/delete/note))
|
(@del editor/delete/note) (@shift/del editor/delete/note))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue