mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-01-11 02:26:41 +01:00
use def_command
This commit is contained in:
parent
5ccbb9719f
commit
cfd19062fd
37 changed files with 1578 additions and 1594 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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" /
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
use crate::*;
|
||||
#[derive(Default, Debug)] pub struct Arrangement {
|
||||
/// Project name.
|
||||
pub name: Arc<str>,
|
||||
/// Base color.
|
||||
pub color: ItemTheme,
|
||||
/// Jack client handle
|
||||
pub jack: Jack<'static>,
|
||||
/// Source of time
|
||||
pub clock: Clock,
|
||||
/// Allows one MIDI clip to be edited
|
||||
pub editor: Option<MidiEditor>,
|
||||
/// List of global midi inputs
|
||||
pub midi_ins: Vec<MidiInput>,
|
||||
/// List of global midi outputs
|
||||
pub midi_outs: Vec<MidiOutput>,
|
||||
/// List of global audio inputs
|
||||
pub audio_ins: Vec<AudioInput>,
|
||||
/// List of global audio outputs
|
||||
pub audio_outs: Vec<AudioOutput>,
|
||||
/// Last track number (to avoid duplicate port names)
|
||||
pub track_last: usize,
|
||||
/// List of tracks
|
||||
pub tracks: Vec<Track>,
|
||||
/// Scroll offset of tracks
|
||||
pub track_scroll: usize,
|
||||
/// List of scenes
|
||||
pub scenes: Vec<Scene>,
|
||||
/// Scroll offset of scenes
|
||||
pub scene_scroll: usize,
|
||||
/// Selected UI element
|
||||
pub selection: Selection,
|
||||
/// Contains a render of the project arrangement, redrawn on update.
|
||||
/// TODO rename to "render_cache" or smth
|
||||
pub arranger: Arc<RwLock<Buffer>>,
|
||||
/// Display size
|
||||
pub size: Measure<TuiOut>,
|
||||
/// Display size of clips area
|
||||
pub inner_size: Measure<TuiOut>,
|
||||
}
|
||||
impl HasJack<'static> for Arrangement {
|
||||
fn jack (&self) -> &Jack<'static> {
|
||||
&self.jack
|
||||
}
|
||||
}
|
||||
has!(Jack<'static>: |self: Arrangement|self.jack);
|
||||
has!(Clock: |self: Arrangement|self.clock);
|
||||
has!(Selection: |self: Arrangement|self.selection);
|
||||
has!(Vec<MidiInput>: |self: Arrangement|self.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self: Arrangement|self.midi_outs);
|
||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
||||
has!(Option<MidiEditor>: |self: Arrangement|self.editor);
|
||||
maybe_has!(Track: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
||||
maybe_has!(Scene: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
||||
//take!(MidiInputCommand |state: Arrangement, iter|state.selected_midi_in().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
//take!(MidiOutputCommand |state: Arrangement, iter|state.selected_midi_out().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
//take!(TrackCommand |state: Arrangement, iter|state.selected_track().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
//take!(SceneCommand |state: Arrangement, iter|state.selected_scene().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
#[tengri_proc::expose] impl Arrangement {
|
||||
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
||||
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||
fn selected_track (&self) -> Option<Track> { todo!() }
|
||||
fn selected_scene (&self) -> Option<Scene> { todo!() }
|
||||
fn selected_clip (&self) -> Option<MidiClip> { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||
fn select_nothing (&self) -> Selection {
|
||||
Selection::Nothing
|
||||
}
|
||||
}
|
||||
impl Arrangement {
|
||||
/// Width of display
|
||||
pub fn w (&self) -> u16 {
|
||||
self.size.w() as u16
|
||||
}
|
||||
/// Width allocated for sidebar.
|
||||
pub fn w_sidebar (&self, is_editing: bool) -> u16 {
|
||||
self.w() / if is_editing { 16 } else { 8 } as u16
|
||||
}
|
||||
/// Width available to display tracks.
|
||||
pub fn w_tracks_area (&self, is_editing: bool) -> u16 {
|
||||
self.w().saturating_sub(self.w_sidebar(is_editing))
|
||||
}
|
||||
/// Height of display
|
||||
pub fn h (&self) -> u16 {
|
||||
self.size.h() as u16
|
||||
}
|
||||
/// Height taken by visible device slots.
|
||||
pub fn h_devices (&self) -> u16 {
|
||||
2
|
||||
//1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0)
|
||||
}
|
||||
/// Get the active track
|
||||
pub fn get_track (&self) -> Option<&Track> {
|
||||
let index = self.selection().track()?;
|
||||
Has::<Vec<Track>>::get(self).get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active track
|
||||
pub fn get_track_mut (&mut self) -> Option<&mut Track> {
|
||||
let index = self.selection().track()?;
|
||||
Has::<Vec<Track>>::get_mut(self).get_mut(index)
|
||||
}
|
||||
/// Get the active scene
|
||||
pub fn get_scene (&self) -> Option<&Scene> {
|
||||
let index = self.selection().scene()?;
|
||||
Has::<Vec<Scene>>::get(self).get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active scene
|
||||
pub fn get_scene_mut (&mut self) -> Option<&mut Scene> {
|
||||
let index = self.selection().scene()?;
|
||||
Has::<Vec<Scene>>::get_mut(self).get_mut(index)
|
||||
}
|
||||
/// Get the active clip
|
||||
pub fn get_clip (&self) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
self.get_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||
}
|
||||
/// Put a clip in a slot
|
||||
pub fn clip_put (
|
||||
&mut self, track: usize, scene: usize, clip: Option<Arc<RwLock<MidiClip>>>
|
||||
) -> Option<Arc<RwLock<MidiClip>>> {
|
||||
let old = self.scenes[scene].clips[track].clone();
|
||||
self.scenes[scene].clips[track] = clip;
|
||||
old
|
||||
}
|
||||
/// Change the color of a clip, returning the previous one
|
||||
pub fn clip_set_color (&self, track: usize, scene: usize, color: ItemTheme)
|
||||
-> Option<ItemTheme>
|
||||
{
|
||||
self.scenes[scene].clips[track].as_ref().map(|clip|{
|
||||
let mut clip = clip.write().unwrap();
|
||||
let old = clip.color.clone();
|
||||
clip.color = color.clone();
|
||||
panic!("{color:?} {old:?}");
|
||||
old
|
||||
})
|
||||
}
|
||||
/// Toggle looping for the active clip
|
||||
pub fn toggle_loop (&mut self) {
|
||||
if let Some(clip) = self.get_clip() {
|
||||
clip.write().unwrap().toggle_loop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sampler")]
|
||||
impl Arrangement {
|
||||
/// Get the first sampler of the active track
|
||||
pub fn sampler (&self) -> Option<&Sampler> {
|
||||
self.get_track()?.sampler(0)
|
||||
}
|
||||
/// Get the first sampler of the active track
|
||||
pub fn sampler_mut (&mut self) -> Option<&mut Sampler> {
|
||||
self.get_track_mut()?.sampler_mut(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScenesView for Arrangement {
|
||||
fn h_scenes (&self) -> u16 {
|
||||
(self.height() as u16).saturating_sub(20)
|
||||
}
|
||||
fn w_side (&self) -> u16 {
|
||||
(self.width() as u16 * 2 / 10).max(20)
|
||||
}
|
||||
fn w_mid (&self) -> u16 {
|
||||
(self.width() as u16).saturating_sub(2 * self.w_side()).max(40)
|
||||
}
|
||||
}
|
||||
|
|
@ -64,23 +64,14 @@ impl Scene {
|
|||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
}
|
||||
|
||||
#[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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()? };
|
||||
93
crates/device/src/browse/browse_api.rs
Normal file
93
crates/device/src/browse/browse_api.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Browse {
|
||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_usize (&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_arc_str (&self) -> Arc<str> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
def_command!(BrowseCommand: |browse: Browse| {
|
||||
SetVisible => Ok(None),
|
||||
SetPath { address: PathBuf } => Ok(None),
|
||||
SetSearch { filter: Arc<str> } => Ok(None),
|
||||
SetCursor { cursor: usize } => Ok(None),
|
||||
});
|
||||
|
||||
// Commands supported by [Browse]
|
||||
//#[derive(Debug, Clone, PartialEq)]
|
||||
//pub enum BrowseCommand {
|
||||
//Begin,
|
||||
//Cancel,
|
||||
//Confirm,
|
||||
//Select(usize),
|
||||
//Chdir(PathBuf),
|
||||
//Filter(Arc<str>),
|
||||
//}
|
||||
//fn begin (browse: &mut Browse) => {
|
||||
//unreachable!();
|
||||
//}
|
||||
//fn cancel (browse: &mut Browse) => {
|
||||
//todo!()
|
||||
////browse.mode = None;
|
||||
////Ok(None)
|
||||
//}
|
||||
//fn confirm (browse: &mut Browse) => {
|
||||
//todo!()
|
||||
////Ok(match browse.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||
////if browse.is_file() {
|
||||
////let path = browse.path();
|
||||
////browse.mode = None;
|
||||
////let _undo = PoolClipCommand::import(browse, index, path)?;
|
||||
////None
|
||||
////} else if browse.is_dir() {
|
||||
////browse.mode = Some(PoolMode::Import(index, browse.chdir()?));
|
||||
////None
|
||||
////} else {
|
||||
////None
|
||||
////}
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||
////todo!()
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn select (browse: &mut Browse, index: usize) => {
|
||||
//todo!()
|
||||
////Ok(match browse.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||
////browse.index = index;
|
||||
////None
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||
////browse.index = index;
|
||||
////None
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn chdir (browse: &mut Browse, dir: PathBuf) => {
|
||||
//todo!()
|
||||
////Ok(match browse.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browse)) => {
|
||||
////browse.mode = Some(PoolMode::Import(index, Browse::new(Some(dir))?));
|
||||
////None
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browse)) => {
|
||||
////browse.mode = Some(PoolMode::Export(index, Browse::new(Some(dir))?));
|
||||
////None
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn filter (browse: &mut Browse, filter: Arc<str>) => {
|
||||
//todo!()
|
||||
//}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::*;
|
||||
|
||||
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,
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod browser_api; pub use self::browser_api::*;
|
||||
mod browser_model; pub use self::browser_model::*;
|
||||
mod browser_view; //pub use self::browser_view::*;
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Browser {
|
||||
fn _todo_stub_path_buf (&self) -> PathBuf {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_usize (&self) -> usize {
|
||||
todo!()
|
||||
}
|
||||
fn _todo_stub_arc_str (&self) -> Arc<str> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(Browser)]
|
||||
impl BrowserCommand {
|
||||
fn set_visible (browser: &mut Browser) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn set_path (browser: &mut Browser, address: PathBuf) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn set_search (browser: &mut Browser, filter: Arc<str>) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn set_cursor (browser: &mut Browser, cursor: usize) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Commands supported by [Browser]
|
||||
//#[derive(Debug, Clone, PartialEq)]
|
||||
//pub enum BrowserCommand {
|
||||
//Begin,
|
||||
//Cancel,
|
||||
//Confirm,
|
||||
//Select(usize),
|
||||
//Chdir(PathBuf),
|
||||
//Filter(Arc<str>),
|
||||
//}
|
||||
//fn begin (browser: &mut Browser) -> Perhaps<Self> {
|
||||
//unreachable!();
|
||||
//}
|
||||
//fn cancel (browser: &mut Browser) -> Perhaps<Self> {
|
||||
//todo!()
|
||||
////browser.mode = None;
|
||||
////Ok(None)
|
||||
//}
|
||||
//fn confirm (browser: &mut Browser) -> Perhaps<Self> {
|
||||
//todo!()
|
||||
////Ok(match browser.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
||||
////if browser.is_file() {
|
||||
////let path = browser.path();
|
||||
////browser.mode = None;
|
||||
////let _undo = PoolClipCommand::import(browser, index, path)?;
|
||||
////None
|
||||
////} else if browser.is_dir() {
|
||||
////browser.mode = Some(PoolMode::Import(index, browser.chdir()?));
|
||||
////None
|
||||
////} else {
|
||||
////None
|
||||
////}
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
||||
////todo!()
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn select (browser: &mut Browser, index: usize) -> Perhaps<Self> {
|
||||
//todo!()
|
||||
////Ok(match browser.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
||||
////browser.index = index;
|
||||
////None
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
||||
////browser.index = index;
|
||||
////None
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn chdir (browser: &mut Browser, dir: PathBuf) -> Perhaps<Self> {
|
||||
//todo!()
|
||||
////Ok(match browser.mode {
|
||||
////Some(PoolMode::Import(index, ref mut browser)) => {
|
||||
////browser.mode = Some(PoolMode::Import(index, Browser::new(Some(dir))?));
|
||||
////None
|
||||
////},
|
||||
////Some(PoolMode::Export(index, ref mut browser)) => {
|
||||
////browser.mode = Some(PoolMode::Export(index, Browser::new(Some(dir))?));
|
||||
////None
|
||||
////},
|
||||
////_ => unreachable!(),
|
||||
////})
|
||||
//}
|
||||
//fn filter (browser: &mut Browser, filter: Arc<str>) -> Perhaps<Self> {
|
||||
//todo!()
|
||||
//}
|
||||
|
|
@ -14,48 +14,33 @@ impl Clock {
|
|||
}
|
||||
|
||||
impl<T: HasClock> Command<T> for ClockCommand {
|
||||
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
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -84,6 +84,4 @@ audio!(|self: DeviceAudio<'a>, client, scope|{
|
|||
}
|
||||
});
|
||||
|
||||
#[tengri_proc::command(Device)]
|
||||
impl DeviceCommand {
|
||||
}
|
||||
def_command!(DeviceCommand: |device: Device| {});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,209 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool {
|
||||
pub visible: bool,
|
||||
/// Collection of clips
|
||||
pub clips: Arc<RwLock<Vec<Arc<RwLock<MidiClip>>>>>,
|
||||
/// Selected clip
|
||||
pub clip: AtomicUsize,
|
||||
/// Mode switch
|
||||
pub mode: Option<PoolMode>,
|
||||
/// Embedded file browser
|
||||
pub browser: Option<Browser>,
|
||||
}
|
||||
//take!(BrowserCommand |state: Pool, iter|Ok(state.browser.as_ref()
|
||||
//.map(|p|Take::take(p, iter))
|
||||
//.transpose()?
|
||||
//.flatten()));
|
||||
impl Default for Pool {
|
||||
fn default () -> Self {
|
||||
//use PoolMode::*;
|
||||
Self {
|
||||
visible: true,
|
||||
clips: Arc::from(RwLock::from(vec![])),
|
||||
clip: 0.into(),
|
||||
mode: None,
|
||||
browser: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn clip_index (&self) -> usize {
|
||||
self.clip.load(Relaxed)
|
||||
}
|
||||
pub fn set_clip_index (&self, value: usize) {
|
||||
self.clip.store(value, Relaxed);
|
||||
}
|
||||
pub fn mode (&self) -> &Option<PoolMode> {
|
||||
&self.mode
|
||||
}
|
||||
pub fn mode_mut (&mut self) -> &mut Option<PoolMode> {
|
||||
&mut self.mode
|
||||
}
|
||||
pub fn begin_clip_length (&mut self) {
|
||||
let length = self.clips()[self.clip_index()].read().unwrap().length;
|
||||
*self.mode_mut() = Some(PoolMode::Length(
|
||||
self.clip_index(),
|
||||
length,
|
||||
ClipLengthFocus::Bar
|
||||
));
|
||||
}
|
||||
pub fn begin_clip_rename (&mut self) {
|
||||
let name = self.clips()[self.clip_index()].read().unwrap().name.clone();
|
||||
*self.mode_mut() = Some(PoolMode::Rename(
|
||||
self.clip_index(),
|
||||
name
|
||||
));
|
||||
}
|
||||
pub fn begin_import (&mut self) -> Usually<()> {
|
||||
*self.mode_mut() = Some(PoolMode::Import(
|
||||
self.clip_index(),
|
||||
Browser::new(None)?
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
pub fn begin_export (&mut self) -> Usually<()> {
|
||||
*self.mode_mut() = Some(PoolMode::Export(
|
||||
self.clip_index(),
|
||||
Browser::new(None)?
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
pub fn new_clip (&self) -> MidiClip {
|
||||
MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random()))
|
||||
}
|
||||
pub fn cloned_clip (&self) -> MidiClip {
|
||||
let index = self.clip_index();
|
||||
let mut clip = self.clips()[index].read().unwrap().duplicate();
|
||||
clip.color = ItemTheme::random_near(clip.color, 0.25);
|
||||
clip
|
||||
}
|
||||
pub fn add_new_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||
let clip = Arc::new(RwLock::new(self.new_clip()));
|
||||
let index = {
|
||||
let mut clips = self.clips.write().unwrap();
|
||||
clips.push(clip.clone());
|
||||
clips.len().saturating_sub(1)
|
||||
};
|
||||
self.clip.store(index, Relaxed);
|
||||
(index, clip)
|
||||
}
|
||||
pub fn delete_clip (&mut self, clip: &MidiClip) -> bool {
|
||||
let index = self.clips.read().unwrap().iter().position(|x|*x.read().unwrap()==*clip);
|
||||
if let Some(index) = index {
|
||||
self.clips.write().unwrap().remove(index);
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Modes for clip pool
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PoolMode {
|
||||
/// Renaming a pattern
|
||||
Rename(usize, Arc<str>),
|
||||
/// Editing the length of a pattern
|
||||
Length(usize, usize, ClipLengthFocus),
|
||||
/// Load clip from disk
|
||||
Import(usize, Browser),
|
||||
/// Save clip to disk
|
||||
Export(usize, Browser),
|
||||
}
|
||||
|
||||
/// Focused field of `ClipLength`
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ClipLengthFocus {
|
||||
/// Editing the number of bars
|
||||
Bar,
|
||||
/// Editing the number of beats
|
||||
Beat,
|
||||
/// Editing the number of ticks
|
||||
Tick,
|
||||
}
|
||||
|
||||
impl ClipLengthFocus {
|
||||
pub fn next (&mut self) {
|
||||
use ClipLengthFocus::*;
|
||||
*self = match self { Bar => Beat, Beat => Tick, Tick => Bar, }
|
||||
}
|
||||
pub fn prev (&mut self) {
|
||||
use ClipLengthFocus::*;
|
||||
*self = match self { Bar => Tick, Beat => Bar, Tick => Beat, }
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays and edits clip length.
|
||||
#[derive(Clone)]
|
||||
pub struct ClipLength {
|
||||
/// Pulses per beat (quaver)
|
||||
ppq: usize,
|
||||
/// Beats per bar
|
||||
bpb: usize,
|
||||
/// Length of clip in pulses
|
||||
pulses: usize,
|
||||
/// Selected subdivision
|
||||
pub focus: Option<ClipLengthFocus>,
|
||||
}
|
||||
|
||||
impl ClipLength {
|
||||
pub fn _new (pulses: usize, focus: Option<ClipLengthFocus>) -> Self {
|
||||
Self { ppq: PPQ, bpb: 4, pulses, focus }
|
||||
}
|
||||
pub fn bars (&self) -> usize {
|
||||
self.pulses / (self.bpb * self.ppq)
|
||||
}
|
||||
pub fn beats (&self) -> usize {
|
||||
(self.pulses % (self.bpb * self.ppq)) / self.ppq
|
||||
}
|
||||
pub fn ticks (&self) -> usize {
|
||||
self.pulses % self.ppq
|
||||
}
|
||||
pub fn bars_string (&self) -> Arc<str> {
|
||||
format!("{}", self.bars()).into()
|
||||
}
|
||||
pub fn beats_string (&self) -> Arc<str> {
|
||||
format!("{}", self.beats()).into()
|
||||
}
|
||||
pub fn ticks_string (&self) -> Arc<str> {
|
||||
format!("{:>02}", self.ticks()).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClipPool = Vec<Arc<RwLock<MidiClip>>>;
|
||||
|
||||
pub trait HasClips {
|
||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||
fn add_clip (&self) -> (usize, Arc<RwLock<MidiClip>>) {
|
||||
let clip = Arc::new(RwLock::new(MidiClip::new("Clip", true, 384, None, None)));
|
||||
self.clips_mut().push(clip.clone());
|
||||
(self.clips().len() - 1, clip)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! has_clips {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> {
|
||||
$cb.read().unwrap()
|
||||
}
|
||||
fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> {
|
||||
$cb.write().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
has_clips!(|self: Pool|self.clips);
|
||||
|
||||
has_clip!(|self: Pool|self.clips().get(self.clip_index()).map(|c|c.clone()));
|
||||
|
||||
from!(|clip:&Arc<RwLock<MidiClip>>|Pool = {
|
||||
let model = Self::default();
|
||||
model.clips.write().unwrap().push(clip.clone());
|
||||
model.clip.store(1, Relaxed);
|
||||
model
|
||||
});
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
crates/device/src/port/port_api.rs
Normal file
17
crates/device/src/port/port_api.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use crate::*;
|
||||
def_command!(MidiInputCommand: |port: MidiInput| {
|
||||
Close => todo!(),
|
||||
Connect { midi_out: Arc<str> } => todo!(),
|
||||
});
|
||||
def_command!(MidiOutputCommand: |port: MidiOutput| {
|
||||
Close => todo!(),
|
||||
Connect { midi_in: Arc<str> } => todo!(),
|
||||
});
|
||||
def_command!(AudioInputCommand: |port: AudioInput| {
|
||||
Close => todo!(),
|
||||
Connect { audio_out: Arc<str> } => todo!(),
|
||||
});
|
||||
def_command!(AudioOutputCommand: |port: AudioOutput| {
|
||||
Close => todo!(),
|
||||
Connect { audio_in: Arc<str> } => todo!(),
|
||||
});
|
||||
|
|
@ -1,20 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
#[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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
183
crates/device/src/port/port_connect.rs
Normal file
183
crates/device/src/port/port_connect.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait JackPort: HasJack<'static> {
|
||||
type Port: PortSpec + Default;
|
||||
type Pair: PortSpec + Default;
|
||||
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
||||
-> Usually<Self> where Self: Sized;
|
||||
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
||||
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
||||
.map_err(|e|e.into())
|
||||
}
|
||||
fn port_name (&self) -> &Arc<str>;
|
||||
fn connections (&self) -> &[Connect];
|
||||
fn port (&self) -> &Port<Self::Port>;
|
||||
fn port_mut (&mut self) -> &mut Port<Self::Port>;
|
||||
fn into_port (self) -> Port<Self::Port> where Self: Sized;
|
||||
fn close (self) -> Usually<()> where Self: Sized {
|
||||
let jack = self.jack().clone();
|
||||
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
|
||||
}
|
||||
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
||||
self.with_client(|c|c.ports(re_name, re_type, flags))
|
||||
}
|
||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||
self.with_client(|c|c.port_by_id(id))
|
||||
}
|
||||
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
|
||||
self.with_client(|c|c.port_by_name(name.as_ref()))
|
||||
}
|
||||
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
|
||||
for connect in self.connections().iter() {
|
||||
//panic!("{connect:?}");
|
||||
let status = match &connect.name {
|
||||
Exact(name) => self.connect_exact(name),
|
||||
RegExp(re) => self.connect_regexp(re, connect.scope),
|
||||
}?;
|
||||
*connect.status.write().unwrap() = status;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn connect_exact <'k> (&'k self, name: &str) ->
|
||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
||||
{
|
||||
self.with_client(move|c|{
|
||||
let mut status = vec![];
|
||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
||||
if port.as_str() == &*name {
|
||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
||||
let port_status = self.connect_to_unowned(&port)?;
|
||||
let name = port.name()?.into();
|
||||
status.push((port, name, port_status));
|
||||
if port_status == Connected {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(status)
|
||||
})
|
||||
}
|
||||
fn connect_regexp <'k> (
|
||||
&'k self, re: &str, scope: ConnectScope
|
||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
||||
self.with_client(move|c|{
|
||||
let mut status = vec![];
|
||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
||||
for port in ports.iter() {
|
||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
||||
let port_status = self.connect_to_unowned(&port)?;
|
||||
let name = port.name()?.into();
|
||||
status.push((port, name, port_status));
|
||||
if port_status == Connected && scope == One {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(status)
|
||||
})
|
||||
}
|
||||
/** Connect to a matching port by name. */
|
||||
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
|
||||
self.connect_to_unowned(port)
|
||||
} else {
|
||||
Ok(Missing)
|
||||
})
|
||||
}
|
||||
/** Connect to a matching port by reference. */
|
||||
fn connect_to_unowned (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
}
|
||||
/** Connect to an owned matching port by reference. */
|
||||
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(self.port(), port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, self.port()) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ConnectName {
|
||||
/** Exact match */
|
||||
Exact(Arc<str>),
|
||||
/** Match regular expression */
|
||||
RegExp(Arc<str>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
||||
One,
|
||||
All
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
||||
Missing,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Mismatch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)] pub struct Connect {
|
||||
pub name: ConnectName,
|
||||
pub scope: ConnectScope,
|
||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
||||
pub info: Arc<String>,
|
||||
}
|
||||
|
||||
impl Connect {
|
||||
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
||||
-> Vec<Self>
|
||||
{
|
||||
let mut connections = vec![];
|
||||
for port in exact.iter() { connections.push(Self::exact(port)) }
|
||||
for port in re.iter() { connections.push(Self::regexp(port)) }
|
||||
for port in re_all.iter() { connections.push(Self::regexp_all(port)) }
|
||||
connections
|
||||
}
|
||||
/// Connect to this exact port
|
||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("=:{}", name.as_ref()).into();
|
||||
let name = Exact(name.as_ref().into());
|
||||
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn regexp (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("~:{}", name.as_ref()).into();
|
||||
let name = RegExp(name.as_ref().into());
|
||||
Self { name, scope: One, status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("+:{}", name.as_ref()).into();
|
||||
let name = RegExp(name.as_ref().into());
|
||||
Self { name, scope: All, status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn info (&self) -> Arc<str> {
|
||||
let status = {
|
||||
let status = self.status.read().unwrap();
|
||||
let mut ok = 0;
|
||||
for (_, _, state) in status.iter() {
|
||||
if *state == Connected {
|
||||
ok += 1
|
||||
}
|
||||
}
|
||||
format!("{ok}/{}", status.len())
|
||||
};
|
||||
let scope = match self.scope {
|
||||
One => " ", All => "*",
|
||||
};
|
||||
let name = match &self.name {
|
||||
Exact(name) => format!("= {name}"), RegExp(name) => format!("~ {name}"),
|
||||
};
|
||||
format!(" ({}) {} {}", status, scope, name).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,24 +1,7 @@
|
|||
use crate::*;
|
||||
|
||||
//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()
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")))))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// The sampler device plays sounds in response to MIDI notes.
|
||||
#[derive(Debug)]
|
||||
pub struct Sampler {
|
||||
/// Name of sampler.
|
||||
pub name: Arc<str>,
|
||||
/// Device color.
|
||||
pub color: ItemTheme,
|
||||
/// Audio input ports. Samples get recorded here.
|
||||
pub audio_ins: Vec<AudioInput>,
|
||||
/// Audio input meters.
|
||||
pub input_meters: Vec<f32>,
|
||||
/// Sample currently being recorded.
|
||||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||||
/// Recording buffer.
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
/// Samples mapped to MIDI notes.
|
||||
pub mapped: [Option<Arc<RwLock<Sample>>>;128],
|
||||
/// Samples that are not mapped to MIDI notes.
|
||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||
/// Sample currently being edited.
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
/// MIDI input port. Triggers sample playback.
|
||||
pub midi_in: MidiInput,
|
||||
/// Collection of currently playing instances of samples.
|
||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||
/// Audio output ports. Voices get played here.
|
||||
pub audio_outs: Vec<AudioOutput>,
|
||||
/// Audio output meters.
|
||||
pub output_meters: Vec<f32>,
|
||||
/// How to mix the voices.
|
||||
pub mixing_mode: MixingMode,
|
||||
/// How to meter the inputs and outputs.
|
||||
pub metering_mode: MeteringMode,
|
||||
/// Fixed gain applied to all output.
|
||||
pub output_gain: f32,
|
||||
/// Currently active modal, if any.
|
||||
pub mode: Option<SamplerMode>,
|
||||
/// Size of rendered sampler.
|
||||
pub size: Measure<TuiOut>,
|
||||
/// Lowest note displayed.
|
||||
pub note_lo: AtomicUsize,
|
||||
/// Currently selected note.
|
||||
pub note_pt: AtomicUsize,
|
||||
/// Selected note as row/col.
|
||||
pub cursor: (AtomicUsize, AtomicUsize),
|
||||
}
|
||||
|
||||
impl Sampler {
|
||||
pub fn new (
|
||||
jack: &Jack<'static>,
|
||||
name: impl AsRef<str>,
|
||||
midi_from: &[Connect],
|
||||
audio_from: &[&[Connect];2],
|
||||
audio_to: &[&[Connect];2],
|
||||
) -> Usually<Self> {
|
||||
let name = name.as_ref();
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?,
|
||||
audio_ins: vec![
|
||||
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||
],
|
||||
audio_outs: vec![
|
||||
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
|
||||
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
|
||||
],
|
||||
input_meters: vec![0.0;2],
|
||||
output_meters: vec![0.0;2],
|
||||
mapped: [const { None };128],
|
||||
unmapped: vec![],
|
||||
voices: Arc::new(RwLock::new(vec![])),
|
||||
buffer: vec![vec![0.0;16384];2],
|
||||
output_gain: 1.,
|
||||
recording: None,
|
||||
mode: None,
|
||||
editing: None,
|
||||
size: Default::default(),
|
||||
note_lo: 0.into(),
|
||||
note_pt: 0.into(),
|
||||
cursor: (0.into(), 0.into()),
|
||||
color: Default::default(),
|
||||
mixing_mode: Default::default(),
|
||||
metering_mode: Default::default(),
|
||||
})
|
||||
}
|
||||
/// Value of cursor
|
||||
pub fn cursor (&self) -> (usize, usize) {
|
||||
(self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
impl NoteRange for Sampler {
|
||||
fn note_lo (&self) -> &AtomicUsize {
|
||||
&self.note_lo
|
||||
}
|
||||
fn note_axis (&self) -> &AtomicUsize {
|
||||
&self.size.y
|
||||
}
|
||||
}
|
||||
|
||||
impl NotePoint for Sampler {
|
||||
fn note_len (&self) -> &AtomicUsize {
|
||||
unreachable!();
|
||||
}
|
||||
fn get_note_len (&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn set_note_len (&self, x: usize) -> usize {
|
||||
0 /*TODO?*/
|
||||
}
|
||||
fn note_pos (&self) -> &AtomicUsize {
|
||||
&self.note_pt
|
||||
}
|
||||
fn get_note_pos (&self) -> usize {
|
||||
self.note_pt.load(Relaxed)
|
||||
}
|
||||
fn set_note_pos (&self, x: usize) -> usize {
|
||||
let old = self.note_pt.swap(x, Relaxed);
|
||||
self.cursor.0.store(x % 8, Relaxed);
|
||||
self.cursor.1.store(x / 8, Relaxed);
|
||||
old
|
||||
}
|
||||
}
|
||||
|
||||
/// A sound sample.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Sample {
|
||||
pub name: Arc<str>,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub channels: Vec<Vec<f32>>,
|
||||
pub rate: Option<usize>,
|
||||
pub gain: f32,
|
||||
pub color: ItemTheme,
|
||||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||
Self {
|
||||
name: name.as_ref().into(),
|
||||
start,
|
||||
end,
|
||||
channels,
|
||||
rate: None,
|
||||
gain: 1.0,
|
||||
color: ItemTheme::random(),
|
||||
}
|
||||
}
|
||||
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
||||
Voice {
|
||||
sample: sample.clone(),
|
||||
after,
|
||||
position: sample.read().unwrap().start,
|
||||
velocity: velocity.as_int() as f32 / 127.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A currently playing instance of a sample.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Voice {
|
||||
pub sample: Arc<RwLock<Sample>>,
|
||||
pub after: usize,
|
||||
pub position: usize,
|
||||
pub velocity: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SamplerMode {
|
||||
// Load sample from path
|
||||
Import(usize, Browser),
|
||||
}
|
||||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit e3e3c163da02165e77a259eb715749b7f0097498
|
||||
Subproject commit 446ec7a71477e1b9ca117b7a23759d6318eb2cf0
|
||||
2
tek.edn
2
tek.edn
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue