mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
compact more (minus 6 files)
This commit is contained in:
parent
b4fbd99248
commit
81ea049773
17 changed files with 432 additions and 433 deletions
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
use crate::{core::*, handle, App, AppFocus};
|
use crate::{core::*, handle, App, AppFocus};
|
||||||
|
|
||||||
submod!{ chain focus mixer plugin }
|
|
||||||
|
|
||||||
handle!{
|
handle!{
|
||||||
App |self, e| {
|
App |self, e| {
|
||||||
if let Some(ref mut modal) = self.modal {
|
if let Some(ref mut modal) = self.modal {
|
||||||
|
|
@ -17,10 +15,10 @@ handle!{
|
||||||
Ok(if self.entered {
|
Ok(if self.entered {
|
||||||
handle_focused(self, e)?
|
handle_focused(self, e)?
|
||||||
|| handle_keymap(self, e, KEYMAP_GLOBAL)?
|
|| handle_keymap(self, e, KEYMAP_GLOBAL)?
|
||||||
|| handle_keymap(self, e, crate::control::focus::KEYMAP_FOCUS)?
|
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|
||||||
} else {
|
} else {
|
||||||
handle_keymap(self, e, KEYMAP_GLOBAL)?
|
handle_keymap(self, e, KEYMAP_GLOBAL)?
|
||||||
|| handle_keymap(self, e, crate::control::focus::KEYMAP_FOCUS)?
|
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|
||||||
|| handle_focused(self, e)?
|
|| handle_focused(self, e)?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -36,9 +34,9 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||||
handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER),
|
handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER),
|
||||||
AppFocus::Chain => Ok(if state.entered {
|
AppFocus::Chain => Ok(if state.entered {
|
||||||
handle_device(state, e)? ||
|
handle_device(state, e)? ||
|
||||||
handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)?
|
handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
|
||||||
} else {
|
} else {
|
||||||
handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)? || handle_device(state, e)?
|
handle_keymap(state, e, crate::control::KEYMAP_CHAIN)? || handle_device(state, e)?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,3 +122,77 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Key bindings for chain section.
|
||||||
|
pub const KEYMAP_CHAIN: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
|
[Up, NONE, "chain_cursor_up", "move cursor up", |_: &mut App| {
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Down, NONE, "chain_cursor_down", "move cursor down", |_: &mut App| {
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Left, NONE, "chain_cursor_left", "move cursor left", |app: &mut App| {
|
||||||
|
if let Some(track) = app.arranger.track_mut() {
|
||||||
|
track.device = track.device.saturating_sub(1);
|
||||||
|
return Ok(true)
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}],
|
||||||
|
[Right, NONE, "chain_cursor_right", "move cursor right", |app: &mut App| {
|
||||||
|
if let Some(track) = app.arranger.track_mut() {
|
||||||
|
track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
||||||
|
return Ok(true)
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}],
|
||||||
|
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
|
||||||
|
app.chain_mode = !app.chain_mode;
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Generic key bindings for views that support focus.
|
||||||
|
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
|
[Char(';'), NONE, "command", "open command palette", |app: &mut App| {
|
||||||
|
app.modal = Some(Box::new(crate::view::HelpModal::new()));
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||||
|
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||||
|
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
||||||
|
app.entered = false;
|
||||||
|
app.transport.entered = app.entered;
|
||||||
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||||
|
app.entered = true;
|
||||||
|
app.transport.entered = app.entered;
|
||||||
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn focus_next (app: &mut App) -> Usually<bool> {
|
||||||
|
app.section.next();
|
||||||
|
app.transport.focused = app.section == AppFocus::Transport;
|
||||||
|
app.transport.entered = app.entered;
|
||||||
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
||||||
|
app.section.prev();
|
||||||
|
app.transport.focused = app.section == AppFocus::Transport;
|
||||||
|
app.transport.entered = app.entered;
|
||||||
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
|
app.arranger.entered = app.entered;
|
||||||
|
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
|
app.sequencer.entered = app.entered;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
use crate::{core::*, model::App};
|
|
||||||
|
|
||||||
/// Key bindings for chain section.
|
|
||||||
pub const KEYMAP_CHAIN: &'static [KeyBinding<App>] = keymap!(App {
|
|
||||||
[Up, NONE, "chain_cursor_up", "move cursor up", |_: &mut App| {
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Down, NONE, "chain_cursor_down", "move cursor down", |_: &mut App| {
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Left, NONE, "chain_cursor_left", "move cursor left", |app: &mut App| {
|
|
||||||
if let Some(track) = app.arranger.track_mut() {
|
|
||||||
track.device = track.device.saturating_sub(1);
|
|
||||||
return Ok(true)
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}],
|
|
||||||
[Right, NONE, "chain_cursor_right", "move cursor right", |app: &mut App| {
|
|
||||||
if let Some(track) = app.arranger.track_mut() {
|
|
||||||
track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
|
||||||
return Ok(true)
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}],
|
|
||||||
[Char('`'), NONE, "chain_mode_switch", "switch the display mode", |app: &mut App| {
|
|
||||||
app.chain_mode = !app.chain_mode;
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
use crate::{core::*, model::{App, AppFocus}};
|
|
||||||
|
|
||||||
/// Generic key bindings for views that support focus.
|
|
||||||
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
|
||||||
[Char(';'), NONE, "command", "open command palette", |app: &mut App| {
|
|
||||||
app.modal = Some(Box::new(crate::view::HelpModal::new()));
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
|
||||||
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
|
||||||
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
|
|
||||||
app.entered = false;
|
|
||||||
app.transport.entered = app.entered;
|
|
||||||
app.arranger.entered = app.entered;
|
|
||||||
app.sequencer.entered = app.entered;
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
|
||||||
app.entered = true;
|
|
||||||
app.transport.entered = app.entered;
|
|
||||||
app.arranger.entered = app.entered;
|
|
||||||
app.sequencer.entered = app.entered;
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn focus_next (app: &mut App) -> Usually<bool> {
|
|
||||||
app.section.next();
|
|
||||||
app.transport.focused = app.section == AppFocus::Transport;
|
|
||||||
app.transport.entered = app.entered;
|
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
|
||||||
app.arranger.entered = app.entered;
|
|
||||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
|
||||||
app.sequencer.entered = app.entered;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
|
||||||
app.section.prev();
|
|
||||||
app.transport.focused = app.section == AppFocus::Transport;
|
|
||||||
app.transport.entered = app.entered;
|
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
|
||||||
app.arranger.entered = app.entered;
|
|
||||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
|
||||||
app.sequencer.entered = app.entered;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
use crate::{core::*, model::*};
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Meters: propagate clipping:
|
|
||||||
// - If one stage clips, all stages after it are marked red
|
|
||||||
// - If one track clips, all tracks that feed from it are marked red?
|
|
||||||
|
|
||||||
//pub const ACTIONS: [(&'static str, &'static str);2] = [
|
|
||||||
//("+/-", "Adjust"),
|
|
||||||
//("Ins/Del", "Add/remove track"),
|
|
||||||
//];
|
|
||||||
|
|
||||||
pub fn handle_mixer (state: &mut Mixer, event: &AppEvent) -> Usually<bool> {
|
|
||||||
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
|
|
||||||
|
|
||||||
match event.code {
|
|
||||||
//KeyCode::Char('c') => {
|
|
||||||
//if event.modifiers == KeyModifiers::CONTROL {
|
|
||||||
//state.exit();
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
KeyCode::Down => {
|
|
||||||
state.selected_track = (state.selected_track + 1) % state.tracks.len();
|
|
||||||
println!("{}", state.selected_track);
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Up => {
|
|
||||||
if state.selected_track == 0 {
|
|
||||||
state.selected_track = state.tracks.len() - 1;
|
|
||||||
} else {
|
|
||||||
state.selected_track = state.selected_track - 1;
|
|
||||||
}
|
|
||||||
println!("{}", state.selected_track);
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Left => {
|
|
||||||
if state.selected_column == 0 {
|
|
||||||
state.selected_column = 6
|
|
||||||
} else {
|
|
||||||
state.selected_column = state.selected_column - 1;
|
|
||||||
}
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Right => {
|
|
||||||
if state.selected_column == 6 {
|
|
||||||
state.selected_column = 0
|
|
||||||
} else {
|
|
||||||
state.selected_column = state.selected_column + 1;
|
|
||||||
}
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
println!("\n{event:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
use crate::{core::*, model::*};
|
|
||||||
|
|
||||||
pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|
|
||||||
handle_keymap(state, event, KEYMAP_PLUGIN)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Key bindings for plugin device.
|
|
||||||
pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
|
|
||||||
[Up, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{
|
|
||||||
s.selected = s.selected.saturating_sub(1);
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Down, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{
|
|
||||||
s.selected = (s.selected + 1).min(match &s.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
_ => unimplemented!()
|
|
||||||
});
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[PageUp, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{
|
|
||||||
s.selected = s.selected.saturating_sub(8);
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[PageDown, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{
|
|
||||||
s.selected = (s.selected + 10).min(match &s.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
_ => unimplemented!()
|
|
||||||
});
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Char(','), NONE, "decrement", "decrement value", |s: &mut Plugin|{
|
|
||||||
match s.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[s.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value - 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
[Char('.'), NONE, "increment", "increment value", |s: &mut Plugin|{
|
|
||||||
match s.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[s.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value + 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
|
|
@ -1 +1,11 @@
|
||||||
crate::core::pubmod! { arranger sampler sequencer transport }
|
//! Music-making apparatuses.
|
||||||
|
//!
|
||||||
|
//! The following devices are provided:
|
||||||
|
//!
|
||||||
|
//! - Transport controller
|
||||||
|
//! - Arranger (clip launcher)
|
||||||
|
//! - Sequencer (phrase editor)
|
||||||
|
//! - Plugin (currently LV2 only)
|
||||||
|
//! - Sampler (currently 16bit samples only)
|
||||||
|
|
||||||
|
crate::core::pubmod!{arranger looper mixer plugin sampler sequencer transport}
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,4 @@ render!(Looper);
|
||||||
handle!(Looper);
|
handle!(Looper);
|
||||||
process!(Looper);
|
process!(Looper);
|
||||||
ports!(Looper);
|
ports!(Looper);
|
||||||
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
use crate::core::*;
|
use crate::{core::*, model::*};
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Meters: propagate clipping:
|
||||||
|
// - If one stage clips, all stages after it are marked red
|
||||||
|
// - If one track clips, all tracks that feed from it are marked red?
|
||||||
|
|
||||||
|
//pub const ACTIONS: [(&'static str, &'static str);2] = [
|
||||||
|
//("+/-", "Adjust"),
|
||||||
|
//("Ins/Del", "Add/remove track"),
|
||||||
|
//];
|
||||||
|
|
||||||
/// TODO: audio mixer.
|
/// TODO: audio mixer.
|
||||||
pub struct Mixer {
|
pub struct Mixer {
|
||||||
|
|
@ -8,7 +18,7 @@ pub struct Mixer {
|
||||||
pub selected_column: usize,
|
pub selected_column: usize,
|
||||||
}
|
}
|
||||||
//render!(Mixer = crate::view::mixer::render);
|
//render!(Mixer = crate::view::mixer::render);
|
||||||
handle!(Mixer = crate::control::handle_mixer);
|
handle!(Mixer = handle_mixer);
|
||||||
process!(Mixer = process);
|
process!(Mixer = process);
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
|
|
@ -130,3 +140,50 @@ impl MixerTrack {
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
pub fn handle_mixer (state: &mut Mixer, event: &AppEvent) -> Usually<bool> {
|
||||||
|
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
|
||||||
|
|
||||||
|
match event.code {
|
||||||
|
//KeyCode::Char('c') => {
|
||||||
|
//if event.modifiers == KeyModifiers::CONTROL {
|
||||||
|
//state.exit();
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
KeyCode::Down => {
|
||||||
|
state.selected_track = (state.selected_track + 1) % state.tracks.len();
|
||||||
|
println!("{}", state.selected_track);
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Up => {
|
||||||
|
if state.selected_track == 0 {
|
||||||
|
state.selected_track = state.tracks.len() - 1;
|
||||||
|
} else {
|
||||||
|
state.selected_track = state.selected_track - 1;
|
||||||
|
}
|
||||||
|
println!("{}", state.selected_track);
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Left => {
|
||||||
|
if state.selected_column == 0 {
|
||||||
|
state.selected_column = 6
|
||||||
|
} else {
|
||||||
|
state.selected_column = state.selected_column - 1;
|
||||||
|
}
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Right => {
|
||||||
|
if state.selected_column == 6 {
|
||||||
|
state.selected_column = 0
|
||||||
|
} else {
|
||||||
|
state.selected_column = state.selected_column + 1;
|
||||||
|
}
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("\n{event:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
278
src/devices/plugin.rs
Normal file
278
src/devices/plugin.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
use ::livi::{
|
||||||
|
World,
|
||||||
|
Instance,
|
||||||
|
Plugin as LiviPlugin,
|
||||||
|
Features,
|
||||||
|
FeaturesBuilder,
|
||||||
|
Port,
|
||||||
|
event::LV2AtomSequence,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|
||||||
|
handle_keymap(state, event, KEYMAP_PLUGIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Key bindings for plugin device.
|
||||||
|
pub const KEYMAP_PLUGIN: &'static [KeyBinding<Plugin>] = keymap!(Plugin {
|
||||||
|
[Up, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{
|
||||||
|
s.selected = s.selected.saturating_sub(1);
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Down, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{
|
||||||
|
s.selected = (s.selected + 1).min(match &s.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
_ => unimplemented!()
|
||||||
|
});
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[PageUp, NONE, "cursor_up", "move cursor up", |s: &mut Plugin|{
|
||||||
|
s.selected = s.selected.saturating_sub(8);
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[PageDown, NONE, "cursor_down", "move cursor down", |s: &mut Plugin|{
|
||||||
|
s.selected = (s.selected + 10).min(match &s.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
_ => unimplemented!()
|
||||||
|
});
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Char(','), NONE, "decrement", "decrement value", |s: &mut Plugin|{
|
||||||
|
match s.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[s.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value - 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
[Char('.'), NONE, "increment", "increment value", |s: &mut Plugin|{
|
||||||
|
match s.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[s.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value + 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
/// A plugin device.
|
||||||
|
pub struct Plugin {
|
||||||
|
pub name: String,
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub plugin: Option<PluginKind>,
|
||||||
|
pub selected: usize,
|
||||||
|
pub mapping: bool,
|
||||||
|
pub ports: JackPorts,
|
||||||
|
}
|
||||||
|
render!(Plugin = render_plugin);
|
||||||
|
handle!(Plugin = handle_plugin);
|
||||||
|
process!(Plugin = Plugin::process);
|
||||||
|
|
||||||
|
/// Supported plugin formats.
|
||||||
|
pub enum PluginKind {
|
||||||
|
LV2(LV2Plugin),
|
||||||
|
VST2 {
|
||||||
|
instance: ::vst::host::PluginInstance
|
||||||
|
},
|
||||||
|
VST3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin {
|
||||||
|
/// Create a plugin host device.
|
||||||
|
pub fn new (name: &str) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
path: None,
|
||||||
|
plugin: None,
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
ports: JackPorts::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
match self.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin {
|
||||||
|
features,
|
||||||
|
ref mut instance,
|
||||||
|
ref mut input_buffer,
|
||||||
|
..
|
||||||
|
})) => {
|
||||||
|
let urid = features.midi_urid();
|
||||||
|
input_buffer.clear();
|
||||||
|
for port in self.ports.midi_ins.values() {
|
||||||
|
let mut atom = ::livi::event::LV2AtomSequence::new(
|
||||||
|
&features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
);
|
||||||
|
for event in port.iter(scope) {
|
||||||
|
match event.bytes.len() {
|
||||||
|
3 => atom.push_midi_event::<3>(
|
||||||
|
event.time as i64,
|
||||||
|
urid,
|
||||||
|
&event.bytes[0..3]
|
||||||
|
).unwrap(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input_buffer.push(atom);
|
||||||
|
}
|
||||||
|
let mut outputs = vec![];
|
||||||
|
for _ in self.ports.midi_outs.iter() {
|
||||||
|
outputs.push(::livi::event::LV2AtomSequence::new(
|
||||||
|
&features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let ports = ::livi::EmptyPortConnections::new()
|
||||||
|
.with_atom_sequence_inputs(
|
||||||
|
input_buffer.iter()
|
||||||
|
)
|
||||||
|
.with_atom_sequence_outputs(
|
||||||
|
outputs.iter_mut()
|
||||||
|
)
|
||||||
|
.with_audio_inputs(
|
||||||
|
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
|
||||||
|
)
|
||||||
|
.with_audio_outputs(
|
||||||
|
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
|
||||||
|
);
|
||||||
|
unsafe {
|
||||||
|
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_plugin (state: &Plugin, buf: &mut Buffer, area: Rect)
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
let Rect { x, y, height, .. } = area;
|
||||||
|
let mut width = 20u16;
|
||||||
|
match &state.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
||||||
|
let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
||||||
|
let end = start + height as usize - 2;
|
||||||
|
//draw_box(buf, Rect { x, y, width, height });
|
||||||
|
for i in start..end {
|
||||||
|
if let Some(port) = port_list.get(i) {
|
||||||
|
let value = if let Some(value) = instance.control_input(port.index) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
port.default_value
|
||||||
|
};
|
||||||
|
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
||||||
|
let label = &format!("{:25} = {value:.03}", port.name);
|
||||||
|
width = width.max(label.len() as u16 + 4);
|
||||||
|
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
|
||||||
|
Some(Style::default().green())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
draw_header(state, buf, area.x, area.y, width)?;
|
||||||
|
Ok(Rect { width, ..area })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let label1 = format!(" {}", state.name);
|
||||||
|
label1.blit(buf, x + 1, y, Some(style.white().bold()))?;
|
||||||
|
if let Some(ref path) = state.path {
|
||||||
|
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
||||||
|
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?;
|
||||||
|
}
|
||||||
|
Ok(Rect { x, y, width: w, height: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LV2 plugin.
|
||||||
|
pub struct LV2Plugin {
|
||||||
|
pub world: World,
|
||||||
|
pub instance: Instance,
|
||||||
|
pub plugin: LiviPlugin,
|
||||||
|
pub features: Arc<Features>,
|
||||||
|
pub port_list: Vec<Port>,
|
||||||
|
pub input_buffer: Vec<LV2AtomSequence>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LV2Plugin {
|
||||||
|
pub fn new (uri: &str) -> Usually<Self> {
|
||||||
|
// Get 1st plugin at URI
|
||||||
|
let world = World::with_load_bundle(&uri);
|
||||||
|
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
|
||||||
|
let features = world.build_features(features);
|
||||||
|
let mut plugin = None;
|
||||||
|
for p in world.iter_plugins() {
|
||||||
|
plugin = Some(p);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let plugin = plugin.unwrap();
|
||||||
|
let err = &format!("init {uri}");
|
||||||
|
|
||||||
|
// Instantiate
|
||||||
|
Ok(Self {
|
||||||
|
world,
|
||||||
|
instance: unsafe {
|
||||||
|
plugin
|
||||||
|
.instantiate(features.clone(), 48000.0)
|
||||||
|
.expect(&err)
|
||||||
|
},
|
||||||
|
port_list: {
|
||||||
|
let mut port_list = vec![];
|
||||||
|
for port in plugin.ports() {
|
||||||
|
port_list.push(port);
|
||||||
|
}
|
||||||
|
port_list
|
||||||
|
},
|
||||||
|
plugin,
|
||||||
|
features,
|
||||||
|
input_buffer: Vec::with_capacity(1024)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin {
|
||||||
|
pub fn lv2 (name: &str, path: &str) -> Usually<JackDevice> {
|
||||||
|
let plugin = LV2Plugin::new(path)?;
|
||||||
|
Jack::new(name)?
|
||||||
|
.ports_from_lv2(&plugin.plugin)
|
||||||
|
.run(|ports|Box::new(Self {
|
||||||
|
name: name.into(),
|
||||||
|
path: Some(String::from(path)),
|
||||||
|
plugin: Some(PluginKind::LV2(plugin)),
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
ports
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::vst::host::Host for Plugin {}
|
||||||
|
|
||||||
|
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
|
||||||
|
let mut loader = ::vst::host::PluginLoader::load(
|
||||||
|
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
||||||
|
host.clone()
|
||||||
|
)?;
|
||||||
|
Ok(PluginKind::VST2 {
|
||||||
|
instance: loader.instance()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -13,7 +13,9 @@
|
||||||
//! * [Sample::load_edn]
|
//! * [Sample::load_edn]
|
||||||
//! * [LV2Plugin::load_edn]
|
//! * [LV2Plugin::load_edn]
|
||||||
|
|
||||||
use crate::{core::*, model::*, App, devices::sampler::{Sampler, Sample, read_sample_data}};
|
use crate::{core::*, model::*, App};
|
||||||
|
use crate::devices::sampler::{Sampler, Sample, read_sample_data};
|
||||||
|
use crate::devices::plugin::{Plugin, LV2Plugin};
|
||||||
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||||
|
|
||||||
/// EDN parsing helper.
|
/// EDN parsing helper.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::{core::*, devices::{arranger::*, sequencer::*, transport::*}};
|
use crate::{core::*, devices::{arranger::*, sequencer::*, transport::*}};
|
||||||
|
|
||||||
submod! { axis looper mixer phrase plugin scene track }
|
submod! { axis phrase scene track }
|
||||||
|
|
||||||
/// Root of application state.
|
/// Root of application state.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
submod! { lv2 vst2 vst3 }
|
|
||||||
|
|
||||||
/// A plugin device.
|
|
||||||
pub struct Plugin {
|
|
||||||
pub name: String,
|
|
||||||
pub path: Option<String>,
|
|
||||||
pub plugin: Option<PluginKind>,
|
|
||||||
pub selected: usize,
|
|
||||||
pub mapping: bool,
|
|
||||||
pub ports: JackPorts,
|
|
||||||
}
|
|
||||||
render!(Plugin = crate::view::render_plugin);
|
|
||||||
handle!(Plugin = crate::control::handle_plugin);
|
|
||||||
process!(Plugin = Plugin::process);
|
|
||||||
|
|
||||||
/// Supported plugin formats.
|
|
||||||
pub enum PluginKind {
|
|
||||||
LV2(LV2Plugin),
|
|
||||||
VST2 {
|
|
||||||
instance: ::vst::host::PluginInstance
|
|
||||||
},
|
|
||||||
VST3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Plugin {
|
|
||||||
/// Create a plugin host device.
|
|
||||||
pub fn new (name: &str) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
path: None,
|
|
||||||
plugin: None,
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
ports: JackPorts::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
match self.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin {
|
|
||||||
features,
|
|
||||||
ref mut instance,
|
|
||||||
ref mut input_buffer,
|
|
||||||
..
|
|
||||||
})) => {
|
|
||||||
let urid = features.midi_urid();
|
|
||||||
input_buffer.clear();
|
|
||||||
for port in self.ports.midi_ins.values() {
|
|
||||||
let mut atom = ::livi::event::LV2AtomSequence::new(
|
|
||||||
&features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
);
|
|
||||||
for event in port.iter(scope) {
|
|
||||||
match event.bytes.len() {
|
|
||||||
3 => atom.push_midi_event::<3>(
|
|
||||||
event.time as i64,
|
|
||||||
urid,
|
|
||||||
&event.bytes[0..3]
|
|
||||||
).unwrap(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
input_buffer.push(atom);
|
|
||||||
}
|
|
||||||
let mut outputs = vec![];
|
|
||||||
for _ in self.ports.midi_outs.iter() {
|
|
||||||
outputs.push(::livi::event::LV2AtomSequence::new(
|
|
||||||
&features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let ports = ::livi::EmptyPortConnections::new()
|
|
||||||
.with_atom_sequence_inputs(
|
|
||||||
input_buffer.iter()
|
|
||||||
)
|
|
||||||
.with_atom_sequence_outputs(
|
|
||||||
outputs.iter_mut()
|
|
||||||
)
|
|
||||||
.with_audio_inputs(
|
|
||||||
self.ports.audio_ins.values().map(|o|o.as_slice(scope))
|
|
||||||
)
|
|
||||||
.with_audio_outputs(
|
|
||||||
self.ports.audio_outs.values_mut().map(|o|o.as_mut_slice(scope))
|
|
||||||
);
|
|
||||||
unsafe {
|
|
||||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
use ::livi::{
|
|
||||||
World,
|
|
||||||
Instance,
|
|
||||||
Plugin,
|
|
||||||
Features,
|
|
||||||
FeaturesBuilder,
|
|
||||||
Port,
|
|
||||||
event::LV2AtomSequence,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A LV2 plugin.
|
|
||||||
pub struct LV2Plugin {
|
|
||||||
pub world: World,
|
|
||||||
pub instance: Instance,
|
|
||||||
pub plugin: Plugin,
|
|
||||||
pub features: Arc<Features>,
|
|
||||||
pub port_list: Vec<Port>,
|
|
||||||
pub input_buffer: Vec<LV2AtomSequence>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LV2Plugin {
|
|
||||||
pub fn new (uri: &str) -> Usually<Self> {
|
|
||||||
// Get 1st plugin at URI
|
|
||||||
let world = World::with_load_bundle(&uri);
|
|
||||||
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
|
|
||||||
let features = world.build_features(features);
|
|
||||||
let mut plugin = None;
|
|
||||||
for p in world.iter_plugins() {
|
|
||||||
plugin = Some(p);
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let plugin = plugin.unwrap();
|
|
||||||
let err = &format!("init {uri}");
|
|
||||||
|
|
||||||
// Instantiate
|
|
||||||
Ok(Self {
|
|
||||||
world,
|
|
||||||
instance: unsafe {
|
|
||||||
plugin
|
|
||||||
.instantiate(features.clone(), 48000.0)
|
|
||||||
.expect(&err)
|
|
||||||
},
|
|
||||||
port_list: {
|
|
||||||
let mut port_list = vec![];
|
|
||||||
for port in plugin.ports() {
|
|
||||||
port_list.push(port);
|
|
||||||
}
|
|
||||||
port_list
|
|
||||||
},
|
|
||||||
plugin,
|
|
||||||
features,
|
|
||||||
input_buffer: Vec::with_capacity(1024)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl super::Plugin {
|
|
||||||
pub fn lv2 (name: &str, path: &str) -> Usually<JackDevice> {
|
|
||||||
let plugin = LV2Plugin::new(path)?;
|
|
||||||
Jack::new(name)?
|
|
||||||
.ports_from_lv2(&plugin.plugin)
|
|
||||||
.run(|ports|Box::new(Self {
|
|
||||||
name: name.into(),
|
|
||||||
path: Some(String::from(path)),
|
|
||||||
plugin: Some(super::PluginKind::LV2(plugin)),
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
ports
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl ::vst::host::Host for Plugin {}
|
|
||||||
|
|
||||||
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
|
|
||||||
let mut loader = ::vst::host::PluginLoader::load(
|
|
||||||
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
|
||||||
host.clone()
|
|
||||||
)?;
|
|
||||||
Ok(PluginKind::VST2 {
|
|
||||||
instance: loader.instance()?
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::{render, App, core::*};
|
use crate::{render, App, core::*};
|
||||||
|
|
||||||
submod! { border chain help plugin split theme }
|
submod! { border chain help split theme }
|
||||||
|
|
||||||
render!(App |self, buf, area| {
|
render!(App |self, buf, area| {
|
||||||
Split::down([
|
Split::down([
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
use crate::{core::*, model::*};
|
|
||||||
|
|
||||||
pub fn render_plugin (state: &Plugin, buf: &mut Buffer, area: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
let Rect { x, y, height, .. } = area;
|
|
||||||
let mut width = 20u16;
|
|
||||||
match &state.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
|
||||||
let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
|
||||||
let end = start + height as usize - 2;
|
|
||||||
//draw_box(buf, Rect { x, y, width, height });
|
|
||||||
for i in start..end {
|
|
||||||
if let Some(port) = port_list.get(i) {
|
|
||||||
let value = if let Some(value) = instance.control_input(port.index) {
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
port.default_value
|
|
||||||
};
|
|
||||||
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
|
||||||
let label = &format!("{:25} = {value:.03}", port.name);
|
|
||||||
width = width.max(label.len() as u16 + 4);
|
|
||||||
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
|
|
||||||
Some(Style::default().green())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
})?;
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
draw_header(state, buf, area.x, area.y, width)?;
|
|
||||||
Ok(Rect { width, ..area })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
label1.blit(buf, x + 1, y, Some(style.white().bold()))?;
|
|
||||||
if let Some(ref path) = state.path {
|
|
||||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
|
||||||
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()))?;
|
|
||||||
}
|
|
||||||
Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue