use def_command

This commit is contained in:
🪞👃🪞 2025-08-23 23:20:15 +03:00
parent 5ccbb9719f
commit cfd19062fd
37 changed files with 1578 additions and 1594 deletions

View file

@ -10,6 +10,8 @@ mod app_view; pub use self::app_view::*;
/// Total state
#[derive(Default, Debug)]
pub struct App {
/// Base color.
pub color: ItemTheme,
/// Must not be dropped for the duration of the process
pub jack: Jack<'static>,
/// Display size
@ -20,26 +22,24 @@ pub struct App {
pub config: Config,
/// Currently selected mode
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
pub history: Vec<(AppCommand, Option<AppCommand>)>,
/// Dialog overlay
pub dialog: Dialog,
/// Base color.
pub color: ItemTheme,
/// Contains all recently created clips.
pub pool: Pool,
/// Contains the currently edited musical arrangement
pub project: Arrangement,
}
/// Various possible dialog modes.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, PartialEq)]
pub enum Dialog {
#[default] None,
Help(usize),
Menu(usize),
Menu(usize, usize),
Device(usize),
Message(Arc<str>),
Browser(BrowserTarget, Arc<Browser>),
Browse(BrowseTarget, Arc<Browse>),
Options,
}
has!(Jack<'static>: |self: App|self.jack);
@ -61,68 +61,9 @@ 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 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() } }
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 {
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> {
if let Self::Device(index) = self { Some(*index) } else { None }
@ -136,10 +77,10 @@ impl Dialog {
pub fn message (&self) -> Option<&str> {
todo!()
}
pub fn browser (&self) -> Option<&Arc<Browser>> {
pub fn browser (&self) -> Option<&Arc<Browse>> {
todo!()
}
pub fn browser_target (&self) -> Option<&BrowserTarget> {
pub fn browser_target (&self) -> Option<&BrowseTarget> {
todo!()
}
}
@ -189,8 +130,8 @@ impl App {
}
}
}
pub fn browser (&self) -> Option<&Browser> {
if let Dialog::Browser(_, ref b) = self.dialog { Some(b) } else { None }
pub fn browser (&self) -> Option<&Browse> {
if let Dialog::Browse(_, ref b) = self.dialog { Some(b) } else { None }
}
pub fn device_pick (&mut self, index: usize) {
self.dialog = Dialog::Device(index);

View file

@ -1,30 +1,62 @@
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)
});
impl<'t> DslNs<'t, AppCommand> for App {
dsl_exprs!(|app| -> AppCommand { /* TODO */ });
dsl_words!(|app| -> AppCommand {
"x/inc" => todo!(),
"x/dec" => todo!(),
"y/inc" => todo!(),
"y/dec" => todo!(),
"y/inc" => match app.dialog {
Dialog::Menu(index, count) => AppCommand::SetDialog {
dialog: Dialog::Menu(if count > 0 {
(index + 1) % count
} else { 0 }, count)
},
_ => todo!(),
},
"y/dec" => match app.dialog {
Dialog::Menu(index, count) => AppCommand::SetDialog {
dialog: Dialog::Menu(if count > 0 {
index.overflowing_sub(1).0.min(count.saturating_sub(1))
} else { 0 }, count)
},
_ => todo!(),
},
"confirm" => todo!(),
});
}
#[derive(Debug)]
pub enum AppCommand { /* TODO */ }
#[tengri_proc::command(Option<Dialog>)]
impl DialogCommand {
fn open (dialog: &mut Option<Dialog>, new: Dialog) -> Perhaps<Self> {
*dialog = Some(new);
Ok(None)
}
fn close (dialog: &mut Option<Dialog>) -> Perhaps<Self> {
*dialog = None;
Ok(None)
}
}
def_command!(AppCommand: |app: App| {
SetDialog { dialog: Dialog } =>
swap_value(&mut app.dialog, dialog, |dialog|Self::SetDialog { dialog }),
});
//AppCommand => {
//("x/inc" /

View file

@ -43,19 +43,19 @@ impl<'t> DslNs<'t, Dialog> for App {
":dialog/device/prev" => Dialog::Device(0),
":dialog/device/next" => Dialog::Device(0),
":dialog/help" => Dialog::Help(0),
":dialog/menu" => Dialog::Menu(0),
":dialog/save" => Dialog::Browser(BrowserTarget::SaveProject,
Browser::new(None).unwrap().into()),
":dialog/load" => Dialog::Browser(BrowserTarget::LoadProject,
Browser::new(None).unwrap().into()),
":dialog/import/clip" => Dialog::Browser(BrowserTarget::ImportClip(Default::default()),
Browser::new(None).unwrap().into()),
":dialog/export/clip" => Dialog::Browser(BrowserTarget::ExportClip(Default::default()),
Browser::new(None).unwrap().into()),
":dialog/import/sample" => Dialog::Browser(BrowserTarget::ImportSample(Default::default()),
Browser::new(None).unwrap().into()),
":dialog/export/sample" => Dialog::Browser(BrowserTarget::ExportSample(Default::default()),
Browser::new(None).unwrap().into()),
":dialog/menu" => Dialog::Menu(0, 0),
":dialog/save" => Dialog::Browse(BrowseTarget::SaveProject,
Browse::new(None).unwrap().into()),
":dialog/load" => Dialog::Browse(BrowseTarget::LoadProject,
Browse::new(None).unwrap().into()),
":dialog/import/clip" => Dialog::Browse(BrowseTarget::ImportClip(Default::default()),
Browse::new(None).unwrap().into()),
":dialog/export/clip" => Dialog::Browse(BrowseTarget::ExportClip(Default::default()),
Browse::new(None).unwrap().into()),
":dialog/import/sample" => Dialog::Browse(BrowseTarget::ImportSample(Default::default()),
Browse::new(None).unwrap().into()),
":dialog/export/sample" => Dialog::Browse(BrowseTarget::ExportSample(Default::default()),
Browse::new(None).unwrap().into()),
});
}

View file

@ -1,5 +1,49 @@
use crate::*;
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("")))),
})
}
impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
dsl_exprs!(|app| -> Box<dyn Render<TuiOut>> {
"text" (tail: Arc<str>) => Box::new(tail),
@ -71,12 +115,12 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
)))),
":browse/title" => Box::new(Fill::x(Align::w(FieldV(Default::default(),
match app.dialog.browser_target().unwrap() {
BrowserTarget::SaveProject => "Save project:",
BrowserTarget::LoadProject => "Load project:",
BrowserTarget::ImportSample(_) => "Import sample:",
BrowserTarget::ExportSample(_) => "Export sample:",
BrowserTarget::ImportClip(_) => "Import clip:",
BrowserTarget::ExportClip(_) => "Export clip:",
BrowseTarget::SaveProject => "Save project:",
BrowseTarget::LoadProject => "Load project:",
BrowseTarget::ImportSample(_) => "Import sample:",
BrowseTarget::ExportSample(_) => "Export sample:",
BrowseTarget::ImportClip(_) => "Import clip:",
BrowseTarget::ExportClip(_) => "Export clip:",
}, Shrink::x(3, Fixed::y(1, Tui::fg(Tui::g(96), RepeatH("🭻")))))))),
":device" => {
let selected = app.dialog.device_kind().unwrap();
@ -130,7 +174,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))))),
//Bsp::e(" ", Align::w(format!("{}", b.0.0.trim()))))))))))),
//Dialog::Browser(BrowserTarget::Load, browser) => {
//Dialog::Browse(BrowseTarget::Load, browser) => {
//"bobcat".boxed()
////Bsp::s(
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
@ -139,7 +183,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
////Outer(true, Style::default().fg(Tui::g(96)))
////.enclose(Fill::xy(browser)))
//},
//Dialog::Browser(BrowserTarget::Export, browser) => {
//Dialog::Browse(BrowseTarget::Export, browser) => {
//"bobcat".boxed()
////Bsp::s(
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(
@ -148,7 +192,7 @@ impl<'t> DslNs<'t, Box<dyn Render<TuiOut>>> for App {
////Outer(true, Style::default().fg(Tui::g(96)))
////.enclose(Fill::xy(browser)))
//},
//Dialog::Browser(BrowserTarget::Import, browser) => {
//Dialog::Browse(BrowseTarget::Import, browser) => {
//"bobcat".boxed()
////Bsp::s(
////Fill::x(Align::w(Margin::xy(1, 1, Bsp::e(

View file

@ -50,56 +50,51 @@ pub enum LaunchMode {
impl Cli {
pub fn run (&self) -> Usually<()> {
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
let config = Config::init()?;
let empty = &[] as &[&str];
let mut midi_ins = vec![];
let mut midi_outs = vec![];
let tracks = vec![];
let scenes = vec![];
let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re);
let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re);
let left_froms = Connect::collect(&self.left_from, empty, empty);
let left_tos = Connect::collect(&self.left_to, empty, empty);
let right_froms = Connect::collect(&self.right_from, empty, empty);
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()];
let name = self.name.as_ref().map_or("tek", |x|x.as_str());
let tracks = vec![];
let scenes = vec![];
let empty = &[] as &[&str];
let left_froms = Connect::collect(&self.left_from, empty, empty);
let left_tos = Connect::collect(&self.left_to, empty, empty);
let right_froms = Connect::collect(&self.right_from, empty, empty);
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()];
let mut config = Config::new(None);
config.init()?;
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 {
jack: jack.clone(),
color: ItemTheme::random(),
dialog: Dialog::Menu(0),
mode,
dialog: Dialog::Menu(0, 0),
mode: config.modes.clone().read().unwrap().get(":menu").cloned().unwrap(),
config,
project: Arrangement {
name: Default::default(),
color: ItemTheme::random(),
jack: jack.clone(),
clock,
clock: Clock::new(&jack, self.bpm)?,
tracks,
scenes,
selection: Selection::TrackClip { track: 0, scene: 0 },
midi_ins,
midi_outs,
midi_ins: {
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()
};
//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|{
let clock = app.clock();
clock.playhead.update_from_sample(state.position.frame() as f64);
@ -110,6 +105,12 @@ impl Cli {
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

View file

@ -20,11 +20,15 @@ use ::{
#[derive(Default, Debug)]
pub struct Config {
pub dirs: BaseDirectories,
pub modes: Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode<Arc<str>>>>>>,
pub views: Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>,
pub binds: Arc<RwLock<BTreeMap<Arc<str>, EventMap<TuiEvent, Arc<str>>>>>,
pub modes: Modes,
pub views: Views,
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,
/// with optional name and description.
#[derive(Default, Debug)]
@ -34,7 +38,7 @@ pub struct Mode<D: Dsl + Ord> {
pub info: Vec<D>,
pub view: Vec<D>,
pub keys: Vec<D>,
pub modes: BTreeMap<D, Mode<D>>,
pub modes: Modes,
}
/// A collection of input bindings.
@ -62,11 +66,16 @@ pub struct Condition(Arc<Box<dyn Fn()->bool + Send + Sync>>);
impl Config {
const CONFIG: &'static str = "tek.edn";
const DEFAULTS: &'static str = include_str!("../../tek.edn");
pub fn init () -> Usually<Self> {
let mut cfgs: Self = Default::default();
cfgs.dirs = BaseDirectories::with_profile("tek", "v0");
cfgs.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?;
Ok(cfgs)
pub fn new (dirs: Option<BaseDirectories>) -> Self {
Self {
dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")),
..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 (
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
@ -89,74 +98,59 @@ impl Config {
let tail = expr.tail()?;
let name = tail.head()?;
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 {
Some("view") if let Some(name) = name => self.load_view(name.into(), body),
Some("keys") if let Some(name) = name => self.load_bind(name.into(), body),
Some("mode") if let Some(name) = name => self.load_mode(name.into(), body),
Some("mode") if let Some(name) = name =>
Mode::<Arc<str>>::load_into(&self.modes, &name, &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())
}
Ok(())
} else {
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(())
}
pub fn load_bind (&mut self, id: Arc<str>, dsl: impl Dsl) -> Usually<()> {
let mut map = EventMap::new();
dsl.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())
})?;
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<()> {
pub fn load_one (&mut self, item: impl Dsl) -> Usually<()> {
Ok(if let Ok(Some(key)) = item.expr().head() {
match key {
"name" => mode.name.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()),
"info" => mode.info.push(item.expr()?.tail()?.map(|x|x.trim()).unwrap_or("").into()),
"name" => {
self.name.push(item.tail()?.map(|x|x.trim()).unwrap_or("").into())
},
"info" => {
self.info.push(item.tail()?.map(|x|x.trim()).unwrap_or("").into())
},
"keys" => {
item.expr()?.tail()?.each(|item|{mode.keys.push(item.trim().into()); Ok(())})?;
item.tail()?.each(|item|{
self.keys.push(item.trim().into());
Ok(())
})?;
},
"mode" => if let Some(id) = item.expr()?.tail()?.head()? {
let mut submode = Mode::default();
Self::load_mode_one(&mut submode, item.expr()?.tail()?.tail()?)?;
mode.modes.insert(id.into(), submode);
"self" => if let Some(id) = item.tail()?.head()? {
Self::load_into(&self.modes, &id, &item.tail().tail())?;
} else {
return Err(format!("load_mode_one: incomplete: {item:?}").into());
return Err(format!("Mode::load_one: self: incomplete: {item:?}").into());
},
_ => mode.view.push(item.expr()?.unwrap().into()),
}
_ if let Some(src) = item.src()? => self.view.push(src.into()),
_ => {},
};
} else if let Ok(Some(word)) = item.word() {
mode.view.push(word.into());
self.view.push(word.into());
} else {
return Err(format!("load_mode_one: unexpected: {item:?}").into());
return Err(format!("Mode::load_one: unexpected: {item:?}").into());
})
}
}
@ -196,6 +190,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> {
pub fn from_dsl (dsl: impl Dsl) -> Usually<Self> {
let command: Option<C> = None;

View file

@ -19,7 +19,7 @@ winit = { workspace = true, optional = true }
default = [ "arranger", "sampler", "lv2" ]
arranger = [ "port", "editor", "sequencer", "editor" ]
browser = []
browse = []
clap = []
clock = []
editor = []
@ -28,7 +28,7 @@ meter = []
mixer = []
pool = []
port = []
sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ]
sampler = [ "port", "meter", "mixer", "browse", "symphonia", "wavers" ]
sequencer = [ "port", "clock", "uuid", "pool" ]
sf2 = []
vst2 = []

View file

@ -2,7 +2,6 @@ use crate::*;
mod arranger_api; pub use self::arranger_api::*;
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_select; pub use self::arranger_select::*;
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("")));
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)
}
}

View file

@ -1,11 +1,10 @@
use crate::*;
#[tengri_proc::command(Arrangement)]
impl ArrangementCommand {
fn home (arranger: &mut Arrangement) -> Perhaps<Self> {
arranger.editor = None;
Ok(None)
}
fn edit (arranger: &mut Arrangement) -> Perhaps<Self> {
def_command!(ArrangementCommand: |arranger: Arrangement| {
Home => { arranger.editor = None; Ok(None) },
Edit => {
let selection = arranger.selection().clone();
arranger.editor = if arranger.editor.is_some() {
None
@ -36,14 +35,13 @@ impl ArrangementCommand {
}
}
Ok(None)
}
/// Set the selection
fn select (arranger: &mut Arrangement, s: Selection) -> Perhaps<Self> {
*arranger.selection_mut() = s;
Ok(None)
}
/// Launch a clip or scene
fn launch (arranger: &mut Arrangement) -> Perhaps<Self> {
},
//// Set the selection
Select { selection: Selection } => { *arranger.selection_mut() = *selection; Ok(None) },
//// Launch the selected clip or scene
Launch => {
match *arranger.selection() {
Selection::Track(t) => {
arranger.tracks[t].sequencer.enqueue_next(None)
@ -59,9 +57,10 @@ impl ArrangementCommand {
_ => {}
};
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 selection = *arranger.selection();
Ok(Some(Self::SetColor { palette: Some(match selection {
@ -88,11 +87,11 @@ impl ArrangementCommand {
},
_ => todo!()
}) }))
}
fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps<Self> {
todo!("delegate")
}
fn track_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
Track { track: TrackCommand } => { todo!("delegate") },
TrackAdd => {
let index = arranger.track_add(None, None, &[], &[])?.0;
*arranger.selection_mut() = match arranger.selection() {
Selection::Track(_) => Selection::Track(index),
@ -102,12 +101,16 @@ impl ArrangementCommand {
_ => *arranger.selection()
};
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 }))
}
fn track_delete (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
},
TrackDelete { index: usize } => {
let index = *index;
let exists = arranger.tracks().get(index).is_some();
if exists {
let track = arranger.tracks_mut().remove(index);
@ -124,52 +127,61 @@ impl ArrangementCommand {
}
Ok(None)
//TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) })
}
fn midi_in (_arranger: &mut Arrangement, _input: MidiInputCommand) -> Perhaps<Self> {
todo!("delegate");
Ok(None)
}
fn midi_in_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
MidiIn { input: MidiInputCommand } => {
todo!("delegate"); Ok(None)
},
MidiInAdd => {
arranger.midi_in_add()?;
Ok(None)
}
fn midi_out (_arranger: &mut Arrangement, _input: MidiOutputCommand) -> Perhaps<Self> {
},
MidiOut { output: MidiOutputCommand } => {
todo!("delegate");
Ok(None)
}
fn midi_out_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
MidiOutAdd => {
arranger.midi_out_add()?;
Ok(None)
}
fn device (_arranger: &mut Arrangement, _input: DeviceCommand) -> Perhaps<Self> {
},
Device { command: DeviceCommand } => {
todo!("delegate");
Ok(None)
}
fn device_add (_arranger: &mut Arrangement, _i: usize) -> Perhaps<Self> {
},
DeviceAdd { index: usize } => {
todo!("delegate");
Ok(None)
}
fn scene (arranger: &mut Arrangement, scene: SceneCommand) -> Perhaps<Self> {
},
Scene { scene: SceneCommand } => {
todo!("delegate");
Ok(None)
}
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
OutputAdd => {
arranger.midi_outs.push(MidiOutput::new(
arranger.jack(),
&format!("/M{}", arranger.midi_outs.len() + 1),
&[]
)?);
Ok(None)
}
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
InputAdd => {
arranger.midi_ins.push(MidiInput::new(
arranger.jack(),
&format!("M{}/", arranger.midi_ins.len() + 1),
&[]
)?);
Ok(None)
}
fn scene_add (arranger: &mut Arrangement) -> Perhaps<Self> {
},
SceneAdd => {
let index = arranger.scene_add(None, None)?.0;
*arranger.selection_mut() = match arranger.selection() {
Selection::Scene(_) => Selection::Scene(index),
@ -180,12 +192,16 @@ impl ArrangementCommand {
_ => *arranger.selection()
};
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 }))
}
fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
},
SceneDelete { index: usize } => {
let index = *index;
let scenes = arranger.scenes_mut();
Ok(if scenes.get(index).is_some() {
let _scene = scenes.remove(index);
@ -193,41 +209,50 @@ impl ArrangementCommand {
} else {
None
})
}
fn scene_launch (arranger: &mut Arrangement, index: usize) -> Perhaps<Self> {
},
SceneLaunch { index: usize } => {
let index = *index;
for track in 0..arranger.tracks.len() {
let clip = arranger.scenes[index].clips[track].as_ref();
arranger.tracks[track].sequencer.enqueue_next(clip);
}
Ok(None)
}
fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps<Self> {
},
Clip { scene: ClipCommand } => {
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] Some(Self::Get(a.unwrap(), b.unwrap())))
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]
//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())))
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))))
todo!()
}
fn clip_enqueue (arranger: &mut Arrangement, a: usize, b: usize) -> Perhaps<Self> {
},
ClipEnqueue { a: usize, b: usize } => {
//(Enqueue [t: usize, s: usize]
//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())))
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" [a: MaybeClip] Some(Self::Edit(a.unwrap())))
todo!()
}
}
},
});

View file

@ -10,17 +10,19 @@ impl MidiClip {
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
}
#[tengri_proc::command(MidiClip)]
impl ClipCommand {
fn set_color (clip: &mut MidiClip, color: Option<ItemTheme>) -> Perhaps<Self> {
def_command!(ClipCommand: |clip: MidiClip| {
SetColor { color: Option<ItemTheme> } => {
//(SetColor [t: usize, s: usize, c: ItemTheme]
//clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o)))));
//("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random())))
todo!()
}
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:?}"))
//("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap())))
todo!()
}
}
});

View file

@ -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)
}
}

View file

@ -64,23 +64,14 @@ impl Scene {
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
}
#[tengri_proc::command(Scene)]
impl SceneCommand {
fn set_name (scene: &mut Scene, mut name: Arc<str>) -> Perhaps<Self> {
std::mem::swap(&mut scene.name, &mut name);
Ok(Some(Self::SetName { name }))
}
fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps<Self> {
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!()
}
}
def_command!(SceneCommand: |scene: Scene| {
SetSize { size: usize } => { todo!() },
SetZoom { size: usize } => { todo!() },
SetName { name: Arc<str> } =>
swap_value(&mut scene.name, name, |name|Self::SetName{name}),
SetColor { color: ItemTheme } =>
swap_value(&mut scene.color, color, |color|Self::SetColor{color}),
});
impl<T: Has<Option<Scene>> + Send + Sync> HasScene for T {}

View file

@ -129,43 +129,21 @@ impl Track {
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
}
#[tengri_proc::command(Track)]
impl TrackCommand {
fn set_name (track: &mut Track, mut name: Arc<str>) -> Perhaps<Self> {
std::mem::swap(&mut name, &mut track.name);
Ok(Some(Self::SetName { name }))
}
fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps<Self> {
std::mem::swap(&mut color, &mut track.color);
Ok(Some(Self::SetColor { color }))
}
fn set_mute (_track: &mut Track, _value: Option<bool>) -> Perhaps<Self> {
todo!()
}
fn set_solo (_track: &mut Track, _value: Option<bool>) -> Perhaps<Self> {
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)
}
}
def_command!(TrackCommand: |track: Track| {
Stop => { track.sequencer.enqueue_next(None); Ok(None) },
SetMute { mute: Option<bool> } => todo!(),
SetSolo { solo: Option<bool> } => todo!(),
SetSize { size: usize } => todo!(),
SetZoom { zoom: usize } => todo!(),
SetName { name: Arc<str> } =>
swap_value(&mut track.name, name, |name|Self::SetName { name }),
SetColor { color: ItemTheme } =>
swap_value(&mut track.color, color, |color|Self::SetColor { color }),
SetRec { rec: Option<bool> } =>
toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }),
SetMon { mon: Option<bool> } =>
toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }),
});
#[derive(Debug, Default)]
pub struct Track {

View file

@ -1,9 +1,11 @@
use crate::*;
use std::path::PathBuf;
use std::ffi::OsString;
mod browse_api; pub use self::browse_api::*;
mod browse_view; //pub use self::browse_view::*;
#[derive(Clone, Debug)]
pub enum BrowserTarget {
pub enum BrowseTarget {
SaveProject,
LoadProject,
ImportSample(Arc<RwLock<Option<Sample>>>),
@ -12,9 +14,21 @@ pub enum BrowserTarget {
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
#[derive(Debug, Clone, Default)]
pub struct Browser {
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Browse {
pub cwd: PathBuf,
pub dirs: Vec<(OsString, String)>,
pub files: Vec<(OsString, String)>,
@ -24,7 +38,7 @@ pub struct Browser {
pub size: Measure<TuiOut>,
}
impl Browser {
impl Browse {
pub fn new (cwd: Option<PathBuf>) -> Usually<Self> {
let cwd = if let Some(cwd) = cwd { cwd } else { std::env::current_dir()? };

View 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!()
//}

View file

@ -1,6 +1,6 @@
use crate::*;
content!(TuiOut: |self: Browser|Map::south(1, ||EntriesIterator {
content!(TuiOut: |self: Browse|Map::south(1, ||EntriesIterator {
offset: 0,
index: 0,
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))));
struct EntriesIterator<'a> {
browser: &'a Browser,
browser: &'a Browse,
offset: usize,
length: usize,
index: usize,

View file

@ -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::*;

View file

@ -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!()
//}

View file

@ -14,48 +14,33 @@ impl Clock {
}
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
}
}
#[tengri_proc::command(Clock)]
impl ClockCommand {
fn play (state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
state.play_from(position)?;
Ok(None) // TODO Some(Pause(previousPosition))
}
fn pause (state: &mut Clock, position: Option<u32>) -> Perhaps<Self> {
state.pause_at(position)?;
Ok(None)
}
fn toggle_playback (state: &mut Clock, position: u32) -> Perhaps<Self> {
if state.is_rolling() {
state.pause_at(Some(position))?;
} else {
state.play_from(Some(position))?;
}
Ok(None)
}
fn seek_usec (state: &mut Clock, usec: f64) -> Perhaps<Self> {
state.playhead.update_from_usec(usec);
Ok(None)
}
fn seek_sample (state: &mut Clock, sample: f64) -> Perhaps<Self> {
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) }))
}
}
def_command!(ClockCommand: |clock: Clock| {
SeekUsec { usec: f64 } => {
clock.playhead.update_from_usec(*usec); Ok(None) },
SeekSample { sample: f64 } => {
clock.playhead.update_from_sample(*sample); Ok(None) },
SeekPulse { pulse: f64 } => {
clock.playhead.update_from_pulse(*pulse); Ok(None) },
SetBpm { bpm: f64 } => Ok(Some(
Self::SetBpm { bpm: clock.timebase().bpm.set(*bpm) })),
SetQuant { quant: f64 } => Ok(Some(
Self::SetQuant { quant: clock.quant.set(*quant) })),
SetSync { sync: f64 } => Ok(Some(
Self::SetSync { sync: clock.sync.set(*sync) })),
Play { position: Option<u32> } => {
clock.play_from(*position)?; Ok(None) /* TODO Some(Pause(previousPosition)) */ },
Pause { position: Option<u32> } => {
clock.pause_at(*position)?; Ok(None) },
TogglePlayback { position: u32 } => Ok(if clock.is_rolling() {
clock.pause_at(Some(*position))?; None
} else {
clock.play_from(Some(*position))?; None
}),
});

View file

@ -84,6 +84,4 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
}
});
#[tengri_proc::command(Device)]
impl DeviceCommand {
}
def_command!(DeviceCommand: |device: Device| {});

View file

@ -1,5 +1,29 @@
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 {
fn _todo_opt_clip_stub (&self) -> Option<Arc<RwLock<MidiClip>>> {
todo!()
@ -91,56 +115,3 @@ use crate::*;
!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
}

View file

@ -32,7 +32,7 @@ macro_rules! def_sizes_iter {
}
#[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 = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*;
#[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))
}
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))))
}
}

View file

@ -1,3 +1,213 @@
mod pool_api; pub use self::pool_api::*;
mod pool_model; pub use self::pool_model::*;
mod pool_view; pub use self::pool_view::*;
use crate::*;
mod pool_api; pub use self::pool_api::*;
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
});

View file

@ -8,85 +8,65 @@ impl Pool {
fn _todo_path_ (&self) -> PathBuf { todo!() }
fn _todo_color_ (&self) -> ItemColor { todo!() }
fn _todo_str_ (&self) -> Arc<str> { todo!() }
fn clip_new (&self) -> MidiClip {
self.new_clip()
}
fn clip_cloned (&self) -> MidiClip {
self.cloned_clip()
}
fn clip_index_current (&self) -> usize {
0
}
fn clip_index_after (&self) -> usize {
0
}
fn clip_index_previous (&self) -> usize {
0
}
fn clip_index_next (&self) -> usize {
0
}
fn color_random (&self) -> ItemColor {
ItemColor::random()
}
fn clip_new (&self) -> MidiClip { self.new_clip() }
fn clip_cloned (&self) -> MidiClip { self.cloned_clip() }
fn clip_index_current (&self) -> usize { 0 }
fn clip_index_after (&self) -> usize { 0 }
fn clip_index_previous (&self) -> usize { 0 }
fn clip_index_next (&self) -> usize { 0 }
fn color_random (&self) -> ItemColor { ItemColor::random() }
}
#[tengri_proc::command(Pool)]
impl PoolCommand {
/// Toggle visibility of pool
fn show (pool: &mut Pool, visible: bool) -> Perhaps<Self> {
pool.visible = visible;
Ok(Some(Self::Show { visible: !visible }))
}
/// Select a clip from the clip pool
fn select (pool: &mut Pool, index: usize) -> Perhaps<Self> {
pool.set_clip_index(index);
Ok(None)
}
/// Rename a clip
fn rename (pool: &mut Pool, command: RenameCommand) -> Perhaps<Self> {
Ok(command.delegate(pool, |command|Self::Rename{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})?
def_command!(PoolCommand: |pool: Pool| {
// Toggle visibility of pool
Show { visible: bool } => {
pool.visible = *visible; Ok(Some(Self::Show { visible: !visible })) },
// Select a clip from the clip pool
Select { index: usize } => {
pool.set_clip_index(*index); Ok(None) },
// Update the contents of the clip pool
Clip { command: PoolClipCommand } =>
Ok(command.execute(pool)?.map(|command|Self::Clip{command})),
// Rename a clip
Rename { command: RenameCommand } =>
Ok(command.delegate(pool, |command|Self::Rename{command})?),
// Change the length of a clip
Length { command: CropCommand } =>
Ok(command.delegate(pool, |command|Self::Length{command})?),
// Import from file
Import { command: BrowseCommand } => {
Ok(if let Some(browse) = pool.browse.as_mut() {
command.delegate(browse, |command|Self::Import{command})?
} else {
None
})
}
/// Export to file
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})?
None }) },
// Export to file
Export { command: BrowseCommand } => {
Ok(if let Some(browse) = pool.browse.as_mut() {
command.delegate(browse, |command|Self::Export{command})?
} else {
None
})
}
None }) },
});
/// Update the contents of the clip pool
fn clip (pool: &mut Pool, command: PoolClipCommand) -> Perhaps<Self> {
Ok(command.execute(pool)?.map(|command|Self::Clip{command}))
}
def_command!(PoolClipCommand: |pool: Pool| {
}
Delete { index: usize } => {
let index = *index;
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
Ok(Some(Self::Add { index, clip })) },
#[tengri_proc::command(Pool)]
impl PoolClipCommand {
Swap { index: usize, other: usize } => {
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 clip = Arc::new(RwLock::new(clip));
let clip = Arc::new(RwLock::new(clip.clone()));
let mut clips = pool.clips_mut();
if index >= clips.len() {
index = clips.len();
@ -94,20 +74,10 @@ impl PoolClipCommand {
} else {
clips.insert(index, clip);
}
Ok(Some(Self::Delete { index }))
}
Ok(Some(Self::Delete { index })) },
fn delete (pool: &mut Pool, index: usize) -> Perhaps<Self> {
let clip = pool.clips_mut().remove(index).read().unwrap().clone();
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> {
Import { index: usize, path: PathBuf } => {
let index = *index;
let bytes = std::fs::read(&path)?;
let smf = Smf::parse(bytes.as_slice())?;
let mut t = 0u32;
@ -124,74 +94,64 @@ impl PoolClipCommand {
for event in events.iter() {
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> {
todo!("export clip to midi file");
}
fn set_name (pool: &mut Pool, index: usize, name: Arc<str>) -> Perhaps<Self> {
SetName { index: usize, name: Arc<str> } => {
let index = *index;
let clip = &mut pool.clips_mut()[index];
let old_name = clip.read().unwrap().name.clone();
clip.write().unwrap().name = name;
Ok(Some(Self::SetName { index, name: old_name }))
}
clip.write().unwrap().name = name.clone();
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 old_len = clip.read().unwrap().length;
clip.write().unwrap().length = length;
Ok(Some(Self::SetLength { index, length: old_len }))
}
clip.write().unwrap().length = *length;
Ok(Some(Self::SetLength { index, length: old_len })) },
fn set_color (pool: &mut Pool, index: usize, color: ItemColor) -> Perhaps<Self> {
let mut color = ItemTheme::from(color);
SetColor { index: usize, color: ItemColor } => {
let index = *index;
let mut color = ItemTheme::from(*color);
std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color);
Ok(Some(Self::SetColor { index, color: color.base }))
}
Ok(Some(Self::SetColor { index, color: color.base })) },
}
});
#[tengri_proc::command(Pool)]
impl RenameCommand {
fn begin (_pool: &mut Pool) -> Perhaps<Self> {
unreachable!();
}
fn cancel (pool: &mut Pool) -> Perhaps<Self> {
def_command!(RenameCommand: |pool: Pool| {
Begin => unreachable!(),
Cancel => {
if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() {
pool.clips()[clip].write().unwrap().name = old_name.clone().into();
}
return Ok(None)
}
fn confirm (pool: &mut Pool) -> Perhaps<Self> {
Ok(None) },
Confirm => {
if let Some(PoolMode::Rename(_clip, ref mut old_name)) = pool.mode_mut().clone() {
let old_name = old_name.clone();
*pool.mode_mut() = None;
return Ok(Some(Self::Set { value: old_name }))
}
return 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)
}
}
Ok(None) },
#[tengri_proc::command(Pool)]
impl CropCommand {
fn begin (_pool: &mut Pool) -> Perhaps<Self> {
unreachable!()
}
fn cancel (pool: &mut Pool) -> Perhaps<Self> {
Set { value: Arc<str> } => {
if let Some(PoolMode::Rename(clip, ref mut _old_name)) = pool.mode_mut().clone() {
pool.clips()[clip].write().unwrap().name = value.clone();
}
Ok(None) },
});
def_command!(CropCommand: |pool: Pool| {
Begin => unreachable!(),
Cancel => {
if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() {
*pool.mode_mut() = None;
}
Ok(None)
}
fn set (pool: &mut Pool, length: usize) -> Perhaps<Self> {
Ok(None) },
Set { length: usize } => {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
@ -204,25 +164,25 @@ impl CropCommand {
*pool.mode_mut() = None;
return Ok(old_length.map(|length|Self::Set { length }))
}
Ok(None)
}
fn next (pool: &mut Pool) -> Perhaps<Self> {
Ok(None) },
Next => {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
focus.next()
}
Ok(None)
}
fn prev (pool: &mut Pool) -> Perhaps<Self> {
Ok(None) },
Prev => {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
focus.prev()
}
Ok(None)
}
fn inc (pool: &mut Pool) -> Perhaps<Self> {
Ok(None) },
Inc => {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
@ -232,9 +192,9 @@ impl CropCommand {
ClipLengthFocus::Tick => { *length += 1 },
}
}
Ok(None)
}
fn dec (pool: &mut Pool) -> Perhaps<Self> {
Ok(None) },
Dec => {
if let Some(PoolMode::Length(clip, ref mut length, ref mut focus))
= pool.mode_mut().clone()
{
@ -244,6 +204,5 @@ impl CropCommand {
ClipLengthFocus::Tick => { *length = length.saturating_sub(1) },
}
}
Ok(None)
}
}
Ok(None) },
});

View file

@ -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
});

View file

@ -1,5 +1,7 @@
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_in; pub use self::port_audio_in::*;
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 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> {
/// Register a MIDI input port.
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)
}
}
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()
}
}

View 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!(),
});

View file

@ -1,20 +1,7 @@
use crate::*;
#[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>,
}
impl HasJack<'static> for AudioInput {
fn jack (&self) -> &Jack<'static> {
&self.jack
}
}
impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl JackPort for AudioInput {
type Port = AudioIn;
type Pair = AudioOut;

View file

@ -1,20 +1,7 @@
use crate::*;
#[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>,
}
impl HasJack<'static> for AudioOutput {
fn jack (&self) -> &Jack<'static> {
&self.jack
}
}
impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl JackPort for AudioOutput {
type Port = AudioOut;
type Pair = AudioIn;

View 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()
}
}

View file

@ -1,24 +1,7 @@
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 {
type Port = MidiIn;
type Pair = MidiOut;
@ -51,17 +34,13 @@ impl JackPort for MidiInput {
Ok(port)
}
}
impl MidiInput {
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
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 {
fn midi_ins (&self) -> &Vec<MidiInput> {
self.get()

View file

@ -1,26 +1,7 @@
use crate::*;
#[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>>>,
}
impl HasJack<'static> for MidiOutput {
fn jack (&self) -> &Jack<'static> {
&self.jack
}
}
impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl JackPort for MidiOutput {
type Port = MidiOut;
type Pair = MidiIn;
@ -59,6 +40,7 @@ impl JackPort for MidiOutput {
Ok(port)
}
}
impl MidiOutput {
/// Clear the section of the output buffer that we will be using,
/// 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 {
fn midi_outs (&self) -> &Vec<MidiOutput> {

View file

@ -1,3 +1,5 @@
use crate::*;
pub(crate) use symphonia::{
core::{
formats::Packet,
@ -14,7 +16,6 @@ mod sampler_api; pub use self::sampler_api::*;
mod sampler_audio;
mod sampler_browse; pub use self::sampler_browse::*;
mod sampler_midi; pub use self::sampler_midi::*;
mod sampler_model; pub use self::sampler_model::*;
mod sampler_data;
mod sampler_view;
@ -23,3 +24,177 @@ mod sampler_view;
// TODO!
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),
}

View file

@ -1,7 +1,69 @@
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]
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> {
//todo!()
//}
@ -40,82 +102,34 @@ impl Sampler {
//fn selected_pitch () -> u7 {
//(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)]
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> {
//select (&self, state: &mut Sampler, i: usize) -> Option<Self> {
//Self::Select(state.set_note_pos(i))
//}
///// 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 old = self.mapped[i].clone();
//self.mapped[i] = sample;
//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!()
//}
//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!()
//}
//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!()
//}
//fn note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
//note_off (&self, state: &mut Sampler, slot: u7) -> Option<Self> {
//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)))
//}
//fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
//import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option<Self> {
//match c {
//FileBrowserCommand::Begin => {
////let voices = &state.state.voices;
@ -170,14 +184,3 @@ impl SamplerCommand {
////Some(Self::NoteOn(p.expect("no slot"), v.expect("no velocity"))))
////("note/off" [p: u7]
////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")))))
}

View file

@ -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

@ -1 +1 @@
Subproject commit e3e3c163da02165e77a259eb715749b7f0097498
Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0

View file

@ -1,4 +1,4 @@
(mode :menu (keys :x :y :confirm) :menu)
(mode :menu (keys :y :confirm) :menu)
(keys :x (@left x/dec) (@right x/inc))
(keys :y (@up y/dec) (@down y/inc))
(keys :confirm (@enter confirm))