mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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};
|
||||
|
||||
submod!{ chain focus mixer plugin }
|
||||
|
||||
handle!{
|
||||
App |self, e| {
|
||||
if let Some(ref mut modal) = self.modal {
|
||||
|
|
@ -17,10 +15,10 @@ handle!{
|
|||
Ok(if self.entered {
|
||||
handle_focused(self, e)?
|
||||
|| handle_keymap(self, e, KEYMAP_GLOBAL)?
|
||||
|| handle_keymap(self, e, crate::control::focus::KEYMAP_FOCUS)?
|
||||
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|
||||
} else {
|
||||
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)?
|
||||
})
|
||||
}
|
||||
|
|
@ -36,9 +34,9 @@ fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
|||
handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER),
|
||||
AppFocus::Chain => Ok(if state.entered {
|
||||
handle_device(state, e)? ||
|
||||
handle_keymap(state, e, crate::control::chain::KEYMAP_CHAIN)?
|
||||
handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
|
||||
} 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)
|
||||
}],
|
||||
});
|
||||
|
||||
/// 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);
|
||||
process!(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.
|
||||
pub struct Mixer {
|
||||
|
|
@ -8,7 +18,7 @@ pub struct Mixer {
|
|||
pub selected_column: usize,
|
||||
}
|
||||
//render!(Mixer = crate::view::mixer::render);
|
||||
handle!(Mixer = crate::control::handle_mixer);
|
||||
handle!(Mixer = handle_mixer);
|
||||
process!(Mixer = process);
|
||||
|
||||
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]
|
||||
//! * [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};
|
||||
|
||||
/// EDN parsing helper.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{core::*, devices::{arranger::*, sequencer::*, transport::*}};
|
||||
|
||||
submod! { axis looper mixer phrase plugin scene track }
|
||||
submod! { axis phrase scene track }
|
||||
|
||||
/// Root of application state.
|
||||
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::*};
|
||||
|
||||
submod! { border chain help plugin split theme }
|
||||
submod! { border chain help split theme }
|
||||
|
||||
render!(App |self, buf, area| {
|
||||
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