mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
flatten modules somewhat
This commit is contained in:
parent
cb7ba855ab
commit
0779560502
29 changed files with 442 additions and 474 deletions
|
|
@ -1,9 +1,6 @@
|
||||||
include!("./lib.rs");
|
include!("./lib.rs");
|
||||||
use tek::tui::ArrangerTui;
|
use tek::ArrangerTui;
|
||||||
|
pub fn main () -> Usually<()> { ArrangerCli::parse().run() }
|
||||||
pub fn main () -> Usually<()> {
|
|
||||||
ArrangerCli::parse().run()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Launches an interactive MIDI arranger.
|
/// Launches an interactive MIDI arranger.
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ pub struct GrooveboxCli {
|
||||||
impl GrooveboxCli {
|
impl GrooveboxCli {
|
||||||
fn run (&self) -> Usually<()> {
|
fn run (&self) -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{
|
||||||
let mut app = tek::tui::GrooveboxTui::try_from(jack)?;
|
let mut app = tek::GrooveboxTui::try_from(jack)?;
|
||||||
let jack = jack.read().unwrap();
|
let jack = jack.read().unwrap();
|
||||||
let midi_in = jack.register_port("i", MidiIn::default())?;
|
let midi_in = jack.register_port("i", MidiIn::default())?;
|
||||||
let midi_out = jack.register_port("o", MidiOut::default())?;
|
let midi_out = jack.register_port("o", MidiOut::default())?;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() }
|
||||||
impl SamplerCli {
|
impl SamplerCli {
|
||||||
fn run (&self) -> Usually<()> {
|
fn run (&self) -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{
|
Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{
|
||||||
let sampler = tek::tui::SamplerTui::try_from(x)?;
|
let sampler = tek::SamplerTui::try_from(x)?;
|
||||||
Ok(sampler)
|
Ok(sampler)
|
||||||
})?)?;
|
})?)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[allow(unused_imports)] use tek::{*, jack::*, plugin::*, audio::*};
|
#[allow(unused_imports)] use tek::{*, jack::*, plugin::*};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
|
mod arranger_command; pub(crate) use self::arranger_command::*;
|
||||||
|
mod arranger_scene; pub(crate) use self::arranger_scene::*;
|
||||||
|
mod arranger_select; pub(crate) use self::arranger_select::*;
|
||||||
|
mod arranger_track; pub(crate) use self::arranger_track::*;
|
||||||
|
mod arranger_mode; pub(crate) use self::arranger_mode::*;
|
||||||
|
mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*;
|
||||||
|
mod arranger_h;
|
||||||
|
|
||||||
/// Root view for standalone `tek_arranger`
|
/// Root view for standalone `tek_arranger`
|
||||||
pub struct ArrangerTui {
|
pub struct ArrangerTui {
|
||||||
jack: Arc<RwLock<JackClient>>,
|
jack: Arc<RwLock<JackClient>>,
|
||||||
|
|
@ -1,9 +1 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
mod sampler;
|
|
||||||
pub(crate) use self::sampler::*;
|
|
||||||
pub use self::sampler::{Sampler, Sample, Voice};
|
|
||||||
|
|
||||||
mod mixer;
|
|
||||||
pub(crate) use self::mixer::*;
|
|
||||||
pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice};
|
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Mixer {
|
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
pub name: String,
|
|
||||||
pub tracks: Vec<MixerTrack>,
|
|
||||||
pub selected_track: usize,
|
|
||||||
pub selected_column: usize,
|
|
||||||
}
|
|
||||||
audio!(|self: Mixer, _client, _scope|Control::Continue);
|
|
||||||
|
|
||||||
pub enum MixerTrackCommand {}
|
|
||||||
|
|
||||||
/// A mixer track.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct MixerTrack {
|
|
||||||
pub name: String,
|
|
||||||
/// Inputs of 1st device
|
|
||||||
pub audio_ins: Vec<Port<AudioIn>>,
|
|
||||||
/// Outputs of last device
|
|
||||||
pub audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
/// Device chain
|
|
||||||
pub devices: Vec<Box<dyn MixerTrackDevice>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl MixerTrackDevice for LV2Plugin {}
|
|
||||||
|
|
||||||
pub trait MixerTrackDevice: Debug + Send + Sync {
|
|
||||||
fn boxed (self) -> Box<dyn MixerTrackDevice> where Self: Sized + 'static {
|
|
||||||
Box::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MixerTrackDevice for Sampler {}
|
|
||||||
|
|
||||||
impl MixerTrackDevice for Plugin {}
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// The sampler plugin plays sounds.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Sampler {
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
pub name: String,
|
|
||||||
pub mapped: BTreeMap<u7, Arc<RwLock<Sample>>>,
|
|
||||||
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
|
||||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
|
||||||
pub midi_in: Port<MidiIn>,
|
|
||||||
pub audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
pub buffer: Vec<Vec<f32>>,
|
|
||||||
pub output_gain: f32
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sound sample.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Sample {
|
|
||||||
pub name: String,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
pub channels: Vec<Vec<f32>>,
|
|
||||||
pub rate: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A currently playing instance of a sample.
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct Voice {
|
|
||||||
pub sample: Arc<RwLock<Sample>>,
|
|
||||||
pub after: usize,
|
|
||||||
pub position: usize,
|
|
||||||
pub velocity: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load sample from WAV and assign to MIDI note.
|
|
||||||
#[macro_export] macro_rules! sample {
|
|
||||||
($note:expr, $name:expr, $src:expr) => {{
|
|
||||||
let (end, data) = read_sample_data($src)?;
|
|
||||||
(
|
|
||||||
u7::from_int_lossy($note).into(),
|
|
||||||
Sample::new($name, 0, end, data).into()
|
|
||||||
)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sampler {
|
|
||||||
|
|
||||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
|
||||||
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
|
|
||||||
let Sampler { midi_in, mapped, voices, .. } = self;
|
|
||||||
for RawMidi { time, bytes } in midi_in.iter(scope) {
|
|
||||||
if let LiveEvent::Midi {
|
|
||||||
message: MidiMessage::NoteOn { ref key, ref vel }, ..
|
|
||||||
} = LiveEvent::parse(bytes).unwrap() {
|
|
||||||
if let Some(sample) = mapped.get(key) {
|
|
||||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Zero the output buffer.
|
|
||||||
pub fn clear_output_buffer (&mut self) {
|
|
||||||
for buffer in self.buffer.iter_mut() {
|
|
||||||
buffer.fill(0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mix all currently playing samples into the output.
|
|
||||||
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
|
||||||
let Sampler { ref mut buffer, voices, output_gain, .. } = self;
|
|
||||||
let channel_count = buffer.len();
|
|
||||||
voices.write().unwrap().retain_mut(|voice|{
|
|
||||||
for index in 0..scope.n_frames() as usize {
|
|
||||||
if let Some(frame) = voice.next() {
|
|
||||||
for (channel, sample) in frame.iter().enumerate() {
|
|
||||||
// Averaging mixer:
|
|
||||||
//self.buffer[channel % channel_count][index] = (
|
|
||||||
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
|
||||||
//);
|
|
||||||
buffer[channel % channel_count][index] += sample * *output_gain;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write output buffer to output ports.
|
|
||||||
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
|
||||||
let Sampler { ref mut audio_outs, buffer, .. } = self;
|
|
||||||
for (i, port) in audio_outs.iter_mut().enumerate() {
|
|
||||||
let buffer = &buffer[i];
|
|
||||||
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
|
||||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sample {
|
|
||||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
|
||||||
Self { name: name.to_string(), start, end, channels, rate: None }
|
|
||||||
}
|
|
||||||
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
|
||||||
Voice {
|
|
||||||
sample: sample.clone(),
|
|
||||||
after,
|
|
||||||
position: sample.read().unwrap().start,
|
|
||||||
velocity: velocity.as_int() as f32 / 127.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Read WAV from file
|
|
||||||
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
|
||||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
|
||||||
for channel in wavers::Wav::from_path(src)?.channels() {
|
|
||||||
channels.push(channel);
|
|
||||||
}
|
|
||||||
let mut end = 0;
|
|
||||||
let mut data: Vec<Vec<f32>> = vec![];
|
|
||||||
for samples in channels.iter() {
|
|
||||||
let channel = Vec::from(samples.as_ref());
|
|
||||||
end = end.max(channel.len());
|
|
||||||
data.push(channel);
|
|
||||||
}
|
|
||||||
Ok((end, data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Voice {
|
|
||||||
type Item = [f32;2];
|
|
||||||
fn next (&mut self) -> Option<Self::Item> {
|
|
||||||
if self.after > 0 {
|
|
||||||
self.after -= 1;
|
|
||||||
return Some([0.0, 0.0])
|
|
||||||
}
|
|
||||||
let sample = self.sample.read().unwrap();
|
|
||||||
if self.position < sample.end {
|
|
||||||
let position = self.position;
|
|
||||||
self.position += 1;
|
|
||||||
return sample.channels[0].get(position).map(|_amplitude|[
|
|
||||||
sample.channels[0][position] * self.velocity,
|
|
||||||
sample.channels[0][position] * self.velocity,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,15 +7,44 @@ pub mod time; pub(crate) use self::time::*;
|
||||||
|
|
||||||
pub mod space; pub(crate) use self::space::*;
|
pub mod space; pub(crate) use self::space::*;
|
||||||
|
|
||||||
pub mod tui; pub(crate) use self::tui::*; pub use tui::*;
|
pub mod tui;
|
||||||
|
pub(crate) use self::tui::*;
|
||||||
|
pub use tui::*;
|
||||||
|
|
||||||
pub mod jack; pub(crate) use self::jack::*; pub use self::jack::*;
|
pub mod jack;
|
||||||
|
pub(crate) use self::jack::*;
|
||||||
|
pub use self::jack::*;
|
||||||
|
|
||||||
pub mod midi; pub(crate) use self::midi::*;
|
pub mod midi;
|
||||||
|
pub(crate) use self::midi::*;
|
||||||
|
|
||||||
pub mod audio; pub(crate) use self::audio::*; pub use self::audio::*;
|
pub mod transport;
|
||||||
|
pub(crate) use self::transport::*;
|
||||||
|
pub use self::transport::TransportTui;
|
||||||
|
|
||||||
pub mod plugin; pub(crate) use self::plugin::*; pub use self::plugin::*;
|
pub mod sequencer;
|
||||||
|
pub(crate) use self::sequencer::*;
|
||||||
|
pub use self::sequencer::SequencerTui;
|
||||||
|
|
||||||
|
pub mod arranger;
|
||||||
|
pub(crate) use self::arranger::*;
|
||||||
|
pub use self::arranger::ArrangerTui;
|
||||||
|
|
||||||
|
mod sampler;
|
||||||
|
pub(crate) use self::sampler::*;
|
||||||
|
pub use self::sampler::{SamplerTui, Sampler, Sample, Voice};
|
||||||
|
|
||||||
|
mod mixer;
|
||||||
|
pub(crate) use self::mixer::*;
|
||||||
|
pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice};
|
||||||
|
|
||||||
|
pub mod plugin;
|
||||||
|
pub(crate) use self::plugin::*;
|
||||||
|
pub use self::plugin::*;
|
||||||
|
|
||||||
|
pub mod groovebox;
|
||||||
|
pub(crate) use self::groovebox::*;
|
||||||
|
pub use self::groovebox::GrooveboxTui;
|
||||||
|
|
||||||
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,30 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub struct Mixer<E: Engine> {
|
#[derive(Debug)]
|
||||||
|
pub struct Mixer {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub tracks: Vec<Track<E>>,
|
pub tracks: Vec<MixerTrack>,
|
||||||
pub selected_track: usize,
|
pub selected_track: usize,
|
||||||
pub selected_column: usize,
|
pub selected_column: usize,
|
||||||
}
|
}
|
||||||
impl<E: Engine> Mixer<E> {
|
|
||||||
|
/// A mixer track.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MixerTrack {
|
||||||
|
pub name: String,
|
||||||
|
/// Inputs of 1st device
|
||||||
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
|
/// Outputs of last device
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
/// Device chain
|
||||||
|
pub devices: Vec<Box<dyn MixerTrackDevice>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
audio!(|self: Mixer, _client, _scope|Control::Continue);
|
||||||
|
|
||||||
|
impl Mixer {
|
||||||
pub fn new (jack: &Arc<RwLock<JackClient>>, name: &str) -> Usually<Self> {
|
pub fn new (jack: &Arc<RwLock<JackClient>>, name: &str) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
|
|
@ -19,57 +35,42 @@ impl<E: Engine> Mixer<E> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn track_add (&mut self, name: &str, channels: usize) -> Usually<&mut Self> {
|
pub fn track_add (&mut self, name: &str, channels: usize) -> Usually<&mut Self> {
|
||||||
let track = Track::new(name)?;
|
let track = MixerTrack::new(name)?;
|
||||||
self.tracks.push(track);
|
self.tracks.push(track);
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
pub fn track (&self) -> Option<&Track<E>> {
|
pub fn track (&self) -> Option<&MixerTrack> {
|
||||||
self.tracks.get(self.selected_track)
|
self.tracks.get(self.selected_track)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//pub const ACTIONS: [(&'static str, &'static str);2] = [
|
impl MixerTrack {
|
||||||
//("+/-", "Adjust"),
|
|
||||||
//("Ins/Del", "Add/remove track"),
|
|
||||||
//];
|
|
||||||
|
|
||||||
|
|
||||||
/// A sequencer track.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Track<E: Engine> {
|
|
||||||
pub name: String,
|
|
||||||
/// Inputs and outputs of 1st and last device
|
|
||||||
pub ports: JackPorts,
|
|
||||||
/// Device chain
|
|
||||||
pub devices: Vec<JackDevice<E>>,
|
|
||||||
/// Device selector
|
|
||||||
pub device: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E: Engine> Track<E> {
|
|
||||||
pub fn new (name: &str) -> Usually<Self> {
|
pub fn new (name: &str) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ports: JackPorts::default(),
|
audio_ins: vec![],
|
||||||
devices: vec![],
|
audio_outs: vec![],
|
||||||
device: 0,
|
devices: vec![],
|
||||||
|
//ports: JackPorts::default(),
|
||||||
|
//devices: vec![],
|
||||||
|
//device: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
//fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||||
self.devices.get(i).map(|d|d.state.write().unwrap())
|
//self.devices.get(i).map(|d|d.state.write().unwrap())
|
||||||
}
|
//}
|
||||||
pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
//pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||||
self.get_device_mut(self.device)
|
//self.get_device_mut(self.device)
|
||||||
}
|
//}
|
||||||
/// Add a device to the end of the chain.
|
///// Add a device to the end of the chain.
|
||||||
pub fn append_device (&mut self, device: JackDevice<E>) -> Usually<&mut JackDevice<E>> {
|
//pub fn append_device (&mut self, device: JackDevice<E>) -> Usually<&mut JackDevice<E>> {
|
||||||
self.devices.push(device);
|
//self.devices.push(device);
|
||||||
let index = self.devices.len() - 1;
|
//let index = self.devices.len() - 1;
|
||||||
Ok(&mut self.devices[index])
|
//Ok(&mut self.devices[index])
|
||||||
}
|
//}
|
||||||
pub fn add_device (&mut self, device: JackDevice<E>) {
|
//pub fn add_device (&mut self, device: JackDevice<E>) {
|
||||||
self.devices.push(device);
|
//self.devices.push(device);
|
||||||
}
|
//}
|
||||||
//pub fn connect_first_device (&self) -> Usually<()> {
|
//pub fn connect_first_device (&self) -> Usually<()> {
|
||||||
//if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) {
|
//if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) {
|
||||||
//device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?;
|
//device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?;
|
||||||
|
|
@ -88,14 +89,14 @@ impl<E: Engine> Track<E> {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TrackView<'a, E: Engine> {
|
pub struct TrackView<'a> {
|
||||||
pub chain: Option<&'a Track<E>>,
|
pub chain: Option<&'a MixerTrack>,
|
||||||
pub direction: Direction,
|
pub direction: Direction,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
pub entered: bool,
|
pub entered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render<Tui> for TrackView<'a, Tui> {
|
impl<'a> Render<Tui> for TrackView<'a> {
|
||||||
fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
|
fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
@ -129,40 +130,40 @@ impl<'a> Render<Tui> for TrackView<'a, Tui> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Content<Tui> for Mixer<Tui> {
|
//impl Content<Tui> for Mixer<Tui> {
|
||||||
fn content (&self) -> impl Render<Tui> {
|
//fn content (&self) -> impl Render<Tui> {
|
||||||
Stack::right(|add| {
|
//Stack::right(|add| {
|
||||||
for channel in self.tracks.iter() {
|
//for channel in self.tracks.iter() {
|
||||||
add(channel)?;
|
//add(channel)?;
|
||||||
}
|
//}
|
||||||
Ok(())
|
//Ok(())
|
||||||
})
|
//})
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
||||||
impl Content<Tui> for Track<Tui> {
|
//impl Content<Tui> for Track<Tui> {
|
||||||
fn content (&self) -> impl Render<Tui> {
|
//fn content (&self) -> impl Render<Tui> {
|
||||||
TrackView {
|
//TrackView {
|
||||||
chain: Some(&self),
|
//chain: Some(&self),
|
||||||
direction: tek_core::Direction::Right,
|
//direction: tek_core::Direction::Right,
|
||||||
focused: true,
|
//focused: true,
|
||||||
entered: true,
|
//entered: true,
|
||||||
//pub channels: u8,
|
////pub channels: u8,
|
||||||
//pub input_ports: Vec<Port<AudioIn>>,
|
////pub input_ports: Vec<Port<AudioIn>>,
|
||||||
//pub pre_gain_meter: f64,
|
////pub pre_gain_meter: f64,
|
||||||
//pub gain: f64,
|
////pub gain: f64,
|
||||||
//pub insert_ports: Vec<Port<AudioOut>>,
|
////pub insert_ports: Vec<Port<AudioOut>>,
|
||||||
//pub return_ports: Vec<Port<AudioIn>>,
|
////pub return_ports: Vec<Port<AudioIn>>,
|
||||||
//pub post_gain_meter: f64,
|
////pub post_gain_meter: f64,
|
||||||
//pub post_insert_meter: f64,
|
////pub post_insert_meter: f64,
|
||||||
//pub level: f64,
|
////pub level: f64,
|
||||||
//pub pan: f64,
|
////pub pan: f64,
|
||||||
//pub output_ports: Vec<Port<AudioOut>>,
|
////pub output_ports: Vec<Port<AudioOut>>,
|
||||||
//pub post_fader_meter: f64,
|
////pub post_fader_meter: f64,
|
||||||
//pub route: String,
|
////pub route: String,
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
||||||
handle!(<Tui>|self:Mixer,engine|{
|
handle!(<Tui>|self:Mixer,engine|{
|
||||||
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
|
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
|
||||||
|
|
@ -212,18 +213,18 @@ handle!(<Tui>|self:Mixer,engine|{
|
||||||
Ok(None)
|
Ok(None)
|
||||||
});
|
});
|
||||||
|
|
||||||
handle!(<Tui>|self:Track<Tui>,from|{
|
handle!(<Tui>|self:MixerTrack,from|{
|
||||||
match from.event() {
|
match from.event() {
|
||||||
//, NONE, "chain_cursor_up", "move cursor up", || {
|
//, NONE, "chain_cursor_up", "move cursor up", || {
|
||||||
key!(KeyCode::Up) => {
|
key_pat!(KeyCode::Up) => {
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
},
|
},
|
||||||
// , NONE, "chain_cursor_down", "move cursor down", || {
|
// , NONE, "chain_cursor_down", "move cursor down", || {
|
||||||
key!(KeyCode::Down) => {
|
key_pat!(KeyCode::Down) => {
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
},
|
},
|
||||||
// Left, NONE, "chain_cursor_left", "move cursor left", || {
|
// Left, NONE, "chain_cursor_left", "move cursor left", || {
|
||||||
key!(KeyCode::Left) => {
|
key_pat!(KeyCode::Left) => {
|
||||||
//if let Some(track) = app.arranger.track_mut() {
|
//if let Some(track) = app.arranger.track_mut() {
|
||||||
//track.device = track.device.saturating_sub(1);
|
//track.device = track.device.saturating_sub(1);
|
||||||
//return Ok(true)
|
//return Ok(true)
|
||||||
|
|
@ -231,7 +232,7 @@ handle!(<Tui>|self:Track<Tui>,from|{
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
},
|
},
|
||||||
// , NONE, "chain_cursor_right", "move cursor right", || {
|
// , NONE, "chain_cursor_right", "move cursor right", || {
|
||||||
key!(KeyCode::Right) => {
|
key_pat!(KeyCode::Right) => {
|
||||||
//if let Some(track) = app.arranger.track_mut() {
|
//if let Some(track) = app.arranger.track_mut() {
|
||||||
//track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
//track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
|
||||||
//return Ok(true)
|
//return Ok(true)
|
||||||
|
|
@ -239,10 +240,24 @@ handle!(<Tui>|self:Track<Tui>,from|{
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
},
|
},
|
||||||
// , NONE, "chain_mode_switch", "switch the display mode", || {
|
// , NONE, "chain_mode_switch", "switch the display mode", || {
|
||||||
key!(KeyCode::Char('`')) => {
|
key_pat!(KeyCode::Char('`')) => {
|
||||||
//app.chain_mode = !app.chain_mode;
|
//app.chain_mode = !app.chain_mode;
|
||||||
Ok(Some(true))
|
Ok(Some(true))
|
||||||
},
|
},
|
||||||
_ => Ok(None)
|
_ => Ok(None)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub enum MixerTrackCommand {}
|
||||||
|
|
||||||
|
//impl MixerTrackDevice for LV2Plugin {}
|
||||||
|
|
||||||
|
pub trait MixerTrackDevice: Debug + Send + Sync {
|
||||||
|
fn boxed (self) -> Box<dyn MixerTrackDevice> where Self: Sized + 'static {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MixerTrackDevice for Sampler {}
|
||||||
|
|
||||||
|
impl MixerTrackDevice for Plugin {}
|
||||||
|
|
@ -127,3 +127,140 @@ audio!(|self: PluginAudio, client, scope|{
|
||||||
//}
|
//}
|
||||||
//Ok(jack)
|
//Ok(jack)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
impl Plugin {
|
||||||
|
/// Create a plugin host device.
|
||||||
|
pub fn new (
|
||||||
|
jack: &Arc<RwLock<JackClient>>,
|
||||||
|
name: &str,
|
||||||
|
) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
//_engine: Default::default(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
name: name.into(),
|
||||||
|
path: None,
|
||||||
|
plugin: None,
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
audio_ins: vec![],
|
||||||
|
audio_outs: vec![],
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
//ports: JackPorts::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Render<Tui> for Plugin {
|
||||||
|
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
||||||
|
Ok(Some(to))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
||||||
|
let area = to.area();
|
||||||
|
let [x, y, _, height] = area;
|
||||||
|
let mut width = 20u16;
|
||||||
|
match &self.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
||||||
|
let start = self.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);
|
||||||
|
let style = if i == self.selected {
|
||||||
|
Some(Style::default().green())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
} ;
|
||||||
|
to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style);
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
draw_header(self, to, x, y, width)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually<()> {
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let label1 = format!(" {}", state.name);
|
||||||
|
to.blit(&label1, 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()))]);
|
||||||
|
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
//Ok(Rect { x, y, width: w, height: 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
handle!(<Tui>|self:Plugin, from|{
|
||||||
|
match from.event() {
|
||||||
|
key_pat!(KeyCode::Up) => {
|
||||||
|
self.selected = self.selected.saturating_sub(1);
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::Down) => {
|
||||||
|
self.selected = (self.selected + 1).min(match &self.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
_ => unimplemented!()
|
||||||
|
});
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::PageUp) => {
|
||||||
|
self.selected = self.selected.saturating_sub(8);
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::PageDown) => {
|
||||||
|
self.selected = (self.selected + 10).min(match &self.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
_ => unimplemented!()
|
||||||
|
});
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::Char(',')) => {
|
||||||
|
match self.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[self.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value - 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::Char('.')) => {
|
||||||
|
match self.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[self.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value + 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
key_pat!(KeyCode::Char('g')) => {
|
||||||
|
match self.plugin {
|
||||||
|
//Some(PluginKind::LV2(ref mut plugin)) => {
|
||||||
|
//plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
||||||
|
//},
|
||||||
|
Some(_) => unreachable!(),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
Ok(Some(true))
|
||||||
|
},
|
||||||
|
_ => Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,155 @@
|
||||||
use crate::*;
|
use crate::{*, tui::piano_h::PianoHorizontalKeys};
|
||||||
use super::{*, piano_h::PianoHorizontalKeys};
|
|
||||||
|
/// The sampler plugin plays sounds.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sampler {
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub name: String,
|
||||||
|
pub mapped: BTreeMap<u7, Arc<RwLock<Sample>>>,
|
||||||
|
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||||
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||||
|
pub midi_in: Port<MidiIn>,
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
pub buffer: Vec<Vec<f32>>,
|
||||||
|
pub output_gain: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sound sample.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Sample {
|
||||||
|
pub name: String,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
pub rate: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A currently playing instance of a sample.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Voice {
|
||||||
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
pub after: usize,
|
||||||
|
pub position: usize,
|
||||||
|
pub velocity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load sample from WAV and assign to MIDI note.
|
||||||
|
#[macro_export] macro_rules! sample {
|
||||||
|
($note:expr, $name:expr, $src:expr) => {{
|
||||||
|
let (end, data) = read_sample_data($src)?;
|
||||||
|
(
|
||||||
|
u7::from_int_lossy($note).into(),
|
||||||
|
Sample::new($name, 0, end, data).into()
|
||||||
|
)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
|
||||||
|
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||||
|
pub fn process_midi_in (&mut self, scope: &ProcessScope) {
|
||||||
|
let Sampler { midi_in, mapped, voices, .. } = self;
|
||||||
|
for RawMidi { time, bytes } in midi_in.iter(scope) {
|
||||||
|
if let LiveEvent::Midi {
|
||||||
|
message: MidiMessage::NoteOn { ref key, ref vel }, ..
|
||||||
|
} = LiveEvent::parse(bytes).unwrap() {
|
||||||
|
if let Some(sample) = mapped.get(key) {
|
||||||
|
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Zero the output buffer.
|
||||||
|
pub fn clear_output_buffer (&mut self) {
|
||||||
|
for buffer in self.buffer.iter_mut() {
|
||||||
|
buffer.fill(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mix all currently playing samples into the output.
|
||||||
|
pub fn process_audio_out (&mut self, scope: &ProcessScope) {
|
||||||
|
let Sampler { ref mut buffer, voices, output_gain, .. } = self;
|
||||||
|
let channel_count = buffer.len();
|
||||||
|
voices.write().unwrap().retain_mut(|voice|{
|
||||||
|
for index in 0..scope.n_frames() as usize {
|
||||||
|
if let Some(frame) = voice.next() {
|
||||||
|
for (channel, sample) in frame.iter().enumerate() {
|
||||||
|
// Averaging mixer:
|
||||||
|
//self.buffer[channel % channel_count][index] = (
|
||||||
|
//(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0
|
||||||
|
//);
|
||||||
|
buffer[channel % channel_count][index] += sample * *output_gain;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write output buffer to output ports.
|
||||||
|
pub fn write_output_buffer (&mut self, scope: &ProcessScope) {
|
||||||
|
let Sampler { ref mut audio_outs, buffer, .. } = self;
|
||||||
|
for (i, port) in audio_outs.iter_mut().enumerate() {
|
||||||
|
let buffer = &buffer[i];
|
||||||
|
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||||
|
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||||
|
Self { name: name.to_string(), start, end, channels, rate: None }
|
||||||
|
}
|
||||||
|
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
||||||
|
Voice {
|
||||||
|
sample: sample.clone(),
|
||||||
|
after,
|
||||||
|
position: sample.read().unwrap().start,
|
||||||
|
velocity: velocity.as_int() as f32 / 127.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Read WAV from file
|
||||||
|
pub fn read_data (src: &str) -> Usually<(usize, Vec<Vec<f32>>)> {
|
||||||
|
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||||
|
for channel in wavers::Wav::from_path(src)?.channels() {
|
||||||
|
channels.push(channel);
|
||||||
|
}
|
||||||
|
let mut end = 0;
|
||||||
|
let mut data: Vec<Vec<f32>> = vec![];
|
||||||
|
for samples in channels.iter() {
|
||||||
|
let channel = Vec::from(samples.as_ref());
|
||||||
|
end = end.max(channel.len());
|
||||||
|
data.push(channel);
|
||||||
|
}
|
||||||
|
Ok((end, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Voice {
|
||||||
|
type Item = [f32;2];
|
||||||
|
fn next (&mut self) -> Option<Self::Item> {
|
||||||
|
if self.after > 0 {
|
||||||
|
self.after -= 1;
|
||||||
|
return Some([0.0, 0.0])
|
||||||
|
}
|
||||||
|
let sample = self.sample.read().unwrap();
|
||||||
|
if self.position < sample.end {
|
||||||
|
let position = self.position;
|
||||||
|
self.position += 1;
|
||||||
|
return sample.channels[0].get(position).map(|_amplitude|[
|
||||||
|
sample.channels[0][position] * self.velocity,
|
||||||
|
sample.channels[0][position] * self.velocity,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use KeyCode::Char;
|
use KeyCode::Char;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
@ -12,40 +12,15 @@ mod tui_style;
|
||||||
mod tui_theme; pub(crate) use self::tui_theme::*;
|
mod tui_theme; pub(crate) use self::tui_theme::*;
|
||||||
mod tui_border; pub(crate) use self::tui_border::*;
|
mod tui_border; pub(crate) use self::tui_border::*;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
mod app_transport; #[allow(unused)] pub(crate) use self::app_transport::*;
|
|
||||||
pub use self::app_transport::TransportTui;
|
|
||||||
|
|
||||||
mod app_sequencer; #[allow(unused)] pub(crate) use self::app_sequencer::*;
|
|
||||||
pub use self::app_sequencer::SequencerTui;
|
|
||||||
|
|
||||||
mod app_sampler; #[allow(unused)] pub(crate) use self::app_sampler::*;
|
|
||||||
pub use self::app_sampler::SamplerTui;
|
|
||||||
|
|
||||||
mod app_groovebox; #[allow(unused)] pub(crate) use self::app_groovebox::*;
|
|
||||||
pub use self::app_groovebox::GrooveboxTui;
|
|
||||||
|
|
||||||
mod app_arranger; #[allow(unused)] pub(crate) use self::app_arranger::*;
|
|
||||||
pub use self::app_arranger::ArrangerTui;
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////
|
||||||
|
|
||||||
mod arranger_command; pub(crate) use self::arranger_command::*;
|
|
||||||
mod arranger_scene; pub(crate) use self::arranger_scene::*;
|
|
||||||
mod arranger_select; pub(crate) use self::arranger_select::*;
|
|
||||||
mod arranger_track; pub(crate) use self::arranger_track::*;
|
|
||||||
mod arranger_mode; pub(crate) use self::arranger_mode::*;
|
|
||||||
mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*;
|
|
||||||
mod arranger_h;
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
mod pool; pub(crate) use self::pool::*;
|
pub mod pool; pub(crate) use self::pool::*;
|
||||||
mod phrase_editor; pub(crate) use self::phrase_editor::*;
|
pub mod phrase_editor; pub(crate) use self::phrase_editor::*;
|
||||||
mod status; pub(crate) use self::status::*;
|
pub mod status; pub(crate) use self::status::*;
|
||||||
mod file_browser; pub(crate) use self::file_browser::*;
|
pub mod file_browser; pub(crate) use self::file_browser::*;
|
||||||
mod piano_h; pub(crate) use self::piano_h::*;
|
pub mod piano_h; pub(crate) use self::piano_h::*;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
/// A plugin device.
|
|
||||||
pub struct Plugin<E> {
|
|
||||||
_engine: PhantomData<E>,
|
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
pub name: String,
|
|
||||||
pub path: Option<String>,
|
|
||||||
pub plugin: Option<PluginKind>,
|
|
||||||
pub selected: usize,
|
|
||||||
pub mapping: bool,
|
|
||||||
pub ports: JackPorts,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<E> Plugin<E> {
|
|
||||||
/// Create a plugin host device.
|
|
||||||
pub fn new (
|
|
||||||
jack: &Arc<RwLock<JackClient>>,
|
|
||||||
name: &str,
|
|
||||||
) -> Usually<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
_engine: Default::default(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
name: name.into(),
|
|
||||||
path: None,
|
|
||||||
plugin: None,
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
ports: JackPorts::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Render<Tui> for Plugin<Tui> {
|
|
||||||
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
|
|
||||||
Ok(Some(to))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
|
|
||||||
let area = to.area();
|
|
||||||
let [x, y, _, height] = area;
|
|
||||||
let mut width = 20u16;
|
|
||||||
match &self.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
|
||||||
let start = self.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);
|
|
||||||
let style = if i == self.selected {
|
|
||||||
Some(Style::default().green())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
} ;
|
|
||||||
to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style);
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
draw_header(self, to, x, y, width)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_header <E> (state: &Plugin<E>, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
to.blit(&label1, 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()))]);
|
|
||||||
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
|
||||||
}
|
|
||||||
Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
handle!(<Tui>|self:Plugin<tui>,from|{
|
|
||||||
match from.event() {
|
|
||||||
key!(KeyCode::Up) => {
|
|
||||||
self.selected = self.selected.saturating_sub(1);
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::Down) => {
|
|
||||||
self.selected = (self.selected + 1).min(match &self.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
_ => unimplemented!()
|
|
||||||
});
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::PageUp) => {
|
|
||||||
self.selected = self.selected.saturating_sub(8);
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::PageDown) => {
|
|
||||||
self.selected = (self.selected + 10).min(match &self.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
_ => unimplemented!()
|
|
||||||
});
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char(',')) => {
|
|
||||||
match self.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[self.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value - 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char('.')) => {
|
|
||||||
match self.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[self.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value + 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
key!(KeyCode::Char('g')) => {
|
|
||||||
match self.plugin {
|
|
||||||
Some(PluginKind::LV2(ref mut plugin)) => {
|
|
||||||
plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
|
|
||||||
},
|
|
||||||
Some(_) => unreachable!(),
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
Ok(Some(true))
|
|
||||||
},
|
|
||||||
_ => Ok(None)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue