diff --git a/src/control.rs b/src/control.rs index 0472b866..63ac5277 100644 --- a/src/control.rs +++ b/src/control.rs @@ -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 { 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] = keymap!(App { Ok(true) }], }); + +/// Key bindings for chain section. +pub const KEYMAP_CHAIN: &'static [KeyBinding] = 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] = 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 { + 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 { + 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) +} diff --git a/src/control/chain.rs b/src/control/chain.rs deleted file mode 100644 index 642b531b..00000000 --- a/src/control/chain.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::{core::*, model::App}; - -/// Key bindings for chain section. -pub const KEYMAP_CHAIN: &'static [KeyBinding] = 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) - }], -}); - diff --git a/src/control/focus.rs b/src/control/focus.rs deleted file mode 100644 index 548baca8..00000000 --- a/src/control/focus.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{core::*, model::{App, AppFocus}}; - -/// Generic key bindings for views that support focus. -pub const KEYMAP_FOCUS: &'static [KeyBinding] = 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 { - 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 { - 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) -} diff --git a/src/control/mixer.rs b/src/control/mixer.rs deleted file mode 100644 index 5ae4d241..00000000 --- a/src/control/mixer.rs +++ /dev/null @@ -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 { - 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) -} diff --git a/src/control/plugin.rs b/src/control/plugin.rs deleted file mode 100644 index 41fd9d87..00000000 --- a/src/control/plugin.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::{core::*, model::*}; - -pub fn handle_plugin (state: &mut Plugin, event: &AppEvent) -> Usually { - handle_keymap(state, event, KEYMAP_PLUGIN) -} - -/// Key bindings for plugin device. -pub const KEYMAP_PLUGIN: &'static [KeyBinding] = 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) - }], -}); diff --git a/src/devices.rs b/src/devices.rs index 787601e8..af7041ff 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -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} diff --git a/src/model/looper.rs b/src/devices/looper.rs similarity index 99% rename from src/model/looper.rs rename to src/devices/looper.rs index 9166aff5..daa27295 100644 --- a/src/model/looper.rs +++ b/src/devices/looper.rs @@ -8,3 +8,4 @@ render!(Looper); handle!(Looper); process!(Looper); ports!(Looper); + diff --git a/src/model/mixer.rs b/src/devices/mixer.rs similarity index 68% rename from src/model/mixer.rs rename to src/devices/mixer.rs index e126dfea..43222fb0 100644 --- a/src/model/mixer.rs +++ b/src/devices/mixer.rs @@ -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 { + 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) +} diff --git a/src/devices/plugin.rs b/src/devices/plugin.rs new file mode 100644 index 00000000..552d523f --- /dev/null +++ b/src/devices/plugin.rs @@ -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 { + handle_keymap(state, event, KEYMAP_PLUGIN) +} + +/// Key bindings for plugin device. +pub const KEYMAP_PLUGIN: &'static [KeyBinding] = 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, + pub plugin: Option, + 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 { + 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 +{ + 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 { + 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, + pub port_list: Vec, + pub input_buffer: Vec +} + +impl LV2Plugin { + pub fn new (uri: &str) -> Usually { + // 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 { + 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>, _path: &str) -> Usually { + 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()? + }) +} + diff --git a/src/edn.rs b/src/edn.rs index 89022b2d..83e64304 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -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. diff --git a/src/model.rs b/src/model.rs index 30961dca..c77f3482 100644 --- a/src/model.rs +++ b/src/model.rs @@ -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 { diff --git a/src/model/plugin.rs b/src/model/plugin.rs deleted file mode 100644 index 540ec10f..00000000 --- a/src/model/plugin.rs +++ /dev/null @@ -1,94 +0,0 @@ -use crate::core::*; - -submod! { lv2 vst2 vst3 } - -/// A plugin device. -pub struct Plugin { - pub name: String, - pub path: Option, - pub plugin: Option, - 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 { - 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 - } -} diff --git a/src/model/plugin/lv2.rs b/src/model/plugin/lv2.rs deleted file mode 100644 index 544ae21c..00000000 --- a/src/model/plugin/lv2.rs +++ /dev/null @@ -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, - pub port_list: Vec, - pub input_buffer: Vec -} - -impl LV2Plugin { - pub fn new (uri: &str) -> Usually { - // 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 { - 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 - })) - } -} diff --git a/src/model/plugin/vst2.rs b/src/model/plugin/vst2.rs deleted file mode 100644 index b94c50c0..00000000 --- a/src/model/plugin/vst2.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::core::*; -use super::*; - -impl ::vst::host::Host for Plugin {} - -fn set_vst_plugin (host: &Arc>, _path: &str) -> Usually { - 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()? - }) -} - diff --git a/src/model/plugin/vst3.rs b/src/model/plugin/vst3.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/view.rs b/src/view.rs index 3efd6fb5..ad6e7231 100644 --- a/src/view.rs +++ b/src/view.rs @@ -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([ diff --git a/src/view/plugin.rs b/src/view/plugin.rs deleted file mode 100644 index 2433f40a..00000000 --- a/src/view/plugin.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{core::*, model::*}; - -pub fn render_plugin (state: &Plugin, buf: &mut Buffer, area: Rect) - -> Usually -{ - 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 { - 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 }) -}